stm32基于HAL库的串口UART中断接收不定长数据代码实现
总体分析:代码使用的串口USART1,GPIO的复用引脚分别是:PA9复用为RX引脚;PA10复用为TX引脚。数据接收标志符为"\r\n"即回车按键按下,当接收到\r\n时接收停止。接收数据缓冲区只能容纳一个字节数据,设置的接收一个字节数据产生一次接收中断,在中断回调函数中进行串口接收协议的编写。
代码现象:将接受的数据重新发送至串口进行回显。
代码实现
uart.h
#ifndef __USART_H
#define __USART_H
#include "stdio.h"
#include "./SYSTEM/sys/sys.h"
#define USART_X USART1
#define DR_DATA USART_X->DR
#define USART_GPIO GPIOA
#define USART_RX GPIO_PIN_10
#define USART_TX GPIO_PIN_9
#define USART_CLK_ENABLE() do{__HAL_RCC_USART1_CLK_ENABLE();}while(0)
#define USART_RX_CLK_ENABLE() do{__HAL_RCC_GPIOA_CLK_ENABLE();}while(0)
#define USART_TX_CLK_ENABLE() do{__HAL_RCC_GPIOA_CLK_ENABLE();}while(0)
#define RX_IT_EN 1
#define RX_BUFFER_SIZE 1
#define SX_BUFFER_SIZE 10
#define USART_IRQHandler USART1_IRQHandler
#define USART_IRQn USART1_IRQn
extern UART_HandleTypeDef UART_InitStructure;
extern uint8_t UART_RX_Buffer[];
extern uint8_t UART_SX_BUFFER[];
extern uint8_t Index;
extern uint8_t RX_Flag;
extern uint8_t End_Flag;
extern uint8_t EnterLine_Flag;
void Clear_Buffer(void);
void UART_Config(uint32_t baudrate);
void Show_Back(void);
#endif
uart.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
UART_HandleTypeDef UART_InitStructure;
uint8_t Index = 0;
uint8_t UART_RX_Buffer[RX_BUFFER_SIZE];
uint8_t UART_SX_BUFFER[SX_BUFFER_SIZE];
uint8_t RX_Flag = 0;
uint8_t EnterLine_Flag = 0;
uint8_t End_Flag = 0;
//缓冲区元素清0
void Clear_Buffer(void)
{
uint8_t i;
UART_RX_Buffer[0]=0;
for(i =0;i<SX_BUFFER_SIZE;i++)
{
UART_SX_BUFFER[i] = 0;
}
}
void UART_Config(uint32_t baudrate)
{
UART_InitStructure.Instance=USART1;
UART_InitStructure.Init.BaudRate=baudrate;
UART_InitStructure.Init.HwFlowCtl=UART_HWCONTROL_NONE;
UART_InitStructure.Init.Mode=UART_MODE_TX_RX;
UART_InitStructure.Init.Parity=UART_PARITY_NONE;
UART_InitStructure.Init.StopBits=UART_STOPBITS_1;
UART_InitStructure.Init.WordLength=UART_WORDLENGTH_8B;
HAL_UART_Init(&UART_InitStructure);
//开启串口异步接收中断,设置缓冲区大小。
HAL_UART_Receive_IT(&UART_InitStructure,(uint8_t *)UART_RX_Buffer,RX_BUFFER_SIZE);
Clear_Buffer();
}
//这个函数才是真正初始化串口的一些接口的函数,这个函数在HAL_UART_Init中进行调用
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
//开启USART1,RX,TX时钟
USART_CLK_ENABLE();
USART_RX_CLK_ENABLE();
USART_TX_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStructure;
//接收RX,复用成串口输入,看103产品手册;
GPIO_InitStructure.Mode=GPIO_MODE_AF_INPUT;
GPIO_InitStructure.Pin=USART_RX;
//串口发送数据的时序图中空闲时刻输入线是高电平,所以初始化为上拉
GPIO_InitStructure.Pull=GPIO_PULLUP;
GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(USART_GPIO,&GPIO_InitStructure);
//发送TX,复用为串口输出
GPIO_InitStructure.Mode=GPIO_MODE_AF_PP;
GPIO_InitStructure.Pin=USART_TX;
HAL_GPIO_Init(USART_GPIO,&GPIO_InitStructure);
//配置接收中断,串口中断要先设置优先级和使能中断
#if RX_IT_EN
HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
#endif
}
}
//在硬件中断入口函数调用hal公共中断函数
void USART_IRQHandler(void)
{
//在这个中断函数中会失能串口数据接收中断,所以如果希望在中断发生后继续产生中断,需要在调用该函数之后再次使能接收中断。
HAL_UART_IRQHandler(&UART_InitStructure);
//再次开启串口接收中断
HAL_UART_Receive_IT(&UART_InitStructure,(uint8_t *)UART_RX_Buffer,RX_BUFFER_SIZE);
}
//接收中断回调处理函数
//输入不定长的数据,设置结束标志位是"\r\n",\r对应0x0D;\n对应0x0A
//实际上这个回调函数只会在接收一个字节时调用一次
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//判断是不是当前选中的串口,进行数据协议编写
if(huart->Instance == USART1)
{
//进入串口中断将接收标志置1
RX_Flag=1;
//判断当前接收缓冲区中的字节是不是结束标志符"\r"
if((UART_RX_Buffer[0] == 0x0D))
{
//如果是\r就将回车标志位置1,等待下一个字符被接收
EnterLine_Flag = 1;
}
//"\r"之后的第一个字符传入进行判断
else if(EnterLine_Flag == 1)
{
//如果是\n,说明数据接收的完成,置标志位End_Flag为1
if(UART_RX_Buffer[0] == 0x0A)
{
End_Flag = 1;
}
//如果不是\n 说明数据接收并未完成,将该数据放入回显缓冲区内
else
{
UART_SX_BUFFER[Index] = UART_RX_Buffer[0];
Index++;
}
}
//接收数据未收到\r情况下进入这个条件判断语句中
else if(EnterLine_Flag != 1)
{
UART_SX_BUFFER[Index] = UART_RX_Buffer[0];
Index++;
}
}
/*使用位来标志*/
}
//接收回显数据函数
void Show_Back(void)
{
//通过标志位判断是否接收到数据并且接收数据操作已经完成
if((RX_Flag == 1) && (End_Flag == 1))
{
//通过hal库函数将数据发送至串口
HAL_UART_Transmit(&UART_InitStructure,(uint8_t *)UART_SX_BUFFER,Index,1000);
//等待串口发送完毕
while(__HAL_UART_GET_FLAG(&UART_InitStructure,UART_FLAG_TC) != 1);
//换行
printf("\r\n");
}
//索引、标志位清零
Index = 0;
EnterLine_Flag = 0;
End_Flag = 0;
RX_Flag =0;
}
注意如果要将数据打印到串口,还需要将printf函数进行重定向。下面的代码是死的,只需要复制到工程内就可以实现数据打印到串口。
///* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while ((USART_X->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART_X->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
main.c
在轮训过程中不建议一直读取串口接收的数据,建议在没有数据被接受时执行其他的代码
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "exti.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
//led_init(); /* LED初始化 */
LED_Init();
UART_Config(115200);
printf("11111111\r\n");
while(1)
{
if((RX_Flag == 1) && (End_Flag == 1))
{
Show_Back();
}
else
{
LED0_TOGGLE;
LED1_TOGGLE;
HAL_Delay(100);
LED0_TOGGLE;
LED1_TOGGLE;
HAL_Delay(100);
}
}
}
代码现象
注意箭头位置输入了回车按键
作者:ls20010901