1.串口
3 f; @% B, ]$ I% [1 M在串行通信中,一个字符一个字符地传输,每个字符一位一位地传输,并且传输一个字符时,总是以“起始位”开始,以“停止位”结束。在进行传输之前,双方一定要使用同一个波特率设置。波特率就是每秒钟传输的数据位数。
4 b/ w( ?/ Q; w& J常用的两种基本串行通信方式包括同步通信和异步通信。我们通常使用的是异步通信.异步通信规定传输的数据格式由起始位(start bit)、数据位(data bit)、奇偶校验位(parity bit)和停止位(stop bit)组成。
, c) Y; E" ^. l! Z+ F6 n
, I7 x k* v$ e' y
) G' J- p& S8 ^( z( u& c- K4 Y5 Q9 e8 ]2 L
2.重定义printf函数。" M- e; B* ?$ ~( P" V
打开STM32CubeMX新建工程,选择STMF746IGT6芯片,选择外部高速晶振(HSE)。USART1选择为异步通信方式。PA10设置RX接收,PA9设置为TX发送。
' f% W( z- I8 c7 S3 X( q! j
- d. C1 e% y. i" V1 T' f4 U! x0 d$ N- I2 G; J+ {
2 y9 A; n/ }+ I9 ~! L2 k/ b- b配置时钟系统时钟为216MHz,STMF746可以单独配置USART时钟,默认为108Mhz。
I2 Q" ~) Y: j* U6 Z
& ~- q4 ^: j! y; t7 A7 ]/ f/ n( l
+ W# F. G7 f, L- g3 }8 w. X& X# z3 m/ W% R
串口配置设置波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1.其他参数默认。
6 F7 k9 Z$ O/ h! {
0 b2 }% Y% s8 }+ G! M# z: q8 v% } x4 ^0 p
生成报告以及代码,编译程序。在usart.c文件中可看到串口1的初始化函数MX_USART1_UART_Init(void),以及管脚配置函数HAL_UART_MspInit()。
+ ?$ Z0 v( z' y# pC语言中的标准库中所用的标准输出函数,默认的输出设备是显示器,要实现串口或LCD的输出,必须重新定义标准库函数里与输出函数相关的函数。例如:printf输出到串口,需要将fputc里面的输出指向串口(重定向),方法如下:只要自己添加一个int fputc(int ch, FILE *f)函数,能够输出字符就可以了。
( Q! b; c6 ]# X在usart.c文件后面添加如下代码,代码中添加了#ifdef宏定义进行条件编译,如果使用GUNC编译,则PUTCHAR_PROTOTYPE 定义为int __io_putchar(int ch)函数,否则定义为int fputc(int ch, FILE *f)函数。
( K! t4 c! N& R( ~9 Q. X+ H) q2 A# A. y7 P: \- d
- /* USER CODE BEGIN 1 */
$ Z$ v5 B) |/ g' [6 c; |3 X - #ifdef __GNUC__
' |+ Q) {! z0 N4 P3 G) [ - /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf& \ S/ t8 r6 L6 N# ?
- set to 'Yes') calls __io_putchar() */
5 \; I- B5 ^6 ]/ X% c F - #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)3 w& V' a4 w3 z( a
- #else
0 U' g4 W3 o, J7 g3 H+ r - #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)3 y, J( ?6 e/ \6 {' g& r- ]7 d
- #endif /* __GNUC__ */. q5 B6 y: X" \6 g" e
- /**9 Z* \5 {, b0 n, A
- * @brief Retargets the C library printf function to the USART.
' \1 l2 M% M; k V: E) J - * @param None
! N- b! E, E% T' h) D, E# A - * @retval None/ Q7 o' U3 c0 Q+ f9 x$ N( e
- */" X1 K! t1 e/ A Q7 U; Z8 k; `
- PUTCHAR_PROTOTYPE
& T* T" h4 s+ B3 B( W R2 h8 [4 p - {# l( E3 A9 C1 G7 {% v- D7 h t
- /* Place your implementation of fputc here */' W" |/ p+ B$ O/ K: d6 j% o
- /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
( B% W$ _! `/ V4 \ - HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
0 i2 u, E' \# N2 h1 \ - $ A( Y' g2 G8 B; S! M
- return ch;+ \9 G* b$ e) e( o c
- }
6 n. m- ]$ v( m X; X8 S - /* USER CODE END 1 */
复制代码 + t8 L* D; b% m7 i
其中HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);这个语句表示通过串口1发个一个字符。ch为字符的存储地址,0xFFFF为超时时间。在stm32f7xx_hal_uart.c文件中可以找到HAL_UART_Transmit函数。
( D4 K0 ]: [& ]) k8 Y( G5 r$ [: U, Q( X' a/ Z4 B+ E6 p
在main.h中添加头文件
+ w* f6 C; c" X
2 K+ J$ G7 d) C; k- /* USER CODE BEGIN Includes */
9 L5 N. L% E# @- w% N8 z$ h& [ - #include "stdio.h"$ d2 r. {) i2 ^" N2 p
- /* USER CODE END Includes */
复制代码 % `& n' ?" [( A% z
在main.c文件中添加应用函数。; u/ j: p. a7 O4 S
' n+ T( Z0 z4 N% y
- /* USER CODE BEGIN 2 */
& ]4 g( l" M% Z4 p - printf("\n\r UART Printf Example: retarget the C library printf function to the UART\n\r"); K ]! Y, H( p% U
- /* USER CODE END 2 */
- F1 z' x3 [6 X5 n& ^, m9 T - - W! {2 _0 m0 I3 D' W
- /* Infinite loop */
- f( [9 n; T! L- K" Z; ~( ]" f* B - /* USER CODE BEGIN WHILE */8 _& h' j6 J# G( S% m w
- while (1)
5 U7 S% j$ p1 b# H - {0 z6 t' O4 m1 k# l$ m2 l4 t
- /* USER CODE END WHILE */
) B. Y I; Y* F; K' c - ! k% m6 ~# W. b
- /* USER CODE BEGIN 3 */0 o# d8 S4 H* H, p) B3 Q8 x
- printf("\n\r welcome to <a href="http://www.waveshere.com" target="_blank">www.waveshere.com</a> !!!\n\r");
1 B/ N8 V/ o6 N8 a$ K - HAL_Delay(1000);
. i$ Y9 b" M8 r9 A9 a - }% u4 Z- q* k( _6 i
- /* USER CODE END 3 */
复制代码 1 b) W6 a* S% [) K
编译程序并下载到开发板。用USB线连接开发板到电脑,在电脑上打开串口调试助手。选择对应的串口号,设置波特率为115200。按下复位按键会接收到如图信息。
2 N" D# Y+ B0 I: W- a+ {7 Y: J$ |; z% ~9 [( F
: v% z6 {& D5 f$ M1 c6 R& C" p
* F# Y' w& l! N w至此可以使用printf函数实现串口输出: i6 r3 F( m" S
8 N, r# f% N7 o1 ]% g打开stm32f7xx_hal_uart.h头文件,在文件后最后面可以看到有如下操作串口的函数。
, E- e& [) h5 G% ?8 D1 h+ M0 Y/ G" S( N! f0 f$ c9 L$ x
, T9 x. ~/ a7 c: V+ L$ t( y
+ H- P: U8 a @5 D. _! T串口的发送接收函数:1 X% M' d: J, f
HAL_UART_Transmit();串口轮询模式发送,使用超时管理机制。
3 w% q+ i" }& S9 O! Q6 aHAL_UART_Receive();串口轮询模式接收,使用超时管理机制。1 S$ n% T) P' N) O5 x; _' J; P% u9 }
HAL_UART_Transmit_IT();串口中断模式发送,5 w: A6 D1 F: W4 t5 j# |
HAL_UART_Receive_IT();串口中断模式接收$ j/ T& O* r4 B. ^( k
HAL_UART_Transmit_DMA();串口DMA模式发送/ H/ ~. w, f( g/ _1 P5 D1 K
HAL_UART_Receive_DMA();串口DMA模式接收
3 e3 z, J1 G1 }
6 }/ j2 \/ s& H0 `' Z7 k8 H$ ]- n串口相关的中断函数:
1 p) Y" Z) D7 m6 Z* n) MHAL_UART_TxHalfCpltCallback():一半数据(half transfer)发送完成后,通过中断处理函数调用。, ]5 V' W D, c* M
HAL_UART_TxCpltCallback():发送完成后,通过中断处理函数调用。
, O( }6 T6 j, [4 SHAL_UART_RxHalfCpltCallback():一半数据(half transfer)接收完成后,通过中断处理函数调用。
8 w, ~4 R8 T* V) _HAL_UART_RxCpltCallback():接收完成后,通过中断处理函数调用。
; j P+ T0 v. L2 T+ Q JHAL_UART_ErrorCallback():传输过程中出现错误时,通过中断处理函数调用。
' Y" s ]$ h; _* a5 t' {+ N- l/ O0 [
可看到串口发送和就是有三种通信模式:1 r* f% U6 d5 x9 U
第一种是上面用到的轮询的模式。CPU不断查询IO设备,如设备有请求则加以处理。例如CPU不断查询串口是否传输完成,如传输超过则返回超时错误。轮询方式会占用CPU处理时间,效率较低。; x- ^" W& r2 T9 j
第二种就是中断控制方式。当I/O操作完成时,输入输出设备控制器通过中断请求线向处理器发出中断信号,处理器收到中断信号之后,转到中断处理程序,对数据传送工作进行相应的处理。
0 P3 |4 I" F l8 h6 d第三种就是直接内存存取技术(DMA)方式。所谓直接传送,即在内存与IO设备间传送一个数据块的过程中,不需要CPU的任何中间干涉,只需要CPU在过程开始时向设备发出“传送块数据”的命令,然后通过中断来得知过程是否结束和下次操作是否准备就绪。* P! c* r- G6 [* Y, T5 s
$ |$ h d2 A6 V" j0 G3.配置DMA。' p9 Y( W, q1 a, k( _, b# U
直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可供其它操作使用。说白了DMA就是一个搬运工,将数据从一个地方搬到另一个地方而不需要CPU处理。
+ \! l1 M3 {9 r \% m5 M, Y$ {, \4 H' v# e; Q: y4 f4 {; [
作为一个搬运工,要他正常工作必须要确定几个重要的参数。
. Z+ m$ S U6 c1.传输模式:数据从哪里搬到哪里。三种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。
5 E: S# ]+ q U4 o! \1 j& }; ]2.通道选择:就是数据传输的是走那条道路
$ Y3 c3 b' @# g7 k2 x- P$ W3.仲裁器:多个DMA传输是优先级高的优先传输。
5 y/ g3 N3 {$ s! y4.数据长度:每次传输的数据长度,可以一个字节,两个字节(半字),四个字节(字)
) A6 k% X3 |, ~5 j+ M5 i5.指针递增:如果使能了递增模式,则下一次传输的地址将是前一次传输的地址递增 1(对于字节)、2(对于半字)或4(对于字)。" H8 r! p4 J% `+ ~4 |8 k% h& } m
* F0 Y7 _ e' w
回到STM32CubeMX重建工程,在DMA设置栏添加UASART发送TX的DMA。发送选择 DMA2 Stream 7通道,方向从存储器到外设。优先级为低。Mode为Normal,Data Width选择Byte。
3 F+ r3 O `3 D& L1 F, j0 _$ v) K( u, v4 g" W' y* [6 ]
E, X( ?6 q1 n4 v. K
% C4 t/ h! h7 D3 E/ @/ r1 C
& h; t/ _3 R" u5 h* g
其中mode设置可以选择Normal表单次传输,传输一次后终止传输,Circular表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。此处选择单次传输
* `" _7 M/ ?# s' J0 K
5 D- u$ O% s" p& M
, f% _. w3 e% j- a; W1 G3 v4 t& q
串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte。 w2 y( x+ r8 A. g/ b
7 R* n& X- B5 t/ ]3 a' t) j& x5 f, z' X( r: m0 H9 x
4 y+ f, B- d. \, D7 `
另外要注意的一点,必须要开启串口中断。DMA2 stream7中断已默认开启。5 a# A% ~% a7 Y2 ~: W- F
, F# T: g+ M0 g- z
" Q' s; P* M& H1 G6 ^) ^) z/ v. t/ b; M* Z+ R. Z' D
生成报告以及代码,编译程序。在usart.c文件中,可以找到刚才的DMA设置7 o+ f6 Y+ l% g |9 i, {) U
9 C! z; l$ V3 H0 O* r9 M# C1 D
9 ]- n7 A: M; [( q8 ]! ]1 ?
" g2 ]' s/ @# X$ K) U# w在main函数前面添加发送的数据。 I& i; i$ u/ j" F* ?0 M
- /* USER CODE BEGIN PV */6 b6 F# y: }. v+ i5 W! m
- /* Private variables ---------------------------------------------------------*/
3 N9 j; j% {+ A4 U - uint8_t aTxMessage[] = "\r\n**** UART-Hyperterminal communication based on DMA ***\r\n WaveShare Open7XXI-C Board \r\n";
/ ~7 r* K) q) s8 L
7 o# A; f: P5 u- /* USER CODE END PV */
复制代码 - \$ r/ S5 d3 E6 z/ P$ D# s. J) i% a" ~
在main()函数的while(1)循环中添加应用程序,通过DMA将数据发送出去0 X, n" ^& K5 Q! w# |- q
; u$ c) |, N$ M- /* USER CODE BEGIN WHILE */, u) y" e( e! s" H
- while (1)% O, [% q' @; |* f! X1 G/ i
- {1 L3 e* k3 _* ~8 W4 O4 t+ Q
- /* USER CODE END WHILE */8 {/ d3 @, N: K; [; X
- ' c' T B" e# ?# p# V
- /* USER CODE BEGIN 3 */
# k) ?6 N+ w7 C/ ^ - HAL_UART_Transmit_DMA(&huart1, (uint8_t *)aTxMessage, sizeof(aTxMessage));
x. F0 t0 N" r - HAL_Delay(1000);
6 l5 I0 E% L; `. c q - }
, v- s0 y- J6 F! F$ ]( F5 ~+ R q - /* USER CODE END 3 */
复制代码 6 Y* F' L8 E ]
编译程序并下载到开发板。用USB线连接开发板到电脑,在电脑上打开串口调试助手。选择对应的串口号,设置波特率为115200。按下复位按键会接收到如图信息。 d% T$ b( z9 X( H% ]
* C( b6 ?) U( a6 ]/ \6 H# e& H1 I" y- B! x }. I
4 o0 d& @6 m9 ]: @- r至此DMA配置结束
+ U; T5 L/ j9 h" E4 `+ q! Q6 Z0 N) a0 s+ H* N
4.串口中断接收不定长数据: G* Q1 c$ J2 H& F
直接利用stm32的IDLE中断进行接收不定字节数据/ K- s- J$ u4 k! s. ^" L
基本知识:
# W1 ]' j) P2 Z: o: p: eIDLE中断什么时候发生?
1 G4 V P! f9 L4 ^2 @; uIDLE就是串口收到一帧数据后,发生的中断。什么是一帧数据呢?比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。
' J: y4 g3 J# {. F1 S如何判断一帧数据结束,就是我们今天讨论的问题。因为很多项目中都要用到这个,因为只有接收到一帧数据以后,你才可以判断这次收了几个字节和每个字节的内容是否符合协议要求。# Z$ g; Q) |3 Z8 ~ h
看了前面IDLE中断的定义,你就会明白了,一帧数据结束后,就会产生IDLE中断。
- h/ u& K$ m0 uRXNE中断和IDLE中断的区别?
' z8 H* H3 @. z6 J# O" }! x6 p. v当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。: d$ e+ X' I8 ] D
0 ]: l1 p! A* r+ ]' k" G; p7 J
下面来编写函数,在usart.c的最后部分添加用户函数:用以存放一帧数据及相关信息
# ?: z2 Q( {, x" {8 ~. j# a3 {6 H. q4 L+ C$ O) u( E
- /* USER CODE BEGIN 1 */
8 z1 ]: A! i, q' q - void UsartReceive_IDLE(UART_HandleTypeDef *huart) % x8 a v3 r# }1 n; B C8 Z4 H
- { % Y0 T* H2 C, H. h
- uint32_t temp;
! k: j; ]4 v# A! L - : T+ @) b' f0 ~: v# c9 z
- if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET)) //如果收到一帧数据
5 X# I! j8 o5 u) q: e" }+ q7 A - { , Y; M0 [' g* u1 ]; x: G& Y6 A
- __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
, d* J; N, C8 e- F' [6 H" s J+ Y - HAL_UART_DMAStop(&huart1);//暂停DMA, v; C7 L4 F; M+ C5 p
- temp = huart1.hdmarx->Instance->NDTR; . P5 |% u; G* x
- UsartType.RX_Size = RX_LEN - temp;//计算数据长度
+ q/ q5 Q+ T; y5 G& X3 A6 ~ - UsartType.RX_flag=1;//标记已收到一帧数据
" v" u' e* p; s+ \* d3 r - HAL_UART_Receive_DMA(&huart1,UsartType.RX_pData,RX_LEN);//从串口缓存接收数据" K) x) o8 [, c7 U! E, x9 m: x
- }
U1 U( v2 Y7 n. v1 @ - }+ C/ b0 s; a8 X
- /* USER CODE END 1 */
复制代码 * l1 N: K2 {# F5 P* v4 C$ w- p( q: J
在usart.h中添加如下定义# T/ W: T% r' g u( B" t7 ?6 c
& s8 S1 f8 S0 K- T5 p/ M- /* USER CODE BEGIN Private defines */& T3 C% v- c$ F/ [' q6 a
- #define RX_LEN 1024 N# Q" m9 u7 F7 K4 @
- ; F' q# }; O8 m; z: X# P
- typedef struct
+ | \% S, ]8 T2 ^/ r% i - {
8 D& I6 z) _& q/ ` - uint8_t RX_flag:1; //IDLE receive flag
: e g L' o) ]: w0 B& z - uint16_t RX_Size; //receive length4 U W& y4 z8 `- I; ^5 V$ ]4 @+ }
- uint8_t RX_pData[RX_LEN]; //DMA receive buffer
* P9 r; h- F! ]1 w+ N7 J+ j w - }USART_RECEIVETYPE; & f" }# i7 t+ o4 a$ j
- " t9 w. z6 r2 f4 \& M% p* c0 N( q) Z. B
- extern USART_RECEIVETYPE UsartType;! d+ |, o+ H( t3 l1 i
- /* USER CODE END Private defines */
复制代码- /* USER CODE BEGIN Prototypes */
+ g% x, R! l4 m - void UsartReceive_IDLE(UART_HandleTypeDef *huart);" m4 O4 q+ S6 n* d3 o$ `6 O+ x
- /* USER CODE END Prototypes */
复制代码
& L2 |8 z0 B# p: |7 l- o; V在stm32f7xx_it.c的USART1_IRQHandler中添加中断执行函数( r3 r$ ]6 i% `5 V
+ j6 o# L O5 c1 M: E- /* USER CODE BEGIN Includes */
4 v. S, E9 C/ Z& V3 S# F1 j' ^ - #include "usart.h"+ Q! ]( E: h) t
- /* USER CODE END Includes */
复制代码- /**% \3 b% L) u! W' ^5 c) ?
- * @brief This function handles USART1 global interrupt.& _( t& h* Q& _' v4 F
- */' P# d4 `2 W$ a
- void USART1_IRQHandler(void)! {6 ~" v0 j# A; m0 Q" O! e& [
- {- c7 r" ]3 O8 M: f M6 p7 X
- /* USER CODE BEGIN USART1_IRQn 0 */9 T8 `/ C1 g, a
- UsartReceive_IDLE(&huart1);//接收不定长数据并保存
( F. c+ N1 h8 X" W" @5 }+ p - /* USER CODE END USART1_IRQn 0 */, {. o9 B3 L* g! g
- HAL_UART_IRQHandler(&huart1);$ v: c5 N! Q) c
- /* USER CODE BEGIN USART1_IRQn 1 */, A( v7 A; I7 h( v; W6 [1 }6 q1 S
- if(UsartType.RX_flag) // Receive flag8 _9 _2 s) P. P+ t' A
- {
; Z' X% c. G/ b" i7 a - UsartType.RX_flag=0; // clean flag0 J! Y! b4 Y6 m
- printf("%s",UsartType.RX_pData);
9 l' _+ @0 D4 Q- U+ g8 w2 Q - }//判断并回显数据0 Y5 o: P" R' C6 p. O9 g$ a
- /* USER CODE END USART1_IRQn 1 */8 ?3 g( G& w: k: h
- }
复制代码 + J: ?9 x' n" s
* l$ x1 A- \9 V& b% e0 B在main.c中的main()函数中添加中断使能函数
% u4 D& n+ v6 e/ A
" g; P9 j# {$ I/ R8 I* w& r) S* ?- /* USER CODE BEGIN 2 */. U( S F2 P3 O* f2 m
- __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); & E% Q9 I; |* U( H/ G1 c$ F& |
- /* USER CODE END 2 */
复制代码
- {. m) m" w: Q" @4 X3 a N编译程序并下载到开发板。用USB线连接开发板到电脑,在电脑上打开串口调试助手。选择对应的串口号,设置波特率为115200。按下复位按键会接收到如图信息。1 d4 Y, O3 k# M# x6 c9 B- O
5 L! w; T6 D& z- p6 r, ]. c- d: _5 c' X8 X, R9 w% q: P
) H: p( [( E. X. c5 I* l至此串口接收并回显不定长数据完成。
( m2 e% A- `0 Z( ^$ c
$ R1 c; H4 j. [+ {1 ~& E+ y& N' F3 s0 H7 F7 }1 B+ R
. [6 x3 t N2 W' O- }$ D& H; y |