0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

USART

修罗 2025-08-21 69 5条评论 stm32

首页 / 正文

USART

​ STM32提供了USART(Universal Synchronous Asynchronous Receiver and Transmitter)通用同步异步收发器。是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。UART相比USART去掉了同步通讯功能。

​ 一共提供5个串口供开发者选择。

image-20250821084803338.png

​ STM32的USART功能框图如下:

image-20250821084714279.png

1.1 功能引脚说明

  1. TX:发送数据输出引脚;
  2. RX:接收数据输入引脚;
  3. SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚;
  4. nRTS:请求以发送(Request To Send),n 表示低电平有效。如果使能RTS流控制,当 USART接收器准备好接收新数据时就会将nRTS变成低电平;当接收寄存器已满时,nRTS 将被设置为高电平。该引脚只适用于硬件流控制; 可以把 nRTS 看作 接收端的“手”,举手(拉高)表示“先别发”
  5. nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能CTS流控制,发送器在发送下一帧数据之前会检测nCTS引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制;可以把 nCTS 看作 接收端给发送端的“绿灯”,低电平=绿灯,允许发;高电平=红灯,暂停;
  6. SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式;

1.2 STM32 串口寄存器配置开发流程(USART1)

1.2.1 使能时钟

需要使能两个时钟:GPIOA 时钟(因为 USART1 默认用 PA9=TX, PA10=RX)、 USART1 外设时钟

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;   // 使能 GPIOA 时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 使能 USART1 时钟

1.2.2 配置STM32的RXTX串口引脚

配置PA9 (TX) → 复用推挽输出、PA10 (RX) → 浮空输入

// PA9 = USART1_TX
GPIOA->CRH &= ~(GPIO_CRH_MODE9 | GPIO_CRH_CNF9);
GPIOA->CRH |=  (GPIO_CRH_MODE9_1 | GPIO_CRH_MODE9_0); // 输出模式 50MHz
GPIOA->CRH |=  GPIO_CRH_CNF9_1; // 复用推挽输出

// PA10 = USART1_RX
GPIOA->CRH &= ~(GPIO_CRH_MODE10 | GPIO_CRH_CNF10);
GPIOA->CRH |=  GPIO_CRH_CNF10_0; // 浮空输入

1.2.3 配置波特率

​ 发送器和接收器的波特率是一致的,都是通过设置BRR寄存器来得到

​ 波特率计算公式:

image-20250821091520508.png

​ 这里的image-20250821091608340.png是给外设的时钟(usart1在APB2上一般是72MHz,usart2,3,4,5在APB1上一般为36MHz)。

​ 如下图BRR寄存器,往这个寄存器写USARTDIV即可完成设置波特率:

image-20250821091809023.png

​ 假设我们需要的波特率是115200,通过公式反推USARTDIV:72*10^6 / 16 / 115200,(72*10^6为72Mhz)则对应的分频值应该是:39.0625,把这个值写入到BRR寄存器中。BRR寄存器DIV_Fraction内容为小数部分 * 16

image-20250821091938167.png

​ 39.0625的小数部分:0.0625 * 16 = 1, 整数部分是:39(0x27 / 100111),所以写入到BRR寄存器的值是:0x0271 (1001110001);注: 16进制添加一位即二级制添加四位。

USART1->BRR = 0x0271; // 设置波特率 115200

1.2.4 配置 USART 控制寄存器

  • UE (USART Enable) = 1 → 使能 USART
  • TE (Transmitter Enable) = 1 → 使能发送器
  • RE (Receiver Enable) = 1 → 使能接收器
  • 8 数据位,1 停止位,无校验(默认)
USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
  • 寄存器

image-20250821094753977.png

image-20250821100451973.png

1.2.5 发送数据(轮询方式)

  • 检查 TXE (发送数据寄存器为空时) 位 → USART1->SR & USART_SR_TXE
  • 写入 USART1->DR
/**
 * @description: 发送一个字节
 * @param {char} byte 要发送的字节
 */
void USART1_SendChar(char byte)
{
    while(!(USART1->SR & USART_SR_TXE)); // 等待 SR寄存器下的TXE=1
    USART1->DR = byte;                      // 写入数据寄存器
}

/**
 * @description: 发送一个字符串
 * @param {uint8_t} *str 要发送的字符串
 * @param {uint16_t} len 字符串中字节的长度
 * @return {*}
 */
void Driver_USART1_SendString(uint8_t *str, uint16_t len)
{
    for (uint16_t i = 0; i < len; i++)
    {
        Driver_USART1_SendChar(str[i]);
    }
}
  • 寄存器

image-20250821094828230.png

image-20250821094838069.png

1.2.6 接收数据(轮询方式)

  • 检查 RXNE (接收数据寄存器非空) 位 → USART1->SR & USART_SR_RXNE
  • 读取 USART1->DR
char USART1_ReceiveChar(void)
{
    while(!(USART1->SR & USART_SR_RXNE)); // 等待 RXNE=1
    return USART1->DR;                    // 读数据寄存器
}


/**
 * @description: 接收变长数据.接收到的数据存入到buff中
 * @param {uint8_t} buff 存放接收到的数据
 * @param {uint8_t} *len 存放收到的数据的字节的长度
 */
void Driver_USART1_ReceiveString(uint8_t buff[], uint8_t *len)
{
    uint8_t i = 0;
    while (1)
    {
        // 等待接收非空
        while ((USART1->SR & USART_SR_RXNE) == 0)
        {
            //在等待期间, 判断是否收到空闲帧
            if (USART1->SR & USART_SR_IDLE)
            {
                *len = i;
                return;
            }
        }
        buff[i] = USART1->DR;
        i++;
    }
}
  • 寄存器

image-20250821094905541.png

1.2.7 完整代码和测试

// Driver_USART

/**
 * @description: 初始化串口1
 */
void Driver_USART1_Init(void)
{
    /* 1. 开启时钟 */
    /* 1.1 串口1外设的时钟 */
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
    /* 1.2 GPIO时钟 */
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

    /* 2. 配置GPIO引脚的工作模式  PA9=Tx(复用推挽 CNF=10 MODE=11)  PA10=Rx(浮空输入 CNF=01 MODE=00)*/
    GPIOA->CRH |= GPIO_CRH_CNF9_1;
    GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
    GPIOA->CRH |= GPIO_CRH_MODE9;

    GPIOA->CRH &= ~GPIO_CRH_CNF10_1;
    GPIOA->CRH |= GPIO_CRH_CNF10_0;
    GPIOA->CRH &= ~GPIO_CRH_MODE10;

    /* 3. 串口的参数配置 */
    /* 3.1 配置波特率 115200 */
    USART1->BRR = 0x271;
    /* 3.2 串口使能,并使能接收和发送 */
    USART1->CR1 |= USART_CR1_UE;
    USART1->CR1 |= USART_CR1_TE;
    USART1->CR1 |= USART_CR1_RE;    
    /* 3.3 配置一个字的长度 8位 */
    USART1->CR1 &= ~USART_CR1_M;
    /* 3.4 配置不需要校验位 */
    USART1->CR1 &= ~USART_CR1_PCE;
    /* 3.5 配置停止位的长度 */
    USART1->CR2 &= ~USART_CR2_STOP;
}

/**
 * @description: 发送一个字节
 * @param {uint8_t} byte 要发送的字节
 */
void Driver_USART1_SendChar(uint8_t byte)
{
    /* 1. 等待发送寄存器为空 */
    while ((USART1->SR & USART_SR_TXE) == 0)
        ;

    /* 2. 数据写出到数据寄存器 */
    USART1->DR = byte;
}

/**
 * @description: 发送一个字符串
 * @param {uint8_t} *str 要发送的字符串
 * @param {uint16_t} len 字符串中字节的长度
 * @return {*}
 */
void Driver_USART1_SendString(uint8_t *str, uint16_t len)
{
    for (uint16_t i = 0; i < len; i++)
    {
        Driver_USART1_SendChar(str[i]);
    }
}

/**
 * @description: 接收一个字节的数据
 * @return {*} 接收到的字节
 */
uint8_t Driver_USART1_ReceiveChar(void)
{
    /* 等待数据寄存器非空 */
    while ((USART1->SR & USART_SR_RXNE) == 0)
        ;
    return USART1->DR;
}

/**
 * @description: 接收变长数据.接收到的数据存入到buff中
 * @param {uint8_t} buff 存放接收到的数据
 * @param {uint8_t} *len 存放收到的数据的字节的长度
 */
void Driver_USART1_ReceiveString(uint8_t buff[], uint8_t *len)
{
    uint8_t i = 0;
    while (1)
    {
        // 等待接收非空
        while ((USART1->SR & USART_SR_RXNE) == 0)
        {
            //在等待期间, 判断是否收到空闲帧
            if (USART1->SR & USART_SR_IDLE)
            {
                *len = i;
                return;
            }
        }
        buff[i] = USART1->DR;
        i++;
    }
}

// main.c
#include "string.h"

uint8_t buff[100] = {0};
uint8_t len = 0;
int main()
{
    Driver_USART1_Init();
    while (1)
    {
        Driver_USART1_ReceiveString(buff, &len);
        Driver_USART1_SendString(buff, len);
    }
}

1.2.8 中断方式接收数据

​ USART提供了多个中断事件

image-20250821151713648.png

  • 使能串口、使能接收中断 RXNEIE
  • 配置 NVIC
// 上节初始化代码Driver_USART1_Init加上下面代码:


USART1->CR1 |= USART_CR1_IDLEIE; /* 空闲中断 */
USART1->CR1 |= USART_CR1_RXNEIE; // 使能接收中断

NVIC_SetPriorityGrouping(3); /*  配置优先级组 */
NVIC_SetPriority(USART1_IRQn, 2);/* 设置优先级 */
NVIC_EnableIRQ(USART1_IRQn);     // 开启 NVIC 中断

USART1->CR1 |= USART_CR1_UE;/* 使能串口 */

// 这里只是声明,不分配存储空间,分配在main.c中,最终编译时,这几个变量只会有一份存储空间
extern uint8_t buffer[100];
extern uint8_t size;
extern uint8_t isOver;
// 中断服务函数
void USART1_IRQHandler(void)
{
    if(USART1->SR & USART_SR_RXNE) { // 收到数据,SR寄存器的USART_SR_RXNE这位为高电平
         buffer[size] = USART1->DR;// 读数据放到缓冲区
         size++;  
    }else if (USART1->SR & USART_SR_IDLE)
    {
        // 字符串整体接收完成
        // 清除IDLE标志位
        USART1->DR;

        isOver = 1;
    }
}

main.c

#include "stm32f10x.h"
#include "Driver_USART.h"
#include "string.h"

// 定义全局变量,接收缓冲区和size
uint8_t buffer[100] = {0};
uint8_t size = 0;

// 定义标志位
uint8_t isOver = 0;
int main(void)
{

    Driver_USART1_Init();
    // 发送字符串
    uint8_t * str = "Hello, World!\n";
    Driver_USART1_SendString(str, strlen((char *)str));
    while(1)
    {
        // 中断接收到数据,回复相同数据
        if (isOver)
        {
            Driver_USART1_SendString(buffer, size);

            // 清除标志
            isOver = 0;
            size = 0;
        }
    }
}

评论(5)

  1. 1 游客 2025-09-01 11:26 回复

    1

  2. 1 游客 2025-09-01 12:02 回复

    1

  3. @@ZZAh2 游客 2025-09-01 12:03 回复

    1

  4. 1 游客 2025-09-01 12:06 回复

    1

  5. 1 游客 2025-09-01 12:07 回复

    1


最新评论

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • @@5Qa2D

    1

  • 1

    1

  • 1

    1

日历

2025年09月

 123456
78910111213
14151617181920
21222324252627
282930    

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis