
在国内,很少有嵌入式编程的朋友是计算机专业认真毕业的,但都是自动控制和电子相关专业毕业的。这些童鞋有丰富的实践经验,但缺乏理论知识;计算机专业毕业的童鞋很大一部分都去了网游和网页,独立于操作系统。我也不愿意从事嵌入式行业。毕竟这条路并不容易。他们有丰富的理论知识,但缺乏电路等相关知识,在嵌入式学习中很难学到一些具体的知识。
从PC机编程看嵌入式问题是第一步;学会使用嵌入式编程思想,那是第二步;第三步,将PC的思想和嵌入式系统的思想结合起来,应用到实际项目中。
虽然我没有做过行业调查,但是从我看到的和招聘到的情况来看,从事嵌入式行业的工程师要么缺乏理论知识,要么缺乏实践经验。两者兼而有之,实属难得。原因是中国的大学教育。这里不讨论这个问题,以免发生口水战。我想列举几个我实践中的例子。引起人们在做嵌入式系统项目时对一些问题的注意。
第一个问题:我同事开发了一个uC/OS-II下的串口驱动,测试中发现驱动和接口都有。在应用中开发了通信程序,串口驱动提供了查询驱动缓冲区中字符的函数:GetRxBuffCharNum()。在解析包之前,较高层需要接受一定数量的字符。一个同事写的代码,用伪代码表示如下:
bExit=FALSE
做{
if (GetRxBuffCharNum()=30)
bExit=ReadRxBuff(buff,GetRxBuffCharNum());
} while(!脱欧);
这段代码判断当前缓冲区中有超过30个字符,将缓冲区中的所有字符读入缓冲区,直到读取成功。逻辑清晰,思路清晰。但是这个代码不能正常工作。如果是在PC上,肯定没有问题,工作正常。但是在嵌入式系统中真的不知道。同事都很郁闷,不知道为什么。来找我解决问题。当时我看到代码就问他,GetRxBuffCharNum()是怎么实现的?打开一看:
无符号getrxbufcharnum(void)
{
cpu_register寄存器;
无符号数字;
reg=中断_禁用();
num=gRxBuffCharNum
中断使能(寄存器);
return(数字);
}
显然,由于循环中interruput_disable()和interrupt_enable()之间有一个全局临界区,所以gRxBufCharNum的完整性是有保证的。但在外层do {} while()循环中,CPU频繁关闭中断再打开,时间很短。事实上,CPU可能无法正常响应UART的中断。当然这和uart的波特率,硬件缓冲区的大小,CPU的速度有关。我们用的波特率很高,3Mbps左右。Uart起始信号和停止信号占用一位。一个字节需要10个周期。3Mbps的波特率需要大约3.3us来传输一个字节。3.3 US US可以执行多少条CPU指令?100MHz ARM可以执行150条左右的指令。结果,关闭中断需要多长时间?一般ARM关闭中断需要4条以上指令,打开中断也有4条以上指令。接收uart中断的代码实际上超过20条指令。所以这种方式可能会出现通信数据丢失的Bug,反映到系统层面就是通信不稳定。
修改这段代码其实很简单,最简单的方法就是从高层开始修改。即:
bExit=FALSE
做{
德拉尤斯(20);//延时20us,一般用空循环指令实现。
num=GetRxBuffCharNum();
如果(数量=30)
bExit=ReadRxBuff(buff,num);
} while(!脱欧);
这样CPU就有时间执行中断的代码,避免了中断代码执行不及时和频繁关闭中断造成的信息丢失。在嵌入式系统中,大多数RTOS应用程序不是由串口驱动的。自己设计代码的时候,没有充分考虑代码和内核的结合。导致代码中的深层问题。RTOS因其对事件的快速反应而被称为RTOS。事件的快速响应取决于CPU对中断的响应速度。Linux中的驱动程序与内核高度集成,一起在内核状态下运行。虽然RTOS不能复制linux的结构,但有一定的借鉴意义。
从上面的例子可以清楚的看出,嵌入式系统要求开发人员对代码的各个方面都要理解清楚。
第二个例子:一个同事驱动一个14094的串并芯片。串口信号是IO模拟的,因为没有专用硬件。同事随便写了个驱动,结果调试出来3、4天,还是有问题。我再也受不了了,就看了一眼。控制的并行信号有时正常,有时不正常。我看了看代码,使用的伪代码大概是:
for(I=0;I 8;我)
{
SetData((数据I)0x 1);
SetClockHigh();
for(j=0;j 5;j);
SetClockLow();
}
在每个高电平上依次发送bit0到bit7的8位数据。应该是正常的。看不出问题出在哪里?我仔细想了想,看到了14094的数据表,我明白了。原来14094要求时钟的高电平持续10 ns,低电平也要持续10 ns。这个代码有一个高级别的延时,而不是低级别的延时。如果中断插入低电平之间,此代码是正常的。但如果CPU不中断,低级别执行,就不能正常工作。所以有好有坏。
修改也相对简单:
for(I=0;I 8;我)
{
SetData((数据I)0x 1);
SetClockHigh();
for(j=0;j 5;j);
SetClockLow();
for(j=0;j 5;j);
}
这是完全正常的。但这仍然是一个不能很好移植的代码,因为编译器一旦优化,可能会造成这两个延迟循环的丢失。如果丢失,则无法保证高电平和低电平持续10ns的要求,无法正常工作。因此,真正的可移植代码应该使这个循环变成纳秒DelayNs(10);
和Linux一样,上电的时候,先测一下执行nop指令需要多长时间,10ns执行了多少nop指令。只需执行某个nop指令。使用编译器防止优化编译指令或特殊关键字,防止延迟循环被编译器优化。就像海湾合作委员会一样。
_ _ volatile _ _ _ _ _ ASM _ _(' nop;');
从这个例子可以明显看出,写一段好的代码需要大量的知识支持。你说什么?










