与I2C、SPI一样,UART是一种通信协议,设备之间依靠Rx(Receive)与Tx(Transmit)两条线进行数据传输。一个单片机通常内置有多个UART,而这些UART通常都与单片机上的USB接口连接在一起,因此只需要将单片机通过数据线与电脑相连,即可实现单片机与电脑的UART串口通信。
下面我们就来看看如何实现串口通信功能。首先配置好Cube中有关UART的设置:
首先选择USART1/2/3/UART4中的任意一个(USART与UART的区别是USART可以进行同步通信,UART只能进行异步通信),将Mode设置为Asynchronous(异步),将Baud Rate(波特率)设置为9600。同步是指通信设备之间使用同一个时钟信号传输数据,而异步是指使用约定的波特率(即数据传输速率)进行数据传输与接收。
打开中断:
在这里中断是用于接收。我们只能控制发送的时间,却不能控制接收的时间,因为不知道对方什么时候会发送数据,因此我们使用接收中断,当接收到数据时进入中断函数进行处理。
首先我们来看看如何用单片机向电脑发送信息,这需要用到HAL库的UART发送函数:
/**
* @brief Send an amount of data in blocking mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the sent data is handled as a set of u16. In this case, Size must indicate the number
* of u16 provided through pData.
* @note When FIFO mode is enabled, writing a data in the TDR register adds one
* data to the TXFIFO. Write operations to the TDR register are performed
* when TXFNF flag is set. From hardware perspective, TXFNF flag and
* TXE are mapped on the same bit-field.
* @param huart UART handle.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be sent.
* @param Timeout Timeout duration.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
与LCD显示一样,这个函数用于发送一个字符串。我们同样可以通过sprintf函数为字符串赋值:
char str[40];
int year = 2024;
sprintf(str, "%d lanqiao\r\n", year); //\r\n表示换行
HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), 50); //50为超时时间,通常可以设定一个很大的值
打开选手资源数据包中的串口助手“ComAssistant”,将波特率设定为9600,选择对应的端口(不知道是哪个就一个一个试,不对的会提示无法打开),然后点击打开串口,运行程序,就可以在串口助手上看到2024 lanqiao的文字了:
下面我们来编写接收的程序。和其他中断一样,我们首先要在初始化中开启UART接收中断:
uint8_t rx;
HAL_UART_Receive_IT(&huart1, &rx, 1); //1表示每次接收一个字节
HAL_UART_Receive_IT函数定义如下:
/**
* @brief Receive an amount of data in interrupt mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the received data is handled as a set of u16. In this case, Size must indicate the number
* of u16 available through pData.
* @param huart UART handle.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be received.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
同样,要编写UART接收中断回调函数(函数名和形参不能修改!!!可按下图进行查找):
void HAL_UART_RxCpltCallback(UART_HandleTypedef *huart) //UART接收完成(Cplt(Complete))中断
{
if (huart->Instance == USART1) //USART1触发的中断
{
HAL_UART_Receive_IT(&huart1, &rx, 1); //由于此中断回调函数会关闭中断,因此要重新打开
}
}
想要验证程序的正确与否,只需要将接收到的数据通过串口再发送出去,或使用LCD显示出来即可。
通常,由于不同设备向单片机发送的数据不同、格式不同,有的可能还会含有帧头、帧尾等防错位措施,UART接收需要根据实际需求进行编写。下面我们以第十三届省赛题为例,讲讲如何根据实际情况书写UART中断函数:
题目要求可以通过串口修改密码,修改的格式为当前密码-新密码(共7个字符),且当前密码需要输入正确。这里隐含了两个判断条件:格式要求正确,如123-789;当前密码要求正确。只有满足这两个条件,才进行下一步处理——修改密码。
char pswd[3] = {'1','2','3'};
char rx_data[100];
int rx_data_i = 0; //用于判断接收位数
bool flag = 0; //用于判断格式与密码是否正确
void HAL_UART_RxCpltCallback(UART_HandleTypedef *huart)
{
if (huart->Instance == USART1)
{
rx_data[rx_data_i++] = rx; //将接收到的数据放进数组中,方便后续判断
HAL_UART_Receive_IT(&huart1, &rx, 1);
}
}
void change_pswd(void)
{
//第一个判断条件:输入字符恰为7个
if (rx_data_i == 7)
{
//第二个判断条件:输入格式正确,为3个数字-3个数字
for (int i=0; i<3; i++)
{
if ((rx_data[i]>'0'&&rx_data[i]<'9') && (rx_data[i+4]>'0'||rx_data[i+4]<'9') && rx_data[3]=='-') flag = 1;
else
{
flag = 0;
break;
}
}
if (flag)
{
//第三个判断条件:当前密码正确
for (int i=0; i<3; i++)
{
if (rx_data[i] == pswd[i]) flag = 1;
else
{
flag = 0;
break;
}
}
}
//前三个判断条件均成立,才能走到这一步
if (flag)
{
for (int i=0; i<3; i++) pswd[i] = rx_data[i+4]; //修改密码
}
}
rx_data_i = 0; //判断接收位数的变量清零
memset(rx_data, 0, 100); //存储接收数据的数组清零,需要调用string.h
flag = 0; //判断格式与密码的标志位清零
}
在主循环中,需要判断是否接收到,且接收是否完成,才能进行修改密码的操作:
while (1)
{
if (rx_data_i != 0)
{
int temp = rx_data_i;
HAL_Delay(1); //延时1ms,若仍在进入中断,rx_data_i会改变,说明接收未完成
if (temp == rx_data_i) change_pswd();
}
}