嵌入式分享合集57

一、两种单片机编程思想

分层思想

    分层的思想,并不是什么神秘的东西,事实上很多做项目的工程师本身自己也会在用。看了不少帖子都发现没有提及这个东西,然而分层结构确是很有用的东西,参透后会有一种恍然大悟的感觉。如果说我不懂LCD怎么驱动,那好办,看一下datasheet,参考一下阿别人的程序,很快就可以做出来。但是如果不懂程序设计的思想的话,会给你做项目的过程中带来很多很多的困惑。

    参考了市面上各种各样的嵌入式书籍,MCS-51,AVR ,ARM 等都有看过,但是没有发现有哪本是介绍设计思想的,就算有也是凤毛麟角。写程序不难,但是程序怎么样才能写的好,写的快,那是需要点经验积累的。结构化模块化的程序设计的思想,是最基本的要求。

    然而这么将这个抽象的概念运用到工程实践当中恩?那需要在做项目的过程中经历磨难,将一些东西总结出来,抽象升华为理论,对经验的积累和技术的传播都大有裨益。所以在下出来献丑一下,总结一些东西。

    就我个人的经验而谈,有两个设计思想是非常重要的。

    一个就是“时间片轮的设计思想”,这个对实际中解决多任务问题非常有用,通常可以用这个东西来判断一个人是单片机学习者,还是一个单片机工程师。这个必须掌握。(下文将介绍)。
     第二个就是“分层屏蔽的设计思想”即分层思想。下面用扫描键盘程序例子作为引子,引出今天说的东西。

问题的提出

    单片机学习板一般为了简单起见,将按键分配的很好,例如整个 4*4 的键盘矩阵分配到 P1 口上面,8条控制线,刚好。这样的话程序也非常好写。只需要简单的:

KEY_DAT= P1;

    端口的数据就读进来了。

    诚然,现实中没有这么好的事情。在实际的项目应用当中,单片机引脚的复用相当厉害,这跟那些所谓的单片机学习板就有很大的差别了。
    另外一个原因,一般设计来说,是“软件配合硬件”的设计流程,简单点说就是,先确定好硬件原理图,硬件布线,最后才是软件的开发,因为硬件修改起来比较麻烦,相对来说软件修改的时候比较好改。这个就是中国传统的阴阳平衡哲学原理。硬件设计和软件设计本来就是鱼和熊掌的关系,两者不可兼得。方便了硬件设计,很可能给写软件带来很大的麻烦。

    反过来说,方便了软件设计,硬件设计也会相当的麻烦。如果硬件设计和软件设计同时方便了,那只有两种可能,一是这个设计方案非常简单,二是设计师已经达到了一个非常高的境界。我们不考虑那么多情况,单纯从常用的实际应用的角度来看问题。
    硬件为了布线的方便,很多时候会可能将IO口分配到不同的端口上面,例如上面说的4*4键盘,8根线分别分配到 P0 P1 P2 P3 上面去了。那么,开发板的那些扫描键盘程序可以去见鬼了。怎么扫按键?我想起了我刚开始学习的时候,分成3段非常相似的程序,一个一个按键的扫描的经历......
    或许有人不甘心,“那些东西我花了很长时间学习的,也用的好好的,怎么能说一句不用就不用?”虽然有点残忍,但是我还是想说“兄弟,接受现实吧,现实是残酷的......”
    不过,人区别于低等动物的差别,是人会创造,在碰到困难的时候会想办法解决,于是我们开始了沉思......
    最后我们引入初中数学学的“映射”的概念来解决问题。基本思想就是,将不同端口的按键映射到相同端口上面。
按键扫描程序如何分成3个层

    最底层的是硬件层,完成端口扫描,20ms延时消抖,将端口的数据映射到一个KEY_DAT寄存器上面,KEY_DAT作为对上层驱动层的一个接口。
    中间的一层是驱动层,驱动层只对 KEY_DAT 寄存器的数值进行操作。简单点说,我们无论底层的硬件是怎么接线的,在驱动层都不需要关心,只需要关心 KEY_DAT 这个寄存器的数值是什么就可以了。这样出来的间接效果就是“屏蔽了底层硬件的差异”,所以驱动层写的程序就可以通用了。
    驱动层的另外一个功能是为了上层提供消息接口。我们用了类似window程序的消息的概念。这里可以提供一些按键消息,例如:按下消息,松开消息,长按键消息,长按键的时候的步进消息,等等。
    应用层属于最上层的程序,这里就是根据项目的不同分别写按键功能程序。它使用的是驱动层提供的消息接口。在应用层写程序的思想就是,我不管下层是怎么工作的,我只关心按键消息。有按键消息来的时候我就执行功能,没有消息来的时候,我就什么也不做。
    下面用一个简单的常用的例子,说明我们这个设计思想的用法。
    秒表调整时间的时候,要求按着某个按键不放,时间能连续的向上增加。这个东西很实用,实际的家电中用途很广泛。
    在看下面的东西之前,大家可以想一下,这东西难吗?相信大家都会很响亮的回答,“不难!!”,然而我再问:“这东西麻烦吗?”我相信很多人肯定会说“很麻烦!!” 这不禁让我想起开始学单片机的时候写这种按键的那程序,乱七八糟的结构。如果不相信的话,可以自己用51写一下哦,那样就更加能体会本文说的分层结构的优越性。

项目要求:

    两个按键,分别分配在P10 和P20,分别是“加”“减”按键,要求长按键的时候实现连续加和连续减的功能。

实战:

    假设按键上拉,没有按键的时候高电平,有按键的时候低电平,另外,为了突出问题,这里没有将延时消抖的程序写上去,在实际项目中应该加上。C语言函数参数的传递多种多样,这里作为例子,用了最简单的全局变量来传递参数,当然你也可以用 unsigned charReadPort(void)返回一个读键结果,甚至还可以 void ReadPort(unsigned char*pt) 用一个指针变量传递地址而达到直接修改变量的目的。方法是多种多样的,这个决定于每个人的程序风格。

 1)开始写硬件层程序,完成映射

#defineKYE_MIN  0X01#defineKEY_PLUS  0X01unsignedchar KeyDat;voidReadPort(void){if (P1 & KEY_PLUS == 0 )  {    KeyDat |= 0x01 ;  }if (P2 & KEY_MIN  == 0 )  {    KeyDat |= 0x02 ;  }}

    C语言应该很容易看懂吧?如果 KEY_PLUS 按下,P10口读到低电平,则 P1 &KEY_PLUS 的结果为 0 (xxxx xxx0 & 0000 0001),满足if 的条件,进入KeyDat |=0x01  是将 KeyDat 的bit0 置一,也就是说,将 KEY_PLUS 映射到 KeyDat 的 bit0

    KEY_MIN是同样的道理映射到 KeyDat 的 bit1,如果 KeyDat 的 bit0 为 1 ,则说明 KEY_PLUS 按下,反则亦然。
    不需要想的很神秘,映射就是这么一回事。如果还有其他按键的话,用同样办法,将他们全部映射到 KeyDat 上面。
2)驱动层程序编写

    如果将 KeyDat想象成 P1 口,那么这个跟学习板那标准的扫描程序不就是一样了吗?对的,这个就是底层映射的目的了。
3)应用层程序编写

    根据消息,硬件层是必须分离出来,然而驱动层和应用层的要求就不那么严格了,事实上一些简单的项目没有必要将这两层分离开来,根据实际应用灵活应对就可以了。

    其实这样写程序是很方便移植的,根据板子的不同而适当的修改一下硬件层那个 ReadPort 函数就完成了,驱动层和应用层很多代码可以不经过修改直接用,很能提高开发效率的。当然这个按键程序会存在一定的问题,特别是遇到常闭按键和点触按键的混合使用的场合。这个留给大家自己去想了,反正问题总是能找到解决办法的,尽管方法有好有坏。

时间片轮设计思想

    先用一个小例子引出今天的主题,想象一下,一个基本的家电控制板,肯定或多或少的会包含 :LED 或者 数码管显示,按键, 继电器或者可控硅的输出 这3部分。数码管需要 10ms到20ms的动态扫描,按键也需要20ms左右的延时消抖,有没有意识到,其实这些时间是同时在进行的。

    回想一下咱们的教科书怎么教 按键 的延时消抖的?没错,死循环,绝对是原地踏步死循环,用指令来计时。这样很自然的引发一个问题,单片机在原地踏步死循环的话,那么其它的工作怎么办?如数码管的动态扫描怎么办?

    唯有等按键扫描之后再进行了,这样出来的效果,数码管肯定会闪烁的,扫描时间过长了,缩短按键消抖时间也不是解决办法,想象如果咱们还有其它很多工作也是同时做的呢?解决办法之一,就是今天的主题,分时扫描的思想。当然不会是唯一的办法,只不过俺一直在用,觉得这个是非常不错的思想,可以解决很多实际问题。大胆妄言一下,分时扫描的思想也是单片机编程最核心的思想了,信不信就由你自己判断了。

核心思想的实现过程

    第一、用RTC中断来计时,RTC的中断时间短一点,我习惯是125us ,为了解红外遥控的码,这个时间是需要的。RTC计时是相当准的,尽量利用。
    第二、在RTC的中断服务程序里面放3个(数量自定)记时器(说白了就是计数器),我的习惯是 2ms 5ms 500ms 这3个是作为基准时间,提供给整个系统来调用的,所以必须准确一点,实际用示波器调一下就OK了,不难。
    第三、在主程序的循环里面放一个专门处理时间的子程序。(注:单片机是不会停的,永远在不断循环的跑,这个跟学校学的貌似有点不同,俺面试的时候被问过这个问题 ….)  将所有的时间处理都放在时间处理子程序里面做,这样是非常方便的,一个单片机系统最起码需要处理 10~20个不同的时间,也需要10~20个计时器了,而且相当多要求同时不同步工作的,如果每个都单独的话是相当的麻烦。
    第四、“程序是跑着来等,而不是站着来等”,这话看来有点玄,一个跟俺一起进去公司的工程师讨论的时候提到的这个问题,俺觉得这个也是分时系统的一个比较重要的思想,所以也这样叫,下面有细说。
    第五、下面用程序来说话,注释尽量详细,可以不用看代码,直接看注释就可以了。

先中断服务程序部分
    每 125us 中断一次,产生几个基准时间。

嵌入式分享合集57_第1张图片

(1) ref_2ms寄存器不断的减1,每次中断减1,一共减 16次,所以这里经过的时间是 125us × 16 = 2ms,这个就是所谓的计时/计数器 了。这样就可以靠一个系统的RTC中断,来实现我们需要的很多个定时时间。
(2)置2ms 计时结束标志,这个是提供给时间处理程序用的,这是一个计时器的框架,下面的5ms计时完全相同。
    这程序还用了一个块的框架,比较方便的,不过跟今天的主题无关,以后郁闷的时候再上来写写这个。上面的程序就是中断服务程序里面的计时器,分别定时 2ms 5ms 500ms,计时完毕溢出是flag_time 标志来记录的,程序通过读这个标志就可以知道定时的时间是否已经到了。

下面看那个统一的时间服务子程序

嵌入式分享合集57_第2张图片

    上面用了按键20ms消抖的计时器作为例子,如果理解之后就可以发现,我们可以完全模仿那个计时器而在下面放很多很多的计时器,则每5ms 进来一下,每个计时器都同时在计数了,谁先计算完毕就先关掉自己,置相应的标志给其它程序调用,而对其它计时器完全没有影响!这样,我们可以在这里放很多个计时器了,一般来说,十来二十个是没有问题的,完全满足一个单片机系统对多个时间的需求了。
    单个计时器的结构很简单,先判断允许计时标志是否进入计时,然后一个专用的寄存器在加1或者减1,加/减相应的数值之后也就是相应的时间到了,关掉计时器,置相应需要用到的标志。
    到这里差不多了,俺们需要的时间都可以出来了,这样做是不是非常方便?咱们再来看看在这段时间里单片机在做了什么东西?只有中断计时够 5ms 或者 500ms ,那个溢出标志才有效,才能进入上面的计时程序,其它时间都是在做其它事情。而且进入上面的计时器的时候,可以看出,并不是在那里死循环,只是单纯的加减一下寄存器就退出了,整个过程耗时极其短,看代码不同吧,5us到 20us左右吧,对主程序的执行没有什么影响。

下面看看具体怎么调用

    最开始谈过的按键的消抖时间处理问题,现在就用上面介绍的办法来看具体怎么解决问题。

嵌入式分享合集57_第3张图片

    大概是这样的:判断什么时候有健,没有的话跳出,有的话开始延时消抖的计时,第二次进来的时候直接由标志位控制过去判断时间时候够。
    同样是等待,这里就是最后一点所说的,咱这是跑着来等,不是站着来等。跟死循环定时比较,在没有定时到20ms 的这段时间里面单片机在做什么?死循环的话,肯定就是在原地等,什么都不做,而看看上面的程序,他只是判断是否定时够,具体的定时在统一的时间子程序里面做,判断没有到时间的话就跳出了,继续跑其它的程序,直到当时间到了,单片机判断出flag_delay,key_flow 符合条件,开始进入按键处理程序了,在这个期间,单片机都在做其它事情,只是一个主循环跑回来判断一次,所以单片机完全有空跑其它的程序,而没有将时间都耗在消抖上面。
主程序循环体

嵌入式分享合集57_第4张图片

    这个就是用到的循环体了,所有功能都做成子程序形式了,需要就挂上去就可以了,比较方便,这样一个总的循环体,单片机就是在不断的执行这个循环体,如果整个程序都采用上面说的分时扫的思想的话,一周循环回来的时间是相当短的,其实是不是跟电脑的思想有点像呢?

    电脑再快也并不是同时处理多个任务,而且每次处理一个,然后非常快的速度来循环处理,让我们感觉上他是在同时处理多个程序那样,我想,我最终想表达的思想也就是这个而已。有这个思想支撑下,单片机的程序变得比较容易上手了,剩下的只是集中精力去用程序来实现我们的思想而已,当然,这里只是说一种可行的办法而已,不是说只有这种办法。

二、嵌入式开发中的C语言编程思想

1 编程风格

    《计算机程序的构造和解释》一书在开篇写到:程序写出来是给人看的,附带能在机器上运行。

1.1 整洁的样式

    使用什么样的编码样式一直都颇具争议性的,比如缩进和大括号的位置。因为编码的样式也会影响程序的可读性,面对一个乱放括号、对齐都不一致的源码,我们很难提起阅读它的兴趣。

    我们总要看别人的程序,如果彼此编码样式相近,读起源码来会觉得比较舒适。但是编码风格的问题是主观的,永远不可能在编码风格上达成统一意见。因此只要你的编码样式整洁、结构清晰就足够了。除此之外,对编码样式再没有其它要求。

    提出匈牙利命名法的程序员、前微软首席架构师Charles Simonyi说:我觉得代码清单带给人的愉快同整洁的家差不多。你一眼就能分辨出家里是杂乱无章还是整洁如新。这也许意义不大。因为光是房子整洁说明不了什么,它仍可能藏污纳垢!

    但是第一印象很重要,它至少反映了程序的某些方面。我敢打赌,我在3米开外就能看出程序拙劣与否。我也许没法保证它很不错,但如果从3米外看起来就很糟,我敢保证这程序写得不用心。如果写得不用心,那它在逻辑上也许就不会优美。

1.2清晰的命名

    变量、函数、宏等等都需要命名,清晰的命名是优秀代码的特点之一。命名的要点之一是名称应能清晰的描述这个对象,以至于一个初级程序员也能不费力的读懂你的代码逻辑。我们写的代码主要给谁看是需要思考的:给自己、给编译器还是给别人看?

    我觉得代码最主要的是给别人看,其次是给自己看。如果没有一个清晰的命名,别人在维护你的程序时很难在整个全貌上看清代码,因为要记住十多个以上的糟糕命名的变量是件非常困难的事;而且一段时间之后你回过头来看自己的代码,很有可能不记得那些糟糕命名的变量是什么意思。

    为对象起一个清晰的名字并不是简单的事情。首先能认识到名称的重要性需要有一个过程,这也许跟谭式C程序教材被大学广泛使用有关:满书的a、b、c、x、y、z变量名是很难在关键的初学阶段给人传达优秀编程思想的;其次如何恰当的为对象命名也很有挑战性,要准确、无歧义、不罗嗦,要对英文有一定水平,所有这些都要满足时,就会变得很困难;此外,命名还需要考虑整体一致性,在同一个项目中要有统一的风格,坚持这种风格也并不容易。

    关于如何命名,Charles Simonyi说:面对一个具备某些属性的结构,不要随随便便地取个名字,然后让所有人去琢磨名字和属性之间有什么关联,你应该把属性本身,用作结构的名字。

1.3恰当的注释

    注释向来也是争议之一,不加注释和过多的注释我都是反对的。不加注释的代码显然是很糟糕的,但过多的注释也会妨碍程序的可读性,由于注释可能存在的歧义,有可能会误解程序真实意图,此外,过多的注释会增加程序员不必要的时间。如果你的编码样式整洁、命名又很清晰,那么,你的代码可读性不会差到哪去,而注释的本意就是为了便于理解程序。

    这里建议使用良好的编码样式和清晰的命名来减少注释,对模块、函数、变量、数据结构、算法和关键代码做注释,应重视注释的质量而不是数量。如果你需要一大段注释才能说清楚程序做什么,那么你应该注意了:是否是因为程序变量命名不够清晰,或者代码逻辑过于混乱,这个时候你应该考虑的可能就不是注释,而是如何精简这个程序了。

2 数据结构

    数据结构是程序设计的基础。在设计程序之前,应该先考虑好所需要的数据结构。

前微软首席架构师Charles Simonyi:编程的第一步是想象。就是要在脑海中对来龙去脉有极为清晰的把握。在这个初始阶段,我会使用纸和铅笔。我只是信手涂鸦,并不写代码。

    我也许会画些方框或箭头,但基本上只是涂鸦,因为真正的想法在我脑海里。我喜欢想象那些有待维护的结构,那些结构代表着我想编码的真实世界。一旦这个结构考虑得相当严谨和明确,我便开始写代码。

    我会坐到终端前,或者换在以前的话,就会拿张白纸,开始写代码。这相当容易。我只要把头脑中的想法变换成代码写下来,我知道结果应该是什么样的。大部分代码会水到渠成,不过我维护的那些数据结构才是关键。我会先想好数据结构,并在整个编码过程中将它们牢记于心。

    开发过以太网和操作系统SDS 940的Butler Lampson:(程序员)最重要的素质是能够把问题的解决方案组织成容易操控的结构。

    开发CP/M操作系统的Gary.A:如果不能确认数据结构是正确的,我是决不会开始编码的。我会先画数据结构,然后花很长时间思考数据结构。在确定数据结构之后我就开始写一些小段的代码,并不断地改善和监测。在编码过程中进行测试可以确保所做的修改是局部的,并且如果有什么问题的话,能够马上发现。

    微软创始人比尔**·**盖茨:编写程序最重要的部分是设计数据结构。接下来重要的部分是分解各种代码块。

    编写世界上第一个电子表格软件的Dan Bricklin:在我看来,写程序最重要的部分是设计数据结构,此外,你还必须知道人机界面会是什么样的。

    我们举个例子来说明。在介绍防御性编程的时候,提到公司使用的LCD显示屏抗干扰能力一般,为了提高LCD的稳定性,需要定期读出LCD内部的关键寄存器值,然后跟存在Flash中的初始值相比较。需要读出的LCD寄存器有十多个,从每个寄存器读出的值也不尽相同,从1个到8个字节都有可能。如果不考虑数据结构,编写出的程序将会很冗长。嵌入式分享合集57_第5张图片

     我们分析这个过程,发现能提取出很多相同的元素,比如每次读LCD寄存器都需要该寄存器的命令号,都会经过读寄存器、判断值是否相同、处理异常情况这一过程。所以我们可以提取一些相同的元素,组织成数据结构,用统一的方法去处理这些数据,将数据与处理过程分开来。

    我们可以先提取相同的元素,将之组织成数据结构:

嵌入式分享合集57_第6张图片

    这里lcd_command表示的是LCD寄存器命令号;lcd_get_value是一个数组,表示寄存器要初始化的值,这是因为对于一个LCD寄存器,可能要初始化多个字节,这是硬件特性决定的;lcd_value_num是指一个寄存器要多少个字节的初值,这是因为每一个寄存器的初值数目是不同的,我们用同一个方法处理数据时,是需要这个信息的。

    就本例而言,我们将要处理的数据都是事先固定的,所以定义好数据结构后,我们可以将这些数据组织成表格:

 /*LCD部分寄存器设置值列表*/   lcd_redu_list_struct const lcd_redu_list_str[]= {   {SSD1963_Get_Address_Mode,{0x20}                                   ,1}, /*1*/    {SSD1963_Get_Pll_Mn      ,{0x3b,0x02,0x04}                         ,3}, /*2*/    {SSD1963_Get_Pll_Status  ,{0x04}                                   ,1}, /*3*     {SSD1963_Get_Lcd_Mode    ,{0x24,0x20,0x01,0xdf,0x01,0x0f,0x00}     ,7}, /*4*/    {SSD1963_Get_Hori_Period ,{0x02,0x0c,0x00,0x2a,0x07,0x00,0x00,0x00},8}, /*5*/    {SSD1963_Get_Vert_Period ,{0x01,0x1d,0x00,0x0b,0x09,0x00,0x00}     ,7}, /*6*/    {SSD1963_Get_Power_Mode  ,{0x1c}                                   ,1}, /*7*/    {SSD1963_Get_Display_Mode,{0x03}                                   ,1}, /*8*/    {SSD1963_Get_Gpio_Conf   ,{0x0F,0x01}                              ,2}, /*9*/    {SSD1963_Get_Lshift_Freq ,{0x00,0xb8}                              ,2}, /*10*  };

    至此,我们就可以用一个处理过程来完成数十个LCD寄存器的读取、判断和异常处理了:

 /**  * lcd 显示冗余  * 每隔一段时间调用该程序一次  */   void lcd_redu(void)  
     uint8_t  tmp[8];     uint32_t i,j;     uint32_t lcd_init_flag;     lcd_init_flag =0;     for(i=0;i     {         LCD_SendCommand(lcd_redu_list_str[i].lcd_command);         uyDelay(10);         for(j=0;j         {             tmp[j]=LCD_ReadData();             if(tmp[j]!=lcd_redu_list_str[i].lcd_get_value[j])             {                 lcd_init_flag=0x55;                 //一些调试语句,打印出错的具体信息                 goto handle_lcd_init;             }         }     }     handle_lcd_init:     if(lcd_init_flag==0x55)     {         //重新初始化LCD           //一些必要的恢复措施       } }

    通过合理的数据结构,我们可以将数据和处理过程分开,LCD冗余判断过程可以用很简洁的代码来实现。更重要的是,将数据和处理过程分开更有利于代码的维护。

    比如,通过实验发现,我们还需要增加一个LCD寄存器的值进行判断,这时候只需要将新增加的寄存器信息按照数据结构格式,放到LCD寄存器设置值列表中的任意位置即可,不用增加任何处理代码即可实现!这仅仅是数据结构的优势之一,使用数据结构还能简化编程,使复杂过程变的简单,这个只有实际编程后才会有更深的理解。

3 阅读书目

    每年都有亿万计的C程序运行在单片机、ARM7、Cortex-M3这些微处理器上,但在这些处理器上如何编写优质高效的C程序,还要在看看相关书籍。 whaosoft aiot http://143ai.com 

3.1关于C语言特性

  • Stephen Prata 著 云巅工作室 译 《C Primer Plus(第五版)中文版》

  • Andrew Koenig 著 高巍 译 《C陷阱与缺陷》

  • Peter Van Der Linden 著 徐波 译 《C专家编程》

  • 陈正冲 编著 《C语言深度解剖》

3.2关于编译器

  • 杜春雷 编著 《ARM体系结构与编程》

  • Keil MDK 编译器帮助手册

3.3关于防御性编程

  • MISRA-C-:2004 Guidelines for the use of the C language in criticalsystems

  • Robert C.Seacord 著 徐波 译 《C安全编码标准》

3.4关于编程思想

  • Pete Goodliffe 著 韩江、陈玉 译 《编程匠艺---编写卓越的代码》

  • Susan Lammers 著 李琳骁、吴咏炜、张菁《编程大师访谈录》

三、了解电容器

电容器是怎么一回事 ? 它是如何装电的,又是如何放电的?怎么同法拉弟扯上关系了?

电容器是这么一回事......

它是一种装电的容器,是一种容纳电荷的器件。

嵌入式分享合集57_第7张图片 

任何两个彼此绝缘且相隔很近的导体(包括导线)间都构成一个电容器

嵌入式分享合集57_第8张图片

绝缘介质不同,导体间距不同,其装电的本领不同的,这个我们后面再详细说。 

我们理解一下

电容器是如何装电又如何放电?

电容器充电示意图

嵌入式分享合集57_第9张图片

电容器放电示意图

嵌入式分享合集57_第10张图片 

电容器装电的本领 — 静电容量

水桶装水的本领用容积表示,基本单位:立方米.

电容器装电的本领用静电容量表示,基本单位:法拉(F).

1法拉的意思是给1V的电压,这个电容器能装1库仑(6.25×1018个电子)电荷!

在电容器这个领域1法拉太大了……!皮法、纳法和微法才是常用的单位。

1法拉(F)=103毫法(mF)=106微法(uF)=109纳法(nF)=1012皮法(pF)

"为什么用法拉做容量的单位?"

童鞋们总是很认真!

用“法拉”做容量的单位,是让世人缅怀那个名叫“法拉弟”的牛人在电学上无与伦比的牛逼贡献!

让我们看看他有多牛逼......

嵌入式分享合集57_第11张图片 

他的全名叫迈克尔·法拉第——Michael Faraday

他幼年时没有受过正规教育,因家里贫穷只读了两年小学.

牛逼的人都早早退学创业去了!


他22岁开始有幸做了当时科技大咖戴维的仆人,帮老师擦鞋,做实验,跟着这个牛逼的大师在欧洲各国混江湖!

嵌入式分享合集57_第12张图片

有个牛逼的导师是你是否能牛逼的前提!

他27岁开始在大师戴维的指导下,做了一系列不仅仅是擦鞋的事儿,其中在研究合金钢时首创了金相分析方法,今天科技研究合金都用这个方法….

嵌入式分享合集57_第13张图片

牛逼的人干啥都像样!

他30岁时,干了一件他自己觉得很牛逼的事,发明一个只要有电通过相关机件就会转动的装置,人类历史上第一台电动机,,装置简陋,但它却是今天世界上使用的所有电动机的祖先。

但由于他没有先向当时的大牛逼导师戴维汇报就自行发表成果,大牛逼导师戴维表示很生气.他被认为不乖被打屁屁了,各门各派向他发出嘘声……

即使你已是个牛逼,但有大牛逼在场时,低调点!

在冷眼、诽谤甚至污辱中郁闷了很多年……, 他只能听服从安排去玩玻璃….,直到大牛逼戴维去世,才又做他喜欢做的事。

他40岁时,干了一件轰动江湖连当时各门各派的大咖们都觉得牛逼的大事,他用实验揭开了电磁感应定律,发明了圆盘发电机。

嵌入式分享合集57_第14张图片

结构虽然简单,但它却是人类创造出的第一个发电机,现代世界上产生电力的发电机就是从它开始的。

……..他还有很多牛逼的事儿,如最先提出电场概念和电场线概念的。在电化学方面精心试验,总结了两个电解定律,这两个定律均以他的名字命名,构成了电化学的基础。他将化学中的许多重要术语给予了通俗的名称,如阳极、阴极、电极、离子等…….

法位第的劳动成果如同太阳般无私而实在地给人类带来光明动力!

是从法拉第的发明成果开始,才有我们人类社会今天五彩缤纷的世界!

法拉第的贡献惠及每个人,把人类文明提高到空前高度,把文明进程提前几百年……!

嵌入式分享合集57_第15张图片

用“法拉”做容量的单位,我们就是这样率真地缅怀这个名叫“法拉弟”的牛人!

关于电容器装电的本领——静电容量的大小可以通过公式C=Q/U进行测算,当然现在有很多专用的仪器可以直接测试出来了。

关键是,电容器装电的本领——静电容量,由电极板正对面积、电极板间距和两电极板间的介质种类三决定的,公式关系如下:

嵌入式分享合集57_第16张图片

即是说,电容器的绝缘介质不同,电极板间距不同,电板板正对面积不同其装电的本领不同的.

还有一点很重要的是,电容器的绝缘介质当存在某方面的优点时总会在另一方面存在缺点。

据此,为提高电容器的装电能力,或适应不同的使用场合,工程师们各显神通,创造出各有五花八门各有特点电容器。

四、红外遥控编解码

红外遥控器原理介绍

    红外线遥控是目前使用最广泛的一种通信和遥控手段。由于红外线遥控装置具有体积小、 功耗低、 功能强、 成本低等特点, 因而, 继彩电、 录像机之后, 在录音机、 音响设备、 空调机以及玩具等其它小型电器装置上也纷纷采用红外线遥控。工业设备中, 在高压、 辐射、 有毒气体、 粉尘等环境下, 采用红外线遥控不仅完全可靠而且能有效地隔离电气干扰。

    红外遥控系统:通用红外遥控系统由发射和接收两大部分组成, 应用编/解码专用集成电路芯片来进行控制操作, 如图1所示。发射部分包括键盘矩阵、 编码调制、 LED红外发送器;接收部分包括光、 电转换放大器、 解调、 解码电路。

红外的简单发射接收原理

    在发射端,输入信号经放大后送入红外发射管发射,在接收端,接收管收到红外信号后,由放大器放大处理后还原成信号,这就是红外的简单发射接收原理。

1、红外遥控系统结构

    红外遥控系统的主要部分为调制、发射和接收,如下图所示:

嵌入式分享合集57_第17张图片

    红外遥控是以调制的方式发射数据,就是把数据和一定频率的载波进行“与”操作,这样既可以提高发射效率又可以降低电源功耗。

    调制载波频率一般在30khz到60khz之间,大多数使用的是38kHz,占空比1/3的方波,载波波形如下图所示,这是由发射端所使用的455kHz晶振决定的。在发射端要对晶振进行整数分频,分频系数一般取12,所以455kHz÷12≈37.9kHz≈38kHz。

嵌入式分享合集57_第18张图片

    目前有很多种芯片可以实现红外发射,可以根据选择发出不同种类的编码。由于发射系统一般用电池供电,这就要求芯片的功耗要很低,芯片大多都设计成可以处于休眠状态,当有按键按下时才工作,这样可以降低功耗芯片所用的晶振应该有足够的耐物理撞击能力,不能选用普通的石英晶体,一般是选用陶瓷共鸣器,陶瓷共鸣器准确性没有石英晶体高,但通常一点误差可以忽略不计。

    红外线通过红外发光二极管(LED)发射出去,红外发光二极管(红外发射管)内部构造与普通的发光二极管基本相同,材料和普通发光二极管不同,在红外发射管两端施加一定电压时,它发出的是红外线而不是可见光。

嵌入式分享合集57_第19张图片

    如上图是LED的驱动最简单电路,选用元件时要注意三极管的开关速度要快,还要考虑到LED的正向电流和反向漏电流,一般流过LED的最大正向电流为100mA,电流越大,其发射的波形强度越大。

    电路有一点缺陷,当电池电压下降时,流过LED的电流会降低,发射波形强度降低,遥控距离就会变小。

嵌入式分享合集57_第20张图片

    上图所示的射极输出驱动电路可以解决这个问题,两个二极管把三级管基极电压钳位在1.2V左右,因此三级管发射极电压固定在0.6V左右,发射极电流IE基本不变,根据IE≈IC,所以流过LED的电流也基本不变,这样保证了当电池电压降低时还可以保证一定的遥控距离。

2、一体化红外接收头嵌入式分享合集57_第21张图片

    红外信号收发系统的典型电路如上图所示,红外接收电路通常被厂家集成在一个元件中,成为一体化红外接收头。内部电路包括红外监测二极管,放大器,限幅器,带通滤波器,积分电路,比较器等。红外监测二极管监测到红外信号,然后把信号送到放大器和限幅器,限幅器把脉冲幅度控制在一定的水平,而不论红外发射器和接收器的距离远近。交流信号进入带通滤波器,带通滤波器可以通过30khz到60khz的负载波,通过解调电路和积分电路进入比较器,比较器输出高低电平,还原出发射端的信号波形。注意输出的高低电平和发射端是反相的,这样的目的是为了提高接收的灵敏度。

    一体化红外接收头,如下图所示:

嵌入式分享合集57_第22张图片

    红外接收头的种类很多,引脚定义也不相同,一般都有三个引脚,包括供电脚,接地和信号输出脚。根据发射端调制载波的不同应选用相应解调频率的接收头。

    红外接收头内部放大器的增益很大,很容易引起干扰,因此在接收头的供电脚上须加上滤波电容,一般在22uf以上。有的厂家建议在供电脚和电源之间接入330欧电阻,进一步降低电源干扰。

    红外发射器可从遥控器厂家定制,也可以自己用单片机的PWM产生,家庭遥控推荐使用红外发射管(L5IR4-45)的可产生37.91KHz的PWM,PWM占空比设置为1/3,通过简单的定时中断开关PWM,即可产生发射波形。

红外编解码解析

1、编码格式

    现有的红外遥控包括两种方式:PWM(脉冲宽度调制)和PPM(脉冲位置调制)。

两种形式编码的代表分别为NEC 和PHILIPS 的RC-5、RC-6 以及将来的RC-7。

    PWM(脉冲宽度调制):以发射红外载波的占空比代表“0”和“1”。为了节省能量,一般情况下,发射红外载波的时间固定,通过改变不发射载波的时间来改变占空比。例如常用的电视遥控器,使用NEC upd6121,其“0”为载波发射0.56ms,不发射0.56ms;其“1”为载波发射0.56ms,不发射1.68ms;此外,为了解码的方便,还有引导码,upd6121 的引导码为载波发射9ms,不发射4.5ms。upd6121 总共的编码长度为108ms。

    但并不是所有的编码器都是如此,比如TOSHIBA 的TC9012,其引导码为载波发射4.5ms,不发射4.5ms,其“0”为载波发射0.52ms,不发射0.52ms,其“1”为载波发射0.52ms,不发射1.04ms。

    PPM(脉冲位置调制):以发射载波的位置表示“0”和“1”。从发射载波到不发射载波为“0”,从不发射载波到发射载波为“1”。其发射载波和不发射载波的时间相同,都为0.68ms,也就是每位的时间是固定的。

    通过以上对编码的分析,可以得出以某种固定格式的“0”和“1”去学习红外,是很有可能不成功的。即市面上所宣传的可以学习64 位、128 位必然是不可靠的。

    另外,由于空调的状态远多于电视、音像,并且没有一个标准,所以各厂家都按自己的格式去做一个,造成差异更大。比如:美的的遥控器采用PWM 编码,码长120ms 左右;新科的遥控器也采用PWM 编码,码长500ms 左右。如此大的差异,如果按“位”的概念来讲,应该是多少位呢?64?128?显然都不可能包含如此长短不一的编码。

2、红外遥控编码格式

    红外遥控器的编码格式通常有两种格式:NEC 和RC5

    NEC 格式的特征:

  • 使用38 kHz 载波频率

  • 引导码间隔是9 ms + 4.5 ms

  • 使用16 位客户代码

  • 使用8 位数据代码和8 位取反的数据代码

嵌入式分享合集57_第23张图片

    不过需要将波形反转一下才方便分析:

嵌入式分享合集57_第24张图片

    NEC 协议通过脉冲串之间的时间间隔来实现信号的调制(英文简写PWM)。

    逻辑“0”是由0.56ms的38KHZ载波和0.560ms 的无载波间隔组成;逻辑“1”是由0.56ms 的38KHZ 载波和1.68ms 的无载波间隔组成;结束位是0.56ms 的38K 载波。

嵌入式分享合集57_第25张图片

    下面实例是已知NEC类型遥控器所截获的波形:

    遥控器的识别码是Address=0xDD20;其中一个键值是Command=0x0E;

嵌入式分享合集57_第26张图片

    注意:波形先是发低位地址再发高位地址。

    所以0000,0100,1011,1011 反转过来就是1101,1101,0010,000 十六进制的DD20;

    键值波形如下:

嵌入式分享合集57_第27张图片

    也是要将0111,0000 反转成0000,1110得到十六进制的0E;另外注意8 位的键值代码是取反后再发一次的,如图0111,0000 取反后为1000,1111。最后一位是一个逻辑“1”。

    RC5 编码相对简单一些:同样由于取自红外接收头的波形需要反相一下波形以便于分析:

嵌入式分享合集57_第28张图片

    反相后的波形:

嵌入式分享合集57_第29张图片

    根据编码规则:

嵌入式分享合集57_第30张图片

    得到一组数字:110, 11010, 001101根据编码定义:

嵌入式分享合集57_第31张图片

    第一位是起始位S通常是逻辑1。

    第二位是场位F通常为逻辑1, 在RC5 扩展模式下它将最后6位命令代码扩充到7 位代码(高位MSB) , 这样可以从64 个键值扩充到128 个键值。

    第三位是控制位C它在每按下了一个键后翻转, 这样就可以区分一个键到底是一直按着没松手还是松手后重复按。

    如图所示是同一按键重复按两次所得波形, 只有第三位是相反的逻辑, 其它的位逻辑都一样。

嵌入式分享合集57_第32张图片

    其后是五个系统地址位:11010=1A, 最后是六个命令位:001101=0D。

你可能感兴趣的:(aiot,单片机,嵌入式硬件)