STM32 USART模块详解及应用笔记

课外知识插入:

NMOS和PMOS电流流向以及导通条件_pmos和nmos_perseverance52的博客-CSDN博客https://blog.csdn.net/weixin_42880082/article/details/121281677

 导通条件
NMOS的特性,Vgs大于一定的值就会导通,适合用于源极接地时的情况(低端驱动),只要栅极电压达到4V或10V就可以了。
PMOS的特性,Vgs小于一定的值就会导通,适合用于源极接VCC时的情况(高端驱动)。但是,虽然PMOS可以很方便地用作高端驱动,但由于导通电阻大,价格贵,替换种类少等原因,在高端驱动中,通常还是使用NMOS。

  • NMOS管的主回路电流方向为D→S,导通条件为VGS有一定的压差,一般为5~10V(G电位比S电位高);
  • PMOS管的主回路电流方向为S→D,导通条件为VGS有一定的压差,一般为-5~-10V(S电位比G电位高)。
  • ​​​​​​​

  • 同步/异步 | 全双工/半双工

    1. uart: 全双工、异步通信
    2. spi : 全双工、同步通信
    3. i2c : 半双工、同步通信

    1. 单工:简单的说就是一方只能发信息,另一方则只能收信息,通信是单向的。
    2. 半双工:比单工先进一点,就是双方都能发信息,但同一时间则只能一方发信息。
    3. 全双工:比半双工再先进一点,就是双方不仅都能发信息,而且能够同时发送。​​​​​​​同步/异步 | 全双工/半双工_同步半双工-CSDN博客https://blog.csdn.net/hua_zai_arm/article/details/53020658

    1. 在cpu和外设通信时外设要和cpu同步,所有使用的时间频率的一致的。
               如:i2c总线中有scl时钟线、sda数据线。我们可以把它看成两个普通gpio接口,当时钟线为高时,数据寄存器读或写。

    2. 同步通信与异步通信:
       a. 同步(如上图1):
          1. 把许多字符组成一个信息组,字符可以一个接一个地传输,每一次是传送一组信息(信息帧),中间没有空隙;
          2. 开始要加上同步字符,在没有信息要传输时,要填上空字符;
          3. 发送方除了发送数据,还要有同步时钟信号(cpu),传输双方用同一时钟信号线,确定传输中每1位的节拍的位置。

       b. 异步(如上图2):
          1. 一次发送一个字符,所发送的字符之间的时间间隔可以是任意的。
          2. 接收端必须时刻做好接收的准备,发送端可任意时刻开始发送字符
          3. 必须在每一个字符的开始和结束的地方加上标志,即加上开始位和停止位,确地将每一个字符接收下来
          4. 特性:通信设备简单、便宜,但传输效率较低(因为开始位和停止位的开销所占比例较大)。

       c. 同步、异步区别:
           1. 同步通信时钟频率一致(同一条时钟线相连),发送端发送连续的比特流;异步通信时不要求;接收端时钟和发送端时钟同步,发送端发送完一个字节后,可经过任意长的时间间隔再发送下一个字节。
           2. 同步通信效率高;异步通信效率较低。
           3. 同步通信较复杂,双方时钟的允许误差较小;异步通信简单,双方时钟可允许一定误差。
           4. 同步通信可用于点对多点;异步通信只适用于点对点。  

    extern

    STM32单片机extern全局变量_stm32全局变量-CSDN博客https://blog.csdn.net/weixin_44259607/article/details/109314133

            如果你把temple定义在A中,然后让A.h和B.h包含在includes.h中,然后把includes.h放在A.c和B.c中单个编译是没有问题的,但是链接的时候会出现问题,

    “Symbol temple mulTIply defined(by A.o and B.o)”
    

            这个变量被多次定义了!!!

            在A中定义temple变量后,在B中用extern 声明一下例如:

    1.在A中定义temple并且赋值:u16 temp2=0;
    
    2.在B中声明extern u16 temp2;
    

            这里只是声明,不再赋值,否则会报错!

    Symbol temp2 mulTIply defined (by catch_pwm.o and app.o)
    

    这里要注意变量定义和变量声明的区别:
            变量定义使用“数据类型+变量名称”的形式,编译器需要给它分配内存单元的;
            而变量声明使用“extern 变量类型+变量名称”的形式,是告诉编译器我这个变量将在其他外部C文件中定义,我这里只是在外部用它。编译器就不会给它分配内存空间,而等到真正遇到变量定义时再给它分配内存空间。

            比如在一个 my.c文件中,我定义了char name[10];那么在别的文件中只要用extern
    char name[],外部声明就可以了,告诉编译器这个变量我已经定义过了,具体怎样,你慢慢找吧。这符合常理,因为char是编译器能自主识别的类型。

    一、USART功能概述

            任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX)。        

            RX:接收数据串行输。通过过采样技术来区别数据和噪音,从而恢复数据。
            TX:发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。总线在发送或接收前应处于空闲状态。
       
         ● 一个起始位
            ● 一个数据字(8或9位),最低有效位在前
            ● 0.5,1.5,2个的停止位,由此表明数据帧的结束
            ● 使用分数波特率发生器 —— 12位整数和4位小数的表示方法。
            ● 一个状态寄存器(USART_SR)
            ● 数据寄存器(USART_DR)
            ● 一个波特率寄存器(USART_BRR),12位的整数和4位小数
            ● 一个智能卡模式下的保护时间寄存器(USART_GTPR)

            下列引脚在硬件流控模式中需要:
                   nCTS: 清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送。

                   nRTS: 发送请求,若是低电平,表明USART准备好接收数据

    二、USART 特性描述

             字长可以通过编程USART_CR1寄存器中的M位,选择成8或9位(见图249)。在起始位期间,TX脚处于低电平,在停止位期间处于高电平。
            空闲符号被视为完全由’1’组成的一个完整的数据帧,后面跟着包含了数据的下一帧的开始位(‘1’的位数也包括了停止位的位数)。断开符号 被视为在一个帧周期内全部收到’0’(包括停止位期间,也是’0’)。在断开帧结束时,发送器再插入1或2个停止位(‘1’)来应答起始位。
            发送和接收由一共用的波特率发生器驱动,当发送器和接收器的使能位分别置位时,分别为其产生时钟。

     三、发送器

            发送器根据M位的状态发送8位或9位的数据字。当发送使能位(TE)被设置时,发送移位寄存器中的数据在TX脚上输出,相应的时钟脉冲在CK脚上输出。
            字符发送
            在USART发送期间,在TX引脚上首先移出数据的最低有效位。在此模式里,USART_DR寄存器包含了一个内部总线和发送移位寄存器之间的缓冲器。
            每个字符之前都有一个低电平的起始位;之后跟着的停止位,其数目可配置。
            USART支持多种停止位的配置:0.5、1、1.5和2个停止位。
            注: 在数据传输期间不能复位TE位,否则将破坏TX脚上的数据,因为波特率计数器停止计数。正在传输的当前数据将丢失。

            可配置的停止位
            随每个字符发送的停止位的位数可以通过控制寄存器2的位13、12进行编程。
            1. 1个停止位:停止位位数的默认值。
            2. 2个停止位:可用于常规USART模式、单线模式以及调制解调器模式。
            3. 0.5个停止位:在智能卡模式下接收数据时使用。
            4. 1.5个停止位:在智能卡模式下发送和接收数据时使用。
            空闲帧包括了停止位。
            断开帧是10位低电平,后跟停止位(当m=0时);或者11位低电平,后跟停止位(m=1时)。不可能传输更长的断开帧(长度大于10或者11位)。

     配置步骤:
            1. 通过在USART_CR1寄存器上置位UE位来激活USART
            2. 编程USART_CR1的M位来定义字长。
            3. 在USART_CR2中编程停止位的位数。
            4. 如果采用多缓冲器通信,配置USART_CR3中的DMA使能位(DMAT)。按多缓冲器通信中的描述配置DMA寄存器。
            5. 利用USART_BRR寄存器选择要求的波特率。
            6. 设置USART_CR1中的TE位,发送一个空闲帧作为第一次数据发送。
            7. 把要发送的数据写进USART_DR寄存器(此动作清除TXE位)。在只有一个缓冲器的情况下,对每个待发送的数据重复步骤7。
            8. 在USART_DR寄存器中写入最后一个数据字后,要等待TC=1,它表示最后一个数据帧的传输结束。当需要关闭USART或需要进入停机模式之前,需要确认传输结束,避免破坏最后一次传输。

            单字节通信
            清零TXE位总是通过对数据寄存器的写操作来完成的。TXE位由硬件来设置,它表明:
            ● 数据已经从TDR移送到移位寄存器,数据发送已经开始
            ● TDR寄存器被清空
            ● 下一个数据可以被写进USART_DR寄存器而不会覆盖先前的数据

            如果TXEIE位被设置,此标志将产生一个中断。
            如果此时USART正在发送数据,对USART_DR寄存器的写操作把数据存进TDR寄存器,并在当前传输结束时把该数据复制进移位寄存器。
         
       如果此时USART没有在发送数据,处于空闲状态,对USART_DR寄存器的写操作直接把数据放进移位寄存器,数据传输开始,TXE位立即被置起。
            当一帧发送完成时(停止位发送后)并且设置了TXE位,TC位被置起,如果USART_CR1寄存器中的TCIE位被置起时,则会产生中断。

            在USART_DR寄存器中写入了最后一个数据字后,在关闭USART模块之前或设置微控制器进入低功耗模式(详见下图)之前,必须先等待TC=1。
          
      使用下列软件过程清除TC位:
            1.读一次USART_SR寄存器;
            2.写一次USART_DR寄存器。
    注: TC位也可以通过软件对它写’0’来清除。此清零方式只推荐在多缓冲器通信模式下使用。

            

     四、接收器

    1. 起始位侦测

    2.字符接收

     五、分数波特率的产生

            发送器和接收器的波特率由波特率寄存器BRR里的DIV确定,计算公式:波特率 = fPCLK2/1 / (16 * DIV)

     如何从USART_BRR寄存器值得到USARTDIV
    例1:
    如果 DIV_Mantissa = 27 , DIV_Fraction = 12 (USART_BRR=0x1BC),

    于是
    Mantissa (USARTDIV) = 27
    Fraction (USARTDIV) = 12/16 = 0.75
    所以 USARTDIV = 27.75
    例2:
    要求 USARTDIV = 25.62,
    就有:
    DIV_Fraction = 16*0.62 = 9.92
    最接近的整数是:10 = 0x0A
    DIV_Mantissa = mantissa (25.620) = 25 = 0x19
    于是,USART_BRR = 0x19A
    例3:
    要求 USARTDIV = 50.99
    就有:
    DIV_Fraction = 16*0.99 = 15.84
    最接近的整数是:16 = 0x10 => DIV_frac[3:0]溢出 => 进位必须加到小数部分
    DIV_Mantissa = mantissa (50.990 + 进位) = 51 = 0x33
    于是:USART_BRR = 0x330,USARTDIV=51

    六、USART中断

    七、HEX数据包发送

            1.1固定包长,含包头包尾(包尾不是必须的)


            1.2可变包长,含包头包尾

    1、包头包尾和数据载荷重复的问题,传输的数据本身是FF和FE,可能引起误判
            解决:限制载荷数据的范围,限幅(例如只发送0~100)
            如果无法避免数据与包头包尾重复,则尽量使用固定长度的数据包
            增加包头包尾的数量,尽量是其呈现出载荷数据出现不了的状态
    2、包头包尾并不是全部都需要的,例如可以只要一个包头
    3、固定包长和可变包长的选择问题
            对HEX来说,若载荷出现和包头包尾重复的情况,最好选择固定包长,避免接收错误。若不重复,可以选择可变包长
    4、各种数据转化为数据流的问题
            数据包都是一个字节一个字节组成的,若想发送16位整型数据、32位整型数据,float、double、甚至是结构体(其内部都是由一个字节一个字节组成的),只需要用一个uint8_t的指针指向它,把数据当作字节数组发送即可。

    接收HEX数据包

            每收到一个字节,函数都会进入一次中断,在中断函数中,可以拿到一个字节,但拿到字节之后,就得退出中断,故每拿到一个数据,都是一个独立的过程,而对数据包来说,有数据、包头、包尾三种状态,根据状态不同处理也不同。

    STM-32:串口收发数据包—串口收发HEX数据包/串口收发文本数据包_单片机串口数据包_Hello xiǎo lěi的博客-CSDN博客https://blog.csdn.net/qq_27928443/article/details/130088050

    代码详解:(详细注释)
    #include "Serial.h"
    uint8_t Serial_Rx_Package[4];
    uint8_t Serial_Tx_Package[4];
    
    void Serial_Init(void)
    {
    	//1.开启时钟
    	//RCC库函数
    	//存储器和总线构架APB2_usart1/gpioa
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    	//复用重映射和调试I/O配置寄存器(AFIO_MAPR)
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	//0+2.GPIO初始化
    	//10.2.3 函数GPIO_Init
    	//GPIO_InitTypeDef GPIO_InitStructure; 
    	//GPIO_Init(GPIOA, &GPIO_InitStructure);
    	//8.2.1 端口配置低寄存器(GPIOx_CRL) (x=A..E)
    	GPIO_InitTypeDef GPIO_InitStructure;
    	//USART复用功能重映射
    	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);
    	//USARTx_RX浮空输入或带上拉输入
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStructure);
    	//USART1_TX PA9  USART1_RX PA10
    
    	//3.USART初始化
    	//25.3.1 USART 特性描述
    	USART_InitTypeDef USART_InitStructure;					//定义结构体变量
    	USART_InitStructure.USART_BaudRate = 9600;				//波特率
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//硬件流控制,不需要
    	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;	//模式,发送模式和接收模式均选择
    	USART_InitStructure.USART_Parity = USART_Parity_No;		//奇偶校验,不需要
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;	//停止位,选择1位
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//字长,选择8位
    	USART_Init(USART1, &USART_InitStructure);				//将结构体变量交给USART_Init,配置USART1
    	
    	//4.中断输出配置
    	//21.2.5 函数USART_ITConfig
    	//USART_IT_RXNE 接收中断
    	//RXNE:读数据寄存器非空 (Read data register not empty)
    	//0:数据没有收到;1:收到数据,可以读出。
    	//对USART_DR的读操作可以将该位清零。
    	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    
    
    	//5.NVIC中断分组
    	//NVIC_PriorityGroup_2	抢占优先级取值:0~3	响应优先级取值:0~3	
    	//整个系统执行过程中,只设置一次中断分组,多次操作以最后一次设置为准。
    	//针对每个中断,设置对应的抢占优先级和响应优先级。
    	//如不设置中断优先级分组,则中断优先级分组默认为0,即0位抢占优先级,4位响应优先级。
    	//13.2.3 函数NVIC_PriorityGroupConfig
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	
    	//6.NVIC初始化
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2U;
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1U;
    	NVIC_Init(&NVIC_InitStructure);
    
    	//7.使能USART1
    	//21.2.4 函数USART_Cmd
    	USART_Cmd(USART1,ENABLE);
    
    }
    
    /**
      * 函    数:串口发送一个字节
      * 参    数:Byte 要发送的一个字节
      * 返 回 值:无
      */
    void Serial_SendByte(uint8_t Byte)
    {
    	//21.2.12 函数USART_SendData
    	USART_SendData(USART1,Byte);
    	//等待发送完成标志位21.2.22 函数USART_GetFlagStatus
    	// USART_FLAG_TC:   Transmission Complete flag
    	//0:发送还未完成;1:发送完成。
    	while(RESET == USART_GetFlagStatus(USART1,USART_FLAG_TC));
    }
    
    
    
    /**
      * 函    数:串口发送一个数组
      * 参    数:Array 要发送数组的首地址
      * 返 回 值:无
      */
    void Serial_SendArray(uint8_t* Array)
    {
    	uint16_t i = 0;
    	if(i < sizeof(Array)/sizeof(Array[0]))
    	{
    		Serial_SendByte(*(Array+i));依次调用Serial_SendByte发送每个字节数据
    	}
    }
    
    
    
    /**
      * 函    数:串口发送一个字符串
      * 参    数:String 要发送字符串的首地址
      * 返 回 值:无
      */
    void Serial_SendString(uint8_t* String)
    {
    	while(*String++)
    	{
    		Serial_SendByte(*String);
    	}
    }
    
    
    /**
      * 函    数:使用printf需要重定向的底层函数
      * 参    数:保持原始格式即可,无需变动
      * 返 回 值:保持原始格式即可,无需变动
      */
    int fputc(int ch, FILE *f)
    {
    	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
    	return ch;
    }
    
    
    /**
      * 函    数:串口发送数字
      * 参    数:Number 要发送的数字,范围:0~4294967295
      * 参    数:Length 要发送数字的长度,范围:0~10
      * 返 回 值:无
      */
    uint32_t Serial_Pow(uint32_t m,uint32_t n)
    {
    	uint32_t result = 1;
    	while(m--)
    	{
    		result *= n;
    	}
    	return result;
    }
    void Serial_SendNumber(uint32_t Number, uint8_t Length)
    {	
    	uint8_t j = 0;
    	for(j = 0; j < Length; j++)
    	{
    		//12345/10000 = 1%10 = 1
    		//12345/1000 = 12%10 = 2
    		//12345/100 = 123%10 = 3
    		//12345/10 = 1234%10 = 4
    		//12345/1 = 12345%10 = 5
    		Serial_SendByte('0' + Number/Serial_Pow(10,Length-j-1));
    	}
    	
    }
    
    
    
    
    /**
      * 函    数:获取串口接收数据包标志位
      * 参    数:无
      * 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零
      */
    uint8_t Serial_Flag = 0;//接收结束标志位
    uint8_t Get_Status_Serial_Flag(void)
    {
    	if(Serial_Flag == 1)
    	{
    		Serial_Flag = 0;
    		return 1;
    	}
    	else	
    	{
    		return Serial_Flag;
    	}
    }
    
    
    
    
    /**
      * 函    数:串口发送数据包
      * 参    数:无
      * 返 回 值:无
      * 说    明:调用此函数后,Serial_Tx_Package数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去
      */
    void Serial_Send_Package(void)
    {
    	Serial_SendByte(0xFF);
    	Serial_SendArray(Serial_Tx_Package);
    	Serial_SendByte(0xFE);
    }
    
    
    
    /**
      * 函    数:USART1中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      *			  DCD     USART1_IRQHandler          ; USART1
      */
    
    void USART1_IRQHandler(void)
    {
    	uint8_t Serial_Station = 0;//位置判断标志
    	uint8_t Serial_Package_Location = 0;//数据包位置
    	if(USART_GetFlagStatus(USART1,USART_IT_RXNE) == RESET)//如果发生接收中断,说明数据从接收移位寄存器到了接收数据寄存器
    	//RXNE位被置位。它表明移位寄存器的内容被转移到RDR。换句话说,数据已经被接收并且可以被读出。
    	{
    		//21.2.13 函数USART_ReceiveData返回USARTx最近接收到的数据
    		//USART_DR寄存器[8:0]:数据值 (Data value)位8:0包含了发送或接收的数据。
    		//uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
    		uint16_t Serial_Data = USART_ReceiveData(USART1);//读取接收到的数据
    		if(Serial_Station == 0)
    		{
    			if(Serial_Data == 0xFF)
    			{
    				//说明找到了数据包头
    				Serial_Station = 1;
    				Serial_Package_Location = 0;//开始发送数据
    			}
    			else
    			{
    				Serial_Station = 0;//没有找到ff,就一直找
    			}
    		}
    		else if(Serial_Station == 1)//就算传入FF也不会出循环
    		{
    			if(Serial_Package_Location < 4)
    			{
    				Serial_Rx_Package[Serial_Package_Location++] = Serial_Data;//传输数据
    				//传输一次就会置标志位
    			}
    			Serial_Station = 2;//说明数据发送完
    		}
    		else if(Serial_Station == 2)
    		{
    			if(Serial_Data == 0xFE)
    			{
    				//说明找到了数据包尾
    				Serial_Station = 0;
    				Serial_Flag = 1;//发送完成标志位
    			}
    			else
    			{
    				Serial_Station = 2;
    			}
    		}
    		//21.2.25 函数USART_ClearITPendingBit清除USARTx的中断待处理位
    		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
    	}
    
    }
    
    
    
    
    
    
    

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 USART模块详解及应用笔记

    发表评论