s1j2h3 发表于 2014-12-1 16:47:45

这是叫框架么?用没有常识啊

吴坚鸿 发表于 2014-12-1 18:15:20

xczxwy 发表于 2014-12-1 16:29
真的一直坚持在写,刚开始看到这个帖子,还以为楼主是一时脑袋发热,真的要写100节啊?写完可以出书了~~ ...

明年下半年是准备整理成书稿出版。

ants133 发表于 2014-12-2 00:03:31

真的很不错!谢谢分享!

Being. 发表于 2014-12-3 16:12:32

感谢分享,一起进步~

堂吉诃德 发表于 2014-12-4 08:51:31

这个好啊

gfy200866 发表于 2014-12-4 08:55:58

支持LZ的无私奉献{:handshake:}

brmxd 发表于 2014-12-4 09:02:11


支持LZ的无私奉献

cyberkit 发表于 2014-12-4 09:06:32

支持一下!

gaoxinjun2001 发表于 2014-12-4 09:39:58

这么威武的楼主想不支持都难哈,赞一个,

tt98 发表于 2014-12-8 23:24:45

坐等鸿哥下文{:titter:}

szhanlee 发表于 2014-12-10 16:23:54

感谢分享

tt98 发表于 2014-12-11 22:38:02

鸿哥,这个是你的还是盗你的?http://www.xuboyi.com/danpianji/25.html

kenanhcf88 发表于 2014-12-12 12:44:23

恩,学习了

heyueming0 发表于 2014-12-12 14:59:20

好多,慢慢看

xxzzhy 发表于 2014-12-12 23:29:17

好久没有来了。好好看看

xiaozhu2014 发表于 2014-12-13 11:18:40

认真不拜读中,有些地方已经用到项目中了

huihui_135 发表于 2014-12-15 12:41:37

多谢分享。慢慢看,正在学习。

zcoder 发表于 2014-12-15 13:05:55

单片机玩成这样很不错。不过程序框架可以有别的更好的选择。

Love911 发表于 2014-12-15 13:18:37

好东西,收藏了!

cjwdyzdd 发表于 2014-12-15 13:26:05

早些时候看过楼主的帖子

jesse2012 发表于 2014-12-15 14:24:43

本帖最后由 jesse2012 于 2014-12-15 14:31 编辑

吴坚鸿 发表于 2014-12-1 18:15
明年下半年是准备整理成书稿出版。

赶紧出书呀,电子出版社14年9月推了一裸编程的,书名不写不打广告,部分思想与你一脉相承,着急{:curse:}

xiong57785 发表于 2014-12-15 14:45:33

wow当初看过一个类似的单片机框架 通过定时器分配不同程序运行觉得挺厉害 后来发现还有单片机上的小型操作系统楼主的不错 简单实用!

枫叶情缘 发表于 2014-12-15 15:02:51

jesse2012 发表于 2014-12-15 14:24
赶紧出书呀,电子出版社14年9月推了一裸编程的,书名不写不打广告,部分思想与你一脉相承,着急 ...

我买了那书,老实说第一版中小错误还挺多的,至于思想嘛,仁者见仁智者见智了,很多是自己不会总结也写不出来而那作者给总结了。

吴坚鸿 发表于 2014-12-15 20:07:06

tt98 发表于 2014-12-11 22:38
鸿哥,这个是你的还是盗你的?http://www.xuboyi.com/danpianji/25.html

这不叫盗。只是他转载而已,没关系的。

吴坚鸿 发表于 2014-12-15 20:10:38

jesse2012 发表于 2014-12-15 14:24
赶紧出书呀,电子出版社14年9月推了一裸编程的,书名不写不打广告,部分思想与你一脉相承,着急 ...

这个没关系的。我写书从来不跟同行刻意对比。我不追求速度,我只追求每一节的高质量和灵感,我活在我的世界里。我估计我的书稿出版至少也要2016年。我打算在下个月左右再开一个零基础入门的连载技术贴。慢工出细活。

雷动三千 发表于 2014-12-15 20:19:31

mark!有时间了好好看看。

刚煜 发表于 2014-12-15 20:21:56

好详细的教程!对于新手入门或者入门者提高很有帮助!

365026266 发表于 2014-12-15 20:31:36

MARK 一下,认真拜读

北宸 发表于 2014-12-15 20:32:21

持续关注中。。。。。

秋夜木语 发表于 2014-12-15 20:55:24

这个不错,果然大神多!

chengshuo 发表于 2014-12-15 21:05:01

楼主比较用心了

xgzn 发表于 2014-12-15 21:44:14

看看还不错,收藏了留着以后慢慢看!

liangying 发表于 2014-12-15 23:21:48

很多还没有看懂,留着慢慢看,谢谢楼主,那个HT1621啥时候出啊?

jesse2012 发表于 2014-12-16 10:28:06

吴坚鸿 发表于 2014-12-15 20:10
这个没关系的。我写书从来不跟同行刻意对比。我不追求速度,我只追求每一节的高质量和灵感,我活在我的世 ...

支持,说得好,项目经验比裸编实在多了,你出书我一定会买的,一本好书就是一位好老师,懂了还会教真不简单。

jesse2012 发表于 2014-12-16 10:35:02

枫叶情缘 发表于 2014-12-15 15:02
我买了那书,老实说第一版中小错误还挺多的,至于思想嘛,仁者见仁智者见智了,很多是自己不会总结也写不 ...

那书还是要专业基础,出书的人都还是走了心,真不适合刚上大学新手看,适合懂了之后研究理论,还是要像楼主样手把手不懂的教得懂点入门了厉害。

simon51 发表于 2014-12-16 10:36:34

看来很不错,学习一下

zyjbcnmdb 发表于 2014-12-16 13:30:38

写的很好

枫叶情缘 发表于 2014-12-16 16:30:12

jesse2012 发表于 2014-12-16 10:35
那书还是要专业基础,出书的人都还是走了心,真不适合刚上大学新手看,适合懂了之后研究理论,还是要像楼 ...

恩,事件驱动、事件数据分离、还有消息机制等平时也在用,不过那作者总结出来说的比较具体。
实不相瞒,当时就是书名吸引了我,毕竟市场上同类型的从头讲到尾杂用xx芯片的太多了,老实说,这书还可以,就是小错误多了点,第二版应该会改过来。

anyanggcmy 发表于 2014-12-16 17:51:04

开一个零基础入门的连载技术贴,支持越快越好

xuejunhj 发表于 2014-12-16 18:21:11

好详细的教程!对于新手入门很有帮助!

yb12345 发表于 2014-12-16 22:31:05

学习一下

panhai0101 发表于 2014-12-16 22:39:31

单片机的技术可能更容易封锁

彼岸花开@ 发表于 2014-12-17 12:32:06

有没有原理,能不能发上来。。这样更好点。

吴坚鸿 发表于 2014-12-17 13:28:32

彼岸花开@ 发表于 2014-12-17 12:32
有没有原理,能不能发上来。。这样更好点。

这个是你要的原理图。

吴坚鸿 发表于 2014-12-17 13:31:21

第八十三节:矩阵键盘输入任意数字或小数点的液晶屏显示程序。

开场白:
本来这节打算讲调用液晶屏内部字库时让某行内容反显的,但是在昨天调试过程中,发现一个很奇怪的问题,当调用内部字库时,按照数据手册,我执行一条反显指令时,应该是仅仅某一行反显,但是却同时出现两行反显。比如,当我执行
      WriteCommand(0x34); //扩充指令集
      WriteCommand(0x04); //第1行反显
指令时,发现第一行和第三行反显,后来想想,我猜测这种12864的屏应该是25632折成左右半屏,左半屏在上面,右半屏在下面。经过这次经验,我觉得大家以后尽量不要用液晶屏的内部字库模式,应该用自构字库的模式(图形模式)。因为我觉得用内部字库模式的时候,这个集成的反显扩展指令不好用。而用自构字库的模式(图形模式),却可以顺心所欲的灵活运用,适合做菜单程序。
既然发现内部字库不好用,所以不再讲内部字库模式,这节仅仅接着前面第79节内容,继续讲在自构字库的模式(图形模式)下,如何通过矩阵键盘直接输入数字和小数点,就像普通的计算器一样键盘输入。这个功能表面简单,其实有以下四个地方值得注意:
第一:如何用数组接收按键输入的BCD码数据。
第二:如何限制输入参数的小数点个数和数组的有效个数。
第三:如果第0个位置是0,那么继续输入的数据直接覆盖0,否则就移位再输入。
第四:如果第0个位置是0,那么继续输入的小数点要移位输入。
要仔细了解以上提到的关键点,必须好好研究本程序中的void set_data(…)函数。同时也要温习一下之前讲的自构字库模式的液晶屏显示内容,尤其是插入画布显示的内容。

具体内容,请看源代码讲解。

(1)        硬件平台:
基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,清零键对应S16,其它按键不用。

(2)        实现功能:
用矩阵键盘输入任意数字或小数点。小数点不能超过2位,一旦超过2位,再按其它按键则输入无效。有效数字也不能超过6位(包括小数点),一旦超过6位,再按其它按键则输入无效。
想重新输入,必须按S16清零按键才能重新输入。

(3)源代码讲解如下:
#include "REG52.H"


#define const_voice_short40   //蜂鸣器短叫的持续时间

#define const_key_time10    //按键去抖动延时的时间

sbit key_sr1=P0^0; //第一行输入
sbit key_sr2=P0^1; //第二行输入
sbit key_sr3=P0^2; //第三行输入
sbit key_sr4=P0^3; //第四行输入

sbit key_dr1=P0^4; //第一列输出
sbit key_dr2=P0^5; //第二列输出
sbit key_dr3=P0^6; //第三列输出
sbit key_dr4=P0^7; //第四列输出

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

sbitLCDCS_dr= P1^6;//片选线
sbitLCDSID_dr = P1^7;//串行数据线
sbitLCDCLK_dr = P3^2;//串行时钟线
sbitLCDRST_dr = P3^4;//复位线

void SendByteToLcd(unsigned char ucData);//发送一个字节数据到液晶模块
void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
void LCDInit(void);//初始化函数内部包括液晶模块的复位
void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
void display_lattice(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
unsigned char *number_to_matrix(unsigned charucBitNumber); //把一位数字转换成字模首地址的函数
void delay_short(unsigned int uiDelayshort); //延时
void delay_long(unsigned int uiDelayLong);

void key_number_input(unsigned char ucKeyNumber); //输入数字按键
void set_data(unsigned char ucKeyNumberTemp,unsigned char ucDotBitMax,unsigned char ucDataCntMax,unsigned char *p_ucDotCnt,unsigned char *p_ucDotBitS,unsigned char *p_ucWdPartCnt,unsigned char *p_ucSetDataBuffer);
void key_delete_input(void); //删除按键

void T0_time(); //定时中断函数
void key_service();
void key_scan(); //按键扫描函数 放在定时中断里

void initial_myself();   
void initial_peripheral();


void lcd_display_service(void); //应用层面的液晶屏显示程序
void clear_all_canvas(void);//把画布全部清零

code unsigned char Zf816_0[]=
{
/*--文字:0--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
};

code unsigned char Zf816_1[]=
{
/*--文字:1--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
};

code unsigned char Zf816_2[]=
{
/*--文字:2--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
};

code unsigned char Zf816_3[]=
{
/*--文字:3--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
};

code unsigned char Zf816_4[]=
{
/*--文字:4--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
};

code unsigned char Zf816_5[]=
{
/*--文字:5--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
};

code unsigned char Zf816_6[]=
{
/*--文字:6--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
};


code unsigned char Zf816_7[]=
{
/*--文字:7--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
};

code unsigned char Zf816_8[]=
{
/*--文字:8--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
};

code unsigned char Zf816_9[]=
{
/*--文字:9--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
};


code unsigned char Zf816_nc[]=//空字模
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Zf816_dot[]=//小数点
{
/*--文字:.--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,
};

code unsigned char Zf816_mao_hao[]=//冒号
{
/*--文字::--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
};

code unsigned char Hz1616_yi[]=
{
/*--文字:一--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Hz1616_xiang[]=
{
/*--文字:项--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24,
0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00,
};

code unsigned char Hz1616_shu[]=
{
/*--文字:数--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4,
0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00,
};

code unsigned char Hz1616_zhu[]=
{
/*--文字:组--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08,
0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00,
};

/* 注释一:
* 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字.
*注意,这节内容的画布跟前面章节的画布大小不一样,前面章节的横向是4个字节,这节的横向是6个字节。
*/
unsigned char ucCanvasBuffer[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,//上半屏
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,

//------------上半屏和下半屏的分割线-----------

0x00,0x00,0x00,0x00,0x00,0x00,//下半屏
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
};


/* 注释二:
* 以下4个变量记录一个参数的4种信息,包括小数点的数量,个数,数据的位置,数组具体值.
*/
unsigned char ucDotCnt_1=0;//记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个量如果超过规定2位,此时再按任何输入按键则无效
unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。
unsigned char ucDataBuffer_1={0,10,10,10,10,10}; //一项的BCD码数组缓冲


unsigned char ucKeyStep=1;//按键扫描步骤变量

unsigned char ucKeySec=0;   //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

unsigned char ucRowRecord=1; //记录当前扫描到第几列了


unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器


unsigned char ucWd=1; //窗口变量
unsigned char ucPart=1; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量1代表更新显示,响应函数内部会清零


void main()
{
      initial_myself();      //第一区,上电后马上初始化
      delay_long(100);       //一线,延时线。延时一段时间
      initial_peripheral();//第二区,上电后延时一段时间再初始化

      while(1)   //第三区
      {
                  key_service(); //按键服务程序
            lcd_display_service(); //应用层面的液晶屏显示程序
      }

}


void initial_myself()//第一区 上电后马上初始化
{

   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

   TMOD=0x01;//设置定时器0为工作方式1

   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
   TL0=0x2f;
}
void initial_peripheral() //第二区 上电后延时一段时间再初始化
{
    LCDInit(); //初始化12864 内部包含液晶模块的复位


    EA=1;   //开总中断
    ET0=1;    //允许定时中断
    TR0=1;    //启动定时中断

}


void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断

key_scan();//按键扫描函数 放在定时中断里

if(uiVoiceCnt!=0)
{
   uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
         beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
   ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
         beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}


TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}


void key_scan()//按键扫描函数 放在定时中断里
{

switch(ucKeyStep)
{
   case 1:   //按键扫描输出第ucRowRecord列低电平
            if(ucRowRecord==1)//第一列输出低电平
                  {
             key_dr1=0;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==2)//第二列输出低电平
                  {
             key_dr1=1;      
             key_dr2=0;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==3)//第三列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=0;   
             key_dr4=1;
                  }
            else   //第四列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=0;
                  }

          uiKeyTimeCnt=0;//延时计数器清零
          ucKeyStep++;   //切换到下一个运行步骤
            break;

   case 2:   //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;   //切换到下一个运行步骤
                  }
            break;

   case 3:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {
             ucKeyStep=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描
             ucKeyLock=0;//按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙   
   
                         ucRowRecord++;//输出下一列
                         if(ucRowRecord>4)
                         {
                            ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
                         }

          }
                  else if(ucKeyLock==0)//有按键按下,且是第一次触发
                  {
                     if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=1;//触发1号键 对应朱兆祺学习板的S1键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=2;//触发2号键 对应朱兆祺学习板的S2键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=3;//触发3号键 对应朱兆祺学习板的S3键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=4;//触发4号键 对应朱兆祺学习板的S4键
                           }

                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=5;//触发5号键 对应朱兆祺学习板的S5键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=6;//触发6号键 对应朱兆祺学习板的S6键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=7;//触发7号键 对应朱兆祺学习板的S7键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=8;//触发8号键 对应朱兆祺学习板的S8键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=9;//触发9号键 对应朱兆祺学习板的S9键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=10;//触发10号键 对应朱兆祺学习板的S10键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=11;//触发11号键 对应朱兆祺学习板的S11键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=12;//触发12号键 对应朱兆祺学习板的S12键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=13;//触发13号键 对应朱兆祺学习板的S13键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=14;//触发14号键 对应朱兆祺学习板的S14键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=15;//触发15号键 对应朱兆祺学习板的S15键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=16;//触发16号键 对应朱兆祺学习板的S16键
                           }
                              }
                        
                         }
                  
                  }
            break;

}


}


void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
    case 1:// 数字1 对应朱兆祺学习板的S1键
          key_number_input(1); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
    case 2:// 数字2 对应朱兆祺学习板的S2键
          key_number_input(2); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 3:// 数字3 对应朱兆祺学习板的S3键
          key_number_input(3); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 4:// 数字4 对应朱兆祺学习板的S4键
          key_number_input(4); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 5:// 数字5 对应朱兆祺学习板的S5键
          key_number_input(5); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 6:// 数字6 对应朱兆祺学习板的S6键
          key_number_input(6); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 7:// 数字7 对应朱兆祺学习板的S7键
          key_number_input(7); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8: //数字8 对应朱兆祺学习板的S8键
          key_number_input(8); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 数字9 对应朱兆祺学习板的S9键
          key_number_input(9); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 数字0对应朱兆祺学习板的S10键
          key_number_input(0); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 小数点按键 对应朱兆祺学习板的S11键
          key_number_input(11); //输入数字按键11代表小数点
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 本节暂时不用 对应朱兆祺学习板的S12键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 本节暂时不用 对应朱兆祺学习板的S13键   

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 本节暂时不用对应朱兆祺学习板的S14键   
      
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 本节暂时不用 对应朱兆祺学习板的S15键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 16:// 清除按键 对应朱兆祺学习板的S16键
          key_delete_input(); //删除按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
}               
}



void key_number_input(unsigned char ucKeyNumber) //输入数字按键
{

        switch(ucWd)
        {
          case 1:   //第1窗口。本节程序只有1个窗口
                   switch(ucPart)
                   {

             case 1://1窗口第1项
                  set_data(ucKeyNumber,2,6,&ucDotCnt_1,&ucDotBitS_1,&ucWdPartCnt_1,ucDataBuffer_1); //设置参数,请看本函数具体内容。本节的核心内容,值得好好研究!               
                                  ucWd1Part1Update=1;//更新显示
                                  break;               
          }
                                       
                    break;
    }                       
                               
}


/* 注释三:
* 本节的核心函数,值得好好研究!
* 涉及到参数的4种信息,包括小数点的数量,个数,数据的位置,数组具体值。以及它们之间的相互作用关系。
* 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。
* 第1个参数ucKeyNumberTemp是当前按键输入的数值。
* 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。
* 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。
* 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
* 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个量如果超过规定2位,此时再按任何输入按键则无效
* 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
* 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。
*/
void set_data(unsigned char ucKeyNumberTemp,unsigned char ucDotBitMax,unsigned char ucDataCntMax,unsigned char *p_ucDotCnt,unsigned char *p_ucDotBitS,unsigned char *p_ucWdPartCnt,unsigned char *p_ucSetDataBuffer)
{
                  unsigned int i;

                  if(ucKeyNumberTemp==11) //等于小数点
                  {
                     if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。
                     {
                           return; //直接返回退出
                     }
                     else if(*p_ucDotCnt>0)//小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。
                     {
                           return; //直接返回退出
                     }
                     else//否则有效,记录当前已经包含一个小数点的信息。
                     {
                           *p_ucDotCnt=1;//只能包含一个小数点
                     }
                  }
                  else if(*p_ucDotCnt==1) //如果输入的不是小数点,并且之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据
                  {
                        if(*p_ucDotBitS<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
                        {
                            *p_ucDotBitS=(*p_ucDotBitS)+1;
                        }
                        else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
                        {
                            return; //直接返回退出
                        }
                  }

      
                                  if(*p_ucWdPartCnt<(ucDataCntMax-1))//当输入的有效BCD码不超过最大数组缓冲时
                                  {               
                     if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer==0&&ucKeyNumberTemp!=11)//如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
                     {
                           ;
                     }       
                     else//否则,移位
                     {               
                           for(i=0;i<(ucDataCntMax-1);i++)//移位
                           {
                              p_ucSetDataBuffer=p_ucSetDataBuffer;
                           }
                                             *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
                     }
                     p_ucSetDataBuffer=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
                                                               
                  }

}


void key_delete_input(void) //删除按键
{
        static unsigned int i;

        switch(ucWd)
        {
          case 1:   //第1窗口。本节程序只有1个窗口
                   switch(ucPart)
                   {

             case 1://1窗口第1项
                  
                                //清零
                  ucDotBitS_1=0;
                  ucDotCnt_1=0;
                  ucWdPartCnt_1=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_1=10;
                  }
                                        ucDataBuffer_1=0; //第0个位置填入0
       
                                  ucWd1Part1Update=1;//更新显示
                                  break;               
      
          }
                                       
                    break;
       
    }                       
                               
}

unsigned char *number_to_matrix(unsigned charucBitNumber)
{
    unsigned char *p_ucAnyNumber;//此指针根据ucBitNumber数值的大小,分别调用不同的字库。

      switch(ucBitNumber)//根据ucBitNumber数值的大小,分别调用不同的字库。
      {
            case 0:
             p_ucAnyNumber=Zf816_0;
                     break;
            case 1:
             p_ucAnyNumber=Zf816_1;
                     break;
            case 2:
             p_ucAnyNumber=Zf816_2;
                     break;
            case 3:
             p_ucAnyNumber=Zf816_3;
                     break;
            case 4:
             p_ucAnyNumber=Zf816_4;
                     break;
            case 5:
             p_ucAnyNumber=Zf816_5;
                     break;
            case 6:
             p_ucAnyNumber=Zf816_6;
                     break;
            case 7:
             p_ucAnyNumber=Zf816_7;
                     break;
            case 8:
             p_ucAnyNumber=Zf816_8;
                     break;
            case 9:
             p_ucAnyNumber=Zf816_9;
                     break;
            case 10://空格
             p_ucAnyNumber=Zf816_nc;
                     break;
                        case 11:   //小数点
             p_ucAnyNumber=Zf816_dot;
                     break;
                default:   //如果上面的条件都不符合,那么默认指向空字模
             p_ucAnyNumber=Zf816_nc;
                     break;
      }

    return p_ucAnyNumber;//返回转换结束后的指针
}



void lcd_display_service(void) //应用层面的液晶屏显示程序
{


    static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
    static unsigned char ucCursorFlag;//光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
    static unsigned int i;

    switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
    {
      case 1:   //显示窗口1的数据
               if(ucWd1Update==1)//窗口1整屏更新,里面只放那些不用经常刷新显示的内容
               {
                     ucWd1Update=0;//及时清零,避免一直更新

                     ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进


                     display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
                     clear_all_canvas();//把画布全部清零

                     display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示
                     display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,0,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,0,Hz1616_zhu,0,2,16,0);
                     display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

               

               }

               if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part1Update=0; //及时清零,避免一直更新

                        if(ucPart==1) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        
                                                for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                                                    p_ucAnyNumber=number_to_matrix(ucDataBuffer_1);
                        insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }

                     
               break;
      //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...      
    }

}



void clear_all_canvas(void)//把画布全部清零
{
   unsigned int j=0;
   unsigned int i=0;

   for(j=0;j<16;j++)//这里的16表示画布有16行
   {
      for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
      {
                  ucCanvasBuffer=0x00;
      }
   }         

}





void display_clear(unsigned char ucFillDate) // 清屏全部显示空填充0x00   全部显示点阵用0xff
{   

    unsigned char x,y;
    WriteCommand(0x34);//关显示缓冲指令            
    WriteCommand(0x34);//关显示缓冲指令故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
    y=0;
    while(y<32)//y轴的范围0至31
    {
         WriteCommand(y+0x80);      //垂直地址
         WriteCommand(0x80);          //水平地址
         for(x=0;x<32;x++)//256个横向点,有32个字节
         {
            LCDWriteData(ucFillDate);
         }
         y++;
    }
    WriteCommand(0x36); //开显示缓冲指令

}

/* 注释四:
* 注意,这节内容的画布跟前面章节的画布大小不一样,前面章节的横向是4个字节,这节的横向是6个字节。
* 把字模插入画布的函数.
* 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是在画布中的坐标体系。
* x的范围是0至5,因为画布的横向只要6个字节。y的范围是0至15,因为画布的纵向只有16行。
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
*/
void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;
   for(j=0;j<y_amount;j++)
   {
      for(i=0;i<x_amount;i++)
      {
            ucTemp=ucArray;
            if(ucFbFlag==0)
            {
               ucCanvasBuffer[(y+j)*6+x+i]=ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
            }
            else
            {
               ucCanvasBuffer[(y+j)*6+x+i]=~ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
            }
      }
   }         

}

/* 注释五:
* 显示任意点阵函数.
* 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
* 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
* 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
*/
void display_lattice(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;

//注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
//WriteCommand(0x34);//关显示缓冲指令            
//WriteCommand(0x34);//关显示缓冲指令故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
   for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
   {
       WriteCommand(y+j+0x80);      //垂直地址
       WriteCommand(x+0x80);          //水平地址
       for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
       {
         ucTemp=ucArray; //uiOffSetAddr是字模数组的偏移地址
         if(ucFbFlag==1)//反白显示
         {
               ucTemp=~ucTemp;
         }
         LCDWriteData(ucTemp);
          //         delay_short(30000);//把上一节这个延时函数去掉,加快刷屏速度
      }
   }
   WriteCommand(0x36); //开显示缓冲指令
}




void SendByteToLcd(unsigned char ucData)//发送一个字节数据到液晶模块
{
      unsigned char i;
      for ( i = 0; i < 8; i++ )
      {
                if ( (ucData << i) & 0x80 )
                {
                        LCDSID_dr = 1;
                }
                else
                {
                        LCDSID_dr = 0;
                }
                LCDCLK_dr = 0;
                LCDCLK_dr = 1;
      }
}

void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
{
      SendByteToLcd( 0xf8 + (ucWRS << 1) );
      SendByteToLcd( ucWData & 0xf0 );
      SendByteToLcd( (ucWData << 4) & 0xf0);
}


void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
{

      LCDCS_dr = 0;
      LCDCS_dr = 1;
      SPIWrite(ucCommand, 0);
      delay_short(90);
}

void LCDWriteData(unsigned char ucData)//发送一个字节的数据给液晶模块
{
      LCDCS_dr = 0;
      LCDCS_dr = 1;
      SPIWrite(ucData, 1);
}

void LCDInit(void) //初始化函数内部包括液晶模块的复位
{
      LCDRST_dr = 1;//复位
      LCDRST_dr = 0;
      LCDRST_dr = 1;
}



void delay_short(unsigned int uiDelayShort) //延时函数
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)//内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}





总结陈词:
这节讲的是键盘输入数字或者小数点的BCD码用来显示,实际项目中,我们经常要知道所输入的BCD码数组到底有效数值是多少,这个该怎么办?欲知详情,请听下回分解----
实时同步把键盘输入的BCD码数组转换成数值的液晶屏显示程序。

(未完待续,下节更精彩,不要走开哦)

武洋洋 发表于 2014-12-17 16:36:58

向前辈致敬!

asbengineer 发表于 2014-12-17 20:18:45

支持一下,有作者自己的特点!!!

吴坚鸿 发表于 2014-12-19 16:08:38

第八十四节:实时同步把键盘输入的BCD码数组转换成数值的液晶屏显示程序。

开场白:
    键盘直接输入的是带小数点的BCD码数组,要把它们转换成具体的数值才可以更好的在程序里运算或者处理。如何把BCD码数组实时同步转换成数值?这一节主要跟大家讲这方面的算法程序。另外,有一个地方值得注意:上一节键盘输入的小数点个数可以限制成最大2位,但是整数部分没有限制。这节为了也能限制整数部分的最大个数为3位,我修改了上一节的void set_data(…)函数。所以这节的void set_data(…)函数跟上一节的void set_data(…)函数有点不一样,需要特别注意。

具体内容,请看源代码讲解。

(1)        硬件平台:
基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,清零键对应S16,其它按键不用。

(2)        实现功能:
用矩阵键盘输入任意数字或小数点。小数点不能超过2位,一旦超过2位,再按其它按键则输入无效。整数部分不能超过3位,一旦超过3位,再按其它按键则输入无效。想重新输入,必须按S16清零按键才能重新输入。每次键盘输入的第一行BCD码数组会同步更新显示在第二行的数值上。

(3)源代码讲解如下:
#include "REG52.H"


#define const_voice_short40   //蜂鸣器短叫的持续时间

#define const_key_time10    //按键去抖动延时的时间

sbit key_sr1=P0^0; //第一行输入
sbit key_sr2=P0^1; //第二行输入
sbit key_sr3=P0^2; //第三行输入
sbit key_sr4=P0^3; //第四行输入

sbit key_dr1=P0^4; //第一列输出
sbit key_dr2=P0^5; //第二列输出
sbit key_dr3=P0^6; //第三列输出
sbit key_dr4=P0^7; //第四列输出

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

sbitLCDCS_dr= P1^6;//片选线
sbitLCDSID_dr = P1^7;//串行数据线
sbitLCDCLK_dr = P3^2;//串行时钟线
sbitLCDRST_dr = P3^4;//复位线

void SendByteToLcd(unsigned char ucData);//发送一个字节数据到液晶模块
void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
void LCDInit(void);//初始化函数内部包括液晶模块的复位
void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
void display_lattice(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
unsigned char *number_to_matrix(unsigned charucBitNumber); //把一位数字转换成字模首地址的函数
void delay_short(unsigned int uiDelayshort); //延时
void delay_long(unsigned int uiDelayLong);

void key_number_input(unsigned char ucKeyNumber); //输入数字按键
void set_data(unsigned char ucKeyNumberTemp, //设置参数
            unsigned char ucDotBitMax,
            unsigned char ucDataCntMax,
            unsigned char *p_ucDotCnt,
            unsigned char *p_ucDotBitS,
                          unsigned char *p_ucWdPartCnt,
                          unsigned char *p_ucSetDataBuffer,
                          unsigned char ucIntCntMax,
                          unsigned char *p_ucIntCnt);

unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer); //把带小数点的BCD数组转换成long类型的数值。

void key_delete_input(void); //删除按键

void T0_time(); //定时中断函数
void key_service();
void key_scan(); //按键扫描函数 放在定时中断里

void initial_myself();   
void initial_peripheral();


void lcd_display_service(void); //应用层面的液晶屏显示程序
void clear_all_canvas(void);//把画布全部清零

code unsigned char Zf816_0[]=
{
/*--文字:0--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
};

code unsigned char Zf816_1[]=
{
/*--文字:1--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
};

code unsigned char Zf816_2[]=
{
/*--文字:2--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
};

code unsigned char Zf816_3[]=
{
/*--文字:3--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
};

code unsigned char Zf816_4[]=
{
/*--文字:4--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
};

code unsigned char Zf816_5[]=
{
/*--文字:5--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
};

code unsigned char Zf816_6[]=
{
/*--文字:6--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
};


code unsigned char Zf816_7[]=
{
/*--文字:7--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
};

code unsigned char Zf816_8[]=
{
/*--文字:8--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
};

code unsigned char Zf816_9[]=
{
/*--文字:9--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
};


code unsigned char Zf816_nc[]=//空字模
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Zf816_dot[]=//小数点
{
/*--文字:.--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,
};

code unsigned char Zf816_mao_hao[]=//冒号
{
/*--文字::--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
};

code unsigned char Hz1616_yi[]=
{
/*--文字:一--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Hz1616_xiang[]=
{
/*--文字:项--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24,
0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00,
};

code unsigned char Hz1616_shu[]=
{
/*--文字:数--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4,
0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00,
};

code unsigned char Hz1616_zhu[]=
{
/*--文字:组--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08,
0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00,
};

code unsigned char Hz1616_zhi[]=
{
/*--文字:值--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x10,0x40,0x18,0x60,0x17,0xFC,0x10,0x40,0x20,0x80,0x33,0xF8,0x62,0x08,0xA3,0xF8,
0x22,0x08,0x23,0xF8,0x22,0x08,0x23,0xF8,0x22,0x08,0x22,0x08,0x2F,0xFE,0x20,0x00,
};

/* 注释一:
* 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字.
*注意,这节内容的画布跟前面79章节的画布大小不一样,79节前面的横向是4个字节,这节的横向是6个字节。
*/
unsigned char ucCanvasBuffer[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,//上半屏
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,

//------------上半屏和下半屏的分割线-----------

0x00,0x00,0x00,0x00,0x00,0x00,//下半屏
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
};


/* 注释二:
* 以下5个变量记录一个参数的5种信息,包括小数点的数量,小数点个数,数据的位置,数组具体值,整数个数
*/
unsigned char ucDotCnt_1=0;//记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。
unsigned char ucDataBuffer_1={0,10,10,10,10,10}; //一项的BCD码数组缓冲
unsigned char ucIntCnt_1=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效

unsigned long ulData_1=0; //用一个long变量表示BCD码的具体数值。


unsigned char ucKeyStep=1;//按键扫描步骤变量

unsigned char ucKeySec=0;   //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

unsigned char ucRowRecord=1; //记录当前扫描到第几列了


unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器


unsigned char ucWd=1; //窗口变量
unsigned char ucPart=1; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量1代表更新显示,响应函数内部会自动把它清零
unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量1代表更新显示,响应函数内部会自动把它清零

void main()
{
      initial_myself();      //第一区,上电后马上初始化
      delay_long(100);       //一线,延时线。延时一段时间
      initial_peripheral();//第二区,上电后延时一段时间再初始化

      while(1)   //第三区
      {
                  key_service(); //按键服务程序
            lcd_display_service(); //应用层面的液晶屏显示程序
      }

}


void initial_myself()//第一区 上电后马上初始化
{

   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

   TMOD=0x01;//设置定时器0为工作方式1

   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
   TL0=0x2f;
}
void initial_peripheral() //第二区 上电后延时一段时间再初始化
{
    LCDInit(); //初始化12864 内部包含液晶模块的复位


    EA=1;   //开总中断
    ET0=1;    //允许定时中断
    TR0=1;    //启动定时中断

}


void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断

key_scan();//按键扫描函数 放在定时中断里

if(uiVoiceCnt!=0)
{
   uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
         beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
   ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
         beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}


TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}


void key_scan()//按键扫描函数 放在定时中断里
{

switch(ucKeyStep)
{
   case 1:   //按键扫描输出第ucRowRecord列低电平
            if(ucRowRecord==1)//第一列输出低电平
                  {
             key_dr1=0;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==2)//第二列输出低电平
                  {
             key_dr1=1;      
             key_dr2=0;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==3)//第三列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=0;   
             key_dr4=1;
                  }
            else   //第四列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=0;
                  }

          uiKeyTimeCnt=0;//延时计数器清零
          ucKeyStep++;   //切换到下一个运行步骤
            break;

   case 2:   //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;   //切换到下一个运行步骤
                  }
            break;

   case 3:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {
             ucKeyStep=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描
             ucKeyLock=0;//按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙   
   
                         ucRowRecord++;//输出下一列
                         if(ucRowRecord>4)
                         {
                            ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
                         }

          }
                  else if(ucKeyLock==0)//有按键按下,且是第一次触发
                  {
                     if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=1;//触发1号键 对应朱兆祺学习板的S1键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=2;//触发2号键 对应朱兆祺学习板的S2键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=3;//触发3号键 对应朱兆祺学习板的S3键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=4;//触发4号键 对应朱兆祺学习板的S4键
                           }

                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=5;//触发5号键 对应朱兆祺学习板的S5键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=6;//触发6号键 对应朱兆祺学习板的S6键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=7;//触发7号键 对应朱兆祺学习板的S7键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=8;//触发8号键 对应朱兆祺学习板的S8键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=9;//触发9号键 对应朱兆祺学习板的S9键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=10;//触发10号键 对应朱兆祺学习板的S10键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=11;//触发11号键 对应朱兆祺学习板的S11键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=12;//触发12号键 对应朱兆祺学习板的S12键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=13;//触发13号键 对应朱兆祺学习板的S13键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=14;//触发14号键 对应朱兆祺学习板的S14键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=15;//触发15号键 对应朱兆祺学习板的S15键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=16;//触发16号键 对应朱兆祺学习板的S16键
                           }
                              }
                        
                         }
                  
                  }
            break;

}


}


void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
    case 1:// 数字1 对应朱兆祺学习板的S1键
          key_number_input(1); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
    case 2:// 数字2 对应朱兆祺学习板的S2键
          key_number_input(2); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 3:// 数字3 对应朱兆祺学习板的S3键
          key_number_input(3); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 4:// 数字4 对应朱兆祺学习板的S4键
          key_number_input(4); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 5:// 数字5 对应朱兆祺学习板的S5键
          key_number_input(5); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 6:// 数字6 对应朱兆祺学习板的S6键
          key_number_input(6); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 7:// 数字7 对应朱兆祺学习板的S7键
          key_number_input(7); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8: //数字8 对应朱兆祺学习板的S8键
          key_number_input(8); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 数字9 对应朱兆祺学习板的S9键
          key_number_input(9); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 数字0对应朱兆祺学习板的S10键
          key_number_input(0); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 小数点按键 对应朱兆祺学习板的S11键
          key_number_input(11); //输入数字按键11代表小数点
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 本节暂时不用 对应朱兆祺学习板的S12键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 本节暂时不用 对应朱兆祺学习板的S13键   

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 本节暂时不用对应朱兆祺学习板的S14键   
      
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 本节暂时不用 对应朱兆祺学习板的S15键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 16:// 清除按键 对应朱兆祺学习板的S16键
          key_delete_input(); //删除按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
}               
}



void key_number_input(unsigned char ucKeyNumber) //输入数字按键
{

    switch(ucWd)
    {
       case 1:   //第1窗口。本节程序只有1个窗口
             switch(ucPart)
             {

               case 1://1窗口第1项
                      set_data(ucKeyNumber,//本函数跟前面第83节内容有所改动,请看本函数具体内容。本节的核心内容,值得好好研究!   
                                                   2,//小数点最大个数
                                                           6,//数组缓冲最大个数
                                                     &ucDotCnt_1,
                                                           &ucDotBitS_1,
                                                           &ucWdPartCnt_1,
                                                           ucDataBuffer_1,
                                                           3, //整数部分的最大个数
                                                           &ucIntCnt_1);

                                          ulData_1=buffer_to_data(6,2,ucDataBuffer_1); //把带小数点的BCD码数组转换成long数值。
                      ucWd1Part1Update=1;//第一行局部更新显示
                                          ucWd1Part2Update=1;//第二行局部更新显示
                      break;               
             }
                                       
             break;
    }                        
                              
}


/* 注释三:
* 本函数在前面第83节内容的函数上有改动,为了限制整数部分的个数,多添加了第8和第9这两个参数。
* 本节的核心函数,值得好好研究!
* 涉及到参数的4种信息,包括小数点的数量,小数点的个数,数据的位置,数组具体值,整数的数量,整数的个数,以及它们之间的相互作用关系。
* 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。
* 第1个参数ucKeyNumberTemp是当前按键输入的数值。
* 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。
* 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。
* 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
* 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
* 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
* 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。
* 第8个参数ucIntCntMax是限定被设置参数的整数部分的最大位数。
* 第9个参数*p_ucIntCnt是记录当前输入的整数部分个数,如果整数部分的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
*/
void set_data(unsigned char ucKeyNumberTemp,
            unsigned char ucDotBitMax,
            unsigned char ucDataCntMax,
            unsigned char *p_ucDotCnt,
            unsigned char *p_ucDotBitS,
                          unsigned char *p_ucWdPartCnt,
                          unsigned char *p_ucSetDataBuffer,
                          unsigned char ucIntCntMax,
                          unsigned char *p_ucIntCnt)
{
                  unsigned int i;

                  if(ucKeyNumberTemp==11) //等于小数点
                  {
                     if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。
                     {
                           return; //直接返回退出
                     }
                     else if(*p_ucDotCnt>0)//小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。
                     {
                           return; //直接返回退出
                     }
                     else//否则有效,记录当前已经包含一个小数点的信息。
                     {
                           *p_ucDotCnt=1;//只能包含一个小数点
                     }
                  }
                  else//如果输入的不是小数点
                  {
                        if(*p_ucDotCnt==1) //如果之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据
                                                {
                           if(*p_ucDotBitS<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
                           {
                               *p_ucDotBitS=(*p_ucDotBitS)+1;
                           }
                           else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
                           {
                              return; //直接返回退出
                           }
                                          }
                                                else if(*p_ucIntCnt<ucIntCntMax)//如果之前没有输入小数点,那么输入的就是整数个数超,整数个数没有超过极限
                                                {
                                                  *p_ucIntCnt=(*p_ucIntCnt)+1;
                                                }
                                                else //整数个数超过极限
                                                {
                           return; //直接返回退出
                                                }
                  }

            
            
                  if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer==0&&ucKeyNumberTemp!=11)//如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
                  {
                        ;
                  }      
                  else//否则,移位
                  {               
                     for(i=0;i<(ucDataCntMax-1);i++)//移位
                     {
                        p_ucSetDataBuffer=p_ucSetDataBuffer;
                     }
                     *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
                  }
                  p_ucSetDataBuffer=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
                                                               
      

}


/* 注释四:
* 本节的核心函数,值得好好研究!
* 功能:把一个带小数点的BCD码数组转换成一个long类型的数值。
* 第1个参数ucConverDataSize是这个数组的最大有效个数。
* 第2个参数ucConverDotCnt是这个数组要转换成的long数值带几个小数点
* 第3个参数*p_ucConverBuffer是具体此数组的数据
* 函数最后返回被转换的long数值。
*/
unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer)
{
   unsigned long ulConverResult=0;
   unsigned long ulConverResultTemp=0;
   unsigned char ucConverResultBuffer; //因为本节内容的ucConverDataSize是6,所以取6.
   unsigned char i;
   unsigned char j;
   unsigned char ucConverFlag;

   for(i=0;i<ucConverDataSize;i++)
   {
      ucConverResultBuffer=0;//先把临时缓冲区清零
   }

   j=0;
   ucConverFlag=0;
   for(i=0;i<ucConverDataSize;i++)
   {
       if(p_ucConverBuffer==11) //小数点
       {
          ucConverFlag=i; //记录小数点的位置
       }
       else if(p_ucConverBuffer<10)
       {
          ucConverResultBuffer=p_ucConverBuffer;//提取数组中的有效数字
          j++;
       }


   }


   for(i=0;i<ucConverDataSize;i++)   //通过处理每一位从而合成一个long类型的数值
   {
       ulConverResultTemp=0;
       ulConverResultTemp=ucConverResultBuffer;
       for(j=0;j<i;j++)
       {
         ulConverResultTemp=ulConverResultTemp*10;//把每一位对应的进位扩大到对应的倍数
       }
       ulConverResult=ulConverResult+ulConverResultTemp;
   }


   for(i=ucConverFlag;i<ucConverDotCnt;i++) //根据数组小数点的位置和实际要转换成的小数点个数,来扩大到对应的倍数。
   {
      ulConverResult=ulConverResult*10;
   }

   return ulConverResult;
}

void key_delete_input(void) //删除按键
{
    static unsigned int i;

   switch(ucWd)
   {
      case 1:   //第1窗口。本节程序只有1个窗口
         switch(ucPart)
         {
            case 1://1窗口第1项
                  //清零
                                        ulData_1=0; //long数值清零
                                       ucIntCnt_1=0;
                  ucDotBitS_1=0;
                  ucDotCnt_1=0;
                  ucWdPartCnt_1=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_1=10;
                  }
                  ucDataBuffer_1=0; //第0个位置填入0
      
                  ucWd1Part1Update=1;//第一行局部更新显示
                                  ucWd1Part2Update=1;//第二行局部更新显示
                  break;               
      
         }
                                       
         break;
      
   }                        
                              
}

unsigned char *number_to_matrix(unsigned charucBitNumber)
{
    unsigned char *p_ucAnyNumber;//此指针根据ucBitNumber数值的大小,分别调用不同的字库。

      switch(ucBitNumber)//根据ucBitNumber数值的大小,分别调用不同的字库。
      {
            case 0:
             p_ucAnyNumber=Zf816_0;
                     break;
            case 1:
             p_ucAnyNumber=Zf816_1;
                     break;
            case 2:
             p_ucAnyNumber=Zf816_2;
                     break;
            case 3:
             p_ucAnyNumber=Zf816_3;
                     break;
            case 4:
             p_ucAnyNumber=Zf816_4;
                     break;
            case 5:
             p_ucAnyNumber=Zf816_5;
                     break;
            case 6:
             p_ucAnyNumber=Zf816_6;
                     break;
            case 7:
             p_ucAnyNumber=Zf816_7;
                     break;
            case 8:
             p_ucAnyNumber=Zf816_8;
                     break;
            case 9:
             p_ucAnyNumber=Zf816_9;
                     break;
            case 10://空格
             p_ucAnyNumber=Zf816_nc;
                     break;
                        case 11:   //小数点
             p_ucAnyNumber=Zf816_dot;
                     break;
                default:   //如果上面的条件都不符合,那么默认指向空字模
             p_ucAnyNumber=Zf816_nc;
                     break;
      }

    return p_ucAnyNumber;//返回转换结束后的指针
}



void lcd_display_service(void) //应用层面的液晶屏显示程序
{


    static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
    static unsigned char ucCursorFlag;//光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
    static unsigned int i;
        static unsigned char ucDataBuffer_temp; //分解一个10进制的long类型数据的每一位

    switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
    {
      case 1:   //显示窗口1的数据
               if(ucWd1Update==1)//窗口1整屏更新,里面只放那些不用经常刷新显示的内容
               {
                     ucWd1Update=0;//及时清零,避免一直更新

                     ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
                     ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进

                     display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
                     clear_all_canvas();//把画布全部清零

                     display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一项数组
                     display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,0,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,0,Hz1616_zhu,0,2,16,0);
                     display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

                     display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一项数值
                     display_lattice(1,16,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,16,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,16,Hz1616_zhi,0,2,16,0);
                     display_lattice(4,16,Zf816_mao_hao,0,1,16,0); //冒号            

               }

               if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part1Update=0; //及时清零,避免一直更新

                        if(ucPart==1) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_1);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }

               if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part2Update=0; //及时清零,避免一直更新

                        if(ucPart==2) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        if(ulData_1>=10000)
                                                {
                                                   ucDataBuffer_temp=ulData_1%100000/10000;
                                                }
                                                else
                                                {
                                                   ucDataBuffer_temp=10; //空格
                                                }

                        if(ulData_1>=1000)
                                                {
                                                      ucDataBuffer_temp=ulData_1%10000/1000;
                        }
                                                else
                                                {
                                                      ucDataBuffer_temp=10; //空格
                        }

                                                ucDataBuffer_temp=ulData_1%1000/100;
                                                ucDataBuffer_temp=11;//11代表小数点
                                                ucDataBuffer_temp=ulData_1%100/10;
                                                ucDataBuffer_temp=ulData_1%10/1;
                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,16,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }

                     
               break;
      //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...      
    }

}



void clear_all_canvas(void)//把画布全部清零
{
   unsigned int j=0;
   unsigned int i=0;

   for(j=0;j<16;j++)//这里的16表示画布有16行
   {
      for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
      {
                  ucCanvasBuffer=0x00;
      }
   }         

}





void display_clear(unsigned char ucFillDate) // 清屏全部显示空填充0x00   全部显示点阵用0xff
{   

    unsigned char x,y;
    WriteCommand(0x34);//关显示缓冲指令            
    WriteCommand(0x34);//关显示缓冲指令故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
    y=0;
    while(y<32)//y轴的范围0至31
    {
         WriteCommand(y+0x80);      //垂直地址
         WriteCommand(0x80);          //水平地址
         for(x=0;x<32;x++)//256个横向点,有32个字节
         {
            LCDWriteData(ucFillDate);
         }
         y++;
    }
    WriteCommand(0x36); //开显示缓冲指令

}

/* 注释五:
* 注意,这节内容的画布跟第79节前面的画布大小不一样,第79节前面的横向是4个字节,这节的横向是6个字节。
* 把字模插入画布的函数.
* 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是在画布中的坐标体系。
* x的范围是0至5,因为画布的横向只要6个字节。y的范围是0至15,因为画布的纵向只有16行。
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
*/
void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;
   for(j=0;j<y_amount;j++)
   {
      for(i=0;i<x_amount;i++)
      {
            ucTemp=ucArray;
            if(ucFbFlag==0)
            {
               ucCanvasBuffer[(y+j)*6+x+i]=ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
            }
            else
            {
               ucCanvasBuffer[(y+j)*6+x+i]=~ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
            }
      }
   }         

}

/* 注释六:
* 显示任意点阵函数.
* 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
* 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
* 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
*/
void display_lattice(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;

//注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
//WriteCommand(0x34);//关显示缓冲指令            
//WriteCommand(0x34);//关显示缓冲指令故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
   for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
   {
       WriteCommand(y+j+0x80);      //垂直地址
       WriteCommand(x+0x80);          //水平地址
       for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
       {
         ucTemp=ucArray; //uiOffSetAddr是字模数组的偏移地址
         if(ucFbFlag==1)//反白显示
         {
               ucTemp=~ucTemp;
         }
         LCDWriteData(ucTemp);
          //         delay_short(30000);//把上一节这个延时函数去掉,加快刷屏速度
      }
   }
   WriteCommand(0x36); //开显示缓冲指令
}




void SendByteToLcd(unsigned char ucData)//发送一个字节数据到液晶模块
{
      unsigned char i;
      for ( i = 0; i < 8; i++ )
      {
                if ( (ucData << i) & 0x80 )
                {
                        LCDSID_dr = 1;
                }
                else
                {
                        LCDSID_dr = 0;
                }
                LCDCLK_dr = 0;
                LCDCLK_dr = 1;
      }
}

void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
{
      SendByteToLcd( 0xf8 + (ucWRS << 1) );
      SendByteToLcd( ucWData & 0xf0 );
      SendByteToLcd( (ucWData << 4) & 0xf0);
}


void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
{

      LCDCS_dr = 0;
      LCDCS_dr = 1;
      SPIWrite(ucCommand, 0);
      delay_short(90);
}

void LCDWriteData(unsigned char ucData)//发送一个字节的数据给液晶模块
{
      LCDCS_dr = 0;
      LCDCS_dr = 1;
      SPIWrite(ucData, 1);
}

void LCDInit(void) //初始化函数内部包括液晶模块的复位
{
      LCDRST_dr = 1;//复位
      LCDRST_dr = 0;
      LCDRST_dr = 1;
}



void delay_short(unsigned int uiDelayShort) //延时函数
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)//内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}




总结陈词:
    这节讲了把BCD码数组同步实时转换成数值的算法程序,相反,把数值转换成BCD码数组的逆运算程序应该怎么写?欲知详情,请听下回分解----实时同步把加减按键输入的数值转换成BCD码数组的液晶屏显示程序。

(未完待续,下节更精彩,不要走开哦)

salahading 发表于 2014-12-19 16:50:18

不错的说

a515509429 发表于 2014-12-19 17:38:52

先留个脚印 之后慢慢看~学习~

laokeiwang 发表于 2014-12-20 23:18:54

不错,草根思路,值得肯定

zheng_wei339 发表于 2014-12-22 08:36:02

讲的详细,容易理解。正学习中,谢谢

xxzzhy 发表于 2014-12-22 21:15:57

群主的程序很多地方值得借签。看得是思想方法!

ansion520 发表于 2014-12-22 22:15:02

相当好的教程,感谢楼主!

a_y_ang 发表于 2014-12-22 23:05:44

学习,谢谢

livekoko 发表于 2014-12-23 10:38:17

欢迎,不管这样都要先看看再说

7inspire 发表于 2014-12-23 10:58:01

顶,慢工出细活,详细,有帮助,

吴坚鸿 发表于 2014-12-24 10:56:16

第八十五节:实时同步把加减按键输入的数值转换成BCD码数组的液晶屏显示程序。

开场白:
    把运算处理完的数值转换成BCD码数组才可以更好方便显示和数字按键的输入编辑。这一节主要跟大家讲这方面的算法程序。本节的核心转换函数是void data_to_buffer(…)。

具体内容,请看源代码讲解。

(1)        硬件平台:
基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,S13按键是加按键,S14按键是减按键,清零键对应S16,其它按键不用。

(2)        实现功能:
通过S13,S14这两个加减按键更改第2行显示的数值,此数值会同步更新显示在第1行的BCD码数组上。

(3)源代码讲解如下:
#include "REG52.H"


#define const_voice_short40   //蜂鸣器短叫的持续时间

#define const_key_time10    //按键去抖动延时的时间

sbit key_sr1=P0^0; //第一行输入
sbit key_sr2=P0^1; //第二行输入
sbit key_sr3=P0^2; //第三行输入
sbit key_sr4=P0^3; //第四行输入

sbit key_dr1=P0^4; //第一列输出
sbit key_dr2=P0^5; //第二列输出
sbit key_dr3=P0^6; //第三列输出
sbit key_dr4=P0^7; //第四列输出

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

sbitLCDCS_dr= P1^6;//片选线
sbitLCDSID_dr = P1^7;//串行数据线
sbitLCDCLK_dr = P3^2;//串行时钟线
sbitLCDRST_dr = P3^4;//复位线

void SendByteToLcd(unsigned char ucData);//发送一个字节数据到液晶模块
void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
void LCDInit(void);//初始化函数内部包括液晶模块的复位
void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
void display_lattice(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
unsigned char *number_to_matrix(unsigned charucBitNumber); //把一位数字转换成字模首地址的函数
void delay_short(unsigned int uiDelayshort); //延时
void delay_long(unsigned int uiDelayLong);

void key_number_input(unsigned char ucKeyNumber); //输入数字按键
void set_data(unsigned char ucKeyNumberTemp, //设置参数
            unsigned char ucDotBitMax,
            unsigned char ucDataCntMax,
            unsigned char *p_ucDotCnt,
            unsigned char *p_ucDotBitS,
                          unsigned char *p_ucWdPartCnt,
                          unsigned char *p_ucSetDataBuffer,
                          unsigned char ucIntCntMax,
                          unsigned char *p_ucIntCnt);

void data_to_buffer(unsigned long ulWillConverData,//把数值转换成数组
                  unsigned char ucConverDotCnt,
                                        unsigned char ucConverDataSize,
                                        unsigned char *p_ucDotCnt,
                                        unsigned char *p_ucDotBitS,
                                        unsigned char *p_ucWdPartCnt,
                                        unsigned char *p_ucConverBuffer);
unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer); //把带小数点的BCD数组转换成long类型的数值。

void key_delete_input(void); //删除按键

void T0_time(); //定时中断函数
void key_service();
void key_scan(); //按键扫描函数 放在定时中断里

void initial_myself();   
void initial_peripheral();


void lcd_display_service(void); //应用层面的液晶屏显示程序
void clear_all_canvas(void);//把画布全部清零

code unsigned char Zf816_0[]=
{
/*--文字:0--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
};

code unsigned char Zf816_1[]=
{
/*--文字:1--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
};

code unsigned char Zf816_2[]=
{
/*--文字:2--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
};

code unsigned char Zf816_3[]=
{
/*--文字:3--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
};

code unsigned char Zf816_4[]=
{
/*--文字:4--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
};

code unsigned char Zf816_5[]=
{
/*--文字:5--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
};

code unsigned char Zf816_6[]=
{
/*--文字:6--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
};


code unsigned char Zf816_7[]=
{
/*--文字:7--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
};

code unsigned char Zf816_8[]=
{
/*--文字:8--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
};

code unsigned char Zf816_9[]=
{
/*--文字:9--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
};


code unsigned char Zf816_nc[]=//空字模
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Zf816_dot[]=//小数点
{
/*--文字:.--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,
};

code unsigned char Zf816_mao_hao[]=//冒号
{
/*--文字::--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
};

code unsigned char Hz1616_yi[]=
{
/*--文字:一--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Hz1616_xiang[]=
{
/*--文字:项--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24,
0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00,
};

code unsigned char Hz1616_shu[]=
{
/*--文字:数--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4,
0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00,
};

code unsigned char Hz1616_zhu[]=
{
/*--文字:组--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08,
0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00,
};

code unsigned char Hz1616_zhi[]=
{
/*--文字:值--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x10,0x40,0x18,0x60,0x17,0xFC,0x10,0x40,0x20,0x80,0x33,0xF8,0x62,0x08,0xA3,0xF8,
0x22,0x08,0x23,0xF8,0x22,0x08,0x23,0xF8,0x22,0x08,0x22,0x08,0x2F,0xFE,0x20,0x00,
};

/* 注释一:
* 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字.
*注意,这节内容的画布跟前面79章节的画布大小不一样,79节前面的横向是4个字节,这节的横向是6个字节。
*/
unsigned char ucCanvasBuffer[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,//上半屏
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,

//------------上半屏和下半屏的分割线-----------

0x00,0x00,0x00,0x00,0x00,0x00,//下半屏
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
};


/* 注释二:
* 以下5个变量记录一个参数的5种信息,包括小数点的数量,小数点个数,数据的位置,数组具体值,整数个数
*/
unsigned char ucDotCnt_1=0;//记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。
unsigned char ucDataBuffer_1={0,10,10,10,10,10}; //一项的BCD码数组缓冲
unsigned char ucIntCnt_1=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效

unsigned long ulData_1=0; //用一个long变量表示BCD码的具体数值。


unsigned char ucKeyStep=1;//按键扫描步骤变量

unsigned char ucKeySec=0;   //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

unsigned char ucRowRecord=1; //记录当前扫描到第几列了


unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器


unsigned char ucWd=1; //窗口变量
unsigned char ucPart=2; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量1代表更新显示,响应函数内部会自动把它清零
unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量1代表更新显示,响应函数内部会自动把它清零

void main()
{
      initial_myself();      //第一区,上电后马上初始化
      delay_long(100);       //一线,延时线。延时一段时间
      initial_peripheral();//第二区,上电后延时一段时间再初始化

      while(1)   //第三区
      {
                  key_service(); //按键服务程序
            lcd_display_service(); //应用层面的液晶屏显示程序
      }

}


void initial_myself()//第一区 上电后马上初始化
{

   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

   TMOD=0x01;//设置定时器0为工作方式1

   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
   TL0=0x2f;
}
void initial_peripheral() //第二区 上电后延时一段时间再初始化
{
    LCDInit(); //初始化12864 内部包含液晶模块的复位


    EA=1;   //开总中断
    ET0=1;    //允许定时中断
    TR0=1;    //启动定时中断

}


void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断

key_scan();//按键扫描函数 放在定时中断里

if(uiVoiceCnt!=0)
{
   uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
         beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
   ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
         beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}


TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}


void key_scan()//按键扫描函数 放在定时中断里
{

switch(ucKeyStep)
{
   case 1:   //按键扫描输出第ucRowRecord列低电平
            if(ucRowRecord==1)//第一列输出低电平
                  {
             key_dr1=0;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==2)//第二列输出低电平
                  {
             key_dr1=1;      
             key_dr2=0;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==3)//第三列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=0;   
             key_dr4=1;
                  }
            else   //第四列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=0;
                  }

          uiKeyTimeCnt=0;//延时计数器清零
          ucKeyStep++;   //切换到下一个运行步骤
            break;

   case 2:   //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;   //切换到下一个运行步骤
                  }
            break;

   case 3:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {
             ucKeyStep=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描
             ucKeyLock=0;//按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙   
   
                         ucRowRecord++;//输出下一列
                         if(ucRowRecord>4)
                         {
                            ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
                         }

          }
                  else if(ucKeyLock==0)//有按键按下,且是第一次触发
                  {
                     if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=1;//触发1号键 对应朱兆祺学习板的S1键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=2;//触发2号键 对应朱兆祺学习板的S2键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=3;//触发3号键 对应朱兆祺学习板的S3键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=4;//触发4号键 对应朱兆祺学习板的S4键
                           }

                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=5;//触发5号键 对应朱兆祺学习板的S5键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=6;//触发6号键 对应朱兆祺学习板的S6键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=7;//触发7号键 对应朱兆祺学习板的S7键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=8;//触发8号键 对应朱兆祺学习板的S8键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=9;//触发9号键 对应朱兆祺学习板的S9键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=10;//触发10号键 对应朱兆祺学习板的S10键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=11;//触发11号键 对应朱兆祺学习板的S11键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=12;//触发12号键 对应朱兆祺学习板的S12键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=13;//触发13号键 对应朱兆祺学习板的S13键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=14;//触发14号键 对应朱兆祺学习板的S14键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=15;//触发15号键 对应朱兆祺学习板的S15键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=16;//触发16号键 对应朱兆祺学习板的S16键
                           }
                              }
                        
                         }
                  
                  }
            break;

}


}


void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
    case 1:// 数字1 对应朱兆祺学习板的S1键
          key_number_input(1); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
    case 2:// 数字2 对应朱兆祺学习板的S2键
          key_number_input(2); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 3:// 数字3 对应朱兆祺学习板的S3键
          key_number_input(3); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 4:// 数字4 对应朱兆祺学习板的S4键
          key_number_input(4); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 5:// 数字5 对应朱兆祺学习板的S5键
          key_number_input(5); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 6:// 数字6 对应朱兆祺学习板的S6键
          key_number_input(6); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 7:// 数字7 对应朱兆祺学习板的S7键
          key_number_input(7); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8: //数字8 对应朱兆祺学习板的S8键
          key_number_input(8); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 数字9 对应朱兆祺学习板的S9键
          key_number_input(9); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 数字0对应朱兆祺学习板的S10键
          key_number_input(0); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 小数点按键 对应朱兆祺学习板的S11键
          key_number_input(11); //输入数字按键11代表小数点
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 本节暂时不用 对应朱兆祺学习板的S12键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 加按键 对应朱兆祺学习板的S13键   
          ulData_1++;
          if(ulData_1>99999)
          {
            ulData_1=99999;
          }

          data_to_buffer(ulData_1,//把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
                                             2,//小数点最大个数
                                               6,//数组缓冲最大个数
                                               &ucDotCnt_1,
                                               &ucDotBitS_1,
                                               &ucWdPartCnt_1,
                                               ucDataBuffer_1);//被转换成的数组

          ucWd1Part1Update=1;   //实时更新显示数组
          ucWd1Part2Update=1;   //实时更新显示数值

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 减按键对应朱兆祺学习板的S14键   
          ulData_1--;
          if(ulData_1>99999) //unsigned long类型的变量0减去1会变成0xffffffff
          {
            ulData_1=0;
          }

          data_to_buffer(ulData_1,//把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
                                             2,//小数点最大个数
                                               6,//数组缓冲最大个数
                                               &ucDotCnt_1,
                                               &ucDotBitS_1,
                                               &ucWdPartCnt_1,
                                               ucDataBuffer_1);//被转换成的数组

          ucWd1Part1Update=1;   //实时更新显示数组
          ucWd1Part2Update=1;   //实时更新显示数值

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 本节暂时不用 对应朱兆祺学习板的S15键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 16:// 清除按键 对应朱兆祺学习板的S16键
          key_delete_input(); //删除按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
}               
}



void key_number_input(unsigned char ucKeyNumber) //输入数字按键
{

    switch(ucWd)
    {
       case 1:   //第1窗口。本节程序只有1个窗口
             switch(ucPart)
             {

               case 1://1窗口第1项
                      set_data(ucKeyNumber,
                                                   2,//小数点最大个数
                                                           6,//数组缓冲最大个数
                                                     &ucDotCnt_1,
                                                           &ucDotBitS_1,
                                                           &ucWdPartCnt_1,
                                                           ucDataBuffer_1,
                                                           3, //整数部分的最大个数
                                                           &ucIntCnt_1);

                                          ulData_1=buffer_to_data(6,2,ucDataBuffer_1); //把带小数点的BCD码数组转换成long数值。
                      ucWd1Part1Update=1;//第一行局部更新显示
                                          ucWd1Part2Update=1;//第二行局部更新显示
                      break;               
             }
                                       
             break;
    }                        
                              
}


/* 注释三:
* 涉及到参数的4种信息,包括小数点的数量,小数点的个数,数据的位置,数组具体值,整数的数量,整数的个数,以及它们之间的相互作用关系。
* 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。
* 第1个参数ucKeyNumberTemp是当前按键输入的数值。
* 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。
* 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。
* 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
* 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
* 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
* 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。
* 第8个参数ucIntCntMax是限定被设置参数的整数部分的最大位数。
* 第9个参数*p_ucIntCnt是记录当前输入的整数部分个数,如果整数部分的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
*/
void set_data(unsigned char ucKeyNumberTemp,
            unsigned char ucDotBitMax,
            unsigned char ucDataCntMax,
            unsigned char *p_ucDotCnt,
            unsigned char *p_ucDotBitS,
                          unsigned char *p_ucWdPartCnt,
                          unsigned char *p_ucSetDataBuffer,
                          unsigned char ucIntCntMax,
                          unsigned char *p_ucIntCnt)
{
                  unsigned int i;

                  if(ucKeyNumberTemp==11) //等于小数点
                  {
                     if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。
                     {
                           return; //直接返回退出
                     }
                     else if(*p_ucDotCnt>0)//小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。
                     {
                           return; //直接返回退出
                     }
                     else//否则有效,记录当前已经包含一个小数点的信息。
                     {
                           *p_ucDotCnt=1;//只能包含一个小数点
                     }
                  }
                  else//如果输入的不是小数点
                  {
                        if(*p_ucDotCnt==1) //如果之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据
                                                {
                           if(*p_ucDotBitS<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
                           {
                               *p_ucDotBitS=(*p_ucDotBitS)+1;
                           }
                           else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
                           {
                              return; //直接返回退出
                           }
                                          }
                                                else if(*p_ucIntCnt<ucIntCntMax)//如果之前没有输入小数点,那么输入的就是整数个数超,整数个数没有超过极限
                                                {
                                                  *p_ucIntCnt=(*p_ucIntCnt)+1;
                                                }
                                                else //整数个数超过极限
                                                {
                           return; //直接返回退出
                                                }
                  }

            
            
                  if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer==0&&ucKeyNumberTemp!=11)//如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
                  {
                        ;
                  }      
                  else//否则,移位
                  {               
                     for(i=0;i<(ucDataCntMax-1);i++)//移位
                     {
                        p_ucSetDataBuffer=p_ucSetDataBuffer;
                     }
                     *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
                  }
                  p_ucSetDataBuffer=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
                                                               
      

}


/* 注释四:
* 功能:把一个带小数点的BCD码数组转换成一个long类型的数值。
* 第1个参数ucConverDataSize是这个数组的最大有效个数。
* 第2个参数ucConverDotCnt是这个数组要转换成的long数值带几个小数点
* 第3个参数*p_ucConverBuffer是具体此数组的数据
* 函数最后返回被转换的long数值。
*/
unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer)
{
   unsigned long ulConverResult=0;
   unsigned long ulConverResultTemp=0;
   unsigned char ucConverResultBuffer; //因为本节内容的ucConverDataSize是6,所以取6.
   unsigned char i;
   unsigned char j;
   unsigned char ucConverFlag;

   for(i=0;i<ucConverDataSize;i++)
   {
      ucConverResultBuffer=0;//先把临时缓冲区清零
   }

   j=0;
   ucConverFlag=0;
   for(i=0;i<ucConverDataSize;i++)
   {
       if(p_ucConverBuffer==11) //小数点
       {
          ucConverFlag=i; //记录小数点的位置
       }
       else if(p_ucConverBuffer<10)
       {
          ucConverResultBuffer=p_ucConverBuffer;//提取数组中的有效数字
          j++;
       }


   }


   for(i=0;i<ucConverDataSize;i++)   //通过处理每一位从而合成一个long类型的数值
   {
       ulConverResultTemp=0;
       ulConverResultTemp=ucConverResultBuffer;
       for(j=0;j<i;j++)
       {
         ulConverResultTemp=ulConverResultTemp*10;//把每一位对应的进位扩大到对应的倍数
       }
       ulConverResult=ulConverResult+ulConverResultTemp;
   }


   for(i=ucConverFlag;i<ucConverDotCnt;i++) //根据数组小数点的位置和实际要转换成的小数点个数,来扩大到对应的倍数。
   {
      ulConverResult=ulConverResult*10;
   }

   return ulConverResult;
}


/* 注释五:
* 本节的核心函数,值得好好研究!
* 功能:把一个long类型的数值转换成一个带小数点的BCD码数组
* 第1个参数ulWillConverData是即将被转换的unsigned long类型数值。
* 第2个参数ucConverDotCnt是这个数值带几个小数点
* 第3个参数ucConverDataSize是这个数组的最大有效个数。
* 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
* 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
* 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
* 第7个参数*p_ucConverBuffer是具体此数组的数据。
*/

void data_to_buffer(unsigned long ulWillConverData,
                  unsigned char ucConverDotCnt,
                                        unsigned char ucConverDataSize,
                                        unsigned char *p_ucDotCnt,
                                        unsigned char *p_ucDotBitS,
                                        unsigned char *p_ucWdPartCnt,
                                        unsigned char *p_ucConverBuffer)
{

   unsigned char ucConverResultBuffer; //因为本节内容的ucConverDataSize是6,所以取6.
   unsigned char i;
   unsigned char ucValidaDotCnt=0;

   if(ucConverDotCnt==0)//没有小数点
   {
      *p_ucDotCnt=0;   //当前没有输入小数点的标志
      *p_ucDotBitS=0;//当前输入的小数点个数是0

      ucConverResultBuffer=10;//没有小数点的时候,第5位必然是显示空格

          //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
      if(ulWillConverData>=10000)
      {
         ucConverResultBuffer=ulWillConverData%100000/10000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=1000)
      {
         ucConverResultBuffer=ulWillConverData%10000/1000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=100)
      {
         ucConverResultBuffer=ulWillConverData%1000/100;
      }
      else
      {
         ucConverResultBuffer=10;
      }

      if(ulWillConverData>=10)
      {
         ucConverResultBuffer=ulWillConverData%100/10;
      }
      else
      {
         ucConverResultBuffer=10;
      }


      ucConverResultBuffer=ulWillConverData%10;


   }
   else if(ucConverDotCnt==1)//1位小数点
   {
      *p_ucDotCnt=1;//当前已经有输入小数点的标志
      *p_ucDotBitS=1; //当前输入的小数点个数是1
      ucConverResultBuffer=11;//第1位填入小数点11

          //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
      if(ulWillConverData>=10000)
      {
         ucConverResultBuffer=ulWillConverData%100000/10000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=1000)
      {
         ucConverResultBuffer=ulWillConverData%10000/1000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=100)
      {
         ucConverResultBuffer=ulWillConverData%1000/100;
      }
      else
      {
         ucConverResultBuffer=10;
      }


      ucConverResultBuffer=ulWillConverData%100/10;
      ucConverResultBuffer=ulWillConverData%10;


   }
   else if(ucConverDotCnt==2)//2位小数点
   {
      *p_ucDotCnt=1;//当前已经有输入小数点的标志
      *p_ucDotBitS=2; //当前输入的小数点个数是2

      ucConverResultBuffer=11; //第2位填入小数点11

          //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
      if(ulWillConverData>=10000)
      {
         ucConverResultBuffer=ulWillConverData%100000/10000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=1000)
      {
         ucConverResultBuffer=ulWillConverData%10000/1000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   


      ucConverResultBuffer=ulWillConverData%1000/100;
      ucConverResultBuffer=ulWillConverData%100/10;
      ucConverResultBuffer=ulWillConverData%10;
   }


   ucValidaDotCnt=0;
   for(i=0;i<ucConverDataSize;i++)
   {
      if(ucConverResultBuffer!=10)//统计数组有效的BCD码位数
      {
         ucValidaDotCnt++;
      }
      p_ucConverBuffer=ucConverResultBuffer;//把转换的结果传输给实际的数组用来外部显示
   
   }

   *p_ucWdPartCnt=ucValidaDotCnt-1;//当前显示的实际位置
}

void key_delete_input(void) //删除按键
{
    static unsigned int i;

   switch(ucWd)
   {
      case 1:   //第1窗口。本节程序只有1个窗口
         switch(ucPart)
         {
            case 1://1窗口第1行
                  //清零
                                        ulData_1=0; //long数值清零
                                       ucIntCnt_1=0;
                  ucDotBitS_1=0;
                  ucDotCnt_1=0;
                  ucWdPartCnt_1=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_1=10;
                  }
                  ucDataBuffer_1=0; //第0个位置填入0
      
                  ucWd1Part1Update=1;//第一行局部更新显示
                                  ucWd1Part2Update=1;//第二行局部更新显示
                  break;               
            case 2://1窗口第2行
                  //清零
                                        ulData_1=0; //long数值清零
                                       ucIntCnt_1=0;
                  ucDotBitS_1=0;
                  ucDotCnt_1=0;
                  ucWdPartCnt_1=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_1=10;
                  }
                  ucDataBuffer_1=0; //第0个位置填入0
      
                  ucWd1Part1Update=1;//第一行局部更新显示
                                  ucWd1Part2Update=1;//第二行局部更新显示
                  break;
         }
                                       
         break;
      
   }                        
                              
}

unsigned char *number_to_matrix(unsigned charucBitNumber)
{
    unsigned char *p_ucAnyNumber;//此指针根据ucBitNumber数值的大小,分别调用不同的字库。

      switch(ucBitNumber)//根据ucBitNumber数值的大小,分别调用不同的字库。
      {
            case 0:
             p_ucAnyNumber=Zf816_0;
                     break;
            case 1:
             p_ucAnyNumber=Zf816_1;
                     break;
            case 2:
             p_ucAnyNumber=Zf816_2;
                     break;
            case 3:
             p_ucAnyNumber=Zf816_3;
                     break;
            case 4:
             p_ucAnyNumber=Zf816_4;
                     break;
            case 5:
             p_ucAnyNumber=Zf816_5;
                     break;
            case 6:
             p_ucAnyNumber=Zf816_6;
                     break;
            case 7:
             p_ucAnyNumber=Zf816_7;
                     break;
            case 8:
             p_ucAnyNumber=Zf816_8;
                     break;
            case 9:
             p_ucAnyNumber=Zf816_9;
                     break;
            case 10://空格
             p_ucAnyNumber=Zf816_nc;
                     break;
                        case 11:   //小数点
             p_ucAnyNumber=Zf816_dot;
                     break;
                default:   //如果上面的条件都不符合,那么默认指向空字模
             p_ucAnyNumber=Zf816_nc;
                     break;
      }

    return p_ucAnyNumber;//返回转换结束后的指针
}



void lcd_display_service(void) //应用层面的液晶屏显示程序
{


    static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
    static unsigned char ucCursorFlag;//光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
    static unsigned int i;
        static unsigned char ucDataBuffer_temp; //分解一个10进制的long类型数据的每一位

    switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
    {
      case 1:   //显示窗口1的数据
               if(ucWd1Update==1)//窗口1整屏更新,里面只放那些不用经常刷新显示的内容
               {
                     ucWd1Update=0;//及时清零,避免一直更新

                     ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
                     ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进

                     display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
                     clear_all_canvas();//把画布全部清零

                     display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一项数组
                     display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,0,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,0,Hz1616_zhu,0,2,16,0);
                     display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

                     display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一项数值
                     display_lattice(1,16,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,16,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,16,Hz1616_zhi,0,2,16,0);
                     display_lattice(4,16,Zf816_mao_hao,0,1,16,0); //冒号            

               }

               if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part1Update=0; //及时清零,避免一直更新

                        if(ucPart==1) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_1);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }

               if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part2Update=0; //及时清零,避免一直更新

                        if(ucPart==2) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        if(ulData_1>=10000)
                                                {
                                                   ucDataBuffer_temp=ulData_1%100000/10000;
                                                }
                                                else
                                                {
                                                   ucDataBuffer_temp=10; //空格
                                                }

                        if(ulData_1>=1000)
                                                {
                                                      ucDataBuffer_temp=ulData_1%10000/1000;
                        }
                                                else
                                                {
                                                      ucDataBuffer_temp=10; //空格
                        }

                                                ucDataBuffer_temp=ulData_1%1000/100;
                                                ucDataBuffer_temp=11;//11代表小数点
                                                ucDataBuffer_temp=ulData_1%100/10;
                                                ucDataBuffer_temp=ulData_1%10/1;
                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,16,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }

                     
               break;
      //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...      
    }

}



void clear_all_canvas(void)//把画布全部清零
{
   unsigned int j=0;
   unsigned int i=0;

   for(j=0;j<16;j++)//这里的16表示画布有16行
   {
      for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
      {
                  ucCanvasBuffer=0x00;
      }
   }         

}





void display_clear(unsigned char ucFillDate) // 清屏全部显示空填充0x00   全部显示点阵用0xff
{   

    unsigned char x,y;
    WriteCommand(0x34);//关显示缓冲指令            
    WriteCommand(0x34);//关显示缓冲指令故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
    y=0;
    while(y<32)//y轴的范围0至31
    {
         WriteCommand(y+0x80);      //垂直地址
         WriteCommand(0x80);          //水平地址
         for(x=0;x<32;x++)//256个横向点,有32个字节
         {
            LCDWriteData(ucFillDate);
         }
         y++;
    }
    WriteCommand(0x36); //开显示缓冲指令

}

/* 注释六:
* 注意,这节内容的画布跟第79节前面的画布大小不一样,第79节前面的横向是4个字节,这节的横向是6个字节。
* 把字模插入画布的函数.
* 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是在画布中的坐标体系。
* x的范围是0至5,因为画布的横向只要6个字节。y的范围是0至15,因为画布的纵向只有16行。
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
*/
void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;
   for(j=0;j<y_amount;j++)
   {
      for(i=0;i<x_amount;i++)
      {
            ucTemp=ucArray;
            if(ucFbFlag==0)
            {
               ucCanvasBuffer[(y+j)*6+x+i]=ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
            }
            else
            {
               ucCanvasBuffer[(y+j)*6+x+i]=~ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
            }
      }
   }         

}

/* 注释七:
* 显示任意点阵函数.
* 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
* 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
* 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
*/
void display_lattice(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;

//注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
//WriteCommand(0x34);//关显示缓冲指令            
//WriteCommand(0x34);//关显示缓冲指令故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
   for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
   {
       WriteCommand(y+j+0x80);      //垂直地址
       WriteCommand(x+0x80);          //水平地址
       for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
       {
         ucTemp=ucArray; //uiOffSetAddr是字模数组的偏移地址
         if(ucFbFlag==1)//反白显示
         {
               ucTemp=~ucTemp;
         }
         LCDWriteData(ucTemp);
          //         delay_short(30000);//把上一节这个延时函数去掉,加快刷屏速度
      }
   }
   WriteCommand(0x36); //开显示缓冲指令
}




void SendByteToLcd(unsigned char ucData)//发送一个字节数据到液晶模块
{
      unsigned char i;
      for ( i = 0; i < 8; i++ )
      {
                if ( (ucData << i) & 0x80 )
                {
                        LCDSID_dr = 1;
                }
                else
                {
                        LCDSID_dr = 0;
                }
                LCDCLK_dr = 0;
                LCDCLK_dr = 1;
      }
}

void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
{
      SendByteToLcd( 0xf8 + (ucWRS << 1) );
      SendByteToLcd( ucWData & 0xf0 );
      SendByteToLcd( (ucWData << 4) & 0xf0);
}


void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
{

      LCDCS_dr = 0;
      LCDCS_dr = 1;
      SPIWrite(ucCommand, 0);
      delay_short(90);
}

void LCDWriteData(unsigned char ucData)//发送一个字节的数据给液晶模块
{
      LCDCS_dr = 0;
      LCDCS_dr = 1;
      SPIWrite(ucData, 1);
}

void LCDInit(void) //初始化函数内部包括液晶模块的复位
{
      LCDRST_dr = 1;//复位
      LCDRST_dr = 0;
      LCDRST_dr = 1;
}



void delay_short(unsigned int uiDelayShort) //延时函数
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)//内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}




总结陈词:
前面两节都讲了数组和数值的相互转换函数,结合前面的基础,下一节讲数字键盘与液晶菜单的综合程序,欲知详情,请听下回分解----数字键盘与液晶菜单的综合程序。

(未完待续,下节更精彩,不要走开哦)

scream 发表于 2014-12-24 14:16:49

这个一定要支持,经验总结啊,谢谢楼主的分享精神

与狼共舞 发表于 2014-12-24 14:36:53

吴坚鸿 发表于 2014-12-15 20:10
这个没关系的。我写书从来不跟同行刻意对比。我不追求速度,我只追求每一节的高质量和灵感,我活在我的世 ...

说的太好了,顶鸿哥,质量才是最重要的。鸿哥这个编程风格会影响好多人,继续支持!

彼岸花开@ 发表于 2014-12-24 15:06:47

与狼共舞 发表于 2014-12-24 14:36
说的太好了,顶鸿哥,质量才是最重要的。鸿哥这个编程风格会影响好多人,继续支持! ...

学习这种编程思想,出于考虑需要使用了一个源文件,以及大量的全局变量,这个问题,需要学习之后,自己解决,。。这样你的风格才会更好。。

阿文 发表于 2014-12-24 15:34:43

额,写了这么多了,辛苦了。

clesun 发表于 2014-12-25 09:57:50

流弊就是任性,必须得顶,楼主,好人呐

吴坚鸿 发表于 2014-12-26 11:15:17

第八十六节:数字键盘与液晶菜单的综合程序。

开场白:
    前面已经介绍完数值跟BCD码数组相互转换的算法,但是按键只能设置一项数据。如果多增加一项数据,变成两项数据,按键与显示菜单之间该如何关联起来,这样的程序框架是什么样的?其实很简单很有规律的,只需要在前面的基础上,在按键和显示函数里,根据不同的uPart行变量添加进不同的代码,即可完成。这就是鸿哥写的程序代码,不管添加多少代码,都是有一个“道”可循,非常有规律性。

具体内容,请看源代码讲解。

(1)        硬件平台:
基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,S12按键是光标移动按键,S13按键是加按键,S14按键是减按键,清零键对应S16,其它按键不用。

(2)        实现功能:
通过S12光标移动按键,可以把负显光标切换到不同的行里面,根据光标所在位置,
通过S13,S14这两个加减按键可以更改对应的数。第1行和第2行的数据会彼此有关联,只要改其中一个,另外一个就会同步被更新。同理,第3行和第4行的数据也会彼此有关联,只要改其中一个,另外一个也会同步被更新。

(3)源代码讲解如下:
#include "REG52.H"


#define const_voice_short40   //蜂鸣器短叫的持续时间

#define const_key_time10    //按键去抖动延时的时间

sbit key_sr1=P0^0; //第一行输入
sbit key_sr2=P0^1; //第二行输入
sbit key_sr3=P0^2; //第三行输入
sbit key_sr4=P0^3; //第四行输入

sbit key_dr1=P0^4; //第一列输出
sbit key_dr2=P0^5; //第二列输出
sbit key_dr3=P0^6; //第三列输出
sbit key_dr4=P0^7; //第四列输出

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

sbitLCDCS_dr= P1^6;//片选线
sbitLCDSID_dr = P1^7;//串行数据线
sbitLCDCLK_dr = P3^2;//串行时钟线
sbitLCDRST_dr = P3^4;//复位线

void SendByteToLcd(unsigned char ucData);//发送一个字节数据到液晶模块
void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
void LCDInit(void);//初始化函数内部包括液晶模块的复位
void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
void display_lattice(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
unsigned char *number_to_matrix(unsigned charucBitNumber); //把一位数字转换成字模首地址的函数
void delay_short(unsigned int uiDelayshort); //延时
void delay_long(unsigned int uiDelayLong);

void key_number_input(unsigned char ucKeyNumber); //输入数字按键
void set_data(unsigned char ucKeyNumberTemp, //设置参数
            unsigned char ucDotBitMax,
            unsigned char ucDataCntMax,
            unsigned char *p_ucDotCnt,
            unsigned char *p_ucDotBitS,
                          unsigned char *p_ucWdPartCnt,
                          unsigned char *p_ucSetDataBuffer,
                          unsigned char ucIntCntMax,
                          unsigned char *p_ucIntCnt);

void data_to_buffer(unsigned long ulWillConverData,//把数值转换成数组
                  unsigned char ucConverDotCnt,
                                        unsigned char ucConverDataSize,
                                        unsigned char *p_ucDotCnt,
                                        unsigned char *p_ucDotBitS,
                                        unsigned char *p_ucWdPartCnt,
                                        unsigned char *p_ucConverBuffer);
unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer); //把带小数点的BCD数组转换成long类型的数值。

void key_delete_input(void); //删除按键

void T0_time(); //定时中断函数
void key_service();
void key_scan(); //按键扫描函数 放在定时中断里

void initial_myself();   
void initial_peripheral();


void lcd_display_service(void); //应用层面的液晶屏显示程序
void clear_all_canvas(void);//把画布全部清零

code unsigned char Zf816_0[]=
{
/*--文字:0--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
};

code unsigned char Zf816_1[]=
{
/*--文字:1--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
};

code unsigned char Zf816_2[]=
{
/*--文字:2--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
};

code unsigned char Zf816_3[]=
{
/*--文字:3--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
};

code unsigned char Zf816_4[]=
{
/*--文字:4--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
};

code unsigned char Zf816_5[]=
{
/*--文字:5--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
};

code unsigned char Zf816_6[]=
{
/*--文字:6--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
};


code unsigned char Zf816_7[]=
{
/*--文字:7--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
};

code unsigned char Zf816_8[]=
{
/*--文字:8--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
};

code unsigned char Zf816_9[]=
{
/*--文字:9--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
};


code unsigned char Zf816_nc[]=//空字模
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Zf816_dot[]=//小数点
{
/*--文字:.--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,
};

code unsigned char Zf816_mao_hao[]=//冒号
{
/*--文字::--*/
/*--宋体12;此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
};

code unsigned char Hz1616_yi[]=
{
/*--文字:一--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Hz1616_er[]=
{
/*--文字:二--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x00,0x10,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Hz1616_xiang[]=
{
/*--文字:项--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24,
0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00,
};

code unsigned char Hz1616_shu[]=
{
/*--文字:数--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4,
0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00,
};

code unsigned char Hz1616_zhu[]=
{
/*--文字:组--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08,
0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00,
};

code unsigned char Hz1616_zhi[]=
{
/*--文字:值--*/
/*--宋体12;此字体下对应的点阵为:宽x高=16x16   --*/
0x10,0x40,0x18,0x60,0x17,0xFC,0x10,0x40,0x20,0x80,0x33,0xF8,0x62,0x08,0xA3,0xF8,
0x22,0x08,0x23,0xF8,0x22,0x08,0x23,0xF8,0x22,0x08,0x22,0x08,0x2F,0xFE,0x20,0x00,
};

/* 注释一:
* 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字.
*注意,这节内容的画布跟前面79章节的画布大小不一样,79节前面的横向是4个字节,这节的横向是6个字节。
*/
unsigned char ucCanvasBuffer[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,//上半屏
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,

//------------上半屏和下半屏的分割线-----------

0x00,0x00,0x00,0x00,0x00,0x00,//下半屏
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
};


/* 注释二:
* 以下5个变量记录一个参数的5种信息,包括小数点的数量,小数点个数,数据的位置,数组具体值,整数个数
*/
unsigned char ucDotCnt_1=0;//记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。
unsigned char ucDataBuffer_1={0,10,10,10,10,10}; //一项的BCD码数组缓冲
unsigned char ucIntCnt_1=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
unsigned long ulData_1=0; //用一个long变量表示BCD码的具体数值。

unsigned char ucDotCnt_2=0;//记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
unsigned char ucDotBitS_2=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
unsigned char ucWdPartCnt_2=0; //记录当前输入的数据在数组中的位置。
unsigned char ucDataBuffer_2={0,10,10,10,10,10}; //一项的BCD码数组缓冲
unsigned char ucIntCnt_2=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
unsigned long ulData_2=0; //用一个long变量表示BCD码的具体数值。


unsigned char ucKeyStep=1;//按键扫描步骤变量

unsigned char ucKeySec=0;   //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

unsigned char ucRowRecord=1; //记录当前扫描到第几列了


unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器


unsigned char ucWd=1; //窗口变量
unsigned char ucPart=1; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量1代表更新显示,响应函数内部会自动把它清零
unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量1代表更新显示,响应函数内部会自动把它清零
unsigned char ucWd1Part3Update=0; //窗口1的第1行局部更新显示变量1代表更新显示,响应函数内部会自动把它清零
unsigned char ucWd1Part4Update=0; //窗口1的第2行局部更新显示变量1代表更新显示,响应函数内部会自动把它清零
void main()
{
      initial_myself();      //第一区,上电后马上初始化
      delay_long(100);       //一线,延时线。延时一段时间
      initial_peripheral();//第二区,上电后延时一段时间再初始化

      while(1)   //第三区
      {
                  key_service(); //按键服务程序
            lcd_display_service(); //应用层面的液晶屏显示程序
      }

}


void initial_myself()//第一区 上电后马上初始化
{

   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

   TMOD=0x01;//设置定时器0为工作方式1

   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
   TL0=0x2f;
}
void initial_peripheral() //第二区 上电后延时一段时间再初始化
{
    LCDInit(); //初始化12864 内部包含液晶模块的复位


    EA=1;   //开总中断
    ET0=1;    //允许定时中断
    TR0=1;    //启动定时中断

}


void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断

key_scan();//按键扫描函数 放在定时中断里

if(uiVoiceCnt!=0)
{
   uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
         beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
   ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
         beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}


TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}


void key_scan()//按键扫描函数 放在定时中断里
{

switch(ucKeyStep)
{
   case 1:   //按键扫描输出第ucRowRecord列低电平
            if(ucRowRecord==1)//第一列输出低电平
                  {
             key_dr1=0;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==2)//第二列输出低电平
                  {
             key_dr1=1;      
             key_dr2=0;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==3)//第三列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=0;   
             key_dr4=1;
                  }
            else   //第四列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=0;
                  }

          uiKeyTimeCnt=0;//延时计数器清零
          ucKeyStep++;   //切换到下一个运行步骤
            break;

   case 2:   //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;   //切换到下一个运行步骤
                  }
            break;

   case 3:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {
             ucKeyStep=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描
             ucKeyLock=0;//按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙   
   
                         ucRowRecord++;//输出下一列
                         if(ucRowRecord>4)
                         {
                            ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
                         }

          }
                  else if(ucKeyLock==0)//有按键按下,且是第一次触发
                  {
                     if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=1;//触发1号键 对应朱兆祺学习板的S1键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=2;//触发2号键 对应朱兆祺学习板的S2键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=3;//触发3号键 对应朱兆祺学习板的S3键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=4;//触发4号键 对应朱兆祺学习板的S4键
                           }

                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=5;//触发5号键 对应朱兆祺学习板的S5键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=6;//触发6号键 对应朱兆祺学习板的S6键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=7;//触发7号键 对应朱兆祺学习板的S7键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=8;//触发8号键 对应朱兆祺学习板的S8键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=9;//触发9号键 对应朱兆祺学习板的S9键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=10;//触发10号键 对应朱兆祺学习板的S10键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=11;//触发11号键 对应朱兆祺学习板的S11键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=12;//触发12号键 对应朱兆祺学习板的S12键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=13;//触发13号键 对应朱兆祺学习板的S13键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=14;//触发14号键 对应朱兆祺学习板的S14键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=15;//触发15号键 对应朱兆祺学习板的S15键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=16;//触发16号键 对应朱兆祺学习板的S16键
                           }
                              }
                        
                         }
                  
                  }
            break;

}


}


void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
    case 1:// 数字1 对应朱兆祺学习板的S1键
          key_number_input(1); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
    case 2:// 数字2 对应朱兆祺学习板的S2键
          key_number_input(2); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 3:// 数字3 对应朱兆祺学习板的S3键
          key_number_input(3); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 4:// 数字4 对应朱兆祺学习板的S4键
          key_number_input(4); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 5:// 数字5 对应朱兆祺学习板的S5键
          key_number_input(5); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 6:// 数字6 对应朱兆祺学习板的S6键
          key_number_input(6); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 7:// 数字7 对应朱兆祺学习板的S7键
          key_number_input(7); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8: //数字8 对应朱兆祺学习板的S8键
          key_number_input(8); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 数字9 对应朱兆祺学习板的S9键
          key_number_input(9); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 数字0对应朱兆祺学习板的S10键
          key_number_input(0); //输入数字按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 小数点按键 对应朱兆祺学习板的S11键
          key_number_input(11); //输入数字按键11代表小数点
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 光标移动按键 对应朱兆祺学习板的S12键
          //ucPart++;
                  //if(ucPart>4)
                  //{
                  //   ucPart=1;
                  //}

                  switch(ucPart)//根据不同的行来进行不同的操作
                  {
                     case 1:
                              ucPart=2;
                                  
                  ucWd1Part1Update=1;   //实时更新显示数组
                  ucWd1Part2Update=1;   //实时更新显示数值
                              break;
                     case 2:
                              ucPart=3;
                  ucWd1Part2Update=1;   //实时更新显示数组
                  ucWd1Part3Update=1;   //实时更新显示数值
                              break;
                     case 3:
                              ucPart=4;
                  ucWd1Part3Update=1;   //实时更新显示数组
                  ucWd1Part4Update=1;   //实时更新显示数值
                              break;
                     case 4:
                              ucPart=1;
                  ucWd1Part4Update=1;   //实时更新显示数组
                  ucWd1Part1Update=1;   //实时更新显示数值
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 加按键 对应朱兆祺学习板的S13键   
                  switch(ucPart)//根据不同的行来进行不同的操作
                  {

                     case 2:
                  ulData_1++;
                  if(ulData_1>99999)
                  {
                     ulData_1=99999;
                  }

                  data_to_buffer(ulData_1,//把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
                                                   2,//小数点最大个数
                                                         6,//数组缓冲最大个数
                                                       &ucDotCnt_1,
                                                         &ucDotBitS_1,
                                                         &ucWdPartCnt_1,
                                                         ucDataBuffer_1);//被转换成的数组

                  ucWd1Part1Update=1;   //实时更新显示数组
                  ucWd1Part2Update=1;   //实时更新显示数值
                              break;
                     case 4:
                  ulData_2++;
                  if(ulData_2>99999)
                  {
                     ulData_2=99999;
                  }

                  data_to_buffer(ulData_2,//把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
                                                   2,//小数点最大个数
                                                         6,//数组缓冲最大个数
                                                       &ucDotCnt_2,
                                                         &ucDotBitS_2,
                                                         &ucWdPartCnt_2,
                                                         ucDataBuffer_2);//被转换成的数组

                  ucWd1Part3Update=1;   //实时更新显示数组
                  ucWd1Part4Update=1;   //实时更新显示数值
                              break;
                  }


          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 减按键对应朱兆祺学习板的S14键   
                  switch(ucPart)//根据不同的行来进行不同的操作
                  {

                     case 2:
                  ulData_1--;
                  if(ulData_1>99999) //unsigned long类型的变量0减去1会变成0xffffffff
                  {
                     ulData_1=0;
                  }

                  data_to_buffer(ulData_1,//把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
                                                   2,//小数点最大个数
                                                         6,//数组缓冲最大个数
                                                         &ucDotCnt_1,
                                                       &ucDotBitS_1,
                                                         &ucWdPartCnt_1,
                                                         ucDataBuffer_1);//被转换成的数组

                  ucWd1Part1Update=1;   //实时更新显示数组
                  ucWd1Part2Update=1;   //实时更新显示数值
                              break;
                     case 4:
                  ulData_2--;
                  if(ulData_2>99999) //unsigned long类型的变量0减去1会变成0xffffffff
                  {
                     ulData_2=0;
                  }

                  data_to_buffer(ulData_2,//把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
                                                   2,//小数点最大个数
                                                         6,//数组缓冲最大个数
                                                         &ucDotCnt_2,
                                                       &ucDotBitS_2,
                                                         &ucWdPartCnt_2,
                                                         ucDataBuffer_2);//被转换成的数组

                  ucWd1Part3Update=1;   //实时更新显示数组
                  ucWd1Part4Update=1;   //实时更新显示数值
                              break;
                  }



          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 本节暂时不用 对应朱兆祺学习板的S15键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 16:// 清除按键 对应朱兆祺学习板的S16键
          key_delete_input(); //删除按键
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
}               
}



void key_number_input(unsigned char ucKeyNumber) //输入数字按键
{

    switch(ucWd)
    {
       case 1:   //第1窗口。本节程序只有1个窗口
             switch(ucPart)
             {

               case 1://1窗口第1行
                      set_data(ucKeyNumber,
                                                   2,//小数点最大个数
                                                           6,//数组缓冲最大个数
                                                     &ucDotCnt_1,
                                                           &ucDotBitS_1,
                                                           &ucWdPartCnt_1,
                                                           ucDataBuffer_1,
                                                           3, //整数部分的最大个数
                                                           &ucIntCnt_1);

                                          ulData_1=buffer_to_data(6,2,ucDataBuffer_1); //把带小数点的BCD码数组转换成long数值。
                      ucWd1Part1Update=1;//第一行局部更新显示
                                          ucWd1Part2Update=1;//第二行局部更新显示
                      break;   
               case 3://1窗口第3行
                      set_data(ucKeyNumber,
                                                   2,//小数点最大个数
                                                           6,//数组缓冲最大个数
                                                     &ucDotCnt_2,
                                                           &ucDotBitS_2,
                                                           &ucWdPartCnt_2,
                                                           ucDataBuffer_2,
                                                           3, //整数部分的最大个数
                                                           &ucIntCnt_2);

                                          ulData_2=buffer_to_data(6,2,ucDataBuffer_2); //把带小数点的BCD码数组转换成long数值。
                      ucWd1Part3Update=1;//第三行局部更新显示
                                          ucWd1Part4Update=1;//第四行局部更新显示
                      break;

             }
                                       
             break;
    }                        
                              
}


/* 注释三:
* 涉及到参数的4种信息,包括小数点的数量,小数点的个数,数据的位置,数组具体值,整数的数量,整数的个数,以及它们之间的相互作用关系。
* 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。
* 第1个参数ucKeyNumberTemp是当前按键输入的数值。
* 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。
* 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。
* 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
* 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
* 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
* 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。
* 第8个参数ucIntCntMax是限定被设置参数的整数部分的最大位数。
* 第9个参数*p_ucIntCnt是记录当前输入的整数部分个数,如果整数部分的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
*/
void set_data(unsigned char ucKeyNumberTemp,
            unsigned char ucDotBitMax,
            unsigned char ucDataCntMax,
            unsigned char *p_ucDotCnt,
            unsigned char *p_ucDotBitS,
                          unsigned char *p_ucWdPartCnt,
                          unsigned char *p_ucSetDataBuffer,
                          unsigned char ucIntCntMax,
                          unsigned char *p_ucIntCnt)
{
                  unsigned int i;

                  if(ucKeyNumberTemp==11) //等于小数点
                  {
                     if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。
                     {
                           return; //直接返回退出
                     }
                     else if(*p_ucDotCnt>0)//小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。
                     {
                           return; //直接返回退出
                     }
                     else//否则有效,记录当前已经包含一个小数点的信息。
                     {
                           *p_ucDotCnt=1;//只能包含一个小数点
                     }
                  }
                  else//如果输入的不是小数点
                  {
                        if(*p_ucDotCnt==1) //如果之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据
                                                {
                           if(*p_ucDotBitS<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
                           {
                               *p_ucDotBitS=(*p_ucDotBitS)+1;
                           }
                           else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
                           {
                              return; //直接返回退出
                           }
                                          }
                                                else if(*p_ucIntCnt<ucIntCntMax)//如果之前没有输入小数点,那么输入的就是整数个数超,整数个数没有超过极限
                                                {
                                                  *p_ucIntCnt=(*p_ucIntCnt)+1;
                                                }
                                                else //整数个数超过极限
                                                {
                           return; //直接返回退出
                                                }
                  }

            
            
                  if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer==0&&ucKeyNumberTemp!=11)//如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
                  {
                        ;
                  }      
                  else//否则,移位
                  {               
                     for(i=0;i<(ucDataCntMax-1);i++)//移位
                     {
                        p_ucSetDataBuffer=p_ucSetDataBuffer;
                     }
                     *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
                  }
                  p_ucSetDataBuffer=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
                                                               
      

}


/* 注释四:
* 功能:把一个带小数点的BCD码数组转换成一个long类型的数值。
* 第1个参数ucConverDataSize是这个数组的最大有效个数。
* 第2个参数ucConverDotCnt是这个数组要转换成的long数值带几个小数点
* 第3个参数*p_ucConverBuffer是具体此数组的数据
* 函数最后返回被转换的long数值。
*/
unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer)
{
   unsigned long ulConverResult=0;
   unsigned long ulConverResultTemp=0;
   unsigned char ucConverResultBuffer; //因为本节内容的ucConverDataSize是6,所以取6.
   unsigned char i;
   unsigned char j;
   unsigned char ucConverFlag;

   for(i=0;i<ucConverDataSize;i++)
   {
      ucConverResultBuffer=0;//先把临时缓冲区清零
   }

   j=0;
   ucConverFlag=0;
   for(i=0;i<ucConverDataSize;i++)
   {
       if(p_ucConverBuffer==11) //小数点
       {
          ucConverFlag=i; //记录小数点的位置
       }
       else if(p_ucConverBuffer<10)
       {
          ucConverResultBuffer=p_ucConverBuffer;//提取数组中的有效数字
          j++;
       }


   }


   for(i=0;i<ucConverDataSize;i++)   //通过处理每一位从而合成一个long类型的数值
   {
       ulConverResultTemp=0;
       ulConverResultTemp=ucConverResultBuffer;
       for(j=0;j<i;j++)
       {
         ulConverResultTemp=ulConverResultTemp*10;//把每一位对应的进位扩大到对应的倍数
       }
       ulConverResult=ulConverResult+ulConverResultTemp;
   }


   for(i=ucConverFlag;i<ucConverDotCnt;i++) //根据数组小数点的位置和实际要转换成的小数点个数,来扩大到对应的倍数。
   {
      ulConverResult=ulConverResult*10;
   }

   return ulConverResult;
}


/* 注释五:
* 本节的核心函数,值得好好研究!
* 功能:把一个long类型的数值转换成一个带小数点的BCD码数组
* 第1个参数ulWillConverData是即将被转换的unsigned long类型数值。
* 第2个参数ucConverDotCnt是这个数值带几个小数点
* 第3个参数ucConverDataSize是这个数组的最大有效个数。
* 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
* 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
* 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
* 第7个参数*p_ucConverBuffer是具体此数组的数据。
*/

void data_to_buffer(unsigned long ulWillConverData,
                  unsigned char ucConverDotCnt,
                                        unsigned char ucConverDataSize,
                                        unsigned char *p_ucDotCnt,
                                        unsigned char *p_ucDotBitS,
                                        unsigned char *p_ucWdPartCnt,
                                        unsigned char *p_ucConverBuffer)
{

   unsigned char ucConverResultBuffer; //因为本节内容的ucConverDataSize是6,所以取6.
   unsigned char i;
   unsigned char ucValidaDotCnt=0;

   if(ucConverDotCnt==0)//没有小数点
   {
      *p_ucDotCnt=0;   //当前没有输入小数点的标志
      *p_ucDotBitS=0;//当前输入的小数点个数是0

      ucConverResultBuffer=10;//没有小数点的时候,第5位必然是显示空格

          //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
      if(ulWillConverData>=10000)
      {
         ucConverResultBuffer=ulWillConverData%100000/10000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=1000)
      {
         ucConverResultBuffer=ulWillConverData%10000/1000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=100)
      {
         ucConverResultBuffer=ulWillConverData%1000/100;
      }
      else
      {
         ucConverResultBuffer=10;
      }

      if(ulWillConverData>=10)
      {
         ucConverResultBuffer=ulWillConverData%100/10;
      }
      else
      {
         ucConverResultBuffer=10;
      }


      ucConverResultBuffer=ulWillConverData%10;


   }
   else if(ucConverDotCnt==1)//1位小数点
   {
      *p_ucDotCnt=1;//当前已经有输入小数点的标志
      *p_ucDotBitS=1; //当前输入的小数点个数是1
      ucConverResultBuffer=11;//第1位填入小数点11

          //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
      if(ulWillConverData>=10000)
      {
         ucConverResultBuffer=ulWillConverData%100000/10000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=1000)
      {
         ucConverResultBuffer=ulWillConverData%10000/1000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=100)
      {
         ucConverResultBuffer=ulWillConverData%1000/100;
      }
      else
      {
         ucConverResultBuffer=10;
      }


      ucConverResultBuffer=ulWillConverData%100/10;
      ucConverResultBuffer=ulWillConverData%10;


   }
   else if(ucConverDotCnt==2)//2位小数点
   {
      *p_ucDotCnt=1;//当前已经有输入小数点的标志
      *p_ucDotBitS=2; //当前输入的小数点个数是2

      ucConverResultBuffer=11; //第2位填入小数点11

          //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
      if(ulWillConverData>=10000)
      {
         ucConverResultBuffer=ulWillConverData%100000/10000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   

      if(ulWillConverData>=1000)
      {
         ucConverResultBuffer=ulWillConverData%10000/1000;
      }
      else
      {
         ucConverResultBuffer=10;
      }   


      ucConverResultBuffer=ulWillConverData%1000/100;
      ucConverResultBuffer=ulWillConverData%100/10;
      ucConverResultBuffer=ulWillConverData%10;
   }


   ucValidaDotCnt=0;
   for(i=0;i<ucConverDataSize;i++)
   {
      if(ucConverResultBuffer!=10)//统计数组有效的BCD码位数
      {
         ucValidaDotCnt++;
      }
      p_ucConverBuffer=ucConverResultBuffer;//把转换的结果传输给实际的数组用来外部显示
   
   }

   *p_ucWdPartCnt=ucValidaDotCnt-1;//当前显示的实际位置
}

void key_delete_input(void) //删除按键
{
    static unsigned int i;

   switch(ucWd)
   {
      case 1:   //第1窗口。本节程序只有1个窗口
         switch(ucPart)
         {
            case 1://1窗口第1行
                  //清零
                                        ulData_1=0; //long数值清零
                                       ucIntCnt_1=0;
                  ucDotBitS_1=0;
                  ucDotCnt_1=0;
                  ucWdPartCnt_1=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_1=10;
                  }
                  ucDataBuffer_1=0; //第0个位置填入0
      
                  ucWd1Part1Update=1;//第一行局部更新显示
                                  ucWd1Part2Update=1;//第二行局部更新显示
                  break;               
            case 2://1窗口第2行
                  //清零
                                        ulData_1=0; //long数值清零
                                       ucIntCnt_1=0;
                  ucDotBitS_1=0;
                  ucDotCnt_1=0;
                  ucWdPartCnt_1=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_1=10;
                  }
                  ucDataBuffer_1=0; //第0个位置填入0
      
                  ucWd1Part1Update=1;//第一行局部更新显示
                                  ucWd1Part2Update=1;//第二行局部更新显示
                  break;
            case 3://1窗口第3行
                  //清零
                                        ulData_2=0; //long数值清零
                                       ucIntCnt_2=0;
                  ucDotBitS_2=0;
                  ucDotCnt_2=0;
                  ucWdPartCnt_2=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_2=10;
                  }
                  ucDataBuffer_2=0; //第0个位置填入0
      
                  ucWd1Part3Update=1;//第三行局部更新显示
                                  ucWd1Part4Update=1;//第四行局部更新显示
                  break;               
            case 4://1窗口第4行
                  //清零
                                        ulData_2=0; //long数值清零
                                       ucIntCnt_2=0;
                  ucDotBitS_2=0;
                  ucDotCnt_2=0;
                  ucWdPartCnt_2=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_2=10;
                  }
                  ucDataBuffer_2=0; //第0个位置填入0
      
                  ucWd1Part3Update=1;//第三行局部更新显示
                                  ucWd1Part4Update=1;//第四行局部更新显示
                  break;
         }
                                       
         break;
      
   }                        
                              
}

unsigned char *number_to_matrix(unsigned charucBitNumber)
{
    unsigned char *p_ucAnyNumber;//此指针根据ucBitNumber数值的大小,分别调用不同的字库。

      switch(ucBitNumber)//根据ucBitNumber数值的大小,分别调用不同的字库。
      {
            case 0:
             p_ucAnyNumber=Zf816_0;
                     break;
            case 1:
             p_ucAnyNumber=Zf816_1;
                     break;
            case 2:
             p_ucAnyNumber=Zf816_2;
                     break;
            case 3:
             p_ucAnyNumber=Zf816_3;
                     break;
            case 4:
             p_ucAnyNumber=Zf816_4;
                     break;
            case 5:
             p_ucAnyNumber=Zf816_5;
                     break;
            case 6:
             p_ucAnyNumber=Zf816_6;
                     break;
            case 7:
             p_ucAnyNumber=Zf816_7;
                     break;
            case 8:
             p_ucAnyNumber=Zf816_8;
                     break;
            case 9:
             p_ucAnyNumber=Zf816_9;
                     break;
            case 10://空格
             p_ucAnyNumber=Zf816_nc;
                     break;
                        case 11:   //小数点
             p_ucAnyNumber=Zf816_dot;
                     break;
                default:   //如果上面的条件都不符合,那么默认指向空字模
             p_ucAnyNumber=Zf816_nc;
                     break;
      }

    return p_ucAnyNumber;//返回转换结束后的指针
}



void lcd_display_service(void) //应用层面的液晶屏显示程序
{


    static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
    static unsigned char ucCursorFlag;//光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
    static unsigned int i;
        static unsigned char ucDataBuffer_temp; //分解一个10进制的long类型数据的每一位

    switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
    {
      case 1:   //显示窗口1的数据
               if(ucWd1Update==1)//窗口1整屏更新,里面只放那些不用经常刷新显示的内容
               {
                     ucWd1Update=0;//及时清零,避免一直更新

                     ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
                     ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进
                     ucWd1Part3Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
                     ucWd1Part4Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进

                     display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
                     clear_all_canvas();//把画布全部清零

                     display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一项数组
                     display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,0,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,0,Hz1616_zhu,0,2,16,0);
                     display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

                     display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一项数值
                     display_lattice(1,16,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,16,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,16,Hz1616_zhi,0,2,16,0);
                     display_lattice(4,16,Zf816_mao_hao,0,1,16,0); //冒号         
   
                     display_lattice(8,0,Hz1616_er,0,2,16,0);    //二项数组
                     display_lattice(9,0,Hz1616_xiang,0,2,16,0);   
                     display_lattice(10,0,Hz1616_shu,0,2,16,0);   
                     display_lattice(11,0,Hz1616_zhu,0,2,16,0);
                     display_lattice(12,0,Zf816_mao_hao,0,1,16,0); //冒号

                     display_lattice(8,16,Hz1616_er,0,2,16,0);    //二项数值
                     display_lattice(9,16,Hz1616_xiang,0,2,16,0);   
                     display_lattice(10,16,Hz1616_shu,0,2,16,0);   
                     display_lattice(11,16,Hz1616_zhi,0,2,16,0);
                     display_lattice(12,16,Zf816_mao_hao,0,1,16,0); //冒号   

               }

               if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part1Update=0; //及时清零,避免一直更新

                        if(ucPart==1) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_1);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }

               if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part2Update=0; //及时清零,避免一直更新

                        if(ucPart==2) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        if(ulData_1>=10000)
                                                {
                                                   ucDataBuffer_temp=ulData_1%100000/10000;
                                                }
                                                else
                                                {
                                                   ucDataBuffer_temp=10; //空格
                                                }

                        if(ulData_1>=1000)
                                                {
                                                      ucDataBuffer_temp=ulData_1%10000/1000;
                        }
                                                else
                                                {
                                                      ucDataBuffer_temp=10; //空格
                        }

                                                ucDataBuffer_temp=ulData_1%1000/100;
                                                ucDataBuffer_temp=11;//11代表小数点
                                                ucDataBuffer_temp=ulData_1%100/10;
                                                ucDataBuffer_temp=ulData_1%10/1;
                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,16,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }


               if(ucWd1Part3Update==1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part3Update=0; //及时清零,避免一直更新

                        if(ucPart==3) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_2);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(13,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }

               if(ucWd1Part4Update==1) //窗口1的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容
               {
                        ucWd1Part4Update=0; //及时清零,避免一直更新

                        if(ucPart==4) //被选中
                        {
                           ucCursorFlag=1; //反显 显示
                        }
                        else //没被选中
                        {
                            ucCursorFlag=0; //正常 显示
                        }

                        if(ulData_2>=10000)
                                                {
                                                   ucDataBuffer_temp=ulData_2%100000/10000;
                                                }
                                                else
                                                {
                                                   ucDataBuffer_temp=10; //空格
                                                }

                        if(ulData_2>=1000)
                                                {
                                                      ucDataBuffer_temp=ulData_2%10000/1000;
                        }
                                                else
                                                {
                                                      ucDataBuffer_temp=10; //空格
                        }

                                                ucDataBuffer_temp=ulData_2%1000/100;
                                                ucDataBuffer_temp=11;//11代表小数点
                                                ucDataBuffer_temp=ulData_2%100/10;
                                                ucDataBuffer_temp=ulData_2%10/1;
                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(13,16,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
               }
                     
               break;
      //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...      
    }

}



void clear_all_canvas(void)//把画布全部清零
{
   unsigned int j=0;
   unsigned int i=0;

   for(j=0;j<16;j++)//这里的16表示画布有16行
   {
      for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
      {
                  ucCanvasBuffer=0x00;
      }
   }         

}





void display_clear(unsigned char ucFillDate) // 清屏全部显示空填充0x00   全部显示点阵用0xff
{   

    unsigned char x,y;
    WriteCommand(0x34);//关显示缓冲指令            
    WriteCommand(0x34);//关显示缓冲指令故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
    y=0;
    while(y<32)//y轴的范围0至31
    {
         WriteCommand(y+0x80);      //垂直地址
         WriteCommand(0x80);          //水平地址
         for(x=0;x<32;x++)//256个横向点,有32个字节
         {
            LCDWriteData(ucFillDate);
         }
         y++;
    }
    WriteCommand(0x36); //开显示缓冲指令

}

/* 注释六:
* 注意,这节内容的画布跟第79节前面的画布大小不一样,第79节前面的横向是4个字节,这节的横向是6个字节。
* 把字模插入画布的函数.
* 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是在画布中的坐标体系。
* x的范围是0至5,因为画布的横向只要6个字节。y的范围是0至15,因为画布的纵向只有16行。
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
*/
void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;
   for(j=0;j<y_amount;j++)
   {
      for(i=0;i<x_amount;i++)
      {
            ucTemp=ucArray;
            if(ucFbFlag==0)
            {
               ucCanvasBuffer[(y+j)*6+x+i]=ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
            }
            else
            {
               ucCanvasBuffer[(y+j)*6+x+i]=~ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
            }
      }
   }         

}

/* 注释七:
* 显示任意点阵函数.
* 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
* 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
* 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
*/
void display_lattice(unsigned int x,unsigned int y,const unsigned char*ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;

//注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
//WriteCommand(0x34);//关显示缓冲指令            
//WriteCommand(0x34);//关显示缓冲指令故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
   for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
   {
       WriteCommand(y+j+0x80);      //垂直地址
       WriteCommand(x+0x80);          //水平地址
       for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
       {
         ucTemp=ucArray; //uiOffSetAddr是字模数组的偏移地址
         if(ucFbFlag==1)//反白显示
         {
               ucTemp=~ucTemp;
         }
         LCDWriteData(ucTemp);
          //         delay_short(30000);//把上一节这个延时函数去掉,加快刷屏速度
      }
   }
   WriteCommand(0x36); //开显示缓冲指令
}




void SendByteToLcd(unsigned char ucData)//发送一个字节数据到液晶模块
{
      unsigned char i;
      for ( i = 0; i < 8; i++ )
      {
                if ( (ucData << i) & 0x80 )
                {
                        LCDSID_dr = 1;
                }
                else
                {
                        LCDSID_dr = 0;
                }
                LCDCLK_dr = 0;
                LCDCLK_dr = 1;
      }
}

void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
{
      SendByteToLcd( 0xf8 + (ucWRS << 1) );
      SendByteToLcd( ucWData & 0xf0 );
      SendByteToLcd( (ucWData << 4) & 0xf0);
}


void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
{

      LCDCS_dr = 0;
      LCDCS_dr = 1;
      SPIWrite(ucCommand, 0);
      delay_short(90);
}

void LCDWriteData(unsigned char ucData)//发送一个字节的数据给液晶模块
{
      LCDCS_dr = 0;
      LCDCS_dr = 1;
      SPIWrite(ucData, 1);
}

void LCDInit(void) //初始化函数内部包括液晶模块的复位
{
      LCDRST_dr = 1;//复位
      LCDRST_dr = 0;
      LCDRST_dr = 1;
}



void delay_short(unsigned int uiDelayShort) //延时函数
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)//内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}




总结陈词:
    液晶屏显示的内容到这一节为止基本讲完。前面第38节到第45节是讲串口的,我的串口程序大部分都是通过靠时间来识别每一串数据是否接收完毕,只要第41节内容不是靠时间来判断,而是根据特定关键字来快速识别数据串是否接收完毕,下一节我打算结合我最新的一个项目经验,继续讲一个这方面的例子。欲知详情,请听下回分解----当主机连续不断地发送一串串数据给从机时,从机串口如何快速截取有效数据串。

(未完待续,下节更精彩,不要走开哦)

与狼共舞 发表于 2014-12-26 11:42:48

继续关注鸿哥!

1139193886 发表于 2014-12-26 11:44:45

吴老师也是大名鼎鼎的人物

zhongsandaoren 发表于 2014-12-26 11:49:46

这个绝对要支持。

xssr123 发表于 2014-12-26 12:28:16

顶一下,期待更多的大作。

扬帆远航 发表于 2014-12-26 18:30:56

谢谢{:lol:}{:lol:}

吴坚鸿 发表于 2014-12-26 18:34:35

第八十七节:郑文显捐赠的工控项目源代码。

开场白:
根据上一节的预告,本来这节要讲关于串口的一个小项目,但是今天中午的时候,有个厦门客户的出现,让我决定先插入这节内容。
他叫郑文显,是做PLC开发的。今天中午他要我帮他写一个工控程序让他来学习,也是基于朱兆祺51单片机学习板的,他想把这个源代码经过自己修改后移植到他自己做的工控板上。我一开始报价4000元,被他砍价到1000元,我看一下也不算很难就答应了下来。刚才下午花了3个小时终于做好了。郑文显爽快的付了款,并且在电话那里跟我讲,他说独乐乐不如众乐乐,资源只有分享才能发挥它的最大价值,因此他决定要把这个源代码捐赠出来给大家一起学。非常感谢他的慈善壮举。种善因,得善果。好人一生平安。他的这个项目不难,跟我第25节内容很类似,略加修改就可以了。具体功能需求请看以下第(2)点。

(1)        硬件平台:
基于朱兆祺51单片机学习板。

(2)        实现功能:
他的系统要控制2个气缸,没有任何传感器。第1个气缸先伸出去,1秒钟后再收回来。然后第2个气缸再伸出去,1秒钟后再收回来,算完成一个过程,然后重头开始循环下去。每一个过程要计数加1显示在右边的4位数码管上,左边的4位数码管显示设定的最大计数上限,一旦超过这个计数上限就自动停止。有4个按键,一个按键用来启动,一个按键用来急停。另外两个按键是加减按键,用来设置左边显示的最大计数上限。断电要求数据不丢失。如果同时按下加减两个按键,可以清零当前计数的内容。
这4个按键都是独立按键。S1键是加键,S5键是减键,S9键是启动键,S13键是急停键。其中74HC595驱动丝印为D1的LED灯模拟第1个气缸,丝印为D2的LED灯模拟第2个气缸。

(3)源代码讲解如下:
#include "REG52.H"

#define const_voice_short40   //蜂鸣器短叫的持续时间
#define const_key_time1    20    //按键去抖动延时的时间
#define const_key_time2    20    //按键去抖动延时的时间
#define const_key_time3    20    //按键去抖动延时的时间
#define const_key_time4    20    //按键去抖动延时的时间
#define const_key_time12   20   //按键去抖动延时的时间

#define const_1s500//1秒钟大概的定时中断次数



void start24(void);//开始位
void ack24(void);//确认位
void stop24(void);//停止位
unsigned char read24(void);//读取一个字节的时序
void write24(unsigned char dd); //发送一个字节的时序
unsigned char read_eeprom(unsigned int address);   //从一个地址读取出一个字节数据
void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
unsigned int read_eeprom_int(unsigned int address);   //从一个地址读取出一个int类型的数据
void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据


void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time();//定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里

void run(); //设备自动控制程序
void left_to_right();//从左边移动到右边
void right_to_left(); //从右边返回到左边
void up_to_down();   //从上边移动到下边
void down_to_up();    //从下边返回到上边
void led_update();//LED更新函数

void delay_timer(unsigned int uiDelayTimerTemp);

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;//作为中途暂停指示灯 亮的时候表示中途暂停

sbit dig_hc595_sh_dr=P2^0;   //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;

sbit eeprom_scl_dr=P3^7;    //时钟线
sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线


//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f,//0       序号0
0x06,//1       序号1
0x5b,//2       序号2
0x4f,//3       序号3
0x66,//4       序号4
0x6d,//5       序号5
0x7d,//6       序号6
0x07,//7       序号7
0x7f,//8       序号8
0x6f,//9       序号9
0x00,//无      序号10
0x40,//-       序号11
0x73,//P       序号12
};

unsigned char ucKeySec=0;   //被触发的按键编号
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器


unsigned char ucDigShow8;//第8位数码管要显示的内容
unsigned char ucDigShow7;//第7位数码管要显示的内容
unsigned char ucDigShow6;//第6位数码管要显示的内容
unsigned char ucDigShow5;//第5位数码管要显示的内容
unsigned char ucDigShow4;//第4位数码管要显示的内容
unsigned char ucDigShow3;//第3位数码管要显示的内容
unsigned char ucDigShow2;//第2位数码管要显示的内容
unsigned char ucDigShow1;//第1位数码管要显示的内容

unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量

unsigned char ucWd1Part1Update=1; //左边4位数码管更新显示标志
unsigned char ucWd1Part2Update=1; //右边4位数码管更新显示标志

unsigned intuiSetData=18;//需要被设置的计数上限
unsigned intuiRunCnt=0; //实际运行的计数值

unsigned char ucRunTimeFlag=0; //延时计数器的开关
unsigned intuiRunTimeCnt=0;//运动中的时间延时计数器变量

unsigned char ucRunStep=1;//运动控制的步骤变量
unsigned char ucRunFlag=0;//是否启动运行的标志   1代表运行

unsigned char ucDelayTimerFlag=0; //计时器的开关
unsigned intuiDelayTimer=0;


unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。


void main()
{
   initial_myself();
   delay_long(100);   
   initial_peripheral();
   while(1)
   {
      key_service(); //按键服务的应用程序
          run(); //设备自动控制程序
      display_service(); //显示的窗口菜单服务程序
      led_update();//LED更新函数
   }
}


void left_to_right()//从左边移动到右边
{
   ucLed_dr1=1;   // 1代表左右气缸从左边移动到右边

   ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。
}
void right_to_left() //从右边返回到左边
{
   ucLed_dr1=0;   // 0代表左右气缸从右边返回到左边

   ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。
}
void up_to_down()   //从上边移动到下边
{
   ucLed_dr2=1;   // 1代表上下气缸从上边移动到下边

   ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。
}
void down_to_up()    //从下边返回到上边
{
   ucLed_dr2=0;   // 0代表上下气缸从下边返回到上边

   ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。
}


void run() //设备自动控制程序
{
   if(ucRunFlag==1)//是否启动运行的标志
   {
       switch(ucRunStep)
       {

          case 1:    //机械手从左边往右边移动
               left_to_right();
                           ucRunTimeFlag=0; //延时计数器关在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
                             uiRunTimeCnt=0;//时间计数器清零,为接下来延时1秒钟做准备
               ucRunTimeFlag=1; //延时计数器开    感谢郑文显捐助本节源代码
               ucRunStep=2;//这就是鸿哥传说中的怎样灵活控制步骤变量
               break;
          case 2:    //延时1秒
               if(uiRunTimeCnt>const_1s)//延时1秒
               {
                  ucRunStep=3;//这就是鸿哥传说中的怎样灵活控制步骤变量
               }
               break;
          case 3:    //机械手从右边往左边移动
               right_to_left();
                           ucRunTimeFlag=0; //延时计数器关在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
                             uiRunTimeCnt=0;//时间计数器清零,为接下来延时1秒钟做准备
               ucRunTimeFlag=1; //延时计数器开
               ucRunStep=4;//这就是鸿哥传说中的怎样灵活控制步骤变量
               break;
          case 4:    //延时1秒
               if(uiRunTimeCnt>const_1s)//延时1秒
               {
                  ucRunStep=5;//这就是鸿哥传说中的怎样灵活控制步骤变量
               }
               break;
          case 5:    //机械手//从上边移动到下边
               up_to_down();   
                           ucRunTimeFlag=0; //延时计数器关在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
                             uiRunTimeCnt=0;//时间计数器清零,为接下来延时1秒钟做准备
               ucRunTimeFlag=1; //延时计数器开
               ucRunStep=6;//这就是鸿哥传说中的怎样灵活控制步骤变量
               break;
          case 6:    //延时1秒
               if(uiRunTimeCnt>const_1s)//延时1秒
               {
                  ucRunStep=7;//这就是鸿哥传说中的怎样灵活控制步骤变量
               }
               break;
          case 7:    //机械手从下边返回到上边
               down_to_up();   
                           ucRunTimeFlag=0; //延时计数器关在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
                             uiRunTimeCnt=0;//时间计数器清零,为接下来延时1秒钟做准备
               ucRunTimeFlag=1; //延时计数器开感谢郑文显捐助本节源代码
               ucRunStep=8;//这就是鸿哥传说中的怎样灵活控制步骤变量
               break;
          case 8:    //延时1秒
               if(uiRunTimeCnt>const_1s)//延时1秒
               {
                  uiRunCnt++; //实际运行的计数值累加
                                  if(uiRunCnt>9999)//数码管最大显示4位9999,如果超过了,继续默认为9999
                  {
                                     uiRunCnt=9999;
                                  }
                  ucWd1Part2Update=1;//右边4位数码管更新显示

                  write_eeprom_int(2,uiRunCnt); //及时把数据存进EEPROM,避免掉电丢失数据

                                  if(uiRunCnt>=uiSetData) //如果实际的计数大于或者等于设定上限,则停止
                                  {
                                     ucRunFlag=0;//停止
                                       ucRunStep=1;//切换到第一步为下一次准备
                                  }
                                  else
                                  {
                     ucRunStep=1;//切换到第一步继续运行
                                  }
               }
               break;
       }
   }
}


void display_service() //显示的窗口菜单服务程序
{
//加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样减少了一点指令消耗的时间。
            static unsigned char ucTemp4;   //中间过渡变量
            static unsigned char ucTemp3;   //中间过渡变量
            static unsigned char ucTemp2;   //中间过渡变量
            static unsigned char ucTemp1;   //中间过渡变量

            //左边4位数码管显示设置的计数上限
            if(ucWd1Part1Update==1)//左边4位数码管要全部更新显示
            {
               ucWd1Part1Update=0;//及时清零标志,避免一直进来扫描

            //先分解数据用来显示每一位
               ucTemp4=uiSetData/1000;   
               ucTemp3=uiSetData%1000/100;
               ucTemp2=uiSetData%100/10;
               ucTemp1=uiSetData%10;


               if(uiSetData<1000)   
               {
                  ucDigShow8=10;//如果小于1000,千位显示无
               }
               else
               {
                  ucDigShow8=ucTemp4;//第8位数码管要显示的内容
               }

               if(uiSetData<100)
               {
                  ucDigShow7=10;//如果小于100,百位显示无
               }
               else
               {
                  ucDigShow7=ucTemp3;//第7位数码管要显示的内容
               }

               if(uiSetData<10)
               {
                  ucDigShow6=10;//如果小于10,十位显示无
               }
               else
               {
                  ucDigShow6=ucTemp2;//第6位数码管要显示的内容
               }

                ucDigShow5=ucTemp1;//第5位数码管要显示的内容
            }

            //右边4位数码管显示实际的计数
            if(ucWd1Part2Update==1)//右边4位数码管要全部更新显示
            {
               ucWd1Part2Update=0;//及时清零标志,避免一直进来扫描


            //先分解数据用来显示每一位
               ucTemp4=uiRunCnt/1000;   
               ucTemp3=uiRunCnt%1000/100;
               ucTemp2=uiRunCnt%100/10;
               ucTemp1=uiRunCnt%10;


               if(uiRunCnt<1000)   
               {
                  ucDigShow4=10;//如果小于1000,千位显示无
               }
               else
               {
                  ucDigShow4=ucTemp4;//第8位数码管要显示的内容
               }

               if(uiRunCnt<100)
               {
                  ucDigShow3=10;//如果小于100,百位显示无
               }
               else
               {
                  ucDigShow3=ucTemp3;//第7位数码管要显示的内容
               }

               if(uiRunCnt<10)
               {
                  ucDigShow2=10;//如果小于10,十位显示无
               }
               else
               {
                  ucDigShow2=ucTemp2;//第6位数码管要显示的内容
               }

                ucDigShow1=ucTemp1;//第5位数码管要显示的内容
            }
}

void key_scan()//按键扫描函数 放在定时中断里
{
//加了static关键字后,此局部变量不会每次进来函数都被初始化一次,这样可以记录保存上一次执行本函数后的数值
static unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
static unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
static unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
static unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
static unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
static unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
static unsigned intuiKeyTimeCnt4=0; //按键去抖动延时计数器
static unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

static unsigned intuiKeyTimeCnt12=0; //按键去抖动延时计数器
static unsigned char ucKeyLock12=0; //按键触发后自锁的变量标志

if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
   ucKeyLock1=0; //按键自锁标志清零
   uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
   uiKeyTimeCnt1++; //累加定时中断次数
   if(uiKeyTimeCnt1>const_key_time1)
   {
      uiKeyTimeCnt1=0;
      ucKeyLock1=1;//自锁按键置位,避免一直触发
      ucKeySec=1;    //触发1号键
   }
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
   ucKeyLock2=0; //按键自锁标志清零
   uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
   uiKeyTimeCnt2++; //累加定时中断次数
   if(uiKeyTimeCnt2>const_key_time2)
   {
      uiKeyTimeCnt2=0;
      ucKeyLock2=1;//自锁按键置位,避免一直触发
      ucKeySec=2;    //触发2号键
   }
}
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
   ucKeyLock3=0; //按键自锁标志清零
   uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
   uiKeyTimeCnt3++; //累加定时中断次数
   if(uiKeyTimeCnt3>const_key_time3)
   {
      uiKeyTimeCnt3=0;
      ucKeyLock3=1;//自锁按键置位,避免一直触发
      ucKeySec=3;    //触发3号键
   }
}

if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
   ucKeyLock4=0; //按键自锁标志清零
   uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
}
else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
{
   uiKeyTimeCnt4++; //累加定时中断次数
   if(uiKeyTimeCnt4>const_key_time4)
   {
      uiKeyTimeCnt4=0;
      ucKeyLock4=1;//自锁按键置位,避免一直触发
      ucKeySec=4;    //触发4号键
   }
}

//5号组合键
if(key_sr1==1||key_sr2==1)//IO是高电平,说明两个按键没有全部被按下,这时要及时清零一些标志位
{
         ucKeyLock12=0; //按键自锁标志清零
         uiKeyTimeCnt12=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
}
else if(ucKeyLock12==0)//有按键按下,且是第一次被按下
{
   uiKeyTimeCnt12++; //累加定时中断次数
   if(uiKeyTimeCnt12>const_key_time12)
   {
      uiKeyTimeCnt12=0;
      ucKeyLock12=1;//自锁按键置位,避免一直触发
      ucKeySec=5;    //触发5号组合键
            
   }
}


}

void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
    case 1:// 加按键 对应朱兆祺学习板的S1键
          if(ucRunFlag==0)//如果系统还没运行
                  {
             uiSetData++;    //被设置的计数上限
             if(uiSetData>9999) //最大值是9999
             {
                uiSetData=9999;
             }
             ucWd1Part1Update=1;//左边4位数码管更新显示
            
                write_eeprom_int(0,uiSetData); //及时保存数据进EEPROM,避免掉电丢失
             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                  }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          if(ucRunFlag==0)//如果系统还没运行
                  {
             uiSetData--;   
             if(uiSetData>9999)//unsigned int 类型的0减去1会变成65535(0xffff)
             {
                uiSetData=0;//最小值是0
             }
             ucWd1Part1Update=1;//左边4位数码管更新显示

             write_eeprom_int(0,uiSetData); //及时保存数据进EEPROM,避免掉电丢失                  
             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                  }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;
    case 3://启动按键 对应朱兆祺学习板的S9键
          if(ucRunFlag==0&&uiRunCnt<uiSetData)//如果系统还没运行,并且实际运行的次数小于设定的最大次数,则启动
                  {
                      ucRunFlag=1;
            ucRunStep=1;
            uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                  }

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;
    case 4://急停按键 对应朱兆祺学习板的S9键
          ucRunFlag=0; //急停
          ucRunStep=1;
          right_to_left(); //从右边返回到左边
          down_to_up();    //从下边返回到上边

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;
   
    case 5://清零的组合按键 对应朱兆祺学习板的(S1+S5)组合键
          if(ucRunFlag==0)//如果系统还没运行
                  {
             uiRunCnt=0; //实际计数清零
             ucWd1Part2Update=1;//右边4位数码管更新显示

             write_eeprom_int(2,uiRunCnt); //存入uiRunCnt,内部占用2个字节地址
             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                  }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
}               
}



void led_update()//LED更新函数
{
//加了static关键字后,此局部变量不会每次进来函数都被初始化一次,这样可以记录保存上一次执行本函数后的数值
static unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
static unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
         {
            ucLedStatus08_01=ucLedStatus08_01|0x01;
         }
         else
         {
            ucLedStatus08_01=ucLedStatus08_01&0xfe;
         }

       if(ucLed_dr2==1)
         {
            ucLedStatus08_01=ucLedStatus08_01|0x02;
         }
         else
         {
            ucLedStatus08_01=ucLedStatus08_01&0xfd;
         }

       if(ucLed_dr3==1)
         {
            ucLedStatus08_01=ucLedStatus08_01|0x04;
         }
         else
         {
            ucLedStatus08_01=ucLedStatus08_01&0xfb;
         }

       if(ucLed_dr4==1)
         {
            ucLedStatus08_01=ucLedStatus08_01|0x08;
         }
         else
         {
            ucLedStatus08_01=ucLedStatus08_01&0xf7;
         }


       if(ucLed_dr5==1)
         {
            ucLedStatus08_01=ucLedStatus08_01|0x10;
         }
         else
         {
            ucLedStatus08_01=ucLedStatus08_01&0xef;
         }


       if(ucLed_dr6==1)
         {
            ucLedStatus08_01=ucLedStatus08_01|0x20;
         }
         else
         {
            ucLedStatus08_01=ucLedStatus08_01&0xdf;
         }


       if(ucLed_dr7==1)
         {
            ucLedStatus08_01=ucLedStatus08_01|0x40;
         }
         else
         {
            ucLedStatus08_01=ucLedStatus08_01&0xbf;
         }


       if(ucLed_dr8==1)
         {
            ucLedStatus08_01=ucLedStatus08_01|0x80;
         }
         else
         {
            ucLedStatus08_01=ucLedStatus08_01&0x7f;
         }

       if(ucLed_dr9==1)
         {
            ucLedStatus16_09=ucLedStatus16_09|0x01;
         }
         else
         {
            ucLedStatus16_09=ucLedStatus16_09&0xfe;
         }

       if(ucLed_dr10==1)
         {
            ucLedStatus16_09=ucLedStatus16_09|0x02;
         }
         else
         {
            ucLedStatus16_09=ucLedStatus16_09&0xfd;
         }

       if(ucLed_dr11==1)
         {
            ucLedStatus16_09=ucLedStatus16_09|0x04;
         }
         else
         {
            ucLedStatus16_09=ucLedStatus16_09&0xfb;
         }

       if(ucLed_dr12==1)
         {
            ucLedStatus16_09=ucLedStatus16_09|0x08;
         }
         else
         {
            ucLedStatus16_09=ucLedStatus16_09&0xf7;
         }


       if(ucLed_dr13==1)
         {
            ucLedStatus16_09=ucLedStatus16_09|0x10;
         }
         else
         {
            ucLedStatus16_09=ucLedStatus16_09&0xef;
         }


       if(ucLed_dr14==1)
         {
            ucLedStatus16_09=ucLedStatus16_09|0x20;
         }
         else
         {
            ucLedStatus16_09=ucLedStatus16_09&0xdf;
         }


       if(ucLed_dr15==1)
         {
            ucLedStatus16_09=ucLedStatus16_09|0x40;
         }
         else
         {
            ucLedStatus16_09=ucLedStatus16_09&0xbf;
         }


       if(ucLed_dr16==1)
         {
            ucLedStatus16_09=ucLedStatus16_09|0x80;
         }
         else
         {
            ucLedStatus16_09=ucLedStatus16_09&0x7f;
         }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);//74HC595底层驱动函数

   }
}


void display_drive()
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1://显示第1位
         ucDigShowTemp=dig_table;
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2://显示第2位
         ucDigShowTemp=dig_table;
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3://显示第3位
         ucDigShowTemp=dig_table;
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4://显示第4位
         ucDigShowTemp=dig_table;
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5://显示第5位
         ucDigShowTemp=dig_table;
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6://显示第6位
         ucDigShowTemp=dig_table;
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7://显示第7位
         ucDigShowTemp=dig_table;
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
         }
         dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8://显示第8位
         ucDigShowTemp=dig_table;
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }
   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
   {
   ucDisplayDriveStep=1;
   }

}

//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;
   ucTempData=ucDigStatusTemp16_09;//先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucDigStatusTemp08_01;//再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);
   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;
}

//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;
   ucTempData=ucLedStatusTemp16_09;//先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucLedStatusTemp08_01;//再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);
   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;
}



//AT24C02驱动程序
void start24(void)//开始位
{

    eeprom_sda_dr_sr=1;
    eeprom_scl_dr=1;
      delay_short(15);
    eeprom_sda_dr_sr=0;
      delay_short(15);
    eeprom_scl_dr=0;   
}


void ack24(void)//确认位时序
{
    eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入

    eeprom_scl_dr=1;
      delay_short(15);
    eeprom_scl_dr=0;
      delay_short(15);

//在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
//有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
}

void stop24(void)//停止位
{
    eeprom_sda_dr_sr=0;
    eeprom_scl_dr=1;
      delay_short(15);
    eeprom_sda_dr_sr=1;
}



unsigned char read24(void)//读取一个字节的时序
{
      unsigned char outdata,tempdata;


      outdata=0;
                eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
      delay_short(2);
      for(tempdata=0;tempdata<8;tempdata++)
      {
            eeprom_scl_dr=0;
            delay_short(2);
            eeprom_scl_dr=1;
            delay_short(2);
            outdata<<=1;
            if(eeprom_sda_dr_sr==1)outdata++;      
            eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
            delay_short(2);
      }
    return(outdata);
   
}

void write24(unsigned char dd) //发送一个字节的时序
{

      unsigned char tempdata;
      for(tempdata=0;tempdata<8;tempdata++)
      {
                if(dd>=0x80)eeprom_sda_dr_sr=1;
                else eeprom_sda_dr_sr=0;
                dd<<=1;
                delay_short(2);
                eeprom_scl_dr=1;
                delay_short(4);
                eeprom_scl_dr=0;
      }


}



unsigned char read_eeprom(unsigned int address)   //从一个地址读取出一个字节数据
{

   unsigned char dd,cAddress;

   cAddress=address; //把低字节地址传递给一个字节变量。

   EA=0; //禁止中断

   start24(); //IIC通讯开始

   write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
                  //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定

   ack24(); //发送应答信号   
   write24(cAddress); //发送读取的存储地址(范围是0至255)
   ack24(); //发送应答信号

   start24(); //开始
   write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
                  //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
   ack24(); //发送应答信号
   dd=read24(); //读取一个字节
   ack24(); //发送应答信号
   stop24();//停止
   EA=1; //允许中断
   delay_timer(2); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

   return(dd);
}

void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
{
   unsigned char cAddress;   

   cAddress=address; //把低字节地址传递给一个字节变量。


   EA=0; //禁止中断

   start24(); //IIC通讯开始

   write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
                  //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
   ack24(); //发送应答信号
   write24(cAddress);   //发送写入的存储地址(范围是0至255)
   ack24(); //发送应答信号
   write24(dd);//写入存储的数据
   ack24(); //发送应答信号
   stop24();//停止
   EA=1; //允许中断
   delay_timer(4); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

}


unsigned int read_eeprom_int(unsigned int address)   //从一个地址读取出一个int类型的数据
{
   unsigned char ucReadDataH;
   unsigned char ucReadDataL;
   unsigned intuiReadDate;

   ucReadDataH=read_eeprom(address);    //读取高字节
   ucReadDataL=read_eeprom(address+1);//读取低字节

   uiReadDate=ucReadDataH;//把两个字节合并成一个int类型数据
   uiReadDate=uiReadDate<<8;
   uiReadDate=uiReadDate+ucReadDataL;

   return uiReadDate;

}

void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
{
   unsigned char ucWriteDataH;
   unsigned char ucWriteDataL;

   ucWriteDataH=uiWriteData>>8;
   ucWriteDataL=uiWriteData;

   write_eeprom(address,ucWriteDataH); //存入高字节
   write_eeprom(address+1,ucWriteDataL); //存入低字节

}


void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断

if(ucRunTimeFlag==1) //void run函数中的延时计数器开关
{
   uiRunTimeCnt++; //延时计数器
}

if(ucDelayTimerFlag==1)//delay_timer函数中的延时计数器开关
{
   if(uiDelayTimer>0)
         {
         uiDelayTimer--;   //一气呵成的定时器延时方式的计时器
         }

}

key_scan(); //按键扫描函数
if(uiVoiceCnt!=0)
{
   uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
   beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//   beep_dr=1;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
   ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
   beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//   beep_dr=0;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
display_drive();//数码管字模的驱动函数

TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}

void delay_timer(unsigned int uiDelayTimerTemp)
{
    ucDelayTimerFlag=0; //延时计时器关在设置参数前,先关闭计时器
    uiDelayTimer=uiDelayTimerTemp;
    ucDelayTimerFlag=1; //延时计时器开

    while(uiDelayTimer!=0);//一气呵成的定时器方式延时等待

}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)//内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}

void initial_myself()//第一区 初始化单片机
{
/* 注释一:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
led_dr=0;//关闭独立LED灯 感谢郑文显捐助本节源代码
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{

   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;
   ucDigDot6=0;
   ucDigDot5=0;
   ucDigDot4=0;
   ucDigDot3=0;
   ucDigDot2=0;
   ucDigDot1=0;
   EA=1;   //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

   uiSetData=read_eeprom_int(0);//读取uiSetData,内部占用2个字节地址
   if(uiSetData>9999)   //不在范围内
   {
       uiSetData=0;   //填入一个初始化数据
       write_eeprom_int(0,uiSetData); //存入uiSetData,内部占用2个字节地址
   }

   uiRunCnt=read_eeprom_int(2);//读取uiRunCnt,内部占用2个字节地址
   if(uiRunCnt>9999)//不在范围内
   {
       uiRunCnt=0;//填入一个初始化数据
       write_eeprom_int(2,uiRunCnt); //存入uiRunCnt,内部占用2个字节地址
   }

}


总结陈词:
    再次感谢郑文显的无私奉献。前面第38节到第45节是讲串口的,我的串口程序大部分都是通过靠时间来识别每一串数据是否接收完毕,只要第41节内容不是靠时间来判断,而是根据特定关键字来快速识别数据串是否接收完毕,下一节我打算结合我最新的一个项目经验,继续讲一个这方面的例子。欲知详情,请听下回分解----当主机连续不断地发送一串串数据给从机时,从机串口如何快速截取有效数据串。

(未完待续,下节更精彩,不要走开哦)

xiaozhu2014 发表于 2014-12-26 23:33:37

很好很不错

kirahan 发表于 2014-12-26 23:47:44

搬个板凳过来学

lylm123 发表于 2014-12-26 23:54:41

楼主加油,继续学习。

linzhongwen 发表于 2014-12-27 01:23:28

楼主奉献精神真强,多谢啦,好好跟你学

unnormal 发表于 2014-12-27 08:40:22

留下来学习用谢谢啦

yanse1214 发表于 2014-12-29 11:29:34

楼主程序写的真好。但是如果温度采集,AD采样,键盘,串口通讯,时钟 ,液晶显示这几部分同时工作。那么程序中如何安排呢?

374533905 发表于 2014-12-29 17:10:58

大榭楼主,还是不要说楼主其实你是个好人好了。

吴坚鸿 发表于 2014-12-29 18:18:51

yanse1214 发表于 2014-12-29 11:29
楼主程序写的真好。但是如果温度采集,AD采样,键盘,串口通讯,时钟 ,液晶显示这几部分同时工作。那么程序 ...

用switch状态机思想就可以了。如果你仔细研究我的代码,你会发现一种规律,答案就在里面。

tt98 发表于 2014-12-29 19:29:16

继续听讲{:titter:}

孤舟蓑笠翁 发表于 2014-12-29 20:07:52

楼主也很牛,mark,谢谢

jdlee 发表于 2014-12-29 22:07:20

谢谢分享!

扬帆远航 发表于 2014-12-29 22:20:00

看的出来楼主很用心整理这些资料,很是感谢{:lol:}{:lol:}

吴坚鸿 发表于 2014-12-30 13:05:44

第八十八节:电子称连续不断从串口对外发送数据,单片机靠关键字快速截取有效数据串。

开场白:
我前面串口程序大部分都是通过靠时间来识别每一串数据是否接收完毕,有一些串口项目的协议是固定不变的,而且也不需要从机反馈任何应答信号,这类项目只需根据特定关键字来快速识别数据串是否接收完毕即可。比如现在有一种电子称,它的测量范围是0.00克到500.00克,他是靠串口不断对外发送当前重量数据的,每串数据固定长度26个字节,最后两个字节是回车换行符0x0d 0x0a,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,其它的数据可以忽略不计。这类串口框架的思路是:根据数据尾是否有0x0d 0x0a来判断数据串是否有效的,一旦发现有此关键字,再判断总的数据长度是否等于或者大于一串数据的固定长度,如果满足,则把相关标志位置位,通知主函数中的串口服务程序进行处理。同时也及时关闭串口中断,避免在处理串口数据期间受到串口数据的中断干扰,等串口服务程序处理完毕再打开。

具体内容,请看源代码讲解。

(1)        硬件平台:
基于朱兆祺51单片机学习板。

(2)        实现功能:
波特率是:9600。把当前电子称的重量数据显示在数码管上,在电脑上用串口助手软件来模拟电子称发送以下格式协议的3串数据,它的协议很简单,每串数据固定长度26个字节,最后两个字节是回车换行符0x0d 0x0a,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,其它的数据可以忽略不计。
(a)字符是:
ST,GS,+      0.77   g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 20 20 30 2E 37 37 20 20 20 20 20 67 0D 0A
数码管显示:0.77
(b)
字符是:
ST,GS,+    136.39   g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 31 33 36 2E 33 39 20 20 20 20 20 67 0D 0A
数码管显示:136.39
(c)
字符是:
ST,GS,+      0.00   g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 20 20 30 2E 30 30 20 20 20 20 20 67 0D 0A
数码管显示:0.00

(3)源代码讲解如下:
#include "REG52.H"


#define const_rc_size36//接收串口中断数据的缓冲区数组大小

#define const_least_size 26   //一串标准数据的大小

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

void usart_service(void);//串口接收服务程序,在main函数里
void usart_receive(void); //串口接收中断函数

void T0_time();//定时中断函数


sbit dig_hc595_sh_dr=P2^0;   //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;


sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

sbit led_dr=P3^5;//独立LED灯


//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f,//0       序号0
0x06,//1       序号1
0x5b,//2       序号2
0x4f,//3       序号3
0x66,//4       序号4
0x6d,//5       序号5
0x7d,//6       序号6
0x07,//7       序号7
0x7f,//8       序号8
0x6f,//9       序号9
0x00,//无      序号10
0x40,//-       序号11
0x73,//P       序号12
};


unsigned intuiRcregTotal=0;//代表当前缓冲区已经接收了多少个数据
unsigned intuiRcregTotalTemp=0;//代表当前缓冲区已经接收了多少个数据的中间变量
unsigned char ucRcregBuf; //接收串口中断数据的缓冲区数组
unsigned char ucReceiveFlag=0; //接收成功标志


unsigned char ucDigShow8;//第8位数码管要显示的内容
unsigned char ucDigShow7;//第7位数码管要显示的内容
unsigned char ucDigShow6;//第6位数码管要显示的内容
unsigned char ucDigShow5;//第5位数码管要显示的内容
unsigned char ucDigShow4;//第4位数码管要显示的内容
unsigned char ucDigShow3;//第3位数码管要显示的内容
unsigned char ucDigShow2;//第2位数码管要显示的内容
unsigned char ucDigShow1;//第1位数码管要显示的内容

unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量

unsigned char ucWd1Part1Update=1; //8位数码管更新显示标志

unsigned long ulWeightCurrent=12345; //显示当前实际的重量

void main()
{
   initial_myself();
   delay_long(100);   
   initial_peripheral();
   while(1)
   {
      usart_service();//串口接收服务程序
      display_service(); //显示的窗口菜单服务程序
   }
}

/* 注释一:
* 本节内容处理串口数据是根据数据尾是否有0x0d 0x0a来判断数据串是否有效的,一旦发现有此关键字,
* 再判断总的数据长度是否等于或者大于一串数据的固定长度,如果满足,则把相关标志位置位,通知主函数中
* 的串口服务程序进行处理。同时也及时关闭串口中断,避免在处理串口数据期间受到串口数据的中断干扰,
* 等串口服务程序处理完毕再打开。
*/
void usart_receive(void) interrupt 4   //串口接收数据中断函数      
{      

   if(RI==1)
   {
      RI = 0;

      ++uiRcregTotal;
      ucRcregBuf=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
      if(uiRcregTotal>=2&&ucRcregBuf==0x0d&&ucRcregBuf==0x0a)//一旦发现后缀是0x0d 0x0a关键字的就进去处理判断
      {
         if(uiRcregTotal<const_least_size)//如果发现总的接收数据小于一串数据的固定长度。则无效,清零重新开始。
         {
               uiRcregTotal=0;
         }
         else
         {
               uiRcregTotalTemp=uiRcregTotal; //把接收到的总数据传递给一个中间变量,在主函数那边处理这个中间变量
               ucReceiveFlag=1;            //通知主程序接收成功
                           ES=0;      // 禁止接收中断,等主函数处理完接收的数据后再打开串口中断,避免在处理串口数据期间受到串口数据的中断干扰。
         }
      }
      else if(uiRcregTotal>=const_rc_size)//超过缓冲区
      {
         uiRcregTotal=0;   
      }


   
   
   }
   else    //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
   {
      TI = 0;
   }
                                                         
}

void usart_service(void)//串口接收服务程序,在main函数里
{
//加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样有可能减少了一点指令消耗的时间。
    static unsigned long ulReceiveData10000; //定义成long类型,是为了方便后面换算的乘法运算,让它不会溢出而出错。
    static unsigned long ulReceiveData1000;
    static unsigned long ulReceiveData100;
    static unsigned long ulReceiveData10;
    static unsigned long ulReceiveData1;


    if(ucReceiveFlag==1)//说明有数据接收成功,进入数据处理分析
    {
       ulReceiveData10000=0;
       ulReceiveData1000=0;
       ulReceiveData100=0;
       ulReceiveData10=0;
       ulReceiveData1=0;

/* 注释二:
* 根据协议,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,因此省略不写。
*/

       if(ucRcregBuf>=0x30)
       {
          ulReceiveData1=ucRcregBuf-0x30; //接收到的ASCII码数字减去0x30变成实际数值.
       }

       if(ucRcregBuf>=0x30)
       {
          ulReceiveData10=ucRcregBuf-0x30;
          ulReceiveData10=ulReceiveData10*10;
       }

       if(ucRcregBuf>=0x30)
       {
          ulReceiveData100=ucRcregBuf-0x30;
          ulReceiveData100=ulReceiveData100*100;
       }

       if(ucRcregBuf>=0x30)
       {
          ulReceiveData1000=ucRcregBuf-0x30;
          ulReceiveData1000=ulReceiveData1000*1000;
       }

       if(ucRcregBuf>=0x30)
       {
          ulReceiveData10000=ucRcregBuf-0x30;
          ulReceiveData10000=ulReceiveData10000*10000;
       }


       ulWeightCurrent=ulReceiveData10000+ulReceiveData1000+ulReceiveData100+ulReceiveData10+ulReceiveData1;
       ucWd1Part1Update=1; //更新显示

       uiRcregTotalTemp=0;//清零实际接收到的字节数的中间变量
       uiRcregTotal=0;//清零实际接收到的字节数
       ucReceiveFlag=0;//清零完成标志

           ES = 1;            // 允许接收中断
    }            
}



void display_service() //显示的窗口菜单服务程序
{
//加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样有可能减少了一点指令消耗的时间。
            static unsigned char ucTemp5;   //中间过渡变量
            static unsigned char ucTemp4;   //中间过渡变量
            static unsigned char ucTemp3;   //中间过渡变量
            static unsigned char ucTemp2;   //中间过渡变量
            static unsigned char ucTemp1;   //中间过渡变量


            if(ucWd1Part1Update==1)//更新显示
            {
               ucWd1Part1Update=0;//及时清零标志,避免一直进来扫描

            //先分解数据用来显示每一位
               ucTemp5=ulWeightCurrent%100000/10000;
               ucTemp4=ulWeightCurrent%10000/1000;   
               ucTemp3=ulWeightCurrent%1000/100;
               ucTemp2=ulWeightCurrent%100/10;
               ucTemp1=ulWeightCurrent%10;

               ucDigDot3=1;//显示第3位数码管的小数点,实际数据带2位小数点。

               ucDigShow8=10;//没有用到第8位数码管,因此显示无。10代表显示空。
               ucDigShow7=10;//没有用到第7位数码管,因此显示无。10代表显示空。
               ucDigShow6=10;//没有用到第6位数码管,因此显示无。10代表显示空。

               if(ulWeightCurrent<10000)   
               {
                  ucDigShow5=10;//如果小于1000,千位显示无
               }
               else
               {
                  ucDigShow5=ucTemp5;//第5位数码管要显示的内容
               }


               if(ulWeightCurrent<1000)   
               {
                  ucDigShow4=10;//如果小于1000,千位显示无
               }
               else
               {
                  ucDigShow4=ucTemp4;//第4位数码管要显示的内容
               }

                //因为带2位小数点,因此最前面3位数据都是有效数,必然要显示,不要判断去0的空显示处理。
                ucDigShow3=ucTemp3;//第3位数码管要显示的内容
                ucDigShow2=ucTemp2;//第2位数码管要显示的内容
                ucDigShow1=ucTemp1;//第1位数码管要显示的内容
            }

   
}




void display_drive()
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1://显示第1位
         ucDigShowTemp=dig_table;
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2://显示第2位
         ucDigShowTemp=dig_table;
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3://显示第3位
         ucDigShowTemp=dig_table;
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4://显示第4位
         ucDigShowTemp=dig_table;
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5://显示第5位
         ucDigShowTemp=dig_table;
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6://显示第6位
         ucDigShowTemp=dig_table;
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7://显示第7位
         ucDigShowTemp=dig_table;
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
         }
         dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8://显示第8位
         ucDigShowTemp=dig_table;
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }
   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
   {
   ucDisplayDriveStep=1;
   }

}

//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;
   ucTempData=ucDigStatusTemp16_09;//先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucDigStatusTemp08_01;//再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);
   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;
}

//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;
   ucTempData=ucLedStatusTemp16_09;//先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucLedStatusTemp08_01;//再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);
   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;
}



void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断


display_drive();//数码管字模的驱动函数

TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}


void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)//内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}

void initial_myself()//第一区 初始化单片机
{

beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
led_dr=0;//关闭独立LED灯
hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯

TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;

//配置串口
SCON=0x50;
TMOD=0X21;

/* 注释三:
* 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
* 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
*/
IP =0x10;//把串口中断设置为最高优先级,必须的。

TH1=TL1=-(11059200L/12/32/9600);//串口波特率为9600。
TR1=1;
}

void initial_peripheral() //第二区 初始化外围
{

   ucDigDot8=0;   //初始化小数点全部不显示
   ucDigDot7=0;
   ucDigDot6=0;
   ucDigDot5=0;
   ucDigDot4=0;
   ucDigDot3=0;
   ucDigDot2=0;
   ucDigDot1=0;

   EA=1;   //开总中断
   ES=1;   //允许串口中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断
}


总结陈词:
    前面我在第48节里讲过用ds1302做的时钟程序,但是后来很多网友建议,为了方便初学者学习编程思路,我应该用单片机定时器做一个时钟程序。因此,我决定下一节讲这方面的内容。欲知详情,请听下回分解----用单片机内部定时器做一个时钟。

(未完待续,下节更精彩,不要走开哦)

tt98 发表于 2015-1-3 19:20:50

继续听讲,再顶鸿哥!{:titter:}

xxzzhy 发表于 2015-1-3 23:13:46

楼主会跟帖搅伴器方面的知识吗?

吴坚鸿 发表于 2015-1-4 11:27:30

xxzzhy 发表于 2015-1-3 23:13
楼主会跟帖搅伴器方面的知识吗?

我没做过搅伴器的项目。

吴坚鸿 发表于 2015-1-4 11:28:29

第八十九节:用单片机内部定时器做一个时钟。

开场白:
很多网友建议,为了方便初学者学习编程思路,我应该用单片机定时器做一个时钟程序供大家参考学习。其实我前面第48节就已经用ds1302做了一个可以显示和更高时间的时钟,这一节只要在第48节的源代码基础上,大的框架不用动,只需要把ds1302产生的时间改成用定时中断产生的时间就可以了,改动的地方非常小。但是为了让时间的精度更高,最后必须跟标准时间进行校验,来修正系统中一秒钟需要多个定时中断的误差,这个误差决定了系统的时间精度,其实这个校验方法我在前面很多章节上跟大家介绍过了:
    第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
    第二步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表,当手机的标准时间跑了780秒(这个标准时间跑得越长校验精度越高),而此时单片机仅仅跑了1632秒。那么最终得出1秒钟需要的定时中断次数是:const_time_1s=(200*1632)/780=418。
第三步:如果发现时钟还是不太准,可以继续返回第一步根据最新1秒钟的时间是418次,多校验几次,来不断调整const_time_1s的数值,直到找到相对精度的时间为止。
本系统仅供学习,精度不可能做得很好,因为影响时间精度的因素还有定时中断的重装值,定时中断里面的代码尽量少,以及晶振等不好控制的因素。所以鸿哥一直不推荐在实际项目中用单片机的内部定时器做实时时钟,因为精度有限。真正想要准确的时钟时间,还是强烈建议大家用外部专用的时钟芯片或者用CPLD/FPGA来做。

    具体内容,请看源代码讲解。

(1 硬件平台.
    基于朱兆祺51单片机学习板。

(2)实现功能:
     本程序有2两个窗口。
     第1个窗口显示日期。显示格式“年-月-日”。注意中间有“-”分开。
     第2个窗口显示时间。显示格式“时 分 秒”。注意中间没“-”,只有空格分开。
     系统上电后,默认显示第2个窗口,实时显示动态的“时 分 秒”时间。此时按下S13按键不松手就会切换到显示日期的第1个窗口。松手后自动切换回第2个显示动态时间的窗口。
     需要更改时间的时候,长按S9按键不松手超过3秒后,系统将进入修改时间的状态,切换到第1个日期窗口,并且显示“年”的两位数码管会闪烁,此时可以按S1或者S5加减按键修改年的参数,修改完年后,继续短按S9按键,会切换到“月”的参数闪烁状态,只要依次不断按下S9按键,就会依次切换年,月,日,时,分,秒的参数闪烁状态,最后修改完秒的参数后,系统会自动把我们修改设置的日期时间一次性更改到定时中断函数内部的时间变量,达到修改日期时间的目的。
S13是电平变化按键,用来切换窗口的,专门用来查看当前日期。按下S13按键时显示日期窗口,松手后返回到显示实时时间的窗口。

(3)源代码讲解如下:
#include "REG52.H"

#define const_dpy_time_half200//数码管闪烁时间的半值
#define const_dpy_time_all   400//数码管闪烁时间的全值 一定要比const_dpy_time_half 大

#define const_voice_short40   //蜂鸣器短叫的持续时间
#define const_key_time120    //按键去抖动延时的时间
#define const_key_time220    //按键去抖动延时的时间
#define const_key_time320    //按键去抖动延时的时间
#define const_key_time420    //按键去抖动延时的时间

#define const_key_time171200//长按超过3秒的时间


/* 注释一:
* const_timer_1s这个是产生多少次定时中断才算1秒钟的标准。这个标准决定了时钟的精度。这个标准最后是需要校验的。
* 那么是如何检验的呢?根据我们前面介绍的校验时间方法:
* 步骤:
* 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
* 第二步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表,当手机的标准时间跑了780秒(这个标准时间跑得越长校验精度越高),
*         而此时单片机仅仅跑了1632秒。那么最终得出1秒钟需要的定时中断次数是:const_time_1s=(200*1632)/780=418。
* 第三步:如果发现时钟还是不太准,可以继续返回第一步根据最新1秒钟的时间是418次,多校验几次。本系统仅供学习,精度不可能做得很好,因为
*         影响时间精度的因素还有定时中断的重装值,定时中断里面的代码尽量少,以及晶振等不好控制的因素。所以鸿哥一直不推荐在实际项目中
*         用单片机的内部定时器做实时时钟,因为精度有限。真正想要准确的时钟时间,还是强烈建议大家用外部专用的时钟芯片或者用CPLD/FPGA来做。
*/


//#define const_timer_1s200   //第一次假设大概1秒的时间需要200个定时中断

#define const_timer_1s418//第二次校验后,最终选定大概1秒的时间需要418个定时中断。如果发现时间还是不准,可以在此基础上继续校验来调整此数据。

void initial_myself(void);   
void initial_peripheral(void);
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);


//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(void); //显示数码管字模的驱动函数
void display_service(void); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

void T0_time(void);//定时中断函数

void key_service(void); //按键服务的应用程序
void key_scan(void);//按键扫描函数 放在定时中断里

void timer_sampling(void); //定时器采样程序,内部每秒钟采集更新一次


unsigned char get_date(unsigned char ucYearTemp,unsigned char ucMonthTemp);//获取当前月份的最大天数

//日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp); //日调整


sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

sbit dig_hc595_sh_dr=P2^0;   //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

unsigned int uiKey4Cnt1=0;//在软件滤波中,用到的变量
unsigned int uiKey4Cnt2=0;
unsigned char ucKey4Sr=1;//实时反映按键的电平状态
unsigned char ucKey4SrRecord=0; //记录上一次按键的电平状态

unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
unsigned charucVoiceLock=0;//蜂鸣器鸣叫的原子锁

unsigned char ucDigShow8;//第8位数码管要显示的内容
unsigned char ucDigShow7;//第7位数码管要显示的内容
unsigned char ucDigShow6;//第6位数码管要显示的内容
unsigned char ucDigShow5;//第5位数码管要显示的内容
unsigned char ucDigShow4;//第4位数码管要显示的内容
unsigned char ucDigShow3;//第3位数码管要显示的内容
unsigned char ucDigShow2;//第2位数码管要显示的内容
unsigned char ucDigShow1;//第1位数码管要显示的内容

unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量


unsigned char ucWd=2;//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

unsigned char ucWd1Update=0; //窗口1更新显示标志
unsigned char ucWd2Update=1; //窗口2更新显示标志

unsigned char ucWd1Part1Update=0;//在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志

unsigned char ucWd2Part1Update=0;//在窗口2中,局部1的更新显示标志
unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志
unsigned char ucWd2Part3Update=0; //在窗口2中,局部3的更新显示标志

unsigned charucYear=15;    //用来显示和设置的时间变量
unsigned charucMonth=1;
unsigned charucDate=1;
unsigned charucHour=12;
unsigned charucMinute=0;
unsigned charucSecond=0;


unsigned int uiTimerCnt=0;   //计时器的时基

unsigned charucTimerYear=15;   //在定时器内部时基产生的时间变量
unsigned charucTimerMonth=1;
unsigned charucTimerDate=1;
unsigned charucTimerHour=12;
unsigned charucTimerMinute=0;
unsigned charucTimerSecond=0;

unsigned charucTimerDateMax=31; //当前月份的最大天数

unsigned charucTimerUpdate=0; //定时器每1秒钟所产生的标志
unsigned charucTimerStart=1;//是否打开定时器内部时间的标志,在本程序相当于原子锁的作用。

unsigned char ucTemp1=0;//中间过渡变量
unsigned char ucTemp2=0;//中间过渡变量

unsigned char ucTemp4=0;//中间过渡变量
unsigned char ucTemp5=0;//中间过渡变量

unsigned char ucTemp7=0;//中间过渡变量
unsigned char ucTemp8=0;//中间过渡变量

unsigned char ucDelayTimerLock=0; //原子锁
unsigned intuiDelayTimer=0;


unsigned char ucDpyTimeLock=0; //原子锁
unsigned intuiDpyTimeCnt=0;//数码管的闪烁计时器,放在定时中断里不断累加

//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f,//0       序号0
0x06,//1       序号1
0x5b,//2       序号2
0x4f,//3       序号3
0x66,//4       序号4
0x6d,//5       序号5
0x7d,//6       序号6
0x07,//7       序号7
0x7f,//8       序号8
0x6f,//9       序号9
0x00,//无      序号10
0x40,//-       序号11
0x73,//P       序号12
};
void main()
{
   initial_myself();
   delay_long(100);   
   initial_peripheral();
   while(1)
   {
      key_service(); //按键服务的应用程序
      timer_sampling(); //定时器采样程序,内部每秒钟采集更新一次
      display_service(); //显示的窗口菜单服务程序
   }
}


/* 注释二:
* 系统不用时时刻刻采集定时器的内部数据,每隔1秒钟的时间更新采集一次就可以了。
* 这个1秒钟的时间是根据定时器内部ucTimerUpdate变量来判断。
*/
void timer_sampling(void) //采样定时器的程序,内部每秒钟采集更新一次
{
   if(ucPart==0)//当系统不是处于设置日期和时间的情况下
   {
      if(ucTimerUpdate==1)//每隔1秒钟时间就更新采集一次定时器的时间数据
      {
          ucTimerUpdate=0;//及时清零,避免一直更新。

          ucYear=ucTimerYear; //读取定时器内部的年
          ucMonth=ucTimerMonth; //读取定时器内部的月
          ucDate=ucTimerDate;//读取定时器内部的日
          ucHour=ucTimerHour;   //读取定时器内部的时
          ucMinute=ucTimerMinute;//读取定时器内部的分
          ucSecond=ucTimerSecond;//读取定时器内部的秒

          ucWd2Update=1; //窗口2更新显示时间
      }

   }
}

/* 注释三:
* 根据年份和月份来获取当前这个月的最大天数。每个月份的天数最大取值不同,有的最大28日,
* 有的最大29日,有的最大30,有的最大31。
*/
unsigned char get_date(unsigned char ucYearTemp,unsigned char ucMonthTemp)
{


   unsigned char ucDayResult;
   unsigned int uiYearTemp;
   unsigned int uiYearYu;

   ucDayResult=31; //默认最大是31天,以下根据不同的年份和月份来决定是否需要修正这个值

   switch(ucMonthTemp)//根据不同的月份来获取当前月份天数的最大值
   {
      case 2://二月份要计算是否是闰年
         uiYearTemp=2000+ucYearTemp;
         uiYearYu=uiYearTemp%4;
         if(uiYearYu==0) //闰年
         {
            ucDayResult=29;
         }
         else
         {
            ucDayResult=28;
         }
         break;
      case 4:
      case 6:
      case 9:
      case 11:
            ucDayResult=30;
         break;

   }

   return ucDayResult;

}

//日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp) //日调整
{


   unsigned char ucDayResult;
   unsigned int uiYearTemp;
   unsigned int uiYearYu;
   

   ucDayResult=ucDateTemp;

   switch(ucMonthTemp)//根据不同的月份来修正不同的日最大值
   {
      case 2://二月份要计算是否是闰年
         uiYearTemp=2000+ucYearTemp;
         uiYearYu=uiYearTemp%4;
         if(uiYearYu==0) //闰年
         {
               if(ucDayResult>29)
               {
                  ucDayResult=29;
               }
         }
         else
         {
               if(ucDayResult>28)
               {
                  ucDayResult=28;
               }
         }
         break;
      case 4:
      case 6:
      case 9:
      case 11:
         if(ucDayResult>30)
         {
            ucDayResult=30;
         }
         break;

   }

   return ucDayResult;

}


void display_service(void) //显示的窗口菜单服务程序
{

   switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示日期窗口的数据数据格式 NN-YY-RR 年-月-日
            if(ucWd1Update==1)//窗口1要全部更新显示
            {
               ucWd1Update=0;//及时清零标志,避免一直进来扫描

               ucDigShow6=11;//显示一杠"-"
               ucDigShow3=11;//显示一杠"-"

               ucWd1Part1Update=1;//局部年更新显示
               ucWd1Part2Update=1;//局部月更新显示
               ucWd1Part3Update=1;//局部日更新显示
            }

            if(ucWd1Part1Update==1)//局部年更新显示
            {
               ucWd1Part1Update=0;
               ucTemp8=ucYear/10;//年
               ucTemp7=ucYear%10;

               ucDigShow8=ucTemp8; //数码管显示实际内容
               ucDigShow7=ucTemp7;
            }


            if(ucWd1Part2Update==1)//局部月更新显示
            {
               ucWd1Part2Update=0;
               ucTemp5=ucMonth/10;//月
               ucTemp4=ucMonth%10;

               ucDigShow5=ucTemp5; //数码管显示实际内容
               ucDigShow4=ucTemp4;
            }


            if(ucWd1Part3Update==1) //局部日更新显示
            {
               ucWd1Part3Update=0;
               ucTemp2=ucDate/10;//日
               ucTemp1=ucDate%10;
                        
               ucDigShow2=ucTemp2; //数码管显示实际内容
               ucDigShow1=ucTemp1;
            }
            //数码管闪烁
            switch(ucPart)//相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
            {
                case 0://都不闪烁
                     break;
                case 1://年参数闪烁
                     if(uiDpyTimeCnt==const_dpy_time_half)
                     {
                           ucDigShow8=ucTemp8; //数码管显示实际内容
                           ucDigShow7=ucTemp7;
                      }
                     else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                     {
                           ucDpyTimeLock=1; //原子锁加锁
                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零
                           ucDpyTimeLock=0;//原子锁解锁

                           ucDigShow8=10;   //数码管显示空,什么都不显示
                           ucDigShow7=10;

                     }
                     break;
                case 2:   //月参数闪烁
                     if(uiDpyTimeCnt==const_dpy_time_half)
                     {
                           ucDigShow5=ucTemp5; //数码管显示实际内容
                           ucDigShow4=ucTemp4;
                      }
                     else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                     {
                           ucDpyTimeLock=1; //原子锁加锁
                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零
                           ucDpyTimeLock=0;//原子锁解锁

                           ucDigShow5=10;   //数码管显示空,什么都不显示
                           ucDigShow4=10;

                     }
                  break;
                case 3:   //日参数闪烁
                     if(uiDpyTimeCnt==const_dpy_time_half)
                     {
                           ucDigShow2=ucTemp2; //数码管显示实际内容
                           ucDigShow1=ucTemp1;
                      }
                     else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                     {
                           ucDpyTimeLock=1; //原子锁加锁
                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零
                           ucDpyTimeLock=0;//原子锁解锁

                           ucDigShow2=10;   //数码管显示空,什么都不显示
                           ucDigShow1=10;

                     }
                  break;      
            }

            break;
       case 2:   //显示时间窗口的数据数据格式 SS FF MM 时 分 秒
            if(ucWd2Update==1)//窗口2要全部更新显示
            {
               ucWd2Update=0;//及时清零标志,避免一直进来扫描

               ucDigShow6=10;//显示空
               ucDigShow3=10;//显示空

               ucWd2Part3Update=1;//局部时更新显示
               ucWd2Part2Update=1;//局部分更新显示
               ucWd2Part1Update=1;//局部秒更新显示
            }

            if(ucWd2Part1Update==1)//局部时更新显示
            {
               ucWd2Part1Update=0;
               ucTemp8=ucHour/10;//时
               ucTemp7=ucHour%10;

               ucDigShow8=ucTemp8; //数码管显示实际内容
               ucDigShow7=ucTemp7;
            }


            if(ucWd2Part2Update==1)//局部分更新显示
            {
               ucWd2Part2Update=0;
               ucTemp5=ucMinute/10;//分
               ucTemp4=ucMinute%10;

               ucDigShow5=ucTemp5; //数码管显示实际内容
               ucDigShow4=ucTemp4;
            }


            if(ucWd2Part3Update==1) //局部秒更新显示
            {
               ucWd2Part3Update=0;
               ucTemp2=ucSecond/10;//秒
               ucTemp1=ucSecond%10;               
      
               ucDigShow2=ucTemp2; //数码管显示实际内容
               ucDigShow1=ucTemp1;
            }
            //数码管闪烁
            switch(ucPart)//相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
            {
                case 0://都不闪烁
                     break;
                case 1://时参数闪烁
                     if(uiDpyTimeCnt==const_dpy_time_half)
                     {
                           ucDigShow8=ucTemp8; //数码管显示实际内容
                           ucDigShow7=ucTemp7;
                      }
                     else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                     {
                           ucDpyTimeLock=1; //原子锁加锁
                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零
                           ucDpyTimeLock=0;//原子锁解锁

                           ucDigShow8=10;   //数码管显示空,什么都不显示
                           ucDigShow7=10;

                     }
                     break;
                case 2:   //分参数闪烁
                     if(uiDpyTimeCnt==const_dpy_time_half)
                     {
                           ucDigShow5=ucTemp5; //数码管显示实际内容
                           ucDigShow4=ucTemp4;
                     }
                     else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                     {
                           ucDpyTimeLock=1; //原子锁加锁
                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零
                           ucDpyTimeLock=0;//原子锁解锁

                           ucDigShow5=10;   //数码管显示空,什么都不显示
                           ucDigShow4=10;

                     }
                  break;
                case 3:   //秒参数闪烁
                     if(uiDpyTimeCnt==const_dpy_time_half)
                     {
                           ucDigShow2=ucTemp2; //数码管显示实际内容
                           ucDigShow1=ucTemp1;
                     }
                     else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                     {
                           ucDpyTimeLock=1; //原子锁加锁
                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零
                           ucDpyTimeLock=0;//原子锁解锁

                           ucDigShow2=10;   //数码管显示空,什么都不显示
                           ucDigShow1=10;

                     }
                  break;      
            }


            break;
      }
   

}

void key_scan(void)//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
   ucKeyLock1=0; //按键自锁标志清零
   uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
   uiKeyTimeCnt1++; //累加定时中断次数
   if(uiKeyTimeCnt1>const_key_time1)
   {
      uiKeyTimeCnt1=0;
      ucKeyLock1=1;//自锁按键置位,避免一直触发
      ucKeySec=1;    //触发1号键
   }
}

if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
   ucKeyLock2=0; //按键自锁标志清零
   uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
   uiKeyTimeCnt2++; //累加定时中断次数
   if(uiKeyTimeCnt2>const_key_time2)
   {
      uiKeyTimeCnt2=0;
      ucKeyLock2=1;//自锁按键置位,避免一直触发
      ucKeySec=2;    //触发2号键
   }
}



/* 注释四:
* 注意,此处把一个按键的短按和长按的功能都实现了。
*/

if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
   ucKeyLock3=0; //按键自锁标志清零
   uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
   uiKeyTimeCnt3++; //累加定时中断次数
   if(uiKeyTimeCnt3>const_key_time3)
   {
      uiKeyTimeCnt3=0;
      ucKeyLock3=1;//自锁按键置位,避免一直触发
      ucKeySec=3;    //短按触发3号键
   }
}
else if(uiKeyTimeCnt3<const_key_time17)   //长按3秒
{
   uiKeyTimeCnt3++; //累加定时中断次数
         if(uiKeyTimeCnt3==const_key_time17)//等于3秒钟,触发17号长按按键
         {
            ucKeySec=17;    //长按3秒触发17号键
         }
}


/* 注释五:
* 注意,此处是电平按键的滤波抗干扰处理
*/
   if(key_sr4==1)//对应朱兆祺学习板的S13键
   {
       uiKey4Cnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiKey4Cnt2++; //类似独立按键去抖动的软件抗干扰处理
       if(uiKey4Cnt2>const_key_time4)
       {
         uiKey4Cnt2=0;
         ucKey4Sr=1;//实时反映按键松手时的电平状态
       }
   }
   else   
   {
       uiKey4Cnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiKey4Cnt1++;
       if(uiKey4Cnt1>const_key_time4)
       {
          uiKey4Cnt1=0;
          ucKey4Sr=0;//实时反映按键按下时的电平状态
       }
   }


}

void key_service(void) //按键服务的应用程序
{

switch(ucKeySec) //按键服务状态切换
{
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)//在不同的窗口下,设置不同的参数
          {
               case 1:
                  switch(ucPart) //在不同的局部变量下,相当于二级菜单
                  {
                        case 1://年
                           ucYear++;
                           if(ucYear>99)
                           {
                              ucYear=99;
                           }
                           ucWd1Part1Update=1;//更新显示
                           break;
                         case 2: //月
                           ucMonth++;
                           if(ucMonth>12)
                           {
                                 ucMonth=12;
                           }
                           ucWd1Part2Update=1;//更新显示                                             
                           break;
                         case 3: //日
                           ucDate++;
                           if(ucDate>31)
                           {
                              ucDate=31;
                           }
                           ucWd1Part3Update=1;//更新显示               
                           break;                                       

                  }


                  break;
               case 2:
                  switch(ucPart) //在不同的局部变量下,相当于二级菜单
                  {
                         case 1://时
                           ucHour++;
                           if(ucHour>23)
                           {
                              ucHour=23;
                           }
                           ucWd2Part1Update=1;//更新显示                                             
                           break;
                         case 2: //分
                           ucMinute++;
                           if(ucMinute>59)
                           {
                              ucMinute=59;
                           }
                           ucWd2Part2Update=1;//更新显示                                                      
                           break;
                         case 3: //秒
                           ucSecond++;
                           if(ucSecond>59)
                           {
                                 ucSecond=59;
                           }
                           ucWd2Part3Update=1;//更新显示      
                           break;                                       

                  }
                  break;
         
          }

          ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)//在不同的窗口下,设置不同的参数
          {
               case 1:
                  switch(ucPart) //在不同的局部变量下,相当于二级菜单
                  {
                        case 1://年
                           ucYear--;
                           if(ucYear>99)
                           {
                              ucYear=0;
                           }
                           ucWd1Part1Update=1;//更新显示
                           break;
                        case 2: //月
                           ucMonth--;
                           if(ucMonth<1)
                           {
                              ucMonth=1;
                           }
                           ucWd1Part2Update=1;//更新显示                                             
                           break;
                        case 3: //日
                           ucDate--;
                           if(ucDate<1)
                           {
                              ucDate=1;
                           }
                           ucWd1Part3Update=1;//更新显示               
                           break;                                       

                  }


                  break;
               case 2:
                  switch(ucPart) //在不同的局部变量下,相当于二级菜单
                  {
                         case 1://时
                           ucHour--;
                           if(ucHour>23)
                           {
                              ucHour=0;
                           }
                           ucWd2Part1Update=1;//更新显示                                             
                           break;
                         case 2: //分
                           ucMinute--;
                           if(ucMinute>59)
                           {
                              ucMinute=0;
                           }
                           ucWd2Part2Update=1;//更新显示                                                      
                           break;
                         case 3: //秒
                           ucSecond--;
                           if(ucSecond>59)
                           {
                              ucSecond=0;
                           }
                           ucWd2Part3Update=1;//更新显示      
                           break;                                       

                  }
                  break;
         
          }

          ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;

    case 3://短按设置按键 对应朱兆祺学习板的S9键
          switch(ucWd)//在不同的窗口下,设置不同的参数
          {
               case 1:
                  ucPart++;
                  if(ucPart>3)
                  {
                         ucPart=1;
                         ucWd=2; //切换到第二个窗口,设置时分秒
                         ucWd2Update=1;//窗口2更新显示
                  }
                  ucWd1Update=1;//窗口1更新显示
                  break;
               case 2:
                  if(ucPart>0) //在窗口2的时候,要第一次激活设置时间,必须是长按3秒才可以,这里短按激活不了第一次
                  {
                     ucPart++;
                     if(ucPart>3)//设置时间结束
                     {
                           ucPart=0;



/* 注释六:
* 每个月份的天数最大值是不一样的,在写入ds1302时钟芯片内部数据前,应该做一次调整。
* 有的月份最大28天,有的月份最大29天,有的月份最大30天,有的月份最大31天,
*/                                                   
                           ucDate=date_adjust(ucYear,ucMonth,ucDate); //日调整 避免日的数值在某个月份超范围

                           ucTimerStart=0;//关闭定时器的时间。在更改定时器内部时间数据时,先关闭它,相当于原子锁的加锁作用。

                           ucTimerYear=ucYear;//把设置和显示的数据更改到定时器内部的时间变量
                           ucTimerMonth=ucMonth;
                           ucTimerDate=ucDate;
                           ucTimerHour=ucHour;
                           ucTimerMinute=ucMinute;
                           ucTimerSecond=ucSecond;

                           ucTimerStart=1;//打开定时器的时间。在更改定时器内部时间数据后,再打开它,相当于原子锁的解锁作用。

                     }
                     ucWd2Update=1;//窗口2更新显示
                  }

                  break;
         
          }

         
          ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 17://长按3秒设置按键 对应朱兆祺学习板的S9键
          switch(ucWd)//在不同的窗口下,设置不同的参数
          {
               case 2:
                  if(ucPart==0) //处于非设置时间的状态下,要第一次激活设置时间,必须是长按3秒才可以
                  {
                     ucWd=1;
                     ucPart=1;//进入到设置日期的状态下
                     ucWd1Update=1;//窗口1更新显示
                  }
                  break;
         
          }
          ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
         
}         


/* 注释七:
* 注意,此处就是第一次出现的电平按键程序,跟以往的下降沿按键不一样。
* ucKey4Sr是经过软件滤波处理后,直接反应IO口电平状态的变量.当电平发生
* 变化时,就会切换到不同的显示界面,这里多用了一个ucKey4SrRecord变量
* 记录上一次的电平状态,是为了避免一直刷新显示。
*/
if(ucKey4Sr!=ucKey4SrRecord)//说明S13的切换按键电平状态发生变化
{
   ucKey4SrRecord=ucKey4Sr;//及时记录当前最新的按键电平状态避免一直进来触发

         if(ucKey4Sr==1) //松手后切换到显示时间的窗口
         {
            ucWd=2;    //显示时分秒的窗口
            ucPart=0;//进入到非设置时间的状态下
            ucWd2Update=1;//窗口2更新显示
         }
         else//按下去切换到显示日期的窗口
         {
            ucWd=1;   //显示年月日的窗口
            ucPart=0;//进入到非设置时间的状态下
            ucWd1Update=1;//窗口1更新显示
         }

}
}

void display_drive(void)
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1://显示第1位
         ucDigShowTemp=dig_table;
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2://显示第2位
         ucDigShowTemp=dig_table;
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3://显示第3位
         ucDigShowTemp=dig_table;
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4://显示第4位
         ucDigShowTemp=dig_table;
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5://显示第5位
         ucDigShowTemp=dig_table;
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6://显示第6位
         ucDigShowTemp=dig_table;
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7://显示第7位
         ucDigShowTemp=dig_table;
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
         }
         dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8://显示第8位
         ucDigShowTemp=dig_table;
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }
   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
   {
   ucDisplayDriveStep=1;
   }

}

//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;
   ucTempData=ucDigStatusTemp16_09;//先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucDigStatusTemp08_01;//再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);
   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;
}

//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;
   ucTempData=ucLedStatusTemp16_09;//先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucLedStatusTemp08_01;//再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;   //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);
   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;
}



void T0_time(void) interrupt 1   //定时中断
{
TF0=0;//清除中断标志
TR0=0; //关中断


/* 注释八:
* 以下是本节内容的核心程序,是定时器内部产生的时间。const_timer_1s这个是产生多少次定时中断才
* 算1秒钟的标准。这个标准决定了时钟的精度。这个标准最后是需要校验的。
*/
if(ucTimerStart==1)//定时器的时间已经打开
{
   uiTimerCnt++;//产生1秒钟的时基
   if(uiTimerCnt>=const_timer_1s) //一秒钟的时间到。这个const_timer_1s具体数值最后需要校验得出。
   {
      uiTimerCnt=0; //清零为产生下一个1秒钟准备
      ucTimerUpdate=1; //定时器每1秒钟所产生的标志,通知主函数及时更新采集时间数据

      ucTimerSecond++; //秒时间累加1
      if(ucTimerSecond>=60)
      {
         ucTimerSecond=0;
         ucTimerMinute++; //分时间累加1
         if(ucTimerMinute>=60)
         {
               ucTimerMinute=0;
               ucTimerHour++;//小时的时间累加1,为了避免if的嵌套过多,把小时的判断放到外面两层的if来继续判断
         }
      }

      if(ucTimerHour>=24)
      {
         ucTimerHour=0;
         ucTimerDate++; //天时间累加1
         ucTimerDateMax=get_date(ucTimerYear,ucTimerMonth);//根据年和月获取当前月份的最大天数
         if(ucTimerDate>ucTimerDateMax)//
         {
               ucTimerDate=1; //每个月都是从1号开始
               ucTimerMonth++;//月时间累加1
               if(ucTimerMonth>12)
               {
                   ucTimerMonth=1; //每年从1月份开始
                   ucTimerYear++; //年时间累加1
                   if(ucTimerYear>99) //本系统的最高有效年份是2099年
                   {
                      ucTimerYear=99;
                   }
               }
         }
      }

   }
   

}

if(ucVoiceLock==0) //原子锁判断
{
   if(uiVoiceCnt!=0)
   {

      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
      beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
   
   }
   else
   {

      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
      beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
      
   }
}




if(ucDpyTimeLock==0) //原子锁判断
{
   uiDpyTimeCnt++;//数码管的闪烁计时器
}



key_scan(); //按键扫描函数
display_drive();//数码管字模的驱动函数

TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)//内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}

void initial_myself(void)//第一区 初始化单片机
{

key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;

}
void initial_peripheral(void) //第二区 初始化外围
{

   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;
   ucDigDot6=0;
   ucDigDot5=0;
   ucDigDot4=0;
   ucDigDot3=0;
   ucDigDot2=0;
   ucDigDot1=0;

   EA=1;   //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断


}


总结陈词:
任何一个电子产品在投入生产的时候都要考虑到生产的测试,朱兆祺51单片机学习板在生产加工后也一样要进行测试。那么这个测试的程序如何能够做到快速,全面,易用这三个要求呢?欲知详情,请听下回分解-----生产朱兆祺51学习板的从机自检测试程序源代码.。

(未完待续,下节更精彩,不要走开哦)

yanse1214 发表于 2015-1-4 16:27:19

看了楼主的数码管菜单程序后,有下面几个疑问:
1.在程序例子的,第三十节程序,三十一节中: 注释二说到:
/* 注释二:
* 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
* 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
*/
不理解怎么会全部更新会优先响应。

看程序中,应该是局部更新先得到相应。      

2.2.程序中,按键处理程序。是当按键在同一功能的时候,不同的窗口,不同的
局部变量进行调用。
有的项目中,要求,只用数字快捷键。功能:有时是作为切换到不同的窗口下;
有时是作为数字输入功能。 那么此时,如何处理好呢?

yanse1214 发表于 2015-1-4 16:30:30

3.比如一个窗口下,三个局部参数都是动态在变。
   那么是不是理解成,三个局部窗口都是更新的。
也可以理解成 整个窗口都是更新的对不对?
如图:

tt98 发表于 2015-1-8 20:04:56

标记下,力顶!{:handshake:}

261854681 发表于 2015-1-8 20:24:56

持续给力,持续顶

nanjing 发表于 2015-1-8 20:45:24

听课学习中。

yat 发表于 2015-1-8 21:16:50

mark初学者入门

wangkangming 发表于 2015-1-10 21:02:15

这个太给力啊。不得不顶啊!!

xxzzhy 发表于 2015-1-15 21:36:39

鸿哥,以前你说连载马达方面的,什么时候连载啊

xxzzhy 发表于 2015-1-16 22:51:16

好多基础方面的。正在结合自己的在消化!

丁满 发表于 2015-1-16 23:00:04

好详细,谢谢分享,有空要认真学习一下

Zenmvol 发表于 2015-1-21 10:24:23

好东西!!!

wgm_123 发表于 2015-1-21 15:52:09

吴坚鸿 发表于 2014-5-3 08:21
(1)我代码的最大优点就是可以复用,正因为可以复用,我才会分享给大家。我相信真正深入学习我代码的初 ...

说的对极了。在用中学习新的东西,深化旧的知识。没有实践,那么所学的再多也只是纸上谈兵。强烈支持楼主

taishandadi 发表于 2015-1-21 15:58:27

不错,一步步跟着能逐渐深入下去。
页: 2 3 4 5 6 7 8 9 10 11 [12] 13 14 15
查看完整版本: 从业将近十年!手把手教你单片机程序框架(连载)