闲谈嵌入式编程的复杂性

姓名:殷晨阳

转载自:http://mp.weixin.qq.com/s/gV5c13U65ZG7mM58SRH_cg有改动和删节。

【嵌牛导读】:在嵌入式编程中,往往会遇到一些问题,而这些问题经常会是与嵌入式系统本身有某些关联,这要求我们对嵌入式系统本身有着深刻的了解与各个环节知识的支撑来进行解决。本文作者举了一些例子来说明了嵌入式编程中的某些问题,同时也介绍了两种嵌入式系统调试的方法。

【嵌牛鼻子】:嵌入式编程、嵌入式系统调试、JTAG

【嵌牛提问】:在嵌入式编程中,有哪些问题是我们必须注重的?

在实际操作中,如何灵活应用JTAG和printf来进行调试?

【嵌牛正文】:

能从PC机器编程去看嵌入式问题,那是第一步;学会用嵌入式编程思想,那是第二步;用PC的思想和嵌入式的思想结合在一起,应用于实际的项目,那是第三步。很多朋友都是从PC编程转向嵌入式编程的。在中国,嵌入式编程的朋友很少是正儿八经从计算机专业毕业的,都是从自动控制啊,电子相关的专业毕业的。这些童鞋们,实践经验雄厚,但是理论知识缺乏;计算机专业毕业的童鞋很大一部分去弄网游、网页这些独立于操作系统的更高层的应用了。也不太愿意从事嵌入式行业,毕竟这条路不好走。他们理论知识雄厚,但缺乏电路等相关的知识,在嵌入式里学习需要再学习一些具体的知识,比较难走。我想列出我实践中的几个例子。引起大家在嵌入式中做项目时对一些问题的关注。

第一个问题:

同事在uC/OS-II下开发一个串口的驱动程序,驱动和接口在测试中均为发现问题。应用中开发了个通讯程序,串口驱动提供了一个查询驱动缓冲区字符的函数:GetRxBuffCharNum()。 高层需要接受一定数量的字符以后才能对包做解析。一个同事撰写的代码,用伪代码表示如下:

bExit = FALSE;

do {

if (GetRxBuffCharNum() >= 30)

bExit = ReadRxBuff(buff, GetRxBuffCharNum());

} while (!bExit);

这段代码判断当前缓冲区中超过30个字符,就将缓冲区中全部字符读到缓冲区中,直到读取成功为止。逻辑清楚,思路也清楚。但这段代码是不能正常工作。如果是在PC机上,定然是没有任何问题,工作的异常正常。但在嵌入式里真的是不得而知了。同事很郁闷,不知道为什么。来请我解决问题,当时我看到代码,就问了他,GetRxBuffCharNum()是怎么实现的?打开一看:

unsigned GetRxBuffCharNum(void)

{

cpu_register reg;

unsigned num;

reg = interrupt_disable();

num = gRxBuffCharNum;

interrupt_enable(reg);

return (num);

}

很明显,由于在循环中,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,体现在系统层面上,就是通信不稳定。

修改这段代码其实很简单,最简单的办法是从高层修改。即:

bExit = FALSE;

do {

DelayUs(20); //延时20us,一般采用空循环指令实现

num = GetRxBuffCharNum();

if (num >= 30)

bExit = ReadRxBuff(buff, num);

} while (!bExit);

这样,让CPU有时间去执行中断的代码,从而避免了频繁关闭中断造成的中断代码执行不及时,产生的信息丢失。在嵌入式系统里,大部分的RTOS应用都是不带串口驱动。自己设计代码时,没有充分考虑代码与内核的结合。造成代码深层次的问题。RTOS之所以称为RTOS,就是因为对事件的快速响应;事件快速的响应依赖于CPU对中断的响应速度。驱动在Linux这种系统中都是与内核高度整合,一起运行在内核态。RTOS虽然不能抄袭linux这种结构,但有一定的借鉴意义。

从上面的例子可以看清楚,嵌入式需要开发人员对代码的各个环节需要了解清楚。

第二个例子:

同事驱动一个14094串转并的芯片。串行信号是采用IO模拟的,因为没有专用的硬件。同事就随手写了个驱动,结果调试了3、4天,仍旧是有问题。我实在看不下去了,就去看了看,控制的并行信号有时候正常有时候不正常。我看了看代码,用伪代码大概是:

for (i = 0; i < 8; i++)

{

SetData((data >> i) & 0x1);

SetClockHigh();

for (j = 0; j < 5; j++);

SetClockLow();

}

将数据的8个bit在每个高电平从bit0到bit7依次发送出去。应该是正常的啊。看不出问题在哪啊?我仔细想了想,有看了14094的datasheet,明白了。原来,14094要求clock的高电平持续10个ns,低电平也要持续10个ns。这段代码之做了高电平时间的延时,没有做低电平的延时。如果中断插在低电平之间工作,那么这段代码是可以的。但是如果CPU没有中断插在低电平时执行,则是不能正常工作的。所以就时好时坏。

修改也比较简单:

for (i = 0; i < 8; i++)

{

SetData((data >> i) & 0x1);

SetClockHigh();

for (j = 0; j < 5; j++);

SetClockLow();

for (j = 0; j < 5; j++);

}

这样就完全正常了。但是这个还是不能很好移植的一个代码,因为编译器一优化,就有可能造成这两个延时循环的丢失。丢失了,就不能保证高电平低电平持续10ns的要求,也就不能正常工作了。所以,真正的可以移植的代码,应该把这个循环做成一个纳秒级的DelayNs(10);

像Linux一样,上电时,先测量一下,nop指令执行需要多长时间执行,多少个nop指令执行10ns。执行一定的nop指令就可以了。利用编译器防止优化的编译指令或者特殊的关键字,防止延时循环被编译器优化掉。如GCC中的

__volatile__ __asm__("nop;\n");

从这个例子中可以清楚的看到,写好一段好代码,是需要很多知识支撑的。你说呢?

嵌入式系统的调试往往很复杂,可用的手段并不像PC编程那么多,开发成本较PC系统也要大很多。嵌入式系统调试主要手段只有JTAG为代表的单步追踪、printf夹杀大法等。

这两种调试方法在嵌入式中也不尽然全部能解决问题。Jtag需要调试者有一个调试设备(有可能很昂贵),和目标系统相连。使用类似GDB Client等软件登录调试设备,跟踪运行程序。说实话,这个方法对嵌入式来讲是终极的调试办法,也是比较好的调试方法。但仍然有几个不足,当断点过多时,超出硬件的限制,某些低档的CPU不支持更多的断点,就需要JTAG利用软件模拟,或采用软件陷阱(软中断或异常)等办法实现断点。机理比较复杂,简单点说,1.不能进行长时间调试,不太稳定; 2.有可能影响程序的运行时刻的行为,通过时序影响。挂接JTAG系统后,利用硬件实现的断点不会影响系统运行的速度,但是软件实现的断点是必定牺牲一些性能的。可靠性也要打折扣的。当断点太多,而系统又进入临界区域,可能会造成断点不起作用。因为嵌入式实现全局临界区域往往需要关闭中断,有些CPU没有非屏蔽中断,当断点超过一定数量,使用软件断点,而软件断点又需要在中断工作的情况下使用……

特别调试时序问题和高速通信类的代码,JTAG帮助并不大。通信过程往往很快,通信包也是接二连三,才能完成一个完整的动作。如果是高速通讯,断点是无法让程序完成工作的。所以只能使用printf夹杀的办法,printf夹杀办法很好。但是也要注意几个问题:嵌入式系统往往没有屏幕,printf输出是通过串口输出。而串口工作模式有两种,一种是查询,另外一种是中断,或DMA。不管哪种,调试输出的printf只能使用查询的办法输出,千万不要使用中断或DMA的办法。不管是前后台程序也好,还是操作系统也好,都有不方便的时候,也许在全局临界内需要打印(关闭了中断),也许需要在中断里打印(不允许嵌套中断),也许要在一些驱动里打印(很多配合的设备没有初始化,内存分配和中断并不能很好的工作)。在这些情况下,利用Uart中断输出字符是不明智的。

以上这两种办法并不能很好的解决全部的问题,在实际中如果嵌入式系统有一两个LED灯,尝试用IO口将其在特殊的情况下点亮熄灭的办法,也可表示程序的状态。这种办法适合调试中断、临界区域这些问题。点量LED灯需要的时间是非常短的,基本上是一条内存读写命令,如果IO口寄存器是CPU统一编址的话。基本上造成的影响微乎其微。在调试一些复杂的时序的时候,还可以使用空闲的IO口,将其在特殊的情况下拉低,拔高,然后利用数字示波器或者逻辑分析仪抓取再具体分析。特别是分析一段代码的执行频度,执行时间,优化效果等。对整体的性能提升等,有非常大的意义。对于简单的单片机,厂商开发软件都有个时序统计的功能。但对于有cache和MMU的单片机,时序统计并不准,往往不如用示波器测得的准。如果没有示波器利用CPU内部的时间计数器也可以实现时间的统计,需要结合printf使用。

对于普通的应用,提高系统性能是基本出发点。但嵌入式系统应用多处理器并不是一个简单的事情。多处理器的软件设计难度很大,调试也是很大的问题。

如果不采用操作系统处理,采用前后台系统。那么自己还要设计一个通信算法,还要设计一个结果整合系统。这样的系统自己设计很多东西,其中总线的可靠和容错设计至关重要。所以可能的话,利用成熟稳定的操作系统来支持多处理器可以减少不少的开发难度。然而,寻找这样的一个操作系统并非易事。

首先要明确自己的应用,需要线程进程迁移吗?需要处理器平衡吗?对于多处理器,如果不支持线程进程迁移,那也就谈不上处理器任务的动态平衡,不然只能事前指定好线程进程运行于哪个处理器。对于异构型多处理器,线程迁移和进程迁移并没有多大的实际意义。对于追求利益的公司来说,目前还谈不上实用价值。所以,迁移只限于对称处理器。然而,对称处理器也不是什么进程可迁移。对于对称处理器,操作系统封装好底层,让用户开发起来像是对一个CPU再做开发,当然不可能与单个CPU完全一致,但起码减轻了许多难度。

看来,嵌入式系统中的多处理器还是与应用高度的相关。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容