USART串口通信

通信接口

通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统

通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

通信协议的最基本的配置如下表

在这里插入图片描述

双工

全双工:通信双方能够同时进行双向通信

半双工:通信双方不能同时接收和发送数据

单工:只能接收或只能发送

时钟

同步:有时钟线,通信双方根据时钟线的频率接收和发送数据

异步:没有时钟线,只能通信双方约定使用相同的频率接收和发送数据

电平

单端:根据两端的电压确定数据,例如STM32就是0V发送数据0,3.3V发送数据1

差分:根据差分线的电压差确定数据

设备

点对点:设备一对一发送和接收

多设备:可以挂载多个设备,通过地址来与不同的设备通信

串口通信

串口通信介绍

串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信

单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力

在这里插入图片描述

串口硬件电路

  1. 简单双向串口通信有两根通信线(发送端TX和接收端RX)
  2. TX与RX要交叉连接
  3. 当只需单向的数据传输时,可以只接一根通信线
  4. 当电平标准不一致时,需要加电平转换芯片
    在这里插入图片描述

串口常用电平标准

电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

  1. TTL电平:+3.3V或+5V表示1,0V表示0
  2. RS232电平:-3-15V表示1,+3+15V表示0
  3. RS485电平:两线压差+2+6V表示1,-2-6V表示0(差分信号)

串口参数及时序

波特率:串口通信的速率
波特率 = fPCLK2\1 / (16 * DIV)

起始位:标志一个数据帧的开始,固定为低电平
空闲状态时一直处于高电平,发送端来一个下降沿,接收端就知道要开始接收数据了

数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行

校验位:用于数据验证,根据数据位计算得来
奇校验:当数据位中的1是偶数位时,校验位为1,将1的个数补位奇数个
偶校验:当数据位中的1是寄数位时,校验位为1,将1的个数补位偶数个

无校验:数据帧就只有8位数据

停止位:用于数据帧间隔,固定为高电平

在这里插入图片描述

实际波形

在这里插入图片描述

STM32的USART

USART简介

USART(Universal Synchronous/Asynchronous
Receiver/Transmitter)通用同步/异步收发器
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
自带波特率发生器,最高达4.5Mbits/s 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
可选校验位(无校验/奇校验/偶校验) 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN STM32F103C8T6
USART资源: USART1、 USART2、 USART3

USART框图

在这里插入图片描述

引脚部分

  1. TX:发送引脚

  2. RX:接收引脚

  3. SW_RX、IRDA_OUT、IRDA_IN:智能卡和IrDA通信的引脚

  4. nRTS:请求发送,告诉发送设备我现在能不能接收数据

  5. nCTS:接受其他设备的nRTS信号,根据接收设备的信号判断现在能不能发送数据

注意:n表示低电平有效

寄存器及控制器

  1. 发送数据寄存器、接收数据寄存器:这两个寄存器占用同一个地址。在程序中只表现为一个寄存器,也就是DR寄存器,但是实际硬件中是分成了两个寄存器。TDR是只写的,RDR是只读的
  2. 发送移位寄存器:把一个字节的数据一位一位的移出去。当我们给TDR(发送数据寄存器)写入数据时,会检测移位寄存器是不是有数据正在移位,如果没有,则会自动把写入TDR的数据全部放到移位寄存器上准备发送数据。当数据从TDR移动到移位寄存器时,会置一个标志位:TXE;当TXE=1,表示TDR为空了,我们就可以写入下一个要发送的数据了
  3. 接收移位寄存器:与发送移位寄存器的原理是类似的。数据从RX引脚通向接受移位寄存器,一位一位的读取RX电平,每一次读的数据放在最高位,然后向右移,移位八次后就接收到了一个字节,再将这个字节整个放到RDR(接收数据寄存器)中。从移位寄存器转移数据到RDR的过程中会置一个标志位:RXNE;当RXNE=1时,说明RDR中已经接收到了数据,可以读取数据了
  4. 发送器控制、接收器控制:根据波特率分别控制发送移位寄存器和接受移位寄存器进行移位的操作
  5. 中断控制:查看SR(状态寄存器)的各种标志位,其中TXE(发送寄存器空)和RXNE(接收寄存器非空)很重要;还控制中断是否通信NVIC
  6. 波特率发生器部分:APB时钟进行分频,得到发送和接受移位的时钟
  7. 硬件数据流控:如果发送设备发的太快,接收设备来不及处理,会造成数据丢失或被覆盖,该控制器就是避免这个问题的
  8. SCLK控制:用于产生同步的时钟信号。配合发送移位寄存器输出,发送移位寄存器每移位一次,同步时钟电平就跳变一个周期
  9. 唤醒单元:实现串口挂载多设备。可以在USART地址这个地方给串口分配一个地址,当发送指定地址时,此设备唤醒开始工作

USART基本结构

在这里插入图片描述

USART数据帧

设置字长

在这里插入图片描述

设置停止位

一个停止位的时长与一个数据位的时长一致
在这里插入图片描述

USART起始位侦测

在这里插入图片描述
当USART的电路检测到下降沿后,如果没有噪声,后面开始就应该是起始位了。

为了检测是否存在噪声导致误判,USART会以波特率的16倍频率进行采样,也就是在一位的时间里进行16次采样,开始起始位侦测

  1. 如果3个采样点都为’0’(在第3、5、7位的第一次采样,和在第8、9、10的第二次采样都为’0’), 则确认收到起始位,这时设置RXNE标志位,如果RXNEIE=1,则产生中断。
  2. 如果两次3个采样点上最少有2个是’0’(第3、5、7位的采样点和第8、9、10位的采样点),那么起始 位仍然是有效的,但是会设置NE噪声标志位。如果不能满足这个条件,则中止起始位的侦测过 程,接收器会回到空闲状态(不设置标志位)。

USART检测噪声的数据采样

在这里插入图片描述
通过USART的起始位侦测后,就开始接收数据了,跟起始位侦测一样,也是以波特率的16倍频率进行采样,也就是在一个数据位的时间里进行16次采样

根据第8、9、10次的采样数据确定接收到的数据

  1. 当三次采样都为0或1时,可以确定接收的数据就是0或1
  2. 当三次采样有两个0或两个1时,则以出现次数多的电平为准,同时会设置NE噪声标志位

波特率计算公式

发送器和接收器的波特率由波特率寄存器BRR里的DIV确定

计算公式:波特率 = fPCLK2\1 / (16 * DIV)

  1. fPCLK2\1:APB1或APB2的时钟频率

  2. DIV:分频系数,可以是小数,小数部分最多4位

  3. 16:数据采样的频率,每一位数据都要进行16次采样

例如要配置USART1为9600的波特率,计算DIV(分频系数):

USART1属于APB2总线的外设,所以fPCLK2=72MHz

9600=72MHz / (16 * DIV)

DIV = 72MHz / 9600 / 16

DIV = 468.75

在这里插入图片描述

CH340原理图

在这里插入图片描述

USART库函数

USART_InitTypeDef

typedef struct
{
  uint32_t USART_BaudRate;            /*!< 波特率 */
  uint16_t USART_WordLength;          /*!< 帧中发送或接收的数据位数。 */
  uint16_t USART_StopBits;            /*!< 停止位的长度 */
  uint16_t USART_Parity;              /*!< 校验模式 */
  uint16_t USART_Mode;                /*!< 指定是否启用或禁用接收或发送模式 */
  uint16_t USART_HardwareFlowControl; /*!< 开启或关闭硬件流控制 */
} USART_InitTypeDef;

/* USART_BaudRate */
直接写需要的波特率就好了,函数会自动根据传入的波特率来计算BRR的值

/* USART_WordLength */
USART_WordLength_8b		// 一帧发送8位数据
USART_WordLength_9b		// 一帧发送9位数据

/* USART_StopBits */
USART_StopBits_1	// 一位个数据位长的停止位
USART_StopBits_0_5	// 0.5个数据位长的停止位
USART_StopBits_2	// 2个数据位长的停止位
USART_StopBits_1_5	// 1.5个数据位长的停止位

/* USART_Parity */
USART_Parity_No		// 无校验
USART_Parity_Even	// 奇校验
USART_Parity_Odd	// 偶校验

/* USART_Mode */
USART_Mode_Rx	// 开启发送数据
USART_Mode_Tx	// 开启接收数据
// 如果即开启发送数据又开启接收数据,可以用 "|" 连接

/* USART_HardwareFlowControl */
USART_HardwareFlowControl_None		// 不开启硬件流控制
USART_HardwareFlowControl_RTS		// 告诉对方是否可以发送数据了
USART_HardwareFlowControl_CTS		// 判断对方是否准备好接收数据了
USART_HardwareFlowControl_RTS_CTS	// RTS和CTS都开启

函数

// 复位USART的配置
void USART_DeInit(USART_TypeDef* USARTx);

// 根据USART_InitTypeDef中的指定参数初始化USART
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

// 用默认值填充每个USART_InitTypeDef成员。
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);

// 根据usart_clockkinitstruct中的指定参数初始化USARTx外设时钟。配置同步时钟输出的
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);

// 用默认值填充每个usart_clockkinitstruct成员。配置同步时钟输出的
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);

// 开启或关闭USART
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);

// 开启或关闭USART的中断
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);

// 开启或关闭USART的DMA功能
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);

// 设置USART节点的地址,挂载多个USART时使用
void USART_SetAddress(USART_TypeDef* USARTx, uint8_t USART_Address);

// 选择USART唤醒方法
void USART_WakeUpConfig(USART_TypeDef* USARTx, uint16_t USART_WakeUp);

// 开启或关闭USART的静音模式
void USART_ReceiverWakeUpCmd(USART_TypeDef* USARTx, FunctionalState NewState);

// 设置USART LIN中断检测长度
void USART_LINBreakDetectLengthConfig(USART_TypeDef* USARTx, uint16_t USART_LINBreakDetectLength);

// 启用或禁用USART LIN模式
void USART_LINCmd(USART_TypeDef* USARTx, FunctionalState NewState);

// 发送数据。写USART的DR寄存器
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

// 接收数据。读USART的DR寄存器
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

/* 智能卡、IrDA的函数用不上 */ 
void USART_SendBreak(USART_TypeDef* USARTx);
void USART_SetGuardTime(USART_TypeDef* USARTx, uint8_t USART_GuardTime);
void USART_SetPrescaler(USART_TypeDef* USARTx, uint8_t USART_Prescaler);
void USART_SmartCardCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_SmartCardNACKCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_HalfDuplexCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_OverSampling8Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_OneBitMethodCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_IrDAConfig(USART_TypeDef* USARTx, uint16_t USART_IrDAMode);
void USART_IrDACmd(USART_TypeDef* USARTx, FunctionalState NewState);

// 获取指定的USART相关标志位的状态
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);

// 清除指定的USART相关标志位
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);

// 获取指定的USART相关中断标志位的状态
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);

// 清除指定的USART相关的中断标志位
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

案例1

串口发送数据

用到函数

// 根据USART_InitTypeDef中的指定参数初始化USART
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

// 开启或关闭USART
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);

// 发送数据。写USART的DR寄存器
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

// 获取指定的USART相关标志位的状态
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);

接线图

在这里插入图片描述

直接使用串口发送

示例代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
  * @brief  配置USART1的发送功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启USART和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 根据引脚图可知USART1的TX端是PA9
	// 初始化PA9为复用推挽输出
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 配置USART1的发送数据
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;											// 波特率9600
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	// 不使用硬件流控制
	USART_InitStruct.USART_Mode = USART_Mode_Tx;									// 开启USART的发送
	USART_InitStruct.USART_Parity = USART_Parity_No;								// 无校验位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;								// 一位停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;						// 一帧8个数据位
	USART_Init(USART1, &USART_InitStruct);
	
	// 启动USART1
	USART_Cmd(USART1, ENABLE);
}

/**
  * @brief  发送字节
  * @param  Data: 要发送的字节
  * @retval 无
  */
void Serial_SendByte(uint8_t Data)
{
	// 写入数据到发送数据寄存器中
	USART_SendData(USART1, Data);
	// 获取USART的TXE标志位是否为1。TXE=1说明发送数据寄存器已经将数据转移到发送移位寄存器了
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    // 通过手册可知:对USART_DR的写操作,硬件将该位清零。所以不用手动清除
    // USART_ClearFlag(USART1, USART_FLAG_TXE);
}

int main()
{
	Serial_Init();
	while(1)
	{
		uint8_t Data = 0x41;
        // 循环打印A~Z
		for(uint8_t i = 0;i < 26; i++)
		{
			Serial_SendByte(Data++);
			Delay_s(1);
		}
		
	}
}

通过printf函数发送

因为printf默认打印输出到屏幕,所以需要重定向printf函数,将printf函数打印的东西输出到串口

勾选Use MicroLIB

在这里插入图片描述

包含stdio头文件

#include <stdio.h>

重写fput函数

因为printf函数是通过fput函数实现的,所以重写fput函数就是重定向printf函数

// 重定向fput函数,通过串口输出
int fput(int ch, FILE *f)
{
	// Serial_SendByte是自己写的输出到串口的函数,参数是字节
	Serial_SendByte(ch);
	return ch;
}

最后就可以直接使用printf函数打印串口输出的数据了

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include <stdio.h>

/**
  * @brief  配置USART1的发送功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启USART和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 根据引脚图可知USART1的TX端是PA9
	// 初始化PA9为复用推挽输出
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 配置USART1的发送数据
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;											// 波特率9600
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	// 不使用硬件流控制
	USART_InitStruct.USART_Mode = USART_Mode_Tx;									// 开启USART的发送
	USART_InitStruct.USART_Parity = USART_Parity_No;								// 无校验位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;								// 一位停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;						// 一帧8个数据位
	USART_Init(USART1, &USART_InitStruct);
	
	// 启动USART1
	USART_Cmd(USART1, ENABLE);
}

/**
  * @brief  发送字节
  * @param  Data: 要发送的字节
  * @retval 无
  */
void Serial_SendByte(uint8_t Data)
{
	// 写入数据到发送数据寄存器中
	USART_SendData(USART1, Data);
	// 获取USART的TXE标志位是否为1。TXE=1说明发送数据寄存器已经将数据转移到发送移位寄存器了
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 重定向fput函数,通过串口输出
int fputc(int ch, FILE *f) // 这个是fputc的函数原型,不能变
{
	// Serial_SendByte是自己写的输出到串口的函数
	Serial_SendByte(ch);
	return ch;
}

int main()
{
	Serial_Init();
    
    // 直接使用printf函数打印数据到串口
	printf("Send=%d\r\n", 666);
	while(1)
	{
		
	}
}

使用sprintf函数重定向不同的串口

printf函数重定向串口1后,串口2、串口3等等就用不了printf函数打印数据了。因此需要使用到sprintf函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include <stdio.h>

/**
  * @brief  配置USART1的发送功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启USART和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 根据引脚图可知USART1的TX端是PA9
	// 初始化PA9为复用推挽输出
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 配置USART1的发送数据
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;											// 波特率9600
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	// 不使用硬件流控制
	USART_InitStruct.USART_Mode = USART_Mode_Tx;									// 开启USART的发送
	USART_InitStruct.USART_Parity = USART_Parity_No;								// 无校验位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;								// 一位停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;						// 一帧8个数据位
	USART_Init(USART1, &USART_InitStruct);
	
	// 启动USART1
	USART_Cmd(USART1, ENABLE);
}

/**
  * @brief  发送字节
  * @param  Data: 要发送的字节
  * @retval 无
  */
void Serial_SendByte(uint8_t Data)
{
	// 写入数据到发送数据寄存器中
	USART_SendData(USART1, Data);
	// 获取USART的TXE标志位是否为1。TXE=1说明发送数据寄存器已经将数据转移到发送移位寄存器了
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

/**
  * @brief  发送字符串
  * @param  string: 要发送的字符串
  * @retval 无
  */
void Serial_SendString(char *string)
{
	for(uint8_t i = 0;string[i] != 0; i++)
	{
		Serial_SendByte(string[i]);
	}
}

// 重定向fput函数,通过串口输出
int fputc(int ch, FILE *f) // 这个是fputc的函数原型,不能变
{
	// Serial_SendByte是自己写的输出到串口的函数
	Serial_SendByte(ch);
	return ch;
}

int main()
{
	Serial_Init();
	// 定义一个足够长的字符串
	char string[100];
	// 通过sprintf将printf函数中的数据打印到定义的字符串String中
	sprintf(string, "num=%d\r\n", 777);
	// 将字符串发送出去
	Serial_SendString(string);
	while(1)
	{
		
	}
}

封装sprintf

添加头文件stdarg

#include <stdarg.h>v

封装代码

void Serial_printf(char *format, ...)
{
	char string[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(string, format, arg);
	va_end(arg);
	Serial_SendString(string);
}

示例代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include <stdio.h>
#include <stdarg.h>

/**
  * @brief  配置USART1的发送功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启USART和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 根据引脚图可知USART1的TX端是PA9
	// 初始化PA9为复用推挽输出
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 配置USART1的发送数据
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;											// 波特率9600
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	// 不使用硬件流控制
	USART_InitStruct.USART_Mode = USART_Mode_Tx;									// 开启USART的发送
	USART_InitStruct.USART_Parity = USART_Parity_No;								// 无校验位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;								// 一位停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;						// 一帧8个数据位
	USART_Init(USART1, &USART_InitStruct);
	
	// 启动USART1
	USART_Cmd(USART1, ENABLE);
}

/**
  * @brief  发送字节
  * @param  Data: 要发送的字节
  * @retval 无
  */
void Serial_SendByte(uint8_t Data)
{
	// 写入数据到发送数据寄存器中
	USART_SendData(USART1, Data);
	// 获取USART的TXE标志位是否为1。TXE=1说明发送数据寄存器已经将数据转移到发送移位寄存器了
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

/**
  * @brief  发送字符串
  * @param  string: 要发送的字符串
  * @retval 无
  */
void Serial_SendString(char *string)
{
	for(uint8_t i = 0;string[i] != 0; i++)
	{
		Serial_SendByte(string[i]);
	}
}

// 重定向fput函数,通过串口输出
int fputc(int ch, FILE *f) // 这个是fputc的函数原型,不能变
{
	// Serial_SendByte是自己写的输出到串口的函数
	Serial_SendByte(ch);
	return ch;
}

void Serial_printf(char *format, ...)
{
	char string[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(string, format, arg);
	va_end(arg);
	Serial_SendString(string);
}

int main()
{
	Serial_Init();
	Serial_printf("num=%d\r\n", 888);
	while(1)
	{
		
	}
}

utf-8格式下使用printf打印中文字符串写不报错

在这里插入图片描述

串口接收和发送数据

用到的函数

// 开启或关闭USART的中断
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);

// 接收数据。读USART的DR寄存器
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

// 获取指定的USART相关中断标志位的状态
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);

// 清除指定的USART相关的中断标志位
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

接线图

在这里插入图片描述

使用查询的方式

示例代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include <stdio.h>
#include <stdarg.h>

/**
  * @brief  配置USART1的发送和接收功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启USART和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 根据引脚图可知USART1的TX端是PA9
	// 初始化PA9为复用推挽输出
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 根据引脚图可知USART1的RX端是PA10
	// 初始化PA10为上拉输入
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 配置USART1的发送数据
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;											// 波特率9600
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	// 不使用硬件流控制
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;					// 开启USART的发送和接收功能
	USART_InitStruct.USART_Parity = USART_Parity_No;								// 无校验位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;								// 一位停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;						// 一帧8个数据位
	USART_Init(USART1, &USART_InitStruct);
	
	// 启动USART1
	USART_Cmd(USART1, ENABLE);
}

int main()
{
	Serial_Init();
	OLED_Init();
	while(1)
	{
		// RXNE=1:读数据寄存器非空
		if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
		{
			// 获取USART_DR寄存器的值
			OLED_ShowHexNum(1,1,USART_ReceiveData(USART1),5);
		}
	}
}

使用中断的方式

#include "stm32f10x.h"                  // Device header
#include "OLED.h"

// 接收串口发送来的数据
uint16_t receive;

/**
  * @brief  配置USART1的发送和接收功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启USART和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 根据引脚图可知USART1的TX端是PA9
	// 初始化PA9为复用推挽输出
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 根据引脚图可知USART1的RX端是PA10
	// 初始化PA10为上拉输入
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 配置USART1的发送和接收数据
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;											// 波特率9600
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	// 不使用硬件流控制
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;					// 开启USART的发送和接收功能
	USART_InitStruct.USART_Parity = USART_Parity_No;								// 无校验位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;								// 一位停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;						// 一帧8个数据位
	USART_Init(USART1, &USART_InitStruct);
	
	// 开启USART1的RXNE的中断通道
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	// NVIC优先级分组2
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	// 联通USART1的NVIC的通道
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;			// 选择USART1的中断通道
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;			// 开启通道
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;	// 抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;			// 响应优先级
	NVIC_Init(&NVIC_InitStruct);
	
	// 启动USART1
	USART_Cmd(USART1, ENABLE);
}

int main()
{
	Serial_Init();
	OLED_Init();
	while(1)
	{
		OLED_ShowHexNum(1, 1, receive, 5);
	}
}

// USART1的中断函数
void USART1_IRQHandler()
{
	// 检测USART1的RXNE是否产生中断
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		// 读取USART_DR寄存器的值
		receive = USART_ReceiveData(USART1);
		// 清除中断标志位,在读取USART_DR寄存器的值后硬件会自动清除,所以这条代码可以不写
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

USART串口数据包

首先数据包的作用是把一个个单独的数据给打包起来,方便我们进行多字节的数据通信

数据包的任务,就是把属于同一批的数据进行打包和分割,方便接收方进行识别

HEX数据包

在这里插入图片描述
0xff为包头,0xfe为包尾

固定包长:每一个数据包的包头和包尾之间的字节数是一样的

  1. 只要包头包尾对齐,就可以避免载荷数据与包头包尾混淆

  2. 可以不写包尾,只使用包头

可变包长:每一个数据包的包头和包尾之间的字节数是不一样的

文本数据包

文本数据包和 HEX 数据包,就分别对应了文本模式和 HEX 这两种模式。在 HEX 数据包里面,数据都是以原始的字节数据本身呈现的,而在文本数据包里面,每个字节就经过了一层编码和译码,最终表现出来的,就是文本格式,但实际上,每个文本字符背后,其实都还是一个字节的 HEX 数据。
在这里插入图片描述

文本和HEX数据包比较

HEX 数据包:

  1. 优点:传输最直接,解析数据非常简单,比较适合一些模块发送原始的数据。比如一些使用串口通信的陀螺仪、温湿度传感器。
  2. 缺点:灵活性不足、载荷容易和包头包尾重复。

文本数据包

  1. 优点:数据直观易理解,非常灵活,比较适合一些输入指令进行人机交互的场合,比如蓝牙模块常用的 AT 指令,CNC 和 3D 打印机常用的 G 代码,都是文本数据包的格式。
  2. 缺点:解析效率低。比如发送一个数 100,HEX 数据包就是一个字节 100,完事,文本数据包就得是 3 个字节的字符,‘1’,‘0’,‘0’,收到之后还要把字符转换成数据,才能得到 100。所以说,我们需要根据实际场景来选择和设计数据包格式。

接收数据包

HEX数据包接收

在这里插入图片描述
先定义一个转态位S。当S=0时,等待包头数据;当S=1时接收传输的数据;当S=2时等待包尾数据

等待包头:接收到字节:0xff后,判断接收到了包头数据,令S=1,准备接收数据

接收数据:

  1. 如果是固定4个包长,还需要定义一个变量num来记录接收的字节个数,每接收一个字节num++,当num=4后全部数据接收完成,并保存这四个数据,后面再接收到的其他数据都不会进行保存。令S=2,等待包尾
  2. 如果是可变包长,就不需要定义num记录接收到的字节,只需要对接收到的所以字节进行判断是否等于包尾:0xfe,如果不等于则一直保存接收到的字节,直到接收到0xfe,令S=2

等待包尾:接收到数据0xfe后,判断接收到了包尾,数据包接收完毕,令S=0,重新准备开始接收下一个数据包

文本数据包接收

在这里插入图片描述
文本数据包接收与HEX数据包接收的原理差不多,只不过文本数据包的包尾习惯性使用\r\n,所以需要判断两次包尾才能确定数据接收结束

案例2

HEX数据包的收发

固定包长

接线图

在这里插入图片描述

示例代码
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

// 接收串口发送来的数据
uint8_t Byte;

// 接收标志位
uint8_t Receive_Flag;

// 保存接收到的数据包
char Receive_Data[4];

// 要发送出去的数据包,可以自定义数据
char Send_Data[4] = {0x11, 0x22, 0x33, 0x44};

// 初始化按键
void Key_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	// PB1上拉输入
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}

// 判断按键是否被按下
uint8_t Key_Inspect()
{
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET)
	{
		// 消抖
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET);
		Delay_ms(20);
		
		// 返回1说明按键被按下
		return 1;
	}
	return 0;
}

/**
  * @brief  配置USART1的发送和接收功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启GPIOA和USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 初始化PA9,USART1的Tx发送端
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 初始化PA9,USART1的Rx接收端
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 初始化USART1
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;
	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);
	
	// 开启USART1的RXNE中断
	USART_ITConfig(USART1,USART_IT_RXNE, ENABLE);
	
	// NVIC优先级分组2
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	// 初始化USART1的NVIC通道
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStruct);
	
	// 开启USART1
	USART_Cmd(USART1, ENABLE);
}

// 发送一个字节
void Send_Byte(uint8_t ByteData)
{
	// 往USART_DR中写入数据
	USART_SendData(USART1, ByteData);
	// TXE=RESET表示发送数据寄存器中的数据还没有转移到发送移位寄存器中
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 显示保存的数据包
void Receive_DataPacket()
{
	// 接收数据包标志位为1说明有新的数据包接收成功了
	if(Receive_Flag == 1)
	{
		// 将标志位清零
		Receive_Flag = 0;
		// 显示数据包中的数据
		for(uint8_t i = 0;i < 4; i++)
		{
			OLED_ShowHexNum(i + 1, 1, Receive_Data[i], 5);
		}
	}
}

// 发送数据包
void Send_DataPacket()
{
	// 包头
	Send_Byte(0xff);
	// 有效载荷
	for(uint8_t i = 0; i < 4; i++)
	{
		// 发送前对数组内的数据自增,可以让每次发送的数据包都不一样
		Send_Byte(Send_Data[i]++);
	}
	// 包尾
	Send_Byte(0xfe);
}

int main()
{
	Serial_Init();
	OLED_Init();
	Key_Init();
	while(1)
	{
		// 显示接收到的数据包
		Receive_DataPacket();
		
		// 如果按键按下则发送数据包
		if(Key_Inspect())
		{
			Send_DataPacket();
		}
	}
}

// USART1的中断函数
void USART1_IRQHandler()
{
	// 记录当前数据包的状态
	static uint8_t Status = 0;
	// 记录保存的字节数据个数
	static uint8_t PData;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		// 读取字节
		Byte = USART_ReceiveData(USART1);
		// Status=0,等待包头0xff
		if(Status == 0 && Byte == 0xff)
		{
			// 接收到了包头,Status置1准备保存数据
			Status = 1;
			// 初始化记录保存的字节个数为0
			PData = 0;
		}
		// Status=1,接收字节
		else if(Status == 1)
		{
			// 将接收到的字节放到数组中保存
			Receive_Data[PData] = Byte;
			// 接收到一个数据就自增一次
			PData++;
			// 固定包长4,收到4个数据后等待包尾,Status=2
			if(PData >=4)
			{
				Status = 2;
			}
		}
		// 等待包尾0xfe
		else if(Status == 2 && Byte == 0xfe)
		{
			// 成功接收到包尾,Status=0,准备接收下一个数据包
			Status = 0;
			// 接收数据包标志位置1,成功接收到了数据
			Receive_Flag = 1;
		}
		
	}
}

文本数据包的收发

可变包长

这里会使用一个的string.h的函数

接线图

在这里插入图片描述

示例代码
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"
#include <string.h>

// 接收串口发送来的数据
uint8_t Byte;

// 接收标志位
uint8_t Receive_Flag;

// 保存接收到的数据包,可变包长,可以把范围给大一点
char Receive_String[100];

// 要发送出去的数据包,可以自定义数据
char Send_String[] = "Hello world";

// 初始化LED在PA1上
void LED_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// PA1推挽输出
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 熄灭LED
void LED_OFF()
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);
}

// 点亮LED
void LED_ON()
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
}

/**
  * @brief  配置USART1的发送和接收功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启GPIOA和USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 初始化PA9,USART1的Tx发送端
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 初始化PA9,USART1的Rx接收端
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 初始化USART1
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;
	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);
	
	// 开启USART1的RXNE中断
	USART_ITConfig(USART1,USART_IT_RXNE, ENABLE);
	
	// NVIC优先级分组2
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	// 初始化USART1的NVIC通道
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStruct);
	
	// 开启USART1
	USART_Cmd(USART1, ENABLE);
}

// 发送一个字节
void Send_Byte(uint8_t ByteData)
{
	// 往USART_DR中写入数据
	USART_SendData(USART1, ByteData);
	// TXE=RESET表示发送数据寄存器中的数据还没有转移到发送移位寄存器中
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 发送数据包
void Send_DataPacket()
{
	// 包头
	Send_Byte('@');
	// 有效载荷
	for(uint8_t i = 0; Send_String[i] != '\0'; i++)
	{
		// 发送前对数组内的数据自增,可以让每次发送的数据包都不一样
		Send_Byte(Send_String[i]);
	}
	// 包尾
	Send_Byte('\r');
	Send_Byte('\n');
}

int main()
{
	Serial_Init();
	OLED_Init();
	LED_Init();
	Send_DataPacket();
	while(1)
	{
		if(Receive_Flag)
		{
			// 标志位清零
			Receive_Flag = 0;
			/*将收到的数据包与预设的指令对比,以此决定将要执行的操作*/
			// strcmp函数用于比较字符串的大小,返回值为0时两个字符串相等,需要添加头文件:string.h
			if(strcmp(Receive_String, "LED_ON") == 0)
			{
				LED_ON();
				OLED_ShowString(1, 1, "                ");
				OLED_ShowString(1,1,"LED_ON");
			}
			else if(strcmp(Receive_String, "LED_OFF") == 0)
			{
				LED_OFF();
				OLED_ShowString(1, 1, "                ");
				OLED_ShowString(1,1,"LED_OFF");
			}
			else
			{
				OLED_ShowString(1, 1, "                ");
				OLED_ShowString(1,1,"ERROR");
			}
		}
	}
}

// USART1的中断函数
void USART1_IRQHandler()
{
	// 记录当前数据包的状态
	static uint8_t Status = 0;
	// 记录保存的字节数据个数
	static uint8_t PData;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		// 读取字节
		Byte = USART_ReceiveData(USART1);
		// Status=0,等待包头字符@
		if(Status == 0 && Byte == '@')
		{
			// 接收到了包头,Status置1准备保存数据
			Status = 1;
			// 初始化记录保存的字节个数为0
			PData = 0;
		}
		// Status=1,接收字节
		else if(Status == 1)
		{
			// 判断是否出现第一个包尾
			if(Byte == '\r')
			{
				Status = 2;
			}
			else
			{
				// 将接收到的字节放到数组中保存
				Receive_String[PData] = Byte;
				// 接收到一个数据就自增一次
				PData++;
			}
		}
		// 等待第二个包尾
		else if(Status == 2 && Byte == '\n')
		{
			// 成功接收到包尾,Status=0,准备接收下一个数据包
			Status = 0;
			// 接收数据包标志位置1,成功接收到了数据
			Receive_Flag = 1;
			// 字符串数据需要在后面加上'\0'结束符
			Receive_String[PData] = '\0';
		}
		
	}
}
USART (Universal Synchronous/Asynchronous Receiver/Transmitter) 是一种通用的串行通信接口,常用于单片机与外部设备之间的通信。USART 可以以同步模式或异步模式进行数据传输。 在使用 USART 进行串口通信时,需要先设置 USART 的通信参数,包括波特率、数据位数、停止位数、奇偶校验等。然后将数据通过 USART 发送出去,或者从 USART 接收数据。 在发送数据时,需要将数据写入 USART 的数据寄存器中,USART 会自动将数据进行传输。在接收数据时,需要读取 USART 的数据寄存器,USART 会自动将接收到的数据存储在数据寄存器中。 通常情况下,我们可以使用 printf 函数来向串口发送数据,或使用 scanf 函数从串口接收数据。在使用这些函数之前,需要先初始化串口的通信参数。 下面是一个使用 USART 进行串口通信的例子,使用的是 STM32 单片机的 HAL 库: ```c #include "stm32f1xx_hal.h" UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); char buffer[20]; while (1) { printf("Enter a string: "); scanf("%s", buffer); printf("You entered: %s\n", buffer); HAL_Delay(1000); } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; 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; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } void Error_Handler(void) { } ``` 在上面的例子中,我们先通过 MX_USART1_UART_Init 函数初始化了 USART1 的通信参数,然后在 while 循环中使用 printf 和 scanf 函数进行串口通信。注意,在使用 printf 函数前,需要在头文件中包含 stdio.h 和 usart.h 文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值