串口接收不定长数据
- 应用场景
- 搬运串口外设中长度未知的数据
- 通常用于下位机串口发送一帧的场合
- 问题:中断发送数据帧的速率很快,MCU来不及处理此次接收到的数据
- 在重新开启接收DMA通道之前,将LumMod_Rx_Buf缓冲区里面的数据复制到另外一个数组中, 然后再开启DMA,然后马上处理复制出来的数据。
- 建立双缓冲,在LumMod_Uart_DMA_Rx_Data函数中,重新配置DMA_MemoryBaseAddr 的缓冲区地址,那么下次接收到的数据就会保存到新的缓冲区中,不至于被覆盖。
- 原理
- 当串口在一定的、很短的单位时间以后没有接收到新的数据,就触发中断
- 方式1:DMA+串口空闲中断
- 参考文档
- https://blog.csdn.net/dddxxxx/article/details/79278967?tdsourcetag=s_pctim_aiomsg
- http://www.51hei.com/bbs/dpj-39885-1.html
- https://www.cnblogs.com/einstein-2014731/p/5768083.html
- 接收
- 串口空闲中断+DMA
- 发送
- DMA发送+DMA发送中断完成函数
- DMA中断配置
- 发生时间
- IDLE就是串口收到一帧数据后,发生的中断。什么是一帧数据呢?比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。
- RXNE和IDLE区别
- 当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。

/*****************************bsp_usart_dma.h**************************/ //bsp_usart_dma.h #ifndef __USARTDMA_H #define __USARTDMA_H #include "stm32f10x.h" #include <stdio.h> // 串口工作参数宏定义 #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_BAUDRATE 115200 // USART GPIO 引脚宏定义 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 // 串口中断宏定义 #define DEBUG_USART_IRQ USART1_IRQn #define DEBUG_USART_IRQHandler USART1_IRQHandler // DMA中断宏定义 #define DMA_TX_FLG_TC DMA1_IT_TC4 #define DMA_TX_FLG_ALL DMA1_IT_GL4 #define DMA_RX_FLG_ALL DMA1_IT_GL5 // 串口对应的DMA请求通道 #define USART_TX_DMA_CHANNEL DMA1_Channel4 #define USART_RX_DMA_CHANNEL DMA1_Channel5 // 外设寄存器地址 #define USART_DR_ADDRESS (USART1_BASE+0x04) // 一次发送的数据量 #define SENDBUFF_SIZE 5000 void USART_Config(void); void USARTx_DMA_Config(void); #endif /* __USARTDMA_H */ /****************************bsp_usart_dma.c*************************/ //bsp_usart_dma.c #include "bsp_usart_dma.h" uint8_t SendBuff[SENDBUFF_SIZE]; /** * @brief USART GPIO 配置,工作参数配置 * @param 无 * @retval 无 */ void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 打开时钟 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 将USART Tx的GPIO配置为推挽复用模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 将USART Rx的GPIO配置为浮空输入模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); // 配置串口的工作参数 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No ; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(DEBUG_USARTx, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //开启空闲中断 USART_Cmd(DEBUG_USARTx, ENABLE); } /** * @brief USARTx TX DMA 配置,内存到外设(USART1->DR) * @param 无 * @retval 无 */ void USARTx_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // 开启DMA时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //DMA发送 通道4 DMA_DeInit(USART_TX_DMA_CHANNEL); // 恢复缺省值 DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE); //关闭DMA DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; // 设置DMA源地址:串口数据寄存器地址 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; // 内存地址(指针) DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 方向:从内存到外设 发送 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; // 传输大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// 外设数据单位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据单位 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; // 模式,一次或者循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 优先级:中 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 禁止内存到内存的传输 // 配置DMA发送 通道4 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); DMA_ClearFlag(DMA_TX_FLG_ALL); DMA_Cmd(USART_TX_DMA_CHANNEL, DISABLE); DMA_ITConfig(USART_TX_DMA_CHANNEL, DMA_IT_TC, ENABLE); //DMA接收 通道5 DMA_Cmd(USART_RX_DMA_CHANNEL,DISABLE); DMA_DeInit(USART_RX_DMA_CHANNEL); DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //配置DMA通道 DMA_Init(USART_RX_DMA_CHANNEL, &DMA_InitStructure); DMA_ClearFlag(DMA_RX_FLG_ALL); DMA_Cmd(USART_RX_DMA_CHANNEL, ENABLE); USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // 开启串口DMA发送 USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); // 开启串口DMA接收 } /**************************bsp_nvic.c**************************************/ void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; /* 配置NVIC为优先级组1 */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //DMA1通道4发送中断 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } /*********************************主函数**********************/ #include "stm32f10x.h" #include "bsp_led.h" #include "bsp_nvic.h" #include "bsp_usart_dma.h" /* 缓冲区,简单软件标志位 */ extern uint8_t SendBuff[SENDBUFF_SIZE]; extern int Key1Flg,Key2Flg,DMA1Flg,Usart1_FLg; //标志位 /* 中断事件 */ void Usart1_Event(void); void DMA1_Event(void); int main(void) { uint_8 a; NVIC_Configuration(); USART_Config(); USARTx_DMA_Config(); /*填充将要发送的数据*/ for(a=0;a<SENDBUFF_SIZE;a++) { SendBuff[a] = 'P'; } /* 等待中断,由于使用中断方式,CPU不用轮询按键,标志位多的时候也可以换成状态机使用swtch */ while(1) { if (DMA1Flg == 1) { DMA1_Event(); DMA1Flg = 0; } if(Usart1_FLg ==1) { Usart1_Event(); Usart1_FLg = 0; } } } /*********************************中断事件******************************************/ /* 串口接收中断事件 */ void Usart1_Event() { //串口接受完成产生空闲中断 if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET) { //关闭接受,清除DMA通道5中断,重启DMA接受 DMA_Cmd(USART_RX_DMA_CHANNEL, DISABLE); DMA_ClearITPendingBit(DMA_RX_FLG_ALL); DMA_Cmd(USART_RX_DMA_CHANNEL, ENABLE); USART_ReceiveData(USART1);//Clear IDLE interrupt flag bit } } /* DMA发送中断事件 */ void DMA1_Event() { int i; if(DMA_GetITStatus(DMA_TX_FLG_TC)) { //关闭DMA DMA_Cmd(USART_TX_DMA_CHANNEL, DISABLE); //测试是否进入通道4中断 for(i = 0;i<100;i++) { USART_SendData(DEBUG_USARTx,i); } //清除标志位 DMA_ClearFlag(DMA_TX_FLG_TC); } }
- 方式2:环形队列
- 环形队列FIFO+结束标志(0x0a...)+溢出标志位
- 当一次接收的数据超出队列结构长度即队列满时,触发溢出标志位;当上一次的数据处理完毕后,回收内存空间,以用于下次队列周期的数据存放;判断一次接收完成的标志是结束标志字节,通常是0x0d或0x0a或0x0d+0x0a。
- 优势是能最大限度合理利用内存空间,缺点是必须要加结束标志位,
- 使用场景
- 串口没有DMA和空闲中断的情况,
- 按键FIFO,
- 参考文章
- 代码部分
- 待补充,参考安富莱代码按键FIFO

1 typedef struct 2 { 3 u16 Head; 4 u16 Tail; 5 u16 Lenght; 6 u8 Ring_Buff[RINGBUFF_LEN]; 7 } RingBuff_t; 8 9 RingBuff_t ringBuff;//创建一个ringBuff的缓冲区 10 11 void RingBuff_Init(void) 12 { 13 //初始化相关信息 14 ringBuff.Head = 0; 15 ringBuff.Tail = 0; 16 ringBuff.Lenght = 0; 17 } 18 19 /** 20 * @brief Write_RingBuff 21 * @param u8 data 22 * @return FLASE:环形缓冲区已满,写入失败; TRUE:写入成功 23 * @note 往环形缓冲区写入u8类型的数据 24 */ 25 u8 Write_RingBuff(u8 data) 26 { 27 if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满 28 { 29 return FLASE; 30 } 31 ringBuff.Ring_Buff[ringBuff.Tail]=data; //写入数据 32 33 ringBuff.Tail = ( ringBuff.Tail + 1 ) % RINGBUFF_LEN;//防止越界非法访问,实现环形队列.尾指针后移 34 ringBuff.Lenght++; 35 return TRUE; 36 } 37 38 39 40 /** 41 * @brief Read_RingBuff 42 * @param u8 *rData,用于保存读取的数据 43 * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功 44 * @note 从环形缓冲区读取一个u8类型的数据 45 */ 46 u8 Read_RingBuff(u8 *rData) 47 { 48 if(ringBuff.Lenght == 0)//判断非空 49 { 50 return FLASE; 51 } 52 53 *rData = ringBuff.Ring_Buff[ringBuff.Head];//先进先出FIFO,从缓冲区头出 54 55 ringBuff.Head = (ringBuff.Head + 1) % RINGBUFF_LEN;//防止越界非法访问,实现环形队列.首指针后移 56 ringBuff.Lenght--; 57 58 return TRUE; 59 }
裸机--RS485通讯
- 物理层
- 协议标准对比
串口协议部分
RS232协议
- 串口通信最远距离是50英尺,
- 可做到双向传输,全双工通讯,最高传输速率20kbps
- 逻辑1:-3 ~-15V
逻辑0:+3~+15V
- RS485
- RS-485 只有2 根信号线,所以只能工作在半双工模式,常用于总线网.
- 电气特性:逻辑“1”以两线间的电压差为+(2~6)V表示;逻辑“0”以两线间的电压差为-(2~6)V表示。接口信号电平比RS-232-C降低了,就不易损坏接口电路的芯片, 且该电平与TTL电平兼容,可方便与TTL电路连接。
- 数据最高传输速率为10Mbps。
- 接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
- 最大的通信距离约为1219m,最大传输速率为10Mb/S,传输速率与传输距离成反比,在100Kb/S的传输速率下,才可以达到最大的通信距离,RS-485总线一般最大支持32个节点
- RS422
- RS-422 有4 根信号线:两根发送、两根接收。由于RS-422 的收与发是分开的所以可以同时收和发(全双工),也正因为全双工要求收发要有单独的信道,所以RS-422适用于两个站之间通信,星型网、环网,不可用于总线网;
文章参考较多网络资源,如有侵权等等,请联系或留言.