## uart 实现重定向, printf ### 添加 usart.h usart.c 在调用的初始化函数 HAL_UART_Init 内部,会先调用 MSP 初始化回调函数进行 MCU 相关的初始化,函数为: void HAL_UART_MspInit(UART_HandleTypeDef *huart) 以上函数在 stm32f1xx_hal_msp.c 定义,无需重复定义 (CubeMX会自己定义) 操作过程中,只需要开启何种类型中断,关闭何种类型中断 HAL_NVIC_EnableIRQ(USART1_IRQn); //使能 USART1 中断通道 HAL_NVIC_SetPriority(USART1_IRQn,3,3); //抢占优先级 3,子优先级 3 __HAL_UART_ENABLE_IT(huart,UART_IT_RXNE); //开启接收完成中断 __HAL_UART_DISABLE_IT(huart,UART_IT_RXNE); //关闭接收完成中断 编写中断服务函数 USART1_IRQHandler 以上函数在 stm32f1xx_hal_it.c 定义, 其引用了实际的中断服务函数HAL_UART_IRQHandler(), 该函数非弱函数 用户可以注销 stm32f1xx_hal_it.c 中的定义,自己在自己定义的usart.c 中处理 如使用系统默认的, 需要在不同中端回调函数处理中断 如果不使用系统默认中断服务函数,需要自己定义 中断回调函数 例如: 接收完成回调 HAL_UART_RxCpltCallback() 该函数定义的时候是__weak ,可以直接重写,无需去原文件注销 开启中断 用__HAL_UART_ENABLE_IT() ### printf uart1 为例子 引入 stdio.h 使用printf处也需#include "stdio.h" 重写_write()函数,正对arm-gcc编译器 ```C int _write(int fd, char *ch, int len) { HAL_UART_Transmit(&huart2, (uint8_t*)ch, len, 0xFFFF); return len; } ``` 在mdk下的printf函数重定向:只需要重写fputc ```C #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif /* __GNUC__ */ ``` ### 调用 extern UART_HandleTypeDef huart2; MX_USART1_UART_Init(115200); MX_USART2_UART_Init(115200); MX_USART3_UART_Init(115200); ### 测试发送 注意后面要有 \n, 否则可能需要等到换行或缓冲区满后一起输出,不能及时看到数据 uint8_t Data[] = "HELLO WORLD !\n"; //参数1:使用的串口,2:要发送的数据,3:数据大小,4:发送的超时时间 HAL_UART_Transmit(&huart1,Data,sizeof(Data),1000); ### 测试接收 https://blog.csdn.net/qq_42652838/article/details/107776265?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-107776265-blog-120872001.235%5Ev36%5Epc_relevant_default_base3&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-107776265-blog-120872001.235%5Ev36%5Epc_relevant_default_base3&utm_relevant_index=1 中断类型及开启方法 开启中断 __HAL_UART_ENABLE_IT(huart,UART_IT_RXNE); //开启接收完成中断 __HAL_UART_DISABLE_IT(huart,UART_IT_RXNE); //关闭接收完成中断 在中断函数中直接判断 ```C void USART3_IRQHandler(void){ uint8_t temp; if(huart3.Instance->SR & UART_FLAG_RXNE){ Rx_uart3_signal=2; temp=huart3.Instance->DR; Rx_uart3_buf[Rx_uart3_cnt++]=temp; } else if(huart3.Instance->SR & UART_FLAG_IDLE){ temp=huart3.Instance->DR; Rx_uart3_signal=0; } } ``` 五种回调函数 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数 void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);//发送完成过半 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数 void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//接收完成过半 void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//错误处理回调函数 main 函数实例 ```C while(1) { if(USART_RX_STA&0x8000) // 接收区是否有数据 { len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度 printf("\r\n 您发送的消息为:\r\n"); HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART_RX_BUF,len,1000); //发送接收到的数据 while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET); //等待发送结束 printf("\r\n\r\n");//插入换行 USART_RX_STA=0; }else { times++; if(times%5000==0) { printf("\r\nALIENTEK 战舰 STM32 开发板 串口实验\r\n"); printf("正点原子@ALIENTEK\r\n\r\n\r\n"); } if(times%200==0)printf("请输入数据,以回车键结束\r\n"); if(times%30==0)LED0=!LED0;//闪烁 LED,提示系统正在运行. delay_ms(10); } } ``` ### 测试DMA 半满中断 ### 接收发送 轮询模式要采用轮询模式接收, 中断模式要采用中断模式接收,DMA同理 HAL_UART_Transmit();串口发送数据,使用超时管理机制 HAL_UART_Receive();串口接收数据,使用超时管理机制 HAL_UART_Transmit_IT();串口中断模式发送 HAL_UART_Receive_IT();串口中断模式接收 HAL_UART_Transmit_DMA();串口DMA模式发送 HAL_UART_Transmit_DMA();串口DMA模式接收 HAL_UART_GetState(); 串口状态查询函数, 判断UART的接收是否结束,或者发送数据是否忙碌 代替方法 (&huart1)->gState == HAL_UART_STATE_READY #### 轮询 发送 ```c // main uint8_t Data[] = "HELLO WORLD !\n"; uint8_t Cmd[] = "\x23\x00\x00\x80\xA8\x00\x81\x01"; HAL_UART_Transmit(&huart2,(uint8_t*)Data,len,1000); // 需要超时参数 ``` 接收 -- 一般用于指定长度 ```c // main uint8_t Rxbuf[4] ={0}; while (1) { if ( HAL_UART_Receive(&huart2, Rxbuf, sizeof(Rxbuf), 1000) == HAL_OK) { HAL_UART_Transmit(&huart2, Rxbuf,sizeof(Rxbuf),100); } } ``` #### 中断模式 main while循环之外 开启中断接收 HAL_UART_Receive_IT(&huart2, Rxbuf, sizeof(Rxbuf)); 重写函数 ```c void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance==USART2) { HAL_UART_Transmit_IT(&huart2, ACKbuf, sizeof(ACKbuf)); HAL_UART_Receive_IT(&huart2, Rxbuf, sizeof(Rxbuf)); // 重新使用串口接收中断 } } ``` #### 处理空闲中断 中断里面增加用户自定义中断处理 ****_it.h ```C void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART1_IRQn 1 */ USER_UART_IRQHandler(&huart2); //新添加的函数,用来处理串口空闲中断 /* USER CODE END USART1_IRQn 1 */ } ``` 自定义中断处理函数 ```C void USER_UART_IRQHandler(UART_HandleTypeDef *huart) { if(USART2 == huart2.Instance) //判断是否是串口1 { if(RESET != __HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) //判断是否是空闲中断 { __HAL_UART_CLEAR_IDLEFLAG(&huart2); //清楚空闲中断标志(否则会一直不断进入中断) USER_UART_IDLECallback(huart); //调用中断处理函数 } } } ``` 最终处理方法 ```C void USAR_UART_IDLECallback(UART_HandleTypeDef *huart) { HAL_UART_DMAStop(&huart2); //停止本次DMA传输 uint8_t data_length = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx); //计算接收到的数据长度 HAL_UART_Transmit_DMA(&huart2,receive_buff,data_length); //DMA发送函数:将接收到的数据打印出去 data_length = 0; } ``` #### DMA方式 HAL_UART_Transmit_DMA();串口DMA模式发送 HAL_UART_Transmit_DMA();串口DMA模式接收 HAL_UART_Receive_DMA(&huart2, Rxbuf, sizeof(Rxbuf)); // 不需要将接收函数放到 whilel里面,自动在后台干活 UART一旦开启DMA之后,DMA收发中断都是强制开启的,所以DMA收发函数也可以编写回调函数。 ```c void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance==USART2) { HAL_UART_Transmit_DMA(&huart2, ACKbuf, sizeof(ACKbuf)); // HAL_UART_Receive_IT(&huart2, Rxbuf, sizeof(Rxbuf)); // 重新使用串口接收中断 } } ``` ### DMA 回调函数 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数 void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);/发送一半回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数 void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//接收一半回调函数 void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//传输出错回调函数