zoukankan      html  css  js  c++  java
  • STM32F103ZET6串口通信

    1、电平标准 

      根据通讯使用的电平标准不同,串口通讯可分为TTL标准和RS-232标准,如下表:

      从图中可以看到,TTL电平标准使用5V表示高电平,使用0V表示低电平。在R232电平标准中,为了增加串口通讯的远距离传输及抗干扰能力,使用的是-15V表示高电平,使用+15V表示低电平。如下图为RS232TLL电平标准表示同一个信号时的对比。

     

      在电子电路中,一般使用TTL电平进行通讯,而在PC机中则使用RS232电平进行通讯。所以为了使电子设备可以和PC机进行串口通讯,必须对TTL电平和RS232电平的信号进行互相转换。

    2、串口协议

      串口通讯的英文全称为Serial Communication,这是一种在设备间非常常用的串行通讯方式。

      串口通讯的协议,串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口。在串口通讯的协议中,规定了数据包的内容,该内容由起始位、数据位、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。格式如下图:

      在数据帧格式中,校验位可以要也可以不要。

      一般在串口通信中,空闲状态下,IO口的电平为高电平。

    3、串口波特率

      串口通讯一般使用的是异步通讯,异步通讯是没有时钟信号的,为了保证两个设备能够正常通讯,必须在两个设备间约定好收发的速率,波特率就是设备的收发速率,波特率表示的是单位时间内收发的bit位,即一个bit的收发时长。比如波特率为9600的设备,那么该设备1S的时间内可以收发9600个bit,发送一个bit的时长位1/9600≈104us。

    4、数据帧的起始信号和停止信号  

      串口通讯的一个数据包是从起始信号开始的,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示,而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示。

      有效数据:

      在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定位5、6、7或8位。

      数据校验:

      在有效数据之后,有一个可选的数据校验位,由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程中加上校验位来解决这个问题,校验方法有奇校验、偶校验、0校验、1校验及无校验。

      奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个8位长的有效数据位:01101001,此时总共有4个“1”,为达到奇校验的效果,校验位为“1”,最后传输的数据将是8位的有效数据加上1位的校验位总共9位。

      偶校验与奇校验要求刚好相反,要求有效数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时有效数据“1”的个数位4,所以偶校验位为“0”。

      0校验是不管有效数据中的内容是什么,校验位总为“0”。

      1校验是不管有效数据中的内容是什么,校验位总为“1”。

    5、UART和USART的区别 

      UART是指通用异步收发器,UASRT是指通用同步/异步收发器。从名称上可是看出,USART是在UART基础上增加了同步功能,即USART是UART的增强型。

      STM32的USART一般做异步通信,即UART功能。

    6、USART功能概述 

      使用USART双向通信至少需要三个脚:GND、RX、TX。

      RX:接收数据引脚,通过采样技术来区别数据和噪音,从而恢复数据。

      TX:发送数据引脚,当发送器被禁止时,输出引脚恢复到它的IO端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平,也就是说USART的空闲状态的电平时高电平。这里需要注意一点的是,当与设备相连的时候,如果设备断电,这需要将发送器禁止,使TX口输出低电平,否者有可能引起IO口电流倒灌。

      TX脚位在单线和智能卡模式里被同时用于数据的发送和接收。

      USART总线在发送或接收前应处于空闲状态,即高电平。

      一个起始位,起始位为1。

      一个数据字,可以使8位数据或9位数据。发送或接收数据的时候,是先发送或接收低位数据。

      停止位可设置为0.5、1、1.5、2个停止位,发送或接收停止位说明数据帧发送或接收完成。

      使用分数波特率发生器,由12位整数和4位小数组成。

      一个状态寄存器USART_SR

      数据寄存器USART_DR

      一个波特率寄存器USART_BRR,12位表示波特率的整数,4位表示波特率的小数。

      一个智能卡模式下的保护时间寄存器USART_GTPR

       USART的框图如下:

    7、STM32的USART波特率配置 

      STM32的每个串口都有一个独立的波特率寄存器USART_BRR,该寄存器由USART分频器除法因子USARTDIV的整数部分和小数部分组成。bit4~bit15这12位组成了整数部分DIV_Mantissa,bit0~bit3这4个位组成了分数部分DIV_Fraction

      波特率的计数公式 = CLK/(16*USARTDIV)。 

      CLK是USART的时钟频率,USART2、USART3、USART4、USART5使用的是APB1总线时钟,一般是36MHZ(STM32F103系列);而USART1则使用的是APB2总线时钟,一般是72MHZ(STM32F103系列)。

      可以根据需要的波特率的值换算出USARTDIV的值。比如设置波特率为9600,则USARTDIV为:

        USARTDIV = CLK / (16 * 9600)

      如果CLK = 72MHZ,则:

      USARTDIV = 72000000/(16*9600) = 468.75

      将小数部分换算为16进制:

        DIV_Fraction = 0.75*16 = 12 = 0x0C

      将整数部分转为16进制:

        DIV_Mantissa = 468 = 0x1D4

      所以波特率寄存器USART_BRR的值为0x1D4C。

      设置波特率时的误差如下图:

      注意不要在通信过程中改变波特率寄存器的值。

    8、USART中断请求

      USART中断请求表:

      

      USART中断映像图:

      从中断映像图中可以看出,USART的各种中断事件被连接到同一个中断向量,也就是说USART产生的中断都会进入到同一个中断服务函数内。

      各种中断事件:

      发送期间:发送完成、清除发送、发送数据。

      接收期间:空闲总线检测、溢出错误、接收数据寄存器非空、校验错误、LIN断开符号检测、噪音标志(仅在多缓冲器通信)和帧错误(仅在多缓冲器通信)。

      如果设置了对应的使能控制位,这些事件就可以产生各自的中断,可以在中断服务函数内通过判断状态寄存器的位来区分是哪个中断。

    9、USART寄存器

      只说明一些用到的位。

      USART_SR状态寄存器:

      Bit7 TXE位:当TDR寄存器中的数据被硬件转移到移位寄存器的时候,TXE被硬件置位。

      Bit6 TC位:当发送完一帧有效数据的时候,TC就会置位。

      Bit5 RXNE位:当USART的RDR移位寄存器中的数据被转移到USART_DR寄存器中时,RXNE位被置1,也就是收到数据的时候RXNE位被置1;如果USART_CR1寄存器中的RXNEIE使能,且USART的中断使能,则产生中断,就是所谓的接收中断。RXNE位可以通过对USART_DR寄存器的读取操作来清除,也可以对USART_SR寄存器的bit5写0来清除。

       USART_DR数据寄存器:

     

      该寄存器用来发送和接收数据。

      USART_BRR波特比率寄存器:

      该寄存器用来设置USART的波特率。

      USART_CR1控制寄存器:

     

      Bit13 UE位:这个位用来使能和停止USART模块,这有UE位被置1的时候,USART才能工作。

      Bit12 M位:该位用来定义数据字的长度,当M=0时,数据字的长度为8个bit;当M=1时,数据字的长度为9个bit。

      Bit10 PCE位:该位用来控制是否使用校验,当PCE=0时,禁止校验控制;当PCE=1时,使能校验控制。

      Bit9 PS位:该位用来选择校验方式,当PS=0时,使用偶校验方式;当PS=1时,使用奇校验。

      Bit8 PEIE位:该位用来控制校验错误中断,当PEIE=0时,禁止校验错误产生中断;当PEIE=1时,使能校验错误产生中断。

      Bit7 TXEIE位:这个是发送缓冲区空中断使能位,当TXEIE=0时,禁止发送缓冲区空时产生中断;当TXEIE=1时,使能发送缓冲区空时产生中断。

      Bit6 TCIE位:这个是发送完成 中断使能,当TCIE=0时,禁止发送完成产生中断;当TCIE=1时,使能发送完成产生中断。

      Bit5 RXNEIE位:这个是接收缓冲区非空中断使能,当RXNEIE=0时,禁止接收完成时产生中断;当RXNEIE=1时,使能接收完成时产生中断。

      Bit4 IDLEIE位:这个是IDLE总线空闲中断使能位,当IDLEIE=0时,禁止检测到总线空闲时产生中断;当IDLEIE=1时,使能检测到总线空闲时产生中断。

      Bit3 TE位:这个是发送使能位,当TE=0时,禁止USART发送数据;当TE=1时,使能USART发送数据。

      Bit2 RE位:这个是接收使能位,当RE=0时,禁止USART接收数据;当RE=1时,使能USART接收数据。

      这里主要区分发送缓冲区空和发送完成这两个点,发送缓冲器空是指USART_DR寄存器的数值被硬件转移到以为寄存器的时候,也就是说要发送的数值进入移位寄存器的时候,发送缓冲器空这个条件就会成立。而发送完成是指移位寄存器里的数据发送完成。

      当将一个数据写入USART_DR寄存器的时候,硬件将USART_DR寄存器的值送入移位寄存器,送完之后会置位USART_SR寄存器的bit7 TXE位,而当移位寄存器里的数值发送完成之后会置位USART_SR寄存器的bit6 TC位。

      USART_CR2控制寄存器:

      Bit12~Bit13 STOP位:这个是停止位长度的选择位,当STOP=00时,使用1个停止位;当STOP=01时,使用0.5个停止位;当STOP=10时,使用2个停止位;当STOP=11时,使用1.5个停止位;

      需要注意的是UART4和UART5不能使用0.5和1.5个停止位。

    10、HAL库操作USART

      在工程之中添加stm32f1xx_hal_uart.c和stm32f1xx_hal_uart.h文件。

      初始化代码如下:

     1 UART_HandleTypeDef UART_Handler;
     2 
     3 uint8_t aRxBuffer;
     4 
     5 void USART_Init(void)
     6 {
     7 
     8     UART_Handler.Instance = USART1;
     9     UART_Handler.Init.BaudRate = 9600;
    10     UART_Handler.Init.Mode = UART_MODE_TX_RX;
    11     UART_Handler.Init.StopBits = UART_STOPBITS_1;
    12     UART_Handler.Init.WordLength = UART_WORDLENGTH_8B;
    13     UART_Handler.Init.Parity = UART_PARITY_NONE;
    14     UART_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    15     HAL_UART_Init(&UART_Handler);
    16     
    17     //HAL_UART_Receive_IT(&UART_Handler,aRxBuffer,2);
    18     __HAL_UART_ENABLE_IT(&UART_Handler, UART_IT_RXNE);
    19     
    20     HAL_NVIC_SetPriority(USART1_IRQn,2,1);
    21     HAL_NVIC_EnableIRQ(USART1_IRQn);
    22              
    23 }

      使用HAL_UART_Init()函数初始化USART设备。

      从初始化代码内,可以看到,需要选择初始化的USART、波特率、USART的模式(接收和发送)、停止位长度的选择、数据的长度选择、校验的选择、硬件控制流的选择。还需要设置USART的时钟和脚位,但是HAL_UART_Init()函数内会调用USART的时钟和脚位的初始化回调函数,可以在回调函数内设置脚位和时钟。

      初始化完USART之后,打开USART的接收中断,由于HAL库的接收程序比较复杂,这里直接调用宏__HAL_UART_ENABLE_IT(&UART_Handler, UART_IT_RXNE)来打开USART的接收中断。

      IO和RCC初始化函数如下:

     1 void HAL_UART_MspInit(UART_HandleTypeDef *husart)
     2 {
     3     GPIO_InitTypeDef GPIO_InitStruct;
     4 
     5     __HAL_RCC_GPIOA_CLK_ENABLE();
     6     __HAL_RCC_USART1_CLK_ENABLE();
     7     
     8     GPIO_InitStruct.Pin = GPIO_PIN_9;
     9     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    10     GPIO_InitStruct.Pull = GPIO_NOPULL;
    11     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    12     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    13 
    14     GPIO_InitStruct.Pin = GPIO_PIN_10;
    15     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    16     GPIO_InitStruct.Pull = GPIO_NOPULL;
    17     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    18     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);    
    19 
    20 }  

      这里注意的是RX脚,既可以设置成浮空输入模式也可以设置成复用输入模式,这两个是一样的。

      中断处理函数如下:

     1 void USART1_IRQHandler(void)
     2 {
     3     //HAL_UART_IRQHandler(&UART_Handler);
     4     
     5     if(__HAL_UART_GET_FLAG(&UART_Handler,UART_FLAG_RXNE))
     6     {
     7         aRxBuffer = UART_Handler.Instance->DR; 
     8         HAL_UART_Transmit(&UART_Handler,&aRxBuffer,1,1000);  
     9     }  
    10 }

      由于HAL库的中断服务处理比较繁琐,这里直接读取USART的中断标志位来处理接收中断。

      收到数据到,通过读取USART_DR寄存器的值来获取接收到的数值,这里将接收到的数据再发送出去。

    HAL库中断处理函数说明:

      在HAL库中断处理中,进入中断处理函数之后会调用HAL_UART_IRQHandler()函数,在HAL_UART_IRQHandler()函数内判断是那种类型的中断,然后去执行响应的操作。比如接收到数据后,会在HAL_UART_IRQHandler()函数内调用UART_Receive_IT()函数。

      这里需要注意一个问题,在UART_Receive_IT()函数中,接收完成后会清除掉接收中断的使能位,实际在用HAL库来处理中断的时候,接收中断使能位莫名会被清掉。

      在UART_Receive_IT()函数中会调用接收数据的回调函数HAL_UART_RxCpltCallback()来处理数据。

      使用HAL_UART_Receive_IT(&UART_Handler,aRxBuffer,len)函数来接收数据的时候,如果数据长度不为1,则接收到1个数据的时候,不会进入到HAL_UART_RxCpltCallback()函数中,只有收到实际设置的数据长度时,才会进入HAL_UART_RxCpltCallback()函数。通过进入UART_Receive_IT()函数查看代码的时候,会发现,当接收完实际设置的数据长度时,会清除掉接收中断的使能位,这里需要小心,防止收到一次数据之后,不能再进入接收中断。

     添加支持printf()函数的代码:

     1 //加入以下代码,支持printf函数,而不需要选择use MicroLIB      
     2 #pragma import(__use_no_semihosting)             
     3 //标准库需要的支持函数                 
     4 struct __FILE 
     5 { 
     6     int handle; 
     7 
     8 }; 
     9 
    10 FILE __stdout;       
    11 //定义_sys_exit()以避免使用半主机模式    
    12 void _sys_exit(int x) 
    13 { 
    14     x = x; 
    15 } 
    16 //重定义fputc函数 
    17 int fputc(int ch, FILE *f)
    18 {      
    19     while(__HAL_UART_GET_FLAG(&UART_Handler,UART_FLAG_TC) == RESET){}; 
    20       
    21     UART_Handler.Instance->DR = (uint8_t) ch;
    22     
    23     return ch;
    24 }

      加上以上代码后就可以使用printf()函数发送UART数据了,比如printf("hello world!")。

  • 相关阅读:
    Thinking in Java 第十六章学习笔记----数组
    Thinking in Java第十五章学习笔记----泛型
    Thinking in Java第十四章学习笔记----类型信息
    Thinking in Java第十三章学习笔记----字符串
    Kaggle竞赛--Titanic:Machine Learning from Disaster
    sklearn 常用算法参数设置与选择
    Pandas 入门知识
    机器学习常用数据集
    感知机PLA
    决策树CART--原理与实现
  • 原文地址:https://www.cnblogs.com/h1019384803/p/11257812.html
Copyright © 2011-2022 走看看