STM32如何高效接收串口数据?

硬件:stm32f103cbt6
软件:stm32f10x_stdperiph_lib_v3.5.0
dma,直接内存存取,可以用它的双手释放cpu的灵魂,所以,本文通过usart3进行串口收发,接受使用dma的方式,无需cpu进行干预,当接受完成之后,数据可以直接从内存的缓冲区读取,从而减少了cpu的压力。
具体的代码实现如下:
usart_driver.h  封装了接口,数据接收回调函数类型,基本数据结构等;
usart_driver.c  函数原型实现,中断服务函数实现等;
拷贝这两个文件即可,可以根据目录下的参考用例,进行初始化。
头文件usart_driver.h已经声明了外部函数可能用到的接口;
usart3_dr的地址
因为usart3接收到数据会存在dr寄存器中,而dma控制器则负责将该寄存器中的内容一一搬运到内存的缓冲区中(比如你定义的某个数组中),所以这里需要告诉dma控制去哪里搬运,因此需要设置usart3_dr的总线地址。
usart3的基址如下图所示;
usart3的基址
dr寄存器的偏移地址如下图所示;
dr偏移地址
所以最终地址为:0x40004800 + 0x004#define usart_dr_base 0x40004804
dma的通道
因为有很多外设都可以使用dma,比如adc,i2c,spi等等,所以,不同的外设就要选择属于自己的dma通道,查找参考手册;
dma通道
因此usart3_rx在这里会使用dma1的通道3,这都是硬件上已经预先分配好的,我们需要遵循这个规则。所以在代码中我们做出相应的定义;如下所示;
#define usart_rx_dma_channel    dma1_channel3
dma的中断
dma支持三种中断:传输过半,传输完成,传输出错;
dma中断
因此在使用是相当安全也相当灵活,而本文只是用了传输完成中断;如下定义了,传输完成中断的标志位,dma1_flag_tc3也就对应了图中的tcif;
#define usart_rx_dma_flag       dma1_flag_tc3
usart接收回调函数
在stm32的hal中封装了大量外设的回调函数,使用起来十分方便,但是标准库中则没有这样的做法,但是这里我们可以自己实现,rx_cbk就是回调,即串口数据接收完成就会执行已经注册的回调函数;
typedef void (*rx_cbk)(void* args);
通过使用接口usart_set_rx_cbk进行回调函数的注册,pargs为将传递的参数指针;
void usart_set_rx_cbk(uart_mod_t *pmod, rx_cbk pfunc,void *pargs);
头文件源码
#ifndef usart_driver_h #define usart_driver_h #include  #include  /* private function prototypes -----------------------------------------------*/ #define use_microlib_usart 1 #if use_microlib_usart #ifdef __gnuc__ /* with gcc/raisonance, small printf (option ld linker->libraries->small printf    set to 'yes') calls __io_putchar() */ #define putchar_prototype int __io_putchar(int ch) #else #define putchar_prototype int fputc(int ch, file *f) //#define getchar_prototype int fgetc(file *f) #endif /* __gnuc__ */ extern putchar_prototype; #else #endif   //default 8n1 #define com_port usart3 #define tx_pin  gpio_pin_10 #define rx_pin  gpio_pin_11 #define baudrate 115200 #define irq_uart_pre 3 #define irq_uart_sub 3 #define usart_rx_dma_channel    dma1_channel3 #define usart_rx_dma_flag       dma1_flag_tc3 #define usart_dr_base           0x40004804 #define usart_buf_size   ((uint16_t)16) typedef void (*rx_cbk)(void* args); struct uart_mod {    uint8_t rx_buf[usart_buf_size];  uint8_t rx_dat_len;  uint8_t head;  uint8_t tail;     void (*init)(void);    void *pargs;  rx_cbk pfunc_rx_cbk; }; typedef struct uart_mod uart_mod_t; extern  uart_mod_t user_uart_mod; void usart_init(void); void usart_set_rx_cbk(uart_mod_t *pmod, rx_cbk pfunc,void *pargs); void usart_send_char(char ch); void usart_test_echo(void); uint8_t usart_recv_char(void); int usart_printf(const char *fmt, ...); //extern getchar_prototype; #endif
dma的基本配置
串口接收dma的配置在函数dma_init中;
static void dma_init(void)
已经定义了数据缓冲区,如下:
uint8_t rxbuffer[usart_buf_size] = { 0 };
因此需要在dma的配置中设置usart_dr的地址,和数据缓冲区的地址,以及两者的大小;还有就是数据流向;
寄存器流向内存;
内存流向寄存器;这个需要搞清楚;相关配置如下所示;
dma_initstructure.dma_peripheralbaseaddr = usart_dr_base;  dma_initstructure.dma_memorybaseaddr = (uint32_t)rxbuffer;    dma_initstructure.dma_buffersize = usart_buf_size;  dma_initstructure.dma_dir = dma_dir_peripheralsrc;
注意:dma_dir_peripheralsrc表示,外设作为源地址,数据是从外设寄存器流向内存,即dma会把数据从地址usart_dr_base搬运到rxbuffer去。如果这个地方搞错,会导致rxbuffer始终没有你想要的数据。
环形队列接收数据
线性缓冲区会因为缓冲器接收数据已满导致无法继续接收的问题;而环形队列进行接收的话,会自动进行覆盖,这样一来,在读取数据的时候,也要配置一个环形队列进行数据处理,下面的配置是把dma配置为循环模式;
dma_initstructure.dma_mode = dma_mode_circular;
在结构体user_uart_mod中,则用两个变量分别指向队首head和队尾tail;具体数据的读取在函数usart3_irqhandler中,会把数据从内存的rxbuffer读取到结构体user_uart_mod的成员变量rx_buf中;最终调用回调函数。
函数原型
usart_driver.c
#include  #include  #include stm32f10x_usart.h #include usart_driver.h uint8_t rxbuffer[usart_buf_size] = { 0 }; uart_mod_t user_uart_mod = {  .rx_dat_len = 0,  .head = 0,  .tail = 0,  .pfunc_rx_cbk = null,  .pargs = null }; static usart_inittypedef usart_initstructure; static void rcc_init(void){  rcc_ahbperiphclockcmd(rcc_ahbperiph_dma1, enable);  /* enable gpio clock */  rcc_apb2periphclockcmd( rcc_apb2periph_gpiob         | rcc_apb2periph_afio, enable);  rcc_apb1periphclockcmd( rcc_apb1periph_usart3, enable); } static void gpio_init(void){   gpio_inittypedef gpio_initstructure;   /* configure usart tx as alternate function push-pull */   gpio_initstructure.gpio_mode = gpio_mode_af_pp;   gpio_initstructure.gpio_pin = tx_pin;   gpio_initstructure.gpio_speed = gpio_speed_50mhz;   gpio_init(gpiob, &gpio_initstructure);   /* configure usart rx as input floating */   gpio_initstructure.gpio_mode = gpio_mode_in_floating;   gpio_initstructure.gpio_pin = rx_pin;      gpio_init(gpiob, &gpio_initstructure); } static void dma_init(void){   dma_inittypedef dma_initstructure;   /* usarty_tx_dma_channel (triggered by usarty tx event) config */    dma_deinit(usart_rx_dma_channel);  dma_initstructure.dma_peripheralbaseaddr = usart_dr_base;  dma_initstructure.dma_memorybaseaddr = (uint32_t)rxbuffer;  //dma_initstructure.dma_dir = dma_dir_peripheraldst;  dma_initstructure.dma_dir = dma_dir_peripheralsrc;  dma_initstructure.dma_buffersize = usart_buf_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_veryhigh;  dma_initstructure.dma_m2m = dma_m2m_disable;  dma_init(usart_rx_dma_channel, &dma_initstructure); } static void irq_init(void){  nvic_inittypedef nvic_initstructure;  /* enable the usart3_irqn interrupt */  nvic_initstructure.nvic_irqchannel = usart3_irqn;  nvic_initstructure.nvic_irqchannelpreemptionpriority = irq_uart_pre;  nvic_initstructure.nvic_irqchannelsubpriority = irq_uart_sub;  nvic_initstructure.nvic_irqchannelcmd = enable;  nvic_init(&nvic_initstructure); } void usart_send_char(char ch){  /* loop until the end of transmission */  //while (usart_getflagstatus(com_port, usart_flag_tc) == reset){}  while((com_port->sr & usart_flag_tc) != usart_flag_tc){    }   usart_senddata(com_port, (uint8_t) ch); } uint8_t usart_recv_char(){  /* wait the byte is entirely received by usarty */     //while(usart_getflagstatus(com_port, usart_flag_rxne) == reset){}  while((com_port->sr & usart_flag_rxne) != usart_flag_rxne){    }       /* store the received byte in the rxbuffer1 */     return (uint8_t)usart_receivedata(com_port); } int usart_printf(const char *fmt, ... ) {     uint8_t i = 0;     uint8_t usart_tx_buf[128] = { 0 };     va_list ap;     va_start(ap, fmt );     vsprintf((char*)usart_tx_buf, fmt, ap);     va_end(ap);    while(usart_tx_buf[i] && i pargs = pargs;  pmod->pfunc_rx_cbk = pfunc; } void dma1_channel3_irqhandler(void){      if(dma_getitstatus(usart_rx_dma_flag) == set){                 dma_clearitpendingbit(usart_rx_dma_flag);     } } /**   * @brief  this function handles usart3 global interrupt request.   * @param  none   * @retval none   */ void usart3_irqhandler(void) {  uint8_t buf[usart_buf_size];  uint16_t rect_len = 0;  if(usart_getitstatus(com_port, usart_it_idle) != reset)   {   uint8_t i = 0;   usart_receivedata(com_port);   user_uart_mod.head = usart_buf_size - dma_getcurrdatacounter(usart_rx_dma_channel);     //fifo is not full    while(user_uart_mod.head%usart_buf_size != user_uart_mod.tail%usart_buf_size){       user_uart_mod.rx_buf[i++] = rxbuffer[user_uart_mod.tail++%usart_buf_size];   }   user_uart_mod.rx_dat_len = i;   //dma_cmd(usart_rx_dma_channel, enable);   if(user_uart_mod.pfunc_rx_cbk != null){    user_uart_mod.pfunc_rx_cbk(user_uart_mod.pargs);   }  }  usart_clearitpendingbit(com_port, usart_it_idle);  //usart_clearitpendingbit(com_port, usart_it_rxne); } #if use_microlib_usart /**   * @brief  retargets the c library printf function to the usart.   * @param  none   * @retval none   */ putchar_prototype {  /* place your implementation of fputc here */  /* e.g. write a character to the usart */  usart_senddata(com_port, (uint8_t) ch);  /* loop until the end of transmission */  while (usart_getflagstatus(com_port, usart_flag_tc) == reset)  {}  return ch; } #else #pragma import(__use_no_semihosting)                           struct __file  {   int handle;  };  file __stdout;        int _sys_exit(int x) {   x = x;   return 0; }  int fputc(int ch, file *f) {        /* place your implementation of fputc here */  /* e.g. write a character to the usart */  usart_senddata(com_port, (uint8_t) ch);  /* loop until the end of transmission */  while (usart_getflagstatus(com_port, usart_flag_tc) == reset)  {}  return ch; } #endif
参考用例
这里需要调用usart_init,并设置回调函数,如果不设置,则不会执行回调。
void motor_get_cmd_from_uart(void *pargs){    if(pargs == null){   return;  }   uart_mod_t *p = pargs;  if(p->rx_dat_len > 0 && p->rx_dat_len == package_size){   if(p->rx_buf[0] == package_head    && p->rx_buf[package_size - 1] == package_tail){    user_cmd_mod.head = p->rx_buf[0];    user_cmd_mod.cmd.value_n[0] = p->rx_buf[1];    user_cmd_mod.cmd.value_n[1] = p->rx_buf[2];        user_cmd_mod.option = p->rx_buf[3];        user_cmd_mod.data.value_n[0] = p->rx_buf[4];    user_cmd_mod.data.value_n[1] = p->rx_buf[5];    user_cmd_mod.data.value_n[2] = p->rx_buf[6];    user_cmd_mod.data.value_n[3] = p->rx_buf[7];        user_cmd_mod.tail = p->rx_buf[package_size - 1];    user_cmd_mod.process_flag = 1;   }    }  p->rx_dat_len = 0;  } int main(void){  usart_init();  usart_set_rx_cbk(&user_uart_mod, motor_get_cmd_from_uart,&user_uart_mod); }
总结
本文简单介绍了基于stm32基于dma,利用串口空闲中断进行串口数据接收的具体配置和实现方法,代码基于标准库3.5版本;
因为标准库st目前已经不再更新,并且st提供了cubemx工具可以进行基于hal库和ll库的外设快速配置,从而简化大量工作;当然为了不掉头发感觉撸寄存器也不错,最终适合自己的才是最好的。


国际显示技术及应用创新展开幕,京东方Mini LED产品引发关注
特斯拉新专利隔离缺陷电芯 提高电池安全性
Xilinx推出Zynq-7000系列最新成员,满足Smarter无线、广播及医疗系统要求
摩根大通与德勤前老板合作将其技术引入区块链世界
小米游戏本新品或下月发布
STM32如何高效接收串口数据?
立柱式机器人码垛机的设备特点和工作原理是什么
新思科技完成对MorethanIP的收购
苹果有意将TouchID重新添加到iPhone中
如何在嵌入式C编码中规范变量
【解决方案】物通博联新能源汽车换电站物联网监控方案
大数据与人工智能将推动医疗行业的发展
移动支付之后,NFC技术即将在智能家居中的应用市场迎来爆发?
开鸿智谷与鸿蒙生态服务公司签署合作协议
浅谈数据中心精细化建设策略分析
华为主张“平台+AI+行业智慧+生态”推动智能世界加速到来
人工智能融合赋能平台,助力智慧城市智能化升级
常见PCB可靠性问题和典型案例
XL3002降压式DC/DC变换器中午资料(特性_引脚功能_典型驱动电路)
百度正式成立智能生活事业群 聚焦对话式人工智能