Bootstrap

自用笔记:USART串口通信知识点

通信总述

通信:设备与设备之间进行数据交换,比如单片机与电脑(要接USB转串口模块)

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

常见的通信协议:USART,I2C,SPI,CAN,USB

串口通信

电平标准:用高低电平来传输数字1,0,从而传输数据

常用电平标准:TTL标准:3.3或5V为高,0V为低;差分电平标准:RX与TX线电压之差

通信协议

所谓通信协议,我所理解的就是规定好数据帧+波特率

数据帧包括:起始位,数据位,校验位,停止位

数据传输方向:

单工:只有一个信道,数据只能沿一个方向传播

半双工:只有一个信道,数据可以双向传播,但是同一时间只能沿一个方向

全双工:两个信道,分别负责数据的收和发,可以同步进行

时钟:

同步:双方共用同一个波特率发生器,因此发送方发送多个数据时,不能有时间间隔

异步:双方规定好一个波特率,按照此波特率发送数据之后,接收方按照此波特率接收,在发送下一个数据之间,可以隔任意时长,不会出现“赶不上”的情况

USART的通信方式是全双工异步通信

数据帧格式:

起始位S:低电平;停止位P:高电平

数据位八位:低位先行(低位在前,高位在后)

比如传输0x55,那么数据D0~D7为1010 1010

USART

USART是stm32内部集成的片上外设,挂载在APB1和APB2上

通过配置结构体来USART的寄存器

主要的寄存器包括:发送数据寄存器,接收数据寄存器

软件代码实现初始化USART

GPIO初始化

①开启时钟

②输出引脚定义为复用推挽,输入引脚定义为上拉输入

USART初始化

①开启时钟

②定义结构体

USART_InitTypeDef USART_InitStructure
USART_InitStructure.USART_BaudRate = 9600;				//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;	//模式,发送模式和接收模式均选择
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_Init,配置USART1

大部分都可以直接通过英文意思明白具体作用

数据接收过程

当一个字符被接收到时,RXEN位被置1,它表明移位寄存器的内容被转移到RDR,也就是说,数据已经被接收并且可以被读出,我们要告诉单片机已经收到了,该执行读出的动作了,那么就需要中断这个操作了,在中断里读出

江科大在这里有一个新的设计,利用中断,自己再构建中断,这样更加灵活

中断初始化代码

/*中断输出配置*/
	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外设
	

NVIC的配置思路

NVIC的分组,指的是如何分配抢占优先级和响应优先级(分别给几个)

NVIC本质上是一个中断管理器,主要完成两件事,一是给中断分配通道并使能通道,二是给中断分配优先级(抢占和响应)

还要开启相关通道的中断,在这里也就是USART_ITConfig

最后一步是对USART进行使能

USART_cmd(USART1,ENABLE)

这样USART就初始化完毕

因为在编程过程中,层层封装的理念用的很深,所以,对于某一个模块,都是在一个单独的文件中将各个功能都封装好,最后在主文件的大循环中调用API即可

因此,我们还要在Serial中封装其他功能的函数

串口发送与接收

发送单个字节:

需要调用函数

USART_SendData(USARTx,Byte)

单字节发送,需要有一个等待缓冲区(发送移位寄存器)清空的过程,等到发送完成,标志位置1,这点和接收数据有点像,接收完数据,接收数据寄存器也置1

因此需要加此函数

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]);		//依次调用Serial_SendByte发送每个字节数据
	}
}
//发送字符串
void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		Serial_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

发送字符串时,回先把字符编码为十六进制数,再进行发送

如果要发送十进制数,需要把每一位分隔开来,都当做单独的字节单独发送

同时,要从高位向低位发送,因为先发送的回出现在前面

比如1234,如果从低位向高位发送,则会在接收方出现4321这样的情况

void Serial_SendNum(uint16_t Num,uint16_t length)
{
    uint16_t Array[length];
	uint16_t i;
	for(i=0;i<length;i++)
	{
		Array[length-1-i]=Num%10;
		Num=Num/10;
	
	}
	
	for(i=0;i<length;i++)
	{
		Serial_SendByte(Array[i]+'0');
	}
	
}
//突然发现为什么当时在写的时候在Array[i]后面要加一个'0'
串口发送Hex数据包与文本数据包

Hex数据,也就是十六进制类型的数据

发送Hex数据包,本质上是模仿了USART发送一个字节的通信协议

包头,相当于通信协议中的起始位;字长,相当于数据位;包尾,相当于停止位

而接收整个数据帧的判别过程是单片机完成的(硬件),接收一个数据包则需要我们用软件实现(代码)。这里有点像是I2C教程中的软件实现与硬件实现。

江科大介绍了一个重要的编程思想:状态机

(接收数据帧或者数据包的过程中,才需要各种位的判别)

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

在不同的状态处理不同的事情,同时也要设置状态转移的条件,核心是理清楚逻辑

整个状态机是在中断函数里面完成的

江科大在这里用到了C语言中的一个高级语法:静态变量

关键字:static

在函数中相当于全局变量,只初始化一次,且在函数退出后仍然有效,具有记忆性,如果想要回到最初的状态,需要人为赋值

中断函数如图

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);		//清除标志位
	}
}

;