前言9 |" B. ^7 e. Z$ S. b
这次我用的板子是一个用STM32F103C8T6作为主控芯片的一个数据采集卡,两个LED灯连接的引脚是PB3与PB4,TX与RX引脚分别是PA9和PA10。
& ]3 m: N& `' n: A3 ^
3 p @9 \; W( h
) c. r* w5 y- }
) m. `6 k" a; {6 S4 |/ Z; \2 k
4 F: S, o6 E' ~3 X一、配置CubeMX
+ Q7 m( I/ ?$ O' n$ X. e& i \2 K0 [1、新建工程;
1 {* N) o/ y' Q2、配置时钟源,在RCC里面的HSE配置的是晶振时钟;8 v* y/ l8 l0 B, T5 n, U
3、配置程序烧录引脚SYS为SWD模式;
+ C6 L! ?* D5 W" H! o8 D" b4、配置GPIO口,配置一个LED灯(我的板子是PB3),起到串口成功接收到数据时的指示作用;' b# k. g" M$ _. X$ y, ]) S* L
5、配置串口收发引脚1 N% ^# ~; c$ \: L9 N4 q
, b$ Z3 x$ C V% j. D( j
此处我们采用的通信方式为UART通信(异步全双工串口通信),PA9作为TX,PA10作为RX使用。直接在可视框图里点击相应的引脚进行配置。' Z* ]& S" |$ y/ S' z7 o
6 ^1 C/ w3 C1 X# Z, e
配置到目前,效果图为:1 L" D$ D' e g
$ G; ]5 S1 }5 x
$ I7 N. [8 Z# n7 z
6 }( \$ t+ f' T3 O/ R/ `3 K可以发现,TX和RX两个引脚配置完之后是黄色的,代表还没有配置完毕,下面继续进行配置。% r5 W! ] I9 Q! }+ `1 i
9 ~7 ?, Z2 U) W: |6 n' b
6、在左边的"Connectivity”里面的USART1模式里选择异步全双工通信模式asynchronous
- u: l4 n' }& y% x1 H6 J8 Q⮚点击USATR1
9 W+ o1 v F! {$ ^* u⮚设置MODE为异步通信(Asynchronous): P* r6 Q* r f. R) Z3 g z0 E, o
⮚基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1,接收和发送都使能
4 z Z. A/ ?& g6 Z⮚GPIO引脚设置 USART1_RX/USART_TX: n% H5 Q1 \5 d. B3 ^# ?
⮚NVIC Settings 一栏使能接收中断
3 k+ Z8 h7 p$ y+ |2 P% Y# r+ u. r/ \- u
这里简单扩展一下:
* ]# S+ W, B o, y+ e- \/ vSTM32F103系列单片机共有5个串口,其中1-3是通用同步/异步串行接口USART(Universal Synchronous/Asynchronous Receiver/Transmitter),4,、5是通用异步串行接口UART(Universal Asynchronous Receiver/Transmitter)。
0 r. q c. ?/ l0 X8 B% y# Y V
L& s. D% @( {4 O" Q! O; s
% d. Y0 O) F7 g' F/ c. a7 p8 P f* |5 d! Z, D e7 ~
& X- N' _5 ?: T* X+ j
2 d8 e) ?9 j" i8 B
2 z8 {9 D# m9 k* S; P( I+ |7 u3 }3 [' s- C8 N/ _) q
2 T3 o) X" q- { A; K2 m7、配置时钟树,我还是开到最高的72MHz
/ A# h6 r2 n! x) `9 F) A8、进行项目设置,最后生成代码,CubeMX部分就大功告成了& B6 i" v5 s! L n6 Q- ]
( g+ N& m }/ y, }" g, r7 z
二、HAL库UART相关函数简介
' X8 L4 u; Z/ w7 p- ^/ ?2.1 串口发送/接收函数
0 z) a, c# Q& q/ D4 G- HAL_UART_Transmit();//串口发送数据,使用超时管理机制
# O# W4 K+ g8 ~) G5 F; ?' h - HAL_UART_Receive();//串口接收数据,使用超时管理机制
; G) M7 S* h+ D( K/ C7 H/ l: A - HAL_UART_Transmit_IT();//串口中断模式发送 4 v2 m/ l, @5 g; m1 S/ @ M
- HAL_UART_Receive_IT();//串口中断模式接收
4 A) N8 x& ~% }! e/ v/ a+ G - HAL_UART_Transmit_DMA();//串口DMA模式发送
4 J* y1 \5 P9 l- H _ - HAL_UART_Transmit_DMA();//串口DMA模式接收
复制代码 ; S9 p l! f: A! a( P
这六个函数参数基本一致,发送/接收各挑一个常用的函数进行简单介绍:
- P6 ]* N( r$ L$ @& I9 W1 G5 |* v2 }/ T) o* n
串口发送数据:
4 U" z7 p( S$ c( l/ _% W& E+ b" I4 x7 p) |, D
- HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
复制代码
. l$ @" a; B3 X功能:# j2 `( t) |6 D( R
8 @4 i, n9 }' Z1 A& C4 L
串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)。0 H2 a% d: n4 h) c( b7 g
1 W% o& j$ u' \2 L/ Y/ D( J
参数:7 u! j% U; @( u+ B8 T v
" x, h! q' h1 B9 U& c! ^
⮚ UART_HandleTypeDef *huart UART的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1+ C2 G; b0 I2 [, O* x" J
⮚ *pData 需要发送的数据
$ R9 F( ~+ z/ Z& l2 U; [ `⮚ Size 发送的字节数
/ E$ F* n( ~4 y7 A7 J n# b⮚ Timeout 最大发送时间,发送数据超过该时间退出发送" @8 \9 H) Q9 n }6 a" o
举例: HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff); //串口发送三个字节数据,最大传输时间0xffff
5 S0 G; \% V" H/ D/ E2 j7 c( b3 u* }6 p% B) |8 _
串口接收数据:
9 G0 L; h# @, T& n$ H+ j: J' N9 e9 x g& m; l
- HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
复制代码 & u% k* @/ f3 O
功能:- d) q: N8 B- [/ U2 n% N
0 r- b' r0 D6 _7 I
串口中断接收,以中断方式接收指定长度数据。& v6 I8 B( O4 |
大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。接收到数据时,会触发串口中断。再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)) q1 v ]. c! W# R, S. K
( g. c# Y0 J* o& i" G参数:
0 j: y' ]7 w( _; z2 }, |$ [! P& \⮚ UART_HandleTypeDef *huart UART的别名$ I: O+ J7 p b; W& E; N, {
⮚ *pData 接收到的数据存放地址
* n2 Q/ r3 {) z, G: ~; U# Q n⮚ Size 接收的字节数) c- e- u X* P% |) p+ k' `
举例: HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1); //中断接收一个字符,存储到value中
7 M4 S' ^! U3 E5 V% ^0 E/ L5 Y5 C! w M# N" D8 R [6 v' k) Q' q
2.2 串口中断函数
8 R8 d- [8 B9 R0 Y) A6 W3 Q- HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数0 K& y \0 I+ M7 W9 m+ B/ A- A0 D
- HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数$ Y! i. Q4 p |8 f' `" C% q8 H; N* l
- HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(不常用)1 E7 T1 u! M* E3 N: \
- HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数
1 i v% x0 u# d, J# o2 c - HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(不常用): I0 e, |* i0 S' T
- HAL_UART_ErrorCallback();//串口接收错误函数
复制代码 W( }* M# x$ O: f+ T
串口接收中断回调函数:; N, S2 q( V' O+ S& b( k) V
' M X& I1 E( T' I
- HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
复制代码 4 X4 C% x& S- G; y, Z; O. @' n& z
功能:: v( k2 a) o. `- L7 z- B
- ~) Q1 _+ {4 uHAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改。
) s" |8 f1 Y1 a, a' p3 r, f1 I }, K( c: A# i. b
参数:
$ u4 Q5 J: b( y K5 G4 S0 t/ x2 g% ?; l+ d$ _% [4 i" ?
⮚ UART_HandleTypeDef *huart UATR的别名6 H, E( l- W* X! h
举例: HAL_UART_RxCpltCallback(&huart1){ //用户设定的代码 // }
6 |7 j( G+ d6 e9 p( d
: N4 E( \5 o2 X+ w串口中断处理函数:- HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
复制代码 1 O. M! Z, `2 U; `
功能:/ h5 P( y! V* L+ W0 A* G* _
5 ?& |5 Q) {! B# [3 {5 f
对接收到的数据进行判断和处理,判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用。$ P3 L: K: S% g( Y9 s
; p2 h" A4 K. k0 j0 V
如果接收数据,则会进行接收中断处理函数
0 M' G; S0 N! }% o& b( Y$ O" I n. D
- /* UART in mode Receiver ---------------------------------------------------*/
3 I/ S0 l: G' T: t' [0 } - if((tmp_flag != RESET) && (tmp_it_source != RESET))/ h- Q, G) G; E, [
- {
/ i0 }7 o2 d. t% N$ }* P - UART_Receive_IT(huart);
% c3 l' {5 D& c; w/ N - }
复制代码
) S# p8 h6 N! ^8 e, s* X1 [ G; {如果发送数据,则会进行发送中断处理函数2 I. c6 u C. p9 a1 @: N
8 e/ {5 V! w- A- /* UART in mode Transmitter ------------------------------------------------*/
" b& P$ A; _+ F, r - if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
" |" l- L) r. C1 A( B- n3 X6 a - {6 U3 n& n+ w& r* ~* B
- UART_Transmit_IT(huart);' E0 I1 g. K! R9 K5 x7 ]
- return;. k) K9 [! M9 {# C3 A
- }
复制代码 2 c4 Y7 _: N; I }1 }- h5 \/ Z
2.3 串口查询函数
9 Z8 N/ K/ Z+ r" z) B/ k3 u) i) r- HAL_UART_GetState(); 判断UART的接收是否结束,或者发送数据是否忙碌
复制代码
" H, C2 D/ L) |4 B' s举例:
4 t/ K' ~2 N" B" \9 M3 e2 \# \2 V. `. Q- b
- while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX) //检测UART发送结束
复制代码 0 [1 Y) K9 i* N
三、逻辑代码部分
: }; ~6 u( W b* l3.1 UART接收中断
* r1 S! k/ R2 q因为中断接收函数只能触发一次接收中断,所以我们需要在中断回调函数中再调用一次中断接收函数。
3 {4 }: C* j' S# z' d
. z/ g, l( S- A: d# V具体流程:
( F h, J" C9 d: o: L, S# y% U M+ c2 R) q- i# e& N) H0 d
1、初始化串口
9 g" ^! [6 U- I& R( w+ j
x! Q' [1 U. t4 d' @2、在main中第一次调用接收中断函数9 z0 N0 b; f5 z1 w8 [1 Q) z9 q/ R: G
& X" ?% s$ U; y: C$ b' I6 a; B3、进入接收中断,接收完数据 进入中断回调函数
: Q- d* d" h. `/ U8 j7 s7 L: ]+ S" I" m% h9 ^; P
4、修改HAL_UART_RxCpltCallback中断回调函数,处理接收的数据,. G& |/ s9 ~1 ]
1 C* E# H* h) V) j o; o: o/ `; E5、回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断1 }" O. R3 l( d: s/ O, a/ [3 O
/ U3 ~( V" j5 |5 Z# e, G函数流程:% f$ b" I1 e7 ]4 F+ e+ w
1 @% n2 a, N# j①HAL_UART_Receive_IT (中断接收函数) ->5 [( }) a$ b- s" e% x: E# q
②USART2_IRQHandler(void) (中断服务函数) ->1 L4 \! g4 e7 ?" R9 |# S7 q
③HAL_UART_IRQHandler(UART_HandleTypeDef *huart) (中断处理函数) ->
$ A+ h+ X9 p2 G6 q8 ]2 D. M④UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数) ->
@4 a" j O: |+ U- ~- j⑤HAL_UART_RxCpltCallback(huart) (中断回调函数)! y; z, b7 Z# n" K# i4 a0 ]/ g
1 n% w! b! t& |7 A$ e: B0 t
HAL_UART_RxCpltCallback函数就是用户要重写在main.c里的回调函数。8 b. h$ x* }$ Q
* ~' ^( E4 E( D- ^; @& J- }
代码实现:
/ D: W$ c5 n- R8 b
" {8 N8 Y1 b5 o6 h y! [) v/ H在main.c中添加下列定义:
5 Z+ W+ |, ]' m2 j9 l, c; h2 P/ c& v6 {3 c+ r
- #include <string.h>
/ q8 k' y8 B1 s; U/ z
Z! ]* Q/ i3 c V- #define RxBuffer_MaxSize 256 //最大接收字节数. U5 Y3 ] [) A l& w' @
- char RxBuffer[RxBuffer_MaxSize]; //接收数据1 k5 F6 `% }- r* p9 k6 M
- uint8_t aRxBuffer; //接收中断缓冲
4 u' Y# ] Y$ { - uint8_t UART1_Rx_Cnt = 0; //接收缓冲计数
复制代码 7 k3 _4 \ d4 v: q0 q
在main()主函数中,调用一次接收中断函数4 A R% e ?& F
$ I& {- o$ y6 N+ G9 W/ C
- /* USER CODE BEGIN 2 */( } E) f' g, x. I
- HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);4 Q; ?9 C2 n+ o. A/ I0 x' p1 E5 N
- /* USER CODE END 2 */
复制代码 4 S# e* ?& m# J* Z# ]
在main.c下方添加中断回调函数
t0 ~" d& T- k9 M/ M- z
, x/ M3 V+ c7 |" ?- /* USER CODE BEGIN 4 */; K+ A0 w* f& z
, F9 t2 z3 H/ Q' a5 p" t- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)4 U4 P, N& U8 s# ?6 C
- {) }- Y& d* |& r5 L$ I/ o
, s3 W0 X, H4 u5 \( s# e- J- UNUSED(huart);
$ Z% z* X( d- {' T: Q* W7 g* z
' m- X' X4 F. n$ m" _- if(UART1_Rx_Cnt >= 255) //溢出判断/ m. N! S4 ^* ?1 C( i
- {' Y2 d. Z/ _! K. v: {: x; o% i1 b
- UART1_Rx_Cnt = 0;
) R' A7 a7 s& h: T9 k - memset(RxBuffer,0x00,sizeof(RxBuffer));' @3 a$ y6 r+ Y
- HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF);
, g6 y' a/ U, r' l2 ]9 Z) D - }1 F1 E) N( R V x+ b
- else
x# _. @0 v; G# V9 ]1 }/ T! t - {0 s; X" \: N; S6 b
- RxBuffer[UART1_Rx_Cnt++] = aRxBuffer; //接收数据转存' v; B% c4 q; V0 I' V* ^" y
- if((RxBuffer[UART1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[UART1_Rx_Cnt-2] == 0x0D)) //判断结束位
2 Y) e7 ?( v; W' q7 V - //此处条件可以改写为if(RxBuffer[UART1_Rx_Cnt-1] == '\n') 因为上面的条件就是表示回车
! I( B$ q: V. @ - {
+ r; x/ [; p4 `( |" S) L - HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, UART1_Rx_Cnt,0xFFFF); //将收到的信息发送出去5 t* f4 ?/ H# J/ Z8 z
- while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
, s0 s% ]. m9 f2 ` `/ O) \/ U - HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_3);//LED指示灯状态翻转
* v6 O/ r* p. T9 I; ^( y - UART1_Rx_Cnt = 0;
- E# f4 Z ?! B# a& V* r2 S0 P - memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
: p: Q! c' g3 C+ | - }$ |. s" b; Q2 t4 a X- b5 m
- }6 ^" p- G% }3 H! y" F
- HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再开启接收中断% {9 u7 R9 ^* P& m$ B0 o. F0 p# g
- }
8 H) F' V8 |+ m4 ]* V& I - /* USER CODE END 4 */5 G( h: c+ q# Z: B) q, ?' s
复制代码 - p! W& W+ G( p7 g; R8 V+ `8 B
现象:
; \" n- V Q0 y! M0 G
2 g7 M6 i: i: V* Z$ U使用USB转TTL(CH340)进行数据发送,XCOM V2.6串口助手进行调试,发送“1”(这里要注意波特率等参数要设置的和之前配置的相同)3 F G$ q' }- A0 W
7 ^; |+ e/ ]- M1 @3 V$ Q2 P0 Y) }
- C: \# L a4 a! f2 s; V
. D; ]; o: L; V5 ?7 S7 F" U在debug模式下,将缓冲计数值UART1_Rx_Cnt加入监视器,串口接收到发来的数据后,计数值+1.
3 {( H; V9 C8 E/ C9 q+ g7 s- y5 s' {- Y! |+ q
0 K' ^# D, d+ i6 l% h$ W9 H5 Q2 q0 Q" q; `2 p! w7 J9 V
同时也可以看到LED灯状态翻转( a( ^6 k$ g0 z) j1 g0 h: g
0 G' t6 @: ~/ w i* V L O; I9 |
3.2 UART发送
/ {6 ]' x4 h! x' P7 D- X. K重新定义printf函数2 X$ Y3 ~# A. K5 Q+ ^
1 L: Z# c$ K. E: z% m* s- A
在 stm32f1xx_hal.c中包含#include <stdio.h>
9 f# W2 ?+ f3 Z' v0 G( \5 L _; I& `% m# h$ w5 J
- #include "stm32f1xx_hal.h", { P: W7 Y8 e5 m
- #include <stdio.h>1 q& {9 {: l$ V
- extern UART_HandleTypeDef huart1; //声明串口
复制代码 在 stm32f1xx_hal.c 中重写fget和fput函数
" j6 [1 [2 r! m4 s1 }
* j. C; M5 m! D1 `$ |- /**' \& _7 H) t& ~) \+ \ N( }
- * 函数功能: 重定向c库函数printf到DEBUG_USARTx
/ J' I& V! R3 L1 D+ P8 O - * 输入参数: 无
# a* d$ z" X2 O9 w# Z - * 返 回 值: 无
* W" O0 r+ \* O1 Z2 @# F& M. } S - * 说 明:无- U6 c; |3 B K+ a: @2 N
- */% |& V) y% v6 J& r7 o5 L( a
- int fputc(int ch, FILE *f)- o; a( G2 K/ H
- {# D1 D7 _0 M `6 V& H. @# I) m
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
, t1 _* `5 ?( z% F% G. { - return ch;5 ^6 {' S k; s4 x+ [( \. }
- }7 X Q1 c/ s+ N
8 i' n u2 C, N) P- /**
7 K. P# p0 ~: r9 ^ - * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
( Q2 L3 ^( V7 `& O - * 输入参数: 无
6 {$ |+ D5 l6 J0 }) \ - * 返 回 值: 无
2 P$ K. [6 N4 n" y7 t- B0 \ - * 说 明:无+ s. K- B: r0 o: B
- */& P3 R8 R, D! S3 U5 D1 L- F
- int fgetc(FILE *f)
~) o( t* |1 _% y; u$ L - {
, W P y! e, p, {# T- ^8 }$ J! q - uint8_t ch = 0;
. @: E5 b( T# D, a - HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
4 x6 d; w7 b( d! q - return ch;
6 C* e; U9 `7 e% } - }
复制代码
- p0 B6 M0 ^6 s3 P在main.c中添加
% y& _2 |0 \$ V# R. H/ L1 V5 }2 b! g+ y9 _$ V
- #define RxBuffer_MaxSize 256
! s7 V9 w5 D4 H3 n - char RxBuffer[RxBuffer_MaxSize];
复制代码- while (1)
# Z$ K3 v/ o7 P' p8 z! F - {
6 t0 z& Z6 z3 v: d - /* USER CODE END WHILE */
1 z9 j8 k0 Z+ U- ] - printf("hello world\r\n");//这里博主只写\n的时候无法换行,百度了一下用\r\n就可以了,估计是编译系统的问题
2 u2 x$ q; X8 p" r, {' a2 l7 R - HAL_Delay(1000);8 N, U6 h# y2 p, Q3 K% l
- /* USER CODE BEGIN 3 */6 v' Z7 {7 _" u8 W; j7 T8 S! F8 L7 G. I
- }
复制代码
+ A0 y- s6 K$ |" R8 w现象:
0 \8 W/ s1 \4 `; _' ^
; P6 A- h. z$ n4 ~/ ?
$ e6 c2 `+ m3 H; q' Q8 c# f4 B; ~/ E/ U5 H# H, @9 @. U
注意:/ z `; Q: h7 J' o" j' e
重定义printf后,必须在target里面勾选上MicroLIB,调用一下这个微型库,不然一直卡在里面。) s Y6 i, L- @5 J
- T) b2 B" X5 T7 m% u( V
; {- k- N4 c5 @# j x: z8 }
: ?( M; f& k$ \ G" Q; D
0 y O& f$ j# V3 }' d" L$ u: M# l( G: E
! [' X: }7 e. d9 Q: I- f
|