1 实现原理8 a% u& [( i% [8 T0 b
使用串口通信可实现数据帧的收发,完成机器人控制、采集数据的传输等任务。制定串口通信协议并定时发送一帧数据,常见的处理方法是利用空闲中断,但STM32 HAL库好像没有专用的空闲中断,自己实现起来比较麻烦,这里利用非空中断可以实现同样的功能,缺点是效率比较低,稳定性经过测试也还可以,如果有更好的思路欢迎提出。
4 L; S# q( d8 B) B6 {8 s& ^% T
% t" P, _8 _ f/ W4 v) S) r1.1 制定串口通信协议
5 P: ?' S/ U4 A) ? D! l8 \制定串口通信协议时一帧数据的长度和内容可以自己定义,STM32的串口传输的数据类型为uint8_t,注意取值不要超过0-255,下面给出一个电机控制的串口通信协议表做示例
0 |+ e5 N9 u- u* D: Y T$ J$ f2 ]. G4 u9 r2 H3 c. [- U+ D5 K9 ]' w
) C4 j: E! x8 W8 M
! E6 W) B' F) S6 x$ D- X6 b
1.2 分析中间变量" h$ D5 b: E4 t" e% v
创建接收数组g_fUART1_Buf,其长度等于串口通信协议一帧的长度# A2 {8 L. j0 v; M% t; X
创建接收一个字节的暂存变量g_fUART1_Byte,配置一个字节中断一次- W0 w% v2 c b+ b! o% l
创建g_fNew_Pack用于主程序中判断是否接收到完整的一帧数据
- F) S7 j6 H- b8 ~( U创建g_fNew_Pack_Cnt记录成功接收一帧数据的次数,方便调试6 K" K) Z5 @& c8 a9 R
- uint8_t g_fUART1_Byte = 0;
0 r( T' J; u, B+ g! S# B( d - uint8_t g_fUART1_Buf[10] = {0};
4 Q: g# j0 Q0 Y# l3 R3 y
2 ^, V- G+ i4 j7 ]- uint8_t g_fNew_Pack = 0;* O* o: ~- b! g% r J9 J
- uint8_t g_fNew_Pack_Cnt = 0;
复制代码
- Q" W" h: q6 Y1 X1 G
+ N2 B. l4 a0 S2 _) ]1.3 接收数据的处理
4 \6 l* r9 P& k新接收到的字节g_fUART1_Byte写入接收数组g_fUART1_Buf的末尾,每接收一个字节,就将前一个字节左移,保证新接收到的字节一直往数组里填充,当接收的字节超过接收数组的长度后,继续左移,去掉旧数据,填入新数据
- R# a. z: |( q: y' H* K8 o# J5 j8 w0 g$ p0 @' X* u
\9 p3 V3 J2 l" O# o: x' e* z4 |7 a" Z4 ~
2 STM32CubeMX配置
8 M) ?* h9 G* e" G" W! t2.1 SWD调试接口配置
; o. d3 M0 P, B h2 m) ySWD接口用于程序的下载、在线调试
" Z. X/ y( R$ J P# ?# b- ?" C5 _$ W: O4 @+ \9 t- _
+ o8 `0 Y" I2 S9 N
& N2 Y* Z& ^0 D: y2.2 时钟树配置
# R. a( k; `7 R6 Y, q7 v LSTM32G0系列内嵌高精度(±1%)RC振荡时钟,无需外部时钟,所以这里并未使能外部高速时钟,开发板上也没有焊接,直接在时钟树配置选项的HCLK输入64,配置最大的64MHz时钟频率即可。
1 D! e' X9 U S, c2 m' {1 x0 P4 Z
! x" s6 Z5 K+ `$ N' g% e
" |: Q8 J/ R$ B# [* K3 F2.3 UART1串口配置
% z5 a9 r& V: }& L2 b; xUART1用于接收上文制定的串口通信协议,并开启非空中断
; A5 s/ {' q' v2 a0 v- ^. a
/ u' O( s* H% C/ X, I! M+ D/ f0 i
6 K' v$ \ H# u* r, }% a
4 F! x1 F1 s# e" P1 Y
) ]# K& r4 @3 n% L
$ A3 b9 H: e4 j5 O S
# z% i: U; H4 \: J) g5 D
$ j+ m5 |# z. d2.4 生成工程! f& J: d9 W! c6 Z l, y
输入工程名,选择存放路径(不要有中文),选择IDE为MDK-ARM;只生成用到的文件(目的是提高工程编译速度,减少工程占用空间),并生成单独的.c/.h文件,点击生成代码8 |4 V* F h( Q9 S, C3 h! t2 u0 z
3 Z1 @% p5 ]/ V1 s' r0 a- Z8 r. M# u& ]# |- f. F# s8 }
3 c& \; n8 z5 a8 [( j2 f) u. W" o. M3 n( V& m
& {! P# Z8 K. `. c3 e8 x
3 添加用户代码
! s I- O3 R; f8 ?3.1 定义全局变量0 K/ x9 l9 g3 _6 u+ A
- /* USER CODE BEGIN PV */
. g( t! _- X* d9 |4 U3 Q5 t1 ~. r - uint8_t g_fUART1_Byte = 0;
2 V; a" R& L9 l* h, Y& q# q- J - uint8_t g_fUART1_Buf[10] = {0};
* q! S; l! A6 O3 N - , o+ x& `4 @% S, W# F9 `
- uint8_t g_fNew_Pack = 0;) P7 @4 { i) }
- uint8_t g_fNew_Pack_Cnt = 0;8 Z& l( H* t! G; `+ v5 ?2 @
- /* USER CODE END PV */
复制代码 " @. {9 s I$ \' Z: t; _+ F5 Q$ b
: b; Z2 u3 y8 f4 F/ A( j
3.2 UART1中断初始化- I, R v$ k1 b( f- b
- /* USER CODE BEGIN 2 */
0 B, z6 B' |+ p! v - HAL_UART_Receive_IT(&huart1, (uint8_t*)&g_fUART1_Byte, 1); // 启动串口非空中断,第3个参数代表1一个字节中断一次
复制代码
, X8 z2 o! X* F/ e3.3 UART1的printf重定向 V8 ~( O. X1 l" r% G% q: v9 l
- /* USER CODE BEGIN 0 */
+ e% _* x5 x8 {- e$ @ - /* printf重定向 */4 t0 @6 y& ^, Z9 o y
- #include "stdio.h"5 j' L$ ~/ ]* {* j1 u0 T1 S- C1 o
- int fputc(int ch, FILE *f)$ ]5 T: u& n9 l# h5 _
- {1 N" W. @- ?3 [" d/ w) x, f
- uint8_t temp[1] = {ch};
/ H! P: `" l1 K' H7 p8 O - HAL_UART_Transmit(&huart1, temp, 1, 5); // huart1根据需要修改8 T4 [: o; M5 Y4 D
- return ch;5 J" V7 t4 |7 ^) j+ n+ E
- }
复制代码 3 z6 h: y9 I! }# U7 S! s
3.4 UART1回调函数2 W% y. f' {8 h( U
- /* USER CODE BEGIN 4 */
: P1 G$ E; W* I5 }! o" a - // 串口接收数据回调函数
$ L, O% G/ Y7 e5 c - void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) . H9 H# v$ D# [' @0 K# e
- {
5 I1 d( J. N9 J: F6 _& B - if(huart -> Instance == huart1.Instance ) - `! v, k) x7 k0 A8 v, o) w
- {
( c9 b p: D% i; m* N - // 往接收数组里填新数据,即实现原理的1.3
( v- A% p; {5 t5 s - for(int i=0;i<9;i++)) J: _. ]5 u8 z2 q2 v _( s
- {
! @$ g3 m5 S/ @' T) C/ v7 s: L, W v" A - g_fUART1_Buf<i> = g_fUART1_Buf[i+1];
0 B, P7 P9 c6 O4 h - </i>}
. m) L" P& Y6 B5 _6 w/ ~ - g_fUART1_Buf[9] = g_fUART1_Byte;: e, W2 r4 X7 J* k
- if(g_fUART1_Buf[0]==0x5a && g_fUART1_Buf[9]==0xa5) {1 ~' g, K/ o4 k$ ?0 C6 Y
- g_fNew_Pack = 1;// 成功接收一帧数据,刷新标志位
) Y1 I p$ _% s9 \: p( j - }9 S/ C% i( c Z$ p1 K$ d v. x4 f0 R
- HAL_UART_Receive_IT(&huart1, (uint8_t*)&g_fUART1_Byte, 1);
- s; P( Y% z: D# T f - }
" c& U5 n( Z. {2 D' ~0 ?# ]" y - }
复制代码 ! p. r2 d4 d; k; P
% e: o9 z; k: _2 T8 r3.5 接收数据的处理# G5 z+ m5 q- g% I4 k' ~
- /* USER CODE BEGIN WHILE */6 h# m; X5 K1 ]% d0 m
- while (1)- n* c# ?- N' i# F5 F
- {( O4 |; {, C3 N- t2 a3 O
- if(g_fNew_Pack) // 主程序中判断是否成功接收一帧数据9 s5 A% k+ w1 `- j' d
- {5 H) ^; B& C% O' G1 v$ h0 ~
- g_fNew_Pack = 0; // 清除标志位
9 p+ H p% E ^$ |' U - g_fNew_Pack_Cnt++;
, W$ ^, W" m- Q9 n+ ]2 V - printf("g_fNew_Pack_Cnt=%d\r\n",g_fNew_Pack_Cnt);// 输出成功接收一帧数据的次数# m$ K! u& u- A5 b0 H$ h
- for(int i=0;i<10;i++){
5 k7 F2 s; g9 u$ ~ - <span style="font-style: italic;"><span style="font-style: normal;"> printf("%x ",g_fUART1_Buf);// 16进制输出接收到的一帧数据: ]+ q2 T n' g
- }
& B5 R. C$ L# e$ E7 D$ @0 t& l - printf("\r\n");: _0 s5 _+ L! C2 P
- // uart1_handle(); // 串口1接收数据的处理函数,根据需求自己添加( R/ ?8 A5 k' K* f- C
: _- f: N8 M; Z3 [0 b \- }! j2 _4 E( V. P+ E
- $ m/ @, i6 v7 g+ D$ N$ p
- /* USER CODE END WHILE */4 L1 a. l6 k+ i3 I2 D8 x& v
- * k( T6 T5 s' D# t& Q
- /* USER CODE BEGIN 3 */9 _4 y( R3 C8 l+ D: w( C& ^
- }</span></span>
复制代码 : S1 @2 X7 X) D: j8 K5 w+ Q0 J
4 效果演示
" k# I! N$ g6 Y5 ~* g1 @gif中为方便演示,除了帧头帧尾,没有使用上文串口协议定义的内容,用11 22等数据(16进制)代替
1 _6 A& S2 c. I ?
$ J s5 J( q& W2 U1 Z8 u3 u5 B- U! c5 q: }7 P8 \1 ]
5 A1 f0 f$ \6 [6 m3 i; A* U* h% b% r u
3 X; {- J8 g" V7 o. w' q' @& ] |