)
14.5.4程序代码编写在实际项目开发中不仅仅希望源程序、头文件等文件结构规范、代码编写规范还希望工程文件规整规范方便维护。因此首先新建一个lesson14_1的文件夹用来存放本章的工程文件。而后新建工程保存的时候在lesson14-1文件夹内再建立一个文件夹取名为project专门用于存放工程文件的如图14-2所示。图14-2 工程文件目录然后新建文件进行保存时在lesson14_1目录下再建立一个文件夹取名为source文件夹专门用来存放源代码如图14-3所示。图14-3 源文件目录看之前的工程例子能看到工程编译后会生成很多额外的文件这些文件可以统称为编译输出文件。在lesson14_1目录下再建立一个output文件夹来存放这些文件。要改变输出文件的路径需要修改两处首先进入Options for Target选择Output选项页点击Select Folder for Objects在弹出的对话框中选择新建的output文件夹然后再进入Listing选项页点击Select Folder for Listings同样指定output文件夹。工程建立完毕文件夹也整理妥当下面就开始正式编写代码。当要进行一个实际产品或者项目开发的时候首先电路原理图是确定的所使用的单片机的引脚也是明确的还有一些比如类型说明一些特殊的全局参数及宏声明放到一个专门的头文件中在这里命名为config.h即全局的配置文件。/****************************config.h文件程序源代码*****************************/#ifndef _CONFIG_H#define _CONFIG_H/* 通用头文件 */#include reg52.h#include intrins.h/* 数据类型定义 */typedef signed char int8; // 8位有符号整型数typedef signed int int16; //16位有符号整型数typedef signed long int32; //32位有符号整型数typedef unsigned char uint8; // 8位无符号整型数typedef unsigned int uint16; //16位无符号整型数typedef unsigned long uint32; //32位无符号整型数/* 全局运行参数定义 */#define SYS_MCLK (11059200/12) //系统主时钟频率即振荡器频率÷12/* 全局数据类型定义 */enum eStaSystem { //系统运行状态枚举E_NORMAL, E_SET_ACT, E_SET_ALARM};/* IO引脚分配定义 */sbit KEY_IN_1 P2^4; //矩阵按键的扫描输入引脚1sbit KEY_IN_2 P2^5; //矩阵按键的扫描输入引脚2sbit KEY_IN_3 P2^6; //矩阵按键的扫描输入引脚3sbit KEY_IN_4 P2^7; //矩阵按键的扫描输入引脚4sbit KEY_OUT_1 P2^3; //矩阵按键的扫描输出引脚1sbit KEY_OUT_2 P2^2; //矩阵按键的扫描输出引脚2sbit KEY_OUT_3 P2^1; //矩阵按键的扫描输出引脚3sbit KEY_OUT_4 P2^0; //矩阵按键的扫描输出引脚4sbit ADDR0 P1^0; //LED位选译码地址引脚0sbit ADDR1 P1^1; //LED位选译码地址引脚1sbit ADDR2 P1^2; //LED位选译码地址引脚2sbit ADDR3 P1^3; //LED位选译码地址引脚3sbit ENLED P1^4; //LED显示部件的总使能引脚sbit I2C_SCL P3^7; //I2C总线时钟引脚sbit I2C_SDA P3^6; //I2C总线数据引脚sbit BUZZ P1^6; //蜂鸣器控制引脚sbit RELAY P3^3; //继电器控制引脚sbit IO_18B20 P3^2; //DS18B20通信引脚#endif这个config.h中包含了系统所共同使用的类型声明以及宏声明方便使用。下边的编程步骤就是从main.c文件整体框架开始。作为资深的研发工程师调试这样一个程序也得几个小时的时间不可能写出来就好用所以在这里无法把整个过程给大家还原出来但是主要的编写代码的过程会尽可能的给大家介绍一下。程序的流程虽然是从main.c开始的但编写代码往往用主程序的流程框架作为主线逐一对每一个单独的功能模块进行调试验证。习惯上首先调试显示LED显示程序。因为调试好了显示程序后再调试其他任何模块都可以通过LED显示出来结果用来确认每个模块的功能是否正常。讲结构体的时候有讲到将小灯、数码管和点阵这三种器件的控制构建sLedBuff 这样一个结构体用这个结构体类型定义了一个统一的显示缓冲区ledBuff那么在动态扫描的中断函数中刷新程序语句只需要用这样一行代码P0 *((uint8 *)ledBuffi)。这行代码首先将ledBuff的地址强制类型转换uint8类型指针这个作用是确保访问的内存区域的大小和类型防止内存越界访问或者类型混淆。然后取自这个指针起的第i个字节的数据送给P0这样就完成了字节为单位的小灯、数码管和点阵这三种LED的全部动态扫描刷新。/***************************Led.h文件程序源代码********************************/#ifndef _LED_H#define _LED_H#include config.hstruct sLedBuff { //LED显示缓冲区结构uint8 array[8]; //点阵缓冲区uint8 number[6]; //数码管缓冲区uint8 single; //独立LED缓冲区};void InitLed();void LedScan();void ShowSystemSta(enum eStaSystem sta);void ShowTempValue(int16 temp);void ShowLedImage(uint8 show);#endif/***************************Led.c文件程序源代码********************************/#include Led.huint8 code LedChar[] { //数码管显示字符转换表0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E};uint8 code LedImage[] { //点阵图像(爱心)0xFF, 0x99, 0x00, 0x00, 0x00, 0x81, 0xC3, 0xE7};struct sLedBuff ledBuff; //LED显示缓冲区默认初值全0正好达到上电全亮的效果/* LED初始化函数初始化IO、配置定时器 */void InitLed(){P0 0xFF;ENLED 0;ADDR3 1;ADDR2 1;ADDR1 1;ADDR0 1;}/* 用独立LED显示系统状态 */void ShowSystemSta(enum eStaSystem sta){if (sta E_SET_ACT)ledBuff.single 0xFE; //设置继电器动作温度时点亮最低位LEDelse if (sta E_SET_ALARM)ledBuff.single 0xFD; //设置高温报警温度时点亮次低位LEDelseledBuff.single 0xFF; //其它状态时LED全熄灭}/* 数码管上显示一位数字index-数码管位索引(从右到左对应05)** num-待显示的数字point-代表是否显示此位上的小数点 */void LedNumber(uint8 index, uint8 num, uint8 point){ledBuff.number[index] LedChar[num]; //输入数字转换为数码管字符0Fif (point ! 0){ledBuff.number[index] 0x7F; //point不为0时点亮当前位的小数点}}/* 在数码管上显示温度值温度值格式为DS18B20数据格式 */void ShowTempValue(int16 temp){uint8 i;uint8 sign; //温度值的符号位int16 intT, decT; //温度值的整数和小数部分if (temp 0) //温度为正数时记录符号位直接分离出整数和小数部分{sign 0;intT temp 4;decT temp 0xF;}else //温度为负数时记录符号位温度转为正数再分离出整数和小数部分{sign 1;intT (-temp) 4;decT (-temp) 0xF;}//处理小数部分的显示decT (decT*10) / 16; //二进制的小数部分转换为1位十进制位LedNumber(0, decT, 0); //显示小数位//处理整数部分的显示LedNumber(1, intT%10, 1); //显示整数个位小数点for (i2; i6; i) //用循环检测更高位数值{intT / 10;if (intT 0) //不为0时显示该位数值LedNumber(i, intT%10, 0);else //为0时则跳出循环break;}//处理符号位的显示if (sign 1){ledBuff.number[i] 0xBF;}//更高位清空for ( ; i6; i){ledBuff.number[i] 0xFF;}}/* 显示点阵图像show-显示或清除图像 */void ShowLedImage(uint8 show){uint8 i;if (show){for (i0; i8; i)ledBuff.array[i] LedImage[i];}else{for (i0; i8; i)ledBuff.array[i] 0xFF;}}/* LED动态扫描函数在定时中断中调用 */void LedScan(){static uint8 i 0; //LED位选索引P0 0xFF; //关闭所有段选位显示消隐P1 (P1 0xF0) | i; //位选索引值赋值到P1口低4位P0 *((uint8*)ledBuffi); //缓冲区中索引位置的数据送到P0口if (i (sizeof(ledBuff)-1)) //索引递增循环遍历整个缓冲区i;elsei 0;}使用EEPROM的作用主要是记忆继电器动作的温度值和高温报警温度值关于I2C和EEPROM的程序代码和前边课程讲过的基本一致。/***************************I2C.h文件程序源代码********************************/#ifndef _I2C_H#define _I2C_H#include config.h#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}void I2CStart();void I2CStop();uint8 I2CReadNAK();uint8 I2CReadACK();bit I2CWrite(uint8 dat);#endif/***************************I2C.c文件程序源代码********************************/#include reg52.h#include intrins.h#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}sbit I2C_SCL P3^7;sbit I2C_SDA P3^6;/* 产生总线起始信号 */void I2CStart(){I2C_SDA 1; //首先确保SDA、SCL都是高电平I2C_SCL 1;I2CDelay();I2C_SDA 0; //先拉低SDAI2CDelay();I2C_SCL 0; //再拉低SCL}/* 产生总线停止信号 */void I2CStop(){I2C_SCL 0; //首先确保SDA、SCL都是低电平I2C_SDA 0;I2CDelay();I2C_SCL 1; //先拉高SCLI2CDelay();I2C_SDA 1; //再拉高SDAI2CDelay();}/* I2C总线写操作dat-待写入字节返回值-从机应答位的值 */bit I2CWrite(unsigned char dat){bit ack; //用于暂存应答位的值unsigned char mask; //用于探测字节内某一位值的掩码变量for (mask0x80; mask!0; mask1) //从高位到低位依次进行{if ((maskdat) 0) //该位的值输出到SDA上I2C_SDA 0;elseI2C_SDA 1;I2CDelay();I2C_SCL 1; //拉高SCLI2CDelay();I2C_SCL 0; //再拉低SCL完成一个位周期}I2C_SDA 1; //8位数据发送完后主机释放SDA以检测从机应答I2CDelay();I2C_SCL 1; //拉高SCLack I2C_SDA; //读取此时的SDA值即为从机的应答值I2CDelay();I2C_SCL 0; //再拉低SCL完成应答位并保持住总线return (~ack); //应答值取反以符合通常的逻辑//0不存在或忙或写入失败1存在且空闲或写入成功}/* I2C总线读操作并发送非应答信号返回值-读到的字节 */unsigned char I2CReadNAK(){unsigned char mask;unsigned char dat;I2C_SDA 1; //首先确保主机释放SDAfor (mask0x80; mask!0; mask1) //从高位到低位依次进行{I2CDelay();I2C_SCL 1; //拉高SCLif(I2C_SDA 0) //读取SDA的值dat ~mask; //为0时dat中对应位清零elsedat | mask; //为1时dat中对应位置1I2CDelay();I2C_SCL 0; //再拉低SCL以使从机发送出下一位}I2C_SDA 1; //8位数据发送完后拉高SDA发送非应答信号I2CDelay();I2C_SCL 1; //拉高SCLI2CDelay();I2C_SCL 0; //再拉低SCL完成非应答位并保持住总线return dat;}/* I2C总线读操作并发送应答信号返回值-读到的字节 */unsigned char I2CReadACK(){unsigned char mask;unsigned char dat;I2C_SDA 1; //首先确保主机释放SDAfor (mask0x80; mask!0; mask1) //从高位到低位依次进行{I2CDelay();I2C_SCL 1; //拉高SCLif(I2C_SDA 0) //读取SDA的值dat ~mask; //为0时dat中对应位清零elsedat | mask; //为1时dat中对应位置1I2CDelay();I2C_SCL 0; //再拉低SCL以使从机发送出下一位}I2C_SDA 0; //8位数据发送完后拉低SDA发送应答信号I2CDelay();I2C_SCL 1; //拉高SCLI2CDelay();I2C_SCL 0; //再拉低SCL完成应答位并保持住总线return dat;}/**************************eeprom.h文件程序源代码*******************************/#ifndef _EEPROM_H#define _EEPROM_H#include config.hvoid E2Read(uint8 *buf, uint8 addr, uint8 len);void E2Write(uint8 *buf, uint8 addr, uint8 len);#endif/**************************eeprom.c文件程序源代码*******************************/#include reg52.hextern void I2CStart();extern void I2CStop();extern unsigned char I2CReadACK();extern unsigned char I2CReadNAK();extern bit I2CWrite(unsigned char dat);/* E2读取函数buf-数据接收指针addr-E2中的起始地址len-读取长度 */void E2Read(unsigned char *buf, unsigned char addr, unsigned char len){do { //用寻址操作查询当前是否可进行读写操作I2CStart();if (I2CWrite(0x501)) //应答则跳出循环非应答则进行下一次查询{break;}I2CStop();} while(1);I2CWrite(addr); //写入起始地址I2CStart(); //发送重复启动信号I2CWrite((0x501)|0x01); //寻址器件后续为读操作while (len 1) //连续读取len-1个字节{*buf I2CReadACK(); //最后字节之前为读取操作应答len--;}*buf I2CReadNAK(); //最后一个字节为读取操作非应答I2CStop();}/* E2写入函数buf-源数据指针addr-E2中的起始地址len-写入长度 */void E2Write(unsigned char *buf, unsigned char addr, unsigned char len){while (len 0){//等待上次写入操作完成do { //用寻址操作查询当前是否可进行读写操作I2CStart();if (I2CWrite(0x501)) //应答则跳出循环非应答则进行下一次查询{break;}I2CStop();} while(1);//按页写模式连续写入字节I2CWrite(addr); //写入起始地址while (len 0){I2CWrite(*buf); //写入一个字节数据len--; //待写入长度计数递减addr; //E2地址递增if ((addr0x07) 0) //检查地址是否到达页边界24C02每页8字节{ //所以检测低3位是否为零即可break; //到达页边界时跳出循环结束本次写操作}}I2CStop();}}程序用到了矩阵按键矩阵按键的驱动程序也可以直接移植之前的程序代码。/************************keyboard.h文件程序源代码******************************/#ifndef _KEY_BOARD_H#define _KEY_BOARD_H#include config.hvoid KeyScan();void KeyDriver();#endif/************************keyboard.c文件程序源代码******************************/#include keyboard.h#include main.hconst uint8 code KeyCodeMap[4][4] { //矩阵按键到标准键码的映射表{ 1, 2, 3, 0x26 }, //数字键1、数字键2、数字键3、向上键{ 4, 5, 6, 0x25 }, //数字键4、数字键5、数字键6、向左键{ 7, 8, 9, 0x28 }, //数字键7、数字键8、数字键9、向下键{ 0, 0x1B, 0x0D, 0x27 } //数字键0、ESC键、 回车键、 向右键};uint8KeySta[4][4] { //全部矩阵按键的当前状态{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};/* 按键驱动函数检测按键动作调度相应动作函数需在主循环中调用 */void KeyDriver(){uint8 i, j;static uint8backup[4][4] { //按键值备份保存前一次的值{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};for (i0; i4; i) //循环检测4*4的矩阵按键{for (j0; j4; j){if (backup[i][j] ! KeySta[i][j]) //检测按键动作{if (backup[i][j] ! 0) //按键按下时执行动作{KeyAction(KeyCodeMap[i][j]); //调用按键动作函数}backup[i][j] KeySta[i][j]; //刷新前一次的备份值}}}}/* 按键扫描函数需在定时中断中调用推荐调用间隔1ms */void KeyScan(){uint8 i;static uint8 keyout 0; //矩阵按键扫描输出索引static uint8 keybuf[4][4] { //矩阵按键扫描缓冲区{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}};//将一行的4个按键值移入缓冲区keybuf[keyout][0] (keybuf[keyout][0] 1) | KEY_IN_1;keybuf[keyout][1] (keybuf[keyout][1] 1) | KEY_IN_2;keybuf[keyout][2] (keybuf[keyout][2] 1) | KEY_IN_3;keybuf[keyout][3] (keybuf[keyout][3] 1) | KEY_IN_4;//消抖后更新按键状态for (i0; i4; i) //每行4个按键所以循环4次{if ((keybuf[keyout][i] 0x0F) 0x00){ //连续4次扫描值为0即4*4ms内都是按下状态时可认为按键已稳定的按下KeySta[keyout][i] 0;}else if ((keybuf[keyout][i] 0x0F) 0x0F){ //连续4次扫描值为1即4*4ms内都是弹起状态时可认为按键已稳定的弹起KeySta[keyout][i] 1;}}//执行下一次的扫描输出keyout; //输出索引递增keyout 0x03; //索引值加到4即归零switch (keyout) //根据索引值释放当前输出引脚拉低下次的输出引脚{case 0: KEY_OUT_4 1; KEY_OUT_1 0; break;case 1: KEY_OUT_1 1; KEY_OUT_2 0; break;case 2: KEY_OUT_2 1; KEY_OUT_3 0; break;case 3: KEY_OUT_3 1; KEY_OUT_4 0; break;default: break;}}DS18B20温度传感器的程序代码也可以直接将之前的程序移植过来。/**************************DS18B20.h文件程序源代码*****************************/#ifndef _DS18B20_H#define _DS18B20_H#include config.hbit Start18B20();bit Get18B20Temp(int16 *temp);#endif/**************************DS18B20.c文件程序源代码*****************************/#include DS18B20.h/* 软件延时函数延时时间(t*10)us */void DelayX10us(uint8 t){do {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();} while (--t);}/* 复位总线获取存在脉冲以启动一次读写操作 */bit Get18B20Ack(){bit ack;EA 0; //禁止总中断IO_18B20 0; //产生500us复位脉冲DelayX10us(50);IO_18B20 1;DelayX10us(6); //延时60usack IO_18B20; //读取存在脉冲while(!IO_18B20); //等待存在脉冲结束EA 1; //重新使能总中断return ack;}/* 向DS18B20写入一个字节dat-待写入字节 */void Write18B20(uint8 dat){uint8 mask;EA 0; //禁止总中断for (mask0x01; mask!0; mask1) //低位在先依次移出8个bit{IO_18B20 0; //产生2us低电平脉冲_nop_();_nop_();if ((maskdat) 0) //输出该bit值IO_18B20 0;elseIO_18B20 1;DelayX10us(6); //延时60usIO_18B20 1; //拉高通信引脚}EA 1; //重新使能总中断}/* 从DS18B20读取一个字节返回值-读到的字节 */uint8 Read18B20(){uint8 dat;uint8 mask;EA 0; //禁止总中断for (mask0x01; mask!0; mask1) //低位在先依次采集8个bit{IO_18B20 0; //产生2us低电平脉冲_nop_();_nop_();IO_18B20 1; //结束低电平脉冲等待18B20输出数据_nop_(); //延时2us_nop_();if (!IO_18B20) //读取通信引脚上的值dat ~mask;elsedat | mask;DelayX10us(6); //再延时60us}EA 1; //重新使能总中断return dat;}/* 启动一次18B20温度转换返回值-表示是否启动成功 */bit Start18B20(){bit ack;ack Get18B20Ack(); //执行总线复位并获取18B20应答if (ack 0) //如18B20正确应答则启动一次转换{Write18B20(0xCC); //跳过ROM操作Write18B20(0x44); //启动一次温度转换}return ~ack; //ack0表示操作成功所以返回值对其取反}/* 读取DS18B20转换的温度值返回值-表示是否读取成功 */bit Get18B20Temp(int16 *temp){bit ack;uint8 LSB, MSB; //16bit温度值的低字节和高字节ack Get18B20Ack(); //执行总线复位并获取18B20应答if (ack 0) //如18B20正确应答则读取温度值{Write18B20(0xCC); //跳过ROM操作Write18B20(0xBE); //发送读命令LSB Read18B20(); //读温度值的低字节MSB Read18B20(); //读温度值的高字节*temp ((int16)MSB 8) LSB; //合成为16bit整型数}return ~ack; //ack0表示操作应答所以返回值为其取反值}程序主要应用层框架都在main.c 文件中。上电对各个模块初始化完毕后对继电器动作和蜂鸣器报警的温度值做限定防止意外错误。程序进入主循环阶段检测按键是否被按下切换系统状态只用到了回车、向上和向下三个按键检测目前的温度值针对温度值判断是否进行温度控制动作。配置初始化定时器0利用定时器0中断进行时间控制。/******************************main.h文件程序源代码*****************************/#ifndef _MAIN_H#define _MAIN_H#include config.h/* 温度相关参数温度数值左移4位是为与DS18B20数据格式保持一致 */#define ACT_TEMP_ADDR 0x30 //继电器动作温度的E2存储地址#define ACT_TEMP_MIN (204) //继电器动作温度有效范围最小值#define ACT_TEMP_MAX (304) //继电器动作温度有效范围最大值#define ACT_TEMP_DEFAULT (254) //继电器动作温度默认值#define ALARM_TEMP_ADDR 0x32 //高温报警温度的E2存储地址#define ALARM_TEMP_MIN (254) //高温报警温度有效范围最小值#define ALARM_TEMP_MAX (354) //高温报警温度有效范围最大值#define ALARM_TEMP_DEFAULT (304) //高温报警温度默认值void TempControl();void KeyAction(uint8 keycode);void ConfigTimer0(uint16 ms);#endif/******************************main.c文件程序源代码*****************************/#include DS18B20.h#include keyboard.h#include eeprom.h#include Led.h#include main.hbit flag2s 0; //2s定时标志位bit staBuzz 0; //蜂鸣器状态标志uint8 T0RH 0; //T0重载值的高字节uint8 T0RL 0; //T0重载值的低字节int16 curTemp 0; //当前读取的温度值int16 actTemp 0; //继电器动作温度设定值int16 alarmTemp 0; //高温报警温度设定值enum eStaSystem staSystem E_NORMAL; //系统运行状态void main(){EA 1; //开总中断InitLed(); //初始化LED模块Start18B20(); //启动首次温度转换ConfigTimer0(1); //配置T0定时1ms//读取继电器动作温度超出有效范围则设置为默认值E2Read((uint8*)actTemp, ACT_TEMP_ADDR, sizeof(actTemp));if ((actTemp ACT_TEMP_MIN) || (actTemp ACT_TEMP_MAX)){actTemp ACT_TEMP_DEFAULT;}//读取高温报警温度超出有效范围则设置为默认值E2Read((uint8*)alarmTemp, ALARM_TEMP_ADDR, sizeof(alarmTemp));if ((alarmTemp ALARM_TEMP_MIN) || (alarmTemp ALARM_TEMP_MAX)){alarmTemp ALARM_TEMP_DEFAULT;}while (!flag2s); //等待2秒ShowSystemSta(staSystem);ShowLedImage(0);while (1) //进入主循环{KeyDriver(); //执行按键驱动if (flag2s) //每隔2s执行以下分支{flag2s 0;if (Get18B20Temp(curTemp)) //读取当前温度{if (staSystem E_NORMAL){ShowTempValue(curTemp); //刷新温度显示TempControl(); //执行温度控制}}Start18B20(); //重新启动下一次转换}}}/* 温度控制函数 */void TempControl(){//检测执行执行继电器动作if (curTemp actTemp){ //高于设定温度时继电器吸合RELAY 0;ShowLedImage(1);}else if (curTemp actTemp){ //低于设定温度时继电器释放RELAY 1;ShowLedImage(0);}//检测执行高温报警if (curTemp alarmTemp)staBuzz 1;elsestaBuzz 0;}/* 按键动作函数根据键码执行相应的操作keycode-按键键码 */void KeyAction(uint8 keycode){if (keycode 0x26) //向上键增加温度设定值{if (staSystem E_SET_ACT){actTemp (14);if (actTemp ACT_TEMP_MAX)actTemp ACT_TEMP_MAX;}else if (staSystem E_SET_ALARM){alarmTemp (14);if (alarmTemp ALARM_TEMP_MAX)alarmTemp ALARM_TEMP_MAX;}}else if (keycode 0x28) //向下键减小温度设定值{if (staSystem E_SET_ACT){actTemp - (14);if (actTemp ACT_TEMP_MIN)actTemp ACT_TEMP_MIN;}else if (staSystem E_SET_ALARM){alarmTemp - (14);if (alarmTemp ALARM_TEMP_MIN)alarmTemp ALARM_TEMP_MIN;}}else if (keycode 0x0D) //回车键切换运行/设置状态{switch (staSystem){case E_NORMAL:staSystem E_SET_ACT;break;case E_SET_ACT:E2Write((uint8*)actTemp, ACT_TEMP_ADDR, sizeof(actTemp));staSystem E_SET_ALARM;break;case E_SET_ALARM:E2Write((uint8*)alarmTemp, ALARM_TEMP_ADDR, sizeof(alarmTemp));staSystem E_NORMAL;TempControl();break;default:break;}ShowSystemSta(staSystem);}//根据系统状态刷新温度显示switch (staSystem){case E_NORMAL: ShowTempValue(curTemp); break;case E_SET_ACT: ShowTempValue(actTemp); break;case E_SET_ALARM: ShowTempValue(alarmTemp); break;default: break;}}/* 配置并启动T0ms-T0定时时间 */void ConfigTimer0(uint16 ms){uint32 tmp;tmp (SYS_MCLK*ms)/1000; //计算所需的计数值tmp 65536 - tmp; //计算定时器重载值tmp tmp 33; //补偿中断响应延时造成的误差T0RH (uint8)(tmp8); //定时器重载值拆分为高低字节T0RL (uint8)tmp;TMOD 0xF0; //清零T0的控制位TMOD | 0x01; //配置T0为模式1TH0 T0RH; //加载T0重载值TL0 T0RL;ET0 1; //使能T0中断TR0 1; //启动T0}/* T0中断服务函数实现系统定时和按键扫描 */void InterruptTimer0() interrupt 1{static uint16 tmr2s 0;static uint16 tmrBuzz 0;TH0 T0RH; //重新加载重载值TL0 T0RL;LedScan(); //LED扫描显示KeyScan(); //矩阵按键扫描//定时2stmr2s;if (tmr2s 2000){tmr2s 0;flag2s 1;}//蜂鸣器驱动if (staBuzz) //蜂鸣器启动实现间隔发声{if (tmrBuzz 500) //0~0.5s发声{BUZZ 0;tmrBuzz;}else if (tmrBuzz 1000) //0.5~1s不发声{BUZZ 1;tmrBuzz;}else //计时到1s后重新开始{tmrBuzz 0;}}else //蜂鸣器关闭{BUZZ 1;tmrBuzz 0;}}程序代码已经完成了但是学习还得继续把思路学差不多之后要能够不看源代码独立把这个程序编写出来那么我就可以很高兴的告诉你你的单片机已经合格了你可以动手开发一些小产品进入下一个层次的历练了。当然了各位读者不要指望这样的代码一下子写出来就好用包括研发工程师调试这种代码也是一步步来的在调试的过程中可能还要穿插修改很多之前写好的代码协调功能工作等等。如果独立写这种代码1到3天调试完成还是比较正常的。学到这里相信各位读者对于做技术的基本耐性已经具备了。做技术耐心、细心、恒心缺一不可。不要像初学那样遇到一个问题动不动就浮躁了慢慢来最终把这个功能实现出来完成你单片机之路的第一个项目。14.6练习题1、学会使用类型说明定义新类型。2、学会建立编写头文件并且掌握头文件的格式。3、掌握条件编译的用法。4、独立将智能温控器项目开发的代码完成。