STM32-USART传输数据实验

串行通信

USART通信是一种串行通信方式,即将数据一位一位的发送出去。

同步通信与异步通信

异步串行通信协议:

通信双方不需要使用同一个时钟信号来同步数据传输,工作双方的"工作效率"允许存在差异。
发送方和接收方各自有独立的时钟源,但要求发送端和接收端保持相同的**解析速率(波特率)**以使得接收端能够正常解析发送端的数据。
接收方通过检测数据中的起始位和停止位等特定标志来确定数据的开始和结束,从而实现数据的异步接收。
由于数据带有特定的起始位和停止位,所以接收端处理数据不需要严格与发送端保持同步,即便接收端处理较慢,一般也不会产生问题,因为发送端数据会缓存在接收端寄存器内部。
同步串行通信协议:

通信双方需要使用同一个时钟信号来同步数据传输。
通常由发送方提供时钟信号,接收方根据这个时钟信号来确定何时接收数据位,确保双方在数据传输过程中保持严格的同步。
通常由发送端发送起始信号、特殊信号标志等来告知接收端发送数据开始,当然传输结束也是一样的。
发送端和接收端必须实时同步处理数据,否则就会导致数据丢失。

USART在UART异步串行通信的基础上,还支持同步串行通信的外设/通信协议。

串口通信设备的连接方式

串口通信的连接方式非常简单,具有以下特点:

点对点通信:UART 通常用于两个设备之间的通信,一个设备作为发送方(TX),另一个设备作为接收方(RX)。
简单易用:UART 只需要两条信号线(TX 和 RX)即可实现全双工通信(可以同时发送和接收数据)。

串口通信的数据帧格式

UART通信采用的通信方式是串行通信,即数据按位(bit)顺序一位一位的传输,所以我们首先就需要了解UART数据帧格式。

一个经典的UART数据帧格式如下图所示:
在这里插入图片描述
引脚在发送数据和接受数据时,默认空闲电位是高电位,起始位低电平,停止位高电平。数据位8-9位,低电平(可以加校验位,但串行通信的稳定性好,不容易出错,所以不加校验位也行)
在这里插入图片描述
数字88转换成二进制补码形式,数据是:0101 1000

其数据帧格式如下图所示
在这里插入图片描述
字符串"abc"转换成二进制表示,即转换成各个字符的编码值表示,各个字符的编码值是:

‘a’:十进制97,0x61,即二进制0110 0001
‘b’:十进制98,0x62,即二进制0110 0010
‘c’:十进制99,0x63,即二进制0110 0011
在通信传输数据时,发送"abc"肯定是先发a再发b,最后发c,这样才能保证接收端也是先接收a再接到b,最后收到c,如此就能收到一个字符串"abc"。

所以其数据帧格式如下图所示:
在这里插入图片描述

波特率

UART串口通信是一种异步串行通信协议,不需要通信双方统一时钟信号,双方设备可以工作在不同的频率下。

这样就出现了一个显而易见的问题:

数据帧是依靠一定频率的高低电平变化来表示数据0和1,那么当发送端发送一个数据帧时,接收端如何才能准确识别每一个比特(bit)的时间间隔呢?

比如发送端发送了一个10位的数据帧,用时1秒钟,那么就是0.1秒发送1位,若接收端不知道0.1s这个间隔,接收端能够正确解析数据帧吗?

显然是不可以的。

为了解决这个问题,UART通信引入了波特率(Baud Rate)的概念。

波特率(Baud Rate) 是指 每秒传输的最大位数,单位是 bps(bits per second),即“每秒传输的比特数”。它用于衡量串行通信(如UART、RS-232、SPI、I2C等)的数据传输速率。

简单来说:

波特率 = 1秒内传输的bit数量
比如 波特率是9600 bps,意味着每秒传输 9600 个 bit。
在UART通信中,最常见的波特率是9600、115200、57600 等。

很显然,发送端和接收端必须使用相同波特率,否则会出现乱码或数据丢失等问题。

USART的工作原理

在这里插入图片描述
也就是说,假如我们想要使用USART模块发送数据,只需要将数据写入到发送数据寄存器中,这样USART模块通过内部电路就会自动将数据逐位的,通过Tx引脚发送出去.
接受数据时,通过Rx引脚接受,由于接收时时是反向接收的,所以可以靠电路设计反向接收,这样数据进入到寄存器中就是正常的了。

连接电路及设备

好的,我们搞清楚了原理,现在要连接硬件设备。
首先搞清楚双方通信的Tx,Rx引脚。
经过查表:
在这里插入图片描述
可以找到,即PA9,PA10的复用功能。
pc端则需要USB转TTL模块。
在这里插入图片描述

连USART外设模块

在这里插入图片描述
这里的硬件流控指的是:
RTS由接收端控制,决定是否允许对方发送数据,连接到发送端的CTS。
CTS由发送端控制,决定是否允许自己发送数据,连接到接收端的RTS。
常用于数据吞吐量大的数据传输,如蓝牙,wifi等模块。这里不需要。

引脚的工作模式

Tx端,选择挽输出高低电平。
Rx端,由于数据是低电平才是数据起始的标志,所以使用上拉输入模式,而下拉输入模式接收端默认是低电平模式,就会一直处于数据接收状态,浮空输入模式也行,因为浮空模式下默认不接收任何电平,所以不影响数据起始位的低电平接收。
注意:
引脚输出什么只能由一方来单独控制,所以输出模式有通用和复用的区别,用来确定控制权的归属。

但输入模式并不需要什么控制权,引脚的输入可以同时由GPIO外设读,也可以由USART1外设来读,所以输入模式并没有复用和通用的区别。
这就是为什么这个枚举类型里面只有输复用输出,而没有复用输入的原因。
在这里插入图片描述

代码实现外设初始化

void GPIO_init(void){
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	//设置PA10使用上拉输入模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	//设置PA11为复用TX功能,使用复用推挽输出模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
}
	
void USART1_init(void){
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART1,ENABLE);//USART是挂在APB2总线上的,所以开启APB2总线
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 115200;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStruct);
	//不要忘记开启外设
	USART_Cmd(USART1, ENABLE);
}

初始化USART1外设之后还需要开启开关,使用这个函数。

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);

在这里插入图片描述

单片机发送数据到PC

STM32的USART外设发送数据帧的过程是这样的:

程序员控制STM32,先将1个字节的数据帧放入发送数据寄存器。
将发送数据寄存器中的数据写入到移位寄存器,此时清空发送数据寄存器。(由USART外设自行完成)
移位寄存器逐位的将数据移除,通过控制电路再通过Tx引脚输出高低电平从而输出发送数据。当发送完当前1个字节的数据帧时,移位寄存器清空。(由USART外设自行完成)
重复上述过程,直到所有的数据都发送完成。
在这个过程中,我们不禁要提出两个问题:

在向发送数据寄存器写数据时,若发送数据寄存器不为空,则会出现数据被覆盖,从而导致数据漏发。
在向移位寄存器写数据时,若移位寄存器不为空,则也会出现数据被覆盖,从而导致数据漏发。
为了解决这两个问题,我们在编程实现串口发送数据时,需要获取这两个寄存器是否为空的状态。下面介绍一个函数,用于获取这些状态。

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);

该函数是查询 USART 状态标志位的函数。
返回值类型是枚举类型。

typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;

枚举值RESET时,其实就是返回0,表示条件没有满足。
枚举值SET时,其实就是返回1,表示条件已经满足。
第二个参数是下面两个标志位
在这里插入图片描述
发送数据函数:

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

代码实现单片机发送数据到PC

//向pc发送一个字节
void USART1_SendByte(uint8_t a){
    USART_SendData(USART1, a);  // 发送字符
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);// 等待移位寄存器为空
	
}
//向pc发送字节数组,
void USART1_Send_Bytes(uint8_t *a, uint8_t len){
	for(uint8_t i = 0; i < len; i++){
		USART1_SendByte(a[i]);  // 发送字符
	}
	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);// 等待移位寄存器为空
	USART_ClearFlag(USART1, USART_FLAG_TC);  // 清除TC标志位
}

//发送字符串到pc
void USART1_Send_String(char *str){
	while(*str){
		USART1_SendByte((uint8_t)*str++);  // 发送字符	
	}
	//检查TC标志位以确保数据发送完成
	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);// 等待移位寄存器为空
	USART_ClearFlag(USART1, USART_FLAG_TC);  // 清除TC标志位
}

int main(void) {
    // 初始化 USART1 的 TX/RX 引脚
    USART1_TxRx_Init();

    // 初始化 USART1 串口(波特率 115200)
    USART1_Init();
    uint8_t bytes[BYTES_LEN] = {'h','e','l','l','o'};
    while (1) {
       USART1_SendByte('a');
        Delay_S(2);  // 延时 2 秒,避免发送过快
    }
}

注意区分何时用TC和TEX标志位:
在发送多个字节时,每次写入新数据前都需要检查 TXE 标志位,确保发送寄存器为空。
在发送完所有数据后,如果需要确保所有数据都已成功发送完毕(包括移位寄存器中的数据),可以检查 TC 标志位。

PC发送数据至单片机

PC端控制PC13指示灯

RXNE (Receive Data Register Not Empty,接收数据寄存器非空),即接收数据寄存器非空标志位,用于指示 USART 已经接收到数据,并且数据已经存入接收数据寄存器,等待被读取。

当RXNE 标志位为RESET(0)时,表示接收数据寄存器为空,此时不能进行数据读取。

当RXNE 标志位为SET(1)时,表示接收数据寄存器非空有数据,此时可以进行数据读取。
还需要了解使用这个函数

uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

其形参USARTx没什么可说的,指定要读取数据的 USART 外设(如 USART1、USART2)。

其返回值就是USART接收到的一个数据帧的数据,虽然返回值是16位无符号数,但实际上只有低9位有效:8 位的数据位 + 可能存在的校验位。

假如不使用校验位,那就是只有低8位有效,表示有效数据位。

注意:

如果 USART 采用 8 位数据格式,则返回值的低 8 位为有效数据。
如果 USART 采用 9 位数据格式,则返回值的低 9 位有效(需手动处理)。
读取 USART_ReceiveData() 会自动设置 RXNE 标志位为RESET,表示接收数据寄存器已空。

最后代码实现:

uint8_t USART1_ReceiveByte(void) {
    while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);  // 等待接收数据寄存器非空
    return (uint8_t)USART_ReceiveData(USART1);
}

void LED_Control(uint8_t byteRcvd) {
    switch (byteRcvd) {
    case '0':
        GPIO_SetBits(GPIOC, GPIO_Pin_13);    // LED 熄灭
        break;
    case '1':
        GPIO_ResetBits(GPIOC, GPIO_Pin_13);    // LED 亮
        break;
    default:
        break;
    }
}

int main(void) {
    // 初始化 USART1 的 TX/RX 引脚
    USART1_TxRx_Init();

    // 初始化 USART1 串口(波特率 115200)
    USART1_Init();
	LED_Init();
    while (1) {
        uint8_t byteRcvd = USART1_ReceiveByte();  // 轮询接收PC发送的指令
        LED_Control(byteRcvd);  // 处理指令,控制LED
    }
   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值