基于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

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

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

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


相关推荐
llilian_164 小时前
总线授时卡 CPCI总线授时卡的工作原理及应用场景介绍 CPCI总线校时卡
运维·单片机·其他·自动化
禾仔仔5 小时前
USB MSC从理论到实践(模拟U盘为例)——从零开始学习USB2.0协议(六)
嵌入式硬件·mcu·计算机外设
The Electronic Cat6 小时前
树莓派使用串口启动死机
单片机·嵌入式硬件·树莓派
先知后行。9 小时前
常见元器件
单片机·嵌入式硬件
恒锐丰小吕9 小时前
屹晶微 EG2302 600V耐压、低压启动、带SD关断功能的高性价比半桥栅极驱动器技术解析
嵌入式硬件·硬件工程
Dillon Dong10 小时前
按位或(|=)的核心魔力:用宏定义优雅管理嵌入式故障字
c语言·stm32
Free丶Chan11 小时前
dsPIC系列-1:dsPIC33点灯 [I/O、RCC、定时器]
单片机·嵌入式硬件
v先v关v住v获v取12 小时前
塔式立体车库5张cad+设计说明书+三维图
科技·单片机·51单片机
恒锐丰小吕12 小时前
屹晶微 EG2106D 600V耐压、半桥MOS/IGBT驱动芯片技术解析
嵌入式硬件·硬件工程