4.STM32F40x 串口通信(文中以USART1为例子)

一、USART概念内容

       串口通信是一种通过串行接口进行数据传输的通信方式。在串口通信中,数据是以的形式逐个传输的,通常使用RS-232、RS-485、USB等接口标准。串口通信可以用于连接计算机和外部设备、嵌入式系统之间的通信,常见的应用包括串口打印机、串口调制解调器、串口通信设备等。串口通信的优点是传输距离远、成本低、稳定可靠,但传输速度相对较慢

       通用同步异步收发器(USART)能够灵活地与外部设备进行全双工数据交换,满足外部设备对工业标准NRZ异步串行数据格式的要求。USART通过小数波特率发生器提供了多种波特率。

      它支持同步单向通信和半双工单线通信;还支持LIN(局域互连网络)、智能卡协议与IrDA (红外线数据协会)SIR ENDEC规范,以及调制解调器操作(CTS/RTS)。而且,它还支持多处理器通信。通过配置多个缓冲区使用DMA可实现高速数据通信

二、USART主要特性

        1. 异步或同步传输:USART可以以异步模式或同步模式进行数据传输。异步传输使用起始位和停止位确定数据帧的开始和结束,而同步传输使用外部时钟信号进行同步。

       2. 支持多种数据格式:USART支持多种数据格式,包括数据位数(通常为8位)、校验位(奇偶校验或无校验)和停止位数(通常为1位或2位)的配置。

      3. 双工通信:USART支持全双工通信,可以同时进行数据的发送和接收。

      4. 可编程波特率:USART允许用户根据需要设置波特率,以适应不同的通信速率要求。

      5. 中断支持:USART提供中断功能,可以在数据接收或发送完成时触发中断,以便及时处理数据。

     6. 多个通信模式:USART可以配置为主模式或从模式。在主模式下,USART可以控制通信的时序和流程,而在从模式下,USART将根据外部设备的控制进行通信。

     7. 缓冲器支持:USART通常具有接收和发送缓冲器,用于存储接收到的数据和待发送的数据。

    8. 硬件流控制:一些USART支持硬件流控制功能,包括使用RTS(请求发送)和CTS(清除发送)信号进行数据流控制。

三、USART功能说明

         接口通过三个引脚从外部连接到其他设备(请参见USART框图)。任何USART双向通信均需要至少两个引脚:接收数据输入引脚(RX)和发送数据输出引脚(TX)。

       RX:接受数据输入引脚是指串行数据输入引脚。过采样技术可区分有效输入数据和噪声,从而用于恢复数据。

      TX:发送数据输出引脚,如果关闭发送器,该输出引脚模式由其I/O端口决定。如果使能了发送器但没有待发送数据,则TX引脚处于高电平。在单线和智能卡模式下,该I/O用于发送和接收数据(USART电平下,随后在SW_RX上接收数据)。

USART框图如下:

        4.STM32F40x 串口通信(文中以USART1为例子)_第1张图片

四、USART字符说明

      可通过对USART_CR1寄存器中的M位进行编程来选择8位或9位的字长(请参见USART字符框图)。TX引脚在起始位工作期间处于低电平状态。在停止位工作期间处于高电平状态。

     空闲字符可理解为整个帧周期内电平均为“1”(停止位的电平也是“1”),该字符后是下一个数据帧的起始位。

      停止字符可理解为在一个帧周期内接收到的电平均为“O”。发送器在中断帧的末尾插入1或2个停止位(逻辑“1”位)以确认起始位。

发送和接收由通用波特率发生器驱动,发送器和接收器的使能位分别置1时将生成相应的发送时钟和接收时钟。

USART字符框图如下所示:

                        4.STM32F40x 串口通信(文中以USART1为例子)_第2张图片                                                  

五、USART可配置停止位

        可以在控制寄存器⒉的位13和位12中编程将随各个字符发送的停止位的数量。

         1个停止位。这是停止位数量的默认值。

        2个停止位。正常USART模式、单线模式和调制解调器模式支持该值。

        0.5个停止位。在智能卡模式下接收数据时使用。

       1.5个停止位。在智能卡模式下发送和接收数据时使用。

        空闲帧发送将包括停止位。

       m=0时,中断发送是10个低电平位,然后是已配置数量的停止位;m=1时,中断发送是11个低电平位,然后是已配置数量的停止位。无法传送长中断(中断长度大于10/11个低电平位)。

USART可配置的停止位框图如下:

4.STM32F40x 串口通信(文中以USART1为例子)_第3张图片

六、USART 中断

USART中断请求框图如下:

4.STM32F40x 串口通信(文中以USART1为例子)_第4张图片

    USART中断事件被连接到相同的中断向量(请参见USART中断映射图)。

发送期间:发送完成、清除以发送或发送数据寄存器为空中断。

接收期间:空闲线路检测、上溢错误、接收数据寄存器不为空、奇偶校验错误、LIN断路检测、噪声标志(仅限多缓冲区通信)和帧错误(仅限多缓冲区通信) 如果相应的使能控制位置1,则这些事件会生成中断。USART中断映射图如下所示:

4.STM32F40x 串口通信(文中以USART1为例子)_第5张图片

七、USART模式配置图

4.STM32F40x 串口通信(文中以USART1为例子)_第6张图片

八、USART复用功能图 

4.STM32F40x 串口通信(文中以USART1为例子)_第7张图片

九、配置USART函数

1.开启串口时钟和GPIO时钟使能

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟

2. GPIO端口模式设置

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用模式

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz

GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出

GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉

GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10

3.PA9和PA10要设置为复用功能

GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //PA9复用为USART1

GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);//PA10复用为USART1

4. 串口参数初始化:设置波特率,字长,奇偶校验等参数

   串口初始化是调用函数USART_Init来实现,各个参数配置如下:

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(USART1, &USART_InitStructure); //初始化串口

  5. 使能串口

USART_Cmd(USART1, ENABLE);  //使能串口

  6. 串口数据发送与接收函数

(1)数据发送函数

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

(2)数据接收函数

uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

 7. 串口状态

    (1)读取串口状态的函数是:

FlagStatus USART_GetFlagStatus(USART_TypeDef*USARTx, uint16_t USART_FLAG);

(2)判断读寄存器是否非空(RXNE)

USART_GetFlagStatus(USART1,USART_FLAG_RXNE);

(3)判断发送是否完成(TC)

USART_GetFlagStatus(USART1,USART_FLAG_TC);

8. 开启中断并且初始化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寄存器

同时,我们还需要使能相应中断,使能串口中断的函数原型是:

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

  接收到数据的时候(RXNE读数据寄存器非空),我们要产生中断时函数为:

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断,接收到数据中断

发送数据结束的时候(TC,发送完成)要产生中断:

USART_ITConfig(USART1,USART_IT_TC,ENABLE);

9. 获取相应中断状态

   使用的函数是

ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);

调用这个函数来判断到底是否是串口发送完成中断:

USART_GetITStatus(USART1, USART_IT_TC);

10. 中断服务函数

void USART1_IRQHandler(void);

十、代码汇总

   1.usart1.c

  

#include "usart.h" 

/*****加入以下代码,支持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; 
	} 
	void _ttywrch(int ch)
	{
		ch = ch;
	}
	//重定义fputc函数 
	int fputc(int ch, FILE *f)
	{ 	
		while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
		USART_SendData(USART1, ch);	
		return ch;
	}
/**********************printf support end**********************/	  
#endif

void USART1_Init(u32 bound){
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);  //使能A口时钟--RCC
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 , ENABLE ); //初始化串口1的时钟--RCC

	 //初始化引脚PA9/10--复用推挽输出--GPIO
	GPIO_InitTypeDef  GPIO_InitStruct={0};
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF;  //复用
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;  //推挽
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9|GPIO_Pin_10;//引脚
    GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP; //上拉
	GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed;//25MHZ翻转速度
	GPIO_Init( GPIOA, &GPIO_InitStruct);

	//引脚映射USART1上--GPIO
	GPIO_PinAFConfig( GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig( GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
		
	
	//初始化串口:--USART
	USART_InitTypeDef  USART_InitStruct={0};
    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(USART1, &USART_InitStructure); //初始化串口

		
	//配置中断--USART
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);  //使能串口1的接收中断
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);  //使能串口1的空闲中断

	//配置NVIC--MISC
	NVIC_InitTypeDef NVIC_InitStruct   = {0};
	NVIC_InitStruct.NVIC_IRQChannel    =  USART1_IRQn;  //37主控芯片头文件stm32f4xx.h
	NVIC_InitStruct.NVIC_IRQChannelCmd =  ENABLE;  //开启串口1的中断
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3; // 优先级 默认
	NVIC_InitStruct.NVIC_IRQChannelSubPriority        = 3; // 响应	
	NVIC_Init(&NVIC_InitStruct);
		
	//开启串口CMD--USART
	USART_Cmd(USART1, ENABLE);
}


//发送--先等待前一个发送完成,再发送下一个  -- USART
void Usart1_Send(char ch)//发送一个字节函数{

	 while( USART_GetFlagStatus( USART1, USART_FLAG_TC ) != SET )//死等发送完成
	 {
	   ;
	 }
	 USART_SendData(USART1, ch);//发送一个字节
}

//接收--先等接收完成,返回接收数据  -- USART
char Usart1_Receive(void){

	 char ch=0;
	 while( USART_GetFlagStatus( USART1, USART_FLAG_RXNE ) != SET )//死等接收完成
	 {
	   ;
	 }
   ch=USART_ReceiveData( USART1 );
   return ch;
}


Usart1struct Usart1dat={0};

//中断服务函数--启动文件(.s--CMSIS)无返回值,无形参  //0 0011 0000 1 1帧 '0'
void USART1_IRQHandler(void){

    //判断是谁产生的中断--USART
	  if(USART_GetITStatus(USART1, USART_IT_RXNE)== SET){

		   Usart1dat.Usart1_Rbuf[Usart1dat.Usart1_Rlen++] = USART_ReceiveData( USART1 );
			 if(Usart1dat.Usart1_Rlen >=256) Usart1dat.Usart1_Rlen = 0;
		}

	  if(USART_GetITStatus(USART1, USART_IT_IDLE)== SET){  //空闲中断接收完成

		    Usart1dat.Usart1_Rbuf[Usart1dat.Usart1_Rlen] = '\0';  //变成字符串
			Usart1dat.Usart1_Rflag = 1;  //接收完成标记位置1
        USART_ReceiveData(USART1);   // 清除空闲中断标志      

		}
	
}
   2.usart1.h
#ifndef USART_H
#define USART_H
#include "stm32f4xx.h" 
#include "stdio.h" 

typedef struct{

   char Usart1_Rbuf[256];
	 int  Usart1_Rlen;
	 char Usart1_Rflag;
}Usart1struct;

extern Usart1struct Usart1dat;

void USART1_Init(u32 bound);
void Usart1_Send(char ch);
char Usart1_Receive(void);

#endif
   3.main.c
#include "systick.h"
#include "usart.h"
#include "string.h"
#include "led.h"

int main(){

    SsyTick_Init(168);  //延时初始化
    LED_Init();  // LED初始化
    UASRT1_Init(9600);  

    printf("我已进入程序,请启动吧!\r\n");
   

    while(1){

      printf("已启动,请输入数据吧!\r\n");
      
      if(Usart1dat.Usart1_Rflag == 1){

			printf("%s\r\n",Usart1dat.Usart1_Rbuf);
			if(strcmp(Usart1dat.Usart1_Rbuf, "ledlon") == 0{

			    LED1 = 0; // 输出低电平,亮灯

			  }
            else if(strcmp(Usart1dat.Usart1_Rbuf, "ledoff") == 0){
              
                LED1 = 1; //灭灯

              }
        }
      
   }
}

十一、重写printf底层函数fputc,让printf映射到串口上

所以我直接使用下面代码,/*****加入以下代码,支持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; 
} 
void _ttywrch(int ch)
{
ch = ch;
}
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1, ch);
return ch;
}
/**********************printf support end**********************/   
#endif

十二、中断服务函数简单说明

     中断服务函数是在相应的中断事件发生时由系统自动调用的函数。上面usart1.c中给出的函数USART1_IRQHandler是用于处理USART1的中断事件的中断服务函数。函数声明中void表示该函数不返回任何值。

十三、NVIC中断补充

程序优先处理部分,通过优先级排序

1、优先级

中断会打断主循环的代码,那如果多个中断同时发生呢?这时候就需要一个东西来约定—>优先级  M4 ---3种

优先级分为三种:(数字越小越高)比如: 15   1的优先级比5

 (1)自然优先级(普通会员)----天生的

      ST工程师设定的优先级,不可修改 给了固定的标号

     A(自然优先级45< B(自然优先级40

(2)响应优先级(VIP会员)----可以修改

         用户自定义的优先级

        A(响应优先级2> B(响应优先级3C(响应优先级为1

假设事件A和事件B同时发生,管理员(NVIC)通知内核优先处理事件A,如果这个时候事件C发送了,那么处理完事件A先处理事件C,最后再处理事件B

//不打断正在发生的中断事件,后续中断事件都需按优先级进行排队

(3)抢占优先级(SVIP会员,—>专属客户)----可以修改

         用户自定义的优先级,抢占优先级 > 响应优先级

         A (抢占优先级2> B(抢占优先级4, C(抢占优先级为1

在处理事件A的过程中,发生了事件B,这个时候事件A不会被打断。如果这个时候发送了事件C,那么事件A将会被打断,优先执行事件C

//会打断正在发生的中断事件,后续中断事件都需按优先级进行排队

如果抢占优先级一样呢?对比响应优先级,如果响应优先级还是一样呢?对比自然优先级,自然优先级必然是不一样的。

2、优先级分组  抢占  响应优先级 最多能设置多少呢?

      NVIC控制器的优先级使用8个二进制位表示,分成两部分表示,分别是抢占优先级和响应优先级(也称子优先级),这8个比特位用于设置中断源的优先级,可以有8种分配方式。Cortex-M允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,但是最少不能低于3ST的工程师在设计的时候只用了四个位,这四个位可以分成5组不同的组合  11    11

0组:所有4位用于指定响应优先级,无抢占优先级。 0~15=16

1组:最高1位用于指定抢占优先级,最低3位用于指定响应优先级。

2组:最高2位用于指定抢占优先级,最低2位用于指定响应优先级。 4 4

3组:最高3位用于指定抢占优先级,最低1位用于指定响应优先级。 8 2

4组:最高4位用于指定抢占优先级,无响应优先级。     

注:系统复位后默认组0

十四、总结

     在学习USART串口通信需要理解基本概念、掌握配置和初始化、熟悉数据收发和中断处理,并在实际应用中进行调试和验证。通过不断的学习和实践,可以掌握USART串口通信并应用于嵌入式系统和通信设备中。

   

注意:本人所写文章均用于记录自己的学习过程!!!

你可能感兴趣的:(STM32F40x,stm32,嵌入式硬件,单片机)