USART串口协议

USART串口协议

文章目录

1. 通信接口

  • 通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
  • 通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

stm32中的通信协议(只列出最典型的参数)

名称 引脚 双工 时钟 电平 设备 说明
USART TX、RX 全双工 异步 单端 点对点 TX是数据发送引脚,TP是数据接收引脚
I2C SCL、SDA 半双工 同步 单端 多设备 SCL是时钟,SDA是数据
SPI SCLK、MOSI、MISO、CS 全双工 同步 单端 多设备 MOSI主机输出数据脚,MISO主机输入数据脚,CS片选,用于指定通信的对象
CAN CAN_H、CAN_L 半双工 异步 差分 多设备 差分数据脚,用两个数据表示一个差分数据
USB DP、DM 半双工 异步 差分 点对点 D+,D-,也是一对差分数据脚

全双工:就是指通信双方能够同时进行双向通信,一般来说全双工的通信都有两根通信线,表中的两个是合成一根的

单工:是指数据只能从一个设备到另一个设备,而不能反着来

同步:有单独的时钟线,可以在时钟信号的指引下进行采样

异步:没有时钟线,需要双方约定一个采样频率,且还要加一些帧头帧率等,进行采样位置对齐

单端:引脚的高低电平都是对GND的电压差,所以单端通信的双方需要共地,就是把GND接在一起

差分:靠两个引脚的电压差来传输信号的,通信的时候可以不需要GND,USB有些地方需要单端信号。优点:使用差分信号可以极大地提高抗干扰特性,所以差分信号一般传输信号和距离都会非常高

2.串口通信

  • 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
  • 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力

串口转USB为例:

2.1硬件电路

  • 简单双向串口通信有两根通信线(发送端TX和接收端RX)
  • TX与RX要交叉连接
  • 当只需单向的数据传输时,可以只接一根通信线
  • 当电平标准不一致时,需要加电平转换芯片

2.2电平标准

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

  • TTL电平:+3.3V或+5V表示1,0V表示0

  • RS232电平:-3~-15V表示1,+3~+15V表示0

  • RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号),该电平可实现远距离通信

2.3串口参数及时序(软件部分)

  • 波特率:串口通信的速率,每秒传码元的个数,单位是码元每秒/s,或者直接叫波特(Baud)(发送和接收必须约定好速率)
  • 起始位:标志一个数据帧的开始,固定为低电平
  • 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
  • 校验位:用于数据验证,根据数据位计算得来
  • 停止位:用于数据帧间隔,固定为高电平

串口时序

总结:TX引脚定时输出翻转的高低电平,RX引脚定时读取引脚的高低电平

字节数据的传递:每个字节的数据+起始位+停止位+可选校验位,打包成数据帧,依次输出在TX引脚,另一端RX引脚依次接收

3.USART串口外设

3.1串口外设

  • 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

硬件控制流:例A向B发送数据,A发的太快了,如果B没有硬件流控制,那么B只有抛弃新数据或覆盖原数据。如果有硬件流控制,在硬件电路上,会多出一根线。如果B没有准备好,就置高电平,如果B准备好了就置低电平,A在接收到B的反馈后会做出反应

3.2USART框图

3.3USART基本结构

3.4数据帧

4.输入电路

4.1起始位侦测

4.2数据采样

5.波特率发生器

  • 发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
  • 计算公式:波特率 = f PCLK2/1 f_{\text{PCLK2/1}} fPCLK2/1/ (16 * DIV)

6.相关函数介绍

没解释

c 复制代码
void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);

用来配置同步时钟输出的,包括时钟是不是要输出,时钟的极性相位等参数

c 复制代码
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);
c 复制代码
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);//开启RXNE标志位的通道
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);//开启USART到DMA的触发通道
c 复制代码
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);//发送数据
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);//接收数据
c 复制代码
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);//获取标志位状态
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);

7.串口发送

7.1接线图

7.2代码编写

7.2.1主程序代码main.c
c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
//#include "OLED_Font.h"
#include "Serial.h"


int main(void){
	
	OLED_Init();
	Serial_Init();
	
	Serial_SendByte(0x41);
	uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};//定义一个数组
	Serial_SendArray(MyArray, 4);
	Serial_SendString("helloWorld!\n");
	Serial_SendNumber(123456,6);
	//方法1:重定向
	printf("Num2=%d\r\n", 222);
	//需要重定向fputc函数,并在工程选项里勾选Use MicroLIB
	/*方法2:使用sprintf打印到字符数组,再用串口发送字符数组,此方法打印到字符数组,之后想怎么处理都可以,可在多处使用*/
	char String[100];					//定义字符数组
	sprintf(String, "\r\nNum3=%d", 333);//使用sprintf,把格式化字符串打印到字符数组
	Serial_SendString(String);			//串口发送字符数组(字符串)
	
	/*方法3:将sprintf函数封装起来,实现专用的printf,此方法就是把方法2封装起来,更加简洁实用,可在多处使用*/
	Serial_Printf("\r\nNum4=%d", 444);	//串口打印字符串,使用自己封装的函数实现printf的效果
	Serial_Printf("\r\n");
	
	while(1){
	}
}
7.2.2函数定义Serial.c
c 复制代码
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

void Serial_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA	,ENABLE);
	
	//GPIO初始化,输出只需要初始化一个
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率(我们给出函数会自己计算)
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制(直接复制参数名称,开启代码提示CTRL+ALT+空格)
	USART_InitStructure.USART_Mode = USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;//检验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,0.5、1、1.5、2
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长,可选8或9位
	
	USART_Init(USART1,&USART_InitStructure);
	
	//开启
	USART_Cmd(USART1,ENABLE);
}

//发送一个Byte
void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	//发送数据寄存器空标志位,等待TXE置1,标志位不需要手动清除
															//USART_FLAG_TXE 是发送数据寄存器空标志位(Transmit data register empty)。当该标志位被置 1 时,表示 USART 的发送数据寄存器为空,可以继续写入新的数据;
														//当该标志位为 0(RESET)时,表示发送数据寄存器仍有数据,需要等待。
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

//遍历数组,并发送
void Serial_SendArray(uint8_t *Array,uint16_t Length){
	uint16_t i;
	
	for(i=0;i<Length;i++){
		Serial_SendByte(Array[i]);
	}
}

//遍历字符串,并发送
void Serial_SendString(char *String){
	uint8_t i;
	for(i=0;String[i] != '\0';i++){
		Serial_SendByte(String[i]);
	}
}

//拆分多位数
uint32_t Serial_Pow(uint32_t x,uint32_t y){
	uint32_t Result = 1;
	while(y--){
		Result = Result*x;
	}
	return Result;
}

//分别发送拆分的数
void Serial_SendNumber(uint32_t Number,uint8_t Length){
	uint8_t i;
	for(i=0;i<Length;i++){
		Serial_SendByte(Number/Serial_Pow(10,Length-i-1)%10+'0');/*在串口通信中,通常需要发送字符的 ASCII 码。
																	数字 0 - 9 的 ASCII 码是连续的,字符 '0' 的 ASCII 码值为 48。
																	将数字加上 '0' 可以将其转换为对应的 ASCII 码字符。例如:
																	数字 1 加上 '0' 得到字符 '1',其 ASCII 码值为 49。
																	数字 2 加上 '0' 得到字符 '2',其 ASCII 码值为 50。
																	数字 3 加上 '0' 得到字符 '3',其 ASCII 码值为 51。
																*/
	}
}

//printf的底层代码,显示信息需要经过该函数
int fputc(int ch,FILE *f){
	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);
}	
7.2.3函数定义Serial.h
c 复制代码
#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array,uint16_t Length);
void Serial_SendString(char *String);
uint32_t Serial_Pow(uint32_t x,uint32_t y);
void Serial_SendNumber(uint32_t Number,uint8_t Length);
int fputc(int ch,FILE *f);
void Serial_Printf(char *format,...);

#endif

8.串口发送+接收

8.1接线图

8.2代码编写

8.2.1主程序代码
c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
//#include "OLED_Font.h"
#include "Serial.h"

uint8_t RxData;

int main(void){
	
	OLED_Init();
	Serial_Init();
	OLED_ShowString(1,1,"RxData:");
	
	while(1){
		//不断的判断RXNE标志位
//		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET){
//			RxData = USART_ReceiveData(USART1);
//			OLED_ShowHexNum(1,1,RxData,2);
//		}

		if(Serial_GetRxFlag() == 1){
			RxData = Serial_GetRxData();
			Serial_SendByte(RxData);
			OLED_ShowHexNum(1,8,RxData,2);
	}
}}
8.2.2函数定义
c 复制代码
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxFlag;
uint8_t Serial_RxData;

void Serial_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA	,ENABLE);
	
	//GPIO初始化
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率(我们给出函数会自己计算)
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制(直接复制参数名称,开启代码提示CTRL+ALT+空格)
	USART_InitStructure.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;//检验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,0.5、1、1.5、2
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长,可选8或9位
	
	USART_Init(USART1,&USART_InitStructure);
	
	//开启RXNE到NVIC的通道
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	//开启NVIC的通道
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	//开启
	USART_Cmd(USART1,ENABLE);
}

//标志位
uint8_t Serial_GetRxFlag(void)
{
	if(Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;//返回1,并自动清零标志位
	}
	return 0;
}

//Rx封装
uint8_t Serial_GetRxData(void)
{
		return Serial_RxData;
}

//中断函数
void USART1_IRQHandler(void){
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
	{
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

//发送一个Byte
void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	//发送数据寄存器空标志位,等待TXE置1,标志位不需要手动清除
															//USART_FLAG_TXE 是发送数据寄存器空标志位(Transmit data register empty)。当该标志位被置 1 时,表示 USART 的发送数据寄存器为空,可以继续写入新的数据;
														//当该标志位为 0(RESET)时,表示发送数据寄存器仍有数据,需要等待。
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

//遍历数组,并发送
void Serial_SendArray(uint8_t *Array,uint16_t Length){
	uint16_t i;
	
	for(i=0;i<Length;i++){
		Serial_SendByte(Array[i]);
	}
}

//遍历字符串,并发送
void Serial_SendString(char *String){
	uint8_t i;
	for(i=0;String[i] != '\0';i++){
		Serial_SendByte(String[i]);
	}
}

//拆分多位数
uint32_t Serial_Pow(uint32_t x,uint32_t y){
	uint32_t Result = 1;
	while(y--){
		Result = Result*x;
	}
	return Result;
}

//分别发送拆分的数
void Serial_SendNumber(uint32_t Number,uint8_t Length){
	uint8_t i;
	for(i=0;i<Length;i++){
		Serial_SendByte(Number/Serial_Pow(10,Length-i-1)%10+'0');/*在串口通信中,通常需要发送字符的 ASCII 码。
																	数字 0 - 9 的 ASCII 码是连续的,字符 '0' 的 ASCII 码值为 48。
																	将数字加上 '0' 可以将其转换为对应的 ASCII 码字符。例如:
																	数字 1 加上 '0' 得到字符 '1',其 ASCII 码值为 49。
																	数字 2 加上 '0' 得到字符 '2',其 ASCII 码值为 50。
																	数字 3 加上 '0' 得到字符 '3',其 ASCII 码值为 51。
																*/
	}
}

//printf的底层代码,显示信息需要经过该函数
int fputc(int ch,FILE *f){
	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);
}	
8.2.3函数定义
c 复制代码
#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array,uint16_t Length);
void Serial_SendString(char *String);
uint32_t Serial_Pow(uint32_t x,uint32_t y);
void Serial_SendNumber(uint32_t Number,uint8_t Length);
int fputc(int ch,FILE *f);
void Serial_Printf(char *format,...);
uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);

#endif

n.总结

n.1USART实现步骤

  1. 开启时钟,把需要的USART和GPIO时钟开启
  2. GPIO初始化,把TX配置成复用输出,RX配置成输入
  3. 配置USART,直接使用一个结构体
  4. 如果只需要发送功能,直接开启USART时钟,初始化结束,

(如果需要配置接收的功能那么还需要配置中断,在开启USART之前,再加上ITConfig和NVIC的代码)

相关推荐
FreakStudio1 小时前
开源一款串口舵机驱动扩展板-FreakStudio多米诺系列
单片机·嵌入式·大学生·电子diy
艾格北峰2 小时前
STM32 物联网智能家居 (六) OLED显示设备
arm开发·stm32·单片机·嵌入式硬件·物联网·智能家居
子豪-中国机器人3 小时前
2月17日c语言框架
c语言·开发语言
张胤尘4 小时前
C/C++ | 每日一练 (2)
c语言·c++·面试
醉城夜风~4 小时前
[C语言]指针进阶压轴题
c语言
weixin_535854225 小时前
oppo,汤臣倍健,康冠科技,高途教育25届春招内推
c语言·前端·嵌入式硬件·硬件工程·求职招聘
热爱嵌入式的小许5 小时前
STM32 HAL库&标准库+ESP8266+机智云
stm32·单片机·嵌入式硬件·stm32移植机智云·stm32连接机智云·hal库移植机智云·标准库移植机智云
无际单片机编程6 小时前
面对STM32的庞大体系,如何避免迷失在细节中?
java·stm32·单片机·嵌入式硬件·嵌入式开发
宋康6 小时前
C/C++ 指针避坑20条
c语言·开发语言·c++
仟濹7 小时前
【二分搜索 C/C++】洛谷 P1873 EKO / 砍树
c语言·c++·算法