文章目录
- 串口基本概念
- 串口的三种编程方式
- uart编程
- 查询方式不常用、其他两个方式用的多
- 中断方式:
- 代码
- 原理
- DMA方式:
- 配置DMA
- 原理
- 代码
- 效率最高的UART编程方式:
- 是什么?
- 操作
- 在freertos里面调用uart
- 应该怎么做?
- 代码
- 面向对象封装UART
- 串口的DMA设置:
- 编写代码:
- 信号量:启动DAM、等待信号量、释放信号量(在回调函数)
- 怎么封装函数
- 编写遇到的问题
- 寄存器
- 串口通讯不许连续发送,串口为什么一次只发一个字节?
- 波特率、比特率
- 通讯协议
- FIFO
串口基本概念
全双工
低位先行
TXD发、RXD接
起始位 | 数据位 | 校验位 | 停止位
0 8-9位 奇/偶校验 1
通讯前的约定(协议)用串口时双方要协定好没传输一个数据需要多少秒(约定好波特率)
奇偶校验位
数据位+校验位个数位奇数个,则正确
波特率bps,每一秒传输数据的位数
串口的三种编程方式
注意:中断方式和DMA方式
第一个Transmit都是使能中断,然后在中断中完成传输,在中断的最后有一个回调函数callback,callback为_weak函数,用户可以自己去写具体要求
1、查询方式
收/发数据时需要不停查看相应寄存器是否为空
2、中断方式
Transmit_IT使能中断
callback会给反馈,但也是会经常打断cpu
3、DMA
使用中断方式时,在传输、接收数据时,会发生中断,还需要 CPU 执行中断处理函数。有另外一种方法: DMA(Direct Memory Access),它可以直接在 2 个设备之间传递数据,无需 CPU 参与
DMA就是跑腿的
uart编程
查询方式不常用、其他两个方式用的多
三种方式,只实现串口2发送、串口4接收;
串口2接收、4发送省去;
中断方式:
收到一个字符就会产生一个中断,就会去中断cpu;DMA是接收完所有字符才产生一次中断
具体实现:
首先要使能中断
代码
static volatile int g_uart2_tx_complete = 0;//用来判断是否完成 static volatile int g_uart4_rx_complete = 0; void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { //数据返送完毕,中断函数会调用这个回调函数 if(huart == &huart2) { g_uart2_tx_complete = 1;//数据发送完后就会置成1,wait看到1则置为0表示完成、如果一直是0直到超时则返回-1表示失败 } } int Wait_UART2_Tx_Complete(int timeout) { while(g_uart2_tx_complete == 0 && timeout) { vTaskDelay(1); timeout--; }; if(timeout == 0)//超时 return -1; else { g_uart2_tx_complete = 0; return 0; } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { //数据返送完毕,中断函数会调用这个回调函数 if(huart == &huart4) { g_uart4_rx_complete = 1;//数据发送完后就会置成1,wait看到1则置为0表示完成、如果一直是0直到超时则返回-1表示失败 } } int Wait_UART4_Rx_Complete(int timeout) { while(g_uart4_rx_complete == 0 && timeout) { vTaskDelay(1); timeout--; } if(timeout == 0)//超时 return -1; else { g_uart4_rx_complete = 0; return 0; } }
extern UART_HandleTypeDef huart4; extern UART_HandleTypeDef huart2; //发送 int Wait_UART2_Tx_Complete(int timeout); //接收 int Wait_UART4_Rx_Complete(int timeout); /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; const osThreadAttr_t defaultTask_attributes = { .name = "defaultTask", .priority = (osPriority_t) osPriorityNormal, .stack_size = 128 * 4 }; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ //任务函数 static void SPILCDTaskFunction( void *pvParameters ) { char bur[100]; int cnt = 0; while(1) { sprintf(bur, "lcd task test:%d" ,cnt++); //Draw_String(0, 0 , bur, 0x0000ff00, 0); vTaskDelay(1000); } } static void CH1_URAT2_TxTaskFunction( void *pvParameters ) { uint8_t c = 0; while(1) { //发数据 HAL_UART_Transmit_IT(&huart2, &c, 1); Wait_UART2_Tx_Complete(100);//等待发送完成 vTaskDelay(500); c++; } } static void CH2_URAT4_RxTaskFunction( void *pvParameters ) { uint8_t c = 0; char bur[100]; int cnt = 0; HAL_StatusTypeDef err; while(1) { //接收数据 err = HAL_UART_Receive_IT(&huart4, &c, 1);//串口、内容地坿、长度㿁超旿 if(Wait_UART4_Rx_Complete(10) == 0)//=0表示接收完成 { sprintf(bur,"receive dataset : 0x:%02x, numember:%d",c, cnt++); Draw_String(0, 0, bur, 0x0000ff00, 0); } else { HAL_UART_AbortReceive_IT(&huart4);//超时或者出错则调用终止中断接收的函数 } } }
原理
DMA方式:
在dma传输过程中不产生中断,传输完指定数量的数据后产生中断;
dma只会去中断cpu一次;
优点:DMA优势就在于可以接收很多数据;
源 | 目的 | 长度
发送:内存的源地址++、TDR
接收:RDR 、目的地址++
配置DMA
原理
代码
就只是把中断的代码的这些换了
效率最高的UART编程方式:
是什么?
正常的三种编程方式1启动2等待完成,一般是等到如下图设置的1000个字节都收到后停止,但是其他比如完整的数据收到了没到1000字节,以及长时间未响应、产生error就要用到IDLE中断;
等待完成如果已经收到完整的数据但是没有达到如下如1000个字节,那么就要靠IDLE中断来告知收到完整数据了。
问题:中断和DMA每次都要手工使能中断/启动DMA,如果代码里面有其他长时间的任务没结束,第二次就要等这个任务结束后才启动下一次;
方法:一开始就启动DMA
使用DAM+IDLE中断:
其他方式都可以用IDLE但是DMA是最好的,中断方式没有必要用这个,因为他要及时的获取数字每读到一个字节、就产生一次中断,去中断一次cpu
空闲而停止mcu检测到长的停止时间,就会产生IDLE中断
操作
1、一开始就使能IDLE的这个函数
2、实现回调函数
回调函数创建队列都是在中断函数中实现的,回调函数就是在中断函数中调用的
中断里面写队列要有一个后缀FromISR
在freertos里面调用uart
多了freertos队列
应该怎么做?
代码
static volatile int g_uart2_tx_complete = 0;//用来判断是否完成 static volatile int g_uart4_rx_complete = 0; static uint8_t g_uart4_rx_buf[100];//定义一个buff来存接收到的数据 static QueueHandle_t g_xUART4_RX_Queue;//创建队列 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { //数据返鿁完毕,中断函数会调用这个回调函敿 if(huart == &huart2) { g_uart2_tx_complete = 1;//数据发鿁完后就会置房1,wait看到1则置丿0表示完成、如果一直是0直到超时则返囿-1表示失败 } } int Wait_UART2_Tx_Complete(int timeout) { while(g_uart2_tx_complete == 0 && timeout) { vTaskDelay(1); timeout--; }; if(timeout == 0)//超时 return -1; else { g_uart2_tx_complete = 0; return 0; } } //接收完毕 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { //数据返鿁完毕,中断函数会调用这个回调函敿 if(huart == &huart4) { g_uart4_rx_complete = 1;//数据发鿁完后就会置房1,wait看到1则置丿0表示完成、如果一直是0直到超时则返囿-1表示失败 //收到数据后把收到的数据存入buff,写队列 for(int i = 0 ; i < 100; i++) { xQueueSendFromISR(g_xUART4_RX_Queue,&g_uart4_rx_buf[i], NULL); } //重新启动DMA+IDLE HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100); } } //void event,接收空闲,表示数据已经接收完成,但是还没到DMA接收设置的值 void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if(huart == &huart4) { g_uart4_rx_complete = 1; //写队列 for(int i = 0 ; i < Size; i++) { xQueueSendFromISR(g_xUART4_RX_Queue,&g_uart4_rx_buf[i], NULL); } //重新启动DMA+IDLE HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100); } } //void error:重新启动DMA+IDLE void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { //重启DMA+IDLE HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100); } int Wait_UART4_Rx_Complete(int timeout) { while(g_uart4_rx_complete == 0 && timeout) { vTaskDelay(1); timeout--; } if(timeout == 0)//超时 return -1; else { g_uart4_rx_complete = 0; return 0; } } //读数据,app从队列中读数据不从串口读数据了 int UART4_GetData(uint8_t *pData) { xQueueReceive(g_xUART4_RX_Queue,pData, portMAX_DELAY); return 0; } void UART4_RX_Start(void) { //开始前把上面定义好的队列创建处来 g_xUART4_RX_Queue = xQueueCreate( 200, 1 ); //启动接收 HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100); //收到的数据保存在哪里,要定义一个buff;收到后回调函数就会被调用 }
extern UART_HandleTypeDef huart4; extern UART_HandleTypeDef huart2; void UART4_RX_Start(void); int UART4_GetData(uint8_t *pData); //发鿿 int Wait_UART2_Tx_Complete(int timeout); //接收 int Wait_UART4_Rx_Complete(int timeout); /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; const osThreadAttr_t defaultTask_attributes = { .name = "defaultTask", .priority = (osPriority_t) osPriorityNormal, .stack_size = 128 * 4 }; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ //任务函数 static void SPILCDTaskFunction( void *pvParameters ) { char bur[100]; int cnt = 0; while(1) { sprintf(bur, "lcd task test:%d" ,cnt++); //Draw_String(0, 0 , bur, 0x0000ff00, 0); vTaskDelay(1000); } } static void CH1_URAT2_TxTaskFunction( void *pvParameters ) { uint8_t c = 0; while(1) { //发数捿 HAL_UART_Transmit_DMA(&huart2, &c, 1); Wait_UART2_Tx_Complete(100);//等待发鿁完房 vTaskDelay(500); c++; } } static void CH2_URAT4_RxTaskFunction( void *pvParameters ) { uint8_t c = 0; char bur[100]; int cnt = 0; HAL_StatusTypeDef err; while(1) { //一开头就调用这个函数来调用到IDLE UART4_RX_Start(); err = UART4_GetData(&c);//读到的数据保存在c if(err == 0)//=0表示接收完成 { sprintf(bur,"receive dataset : 0x:%02x, numember:%d",c, cnt++); Draw_String(0, 0, bur, 0x0000ff00, 0); } else { HAL_UART_DMAStop(&huart4);//超时或迅出错则调用终止中断接收的函敿 } } }
200个数据,每个数据一个字节
面向对象封装UART
构造处结构体,包含uart里面的初始话函数、构造函数等等;
串口的DMA设置:
前面只设置了uart2发送和uart4接收;
现在设置uart4接收和uart2发送;
源地址叠加和目的地址是否叠加在前面写了;
发送一定是内存到外设,接收则相反
编写代码:
uart接收复制uart4接收,等待、获取数据、启动函数(等待接收函数不需要了删除即可,直接等待队列完成);
callback直接在callback里面复制;
getData设置超时时间;
等待函数去掉,等待队列就行了,换成freertos的信号量:
中断里面不能give互斥量mutex,啥是互斥量?信号量和互斥量
优先级的恢复工作不太好做
信号量:启动DAM、等待信号量、释放信号量(在回调函数)
过程原理
二进制信号量先定义出来->调用创建信号量函数
send函数发送出去,然后等待中断里面的callback回调函数give,计数值变成1
send函数take拿走这个1;
怎么封装的?
声明和定义结构体
把uart里面的这些函数封装起来
怎么封装函数
把这几个函数放入结构体中
这个结构体的成员函数如下,这样就能直接定义出这个结构体,用->来初始化、发数据、收数据
代码
#include "uart_device.h" #include
#include extern struct UART_Device g_uart2_dev; extern struct UART_Device g_uart4_dev; static struct UART_Device *g_uart_devices[] = {&g_uart2_dev, &g_uart4_dev}; //根据名字遍历这个指针,返回结构体地址 struct UART_Device * GetUARDevice(char *name) { int i = 0; for(i = 0; i < sizeof(g_uart_devices)/sizeof(g_uart_devices[0]); i++) { if(!strcmp(name, g_uart_devices[i]->name)) return g_uart_devices[i]; } return NULL; } #ifndef __UART_DEVICE_H #define __UART_DEVICE_H #include
struct UART_Device { char *name; int (*Init)( struct UART_Device *pDev, int baud, char parity, int data_bit, int stop_bit); int (*Send)( struct UART_Device *pDev, uint8_t *datas, uint32_t len, int timeout); int (*RecvByte)( struct UART_Device *pDev, uint8_t *data, int timeout); }; struct UART_Device *GetUARDevice(char *name); #endif /* __UART_DEVICE_H */ //任务函数 static void SPILCDTaskFunction( void *pvParameters ) { char bur[100]; int cnt = 0; while(1) { sprintf(bur, "lcd task test:%d" ,cnt++); //Draw_String(0, 0 , bur, 0x0000ff00, 0); vTaskDelay(1000); } } static void CH1_URAT2_TxTaskFunction( void *pvParameters ) { uint8_t c = 0; struct UART_Device *pdev = GetUARDevice("uart2"); pdev->Init(pdev , 115200, 'N', 8, 1); while(1) { pdev->Send(pdev, &c, 1, 100); vTaskDelay(500); c++; } } static void CH2_URAT4_RxTaskFunction( void *pvParameters ) { uint8_t c = 0; char bur[100]; int cnt = 0; int err; struct UART_Device *pdev = GetUARDevice("uart4"); pdev->Init(pdev , 115200, 'N', 8, 1); while(1) { err = pdev->RecvByte(pdev, &c, 100); if(err == 0)//=0表示接收完成 { sprintf(bur,"receive dataset : 0x:%02x, numember:%d",c, cnt++); Draw_String(0, 0, bur, 0x0000ff00, 0); } else { //HAL_UART_DMAStop(&huart4);//超时或迅出错则调用终止中断接收的函敿 } } } /* USER CODE END FunctionPrototypes */ /** * @brief FreeRTOS initialization * @param None * @retval None */ void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* USER CODE BEGIN RTOS_MUTEX */ /* add mutexes, ... */ /* USER CODE END RTOS_MUTEX */ /* USER CODE BEGIN RTOS_SEMAPHORES */ /* add semaphores, ... */ /* USER CODE END RTOS_SEMAPHORES */ /* USER CODE BEGIN RTOS_TIMERS */ /* start timers, add new ones, ... */ /* USER CODE END RTOS_TIMERS */ /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ /* USER CODE END RTOS_QUEUES */ /* creation of defaultTask */ defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ xTaskCreate( SPILCDTaskFunction, // 函数指针, 任务函数 "spi_lcd_task", // 任务的名孿 200, // 栈大尿,单位为word,10表示40字节 NULL, // 调用任务函数时传入的参数 osPriorityNormal, // 优先线 NULL ); // 任务句柄, 以后使用它来操作这个任务 xTaskCreate( CH1_URAT2_TxTaskFunction, // 函数指针, 任务函数 "ch1_uart2_tx_task", // 任务的名孿 200, // 栈大尿,单位为word,10表示40字节 NULL, // 调用任务函数时传入的参数 osPriorityNormal, // 优先线 NULL ); // 任务句柄, 以后使用它来操作这个任务 xTaskCreate( CH2_URAT4_RxTaskFunction, // 函数指针, 任务函数 "ch2_uart4_rx_task", // 任务的名孿 200, // 栈大尿,单位为word,10表示40字节 NULL, // 调用任务函数时传入的参数 osPriorityNormal, // 优先线 NULL ); // 任务句柄, 以后使用它来操作这个任务 /* USER CODE END RTOS_THREADS */ /* USER CODE BEGIN RTOS_EVENTS */ /* add events, ... */ /* USER CODE END RTOS_EVENTS */ }
static SemaphoreHandle_t g_UART2_TX_Semaphore; static uint8_t g_uart4_rx_buf[100];//定义丿个buff来存接收到的数据 static QueueHandle_t g_xUART4_RX_Queue;//创建队列 static SemaphoreHandle_t g_UART4_TX_Semaphore; static uint8_t g_uart2_rx_buf[100]; static QueueHandle_t g_xUART2_RX_Queue; struct UART_Device;//表示这是一个结构体类型 //send callback void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { //数据返鿁完毕,中断函数会调用这个回调函敿 if(huart == &huart2) { xSemaphoreGiveFromISR(g_UART2_TX_Semaphore, NULL); } if(huart == &huart4) { xSemaphoreGiveFromISR(g_UART4_TX_Semaphore, NULL); } } //receive callback void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { //数据返鿁完毕,中断函数会调用这个回调函敿 if(huart == &huart4) { //收到数据后把收到的数据存入buff,写队列 for(int i = 0 ; i < 100; i++) { xQueueSendFromISR(g_xUART4_RX_Queue,&g_uart4_rx_buf[i], NULL); } //重新启动DMA+IDLE HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100); } if(huart == &huart2) { //收到数据后把收到的数据存入buff,写队列 for(int i = 0 ; i < 100; i++) { xQueueSendFromISR(g_xUART2_RX_Queue,&g_uart2_rx_buf[i], NULL); } //重新启动DMA+IDLE HAL_UARTEx_ReceiveToIdle_DMA(&huart2, g_uart2_rx_buf, 100); } } //receive void event,接收空闿,表示数据已经接收完成,但是还没到DMA接收设置的忿 void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if(huart == &huart4) { //写队刿 for(int i = 0 ; i < Size; i++) { xQueueSendFromISR(g_xUART4_RX_Queue,&g_uart4_rx_buf[i], NULL); } //重新启动DMA+IDLE HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100); } if(huart == &huart2) { //写队刿 for(int i = 0 ; i < Size; i++) { xQueueSendFromISR(g_xUART2_RX_Queue,&g_uart2_rx_buf[i], NULL); } //重新启动DMA+IDLE HAL_UARTEx_ReceiveToIdle_DMA(&huart2, g_uart2_rx_buf, 100); } } //receive void error:重新启动DMA+IDLE void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { //重启DMA+IDLE if(huart == &huart4) { HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100); } if(huart == &huart2) { HAL_UARTEx_ReceiveToIdle_DMA(&huart2, g_uart2_rx_buf, 100); } } //读数据,app从队列中读数据不从串口读数据亿 /**************/ /**************/ /* uart4接收、uart2发送 */ int UART2_Send(struct UART_Device *pDev, uint8_t *datas,uint32_t len, int timeout) { HAL_UART_Transmit_DMA(&huart2, datas, len); //wait Semaphore 信号量 if(pdTRUE == xSemaphoreTake(g_UART2_TX_Semaphore,timeout)) return 0; else return -1; } int UART4_GetData(struct UART_Device *pDev,uint8_t *pData, int timeout) { if(pdPASS == xQueueReceive(g_xUART4_RX_Queue,pData, timeout)) return 0; else return -1; } int UART4_RX_Start(struct UART_Device *pDev, int baud, char parity, int data_bit, int stop_bit) { //弿始前把上面定义好的队列创建处板 if(!g_xUART2_RX_Queue) { g_xUART4_RX_Queue = xQueueCreate( 200, 1 ); //启动接收 HAL_UARTEx_ReceiveToIdle_DMA(&huart4, g_uart4_rx_buf, 100); //收到的数据保存在哪里,要定义1个buff;收到后回调函数就会被调用 //创建信号量 g_UART4_TX_Semaphore = xSemaphoreCreateBinary(); } return 0; } /*****************/ /* uart2接收、uart4发送 */ int UART2_GetData(struct UART_Device *pDev, uint8_t *pData, int timeout) { if(pdPASS == xQueueReceive(g_xUART2_RX_Queue,pData, timeout)) return 0; else return -1; } int UART2_RX_Start(struct UART_Device *pDev, int baud, char parity, int data_bit, int stop_bit) { if (!g_xUART2_RX_Queue) { g_xUART2_RX_Queue = xQueueCreate(200, 1); g_UART2_TX_Semaphore = xSemaphoreCreateBinary(); HAL_UARTEx_ReceiveToIdle_DMA(&huart2, g_uart2_rx_buf, 100); } return 0; } int UART4_Send(struct UART_Device *pDev, uint8_t *datas,uint32_t len, int timeout) { HAL_UART_Transmit_DMA(&huart4, datas, len); //wait Semaphore 信号量 if(pdTRUE == xSemaphoreTake(g_UART4_TX_Semaphore,timeout)) return 0; else return -1; } struct UART_Device g_uart2_dev = {"uart2", UART2_RX_Start, UART2_Send, UART2_GetData}; struct UART_Device g_uart4_dev = {"uart4", UART4_RX_Start, UART4_Send, UART4_GetData};
编写遇到的问题
1、./Core/Src/usart.c(377): warning: passing ‘volatile uint8_t [100]’ to parameter of type ‘uint8_t *’ (aka ‘unsigned char *’) discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
2、undefined symbol
把static去掉后就好了
另一个文件
寄存器
串口通讯不许连续发送,串口为什么一次只发一个字节?
1、避免累计误差;
2、串口通讯是异步发送,就是发送方和接受方有各自的时钟,时钟不同步,时钟同步的话可以发好多个字节;
波特率、比特率
波特率表示每秒传输信号的状态数,如果一个波形传输一个bit,那就=bit率,每秒传输的二进制位
一个波形传输n个比特
波特率= n比特率
总之:
波特率: 1 秒内传输信号的状态数(波形数)。比特率: 1 秒内传输数据的 bit数。如果一个波形,能表示 N 个 bit,那么:波特率 * N = 比特率。
通讯协议
并行8根线一次发8位
串行通信一根线发
单工,只能单向
双工双向,半双工一条通道接受和发送不能同时工作
全双工两个通道可以,同时收发
FIFO
FIFO(First In First Out,即先入先出),是一种数据缓冲器。先被写入的数据会按顺序先被读出。FIFO可看做一个管道,有数据写入端口和 数据读取端口:
设置异步通信
设置数据位,校验位、波特率、停止位
memset
还没有评论,来说两句吧...