矿石收音机论坛

 找回密码
 加入会员

QQ登录

只需一步,快速开始

搜索
楼主: wey05

帮助新手学习单片机数码管电子钟:原理,c程序详解

  [复制链接]
     
发表于 2011-4-21 22:48:19 | 显示全部楼层
millwood兄的是在美国?
2011-4-21 05:18  只看该作者
2011-4-21 20:07  只看该作者
这2个时间点之间相差大约9个小时,如果这是工作时间,那么
millwood兄午饭时间大约是北京时间凌晨0点,和这里大家午饭时间相差12个小时,
所以推出millwood兄在美国,不知道对不
回复 支持 反对

使用道具 举报

     
发表于 2011-4-21 23:17:34 | 显示全部楼层

回复 15# Paktu 的帖子

真是受教了 ,一般书上定时器2讲的都很少,
大部分都是用定时器1和2的,还有wey05兄
“这两位00:方式0,13位计数,01:方式1:16位计数。10,方式3,自动加载8位计数,11,方式4;两个8位计数器,现在我们使用16为计数器,所以M1为0,M0为1.”
这段话显然少了方式2 ,同时方式4要改为方式3。
当然也很感谢wey05兄的帖子,学习了。
回复 支持 反对

使用道具 举报

发表于 2011-4-22 00:16:04 | 显示全部楼层
这个能详细注释下或讲下不?


it is fairly simple. the xtal frequency is specified as F_XTAL. if you are running the code on a 12-state 8051, the timer is driven by F_XTAL / 12 = F_CPU. so if the timer has counted F_CPU ticks, you know that a second has passed.

in the isr, since we are running a 16-bit timer, the number of ticks between each isr is 0x10000ul. so you increment the number of timer ticks in rtc_counter by 0x10000ul. once rtc_counter is greater than PERIOD, you reset rtc_counter, by subtracting PERIOD from it in order to maintain long-term timing accuracy.

the timer is free running in this case.

this approach applies to pretty much any mcu and can get you really long timing intervals with minimum efforts on the part of the programmer.

you can also implement the ability to correct timing errors here, either via F_XTAL, or explicitly through PERIOD_ERR.
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-4-22 00:43:58 | 显示全部楼层
原帖由 xiaomu 于 2011-4-21 23:17 发表
真是受教了 ,一般书上定时器2讲的都很少,
大部分都是用定时器1和2的,还有wey05兄
“这两位00:方式0,13位计数,01:方式1:16位计数。10,方式3,自动加载8位计数,11,方式4;两个8位计数器,现在我们使用 ...

谢谢指正!只有0,1,2,3四种方式,怎么跑出4来了呢?打错了。谢谢!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-4-22 01:10:52 | 显示全部楼层
原帖由 Paktu 于 2011-4-21 08:48 发表
怎么不用51单片机的T2定时器?16位自动重载方式定时的误差为0.

对不起,先头没仔细看,你说的很对,应用自动重载可以消除误差,但是51里面没有T2,只有T0 T1 .T0 T1自动重载只能是8位的,用12兆晶振可以。52里面有T2可以使用16位自动重载。这样结合millwood的方法就可以最大限度消除误差。这次考虑到一般性所以没有使用T2,最后再补充一下。谢谢!
回复 支持 反对

使用道具 举报

发表于 2011-4-22 01:28:28 | 显示全部楼层
here is what I put together quickly.


===========================
#include <regx51.h>                                        //we use keil c51
#include <stdio.h>                                        //we use sprintf
#include "gpio.h"
#include "delay.h"                                        //we use delay routines
#include "rtc0.h"                                        //we use tmr0
#include "lcd_4bit.h"                                //we use lcd1602, 4bit mode

//hardware configuration
#define RTC_PERIOD                RTC_500ms        //rtc runs at 500ms to generate halfsec clicks
//end hardware configuration

typedef struct {
        unsigned char halfsec;                        //half a second: 0=1st half, 1=2nd half
        unsigned char sec;                                //second
        unsigned char min;                                //minute
        unsigned char hour;                                //hour
        unsigned char day;                                //day. 0=starting day
} RTC_TIME_t;                                                //rtc time type

//global variable
code const unsigned char str0[]="AT89C51 RTC Demo";
code const unsigned char str1[]="time=           ";
unsigned char vRAM[17];                                //display buffer
volatile RTC_TIME_t _rtc_time;                                //global variable updated by rtc_update()

//update _rtc_time in the isr
void rtc_update(void) {                                //update time
        if (_rtc_time.halfsec==0) {                //1st half?
                _rtc_time.halfsec=1;                //make it 2nd half
        } else {
                _rtc_time.halfsec=0;
                _rtc_time.sec+=1;                        //increment to the next second
                if (_rtc_time.sec>=60) {        //second overflown?
                        _rtc_time.sec-=60;                //reset the second
                        _rtc_time.min+=1;                //increment min
                        if (_rtc_time.min>=60) {        //min overflown?
                                _rtc_time.min-=60;                //reset min
                                _rtc_time.hour+=1;                //increment hour
                                if (_rtc_time.hour>=24) {        //hour overflown
                                        _rtc_time.hour-=24;                //reset hour
                                        _rtc_time.day+=1;                //increment day
                                }
                        }
                }
        }

}

//set rtc_time
void rtc_set(RTC_TIME_t time) {
        _rtc_time.day=time.day;
        _rtc_time.hour=time.hour;
        _rtc_time.min=time.min;
        _rtc_time.sec=time.sec;
        _rtc_time.halfsec=time.halfsec;

        //alternatively, use memcpy()
}

//read the rtc
void rtc_get(RTC_TIME_t * time_ptr) {
        time_ptr->halfsec=_rtc_time.halfsec;
        time_ptr->sec=_rtc_time.sec;
        time_ptr->min=_rtc_time.min;
        time_ptr->hour=_rtc_time.hour;
        time_ptr->day=_rtc_time.day;

        //alternatively, use memcpy()
}


void mcu_init(void) {                                //reset the mcu
}

int main(void) {
        RTC_TIME_t time ={0, 58, 59, 23, 0};                                        //rtc time
       
        mcu_init();                                                //reset the mcu
       
        //set up the lcd
        lcd_init();                                                //reset the lcd
        //display the first line on the lcd
        lcd_display(LCD_Line0, str0);        //display str0

        //set up the rtc
        rtc0_init(RTC_PERIOD);                        //reset the rtc to run isr every 0.5 second
        rtc0_act(rtc_update);                        //run rtc_update() in the tmr0 isr
        ei();                                                        //enable global interrupt
       
        //initialize rtc
        rtc_set(time);                                        //initialize rtc
        while (1) {
                rtc_get(&time);                                //read the rtc
                if (time.halfsec==0) sprintf(vRAM, "time=%2d-%02d:%02d:%02d", (unsigned short) time.day, (unsigned short) time.hour, (unsigned short) time.min, (unsigned short) time.sec);
                else sprintf(vRAM, "time=%2d-%02d:%02d %02d", (unsigned short) time.day, (unsigned short) time.hour, (unsigned short) time.min, (unsigned short) time.sec);
                lcd_display(LCD_Line1, vRAM);
                delay_ms(100);
        }
}

========================
the code runs tmr0 to interrupt once every RTC_PERIOD(=500ms). the time is kept in _rtc_time. two routines, rtc_set() and rtc_get() writes/reads from rtc_time. the time is then displayed via lcd_display().

at this level, the code doesn't directly touch the  hardware so it is 100% portable to any mcu: all you need is to write your own low-level routines.

the code doesn't keep a calendar or alarm but that's not difficult to add.
回复 支持 反对

使用道具 举报

     
发表于 2011-4-22 08:41:40 | 显示全部楼层
原帖由 wey05 于 2011-4-21 14:41 发表

是的,你说得对,用方式2最好,但因为这里用的是现成的STC板子,为了保证232通信速率没有误差,晶振是11.0592兆赫,要是自己使用51板和12兆晶振,机器周期是整数1微秒就好了
另外这只是个例子,旨在说明51的基本实 ...

-------------------------------------------------------------------------------------------
好吧,我说说用11.0592MHz时的方法吧——先看一些数字:

11059200÷12=921600
921600÷240=3840

如果将定时器T2设置为自动重载方式,设置RACP2H:RACP2L=65536-3840=0xF100
那么每秒中断的次数就是240次,对于你的8个数码管,那就是每秒将每个数码管扫描240÷8=30次,让你看不出有闪烁,而且每次中断期间可以执行的指令数量大约是3840÷2=1920条,你能干不少事情了。

所以扫描数码管的事情就放在T2中断中进行,每次中断时只点亮一个数码,下一次中断时点亮下一个数码管。因此你只要设置好一个显示缓冲区,在中断时从缓冲区取数字显示即可,不需要你在主程序中控制显示,你只要在规定的时间去修改缓冲区中的数值即可。

在定时器中断中还可以设定1s时间源,那就是240次中断的时间为1s,所以你可以在定时器中断中设置一个变量k,k从0开始累加,到240时表示到1s了,此时能做的事情就是:
        S++;
        if(S==60){M++;S=0;}
        if(M==60){H++;M=0;}
        if(H==24){H=0;DD++;}
这可以在中断中完成,如果认为不太好就放在主程序中完成。

你还可以在中断中设置一段程序去扫描按键的情况,在确认有按键之后设置标志位,然后在主程序中区检查标志位并处理按键,这样实时性非常好。
还有很多。
回复 支持 反对

使用道具 举报

     
发表于 2011-4-22 08:48:49 | 显示全部楼层
To millwood @Floor26

We shared some idea!

But the sprintf() function is not a good solution.The time mubers in range of 0~59 or 0~23,so they can be divided by 10,so:
x=sec/10+0x30;
y=sec%10+0x30;

In assemble,the code is:

mov   a,sec
mov   b,#10
div      ab
add    a,#30h
mov   x,a
orl      b,#30h
mov   y,b
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-4-22 10:28:27 | 显示全部楼层
原帖由 Paktu 于 2011-4-22 08:41 发表

-------------------------------------------------------------------------------------------
好吧,我说说用11.0592MHz时的方法吧——先看一些数字:

11059200÷12=921600
921600÷240=3840

如果将定 ...

谢谢指教!
用T2的16位自动重载是很好实际上110592在16位情况可以得到整数毫秒中断以消除计时误差,但是51没有T2,52才有,所以51要想别的办法,这只是个针对初学者的例子,51比比较具有一般性,所以没有深入探究。
回复 支持 反对

使用道具 举报

发表于 2011-4-22 18:54:41 | 显示全部楼层
you can emulate auto reload in software as well, by loading the counters with offset in the isr:

//in your isr
  TH0+=MSB(offset);  //load th0 with the most significant byte of the offset
  TL0+=LSB(offset);  //load tl0 with the least significant byte of the offset


the trick here is the increment operator used, rather than an assignment operator that most people use. by using the increment operator,  you have preserved the value in TH0/TL0 so isr latency has no impact on the long-term accuracy of the timer.

however, there is one issue: then you do "TL0+=LSB(offset);", it may take more than one cpu ticks to execute: the cpu reads TL0, add to it LSB(offset) and then reload it back to TL0. during that time, TL0 will have incremented a few ticks. so the right thing to do is to add an error term to that:

  TL0+=LSB(offset) + TL0_ERR; //where TL0_ERR is the error term, typically 2 - 3 ticks.

a better way is to never touch TL0 and let's it be a free running counter.
回复 支持 反对

使用道具 举报

     
发表于 2011-4-22 21:54:17 | 显示全部楼层

回复 28# Paktu 的帖子

to Paktu and millwood兄
我在89s52(Atmel)上开启了T2定时器(16位自动加载方式),
中断 函数大致如下,我的目的是利用T2的定时功能,把IO口
置高置低,输出一定频率f、 一定占空比ratio的方波去一个模块的控制端,
控制模块的工作和停止,控制的频率设定在1Hz~1kHz。
isr() interrupt 5
{
        TF2 = 0;       
        if(++timecount== hightime)/*hightime是周期方波中“1”的时间*/
        {
                P1_0 =0;       
        }
        else if(timecount == period)
        {
                 P1_0 = 1;
        }
}
我用单位的示波器(都是高级货)看了下I/O口输出方波的波形,
经过尝试,发现能输出的最高频率也就50Khz(占空比50%情况下)左右,再高
方波波形很不稳,上跳沿和下跳沿在时间轴上前后抖动很厉害。
目前我T2 设定50us中断一次,输出的方波(50%)波形还可接受,但把示波器辉度
打高,可看到上跳沿和下跳沿在时间上抖动还是有的,我又看了下单片机晶振管脚
12MHz的波形,辉度打高,可以看到虽然幅度上有些跳刺,但波形在时间上的抖动相比之下
要小的多,就是这些情况,当然我知道avr单片机有专门的PWM输出功能,但我没用过avr,
目前就是89s52了,如果想改善 (一定频率f一定占空比ratio)方波波形在跳变时的抖动
如何为之呢?有劳啦:)
回复 支持 反对

使用道具 举报

发表于 2011-4-22 22:46:35 | 显示全部楼层

回复 31# millwood 的帖子

why you like to speak in english ?
回复 支持 反对

使用道具 举报

发表于 2011-4-22 23:26:00 | 显示全部楼层
发现能输出的最高频率也就50Khz


yeah, because of isr latency. it depends on the compiler/chip but 20 ticks for isr latency isn't uncommon. so even if your isr does nothing, it will take 20us for the hardware to preserve context and jump to the isr. that means the fastest speed you get is about 1/20us = 50khz. I typically use 30 ticks as my minimum latency buffer.

if your timer trips more frequently than that, you will have the mcu going into isr constantly and how that works is basically unknown / unstable.

to increase the speed, you may need to run the mcu faster, or using hardware pwm.
回复 支持 反对

使用道具 举报

     
发表于 2011-4-23 11:14:48 | 显示全部楼层
32L:那个叫过冲,可以适当的加电容来补偿,但是电容不能太大,否则会让方波变形。

使用AVR单片机的PWM方式来产生PWM信号就太简单了,只要设置好几个寄存器,PWM输出就搞定了,想改变PWM的频率和占空比时只要修改两个寄存器的数值即可,比你用程序模拟的简单和方便吧?

比如使用Atmega8单片机,让OC1A和OC1B输出两路频率相同,占空比不一样的PWM,假如单片机工作频率是1MHz,输出PWM频率是1KHz,所以PWM可以精细到1K/1M=0.001,就是0.1%,只要写好这几个程序:

1.首先让端口B的OC1A和OC1B设置为输出:
DDRB=0x06;
2.设置定时器1为PWM方式:
TCCR1A=0xA2;
TCCR1B=0x19;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x03;                         //ICR=0x03E7=999,此时PWM的频率为1M÷(999+1)=1KHz
ICR1L=0xE7;
OCR1AH=0x00;                 //OCR1A=0x0064=100,OC1A输出PWM占空比为100÷(999+1)=10%
OCR1AL=0x64;
OCR1BH=0x00;
OCR1BL=0xC8;                 //OCR1B=0x00C8=200,OC1A输出PWM占空比为200÷(999+1)=20%

完成!
回复 支持 反对

使用道具 举报

     
发表于 2011-4-23 22:27:29 | 显示全部楼层

回复 35# Paktu 的帖子

0.1%这个精度太高了,目前我用89s52,最高输出频率1Khz,占空比只能达到5%,
另外我那个不是过冲,过冲指的是上跳沿有刺,把示波器时间打窄,可以看到在上升沿边缘有个 幅度的跳变,时间很短,经过振荡很快稳定到高电平 幅度水平线上,对应下跳沿也有个下冲。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 加入会员

本版积分规则

小黑屋|手机版|矿石收音机 ( 蒙ICP备05000029号-1 )

蒙公网安备 15040402000005号

GMT+8, 2024-5-3 11:27

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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