找回密码
 注册
关于网站域名变更的通知
查看: 381|回复: 1
打印 上一主题 下一主题

高手讲解系列!闲谈嵌入式编程的复杂性

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2019-2-27 09:20 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
高手讲解系列!闲谈嵌入式编程的复杂性  e% l: F. O2 v3 e) M) `
: G* v- p  H$ w

) v3 C8 l0 W2 r6 r0 v/ c能从PC机器编程去看嵌入式问题,那是第一步;学会用嵌入式编程思想,那是第二步;用PC的思想和嵌入式的思想结合在一起,应用于实际的项目,那是第三步。很多朋友都是从PC编程转向嵌入式编程的。在中国,嵌入式编程的朋友很少是正儿八经从计算机专业毕业的,都是从自动控制啊,电子相关的专业毕业的。这些童鞋们,实践经验雄厚,但是理论知识缺乏;计算机专业毕业的童鞋很大一部分去弄网游、网页这些独立于操作系统的更高层的应用了。也不太愿意从事嵌入式行业,毕竟这条路不好走。他们理论知识雄厚,但缺乏电路等相关的知识,在嵌入式里学习需要再学习一些具体的知识,比较难走。
; v0 q7 m$ B; [' P2 S
  A* t; `& U- h6 |$ Q0 T虽然没有做过产业调查,但从我所见和所招聘人员,从事嵌入式行业的工程师,要么缺乏理论知识,要么缺乏实践经验。很少两者兼备的。究其原因,还是中国的大学教育的问题。这里不探讨这个问题,避免口水战。我想列出我实践中的几个例子。引起大家在嵌入式中做项目时对一些问题的关注。) ^% z0 N' J& C' v7 G

" D" Q1 m% `' z+ S  |9 S; b6 Y# q+ p第一个问题:
  u7 G. ^& B/ u4 M! u7 D3 k/ N# N! ^$ _0 B/ O+ i0 g
同事在uC/OS-II下开发一个串口的驱动程序,驱动和接口在测试中均为发现问题。应用中开发了个通讯程序,串口驱动提供了一个查询驱动缓冲区字符的函数:GetRxBuffCharNum()。 高层需要接受一定数量的字符以后才能对包做解析。一个同事撰写的代码,用伪代码表示如下:
2 h" p: q* u4 z" E- r& H
% i- J7 P0 L; {. A1 AbExit = FALSE;
4 d4 f& V, q/ v1 p; ]& p7 C2 O" d5 d: N" C# z" B
do {, q: y5 i; Y) D1 D, h+ P, ]* G
8 b: \0 u% ?  [
if (GetRxBuffCharNum() >= 30)# F+ ~, e+ ~; l' a  I
! C0 i  A: i1 _' u: z! C" [
bExit = ReadRxBuff(buff, GetRxBuffCharNum());2 a; h! x; E( B# l8 W7 a. r

. [  t% Q) ]8 T. S9 t} while (!bExit);( \9 Q/ y1 g3 p

! K* B6 R# t3 O8 y这段代码判断当前缓冲区中超过30个字符,就将缓冲区中全部字符读到缓冲区中,直到读取成功为止。逻辑清楚,思路也清楚。但这段代码是不能正常工作。如果是在PC机上,定然是没有任何问题,工作的异常正常。但在嵌入式里真的是不得而知了。同事很郁闷,不知道为什么。来请我解决问题,当时我看到代码,就问了他,GetRxBuffCharNum()是怎么实现的?打开一看:
6 Y  k3 g7 r2 a/ K4 R; k' v) K9 r$ U
unsigned GetRxBuffCharNum(void). p, X2 M; y. H
+ O+ F' b1 r0 Y- j8 q$ v! i6 q7 f
{
* n+ w( u9 t/ d0 M; D, P
$ f# \$ V8 \' E) P1 Acpu_register reg;* K9 z  y/ h  m
; n% R" L. V5 J6 q
unsigned num;
5 [4 [0 x, }' t, T' S3 I7 p: l; U& n4 E# l' x) J( i9 G8 g
reg = interrupt_disable();2 y+ g- A+ {4 V: }1 B

/ f( y6 @$ S( k$ E! e( Unum = gRxBuffCharNum;3 j( E( N3 _4 i& J  u1 e, I
  z, ?4 u, Q( K! M
interrupt_enable(reg);
8 P/ _4 [/ R2 Y5 n3 n6 m  w' |5 C9 Q4 D% \2 r$ G* s
return (num);' }9 ]8 [. A& E6 V& R

: T6 R7 {7 y6 G6 C9 A0 \- t/ s}  v' b2 ]7 o7 n. M
1 R* h' Y& S% D9 w8 y; T$ n. l
很明显,由于在循环中,interruput_disable()和interrupt_enable()之间是个全局临界区域,保证gRxBufCharNum的完整性。但是,由于在外层的do { } while() 循环中,CPU频繁的关闭中断,打开中断,这个时间非常的短。实际上CPU可能不能正常的响应UART的中断。当然这和uart的波特率、硬件缓冲区的大小还有CPU的速度都有关系。我们使用的波特率非常高,大约有3Mbps。uart起始信号和停止信号占一个比特位。一个字节需要消耗10个周期。3Mbps的波特率大约需要3.3us传输一个字节。3.3us能执行多少个CPU指令呢?100MHz的ARM,大约能执行150条指令左右。结果关闭中断的时间是多长呢?一般ARM关闭中断都需要4条以上的指令,打开又有4条以上的指令。接收uart中断的代码实际上是不止20条指令的。所以,这样下来,就有可能出现丢失通信数据的Bug,体现在系统层面上,就是通信不稳定。
5 x) I3 _0 j# g) ]. A7 |5 E1 x5 a& h& R$ q9 v1 Z7 \
修改这段代码其实很简单,最简单的办法是从高层修改。即:
" R0 Z6 O- _- Y0 @5 z8 h
: s( m' }: U- V2 k0 k* _bExit = FALSE;+ P% `- ~. |* A' c; e* U/ u+ h+ d

: A5 n& Q* V9 M8 l# k- w! d6 Qdo {
7 H+ y6 ?- [4 x, Q4 W0 A) t% O
: N4 e* w6 k5 l. d( [3 c; kDelayUs(20); //延时 20us,一般采用空循环指令实现
( ]1 `# P$ A  F4 g9 V3 J# s3 Q- _6 r6 ~
num = GetRxBuffCharNum();* |. ~, l7 V* M9 j( y( T
3 j/ `3 c+ Q0 p; C1 \/ o% \& R
if (num >= 30)) H2 g. ^6 t  k' i+ r1 F% k
( F3 G; ^7 @% ?- |0 L+ N; s6 M3 O
bExit = ReadRxBuff(buff, num);
+ C% }5 P5 s8 S! q2 ?6 m! E  @5 O; T
} while (!bExit);3 P) b* X% Q3 x' S9 I
; [3 O' L5 X0 h2 d$ O
这样,让CPU有时间去执行中断的代码,从而避免了频繁关闭中断造成的中断代码执行不及时,产生的信息丢失。在嵌入式系统里,大部分的RTOS应用都是不带串口驱动。自己设计代码时,没有充分考虑代码与内核的结合。造成代码深层次的问题。RTOS之所以称为RTOS,就是因为对事件的快速响应;事件快速的响应依赖于CPU对中断的响应速度。驱动在Linux这种系统中都是与内核高度整合,一起运行在内核态。RTOS虽然不能抄袭linux这种结构,但有一定的借鉴意义。
/ h; B1 b" M, o  E3 C
  C1 ^# o" W' I2 |3 b5 T9 P从上面的例子可以看清楚,嵌入式需要开发人员对代码的各个环节需要了解清楚。
6 S) J* R) }% \& k0 x& d2 l% c" I- X% y' `$ s7 P8 x
第二个例子:4 v1 I  x) v' J8 m

# T' d1 c1 f1 X2 U' I同事驱动一个14094串转并的芯片。串行信号是采用IO模拟的,因为没有专用的硬件。同事就随手写了个驱动,结果调试了3、4天,仍旧是有问题。我实在看不下去了,就去看了看,控制的并行信号有时候正常有时候不正常。我看了看代码,用伪代码大概是:8 G, H7 x$ z0 ~  Z0 a6 ^

% f4 X# W- w7 S6 N! y/ Pfor (i = 0; i < 8; i++)
4 ~( R! ~0 c- G' R4 R. t! W5 j) S& j/ U9 I% N, D4 o
{  X0 d. a7 B, I, u' g
  Z& O# G; U0 X' j! m
SetData((data >> i) & 0x1);! I  p& a6 w% @. F# z( S2 l

9 r9 m1 I1 S: G/ ^! ^SetClockHigh();9 F- W; p+ f9 d  L; l6 {

- X! [# M6 n$ O$ H! P, h8 Ffor (j = 0; j < 5; j++);
: {& B9 v) M; V! M6 l) s0 D+ E# G3 E
- J0 z' j7 A$ m' |7 YSetClockLow();
; Z5 ~. U  o+ f- R9 f/ J! |0 i
9 _) B+ m9 s1 I6 X8 z. d}
5 a+ _) W' f+ O# ?+ `, ?1 E1 a1 Q3 w' o* Y) w; J
将数据的8个bit在每个高电平从bit0到bit7依次发送出去。应该是正常的啊。看不出问题在哪啊?我仔细想了想,有看了14094的datasheet,明白了。原来,14094要求clock的高电平持续10个ns,低电平也要持续10个ns。这段代码之做了高电平时间的延时,没有做低电平的延时。如果中断插在低电平之间工作,那么这段代码是可以的。但是如果CPU没有中断插在低电平时执行,则是不能正常工作的。所以就时好时坏。
7 B- }, x/ G4 I
) H$ s! u4 ?$ w修改也比较简单:. S$ d9 D( e5 O; j# m
) C+ k+ M/ k. ?' X4 X  p. U( }
for (i = 0; i < 8; i++)
# `' V3 U: x+ V
/ q" O" ~4 p, A2 r' d8 V+ J{
2 n, Z$ I. G9 c. ~4 Y$ S4 @$ T7 [8 c# z( b$ l6 i7 r: v) @; T
SetData((data >> i) & 0x1);# M0 g) y: B# E

7 a* t& S3 O3 [0 Z* K- bSetClockHigh();9 H# X6 Z' Y2 Z( v
/ a2 T: H! d( }4 a( D6 @
for (j = 0; j < 5; j++);* a( g5 x  T2 V/ ?& J
: J' t# {: R) Q# }1 m/ p
SetClockLow();
" p2 u7 l& L& Y+ |9 P7 U
7 T% D$ t" j. D+ cfor (j = 0; j < 5; j++);
/ t4 ~6 x0 n/ h3 y$ p8 q( c/ r1 w2 y. n# S( l' U5 m
}
: Z# @  L9 [+ s8 y% m# [( v6 `3 h
7 F) H& X6 h' ]2 ]  k: x这样就完全正常了。但是这个还是不能很好移植的一个代码,因为编译器一优化,就有可能造成这两个延时循环的丢失。丢失了,就不能保证高电平低电平持续10ns的要求,也就不能正常工作了。所以,真正的可以移植的代码,应该把这个循环做成一个纳秒级的DelayNs(10);
) J7 W# H$ u0 q3 f5 R
; \4 v; C8 g: A  N4 t# f像Linux一样,上电时,先测量一下,nop指令执行需要多长时间执行,多少个nop指令执行10ns。执行一定的nop指令就可以了。利用编译器防止优化的编译指令或者特殊的关键字,防止延时循环被编译器优化掉。如GCC中的
+ _5 F! P/ O7 H$ A8 P; g
! J4 Y# }5 N( [2 z# j5 |' S2 M__volatile__ __asm__("nop;\n");
4 A- E7 |) m8 {) R
4 A2 I) Y: E4 h从这个例子中可以清楚的看到,写好一段好代码,是需要很多知识支撑的。
0 V8 x9 C7 R3 w$ m
  c$ _. ?! x+ n8 f

该用户从未签到

2#
发表于 2019-2-27 10:31 | 只看该作者
说的很不错,谢谢分享
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-6-22 09:52 , Processed in 0.093750 second(s), 23 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表