×
嵌入式 > 嵌入式开发 > 详情

单片机分时思想

发布时间:2020-08-28 发布时间:
|
下面说的单片机应用也主要是家电控制方面,也就是现在的工作。其它应用不在讨论范围。才识有限,也敬请前辈们勘误,指教。

先用一个小例子引出今天的主题,想象一下,一个基本的家电控制板,肯定或多或少的会包含 : LED 或者 数码管显示, 按键, 继电器或者可控硅的输出 这3部分。数码管需要 10ms到20ms的动态扫描,按键也需要 20ms左右的延时消抖,有没有意识到,其实这些时间是同时在进行的。回想一下咱们的教科书怎么教 按键 的延时消抖的?没错,死循环,绝对是原地踏步死循环,用指令来计时。这样很自然的引发一个问题,单片机在原地踏步死循环的话,那么其它的工作怎么办?如数码管的动态扫描怎么办?唯有等按键扫描之后再进行了,这样出来的效果,数码管肯定会闪烁的,扫描时间过长了,缩短按键消抖时间也不是解决办法,想象如果咱们还有其它很多工作也是同时做的呢?解决办法之一,就是今天的主题,分时扫描的思想。当然不会是唯一的办法,只不过俺一直在用,觉得这个是非常不错的思想,可以解决很多实际问题。大胆妄言一下,分时扫描的思想也是单片机编程最核心的思想了,信不信就由你自己判断了。

程序的执行环境

用的是纯汇编,因为用习惯的汇编,以致于整个脑袋都是汇编的思想,某种程度上说,C的废码确实是多了一点,对于实际工业用的单片机ROM和RAM都不是很充足的情况下,代码还是要求尽量精简的。所以俺一直都在汇编的苦海中沉浮着……说明一点,俺用的是EMC的单片机(Elan公司的),传闻中最烂的单片机,所以代码不值的去学,也正是这个原因,但愿我想表达的思想能表达清楚, 阿门!

核心思想的实现

其实是几个步骤,

第一, 用TCC中断来计时,TCC的中断时间断一点,我习惯是 125us ,为了解红外遥控的码,这个时间是需要的。TCC计时是相当准的,尽量利用。

第二, 在TCC的中断服务程序里面放3个(数量自定)记时器(说白了就是计数器),我的习惯是 2ms 5ms 500ms 这3个是作为基准时间,提供给整个系统来调用的,所以必须准确一点,实际用示波器调一下就OK了,不难。

第三, 在主程序的循环里面放一个专门处理时间的子程序。(注:单片机是不会停的,永远在不断循环的跑,这个跟学校学的貌似有点不同,俺面试的时候被问过这个问题 ….) 将所有的时间处理都放在时间处理子程序里面做,这样是非常方便的,一个单片机系统最起码需要处理 10~20个不同的时间,也需要10~20个计时器了,而且相当多要求同时不同步工作的,如果每个都单独的话是相当的麻烦。

第四, “程序是跑着来等,而不是站着来等”,这话看来有点玄,一个跟俺一起进去公司的工程师讨论的时候提到的这个问题,俺觉得这个也是分时系统的一个比较重要的思想,所以也这样叫,下面有细说。

第五, 下面用程序来说话,注释尽量详细,可以不用看代码,直接看注释就可以了。

(一)先中断服务程序部分:

每 125us 中断一次

;-------------------产生几个基准时间---------------------------

int_2ms:

djz ref_2ms (1)

jmp int_5ms

mov a,@16 ; 恢复原始数据

mov ref_2ms,a

bs flag_time,flag_2ms (2)

int_5ms:

djz ref_5ms

jmp ref_time_end

mov a,@40 125us×40=5ms

mov ref_5ms,a

bs flag_time,flag_5ms

int_500ms:

djz ref_500ms

jmp ref_time_end

mov a,@100

mov ref_500ms,a

bs flag_time,flag_500ms

ref_time_end:

nop

(1) ef_2ms寄存器不断的减1,每次中断减1,一共减 16次,所以这里经过的时间是 125us × 16 = 2ms,这个就是所谓的计时/计数器 了。这样就可以靠一个系统的TCC中断,来实现我们需要的很多个定时时间。

(2)置 2ms 计时结束标志,这个是提供给时间处理程序用的,这是一个计时器的框架,下面的5ms计时完全相同。

这里解释一下:bs flag_time,flag_2ms,其中bs是将某一位置1的指令,该指令的意思是,将 flag_timg寄存器里面第flag_2ms位置1,设立一个溢出标志,后面的程序可以通过读这个标志,就知道 2ms 是否已经计时到了

这程序还用了一个块的框架,比较方便的,不过跟今天的主题无关,以后郁闷的时候再上来写写这个。上面的程序就是中断服务程序里面的计时器,分别定时 2ms 5ms 500ms,计时完毕溢出是flag_time 标志来记录的,程序通过读这个标志就可以知道定时的时间是否已经到了。

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

;-------------------时间处理子程序-----------------------------

time_proc:

jbs flag_time,flag_5ms 判断5ms是否到

jmp time_500ms_proc 没有的话判断500ms

time_5ms_proc:

bc flag_time,flag_5ms 5ms已经到了,进入服务程序,先清掉那个标志,这样下次再过5ms才允许进来这里。

;------------------ 按键延时部分--------------------- ;这里用这个做例子

key_delay_proc:

jbs flag_delay,key_start ; 按键延时开始标志,这个用来控制是否进入计时,允许计时标志

jmp key_delay_end

djz reg_key ; 预先设置的延时时间,每5秒减一次,这里预设的值是4,也就是说 5ms * 4 = 20ms

jmp key_delay_end 有减到0的话直接跳出

bs flag_delay,key_flow 时间到,置需要用的标志

mov a,@4 ; 重新置数据下次用。

mov reg_key,a

key_delay_end:

nop

;---------------------500ms处理部分------------------------

time_500ms_proc:

jbs flag_time,flag_500ms

ret

bc flag_time,flag_500ms

这里放需要的计时器

Ret

上面用了按键20ms消抖的计时器作为例子,如果理解之后就可以发现,我们可以完全模仿那个计时器而在下面放很多很多的计时器,则每5ms 进来一下,每个计时器都同时在计数了,谁先计算完毕就先关掉自己,置相应的标志给其它程序调用,而对其它计时器完全没有影响!这样,我们可以在这里放很多个计时器了,一般来说,十来二十个是没有问题的,完全满足一个单片机系统对多个时间的需求了。

单个计时器的结构很简单,先判断允许计时标志是否进入计时,然后一个专用的寄存器在加1或者减1,加/减购相应的数值之后也就是相应的时间到了,关掉计时器,置相应需要用到的标志。

到这里差不多了,俺们需要的时间都可以出来了,这样做是不是非常方便?咱们再来看看在这段时间里面单片机在做了什么东西?只有中断计时够 5ms 或者 500ms ,那个溢出标志才有效,才能进入上面的计时程序,其它时间都是在做其它事情。而且进入上面的计时器的时候,可以看出,并不是在那里死循环,只是单纯的加减一下寄存器就退出了,整个过程耗时极其短,看代码不同吧,5us到 20us左右吧,对主程序的执行没有什么影响。

(三)下面看看具体怎么调用

最开始谈过的按键的消抖时间处理问题,现在就用上面介绍的办法来看具体怎么解决问题。按键的处理也是重要的基础学问,不过不在本次的讨论范围,所以只是单单的讨论怎么解决时间问题,而对于按键的一些问题,下次有机会继续讨论吧,hoho~~~

scan_key:

jbc flag_key,first_on ; 用来控制跳转的

jmp have_key

jbs port6,1 假设P61高电平是有键

jmp key_end

;-------------------------------------

bs flag_key,first_on 第一次按键的标志

bs flag_delay,key_start 启动计时器开始定时20ms消抖

have_key:

jbs flag_delay,key_flow 等待定时结束溢出

jmp key_end

大概是这样的:判断时候有健,没有的话跳出,有的话开始延时消抖的计时,第二次进来的时候直接由标志位控制过去判断时间时候够。

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

(四)看看我的主程序循环体

start:

call clear_all_ram 清空RAM

call initialize 初始化

loop:

eni 开中断

wdtc 清看门狗

call scan_proc 调用扫描LED和按键程序

call ir_data_proc 调用红外处理程序

call time_proc 调用时间统一处理程序

call output_proc 调用输出处理程序

jmp loop

这个就是我用的循环体了,所有功能都做成子程序形式了,需要就挂上去就可以了,比较方便,这样一个总的循环体,单片机就是在不断的执行这个循环体,如果整个程序都采用上面说的分时扫的思想的话,一周循环回来的时间是相当短的,其实是不是跟电脑的思想有点像呢?电脑再快也并不是同时处理多个任务,而且每次处理一个,然后非常快的速度来循环处理,让我们感觉上他是在同时处理多个程序那样,我想,我最终想表达的思想也就是这个而已。

啰啰唆唆的说了一堆,也不知道是否能看懂,或者是否去看。不知道我对分时扫描这个概念是否理解错了呢?在我看来,有这个思想支撑下,单片机的程序变得比较容易上手了,剩下的只是集中精力去用程序来实现我们的思想而已,当然,这里只是说一种可行的办法而已,不是说只有这种办法,如果大家有好的思想也分享一下哦,编写程序是一门艺术,写出来很容易,但是写得好,写得精巧,那就很难了。



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
ARM入门篇之(一)概念