文章目录
前言
串口的发送包括:
- 阻塞式的发送
- 中断发送
- DMA发送
串口的接收包括:
- 扫描接收
- 中断接收
- DMA接收
一、阻塞发送函数:HAL_UART_Transmit
1、函数原型:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数:
huart: 指向uart _ handletypedef结构的huart指针,该结构包含指定uart模块的配置信息。
PData: 指向数据缓冲区的pData指针(U8或u16数据元素)。
Size: 要发送的数据元素( u8或U16 )的大小
Timeout:超时持续时间,单位ms,0就是0ms超时,数据最大发送时间,超过则返回异常
//返回:
//HAL 状态
typedef enum
{
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
} HAL_StatusTypeDef;
举例:
HAL_UART_Transmit(&huart1,"ok\r\n",strlen("ok\r\n"),0xFFFF);
2、Timeout的意义
这里先搞懂什么是波特率:
波特率:发送二进制数据位的速率,习惯上用 baud 表示,即我们发送一位二进制数据的持续时间=1/baud。
如果波特率为9600,发送一个位需要1/9600s=0.0001042s=0.1042ms,这里按数据位为8位,停止位为2位,加起来就是10位,10个位发送所需的时间为:0.104210ms = 1.042ms,如果我要发送10个字节的数据,那发送这10个字节数据给接收方需要的时间为:101.042ms = 10.42ms。
这是算实际的发送10个字节的数据所需要的时间。我们在接收方接收数据时可以把时间再加宽一些,让它有一点余量。让接收方能稳定的把数据从发送方接手过来,可以加个5ms,或更宽一点10ms,加上发送10个字节所花的时间,就是15ms或20ms。
3、注意的问题
- 在HAL_UART_RxCpltCallback函数中不可使用会发送乱码。
二、串口扫描接收:HAL_UART_Receive
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数:
huart: 指向uart _ handletypedef结构的huart指针,该结构包含指定uart模块的配置信息。
pData:指向数据缓冲区的指针( u8或U16数据元素)。
Size: 要接收的数据元素数量( u8或U16 )。
Timeout:超时持续时间,单位ms,0就是0ms超时,数据接收最大等待时间,超出则异常
HAL 状态
typedef enum
{
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
} HAL_StatusTypeDef;
例如:
uint8_t data=0;
while (1)
{
if(HAL_UART_Receive(&huart1,&data,1,0)==HAL_OK){
}
}
代码实现:
uint8_t data=0;
while (1)
{
//串口接收数据
if(HAL_UART_Receive(&huart1,&data,1,0)==HAL_OK){
//将接收的数据发送
HAL_UART_Transmit(&huart1,&data,1,0);
}
}
其中timeout为0表示没有延时,所以串口接收函数是不阻塞的,while循环将一直轮询。
加个延时函数
这样一来的话,接收数据就异常了,会接收数据不全,所以这样是不可靠的
那改成这样呢?
uint8_t data[100]={0};
if(HAL_UART_Receive(&huart1,data,100,1000)==HAL_OK){
}
接收100个数据,等待时间为1秒,这样的话接收区没满时,每次运行这条语句都要延时等待1S,这时相当于一个HAL_Dealy(1000),这会阻塞while循环。只有数据接收到刚刚等于100才能返回HAL_OK,所以不能用于接收变成数据,这是不理想的。
三、中断发送函数:HAL_UART_Transmit
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
在main函数中运行
while (1)
{
HAL_UART_Transmit_IT(&huart1, "OK\r\n", 4);
HAL_Delay(500);
}
则在中断正确发送完数据后会进入中断响应函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
}
此时会处理两个小灯会反转颜色,所为成功发送的标志
四、串口中断接收:HAL_UART_Receive_IT
打开中断需要调用以下的函数:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
例如:
HAL_UART_Receive_IT(&huart1, buffer, 1);
当接受数据完整或者出现错误会调用以下的callback函数。
//发生溢出中断或DMA中断时
uint8_t Rx_HAL_Buff_Temp[2]; //HAL函数接收临时缓存
uint8_t Rx_Buff[100]; //接收帧缓存,自己定义大小
uint8_t RxCounter = 0; //接收计数器
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
huart->ErrorCode = HAL_UART_ERROR_NONE;
HAL_UART_Receive_IT(&huart6,Rx_HAL_Buff_Temp,1);
}
//接收到1字节数据,回调函数如下
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART6)
{
Rx_Buff[[RxCounter++] = Rx_HAL_Buff_Temp[0];
//加上自己的代码
HAL_UART_Receive_IT(&huart6,Rx_HAL_Buff_Temp,1);
}
}
串口中断的接收流程:
USART1_IRQHandler(void)(中断服务函数)
-> HAL_UART_IRQHandler(UART_HandleTypeDef *huart)(中断处理函数)
-> UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数)
-> HAL_UART_RxCpltCallback(huart);(中断回调函数)
五、串口DMA接收数据
1、cubemx配置
串口配置成全局中断模式
DMA的RX请求设置为循环模式,TX请求设置为正常。
2、程序配置
当DMA串口接收开始后,DMA通道会不断的将发送来的数据转移到内存,那该如何判断串口接收是否完成从而及时关闭DMA通道?如何知道接收到数据的长度?答案便是使用串口空闲中断。
思路流程:
1、开启串口DMA接收
2、串口收到数据,DMA不断传输数据到内存
3、一帧数据发送完毕,串口暂时空闲,触发串口空闲中断
4、在中断服务函数中,可以计算刚才收到了多少个字节的数据
5、存储接收到的数据,清除标志位,开始下一帧接收
串口设置:
volatile uint8_t rx_len=0;
volatile uint8_t recv_end_flag=0;
uint8_t rx_buffer[200];
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
//上面的usart配置代码为cubemx自动生成的,在下方添加使能idle中断和打开串口DMA接收语句
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//使能idle中断
HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//打开DMA接收,数据存入rx_buffer数组中。
}
只有加上了:
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//使能idle中断
才会进入到串口中断函数,接下来修改串口中断函数:
void USART1_IRQHandler(void)
{
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmp_flag != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
temp = huart1.Instance->SR; //清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能
temp = huart1.Instance->DR; //读取数据寄存器中的数据
HAL_UART_DMAStop(&huart1); //
temp = hdma_usart1_rx.Instance->CNDTR;// 获取DMA中未传输的数据个数,NDTR寄存器分析见下面
rx_len = BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
recv_end_flag = 1; // 接受完成标志位置1
}
HAL_UART_IRQHandler(&huart1);
}
主程序:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
while (1)
{
if(recv_end_flag ==1)
{
printf("rx_len=%d\r\n",rx_len);//打印接收长度
HAL_UART_Transmit(&huart1,rx_buffer, rx_len,200);接收数据打印出来
for(uint8_t i=0;i<rx_len;i++)
{
rx_buffer[i]=0;//清接收缓存
}
rx_len=0;//清除计数
recv_end_flag=0;//清除接收结束标志位
}
HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//重新打开DMA接收
}
}
六、串口DMA发送
1、在main.c中添加
uint8_t Senbuff[] = "fsdfdsfdsfsdfsd \r\n";
2、在while中添加
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
HAL_Delay(1000);
}
总结需要注意的点
标准库中打开串口的函数:
USART_Cmd(USART1, ENABLE);
标准库中打开中断的函数:需要单独设置被打开的中断的类型:
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
在HAL中,打开串口和中断合并成了一个函数:发送就打开发送中断,接收就打开接收中断:
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
标准库和HAL库中都有阻塞式发送接收和非阻塞式发送接收(中断或DMA)
标准库中阻塞式发送和接收函数:
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
HAL库中阻塞式发送和接收
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
阻塞式发送和接收在使用上都没有太大的差别,发送就等着发送完,HAL中阻塞式发送函数的第4个参数Timeout,可以设置一个超时时间,超时后没发完就不再阻塞。
接收没有差别。超时时间根据systic的计数频率和计数值确定
中断式发送和接收
先说接收
标准库中接收的话需要自己在中断函数里面判断中断类型,清标志位在HAL中void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);这个库函数帮我们完成了中断类型判断和清除标志位,我们只需要在具体的函数中写逻辑即可。
上面这个库函数判断出不同的类型,然后调用不同的回调函数,我们处理接收中断回调函数HAL_UART_TxCpltCallback即可。
全部回调函数如下:
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);
发送和接收中断的一些问题:
HAL_UART_Transmit_IT(huart11,datass,2 );
HAL_UART_Receive_IT(&huart1,recivedatass,3);
关于中断式发送和接收,可以看到这两个函数的第3个参数,是设置要接收多少个、发送多少个字节。发送的中断就不用说了,有个全部发送完成和发送一半。接收的话,每接收到一个字节就会产生一个接收中断,但是我们可能需要接收完3个字节的数据才想处理一次。HAL_UART_IRQHandler内部做了判断,当接收到3个字节后才会调用一个回调函数 HAL_UART_RxCpltCallback