STM32学习日志3 —— 串口通信

本文详细介绍了STM32的串口通信原理,包括USART的基本概念、工作方式、帧格式、参数配置以及中断接收。文中提供了串口初始化、发送单个字节、数组、字符串和重定义printf函数的示例代码,并讲解了中断接收数据的配置和中断处理函数的编写。此外,还提到了串口工具的使用链接。
摘要由CSDN通过智能技术生成

前言

        本节将会学习到串口的基本概念、发送形式、如何使用串口发送各类型数据、重定义printf函数、如何使用中断函数,所使用的串口工具附在最后。


目录

前言

1、串口概述

2、串口的工作方式

2、USART帧格式

3、串口参数

4、USART简化结构图

4、串口通信的具体程序实现

4.1 将需要用的USART和GPIO的时钟打开

4.2 GPIO初始化,将TX配置成复用输出

4.3 配置USART,打包为结构体

4.4 发送函数

4.4.1 发送一个字节数据函数

 4.4.2 发送数组函数

 4.4.3 发送字符串函数

 4.4.4 发送printf函数

4.5 接收数据

4.5.1开启中断 

4.5.2 配置NVIC 

4.5.3 编写使用中断代码

总结

参考资料(以下排序不分先后)

所用工具


1、串口概述

        在STM32的参考手册中,串口被描述成通用同步异步收发器(USART),它提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。

        USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互联网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。

        它还允许多处理器通信。还可以使用DMA方式,实现高速数据通信。

        USART通过3个引脚与其他设备连接在一起,任何USART双向通信至少需要2个引脚:接受数据输入(RX)和发送数据输出(TX)。

        RX: 接受数据串行输入。通过过采样技术来区别数据和噪音,从而恢复数据。

        TX: 发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。

2、串口的工作方式

        一般只有 查询 和 中断 两种方式

  • 查询:串口程序不断地刷新查询,查看当前有无数据需要传送。
  • 中断:需要在程序中编写中断函数。若有中断信号,则进行数据的传输。

2、USART帧格式

        以下为串口发送一个字节的格式,格式为串口协议所规定。串口中,每个字节都装在一个数据帧中。每个数据帧都由起始位、数据位和停止位组成,下图共10位。

        也可以在数据位最后一位加一位奇偶校验位。

         其中有效载荷为前八位,校验位在最后占一位。

3、串口参数

  • 波特率:串口通信的速率。单位是码元/s,或者直接叫波特(Baud)。
  • 比特率:也可以表示串口通信的速率。每秒传输的比特数,单位是bit/s,或者叫bps。

在二进制情况下1码元=1bit,此时波特率等于比特率。

  • 起始位:标志一个数据帧的开始,固定为低电平。
  • 数据位:数据真的有效载荷,1为高电平,0为低电平,低位先行。
  • 校验位:用于数据验证,根据数据位计算得来。
  • 停止位:用于数据帧间隔,固定为高电平。

4、USART简化结构图

4、串口通信的具体程序实现

4.1 将需要用的USART和GPIO的时钟打开

        与之前一致,不再赘述,直接上结果

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
}

4.2 GPIO初始化,将TX配置成复用输出

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	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使用。

4.3 配置USART,打包为结构体

USART_InitStruture.USART_BaudRate = 9600;

        将波特率设置为9600,只要设置好就可以不用管了,会自动计算出所需要的各种参数。

USART_InitStruture.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

        这条代码是选择硬件控制流,因为不需要所以直接选None。

USART_InitStruture.USART_Mode = USART_Mode_Tx;

        这条是选择USART的模式,可以选择发送或者接收模式。

USART_InitStruture.USART_Parity = USART_Parity_No;

        这条是选择有无校验位,因需要校验位所以选择无校验。

USART_InitStruture.USART_StopBits = USART_StopBits_1;

        这条是选择停止位,可以选择0.5、1、1.5、2,这里选择1便可。

USART_InitStruture.USART_WordLength = USART_WordLength_8b;

        这条是选择字长,可以选择8位或9位,因不需要校验选择8位即可。

最后再加一个 USART_Cmd(USART1,ENABLE);

        初始化完毕

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	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);
	
	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);
	
	USART_Cmd(USART1,ENABLE);
}

4.4 发送函数

4.4.1 发送一个字节数据函数

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

        这条函数可以在USART.H中找到。

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1,Byte);
}

        将两个函数声明一下后,准备编写主程序代码,代码流程如下:

  1. 初始化串口
  2. 发送数据
int main(void)
{
	Serial_Init();
	Serial_SendByte(0x22);
	
	while (1)
	{
		
	}
}

        电脑端的话需要用串口工具来观察发送的数据是否成功以及正确,还要注意端口号、波特率等参数一定要和单片机端一致。

        测试结果如下

        代码测试正确无异常。

注意: 如果 输出函数 放置到 主函数的循环函数 中便会一直发送数据。

 4.4.2 发送数组函数

        发送数组函数的本质也是发送一个字节数据,但是利用循环将数组中的数据一个一个发送出来,变成了发送数组函数。

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

        创建一个数组然后进行测试

int main(void)
{
	Serial_Init();
	
	uint8_t a[8] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
	Serial_SendArray(a,8);
	
	while (1)
	{
		
	}
}

         测试结果如下:

         代码测试正确无异常。

 4.4.3 发送字符串函数

         字符串函数可以用来输出一些日志信息。

int main(void)
{
	Serial_Init();
	
	Serial_SendString("HelloWorld");
	
	while (1)
	{
		
	}
}

         测试结果如下:

         代码测试正确无异常。

 4.4.4 发送printf函数

1.打开工程选项,将MicroLIB勾选

2.对printf重定向

#include <stdio.h>

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

 3.主函数测试

int main(void)
{
	Serial_Init();

	printf("Num2=%d\r\n", 222);    //\r\n换行
	
	while (1)
	{
		
	}
}

测试结果如下:

4.5 接收数据

        与发送数据一致,首先需要初始化引脚,代码与发送差不多,只需简单修改即可。

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	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);
	
	USART_InitTypeDef USART_InitStruture;
	USART_InitStruture.USART_BaudRate = 9600;
	USART_InitStruture.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruture.USART_Mode = 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);
	
	USART_Cmd(USART1,ENABLE);
}

4.5.1开启中断 

        需要中断接收数据,所以程序中需要打开中断。这条代码可以在USART中找到。

void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)

        第一个参数选择  USART1 ,因为PA9、PA10在技术手册中是属于USART1。

        第二个参数选择 USART_IT_RXNE ,因为我们想达到的效果是收到数据后进行中断,也就意味着接收 数据寄存器 在收到数据后会置1,会达到中断的条件。

        第三个参数的意思是开启。

	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

4.5.2 配置NVIC 

        先分组

 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

         使用结构体变量

        NVIC_InitTypeDef NVIC_InitStructure;

         继续使用.引出所有结构体变量。

第一项根据中断通道选择,这里选择 USART1_IRQn 。

第二项开启中断。

第三项选择先占优先级。

第四项选择从优先级。

因为目前只有这一个中断所以都选1就可以。

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

 最后跟上结构体地址,完整开启代码如下:

	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	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);

4.5.3 编写使用中断代码

        首先要检测中断标志位是否置1

if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)

         如果置1进入该程序,看是否读取DR,如果读取了会自动清除,没有读取需要手动清除。

USART_ClearITPendingBit(USART1, USART_IT_RXNE).

        注意:不管是手动清除还是自动清除,一定要确保清除掉标志位!!!

        接下来需要将收到的数据放入缓存(可以自己定义),并且设置 标志位 置1 (自己定义)。

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

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

        至此 中断程序结束,以上程序可以自己单独写一个封装进行独立操作。

4.5.4 主函数使用

        同样在主程序中需要保证与上面一致,进入标志位后记得进行清除。

        最终主函数代码如下:

uint8_t RxData;

int main(void)
{
	Serial_Init();
	
	while (1)
	{
		if (Serial_GetRxFlag() == 1)
		{
			RxData = Serial_GetRxData();
			Serial_SendByte(RxData);
		}
	}
}

 测试结果如下:

总结

  • TX定时输出引脚的高低电平
  • RX定时读取引脚的高低电平
  • 每个字节的数据加上起始位、停止位、可选的校验位,打包为数据帧,依次让TX引脚输出,另一侧RX依次接受。
  • 发送数据的所有形式都是由最基础发送单个字符组成的。
  • 如果 输出函数 放置到 主函数的循环函数 中便会一直发送数据。
  • 中断代码过程:开启中断、使用中断。使用中断的过程中不管是手动清除还是自动清除,一定要确保清除掉标志位!!!

参考资料(以下排序不分先后)

【1】江科大 [9-1] USART串口协议_哔哩哔哩_bilibili

【2】stm32中常见的通信协议之USART(串口)_usart串口协议_嵌入式进阶之路的博客-CSDN博客

【3】 STM32------USART详解_休闲可乐的博客-CSDN博客

【4】stm32中断优先级NVIC_IRQChannelPreemptionPriority的理解及使用_静戴冠的博客-CSDN博客 

所用工具 

串口工具:

链接:https://pan.baidu.com/s/1Ir2DbBOkO7VyND7zkCdazw?pwd=vm32 
提取码:vm32

stm32串口1和串口3数据互通,经测试同时发256字节不丢包 void usart3_init(u32 bound) { NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIOBʱÖÓ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE); //´®¿Ú3ʱÖÓʹÄÜ USART_DeInit(USART3); //¸´Î»´®¿Ú3 //USART3_TX PB10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB10 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //¸´ÓÃÍÆÍìÊä³ö GPIO_Init(GPIOB, &GPIO;_InitStructure); //³õʼ»¯PB10 //USART3_RX PB11 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//¸¡¿ÕÊäÈë GPIO_Init(GPIOB, &GPIO;_InitStructure); //³õʼ»¯PB11 USART_InitStructure.USART_BaudRate = bound;//²¨ÌØÂÊÒ»°ãÉèÖÃΪ9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b;//×Ö³¤Îª8λÊý¾Ý¸ñʽ USART_InitStructure.USART_StopBits = USART_StopBits_1;//Ò»¸öֹͣλ USART_InitStructure.USART_Parity = USART_Parity_No;//ÎÞÆæżУÑéλ USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//ÎÞÓ²¼þÊý¾ÝÁ÷¿ØÖÆ USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //ÊÕ·¢Ä£Ê½ USART_Init(USART3, &USART;_InitStructure); //³õʼ»¯´®¿Ú 3 USART_Cmd(USART3, ENABLE); //ʹÄÜ´®¿Ú //ʹÄܽÓÊÕÖÐ¶Ï USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//¿ªÆôÖÐ¶Ï //ÉèÖÃÖжÏÓÅÏȼ¶ NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//ÇÀÕ¼ÓÅÏȼ¶3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //×ÓÓÅÏȼ¶3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQͨµÀʹÄÜ NVIC_Init(&NVIC;_InitStructure); //¸ù¾ÝÖ¸¶¨µÄ²ÎÊý³õʼ»¯VIC¼Ä´æÆ÷ TIM4_Int_Init(1000-1,7200-1); //10msÖÐ¶Ï USART3_RX_STA=0; //ÇåÁã TIM_Cmd(TIM4,DISABLE); //¹Ø±Õ¶¨Ê±Æ÷7 } //´®¿Ú3,printf º¯Êý //È·±£Ò»´Î·¢ËÍÊý¾Ý²»³¬¹ýUSART3_MAX_SEND_LEN×Ö½Ú void u3_printf(char* fmt,...) { u16 i,j; va_list ap; va_start(ap,fmt); vsprintf((char*)USART3_TX_BUF,fmt,ap); va_end(ap); i=strlen((const char*)USART3_TX_BUF); //´Ë´Î·¢ËÍÊý¾ÝµÄ³¤¶È for(j=0;j<i;j++) //Ñ­»··¢ËÍÊý¾Ý { while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); //Ñ­»··¢ËÍ,Ö±µ½·¢ËÍÍê±Ï USART_SendData(USART3,USART3_TX_BUF[j]); } }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值