搜索
bottom↓
楼主: tangwei039

从单片机初学者迈向单片机工程师(一份正真的让你成为工程师的贴子)

  [复制链接]

出0入0汤圆

发表于 2010-4-21 22:59:54 | 显示全部楼层
支持

出0入0汤圆

发表于 2010-4-21 23:39:05 | 显示全部楼层
超佩服,对于我这新手来说,真是一种全新的编程理念,看来我得改改了,我的程序中经常出现delay

出0入0汤圆

发表于 2010-4-22 00:40:43 | 显示全部楼层
楼主很有耐心 ~值得向楼主学习

出0入0汤圆

发表于 2010-4-22 08:36:35 | 显示全部楼层
回复【78楼】zhfeng
-----------------------------------------------------------------------

g_bSystemTime1Ms是在中断里置1的,只要1Ms中断产生就会把这个变量置1,也就执行后面的函数。如果用!g_bSystemTime1Ms,那就一直执行后面的函数,这个时间标志的效果就没有了啊

出0入0汤圆

发表于 2010-4-22 08:41:17 | 显示全部楼层
非常喜欢原作者这种实际运用的经验之淡的文章,我也是最近一个月来一直在追原作者的帖子,不过原作者已经一个多月没有继续更新了。这是我的百度空间http://hi.baidu.com/dxstar上面也把原作者的文章转了过去,更重要的是加上自己的一些理解,希望和更多的单片机初学者共同学习,共同进步。

出0入0汤圆

发表于 2010-4-22 09:42:19 | 显示全部楼层
记号,向高手迈进中!!!

出0入0汤圆

发表于 2010-4-22 09:55:21 | 显示全部楼层
很好,很长,mark之,后面再读。。
感谢楼主。。。

出0入0汤圆

发表于 2010-4-22 11:56:30 | 显示全部楼层
擦看到-----顶

出0入0汤圆

发表于 2010-4-22 12:49:00 | 显示全部楼层
mark

出235入235汤圆

发表于 2010-4-22 12:53:23 | 显示全部楼层
make!

出0入0汤圆

发表于 2010-4-22 14:10:31 | 显示全部楼层
lz.向你学习啊!

出0入0汤圆

发表于 2010-4-22 14:58:43 | 显示全部楼层
我也来顶一下!

出0入0汤圆

发表于 2010-4-22 15:11:19 | 显示全部楼层
顶一下

出0入0汤圆

发表于 2010-4-22 16:11:06 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-22 16:27:45 | 显示全部楼层
讲的很好啊

出0入0汤圆

发表于 2010-4-22 16:33:12 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-22 16:50:46 | 显示全部楼层
lz的精神可赞。好好向你学习!

出0入0汤圆

发表于 2010-4-22 16:59:55 | 显示全部楼层
Mark

出0入0汤圆

发表于 2010-4-22 19:11:59 | 显示全部楼层
支持,希望继续,多出几章来,顶

出0入0汤圆

发表于 2010-4-22 21:36:24 | 显示全部楼层
太好了,严重支持

出0入0汤圆

 楼主| 发表于 2010-4-22 22:18:38 | 显示全部楼层
“从单片机初学者迈向单片机工程师”之
                             LED主题讨论周第四章----渐明渐暗的灯




看着学习板上的LED按照我们的意愿开始闪烁起来,你心里是否高兴了,我相信你会的。但是很快你就会感觉到太单调,总是同一个频率在闪烁,总是同一个亮度在闪烁。如果要是能够由暗逐渐变亮,然后再由亮变暗该多漂亮啊。嗯,想法不错,可以该从什么地方入手呢。
在开始我们的工程之前,首先来了解一个概念:PWM。
PWM(Pulse Width Modulation)是脉冲宽度调制的英文单词的缩写。下面这段话是通信百科中对其的定义:
脉冲宽度调制(PWM)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。脉宽调制是开关型稳压电源中的术语。这是按稳压的控制方式分类的,除了PWM型,还有PFM型和PWM、PFM混合型。脉宽调制式开关型稳压电路是在控制电路输出频率不变的情况下,通过电压反馈调整其占空比,从而达到稳定输出电压的目的。
读起来有点晦涩难懂。其实简单的说来,PWM技术就是通过调整一个周期固定的方波的占空比,来调节输出电压的平均当电压,电流或者功率等被控量。我们可以用一个水龙头来类比,把1S时间分成50等份,即每一个等份20MS。在这20MS时间里如果我们把水龙头水阀一直打开,那么在这20MS里流过的水肯定是最多的,如果我们把水阀打开15MS,剩下的5MS关闭水阀,那么流出的水相比刚才20MS全开肯定要小的多。同样的道理,我们可以通过控制20MS时间里水阀开启的时间的长短来控制流过的水的多少。那么在1S内平均流出的水流量也就可以被控制了。
当我们调整PWM的占空比时,就会引起电压或者电流的改变,LED的明暗状态就会随之发生相应的变化,听起来好像可以通过这种方法来实现我们想要的渐明渐暗的效果。让我们来试一下吧。
大家都知道人眼有一个临界频率,当LED的闪烁频率达到一定的时候,人眼就分辨不出LED是否在闪烁了。就像我们平常看电视一样,看起来画面是连续的,实质不是这个样子,所有连续动作都是一帧帧静止的画面在1S的时间里快速播放出来,譬如每秒24帧的速度播放,由于人眼的视觉暂留效应,看起来画面就是连续的了。同样的道理,为了让我们的LED在变化的过程中,我们感觉不到其在闪烁,可以将其闪烁的频率定在50Hz以上。同时为了看起来明暗过渡的效果更加明显,我们在这里定义其变化范围为0~99(100等分).即最亮的时候其灰度等级为99,为0的时候最暗,也就是熄灭了。
于是乎我们定义PWM的占空比上限为99, 下限定义为0
#define  LED_PWM_LIMIT_MAX    99
#define  LED_PWM_LIMIT_MIN      0

假定我们LED的闪烁频率为50HZ,而亮度变化的范围为0~99共100等分。则每一等分所占用的时间为 1/(50*100)  =  200us 即我们在改变LED的亮灭状态时,应该是在200us整数倍时刻时。在这里我们用单片机的定时器产生200us的中断,同时每20MS调整一次LED的占空比。这样在20MS * 100 = 2S的时间内LED可以从暗逐渐变亮,在下一个2S内可以从亮逐渐变暗,然后不断循环。
由于大部分的内容都可以在中断中完成,因此,我们的大部分代码都在Timer.c这个文件中编写,主函数中除了初始化之外,就是一个空的死循环。






Timer.c内容如下。
#include <reg52.h>
#include "MacroAndConst.h"

#define LED P0            //定义LED接口
#define LED_ON()      LED = 0x00 ;  //所有LED亮
#define LED_OFF()    LED = 0xff ;  //所有LED熄灭

#define  LED_PWM_LIMIT_MAX    99
#define  LED_PWM_LIMIT_MIN      0

static uint8 s_u8TimeCounter = 0 ; //中断计数
static uint8 s_u8LedDirection = 0 ; //LED方向控制 0 :渐亮 1 :渐灭
static int8  s_s8LedPWMCounter  = 0 ; //LED占空比
void Timer0Init(void)
{
    TMOD &= 0xf0 ;
    TMOD |= 0x01 ;     //定时器0工作方式1
    TH0  =     0xff ;     //定时器初始值(200us中断一次)
    TL0  =  0x47 ;
    TR0  = 1 ;
    ET0  = 1 ;
}

void Time0Isr(void) interrupt 1
{     
    static int8 s_s8PWMCounter = 0  ;
    TH0  =     0xff ;           //定时器重新赋初值
    TL0  =  0x47 ;

    if(++s_u8TimeCounter >= 100) //每20MS调整一下LED的占空比
    {
        s_u8TimeCounter = 0 ;
        //如果是渐亮方向变化,则占空比递增
         if((s_s8LedPWMCounter <= LED_PWM_LIMIT_MAX) &&(0 == s_u8LedDirection))
        {
            s_s8LedPWMCounter++  ;
            if(s_s8LedPWMCounter > LED_PWM_LIMIT_MAX)
            {
                s_u8LedDirection = 1 ;
                s_s8LedPWMCounter =  LED_PWM_LIMIT_MAX ;               
            }            
        }
        //如果是渐暗方向变化,则占空比递渐
                if((s_s8LedPWMCounter >= LED_PWM_LIMIT_MIN) &&(1 == s_u8LedDirection))
                                                                                                
        {
            s_s8LedPWMCounter-- ;
            if(s_s8LedPWMCounter < LED_PWM_LIMIT_MIN)
            {
                s_u8LedDirection = 0 ;
                s_s8LedPWMCounter =  LED_PWM_LIMIT_MIN ;               
            }   
        }
        s_s8PWMCounter = s_s8LedPWMCounter ;  //获取LED的占空比
    }

    if(s_s8PWMCounter > 0) //占空比大于0,则点亮LED,否则熄灭LED
    {
        LED_ON() ;
        s_s8PWMCounter-- ;
    }
    else
    {
        LED_OFF();   
    }

}


其实PWM技术在我们实际生活中应用的非常多。比较典型的应用就是控制电机的转速,控制充电电流的大小,等等。而随着技术的发展,也出现了其他类型的PWM技术,如相电压PWM,线电压PWM,SPWM等等,如果有兴趣可以到网上去获取相应资料学习。

关于渐明渐暗的灯就简单的讲到这里。...

继续更新.........

出0入0汤圆

发表于 2010-4-23 00:21:40 | 显示全部楼层
mark

出0入0汤圆

 楼主| 发表于 2010-4-23 08:17:38 | 显示全部楼层
今天讲个技巧方面的知识:                                       
                                  一个有关0.0625℃的运算想到的问题
            
碰到一哥们号称挺NB的嵌入软件工程师,看了他的代码后就欧拉,事情是在一个只有4K代码的单片机接2个DS18B20测温传感器,都知道DS18B20输出数据只要乘以0.0625就是测量的温度值,这哥们说程序空间怎么也不够,实际上程序只有简单的采集两个DS18B20的数据转换成温度值,之后在1602液晶上显示,挺简单个程序,怎么也想不通为什么程序空间不够。只读了一下代码发现程序就没动脑子,真的用浮点库把DS18B20数据直接乘以0.0625了,那程序不超才怪呢,稍微动动脑子也会知道0.0625不就是1/16吗,把DS18B20的数据直接右移4位不就是了(当然要注意符号),这右移程序可十分简单还省空间,问题很好解决,空间自然也就够了。
    现在想来嵌入处理器确实是进步了,程序空间是越来越大,数据RAM空间也越来越大,导致很多人在写程序的时候真的是什么都不顾,借着C语言的灵活性真是纵横驰骋,压根也不讲个程序效率和可靠性。正如前些日子见到一孩子用ARM cortex-m3处理器给人接活写个便携表的1024点FFT算法,本身12位的AD系统,这小家伙直接到网上下载了浮点的FFT算法代码就给人加上了,结果整个程序死慢死慢的,人家用户可不买单啊,这时要动动脑子把数据直接变成乘以某个数变成整数后用定点FFT处理,之后再把数据除一下不就行了。速度自然也快了,而且也能省下空间。实际当中我们做嵌入软件很多时候犯懒都忽视程序执行效率问题,是都能实现功能,但有时候就是没法谈性能。我几次碰到这样的工程师,直接把传感器的信号放大后进嵌入处理器的AD,也不看看AD数据是否稳定有效,直接就进行FFT运算,那FFT结果真是热闹,不难看出混叠很严重,于是又机械地在FFT基础上再去衍生算法,系统程序越做越大,速度越做越慢。实际上也很简单的事,在传感器放大信号进AD之前来一级抗混叠滤波基本也就解决了,大有所谓嵌入软件高手的概念是程序几乎是万能,实在解决不了就换大程序空间更高速的处理器,整个恶性循环。
    经常听说现在流行低碳族,我想出色的嵌入软件工程师最容易成为低碳一族,只要让代码高效那处理器频率自然可以灵活降下来,自然耗电也就少了,二氧化碳排放也就少了。想想目前到处都是嵌入处理器,代码条数看来也别有效果。  
   


我会继续更新。。。。。。。。。。。。。。。。。。

出0入0汤圆

发表于 2010-4-23 08:47:27 | 显示全部楼层
mark,好东西,通俗易懂,其中的思想尤其值得学习,哈哈

出0入0汤圆

发表于 2010-4-23 09:37:02 | 显示全部楼层
mark,学习了。感谢楼主

出0入0汤圆

发表于 2010-4-23 11:02:17 | 显示全部楼层
这种帖子不多见的,应该加精

出0入0汤圆

发表于 2010-4-23 12:15:56 | 显示全部楼层
谢谢,期待楼主更新!

出0入0汤圆

 楼主| 发表于 2010-4-23 12:44:03 | 显示全部楼层
只要大家能过来看看,每天都有新贴。呵呵

出0入0汤圆

发表于 2010-4-23 12:50:33 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-23 13:30:12 | 显示全部楼层
希望楼主能转成PDF完整版文件发上来 !!感谢

出0入0汤圆

发表于 2010-4-23 15:25:07 | 显示全部楼层
马克 还有没?

出0入0汤圆

发表于 2010-4-23 16:29:14 | 显示全部楼层
mark,谢谢~

出0入0汤圆

发表于 2010-4-23 16:35:00 | 显示全部楼层
楼主有完整版的吗,不要吝啬啊哈哈

出0入0汤圆

发表于 2010-4-23 16:44:38 | 显示全部楼层
强烈期待后续啊

出0入0汤圆

发表于 2010-4-23 17:01:56 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-23 18:25:22 | 显示全部楼层
顶一下

出0入0汤圆

发表于 2010-4-23 18:42:28 | 显示全部楼层

出0入264汤圆

发表于 2010-4-23 18:45:10 | 显示全部楼层
应该置酷!

出0入0汤圆

 楼主| 发表于 2010-4-23 18:54:53 | 显示全部楼层
回复【138楼】mcu_lover  
-----------------------------------------------------------------------

谢谢,版主能给个酷,我会更努力的!!!呵呵呵~~~~

出0入0汤圆

 楼主| 发表于 2010-4-23 19:04:43 | 显示全部楼层
前面我讲了这多,呵呵~~现在我给大家来个综合:也是最重要的。工程中常用的设计方法以。

                                         如何设计复杂的多任务程序

我们在入门阶段,一般面对的设计都是单一的简单的任务,流程图可以如图 1 所示,通
常会用踏步循环延时来满足任务需要。
面对多任务,稍微复杂的程序设计,沿用图 1 的思想,我们会做出如图 2 所示的程序,
在大循环体中不断增加任务,通常还要用延时来满足特定任务节拍,这种程序设计思想它有
明显的不足,主要是各个任务之间相互影响,增加新的任何之后,以前很好的运行的任务有
可能不正常,例如数码管动态扫描,本来显示效果很好的驱动函数,在增加新的任务后出现
闪烁,显示效果变差了。
  

(原文件名:1.JPG)


图1 单一任务简单流程图                 图2 多任务简单流程图


很明显,初学者在设计程序时,需要从程序构架思想上下功夫,在做了大量基本模块练
习之后,需要总结提炼自己的程序设计思路(程序架构思想)。
首先我们来理解“任务”,所谓任务,就是需要 CPU 周期“关照”的事件,绝大多数任
务不需要 CPU 一直“关照” ,例如启动 ADC 的启动读取。甚至有些任务“害怕”CPU 一直
“关照”例如 LCD 的刷新,因为 LCD 是显示给人看的,并不需要高速刷新,即便是显示的
内容在高速变化,也不需要高速刷新,道理是一样的。这样看来,让CPU做简单任务一定很
浪费,事实也是如此,绝大多数简单任务,CPU都是在“空转” (循环踏步延时) 。对任务总
结还可以知道,很多任务需要 CPU 不断“关照” ,其实这种“不断”也是有极限的,比如数
码管动态扫描,能够做到40Hz 就可以了,又如键盘扫描,能够做到20Hz(经验值),基本上
也就不会丢有效按键键值了,再如LCD刷新,我觉得做到 10Hz 就可以了,等等。看来,绝
大多数任务都是工作在低速频度。而我们的CPU一旦运行起来,速度又很快,CPU本身就是
靠很快的速度执行很简单的指令来胜任复杂的任务(逻辑)的。如果有办法把“快”的 CPU
分成多个慢的CPU,然后给不同的任务分配不同速度的CPU,这种设想是不是很好呢!确实
很好,下面就看如何将“快”的CPU划分成多个“慢”的 CPU。
根据这种想法,我们需要合理分配CPU资源来“关照”不同的任务,最好能够根据任务
本身合理占用CPU资源,首先看如图 3 所示的流程图,各个任务流程独立,各任务通过全局
变量来交互信息,在流程中有一个重要的模块“任务切换”,就是任务切换模块实现 CPU 合
理分配,这个任务切换模块是怎么实现的呢?

(原文件名:2.JPG)


图3 多任务复杂流程图
首先需要理解,CPU 一旦运行起来,就无法停止(硬件支持时钟停止的不在这里讨论),
谁能够控制一批脱缰的马呢?对了,有中断,中断能够让CPU回到特定的位置,设想,能不
能用一个定时中断,周期性的将 CPU这匹运行着的脱缰的马召唤回来,重新给它安排特定的
任务,事实上,任务切换就是这样实现的。

(原文件名:3.JPG)

                  



图 4  定时中断实现任务切换
如图 4A 所示,CPU 在空闲任务循环等待,定时中断将 CPU 周期性唤回,根据任务设计
了不同的响应频度,满足条件的任务将获得CPU资源,CPU为不同任务“关照”完成后,再
次返回空闲任务,如此周而复始,对于各个任务而言,好像各自拥有一个独立的CPU,各自
独立运行。用这种思想构建的程序框架,最大的好处是任务很容易裁剪,系统能够做得很复
杂。
在充分考虑单片机中断特性(在哪里中断就返回到哪里)后,实际可行的任务切换如图
4B所示,定时中断可能发生在任务调度,随机任务执行的任何时候,图中最大的框框所示,
不管中断在何时发生,它都会正常返回,定时中断所产生的影响只在任务调度模块起作用,
即依次让不同的任务按不同的节拍就绪。任务调度会按一定的优先级执行就绪任务。
总结不同的任务需要CPU关照的频度,选择最快的那个频度来设定定时器中断的节拍,
一般选择 200Hz,或者 100Hz 都可以。另外再给每个任务设定一个节拍控制计数器 C,也就
是定时器每中断多少次后执行任务一次。例如取定时中断节拍为 200Hz,给任务设定的 C=10,
则任务执行频度为 200/10=20Hz,如果是数码管扫描,按 40Hz 不闪烁规律,则任务节拍控制
计数器 C=5 即可。在程序设计中,C 代表着任务运行的节拍控制参数,我们习惯用 delay 来
描述,不同的任务用task0,task1……来描述。
明天继续写如何用代码实现!2009-6-29
下面我们来用代码实现以上多任务程序设计思想。
首先是任务切换
while(1)
{
if(task_delay[0]==0)   task0();  //task0就绪,
if(task_delay[1]==0)   task1();  //task1就绪,
……
}
很显然,执行任务的条件是任务延时量task_delay=0,那么任务延时量谁来控制呢?定时
器啊!定时器中断对任务延时量减一直到归零,标志任务就绪。当没有任务就绪时,任务切
换本身就是一个Idle 任务。
void timer0(void) interrupt 1
{
if(task_delay[0]) task_delay[0]--;
if(task_delay[1]) task_delay[1]--;
……
}
例如 timer0 的中断节拍为 200Hz,task0_delay 初值为 10,则 task0()执行频度为
200/10=20Hz。
有了以上基础,我们来设计一个简单多任务程序,进一步深入理解这种程序设计思想。
任务要求:用单片机不同 IO 脚输出 1Hz,5Hz,10Hz,20Hz 方波信号,这个程序很短,将
直接给出。
#include "reg51.h"
#define TIME_PER_SEC 200    //定义任务时钟频率,200Hz
#define CLOCK 22118400    //定义时钟晶振,单位Hz  
#define MAX_TASK 4      //定义任务数量

extern void task0(void);  //任务声明
extern void task1(void);
extern void task2(void);
extern void task3(void);

sbit f1Hz  = P1^0;  //端口定义
sbit f5Hz  = P1^1;
sbit f10Hz = P1^2;
sbit f20Hz = P1^3;

unsigned char task_delay[4];  //任务延时变量定义

//定时器0初始化
void timer0_init(void)
{
    unsigned char i;
   for(i=0;i<MAX_TASK;i++) task_delay=0;  //任务延时量清零
    TMOD = (TMOD & 0XF0) | 0X01;        //定时器 0工作在模式 1, 16Bit定时器模
式  
    TH0 = 255-CLOCK/TIME_PER_SEC/12/256;   
  TL0 = 255-CLOCK/TIME_PER_SEC/12%256;   
   TR0 =1;  
ET0 =1;         //开启定时器和中断
}

// 系统 OS定时中断服务
void timer0(void) interrupt 1
{
    unsigned char i;
    TH0 = 255-CLOCK/TIME_PER_SEC/12/256;
    TL0 = 255-CLOCK/TIME_PER_SEC/12%256;        
    for(i=0;i<MAX_TASK;i++) if(task_delay) task_delay--;  
//每节拍对任务延时变量减1 ,减至 0  后,任务就绪。   
}   

/*main主函数*/
void main(void)
{
    timer0_init();
    EA=1;//开总中断   
   while(1)
   {  
     if(task_delay[0]==0) {task0(); task_delay[0] = TIME_PER_SEC/ 2;}
  //要产生 1hz 信号,翻转周期就是 2Hz,以下同
  if(task_delay[1]==0) {task1(); task_delay[1] = TIME_PER_SEC/10;}
  //要产生 5hz 信号,翻转周期就是 10Hz,以下同
  if(task_delay[2]==0) {task2(); task_delay[2] = TIME_PER_SEC/20;}
  if(task_delay[3]==0) {task3(); task_delay[3] = TIME_PER_SEC/40;}     
   }
}

void task0(void)
{   
    f1Hz = !f1Hz;   
}

void task1(void)
{   
    f5Hz = !f5Hz;   
}

void task2(void)
{   
    f10Hz = !f10Hz;   
}

void task3(void)
{   
    f20Hz = !f20Hz;   
}
仿真效果如图5 所示。


(原文件名:4.JPG)

图 5  仿真波形图  
同样的程序,同学们可以考虑用图 2 所示的思想设计,看看容易不容易,如果你的程序
实现了相同的功能,如果我改变要求,改变信号的频率,你的程序容易修改吗?
要进一步完善这种程序设计思想,有几个问题还需要考虑:
对任务本身有什么要求?
不同任务之间有没有优先级?(不同的事情总有个轻重缓急吧!)
任务间如何延时?
……
为了回答这些问题,下面我们来分析 CPU的运行情况。


(原文件名:5.JPG)
图 6 CPU运行情况示意图
CPU运行情况如图 6 所示,黑色区域表示 CPU进程,系统启动后, CPU将无休止的运行,
CPU资源将如何分配呢?程序首先进入“任务切换”进程,如果当前没有任务就绪,就在任
务切换进程循环(也可以理解为空闲进程),定时中断将 CPU 当前进程打断,在定时中断进
程可能让某些任务就绪,中断返回任务切换进程,很快会进入就绪任务 0,CPU“关照”完
任务 0,再次回到任务切换进程,如果还有其它任务就绪,还会再次进入其它任务,没有任
务就循环等待,定时中断会不断让新的任务就绪,CPU 也会不断进入任务“关照” 。这样不
同的任务就会获得不同的CPU资源,每一个任务都像是拥有一个独立的CPU 为之服务。
从这种进程切换我们可以看出,在定时中断和任务切换过程中,额外的占用了一些 CPU
资源, 这就是定时中断频度不宜太快, 否则将大大降低CPU的有效资源率, 当然太慢也不行。
另外就是 CPU每次关照任务的时间不能太长,如果超过一个中断周期,就会影响到其它任务
的实时性。所谓的实时性就是按定时中断设定的节拍,准时得到CPU关照。这样,每一个子
任务就必须简单,每次“关照”时间最好不要超过定时中断节拍周期(5ms 或 10ms,初学者
要对 ms 有一个概念,机器周期为 us 级的单片机,1ms 可以执行上千条指令,对于像数码管
扫描,键盘扫描,LCD显示等常规任务都是绰绰有余的,只是遇到大型计算,数据排序就显
得短了)
关于任务优先级的问题:一个复杂系统,多个任务之间总有“轻重缓急”之区别,那些
需要严格实时的任务通常用中断实现,中断能够保证第一时间相应,我们这里讨论的不是那
种实时概念,是指在最大允许时差内能够得到 CPU“关照” ,例如键盘扫描,为了保证较好
的操作效果,快的/慢的/长的/短的(不同人按键不一样)都能够正确识别,这就要保证足够
的扫描速度,这种扫描速度对不同的按键最好均等,如果我们按 50Hz 来设计,那么就要保
证键盘扫描速度在任何情况下都能够做到 50Hz 扫描频度,不会因为某个新任务的开启而被
破坏,如果确实有新的任务有可能破坏这个 50Hz 扫描频度,我们就应该在优先级安排上让
键盘扫描优先级高于那个可能影响键盘扫描的任务。这里体现的就是当同时多个任务就绪时,
最先执行哪个的问题,任务调度时要优先执行级别高的任务。
关于“长”任务的问题:有些任务虽然很独立,但完成一次任务执行需要很长时间,例
如 DS18B20,从复位初始化到读回温度值,最长接近 1s,这主要是 DS18B20 温度传感器完
成一次温度转换需要500 到 750ms,这个时间对 CPU 而言,简直是太长了,就像一件事情需
要我们人等待 10 年一样,显然这样的任务是其它任务所耽搁不起的。像类似 DS18B20 这样
的器件(不少 ADC 也是这样) ,怎么设计任务体解决“长”的问题。进一步研究这些器件发
现,真正需要CPU“关照”它们的时间并不长,关键是等待结果要很长时间。解决的办法就
是把类似的器件驱动分成多个段:初始化段、启动段、读结果段,而在需要花长时间等待时
间段,不要 CPU关照,允许 CPU去关照其它任务。


将一个任务分成若干段,确保每段需要CPU 关照时长小于定时器
中断节拍长,这样CPU在处理这些长任务时,就不会影响到其它任务的执行。
Easy51RTOS
正是基于以上程序设计思想,总结完善后提出一种耗费资源特别少并且不使用堆栈的多
线程操作系统,这个操作系统以纯C语言实现,无硬件依赖性,需要单片机的资源极少。起
名为 Easy51RTOS,特别适合初学者学习使用。有任务优先级,通过技巧可以任务间延时,
缺点是高优先级任务不具有抢占功能,一个具有抢占功能的操作系统,一定要涉及到现场保
护与恢复,需要更多的 RAM 资源,涉及到堆栈知识,文件系统将很复杂,初学者学习难度
大。
为了便于初学者学习,将代码文件压缩至 4 个文件。
Easy51RTOS.Uv2 Keil工程文件,KEIL用户很熟悉的
main.c    main函数和用户任务 task 函数文件
os_c.c    Easy51RTOS相关函数文件
os_cfg.h    Easy51RTOS相关配置参数头文件
文件解读如下:
os_cfg.h
#include "reg51.h"
#define TIME_PER_SEC 200    //定义任务时钟频率,200Hz
#define CLOCK 22118400    //定义时钟晶振,单位Hz
#define MAX_TASK 4      //定义任务数量  
//函数变量声明,在需要用以下函数或变量的文件中包含此头文件即可
extern void task0(void);
extern void task1(void);
extern void task2(void);
extern void task3(void);
extern unsigned char task_delay[MAX_TASK];
extern void run(void (*ptask)());
extern void os_timer0_init(void);

os_c.c
#include "os_cfg.h"
unsigned char task_delay[MAX_TASK];  //定义任务延时量变量
//定时器0初始化
void os_timer0_init(void)
{
    unsigned char i;
   for(i=0;i<MAX_TASK;i++) task_delay=0;
    TMOD = (TMOD & 0XF0) | 0X01;    //定时器 0工作在模式 1,16Bit 定时器模式  
    TH0 = 255-CLOCK/TIME_PER_SEC/12/256;   
//CRY_OSC,TIME_PER_SEC在 os_cfg.h中定义
    TL0 = 255-CLOCK/TIME_PER_SEC/12%256;   
   TR0 =1;  
ET0 =1;         //开启定时器和中断
}

// 系统 OS定时中断服务
void os_timer0(void) interrupt 1
{
    unsigned char i;
    TH0 = 255-CLOCK/TIME_PER_SEC/12/256;
    TL0 = 255-CLOCK/TIME_PER_SEC/12%256;        
    for(i=0;i<MAX_TASK;i++) if(task_delay) task_delay--;  
//每节拍对任务延时变量减1 ,减至 0  后,任务就绪。   
}  

//指向函数的指针函数
void run(void (*ptask)())
{  
   (*ptask)();  
}

main.c
#include "os_cfg.h"  
#define TASK_DELAY0 TIME_PER_SEC/1      //任务执行频度为1Hz
#define TASK_DELAY1 TIME_PER_SEC/2      //任务执行频度为2Hz
#define TASK_DELAY2 TIME_PER_SEC/10      //任务执行频度为10Hz
#define TASK_DELAY3 TIME_PER_SEC/20     //任务执行频度为20Hz
void (* code task[])() = {task0,task1,task2,task3};  //获得任务PC指针

sbit LED0 = P1^0;  //演示用 LED接口定义
sbit LED1 = P1^1;
sbit LED2 = P1^2;
sbit LED3 = P1^3;

/*main主函数*/
void main(void)
{
    unsigned char i;   
    os_timer0_init();   //节拍发生器定时器初始化
    EA = 1;       //开总中断
   
   while(1)
   {
     for(i=0;i<MAX_TASK;i++)  
   if (task_delay==0) {run(task); break;}  //就绪任务调度
   }  //上一行 break有特殊作用,详细解释见后文
}

void task0(void)  //任务 0
{   
    LED0 = !LED0;   
task_delay[0] = TASK_DELAY0;
}

void task1(void)  //任务 1
{   
    LED1 = !LED1;   
task_delay[1] = TASK_DELAY1;
}

void task2(void)  //任务 2
{   
    LED2 = !LED2;   
task_delay[2] = TASK_DELAY2;
}

void task3(void)  //任务内分段设计  
{   
      static unsigned char state=0;  //定义静态局部变量
switch (state)
{
  case 0:
   LED3 = !LED3;   
   state = 1;
   task_delay[3] = TASK_DELAY3;
   break;

  case 1:
   LED3 = !LED3;   
   state = 2;
   task_delay[3] = TASK_DELAY3*2;
   break;

  case 2:
   LED3 = !LED3;   
   state = 0;
   task_delay[3] = TASK_DELAY3*4;
   break;

  default:
   state = 0;
   task_delay[3] = TASK_DELAY3;
   break;
}  
}

仿真图如图8 所示

(原文件名:6.JPG)

图 8  仿真波形图
主程序巧妙实现优先级设定:
for(i=0;i<MAX_TASK;i++)   
  if (task_delay==0) {run(task); break;}  //就绪任务调度
这里的 break 将跳出 for 循环,使得每次重新任务调度总是从 task0 开始,就意味着优先
级高的任务就绪会先执行。这样task0具有最高优先级,task1、task2、task3优先级依次降低。  
特别是 void task3(void)用 switch(state)状态机实现了任务分段,这也是任务内系统延时的
一种方法。


我会继续更新的。。。。。。。。。。。

出0入0汤圆

发表于 2010-4-23 21:55:42 | 显示全部楼层
回复【140楼】tangwei039
-----------------------------------------------------------------------

兄弟!顶你!

强烈建议,加酷,这么好的贴不加酷,这就说不过去了!

出0入0汤圆

发表于 2010-4-23 21:56:28 | 显示全部楼层
兄弟!咱们看帖的可都眼巴巴的望着你哦

加油哇!

出0入0汤圆

 楼主| 发表于 2010-4-23 22:08:15 | 显示全部楼层
回复【141楼】TCMCU
-----------------------------------------------------------------------

回复【141楼】TCMCU
回复【140楼】tangwei039  
-----------------------------------------------------------------------
兄弟!顶你!
强烈建议,加酷,这么好的贴不加酷,这就说不过去了!

-----------------------------------------------------------------------

回复【142楼】TCMCU
兄弟!咱们看帖的可都眼巴巴的望着你哦
加油哇!
-----------------------------------------------------------------------

有您这句话,我满足了!!!!

出0入264汤圆

发表于 2010-4-23 22:11:50 | 显示全部楼层
从单片机初学者迈向单片机工程师LED篇ourdev_548769.pdf(文件大小:1.37M) (原文件名:LED篇.pdf)

出0入0汤圆

发表于 2010-4-23 22:47:26 | 显示全部楼层
不错,谢谢!

出0入0汤圆

发表于 2010-4-24 00:54:03 | 显示全部楼层
感谢。

出0入0汤圆

发表于 2010-4-24 00:57:34 | 显示全部楼层
你们走过的路让后来人少走弯路,谢谢,强力顶。。。顶,。。。顶。。。。呵呵谢谢

出0入0汤圆

发表于 2010-4-24 09:45:26 | 显示全部楼层
学习,向楼主至敬!!!

出0入0汤圆

发表于 2010-4-24 11:12:42 | 显示全部楼层
真是好帖,读后有茅塞顿开的感觉。如果还能多配合一些流程图等示意图的话,那效果更好。谢谢!

出0入0汤圆

发表于 2010-4-24 11:46:19 | 显示全部楼层
斑竹能否加个酷啊。难得的好帖啊。让新手少走N多弯路。

出0入0汤圆

 楼主| 发表于 2010-4-24 13:53:28 | 显示全部楼层
回复【150楼】cbs110
斑竹能否加个酷啊。难得的好帖啊。让新手少走N多弯路。
-----------------------------------------------------------------------

谢谢!!!!

出0入0汤圆

发表于 2010-4-24 19:37:00 | 显示全部楼层
留名标记一下,太好了的文章

出0入0汤圆

发表于 2010-4-24 21:27:31 | 显示全部楼层
强帖,cool!!!!!!!!

出0入0汤圆

 楼主| 发表于 2010-4-25 13:39:54 | 显示全部楼层
发一个51单片机的键盘扫描程序,算法简单有效
                                      再给大家分享一个不错按键程序(来自ourdev)
/****************************************

键盘_不采用定时器_不延时

特点:
按键在松手后有效,灵敏度高,消耗资源少,运行效率高

独立键盘为:K01=P2^4;K02=P2^5;K03=P2^6;K04=P2^7;
矩阵键盘为:行(上到下)_P2.3_P2.2_P2.1_P2.0
           列(左到右)_P2.7_P2.6_P2.5_P2.4

提供的操作函数:

//独立键盘.无按键动作时其返回值num_key=0,否则返回按键号num_key
extern unsigned char keyboard_self();

//矩阵键盘.无按键动作时其返回值num_key=0,否则返回按键号num_key****检测高四位
extern unsigned char keyboard_matrix();

****************************************/

.
 

先看独立键盘(和矩阵键盘的算法一样)
-----------------------------------------------------------------------
#include<reg52.h>
#include<intrins.h>

//独立键盘.无按键动作时其返回值num_key=0,否则返回按键号num_key
extern unsigned char keyboard_self()
{
        unsigned char num_key=0;//按键号
        unsigned char temp=0;//用于读取P2线上按键值
        static unsigned char temp_code=0;//保存按键值
        static unsigned char num_check=0;//低电平有效次数
        static unsigned char key_flag=0;//按键有效标识

        temp=P2&0xF0;//读取P2线数据

        if(temp!=0xF0)//低电平判断
        {
                num_check++;
                if(num_check==10)//连续10次(10ms)低电平有效,则认为按键有效
                {
                        key_flag=1;//使能按键有效标识
                        temp_code=temp;//保存按键值
                }
        }
        else//松手时判断
        {
                num_check=0;

                if(key_flag==1)//按键有效
                {
                        key_flag=0;

                        switch(temp_code)//读取按键号
                        {
                                case 0xE0: num_key=1;
                                           break;
                                case 0xD0: num_key=2;
                                           break;
                                case 0xB0: num_key=3;
                                           break;
                                case 0x70: num_key=4;
                                           break;
                        }
                }
        }

        return(num_key);
}
 

现在是矩阵键盘的
-----------------------------------------------------------------------
#include<reg52.h>
#include<intrins.h>  

//矩阵键盘.无按键动作时其返回值num_key=0,否则返回按键号num_key****检测高四位
extern unsigned char keyboard_matrix()
{
        unsigned char num_key=0;//按键号
        unsigned char temp=0;//读取P2口线数据
        static unsigned char temp_code=0;//用于保存按键值
        static unsigned char temp_circle=0xFE;//保存P2线上的循环扫描值
        static unsigned char num_check=0;//低电平计数
        static unsigned char key_flag=0;//按键有效标识

        P2=temp_circle;//0xFX
        temp=P2;//读取P2口线数据
        if(temp!=temp_circle)//有按键动作
        {
                num_check++;//低电平计数|逢低电平加1
                if(num_check==10)//连续10次(10ms)低电平有效
                {
                        key_flag=1;//按键有效标识置1
                        temp_code=temp;//保存按键值
                }
        }
        else//松手OR无按键动作,此时应该改变扫描线
        {
                num_check=0;
                if(key_flag==1)//按键有效判断
                {
                        key_flag=0;
                        switch(temp_code)//读取按键号
                        {
                                //P2^0线
                                case 0xEE: num_key=1;
                                           break;
                                case 0xDE: num_key=2;
                                           break;
                                case 0xBE: num_key=3;
                                           break;
                                case 0x7E: num_key=4;
                                           break;
                                //P2^1线
                                case 0xED: num_key=5;
                                           break;
                                case 0xDD: num_key=6;
                                           break;
                                case 0xBD: num_key=7;
                                           break;
                                case 0x7D: num_key=8;
                                           break;
                                //P2^2线
                                case 0xEB: num_key=9;
                                           break;
                                case 0xDB: num_key=10;
                                           break;
                                case 0xBB: num_key=11;
                                           break;
                                case 0x7B: num_key=12;
                                           break;
                                //P2^3线
                                case 0xE7: num_key=13;
                                           break;
                                case 0xD7: num_key=14;
                                           break;
                                case 0xB7: num_key=15;
                                           break;
                                case 0x77: num_key=16;
                                           break;
                        }
                }
                temp_circle=_crol_(temp_circle,1);//改变扫描线
                if(temp_circle==0xEF)
                {
                        temp_circle=0xFE;
                }
        }
        return(num_key);//返回按键号
}

/*************************************************************************

未按键时,扫描线一直变化。
长按键时,扫描线不变化,使得该行按键变成了独立按键,这样的扫描效率极高。
如当按下P2.0线上的某个键时,程序将扫描到这个键,而后扫描线不变化,
当键盘程序连续10次进入时检测到10次按键有效,直到松手后扫描线才变化

*************************************************************************/

出145入215汤圆

发表于 2010-4-25 14:28:30 | 显示全部楼层
我也只是流水灯阶段啊

出0入0汤圆

发表于 2010-4-25 14:35:27 | 显示全部楼层
大谢,楼主功德无量!

出0入0汤圆

发表于 2010-4-25 14:39:50 | 显示全部楼层
挺好的

出0入0汤圆

发表于 2010-4-25 15:12:20 | 显示全部楼层
lz很不一般的见识  学习了

出0入0汤圆

发表于 2010-4-25 17:50:49 | 显示全部楼层
不错最好能打个包一次性的上传来,,顶一下

出0入0汤圆

发表于 2010-4-25 18:13:39 | 显示全部楼层
mark...

出0入0汤圆

发表于 2010-4-25 20:29:42 | 显示全部楼层
感谢楼中能在百忙之中献上这么好的资料!谢谢

出0入0汤圆

发表于 2010-4-25 21:00:07 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-25 21:35:38 | 显示全部楼层
MARK

出0入0汤圆

发表于 2010-4-25 21:40:23 | 显示全部楼层
关注

出0入0汤圆

发表于 2010-4-25 21:45:56 | 显示全部楼层
强帖留名

出0入0汤圆

发表于 2010-4-25 23:24:19 | 显示全部楼层
看完两章了,不错不错,

出0入0汤圆

发表于 2010-4-25 23:34:03 | 显示全部楼层
记号,此贴必火

出0入0汤圆

发表于 2010-4-26 08:08:34 | 显示全部楼层

出0入0汤圆

发表于 2010-4-26 09:12:08 | 显示全部楼层
我提一下, 刚按照LZ的方法重做了PWM控制流水灯。其中有部分有点问题。
             if(s_s8LedPWMCounter < LED_PWM_LIMIT_MIN)
            {
                s_u8LedDirection = 0 ;
                s_s8LedPWMCounter =  LED_PWM_LIMIT_MIN ;                 
            }     
  改为
  if(s_s8LedPWMCounter < =LED_PWM_LIMIT_MIN)
            {
                s_u8LedDirection = 0 ;
                s_s8LedPWMCounter =  LED_PWM_LIMIT_MIN ;                 
            }
这样才能持续看到效果。
另外LZ:帮忙再更新啊,期待啊,每天都在关注这帖子。

出0入0汤圆

发表于 2010-4-26 10:32:07 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-26 12:52:15 | 显示全部楼层

出0入8汤圆

发表于 2010-4-26 14:22:46 | 显示全部楼层
顶.

出0入0汤圆

发表于 2010-4-26 16:42:41 | 显示全部楼层
太感动了,非常感谢楼主这样无私的人!楼主值得我们学习!努力学习中。。。。。。。。

出0入0汤圆

 楼主| 发表于 2010-4-26 20:44:35 | 显示全部楼层
回复【173楼】zhangdya  
---------------------------------------------会--------------------------

不客气。这几天工作忙,没有更新!!!我尽快更新的!!!

出0入0汤圆

发表于 2010-4-26 20:58:37 | 显示全部楼层
回复【149楼】xshui
真是好帖,读后有茅塞顿开的感觉。如果还能多配合一些流程图等示意图的话,那效果更好。谢谢!
-----------------------------------------------------------------------

同感

出0入0汤圆

发表于 2010-4-27 17:13:10 | 显示全部楼层
真是好东西,我还处在流水灯的阶段。

出0入0汤圆

发表于 2010-4-27 18:53:26 | 显示全部楼层
真是好东西啊

出0入0汤圆

发表于 2010-4-27 23:13:42 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-28 09:25:33 | 显示全部楼层
lz拜托继续更新啊。

出0入0汤圆

发表于 2010-4-28 16:05:54 | 显示全部楼层
好帖 楼主把全部篇章都传上来

出0入0汤圆

发表于 2010-4-28 17:37:55 | 显示全部楼层
留个记号,以后看

出0入0汤圆

发表于 2010-4-28 19:04:02 | 显示全部楼层
支持,好帖。

出0入0汤圆

发表于 2010-4-28 19:52:36 | 显示全部楼层
学习了,再顶。

出0入0汤圆

 楼主| 发表于 2010-4-28 20:35:14 | 显示全部楼层
好几天没有更新了,呵呵~~今天我把咱们常用的传感器DS1320 DS18B20给大家介绍下。

对于市面上的大多数51单片机开发板来说。ds1302和ds18b20应该是比较常见的两种外围芯片。ds1302是具有SPI总线接口的时钟芯片。ds18b20则是具有单总线接口的数字温度传感器。下面让我们分别来认识并学会应用这两种芯片。
首先依旧是看DS1302的datasheet中的相关介绍。

(原文件名:1.jpg)


上面是它的一些基本的应用介绍。
下面是它的引脚的描述。


(原文件名:2.jpg)

下面是DS1302的时钟寄存器。我们要读取的时间数据就是从下面这些数据寄存器中读取出来的。当我们要想调整时间时,可以把时间数据写入到相应的寄存器中就可以了。

(原文件名:3.jpg)

这是DS1302内部的31个RAM寄存器。在某些应用场合我们可以应用到。如我们想要做一个带定时功能的闹钟。则可以把闹钟的时间写入到31个RAM寄存器中的任意几个。当单片机掉电时,只要我们的DS1302的备用电池还能工作,那么保存在其中的闹钟数据就不会丢失~~

(原文件名:4.jpg)


由于对于这些器件的操作基本上按照数据手册上面提供的时序图和相关命令字来进行操作就可以了。因此在我们应用这些器件的时候一定要对照着手册上面的要求来进行操作。如果觉得还不够放心的话。可以到网上下载一些参考程序。对着手册看别人的程序,看别人的思路是怎么样的。
    DS1302和单片机的连接很简单。只需一根复位线,一根时钟线,一根数据线即可。同时它本身还需要接一个32.768KHz的晶振来提供时钟源。对于晶振的两端可以分别接一个6PF左右的电容以提高晶振的精确度。同时可以在第8脚接上一个3.6V的可充电的电池。当系统正常工作时可以对电池进行涓流充电。当系统掉电时,DS1302由这个电池提供的能量继续工作。
    下面让我们来驱动它。
    sbit io_DS1302_RST  = P2^0 ;
    sbit io_DS1302_IO    = P2^1 ;
    sbit io_DS1302_SCLK = P2^2 ;
   
    //-------------------------------------常数宏---------------------------------//
    #define DS1302_SECOND_WRITE        0x80          //写时钟芯片的寄存器位置
    #define DS1302_MINUTE_WRITE        0x82
    #define DS1302_HOUR_WRITE        0x84
    #define DS1302_WEEK_WRITE        0x8A
    #define DS1302_DAY_WRITE        0x86
    #define DS1302_MONTH_WRITE        0x88
    #define DS1302_YEAR_WRITE        0x8C

    #define DS1302_SECOND_READ        0x81          //读时钟芯片的寄存器位置
    #define DS1302_MINUTE_READ        0x83
    #define DS1302_HOUR_READ        0x85
    #define DS1302_WEEK_READ        0x8B
    #define DS1302_DAY_READ        0x87
    #define DS1302_MONTH_READ        0x89
    #define DS1302_YEAR_READ        0x8D
//-----------------------------------操作宏----------------------------------//
    #define DS1302_SCLK_HIGH        io_DS1302_SCLK = 1 ;
    #define    DS1302_SCLK_LOW            io_DS1302_SCLK = 0 ;
   
    #define DS1302_IO_HIGH            io_DS1302_IO  = 1 ;
    #define DS1302_IO_LOW            io_DS1302_IO  = 0 ;
    #define DS1302_IO_READ          io_DS1302_IO

    #define DS1302_RST_HIGH            io_DS1302_RST  = 1 ;
    #define DS1302_RST_LOW          io_DS1302_RST  = 0 ;




/******************************************************
* 保存时间数据的结构体        *
******************************************************/
struct
{
    uint8 Second ;
    uint8 Minute ;
    uint8 Hour ;
    uint8 Day ;
    uint8 Week ;
    uint8 Month ;
    uint8 Year ;
}CurrentTime ;



/******************************************************************************
* Function:    static void v_DS1302Write_f( uint8 Content )        *
* Description:向DS1302写一个字节的内容            *
* Parameter:uint8 Content :    要写的字节            *
*                        *
******************************************************************************/

static void v_DS1302Write_f( uint8 Content )
{
    uint8 i ;
    for( i = 8 ; i > 0 ; i-- )
    {
        if( Content & 0x01 )
        {
            DS1302_IO_HIGH
        }
        else
        {
            DS1302_IO_LOW
        }
        Content >>= 1 ;

        DS1302_SCLK_HIGH  
        DS1302_SCLK_LOW
    }
}


/******************************************************************************
* Function:    static uint8 v_DS1302Read_f( void )          *
* Description: 从DS1302当前设定的地址读取一个字节的内容    *
* Parameter:                    *
* Return:    返回读出来的值(uint8)                *
******************************************************************************/
static uint8 v_DS1302Read_f( void )
{
    uint8 i, ReadValue ;
    DS1302_IO_HIGH
    for( i = 8 ; i > 0 ; i-- )
    {
        ReadValue >>= 1 ;
          if( DS1302_IO_READ )
        {
            ReadValue |= 0x80 ;
        }
        else
        {
            ReadValue &= 0x7f ;
        }

        DS1302_SCLK_HIGH
        DS1302_SCLK_LOW
        
    }
    return ReadValue ;
}

/******************************************************************************
* Function:    void v_DS1302WriteByte_f( uint8 Address,  uint8 Content )    *
* Description:  从DS1302指定的地址写入一个字节的内容              *
* Parameter:  Address:    要写入数据的地址            *
*    Content:    写入数据的具体值            *
* Return:                                                                                                                *
******************************************************************************/
void v_DS1302WriteByte_f( uint8 Address,  uint8 Content )
{
    DS1302_RST_LOW
    DS1302_SCLK_LOW
    DS1302_RST_HIGH   

    v_DS1302Write_f( Address ) ;
    v_DS1302Write_f( Content ) ;

    DS1302_RST_LOW
    DS1302_SCLK_HIGH
}

/******************************************************************************
* Function:    uint8 v_DS1302ReadByte_f( uint8 Address )                          *
* Description:从DS1302指定的地址读出一个字节的内容        *
* Parameter:Address:    要读出数据的地址            *
*                        *
* Return:        指定地址读出的值(uint8)        *
******************************************************************************/
uint8 v_DS1302ReadByte_f( uint8 Address )
{
    uint8 ReadValue ;
    DS1302_RST_LOW
    DS1302_SCLK_LOW
    DS1302_RST_HIGH   

    v_DS1302Write_f( Address ) ;
    ReadValue = v_DS1302Read_f() ;

    DS1302_RST_LOW
    DS1302_SCLK_HIGH
    return ReadValue ;
}
/******************************************************************************
* Function:    void v_ClockInit_f( void )            *
* Description:初始化写入DS1302时钟寄存器的值(主程序中只需调用一次即可) *
* Parameter:                                                                                                                                        *
*                                              *
* Return:                            *
******************************************************************************/
void v_ClockInit_f( void )
{
    if( v_DS1302ReadByte_f( 0xc1) != 0xf0 )
    {
        v_DS1302WriteByte_f( 0x8e, 0x00 ) ;    //允许写操作
        v_DS1302WriteByte_f( DS1302_YEAR_WRITE, 0x08 ) ;    //年
        v_DS1302WriteByte_f( DS1302_WEEK_WRITE, 0x04 ) ;    //星期
        v_DS1302WriteByte_f( DS1302_MONTH_WRITE, 0x12 ) ;    //月
        v_DS1302WriteByte_f( DS1302_DAY_WRITE, 0x11 ) ;          //日
        v_DS1302WriteByte_f( DS1302_HOUR_WRITE, 0x13 ) ;    //小时
        v_DS1302WriteByte_f( DS1302_MINUTE_WRITE, 0x06 ) ;    //分钟
        v_DS1302WriteByte_f( DS1302_SECOND_WRITE, 0x40 ) ;    //秒
        v_DS1302WriteByte_f( 0x90, 0xa5 ) ;                      //充电
    v_DS1302WriteByte_f( 0xc0, 0xf0 ) ;            //判断是否初始化一次标识写入
        v_DS1302WriteByte_f( 0x8e, 0x80 ) ;                      //禁止写操作
    }
}
/******************************************************************************
* Function:    void v_ClockUpdata_f( void )            *
* Description:读取时间数据,并保存在结构体CurrentTime中        *
* Parameter:                                                                                                                                                      *
*                        *
* Return:                                                                                                                                          *
******************************************************************************/
void v_ClockUpdata_f( void )
{
    CurrentTime.Second = v_DS1302ReadByte_f( DS1302_SECOND_READ ) ;
    CurrentTime.Minute = v_DS1302ReadByte_f( DS1302_MINUTE_READ ) ;
    CurrentTime.Hour  = v_DS1302ReadByte_f( DS1302_HOUR_READ ) ;
    CurrentTime.Day    = v_DS1302ReadByte_f( DS1302_DAY_READ ) ;
    CurrentTime.Month  = v_DS1302ReadByte_f( DS1302_MONTH_READ ) ;
    CurrentTime.Week  = v_DS1302ReadByte_f( DS1302_WEEK_READ ) ;
    CurrentTime.Year  = v_DS1302ReadByte_f( DS1302_YEAR_READ ) ;
}

有了上面的这些函数我们就可以对DS1302进行操作了。当我们想要获取当前时间时,只需要调用v_ClockUpdata_f( void )这个函数即可。读取到的时间数据保存在CurrentTime这个结构体中。至于如何把时间数据在数码管或者是液晶屏上显示出来我相信大家应该都会了吧^_^.

看看显示效果如何~~

(原文件名:5.jpg)

下面再让我们看看DS18B20吧。
DS18B20是单总线的数字温度传感器。其与单片机的接口只需要一根数据线即可。当然连线简单意味着软件处理上可能要麻烦一点。下面来看看它的优点:

(原文件名:1.jpg)


看看它的靓照。外形和我们常用的三极管没有什么两样哦。

(原文件名:2.jpg)

   
DS18B20的内部存储器分为以下几部分
ROM:存放该器件的编码。前8位为单线系列的编码(DS18B20的编码是19H)后面48位为芯片的唯一序列号。在出场的时候就已经设置好,用户无法更改。最后8位是以上56位的CRC码。

(原文件名:3.jpg)

RAM:DS18B20的内部暂存器共9个字节。其中第一个和第二个字节存放转换后的温度值。第二个和第三个字节分别存放高温和低温告警值。(可以用RAM指令将其拷贝到EEPROM中)第四个字节为配置寄存器。第5~7个字节保留。第9个字节为前8个字节的CRC码。
   
DS18B20的温度存放如上图所示。其中S位符号位。当温度值为负值时,S = 1 ,反之则S = 0 。我们把得到的温度数据乘上对应的分辨率即可以得到转换后的温度值。
DS18B20的通讯协议:
    在对DS18B20进行读写编程时,必须严格保证读写的时序。否则将无法读取测温结果。根据DS18B20的通讯协议,主机控制DS18B20完成温度转换必须经过3个步骤:每一次读写之前都要对DS18B20进行复位,复位成功后发送一条ROM指令,最后发送RAM指令。这样才能对DS18B20进行预定的操作。
复位要求主机将数据线下拉500us,然后释放,DS18B20收到信号后等待16~160us然后发出60~240us的存在低脉冲,主机收到此信号表示复位成功。

(原文件名:4.jpg)


上图即DS18B20的复位时序图。
下面是读操作的时序图

(原文件名:5.jpg)
   
这是写操作的时序图

(原文件名:6.jpg)


下面让我们来看看它的驱动程序如何写吧。

sbit io_DS18B20_DQ  = P2^3 ;
#define DS18B20_DQ_HIGH  io_DS18B20_DQ = 1 ;
#define DS18B20_DQ_LOW  io_DS18B20_DQ = 0 ;
#define DS18B20_DQ_READ  io_DS18B20_DQ

/*******************************************************************
* 保存温度值的数组.依次存放正负标志,温度值十位,个位,和小数位        *
*******************************************************************/
uint8 Temperature[ 4 ] ;

void v_Delay10Us_f( uint16 Count )
{
    while( --Count )
    {
        _nop_();
    }
}

/**************************************************************************
* Function:        uint8 v_Ds18b20Init_f( void )                                *
* Description:    初始化DS18B20                                                *
* Parameter:                                                                  *
*                                                                          *
* Return:        返回初始化的结果(0:复位成功  1:复位失败)                        *
**************************************************************************/
uint8 v_Ds18b20Init_f( void )
{
    uint8 Flag ;
    DS18B20_DQ_HIGH          //稍作延时
    v_Delay10Us_f( 3 ) ;
    DS18B20_DQ_LOW          //总线拉低

    v_Delay10Us_f( 80 ) ; //延时大于480us

    DS18B20_DQ_HIGH      //总线释放

    v_Delay10Us_f( 15 ) ;

    Flag = DS18B20_DQ_READ ;  //如果Flag为0,则复位成功,否则复位失败
    return Flag ;
}
/******************************************************************************
* Function:        void v_Ds18b20Write_f( uint8 Cmd )                                *
* Description:    向DS18B20写命令                                                *
* Parameter:    Cmd:    所要写的命令                                              *
*                                                                              *
* Return:                                                                        *
******************************************************************************/
void v_Ds18b20Write_f( uint8 Cmd )
{
    uint8 i ;
    for( i = 8 ; i > 0 ; i-- )
    {
        DS18B20_DQ_LOW            //拉低总线,开始写时序
        DS18B20_DQ_READ = Cmd & 0x01  ;    //控制字的最低位先送到总线
        v_Delay10Us_f( 5 ) ;            //稍作延时,让DS18B20读取总线上的数据
        DS18B20_DQ_HIGH            //拉高总线,1bit写周期结束
        Cmd >>= 1 ;
    }
}



/******************************************************************************
* Function:        uint8 v_Ds18b20Read_f( void )                                      *
* Description:    向DS18B20读取一个字节的内容                                    *
* Parameter:                                                                      *
*                                                                              *
* Return:        读取到的数据                                                    *
******************************************************************************/
uint8 v_Ds18b20Read_f( void )
{
    uint8 ReadValue, i ;
    for( i = 8 ; i > 0 ; i-- )
    {
        DS18B20_DQ_LOW   
        ReadValue >>= 1 ;
        DS18B20_DQ_HIGH
        if( DS18B20_DQ_READ == 1 )
        ReadValue |= 0x80 ;
        v_Delay10Us_f( 3 ) ;
    }
    return ReadValue ;
}

/******************************************************************************
* Function:        uint16 v_Ds18b20ReadTemp_f( void )                                  *
* Description:    读取当前的温度数据(只保留了一位小数)                                *
* Parameter:                                                                        *
*                                                                                *
* Return:        读取到的温度值                                                    *
******************************************************************************/
uint16 v_Ds18b20ReadTemp_f( void )
{
    uint8 TempH, TempL ;
    uint16 ReturnTemp ;
   
/*    if( v_Ds18b20Init_() ) return ;  //复位失败,在这里添加错误处理的代码      */
    v_Ds18b20Init_f() ;            /复位DS18B20
    v_Ds18b20Write_f( 0xcc ) ;    //跳过ROM
    v_Ds18b20Write_f( 0x44 ) ;        //启动温度转换
    v_Ds18b20Init_f() ;            
    v_Ds18b20Write_f( 0xcc ) ;    //跳过ROM
    v_Ds18b20Write_f( 0xbe ) ;    //读取DS18B20内部的寄存器内容
    TempL = v_Ds18b20Read_f() ;    //读温度值低位(内部RAM的第0个字节)
    TempH = v_Ds18b20Read_f() ;    //读温度值高位(内部RAM的第1个字节)
    ReturnTemp = TempH ;
    ReturnTemp <<= 8 ;
    ReturnTemp |= TempL ;    //温度值放在变量ReturnTemp中
    return ReturnTemp ;
}
/******************************************************************************
* Function:    void v_TemperatureUpdate_f( void )        *
* Description:读取当前的温度数据并转化存放在数组Temperature(只保留了一位小数) *
* Parameter:                                                                                                                                          *
*                            *
* Return:                                                                                                                                            *
******************************************************************************/
void v_TemperatureUpdate_f( void )
{
    uint8 Tflag = 0 ;
    uint16 TempDat ;
    float Temp ;
    TempDat = v_Ds18b20ReadTemp_f() ;
    if( TempDat & 0xf000 )
    {
        Tflag = 1 ;                          
        TempDat = ~TempDat + 1 ;
    }
    Temp =    TempDat >> 4;  (TempDat * 0.0625  )  请大家不要用乘以,不知道为什么可以看我上面的贴子      
    TempDat = Temp * 10          ;                 ;小数部用可以用查表法,大家有什么好办法来讨论下,呵呵
    Temperature[ 0 ] = Tflag ;          //温度正负标志
    Temperature[ 1 ] = TempDat / 100  + '0' ;      //温度十位值
    Temperature[ 2 ] = TempDat % 100 / 10 + '0' ;  //温度个位值
    Temperature[ 3 ] = TempDat % 10        + '0' ;//温度小数位
}
如果想获取当前的温度数据,在主函数中调用v_TemperatureUpdate_f( void )就可以了。温度数据就保存到Temperature中去了。至于如何显示,就不用多说了吧~__@~
  
时间和温度一起显示出来看看

(原文件名:7.jpg)


OK,至此ds18b20和ds1302的应用告一段落。如果有不懂的,记得多看datasheet,多交流。
原文地址:http://www.eehome.cn/read.php?tid=14139

出0入0汤圆

 楼主| 发表于 2010-4-28 20:54:44 | 显示全部楼层
看的人多了,我明天再给大家分享下  红外摇控解码教程。让大家可以远程控制!!!呵呵~~~

出0入0汤圆

发表于 2010-4-28 21:15:20 | 显示全部楼层
顶,太棒了

出0入0汤圆

发表于 2010-4-28 21:32:19 | 显示全部楼层
mark!!!

出0入0汤圆

发表于 2010-4-28 21:41:11 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-28 22:15:50 | 显示全部楼层

出0入0汤圆

发表于 2010-4-29 11:28:43 | 显示全部楼层
留印。。。有空细看

出0入0汤圆

发表于 2010-4-29 11:52:32 | 显示全部楼层
希望楼主能进一步讲讲程序中怎么能少用点全局变量,怎么能多点结构体,联合体,指针,指针函数,函数指针这一类复杂的东西,结合实际最好~~

出0入0汤圆

发表于 2010-4-29 18:34:08 | 显示全部楼层
记号一下, 有空细看

出0入0汤圆

发表于 2010-4-29 19:16:24 | 显示全部楼层
有些触动,我是初学者,也许这资料能给我些帮助 谢谢LZ

出0入0汤圆

发表于 2010-4-29 19:46:08 | 显示全部楼层
写的很好!标记!帮顶!

出0入0汤圆

 楼主| 发表于 2010-4-29 21:48:07 | 显示全部楼层
回复【193楼】dong25123560  
-----------------------------------------------------------------------

其实这个资料不适合刚入门的同学。

出0入0汤圆

发表于 2010-4-30 09:17:57 | 显示全部楼层
如果大家需要,我把后面也传上来!

KEY篇第一章----按键程序编写的基础

KEY篇第二章----基于状态转移的独立按键程序设计

LED篇第三章----模块化编程初识

LED篇第四章----渐明渐暗的灯

LED篇第五章----多任务环境下的数码管编程设计

好像没上传吧 希望能上传  好东西   谢谢楼主了

出0入0汤圆

发表于 2010-4-30 09:28:16 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-30 09:28:42 | 显示全部楼层
刚来 不熟悉 原来是隐藏了

出0入0汤圆

发表于 2010-5-1 16:23:25 | 显示全部楼层
大家好,初学者,在DS1302的使用,有点不明白,希望大家指点指点,谢谢
void v_ClockInit_f( void )  
{  
    if( v_DS1302ReadByte_f( 0xc1) != 0xf0 )//这一句有什么用???  
    {  
        v_DS1302WriteByte_f( 0x8e, 0x00 ) ;    //允许写操作  
        v_DS1302WriteByte_f( DS1302_YEAR_WRITE, 0x08 ) ;    //年  
        v_DS1302WriteByte_f( DS1302_WEEK_WRITE, 0x04 ) ;    //星期  
        v_DS1302WriteByte_f( DS1302_MONTH_WRITE, 0x12 ) ;    //月  
        v_DS1302WriteByte_f( DS1302_DAY_WRITE, 0x11 ) ;          //日  
        v_DS1302WriteByte_f( DS1302_HOUR_WRITE, 0x13 ) ;    //小时  
        v_DS1302WriteByte_f( DS1302_MINUTE_WRITE, 0x06 ) ;    //分钟  
        v_DS1302WriteByte_f( DS1302_SECOND_WRITE, 0x40 ) ;    //秒  
        v_DS1302WriteByte_f( 0x90, 0xa5 ) ;                      //充电  
    v_DS1302WriteByte_f( 0xc0, 0xf0 ) ;            //判断是否初始化一次标识写入  
        v_DS1302WriteByte_f( 0x8e, 0x80 ) ;                      //禁止写操作  
    }  
}

出0入0汤圆

发表于 2010-5-1 21:24:02 | 显示全部楼层
有用,期待下文。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-15 04:22

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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