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数据包

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

相关推荐
杰克逊的日记9 天前
MCU编程
单片机·嵌入式硬件
Python小老六9 天前
单片机测ntc热敏电阻的几种方法(软件)
数据库·单片机·嵌入式硬件
懒惰的bit9 天前
STM32F103C8T6 学习笔记摘要(四)
笔记·stm32·学习
HX科技9 天前
STM32给FPGA的外挂FLASH进行升级
stm32·嵌入式硬件·fpga开发·flash·fpga升级
Suagrhaha9 天前
驱动入门的进一步深入
linux·嵌入式硬件·驱动
国科安芯9 天前
基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
嵌入式硬件·硬件架构·硬件工程
Li Zi9 天前
STM32 ADC(DMA)双缓冲采集+串口USART(DMA)直接传输12位原始数据到上位机显示并保存WAV格式音频文件 收藏住绝对实用!!!
经验分享·stm32·单片机·嵌入式硬件
进击的程序汪9 天前
触摸屏(典型 I2C + Input 子系统设备)从设备树解析到触摸事件上报
linux·网络·嵌入式硬件
damo王9 天前
Zephyr 系统深入解析:SoC 支持包结构与中断调度器调优实践
单片机·嵌入式硬件·zephyr
逼子格9 天前
硬件工程师笔试面试高频考点汇总——(2025版)
单片机·嵌入式硬件·面试·硬件工程·硬件工程师·硬件工程师真题·硬件工程师面试