1 实现原理$ H( m- g; v$ f; u
使用串口通信可实现数据帧的收发,完成机器人控制、采集数据的传输等任务。制定串口通信协议并定时发送一帧数据,常见的处理方法是利用空闲中断,但STM32 HAL库好像没有专用的空闲中断,自己实现起来比较麻烦,这里利用非空中断可以实现同样的功能,缺点是效率比较低,稳定性经过测试也还可以,如果有更好的思路欢迎提出。
7 Q# j6 T( |: p; M8 F% Y) A
% W- \8 l4 `1 h7 Z( l6 M3 ~/ B' j' d1.1 制定串口通信协议! o! f7 I9 x5 O* {* Y) U
制定串口通信协议时一帧数据的长度和内容可以自己定义,STM32的串口传输的数据类型为uint8_t,注意取值不要超过0-255,下面给出一个电机控制的串口通信协议表做示例
2 A) J. v9 a# D0 S/ w! |; X% q u1 U$ e# F; b! Z) T
: E8 i4 A9 N* W O$ {6 N& g2 q6 p& c" F7 Z9 I
1.2 分析中间变量2 [) Y3 m* ]6 F+ F( E. k# Z3 n
创建接收数组g_fUART1_Buf,其长度等于串口通信协议一帧的长度
* |) b a8 Z `0 g# b创建接收一个字节的暂存变量g_fUART1_Byte,配置一个字节中断一次
( ^! Q# z* D" W/ n# Y创建g_fNew_Pack用于主程序中判断是否接收到完整的一帧数据
" Y* H1 y5 ^, I) ]+ I3 `创建g_fNew_Pack_Cnt记录成功接收一帧数据的次数,方便调试
% m @( \3 p1 W( s( x- uint8_t g_fUART1_Byte = 0;
, ^! }7 p- d; @' ~0 Z+ f - uint8_t g_fUART1_Buf[10] = {0};
6 u) L* H' D4 Z: O' e
7 b$ W ], A! ^1 C7 y: q4 x7 }- uint8_t g_fNew_Pack = 0;. X6 \6 d& p4 i6 T. L F4 n
- uint8_t g_fNew_Pack_Cnt = 0;
复制代码
5 H9 T3 ^; d. e
. K9 ^3 @; T3 ?8 J2 g1.3 接收数据的处理4 [1 i; n! n* b/ t$ \
新接收到的字节g_fUART1_Byte写入接收数组g_fUART1_Buf的末尾,每接收一个字节,就将前一个字节左移,保证新接收到的字节一直往数组里填充,当接收的字节超过接收数组的长度后,继续左移,去掉旧数据,填入新数据
3 j+ L% D0 o- j: i! ?! g2 i& {0 ] O' |; R5 N
6 J6 r3 T- |; |; i; W: L( f
- W$ I+ b' {' H3 {
2 STM32CubeMX配置 g7 H" d6 _1 O" |( @ X
2.1 SWD调试接口配置
5 w/ R: G% k, @' [SWD接口用于程序的下载、在线调试1 E, Z: a& G# @2 q4 x2 U- w
2 |9 c. c" {& r! m# F0 \0 r5 n3 q4 B x# I- ]0 x) }6 p7 t! r' ^9 |
" n V4 w N) \# k0 `" l2.2 时钟树配置* _2 p- M' x; P" ^' }* H
STM32G0系列内嵌高精度(±1%)RC振荡时钟,无需外部时钟,所以这里并未使能外部高速时钟,开发板上也没有焊接,直接在时钟树配置选项的HCLK输入64,配置最大的64MHz时钟频率即可。8 G7 c6 ~' Z! I, K" b& [4 ^. i0 H
4 A _) O/ H6 j. v
9 D% Z- n( r, s. i
; T1 q7 Z9 e1 x, J* y2.3 UART1串口配置0 x9 P& N/ W. V0 _ R
UART1用于接收上文制定的串口通信协议,并开启非空中断
, T1 U) M* }0 h' |" }+ q! \
6 i: w7 t8 J3 W; N4 z2 L2 W2 L' v* Q
8 n( ~! h9 K* W$ O% t
- E q2 J+ l8 q, y2 i & t4 [0 j0 T: d9 e, y9 ~) t
- u; o7 p5 j a6 ~9 G; ^
: G8 I, R$ Z& V6 o1 V2 v+ F3 q) R6 I) j) g8 P- V7 n
2.4 生成工程
8 G+ `$ I2 V3 H! d4 x输入工程名,选择存放路径(不要有中文),选择IDE为MDK-ARM;只生成用到的文件(目的是提高工程编译速度,减少工程占用空间),并生成单独的.c/.h文件,点击生成代码
: \( l. h. P9 D/ F) A9 {. ]* E I5 g* y1 n& O
0 Z+ n" P6 f/ Q# H/ D Y- \3 m9 ?8 u
$ z7 m, F! S$ s- l5 D
+ Z! V, ?6 ~" Q0 c7 h5 Q
3 添加用户代码
9 F# E: r3 Z! B" f! m. o8 {5 G3.1 定义全局变量- D5 M8 z. f$ c) D3 f
- /* USER CODE BEGIN PV */
/ f3 n! u8 `& ~) }& ^ - uint8_t g_fUART1_Byte = 0;$ n# ]; T9 t6 L: `# n, l4 {
- uint8_t g_fUART1_Buf[10] = {0};
: U% a% n; z8 V+ X. Z - + [6 K) j T' H, E5 S: ]6 q, m
- uint8_t g_fNew_Pack = 0;
4 C9 L+ x/ N" J9 p6 y - uint8_t g_fNew_Pack_Cnt = 0;2 Q8 U, M1 c( |7 O6 H/ v
- /* USER CODE END PV */
复制代码
; m# v( R. L' B* d6 ~ l6 j- } F9 K! ?$ l
3.2 UART1中断初始化
9 D7 `: S j1 s8 I& r. N5 f- Y- /* USER CODE BEGIN 2 */2 X, r8 j# _% u9 p
- HAL_UART_Receive_IT(&huart1, (uint8_t*)&g_fUART1_Byte, 1); // 启动串口非空中断,第3个参数代表1一个字节中断一次
复制代码
8 N; A: \' m1 E8 f8 t* Q4 y$ G3.3 UART1的printf重定向 u+ \, E! N G: w+ I
- /* USER CODE BEGIN 0 */5 e- I+ z$ g, O% U" X
- /* printf重定向 */+ X% t: j) T: I
- #include "stdio.h"
+ w# t f% y3 h" C( |2 A - int fputc(int ch, FILE *f)' u3 P' Y/ Z& i, Q! s' M: Q
- {
! L6 Q0 ~/ \8 a- ^9 j - uint8_t temp[1] = {ch};
8 M! a* u% O( Y8 A) F! e - HAL_UART_Transmit(&huart1, temp, 1, 5); // huart1根据需要修改
) T$ O9 N. E# k/ A+ B, g' p - return ch;5 n& p" G0 G1 F0 P g" h. T5 W& Q
- }
复制代码 6 J% I; n2 V$ S; A7 o. ^! G7 z
3.4 UART1回调函数
; g ]5 x( a% h. S1 p5 {5 V; o- /* USER CODE BEGIN 4 */; c% {- I7 R9 [! G& [# Q* P$ x
- // 串口接收数据回调函数0 t9 n% U: @. g
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
5 t6 y; M8 X# U. ^1 p - {: W! r4 }1 i5 y
- if(huart -> Instance == huart1.Instance )
. D" \% l' X) C, Z - {
. c7 r- l" J5 V! O% t5 N5 m( F - // 往接收数组里填新数据,即实现原理的1.3
3 A: {% g0 m8 W; J( ~2 Z. t - for(int i=0;i<9;i++)
2 W7 o# Q2 ]! r; B& d# B - {
* k2 c) G4 P$ B d5 I - g_fUART1_Buf<i> = g_fUART1_Buf[i+1];9 h$ W9 [! _+ F4 ]$ ^2 U4 A! B
- </i>}6 ^$ w. }) x: [3 a+ I6 E
- g_fUART1_Buf[9] = g_fUART1_Byte;& B, m8 T& [5 ]
- if(g_fUART1_Buf[0]==0x5a && g_fUART1_Buf[9]==0xa5) {
" a1 }; I* J- U( l9 U0 {" S) s ?* R4 A - g_fNew_Pack = 1;// 成功接收一帧数据,刷新标志位! Q! b- }3 e: ]+ |* A# `: T
- }0 Q# W7 I3 H5 e5 @
- HAL_UART_Receive_IT(&huart1, (uint8_t*)&g_fUART1_Byte, 1);
, P- a( }/ d1 n. z/ z o( O4 z - }
' `" v+ r7 c( o% P/ n l% n; @ - }
复制代码 . g0 F" [+ g; G6 Z
w; u- s5 f, R3.5 接收数据的处理
6 Y& ]! Q q9 {' t- A$ m - /* USER CODE BEGIN WHILE */
$ y' ]+ m2 n: M; f8 W- M - while (1)+ U1 j9 }5 s! i
- {( q3 Q& _, ?2 M. t" j2 K
- if(g_fNew_Pack) // 主程序中判断是否成功接收一帧数据
: r9 ?) h0 t; e# j1 J- p - {
8 f: A. T9 D- x( z - g_fNew_Pack = 0; // 清除标志位
" Y1 G$ r! b, h2 c3 ?! P - g_fNew_Pack_Cnt++;
U7 @" ~( Z- @! E% P# Z - printf("g_fNew_Pack_Cnt=%d\r\n",g_fNew_Pack_Cnt);// 输出成功接收一帧数据的次数
8 D8 e, h! w8 D - for(int i=0;i<10;i++){
! [* o) @- f4 x5 w4 C - <span style="font-style: italic;"><span style="font-style: normal;"> printf("%x ",g_fUART1_Buf);// 16进制输出接收到的一帧数据. e% c( j* a5 L0 x6 y4 Q
- }
7 S; w2 Z# K5 m) t6 _5 W - printf("\r\n");" h7 }. B0 ~3 Q: u# B6 ]
- // uart1_handle(); // 串口1接收数据的处理函数,根据需求自己添加( ~+ E3 A( x! |' E3 W$ d
$ d3 ~( K' f4 C, ]% o# ^- }5 D, o. q' d/ O4 w* c
- * @9 B+ n# T: t- ^: ~
- /* USER CODE END WHILE */
% ]8 H3 U3 [9 p* m. d T+ P
) s$ ^9 V% S! B, ?, y4 Y+ t- /* USER CODE BEGIN 3 */( x6 i" c, F# ~) k2 e# O$ k4 `8 P
- }</span></span>
复制代码 $ [- R" r- h, o& x# i) F
4 效果演示
* ?+ M5 R, }9 n) D3 K6 ^3 V/ Dgif中为方便演示,除了帧头帧尾,没有使用上文串口协议定义的内容,用11 22等数据(16进制)代替
, R% `. s$ q7 }6 h# z6 p( p
1 E9 S4 n, w( |2 v6 x9 X4 Q2 Z8 K0 e: t! Y1 l
- Q3 v. W7 j# d0 @* X" z
0 Q1 F' ^ D3 c9 |) C) U5 i0 d! U; R/ S3 ^0 m' q1 T
|