与I2C、SPI一样,UART是一种通信协议,设备之间依靠Rx(Receive)与Tx(Transmit)两条线进行数据传输。一个单片机通常内置有多个UART,而这些UART通常都与单片机上的USB接口连接在一起,因此只需要将单片机通过数据线与电脑相连,即可实现单片机与电脑的UART串口通信。

下面我们就来看看如何实现串口通信功能。首先配置好Cube中有关UART的设置:

cf50cc20e9044a3c8d7a16bf2cd39fdc.png

首先选择USART1/2/3/UART4中的任意一个(USART与UART的区别是USART可以进行同步通信,UART只能进行异步通信),将Mode设置为Asynchronous(异步),将Baud Rate(波特率)设置为9600。同步是指通信设备之间使用同一个时钟信号传输数据,而异步是指使用约定的波特率(即数据传输速率)进行数据传输与接收。 

打开中断:

db43fb81d4704e09833fbcf1fa5fa3cd.png

在这里中断是用于接收。我们只能控制发送的时间,却不能控制接收的时间,因为不知道对方什么时候会发送数据,因此我们使用接收中断,当接收到数据时进入中断函数进行处理。

首先我们来看看如何用单片机向电脑发送信息,这需要用到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的文字了:

6effc32fb88a425681a3ce3152d3d545.png

下面我们来编写接收的程序。和其他中断一样,我们首先要在初始化中开启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接收中断回调函数(函数名和形参不能修改!!!可按下图进行查找)

bf1cc36c9ecf473cae40434b533ca3dd.png9125871a491a48cb9d30e20d52db0b82.png

513ebdceaaa0413b846ba700346e09fd.png

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中断函数:

714f5bf1768740ca808007c1bc98ed14.png

题目要求可以通过串口修改密码,修改的格式为当前密码-新密码(共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();
    }
}

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐