当前位置: 首页 > article >正文

STM32学习笔记4 --- USART

目录

通信接口1  USART

串口的通信协议

硬件部分:

软件部分:

字节数据的传递:

stm32内部的USART外设

串口发送

串口发送+接收

Hex数据包

文本数据包

数据包的收发流程

串口收发Hex数据包

串口收发文本数据包


通信接口1  USART

USART----- USB转串口 -----电脑

USART是STM32内部集成的硬件外设,usb转串口是连接stm32和电脑的

usb转串口:usb协议  ch340芯片转换  输出TXD RXD(串口协议)

通信的目的:将一个设备的数据传送到另一个设备

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

USART  引脚:TX(数据发送脚)、RX(数据接收脚)

这里全双工,就是指通信双方能够同时进行双向通信

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

无单独时钟线,异步,需双方约定一个采样频率,采样位置对齐

单端通信接GND

差分信号抗干扰

在USART、I2C、SPI、CAN、USB这几个通信协议中,USART、I2C、SPI、USB都属于串口通信,而CAN虽然也用于串行通信,但其通常不被直接归类为传统的串口通信协议,而是作为一种特定的串行通信协议存在。

串口的通信协议

硬件部分:

 串口:单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信

串口接线(硬件电路)

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

软件部分:

发送的一个字节的数据,串口中,每一个字节都装载在一个数据帧里面

每个数据帧都由起始位、数据位和停止位组成

字节数据的传递:

TX引脚输出定时翻转的高低电平

RX引脚定时读取引脚的高低电平

每个字节的数据加上起始位、停止位、可选的校验位,打包为数据帧,依次输出在TX引脚,另一端RX引脚依次接收

stm32内部的USART外设

(按照串口协议来产生和接收高低电平信号)

是串口通信的硬件支持电路

一个字节数据 \Leftrightarrow 波形

STM32F103C8T6 USART资源: USART1(APB2)、 USART2(APB1)、 USART3(APB1)

所以这个跳线帽。是用来选择通信电平的,也是给CH340芯片供电的

数据显示

串口发送

接线:共地

1.开启时钟(USART,GPIO)

2.GPIO初始化,TX复用输出,RX输入(这个程序仅输出可以先只初始化一个pa9  把PA9配置为复用推挽输出,供USART1的TX使用)

3.配置USART(发送:直接开启USART并初始化)(接收:配置中断,ITConfig,NVIC,开启USART)

4.发送函数,接收函数,获取发送和接收的状态,就调用获取标志位的函数

引脚定义表,USART1的TX是PA9,RX是PA10(上拉输入,浮空输入)

\r\n实现换行

//Serial.c
void Serial_Init(void)
{
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//2
    //1
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					
	//2
	USART_InitTypeDef USART_InitStructure;	
				
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//硬件流控制,不需要
	USART_InitStructure.USART_Mode = USART_Mode_Tx;//模式,选择为发送模式
	USART_InitStructure.USART_Parity = USART_Parity_No;//奇偶校验,不需要
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//字长,选择8位
	USART_Init(USART1, &USART_InitStructure);			
	
	
	USART_Cmd(USART1, ENABLE);								//使能USART1,串口开始运行
}

//发送数据(从TX引脚发送一个字节数据)/
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//等待发送完成(等着置1)
	//下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位//
}

//发送数组
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]);		
	}
}

//返回X的Y次方 
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将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');	//依次调用Serial_SendByte发送每位数字
	}
}

/// 
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);//将printf的底层重定向到自己的发送字节函数
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];				//定义字符数组
	va_list arg;					//定义可变参数列表数据类型的变量arg
	va_start(arg, format);			//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);					//结束变量arg
	Serial_SendString(String);		//串口发送字符数组(字符串)
}
/// 

串口发送+接收

初始化GPIO  PA9  PA10

上拉输入/浮空输入

USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;	//模式,发送模式和接收模式均选择

串口接收来说,可以使用查询和中断两种方法

查询的流程是,在主函数里不断判断RXNE标志位,如果置1了,就说明收到数据了,那再调用ReceiveData,读取DR寄存器

while (1)
	
		if (USART_GetFlagStatus (USART1, USART_FLAG_RXNE) == SET)//检查串口接收数据的标志位
		{
			RxData = USART_ReceiveData (USART1);
            OLED_ShowHexNum(1, 1, RxData, 2);|
		}
	}

中断方法

开启RXNE标志位到NVIC的输出

配置NVIC

	/*中断输出配置*/
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;					//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;		//选择配置NVIC的USART1线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);							//将结构体变量交给NVIC_Init,配置NVIC外设
	

中断函数接收数据

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

uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;			//返回接收的数据变量
}

void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断
	{
		Serial_RxData = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		Serial_RxFlag = 1;										//置接收标志位变量为1
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);			//清除USART1的RXNE标志位
																//读取数据寄存器会自动清除此标志位
																//如果已经读取了数据寄存器,也可以不执行此代码
	}
}

以上仅能支持一个字节的接收

USART串口数据包

大量数据,以数据包的形式

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

数据包分割方法:额外添加包头包尾 

在连续不断的数据流中分割出数据包(抱团的部分   有特殊联系)

数据转换为字节流

载荷和包头包尾重复

Hex数据包

HEX数据包里面,数据都是以原始的字节数据本身呈现的

文本数据包

文本数据包里面。每个字节就经过了一层编码和译码

每个文本字符背后,还是一个字节的HEX数据

所以这个文本数据包,通常会以换行作为包尾,打印时一行一行显示

数据包的收发流程

发送

接收

状态机:我们需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移

串口收发Hex数据包

uint8_t Serial_TxPacket[4];	//定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
//需要在.h文件声明一下(extern uint8_t Serial_TxPacket[];因为需要在main文件进行一个内容填充)
uint8_t Serial_RxPacket[4];//定义接收数据包数组(不含包头包尾)
uint8_t Serial_RxFlag;//定义 接收数据包标志位
······

//Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,并发送出去
void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);
	Serial_SendArray(Serial_TxPacket, 4);
	Serial_SendByte(0xFE);
}


//USART1中断函数
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;	//定义表示当前状态机状态的静态变量
	static uint8_t pRxPacket = 0;//定义表示当前接收数据位置的静态变量
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//判断是否是USART1的接收事件触发的中断
	{
		uint8_t RxData = USART_ReceiveData(USART1);	//读取数据寄存器,存放在接收的数据变量
		

		/*使用状态机的思路,依次处理数据包的不同部分*/
		//当前状态为0,接收数据包包头
		if (RxState == 0)
		{
			if (RxData == 0xFF)			//如果数据确实是包头
			{
				RxState = 1;			//置下一个状态
				pRxPacket = 0;			//数据包的位置归零
            }
		}
		//当前状态为1,接收数据包数据
		else if (RxState == 1)
		{
			Serial_RxPacket[pRxPacket] = RxData;	//将数据存入数据包数组的指定位置
			pRxPacket ++;				//数据包的位置自增
			if (pRxPacket >= 4)			//如果收够4个数据
			{
				RxState = 2;			//置下一个状态
			}
		}
		//当前状态为2,接收数据包包尾
		else if (RxState == 2)
		{
			if (RxData == 0xFE)			//如果数据确实是包尾部
			{
				RxState = 0;			//状态归0
				Serial_RxFlag = 1;		//接收数据包标志位置1,成功接收一个数据包
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}

static uint8_t RxState = 0;    (S)

静态变量类似全局变量,函数进入只会初始化一次0,函数退出后,数据仍有效

串口收发文本数据包

void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;		//定义表示当前状态机状态的静态变量
	static uint8_t pRxPacket = 0;	//定义表示当前接收数据位置的静态变量
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)	//判断是否是USART1的接收事件触发的中断
	{
		uint8_t RxData = USART_ReceiveData(USART1);			//读取数据寄存器,存放在接收的数据变量
		
		/*使用状态机的思路,依次处理数据包的不同部分*/
		
		/*当前状态为0,接收数据包包头*/
		if (RxState == 0)
		{
			if (RxData == '@' && Serial_RxFlag == 0)		//如果数据确实是包头,并且上一个数据包已处理完毕
			{
				RxState = 1;			//置下一个状态
				pRxPacket = 0;			//数据包的位置归零
			}
		}
		/*当前状态为1,接收数据包数据,同时判断是否接收到了第一个包尾*/
		else if (RxState == 1)
		{
			if (RxData == '\r')			//如果收到第一个包尾
			{
				RxState = 2;			//置下一个状态
			}
			else						//接收到了正常的数据
			{
				Serial_RxPacket[pRxPacket] = RxData;		//将数据存入数据包数组的指定位置
				pRxPacket ++;			//数据包的位置自增
			}
		}
		/*当前状态为2,接收数据包第二个包尾*/
		else if (RxState == 2)
		{
			if (RxData == '\n')			//如果收到第二个包尾
			{
				RxState = 0;			//状态归0
				Serial_RxPacket[pRxPacket] = '\0';			//将收到的字符数据包添加一个字符串结束标志
				Serial_RxFlag = 1;		//接收数据包标志位置1,成功接收一个数据包
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}


http://www.kler.cn/news/294979.html

相关文章:

  • 实现点击 `el-dialog` 里面的一个图标将对话框放大至全屏
  • QT自动获取编译日期与git commit ID
  • 【C++11】深入理解与应用右值引用
  • python可执行文件exe
  • Openharmony 下载到rk3568实现横屏
  • 案例-上海某科技公司:监控易7.0重塑服务器监控模式
  • 简单梳理一个历史脉络
  • urllib与requests爬虫简介
  • 【Nginx系列】Nginx中rewrite模块
  • 牛客(除2!)
  • 设计模式 19 观察者模式
  • 【AIGC】AI编程工具合集及其特点介绍
  • 1-18 平滑处理——高斯滤波 opencv树莓派4B 入门系列笔记
  • 【LabVIEW学习篇 - 17】:人机交互界面设计01
  • 以后写代码都是AI自动写了,Cursor+Claude-3.5-Sonnet,Karpathy 点赞的 AI 代码神器。如何使用详细教程
  • 解决异步任务上下文丢失问题
  • 【Python】6.基础语法(6)文件
  • DataLoader使用
  • [数据集][目标检测]电动车头盔佩戴检测数据集VOC+YOLO格式4235张5类别
  • 计算机网络与Internet应用
  • OpenCV与Matplotlib:灰度图像
  • 漫谈设计模式 [20]:解释器模式
  • 实战项目-快速实战-springboot dataway
  • linux 检查cpu 内存命令
  • Flutter基本组件Text使用
  • 嵌入式面试刷题
  • 商城系统的数据库
  • 电脑录屏杂音太大怎么办 电脑录屏杂音去除办法有哪些 解决录屏电流声等问题技巧与工具推荐
  • 如何选择国内大带宽服务器租用?
  • ❤《实战纪录片 1 》原生开发小程序中遇到的问题和解决方案