矿石收音机论坛

 找回密码
 加入会员

QQ登录

只需一步,快速开始

搜索
楼主: wey05

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

  [复制链接]
发表于 2011-7-11 09:25:08 | 显示全部楼层
wey05 你好!
这两天学习数码管显示。重新对数组的赋值进行了学习,收益不少。
开始看到你的代码中的uchar ucDispBuff[7]={0,0,10,0,0,0,0};我自己总认为0到8的值已经指定,后面显示应一直是大括号的数值。但是从新学习数组的时候,发现0可以是代表没有赋值,你的第3个值10表示的是横岗(就是减号)。看到这里就明白是怎么回事了。相当于后面在中断服务程序中从新对这个数组赋值。
另外我发现当不指定初始值的时候代码:uchar ucDispBuff[7]={};
然后在后面指定ucDispBuff[0]=hour/10;  //显示缓冲数组各位更新。小时十位
                 ucDispBuff[1]=hour%10;  //小时个位
                 ucDispBuff[3]=min/10;   //分十位
                 ucDispBuff[4]=min%10;   //分个位
                 ucDispBuff[5]=sec/10;   //秒十位
                 ucDispBuff[6]=sec%10;   //秒个位
         
                 ucDispBuff[2]=10;   //显示减号
这样编译下来的代码比实际先赋值ucDispBuff[7]={0,0,10,0,0,0,0};的代码要小很多,大概100多个字节。昨天一直没有想通为什么代码会小。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-7-11 10:35:11 | 显示全部楼层
对不起,millwood老师,先把你的程序整理一下,因为在0124x768显示单注释行被分为两行,或者直接用/* */好点
//time keeping on 8051

//tmr0 runs as a 16-bit rtc (black roman approach)

//tmr1 controls the 7-segment display



#include<regx51.h>            //we use keil c51

#include "gpio.h"

#include <intrins.h>



//hardware configuration

#define _7SEG_PORT       P2    //7seg connection, active low

#define _7SEG_DDR         P2

#define _7SEGs          0xff

#define _7SEG_OFF(segs)   {_7SEG_PORT= (segs);}    //turn on segments, active low

#define _7SEG_ON(segs)    {_7SEG_PORT=~(segs);}  //turn off segments



#define DIG_PORT      P3   //digits connection, active high

#define DIG_DDR       P3

#define DIGs         0xff

#define DIG_ON(digs)   {DIG_PORT= (digs);}   //turn on digs, active high

#define DIG_OFF(digs)  {DIG_PORT=~(digs);}   //turn off digs



#define HALF_SEC    (500000ul)      //half a second, in us

#define FULL_SEC     (2*HALF_SEC)     //a full second, in us

#define TMR0_PERIOD   HALF_SEC      //tmr0's period

#define TMR1_PERIOD   1000ul           //tmr1's period

#define TMR0_ERROR    0                   //tmr0 error term

//end hardware configuration



#define F_CPU     2000000ul      //cpu frequency, in hz

#define DLY_MS    100               //delay cycles to achieve 1ms at 1MIPS

#define MSB(word_t)    ((word_t) >> 8)    //word_t's most significant byte

#define LSB(word_t)    ((word_t) & 0x00ff)  //word_t's least significant byte



typedef struct {

        unsigned char half_sec;    //half second

        unsigned char sec;           //second

        unsigned char fen;           //minute

        unsigned char shi;           //hour

} _time_type;



//global variables

unsigned long _time_counter=0;   //time counter, in timer ticks

unsigned short _tmr1_period;       //tmr1 period

_time_type _time={                     //time for time keeping

        0, 59, 59, 23};                     //23:59:59:0

unsigned char vRAM[8];              //display buffer



unsigned char disp_on_off,disp,i,sec1,sec,fen,shi;



//sbit =P2;

//unsigned int count;



sbit k1=P1^0;                              //??,????  ???????



void delay(unsigned char dly) {

        while (dly--) continue;

}



void delay_ms(unsigned char ms) //????

{

        //unsigned char i,k;

        while (ms--)

                delay(DLY_MS * (F_CPU / 1000000ul));

}



/****************??0??***************/

void tmr0_init(void)

{

     TR0=0;                                         //stop tmr0

     TMOD= (TMOD & 0xf0) |   0x01;  //reset tmod's lower 4 bits
                                                       //tmr0 running in mode 1 (16-bit timer), not gated

        TH0=0;                                   //reset tmr0

        TL0=0;

        ET0=1;                                   //enable tmr0 interrupt

//        EA=1;

        TR0=1;

}



//update time

void update_time(_time_type * time) {

        _time_counter+=0x10000ul+TMR0_ERROR;   //increment time_counter;
//need to make sure that it doesn't overflow at high f_cpu

        if (_time_counter >= (TMR0_PERIOD * (F_CPU / 1000000ul))) {   
                //IO_FLP(P1, 1<<1);                                                //for testing  only

                _time_counter -= (TMR0_PERIOD * (F_CPU / 1000000ul));  //reset _time_counter

                time->half_sec+=1;                                                //increment half second

                if(time->half_sec==2)                                        //full second reached?

                {

                        time->half_sec=0;    //reset half second indicator

                        time->sec+=1;         //increment the second

                        if(time->sec==60)   //full 60 seconds passed?

                        {

                           time->sec=0;    //reset the second

                           time->fen+=1;     //increment the minute

                           if(time->fen==60)   //full 60 minutes passed?

                           {

                                   time->fen=0;       //reset the minute

                                   time->shi+=1;       //increment the hour

                                   if(time->shi==24)     //full 24 hour passed?

                                   {

                                           time->shi=0;      //reset the hour

                                   }

                                        vRAM[5]=0x10;           //update hour display

                                        vRAM[6]=time->shi%10;

                                        vRAM[7]=time->shi/10;

                                   }

                                vRAM[3]=time->fen%10;             //update minute display

                                vRAM[4]=time->fen/10;

                        }

                        vRAM[0]=time->sec%10;  //???????      //update second display

                        vRAM[1]=time->sec/10;

                }

                vRAM[2]=(time->half_sec)?0x10:0x7f;         //???? - blinking

        }

}





/****************??0??*************/

void tmr0_isr(void) interrupt TF0_VECTOR

{

        update_time(&_time);                           //update time keeping

}



//initialize the display

void _7seg_init(void) {

        _7SEG_OFF(_7SEGs);             //clear leds - turn off all segments

        IO_OUT(_7SEG_DDR, _7SEGs);      //leds as output



        DIG_OFF(DIGs);                             //all display off

        IO_OUT(DIG_DDR, DIGs);              //all digit pins as output



        //initialize vRAM

        vRAM[5]=0x10;                      //update hour display

        vRAM[6]=_time.shi%10;

        vRAM[7]=_time.shi/10;

        vRAM[3]=_time.fen%10;            //update minute display

        vRAM[4]=_time.fen/10;

        vRAM[0]=_time.sec%10;  //???????     //update second display

        vRAM[1]=_time.sec/10;

        vRAM[2]=(_time.half_sec)?0x10:0x7f;   //???? - blinking the half second indicator

}



//display the content of vRAM[8]

void _7seg_display(void) {

        unsigned char code _7seg_font[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x40};//???????

        //unsigned char _7seg_font[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; //???????????

        static unsigned char digit=0;        //digit to be displayed



        DIG_OFF(DIGs);                            //turn all digits off        

        _7SEG_ON(_7seg_font[vRAM[digit]]);        //display the digit

        DIG_ON(1<<digit);                                     //turn on the digit

        digit+=1;                                                    //increment the digit

        if (digit==8) digit=0;                                   //reset the digit

}



/****************??1??***************/

void tmr1_init(unsigned short period)

{

        _tmr1_period=period;                  //save tmr1_period

        TR1=0;                                         //stop tmr1

        TMOD=        (TMOD & 0x0f) | 0x10;      //reset tmod's higher 4 bits

                                                     //tmr1 running in mode 1 (16-bit timer), not gated

        TH1=MSB(-_tmr1_period);    //load up the offset

        TL1=LSB(-_tmr1_period);

        ET1=1;

        //EA=1;

        TR1=1;

}



/***************??1????***********/

void tmr1_isr(void) interrupt TF1_VECTOR

{

        //IO_FLP(P1, 1<<2);                           //for testing only

        TH1+=MSB(-_tmr1_period);                //load up the offset

        TL1+=LSB(-_tmr1_period);               //load up the offset

        _7seg_display();                                //display

}



void mcu_init(void) {

}



void main(void)        //???

{

        //shi=12;

        //fen=12;

        //P0=0xff;

        //disp_on_off=0xfe;        //?????

        //disp=0;

        mcu_init();                           //reset the mcu

        _7seg_init();                        //reset the 7seg display

        tmr0_init();                           //reset tmr0 - period set by TMR0_PERIOD

        tmr1_init(TMR1_PERIOD * (F_CPU / 1000000ul));    //reset tmr1

        EA=1;                                                                        //enable global interrupt



        while(1)

        {

                //delay();

        

        }

}
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-7-11 11:30:14 | 显示全部楼层
清风朋友:你好,你所说的数组定义如:
unsugned char array[5]={0,0,0,0,0};其中每个元素都是0,如果仅仅
unsigned char array[5];那么是内存划定一块5个字节大小的数组区域,每个元素的值是不确定的,编译时在用到它时才会对其中的元素赋值,而上一个则是连同数组定义带初始化需要把其中元素逐一赋值0;以后用到它再改变赋值。所以不赋值的代码比逐一赋值的就会小一些了。
回复 支持 反对

使用道具 举报

发表于 2011-7-11 15:17:59 | 显示全部楼层
本帖最后由 清风车影18 于 2011-7-11 15:20 编辑

wey05朋友:你好!
请教液晶问题。比如,我现在的液晶显示“正在打开第-?-路。”。其中汉字部分是固定的(相对于这个显示,可能还显示其他的内容比如“正在采集第?路”),这个时候是不允许其他按键动作的。如果这个时候有人按动按键,我希望液晶提示“正在。。。。不能操作”,几秒后返回原来显示的界面。
因为我最初液晶显示的更新只是第?路,其他的因为内容固定不做更新。
我的想法是在用户非法操作的时候,先将液晶显示的内容从液晶读取保存,然后清屏,显示错误提示。几秒后,将保存的数据在送入液晶从新显示。
不知道你有什么好的想法实现这个功能?

另外那个电子钟如果让那个减号闪动如何操作?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-7-11 17:30:57 | 显示全部楼层
本帖最后由 wey05 于 2011-7-11 17:33 编辑

打开?路,的?,写入lcd前,先存入static变量n中,显示“正在打开?路”没问题吧?在程序中设立一个static标志b,显示上述信息对应的操作时,b=1,在按键检测时如果b==1,那么显示出错提示,延时后(可以用定时器反复计数)恢复原来显示当然还是写n到lcd中。直到这项操作完成,改标志b=0,允许按键操作。static是局部静态变量,它的值是如同全局变量一样不改就不变的。
回复 支持 反对

使用道具 举报

发表于 2011-7-11 17:49:36 | 显示全部楼层
关键是这个液晶可能在程序初始化的时候显示一些特定的字符,这个字符在以后可能不更改。比如时间的减号,我用液晶显示后可以不用管它,他是一直显示的。
还有这个液晶显示的内容很多,不是同时显示的,比如说温度,我指定写入位置是第一行的第二个位置开始,打开几路的?个在第五个显示,其他的还有显示。
就是在错误提示返回的时候,这些动态的数据要全部保存。
现在还没有头绪
想法是如果液晶可以将当前的信息通过P0口保存就好了
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-7-11 19:37:29 | 显示全部楼层
假如已经有写字符函数和写字串函数
void lcd 1602WriChar(uchar x,uchar y,char data);
void lcd1602WriStr(uchar x,uchar y,str*s);
其中x:0-15,列,y:0,1行,对于固定显示的,显示一个带空格的字串,空格预留给要显示的可变值
如char *str1="t  p  xx";第二、三个位置留空第四个位置是p第五个位置留空,第六、七个位置是xx,那么
lcd1602WriStr(0,0,str1);就在上行显示t空空p空空xx.
全局变量
char *str1="t  p  xx";
char *str2="DONT KEY ACT!  "
uchar dspt10;//显示温度值十位
uchar dspt1;//显示温度值个位
uchar dspch;//通道号
bit b=1; //标志,无键为1有键为0
if(b==1)
{
lcd1602WriStr(0,0,str1);//显示固定字符串1
lcd1602WriChar(0,1,dspt10);
lcd1602WriChar(0,2,dspt1);
lcd1602WriChar(0,4,dspch);//显示温度和通道号
}
else
{
  lcd1602WriStr(0,0,str2);//显示警告
Delay();
b=1;
}
lcd1602WriStr(0,0,str1);//回到显示固定字符串1
lcd1602WriChar(0,1,dspt10);
lcd1602WriChar(0,2,dspt1);
lcd1602WriChar(0,4,dspch);//显示温度和通道号
回复 支持 反对

使用道具 举报

发表于 2011-7-11 21:17:31 | 显示全部楼层
你在15分钟做成这个程序


it is very simple: my rtc/tmr code is already in a module and all I had to do is to include them in my source file and then compile.

你说的在MCU上设置一个display,


the vRAM[] in the above code is one example: all your other code needs to do is to form vRAM and then pass it to your display code.

here is a sketch of my 1602 code, to show you how it is done:

  1. #include "lcd_3wi.h"  //we use lcd 3wire interface
  2. //#include "lcd_4bit.h"  // we use lcd 4 bit interface

  3. //global variable
  4. unsigned char vRAM[17]; //1602 display buffer

  5.   lcd_init();  //reset the lcd

  6.   ...

  7.   sprintf(vRAM, "Wrong Key -- %4s", key_str); //form vRAM
  8.   lcd_display(LCD_Line0, vRAM);  //display vRAM on LCD_Line0

  9.   ...
复制代码
you may use other ways to form the display buffer but the key here is to keep it in mcu's ram and send it to the lcd when you need to display a message. a typical write of one 16-char string to a lcd can be done in less than 2ms - vs. 6ms display reaction time.
回复 支持 反对

使用道具 举报

发表于 2011-7-11 21:24:57 | 显示全部楼层
另外那个电子钟如果让那个减号闪动如何操作?


that's very easy:

  1.   sprintf(vRAM, "%2d:%2d:%1d:%2d", time.hour, time.minue, (time.half_sec)?" ":":", time.sec);
复制代码
when time.half_sec=0, it displays "23:59 59"; when time.half_sec=1, it displays "23:59:59".

so every half a second, the ":" goes on and then goes off.

that's how I did it in the 7-segment led example I give out earlier.

if you go down to more details, you can actually display more chars at that spot to provide, for example, fancier display, for example, turning on segment D to indicate 1/4th of a second, segment G to indicate 1/2 of a second, segment A to indicate the 3/4th of a second, etc.
回复 支持 反对

使用道具 举报

发表于 2011-7-11 21:54:51 | 显示全部楼层
本帖最后由 清风车影18 于 2011-7-11 21:58 编辑

非常感谢上面两位。我大概有一个思路了。
另外电子钟的那个闪动,刚才脑子一转就解决了。
方法:
unsigned char shan;
shan=sec%2;  //除2求余数,如果等于0显示减号,否则不显示。
if(shan==0)
tab1[2]=0X10; //上面表格的第16个赋值是减号。
else
tab1[2]=0x17;  //上面的17是随便写的,因为这个数组只有16个,所以17的时候什么都不显示,当然可以设定数组有17个数tab1[17]赋值一个17,值是0x00,即可。
上面的代码通过仿真完全通过!
回复 支持 反对

使用道具 举报

发表于 2011-7-11 21:57:15 | 显示全部楼层
上面举例1602显示的 我仔细琢磨一下看看。
谢谢两位帮助。很开心能够向你们学习。
回复 支持 反对

使用道具 举报

发表于 2011-7-11 22:47:59 | 显示全部楼层
刚才脑子一转就解决了。


tab1[2]=(sec%2)?0x10:0x17;

one sentence.
回复 支持 反对

使用道具 举报

发表于 2011-7-12 00:13:35 | 显示全部楼层
本帖最后由 清风车影18 于 2011-7-12 00:16 编辑

回复 126# millwood


    因为上面定义
uchar code tab[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40,0x00};//
tab1[]={};
然后
shan=sec%2;//因为秒除以2求余数,如果是0,表示偶数,0,2,4,6.。。。。否则是1,3,5,7,9.当是偶数的时候显示减号。否则不显示。
tab1[0]=sec%10;  //先显示秒个位。
tab1[1]=sec/10;
shan=sec%2;
if(shan==0)
tab1[2]=11;     //显示负号
else
tab1[2]=10;

tab1[3]=fen%10;
tab1[4]=fen/10;
tab1[5]=10;
tab1[6]=shi%10;
tab1[7]=shi/10;
然后 P0=tab[tab1[disp]];
回复 支持 反对

使用道具 举报

发表于 2011-7-12 02:33:55 | 显示全部楼层
quite a few things:

1) you should minimize the updating of the display to only those chars that are changing. this helps minimize the time it takes for each update;

2) "然后 P0=tab[tab1[disp]];"

you should never ever make your code so hardware dependent - what if you were to move the segment port (P0) to a different port?

instead, define a macro, _7SEG_PORT in my code, and write your code so that it writes to _7SEG_PORT, and then you can redefine _7SEG_PORT to P0 or P1 or P2 if you are going to re-route those pins.

the goal is to minimize the hardware touch points of any code so that it is as portable as possible.

high portability improves your code's reusability, which improves your coding efficiency and minimizes coding errors.
回复 支持 反对

使用道具 举报

发表于 2011-7-12 07:43:26 | 显示全部楼层
in case you are wondering how something like this can be done in 15 minutes (or even faster).

here is my code, in its "modular" form.
  1. //time keeping on 8051
  2. //tmr0 runs as a 16-bit rtc (black roman approach)
  3. //tmr1 controls the 7-segment display

  4. #include<regx51.h>                                                                //we use keil c51
  5. #include "gpio.h"
  6. //#include <intrins.h>
  7. #include "rtc0.h"                                                                //we use tmr0 as rtc
  8. #include "tmr1.h"                                                                //we use tmr1 for display
  9. #include "_7seg.h"                                                                //we use 7seg led driver

  10. //hardware configuration
  11. //end hardware configuration

  12. //global variables

  13. //convert current time and store it in vram
  14. void time2vRAM(void) {
  15.         vRAM[7]=_rtc0.hour/10;
  16.         vRAM[6]=_rtc0.hour%10;
  17.         vRAM[5]=0x10;                                //update hour display
  18.         vRAM[4]=_rtc0.min/10;
  19.         vRAM[3]=_rtc0.min%10;                        //update minute display
  20.         vRAM[2]=(_rtc0.half_sec)?0x10:0x7f;                //???? - blinking the half second indicator
  21.         vRAM[1]=_rtc0.sec/10;
  22.         vRAM[0]=_rtc0.sec%10;  //???????                //update second display
  23. }

  24. void mcu_init(void) {
  25. }

  26. void main(void)        //???
  27. {
  28.         mcu_init();                                                                //reset the mcu
  29.         _7seg_init();                                                        //reset the 7seg display

  30.         //configure tmr0 as a rtc. isr fired every 500ms
  31.         rtc0_init(RTC_500ms);                                        //reset tmr0 - period set by TMR0_PERIOD
  32.         rtc0_act(time2vRAM);                                        //install tmr0 isr handler -> time2vRAM is called every 500ms to update vRAM

  33.         //configure tmr1 as a display driver -> update digits every 1ms
  34.         tmr1_init(1, TMR_1ms);                                        //reset tmr1 -> tmr1 isr fires off once every 1ms
  35.         tmr1_act(_7seg_display8);                                //install tmr1 isr handler -> _7seg_display8 is called every 1ms

  36.         EA=1;                                                                        //enable global interrupt

  37.         while(1)
  38.         {
  39.                 //delay();
  40.        
  41.         }
  42. }
复制代码
as you can see, the main() is very simple: it configures tmr0 to run as a rtc, calling time2vRAM() every 500ms. time2vRAM basically converts time kept in _rtc0 (by tmr0) into vRAM, to be displayed by _7seg_display8().

tmr1 is configured to run once every 1ms (for each digit). it calls _7seg_display8() to display vRAM.

the reset of the code is really hidden (modularized) in rtc0.c/rtc0.h, tmr1.c/tmr1.h, and _7seg.c/_7seg.h. those modules can then be easily reused in the future for other applications.

what I did ealier on is essentially to take code in rtc0/tmr1/_7seg and roll them into main.c for easier presentation. in reality, they should really be designed in well packaged modules.

hope it helps.
回复 支持 反对

使用道具 举报

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

本版积分规则

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

蒙公网安备 15040402000005号

GMT+8, 2024-5-3 02:10

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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