问题:
3 `0 C. N# b: r8 O该问题由某客户提出,发生在 STM8S105C6T6 STM8S105C6T6 器件上。据其工程师讲述:当他所撰写的程序不使用奇偶校验的时候,程序工作是正常的;但是当他把奇偶校验改成偶检验 EVEN 时,程序无法正常工作;现象为:一、不管上位机发送的数据是不带校验位的,还是带奇偶校验位的,STM8S 都可以正常地接收到数据;二、当奇偶校验位使能后,接收到的数据再返回上位机,显示的数据不一定是正确的。- K! V) v w1 V
4 S$ J& ~: i; L2 y+ {$ p6 N
. j- G( u" x8 O& W7 b9 C# L调研:
; M. W& ?4 Z+ _ k! t7 s检查客户的 UART 程序,UART 初始化程序如下:. f2 | x3 k X/ q/ c% n
- static void UART_Config(void). V) L1 { [5 w3 V8 P# H
- {; J- v. ]# K# N0 ~9 b% J# W
- /* UART2 configured as follow:
1 s( y5 C7 V; t, ?' r - - BaudRate = 9600 baud
9 _* I1 o9 o5 N1 e! J: s9 F4 n' J - - Word Length = 8 Bits( t8 c3 j/ M6 f2 b6 ~! e
- - One Stop Bit
* {& ^5 D' u T" G% A4 \; a3 d - - No parity9 v/ n5 H4 f6 ^! j7 @
- - Receive and transmit enabled, H) b9 ~, P, @ u! x, B3 v( t: y& I
- */
+ o+ {: T4 g5 ?# I - UART2_DeInit();
9 c* `+ E4 q# r0 V4 S" c# f2 q - UART2_Init((uint32_t)9600, UART2_WORDLENGTH_8D, UART2_STOPBITS_1, UART2_PARITY_EVEN,& y( A/ _! A# t4 v3 d9 V' K2 b
- UART2_SYNCMODE_CLOCK_DISABLE, UART2_MODE_TXRX_ENABLE);
" S+ l" o$ {# g: y% Z/ \1 _
, z7 V4 }0 Y# r3 s- /* Enable UART2 */7 f4 V* u o; b+ s* @- r- h
- UART2_Cmd(ENABLE);
/ @5 f# r+ f E5 u0 H5 B - } 5 B# m5 f0 Q9 G3 l; k0 a. _9 b" ]. l
复制代码 - v- r9 r/ {& G7 K- @
主程序可简化为:
5 J8 g+ ~3 x$ b4 G; F2 M. b, Z+ H* H+ r3 p7 k( G
' _9 o2 L5 ~1 {( k9 q/ k- void main(void)
& _1 L! G4 y8 I! I - {- c" K' b+ _+ h
- unsigned char val = 0x00;" f W5 T7 s! V" |1 d% N# H7 D
- /* CLK configuration -----------------------------------------*/
) Y; Q, |& B/ G - CLK_Config();& n8 {6 q7 O; r! f
- /* UART configuration -----------------------------------------*/. } D+ C9 Q0 S* ?
- UART_Config();
) w o6 X! }% R' n' H - ! M: u7 @- }& f9 \ t9 t8 v! u, Q
- while(1)- q9 h# u+ |! B4 B0 k {: D
- {
; k6 K3 t6 @8 r# D$ K - /* Wait the byte is entirely received by UART2 */4 d4 c! {5 h& ]. v4 t1 j
- while (UART2_GetFlagStatus(UART2_FLAG_RXNE) == RESET)& V, v v: j% K3 G9 f
- {
& C0 ~) F* {8 j - }
3 J& V: h/ n5 ~* M# O" G: k6 L - /* Get data *// C- g1 d0 V: j% K
- val = UART2_ReceiveData8();
$ Z3 E/ Q0 E$ o3 {* j1 I8 i - % g7 z. W4 F- K$ i: I$ ~2 Z, |
- /* Wait until end of transmit */: P7 _ S0 R+ ` Z
- while (UART2_GetFlagStatus(UART2_FLAG_TXE) == RESET)/ s- b0 F% I; `- h+ Y- M
- {
- F$ O4 o* H5 W0 `" f - }
1 B' m3 j" z) A8 g* y9 q/ _( |. C - 7 q% i, [- b8 E2 q: R7 Z: ~
- /* Send data */
0 t/ O ~7 R: r" b4 ~' q) w - /* Write one byte in the UART1 Transmit Data Register */
/ ^6 W' E$ h8 e V0 v7 } - UART2_SendData8(val);
i% `3 \5 u* X* I, q$ x# l) G' H - }( _0 y- j+ a9 q( J7 B9 d7 h$ Y4 F
- }
复制代码 0 e8 M$ _) A( h8 ~3 M6 s1 G" d
观察客户的程序,把 UART2_PARITY_EVEN 改回 UART2_PARITY_NO,确实就是正常的 8 个数据位不带奇偶校验的程序。那么为什么修改成带奇偶校验后,就会有客户所描述的问题呢?
$ A1 B8 g) o. [继续使用 UART2_PARITY_EVEN,我们用串口分析工具、示波器或逻辑分析仪来分析一下数据。6 F1 f' F, {" M3 V+ p. |! b9 v! n* {
• 当上位机发送 0x01 时(数据中有 1 个“1”),从串口分析工具可以看到上位机收到回来的数据变为 0x81;观察示波器波形,发现 STM8S 发出来的数据位在 Bit7 确实变为 1;
4 w( E* t2 a T' d, |/ v# E% u• 当上位机发送 0x03 时(数据中有 2 个“1”),从串口分析工具可以看到上位机收到回来的数据为 0x03;观察示波器波形,发现数据倒是正确的,可是校验位“0”并不存在。- P1 u% Z. @9 ]& ^
通过这两个实验,我们可以看到,好像是数据的位数不一致,STM8S 发出来的数据是 7 位的数据,也就是只有 Bit0~Bit6,Bit7 变成了奇偶校验位!
( B# w6 l# U& q/ d3 B: E% l6 D6 W$ _# C/ n7 G9 B
我们来看一下 STM8S 的参考手册是如何描述奇偶校验控制的,我们看一下帧格式的表格:
" i% h/ a9 X" b) K |2 A3 J( ~, V. c" E% Q/ H/ l' P
+ p5 m3 C: E4 S' S; J
2 y) N3 x4 C) c; c我们发现,在 STM8S 中 M 位所定义是帧长度,而不是数据位的长度!也就是说 M 位所定位的长度为“数据位+奇偶校验位”个数的总和。当数据位为 8 位时,不使用奇偶校验的时候,M 的长度为 8 位;而要使用奇偶校验的时候,M 的长度应该为 9 位!, U9 H- m" O* Z; r" D6 Q
( Z7 E& n% L* L& O0 n* U0 {到此,我们应该知道如何去修改程序了,我们将主程序和 UART 的初始化程序分别修改为:
8 m6 E/ P. W+ d8 I6 c' e( W- void main(void)1 i4 n- B% q! q, w/ g( @9 N
- {% H* D" G ~3 l; X
- unsigned char val = 0x00;
% B: b1 X8 |0 V- l3 S" A - /* CLK configuration -----------------------------------------*/
Y" n2 H. V% F2 D! i( X - CLK_Config();
/ I9 N1 y: |* B5 K$ T( R7 B - /* UART configuration -----------------------------------------*/
: M' K/ v/ c) U$ B& c# N - UART_Config();4 V2 c3 D3 N# C" ]
- + V- ?' L6 `; e4 R8 p1 L" k+ V: T) b
- while(1)
5 K2 f/ J5 R/ q5 `2 |1 t/ c( ] - {6 g$ a4 \$ I1 S; X# i* i
- /* Wait the byte is entirely received by UART2 */* s& n/ ? X- \2 h2 M/ z7 h
- while (UART2_GetFlagStatus(UART2_FLAG_RXNE) == RESET)
/ K/ E4 B& K( Y, j, D2 X - {
1 M5 }9 }9 O; r8 X" U - }
" A2 J3 u$ r7 |* g7 Q
% Q; n. P* x2 S4 _0 x' `- /* Get data */' b( W2 v c: H
- val = UART2_ReceiveData9();
8 \/ B$ Y% R& R" X) @
; ?" w; D! \: c8 G. [$ U0 g! c- /* Wait until end of transmit */
* a9 _5 Y- ^3 H& R - while (UART2_GetFlagStatus(UART2_FLAG_TXE) == RESET)
. w1 H% x! O6 l9 W% B - {
6 O+ Y( v) q5 r3 C: D - }5 m" c7 k; q9 X) [* I" @. o
* o$ D& w) v i9 Z6 r- /* Send data */
& A8 Z# R* G6 ]' { - /* Write one byte in the UART1 Transmit Data Reg6 Z7 I6 S, Q6 x1 V# l" _3 `- H' O1 C* I
- CLK_Config();* k S& }0 [5 W& n s
- /* UART configuration -----------------------------------------*/( i% {! U2 `, F2 u: o
- UART_Config();- G' c- C9 H+ S @& T* K) C
- 7 P h4 ^( D8 F( J
- while(1)
1 f3 l, @) ?0 v" p2 l7 W - {1 f4 w7 _/ E; v) `( T
- /* Wait the byte is entirely received by UART2 */
?+ Z) d: J x1 R. g* _2 k - while (UART2_GetFlagStatus(UART2_FLAG_RXNE) == RESET)
+ Z9 H& P Z/ x; [, N8 @+ \ - {4 g5 N5 I! j! Y& z2 |
- }
! B4 \7 @7 o# z* K1 v - / R4 k+ ?7 a% o6 k* ]. t
- /* Get data */
' a4 u& z# _) g3 E6 @7 l- P1 B: ~ - val = UART2_ReceiveData9();3 p. m+ y6 r$ u; \0 M& M
- ( ]" L2 h+ @: e- V( Q
- if(UART2_GetFlagStatus(UART2_FLAG_PE) == RESET), F: S0 i' Q+ ?5 d: P
- {
0 V8 T5 r6 b9 b9 y: i - /* Wait until end of transmit */
7 g1 l& |2 {7 F _. D - while (UART2_GetFlagStatus(UART2_FLAG_TXE) == RESET)
3 O8 K: N' R" U8 i5 B; u% w - {
' P0 g, l$ z, T$ P' g, ~5 {% e - }
8 b1 z5 k* K j: Z5 e - 6 G5 ^7 ~$ T% j; z# X P5 F
- /* Send data */3 w9 D) q! g" r8 \! y" ]' M
- /* Write one byte in the UART1 Transmit Data Register */5 G" n- f5 Z ]+ q x
- UART2_SendData9(val);% `2 g2 y% P4 D# d
- }. @' U( o: Z% p C, ?
- }' t) {0 i" u, o, A |: w3 l
- }
复制代码
4 m5 F$ \2 T& E; q, H编译,再测试看看。我们将上位机端的串口分析软件的奇偶校验位仍然修改为 ODD,上位机发送“0103 07 0F”,结果发现上位机还是可以收到 STM8S 发来的“01 03 07 0F”!这是为什么呢?难道 PE是不起作用的,奇偶校验仍然是假的?当然,我们还是得来查一下为什么 PE 会检测不到呢。
* ?' {1 S- A# R& P, z( k+ ~& c4 z
1 X( x7 H, F: B" Y4 q我们再来看一下 PE 标志位是在什么情况下被清除的,我们在参考手册可以看到:要清除 PE 标志位,软件要按以下操作顺序进行执行:先读取 UART_SR,再读取 UART_DR。于是我们再看一下上面的程序,可以看到,程序中先执行了 UART2_GetFlagStatus(UART2_FLAG_RXNE),这是一个读取UART_SR 的过程,然后再执行 UART2_ReceiveData9(),这是一个读取 UART_DR 的过程,那么,至此,PE 标志即使之前已经置位,那么经过这两个操作后,早已被清除,这个时候再执行$ T/ N8 V5 E3 H0 j. M
if(UART2_GetFlagStatus(UART2_FLAG_PE) == RESET)显然没什么意义。; a' n0 F5 k" c6 o& ~% J
. A6 Q8 R; O% l4 O2 k9 }4 [
- r8 u% A. x6 w7 z所以,我们还得再继续修改一下程序:
P4 g4 _* M! t6 O5 Q/ T+ A- void main(void)
, Q: B# E0 e$ X' { - {- i: k% o0 ^& |. ~
- unsigned char val = 0x00;% O$ z: g* g& A( M% }: M
- /* CLK configuration -----------------------------------------*/
( u I1 T$ \6 K) T) a: t - CLK_Config();
7 n0 p( U& Y- i% b1 u) D. _ - /* UART configuration -----------------------------------------*/
+ v; }' t+ J: A/ c0 S S - UART_Config();- b) d& J3 ^# _3 `
& u; `" }1 J2 e: E- q# b$ Q( I- while(1)
2 u5 D: k. X6 \: W# L, u - {
7 \* V9 j, \. e+ t" D. r - /* Wait the byte is entirely received by UART2 */; }, J2 k: v& E. [9 z& Z9 E' x
- while (UART2_GetFlagStatus(UART2_FLAG_RXNE) == RESET)8 u# Q9 n& i m# e0 e) K
- {8 S$ N6 K5 k* i3 {
- }
+ V" Q L2 g0 S
4 F* I$ ^7 G* l: F- if(UART2_GetFlagStatus(UART2_FLAG_PE) == RESET)- z6 U$ [- i, t, n8 C! ?; p
- {
8 `' \7 y8 Q8 D1 r0 i - /* Get data */
/ Z5 ~; |7 J1 i& p ^3 I! H( q4 ^: |: g - val = UART2_ReceiveData9();& F9 {' u. T) w9 ]* T
- $ i9 W3 K# Y# g. P) ^& s/ M
- /* Wait until end of transmit */9 v/ M5 e- q. a% ]
- while (UART2_GetFlagStatus(UART2_FLAG_TXE) == RESET)% i9 C. N0 W7 c& n$ _2 J* B( S- V* g
- {4 ?) X" A' Z. P
- }5 m9 s% w8 O/ v3 ^
- : n8 `; d7 x9 ~
- /* Send data */7 T h3 n: m9 e' i- J& n+ M
- /* Write one byte in the UART1 Transmit Data Register */
# g: _8 k1 p* n$ c) l' J - UART2_SendData9(val);! f3 B6 |9 t' w6 p: H1 u$ R
- }& ~! q0 B( }0 _% m' [
- else
7 ]1 Y8 e4 }5 S& d- b - {' f' |% f* ~/ z7 u) |# S5 z6 \; Y* x6 _
- /* Get data */
+ O5 W5 M9 Y$ U+ r - val = UART2_ReceiveData9(); // For clear PE; g7 U' U0 e1 s. O2 N% ~% q+ r% u* l
- }- w; o' g) a4 `! U
- }& K) Z) w. W9 p/ A7 ^
- }
复制代码
! q& R2 ^8 P$ A) c& y! m9 n( }再编译,再测试,得到了正确的结果:1 `. y E; Y2 R f
• 将上位机端的串口分析软件的奇偶校验位设置为 ODD,上位机发送“01 03 07 0F”,STM8S发现奇偶校验位不对,没有回传任何数据给上位机;' X" J. e, a" k
• 将上位机端的串口分析软件的奇偶校验位仍然设置为 EVEN,上位机发送“01 03 07 0F”,上位机正确地收到 STM8S 发回来的“01 03 07 0F”。
& j: r p- b S, u* t2 v
7 d$ d5 P; [8 i4 S* x9 c结论:+ J' w% M7 U( k$ f( d+ Q
由于客户对 STM8S 的参考手册没有详细研究,误以为只要在程序中将 UART2_PARITY_NO 改为
: I( e B; n/ F6 G, ~0 E$ x' RUART2_PARITY_EVEN,就可以实现 UART 的奇偶校验功能。0 [6 I. F$ {" Q1 u3 U
3 [( `( Q/ z* h( H7 b处理:
: f1 {6 I/ l2 X: o修改程序,将帧长度相应的都改成 9 位,加入对 PE 标志位的判断,并且经过正确的操作顺序来进行判
3 J8 h6 d5 b( a! k断处理。
+ L! S C; M* O( k3 W. t+ I0 I' x ~" P3 h+ |5 n
建议:. F( @" V y! d1 k
; Q$ u( M5 K8 ]: q) H( }# X当需要使用 UART 的奇偶校验功能时,需要注意以下几个方面:
- p. }" U+ I/ x% ]5 x; P7 O$ a$ t7 f$ \ Z! T. @1 M1 x
1. M 所代表为帧长度,而不是数据位的长度。当使用奇偶校验的时候,帧长度=数据位长度+奇偶校验位长度;. Y9 p6 R% c2 c# u" v
2. 不管数据奇偶校验位是否正确,UART 都会将数据接收回来。要对奇偶校验进行判断的话,必须对 PE 标志位进行检测,PE 标志位由硬件置位,当 PE=1 时,奇偶校验出错,做相应处理;# K0 |$ a- X5 N! ^8 i
3. PE 标志位的清除方式是:先读 UART_SR,再读 UART_DR。所以在程序中一定要注意:在判断RXNE 之后,必须在读取 UART_DR 之前就得先读取 PE,否则 PE 将在读取之前被清除;2 t- N8 V+ b; K* u. H
4. 必须在 RXNE 标志位被置位的情况下才可对 PE 位进行清除。0 {/ c! F- D& _; z; w
$ a$ ]. E+ D3 C& ], [+ ?( g |