一、USART/UART串口介绍

(一)串口通讯的简介

        一条信息的各位数据被逐位按顺序传送的通讯方式称为串行通讯。串行通讯的特点是:数据位的传送,按位顺序进行,最少只需一根传输线即可完成;成本低但传送速度慢。串行通讯的距离可以从几米到几千米;根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。

(二)不同设备间的通方式

并行通讯 串行通讯
传输方式 多个数据位一起传输 一个bit位一个bit位传输
优点 速度快 占用资源少
缺点 占用资源多 速度慢

(三)串行通信分类

1、按数据传输方向

模式 介绍
单工 单工通信是一种单向传输数据的通信方式。
半双工 半双工通信允许数据双向传输,但发送和接收不能同时进行,双方需要轮流发送和接收数据。
全双工 全双工通信可以同时进行数据的发送和接收,两者互不干扰。

2、按通讯方式

模式 介绍
同步通讯(USART) 同步通信依赖共享的时钟信号,发送方和接收方根据时钟信号的节奏来传输和接收数据。这种通信方式的效率高,通常用于实时性要求较高的场景,如嵌入式系统中的I2C、SPI等协议。
异步通讯(UART) 异步通信无需共享时钟信号,发送方和接收方以各自的速度传输和接收数据。常见的异步通信协议有UART、RS-232等,通常用于突发性、间歇性数据传输。

(四)UART串口通讯介绍

1、串口通讯接线

        TX:数据发送端

        RX : 数据接收端

2、串口传输数据帧

        起始位:代表一个帧数据的开始一个低电平

        数据位:有效数据的个数,5~8个数据位,一般配置为8个(一个byte)。

        奇偶校验位:可配置为奇校验(奇数个1)、偶校验(偶数个1)、无校验(即不传输此位)。

        停止位:是一个帧数据的结束,可以是1个、1.5个、2个空闲电平(高电平)

        波特率:每秒传输二进制数的个数,

二、STM32串口通讯模块

(一)STM32串口特性

  • 全双工
  • 数据长度可编程(8位或9位)
  • 停止位可配置(1个或2个)
  • 可配置DMA

(二)STM32 USART硬件框图

发送数据:
        通过写操作,将数据写入TDR,写入完成后,硬件就会自动检查发送移位寄存器中是否有数据正在进行移位,有则等待,若没有,则数据就会立刻转入发送移位寄存器,此时会将标志位TXE(发送寄存器空)置1,代表TDR为空,新转入的数据进入准备发送状态。此时发送移位寄存器就会在下面的发送器控制下,将数据输出到TX引脚。这样可以保证连续发送数据的时候,数据帧之间不会有空闲,提高了工作效率。
接收数据:
        通过RX引脚接收到的数据会进入接收移位寄存器,数据由高位向低位逐渐移位,然后全部保存到接收数据寄存器(RDR),当完成一个字节的移位后,会将标志位RXNE(接收寄存器非空)置1,通过标志位,就可以对数据进行读取处理。

数据寄存器USART_DR:

        位 31:9 保留,必须保持复位值

        位 8:0 DR[8:0]:数据值

        因为数据寄存器包含两个寄存器,一个用于发送 (TDR),一个用于接收 (RDR),因此它具有 双重功能(读和写)。根据所执行的操作确定是“读取”还是“写入”。

三、相关寄存器

寄存器映射:

        USART_SR :状态寄存器

        USART_DR :数据寄存器

        USART_BRR :波特率寄存器

        USART_CRx :控制寄存器x(x = 1,2,3)

        USART_GTPR :保护时间和预分频器寄存器(用来控制预分频值)

寄存器各个位功能详情参考STM32F1xx参考手册

四、STM32串口通讯配置

(一)配置流程

  1. 配置串口工作参数(波特率、数据长度、奇偶校验位等)
  2. 使能USART模块和使用到的GPIO时钟
  3. 配置串口的Tx和Rx引脚的GPIO模式:
  4. 使能串口、启动串口通讯
  5. 发送和接收数据

(二)相关函数

//1、初始化串口函数
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);

/*2、串口初始化回调函数,在HAL_UART_Init中会调用该函数来使能串口时钟和GPIO时钟,以及配置GPIO
    此函数需要自己实现,在HAL中是一个弱定义函数,类似的HAL_xxx_MspInit()都是类似的
*/ 
void HAL_UART_MspInit(UART_HandleTypeDef *huart);

//3、串口发送
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
                    const uint8_t *pData, uint16_t Size,
                     uint32_t Timeout);    //以阻塞的方式发送
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart,
                  const uint8_t *pData, uint16_t Size);    //以中断的方式发送

//4、串口接收
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData,
                    uint16_t Size, uint32_t Timeout);    //以阻塞的方式接收
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData,
                    uint16_t Size);    //以中断的方式接收
HAL_StatusTypeDef UART_Start_Receive_IT(UART_HandleTypeDef *huart,
                  uint8_t *pData, uint16_t Size);    //开启接收中断
    

五、实战编程

        使用的开发板芯片:STM32F103C8t6

        使用到的串口:UART1

        引脚:Tx(PA9)、Rx(PA10)

usart.h:

#ifndef __USART_H
#define __USART_H

extern UART_HandleTypeDef huart1;


void MX_USART1_UART_Init(void);


void Usart1_SendByte(char ch); // 发送一个字节
void Usart1_SendString(u8 *str);


#endif //__USART_H

usart.c:

#include "usart.h"
#include <stdio.h>
#include <string.h>



UART_HandleTypeDef huart1;


//配置串口1的工作参数

void MX_USART1_UART_Init(void) {

    
    huart1.Instance = USART1;    //串口1
    huart1.Init.BaudRate = 115200;    //波特率:115200
    huart1.Init.WordLength = UART_WORDLENGTH_8B;    //8位数据位
    huart1.Init.StopBits = UART_STOPBITS_1;    //一个停止位
    huart1.Init.Parity = UART_PARITY_NONE;    //无奇偶校验位
    huart1.Init.Mode = UART_MODE_TX_RX;    //收发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;    //无硬件流控制
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;    //16倍采样率
    if (HAL_UART_Init(&huart1) != HAL_OK) {
        Error_Handler();
    }
  
}

//配置串口使用到的GPIO
void HAL_UART_MspInit(UART_HandleTypeDef *uartHandle) {

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if (uartHandle->Instance == USART1) {
       
        //使能串口1 时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        //使能串口1使用到的GPIO时钟
        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**USART1 GPIO Configuration
        PA9     ------> USART1_TX
        PA10     ------> USART1_RX
        */
        GPIO_InitStruct.Pin = GPIO_PIN_9;    
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;    //复用推挽输出
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;    //快速
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;    //输入模式
        GPIO_InitStruct.Pull = GPIO_NOPULL;        //无上下拉
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


        HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);    //设置中断优先级
        HAL_NVIC_EnableIRQ(USART1_IRQn);    //使能串口中断
   
    }
}

//发送一个字符
void Usart1_SendByte(char ch) {
    HAL_UART_Transmit(&huart1, (u8 *)&ch, 1, 1000);
    while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) != SET)
        ;
}
//发送一个字符串
void Usart1_SendString(u8 *str) {
    unsigned int num = 0;
    char ch = str[num];
    while (num < strlen(str)) {
        Usart1_SendByte(ch);
        num++;
        ch = str[num];
    }
}

在main.c中添加如下代码以支持printf功能:

#ifdef __GNUC__

/* With GCC, small printf (option LD Linker->Libraries->Small printf
   set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
extern UART_HandleTypeDef huart1;
PUTCHAR_PROTOTYPE {
    Usart1_SendByte(ch);
    return ch;
}

int _write(int file, char *ptr, int len) {
    int DataIdx;

    for (DataIdx = 0; DataIdx < len; DataIdx++) {
        __io_putchar(*ptr++);
    }
    return len;
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐