自学内容网 自学内容网

【STM32】RTT-Studio中HAL库开发教程八:模拟串口通信

一、简介

  本文详细介绍了RT-Thread实时操作系统,如何通过空闲IO口模拟UART通信。内容涵盖UART协议解析、发送端和接收端的硬件配置、软件实现、定时器及中断处理,以及调试过程。通过IO口模拟UART通信,实现了数据的发送和接收,为无硬件UART接口的场景提供了解决方案。

1.串口协议
UART的通信方式是由1个起始位,8个数据位,包含一个奇偶校验位,和结束位构成
在这里插入图片描述
空闲位:当uart处于空闲状态(线路没有数据传输)时,TX、RX线都处于高电平状态(逻辑“1”),一般需要把相应IO口配置为上拉。
起始位:由高电平跳变为低电平,且持续一个位宽度,表示触发起始信号。
数据位:数据位可以5、6、7或8位,从最低位开始一位接着一位的传送。
校验位:有奇、偶或无校验。
  奇校验:数据位+校验位的“1”的位数总和为奇数;
  偶校验:数据位+校验位的“1”的位数总和为偶数;
  无校验:顾名思义就是没有校验位,数据位后面接停止位。(通常配置为无校验。)
例如:奇校验中,数据位“1”的位数为偶数个,则此时校验位为“1”。
停止位:将数据线拉为高电平,可以设置停止位宽度为1位、1.5位或者2位。
波特率:在串口中波特率为每秒传送的bit数;通信双方必须设置相同的波特率,否则接收的数据为乱码。波特率9600时,传输1bit所需时间为(1/9600)us,大约为104us

2.UART工作原理
UART 为全双工通信,通常需要三条线: TX(发送)、 RX(接收)和 GND(地线)。数据串行一位一位的传送。

发送数据:空闲状态TX处于高电平,将TX拉为低电平,宽度为1bit时间,接着数据按低位到高位依次发送,数据发送完毕后,如果有校验位,则发送奇偶校验位,最后发送停止位,这样一帧数据就发送完成了。(若发送多字节数据时,就连续发送多帧数据即可。)

接收数据:RX线路处于高电平(空闲态)时,当RX检测到下降沿(高电平变为低电平)时,说明线路有数据要传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,如果有校验位,则接收奇偶校验位,最后接收停止位。


二、RTT定时器配置

  本文可以用到三个定时器,分别用来进行数据的接收、数据的发送以及数据接收时的超时时间设置,故此需要进行定时器的相关配置,具体配置如下:

1.启动定时器驱动: 打开RTT中的定时器驱动。

在这里插入图片描述

2.定义所需要的定时器: 主要是打开定时器的宏定义,用STM32CubeMx生成定时器的初始化代码。

在这里插入图片描述
在这里插入图片描述

3.初始化定时器配置: 在board.c中定义void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)这个函数,并将定时器初始化在此定义。

/**
 * @brief 定时器中断初始化
 * @param htim_base
 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
    if (htim_base->Instance == TIM7)
    {
        /* Peripheral clock enable */
        __HAL_RCC_TIM7_CLK_ENABLE();

        /* TIM7 interrupt Init */
        HAL_NVIC_SetPriority(TIM7_IRQn, 2, 1);
        HAL_NVIC_EnableIRQ(TIM7_IRQn);
    }
    else if (htim_base->Instance == TIM8)
    {
        /* Peripheral clock enable */
        __HAL_RCC_TIM8_CLK_ENABLE();

        /* TIM8 interrupt Init */
        HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
    }
    else if (htim_base->Instance == TIM9)
    {
        /* Peripheral clock enable */
        __HAL_RCC_TIM9_CLK_ENABLE();

        /* TIM9 interrupt Init */
        HAL_NVIC_SetPriority(TIM1_BRK_TIM9_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(TIM1_BRK_TIM9_IRQn);
    }
}

三、IO模拟UART通信

1.自定义数据结构体
  自定义发送和接收所使用的全局变量。

typedef enum _TX_STATE_
{
    UART_TX_IDLE = 0,        // 发送空闲
    UART_TX_SEND,            // 数据发送

} TX_STATE;

typedef struct _USART_TX_
{
    uint8_t tx_data_buffer[IO_Uart_Length];// 模拟串口发送数据缓冲区
    uint8_t cur_bit;                       // 当前数据字节位
    volatile uint8_t start_flag;           // 启动位标志位
    TX_STATE tx_state;                     // 发送状态
    uint32_t bound;                        // 波特率
    uint16_t one_bit_time;                 // 发送1bit时间
    uint16_t data_cur;                     // 当前数据
    uint16_t tx_data_len;                  // 发送数据长度

} USART_TX;

typedef struct _USART_RX_
{
    uint8_t rx_data_buffer[IO_Uart_Length];// 模拟串口接收数据缓冲区
    uint8_t RecvData;                      // 接收一字节数据
    uint8_t RecvState;                     // 接收状态
    uint32_t RxDataNum;                    // 接收数据个数,需要手动清零

    uint32_t RxTimeOut;                    // 模拟串口接收超时计数
    uint8_t RxDataOK;                      // 数据接收完成标志
    uint8_t StartRxFlag;                   // 开始接收标志,1开始接收,0不接收

} USART_RX;

USART_TX uart_tx;
USART_RX uart_rx;

2.配置模拟串口IO口
  使能对应IO口的时钟,然后把IO口配置为推挽输出、上拉,如果配置为开漏输出则只能拉低电平,高电平需要外部接上拉电阻,上拉作用为不工作时处于高电平(空闲态)。
  接收IO口配置为输入模式、上拉,并开启外部中断,设置优先级,下降沿触发中断;当空闲态转为接收态的时候就会触发外部中断,在外部中断处理函数中关闭外部中断,并打开接收定时器,等待接收的数据处理完成再次打开外部中断和关闭接收定时器即可。

/**
 * @brief 发送端口初始化
 */
static void Usart_IO_Config(void)
{
    GPIO_InitTypeDef GPIO_Initure;

    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_Initure.Pin   = TX_GPIO_PIN;           // PA8 -- TX
    GPIO_Initure.Mode  = GPIO_MODE_OUTPUT_PP;   // 输出推挽模式
    GPIO_Initure.Pull  = GPIO_PULLUP;           // 上拉
    GPIO_Initure.Speed = GPIO_SPEED_HIGH;
    HAL_GPIO_Init(TX_GPIO_PORT, &GPIO_Initure);

    GPIO_Initure.Pin   = RX_GPIO_PIN;           // PA0 -- RX
    GPIO_Initure.Mode  = GPIO_MODE_IT_FALLING;  // 输入模式下降沿触发
    HAL_GPIO_Init(RX_GPIO_PORT, &GPIO_Initure);

    HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);

    TX_IO_Write(GPIO_HIGH);
    RX_IO_Write(GPIO_HIGH);
}

3.接收定时器配置
  假设波特率为9600,则自动重加载值配置为104,这样每104us定时器就会触发溢出更新,调用HAL_TIM_PeriodElapsedCallback进行数据处理。在配置定时器时可以先单独编写程序测试定时器配置是否正确,例如让它1s触发一次溢出中断,这样在后面接收数据出错时就可以一定程度上先排除定时器时序的问题。

/**
 * @brief 串口定时器初始化
 */
static void Usart_TIM_Init(uint32_t Period)
{
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};

    // 发送定时器
    htim7.Instance = TIM7;
    htim7.Init.Prescaler = 84 - 1;
    htim7.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim7.Init.Period = Period - 1;
    htim7.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(_OUT_TIM_Huart_) != HAL_OK)
    {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(_OUT_TIM_Huart_, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }
    HAL_TIM_Base_Start_IT(_OUT_TIM_Huart_);

    // 接收定时器
    htim8.Instance = TIM8;
    htim8.Init.Prescaler = 168 - 1;
    htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim8.Init.Period = Period - 1;
    htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim8.Init.RepetitionCounter = 0;
    htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(_IO_RX_TIM_Huart_) != HAL_OK)
    {
        Error_Handler();
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(_IO_RX_TIM_Huart_, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(_IO_RX_TIM_Huart_, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }

#ifdef USART_RX_TIMEOUT
    // 超时定时器
    htim9.Instance = TIM9;
    htim9.Init.Prescaler = 168 - 1;
    htim9.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim9.Init.Period = Period - 1;
    htim9.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim9.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(_IO_RX_OutTime_TIM_Huart_) != HAL_OK)
    {
        Error_Handler();
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(_IO_RX_OutTime_TIM_Huart_, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }
#endif
}

4.接收和发送数据处理
  通过判断起始位的电平是否为低电平,是则开始进行数据的接收/发送。
  接收:采用下降沿触发中断,来判断什么时候可以进行数据的接收,接收完成可以使用定时器超时计数来判断,也可以使用结束符的作为数据结束的标志位。
  发送:通过启动定时器定时104us(波特率9600),先发送起始位,再发送数据位,最后发送停止位,并且发送的每位数据的间隔都是104us。

/**
 * @brief 串口数据发送处理函数
 */
static void Usart_Send_Data_Handler(void)
{
    if (uart_tx.data_cur < uart_tx.tx_data_len)
    {
        // 发送起始位
        if (uart_tx.start_flag == 0)
        {
            Usart_Send_Start();
            uart_tx.start_flag = 1;
            return;
        }

        // 发送数据位
        if (uart_tx.cur_bit < 8)
        {
            if ((uart_tx.tx_data_buffer[uart_tx.data_cur] & (0x01 << uart_tx.cur_bit)) == 0)
            {
                TX_IO_Write(GPIO_LOW);
            }
            else
            {
                TX_IO_Write(GPIO_HIGH);
            }
            uart_tx.cur_bit++;
        }
        // 发送停止位
        else
        {
            uart_tx.cur_bit = 0;
            uart_tx.data_cur++;
            uart_tx.start_flag = 0;
            Usart_Send_Stop();
        }
    }
    else
    {
        Usart_Send_Idle();
    }
}

/**
 * @brief 定时器中断回调函数
 * @param htim
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    /* 定时发送 */
    if (htim->Instance == TIM7)
    {
        if (uart_tx.tx_state == UART_TX_SEND)
        {
            Usart_Send_Data_Handler();
        }
    }
    /* 接收数据定时器 */
    else if (htim->Instance == TIM8)
    {
        if (uart_rx.StartRxFlag == 1)
        {
            uart_rx.RecvState++;
            // 停止位
            if (uart_rx.RecvState == COM_STOP_BIT)
            {
                HAL_TIM_Base_Stop_IT(_IO_RX_TIM_Huart_);

                // 接收到完整的1个字节数据
                if (uart_rx.RxDataNum < IO_Uart_Length)
                {
                    uart_rx.rx_data_buffer[uart_rx.RxDataNum++] = uart_rx.RecvData;
                }
                else
                {
                    uart_rx.RxDataNum = 0;
                }

                uart_rx.StartRxFlag = 0;

#ifdef USART_RX_TIMEOUT
                HAL_TIM_Base_Start_IT(_IO_RX_OutTime_TIM_Huart_);         //开始计算接收数据超时
#else
                // 检查是否接收到结束标志
                if (uart_rx.rx_data_buffer[uart_rx.RxDataNum - 1] == '\n' &&
                    uart_rx.rx_data_buffer[uart_rx.RxDataNum - 2] == '\r')
                {
                    uart_rx.RxDataOK = 1;
                }
#endif

                uart_rx.RecvData = 0;
                return;
            }

            // 读取接收引脚的状态
            if (RX_IO_Read)
            {
                uart_rx.RecvData |= (1 << (uart_rx.RecvState - 1));
            }
            else
            {
                uart_rx.RecvData &= ~(1 << (uart_rx.RecvState - 1));
            }
        }
    }

#ifdef USART_RX_TIMEOUT
    /* 接收数据超时计数 */
    if (htim->Instance == TIM9)
    {
        // 清除中断标志位
        __HAL_TIM_CLEAR_FLAG(_IO_RX_OutTime_TIM_Huart_, TIM_FLAG_UPDATE);

        uart_rx.RxTimeOut++;

        // 超时5个字节的时间,8个位数据位 * 5个
        if (uart_rx.RxTimeOut > 8 * 5)
        {
            // 一条数据接收完成
            uart_rx.RxDataOK = 1;
            HAL_TIM_Base_Stop_IT(_IO_RX_OutTime_TIM_Huart_);
        }
    }
#endif
}

/**
 * @brief 串口接收中断回调函数
 * @param GPIO_Pin
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == RX_GPIO_PIN)
    {
        // RX引脚为低电平
        if (RX_IO_Read == 0)
        {
            // 状态为停止位--空闲状态
            if (uart_rx.RecvState == COM_STOP_BIT)
            {
                uart_rx.RecvData = 0;               // 接收数据
                uart_rx.RecvState = COM_START_BIT;  // 设置状态为起始位
                uart_rx.StartRxFlag = 1;            // 开始接收

                __HAL_TIM_SET_COUNTER(_IO_RX_TIM_Huart_, 0);            // 设置 TIM8 计数器寄存器值为0
                HAL_TIM_Base_Start_IT(_IO_RX_TIM_Huart_);               // 打开接收数据定时器

#ifdef USART_RX_TIMEOUT
                __HAL_TIM_SET_COUNTER(_IO_RX_OutTime_TIM_Huart_, 0);    // 设置 TIM9 计数器寄存器值为0
                uart_rx.RxTimeOut = 0;                                  // 把接收超时清零
                HAL_TIM_Base_Stop_IT(_IO_RX_OutTime_TIM_Huart_);        // 关闭超时计数定时器
#endif
            }
        }
    }
}

四、完整代码

1.usart.h

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-10-30     IBM       the first version
 */
#ifndef APPLICATIONS_USART_H_
#define APPLICATIONS_USART_H_

#include <drv_common.h>
#include <rtthread.h>
#include <string.h>

/**=====================================================###### 宏定义 ######==================================================*/
#define USART_RX_TIMEOUT            // 启用串口接收超时

#define GPIO_HIGH       1
#define GPIO_LOW        0

#define IO_Uart_Length  200         // 模拟串口缓冲区长度

#define TX_GPIO_PORT    GPIOA
#define TX_GPIO_PIN     GPIO_PIN_8
#define RX_GPIO_PORT    GPIOA
#define RX_GPIO_PIN     GPIO_PIN_0

#define TX_IO_Read      HAL_GPIO_ReadPin(TX_GPIO_PORT, TX_GPIO_PIN)
#define TX_IO_Write(x)  HAL_GPIO_WritePin(TX_GPIO_PORT, TX_GPIO_PIN, x)
#define RX_IO_Read      HAL_GPIO_ReadPin(RX_GPIO_PORT, RX_GPIO_PIN)
#define RX_IO_Write(x)  HAL_GPIO_WritePin(RX_GPIO_PORT, RX_GPIO_PIN, x)

TIM_HandleTypeDef htim7;
TIM_HandleTypeDef htim8;
TIM_HandleTypeDef htim9;

#define _OUT_TIM_Huart_             &htim7  // 发送定时器变量地址
#define _IO_RX_TIM_Huart_           &htim8  // 接收定时器变量地址
#define _IO_RX_OutTime_TIM_Huart_   &htim9  // 超时定时器变量地址

enum
{
    COM_START_BIT,  // 0
    COM_D0_BIT,
    COM_D1_BIT,
    COM_D2_BIT,
    COM_D3_BIT,
    COM_D4_BIT,
    COM_D5_BIT,
    COM_D6_BIT,
    COM_D7_BIT,
    COM_STOP_BIT,
};

typedef enum _TX_STATE_
{
    UART_TX_IDLE = 0,        // 发送空闲
    UART_TX_SEND,            // 数据发送

} TX_STATE;

typedef struct _USART_TX_
{
    uint8_t tx_data_buffer[IO_Uart_Length];// 模拟串口发送数据缓冲区
    uint8_t cur_bit;                       // 当前数据字节位
    volatile uint8_t start_flag;           // 启动位标志位
    TX_STATE tx_state;                     // 发送状态
    uint32_t bound;                        // 波特率
    uint16_t one_bit_time;                 // 发送1bit时间
    uint16_t data_cur;                     // 当前数据
    uint16_t tx_data_len;                  // 发送数据长度

} USART_TX;

typedef struct _USART_RX_
{
    uint8_t rx_data_buffer[IO_Uart_Length];// 模拟串口接收数据缓冲区
    uint8_t RecvData;                      // 接收一字节数据
    uint8_t RecvState;                     // 接收状态
    uint32_t RxDataNum;                    // 接收数据个数,需要手动清零

    uint32_t RxTimeOut;                    // 模拟串口接收超时计数
    uint8_t RxDataOK;                      // 数据接收完成标志
    uint8_t StartRxFlag;                   // 开始接收标志,1开始接收,0不接收

} USART_RX;

USART_TX uart_tx;
USART_RX uart_rx;

extern void Usart_Init(uint32_t bound);
extern void Usart_Send_Data(uint8_t *str);
extern void IO_UART_RxDeal(void);
/**====================================================#######  END  #######=================================================*/

#endif /* APPLICATIONS_USART_H_ */

2.usart.c

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-10-30     IBM       the first version
 */
#include "usart.h"

/*=====================================================###### 初始化 ######==================================================*/
/**
 * @brief 发送端口初始化
 */
static void Usart_IO_Config(void)
{
    GPIO_InitTypeDef GPIO_Initure;

    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_Initure.Pin   = TX_GPIO_PIN;           // PA8 -- TX
    GPIO_Initure.Mode  = GPIO_MODE_OUTPUT_PP;   // 输出推挽模式
    GPIO_Initure.Pull  = GPIO_PULLUP;           // 上拉
    GPIO_Initure.Speed = GPIO_SPEED_HIGH;
    HAL_GPIO_Init(TX_GPIO_PORT, &GPIO_Initure);

    GPIO_Initure.Pin   = RX_GPIO_PIN;           // PA0 -- RX
    GPIO_Initure.Mode  = GPIO_MODE_IT_FALLING;  // 输入模式下降沿触发
    HAL_GPIO_Init(RX_GPIO_PORT, &GPIO_Initure);

    HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);

    TX_IO_Write(GPIO_HIGH);
    RX_IO_Write(GPIO_HIGH);
}

/**
 * @brief 串口定时器初始化
 */
static void Usart_TIM_Init(uint32_t Period)
{
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};

    // 发送定时器
    htim7.Instance = TIM7;
    htim7.Init.Prescaler = 84 - 1;
    htim7.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim7.Init.Period = Period - 1;
    htim7.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(_OUT_TIM_Huart_) != HAL_OK)
    {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(_OUT_TIM_Huart_, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }
    HAL_TIM_Base_Start_IT(_OUT_TIM_Huart_);

    // 接收定时器
    htim8.Instance = TIM8;
    htim8.Init.Prescaler = 168 - 1;
    htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim8.Init.Period = Period - 1;
    htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim8.Init.RepetitionCounter = 0;
    htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(_IO_RX_TIM_Huart_) != HAL_OK)
    {
        Error_Handler();
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(_IO_RX_TIM_Huart_, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(_IO_RX_TIM_Huart_, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }

#ifdef USART_RX_TIMEOUT
    // 超时定时器
    htim9.Instance = TIM9;
    htim9.Init.Prescaler = 168 - 1;
    htim9.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim9.Init.Period = Period - 1;
    htim9.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim9.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(_IO_RX_OutTime_TIM_Huart_) != HAL_OK)
    {
        Error_Handler();
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(_IO_RX_OutTime_TIM_Huart_, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }
#endif
}
/*=====================================================#######  END  #######=================================================*/

/*=====================================================##### 串口发送 #####==================================================*/
/**
 * @brief 串口发送开始位
 */
static void Usart_Send_Start(void)
{
    if (TX_IO_Read != GPIO_HIGH)
    {
        TX_IO_Write(GPIO_HIGH);
        rt_hw_us_delay(5);
    }
    TX_IO_Write(GPIO_LOW);
}

/**
 * @brief 串口发送停止位
 */
static void Usart_Send_Stop(void)
{
    TX_IO_Write(GPIO_HIGH);
}

/**
 * @brief 串口空闲状态
 */
static void Usart_Send_Idle(void)
{
    TX_IO_Write(GPIO_HIGH);
    HAL_TIM_Base_Stop_IT(_OUT_TIM_Huart_);
    uart_tx.tx_state = UART_TX_IDLE;
}

/**
 * @brief 串口发送启动
 */
static void Usart_Send_Open(void)
{
    HAL_TIM_Base_Start_IT(_OUT_TIM_Huart_);
    uart_tx.tx_state = UART_TX_SEND;
}

/**
 * @brief 串口数据发送处理函数
 */
static void Usart_Send_Data_Handler(void)
{
    if (uart_tx.data_cur < uart_tx.tx_data_len)
    {
        // 发送起始位
        if (uart_tx.start_flag == 0)
        {
            Usart_Send_Start();
            uart_tx.start_flag = 1;
            return;
        }

        // 发送数据位
        if (uart_tx.cur_bit < 8)
        {
            if ((uart_tx.tx_data_buffer[uart_tx.data_cur] & (0x01 << uart_tx.cur_bit)) == 0)
            {
                TX_IO_Write(GPIO_LOW);
            }
            else
            {
                TX_IO_Write(GPIO_HIGH);
            }
            uart_tx.cur_bit++;
        }
        // 发送停止位
        else
        {
            uart_tx.cur_bit = 0;
            uart_tx.data_cur++;
            uart_tx.start_flag = 0;
            Usart_Send_Stop();
        }
    }
    else
    {
        Usart_Send_Idle();
    }
}
/*=====================================================#######  END  #######=================================================*/

/*=====================================================##### 串口接收 #####==================================================*/
/**
 * @brief 清除接收缓存
 */
static void IO_UART_ClearRxBuff(void)
{
    memset(uart_rx.rx_data_buffer, 0, sizeof(uart_rx.rx_data_buffer));
    uart_rx.RxDataNum = 0; // 数据长度清零
    uart_rx.RxDataOK = 0;
}

/**
 * @brief 定时器中断回调函数
 * @param htim
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    /* 定时发送 */
    if (htim->Instance == TIM7)
    {
        if (uart_tx.tx_state == UART_TX_SEND)
        {
            Usart_Send_Data_Handler();
        }
    }
    /* 接收数据定时器 */
    else if (htim->Instance == TIM8)
    {
        if (uart_rx.StartRxFlag == 1)
        {
            uart_rx.RecvState++;
            // 停止位
            if (uart_rx.RecvState == COM_STOP_BIT)
            {
                HAL_TIM_Base_Stop_IT(_IO_RX_TIM_Huart_);

                // 接收到完整的1个字节数据
                if (uart_rx.RxDataNum < IO_Uart_Length)
                {
                    uart_rx.rx_data_buffer[uart_rx.RxDataNum++] = uart_rx.RecvData;
                }
                else
                {
                    uart_rx.RxDataNum = 0;
                }

                uart_rx.StartRxFlag = 0;

#ifdef USART_RX_TIMEOUT
                HAL_TIM_Base_Start_IT(_IO_RX_OutTime_TIM_Huart_);         //开始计算接收数据超时
#else
                // 检查是否接收到结束标志
                if (uart_rx.rx_data_buffer[uart_rx.RxDataNum - 1] == '\n' &&
                    uart_rx.rx_data_buffer[uart_rx.RxDataNum - 2] == '\r')
                {
                    uart_rx.RxDataOK = 1;
                }
#endif

                uart_rx.RecvData = 0;
                return;
            }

            // 读取接收引脚的状态
            if (RX_IO_Read)
            {
                uart_rx.RecvData |= (1 << (uart_rx.RecvState - 1));
            }
            else
            {
                uart_rx.RecvData &= ~(1 << (uart_rx.RecvState - 1));
            }
        }
    }

#ifdef USART_RX_TIMEOUT
    /* 接收数据超时计数 */
    if (htim->Instance == TIM9)
    {
        // 清除中断标志位
        __HAL_TIM_CLEAR_FLAG(_IO_RX_OutTime_TIM_Huart_, TIM_FLAG_UPDATE);

        uart_rx.RxTimeOut++;

        // 超时5个字节的时间,8个位数据位 * 5个
        if (uart_rx.RxTimeOut > 8 * 5)
        {
            // 一条数据接收完成
            uart_rx.RxDataOK = 1;
            HAL_TIM_Base_Stop_IT(_IO_RX_OutTime_TIM_Huart_);
        }
    }
#endif
}

/**
 * @brief 串口接收中断回调函数
 * @param GPIO_Pin
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == RX_GPIO_PIN)
    {
        // RX引脚为低电平
        if (RX_IO_Read == 0)
        {
            // 状态为停止位--空闲状态
            if (uart_rx.RecvState == COM_STOP_BIT)
            {
                uart_rx.RecvData = 0;               // 接收数据
                uart_rx.RecvState = COM_START_BIT;  // 设置状态为起始位
                uart_rx.StartRxFlag = 1;            // 开始接收

                __HAL_TIM_SET_COUNTER(_IO_RX_TIM_Huart_, 0);            // 设置 TIM8 计数器寄存器值为0
                HAL_TIM_Base_Start_IT(_IO_RX_TIM_Huart_);               // 打开接收数据定时器

#ifdef USART_RX_TIMEOUT
                __HAL_TIM_SET_COUNTER(_IO_RX_OutTime_TIM_Huart_, 0);    // 设置 TIM9 计数器寄存器值为0
                uart_rx.RxTimeOut = 0;                                  // 把接收超时清零
                HAL_TIM_Base_Stop_IT(_IO_RX_OutTime_TIM_Huart_);        // 关闭超时计数定时器
#endif
            }
        }
    }
}
/*=====================================================#######  END  #######=================================================*/

/*======================================================##### 外部调用 #####==================================================*/
/**
 * @brief 串口初始化
 */
void Usart_Init(uint32_t bound)
{
    Usart_IO_Config();
    Usart_TIM_Init(1000000 / bound);

    //上电启动一次接收定时器,不启动接收到的第一个字节会乱码
    HAL_TIM_Base_Start_IT(_IO_RX_TIM_Huart_);
    HAL_Delay(5);
    HAL_TIM_Base_Stop_IT(_IO_RX_TIM_Huart_);

    uart_rx.RecvState = COM_STOP_BIT;
}

/**
 * @brief 串口发送数据
 * @param str:发送的数据
 */
void Usart_Send_Data(uint8_t *str)
{
    uart_tx.data_cur = 0;
    uart_tx.cur_bit  = 0;
    uart_tx.start_flag = 0;
    uart_tx.tx_data_len = 0 ;
    memset(&uart_tx.tx_data_buffer , 0 , sizeof(uart_tx.tx_data_buffer));

    for (int i = 0; *str != '\0'; i++)
    {
        uart_tx.tx_data_buffer[i] = *str;
        str++;
        uart_tx.tx_data_len++;
    }
    Usart_Send_Open();
}

/**
 * @brief IO串口接收数据处理
 */
void IO_UART_RxDeal(void)
{
    if (uart_rx.RxDataOK == 1)
    {
        Usart_Send_Data(uart_rx.rx_data_buffer);
        IO_UART_ClearRxBuff();
    }
}
/*=====================================================#######  END  #######=================================================*/

3.main.c

/*
 * Copyright (c) 2006-2024, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-09-19     RT-Thread    first version
 */
#include <drv_common.h>
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "usart.h"

/* 心跳灯线程的入口函数 */
static void thread_heartbeat_entry(void *parameter)
{
    int count = 1;

    while (1)
    {
        count++;
        rt_pin_write(GET_PIN(E, 12), count % 2);
        rt_thread_mdelay(1000);
    }
}

/* 创建心跳灯线程 */
static int thread_heartbeat(void)
{
    rt_pin_mode(GET_PIN(E, 12), PIN_MODE_OUTPUT);

    /* 创建线程 1,名称是 thread1,入口是 thread1_entry,动态创建*/
    rt_thread_t tid1 = rt_thread_create("heartbeat", thread_heartbeat_entry, RT_NULL, 256, 25, 5);

    /* 如果获得线程控制块,启动这个线程 */
    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);

    return 0;
}

int main(void)
{
    int count = 1;

    thread_heartbeat();
    Usart_Init(9600);

    while (count)
    {
        IO_UART_RxDeal();
        rt_thread_mdelay(10);
    }

    return RT_EOK;
}


五、测试验证

  Usart-IO模拟串口通信工程代码
  通过使用串口助手进行数据的发送和接收,可以观察到数据发送和接收都很正常,并且没有出现数据的丢失问题。同时也测试过使用超时定时器和结束符作为结束符的方式,都是可行的,两种方法都可以选择,但不要同时使用。
在这里插入图片描述



原文地址:https://blog.csdn.net/Hei_se_meng_yan/article/details/143424347

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!