前言. z" X6 s7 d+ E6 u. F
这次我用的板子是一个用STM32F103C8T6作为主控芯片的一个数据采集卡,两个LED灯连接的引脚是PB3与PB4,TX与RX引脚分别是PA9和PA10。
1 r. I8 P- w" D0 h7 K! C9 D
4 }3 d8 V# ]. d; K' X
( S8 {! M: |$ F
( s# T# [1 d$ E* ~* W1 T
; C) `8 j) _/ `, Y1 ~
一、配置CubeMX
! T0 H7 F& _; `- i1 g1、新建工程;7 `2 O( J4 c4 N4 @% ]
2、配置时钟源,在RCC里面的HSE配置的是晶振时钟;3 B2 [. v8 a9 T; O" B
3、配置程序烧录引脚SYS为SWD模式;
5 M# x0 w3 h0 l4 c3 T2 Y4、配置GPIO口,配置一个LED灯(我的板子是PB3),起到串口成功接收到数据时的指示作用;
! f. V2 d9 ~4 w; W' ], c i5、配置串口收发引脚7 a P1 W* e1 o* c6 ~- S
( z, J M8 z: H- N4 z8 O5 a此处我们采用的通信方式为UART通信(异步全双工串口通信),PA9作为TX,PA10作为RX使用。直接在可视框图里点击相应的引脚进行配置。
2 q7 z- k+ H) X' o. p5 H& l( d; k4 K
配置到目前,效果图为:
# A% ~) I" @' E9 ^) S t0 N9 T1 a( o! [' M: @9 R
1 o& u8 Y( ~# b, n q/ r
' M3 @3 W( D2 o3 Y" U0 o& _7 b可以发现,TX和RX两个引脚配置完之后是黄色的,代表还没有配置完毕,下面继续进行配置。) I* t) K: {2 V) z8 B1 a( }
/ W( d+ L t7 }& W: {
6、在左边的"Connectivity”里面的USART1模式里选择异步全双工通信模式asynchronous5 C% @6 J: l% t. S4 F3 \/ {
⮚点击USATR1
" d) l. [; ]/ V& K. ]+ x⮚设置MODE为异步通信(Asynchronous)
+ f& M+ P: {" s( t, [⮚基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1,接收和发送都使能7 t2 L9 u" A+ i8 E$ j
⮚GPIO引脚设置 USART1_RX/USART_TX
, q+ J0 u. z% v4 i1 R( d8 X⮚NVIC Settings 一栏使能接收中断$ K0 H0 T \4 B% P$ \6 G& S4 t6 P
. {: t+ m5 V- U- x! A这里简单扩展一下:. {+ G% Z; G' J
STM32F103系列单片机共有5个串口,其中1-3是通用同步/异步串行接口USART(Universal Synchronous/Asynchronous Receiver/Transmitter),4,、5是通用异步串行接口UART(Universal Asynchronous Receiver/Transmitter)。
) P2 r8 G4 a( g; M" ?5 M$ A& P
8 e! h, m1 r" G- V M- Z- b
- T' d) y# I) ]$ l3 [: F: y6 N6 k |
0 `' {8 {% p/ B* X m% \
0 W! J5 L- ?- _; _- h6 ]2 j
* k; V5 G) ?& o3 K# C' N
; O; M) i2 w! S- k$ p9 n
' r" G2 E* O; d' d; y/ e D$ B7、配置时钟树,我还是开到最高的72MHz1 y$ u& `) p6 {
8、进行项目设置,最后生成代码,CubeMX部分就大功告成了# ^* g% L( A/ g0 f
2 q6 T' U5 S8 r# ]
二、HAL库UART相关函数简介
7 g& y4 O w/ ^, W$ g2.1 串口发送/接收函数# D5 i6 d6 n$ Y$ O/ E' `- h$ p5 U* z
- HAL_UART_Transmit();//串口发送数据,使用超时管理机制 # a- C: q5 ~0 V/ d- `/ d
- HAL_UART_Receive();//串口接收数据,使用超时管理机制/ L2 ?: P0 r2 H; r* M+ S
- HAL_UART_Transmit_IT();//串口中断模式发送
, W9 v3 ?; g2 t) N% Z* ~; c - HAL_UART_Receive_IT();//串口中断模式接收
$ x# l/ C( y, U, G9 t. T: b - HAL_UART_Transmit_DMA();//串口DMA模式发送$ F% ^4 m6 r+ l. a; W1 V
- HAL_UART_Transmit_DMA();//串口DMA模式接收
复制代码
/ r. D' t6 X: `' I这六个函数参数基本一致,发送/接收各挑一个常用的函数进行简单介绍:
6 t% _' N% d4 v+ t c2 E/ ~( q5 o3 y5 f w& Y) `- T
串口发送数据:
1 f/ g& s8 F( x2 L- R8 x" q/ z# P0 C( V
- HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
复制代码
5 D! V- }, I3 k: E+ g0 Y- e功能:3 C/ c0 O7 D1 V6 g5 B/ ?
& l% {1 J' s) G' y& p( B: m( j. ~
串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)。6 m# |3 F) u0 x8 t. _9 {: u- q; I
0 Z& c1 K5 Z- x1 { b D参数:% v, Y/ `# ^. k* {1 R
5 F9 X; [! t6 w( h/ I( F; v( O1 K' U* o⮚ UART_HandleTypeDef *huart UART的别名 如 : UART_HandleTypeDef huart1; 别名就是huart19 i0 m0 ]' F6 Z1 [. W0 J/ ]
⮚ *pData 需要发送的数据) F+ v! F' y# i; h: b
⮚ Size 发送的字节数9 p3 H( F' R+ d+ Q1 D) N% s
⮚ Timeout 最大发送时间,发送数据超过该时间退出发送
% d& u' u5 [* y; z" R9 x7 B举例: HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff); //串口发送三个字节数据,最大传输时间0xffff4 @5 H0 r% T7 d& f! ^; h& ?
. D, T: j' S$ k串口接收数据:
4 }% A2 F$ f' L4 }; p- S+ H) K4 ]
: b1 t! {% I+ q5 G ?5 R1 L: s- HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
复制代码
+ S! m# J: `5 a- }! L* F1 |功能:$ L: H* F$ P2 D ?! x5 [
0 }. D2 x2 r, q6 `串口中断接收,以中断方式接收指定长度数据。
% p# m, ?4 N9 v大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。接收到数据时,会触发串口中断。再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)
8 b" r# i7 Y( ?5 C% A& q5 [8 ]' _5 Q% E. I7 B9 B* i1 p( y$ N# F
参数:
) U% G: M m2 H7 O* h5 V% }! P4 b8 N⮚ UART_HandleTypeDef *huart UART的别名, ]" h9 R$ h) l8 f0 o
⮚ *pData 接收到的数据存放地址7 o& v0 M& x$ @8 k. \0 f
⮚ Size 接收的字节数
, Q, f6 G3 s, K! s举例: HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1); //中断接收一个字符,存储到value中
1 _0 A! w5 n1 E9 G5 O$ M
' x! H7 ?' a {0 f5 n" }# D1 d2.2 串口中断函数, W2 N; p+ M) U! W* O* M
- HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数9 Y3 S/ a7 [0 E" ~- a
- HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数
- o$ j" d8 r0 ~# x+ d - HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(不常用)( q* V# d- }) S
- HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数
5 S* i2 E+ X$ ^' r - HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(不常用)1 l& `, _& g" S' d
- HAL_UART_ErrorCallback();//串口接收错误函数
复制代码 , Y6 }7 c: g4 ] {. |5 z
串口接收中断回调函数:, @. b( e* `# D
1 ] N- H* k: U$ j# M- HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
复制代码
) Y7 P; t# n8 f( a, f功能:
/ D- m: V, ^, X, x7 r1 p# p& p3 `: {, y' y$ L% O
HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改。5 n+ E) A b Y: a
, q E" g0 M# w* P. L
参数:
. V$ j2 ]9 z& _2 V& g3 Q5 ]" L! g
4 ]3 [* E# b* \' f( U J⮚ UART_HandleTypeDef *huart UATR的别名& w- S6 a4 m0 E$ h m- z U `
举例: HAL_UART_RxCpltCallback(&huart1){ //用户设定的代码 // } i) P W/ I1 I
5 ?5 x" ~$ T1 w$ c# v2 D' d
串口中断处理函数:- HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
复制代码 ( u: ~- E; V. I
功能:3 r8 D/ W: [! m# ]% _
4 v* D9 H/ j" ^4 @* n6 ~+ \! v对接收到的数据进行判断和处理,判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用。2 @9 z4 F+ a9 L6 I% [
% \4 r. w; @( h- Q( X如果接收数据,则会进行接收中断处理函数& h9 V% q7 w4 V% D5 N6 B; y
' S2 N! T3 [2 j, E, e A! S% P+ n4 [+ h: r
- /* UART in mode Receiver ---------------------------------------------------*/- q" N; d$ |8 t2 A5 Q6 U- @
- if((tmp_flag != RESET) && (tmp_it_source != RESET))
: o% n& t% Y2 [$ {4 i6 Z" B - { $ L/ }6 R8 ]& h
- UART_Receive_IT(huart);6 Z w4 [ T( r4 n5 i' q1 p
- }
复制代码 S' y- L. n( x
如果发送数据,则会进行发送中断处理函数
- S) a j5 n& I" s: V* D3 B8 v! M
3 I' Y3 |) Z5 ]: H' P9 t- /* UART in mode Transmitter ------------------------------------------------*/% f- w' e' i6 A! {8 s) o
- if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))% ~% V6 X2 l9 T
- {
6 w A" y( d* C) Q: b H - UART_Transmit_IT(huart);
0 c# [( c0 W6 m4 D4 ]# s. n - return;( L$ m. V% L4 s: x! T; s
- }
复制代码 , V2 L Y1 |8 @1 j2 q; M, e: n
2.3 串口查询函数+ V p9 M) s/ V; v
- HAL_UART_GetState(); 判断UART的接收是否结束,或者发送数据是否忙碌
复制代码
' B) \1 }9 |, o. A, c9 {3 ~举例:* T9 t- R) x- k/ E+ y
) V6 C5 s) U5 w* `% p# ^3 U, n- while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX) //检测UART发送结束
复制代码
* R" h( J- \# q$ }3 p0 b" E3 s% y三、逻辑代码部分$ P" v+ i9 `2 w" O1 a& W# V
3.1 UART接收中断
3 W; C' s. Z# k) G; ? [8 |' y因为中断接收函数只能触发一次接收中断,所以我们需要在中断回调函数中再调用一次中断接收函数。
$ ]1 m- p( q5 [2 H7 @5 ~* \. r& w0 C* ]- i
具体流程:) t9 F6 R+ Q n' ]
/ U' b( Z$ T* c! d
1、初始化串口
+ ~+ M+ E: h' w" E2 k$ R( g- ?) E6 [/ o1 a, O; o
2、在main中第一次调用接收中断函数
+ @2 j" G0 K4 r# u) J" y' a. ~9 f. h, a* K: n+ m
3、进入接收中断,接收完数据 进入中断回调函数
4 l1 N9 ^ D- P* \5 A
P) }4 @3 v. C8 c' T; v4、修改HAL_UART_RxCpltCallback中断回调函数,处理接收的数据,) c5 [$ _9 s/ I( n. s9 e o: b9 T
- ~9 M% u- c% W$ V8 z5、回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断
1 i9 i/ Y& c) n, H, x1 o! u
# f- {0 e0 F \7 C函数流程:
1 D/ S2 B5 d8 R, B3 h1 e7 Q8 @2 M4 @( V" E) M
①HAL_UART_Receive_IT (中断接收函数) ->8 _0 s% L2 s8 M
②USART2_IRQHandler(void) (中断服务函数) ->) W+ j) y! O4 C" ]' f
③HAL_UART_IRQHandler(UART_HandleTypeDef *huart) (中断处理函数) ->& g- M+ h* |* t7 ?6 z8 y1 p% N
④UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数) ->" b( z0 K0 T# z( H
⑤HAL_UART_RxCpltCallback(huart) (中断回调函数)
' @' H% e2 j( m7 ]5 z2 w2 U1 E( k/ u7 n# G! W
HAL_UART_RxCpltCallback函数就是用户要重写在main.c里的回调函数。- N' r$ [' H6 F
% c% k" O# O% }5 q# |代码实现:1 u: x# K" Y' H
+ u; t# Q D5 g在main.c中添加下列定义:' t( _3 c; m! X9 j% P) v9 L. k
- t/ _6 B1 Y# l u. u2 Y7 V, Z/ D- #include <string.h>- y5 f* _( [8 `. O" S
- ; y4 k* F1 M8 [6 P, r, I
- #define RxBuffer_MaxSize 256 //最大接收字节数+ H1 r$ t K/ B2 [! f
- char RxBuffer[RxBuffer_MaxSize]; //接收数据' D- [# S# B* p, N5 f3 u, m
- uint8_t aRxBuffer; //接收中断缓冲 l6 Y8 {6 ~6 S
- uint8_t UART1_Rx_Cnt = 0; //接收缓冲计数
复制代码
- y) p$ f7 V* S) b) J5 {在main()主函数中,调用一次接收中断函数6 K0 b" i- n/ ]
* w# ~" u; ^) q5 J* u, s
- /* USER CODE BEGIN 2 */$ u/ y( F" d! Y, j7 A2 n
- HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
1 j3 f5 @$ j7 N9 G; T - /* USER CODE END 2 */
复制代码 7 I6 T( P+ J" J
在main.c下方添加中断回调函数; J4 t5 h9 a1 [, O% q' x
, Y( A* X/ q k. X8 N- /* USER CODE BEGIN 4 */
0 H8 ], t u% T5 F( l8 i& ]0 ^
' N- a9 C2 s, B$ A- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
! s; {% q" {/ {3 ~9 z8 ^1 v9 T: a - {9 N. A, p# l" ]
- & a6 m& _/ w0 f" I- L4 R
- UNUSED(huart);; Z" R& I, n( g: Y; I5 I# |
4 }+ D: E, g3 }( L! m+ m- if(UART1_Rx_Cnt >= 255) //溢出判断* F# d) S t' s
- {( B% g" M- i% n- R5 [
- UART1_Rx_Cnt = 0;% U, t1 p, m( G
- memset(RxBuffer,0x00,sizeof(RxBuffer));
1 I: B5 E& g: V& _6 l - HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); ! i* d+ g, [* C9 f$ ^
- }& u' Q! ]) @! O, H+ z3 }
- else
; }2 Y' O- r: `4 b3 ~ - {
3 i* I* y+ j: w* l4 ]5 @! P - RxBuffer[UART1_Rx_Cnt++] = aRxBuffer; //接收数据转存+ |0 N+ H5 K; {/ ^ V
- if((RxBuffer[UART1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[UART1_Rx_Cnt-2] == 0x0D)) //判断结束位
5 T1 m4 k. ?0 q( [/ { e - //此处条件可以改写为if(RxBuffer[UART1_Rx_Cnt-1] == '\n') 因为上面的条件就是表示回车* u3 ^6 X: x% b7 ]7 s+ b4 F1 T r
- {, {5 t1 E5 c) [ k7 {7 J8 X
- HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, UART1_Rx_Cnt,0xFFFF); //将收到的信息发送出去$ [% d4 s( I5 J% a
- while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
3 ^* h1 t# F& w* M: v, R' V; a! e - HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_3);//LED指示灯状态翻转" n! q/ w0 t- K- l6 G
- UART1_Rx_Cnt = 0;, t( [! S- z, f$ s' O2 ?& j7 s
- memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
: t; ]2 i! `* p7 F" \3 m - }
3 o8 c6 @3 Z( b) @+ i# i; N - }6 x K9 q8 l$ P1 |4 a% k- X C
- HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再开启接收中断
& B! L, \7 c. d- d - }
8 G' H0 X: A& R; B - /* USER CODE END 4 */
1 O# E& ]! Y7 p; W
复制代码 8 L2 A8 Z; H: V, U( M) @ n" H$ C
现象:
2 ^7 n) X& ]5 o- N) L0 ^' m0 u' ~; ^: m* e% a5 J$ Q
使用USB转TTL(CH340)进行数据发送,XCOM V2.6串口助手进行调试,发送“1”(这里要注意波特率等参数要设置的和之前配置的相同)5 r! ^' R* V9 g. D) r8 H- _% Y9 x
& A# B% L! T: H# Q( ^, u3 j" X( C) S! m
2 R5 m6 W P, D! f0 J7 }, K' H# n. E: E6 U
在debug模式下,将缓冲计数值UART1_Rx_Cnt加入监视器,串口接收到发来的数据后,计数值+1.
' u" |1 i# f& k0 D+ @9 U" ]: r- f3 K7 |+ u) E9 u) e
1 E2 a/ t; g0 S; B, ?: x
1 @) A; w4 j" D- ]1 y同时也可以看到LED灯状态翻转. K+ e' Z; M$ ^; B# \; E9 ?4 ^5 D
# W6 R% W5 x3 s3.2 UART发送; B' q" e" D0 n7 ]/ u" h3 E
重新定义printf函数& {) c( j5 V e/ D+ G5 p. Y$ \( P% U
$ n3 a G$ d; t0 y8 Y
在 stm32f1xx_hal.c中包含#include <stdio.h>' g4 T8 F; M; X! m+ ]0 K
+ x* o. C+ x5 y1 ^3 l- \
- #include "stm32f1xx_hal.h"4 z; z, u7 `: M* [7 n
- #include <stdio.h>, Z" o' N/ t" X$ r v; {' D
- extern UART_HandleTypeDef huart1; //声明串口
复制代码 在 stm32f1xx_hal.c 中重写fget和fput函数; u! o" ]$ c4 J4 w+ z5 n
p4 M+ Q& J# K- /**
. d0 `3 |$ D2 U5 I- n0 S3 C, w - * 函数功能: 重定向c库函数printf到DEBUG_USARTx4 M0 J, [1 l5 _* \$ l" C j4 _
- * 输入参数: 无% l& i( Y, ` f! A1 _
- * 返 回 值: 无
7 |0 ~1 H& `0 }1 E" Y( w - * 说 明:无6 c: s) w" N j) K- P! \
- */# ]+ ~& p: J ?- D2 I# o
- int fputc(int ch, FILE *f)
, t, }/ f V6 i" i' i - {
1 K$ L! ?: `2 Y' d( ?, `, y - HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);$ t' R+ |0 f& V1 A9 r, Q+ g: P
- return ch;
; a& w$ e8 F8 s { Q0 \ - }
7 n a5 c! g; Y" V1 C% {, M
, v3 a! T! `- x1 K( p% z( D, W- /**/ Q' A" m$ x( v3 M3 v
- * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx3 x H; l9 ^( S' [3 b
- * 输入参数: 无4 H* c; h/ @) o! q* W
- * 返 回 值: 无8 R# C& p' h* ?8 Z5 P
- * 说 明:无- ^, I* ]0 f5 D8 f0 J! i
- */
! [# H8 X& W2 `% X* e - int fgetc(FILE *f)
4 x1 j$ I- Y* p* f8 K& {$ O( c - {
) }2 {" D( r& N' S - uint8_t ch = 0;
+ w r9 U9 A3 L1 Y - HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
5 r( r, B" Z( V6 a - return ch;7 `+ t8 z- Y6 X' ^. }1 W) ?2 H
- }
复制代码
, Y7 K! n1 a) @. \在main.c中添加
) d% {; r2 w. E2 r" A* ~
2 k3 O, K* q4 f# r6 w- #define RxBuffer_MaxSize 256% T! d, w! ]6 u1 i. K
- char RxBuffer[RxBuffer_MaxSize];
复制代码- while (1)
& `' n" M( z5 F; P4 Z9 i. |2 M" N - {6 @' z2 [3 Q( m" r! v; O
- /* USER CODE END WHILE */+ c& h! L& n" f5 S6 Z5 F
- printf("hello world\r\n");//这里博主只写\n的时候无法换行,百度了一下用\r\n就可以了,估计是编译系统的问题/ | y/ Q! m# i \% r. `0 F
- HAL_Delay(1000);% {# T& `' I4 ~# [
- /* USER CODE BEGIN 3 */+ \. R$ K* Q3 w% S
- }
复制代码
1 y% `# m+ }/ J _4 L6 s* W现象:
3 t; R% P3 L* Y; i; n4 H! j* ~& j4 V3 O- H* r8 ?' Z- G/ G
Z7 E" U! t) _; q" R" R5 K# k( i; }
& N$ i! ?9 T& z4 y注意:" X5 z! a$ @! E U" r! M
重定义printf后,必须在target里面勾选上MicroLIB,调用一下这个微型库,不然一直卡在里面。. r* }3 k( I7 S& h
/ y8 ]8 }: g* N M0 U# x
2 ]% S2 ?+ _4 z( ?3 c" M% O9 y
o; L4 i$ F- X/ E5 G1 L& R# P
% {0 ?: W" F7 k7 h. C4 z+ |4 ?. ]7 | m
# s/ |2 u* |/ i6 P2 A: C$ i, I& Z8 Y
|