基于STM32 + UART串口通信新手详解和应用发送接收

前言

本篇博客主要学习了解UART串口通信,了解串口基本通信方案,了解串口的时序和中断,实现最基本的发送和接收。本篇博客大部分是自己收集和整理,如有侵权请联系我删除。

本次博客开发板使用的是正点原子精英版,芯片是STM32F103ZET6,需要资料可以@我拿取。

交流群:717237739

如果觉得有用点赞关注收藏三连,多谢支持

本博客内容原创,创作不易,转载请注明


一. 认识基本的通信

1.通信接口:

目的:将一个设备的数据通过某种协议格式传输给另外一个设备,拓展硬件系统。

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

2.通信简介:

串行通信和并行通信:

计算机与外界的信息交换称为通信, 基本的通信方式有两种。

    1. 并行通信: 所传送数据的各位同时发送或接收。
    1. 串行通信: 所传送数据的各位按顺序一位一位地发送或接收。
串行通信的方式:

按照数据传送方向,串行通信可分为单工制式、半双工制式和全双工制式。

单工制式: 数据在甲机和乙机之间只允许单方向传送。两机之间只需 1 条数据线。

**半双工制式:**数据在甲机和乙机之间允许双方向传送,但只能分时复用,因而两机之间只需 1 条数据线。

全双工制式: 甲、 乙两机之间数据的发送和接收可以同时进行, 全双工形式的串行通信必须使用 2 条数据线

同步通信和异步通信:

同步通信:

接收和发送时钟严格保持同步,在通信时通常要求有同步时钟信号。

对硬件结构要求较高。由于这种方式易于进行串行外围扩展,所以目前很多型号的单片机都增加了串行同步通信接口,如目前已得到广泛应用的 I2C 串行总线和 SPI 串行接口等。

异步通信:

进行串行通信的单片机的时钟相互独立。通信双方只需按约定的帧格式来发送和接收数据,所以硬件结构比同步通信方式简单。此外,它还能利用校验位检测错误,所以这种通信方式应用较广泛。在单片机中主要是采用异步通信方式。

二 . 简单认识串口UART

1.串口通信(全双工的,异步通信)

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

2.硬件电路

UART异步通信方式引脚连接方法:有供电可以不接VCC,接不接都行

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

3.电平标准

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

  • TTL电平:+3.3V或+5V表示1,0V表示0
  • RS232电平:-3~-15V表示1,+3~+15V表示0
  • RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)

三. 串口简介

1.基本特性简介

  • 全双工异步通信。
  • 小数波特率发生器系统,提供精确的波特率。
  • 可配置的16倍过采样或8倍过采样,因而为速度容差与时钟容差的灵活配置提供了可能。
  • 可编程的数据字长度(8位或者9位);
  • 可配置的停止位长度(0.5/1/1.5/2);
  • 可选校验位(无校验/奇校验/偶校验)
  • 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
  • 可配置的使用DMA多缓冲器通信。 单独的发送器和接收器使能位。
  • 检测标志:① 接受缓冲器 ②发送缓冲器空 ③传输结束标志 多个带标志的中断源。
  • 校验控制:─ 发送校验位 ─ 对接收数据进行校验
  • 四个错误检测标志:溢出错误 ─ 噪音错误 ─ 帧错误 ─ 校验错误

2.STM32串口通信过程

3.串口参数及时序

  • 波特率:串口通信的速率,表示一秒传输的位数,一秒传多少个字节?9600/(1+8+1)=960

波特率计算公式:波特率/(起始位+数据位+停止位+校验位)

(两个使用串口通信的设备,它们的波特率必须设置为相同,不然无法进行通信,会乱码)

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

这些配置的参数,其实就是一些协议的规定,基本就是按照常见的配置就行了

注意:如果使用奇偶校验,数据位不是八位而是九位了

串口时序:

传输数据时先传送字符的低位,后传送字符的高位。即低位(LSB)在前,高位(MSB)在后

注意:数据都是从低位(0)开始,然后按照时序读出对应的0/1,完成之后,正确的读法应该是从后往前,例如第一个0X55,十六进制是 01010101,从后面开始读。

四.串口配置 和 库函数详解

1.配置过程(正点原子精英板)

1)查看原理图,具体使用的功能。

2)配置串口对应的IO口

TXD(PA.9 ) RXD(PA.10)

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

3)配置串口的功能

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

4)开启IO口和串口的时钟

每次配置都要打开时钟,找到对应的总线并使能。

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

5)使能串口

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);

6)串口发送数据

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

7)串口接收数据

uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

这是最基础的串口使用,如果需要中断可以参考我的博客文章,这里暂时不做演示,附送中断代码。NVIC中断详解

2.串口初始化代码

void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	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(USART1, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 

实验现象:

3.自己编写的发送接收字节和字符串代码 (参考)

串口(发送)一个字节

/********************************************************************
*函数名 :void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data)                     
*功能描述 :串口发送一个字节函数
*入口参数 :USARTx,哪个串口 ; Data 发送的数据
*出口参数 :无
*函数返回:  无
*调用提示 :无   
*********************************************************************/	
void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data)
{
	while (!USART_GetFlagStatus(USARTx,USART_FLAG_TC));//等等上一个字节发送完成
	USART_SendData(USARTx,Data);
}

串口(接收)一个字节

/********************************************************************
*函数名 :uint16_t USART_ReceiveByte(USART_TypeDef* USARTx)                     
*功能描述 :串口接收一个字节函数
*入口参数 :USARTx ,哪个串口
*出口参数 :无
*函数返回:  返回收到的字符
*调用提示 :无   
*********************************************************************/	
uint16_t USART_ReceiveByte(USART_TypeDef* USARTx)
{
	while (!USART_GetFlagStatus(USARTx,USART_FLAG_RXNE)); //等待收到一个字节
	return USART_ReceiveData(USARTx); //返回收到的数据
}

串口发送字符串函数

/********************************************************************
*函数名 :void USART_SendStr(USART_TypeDef* USARTx, uint8_t *str)                    
*功能描述 :串口发送字符串函数
*入口参数 :USARTx,哪个串口 ; str 发送的数据
*出口参数 :无
*函数返回:  无
*调用提示 :无   
"hello322432422342" --字符串以'\0'
*********************************************************************/	
void USART_SendStr(USART_TypeDef* USARTx, uint8_t *str)
{
	while(*str != '\0'){
		USART_SendByte(USARTx,*str++);
	}
}

串口接收字符串函数

/********************************************************************
*函数名 :uint16_t USART_ReceiveStr(USART_TypeDef* USARTx)                     
*功能描述 :串口接收字符串函数
*入口参数 :USARTx ,哪个串口
*出口参数 :无
*函数返回:  无
*调用提示 :无   
*说明:			以收到\r\n作为结束。 即发送过来的数据必须以\r\n作为结尾。
*********************************************************************/	
void USART_ReceiveStr(USART_TypeDef* USARTx)
{
	u8 recLen = 0;
	u8 data;
	while(1)
	{
			data = USART_ReceiveByte(USARTx);
			if(data == '\r' || data == '\n'){//收到了结束标志
				break;
			}
			recDataBuf[recLen] =  data; //收到的数据不是结束标志,就把它存到数组中
			recLen++;
	}
	recDataBuf[recLen] = '\0';//添加结束符recDataBuf[recLen] =0;
	USART_SendStr(USARTx,recDataBuf); //返回收到的数据
}

4.使用printf函数打印数据

1)包含头文件:stdio.h

2)重写fputc函数

//

//加入以下代码,支持printf函数,而不需要选择use MicroLIB

#if 1

#pragma import(__use_no_semihosting)

//标准库需要的支持函数

struct __FILE

{

 int handle;

};

FILE __stdout;

//定义_sys_exit()以避免使用半主机模式

void _sys_exit(int x)

{

 x = x;

}

//重定义fputc函数

int fputc(int ch, FILE *f)

{

 while((USART1->SR&0X40)==0);//循环发送,直到发送完毕

    USART1->DR = (u8) ch;

 return ch;

}

#endif





//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;
}

3)包含微库

总结:

串口在我们项目中是属于比较常用的,掌握程度也不难,多实践多使用对调试很有帮助,串口的拓展就是收发数据包,或者队列接收,IAP升级等等,协议还有485-modbus,接下来的博客可能会写,大家记得关注。大家如果对我的博客有疑问或者错误,可以@我修改,大家相互交流。

交流群:717237739

如果觉得有用点赞关注收藏三连,多谢支持

本博客内容原创,创作不易,转载请注明

点赞收藏关注博主,不定期分享单片机知识,互相学习交流。


相关推荐
scan12 小时前
单片机串口接收状态机STM32
stm32·单片机·串口·51·串口接收
Qingniu012 小时前
【青牛科技】应用方案 | RTC实时时钟芯片D8563和D1302
科技·单片机·嵌入式硬件·实时音视频·安防·工控·储能
Mortal_hhh4 小时前
VScode的C/C++点击转到定义,不是跳转定义而是跳转声明怎么办?(内附详细做法)
ide·vscode·stm32·编辑器
深圳市青牛科技实业有限公司4 小时前
【青牛科技】应用方案|D2587A高压大电流DC-DC
人工智能·科技·单片机·嵌入式硬件·机器人·安防监控
Mr.谢尔比5 小时前
电赛入门之软件stm32keil+cubemx
stm32·单片机·嵌入式硬件·mcu·信息与通信·信号处理
LightningJie5 小时前
STM32中ARR(自动重装寄存器)为什么要减1
stm32·单片机·嵌入式硬件
鹿屿二向箔5 小时前
STM32外设之SPI的介绍
stm32
西瓜籽@6 小时前
STM32——毕设基于单片机的多功能节能窗控制系统
stm32·单片机·课程设计
远翔调光芯片^138287988728 小时前
远翔升压恒流芯片FP7209X与FP7209M什么区别?做以下应用市场摄影补光灯、便携灯、智能家居(调光)市场、太阳能、车灯、洗墙灯、舞台灯必看!
科技·单片机·智能家居·能源
极客小张9 小时前
基于STM32的智能充电桩:集成RTOS、MQTT与SQLite的先进管理系统设计思路
stm32·单片机·嵌入式硬件·mqtt·sqlite·毕业设计·智能充电桩