本系列文章由江山(csdn名:补不补布)(github:jianggogogo)自己写成,当中用到引用时都已经标记出来,如果出现版权问题,请直接联系我修改。当然,技术在于分享,欢迎大家转载,不过请注明出处。最后,如果出现有错误的地方欢迎大家指正。
HAL库是ST新推出的官方库,该库同图形化开发工具CUBEMX可以结合使用,从而方便开发者的快速操作。
但是在实际的使用中,也会存在一些问题。不过,看着ST的趋势似乎是要将该库作为主流来发展,大有淘汰原来的库文件的趋势。我们也应该多看看。
当然在博客里面有很多文章写的很好,不过系统的文档、源码资料大家可以去原子或者野火的论坛找,那里资料很全:
正点原子的HAL例程
首先,串口中断处理函数,这个函数是我们在cube里面设置打开串口全局中断之后,就会自动生成的。
在这个函数里面,系统会进行中断的处理和实现:
/**
* @brief 处理串口中断请求
* @param huart 串口信息结构体指针
* @retval None 无返回值
*/
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
下面是很多的中断回调函数,但是我们常用的中断回调函数也就是接受中断:
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);
void HAL_UART_AbortCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_AbortTransmitCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart);
接受中断回调函数:
我们可以看到这是一个弱定义函数,也就是我们需要自己重新写一下这个函数的定义:
注意,回调函数的实现其实也是大多数也在中断中实现,所以我们使用回调的时候也要注意在中断中操作的特殊性:
/**
* @brief 接受回调
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback could be implemented in the user file
*/
}
串口发送函数,分为两种,一个是普通发送,另一个则是中断发送:
区别在于:是否为blocking mode 也就是是否为阻塞模式
关于blocking mode的解释
阻塞模式就是要等到一些通道,但是中断里面不可能去等待什么,必须立即执行,所以采用非阻塞模式。
/**
* @brief 阻塞模式发送数据
*/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
/**
* @brief 非阻塞模式发送数据
*/
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
同样,我们也可以设置为不同的接受模式,中断或者非中断。
注意,我们打开全局中断之后,接受中断并不会自动打开,采用HAL_UART_Receive_IT这个函数可以进入一次接受中断
/**
* @brief 阻塞模式接受信号
*/
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
/**
* @brief 非阻塞模式接收信号
*/
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
注意,我们打开全局中断之后,接受中断并不会自动打开,采用HAL_UART_Receive_IT这个函数可以进入一次接受中断
初始化uart1之后,我们要注意,现在一旦uart1接收到了数据,我们是没有办法接受到的,因为我们没有设置对应的buffer来存储发来的数据
在主函数中开启接受,定义一个自己的buffer,接受数据:
uint8_t Uart_rx_buffer[256] ={0};
//数字一可以改为你想收的数据
HAL_UART_Receive_IT(&huart1,Uart_rx_buffer,1); //将uart的一个数据接受到我们对应的buffer
在stm32_it.c里面会有产生的各种中断函数,我们找到对应的函数添加内容:
添加继续接受的函数和发送的函数:
这里直接将收到的函数发送出去,测试是否可行
注意,这里是为了测试中断是否成功才采用发送函数。建议大家不要在中断中采用打印函数
HAL_UART_Receive_IT(&huart1, (uint8_t *)&uart_1, 1); //再开启接收中断
//注意,这里的100可以改成你想要的长度,来实现定长数据收发
HAL_UART_Transmit(&huart1,uart_1,1,100); //发送接收到的数据
该cubemx配置同 二、串口的收发定长数据
该例程参考了正点原子的串口教程,大家可以去看看:
设置串口数据帧头为:0x0a,帧尾为0x0d。
//注意,读取USARTx->SR能避免莫名其妙的错误
uint8_t USART_RX_BUF[256]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
uint16_t USART_RX_STA=0; //接收状态标记
uint8_t aRxBuffer[256];//HAL库使用的串口接收缓冲
## 3.1、设置一个buffer接受数据:
```c
HAL_UART_Receive_IT(&huart1,USART_RX_BUF,1);
注意,我们打开全局中断之后,接受中断并不会自动打开,采用HAL_UART_Receive_IT这个函数可以进入一次接受中断
HAL_UART_Receive_IT(&huart1,USART_RX_BUF,1);
## 3.3、编写中断函数:
- 注意这一步是关键,在中断中实现对串口消息的帧头帧尾的判断:
```c
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint8_t Res;
HAL_StatusTypeDef err;
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET)) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res=USART1->DR;
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(256-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
HAL_UART_Receive_IT(&huart1,USART_RX_BUF,1); //再开启接收中断
/* USER CODE END USART1_IRQn 1 */
}
如果有数据,就会打印出来。
while (1)
{
/* USER CODE END WHILE */
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
HAL_UART_Transmit(&huart1,(uint8_t*)USART_RX_BUF,len,1000); //发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart1,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");
HAL_Delay(10);
}
/* USER CODE BEGIN 3 */
}
发送0+回车的结果
发送0x0a,0x31,0x32,0x33,0x0d的结果,注意都是十六进制:
i
该cubemx配置同 二、串口的收发定长数据
- 在接受中断中接受数据,然后在空闲中断中实现判断一帧是否完成,空闲中断产生,则一帧结束。
注意,我们打开全局中断之后,接受中断并不会自动打开,采用HAL_UART_Receive_IT这个函数可以进入一次接受中断。
但是,这里我们也可以不采用HAL_UART_Receive_IT进入一次中断,而是直接打开接受中断,然后我们自己清除标志位。
放几个大佬的参考:
用自己的中断函数来替代别人的
编写一个自己的回调函数,处理中断
下面为流程
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//打开空闲中断
//这些都是数据接受状态标志位
#define UART_RX_STATE_START 0
#define UART_RX_STATE_READY 2
#define UART_RX_STATE_DEAL 1
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint8_t clear = clear;
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
uart1RxState = UART_RX_STATE_READY;
uart1RxBuf[uart1RxCounter]=(uint8_t)(huart1.Instance->DR&(uint8_t)0x00FF);
uart1RxCounter++;
}
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=RESET)
{
clear=USART1->SR;
clear=USART1->DR;
uart1RxState = UART_RX_STATE_DEAL;//状态表明一帧数据接收完成了,需要处理。处理完以后再把接收中断打开
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
发送数据后,打印数据长度,结果成功
1
01
123456789864564561456
15
采用DMA会减少CPU的运行压力,这里是推荐大家使用的:
#define Rx_uart1_Max 256
extern uint8_t Rx_uart1_flag;
extern uint16_t Rx_uart1_len;
extern uint8_t Rx_uart1_buf[Rx_uart1_Max];
//在uart初始化或者和main里面使能
/*Enable DMA IRQ*/
HAL_UART_Receive_DMA(&huart1, Rx_uart1_buf, Rx_uart1_Max);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t temp;
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET))
{
/*清除状态寄存器和串口数据寄存器*/
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
/*失能DMA接收*/
HAL_UART_DMAStop(&huart1);
/*读取接收长度,总大小-剩余大小*/
temp = huart1.hdmarx->Instance->CNDTR;
Rx_uart1_len = Rx_uart1_Max - temp;
/*接收标志位置1*/
Rx_uart1_flag=1;
/*使能接收DMA接收*/
HAL_UART_Receive_DMA(&huart1,Rx_uart1_buf,Rx_uart1_Max);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(Rx_uart1_flag) // Receive flag
{
Rx_uart1_flag=0; // clean flag
//这里写处理函数:
HAL_UART_Transmit(&huart1,Rx_uart1_buf,Rx_uart1_len,10);
// deal Rx_uart1_buf
}
else
{
HAL_Delay(10);
}
}
在任意地方调用这一个函数,都可以进入一次接受中断。所以,我们使用这种方法的时候要注意,在接受中断处理完毕之后要再调用一次,从而可以进入下一次中断
HAL_UART_Receive_IT(&huart1,USART_RX_BUF,1);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
链接:https://github.com/jianggogogo/sayhi_stm_drive/tree/master/sayhi_hal_uart
目录 | 内容 |
---|---|
m_uart | 定长数据的接受和发送 |
uart_head_tail | 采用帧头帧尾接受不定长数据 |
uart_idle | 采用空闲中断接受不定长数据 |
uart_dma | 采用dma接受不定长数据 |