STM32 Hal库版—串口、USART(通用同步异步收发器)

一、数据通信的基本概念

1.1.串行 / 并行通信

按数据通信方式分类:串行通信、并行通信
下图是串行通信:
在这里插入图片描述
下图是并行通信:
在这里插入图片描述

特点传输速率抗干扰能力通信距离IO资源占用成本
串行通信较低较强较长较少较低
并行通信较强较弱较短较多较高

1.2.单工 / 半双工 / 全双工通信

按数据传输方向分类:单工通信、半双工通信、全双工通信
下图是单工通信,数据只能沿着一个方向传输。
在这里插入图片描述
下图是半双工通信,数据可以沿着两个方向传输,但需要分开时间进行,当a正在发送数据给b的时候,b不可以发送数据给a。
在这里插入图片描述
下图是全双工通信,数据可以同时进行双向传输。
在这里插入图片描述

1.3.同步 / 异步通信

按数据同步方式分类:同步通信、异步通信。区别该通信方式是否同步或异步,通过该通信有没有时钟线即可。
下图是同步通信,共用同一时钟信号。
在这里插入图片描述
下图是异步通信,没有时钟信号,通过在数据信号中加入起始位和停止位等一些同步信号。
在这里插入图片描述

1.4.波特率

在这里插入图片描述

  • 比特率:每秒传送的比特数,单位bit/s
  • 波特率:每秒钟传送的码元数,单位Baud
  • 二进制系统中,波特率数值上等于比特率,意义不一样
  • 码元数:通信中波特率与采样率的关系(如 USART 中 1 位数据对应 16 个采样时钟),但需结合具体外设(如 SPI、CAN 等)确定其在数据传输里的具体含义。

1.5.常见的串行通信接口

通信接口接口引脚数据同步方式数据传输方向
UART(通用异步收发器)TXD(发送端)、RXD(接收端)、GND(公共地)异步通信全双工
1-wireDQ(发送 / 接收端)异步通信半双工
IICSCL(时钟线)、SDA(数据线)同步通信半双工
SPISCL(时钟线)、MISO(主机输入、从机输出)、MOSI(主机输出、从机输入)同步通信全双工

二、串口(RS-232接口)

2.1.RS-232接口的引脚

在这里插入图片描述
串口是指串行通信接口,按位发送和接收的接口。下面以RS-232接口为例:
以下由9个引脚,一般只是用RXD、TXD和GND。

  • DCD(PIN1):数据载波检测
  • RXD(PIN2):串口数据输入
  • TXD(PIN3):串口数据输出
  • DTR(PIN4):数据终端就绪
  • GND(PIN5):信号地
  • DSR(PIN6):数据发送就绪
  • RTS(PIN7):请求发送
  • CTS(PIN8):清除发送
  • RI(PIN9):振铃指示

2.2.RS-232电平与COMS / TTL电平对比

电平逻辑1逻辑0
RS-232电平-15V ~ -3V+3V ~ +15V
COMS电平3.3V0V
TTL电平5V0V

一般51单片机使用TTL电平,STM32使用COMS电平,如果直接使用RS-232电平,会烧毁机器。COMS / TTL电平不能与RS-232电平直接交换信息。

2.3.设备间的RS-232通信示意图

使用RS-232电平,需要在硬件部分接上一个电平转换芯片。
在这里插入图片描述
两设备之间的TXD和RXD必须交叉连接才能正常通信。
在这里插入图片描述

2.4.RS-232异步通信协议

RS-232

  • 启动位:必须占1个位长,保持逻辑0电平
  • 有效数据位:可选5、6、7、8、9个位长,LSB在前,MSB在后,也就是低位在前
  • 校验位:可选占1个位长,也可以没有该位
  • 停止位:必须有,可选0.5、1、1.5、2个位长,保持逻辑1电平

三、USART(通用同步异步收发器)

3.1.简介

USART的全称是Universal synchronous asynchronous receiver transmitter,通用同步异步收发器;UART是裁剪了同步功能,所以叫通用异步收发器,它们都可以与外部设备进行全双工通信。

3.2.主要特征

  • 全双工异步通信
  • 单线半双工通信
  • 单独的发送器和接收器使能位
  • 可配置使用DMA的多缓冲器通信
  • 多个带标志的中断源

3.3.工作原理框图

CPU发送一个数据,CPU对数据寄存器(DR)进行写操作,不能直接对发送数据寄存器(TDR)进行操作,在数据寄存器(DR)写好了数据之后,自动传入发送数据寄存器,等该寄存器存到满数据之后,并发现发送移位寄存器为空才把数据传入,最后由编解码模块发送出去;CPU接收一个数据,RX引脚从外部接收到数据,先是传入接收移位寄存器,再传入接收数据寄存器(RDR),CPU通过读取数据寄存器(DR)读到该数据,CPU不能对接收数据寄存器进行直接操作。图中最下面有一个传统的波特率发生器,通过USART_BRR寄存器的低16位,来控制发送器和接收器波特率的控制。
在这里插入图片描述

3.4.设置波特率(F1)

f(PCK1)是APB1总线,最高时钟频率36M;f(PCK2)是APB2总线,最高时钟频率72M。如要设置USART1的波特率为9600,可知道以下参数,f(CK)=72M,通过数据手册可知USART1是挂在APB2总线上,USARTDIV也可以算出来,其中DIV_Mantissa是整数部分,由[15:4]位控制,(DIV_Fraction/16)是小数部分,由[3:0]位来控制。
在这里插入图片描述
在这里插入图片描述

3.4.1.下图是波特率寄存器(USART_BRR):

在这里插入图片描述

3.4.2.使用寄存器设置波特率

将USART1设置波特率为115200,USART1挂载在APB2总线上,最大时钟频率为72M,并根据上述公式可知,115200 = 72000000 / 16*USARTDIV,求出USARTDIV = 39.0625。下面代码是将39.0625写入USART_BRR寄存器里:

uint16_t mantissa;
uint16_t fraction;
mantissa = 39;
fracion = 0.625 * 16 + 0.5 = 0x01;	//加上0.5是四舍五入,减少误差
USART->BRR = (mantissa<<4) + fracion;	//小数部分占用[3:0]这四位,因此整数部分要要左移四位

四、USART相关寄存器

4.1.控制寄存器1(USART_CR1)

在这里插入图片描述

该寄存器需要完成的配置:

  • 位13:使能USART
  • 位12:配置8个数据位
  • 位10:禁止检验控制
  • 位5:使能接收缓冲区非空中断
  • 位3:使能发送
  • 位2:使能接收
    在这里插入图片描述

4.2.控制寄存器2(USART_CR2)

在这里插入图片描述
该寄存器需要完成的配置:
位13和位12,设置暂停位的位数。
在这里插入图片描述

4.3.控制寄存器3(USART_CR3)

在这里插入图片描述
该寄存器需要配置:
位3,不选择半双工模式。
在这里插入图片描述

4.4.状态寄存器(USART_SR)

在这里插入图片描述
该寄存器一般用到这三个位:

  • 位7:当发送数据寄存器空了,但是发送移位寄存器非空的情况置1
  • 位6:发送数据寄存器和发送移位寄存器都空的情况置1,
  • 位5:读数据寄存器(DR)非空,意味着CPU可以再数据寄存器里读取数据

根据位6可以知道能否发数据,根据位5知道是否收到数据。
在这里插入图片描述

4.5.数据寄存器(USART_DR)

在这里插入图片描述
该寄存器只有[8:0]位有效,其他都保留了。
在这里插入图片描述

五、回调机制

5.1.HAL库外设初始化MSP回调机制

HAL_PPP_Init(),用户调用该类函数初始化PPP外设的工作参数,它的里面又调用MSP回调函数:HAL_PPP_MspInit()。MSP回调函数的作用是配置PPP外设用到的硬件,如:GPIO、NVIC、CLOCK等,用户可选择是否使用并重新定义该函数。下面是使用MSP回调函数的注意事项:

  • 当多个PPP外设同时使用MSP函数,则可以通过判断外设寄存器基地址区分是哪一个外设,再配置PPP外设用到的硬件
  • 当多个PPP外设共用该函数,文件不好管理,而且放到一起看着比较乱,这时候就不建议使用该函数

下面以UART为例:
HAL_UART_Init();,里面调用MSP回调函数HAL_UART_MspInit();

void HAL_UART_MspInit(UART_HandleTypeDef *huart)	//参数里面称为句柄
{
	GPIO_InitTypeDef gpio_init_struct;
	if(huart->Instance == USART1)	//如果是USART1的基地址,就执行if语句里面的操作
	{
		//使能USART1和对应IO时钟;初始化IO;使能USART1中断,设置优先级	
	}
}

5.2.HAL库中断回调机制

在.s文件里寻找相应的中断服务函数HAL_PPP_IRQHandler();,该函数里面也调用一系列中断回调函数HAL_PPP_xxxCallback();,该回调函数的作用是根据回调函数类型,编写对应的中断处理程序,用户可选择是否使用并重新定义该函数。下面是使用中断回调函数的注意事项:

  • 当多个PPP外设同时使用MSP函数,则可以通过判断外设寄存器基地址区分是哪一个外设,再配置PPP外设用到的硬件
  • 当多个PPP外设共用该函数,文件不好管理,而且放到一起看着比较乱,这时候就不建议使用该函数

下面也是以UART为例:
用户调用HAL库中断共用处理函数HAL_UARTx_IRQHandler();,HAL库再自己调用中断回调函数。

下图是UART中断回调函数,用户需要用到哪一个函数只需重新定义即可。
在这里插入图片描述

六、配置步骤

6.1.UART异步通信配置步骤

  • 配置串口工作参数:HAL_UART_Init()
  • 串口底层初始化:HAL_UART_MspInit(),配置GPIO、NVIC、CLOCK等
  • 开启串口异步接收中断:HAL_UART_Receive_IT()
  • 设置优先级,使能中断:HAL_NVIC_SetPriority()HAL_NVIC_EnableIRQ()
  • 编写中断服务函数:UARTx_IRQHandler()
  • 串口数据发送:USART_DR()HAL_UART_Transmit()

6.2.配置串口工作参数

该函数完整写法是:HAL_StatusTypeDef HAL_UART_Init(UART_HandlerTypeDef *huart)HAL_StatusTypeDef 是一个枚举类型:只有返回0x00才标志着OK;返回0x01是初始化错误;返回0x02是串口处于忙状态;返回0x03是初始化超时。
在这里插入图片描述
形参1是串口的句柄,结构体类型是 UART_HandleTypeDef,下图是该句柄的成员:
在这里插入图片描述
初始化操作只用到前面两个成员USART_TypeDef(配置相关的寄存器)、UART_InitTypeDef(配置对应的功能):
在这里插入图片描述
在这里插入图片描述

6.3.开启串口异步接收中断

该函数的完整写法是:HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size),作用是以中断方式接收指定字节数据,它的函数类型也是枚举类型,与上面小节的函数类型一样,形参1是UART_HandleTypeDef结构体类型指针变量;形参2是指向接收数据缓冲区;形参3是要接收的数据大小,以字节位单位。

6.4.串口数据发送

该函数完整写法是:HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout),作用是以阻塞的方式发送指定字节数据,阻塞的方式就是必须把设定的全部字节发送出去才走出该函数,形参1与上小节的形参1一样;形参2是指向要发送的数据地址;形参3要发送的数据大小并以字节位单位;形参4是设置超时时间,以ms单位。

七、IO引脚复用

7.1.复用

复用是建立在通用上面的,通用:IO端口的输入或输出是由GPIO外设控制(由GPIO的寄存器进行控制,如:ODR,BSRR);复用:IO端口的输入或输出是由其他非GPIO外设控制(由非GPIO寄存器控制,如:USART的DR,TIM的某个通道等)。

7.2.F1系列的IO引脚复用

  • 可查询数据手册引脚定义了解引脚的复用情况
  • 同一时间IO只能用作一种复用功能,只能由用户尽量去避免,否则会造成冲突,两个功能都失效
  • 如果遇到复用冲突情况,考虑重映射功能,重映射到另一个IO口

八、通过串口接受或发送一个字符

8.1.初始化usart

uint16_t g_usart_rx_sta = 0;		//接收标志,初始化为0,接收到置1
uint8_t g_rx_buffer[RXBUFFERSIZE]; 	//HAL库使用的串口接收缓存区
UART_HandleTypeDef g_uart1_handle; 	//UART句柄
void usart_init(uint32_t baudrate)
{
	//UART_InitTypeDef里的成员:
    g_uart1_handle.Instance = USART_UX;                   //USART1             
    g_uart1_handle.Init.BaudRate = baudrate;              //波特率                    
    g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;  //字长为8                    
    g_uart1_handle.Init.StopBits = UART_STOPBITS_1;       //一个停止位                    
    g_uart1_handle.Init.Parity = UART_PARITY_NONE;        //无奇偶校验位                    
    g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;  //无硬件流控                    
    g_uart1_handle.Init.Mode = UART_MODE_TX_RX;           //收发模式                    
    HAL_UART_Init(&g_uart1_handle);          			  //使能USART1
    	                                 
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); 
    //上面函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}

8.2.串口底层初始化;设置优先级,使能中断

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;

    if (huart->Instance == USART_UX)     //如果是串口1,进行串口1 MSP初始化                       
    {
        USART_TX_GPIO_CLK_ENABLE();       //使能串口TX引脚时钟                      
        USART_RX_GPIO_CLK_ENABLE();       //使能串口RX引脚时钟                      
        USART_UX_CLK_ENABLE();            //使能串口时钟                     
		
		//配置PA9为发送引脚,复用推挽输出
        gpio_init_struct.Pin = USART_TX_GPIO_PIN;              
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;               
        gpio_init_struct.Pull = GPIO_PULLUP;                   
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;         
        HAL_GPIO_Init(USART_TX_GPIO_PORT, &gpio_init_struct);
        
        //配置PA10为接收引脚,复用输入模式        
        gpio_init_struct.Pin = USART_RX_GPIO_PIN;               
        gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;    
        HAL_GPIO_Init(USART_RX_GPIO_PORT, &gpio_init_struct); 
        
#if USART_EN_RX		
        HAL_NVIC_EnableIRQ(USART_UX_IRQn);    //使能USART1中断通道                
        HAL_NVIC_SetPriority(USART_UX_IRQn, 3, 3);              
#endif
    }
}

8.3.编写中断服务函数

void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&g_uart1_handle); 	//调用HAL库中断处理公共函数,该函数会使能接收中断,具体情况如下图,因此要再次开启中断
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); 
}

在这里插入图片描述

8.4.定义Callback函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	g_usart_rx_sta = 1;		//标志为1,收到信息
}

8.5.主函数

int main(void)
{
    HAL_Init();                             
    sys_stm32_clock_init(RCC_PLL_MUL9);     
    delay_init(72);                        
    usart_init(115200);                    
    led_init();                            

	while(1)
	{
		if(g_usart_rx_sta == 1)
		{
			HAL_UART_Transmit(&g_uart1_handle, (uint8_t*)g_rx_buffer, 1, 1000);	//发送一个字符,1s为超时时间
			while(__HAL_UART_GET_FLAG(&g_uart1_handle,UART_FLAG_TC) != SET);	//如果没发送完成就一直在该循环里
			printf("\r\n");	//回车换行
			g_usart_rx_sta = 0//发送完成就把标志位置0
		}
		else	//如果没收到,就执行下面程序
        {
            times++;

            if (times % 5000 == 0)
            {
                printf("ALIENTEK\r\n\r\n\r\n");
            }
            if (times % 200 == 0) printf("输入数据,以回车结束\r\n");
            delay_ms(10);
        }
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值