板子为国信长天CT107D,视频资料为小蜜蜂,这里将记录学习的一些笔记和心得。
一.基础知识篇
1.绪论
内核资源分布浓缩图:
2.LED指示灯的基本控制
一些常用的逻辑门
按位右移和按位左移:
注意这里是整体移动,末尾补零,比如以下这个向左移一位(<<)
假设是左移两位的话,也是类似的
然后按位右移也是类似的,要求整体进行移动
假如是右移四位的话
点亮LED可以通过移位操作,逐个点亮,也可以直接将P0口赋值,一键点亮(点亮多少都可以)
这里点亮led需要通过138译码器进行操作,138译码器一般是选中哪位,哪位就不亮
3.继电器与蜂鸣器的基本控制
P0^7默认和其他引脚一样,只要关注06和04即可
达林顿管是一个非门(ULN2003)
通过实现Y5C给LE使能,然后给高电平信号,通过达林顿管实现非门运算,就可以实现继电器和蜂鸣器的控制。
switch语句的用法->可以将138译码器整合成一个函数,然后直接控制即可,提高代码简洁性
swich(表达式)
{
case 1:
语句1;
break;
case 2:
语句2;
break;
.......
default:
break;
}
然后补充一个小小的知识点:
unsigned char ->0~255
如果需要存储范围较小的整数数据,且占用空间较小,则可以选择 unsigned char。
unsigned int ->0~65535
如果需要存储较大范围的整数数据,则应选择 unsigned int。
HC138译码器定义的另一种方法
这种定义的含义就是在一开始P2处在一个全为高电平的状态,一共8位,分别是2^0~2^7,用数字信号表示就是1111 1111,然后先与一个0x1f,0x1f数字信号就是0001 1111,秉持着与门有零为零,先将后三位置零,就是2^5~2^7置零,然后再对5~7进行操作,后面并就是对后三位进行操作,也是从高位到低位这样,比如0x80,即1000 0000,就是让2^7=1,2^6=0,2^5=0;得到y4c为低电平的状态。
void InitHC138(unsigned char n)
{
switch(n)
{
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;
}
}
4.共阳数码管的静态显示
这里单片机的数码管是共阳极的,就是说要给一个低电平才能触发,比如要点亮数字1,在图中可以知道数字1的段码是b,c,这时候就要给到b,c低电平,其他给到高电平,也是按照从高位到低位,即dp~a,数字信号表示为1111 1001,转化为16进制就是0xf9,其他的转化也是类似的。
然后这里选中哪一个数字是由com端控制,com端是给定高电平就选中,同时受到y6c的控制
数码管在显示之后一定要延迟!!!
5.共阳数码管的动态显示
和静态的不同,这里利用的是数码管的余晖效应,也就是人眼的视觉残留效果,因为本身是用一个p0口控制全部,同时点亮的话就会只能生成一个数字,然后如果是分别点亮,每一位亮1~2ms,就能实现数码管显示不同的数字。
月份更新的时候要延时显示,这样能够在更新时候让显示类容停留片刻,防止月份更新之后前面的熄灭,一般这里用的是STC的延时函数,然后delay(2)是比较合适的。
主函数:
void main(){
while(1){
display();
month++;
if(month>12){
month=1;
}
Delay_smg(10);
}
}
延时函数:
void Delay_smg(unsigned char t)
{
while(t--)
{
display(); //数码管显示
}
}
6.独立按键的基本操作
这里先用sbit定义引脚,然后判断是否为0,为0就是按下(因为是和GND相连),这里要进行消抖,因为不消抖无法判断是否已经按下,具体操作就是就是按下之后(判断第一次是否为0),然后延时一段时间,再判断第二次是否为0,然后就可以进行其他操作,这里如果是和数码管挂钩的话,那么每次按下的状态瞬间,都要去重新显示一下数码管,不然会一闪一闪。
这里还学习了状态机的设计,就是定义一个变量,给定这个变量一个数值,然后每改变一种状态就让数值变化一次,通过数值判断进行操作。
这里实现的流程主要是定义好引脚,比如不同的按键对应不同的引脚,因为有一端是接地的,所有如果按键按下,电路就会导通,定义的方法如下:
sbit S7=P3^0;
sbit S6=P3^1;
sbit S5=P3^2;
sbit S4=P3^3;
7.矩阵键盘的基本操作
记得要改变跳帽的接线
因为接的是两个io端口,一个io端口输出0,按键按下,接通,另一个io端口也是输出0,逐行逐行扫描,逐列读取状态。
这里是通过先定义一行为0,其余行为1,然后判断一列一列是否为0,为0即按下,那么就可以进行其他操作
示例代码(这里还加入了其他功能,参考性比较低,但是实现的大体流程就是如下):
unsigned char sta_key=0;
void scankeys(){
if(S7==0){
delay(5);
if(S7==0){
if(sta_key==0){
L1=0;
sta_key=1;
}
else if(sta_key==1){
L1=1;
sta_key=0;
}
}
while(S7==0);
}
if(S6==0){
delay(5);
if(S6==0){
if(sta_key==0){
L2=0;
sta_key=2;
}
else if(sta_key==2){
L2=1;
sta_key=0;
}
while(S6==0);
}
}
if(S5==0){
delay(5);
if(S5==0){
if(sta_key==1){
L3=0;
while(S5==0);
L3=1;
}else{
L5=0;
while(S5==0);
L5=1;
}
}
}
if(S4==0){
delay(5);
if(S4==0){
if(sta_key==1){
L4=0;
while(S4==0);
L4=1;
}else{
L6=0;
while(S4==0);
L6=1;
}
}
}
}
这里存在一个问题,就是一样的代码,这里为什么不能实现动态显示
8.中断系统与外部中断应用
这里可以参考手册439——中断结构图,去配置中断,中断号要记住
中断执行流程,简单来说,就是跳开当前执行的程序,去执行其他程序,执行完其他程序,在执行回原来的程序
中断系统结构图
可以参考 单片机—外部中断与定时器 学习笔记_6318h用0x怎么表示-CSDN博客
这里使用中断时,需要将J5打到独立按键处,因为这里是共用端口
按照自然优先级从高到低排序
这张图是理解中断的关键,首先是中断源接受到中断请求,在TCON(串口为SCON)发出中断请求,中断标志位置1,发生中断,若置零,则不发生中断,然后在IE打开中断系统,IE可以理解为门这样子,有一个小门和一个大门,把门打开,才能中断。
在TCON中,比如拿外部中断0(INT0)举例,IT0=1为下降沿触发,即由高电平变为低电平时触发,然后IT0=0时就是高电平触发,这里通过IT0门,到IE0,IE0置1,中断打开,到IE,IE中两个门都置1,允许中断,IP用的比较少。
以下这张图解释的更为清晰,这里IP是改变优先级的意思,这里用的比较少
还需要记住中断号(最左边的)
这里写中断的函数记得是中断初始化函数+中断服务函数(中断后要干什么在这里写,记得加上中断号,否则系统不知道是中断函数) ,中断初始化函数要在while(1)之前调用一下。
9.定时器的基本原理与应用
tmod中,高4位控制t1,低四位控制t0
这里没有自动重装,每次都需要自己重新赋予初值
这里做了第一个案例:秒表
这里让定时器运行和之前的中断是差不多的,这里多了TMOD,以及TR(timer run,控制定时器运行和停止)通过利用16位加法计数器,每满65536(2^16)溢出作为一次定时周期,就是说最多只能计数65.5ms,就比如你想要计数0.5s,可以设定一个变量,并且赋值为0,0.5s就是500ms,可以让定时周期设置为50ms,然后计时10次,这样就能实现0.5s的计数,其他计数也是类似的。
函数体也是类似的,中断初始化函数+中断服务函数
void InitTimer0()
{
TMOD = 0x01; //模式选择-16位
TH0 = (65535 - 50000) / 256;
TL0 = (65535 - 50000) % 256; //计数50ms
ET0 = 1;
EA = 1; //打开门
TR0 = 1; //打开定时/计数器
}
10.PWM脉宽调制
利用高电平在一个周期内所占时间进行调制,LED灯是低电平亮,所以占空比越高,灯越暗(占空比越高,低电平所占比例越小)
11.串口通讯
这里我的目的是只要能够实现收发就可以了,复杂的就不做了。
其实总体在手册上都有,主要是配置tmod,tcon,这里比定时器还多了个scon
配置参照
这里大部分已经给出,需要修改的是:
记住这里是用的定时器1
AUXR=0x00
TH1 = 0xfd;
TL1 = 0xfd;
初始化函数
参考手册627,还有个smod,就是对TH和TL配置,参考11.0594,两个都要配置成0xfd
中断服务函数也是参照stc,这里中断我只要收字节,所以把下面的(TI)删了,设置一个变量存储SBUF的值(这是串口收到的值)
然后接下来写个发的函数,直接发就可以了,记得发16位的(0x kk之类的)
void SendByte(unsigned char dat)
{
SBUF = dat;
while(TI == 0);
TI = 0;
}
void main()
{
InitUart();
SendByte(0x5a);
SendByte(0xa5);
while(1);
}
一些具体的外设:
SCON
通用异步8位UART:0x50
12.IO拓展技术与存储器映射拓展(了解)
存储器映射拓展
可以直接通过地址去访问和实现功能,不用通过hc138和或非门的选择,可以起到节省代码的作用。当然跳帽记得从IO转到MM,但是一般都是IO拓展方式,存储器拓展如果有矩阵键盘,就会有冲突,因为占用了P3^6的io口
13.实训1
P0口复用,拿一个按键举例,并与并,这里主要是防止相互影响
if(S4==0){
delay(2);
if(S4==0){
while(S4==0){
show_time();
}
hc138(4); //这三句一定要这样写
static_led=(static_led|0x80)&(~static_led | 0x7f); //先将操作位设为高电平,然后与上取反的结果,或0不变,与1不变。
P0=static_led;
}
}
这里有个新思路(防止冲突)对于P0口 ,就是说我可以再定义一个专门给led显示的函数,类似hc138的,防止数码管和led冲突,每次用之前,关hc138,赋完值之后,再打开hc138,然后再关闭
void Set_HC573(unsigned char channel, unsigned char dat)
{
P2 = (P2 & 0x1f) | 0x00; //赋值之前,关闭所有锁存器,防止冲突
P0 = dat; //设置待赋值数据
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 = (P2 & 0x1f) | 0x00; //所有锁存器不选择
break;
}
P2 = (P2 & 0x1f) | 0x00; //赋值完成后,关闭所有锁存器
}
unsigned char stat_led = 0xff; //定义LED灯当前状态
void LED_Control()
{
stat_led &= ~0x80;
Set_HC573(4, stat_led); //L8灯点亮
Delay(200); //延时
stat_led |= 0x80;
Set_HC573(4, stat_led); //L8灯熄灭
Delay(200);
14.模块化编程
特别要注意,要声明才能使用函数、变量,比如在下方的.c文件引入头文件,直接从source group里面新建.h和.c文件就可以了,注意要编译
以下参考:蓝桥杯单片机组——榨干选手资源包(芯片数据手册)_蓝桥杯单片机定时器中断芯片资料手册-CSDN博客
15.pcf8591(模数转换器)
ad:模拟转数字;
da:数字转模拟
控制字为0x41就是读取的光敏电阻
控制字读取0x43为滑动变阻器
参考芯片手册第五页(了解怎么读写地址):
先确定器件地址,图中A2,A1,A0都是默认置零,如果最低位是0,就是write,如果是1,就是read
功能选择相关:
这里如果是选择通道1,即0x01,就是光敏电阻,如果是0x03,就是滑动变阻器,但是,如果想两者都显示的话,0x03就变成了光敏电阻,0x01就变成了滑动变阻器
然后写相关代码也是可以根据表去写,比如我想写ad是,模数转换,就根据说明书第6页(这里标注有ad),注意sendbyte和sendack是不同的,就是因为这个原因导致显示一直为255不变。
具体代码:
接收到信号,不用等待,直接发送指令说不用了,然后返回的结果*1.96,因为数字输出是255,要转为电压输出,就是500,255*1.96=500,也可以在显示函数里面乘,但是这里要注意要进行强制类型转化,因为最后得到的数是无限不循环小数,要把小数部分舍去,如第二个代码图,但是记得接收的变量也必须是整形的,可以用unsigned int类型
unsigned char pcf8591_ad(unsigned char addr){
unsigned char temp;
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0x91);
I2CWaitAck();
temp=I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return temp;
}
//强制类型转换,由255变成500,再输出出来
unsigned int receive_ad;
void show_ad(){
receive_ad=(int)(pcf_adread()*1.96);
showsmg_pot(0,receive_ad/100);
delay(2);
showsmg_nopot(1,receive_ad/10%10);
delay(2);
showsmg_nopot(2,receive_ad%100%10);
delay(2);
}
da,也是类似的,但是配置功能(使能转换)那一步需要为4,0x41,0x43都可以,可以记作发三次字节,最后一次发自己要的,dat是自己输入的数字量,范围是0~255,这里通过*51实现转换,因为电压输出是5v,转化成数字信号输出必需转化成255,5*51=255
还有个stop
16.ds18b20(温度传感器)
比如读到23.7,想逐位显示到数码管上,就可以先/10,得到23,再%10,得到3,以此类推
可以查看关于ds18b20的数据手册(18页):
这里可以参考第一个,第一个是对多个ds18b20进行操作,第二个是对单个ds18b20进行操作,但是官方添加了很多的寄存器,所以参考第一个表格,但是第一个表格也要做一些更改,这里是要跳过ROM操作,所以具体如下:
这里是不需要用到ROM操作,所以在以上标注出来的presence和55h还有64-bit ROM code是直接跳过的,参考第二个表格(skip rom):
然后我们就可以根据第一个去写自己的代码:
/**
* @brief 以带小数点的形式读取温度
* @param None
* @retval temperature - float
*
*/
float rd_temperature_f(void)
{
unsigned char low,high;
init_ds18b20();
Write_DS18B20(0xCC); //跳过ROM
Write_DS18B20(0x44); //开始温度转换
Delay_OneWire(200);
init_ds18b20();
Write_DS18B20(0xCC); //跳过ROM
Write_DS18B20(0xBE); //读取温度寄存器
low = Read_DS18B20();
high = Read_DS18B20();
return ((high<<8)|low)/16.0;
}
这里读取ds18b20,首先读到低八位,然后再读取高八位,先用变量存储低八位,再存储高八位,然后需要将他们合并在一起,通过“并”的操作。
除了以上步骤外还要修改onewire.c的延时函数,由于单总线协议没有时钟线,因此必须有准确的时间要求,而官方提供的是普通51单片机的代码,与IAP的时钟不一致,可以理解为IAP15的时钟比51单片机的时钟快12倍,也就是说源代码中的1us实际上在IAP15上运行只有1/12us,所以需要乘12倍。
/**
* @brief 更改为之前12倍的延时
* @param None
* @retval t - 代表延时的时长 单位:us
* @author
*/
void Delay_OneWire(unsigned int t) //STC89C52RC ->IAP15
{
unsigned char i;
while(t--)
{
for(i=0;i<12;i++);
}
}
17.ds1302
注意看给的原始代码有没有问题,参考芯片图,这里IO是SDA,注意头文件要引入
#include <intrins.h>还有芯片的头文件
这里io是sda(data),ce是rst,sck不变
这里是采用bcd码(4位二进制数表示一个十进制数,转换时通过/16或者%16)来显示(就是在读取的时候要通过这个转换才能得到十进制的数,比如我读取到0x59,通过/16得到5,%16得到9),比如你想设定初始时间,就要先写入ds18b20,然后再读出ds18b20,比如想写入20年 4月18日 周六 23:59:55 那么可以设一个数组(这里采取由低位到高位排序):time[ ]={0x55,0x59,0x23,0x18,0x04,0x06,0x20},注意这里日期是夹在月和年中间。
这里8e,也就是倒数第二个,是保护位,如果写1,打开保护,写0,关闭保护。
在写入数据之前,打开保护位(0x00),在写完数据之后,关闭保护位(0x80)
注意在这里读数据时,高八位通过/16转换出来,低八位通过%16转换出来(这里是转换十进制)
这里函数是比较容易实现的,参考相关数据手册第9页即可,注意写保护的打开和关闭
//读取和设定初值
void setclock(){
Write_Ds1302_Byte(0x8e,0x00);
Write_Ds1302_Byte(0x84,0x29);
Write_Ds1302_Byte(0x82,0x55);
Write_Ds1302_Byte(0x80,0x55);
Write_Ds1302_Byte(0x8e,0x80);
}
void getclock(){
time[2]=Read_Ds1302_Byte(0x85);
time[1]=Read_Ds1302_Byte(0x83);
time[0]=Read_Ds1302_Byte(0x81);
}
//显示:
void showclock(){
getclock();
show_smg(0,time[2]/16,1);
delay(2);
show_smg(1,time[2]%16,1);
delay(2);
show_smg(2,17,1);
delay(2);
show_smg(3,time[1]/16,1);
delay(2);
show_smg(4,time[1]%16,1);
delay(2);
show_smg(5,17,1);
delay(2);
show_smg(6,time[0]/16,1);
delay(2);
show_smg(7,time[0]%16,1);
delay(2);}
//主函数:
void main(){
setclock();
while(1){
showclock();
}
}
18.at24c02
这是也是用的单总线,iic,注意和pcf区分开,这个主要是起到保存数据的作用
这里要注意写/读地址的地址位(参考相应数据手册11页):
解释一下:R/W,这里如果是1,选中R,反之如果是0,选中W
在向AT24C02写入数据时,从发出命令到写完成,这个中间大致需要花费10ms的时间,如果再次期间重复发送就会导致数据错误,因此在官方IIC.c代码的基础上首先需要添加一个10ms延时函数,保证数据传输的稳定性。
AT24C02写入流程:
(1)开启总线;(2)发送器件地址;(3)等待器件应答;
(4)发送要写入数据的地址;(5)等待应答;(6)发送写入的数据;
(7)等待应答;(8)关闭总线;(9)必要的延时;
一开二验三应答;
四地五答六数据;
七答八关九延时。
写入数据代码如下:
先写地址再写数据,记得延时,先写位置再写地址
参考,只需要记住start,device a ,ack,wode a,ack,data,ack,stop
void Write_Eeprom(unsigned char add,unsigned char val)
{
i2c_start();
i2c_sendbyte(0xa0);//写
i2c_waitack();
i2c_sendbyte(add);
i2c_waitack();
i2c_sendbyte(val);
i2c_waitack();
i2c_stop();
Delay10ms();
}
AT24C02读取流程:
读数据流程大致与写入相同,需要先写入要读取的数据地址add,然后写入读取的命令,最后按位接收数据代码如下,顺序记不住可以参考芯片手册(pcf8091的,有个bite write和Sequential Read,分别是字节写入和顺序读取),这里注意是要再写一次,每次读完,都要说不要读了,不用再等待,这里和pcf8091类似,也是要开始两次:
参考:
unsigned char Read_Eeprom(unsigned char add)
{
unsigned char da;
i2c_start();
i2c_sendbyte(0xa0);
i2c_waitack();
i2c_sendbyte(add);
i2c_waitack();
i2c_start();
i2c_sendbyte(0xa1);
i2c_waitack();
da = i2c_receivebyte();
i2c_sendack(1);
i2c_stop();
return da;
}
然后在自己需要的位置写代码:
write_eeprom(1,(led_set_cnt[1]/100)); //向AT24C02地址0x00中写入数据
delay();//延时10ms
write_eeprom(2,(led_set_cnt[2]/100));
delay();
write_eeprom(3,(led_set_cnt[3]/100));
delay();
write_eeprom(4,(led_set_cnt[4]/100));
19.555定时器 (测频率用)
注意这里读取数据的时候,是要按照以下格式写,可以认为是每次都取出最低位(先除去高位)
方法一
if(get_mai){
if(get_mai>9999){
show_smg(4,get_mai/10000);
delay(2);
}
else if(get_mai>999){
show_smg(4,get_mai/1000%10);
delay(2);}
else if(get_mai>99){
show_smg(4,get_mai/100%10);
delay(2);}
else if(get_mai>9){
show_smg(5,get_mai/10%10);
delay(2);
方法二;
buff[3]=~t_display[num/10000];
buff[4]=~t_display[num/1000%10];
buff[5]=~t_display[num/100%10];
buff[6]=~t_display[num/10%10];
buff[7]=~t_display[num%10];
这里使用到P3^4引脚,和定时器0冲突,这里是通过两个定时器来实现的,一个是定时器0,一个是定时器1,这里是通过计时1s,输出计了多少数,tmod是先高位配置定时器1,低位配置定时器0,定时器0使用8位重装载并且用作计数器
计数器计算的是有多少脉冲,直接用变量赋值就可以了,然后定时器定时到1s,也就是20个50ms,读取变量的值,就实现了频率的测量。
最终是要显示dat_f,直接显示就可以了,一般是10000+,所以从10000+操作(也就是类似/10000这样子)
20.超声波模块(测量距离用)
记住两个脚N_a1和N_b1
a1是输入端,b1是输出端
实现:
1.初始化函数(超声波发射函数),发射8个方波信号,意思就是占空比为50%,一个周期内高电平占一半,低电平占一半
超声波 读取函数(通过定时器1实现),这里tmod的配置参考stc-isp,是与0x0f,16为自动重装载
注意tx和rx
然后直接用变量读取就可以了
void show_dis(){
wave_data=wave_read();
show_smgea(0,18);
delay(2);
show_smgea(6,wave_data/10%10);
delay(2);
show_smgea(7,wave_data%10);
delay(2);
}
二.实训
1.模拟一
实现:将输入数据(高字节)存入E2PROM内部地址2; 将输入数据(低字节)存入E2PROM内部地址3;
write_eeprom(0x02,num_value >> 8);
Delay5ms();
write_eeprom(0x03,num_value & 0x00ff);
Delay5ms();
功能没有完全实现,实现了一部分:
main函数
按键不知道为什么切换不了
#include <STC15F2K60S2.H>
#include "ds1302.h"
#include "iic.h"
//https://blog.csdn.net/2301_80600743/article/details/135716602
//https://blog.csdn.net/m0_60524373/article/details/123340419?ops_request_misc=&request_id=&biz_id=102&utm_term=at24c02%E5%AD%98%E9%AB%98%E4%BD%8D&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-123340419.142^v100^pc_search_result_base8&spm=1018.2226.3001.4187
sbit R1=P3^0;
sbit R2=P3^1;
sbit R3=P3^2;
sbit R4=P3^3;
sbit L1=P4^4;
sbit L2=P4^2;
sbit L3=P3^5;
sbit L4=P3^4;
void show_clock();
void show_recond();
void show_input();
unsigned char sta_count=0;
unsigned char time[7];
unsigned char input_num=0;
unsigned char count_time=0;
unsigned char state[4];
unsigned char keynum;
unsigned char i;
void hc138(unsigned char channal){
switch(channal){
case 0:
P2=(0x1f&P2)|0x00;
break;
case 4:
P2=(0x1f&P2)|0x80;
break;
case 5:
P2=(0x1f&P2)|0xa0;
break;
case 6:
P2=(0x1f&P2)|0xc0;
break;
case 7:
P2=(0x1f&P2)|0xe0;
break;
}
}
void delay(unsigned char countime) //@12.000MHz
{
while(countime--){
unsigned char i, j;
i = 12;
j = 169;
do
{
while (--j);
} while (--i);}
}
unsigned char SMG[]={
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0x88, //A
0x83, //b
0xc6, //C
0xa1, //d
0x86, //E
0x8e, //F
0x7f, //.
0xbf
};
void scankeys(){
//ÅжϵڶþÐÐ
R2=0;
R1=R3=R4=1;
L1=L2=L3=L4=1;
if(L1==0&&sta_count==1){
delay(2);
if(L1==0){
if(count_time<4){
state[count_time]=0;
count_time++;
}
}
}
else if(L2==0&&sta_count==1){
delay(2);
if(L2==0){
if(count_time<4){
state[count_time]=1;
count_time++;
}
}
}
else if(L3==0&&sta_count==1){
delay(2);
if(L3==0){
if(count_time<4){
state[count_time]=2;
count_time++;
}
}
}
else if(L4==0&&sta_count==1){
delay(2);
if(L4==0){
if(count_time<4){
state[count_time]=3;
count_time++;
}
}
}
//ÅжϵÚÈýÐÐ
R3=0;
R1=R2=R4=1;
L1=L2=L3=L4=1;
if(L1==0&&sta_count==1){
delay(2);
if(L1==0){
while(L1==0);
for(i=0;i<4;i++){
state[i]=0;
}
}
}
else if(L2==0&&sta_count==1){
delay(2);
if(L2==0){
if(count_time<4){
state[count_time]=4;
count_time++;
}
}
}
else if(L3==0&&sta_count==1){
delay(2);
if(L3==0){
if(count_time<4){
state[count_time]=5;
count_time++;
}
}
}
else if(L4==0&&sta_count==1){
delay(2);
if(L4==0){
if(count_time<4){
state[count_time]=6;
count_time++;
}
}
}
//ÅжϵÚËÄÐÐ
R4=0;
R1=R3=R2=1;
L1=L2=L3=L4=1;
if(L1==0&&sta_count==1){
delay(2);
if(L1==0){
while(L1==0);
if(sta_count==0){
sta_count=1;
}
else if(sta_count==1){
sta_count=0;
}
}
}
else if(L2==0&&sta_count==1){
delay(2);
if(L2==0){
if(count_time<4){
state[count_time]=7;
count_time++;
}
}
}
else if(L3==0&&sta_count==1){
delay(2);
if(L3==0){
if(count_time<4){
state[count_time]=8;
count_time++;
}
}
}
else if(L4==0&&sta_count==1){
delay(2);
if(L4==0){
if(count_time<4){
state[count_time]=9;
count_time++;
}
}
}
}
//ÊýÂë¹ÜÏÔʾ
void show_smg(unsigned char pos,unsigned char channal){
hc138(6);
P0=0x01<<pos;
hc138(7);
P0=SMG[channal];
}
//ģʽ¶þ
//ÊäÈëÊý×Ö
void set_clock(){
//´ò¿ª±£»¤
Write_Ds1302_Byte(0x8e,0x00);
Write_Ds1302_Byte(0x80,0x59);
delay(5);
Write_Ds1302_Byte(0x82,0x09);
delay(5);
Write_Ds1302_Byte(0x84,0x23);
delay(5);
Write_Ds1302_Byte(0x86,0x01);
delay(5);
Write_Ds1302_Byte(0x88,0x02);
delay(5);
Write_Ds1302_Byte(0x8a,0x06);
delay(5);
Write_Ds1302_Byte(0x8c,0x24);
delay(5);
//¹Ø±Õ±£»¤
Write_Ds1302_Byte(0x8e,0x80);
}
unsigned char de1302[]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
void get_time(){
char t;
for(t=0;t<7;t++){
time[t]=Read_Ds1302_Byte(de1302[t]);
}
}
void show_screen(){
switch(sta_count){
case 0:
show_smg(0,time[2]/16);
delay(2);
show_smg(1,time[2]%16);
delay(2);
show_smg(2,17);
delay(2);
show_smg(3,time[1]/16);
delay(2);
show_smg(4,time[1]%16);
delay(2);
show_smg(5,17);
delay(2);
show_smg(6,time[0]/16);
delay(2);
show_smg(7,time[0]%16);
delay(2);
break;
case 1:
show_smg(0,12);
delay(2);
if(count_time==0)
{
show_smg(4,state[0]);
delay(2);
show_smg(5,state[1]);
delay(2);
show_smg(6,state[2]);
delay(2);
show_smg(7,state[3]);
delay(2);
}
if(count_time==1)
{
show_smg(7,state[0]);
delay(2);
}
else if(count_time==2){
show_smg(6,state[0]);
delay(2);
show_smg(7,state[1]);
delay(2);
}
else if(count_time==3)
{
show_smg(5,state[0]);
delay(2);
show_smg(6,state[1]);
delay(2);
show_smg(7,state[2]);
delay(2);
}
else if(count_time==4){
show_smg(4,state[0]);
delay(2);
show_smg(5,state[1]);
delay(2);
show_smg(6,state[2]);
delay(2);
show_smg(7,state[3]);
delay(2);
}
break;
}
}
void init_led(){
hc138(4);
P0=0xff;
}
void main(){
set_clock();
init_led();
while(1){
get_time();
scankeys();
show_screen();
}
}
2.第十届
这里考察了pcf8591
注意要启动两次
用iic
实现流程:
总线开始
写 写的地址
等待响应
写入地址
等待响应
总线开始()
写 读的地址
等待响应
用变量接收读的结果
发送1(用sendack而不是sendbyte,前面用的是sendbyte)
总线停止
返回变量
这里555定时器不知道为什么运行不了,但是大概思路大差不差,在中断初始函数,直接定义定时器1和定时器0,因为我要定时器0做为计数器,定时器1作为定时器,定时器0是接收脉冲信号的,所以tl0和th0都要设置成0xff(255),这里是看定时1s收到多少个脉冲。这里tmod是0x16(定时器1是定时器正常设置成0x01,定时器0是计数器,选中计数器c(1),8位自动重载10,所以tmod是要设置成0x16),设置完之后,要把全部门都打开
da和上面不一样,三个发送,发送写的地址,发送0x41(这里一定要有4,表示选的是da,也就是转为模拟信号),发送数字信号,如果想和ad联动,可以将ad得到的值作为参数放入da中
点亮led,可以在hc138(channal,dat),然后p0=dat,sta-led(设一个初始状态)=0xff,应该每次保留,可以实现不冲突,这里也是要收到状态机的指令再操作,然后通过|和&的操作进行赋值。
还有状态机的设置,非常好用,可以每个按键都设一个状态机(这是重点)
注意if语句和if外面的语句,等于号是一个还是两个
3.第十一届
这里主要用的是状态机来写的,这里有一个新的思路,就是引入定时器,可以参考stc-isp:
这里要自己打开门,比如et1,ea等等,通过1ms的定时器,不断去扫描状态
这里按键去抖动也可以参考(效果相对来说还是比较好的):
但是有个重影问题没解决
R4=0;
R3=1;
C3=C4=1;
if(C3==0){
delay(2);
if(C3==0){
if(mode==0){
mode=1;
}
else if(mode==1){
mode=2;
}
else if(mode==2){
mode=0;
}
}}
4.第十二届
那个ds18b20卡了很久,最后发现原因是DQ写错了,本来是1.4,赋值成了1.2。
注意有一点非常重要,最后精度换算的时候,要除以16.0,而不是16,除以16得不到结果
还有变量的赋值也要仔细,因为我温度得到的是类似27.5这样的,所以函数要写为浮点类型(float),然后取值的变量也要写成float类型,读取的时候要进行强制类型转换,(unsigned
char),不然也读不出来:
完整代码(一点问题也没有)
//温度获取
float get_temper(){
unsigned char low,high;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
low=Read_DS18B20();
high=Read_DS18B20();
return ((high<<8)|low)/16.0;
}
//读温度并且显示出来
float temp;
void show(){
temp=get_temper();
show_smg(0,(unsigned char)temp/10);
delay(2);
show_smg(1,(unsigned char)temp%10);
delay(2);
show_smg(2,(unsigned char)(temp*10)%10);
delay(2);
}
上面的显示函数,比如我想将27.5显示出来,/10就是得到2,%10就是得到7,然后乘以10再%10就是得到5,不要乘以10再转换,这样精度会大幅度下降。
这里晶振最好改成12,变得没这么快:
5.第十四届
按键功能多的时候,可以将按键设为一个函数,类似:
总体的做题思路:
1.模板写好:
138锁存器
数码管点亮(位选,段选)
led初始状态(0xff),用与和并进行操作,熄灭,就并上含1的,点亮,就与上含1的取反
数码管编码
main函数
初始化函数
2.外设功能写好
3.给led灯赋予一个初始状态(0xff),直接对位进行操作
毕