STM32-USART串口协议

一、USART与UART

1、区别

  • 同步通信‌:USART在同步通信时需要时钟来触发数据传输,能够提供主动时钟,这使得通信双方可以共享一个时钟信号来采样数据线。
  • ‌异步通信‌:在异步通信中,USART与UART没有区别,因为两者都不需要共享时钟信号,设备间的数据传输速

2、特点

UART

  • 全双工通信
  • 简单易用
  • 异步通信
  • 传输速率可调

USART:

  • 全双工通信
  • 支持同步和异步通信
  • 支持多种高级功能,如硬件流控制、DMA
  • 自带波特率发生器,最高达4.5Mbps/s
  • 具有多种错误检测标志:包括帧错误、溢出错误、噪声检测、奇偶校验错误

二、USART

一、概述

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器

  • USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
  • 自带波特率发生器,最高达4.5Mbits/s
  • 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2) 可选校验位(无校验/奇校验/偶校验)
  • 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN

STM32F103C8T6 USART资源: USART1、 USART2、 USART3。

硬件流控制,就是防止接收设备处理数据速度慢,而导致数据丢失的问题,比如A设备Tx向B设备的Rx发送数据,A设备一直在发,发的太快了,B设备处理不过来,如果没有硬件流控制,那B设备就只能抛弃新数据或者覆盖原数据了。如果有硬件流控制,在硬件电路上,会多出一根线,如果B没有准备好接收,就置高电平,如果准备好了,就置低电平。

二、USART框图

如下:

发送器控制者发送移位寄存器,接收器控制接收移位寄存器。

硬件数据流控制:nRTS(Require to Send,n表示低电平,发送),nCTS(Clear to Send,n表示低电平,接收)。

如下,设备A向设备B发送数据,设备A需要根据设备B上nRTS发送来的电平进行判断,如果是低电平设备A可以发送数据,检测到高电平就停止发送数据。

当我们向TDR写数据时,等待发送移位寄存器移完数据后,TDR向发送移位寄存器转移数据,这时TXE(TX Empty)标志位会置1,我们可以写入新的数据。

接收寄存器类似,当我们接收数据时,数据会从高位往低位这个方向移动,也就是向右移,当接收一个字节后,会向RDR寄存器转运,同时,RXNE(RX Not empty,接收数据寄存器非空)会置1,者样我们就可以读走数据了,

SCLK(同步时钟)用于同步通信,只支持发送,不支持接收。

唤醒单元就是挂载多个设备。

USART1挂载在APB2总线上,所以就是PCLK2的时钟,一般是72MHz。其他USART挂载在APB1,就是PCLK1的时钟,一般36MHz。

TE(TX Enable)置1就是发送器使能,发送部分的波特率就有效了。RE(RX Enable)置1就是接收器使能,接收部分的波特率就有效了。

三、USART基本结构

四、数据帧

停止位就是控制高平时间。

五、起始位侦测

每个码元都要侦测。

手册上写的说明:

六、波特率发生器

发送器和接收器的波特率由波特率寄存器BRR里的DIV确定 ,计算公式:波特率 = fPCLK2/1 / (16 bote。DIV就是分频器。

三、串口发送代码

可以使用下面的函数可以重定向到串口,第一个函数可以用printf();第二个直接调用函数。

cpp 复制代码
//重定向到串口
int fputc(int ch,FILE *f)
{
	Serial_SendByte(ch);
 	return ch;
}

//重定向到串口
void Serial_Printf(char *format,...)
{
	
	char String[100];
	va_list arg;
	va_start(arg,format);
	vsprintf(String,format,arg);//都添加到string字符串里
	va_end(arg);
	Serial_SendString(String);
	
}

发送汉字时,要加一点东西,在魔法棒------>C/C++------>Misc Controls上加--no-multibyte-chars

可以在如下所示修改适合的编码形式。

Serial.c:

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "stdarg.h"
void Serial_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;  //如果有错误,就把这个函数放在最前面
	USART_InitTypeDef  USART_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	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);
//	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //推挽输出
//	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
	
	//Polarity  极性
	
	USART_InitStruct.USART_Parity=USART_Parity_No ;               //奇偶
	USART_InitStruct.USART_BaudRate=9600;
	USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode=USART_Mode_Tx;   //作为发送器,同时收发或上
	USART_InitStruct.USART_StopBits=USART_StopBits_1 ;
	USART_InitStruct.USART_WordLength=USART_WordLength_8b ;
	
	USART_Init(USART1,&USART_InitStruct);
	

	USART_Cmd(USART1,ENABLE);

}

void Serial_SendByte(uint8_t Byte)
{
	 
	 //每次写入数据时,硬件会自动将TXE标志位清零
    USART_SendData(USART1, Byte);	
	 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	
	
}

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

void Serial_SendString(char *Array)
{
	
		while(*Array!='\0')
		{
		  Serial_SendByte(*Array);
	     Array++;
		}
}

uint32_t Serial_Pow(uint16_t x,uint16_t y)
{
     uint32_t Result=1;
     	while(y--)
		{
			Result*=x;
		}
	  return  Result;
}

void Serial_SendNumber(uint32_t Number,uint8_t length)
{
	
	uint16_t i;
	for(i=0;i<length;i++)
	{
		
		Serial_SendByte(Number/Serial_Pow(10,length-1-i)%10+0x30);
		
	}
	
}

//重定向到串口
int fputc(int ch,FILE *f)
{
	Serial_SendByte(ch);
 	return ch;
}


void Serial_Printf(char *format,...)
{
	
	char String[100];
	va_list arg;
	va_start(arg,format);
	vsprintf(String,format,arg);
	va_end(arg);
	Serial_SendString(String);
	
}

Serial.h:

cpp 复制代码
#ifndef _SERIAL_H
#define _SERIAL_H



void Serial_Init(void);


void Serial_SendByte(uint8_t Byte);
void Serial_SendString(char *Array);

void Serial_SendArray(uint8_t *Array,uint8_t length);



void Serial_SendNumber(uint32_t Number,uint8_t length);
void Serial_Printf(char *format,...);


#endif

main.c:

cpp 复制代码
#include  "stm32f10x.h"                  // Device header
#include  "OLED.h"
#include  "delay.h"
#include  "Serial.h"
#include  "stdio.h"

int main(void)
{
	
//   uint8_t MyArray[]={0x41,0x43,0x44,0x45};
	//uint8_t MyArray[]="ABCDE11111111111";
   OLED_Init();
	Serial_Init();
	Serial_SendByte(0x41);
	//Serial_SendString(MyArray);
	//Serial_SendArray(MyArray,4);
	//Serial_SendNumber(12345,5);
	//printf("Num=%d\r\n",666);	
	//Serial_Printf("Num=%d\r\n",666);
	Serial_Printf("你好,世界");
	
	while(1) 
	{
     
	}
	
}

四、串口接收

Serial.c:

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "stdarg.h"

uint16_t Rxdata;
char String[16]={0};
uint16_t Flag;
uint16_t Flag_String;
void Serial_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;  //如果有错误,就把这个函数放在最前面
	USART_InitTypeDef  USART_InitStruct;
	NVIC_InitTypeDef NVIC_InitStructure ;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	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);
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //推挽输出
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	//Polarity  极性
	
	USART_InitStruct.USART_Parity=USART_Parity_No ;               //奇偶
	USART_InitStruct.USART_BaudRate=9600;
	USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;   //作为发送器,同时收发或上
	USART_InitStruct.USART_StopBits=USART_StopBits_1 ;
	USART_InitStruct.USART_WordLength=USART_WordLength_8b ;
	
	USART_Init(USART1,&USART_InitStruct);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
   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);

}

void Serial_SendByte(uint8_t Byte)
{
	 
	 //每次写入数据时,硬件会自动将TXE标志位清零
    USART_SendData(USART1, Byte);	
	 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	
	
}

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

void Serial_SendString(char *Array)
{
	
		while(*Array!='\0')
		{
		  Serial_SendByte(*Array);
	     Array++;
		}
}

uint32_t Serial_Pow(uint16_t x,uint16_t y)
{
     uint32_t Result=1;
     	while(y--)
		{
			Result*=x;
		}
	  return  Result;
}

void Serial_SendNumber(uint32_t Number,uint8_t length)
{
	
	uint16_t i;
	for(i=0;i<length;i++)
	{
		
		Serial_SendByte(Number/Serial_Pow(10,length-1-i)%10+0x30);
		
	}
	
}

//重定向到串口
int fputc(int ch,FILE *f)
{
	Serial_SendByte(ch);
 	return ch;
}


void Serial_Printf(char *format,...)
{
	
	char String[100];
	va_list arg;
	va_start(arg,format);
	vsprintf(String,format,arg);
	va_end(arg);
	Serial_SendString(String);
	
}





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

uint8_t Serial_GetRxData(void)
{
	
   	return Rxdata;
	
}


void  USART1_IRQHandler(void)
{
	
	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
	{
		
		 Rxdata=USART_ReceiveData(USART1);
		 Flag=1;
		 Flag_String++;
		 
		 USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
	
}

Serial.h:

cpp 复制代码
#ifndef _SERIAL_H
#define _SERIAL_H



void Serial_Init(void);


void Serial_SendByte(uint8_t Byte);
void Serial_SendString(char *Array);

void Serial_SendArray(uint8_t *Array,uint8_t length);



void Serial_SendNumber(uint32_t Number,uint8_t length);
void Serial_Printf(char *format,...);
uint8_t Serial_GetRxData(void);
uint8_t Serial_GetRxFlag(void);



#endif

main.c:

cpp 复制代码
#include  "stm32f10x.h"                  // Device header
#include  "OLED.h"
#include  "delay.h"
#include  "Serial.h"
#include  "stdio.h"

extern char String[16];
uint16_t Serial_Rxdata;
int main(void)
{
	
   
   OLED_Init();
	Serial_Init();
   
	OLED_ShowString(1,1,"RXData:");
	
	while(1) 
	{
         if(Serial_GetRxFlag()==1)
			{
//				Serial_Rxdata=Serial_GetRxData();
//				Serial_SendByte(Serial_Rxdata);
//				OLED_ShowHexNum(1,8,Serial_Rxdata,2);
				
				OLED_ShowString(1,8,String);
			}
	}
	
}

五、串口收发-数据包

1、数据模式

显示汉字时/字符时,需要两端的编码相同,否则会产生错误。

2、HEX数据包

数据的包头包尾可以自己随便设置,但最好不要设置与传输的数据一样,否则会出现判错。

相关推荐
Whappy001几秒前
STM32单片机(F03C8T6)-点灯(寄存器点灯和库函数点灯)
stm32·单片机·嵌入式硬件
zyjishuyuan19 分钟前
20 基于STM32的温度、电流、电压检测proteus仿真系统(OLED、DHT11、继电器、电机)
stm32·嵌入式硬件·proteus
中智讯21 分钟前
健康腕带系统
嵌入式硬件
飞凌嵌入式2 小时前
飞凌嵌入式FET527N-C核心板已适配OpenHarmony4.1
arm开发·嵌入式硬件·飞凌嵌入式
每天一杯冰美式oh3 小时前
51单片机的智能水温控制系统【proteus仿真+程序+报告+原理图+演示视频】
嵌入式硬件·51单片机·proteus
cykaw25903 小时前
ESP-01S WIFI模块指南
c语言·单片机·嵌入式硬件·51单片机
ZARD9964 小时前
I2C通信时序
单片机·嵌入式硬件
梦境虽美,却不长6 小时前
51单片机快速入门之PWM控制 灯的亮度 2024年10/15
单片机·嵌入式硬件·51单片机
Abaaba+6 小时前
【树莓派 5B】重启后vnc无法使用失效
python·stm32·单片机·嵌入式硬件