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

【经验分享】STM32 通信—调试串口

[复制链接]
STMCU小助手 发布时间:2022-4-28 21:05
15.1 关于串口! h1 H' n+ }* z! f4 A# [" j
15.1.1 串口理论知识
, y& W6 h; {( W  b. e% n1 n5 e4 f% d# r
$ m$ H) [% x* F) x
说到串口,经常提到TTL、RS232、RS422、RS485,简单的说,就是为了适应不同的环境条件,使用了不同的电平标准。假如微处理器和板载的蓝牙透传模块通信时,一般就使用TTL电平,引脚直接连接即可。假如微处理器在工业现场,需要连接一个几十米外的装置,则应该考虑将TTL电平转为RS232、RS422、RS485。, [4 U- e3 c% W; C

% z( q, Q" R9 C% i: N+ J
& V7 G+ [3 w! T! p; G- i+ E8 f( w: N  V' Z2 S' t0 d  ~& |$ x: F
图 15.1.1 不同通信电平应用的场景  T) M2 p1 W4 H+ H

, b4 s2 d$ |8 x' o& a
' X- R5 |* H$ N; f! j如下表 15.1.1 是几个通信接口标准总结。可以发现为了加大传输距离,人们依次尝试了增加电压、差分传输等方式。
) \+ i+ m& C) ^5 y
( i9 a3 l2 J" Q2 Z L}G4~X){YNN7CI76EU3QW0V.png 6 ~3 F& A, g' X% U
5 v# N3 h- Y- b6 B! n" v0 p4 P
表 15.1.1 常见通信接口标准2 Z) @) M8 K& d7 T! L
; x* |& [* y; Z: v% X
% i2 U% Y( n- |3 D: j7 P/ Q
以TTL电平的接口为例讲解串口怎么传输数据,对于RS232/RS422/RS485等接口,仅仅是把TTL电平转换为不同的电平值,或者转换为差分信号。
; {$ A& U* L: D$ O: s# l5 u. |1 p
TTL接口的串口,硬件连接如图 15.1.2 所示。
* `4 c! l9 M' p
4 N" |$ U3 E$ z2 P% t$ U {N%OG%S53K_MF)V_5SMY78X.png
, Y( S" e" b1 K, o7 |# i' A
+ q1 ^) v7 [, R- H6 L图 15.1.2 TTL电平的串口硬件连接示意图% s: i- |5 x) \( i' |
5 k* j% d; ^( N) c
& r8 P0 B9 I" i9 e
串口传输中的一些概念如下:
. c- V; {3 W4 i: B! ]8 z1 t! Y4 B& A! U+ s1 k, l' N5 a% Z* |6 q  t
波特率:一般选波特率都会有9600,19200,115200等选项。其实意思就是每秒传输这么多个比特位数(bit);; T$ F" l5 _, E8 d% X3 U

/ u3 X3 Y( L. x7 W4 k" [  \( N起始位:先发出一个逻辑”0”的信号,表示传输数据的开始;
/ ?, T) f9 y$ G/ [$ C8 P
- ^; r# R4 H$ B' G, o0 e" ~数据位:可以是5~8位逻辑”0”或”1”,先传输bit 0,再传输bit 1,依次类推;+ n/ n- G8 ]1 U- Q- Z. N0 W
( N! A7 C; b% K3 B
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。校验位是可选的,可以不传输;) b9 B4 O: \9 w! M: B
: ]' v+ V# C, h  y& L
停止位:它是一个字符数据的结束标志,数据线变回逻辑”1”;
( N  A, b+ g* B8 @  e% k' Y3 J- v+ X4 t( \( i& b

- _5 J- C! V9 ^8 }6 e- O+ R3 _' H. K6 C9 W
怎么发送一字节数据,比如“A”?“A”的ASCII值是0x41,二进制就是01000001,怎样把这8位数据发送给对方呢?, U7 R& @* s4 g2 [- u
# h+ ]4 S$ l( O1 V
双方约定好波特率、数据格式(数据位个数、停止位个数、是否使用校验位、奇校验还是偶校验),假设数据位是8,停止位是1,校验位是1;/ @0 R/ _$ q* i* J

# S. `$ x9 O) L9 R初始电平为逻辑1;9 N' S( E7 o' T  b, d
7 I/ B9 \, @$ M; c4 e% ?
发送方输出逻辑0,并保持1位的时间;接收方检测到逻辑0,就知道对方准备发送数据了;
0 r8 M, Q7 [) s; e( T3 I
6 R/ Q( J7 C' r: n4 Q9 z8 I发送方根据数据的bit 0设置引脚电平,并保持1位的时间;接收方读取引脚电平,得到bit 0;
, G8 Z9 n3 ~; E# T! T  a2 S: @
$ b: N- d+ B3 i' C发送方根据数据的bit 1设置引脚电平,并保持1位的时间;接收方读取引脚电平,得到bit 1;' S) g% ^+ R4 a" A0 L

) ?# J4 {. p# n: i. Q5 Z. c以此类推,发出8位数据;1 K* D' V5 e- _, v# S% L4 i2 A8 O
( b  _; H0 Q1 z+ O
l发送方计算出校验值,设置引脚,并保持1位的时间;接收方读取引脚电平,得到校验值;注意,这步可以省略;
0 z# W7 _  w2 w  A: Y4 f+ G2 d% h7 l# W5 x' a' j% @) W
发送方输出逻辑1,并保持1位的时间;接收方读取引脚电平,直到数据传输结束;
3 [- ?: R1 Q+ v3 d: B
6 p, C6 |/ p7 g 信号的波形图如图 15.1.3 所示。* |1 ^9 D4 ?+ E
, m  C! N/ k: J+ l3 O# i* w
NP$IJU8(FH{AC@ZT5KR.png
4 s7 z# O; a9 X& J  r# a# T8 p( w8 U" K4 K+ g" u
图 15.1.3 串口信号传输波形图
/ C. o& p" R  D( O: U
' O# N& v  j& b8 V# ?+ Y* |+ T8 D( f  S; D- d* V2 b- N$ N
15.1.2 STM32的串口
: C0 P, `7 N5 i+ W0 H* v

* \  _! \. P& H( i在嵌入式中,很多MCU和外设模块都集成有UART外设。STM32F103系列最多有3个通用同步异步收发器(Universal synchronous asynchronous receiver transmitter,USART),2个通用异步收发器(Universal asynchronous receiver transmitter,UART)。USART和UART的主要区别在于,USART支持同步通信,该模式有一根时钟线提供时钟。' N! j9 U3 H% e& l
# S4 i. `" f: g& _) M! V
串口在嵌入式中经常使用,一般使用UART就足够了,常见的用途如下:
0 d% ]1 ?# g3 ^* Z! s" F% f3 O  N' W% j/ ~0 U
1. 作为调试口,打印程序运行的状态信息;/ Q2 K4 t8 c- b0 F" M% T

5 r9 `# B2 v! }' y8 k, m3 ]  ^# d2. 连接串口接口的模块(比如GPS模块),传输数据;
4 U# _7 w. K* n+ d
, A; u- y/ ~0 H3. 通过电平转换芯片变为RS232/RS485电平,连接工控设备;8 [8 R( U( J+ y6 ~. e7 D
) J! q# O  e2 R- t

! t2 a3 j8 I% m1 c5 u0 r! _5 W4 HSTM32F103系列不同USART所支持的功能如表 15.1.2 所示,STM32F103C8T6只有USART1/2/3。
2 {- Q5 u3 ], {, M1 j: o* x2 S. Q7 }
BRH{BV`)D@J%SH{JN~SBHGO.png 4 Z7 C) l" E0 ?$ n, m

2 E' ~. V- k$ D* m) r' g7 b表 15.1.2 STM32F103系列不同USART所支持功能8 D0 ?1 n& r/ |, h
USART内部结构的结构如图 15.1.4 所示。
& V8 u; Z  i% f9 F& V; _! J/ |
5 N3 \" e8 b5 x4 d7 p/ A FJ700]3US6IW6F5DQ3FFME4.png   W0 e0 P' \; s1 F

& D0 l, d5 q9 c: W, ?图 15.1.4 USART内部结构  @1 H! T8 P( J' [* U

, G" R; o4 o5 Q( ]; O6 Y
  a( j* n2 O* I1 E可以把USART分成四部分:& U$ `' S! E6 ]

7 `( c0 G9 L. ~①:USART引脚1 z* A) |) `; i: O2 t
) F" t- K+ A0 d9 q! F
TX:数据发送;
% v) `( O# i9 J' k0 x1 Y$ h' L7 |* F1 a& s6 g
RX:数据接收;2 a- [  k9 J" F# W
% c. a1 z; T. ~6 x, ^
SW_RX:在单线和智能卡模式下接收数据,属于内部引脚,没有具体外部引脚;5 m0 k3 V; j- O

9 U' P) l& [' l% f, p% H' XRTS:在硬件流控制时,用于指示本设备准备好可接收数据,低电平说明本设备可以接收数据;" h: `: {& ^% q. F  k+ a5 `
# x& W0 p) F8 N1 X  H. B
CTS:在硬件流控制时,用于指示本设备准备好可发送数据,低电平说明本设备可以发送数据;5 Z8 f& H* ]7 A5 O% c

& g) ^* H6 M) }( ^# c! lCK:在同步模式时,用于输出时钟;
# J) ]1 ?, Q" |' {) s) f2 M3 e2 A5 E( i1 T4 G. m) M, U& u- i7 v+ a
②:波特率发生器
. P$ J# j4 F. @; m& g. z; u' n; B7 D
通过设置USART_BRR寄存器的值,实现串口通信数据传输速率的设置。由《参考手册》可知计算公式为:. j% w, }, R5 g" ]7 J# s
* _, [& f6 B! [1 C7 M4 ^6 P
I{8J]%Z8S]W[V4YX@~BK.png
, Z$ \9 O0 n, t" x! A6 ^/ N+ T
其中“Tx/Rx Baudrate”为波特率,“f_PCLK”为该外设USART的时钟频率,“USARTDIV”为USART_BRR寄存器的值。) K: C6 v, f2 Y! `7 F: V# `
, N6 a  A8 m$ X3 k/ {
假设所需波特率为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即可。
8 T1 W; j/ R! X* G$ w" v0 F' D0 p* }, t: m
在利用寄存器配置USART的波特率的时候需要依据此公式计算USART_BRR的值,而在HAL库中无需计算,只需传入所需波特率,自动写USART_BRR寄存器值,但是我们仍然要学习这个波特率的计算公式,也许的开发调试过程中会使用到。
5 o/ c! v. x: r/ t. V4 [8 z* u% D3 K$ a7 z9 W
前面计算波特率需要知道外设时钟的值,由前面图 6.1.2 可知,USART1挂载APB1上,USART2/3和USART4/5挂载APB2上。由前面图 9.1.1 可知,APB1时钟最大为36MHz,APB2时钟最大为72MHz。因此,只有USART1的波特率计算中的能取最大系统时钟72MHz,而其它的USART/UART只能取36MHz。
' W: k- i  C2 i9 t+ Y
+ W! I# z$ h: v③:发送器/接收器控制单元
6 v- O: z4 T- ~8 x% [
# Y9 N' s4 |5 P, h' n' G. G) F. E8 x通过向控制寄存器CR1、CR2、CR3和状态寄存器SR写入相应的位,可实现对USART数据的发送和接收控制。其中CR1主要用于配置USART的数据位、校验位和中断使能,CR2用于配置USART的停止位和SCLK时钟控制,CR3用于CTS硬件流控制、DMA多缓冲控制等。通过读取状态寄存器SR的值,可查询USART的状态。1 ]# k. F6 a1 ^- |+ D' A* Y6 e
5 L+ Y6 w3 U- S9 H
④:数据收发寄存器单元
- Z$ y- x  U8 r3 p0 ]1 v6 r+ d) f  x0 @" e( G. m
该部分主要由发送数据寄存器(TDR)、发送移位寄存器、接收数据寄存器(RDR)、接收移位寄存器组成。发送移位寄存和接收移位寄存器,分别负责将发送数据并串转换和接收数据串并转换,从而实现数据在传输时,是一位一位的发送和接收。) U( I0 S( ]& x6 N6 u6 D% S
: X, p$ u( L' I# t! [6 C/ k( b% X- d

! A: W  ?! T9 E" `. J  K3 S15.2 硬件设计
/ v" M. d4 e+ u& q2 x8 u1 ]如图 15.2.1 为开发板USB串口部分的原理图,U2是UART转USB的芯片CH340G,可自动将UART信号转换为USB信号,右边是串口自动下载电路,在前面5.2.4 调试/下载电路有过分析,这里不再赘述。6 V8 N1 l. \, w; L
/ d' c# s- N. a( Y6 T
MCU的USART1_TX(PA9)和USART1_RX(PA10)接到U2,U2收到数据后自动将UART信号转换成USB信号。
8 o7 @% A8 b, C" `# q, l5 B: s$ A, v; F9 M! j  v7 M( m
如图 15.2.2 所示为开发板USB切换电路,拨码开关P2决定开关芯片U4、U5的6脚电平高低。如果6脚为低,开关芯片3脚与4脚接通,即串口的USB信号连接USB口;如果6脚为高,开关芯片1脚与4脚接通,即MCU的USB信号连接USB口,实现了USB口的两种状态。+ J& R7 @  U" t% {
2 A- d) L+ T- h
5~U0A~{G`WF{O{@J$RDAXHX.png
- ]$ U" I* |; b+ k1 W) q" E8 q  @
图 15.2.1 USB调试串口电路7 t; ]& d: I* V7 X( M* A

. v+ a0 o4 n) H (=C(9T01[SYDKXC(O{090.png
' M  G2 I0 U9 k" w. j, a/ ^
9 B1 R" d$ V. H) _& M& c图 15.2.2 USB切换电路
- K& p" \1 }2 h# L/ \' b- i. l( ?9 e  i8 ~
, U! O* z8 K) {$ H8 g
15.3 软件设计
' i4 M, h3 b* E& b# W, z15.3.1 软件设计思路
0 Y  {+ X+ t7 n3 V. U1 ]

6 U3 b) y- P- J( L5 B( g) k) k- r/ F实验目的:本实验使用USART1向电脑发送打印信息。7 @  ?, |8 ~% `3 A# @1 C" K, a/ \

! V: O% `8 x! b* d" F8 g  l3 W1) 初始化USART:设置波特率,收发选择,有效数据位等9 ?, Z4 i9 w# g$ X; S6 z$ r
5 t& C8 r) x$ N8 \: V# S8 c% K
2) 串口引脚初始化:USART使能、GPIO端口时钟使能、GPIO引脚设置为USART复用;9 t/ H/ I. u2 a3 R5 }7 S

% J# p0 Y; `4 h+ r% S# g3) 重定向printf和scanf;
6 R+ Q+ P( [! @* u: `4 R1 D2 }& S% R  h' S* {/ k( M9 q4 q
4) 主函数调用USRAT初始化函数,使用printf打印输出,使用scanf获取输入;/ i# `3 l" v9 U( Q$ g
; U/ A; ~. W/ T
本实验配套代码位于“5_程序源码\7_通信—调试串口\”。
1 o3 P' Y  f# L* Q' X
  ]/ P% p9 v. a5 c, g
9 L5 R) b4 @: q% x3 {
5 P$ c  g; h4 f/ [! I15.3.2 软件设计讲解7 N/ t2 s6 t+ A; g) h! N5 V
- q: {  X% R$ ?, P" x
1) GPIO引脚选择与串口选择
% ^: Q; U3 _0 c7 D' h- H
9 s; n% d0 N% d1 }+ x: k代码段 15.3.1 引脚宏定义(driver_usart.h)
! D( E- X, a& ~5 I
' M9 ~$ V; j6 h/ q4 C
  1. /*********************
    8 O# q) f) a7 V4 ~% E( p
  2. * 引脚宏定义. |+ @0 ^- b" J( Y7 T6 s
  3. **********************/
    / `. ^$ C( ^4 F: m: u" O
  4. #define USARTx                  USART1
    6 U; z9 t6 H) h( U; D1 B+ Q
  5. #define USARTx_TX_PIN           GPIO_PIN_9
    + f. P  j' V0 ~+ B; C  S4 b
  6. #define USARTx_RX_PIN           GPIO_PIN_10
    # _: W1 u4 |7 g, a2 s
  7. #define USARTx_PORT             GPIOA
    + Z- E; a4 O' [, e
  8. #define USARTx_GPIO_CLK_EN()    __HAL_RCC_GPIOA_CLK_ENABLE()" L- h- m; i& `+ s8 ^$ z
  9. #define USARTx_CLK_EN()         __HAL_RCC_USART1_CLK_ENABLE()9 [: N& @- I) x! G
  10. #define USARTx_CLK_DIS()        __HAL_RCC_USART1_CLK_D<span style="background-color: rgb(255, 255, 255);">ISABLE()</span>
复制代码
8 [: H; @) c& [$ Y
定义使用串口、对应GPIO、时钟使能,方便代码复用。0 s, a( y. u- B7 _! w& {

# [0 b, C) C& J/ B* \4 C) w4 w4 X0 E: m
2) 初始化USART
7 j' Y& {$ D. q( D; B# P/ a1 k* Q( v' B/ c' y* r& d
USART初始化包含两部分:协议部分和硬件部分。" Q8 R6 [4 v8 C! U/ e

4 d/ w& e& `: p9 V协议部分与硬件无关,比如USART的波特率、奇偶校验、停止位等,通过“HAL_UART_Init()”设置。
0 H5 [  h* {; b+ \9 R& }+ }: u/ X* C
+ i, P' y# b; J9 i( C硬件部分指承载的硬件载体,比如串口所使用的发送、接收引脚的复用,通过“HAL_UART_MspInit()”设置。函数名中的Msp(MCU Specific Package,MCU特定软件包),就是指MCU相关的硬件初始化。# n/ ^& M3 W" s; f/ C
" l6 ~) E4 s$ @+ h# S9 O2 k
代码段 15.3.2 USART初始化(driver_usart.c)
8 D! P( f# v8 @) ~2 i/ ~6 A. T9 _  @" X, K1 h
  1. /*
    7 ?  f( B' D! B5 v1 [$ Z; k. A
  2. * 定义全局变量
    % V$ O2 [# _5 i$ A% `* E  {
  3. */2 j1 p; N7 f4 y  V; Q+ m, e
  4. UART_HandleTypeDef husart;
    5 d& {: @2 V) u8 {6 V

  5. 5 B% ~7 W; c" S- B" [4 Q9 K' V6 K
  6. /*
    6 A' e# @- g' M( e- p0 V6 g! x" D  Y1 d/ {
  7. *  函数名:void UsartInit(uint32_t baudrate)3 k: B9 e" f' b  }
  8. *  输入参数:baudrate-串口波特率
    # K- z& h5 X( y' x8 G
  9. *  输出参数:无0 A& X) M6 k/ b  o+ c
  10. *  返回值:无/ g7 V" T8 I! L" y
  11. *  函数作用:初始化USART的波特率,收发选择,有效数据位等& K" u$ @1 t' u* o) k
  12. */: o5 X; U% h5 D9 J' X
  13. void UsartInit(uint32_t baudrate)
    ' p/ V1 Q8 s. D% m. \
  14. {" `" g- q& U* C0 `" F  X: W$ }
  15.     husart.Instance         = USARTx;                  // 选择USART1
    & R) u( g$ s- Z% t+ D5 q* K; Y& R7 r7 W
  16.     husart.Init.BaudRate    = baudrate;                // 配置波特率
    7 @' _, H$ R: E, z! c# Y% [
  17.     husart.Init.WordLength  = USART_WORDLENGTH_8B;     // 配置数据有效位为8bit% ?/ K5 J$ y4 Y% |3 X/ c; F* Q
  18.     husart.Init.StopBits    = USART_STOPBITS_1;        // 配置一位停止位
    7 I; P' H6 `& e
  19.     husart.Init.Parity      = USART_PARITY_NONE;       // 不设校验位
      u4 _' F0 E" X7 z1 L3 t
  20.     husart.Init.Mode        = USART_MODE_TX_RX;        // 可收可发) A" Y! o9 @: d( ^' R) y
  21.     husart.Init.HwFlowCtl   = UART_HWCONTROL_NONE;5 r" Y& E! ^) J2 I3 Y. j! C9 r
  22. # A; u! V& J5 K  g8 b: K) P% v( ]: g
  23.     // 使用库函数初始化USART1的参数
    & h4 U2 C+ t( a. f
  24.     if (HAL_UART_Init(&husart) != HAL_OK)" Q" A/ Y0 ~2 i' O  o3 O" |
  25.     {6 |6 }/ w; @6 ?- |( k! ?5 R  E
  26.         Error_Handler();! v: E  s2 e: p; o4 Z
  27.     }
    / y2 y3 H. w8 ]( n, |6 w9 T$ C
  28. }
复制代码

5 n" v( [8 Q: w: t4行:定义一个“UART_HandleTypeDef”结构体(官方也称句柄)变量husart,用于保存串口参数设置;
+ j5 T) k9 |% y6 ?8 \8 |# a
9 E2 D4 {; I6 ]+ W2 S* I: \% Y15~21行:设置串口参数,也就是husart:% N7 u+ G9 v4 H
2 N! l$ K8 }0 p( \6 J! q
Instance:USART寄存器及地址,即哪一个USART;
( H$ S* k7 G; j  o1 ?# G) v) N" W0 H% l1 q7 \5 I* P6 M: _
Init.BaudRate:“UART_HandleTypeDef”结构体里“UART_InitTypeDef”结构体成员,该属性为波特率;
; U+ H( F$ [5 j( h! n! |/ A/ f: Z8 f/ ^) C" A# _5 l* O! E
Init.WordLength:每次发送的字节长度,通常设置为8bit;
$ I' I# ?# _4 y: i; J3 E7 F  u$ o! E
% s$ o1 N7 H. B2 A; S& LInit.StopBits:每次发送后停止位长度,通常设置为1bit;
; m* w1 F- _1 J- G2 z8 \& d: y0 W. V  X; v6 T9 m
Init.Parity:奇偶校验,通常设置为不校验;7 h0 a: t5 D" ?* p/ x1 }+ C  e4 w
$ @; \0 I3 g( z2 ]7 P9 ~6 J7 E8 @
Init.Mode:收发模式,通常设置为可收可发;
( {! L+ J0 f7 k' G
' h* i1 n( o) z3 P* B5 B- }Init.HwFlowCtl:流控设置,没有用到(CTS/RTS),通常设置为None;
9 w+ ~! j; C/ K7 v8 r3 S+ q3 Z( M# d5 G) M- ]3 p$ S4 D
24行:调用“HAL_UART_Init()”,传入设置的husart,初始化串口;5 M3 u2 M% W0 s$ t

$ k& L# f7 {0 V% a9 T“HAL_UART_Init()”会调用“HAL_UART_MspInit()”,从而实现对涉及的硬件初始化,用户需要覆写(HAL库提供函数名,函数内容需要自己编写)该函数,完成使能串口时钟、初始化TX/RX的引脚、设置USART1的中断优先级且使能中断等。
0 @! i" K9 ^* G6 A* z( d
: p" c/ V' e% [; r( }6 I8 n. f代码段 15.3.3 USART MSP初始化(driver_usart.c)
$ t/ z- _% ?! a$ n/ a, H3 B4 F; o1 F+ ]8 q$ D2 K. s  z7 V
  1. /*& y1 ]" n! h" ?; G" q
  2. *  函数名:void HAL_USART_MspInit(USART_HandleTypeDef* husart)
    3 t* p; J) n" A; R
  3. *  输入参数:husart-USART句柄) I* e/ R6 Z0 R6 P/ [
  4. *  输出参数:无
    ) ]: n+ L& {' }, X) `" M
  5. *  返回值:无; P# ~# b' @* S% x3 h+ \/ E6 X
  6. *  函数作用:使能USART1的时钟,使能引脚时钟,并配置引脚的复用功能
    - `0 A5 _. [" L
  7. */0 M4 l% k7 y! |
  8. void HAL_UART_MspInit(UART_HandleTypeDef* husart)
    . d0 o0 B" V) D6 X
  9. {
    8 Y0 m5 T' r8 |+ i
  10.     // 定义GPIO结构体对象8 h* f" L1 O4 `8 F
  11.     GPIO_InitTypeDef GPIO_InitStruct = {0};
    ' t& v# Y; W3 Q- l& I6 p& V
  12.     if(husart->Instance==USARTx)
    1 l7 \2 C# T+ b9 Q4 z2 B# z
  13.     {' ~% O1 r2 Y) c. e
  14.         // 使能USART1的时钟
    # ~4 o2 ~! B8 P; q$ Z/ U* ]
  15.         USARTx_CLK_EN();3 m8 D  C5 x% R$ |
  16. 0 J$ A  E4 Y+ t: P5 `5 T, G- Z
  17.         // 使能USART1的输入输出引脚的时钟/ K3 b6 ^: C) r- w
  18.         USARTx_GPIO_CLK_EN();6 F+ |2 r6 ]& t3 ^
  19.         /**USART1 GPIO Configuration
    6 u# G" `% @  d' g! c: V
  20.         PA9      ------> USART1_TX
    3 C7 M% O$ n& w3 E  Q+ M, C! }
  21.         PA10     ------> USART1_RX2 _9 q# z2 D6 u' d9 N' v
  22.         */
    2 a3 J  i) A, M) L' M* E' Y: V& h
  23.         GPIO_InitStruct.Pin = USARTx_TX_PIN;            // 选择USART1的TX引脚% q) C; E7 ?3 a; \
  24.         GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;         // 配置为复用推挽功能
    ' S0 `+ |% R3 f2 U
  25.         GPIO_InitStruct.Pull = GPIO_PULLUP;8 j0 W4 d6 D: a0 {( k
  26.         GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;   // 引脚翻转速率快
    & _( c4 S$ F6 A6 f% S- J, }/ K
  27.         HAL_GPIO_Init(USARTx_PORT, &GPIO_InitStruct);   // 初始化TX引脚2 n) J6 Y- Z3 E: c3 h+ ~! Y
  28. 3 }# M0 d$ ^4 \! P  s. a- A6 Q
  29.         GPIO_InitStruct.Pin = USARTx_RX_PIN;            // 选择RX引脚. y) Y$ n! Z/ V4 h4 {( F
  30.         GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;      // 配置为输入
    2 s! [$ e, B9 _) o: q- l
  31.         HAL_GPIO_Init(USARTx_PORT, &GPIO_InitStruct);   // 初始化RX引脚" j  ?4 [3 f3 s7 N
  32.     }$ _( s6 D8 O" j
  33. }) m$ t6 P& X/ O
复制代码

6 y  v) G" [. P" f8 K% ?9 ]
) W* H/ `; f7 @! l! E11行:定义一个“GPIO_InitTypeDef”结构体变量GPIO_InitStruct,用于保存GPIO参数设置;2 I9 {( `2 R) X0 C! R' E
: X. g# B: s6 C0 Z7 e
12行:当系统中用到多个串口时,都会调用“HAL_USART_MspInit”进行硬件初始化,因此需要判断是否为USARTx(宏定义对应USART1);
. t  O$ b2 ?: h# G0 z, Z! g! D. F; d' {6 E) n
15行:使能USART1时钟;
; R5 w* V' D4 V- p& e0 x5 ^
# J+ f9 B4 l: w- L! m( D3 h/ Y18行:使能USART1_TX(PA9)和USART1_RX(PA10)所在组时钟;
4 h* z7 |% P6 J9 g' }
- e7 r5 \( ]/ c. i  D4 M23~26行:设置USART的发送引脚:
" T2 _( @0 `! q' b& m4 E* R1 X3 X4 j7 w# ^5 `8 |3 C
Pin:指定引脚号;& l( g; Y! l$ b3 ^! J$ x/ P
1 @; j8 y1 @, g; v, O: s7 m
Mode:配置为复用推挽功能;
6 B* I4 L( D  l6 r+ X" n2 Z8 b& w
0 a# `! n4 J! B) r  M: \Pull:默认上拉即可;
' O' y# C: n6 X8 e8 u2 S; C/ t' ~9 Q3 P5 P) @7 B- g
Speed:作为通信信号引脚,设置为High;9 w% x' w" ~3 Q; H- L" z* D
! x& O0 g% m% _- {
27行:使用“HAL_GPIO_Init()”初始化该引脚;
% T+ q2 }# p6 @# ^: {6 |0 P- i0 L* G# G! t
29~31行:设置USART的接收引脚;
- E& k( C1 n/ j3 ?0 O" G8 C. h" f& G. h4 f2 u9 z! a) U& {) J

, X0 s! p' D% ?8 b$ T) {; o3 U3) 重定向打印函数, ^$ o2 K# U# V9 U* R+ C0 v* I' Z
  T0 O2 u- Q! F) M7 ]
以上初始化完成后,就可以使用HAL库提供的“HAL_UART_Transmit()”从串口发送数据,使用“HAL_UART_Receive()”接收数据,但这样使用不方便,需要自己处理数据类型。在学习C语言时,通常使用printf将数据格式化打印,比较方便。因此,这里需要重定向打印函数,在使用printf时调用“HAL_UART_Transmit()”打印。6 \" R9 d6 j1 J3 _, |. L

8 Q1 @9 \+ i+ J7 `" d" t6 P代码段 15.3.4 重定向打印函数(driver_usart.c)
+ ~- w$ M6 f2 \0 E3 M7 T% o$ B
  d# W( S( E5 m4 `% e! c
  1. /*****************************************************
      r2 J4 e" j. p6 [- ~. `) V8 z$ {
  2. *function:  写字符文件函数
    ; O- D: e7 w6 \# X* K1 O
  3. *param1:    输出的字符
    ! Y! i2 S$ ]/ _: W% C; X+ T% O
  4. *param2:    文件指针
    * p; N/ J% d/ ~
  5. *return:    输出字符的ASCII码
    , N" D- R- r. M4 h: @
  6. ******************************************************/! c$ h- \5 ?! u# I' T2 `
  7. int fputc(int ch, FILE *f). C( b* O- ^, S6 @0 c
  8. {. Z2 V: e' ^! O7 q
  9.     HAL_UART_Transmit(&husart, (uint8_t*)&ch, 1, 10);# O8 p8 y( m5 y4 M
  10.     return ch;  E! E6 `' O4 B: e+ P; S& [6 x
  11. }: t/ {+ w, y( `$ k

  12. / Y- \/ w3 Q3 Q9 Q
  13. /*****************************************************  N9 w: ]  I# @7 D
  14. *function:  读字符文件函数
    : K+ C( G* n8 U, X9 Q3 {, Y8 y
  15. *param1:    文件指针
    ! f2 j+ H' i6 ~# m4 H/ G( z8 c
  16. *return:    读取字符的ASCII码. Z, c8 j" U1 A2 e6 m2 J$ R
  17. ******************************************************/
    ! y* {# p. Z# `, ~
  18. int fgetc(FILE *f)
    * a, C5 m7 L" A
  19. {6 ^1 n0 O# {! e+ }  S, h
  20.     uint8_t ch = 0;
    0 k' S& Y( ]/ e8 k5 T
  21.     HAL_UART_Receive(&husart, (uint8_t*)&ch, 1, 10);8 C3 W. |/ s! l! C
  22.     return (int)ch;
    # [4 X# \4 S# G3 |, q
  23. }
复制代码

& R, G& E; i# N' Z4 ]5 Fprintf和scanf会分别调用“fputc()”和“fgetc()”,因此这里覆写这两个函数,使用HAL提供的函数实现收发数据。5 [2 W+ L4 j$ z* r" ?2 L: d: o5 J

' c% h% b8 B# t1 m同时,还需要需要点击“   ”,打开工程选项界面,切换到“Target”标签,勾选上“Use MicroLIB”,如图 15.3.1 所示。! F# q5 h1 d& f8 W$ I$ b  T
* \7 V* f. @1 R1 I1 q8 ]
R{1ZHBOF$(KG)3O%3M_KJXS.png
2 {+ V# k) Y. @
! x& G. |5 Y& p图 15.3.1 勾选“Use MicroLIB”
7 v" l0 c/ h- Y, X- i2 [; i
0 [7 ^/ {3 \" o
$ B8 M! J6 d% m/ T也可以添加如下代码,就不用勾选“Use MicroLIB”,两个方法二选其一即可。
' k* D; e( P3 o, t* z+ a1 d! C  J9 S/ x6 H
代码段 15.3.5 可选添加内容(driver_usart.c)
, V! e; b% [# q4 k. P+ X+ u
% d, {3 j* P3 \: ]/ W6 Y" F
  1. /*
    * ^+ y5 W) c! A& b. _0 G0 ^0 E
  2. * 添加如下代码,可不在工程设置中勾选Use MicroLIB
    - F3 u- ]! ~8 `  u' M
  3. */8 L4 K7 e2 V+ F9 j6 ~

  4. 3 w% `+ g" r1 ^6 ]/ L5 B
  5. #pragma import(__use_no_semihosting)
    6 t. x+ p' E' e" u
  6. : \3 R+ U% }( U# d
  7. struct __FILE. M. }. G0 u& K: ^) s1 ?! L7 k
  8. {* Q3 y; t/ h3 J3 S6 E
  9.     int a;! R5 y( y& a, c6 X2 X( l# P6 h
  10. };
    ' r' x$ P6 o0 _) g. j

  11. % {9 q/ y, a5 ]. J9 J
  12. FILE __stdout;
    2 p+ Q, _7 W* r( E" f  z' p4 {
  13. FILE __stdin;5 v( ?8 K9 y% M0 i
  14. void _sys_exit(int x)
    ' l5 v3 J* s. s7 Y- b, v
  15. {/ I. G1 O' R5 ^5 E0 U3 s
  16. }
复制代码
- m, p- j; q  J: S0 f
4) 主函数测试
4 V' g0 ?5 y- w+ L/ o4 C( K' l% {6 g, `6 ]; ^
  1. // 初始化USART1,设置波特率为115200 bps
    4 W) g% c3 q# v) i/ x6 P
  2.    UsartInit(115200);
    8 a. l- r3 C  M4 z) z+ e
  3. 9 z: q9 \. e" G
  4.    // 在windows下字符串\n\r表示回车
    7 H# p3 A. q2 L/ |* P) s  K* s$ v
  5.    // 如果工程在编译下面这句中文的时候报错,请在魔术棒“Option for target”->"C/C++"->"Misc Controls"添加“--locale=english”/ N( @* R% q% f5 t; F6 R! e' s
  6.    printf("百问科技<a href="http://www.100ask.net" target="_blank">www.100ask.net</a>\n\r");
    $ `/ _/ ^  ~0 u3 P
  7.    printf("UART实验\n\r");
    : a( b& b! G" j" Y
  8.    printf("test char            = %c,%c\n\r", 'H', 'c');) {5 {& m4 H. a- \7 `" u
  9.    printf("test string1         = %s\n\r", "www.100ask.net");3 C2 i9 ~, z8 e2 Z( e
  10.    printf("test string2         = %s\n\r", "深圳百问网科技有限公司");" [+ f9 Z1 W' b* @5 M4 y# T  v% B3 v
  11.    printf("test decimal1 number = %d\n\r", 123456);" x+ H. @1 _: U1 Z3 \) s
  12.    printf("test decimal2 number = %d\n\r", -123456);
    . A* J2 e/ \2 C6 K- l
  13.    printf("test hex1     number = 0x%x\n\r", 0x123456);- i5 J% X0 N+ `- j( Y' K9 ^/ W
  14.    printf("test hex2     number = 0x%08x\n\r", 0x123456);
    / D. L# S% R+ h
  15.    printf("test float           = %.5f\n\r", 3.1415);
    $ c1 l8 s% W6 B1 X; `
  16.    printf("test double          = %.10lf\n\r", 3.141592653);+ a7 _6 S; ^/ b" b

  17. % O# o+ _5 K  F1 R. X
  18. % `, G. y9 _1 S* x3 U3 k/ C
  19.    printf("\r\n键盘输入‘C’或者‘c’控制串口打印‘Hello world’");
    # w* j8 t$ F, o! X
  20.    while(1)
    $ J1 r$ R1 [  h$ M! n* i1 h6 U
  21.    {$ L: x- N2 [, v. j' b
  22.        scanf("%c", &cmd);
    , G3 z! F7 [- c
  23.        if(cmd=='C' || cmd=='c')7 E( \& y# f% S( P7 \; u1 x
  24.        {
    & m3 I' E& z" v6 B) O
  25.            cmd = 0;
    ; w6 T5 A, `) d/ A
  26.            printf("\r\nHello World.");( _  {3 L& u/ A$ _% k2 M2 Z7 J
  27.        }
    % w; C' C) p3 ^+ X3 I/ q; n- M( C# c
  28.        HAL_Delay(100);9 }9 ]& S4 N& G& M; @2 h
  29.    }
复制代码
: |$ L& {0 |8 @) q
主函数直接使用“printf()”便可调用USART1打印,使用“scanf()”便可调用USART1接收数据。% B  v6 v7 `2 y. B

/ B% O- O1 T8 Q9 g  y. b2行:初始化USART1,设置波特率为常用的115200;5 u2 p- ^/ O7 ?0 A
/ N& L/ B! T- X- W% N
6~7行:中文打印测试;如果编译报错,需要点击“   ”,打开工程选项界面,切换到“C/C++”标签,在“Misc Controls”里加上“"--locale=english"”,如图 15.3.2 所示。/ q: o' P# j3 J) J+ n" e

: l( E0 p2 M  S2 ?/ t8行:字符打印测试;+ C" _9 c3 U2 O% ?

4 y9 C6 V) k! w7 G1 f6 W/ c6 f9~10行:字符串打印测试;/ r+ D5 J3 j/ j, r  X
8 `1 r, V5 ?8 P) h5 G. @
11~12行:正负整打印疑测试;
3 @: U$ Q# t" Y# F6 I+ q, j1 x8 i; D& C& i; |
13~14行:十六进制打印测试;
. y) N: G+ e! q' q: v0 D- @" K- U3 E' c2 V/ P- l( \2 ^1 G
15~16行:浮点数打印测试;
8 b: h* K+ N* D6 X5 O- y7 F6 m3 z! o, j! z/ o
21~29行:通过“scanf()”函数获取输入,如果输入的指定内容,则打印指定内容;
4 ^" A# O; ^( S& X) E* m5 j
: X1 o5 s* a+ F N{$QW8W4Z~M@7V~ZWL)XHER.png ( k3 N4 P; {1 y" L8 z& S& v
, x  p' C0 r( l  }9 f1 g0 ]: q
图 15.3.2 指定本地编译环境% Q* p: U# z) Q! c! q2 \' \3 x
! b% V- _' \# L; {, }) [; P
2 i/ _$ [$ z5 D7 v) ?% C* d
15.4 实验效果) J" n! g3 m6 h1 t/ B+ M* a
本实验对应配套资料的“5_程序源码\7_通信—调试串口\”。首先如图 15.4.1 所示将开发板插入电脑USB口连接,注意拨码开关拨向非ON一端。打开MobaXterm,设置串口会话,如图 15.4.2 所示。# y& N2 K9 h- s8 ^" t: o5 S9 }: t' _- r
' w  u9 H; J- g* D
①点击做上角“Session”,创建会话;+ O: f; g' ^- d) \8 n! A
0 x. ]8 x+ h1 t+ S  f
②在中间弹出的窗口,选择“Serial”,即串口;  h' T1 c, _) p
3 _# n3 _- T7 L1 [
③下拉选择串口端口,我这里是COM6,读者可能编号不一样,但后面显示的芯片型号是一样的,选择“USB-SERIAL CH340 ”即可;
, V& p: J- t6 G6 H2 U2 I9 C1 Z' R/ e# u! n4 r
④波特率选择115200,与主函数中串口初始化设置的波特率保持一致;& H# }: D0 P- e0 ~

# z. B2 |+ k- M; T& K⑤以为串口是异步通信,需要双方统一传输规则,这里和代码段 15.3.2 中的设置保持一致。数据位8位,停止位1位,无校验,无流控;2 {9 r* a; {& S% a5 u4 P! U: h
7 u6 U8 Q9 M8 Q5 d$ H/ |3 [
⑥设置完后,点击“OK”;) `0 }/ C+ u( K) }5 ~& S7 P) i

6 ]* r7 {: x. w; ]) ^& o8 A打开代码工程,使用Keil编译,下载程序,MobaXtrem显示如图 15.4.3 所示界面,所有打印正常显示。在电脑键盘输入“C”或“c”,即可控制串口打印“Hello World”。- u3 w* Z8 T% r5 R

2 `( N. u6 a' [( r7 F9 T  B) ?- ~ ZEBS%)U{I([QL7UGK_MX7SM.png : L% \# f3 M2 q3 {& E7 d9 }
$ Y7 t9 s# \" J; Z& `  M
图 15.4.1 调试串口连接实物图) s7 Y! X. f4 }( T  U# g, _. A: V

0 _8 P" d: O5 f7 t  V }(]U$T9X%NC3W1_KP{(@9@G.png
! F: a- b( r0 p/ {: {9 {- {
  ^) l$ I* H( M; e& Q& M图 15.4.2 创建串口会话1 x' t4 m1 Q7 @6 T
# v: p% `4 ?& J+ L9 e! e( B$ @
0{{1ZFVOE@BRW(EI$%FT(XT.png * |8 ?% J5 X3 z% o7 j4 d

+ X; a* n5 x- f) Q# X图 15.4.3 串口打印效果 & D, d4 z: [7 r! t( I7 J( W7 I

3 }1 x; L& q1 \0 `( k: l' P1 a/ w( c8 O, I! z$ J7 q
赞 收藏 评论0 发布时间:2022-4-28 21:05

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版