参考文件
预期实现的功能
通过XCOM连接电脑(上位机)和开发板。
1.当上位机向开发板发送“R0”时,熄灭开发板上的红色LED。
2.当上位机向开发板发送“R1”时,点亮开发板上的红色LED。
3.当上位机向开发板发送“G0”时,熄灭开发板上的绿色LED。
4.当上位机向开发板发送“G1”时,点亮开发板上的绿色LED。
开发板成功接收到数据后,向上位机发送“Wilco”表明上一次发送的数据被成功接收。
CubeMX初始化
注意:不要忘记在NVIC中启用USART1全局中断,因为一会儿要用的是串口的中断模式
HAL库部分
启用USART后,CubeMX会自动按照之前设置的数据生成usart.c和usart.h文件。我们主要会使用这些函数:
HAL_UART_Transmit_IT()
该函数在HAL中的声明如下:
HAL_StatusTypeDef HAL_UART_Transmit_IT (UART_HandleTypeDef * huart, const uint8_t * pData,
uint16_t Size)
其作用为在非阻塞模式下发送一定量的数据。其中,
- huart:指向UART_HandleTypeDef结构体的指针(UART_HandleTypeDef一般在usart.c的头部声明),用于指定是哪个UART模块工作
- pData:指向数据缓冲区(data buffer)的指针
- size:数据的大小
该函数的返回值为:HAL:status
HAL_UART_Receive_IT()
该函数在HAL中的声明如下:
HAL_StatusTypeDef HAL_UART_Receive_IT (UART_HandleTypeDef * huart, const uint8_t * pData,
uint16_t Size)
其作用为在非阻塞模式下接收一定量的数据。其中,
- huart:指向UART_HandleTypeDef结构体的指针(UART_HandleTypeDef一般在usart.c的头部声明),用于指定是哪个UART模块工作
- pData:指向数据缓冲区(data buffer)的指针
- size:将要接收数据的大小
该函数的返回值为:HAL:status
HAL_UART_IRQHandler()
该函数负责处理UART中断请求。通常来讲这一部分CubeMX会帮我们自动配置好,我们不需要进行额外的操作。该函数的声明位于stm32f4xx_hal_uart.c中。
HAL_UART_RxCpltCallback()
该函数在HAL中的声明如下:
void HAL_UART_RxCpltCallback (UART_HandleTypeDef * huart)
其中,
- huart:指向UART_HandleTypeDef结构体的指针(UART_HandleTypeDef一般在usart.c的头部声明),用于指定是哪个UART模块工作
这也就是UART中断中的接收回调函数。当USART进行一次中断模式下的接收操作后,HAL_UART_IRQHandler会自动调用回调函数并运行其中的内容,这一部分和之前外部中断EXTI的执行逻辑是相似的。主要的点灯代码也是在这一部分编写。
HAL中关于UART的函数还有不少,比如HAL_UART_TxCpltCallback(发送中断调用)和HAL_UART_RxHalfCpltCallback(接收一半时中断调用)等等。具体的可以自己看手册。
USART.c部分
USART.c中包含CubeMX已经为我们配置好的内容,包括MX_USART1_UART_Init(串口初始化函数)等。这一部分不需要我们修改,如果要改某个参数直接在CubeMX里改然后重新生成一遍工程即可。我们需要做的只有重写一遍fget和fput函数,把一些要用的库函数重新定向到huart1,确保打印串口的时候不会出问题。记得在头文件里引用<stdio.h>。
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
main.c部分
我们挨个来处理要做的事情。
首先,定义一个用于接收数据的接收缓冲区,和一个用于回发确认信息的发送缓冲区。接收数据按照我们的功能预期只会是R0、R1、G0、G1中的一种,都是2个字节,因此我们直接定义接收缓冲区数组的长度为2。
uint8_t RxBuffer[2];
uint8_t TxBuffer[] = "Wilco";
然后,在int main()中调用一次中断模式接收函数:
HAL_UART_Receive_IT(&huart1,RxBuffer,2);
这表明我们即将启用USART1来接收数据并将其存放在RxBuffer中,长度为2字节。当MCU跑这条指令时将会进入到中断环节,由HAL_UART_IRQHandler() 请求中断并在中断中执行串口接收,接收完成后,回调HAL_UART_RxCpltCallback() 执行回调函数中的内容。
需要注意的是,HAL_UART_RxCpltCallback() 是一个弱定义函数。这个函数在stm32f4xx_hal_uart.c的2622行已经以__weak的方式被声明了。如果用户需要调用这个函数,则需要在main.c或是usart.c里重新声明该函数。不要直接在stm32f4xx_hal_uart.c里修改这个函数。
回调函数中的内容如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) //确认串口为USART1
{
HAL_UART_Transmit_IT(&huart1,(uint8_t *)TxBuffer,sizeof(TxBuffer)); //发送Wilco表明已收到数据
GPIO_PinState state = GPIO_PIN_SET; //定义引脚状态
if (RxBuffer[1] == '1') //如果接收到的数据第二位为‘1’
{
state = GPIO_PIN_RESET; //下拉引脚电平(即点亮LED)
}
if (RxBuffer[0] == 'R') //如果接收到的数据第一位为‘R’
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,state); //点亮红色LED
}
else if (RxBuffer[0] == 'G') //如果接收到的数据第一位为‘G’
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,state); //点亮绿色LED
}
HAL_UART_Receive_IT(&huart1,RxBuffer,2); //再次启用接收
}
}
最好在HAL_UART_Transmit_IT()中强制将TxBuffer声明成uint8_t格式,否则可能会出奇奇怪怪的问题。
注意最后的 HAL_UART_Receive_IT(&huart1,RxBuffer,2) ,回调函数的末尾必须包括这行指令,表明第一次进入中断并回调结束后,串口还会继续停留在中断接收状态以准备接受下一条来自上位机的数据。否则UART只会接收一次然后退出中断,无法重复响应。
在回调函数中还可以加一些其他的内容,比如加入计数器来判断接收到的数据是否溢出,加入人工标志位来检测数据是否有错误等等。