硬件: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变换器中午资料(特性_引脚功能_典型驱动电路)
百度正式成立智能生活事业群 聚焦对话式人工智能