STM32 -- USB虚拟串口通信
本篇操作:
- 通过CubeMX + Keil,配置STM32作为USB设备端,与电脑上位机进行通信(CDC);
- 通用带USB功能的 STM32 芯片 (如F1、F4等,系统时钟配置不同,代码通用)。
目录
一、 STM32内置USB、虚拟串口简述
二、CubeMX 新建工程
三、Keil 工程配置
四、实现USB模拟插拔
五、发送
六、发送优化(连续发送)
七、接收
八、接收优化(在外部处理数据)
一、 STM32内置USB、虚拟串口简述
STM32 芯片,绝大部分型号都带内置USB,如常用的 F1、F4、H7、G4 等系列,能够通过USB接口与计算机或其他USB设备进行通信。
STM32内置的USB,均可支持USB 2.0标准,可以支持三种传输速率:
- 高速模式:最高可达480 Mbps (部分型号支持,且需搭配外部芯片,不常用 )
- 全速模式:最高可达12 Mbps (最常用)
- 低速模式:最高可达1.5 Mbps
高速模式,需要搭配外围USB PHY芯片,如USB3300,硬件成本偏高 。
全速模式,电路很简单。从机在PCB布线时,仅需把STM32的引脚PA11、PA12, 连接至USB座的DP、DM,然后,PA12(DP线)用1.5K电阻上拉至3.3V。具体如下图:
上拉说明
插拔检测:设备未插入时,主机端DP、DM为低电平,当发现被置高,即为有设备插入;
区分速率:DM线上拉是低速模式,DP线上拉是全速\高速模式;
上拉电压:3.3V。USB通信电平是3.3V,而不是总线供电的5V。
USB虚拟串口,简称VPC,Virtual Port Com 的简写。但更习惯于把虚拟串口叫作: CDC,因为它是利用 USB 的 CDC类 实现的一种通信接口。
我们可以利用STM32自带的USB功能,通过CubeMX的配置,很方便地实现一个USB虚拟串口,从而通过USB线,实现电脑与STM32的数据互传。
哪些win系统支持虚拟串口?
Win10、Win11 已带虚拟串口驱动;无需安装任何驱动;
Win7 要提前手动安装驱动,否则无法识别 :虚拟串口驱动 下载
二、CubeMX 新建工程
本篇为了工程的清晰,将从0开始, 新建一个虚拟串口通信的工程。
日常做项目,不建议新建,而是复制已有的旧工程,通过CubeMX增删需要的功能。
这样能减少一些常用功能的再次配置,如按键、UART等;
复用旧工程里已验证过的功能,能有效地减少常用功能的调试时间。
1、以芯片型号新建
2、搜索芯片型号
3、设置调试模式
进入配置页面后,养成习惯,优先设置调试模式:Serial Wire。
4、选择晶振源
外部高速晶振源(HSE):Crystal/Ceramic Resonator
5、USB工作模式
USB_OTG_FS:选择 Device_Only; 设备模式(从机模式); 其它参数,默认。
有些芯片型号,如F103系列,CubeMX上的显示是:USB_FS,配置步骤是一样的。
6、中间件组件
USB_DEVICE:选择 CDC (VPC); 其它参数,默认;
6、配置系统时钟
① 当启用USB功能后,进入时钟配置页面时,弹窗: 是否自动配置系统时钟? 选择:No 。
② 先确认板上的晶振值
- 配置时钟前,很重要的一个事:先核对开发板上的晶振频率(在晶振上的数字)!
- 晶振频率配置错误时,编译不会报错,但系统可能不运行、通信错乱等,后期排查很费时间!
- 目前STM32的板子,常用的外部高速晶振有三种:8M、12M、25M。
③ STM32F103 时钟配置:
晶振值 | 输入分频 | 输出倍频 | USB分频 | APB1分频 | APB2分频 | 系统 时钟 |
---|---|---|---|---|---|---|
8 | 1 | 9 | 1.5 | 2 | 1 | 72MHz |
④ STM32F4xx 时钟配置:
注意:F4系列,各板商略有不同,大部分是25M, 少部分是8M,使用效果一样。
晶振值 | 输入分频 | 输出倍频 | 输出分频 | USB分频 | APB1分频 | APB2分频 | 系统时钟 |
---|---|---|---|---|---|---|---|
25 | 25 | 336 | 2 | 7 | 4 | 2 | 168MHz |
7、工程配置
- 工程名称、路径,这两项,必须英文。否则,生成的工程将会缺少启动文件
- 开发工具:MDK-ARM, 即生成Keil工程
- 堆大小,建议:0x400
- 栈大小,建议:0x1000
8、文件和代码的配置
9、生成
稍等 片刻:
生成的工程文件夹:
Keil工程的入口文件:
三、Keil 工程配置
按上述,双击打开Keil工程。
1、新建的工程,需要设置一次仿真器参数 。(点击 OK 保存,否则无效)。
2、配置常用的调试选项
下面这两项是非必要的,建议打勾使用;编译后生效; 打勾会令编译速度变慢;
- Debug Infomation: 生成调试信息。debug模式中无法设置断点,就是这个选项没打勾。
- Bowse Infomation: 生成追踪信息。如,右击函数、变量,点击弹出菜单:Go To Definition...
3、编译 验证
- 0 Error,正程正常。
- 有 Error,失败;应该是 (2-7) 那一步工程名称、路径有中文。修改后重新生成即可。
- 先别烧录,别烧录,别烧录。
四、实现USB模拟插拔
通过 CubeMX 配置后生成的工程,它已带需要的初始化代码、配置代码、基础函数等。
我们只需在工程里,按需进行简单的配置、修改代码,即可使用。
1、包含 USB接口 的头文件
- 打开 main.c文件,大约第26行,配对的 /* USER CODE ...... Includes */ 注释之间,
- 添加:#include "usbd_cdc_if.h"
完成后,是这个样子的:
2、增加 USB模拟插拔
我们在调试STM32程序期间,需要反复地 修改程序、编译、烧录; 这是常规操作,用于调试其它通信模块,如DHT11、ESP8266等,是没有问题的,但用于调试USB的通信,就会翻车。
当虚拟串口所用的USB线一直插在USB口上,程序重新烧录后,程序的重新运行,将导致通信错误、USB端口"假死"等现象;
上文中已介绍,电脑端USB口没有插入设备时,DP和DM线,是低电平状态,而设备端的DP线,有1.5K电阻上拉到3.3V,当设备插入到电脑USB口,USB口的DP线就会被置高电平,主机是依靠这个机制判断设备是否插入、拔出,继而触发不同的动作,如枚举、释放端口等。
当虚拟串口所用的USB线一直插在USB口上,在STM32烧录程序重新运行后,程序里的USB代码等待着主机方发起枚举过程;而这个期间虚拟串口的USB线没有断开,主机方认为设备方一直在线,早已枚举成功,一直对其轮询数据收发。双方“南辕北辙,胡言乱语”,注定翻车。
正常操作是:每次烧录前,先把虚拟串口的USB线拔下来,烧录好了,再插上,......。
为了避免调试期间频繁地手动操作,我们可以在程序开跑后、USB初始化前,用代码把PA12置低,使D+线为低电平,持续一段时间,模拟USB拔出动作,令主机认为设备已断开连接,释放端口;
然后,当程序运行到后面的USB初始化函数时,PA12会被正常配置(DP线电平被置高),USB主机就会"发现"有设备插入,开始尝试枚举、配置;
具体操作:
- 打开 usbd_conf.c 文件,大约第70行附近,找到HAL_PCD_MspInit ( )函数;
- 在两对 /* USER ... 0 */ 注释之间,约75行,添加PA12引脚置低电平操作, 如下(可复制):
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA端口GPIO_InitTypeDef GPIO_InitStruct = {0}; // 声明结构体; 如果与文中位置相同,这行可不写GPIO_InitStruct.Pin = GPIO_PIN_12; // 引脚PA12, 即D+GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 引脚工作模式GPIO_InitStruct.Pull = GPIO_PULLDOWN; // 下拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 引脚反转速度HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化HAL_Delay(5); // 持续片刻
注意,最后一行的延时,是必须的,建议在5ms左右;
完成后,是这个样子的:
增加这段代码后,再无需手动插拔 虚拟串口的USB 线了,程序将模拟 “ 断开、插入”;
再次编译,确保上述操作正常 (先别烧录)。
五、发送
发送数据的函数
uint8_t CDC_Transmit_FS ( uint8_t* Buf, uint16_t Len );
函数接受两个参数:数据缓冲区的地址、字节数。
如果USB设备正忙,它会返回USBD_BUSY
状态。
这个函数的作用是设置传输数据的缓冲区,并标记数据包为待发送。数据并非立刻发出,而是被存储在USB外设的缓冲区中,等待主机轮询请求传输。
发送示范操作
- 在main.c 的 while 循环中,添加三行测试代码:1行延时、两行发送数据;
注意:是共三行,1行延时,2行发送。
请严格按照这三行来写,因为我们要顺带实现一个bug, 然后下面方便解释具体原因!
新手,想省时间,就请按步骤操作 :
- 先别插虚拟串口所用的USB线
- 编译、烧录代码
- 打开串口助手, (这时是没有插虚拟串口USB线的),查看目前有哪些端口号
- 插入虚拟串口的USB线,到开发板的 USB-Slave接口
- ( 前提:win10、11系统已带虚拟串口驱动; win7要手动安装驱动:虚拟串口下载)
- ( 电脑会自动识别到设备;如果是第一次使用虚拟串口,电脑将自动安装驱动程序)
- 检查串口助手,发现多了一个端口号, 选择它,波特率等参数不用修改,打开端口;
接线,如下图(示例所用的开发板):
- 左侧为用户USB接口,已连接PA11、PA12,我们就是用这个U口实现虚拟串口通信。
- 右侧是板载仿真器CMSIS DAP的接口,它自带了USB转TTL功能。
注意端口的选择:
当使用的开发板,已带USB转TTL功能,如上面这个。在烧录虚拟串口的程序后,串口助手会有至少两个端口号:板子自带的USB转TTL(右侧)、程序实现的虚拟串口(左侧),注意不要选择错了。
如果不知道哪根线对应哪个端口:在烧录后,拔一下USB线,看看哪个端口消失了。
另一方法,有些串口助手的端口列表,能显示设备信息,找到带“STM...”描述的那个。
现在能看到,串口助手能接收到程序持续发出的数据了!
效果如下图:
六、发送优化(连续发送)
上节的发送,实现时,只能收到第一行"Hello",而第二行发出的数据:"借点钱 "却没收到!!
不是没收到。其实,从STM32程序的角度,是没有发出数据!
先说说,USB虚拟串口通信的几个重点 (特指:USB2.0、全速模式、中断传输):
- USB是轮询机制,主机对设备不断轮询,间隔最小1ms;不是固定的1ms, 是最小间隔时间;
- USB的数据,是按包传输的;
- 每个设备,每1ms,最多传输1包数据;
- 每包最多64字节(有效负载);
再说说,CDC_Transmit_FS ( ) 函数:
- 它的第2个参数,"字节数",范围:0~2048; 这个2048可以在CubeMX里进行设置大小;
- 字节数 <= 64,算1包。如:发3个字节,也算1包。
- 字节数 == 0,也算1包。俗称:空包; 如果上一帧刚好发送64字节,再发一个空包作为结束包;
- 字节数 > 64, CDC_Transmit_FS ( ) 背后有缓存,它自动分包,1ms左右发1包,直至发完;
- 如果上一包还没发完,再次调用CDC_Transmit_FS ( ) ,将放弃本次调用。
上面while循环中,连续、两次调用CDC_Transmit_FS ( ) .
- 第1次调用,将正常发出一包数据;
- 第2次调用,再发送一包。但是,两包没有间隔1ms以上,导致了第2次的发送,被舍弃了。
我们先打开 CDC_Transmit_FS ( ) 函数,看看函数原型。
在代码中,右击CDC_Transmit_FS ( ) ,弹出菜单,选择Go To Definition...,将跳转到函数位置;
或者,在左侧文件树中,双击打开usbd_cdc_if.c 文件 ,CDC_Transmit_FS ( ) 位于大约280行 ;
特别注意:
usbd_cdc_if.c 是应用层文件,我们对收、发有啥特殊需求,通过修改文件里的发送函数、接收回调函数、类请求函数,基本都能实现。
下图,是 CDC_Transmit_FS ( ) 函数截图。
红框的代码作用是:当设备忙时,直接放弃发送,:
修改:
- 注释掉刚才 if 体的3行代码;
- 增加等待发送空闲、判断超时,如下6行;
- 整个CDC_Transmit_FS ( ) 函数,如下:(可复制)
uint8_t CDC_Transmit_FS(uint8_t *Buf, uint16_t Len)
{uint8_t result = USBD_OK;/* USER CODE BEGIN 7 */USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)hUsbDeviceFS.pClassData; // 获得设备的状态信息结构体// if (hcdc->TxState != 0){// return USBD_BUSY;// }uint32_t timeStart = HAL_GetTick();while (hcdc->TxState){if (HAL_GetTick() - timeStart > 20)return USBD_BUSY;}USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);/* USER CODE END 7 */return result;
}
完成后,是这个样子:
再次编译、烧录程序。
串口助手,现在是这个样子的:
连续发送已实现了。
上述方法能够解决连续发送失败的问题。
但它存在一个显著缺点:由于其阻塞性质,频密连续发送时,将导致运行“死等”,影响程序效率。
(对于大部分场景,上述方法已足够。本节内容,只是为你预埋一种备用思路,无需死磕。)
如果项目对实时性有较高的要求,可以通过结合使用发送数据函数
CDC_Transmit_FS()
和发送完成回调函数CDC_TransmitCplt_FS()
来提高传输效率。CDC_TransmitCplt_FS()
会在CDC_Transmit_FS()
函数发送数据完毕后自动被调用。根据这两个函数的特点,可以设计一套高效的发送缓存机制。例如,可以维护一个发送队列,当
CDC_Transmit_FS()
完成发送后,CDC_TransmitCplt_FS()
被调用时,从队列中取出下一个数据项进行发送,这样可以确保数据传输的连续性。这个发送完成回调函数
CDC_TransmitCplt_FS()
,位于发送函数CDC_Transmit_FS()
的正下方。至于具体的代码实现,不同项目需求各异,无法提供一个通用的解决方案。因此,需要根据具体的项目需求,进行针对性的设计和优化,不能一药治百病。
七、接收
1、接收方式的简述
当USB CDC接收到来自USB主机的数据时,触发中断进入中断函数,继而自动调用接收回调函数:
int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len);
uint8_t* Buf
: 指向接收缓冲区的指针,即数据缓存的地址。uint16_t* Len
: 当前数据包的字节数。
我们就在这个回调函数里,处理接收到的数据!
它在 usbd_cdc_if.c 文件,位于发送函数的正上方;
函数内部,生成的代码里,只有2行执行代码,指定下次接收的存放位置; 如下图所示:
而本次所接收到的数据,该如何处理,需要我们自行添加代码。
接收机制注意事项:
- 每当接收到一包数据,硬件自动触发中断函数, 继而调用此接收回调函数,无需人工调用。
- 与发送机制相似,每间隔1ms,最多接收1包数据,每包最大64字节。。
- 如果需要接收超过64字节的数据帧,注意,指上位机发送的1个完整数据帧,而非USB的单包数据,如,上位机发来一张图片数据,8350个字节,则需要在此回调函数中添加额外的代码来判断帧数据传输完整结束 、手动将多个数据包拼接成完整的数据帧。
- 接收到数据时,缓存不会提前自动清零,新数据从
Buf的
起始位置开始,覆盖存放。 - 由于该回调函数是被中断函数调用的,因此建议函数内部的处理尽可能地简短,以避免影响系统的实时性(中断函数运行期间,会令程序持续挂起)。
2、接收示范
本节将示范:
- 通过串口助手,发送字符串
- STM32(设备端)收到数据后,把收到的字节数、字符串,发回串口助手显示(主机端)
在函数内的注释行 /* USER CODE BEGIN */ 下方,添加4行自定义代码(可复制)。
char myStr[64] = {0}; // 定义一个数组,用于存放要输出的字符串sprintf(myStr, "\r\r收到 %d 个字节;\r内容是:%s\r\r", *Len, (char *)Buf); // 格式化字符串CDC_Transmit_FS((uint8_t *)myStr, strlen(myStr)); // 发送memset(Buf, 0, 64); // 处理完数据,清0接收缓存;
注意:
- 用char 声明myStr[ ], 是因为此处想把它作为一段字符串空间;
- sprintf是C语言标准输入输出库的函数,如果报错没有这个函数,就:#include <stdio.h>
- 获取字节数,是*Len,而不是Len; 因为它在函数参数里的声明,是一个指针;
- 为了格式化成字符串,Buf用了(char*)进行强制转换成字符类型;
- CDC_Transmit_FS( )里,用了strlen获取字符串的字节数,它只对字符串有效,对其它数据类型无效; 如果报错没有这个函数,就:#include <string.h>
- 如果传输的是16进制数,用uint8_t 声明上面数组,然后修改sprintf的格式化方式。
添加完成后,文件是这样子的:
再次编译、烧录程序。
串口助手,打开对应的端口号(波特率等参数不用修改),
在发送区,以ASCII方式,发送字符串(因为添加的代码里用%s格式化,处理的是字符串),
然后,串口助手的接收区,马上能接收到刚才发出的数据!
至此,已实现接收的处理。
八、接收优化(在外部处理数据)
上面,我们已实现:获取、使用接收到的数据。
在接收回调函数中,直接操作数据的发送,通常是安全的,因为这种操作耗时非常短,最多等待1次主机轮询周期(1ms)。这种情况下,不会对系统的稳定性和数据接收造成显著影响。
但是,如果在接收回调函数中执行耗时较长的操作,如显示到LCD或存储到Flash等,这些操作可能需要数毫秒到数十毫秒才能完成。耗时较长的操作,可能会导致接收过程中出现漏包现象。
因为接收回调函数是由USB中断服务程序调用的,属于中断处理的一部分,在回调函数执行期间,主程序还处于中断挂起状态,其他代码和中断也会被暂停执行,形象地描述:“卡死”。
如果中断服务程序的执行操作较耗时,会导致下一包数据无法及时进入中断,从而造成数据丢失。
举例说明:
- A (电脑USB主机)每隔1ms扔出1枚鸡蛋,B(STM32中断服务函数)负责接鸡蛋。
- 当B接鸡蛋处理得比A快,即比A扔出的间隔更短,如0.5ms,那,没问题。
- 但是,当B接鸡蛋的处理时间较长,如,接了鸡蛋还要写上价格,再放置到货架,共20ms, 那肯定就接不住A持续扔过来的鸡蛋了,每接1个,就会丢失后面的19个,再接1个,再丢失19个.......; 注意,A是不管B是否数据丢包的,它只负责每1ms扔(发送)一次。
我们需要采取一种策略,使得程序的中断响应、“卡死”占时,尽可能地短:
- 接收数据:在中断回调函数中,我们仅执行必要的数据复制操作,即把接收到的数据迅速复制到外部缓存中。这一操作的耗时通常在us级别;
- 处理数据:在主程序的while循环中,我们再对数据进行进一步的处理。由于这一处理过程不占用中断资源,因此不会影响程序对新数据的接收;
这种策略通过分离数据接收和数据处理两个步骤,确保了程序能够快速响应连续的数据流,同时避免了因处理时间过长而导致的数据丢失。
操作共4个步骤,具体如下:
1、增加全局变量
在usbd_cdc_if.c文件大约97行,配对的注释内,定义两个变量:
/* USER CODE BEGIN PRIVATE_VARIABLES */
uint8_t myUsbRxData[64] = { 0 }; // 接收到的数据
uint16_t myUsbRxNum = 0; // 接收到的字节数/* USER CODE END PRIVATE_VARIABLES */
现在,它俩只是本地变量,等会要在main中用extern再声明一次,才能被外部调用。
完成后,是这个样子的:
2、修改接收回调函数
在CDC_Receive_FS() 里,删除我们上节增加的测试代码;
把Buf和*Len的数据,复制到我们刚才的两个变量里。函数修改成:
static int8_t CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)
{/* USER CODE BEGIN 6 */// 把Buf里面的数据,复制到外部缓存memset(myUsbRxData, 0, 64); // 清0缓存区 memcpy(myUsbRxData, Buf, *Len); // 把接收到的数据,复制到自己的缓存区中myUsbRxNum = *Len; // 复制字节数 memset(Buf, 0, 64); // 处理完数据,清0接收缓存; // CubeMX生成的代码,保留 USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]); // 设置下-个接收缓冲区USBD_CDC_ReceivePacket(&hUsbDeviceFS); // 启动下一个数据包的接收return (USBD_OK); /* USER CODE END 6 */
}
完成后,是这个样子的:
现在,数据接收部分,已处理好了。
以后回调函数运行时,只复制数据至外部缓存(备用),中断时间占用极短,不会影响下包接收。
3、 在外部用extern声明变量,令外部可调用数据
外部,哪个文件里要使用CDC接收的数据,就在这个文件里,用extern声明那俩变量。
如,可以在LCD文件,也可以在SD卡的文件中,都行。
建议在main.h文件中声明,其它文件再#include "main.h",这样,可以令变量全局可用。
- 打开 main.c,右击空白,点击"Toggle Header/Code File",可以跳转到头文件:main.h
在main.h中,大约38行,找到 配对的注释行 /* USER CODE BEGIN ET */
用 extern 再次声明刚才两个变量。如下(可复制):
注意,是只声明,不要赋值,否则编译错误。
/* USER CODE BEGIN ET */
extern uint8_t myUsbRxData[ ] ;
extern uint16_t myUsbRxNum ;/* USER CODE END ET */
完成后,是这个样子的:
代码规范:
这里的示范,使用全局变量,只是为了更清晰地演示操作思路。项目中,尽量避免使用全局变量;不同文件间的数据获取,可以封装成函数,如 CDC_GetRxData()、CDC_GetRxNum(),返回数据地址、接收的字节数。
4、使用接收到数据
在main.c的while循环中,通过判断myUsbRxNum的值,只要大于0,就表示收到数据了
记得每次处理完数据,把myUsbRxNum置0,以便于下一轮的判断。
再次烧录,烧录程序。
打开串口助手,发送测试文本,可以发现,能成功收到STM32发过来的回传数据。
如果,外部处理数据的速度跟不上,如,在while里每次收到数据都要显示到LCD,LCD的速度远慢于USB的传输,那,还不是变相丢了数据?!是的,会有这种情况!
但,那已经是程序逻辑和时间片机制的问题了,3天3夜也嗑不完!
本节只讨论:确保每一包数据,都能被正常接收到。外部能否及时处理,不述。
至此,本篇完结。
如有错漏,望留言指正,及时更新!!
相关文章:

STM32 -- USB虚拟串口通信
本篇操作: 通过CubeMX Keil,配置STM32作为USB设备端,与电脑上位机进行通信(CDC);通用带USB功能的 STM32 芯片 (如F1、F4等,系统时钟配置不同,代码通用)。 目录 一、 S…...

uni-app开发特殊社交APP
uni-app开发特殊社交APP 目录 1.展示APP功能 2.展示项目结构 3.关于我的GitHub 引言 博主最近自己在GitHub上面上传了一个关于社交软件的项目(该项目早已开发完毕), 这个社交软件比较特殊, 被称之为blind-date, blind-date 是基于 uni-…...

Linux中Shell脚本的常用命令
一、设置主机名称 1、通过修改系统文件来修改主机名称 [rootsakura1 桌面]# vim /etc/hostname sakura /etc/hostname:Linux 系统中存储主机名的配置文件。修改完文件后,在当前的shell中是不生效的,需要关闭当前shell后重新开启才能看到效…...

RabbitMQ项目实战
先参考文章:(必看) 06-MQ基础_mq服务-CSDN博客 07-MQ高级(幂等性)-CSDN博客 https://cloud.iocoder.cn/message-queue/rabbitmq/#_2-0-%E5%BC%95%E5%85%A5%E4%BE%9D%E8%B5%96%E4%B8%8E%E9%85%8D%E7%BD%AE 1、Rabbi…...

安卓开发用到的设计模式(3)行为型模式
安卓开发用到的设计模式(3)行为型模式 文章目录 安卓开发用到的设计模式(3)行为型模式1. 命令模式(Command Pattern)2. 策略模式(Strategy Pattern)3. 观察者模式(Observ…...
生成模型:从数据学习到创造的 AI 新范式
一、生成模型:定义与核心逻辑 生成模型是一类通过学习数据潜在分布来创造新样本的机器学习模型。其核心目标是构建数据的概率分布模型 P(X),使生成的样本 X^ 与真实数据 X 具有相似的统计特征。 1.1 与判别模型的本质区别 维度生成模型判别模型核心目…...

尚硅谷redis7 90-92 redis集群分片之集群扩容
90 redis集群分片之集群扩容 三主三从不够用了,进行扩容变为4主4从 问题:1.新建两个redis实例,怎么加入原有集群?2.原有的槽位分3段,又加进来一个槽位怎么算? 新建6387、6388两个服务实例配置文件新建后启…...
RabbitMQ性能调优:关键技术、技巧与最佳实践
RabbitMQ作为一款高可靠、高扩展性的消息中间件,其性能表现直接影响到分布式系统的吞吐量和响应延迟。本文基于RabbitMQ官方文档和最佳实践,结合核心性能优化方向,详细探讨RabbitMQ性能调优的关键技术、技巧和策略。 通过以下优化策略&#…...
系统架构中的组织驱动:康威定律在系统设计中的应用
康威定律(Conway’s Law) 是由计算机科学家 Melvin Conway 在1967年提出的理论,其核心观点是:“系统的架构设计会不可避免地反映其开发组织的沟通结构。换句话说,软件系统的结构会与构建它的团队的组织结构高度相似。 …...
TypeScript 中高级类型 keyof 与 typeof的场景剖析。
文章目录 前言一、typeof:从值到类型的映射1. 核心概念2. 类型推导示例3. 常见用途 二、keyof:从类型到键的映射1. 核心概念2. 常见用途 三、typeof keyof:强强联合的实战场景1. 场景一:对象属性的安全访问2. 场景二:…...
Android LiveData 详解
一、LiveData 核心概念与特性 1.1 定义与基本功能 LiveData 是 Android Jetpack 架构组件中的一个可观察数据持有者类,其核心功能是实现数据与 UI 的响应式绑定。与传统观察者模式不同,LiveData 具有生命周期感知能力,能够自动根据观察者…...
为什么共现矩阵是高维稀疏的
为什么共现矩阵是高维稀疏的? 共现矩阵(Co-occurrence Matrix)的高维稀疏性是其固有特性,主要由以下原因导致: 1. 高维性的根本原因 词汇表大小决定维度: 共现矩阵的维度为 ( V \times V ),其…...

离散化算法的二分法应用
我们思考一个问题:其实这里的二分法回归本源也是基于下标映射的原理,只是实现是借助二分的形式。 在排序好的数组中对目标数值进行二分搜索,在 O(logn) 的时间复杂度内找到该数值是整体数据中的第几个。 具体的我们可以如下操作: …...
IntelliJ IDEA 中进行背景设置
🎨 一、全局主题切换 操作路径 File → Settings → Appearance & Behavior → Appearance → Theme可选主题: Darcula:深色模式(默认暗黑主题)IntelliJ Lightÿ…...
Dart语言学习指南「专栏简介」
Dart 是 Google 开发的一款开源通用编程语言,它不仅支持客户端和服务器端的应用开发,还因其与 Flutter 框架的深度集成,在移动端和 Web 开发中广受欢迎。Dart 适用于 Android 应用、iOS 应用、物联网(IoT)项目以及 Web…...
AWS之AI服务
目录 一、AWS AI布局 1. 底层基础设施与芯片 2. AI训练框架与平台 3. 大模型与应用层 4. 超级计算与网络 与竞品对比 AI服务 1. 机器学习平台 2. 预训练AI服务 3. 边缘与物联网AI 4. 数据与AI…...
Docker 部署项目
使用 Docker 部署项目是一个很好的选择,可以避免服务器环境不兼容的问题,并且能够实现一致性和可移植性。我会给你一个详细的步骤,帮你从零开始理解 Docker,最终在服务器上部署 Roop 项目。 1. 安装 Docker 首先,你需…...

半导体厂房设计建造流程、方案和技术要点-江苏泊苏系统集成有限公司
半导体厂房设计建造流程、方案和技术要点-江苏泊苏系统集成有限公司 半导体厂房的设计建造是一项高度复杂、专业性极强的系统工程,涉及洁净室、微振动控制、电磁屏蔽、特殊气体/化学品管理等关键技术。 一、设计建造流程: 1.需求定义与可行性分析 &a…...
(c++)string的模拟实现
目录 1.构造函数 2.析构函数 3.扩容 1.reserve(扩容不初始化) 2.resize(扩容加初始化) 4.push_back 5.append 6. 运算符重载 1.一个字符 2.一个字符串 7 []运算符重载 8.find 1.找一个字符 2.找一个字符串 9.insert 1.插入一个字符 2.插入一个字符串 9.erase 10…...

一种通用图片红色印章去除的工具设计
朋友今天下午需要处理个事情,问我有没有什么好的办法能够去除,核心问题是要去除图片上的印章。记得以前处理过类似的需求,photoshop操作比较简单,本质是做运算。这种处理方式有很多,比如现在流行的大模型,一…...
企业应用AI对向量数据库选型思考
一、向量数据库概述 向量数据库是一种专门用于存储和检索高维向量数据的数据库系统,它能够高效地处理基于向量相似性的查询,如最近邻搜索等,在人工智能、机器学习等领域的应用中发挥着重要作用,为处理复杂的向量数据提供了有力的…...
时序数据库IoTDB安装学习经验分享
1. JDK安装问题 在安装IoTDB时,我遇到了“无法加载主类”的错误,这通常表明Java环境存在问题。尽管我能正确输出classpath和查询JDK版本,但问题依旧存在。经过查阅相关资料,我发现问题出在多余的classpath设置上。Java编译器和虚…...

RapidOCR集成PP-OCRv5_det mobile模型记录
该文章主要摘取记录RapidOCR集成PP-OCRv5_mobile_det记录,涉及模型转换,模型精度测试等步骤。原文请前往官方博客: https://rapidai.github.io/RapidOCRDocs/main/blog/2025/05/26/rapidocr%E9%9B%86%E6%88%90pp-ocrv5_det%E6%A8%A1%E5%9E%8B…...
当 Redis 作为缓存使用时,如何保证缓存数据与数据库(或其他服务的数据源)之间的一致性?
当 Redis 作为缓存使用时,保证缓存数据与数据库(或其他数据源)之间的一致性是一个核心挑战。通常,我们追求的是“最终一致性”,而不是“强一致性”,因为强一致性往往会牺牲性能和可用性,这与使用…...

Dify理论+部署+实战
概述 一个功能强大的开源AI应用开发平台,融合后端即服务(Backend as Service)和LLMOps理念,使开发者能够快速搭建生产级的生成式AI应用。 核心优势 直观的用户界面:提供简洁明了的操作界面,使得用户能够…...

内网穿透系列五:自建SSH隧道实现内网穿透与端口转发,Docker快速部署
以下是对这个自建SSH隧道工具的简单介绍: 一款基于OpenSSH构建的内网穿透与端口转发工具,通过SSH隧道技术实现支持所有TCP协议通信,包括SSH、HTTP、HTTPS等各类应用提供灵活部署方式,特别支持Docker容器化快速部署开源工具地址…...

桥梁进行3D建模时的数据采集、存储需求及技术参数
桥梁进行3D建模时的数据采集、存储需求及技术参数 1公里桥梁进行3D建模时的数据采集、存储需求及技术参数的详细分析 1. 照片数量估算 关键影响因素 桥梁类型:梁桥/拱桥/斜拉桥(结构复杂度不同) 建模精度:工程级(1-…...

Transformer架构技术学习笔记:从理论到实战的完整解析
引言:重新定义序列建模的里程碑 2017年,Vaswani等人在论文《Attention Is All You Need》中提出的Transformer架构,彻底改变了自然语言处理领域的游戏规则。与传统RNN/LSTM相比,Transformer具有三大革命性特征: 全注意…...

1、python代码实现与大模型的问答交互
一、基础知识 1.1导入库 torch 是一个深度学习框架,用于处理张量和神经网络。modelscope是由阿里巴巴达摩院推出的开源模型库。 AutoTokenizer 是ModelScope 库的类,分词器应用场景包括自然语言处理(NLP)中的文本分类、信息抽取…...
CPU服务器的主要功能有哪些?
服务器作为互联网社会中基础的网络设施,为企业提供了存储和传输文件的功能,而中央处理器作为服务器计算能力的核心部分,能够帮助企业进行十分复杂的科学计算任务,本文就主要来探索一下CPU服务器的主要功能都有哪些吧! …...