赞
踩
#include <REGX52.H>
/*
Debug 总结:
*/
unsigned char code SMG_duanma[] = {0xc0,0xf9,0xa4,0xb0,
0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,
0xc6,0xa1,0x86,0x8e,
0xbf,0x7f,0xff
};
unsigned char Segbuff[] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
void Delay_1ms(unsigned int t)
{
unsigned int x,y;
for(x = t; x > 0; x--)
for(y = 900; y > 0; y--);
}
void SelectHC573(unsigned char channel)
{
switch(channel)
{
case 0 : P2 = (P2 & 0X1F) | 0X00;break;
case 4 : P2 = (P2 & 0X1F) | 0X80;break;
case 5 : P2 = (P2 & 0X1F) | 0Xa0;break;
case 6 : P2 = (P2 & 0X1F) | 0Xc0;break;
case 7 : P2 = (P2 & 0X1F) | 0Xe0;break;
}
}
void InitSystem()
{
SelectHC573(5);
P0 = 0x00;
SelectHC573(4);
P0 = 0xFF;
SelectHC573(0);
}
void DispalySMG_Bit(unsigned char pos, unsigned char dat)
{
/**********消隐效果最佳******************/
SelectHC573(7);
P0 = 0xff;//关闭所有段选
SelectHC573(6);
P0 = 0x01 << pos;
SelectHC573(0);
P0 = 0xff;
SelectHC573(7);
P0 = dat;
//不要加SelectHC573(0);
/**********消隐效果最佳******************/
}
void Timer0_Init()
{
TMOD = 0X00;
TH0 = (65536 - 1000) / 256;
TL0 = (65536 - 1000) % 256;
EA = 1;
ET0 = 1;
TR0 = 1;
}
unsigned char Seg_signal = 0;
void KeyScan()
{
if(P3_0 == 0)
{
Delay_1ms(10);
if(P3_0 == 0)
{
while(P3_0 == 0);
switch(Seg_signal)//()里面不能放bit类型的变量
{
case 0: Seg_signal = 1;break;
case 1: Seg_signal = 0;break;
}
}
}
}
unsigned char month = 1;
void Display1()//最好把所有的Segbuff都赋值一下,不用的可以赋0xff,这样就不受到其他Display函数的影响。
{
Segbuff[0] = SMG_duanma[18];
Segbuff[1] = SMG_duanma[18];
Segbuff[2] = SMG_duanma[18];
Segbuff[3] = SMG_duanma[18];
Segbuff[4] = SMG_duanma[16];
Segbuff[5] = SMG_duanma[month/10];
Segbuff[6] = SMG_duanma[month%10];
Segbuff[7] = SMG_duanma[16];
}
void Display2()
{
Segbuff[0] = SMG_duanma[0];
Segbuff[1] = SMG_duanma[1];
Segbuff[2] = SMG_duanma[2];
Segbuff[3] = SMG_duanma[3];
Segbuff[4] = SMG_duanma[4];
Segbuff[5] = SMG_duanma[5];
Segbuff[6] = SMG_duanma[6];
Segbuff[7] = SMG_duanma[7];
}
void main()
{
InitSystem();
Timer0_Init();
while(1)
{
switch(Seg_signal)
{
case 0: Display1();break;
case 1: Display2();break;
}
KeyScan();
}
}
void Timer0_Service() interrupt 1
{
static unsigned int count_1ms = 0;
static unsigned char pos = 0;
count_1ms++;
switch(pos)
{
case 0 : DispalySMG_Bit(pos, Segbuff[0]);pos++;break;
case 1 : DispalySMG_Bit(pos, Segbuff[1]);pos++;break;
case 2 : DispalySMG_Bit(pos, Segbuff[2]);pos++;break;
case 3 : DispalySMG_Bit(pos, Segbuff[3]);pos++;break;
case 4 : DispalySMG_Bit(pos, Segbuff[4]);pos++;break;
case 5 : DispalySMG_Bit(pos, Segbuff[5]);pos++;break;
case 6 : DispalySMG_Bit(pos, Segbuff[6]);pos++;break;
case 7 : DispalySMG_Bit(pos, Segbuff[7]);pos = 0;break;
}
if(count_1ms == 1000)
{
count_1ms = 0;
month++;
if(month == 13)
month = 1;
}
}
用定时器显示数码管的原因是因为,用定时器中断控制LED闪烁后,数码管因为与延时函数不能正常显示,因此想要使用定时器中断控制数码管显示,然后用延时函数控制LED闪烁,这样就可以实现数码管动态显示。
**
**
1.头文件
#include <STC15F2K60S2.H>//单片机寄存器专用头文件
#include "HC138.h"//HC138选通专用头文件
#include "Led.h"//Led底层驱动专用头文件
#include "Init.h"//初始化底层驱动专用头文件
#include "KEY.h"//独立按键专用头文件
#include "Delayms.h"//延时1ms专用头文件
#include "SMG.h"//数码管底层驱动专用头文件
2.变量声明
// "0-9" "A-F" " - " " . "
unsigned char SMG_duanma[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e,
0xbf,0x7f,0xff};
unsigned char SMG_Display[8] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};//数码管显示数据存放数组
unsigned char LED_Display[8] = {0,0,0,0,0,0,0,0}; //Led显示数据存放数组
unsigned char Key_Val,Key_Down,Key_Old,Key_Up;//按键专用变量
unsigned char smg_pos;//定时器中断中数码管扫描专用变量
unsigned char led_pos;//定时器中断中数码管扫描专用变量
unsigned char Led_Flag;//LED标志位(可用于闪烁)
unsigned int SMG_Slow_Down;//数码管减速专用变量 500ms (这里用int char是-128到128)
unsigned char Key_Slow_Down;//按键减速专用变量 10ms
3.键盘处理函数
/* 键盘处理函数 */
void Key_Proc()
{/*
键盘减速程序(比如主程序只要20ms 但是温度检测需要750ms,可能上一个数据还没处理完
下一个数据就进来了,因此需要进行一个等待,相当于延时函数)
*/
if(Key_Slow_Down) return;//如果不是0就返回,是0的话就变成1,然后进入中断中断中为0-9循环,为10ms
Key_Slow_Down = 1;//按键减速程序
Key_Val = KeyScan();//实时读取键码值
Key_Down = Key_Val & (Key_Old ^ Key_Val);//捕捉按键下降沿
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//捕捉按键上降沿
Key_Old = Key_Val;//辅助扫描变量
switch(Key_Down) //哪个按键进行按下
{
}
}
4.数码管处理函数
//数码管显示函数
void SMG_Proc()
{
if(SMG_Slow_Down) return;//如果不是0就返回,是0的话就变成1,然后进入中断中断中为0-500循环,为500ms
SMG_Slow_Down = 1;//数码管减速程序
SMG_Display[0] = SMG_duanma[0];
SMG_Display[1] = SMG_duanma[1];
SMG_Display[2] = SMG_duanma[2];
SMG_Display[3] = SMG_duanma[3];
SMG_Display[4] = SMG_duanma[4];
SMG_Display[5] = SMG_duanma[5];
SMG_Display[6] = SMG_duanma[6];
SMG_Display[7] = SMG_duanma[7];
}
5. LED处理函数
//LED处理函数
void Led_Proc()
{
LED_Display[0] = Led_Flag; //ucLed[第几个灯] = 1亮/0灭
}
6.定时器初始化
void Timer0Init(void) //1毫秒@12.000MHz 16位重装载
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1;
}
7.中断
/* 定时器0中断服务函数 */
void Timer0Server() interrupt 1
{
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;//键盘减速专用
if(++SMG_Slow_Down == 500) SMG_Slow_Down = 0;//数码管减速专用
if(smg_pos++==7)smg_pos=0;
if(led_pos++==7)led_pos=0;
DispalySMG_Bit(smg_pos,SMG_Display[smg_pos]); //数码管0-7扫描并显示
LED_Disp(led_pos,LED_Display[led_pos]); //LED进行0-7扫描并显示
/*
if(count_1==1000) //用于定时器控制LED闪烁
{
count_1=0; //计时复位
Led_Flag ^= 1; //^是异或符号 用于标志位取反
}
*/
}
8.主函数
void main()
{
InitAll();
Timer0Init();
while(1)
{
Key_Proc();
SMG_Proc();
Led_Proc();
}
}
个位:n/1%10
十位:n/10%10
百位:n/100%10
千位:n/1000%10
unsigned char SMG_duanma[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e,
0xbf,0x7f,0xff};
unsigned char SMG_Display[8] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};//数码管显示数据存放数组
unsigned char LED_Display[8] = {0,0,0,0,0,0,0,0}; //Led显示数据存放数组
unsigned char Key_Val,Key_Down,Key_Old,Key_Up;//按键专用变量
unsigned char smg_pos;//定时器中断中数码管扫描专用变量
unsigned char led_pos;//定时器中断中数码管扫描专用变量
unsigned char Led_Flag;//LED标志位(可用于闪烁)
unsigned int SMG_Slow_Down;//数码管减速专用变量 500ms (这里用int char是-128到128)
unsigned char Key_Slow_Down;//按键减速专用变量 10ms
unsigned int Timer_count_1;//LED系统计时变量
unsigned char SMG_Mode;//如果有显示界面 一开始就定义这个参数 0-显示界面 1-设置界面
unsigned int Timer_1000ms;//1000毫秒定时标志位
unsigned char Timer_Count = 30;//系统计时变量
unsigned char System_Flag;//系统标志位 0-暂停 1-开始
unsigned char Set_Dat[3] = {15,30,60};//设置参数储存数组
unsigned char Set_Dat_Index = 1;//相当于指针 来表示数组中的数如Set_Dat[Set_Dat_Index]
unsigned int Timer_500ms;//1000毫秒定时标志位
unsigned char SMG_Flag;//SMG闪烁标志位
因为所有的外设基本上都是共用的p0寄存器对吧
如果你不加限制 在while循环里面一直赋值 就会影响其他模块
最常见的是 继电器有电流声 数码管乱闪 会造成引脚冲突 因此一定要在不使用的时候进行限制
*例如蜂鸣器模块
#include <STC15F2K60S2.H>
#include "HC138.h"
void Beep(unsigned char enable) //enable是是否启动蜂鸣器 1为启动 0为熄灭
{
static unsigned char temp = 0x00; //只有第一次时temp为0x00
static unsigned char temp_old = 0xff;
if(enable) //如果enable=1就进入,启动蜂鸣器
temp |= 0x40;
else //如果enable=0就进入,关闭蜂鸣器
temp &= ~0x40;
if(temp != temp_old) //temp值与temp_old不相等,说明temp值改变了就进入一次
{
P0=temp;
P2=(P2 & 0X1f) | 0xa0; //Y5输出0,蜂鸣器和继电器控制
P2 &= 0x1f;
temp_old = temp; //temp_old都是被动改变,只有temp改变他才会改变
}
}
最后5行就是加以限制,只有改变了才会进入不然不会进行改变
void LED_Disp(unsigned char pos,enable) //pos是位置,enable是是否点亮LED 1为点亮 0为熄灭
{
static unsigned char temp = 0x00; //只有第一次时temp为0x00
static unsigned char temp_old = 0xff;
if(enable) //如果enable=1就进入,点亮对应LED
{temp |= 0x01<<pos;}
else //如果enable=0就进入,熄灭对应LED(这里面虽然是0000 0001但是后面要取反变为1111 1110)
{temp &= ~(0x01<<pos);}
if(temp != temp_old) //temp值与temp_old不相等,说明temp值改变了就进入一次
{
P0=~temp; //对temp进行取反,不然就是点亮命令之外的所有灯
SelectHC138(4);
SelectHC138(0);
temp_old = temp; //temp_old都是被动改变,只有temp改变他才会改变
}
}
1.设置一个数码管闪烁位
unsigned char SMG_Flag;//SMG闪烁标志位
2.在中断中设置定时时间
if(++Timer_500ms == 500)
{
Timer_500ms=0;
SMG_Flag ^= 1;
}
3.书写对应函数
if(SMG_Flag == 1) //500ms
{
SMG_Display[4] = SMG_duanma[Set_Dat[Set_Dat_Index]/10%10];//取出Timer_Count(倒计时)十位
SMG_Display[5] = SMG_duanma[Set_Dat[Set_Dat_Index]%10];//取出Timer_Count(倒计时)个位
}
else //熄灭
{
SMG_Display[4] = SMG_duanma[18];
SMG_Display[5] = SMG_duanma[18];
}
对齐多个光标修改(shift + alt + 鼠标选择或者上下键)
我们可看到,在 include 后面有一系列的光标,这样就可以删掉对应的所在行了。
这个技巧在编辑多行代码时,非常有用,比如一个结构体的名字需要修改,用这个很方便。
1.每个基本模块写完要做基本的检测,不能等所有都写完再一起调试。
2.确定每个读取数据模块的使用次数和触发条件,如果多个模式使用,应注意读取方式。
不要再一个需要取用的模块里用一个需要实时更新的模块(如DS1302)。
一个数据需要再多个模式下使用就要单独独立出来或者在每次使用前都读取一次(典型的器件如DS1302)。
比如超声波再模式下0x121,秒读数是2的整数倍才可以读取数据。
如果把DS1302放在另一个如0x122的模式下。则秒读书无法变化,则可能无法读数。
解决方法:再每一个需要检测秒读数的地方都读一遍。或者再可以一直执行的地方读取数据。
3.延时和中断的应用,有延时的程序放主函数,没有延时的的程序放中断。
不要把有延时的代码放到中断函数中。
一些我遇见的报错总结
ERROR: PREPROCESSOR: MACROS TOO NESTED宏嵌套过多
ACTION: PARSING SOURCE-FILE
检查头文件是否嵌套
…\Driver\iic.c(10): error C202: ‘P21’: undefined identifier
检查C文件是否加头文件。
出现类似 0xA3这类报错的看样子没错的可能是多一个空格。
User\mian.c(161): warning C206: ‘Pcf8591_Adc’: missing function-prototype
User\mian.c(161): error C267: ‘Pcf8591_Adc’: requires ANSI-style prototype
编译问题:.c文件里的函数头文件未声明;
*** ERROR L107: ADDRESS SPACE OVERFLOW
SPACE: DATA
SEGMENT: DATA_GROUP
LENGTH: 001CH
Program Size: data=112.3 xdata=0 code=5537
片内内存不足,可以将变量存储到片外ram中,例如:
xdata unsigned long ms = 0;
xdata unsigned long key_time;
如果代码未报错,烧录进单片机的程序有问题
仅单个模块出现问题
首先检查硬件部分,跳线帽有没有接好,例如独立按键和矩阵按键的条线帽,555测频时需要接跳线帽,超声波模块也需要接跳线帽。
如果没有问题,检查该模块代码内容上是否出现错误。
多个模块集体不响应
首先检查硬件部分,此时需要换一块开发板,再次尝试。
如果不行可能的原因再代码上。
把每个模块代码单独隔开。注释其他代码,单独烧录某个模块的代码。
如果只有某一模块不响应,可能这个模块存在大延时。这种问题极有可能出现与超声波模块中。、
定时器TR0没关或没开导致长时间等待。长时间读不到数值,没有返回值。
如果还是各个模块都不响应,极有可能在于中断未开,两个中断一定要开,总中断和用到的定时器中断。
首先单片机不是那么简单的,目前简单的原因是,有许多搞蓝桥杯单片机培训的,比如蚂蚁工厂科技,电子社,是他们把底层驱动,main函数的基本架构的方法写好了,大多数人都是再他们的构建的基础上去修改。如果大家都靠自己去写底层驱动,靠大家的经验去构建主函数。有一说一,大部分大一,大二的学生哪有这些做项目的经验。所以说如果,你想要比较轻松取得比较高的奖项的话,就需要你去学习他们得代码。个人而言,蚂蚁工厂科技的代码,模块化更好一点,便于学习和移植。就我个人而言,通过学习他们家的代码,对我一些其他实操的项目有很大的帮助。
其次在省赛之前一定会有几套模拟题,你可以不去买,但一定要搞到手。应为它们就是接下来省赛的考试范围,比如模拟题考了超声波,那你就一定要会,因为真的会考。
国赛的时候,建议带两块板子。因为跳线帽可能不够用。比如超声波和555测频一起考。
51单片机中DS1302使用的是BCD码
调用写/读函数可以发现数字从9直接跳到了16。因为DS1302内部并不是以二进制存储数据,而是使用BCD码。
BCD码:用四个二进制数表示一个十进制数。所以9的BCD码是0000 1001,10的BCD码是0001 0000。但是解码读取到的并不是BCD码,而是16进制,所以会从9突变到16的现象。
BCD码转10进制:DEC=BCD/16*10+BCD%16;(两位BCD)
十进制转BCD码:BCD=DEC/10*16+DEC%10;(两位BCD)
(难点包含 一个闹钟界面三个闹钟的设置 )
DS1302 题目分类
1.实时时钟
2.时钟设置
3.闹钟设置
//DS18B20初始化
bit init_ds18b20(void)
{
bit initflag = 0;
DQ = 1;
Delay_OneWire(120);//都是乘10以后
DQ = 0;
Delay_OneWire(800);//都是乘10以后
DQ = 1;
Delay_OneWire(100); //都是乘10以后
initflag = DQ;
Delay_OneWire(50);//蓝桥杯此处给的是Delay_OneWire(5); 延时函数后面都要乘10 因为这个板子只有1T
return initflag;
}
- 2.自己写调佣温度函数时一定要按照顺序写,具体可看芯片手册 不然会出现错误 按照初始化-写温度-初始化-读取温度-将温度读取的值送入高八位跟低八位中
float read_Temperaturn(void)
{
unsigned char LSB,MSB;//返回温度数据的高低八位
init_ds18b20();
Write_DS18B20(0xcc); //跳过ROM
Write_DS18B20(0x44); //开始温度转换
init_ds18b20(); //复位
Write_DS18B20(0xcc); //跳过ROM
Write_DS18B20(0xbe); //读取高速暂存器
LSB=Read_DS18B20(); //读取温度数据的LSB低八位
MSB=Read_DS18B20(); //读取温度数据的MSB高八位
return ((MSB<<8)|LSB) / 16.0;//因为芯片里变成了16位进制要变成10进制 可以除以16或者*0.625
}
注意此处一定要设置为16.0不然会出现没有小数的问题
3.读取温度显示数码管会出现16进制转换值,然后再出现十进制,此时就需要在主函数中延时750ms即可
/*延时函数*/
void Delay750ms() //@12.000MHz
{
unsigned char i, j, k;
_nop_();
i = 6;
j = 180;
k = 26;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
/* Main */
void main()
{
read_Temperaturn();
Delay750ms();
}
4.在温度控制中,温度显示与温度控制一般都是以两个数组同时出现,一个用来储存显示温度的数组,一个用来存储控制温度变化的数组。
unsigned char Temperature_Data[2] = {30,20};//温度参数储存数组 第一位是最大值 第二位为最小值
unsigned char Temperature_Set[2] = {30,20};//真实参数控制数组 因为还没确定的时候是不能改变显示的
AT24C02 EEPROM详细介绍链接
蓝桥杯AT24C02代码
unsigned int 是占两个字节其中EEPROM是每八位八位一写 如果用unsigned int 直接写会溢出,因为unsigned int是16位 所以需要拆分
//函数名:写EEPROM函数
//入口参数:需要写入字符串,写入地址(务必为8的倍数),写入数量
//返回值:无
//函数功能:向EEPROM的某个地址写入字符串中特定数量的字符
/* AT24C02是一种12C接口的EEPROM芯片,它每个字节都有一个独特的地址,其中一个地址对应一个字节。在这里,
地址必须是8的倍数,因为该芯片具有8个字节的页长。这意味着,在一次写入操作中,最多只能写入8个字节。
如果写入的地址不是8的倍数,那么将不能写入整页字节,可能会导致写入错误或数据丢失。*/
/*写24C02的时候,从器件地址为10100000(0xA0);
读24C02的时候,从器件地址为10100001(0xA1)*/
void EEPROM_Write(unsigned char* EEPROM_String,unsigned char addr,unsigned char num)
{
IIC_Start();//发送开始信号
IIC_SendByte(0xA0);//选择EEPROM芯片,确定写的模式 1010 0000 0是写
IIC_WaitAck();//等待EEPROM反馈
IIC_SendByte(addr);//写入要储存的数据地址 把页码告诉他
IIC_WaitAck();//等待EEPROM反馈
while(num--)//写多少字节循环多少次
{
IIC_SendByte(*EEPROM_String++);//将要写入的信息写入
IIC_WaitAck();//等待EEPROM反馈
IIC_Delay(200);
}
IIC_Stop();//停止发送
}
//函数名:读EEPROM函数
//入口参数:需要读取的字符串,读取地址(务必为8的倍数),读取数量
//返回值:无
//函数功能:读取EEPROM的某个地址中的数据,并存放在字符串数组当中。
void EEPROM_Read(unsigned char* EEPROM_String,unsigned char addr,unsigned char num)
{
IIC_Start();//发送开始信号
IIC_SendByte(0xA0);//选择EEPROM芯片,确定写的模式 1010 0000 0是写
IIC_WaitAck();//等待EEPROM反馈
IIC_SendByte(addr);//写入要储存的数据地址 把页码告诉他
IIC_WaitAck();//等待EEPROM反馈
IIC_Start();//发送开始信号
IIC_SendByte(0xA1);//选择EEPROM芯片,确定读的模式 1010 0000 0是写
IIC_WaitAck();//等待EEPROM反馈
while(num--)//写多少字节循环多少次
{
*EEPROM_String++ = IIC_RecByte();//将要写入的信息写入
if(num) IIC_SendAck(0);//发送应答
else IIC_SendAck(1);//不应答
}
IIC_Stop();//停止发送
}
1.方波就是占空比为百分之五十的一个波
#include <STC15F2K60S2.H>
sbit Tx = P1^0;
sbit Rx = P1^1;
void Ultrasonic_Timer0Init(void) //12us 一个周期就是24us 对应差不多41kHZ
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xF4; //设置定时初值
TH0 = 0xFF; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 0; //定时器0并不开始计时
}
unsigned char Wave_Recv(void)
{
unsigned char Wave_Num = 10;
unsigned char Dist;
//完成超声波的发送 Num是发送次数 24us一次 发送10次 就是240us 差不多为41kHZ
Tx = 0;
TL0 = 0xF4; //设置定时初值
TH0 = 0xFF; //设置定时初值
TR0 = 1;
while( Wave_Num-- )
{
while(!TF0);//TF0为溢出标志位 没有溢出就一直在while中循环 溢出了就跳出while
Tx ^= 1;//高低电平切换 12us低电平 12us高电平 24us为一个周期 一共有五个周期
TF0 = 0;
}
//完成超声波的接收
TR0 = 0; //已经发送完毕 先关闭定时器 关掉定时器的目的是为了重新装载
TL0 = 0; //设置定时初值,用于计时
TH0 = 0; //设置定时初值
TR0 = 1; //重新装载完毕 打开定时器 开始计时
while(Rx && (~TF0));//等待RX收到下降沿信号 RX大部分时间为1 跳出去前提 RX为0并且TF0溢出标志位不溢出
TR0 = 0; //关闭的目的是计算距离
if(TF0 == 1)//表示溢出了
{
Dist = 250;//表示数据太大了,数据有问题,为什么要测量这么长的高度 测出最大为1m
TF0 = 0;//因为溢出了,因此需要清零
}
else //没有产生溢出
{
Dist = (((TH0<<8) | TL0) * 0.017);//0.017由来 (t*(10-6)十的负六次方*340/2)*100(换算成厘米)
}
return Dist;
}
1.先写定时0模块 (此模块专门用来给频率测量使用) 频率测量智能使用定时器0!!!!
/* 定时器0中断初始化函数 */
void Timer0Init(void) //0毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x05; //设置自动重装载模式
TL0 = 0x00; //设置定时初始值
TH0 = 0x00; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
2.写定时器1
/* 定时器1计数初始化函数 */
void Timer1Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初始值
TH1 = 0xFC; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1;
EA = 1;
}
3.最重要的就是在中断中进行计数
if(++Freq_Timer_1000Ms == 1000) //NE555每隔1s进来一次 用来计算频率
{
Freq_Timer_1000Ms = 0;
Freq = TH0 << 8 | TL0; //将频率高八位与低八位合在一起
TH0 = TL0 = 0; //高八位跟低八位清0
}
易错知识点
1.ASCII值 要进行减48
2.
串口介绍
Uart.c文件
#include <Uart.h>
/* 串口初始化函数 */
void UartInit(void) //9600bps@12.000MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x01; //串口1选择定时器2为波特率发生器
AUXR |= 0x04; //定时器时钟1T模式
T2L = 0xC7; //设置定时初始值
T2H = 0xFE; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
ES = 1;
EA = 1;
}
/* 字节发送函数 */
void SendByte(unsigned char dat)
{
SBUF=dat;//将dat数据赋给SBUF,将数据发送出去
while(TI == 0);//等待数据发送
TI = 0;//将发送标志位清零
}
/* 字符串发送函数 */
void Uart_Send_String(unsigned char *dat)
{
while(*dat != '\0')//当字符不为空时,继续发送
SendByte(*dat++);//发送后指针dat加1,指向下一个字节
}
main函数要在大模板中进行补充
/* 串口1中断服务函数 */
void Uart1Server() interrupt 4
{
if(RI == 1) //串口接收数据
{
Uart_Recv[Uart_Recv_Index] = SBUF;
Uart_Recv_Index++;
RI = 0;
}
}
1.串口存放数组一定要足够大,不然会出现存储不下导致发送不了的问题
2.如果发送串口超过数组存储,会出现程序崩溃的情况,因此必须在发送时加以限制
修改方案
在发送中断函数中加以限制,避免胡乱发送的情况
3.按下按键s19会出现数码管直接乱码的情况 解决方法
在扫描之前将定时器关闭,在扫描之后将P3复位,复位后再将ET1打开。
4.内存的问题 用xdata
5.继电器与蜂鸣器不能同时使用的问题如何解决
把局部变量变成全局变量
放在外面
长按 短按 双击 单击
按键解析(重点)
按键的各种情况
代码具体解释可以看上图
Key_Val = Key_Read();//实时读取键码值
Key_Down = Key_Val & (Key_Old ^ Key_Val);//捕捉按键下降沿 按键按下多少就是多少
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//捕捉按键上降沿
Key_Old = Key_Val;//辅助扫描变量 按键一直按下则Key_Old一直为0 松开则变成1
1. 先看数码管有无问题 在数码管上显示12345678看看
2.再看LED有无问题 先全部熄灭再全部点亮
3. 用stc生成定时器T0后一定要注意写ET0 = 1;EA = 1;
4.注意在写代码的时候一定要有写注释的习惯
5.定义数码管减速变量时一定要用unsigned int 因为他是减速500ms 按键是减速10ms
6.一定要检查底层代码 矩阵键盘中定义返回值一定要赋予初始值0,不然会出现错误。
1. 数码管闪烁 只灭不闪 1.先看代码有无问题 2.再看中断中标志位是否取反 3.再看总结定义的变量是不是用unsigned int不能用unsigned char。
解决问题思路可以用LED作为检查问题的显示
## 2.DA输出一般放到LED模块显示函数中
1. LED底层模板
#include <STC15F2K60S2.H>
#include "HC138.h"
/**
* @brief(简介) 用来选择LED点亮与熄灭
* @param(参数) pos enable
* @retval(返回值) 无
*/
void Led_Disp(unsigned char pos,enable) //pos是位置,enable是是否点亮LED 1为点亮 0为熄灭
{
static unsigned char temp = 0x00; //只有第一次时temp为0x00
static unsigned char temp_old = 0xff;
if(enable) //如果enable=1就进入,点亮对应LED
{temp |= 0x01<<pos;}
else //如果enable=0就进入,熄灭对应LED(这里面虽然是0000 0001但是后面要取反变为1111 1110)
{temp &= ~(0x01<<pos);}
if(temp != temp_old) //temp值与temp_old不相等,说明temp值改变了就进入一次
{
P0=~temp; //对temp进行取反,不然就是点亮命令之外的所有灯
SelectHC138(4);
SelectHC138(0);
temp_old = temp; //temp_old都是被动改变,只有temp改变他才会改变
}
}
2.HC138选通底层模板
#include <STC15F2K60S2.H>
/**
* @brief(简介) 用来选择74HC138选通的通道
* @param(参数) 无
* @retval(返回值) 无
*/
void SelectHC138(unsigned char channel)
{
switch(channel)
{
case 4:
P2=(P2 & 0X1f) | 0x80; //Y4输出0,LED控制选择
break;
case 5:
P2=(P2 & 0X1f) | 0xa0; //Y5输出0,蜂鸣器和继电器控制
break;
case 6:
P2=(P2 & 0X1f) | 0xc0; //Y6输出0,数码管位选
break;
case 7:
P2=(P2 & 0X1f) | 0xe0; //Y7输出0,数码管段码
break;
case 0:
P2 &= 0X1f; //所有锁存器不选择
break;
}
}
3.矩阵按键底层模板
#include <STC15F2K60S2.H>
unsigned char Key_Read()
{
unsigned char num = 0;//一定要给num赋值 不然会出现错误
P34 = 1;P35 = 1;P42 = 1;P44 = 0;
if(P33==0) num = 1;
if(P32==0) num = 2;
if(P31==0) num = 3;
if(P30==0) num = 4;
P34 = 1;P35 = 1;P42 = 0;P44 = 1;
if(P33==0) num = 5;
if(P32==0) num = 6;
if(P31==0) num = 7;
if(P30==0) num = 8;
P34 = 1;P35 = 0;P42 = 1;P44 = 1;
if(P33==0) num = 9;
if(P32==0) num = 10;
if(P31==0) num = 11;
if(P30==0) num = 12;
P34 = 0;P35 = 1;P42 = 1;P44 = 1;
if(P33==0) num = 13;
if(P32==0) num = 14;
if(P31==0) num = 15;
if(P30==0) num = 16;
return num;
}
4.数码管底层模板
#include <STC15F2K60S2.H>
#include "HC138.h"
unsigned char duan_pos[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff};
unsigned char wei_dat[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
void Seg_Disp(unsigned char pos,dat,point)
{
P0 = 0xff;
SelectHC138(7);
SelectHC138(0);
P0 = wei_dat[pos];
SelectHC138(6);
SelectHC138(0);
P0 = duan_pos[dat];
if(point)
P0 &= 0x7f;
SelectHC138(7);
SelectHC138(0);
}
5.LED,蜂鸣器初始化底层模板
#include <STC15F2K60S2.H>
#include "HC138.h"
void ALLInit()
{
SelectHC138(4);
P0 = 0xff;
SelectHC138(5);
P0 = 0x00;
SelectHC138(0);
}
6.主函数中T0定时器模板函数
/*定时器T0函数*/
void Timer0Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
EA = 1;
ET0 = 1;
}
7.T0定时器中断服务函数模板
/*中断函数*/
void Timer0Servic() interrupt 1
{
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;
if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;
if(++Seg_Pos == 8) Seg_Pos = 0;
Seg_Disp(Seg_Pos,Seg_Display[Seg_Pos],Seg_Point[Seg_Pos]);
LED_Disp(Seg_Pos,Led_Display[Seg_Pos]);
}
unsigned char Seg_Disp_Mode;//设置模式切换变量
按键控制模式切换
case 12:
if(Seg_Disp_Mode == 2) Seg_Disp_Mode = 0;//界面切换
break;
设置界面显示内容
switch(Seg_Disp_Mode)
{
case 0:
Seg_Display[0] = 11;//C
Seg_Display[1] = Seg_Display[2] = Seg_Display[3] = 10;
/*如果不见unsigned char会出现变量形式错误 如果Temperature用char定义 显示温度就只会出现整数*/
Seg_Display[4] = (unsigned char)Temperature/10%10;
Seg_Display[5] = (unsigned char)Temperature%10;
Seg_Display[6] = (unsigned int)(Temperature*10)%10; //温度可能会超过char最大值 因此用int
Seg_Display[7] = 11;//C
Seg_Point[5] = 1;
break;
case 1:
Seg_Display[0] = 12;
Seg_Display[1] = Seg_Display[2] = 10;
Seg_Display[3] = Temperature_Data[0]/10;
Seg_Display[4] = Temperature_Data[0]%10;
Seg_Display[5] = 14;
Seg_Display[6] = Temperature_Data[1]/10;
Seg_Display[7] = Temperature_Data[1]%10;
Seg_Point[5] = 0;
break;
}
bit Seg_Star_Flag;//数码管闪烁标志位
unsigned int Timer_500Ms;//500毫秒定时专用变量
unsigned char Temperature_Data_Index;//温度参数数据指针 0-上限 1-下限
Temperature_Data_Index相当于指向数码管指针 用来控制哪个位闪烁
// if(Temperature_Data_Index == 0)
// {
// Seg_Display[3] = Seg_Star_Flag?Temperature_Data[0]/10:10;
// Seg_Display[4] = Seg_Star_Flag?Temperature_Data[0]%10:10;
// }
// else
// {
// Seg_Display[6] = Seg_Star_Flag?Temperature_Data[1]/10:10;
// Seg_Display[7] = Seg_Star_Flag?Temperature_Data[1]%10:10;
// }
Seg_Display[3*(Temperature_Data_Index+1)] = Seg_Star_Flag?Temperature_Data[Temperature_Data_Index]/10:10;
Seg_Display[3*(Temperature_Data_Index+1)+1] = Seg_Star_Flag?Temperature_Data[Temperature_Data_Index]%10:10;
break;
闪烁核心代码 一个简化一个未简化
if(++Timer_500Ms == 500)
{
Timer_500Ms = 0;
Seg_Star_Flag ^= 1;
}
延时500ms函数
case 13://参数切换
if(Seg_Disp_Mode == 1)//在参数界面
{
Temperature_Data_Index ^= 1;
}
用if函数判断是否在这个界面 在这个界面才执行下一句函数。
if(Key_Down == 7)
{
Ms_Tick_S7 = Ms_Tick;
}
if(Key_Up == 7)//如果松手
{
if(Ms_Tick - Ms_Tick_S7 >= 1000) //如果按键时间超过1s
Led_Enable_Flag ^= 1; //LED使能开关
else//短按
Freq_Val = Freq; //将频率值保存
}
if(Seg_Disp_Mode == 0)//从参数界面跳转到温度显示界面
{
Temperature_Data_Index = 0;//用于每次切换到参数界面 当前选择的参数是温度上限TMAX
if(Temperature_Data[0] > Temperature_Data[1])//判断上限是否大于下限
{
/*如果大于则执行下面的语句 如果不大于则不执行那么值还是不变的状态 就可以实现判断数据有效性的作用*/
Temperature_Set[0] = Temperature_Data[0];
Temperature_Set[1] = Temperature_Data[1];
}
}
每次显示与设置都要定义两个数组,一个用来显示,一个用来真正控制。
if(Seg_Disp_Mode == 2)
{
Voltage_Set = Voltage_Disp;
Voltage_Set_EEPROM = Voltage_Set*10;
EEPROM_Write(&Voltage_Set_EEPROM,0,1);
}
主函数
void main()
{
EEPROM_Read(&Voltage_Set_EEPROM,0,1);
Voltage_Disp = Voltage_Set = Voltage_Set_EEPROM/10;
System_Init();
Timer0Init();
while (1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
1.定义一个EEPROM存储数组
unsigned char EEPROM_String[5] = {0};
2.核心代码(用来存储)
EEPROM_String[0] = (unsigned char)(AD_Voltage*10);//用于存储0-255的电压数值
EEPROM_String[1] = Freq >> 8;//用于存储频率原始数值,高8位
EEPROM_String[2] = Freq & 0xff;//用于存储频率原始数值,低8位
EEPROM_Write(EEPROM_String,0,5);
EEPROM_Read(EEPROM_String,0,5);
AD_Voltage_EEPROM = EEPROM_String[0];
Freq_EEPROM = (EEPROM_String[1]<<8) | EEPROM_String[2];
1.定义输入储存数组跟输入储存指针
unsigned char Seg_Input[4] = {11,11,11,11};//数码管输入数据存储数组
unsigned char Seg_Input_Index;//数码管输入数据存储数组指针
2.规定条件 并把按键的值送入输入储存数组中
if(Key_Down >= 1 && Key_Down <= 10)//键盘使能条件
{
if(Seg_Disp_Mode == 0 && Seg_Input_Index < 4)//如果处在模式0 且输入的数字少于4个的话就执行
{
Seg_Input[Seg_Input_Index] = Key_Down-1;//输入的值为键盘值减1
Seg_Input_Index++;//输入指针加1
}
}
3.将输入的值提取出来 并且进行四舍五入
Voltage = (Seg_Input[0]*1000 + Seg_Input[1]*100+Seg_Input[2]*10+Seg_Input[3]+5)/1000.0;
4.将储存的值数码管显示出来
case 1://数据显示界面
Seg_Point[3+(int)Voltage/10] = 1;//控制小数点的在第几位
Seg_Display[0] = 12;
Seg_Display[1] = Seg_Display[2] = 10;
Seg_Display[3] = (int)Voltage/10?1:(unsigned char)Voltage %10; //前半部分担心从0.999变成1
Seg_Display[4] = (unsigned int)(Voltage*100)/10%10;
Seg_Display[5] = (unsigned int)(Voltage*100)%10;
break;
if(Key_Old == 1)
Seg_Disp_Mode = 1;
else
Seg_Disp_Mode = 0;
注意不要放到switch中去 会造成切换到B界面返回不来的情况,因为case中要检测1中下降沿才会继续执行
unsigned int ms_Tick,ms_statrt;//按键长短按计时变量
//======按键长短按(抬起时长按才生效)==============
if(Key_Down == 7)
{
ms_statrt = ms_Tick;//定时器中断ms每1毫秒加1,检测S7下降沿,将ms赋值到ms_statrt。
}
if(Key_Up == 7)
{
if((ms_Tick - ms_statrt) >= 1000) //抬起时检测(ms - ms_statrt)是否超过规定时间
Led_Display[7] = 1; //所要执行的任务
else
Led_Display[5] = 1;
}
//=
void Timer0Servic() interrupt 1
{
ms_Tick++;
}
case 4://-按键
if(Seg_Disp_Mode == 0)//时钟相关界面
{
if(Seg_Index == 1)//处于时钟设置界面
{
ucRtc_Set[ucRtc_Index]--;
//因为是16进制 所以0x10-1会变成0x0f 此时就需要将0x0f进行变化变成0x09
if(ucRtc_Set[ucRtc_Index] % 16 == 0x0f)
ucRtc_Set[ucRtc_Index] -= 6;
//进行界限判断 界限-1变成0xff因为后面又-6 因此变成0xf9
if(ucRtc_Set[ucRtc_Index] == 0xf9)
ucRtc_Set[ucRtc_Index] = 0;//直接进行卡死 不再减了
}
if(Seg_Index == 2)//处于闹钟设置界面
{
Alarm[ucRtc_Index]--;
//因为是16进制 所以0x10-1会变成0x0f 此时就需要将0x0f进行变化变成0x09
if(Alarm[ucRtc_Index] % 16 == 0x0f)
Alarm[ucRtc_Index] -= 6;
//进行界限判断 界限-1变成0xff因为后面又-6 因此变成0xf9
if(Alarm[ucRtc_Index] == 0xf9)
Alarm[ucRtc_Index] = 0;//直接进行卡死 不再减了
}
}
break;
case 5://加按键
if(Seg_Mode_Disp == 1)//处于时钟设置界面
{
ucRtc_Set[ucRtc_Set_Index]++;
//因为是16进制 所以0x09+1会变成0x0a 此时就需要将0x0a进行变化变成0x10
if(ucRtc_Set[ucRtc_Set_Index]%16 == 0x0a)
ucRtc_Set[ucRtc_Set_Index] += 6;
if(ucRtc_Set[ucRtc_Set_Index] == (ucRtc_Set_Index?0x60:0x24))
ucRtc_Set[ucRtc_Set_Index] = (ucRtc_Set_Index?0x59:0x23);
}
if(Seg_Mode_Disp == 2)//处于闹钟设置界面
{
Alarm_Disp[ucRtc_Set_Index]++;
//因为是16进制 所以0x09+1会变成0x0a 此时就需要将0x0a进行变化变成0x10
if(Alarm_Disp[ucRtc_Set_Index]%16 == 0x0a)
Alarm_Disp[ucRtc_Set_Index] += 6;
if(Alarm_Disp[ucRtc_Set_Index] == (ucRtc_Set_Index?0x60:0x24))
Alarm_Disp[ucRtc_Set_Index] = (ucRtc_Set_Index?0x59:0x23);
}
break;
Led_Display[0] = (unsigned char)Temperature / Temperature_Set[0];//当温度大于设置最大温度 LED1点亮
Led_Display[1] = (!((unsigned char)Temperature / Temperature_Set[0]))&((unsigned char)Temperature / Temperature_Set[1]);//当温度大于设置最大温度 LED1点亮
Led_Display[2] = !((unsigned char)Temperature / Temperature_Set[1]);//当温度小于设置最大温度 LED3点亮
高级方法(互斥点亮)
for(i=0;i<3;i++)//模式指示灯
ucLed[1+i] = (i == Seg_Disp_Mode);
1.首先每次先定义两个变量
unsigned char Led_PWM;//Led的PWM变量
unsigned char Led_level;//Led等级变量
2.中断中进行定时与判断
if(++Led_PWM == 12) Led_PWM = 0;
if(Led_PWM < Led_level)
Led_Disp(Seg_Pos,Led_Display[Seg_Pos]);
else
Led_Disp(Seg_Pos,0);
3.对KED_level进行分级赋值,从而实现PWM效果
if(Temperature > Temperature_Set[0])
Led_level = 4;
else if(Temperature < Temperature_Set[0] && Temperature > Temperature_Set[1])
Led_level = 8;
else
Led_level = 12;
void Led_Proc()
{
if(Voltage < Voltage_Parameter_Ctrol)
{
if(Sys_Tick >= 5000) //:当实际电压小于电压设置参数状态持续超过五秒后,L1 点亮,否则熄灭
Led_Display[0] = 1;
else
Led_Display[0] = 0;
}
else
{
Sys_Tick = Led_Display[0] = 0;
}
Led_Display[1] = Count % 2;//计数值为奇数时,L2 点亮
Led_Display[2] = Key_Error_Count / 3;//连续触发三次无效按键以上L3点亮
}
if(++Timer_100Ms == 100)
{
Timer_100Ms = 0;
Led_Star_Flag ^= 1;
}
if(Relay_Flag == 1)
Led_Display[2] = Led_Star_Flag;
else
Led_Display[2] = 0;
switch(Led_Mode)
{
case 0://模式1-从L1到L8
if(++Led_Pos == 8)
{
Led_Pos = 7;//模式2起始值
Led_Mode = 1;//切换到模式2
}
break;
case 1://模式2-从L8到L1
if(--Led_Pos == 255)
{
Led_Pos = 07;//模式3起始值
Led_Mode = 2;//切换到模式3
}
break;
case 2://模式3-07 16 25 34
Led_Pos += 9;
if(Led_Pos > 34)
{
Led_Pos = 34;//模式4起始值
Led_Mode = 3;//切换到模式4
}
break;
case 3://模式4-34 25 16 07
Led_Pos -= 9;
if(Led_Pos > 200)
{
Led_Pos = 0;//模式1起始值
Led_Mode = 0;//切换到模式1
}
break;
}
}
}
LED点亮
if(Led_Mode <= 1) //系统处于前两种流转模式时
{
for(i=0;i<8;i++)
ucLed[i] = (i == Led_Pos);
}
else //系统处于后两种流转模式时
{
for(i=0;i<8;i++)
ucLed[i] = ((i == (Led_Pos / 10)) || (i == (Led_Pos % 10))); //点亮十位跟个位所对应的Led
}
1.定义两个时间变量
unsigned long int Ms_Tick;//滴答计时器
unsigned long int Ms_Tick_Relay;//LED滴答计时器
2.核心代码
if(ucRtc[1] == 0x00 && ucRtc[2] == 0x00)
{
Ms_Tick_Relay = Ms_Tick;
Relay_Flag = 1;
Led_Display[0] = 1;
}
if(Ms_Tick - Ms_Tick_Relay == 5000)
{
Relay_Flag = 0;
Led_Display[0] = 0;
}
3.中断函数代码
Ms_Tick++;//中断中需要额外补充
if(Vin3 < Vp && ms_tick_point == 0)
ms_tick_point = ms_tick;
if(Vin3 < Vp && (ms_tick - ms_tick_point) >= 5000)
Led_Disp(1,1);//点亮L1
if(Vin3 > Vp)
{
ms_tick_point = 0;
Led_Disp(1,0);
}
case 0://频率显示界面
Seg_Display[0] = 11;//-F
Seg_Display[1] = Seg_Display[2] = 10;//熄灭
Seg_Display[3] = Freq/10000%10;
Seg_Display[4] = Freq/1000%10;
Seg_Display[5] = Freq/100%10;
Seg_Display[6] = Freq/10%10;
Seg_Display[7] = Freq%10;
Seg_Point[5] = 0;
while(Seg_Display[i] == 0)//高位熄灭判断
{
Seg_Display[i] = 10;//熄灭
if(i++ == 6)
break;
}
break;
case 0://频率显示界面
Seg_Display[0] = 11;//-F
Seg_Display[1] = Seg_Display[2] = 10;//熄灭
Seg_Display[3] = Freq/10000%10;
Seg_Display[4] = Freq/1000%10;
Seg_Display[5] = Freq/100%10;
Seg_Display[6] = Freq/10%10;
Seg_Display[7] = Freq%10;
Seg_Point[5] = 0;
while(Seg_Display[i] == 0)//高位熄灭判断
{
Seg_Display[i] = 10;//熄灭
if(i++ == 6)
break;
}
break;
onewire-----温度模块(DS18B20)
iic-------------EEPROM(AT24C02) PCF8591(AD/DA)
DS1302------时钟模块(DS1302)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。