你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32经验分享 第15章 通信—调试串口

[复制链接]
STMCU小助手 发布时间:2022-8-30 19:13
15.1 关于串口
15.1.1 串口理论知识

说到串口,经常提到TTL、RS232、RS422、RS485,简单的说,就是为了适应不同的环境条件,使用了不同的电平标准。假如微处理器和板载的蓝牙透传模块通信时,一般就使用TTL电平,引脚直接连接即可。假如微处理器在工业现场,需要连接一个几十米外的装置,则应该考虑将TTL电平转为RS232、RS422、RS485。

_NF[RN~NE`R_O`())_Q@N@M.png


图 15.1.1 不同通信电平应用的场景



如下表 15.1.1 是几个通信接口标准总结。可以发现为了加大传输距离,人们依次尝试了增加电压、差分传输等方式。
1@UV5]AMK}AM[NR4F3@LY%I.png


表 15.1.1 常见通信接口标准



以TTL电平的接口为例讲解串口怎么传输数据,对于RS232/RS422/RS485等接口,仅仅是把TTL电平转换为不同的电平值,或者转换为差分信号。

TTL接口的串口,硬件连接如图 15.1.2 所示。

N3O9@]2J})D516_SYV($RSF.png


图 15.1.2 TTL电平的串口硬件连接示意图



串口传输中的一些概念如下:

波特率:一般选波特率都会有9600,19200,115200等选项。其实意思就是每秒传输这么多个比特位数(bit);

起始位:先发出一个逻辑”0”的信号,表示传输数据的开始;

数据位:可以是5~8位逻辑”0”或”1”,先传输bit 0,再传输bit 1,依次类推;

校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。校验位是可选的,可以不传输;

停止位:它是一个字符数据的结束标志,数据线变回逻辑”1”;



怎么发送一字节数据,比如“A”?“A”的ASCII值是0x41,二进制就是01000001,怎样把这8位数据发送给对方呢?

双方约定好波特率、数据格式(数据位个数、停止位个数、是否使用校验位、奇校验还是偶校验),假设数据位是8,停止位是1,校验位是1;

初始电平为逻辑1;

发送方输出逻辑0,并保持1位的时间;接收方检测到逻辑0,就知道对方准备发送数据了;

发送方根据数据的bit 0设置引脚电平,并保持1位的时间;接收方读取引脚电平,得到bit 0;

发送方根据数据的bit 1设置引脚电平,并保持1位的时间;接收方读取引脚电平,得到bit 1;

以此类推,发出8位数据;

l发送方计算出校验值,设置引脚,并保持1位的时间;接收方读取引脚电平,得到校验值;注意,这步可以省略;

发送方输出逻辑1,并保持1位的时间;接收方读取引脚电平,直到数据传输结束;

信号的波形图如图 15.1.3 所示。

}{@RD98VCR[@{)SUXG9K{{G.png


图 15.1.3 串口信号传输波形图




15.1.2 STM32的串口


在嵌入式中,很多MCU和外设模块都集成有UART外设。STM32F103系列最多有3个通用同步异步收发器(Universal synchronous asynchronous receiver transmitter,USART),2个通用异步收发器(Universal asynchronous receiver transmitter,UART)。USART和UART的主要区别在于,USART支持同步通信,该模式有一根时钟线提供时钟。

串口在嵌入式中经常使用,一般使用UART就足够了,常见的用途如下:

1. 作为调试口,打印程序运行的状态信息;

2. 连接串口接口的模块(比如GPS模块),传输数据;

3. 通过电平转换芯片变为RS232/RS485电平,连接工控设备;


STM32F103系列不同USART所支持的功能如表 15.1.2 所示,STM32F103C8T6只有USART1/2/3。

WA_]89NVGL3ZZF[MYVW}D.png


表 15.1.2 STM32F103系列不同USART所支持功能

USART内部结构的结构如图 15.1.4 所示。

SLW(WF`F]D8$D$ITP33XVA9.png


图 15.1.4 USART内部结构



可以把USART分成四部分:

①:USART引脚

TX:数据发送;

RX:数据接收;

SW_RX:在单线和智能卡模式下接收数据,属于内部引脚,没有具体外部引脚;

RTS:在硬件流控制时,用于指示本设备准备好可接收数据,低电平说明本设备可以接收数据;

CTS:在硬件流控制时,用于指示本设备准备好可发送数据,低电平说明本设备可以发送数据;

CK:在同步模式时,用于输出时钟;

②:波特率发生器

通过设置USART_BRR寄存器的值,实现串口通信数据传输速率的设置。由《参考手册》可知计算公式为:

C{5YUHATEE%5N1@MGY@~JJU.png

其中“Tx/Rx Baudrate”为波特率,“f_PCLK”为该外设USART的时钟频率,“USARTDIV”为USART_BRR寄存器的值。

假设所需波特率为115200,当前USART时钟为72MHz,则USARTDIV=72000000/(115200*16)=39.0625。USART_BRR寄存器使用高12位[15:4]存放整数部分,低4位[3:0]存放小数部分,小数部分每一位对应1/24=0.0625。因此,整数39对应16进制为0x27,左移4位为0x270,小数0.0625,对应0x1,USART_BRR=0x271即可。

在利用寄存器配置USART的波特率的时候需要依据此公式计算USART_BRR的值,而在HAL库中无需计算,只需传入所需波特率,自动写USART_BRR寄存器值,但是我们仍然要学习这个波特率的计算公式,也许的开发调试过程中会使用到。

前面计算波特率需要知道外设时钟的值,由前面图 6.1.2 可知,USART1挂载APB1上,USART2/3和USART4/5挂载APB2上。由前面图 9.1.1 可知,APB1时钟最大为36MHz,APB2时钟最大为72MHz。因此,只有USART1的波特率计算中的能取最大系统时钟72MHz,而其它的USART/UART只能取36MHz。



③:发送器/接收器控制单元

通过向控制寄存器CR1、CR2、CR3和状态寄存器SR写入相应的位,可实现对USART数据的发送和接收控制。其中CR1主要用于配置USART的数据位、校验位和中断使能,CR2用于配置USART的停止位和SCLK时钟控制,CR3用于CTS硬件流控制、DMA多缓冲控制等。通过读取状态寄存器SR的值,可查询USART的状态。



④:数据收发寄存器单元

该部分主要由发送数据寄存器(TDR)、发送移位寄存器、接收数据寄存器(RDR)、接收移位寄存器组成。发送移位寄存和接收移位寄存器,分别负责将发送数据并串转换和接收数据串并转换,从而实现数据在传输时,是一位一位的发送和接收。

15.2 硬件设计
如图 15.2.1 为开发板USB串口部分的原理图,U2是UART转USB的芯片CH340G,可自动将UART信号转换为USB信号,右边是串口自动下载电路,在前面5.2.4 调试/下载电路有过分析,这里不再赘述。

MCU的USART1_TX(PA9)和USART1_RX(PA10)接到U2,U2收到数据后自动将UART信号转换成USB信号。

如图 15.2.2 所示为开发板USB切换电路,拨码开关P2决定开关芯片U4、U5的6脚电平高低。如果6脚为低,开关芯片3脚与4脚接通,即串口的USB信号连接USB口;如果6脚为高,开关芯片1脚与4脚接通,即MCU的USB信号连接USB口,实现了USB口的两种状态。

7JWY{{OTAJ]LVVQC2HMMIJD.png


图 15.2.1 USB调试串口电路


DC6RCP}@CV6[4AP1O0{DU.png


图 15.2.2 USB切换电路



15.3 软件设计
15.3.1 软件设计思路

实验目的:本实验使用USART1向电脑发送打印信息。

1) 初始化USART:设置波特率,收发选择,有效数据位等

2) 串口引脚初始化:USART使能、GPIO端口时钟使能、GPIO引脚设置为USART复用;

3) 重定向printf和scanf;

4) 主函数调用USRAT初始化函数,使用printf打印输出,使用scanf获取输入;

本实验配套代码位于“5_程序源码\7_通信—调试串口\”。



15.3.2 软件设计讲解

1) GPIO引脚选择与串口选择

代码段 15.3.1 引脚宏定义(driver_usart.h)

  1. /*********************
  2. * 引脚宏定义
  3. **********************/
  4. #define USARTx                  USART1
  5. #define USARTx_TX_PIN           GPIO_PIN_9
  6. #define USARTx_RX_PIN           GPIO_PIN_10
  7. #define USARTx_PORT             GPIOA
  8. #define USARTx_GPIO_CLK_EN()    __HAL_RCC_GPIOA_CLK_ENABLE()
  9. #define USARTx_CLK_EN()         __HAL_RCC_USART1_CLK_ENABLE()
  10. #define USARTx_CLK_DIS()        __HAL_RCC_USART1_CLK_DISABLE()
复制代码

定义使用串口、对应GPIO、时钟使能,方便代码复用。



2) 初始化USART

USART初始化包含两部分:协议部分和硬件部分。

协议部分与硬件无关,比如USART的波特率、奇偶校验、停止位等,通过“HAL_UART_Init()”设置。

硬件部分指承载的硬件载体,比如串口所使用的发送、接收引脚的复用,通过“HAL_UART_MspInit()”设置。函数名中的Msp(MCU Specific Package,MCU特定软件包),就是指MCU相关的硬件初始化。

代码段 15.3.2 USART初始化(driver_usart.c)

  1. /*
  2. * 定义全局变量
  3. */
  4. UART_HandleTypeDef husart;

  5. /*
  6. *  函数名:void UsartInit(uint32_t baudrate)
  7. *  输入参数:baudrate-串口波特率
  8. *  输出参数:无
  9. *  返回值:无
  10. *  函数作用:初始化USART的波特率,收发选择,有效数据位等
  11. */
  12. void UsartInit(uint32_t baudrate)
  13. {
  14.     husart.Instance         = USARTx;                  // 选择USART1
  15.     husart.Init.BaudRate    = baudrate;                // 配置波特率
  16.     husart.Init.WordLength  = USART_WORDLENGTH_8B;     // 配置数据有效位为8bit
  17.     husart.Init.StopBits    = USART_STOPBITS_1;        // 配置一位停止位
  18.     husart.Init.Parity      = USART_PARITY_NONE;       // 不设校验位
  19.     husart.Init.Mode        = USART_MODE_TX_RX;        // 可收可发
  20.     husart.Init.HwFlowCtl   = UART_HWCONTROL_NONE;

  21.     // 使用库函数初始化USART1的参数
  22.     if (HAL_UART_Init(&husart) != HAL_OK)
  23.     {
  24.         Error_Handler();
  25.     }
  26. }
复制代码

4行:定义一个“UART_HandleTypeDef”结构体(官方也称句柄)变量husart,用于保存串口参数设置;

15~21行:设置串口参数,也就是husart:

Instance:USART寄存器及地址,即哪一个USART;

Init.BaudRate:“UART_HandleTypeDef”结构体里“UART_InitTypeDef”结构体成员,该属性为波特率;

Init.WordLength:每次发送的字节长度,通常设置为8bit;

Init.StopBits:每次发送后停止位长度,通常设置为1bit;

Init.Parity:奇偶校验,通常设置为不校验;

Init.Mode:收发模式,通常设置为可收可发;

Init.HwFlowCtl:流控设置,没有用到(CTS/RTS),通常设置为None;

24行:调用“HAL_UART_Init()”,传入设置的husart,初始化串口;

“HAL_UART_Init()”会调用“HAL_UART_MspInit()”,从而实现对涉及的硬件初始化,用户需要覆写(HAL库提供函数名,函数内容需要自己编写)该函数,完成使能串口时钟、初始化TX/RX的引脚、设置USART1的中断优先级且使能中断等。

代码段 15.3.3 USART MSP初始化(driver_usart.c)

  1. /*
  2. *  函数名:void HAL_USART_MspInit(USART_HandleTypeDef* husart)
  3. *  输入参数:husart-USART句柄
  4. *  输出参数:无
  5. *  返回值:无
  6. *  函数作用:使能USART1的时钟,使能引脚时钟,并配置引脚的复用功能
  7. */
  8. void HAL_UART_MspInit(UART_HandleTypeDef* husart)
  9. {
  10.     // 定义GPIO结构体对象
  11.     GPIO_InitTypeDef GPIO_InitStruct = {0};
  12.     if(husart->Instance==USARTx)
  13.     {
  14.         // 使能USART1的时钟
  15.         USARTx_CLK_EN();

  16.         // 使能USART1的输入输出引脚的时钟
  17.         USARTx_GPIO_CLK_EN();
  18.         /**USART1 GPIO Configuration
  19.         PA9      ------> USART1_TX
  20.         PA10     ------> USART1_RX
  21.         */
  22.         GPIO_InitStruct.Pin = USARTx_TX_PIN;            // 选择USART1的TX引脚
  23.         GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;         // 配置为复用推挽功能
  24.         GPIO_InitStruct.Pull = GPIO_PULLUP;
  25.         GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;   // 引脚翻转速率快
  26.         HAL_GPIO_Init(USARTx_PORT, &GPIO_InitStruct);   // 初始化TX引脚

  27.         GPIO_InitStruct.Pin = USARTx_RX_PIN;            // 选择RX引脚
  28.         GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;      // 配置为输入
  29.         HAL_GPIO_Init(USARTx_PORT, &GPIO_InitStruct);   // 初始化RX引脚
  30.     }
  31. }
复制代码


11行:定义一个“GPIO_InitTypeDef”结构体变量GPIO_InitStruct,用于保存GPIO参数设置;

12行:当系统中用到多个串口时,都会调用“HAL_USART_MspInit”进行硬件初始化,因此需要判断是否为USARTx(宏定义对应USART1);

15行:使能USART1时钟;

18行:使能USART1_TX(PA9)和USART1_RX(PA10)所在组时钟;

23~26行:设置USART的发送引脚:

Pin:指定引脚号;

Mode:配置为复用推挽功能;

Pull:默认上拉即可;

Speed:作为通信信号引脚,设置为High;

27行:使用“HAL_GPIO_Init()”初始化该引脚;

29~31行:设置USART的接收引脚;



3) 重定向打印函数

以上初始化完成后,就可以使用HAL库提供的“HAL_UART_Transmit()”从串口发送数据,使用“HAL_UART_Receive()”接收数据,但这样使用不方便,需要自己处理数据类型。在学习C语言时,通常使用printf将数据格式化打印,比较方便。因此,这里需要重定向打印函数,在使用printf时调用“HAL_UART_Transmit()”打印。

代码段 15.3.4 重定向打印函数(driver_usart.c)

  1. /*****************************************************
  2. *function:  写字符文件函数
  3. *param1:    输出的字符
  4. *param2:    文件指针
  5. *return:    输出字符的ASCII码
  6. ******************************************************/
  7. int fputc(int ch, FILE *f)
  8. {
  9.     HAL_UART_Transmit(&husart, (uint8_t*)&ch, 1, 10);
  10.     return ch;
  11. }

  12. /*****************************************************
  13. *function:  读字符文件函数
  14. *param1:    文件指针
  15. *return:    读取字符的ASCII码
  16. ******************************************************/
  17. int fgetc(FILE *f)
  18. {
  19.     uint8_t ch = 0;
  20.     HAL_UART_Receive(&husart, (uint8_t*)&ch, 1, 10);
  21.     return (int)ch;
  22. }
复制代码


printf和scanf会分别调用“fputc()”和“fgetc()”,因此这里覆写这两个函数,使用HAL提供的函数实现收发数据。

同时,还需要需要点击“   ”,打开工程选项界面,切换到“Target”标签,勾选上“Use MicroLIB”,如图 15.3.1 所示。

]_YM_6{13H)747O$W9AIA(C.png


图 15.3.1 勾选“Use MicroLIB”



也可以添加如下代码,就不用勾选“Use MicroLIB”,两个方法二选其一即可。

代码段 15.3.5 可选添加内容(driver_usart.c)

  1. /*
  2. * 添加如下代码,可不在工程设置中勾选Use MicroLIB
  3. */

  4. #pragma import(__use_no_semihosting)

  5. struct __FILE
  6. {
  7.     int a;
  8. };

  9. FILE __stdout;
  10. FILE __stdin;
  11. void _sys_exit(int x)
  12. {
  13. }
复制代码


4) 主函数测试

  1. // 初始化USART1,设置波特率为115200 bps
  2.    UsartInit(115200);

  3.    // 在windows下字符串\n\r表示回车
  4.    // 如果工程在编译下面这句中文的时候报错,请在魔术棒“Option for target”->"C/C++"->"Misc Controls"添加“--locale=english”
  5.    printf("百问科技<a href="http://www.100ask.net" target="_blank">www.100ask.net</a>\n\r");
  6.    printf("UART实验\n\r");
  7.    printf("test char            = %c,%c\n\r", 'H', 'c');
  8.    printf("test string1         = %s\n\r", "www.100ask.net");
  9.    printf("test string2         = %s\n\r", "深圳百问网科技有限公司");
  10.    printf("test decimal1 number = %d\n\r", 123456);
  11.    printf("test decimal2 number = %d\n\r", -123456);
  12.    printf("test hex1     number = 0x%x\n\r", 0x123456);
  13.    printf("test hex2     number = 0x%08x\n\r", 0x123456);
  14.    printf("test float           = %.5f\n\r", 3.1415);
  15.    printf("test double          = %.10lf\n\r", 3.141592653);


  16.    printf("\r\n键盘输入‘C’或者‘c’控制串口打印‘Hello world’");
  17.    while(1)
  18.    {
  19.        scanf("%c", &cmd);
  20.        if(cmd=='C' || cmd=='c')
  21.        {
  22.            cmd = 0;
  23.            printf("\r\nHello World.");
  24.        }
  25.        HAL_Delay(100);
  26.    }
复制代码

主函数直接使用“printf()”便可调用USART1打印,使用“scanf()”便可调用USART1接收数据。

2行:初始化USART1,设置波特率为常用的115200;

6~7行:中文打印测试;如果编译报错,需要点击“   ”,打开工程选项界面,切换到“C/C++”标签,在“Misc Controls”里加上“"--locale=english"”,如图 15.3.2 所示。

8行:字符打印测试;

9~10行:字符串打印测试;

11~12行:正负整打印疑测试;

13~14行:十六进制打印测试;

15~16行:浮点数打印测试;

21~29行:通过“scanf()”函数获取输入,如果输入的指定内容,则打印指定内容;

OC2D)]W44]SSJ7V_S%_6AXG.png


图 15.3.2 指定本地编译环境



15.4 实验效果

本实验对应配套资料的“5_程序源码\7_通信—调试串口\”。首先如图 15.4.1 所示将开发板插入电脑USB口连接,注意拨码开关拨向非ON一端。打开MobaXterm,设置串口会话,如图 15.4.2 所示。

①点击做上角“Session”,创建会话;

②在中间弹出的窗口,选择“Serial”,即串口;

③下拉选择串口端口,我这里是COM6,读者可能编号不一样,但后面显示的芯片型号是一样的,选择“USB-SERIAL CH340 ”即可;

④波特率选择115200,与主函数中串口初始化设置的波特率保持一致;

⑤以为串口是异步通信,需要双方统一传输规则,这里和代码段 15.3.2 中的设置保持一致。数据位8位,停止位1位,无校验,无流控;

⑥设置完后,点击“OK”;



打开代码工程,使用Keil编译,下载程序,MobaXtrem显示如图 15.4.3 所示界面,所有打印正常显示。在电脑键盘输入“C”或“c”,即可控制串口打印“Hello World”。
JJS4UC9~]}PJN`ZWC){6S_E.png

图 15.4.1 调试串口连接实物图


91Y]{ZS)J{85`}0PXOFZA%G.png

图 15.4.2 创建串口会话


%(EBLR{`76VRCEB0HC($G)3.png


图 15.4.3 串口打印效果



作者:攻城狮子黄


收藏 评论0 发布时间:2022-8-30 19:13

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版