前言9 n+ k% Y }- ]/ v3 ~9 F
这次我用的板子是一个用STM32F103C8T6作为主控芯片的一个数据采集卡,两个LED灯连接的引脚是PB3与PB4,TX与RX引脚分别是PA9和PA10。2 |- n9 W) r9 J7 Q/ z$ a& Z
: t7 g2 W# O2 k' y1 X- g
3 D0 K( D3 v. d4 C2 J
$ _6 D7 _3 @$ l& {* G2 h
" A+ d& W5 ]# O* I, [4 _! c8 I
一、配置CubeMX& @& s6 N) R$ T5 i2 q6 t. G' c
1、新建工程;
% m- v; }' N* r% s2、配置时钟源,在RCC里面的HSE配置的是晶振时钟;
- T: N6 y; J) N6 z3 m6 Z3、配置程序烧录引脚SYS为SWD模式;' }5 l0 g( A' K+ I$ J
4、配置GPIO口,配置一个LED灯(我的板子是PB3),起到串口成功接收到数据时的指示作用;
7 m8 a [/ D2 M% ` r& l# z5、配置串口收发引脚6 W0 Z# B; R" p; U
7 F$ s; S& L2 W" M7 @! c
此处我们采用的通信方式为UART通信(异步全双工串口通信),PA9作为TX,PA10作为RX使用。直接在可视框图里点击相应的引脚进行配置。+ X3 _0 X8 [5 O' p5 ?- I+ B% V4 o( x
0 I) c5 e, W- v8 ]0 w7 U配置到目前,效果图为:! b' v5 j w4 ~, A1 a; T
% }( q7 e5 q* _8 \8 w0 X
- q$ G( K/ Z' {/ ]7 ^+ g
2 q. x1 L+ ]. Q+ l2 M/ J+ x可以发现,TX和RX两个引脚配置完之后是黄色的,代表还没有配置完毕,下面继续进行配置。( d2 b! v" H/ G% k, v
" B# M$ D. O0 x5 G) [2 d1 c+ A0 k6、在左边的"Connectivity”里面的USART1模式里选择异步全双工通信模式asynchronous
3 A) L P% _* k1 I& {! {7 M4 ?# U⮚点击USATR1
9 { d$ _! S+ ?⮚设置MODE为异步通信(Asynchronous)' A H: v6 @8 }5 O' L
⮚基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1,接收和发送都使能1 l% C0 X4 K6 D% |, ]
⮚GPIO引脚设置 USART1_RX/USART_TX% A2 \* i( _' x& a! i
⮚NVIC Settings 一栏使能接收中断- H4 @ r4 k( I! p- r2 \3 V" l! V
- g- F9 Y7 Q; V" N7 b, ~: X
这里简单扩展一下:
% I$ J! S7 V/ }7 r. g2 JSTM32F103系列单片机共有5个串口,其中1-3是通用同步/异步串行接口USART(Universal Synchronous/Asynchronous Receiver/Transmitter),4,、5是通用异步串行接口UART(Universal Asynchronous Receiver/Transmitter)。
0 o' u2 c! `/ s. D6 D4 O" u2 |$ W! u4 K4 F( b/ n8 C: o
9 j( ~0 b6 j6 R! y- T# b9 J% i
" U2 s: `4 K) e# C7 n& X
" K0 t/ Q t( v+ U3 U! {: a8 _ I
, D8 @" c4 f" t1 P, b
1 u0 I9 i) s5 m, E7 N( Y
h9 w7 K4 ]" i2 x8 l, |; k: n N1 U$ P0 E+ p0 ~- N
7、配置时钟树,我还是开到最高的72MHz
$ A7 W) G$ W9 Z- g% Z! A. O8、进行项目设置,最后生成代码,CubeMX部分就大功告成了
7 @* ^) l# u% c$ D' i K: Q5 x+ k9 D* L2 L2 y4 i3 N
二、HAL库UART相关函数简介
( W# b& m1 O9 M% a- M0 m! R2.1 串口发送/接收函数
1 ^5 ]9 |9 y. s$ d8 O- HAL_UART_Transmit();//串口发送数据,使用超时管理机制
3 [3 I, I& u( ?6 |# a9 v - HAL_UART_Receive();//串口接收数据,使用超时管理机制
: |+ s( ~" x2 {3 L, l. l- e - HAL_UART_Transmit_IT();//串口中断模式发送 * w& Y4 n' s4 y0 \1 `/ h
- HAL_UART_Receive_IT();//串口中断模式接收. w A. D& r O
- HAL_UART_Transmit_DMA();//串口DMA模式发送
6 r# ?/ n' y( f7 R# W' _ - HAL_UART_Transmit_DMA();//串口DMA模式接收
复制代码
5 U1 P# P7 [, s& o; z: j这六个函数参数基本一致,发送/接收各挑一个常用的函数进行简单介绍:8 }% b$ f$ `$ \" E7 d/ q2 V4 F, T; h
4 K! M0 T$ T/ S. s0 W串口发送数据:
" _4 b3 D2 V3 l% S* ^- K- C }$ Q+ ]5 Q) i8 T0 v+ ]4 \3 m! m
- HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
复制代码 - V8 n; t+ U# T. z
功能:
7 j) P7 v( j( A
% \( B5 k/ Z5 w6 ]串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)。
* f$ f: ]. J0 F5 b1 a9 u+ G$ K, _/ x7 K6 [7 ? N" q; c
参数:/ ~$ n, E) ?4 G4 ~
+ H2 h. W$ R8 {* c
⮚ UART_HandleTypeDef *huart UART的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1% o9 k! S t; x$ W7 h) o) _5 I
⮚ *pData 需要发送的数据
' u5 A2 j% R, M, m# l5 l⮚ Size 发送的字节数* V; v* L) X; z$ f0 v y# K
⮚ Timeout 最大发送时间,发送数据超过该时间退出发送, `9 p7 i( {# W5 u
举例: HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff); //串口发送三个字节数据,最大传输时间0xffff
; H! Z6 t0 f) z1 U& S3 O1 i4 Y3 y3 r- f+ ?) p6 S
串口接收数据:
) L" D: s% y9 a% A+ `! U7 i0 Q
: Y. b3 u8 C' ~# r- HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
复制代码 . ^6 [0 j$ r# h5 l, j' _$ q9 f
功能:: F9 V/ i6 h) t
[* x1 [9 q9 X6 b0 \
串口中断接收,以中断方式接收指定长度数据。
) \5 |+ l; P* ?' f+ R( p大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。接收到数据时,会触发串口中断。再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)
% H A' t7 p- R
2 G5 n3 R9 q8 I5 ?5 ^+ w* d参数:" Y3 p& N# f6 G( |- d5 V: \
⮚ UART_HandleTypeDef *huart UART的别名% D' f2 x) _& K& Y" f. H
⮚ *pData 接收到的数据存放地址# |( g( Z4 U, Y% r
⮚ Size 接收的字节数% H6 T/ M3 f4 d+ u' e3 n% x
举例: HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1); //中断接收一个字符,存储到value中8 M3 L1 x9 c: M1 C) U
) f$ U! q8 V9 b' m2.2 串口中断函数
- [6 t1 n* |. y' f1 `- HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数1 R) V& b" [# m6 m
- HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数; E% k* J7 Y0 w& Q
- HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(不常用)
0 |0 C7 V2 b$ a4 O5 q2 f - HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数
7 p( o% l* O: w- V3 o - HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(不常用)
5 T: m' l8 T4 D6 q - HAL_UART_ErrorCallback();//串口接收错误函数
复制代码
2 S4 Q! U s' X7 z' ~串口接收中断回调函数:# C. i) Q. A8 d. f% x3 }
; w2 F0 b: G: z. z& o" @( S/ W/ Y+ s: [- HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
复制代码
, v) D" G& r9 E; ~/ n7 u0 r6 t功能:
) t8 B3 {/ j4 n ]& q: J4 r; Y) s; ]/ D/ F3 p: ~; ]2 L- |' U) o$ P
HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改。
* }% A" l ?: y) I+ }
% Z( @4 @7 _ t2 v M) E! ^参数:
v/ {0 M, ?% N7 L/ e/ W& v, A. d, E+ v8 o" g- }" h8 A$ O7 B' j
⮚ UART_HandleTypeDef *huart UATR的别名# E. C+ a8 |6 `; M0 ?* l! ~" L
举例: HAL_UART_RxCpltCallback(&huart1){ //用户设定的代码 // }$ _% L' l6 j* A l7 ]8 ?
/ a# r8 V z" U; `0 [6 p& A/ Y0 f; F) Y串口中断处理函数:- HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
复制代码
' x) j6 x/ I0 c功能:
( J2 ~. r5 b1 w; l/ C' c7 M
7 m4 z# ? l0 ~, n5 i对接收到的数据进行判断和处理,判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用。) J+ Z! {4 v" A5 [, {# b
% I7 x v% ~7 E' n如果接收数据,则会进行接收中断处理函数
$ H7 V# V: o# f2 K0 c% q0 G6 k3 f; r
- /* UART in mode Receiver ---------------------------------------------------*/ J# [) Q8 k& w- e6 F
- if((tmp_flag != RESET) && (tmp_it_source != RESET))
) K; P1 v1 U9 f9 o - { , j3 w7 ^/ p# O2 }4 G3 e/ n
- UART_Receive_IT(huart);4 u: l0 I; A7 ?
- }
复制代码 " s" k! f1 c9 N9 g+ \: i O
如果发送数据,则会进行发送中断处理函数
. w" [ N6 Z. ^' |
! b0 L8 e0 ~: d5 z$ {4 k' {- /* UART in mode Transmitter ------------------------------------------------*/; p% N2 b r5 m! Q; T, S& @
- if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))- q) Y& [3 {. S. G
- {
' ]0 {4 o9 D# x( ^7 e6 r - UART_Transmit_IT(huart);
4 E% @: C( e. h - return;" Y# ~7 F' l) z! ~% m4 b& X$ \
- }
复制代码
. N% j, ^2 h! a2.3 串口查询函数
/ D8 X7 t! v' R: R, y% K& {- HAL_UART_GetState(); 判断UART的接收是否结束,或者发送数据是否忙碌
复制代码 8 m* ?& i# b4 {% ?; `& m: K
举例:
# L$ e6 {" j i/ O
1 o5 H5 C0 s% v5 v- while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX) //检测UART发送结束
复制代码
( E8 f/ j9 Y; [( g三、逻辑代码部分
% E8 f$ t m O9 [3.1 UART接收中断
+ H- f5 T. @8 ]4 Z/ ?因为中断接收函数只能触发一次接收中断,所以我们需要在中断回调函数中再调用一次中断接收函数。
, v. ` N/ x& a; k. z* Y* d! |5 g
具体流程:" E3 W, y: G+ V$ V6 d% k, D& s5 H
6 D6 d. }6 _5 R, o
1、初始化串口
( V5 c# }8 c- B4 @+ i# |& L8 g$ Z' _/ k5 Q) ^3 ]
2、在main中第一次调用接收中断函数! D* |) v! b2 E% G# a
! D U! \* S! g d `3、进入接收中断,接收完数据 进入中断回调函数
+ E+ Q% ?; j& h0 w; i1 K
$ K0 Z% W0 f- x1 q! |4、修改HAL_UART_RxCpltCallback中断回调函数,处理接收的数据, b" B' x5 Q8 {. C7 K$ k0 L
: I9 C5 l1 Z" }& o [. y
5、回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断) J; e& l) ~9 w0 b8 y/ ^8 N" M! h
6 f1 J. W% j& _# y) q
函数流程:5 x( W, [/ A/ U2 P% E
8 I( J/ H# [7 t% D①HAL_UART_Receive_IT (中断接收函数) ->) Z% Z2 Z! ~$ ~! d! K3 I e
②USART2_IRQHandler(void) (中断服务函数) ->
5 v+ ~ i F- N9 N# X/ d③HAL_UART_IRQHandler(UART_HandleTypeDef *huart) (中断处理函数) ->: n8 H' z$ P" @5 v& t
④UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数) ->! t6 v5 b8 o( T8 s% |) g6 D: f( [3 D
⑤HAL_UART_RxCpltCallback(huart) (中断回调函数); h7 f$ v( [( o1 {* u& J
7 C1 \8 I) ?! `& n( B
HAL_UART_RxCpltCallback函数就是用户要重写在main.c里的回调函数。! t2 W, O4 n9 ~: k) ]
; Y" r- l# M, o, N( g代码实现:
* c) z% [# u4 l# Y+ i2 p- |% @) ~: s, V$ ^5 \1 o, R: B
在main.c中添加下列定义:) `7 M( Q b8 Y4 L) M' c5 z
6 Y1 Y& Q( ^& t- #include <string.h>8 G y$ L: T0 x; [" s8 |& Z4 k% u$ }
- ; |+ ~' c0 m% O: ~: D- a5 I F
- #define RxBuffer_MaxSize 256 //最大接收字节数) X! v C; o! d& h- f p
- char RxBuffer[RxBuffer_MaxSize]; //接收数据
9 c$ n* O( l- i - uint8_t aRxBuffer; //接收中断缓冲
% V$ z9 e s+ g7 i' ] - uint8_t UART1_Rx_Cnt = 0; //接收缓冲计数
复制代码 + P& o$ s& R, z0 A4 q5 ]- N6 k
在main()主函数中,调用一次接收中断函数$ j- I+ `& E; H& q
" _% u& l: p5 i4 t. O
- /* USER CODE BEGIN 2 */
$ u( \5 r6 s8 b - HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
?& Z1 ^) u" ~9 \% C) _" O - /* USER CODE END 2 */
复制代码
" t# x7 J! A( m# e! i在main.c下方添加中断回调函数% e" D. T9 x; j7 Z- [& w# Q
: A) F \% n7 T; h" w: F
- /* USER CODE BEGIN 4 */
+ x4 X- A$ J" J
3 d. Q3 v- p8 m/ L* B. u b7 G- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)1 \- i b7 l8 M' F" T
- {! _4 G" r, j! u: t0 y
- $ M3 l& Y6 I# ?& N
- UNUSED(huart);; c% h$ P& Z# [ {4 w$ N
- 0 M4 @& }: U9 h% e/ @
- if(UART1_Rx_Cnt >= 255) //溢出判断( M& m* N: B' W9 |
- {
( C0 V7 M% O( m+ S) \ - UART1_Rx_Cnt = 0;
, ^" z' V+ U3 v# j: ~4 Y' h8 R - memset(RxBuffer,0x00,sizeof(RxBuffer));
& s" h8 q7 A8 P7 t1 r - HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); # `* z! v6 U* T4 Q# z1 q
- }
9 @! U+ V: t x. m2 _ - else: y- P& {; |# t9 O0 n8 j
- {
6 V0 D' x& q! G8 Q - RxBuffer[UART1_Rx_Cnt++] = aRxBuffer; //接收数据转存
/ i% _" e9 P+ }" `- e( \+ X - if((RxBuffer[UART1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[UART1_Rx_Cnt-2] == 0x0D)) //判断结束位6 _3 X) H! ~- o, t
- //此处条件可以改写为if(RxBuffer[UART1_Rx_Cnt-1] == '\n') 因为上面的条件就是表示回车2 m1 ~ |! T1 l z2 T, L
- {) z9 h! G+ s! u" R. X9 l! @
- HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, UART1_Rx_Cnt,0xFFFF); //将收到的信息发送出去, H* d" d! h5 u% |/ V5 [: _
- while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
( s/ C y$ B( {2 P# F* w - HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_3);//LED指示灯状态翻转
m1 m+ h" d2 @) ] - UART1_Rx_Cnt = 0;
+ ^ g/ }& x/ q4 w& ?* V - memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
, {3 ^# A6 G3 u4 H; Y - } x! C4 U* c& m- E. n2 H
- }7 T1 U7 ?9 ]& T' i7 X
- HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再开启接收中断
( E" i& s/ `" P; P8 M3 ~) v T$ R - } P7 v9 ?" W1 f5 m& ]) k
- /* USER CODE END 4 */. [* j- k+ g+ T/ _3 c
复制代码
/ o# N. A3 E! n现象:
s1 K( f" X+ e- U* g; _6 k" B9 c' q4 L0 [1 w/ V4 ~5 B% m
使用USB转TTL(CH340)进行数据发送,XCOM V2.6串口助手进行调试,发送“1”(这里要注意波特率等参数要设置的和之前配置的相同)! I' P0 [# w# i4 c4 A
- n! D" v, l/ v$ Y/ s9 C
' r; [7 v6 r! a9 f! w
# q: R4 [- ^$ ?# g, X1 L5 Q2 m
在debug模式下,将缓冲计数值UART1_Rx_Cnt加入监视器,串口接收到发来的数据后,计数值+1.
* Y: G# i& p, p8 ~ s
% \( A4 {0 V# {* f3 f/ _- O
" `2 l! j1 ^- [
. U4 a7 B- q! R+ {/ R) `& F8 u同时也可以看到LED灯状态翻转
, H1 @: e# X* L8 H9 h ^7 h+ I& a; Y, v8 R {4 }, n( ^4 A( [
3.2 UART发送4 O, b8 f8 u. i5 {% w' s# n
重新定义printf函数
3 W7 G; }. N5 f$ y8 o3 ~4 ~% M" J! ^/ @% U
在 stm32f1xx_hal.c中包含#include <stdio.h>
( L, o& x9 c& S8 x4 E' F9 _" t1 D, G: C3 q
- #include "stm32f1xx_hal.h": ?, b$ F$ K7 Q& x7 a+ _$ [/ K( n
- #include <stdio.h>
% M# I- ~; h5 T5 H - extern UART_HandleTypeDef huart1; //声明串口
复制代码 在 stm32f1xx_hal.c 中重写fget和fput函数
0 c4 u2 ?8 [5 C8 @6 U3 ~% |# Q9 G! v
- /**
" p3 c3 n a# t% R% g7 e" L- O - * 函数功能: 重定向c库函数printf到DEBUG_USARTx
6 g* z) ?( f% t6 k7 D - * 输入参数: 无& g( m' q }, r# h$ e* K
- * 返 回 值: 无
) i$ m7 _5 n; C/ o* g - * 说 明:无; y/ _! A$ n6 r( l4 @
- */; \# j }. w0 u( _5 t
- int fputc(int ch, FILE *f)1 h5 _: k0 b' L$ I& f3 j
- {
, |, m2 q) h6 _ - HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
% K$ a [9 A* T8 I5 E6 N" v - return ch;, z9 ]( U' W6 w# a
- }9 y5 _5 P0 x/ A
- H' u7 z0 a% X- /**) h- ]6 S4 G" y" F! ] {
- * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx/ q" C4 v3 \, _3 P
- * 输入参数: 无
4 X2 z1 ~7 R/ M2 @ - * 返 回 值: 无2 |- l7 i7 m' j- Z2 t) \! l
- * 说 明:无
! H* d: ]+ C' Q - */% H' p! C G" d) A
- int fgetc(FILE *f)
* y; o2 @6 ?, ~2 {; u3 z- V - {
% K- l# |9 y+ A3 E4 C - uint8_t ch = 0;
$ w# Y0 o& W9 G6 J. X [ - HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
; I, w! P- t* a1 W- T - return ch;
M! e" l) P; t1 [4 Y# @ - }
复制代码
6 F: Y8 b# h' Z" w: _1 Q+ Z8 e& \在main.c中添加' r7 I" `# I0 X* q& n3 T$ X4 E2 V
' v3 p; o: D ^/ T- #define RxBuffer_MaxSize 256, c2 _, c9 Y& Y( }# E
- char RxBuffer[RxBuffer_MaxSize];
复制代码- while (1)
" M4 |) G6 A# p, X - {
) }# f+ y6 K- o0 } a - /* USER CODE END WHILE */& U2 z0 l2 R/ T _
- printf("hello world\r\n");//这里博主只写\n的时候无法换行,百度了一下用\r\n就可以了,估计是编译系统的问题
/ v) A& \+ g- I4 s7 ^( c0 E+ ^ - HAL_Delay(1000);/ P1 L8 T& a- {
- /* USER CODE BEGIN 3 */
, Z2 D. u. w2 v0 z - }
复制代码
& ~# f. v# h+ @# n0 c) G& O5 s现象:( Y$ D0 y3 K1 O- ?; @
# ]+ o& D& P& i+ M. y
- H8 b2 z/ r: b0 i# D0 ?: E: T- u' G
注意:$ \4 a1 L$ ^: ]6 X. {6 b
重定义printf后,必须在target里面勾选上MicroLIB,调用一下这个微型库,不然一直卡在里面。
9 X5 K% j0 K1 Z7 p- G8 p/ `5 v
# x7 P, z+ O$ C- s, L+ _; b
2 `- k {$ T/ B& q1 d. u( V- D4 c) z4 ` X/ T t% \
+ \: {3 L0 R6 d& }0 o( Y
8 M, N) E: H1 [7 s1 h
& X' P1 r6 L( _2 {+ h) B |