基于STM32F411系列_USATR外设知识总结
一.常用的串口通讯1.串口通信核心分类同步/异步串口通信从时序上分为两种模式是所有串口协议的基础同步通信英文 Synchronous缩写 Sync收发双方依靠统一时钟信号对齐数据异步通信英文 Asynchronous缩写 Async无统一时钟依靠起始位、停止位帧结构对齐数据注日常单片机 TTL、RS232、RS485 串口全部默认是异步通信。2.波特率核心原理定义波特率表示1秒钟发送的二进制bit位数作用是让收发双方数据严格对应、精准解析。异步串口帧结构单片机发送 1 字节有效数据时必须额外携带1位起始位 1位停止位单次传输实际占用 10 bit。举例计算115200 波特率每秒比特数115200 bit/s每秒字节数 115200 ÷ 10 11520 byte/s3.TTL 串口电平与特性单片机原生电平标准 TTL 电平定义逻辑 0低电平0V ~ 0.4V逻辑 1高电平2.4V ~ 5V单片机硬件适配常规 3.3V 单片机硬件限制实际以 2.4V ~ 3.3V 判定为高电平逻辑1。硬件本质单片机直接输出的串口电平默认就是 TTL 电平。通信帧规则和所有异步串口一致发送1字节数据需拼接起始位、停止位单次传输10个bit。优缺点电平电压范围极小极易受外界电磁干扰传输距离极短常规有效传输距离 1m 以内。4.RS232 串口电平与特性电平标准与 TTL 完全相反、电压范围更广逻辑 0-3V ~ -15V负电压逻辑 13V ~ 15V正电压硬件要求单片机只能输出 TTL 电平无法直接输出 RS232 电平必须外接 MAX232 电平转换芯片完成电压和逻辑翻转。硬件接线标准三线制通信 TX / RX / GND。通信模式和 TTL 串口一致属于异步单端通信依赖公共 GND 做参考。优缺点相比 TTL 电平电压范围宽抗干扰能力大幅提升标准最大传输距离可达 15m。5.RS485 差分通信原理与特性传输方式采用差分平衡传输依靠 A、B 两根信号线的电压差值判断逻辑不依赖单一 GND 参考彻底解决地电位偏移问题 但只能半双工通讯。共模干扰抵抗核心机制工业环境中的外界干扰噪声会同时叠加在 A、B 两根信号线上形成共模电压RS485 电路只识别 A、B 之间的电压差值会自动抵消两路相同的干扰电压完美过滤共模干扰适配强干扰工业场景。逻辑判定标准驱动器输出实际测量电压Va-Vb 2V ~ 6V → 逻辑 1驱动器输出实际测量电压Va-Vb -6V ~ -2V → 逻辑 0补充关键考点必考接收端识别阈值更宽松只要差分电压绝对值 ≥ 200mV 就能正确识别逻辑这也是485抗干扰、远距离传输的容错余量来源。核心优势抗干扰能力极强、传输距离远最大有效传输距离可达 1200m支持一主多从总线架构可实现多设备组网通信但是连接单片机需要485转换芯片。传输速度大概能达到10Mbps 10 000000 bit/s 即每秒能传送1M字节的数据随距离增加。6.三种串口核心总结TTL单片机原生电平、距离短、易受干扰、板内短距离调试使用RS232电平翻转、电压范围大、抗干扰更强、点对点短距离外接设备通信15mRS485差分传输、抗共模干扰、远距离、支持多机组网工业通信首选二.串口通讯硬件基本原理三.串口通讯标准库学习先开 USART1 和 GPIOA 的时钟再把 PA9/PA10 配成 AFGPIO_PinAFConfig() 一定用 GPIO_PinSource9/10然后 USART_Init()最后 USART_Cmd(ENABLE)Q:void USART_SendData(USART_TypeDef* USARTx, uint16_t Data) 传入的数据是uint16但是我怎么可以写入uint8照样可以发出去数据发送寄存器和接收寄存器公用一个DR寄存器吗Auint16_t Data 不是说你必须发 16 位而是因为这个 API 要兼容不同的数据长度配置尤其是 9-bit 模式。函数内部实际上做了掩码USARTx-DR (Data 0x01FF);Data 用 uint16_t 是为了兼容 8/9 位发送四.串口通讯HAL库学习1、串口通讯常用的函数基于USART_IT中断HAL_UART_Receive_IT(huart1,buffer_uart_data,5);用于开启串口接收中断每来一个字节会触发void USART1_IRQHandler(void)由于是定长中断触发所以HAL_UART_Receive_IT函数对应的5字节触发回调函数void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)串口的数据是每来一个字节就会进行一次数据拷贝HAL 库的中断接收HAL_UART_Receive_IT是一次性的因此在中断函数中还要进行下次使能。2.串口基于DMA中断接收数据关于USART-DMA的配置原理使用DMA需要注意所设置的数据宽度是多少如果是字那么接收一个字节就会放进4byte的内存中 最低位。修改数据宽度为byte如下因为 DMA 接收完成后最终也会调用同一个回调函数HAL_UART_RxCpltCallback不管你用的是HAL_UART_Receive_ITHAL_UART_Receive_DMA完成后进的都是同一个回调HAL_UART_RxCpltCallbackHAL针对于USART单独封装了一个DMA的APIHAL_UART_Receive_DMA在UART_Start_Receive_DMA函数中完成了DMA函数指针的绑定DMA中断的注册3.CPU的SRAM的搬运速度和AHB总线速度相关AHB 16 MHz→ 一次搬运循环50 次耗时33 usAHB 100 MHz→ 一次搬运循环50 次耗时4 usCPU 内核 通过 AHB 总线 访问 SRAMSRAM 挂在 AHB 总线 上你写的(uint32_t)addr data 是 AHB 上的读写操作三个中断的配合例子中断一DMA 半满中断中断二DMA 全满中断中断三串口空闲中断之间的关系当使用 HAL_UARTEx_ReceiveToIdle_DMA 函数时假设参数三 Size 是 32 字节半满 16 字节。当发送 17 个字节时会先进入中断一DMA 半满中断然后再进入中断三串口空闲中断。当发送 15 个字节时会直接进入中断三串口空闲中断。当发送 33 个字节时会先进入中断一DMA 半满中断然后进入中断二DMA 全满中断然后进入中断三串口空闲中断。并且由于是 33 个字节如果这个时候 DMA 的配置为 DMA_NORMAL 模式则第 33 个字节会被丢掉。如果DMA 被配置为 DMA_CIRCULAR则第 33 个字节会覆盖最开始的第一个字节。3.1半满全满中断介绍每次数据达到半满或者全满会触发一次中断。HAL_UART_Receive_DMA(huart1, recv_data, 20);NDTR 会被配置为20每当写入一个数据NDTR就会-1但是要注意这里是写入一个数据而不是接收一个数据。如果打开了FIFO,Threshold设置为Full 那么由于DMA FIFO深度是4*word 4*4字节16字节那么就会接收数据一直存在FIFO里且Threshold配置为FULL那当FIFO满了会触发 突发传输 一次性把16字节数据全写进SRAM。那么此时NDTR的值直接从20变为了20-164直接触发半满中断再发4个字节数据触发全满中断。所以在使用FIFO时候需要注意半满全满。如果设置为ThresholdFullDMA FIFO满了UART 内部 RX FIFO 满了外部还在发数据并且此时CPU在访问DMA的目的地-SRAM导致DMA无法写入那么此时会丢UART数据。所以ThresholdFull 会让 “抗阻塞余量” 变小高速 / 高负载下建议用 1/2 或 1/4 阈值更安全。开 FIFO 后是批量搬运NDTR 是跳变但 HT 逻辑仍然按 “累计量过半” 来算关于DMA的IRQHandler函数触发条件void DMA2_Stream2_IRQHandler(void) { /* USER CODE BEGIN DMA2_Stream2_IRQn 0 */ /* USER CODE END DMA2_Stream2_IRQn 0 */ HAL_DMA_IRQHandler(hdma_usart1_rx); /* USER CODE BEGIN DMA2_Stream2_IRQn 1 */ /* USER CODE END DMA2_Stream2_IRQn 1 */ }只要下面 任意一种中断发生就会进入HT 中断Half Transfer半传输→ 你设置 20 字节 → 收到 ≥10 字节就进TC 中断Transfer Complete传输完成→ 20 字节收完 → 进TE 中断Transfer Error传输错误FE 中断FIFO ErrorFIFO 上溢 / 下溢DME 中断Direct Mode Error3.2全满半满中断函数void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { if(huart1 huart) { printf(half_interrupt\r\n); } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart1 huart) { HAL_UART_Receive_DMA(huart1, recv_data, 20); printf(full_interrupt\r\n); } }3.3Burst Size 配置介绍如果DMA正在向SRAM写入数据而此时CPU需要访问SRAM那么AHB总线仲裁就会让DMA停止写入转而给CPU权限。如果DMA写入的数据正是CPU要拿的数据那么会导致出现问题。因此只有两种方式防止DMA被打断1、互斥锁 2、BurstUSART的Burst Size 意味着每接受一个字节触发多少次的DMA请求。memory的Burst Size意味着每次触发多少次突发传输。并且此突发传输不会被高优先级的任务打断。即将AHB总线锁死只会锁死当前正在使用的那一条 AHB 总线。对于USART 接收一个字节就触发一次DMA请求如Buest Size 设置为4 那么就会连续触发4次的DMA请求发生如下情况USART接收到 0x12此时DMA的FIFO里存入 0x12 0x12 0x12 0x12 (连着存四个)---所以一般设置USART的buestSize singleBurst Size 的设置和FIFO的阈值字节数以及数据宽度有关根据Threshold 所配置的FIFO 阈值字节数 N × (BURST × SIZE)N1,2,3,4;就是说BURST的设置就是分几拍写入到内存中可以是MSIZE 1B ;那么MBURST 4;分四拍由于阈值容量选的FULL 那么FIFO满了才写入。就是每当FIFO满了就开始写入一次写入MSIZE 1B N 就是44拍就是 16 字节完成写入以确保原子操作。3.4接收不定长数据-串口空闲中断介绍HAL_UART_Receive_DMA() 是标准定长 DMA 接收 API工作模式标记为 HAL_UART_RECEPTION_STANDARD而 HAL_UARTEx_RxEventCallback 是扩展事件回调仅当接收模式为 HAL_UART_RECEPTION_TOIDLE空闲帧模式时才会被 HAL 调度执行二者底层完全隔离天然不会联动。1.HAL_UART_Receive_DMA标准 DMA 接收内部行为仅开启 DMA TC传输完成、HT半满中断不会开启串口 IDLE 空闲中断huart-ReceptionType HAL_UART_RECEPTION_STANDARD中断触发 对应回调DMA 收满Size字节TC 全满 → 进入 HAL_UART_RxCpltCallbackDMA 收到一半数据HT 半满 → 进入 HAL_UART_RxHalfCpltCallback完全不涉及任何 IDLE 空闲事件HAL 中断服务函数里不会调用 RxEventCallback。2. HAL_UARTEx_ReceiveToIdle_DMA空闲帧 DMA 接收你要的版本内部行为自动同时开启 DMA HT/TC 中断 USART IDLE 空闲中断huart-ReceptionType HAL_UART_RECEPTION_TOIDLE中断触发 对应回调总线空闲 IDLE / DMA 半满 HT / DMA 填满 TC全部统一进入 HAL_UARTEx_RxEventCallback在回调内可用 HAL_UARTEx_GetRxEventType(huart) 判断是哪种事件。3.在HAL_UART_IRQHandler中会根据具体情况进行中断事件分发高速无缝连续数据流场景1.IDLE 空闲中断的触发前提必须总线出现至少 1 个字符时长的空闲电平硬件才会置 IDLE 标志、触发中断 当多帧数据首尾紧挨着、帧之间无任何间隔整条数据流是连续电平IDLE 标志永远不会置位。2.此时的尴尬局面 DMA 缓冲区还没收满预设Size不会触发 TC 全满中断又没有空闲IDLE 中断也不进接收缓存持续不断写入新数据旧数据没有机会被业务代码读取解析最终 DMA 循环缓存覆盖旧帧、普通 DMA 缓存直接溢出丢包。3.HT 半满中断的补偿作用DMA 接收达到缓冲区一半长度时强制触发中断提前把前半段数据取走解析提前释放缓存空间不让缓存被持续填满积压。防止串口通信中数据丢失或错误我通常从以下几个维度出发一是使用 DMA 结合空闲中断和环形缓冲区机制减少 CPU 干预同时避免缓冲溢出二是启用硬件或软件校验机制如 CRC、奇偶校验确保数据正确性三是从协议层设计鲁棒的帧结构、加上超时与重传机制防止数据错乱四是提高串口中断优先级并将解包逻辑下沉至后台任务五是硬件层面选用 RS-485 和滤波抗干扰设计。实际项目中根据使用场景做不同策略组合以保证串口通信可靠。五.串口环形缓冲区学习串口缓冲区辅助函数注意WB_index 始终指向已写入的数据的后一个字节。其余任务执行数据处理环形缓冲区设计千变万化但是都应该有下面几种能力设计出稳定通用的中间件。1.面向对象的程序设计能力2.能够熟练搭建基本的数据处理框架结合外设的使用3.自定义协议的能力六.需要注意的点传输数据的时候注意大小端问题6.1uint16_t cmd和uint8_t cmd[2]的区别1.存储结构uint16_t cmd连续 2 字节代表一个 16 位整数有高低字节区分大小端由芯片内核决定Cortex-M 默认小端uint8_t cmd[2]两个独立 8 字节变量仅单纯两块内存无 “16 位数值” 语义2.操作语义完全不同uint16_t a 0x1234; uint8_t b[2] {0x34,0x12}; // 小端下内存布局和a一致但类型含义不一样 a 0x5678; // 直接整体赋值16位数 b[0] 0x78; b[1] 0x56; // 必须分别操作字节只是占用内存大小相同都是 2 字节不能划等号。6.2memcpy(data_struct.sw_ver,temp[2],2);假设 data_struct.sw_ver 是 uint16_t 类型功能从 temp 数组下标 2 开始拷贝 2 个字节覆盖 sw_ver 的两个字节内存内存层面等价手动写法((uint8_t*)data_struct.sw_ver)[0] temp[2];((uint8_t*)data_struct.sw_ver)[1] temp[3];6.3关键坑大小端问题memcpy 只搬运原始字节不做字节交换芯片小端STM32 等 Cortex-M低地址存低字节如果 temp [2] 是高字节、temp [3] 是低字节拷贝后数值会颠倒需要手动调换两个字节。