2.26 串口发送&接收

串口收发接线图

串口初始化流程

【Ctrl + alt + 空格】可以进行代码联想,可以方便的选取参数。

第一步,开启时钟,把需要用的USART和GPIO的时钟打开。

第二步,GPIO初始化,把TX配置成复用输出,RX配置成输入。(TX引脚是USART外设控制的输出脚,所以要选择复用推挽输出)

第三步,配置USART,直接使用一个结构体,就可以把图上面所有的参数都配置好了。

第四步,如果你只需要发送的功能,就直接开启USART,初始化就结束了。

第五步,如果你还需要接收的功能,可能还需要配置中断。那就在开启USART之前,再加上ITConfig和NVIC的代码就行了。

初始化完成之后,如果要发送数据,就调用一个发送函数就行了;如果要接收数据,就调用一个接收函数就行了。

如果要获取发送和接收的状态,就调用获取标志位的函数,这就是USART外设的使用思路。

USART库函数

常规函数不再讲解

void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);

//中断
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);

//标志位

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

USART发送接收数据

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

写DR寄存器。

uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

读DR寄存器。

DR寄存器内部有4个寄存器,控制发送与接收。

需要时钟输出的可以了解一下

void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);

USART到DMA的触发通道

void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);

USART初始化

初始化USART,使得单片机连接至的PA9可以接收到单片机发送的信息:

void Serial_Init()
{
	//开启RCC USART1、GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//配置PA9(输出)
	//一般RX配置是浮空/上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入。
	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); //PA9 配置成复用推挽输出,供USART1的TX使用。
	
	//初始化USART1
	USART_InitTypeDef USART_InitStruture;
	//波特率
	USART_InitStruture.USART_BaudRate=9600;
	//硬件流控制
	USART_InitStruture.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	//串口模式
	USART_InitStruture.USART_Mode=USART_Mode_Tx;
	//校验位
	USART_InitStruture.USART_Parity=USART_Parity_No;
	//停止位
	USART_InitStruture.USART_StopBits=USART_StopBits_1;
	//字长
	USART_InitStruture.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStruture);
	
	//使能USART1
	USART_Cmd(USART1,ENABLE);
}

发送单个字节的函数:

对USART_DR的写操作,会将TXE标志位清零,所以标志位置1之后,不需要手动清零。

 void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	//发送数据寄存器空 标志位
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
	//这个标志位不需要清0,再发送下一帧数据的时候,标志位自动清0
}

发送数组的函数:

void Serial_SendArray(uint8_t* Array,uint16_t Length)
{
	uint16_t i;
	for(i=0;i<Length;i++)
	{
		Serial_SendByte(Array[i]);
	}
}

发送数字的函数:

void Serial_SendNumber(uint32_t Number)
{
	
int8_t Length = 0;
int32_t Y = 1, Count = Number;

// 计算数字的长度和最高位的除数
for ( ; Count > 0; Count /= 10, Length++) {
    Y *= 10;  // Y 最终会是 Number 的最高位的10的倍数
}
Y /= 10;  // 调整 Y 为最高有效位的除数

// 发送每位数字
for (int i = 0; i < Length; i++) {
    int digit = (Number / Y) % 10;  // 提取当前最高位
    Serial_SendByte(digit+0x30);
    Y /= 10;	// 移动到下一位
}
//换行
Serial_SendByte('\r');
Serial_SendByte('\n');
}

 

目前这个状态是已经可以进行单片机串口的发送了。我们来用串口工具软件来进行一下实验:

可以看到STM32可以正常的发送数据。

另外,再介绍一下printf函数移植的方法,使用printf函数之前需要打开工程选项,把Use MicroLIB勾上,MicroLIB是Keil为嵌入式平台优化的一个精简库,我们等会要用到的printf函数就可以用这个MicroLIB;接下来我们需要对printf重定向,将printf打印的东西输出到串口,因为printf默认是输出到屏幕,我们单片机没有屏幕,所以要进行重定向:

方法1(printf只能有1个,比如重定向到串口1,串口2再用就没有了)

1.在串口模块里加上stdio.h头文件。

2.重写fputc函数。

int fputc(int ch,FILE* f)
{
	Serial_SendByte(ch);
	return ch;
}

重定向fputc跟printf有什么关系呢?这是因为fputc是printf函数的底层,printf函数在打印的时候,就是不断调用fputc函数一个个打印的,我们把fputc函数重定向到了串口,那printf自然就输出到串口了。

方法2(sprintf可以指定打印位置,不涉及重定向,每个串口都可以使用其打印)

1.sprintf可以把格式化字符输出到一个字符串里,所以这里可以先定义一个字符串char,长度给够就行。

char String[100];
sprintf(String,"Num=%d\r\n",666);
Serial_SendString(String);

方法3(封装sprintf)

sprintf每次都得先定义字符串,再打印到字符串,再发送字符串,太麻烦了,我们要是能封装一下这个过程,就再好不过了。

由于spritnf函数比较特殊,它支持可变的参数,像我们之前写的函数,参数的个数都是固定的,可变参数这个执行起来比较复杂。

串口工具数据模式

HEX模式/十六进制模式/二进制模式:以原始数据的形式显示。

文本模式/字符模式:以原始数据编码后的形式显示,下面是ASCII码字符集。

串口的接收

对于串口接收初始化来说,可以使用查询和中断两种方法,如果使用查询,那初始化配置完成之后就结束了;如果使用中断,那还需要再初始化中开启中断,配置NVIC。

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

串口接收初始化

void Serial_Init()
{
	//开启RCC USART1、GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//配置PA9(输出)
	//一般RX配置是浮空/上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入。
	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); //PA9 配置成复用推挽输出,供USART1的TX使用。
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure); //PA10 配置成上拉输入,供USART1的RX使用。
	
	
	//初始化USART1
	USART_InitTypeDef USART_InitStruture;
	//波特率
	USART_InitStruture.USART_BaudRate=9600;
	//硬件流控制
	USART_InitStruture.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	//串口模式
	USART_InitStruture.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;
	//校验位
	USART_InitStruture.USART_Parity=USART_Parity_No;
	//停止位
	USART_InitStruture.USART_StopBits=USART_StopBits_1;
	//字长
	USART_InitStruture.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStruture);
	
	//使能USART1
	USART_Cmd(USART1,ENABLE);
}

 串口查询接收

int main(void){
	OLED_Init();
	Serial_Init();
	
	
	while(1)
	{
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
		{	
			//接收到一个字节的数据已经在RxData里面了。
			RxData = USART_ReceiveData(USART1);
			OLED_ShowHexNum(1,1,RxData,2);
			//当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。
			/*如果USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作
			  可以将该位清零。*/
			//所以读DR硬件会自动清除标志位,我们就不需要再清除标志位了。
		}
	}
}

串口中断接收

void USART1_IRQHandler()
{
	if(USART_GetFlagStatus(USART1,USART_IT_RXNE) ==SET)
	{
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1; 
		//USART_ClearFlag(USART1,USART_IT_RXNE);
	}
}

uint8_t Serial_GetRxFlag(void)
{
	if(Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}

主函数

uint8_t RxData;

int main(void){
	OLED_Init();
	Serial_Init();
	OLED_ShowString(1,1,"RxData:");
	
	while(1)
	{
		if(Serial_GetRxFlag() == 1)
		{	
			RxData = Serial_GetRxData();
			Serial_SendByte(RxData);
			OLED_ShowHexNum(1,8,RxData,2);
		}
	}
}