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

目录

  • USART3_DR的地址

  • DMA的通道

  • DMA的中断

  • USART接收回调函数

  • 头文件源码

  • DMA的基本配置

  • 环形队列接收数据

  • 函数原型

  • 参考用例

  • 总结


硬件: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,比如ADCI2CSPI等等,所以,不同的外设就要选择属于自己的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接收回调函数

STM32HAL中封装了大量外设的回调函数,使用起来十分方便,但是标准库中则没有这样的做法,但是这里我们可以自己实现,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 <stdio.h>
#include <stdint.h>

/* 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 <stdio.h>#include <stdarg.h>#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 < 128){  usart_send_char(usart_tx_buf[i]);     i++; }     usart_send_char('\0'); return 0;}

void usart_test_echo(){ uint8_t tmp_dat = 0xff;

 tmp_dat = usart_recv_char(); usart_send_char(tmp_dat);}

void usart_init(void){

 rcc_init (); gpio_init (); irq_init();

 /* USARTx configured as follow:  - BaudRate = 115200 baud    - Word Length = 8 Bits  - One Stop Bit  - No parity  - Hardware flow control disabled (RTS and CTS signals)  - Receive and transmit enabled */ USART_InitStructure.USART_BaudRate = 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 configuration */ USART_Init(COM_PORT, &USART_InitStructure);

 USART_ITConfig(COM_PORT, USART_IT_IDLE, ENABLE); //USART_ITConfig(COM_PORT, USART_IT_RXNE, ENABLE); /* Enable USART */ USART_Cmd(COM_PORT, ENABLE);

 USART_DMACmd(COM_PORT,USART_DMAReq_Rx, ENABLE); dma_init(); DMA_ITConfig(USART_Rx_DMA_Channel, DMA_IT_TC, ENABLE);  DMA_ITConfig(USART_Rx_DMA_Channel, DMA_IT_TE, ENABLE); DMA_Cmd(USART_Rx_DMA_Channel, ENABLE); 

}

void usart_set_rx_cbk(uart_mod_t *pmod, rx_cbk pfunc,void *pargs){ pmod->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库的外设快速配置,从而简化大量工作;当然为了不掉头发感觉撸寄存器也不错,最终适合自己的才是最好的。

—— The End ——
推荐好文<span data-darkmode-bgcolor="rgb(36, 36, 36)" data-darkmode-original-bgcolor="rgb(255, 255, 255)" data-darkmode-color="rgb(230, 230, 230)" data-darkmode-original-color="rgb(0, 0, 0)" data-style="max-width: 100%; color: rgb(0, 0, 0); font-family: mp-quote, -apple-system-font, system-ui, " helvetica="" neue',="" 'pingfang="" sc',="" 'hiragino="" sans="" gb',="" 'microsoft="" yahei="" ui',="" yahei',="" arial,="" sans-serif;="" font-size:="" 16px;="" letter-spacing:="" 0.544px;="" text-align:="" start;="" box-sizing:="" border-box="" !important;="" overflow-wrap:="" break-word="" !important;'="">
(0)

相关推荐

  • STM32串口发送数据和接收数据方式总结

    串口发送数据 1.串口发送数据最直接的方式就是标准调用库函数 . void USART_SendData(USART_TypeDef* USARTx, uint16_t Data); 第一个参数是发送 ...

  • STM32使用DMA接收串口数据

    STM32使用DMA接收串口数据

  • Python串口数据打包发送STM32接收数据解析

    Python串口数据打包发送STM32接收数据解析 尝试使用python中的struct.pack函数打包数据通过串口发送,由STM32接收解析. 1. struct.pack: struct.pac ...

  • 如何写一个健壮且高效的串口接收程序?

    导读:学单片机的大概最先.最常写的通信程序应该就是串口程序了,但是如何写出一个健壮且高效的串口接收程序呢?接下来鱼鹰将根据多年的开发经验教你如何编写串口接收程序(可在公众号获取个人编写的串口接收源码) ...

  • 串口发送接收浮点型数据

    转自:https://blog.csdn.net/liangwei88624/article/details/6885803 转自:https://blog.csdn.net/newstoy/arti ...

  • 解决STM32 Flash擦写操作导致USART接收丢数据

    问题: 该问题由客户提出,发生在STM32F103VDT6器件上.据客户工程师讲述,在其产品设计中使用了STM32片上Flash模拟了EEPROM的功能,用于存贮数据.在软件调试时,发现开启此功能,会 ...

  • 八大步骤带你高效制作大数据观察报告丨iCourt

    作者:陆军 单位:有度商事诉讼团队 来源公众号:诉讼逆转 编者按 大数据产品是一种工具.一种有效交付,能帮助律师快速获得客户的信任和更好决策.有度商事诉讼团队作为一支以大数据为基础完成商事诉讼逆转的青 ...

  • java读取硬件串口——数据断行问题

    如题,因为项目上的需要,让我使用Java读取硬件外设的串口数据并进行处理.之前也有C语言的基础,使用过串口读写程序,觉得挺简单的,,没放在心上.毕竟串口这也算是各种语言里面最基础的应用了吧,大的使用步 ...

  • 陈根:研究开发新软件——高效处理基因数据

    文/陈根 现代医学的发展,让基因表达得以检测,其最经典的方法是根据在细胞或生物体中所观察到的生物化学或表型的变化来决定某种特定基因是否表达.大分子分离技术的进步,使得特异的基因产物或蛋白分子的识别和分 ...

  • 工具推荐|利用python-cdo高效处理气象数据

    好奇心Log 今天 以下文章来源于气象汇 ,作者lightning 气象汇#气象汇#汇集气象领域的前沿进展和实时资讯,并与大家一起分享气象应用的数据分析和可视化工具等. 如果你不喜欢命令行的操作方式, ...