蓝桥杯 Bilibili UP 机械狗学习笔记

Up 个人空间:https://space.bilibili.com/342764709

138 译码器的使用方法

使用位操作

清零操作:将 P2 口的高 3 位分别和 0 相与,使得不管高 3 位以前为何值,执行操作后一定变为 0 此时高三位均为 0 ,选通 Y0,不影响 Y4、Y5、Y6、Y7 通道

1
2
P2 = P2 & 1F;  //与操作的值可使用 Windows10 系统计算器科学模式计算
P2 = P2 0xA0; //选通 Y5 通道,放行 P0 操作

上述代码也等价于:

1
2
P2 =& 1F;  //与操作的值可使用 Windows10 系统计算器科学模式计算
P2 = 0xA0; //选通 Y5 通道,放行 P0 操作

P0 先于 P2 控制

先将 P0 置于想要的操作,再选通 138 译码器,防止选通时 P0 被置错误数据 当锁存器被选通后,给他赋的值在译码器被清零后仍然有效

1
2
3
P0 = 0x00;  
P2 = 0xA0; //选通 Y5 通道,放行 P0 操作
P2 =& 1F; //完成操作后进行清零操作

选通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Choose(u8 n)
{
switch(n)
{
case 4:
P2 = 0x80; //100
break;
case 5:
P2 = 0xA0; //101
break;
case 6:
P2 = 0xC0; //110
break;
case 7:
P2 = 0xE0; //111
break;
}
P2 &= 0x1F; //清零
}

初始化系统

1
2
3
4
5
6
7
8
void InitSystem()
{
P2 &= 0x1F; //清零
P0 = 0xFF; //关闭 LED
Choose(4); //选通 Y4:LED
P0 = 0x00; //关闭蜂鸣器继电器
Choose(5); //选通 Y5
}

LED 点亮

1
2
3
4
5
6
7
8
9
void LEDFlash()
{
P0 = 0x00; //点亮 LED
Choose(4);
Delay1000ms();
P0 = 0xFF; //熄灭 LED
Choose(4);
Delay1000ms();
}

数码管显示

烧录器提供的标准字库

1
2
3
4
5
6
7
8
9
10
11
12
#define u8 unsigned char

//段码
u8 code t_display[]={
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black - H J K L N o P U t G Q r M y
0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1

//位码
u8 code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

按位显示

1
2
3
4
5
6
7
8
9
10
11
void DisplayBit(dat, pos)
{
P0 = 0x00; //消隐
Choose(6); //选通 Y6:COM

P0 = ~t_display[dat];
Choose(7); //选通 Y7:段码

P0 = T_COM[pos-1];
Choose(6); //选通 Y6:COM
}

动态显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
u8 dig_dat[] = {2, 0, 2, 2, 0, 3, 2, 6};  //要显示的数据
u8 dig_pos = 0; //要显示的位置

void display_dynamic()
{
P0 = ~t_display[dig_dat[dig_pos]]; //显示没个位置所对应的段码
Choose(7);

P0 = T_COM[dig_pos]; //设置 COM 口
Choose(6);

if (++dig_pos >= 8 ) //COM 自加 1
{
dig_pos = 0; //COM 口归零
}
}

void Timer0Init(void)//1毫秒@12.000MHz
{
AUXR = 0x80;//定时器时钟1T模式
TMOD &= 0xF0;//设置定时器模式
TL0 = 0x20;//设置定时初值
TH0 = 0xD1;//设置定时初值
TF0 = 0;//清除TF0标志
TR0 = 1;//定时器0开始计时
EA = 1; //打开总中断
ET0 = 1;
}

void Timer0Service() interrupt 1
{
display_dynamic();
count_1s++; //可以在这里使用定时器循环 1000 次计时 1 秒
}

void main()
{
Timer0Init(); //在主函数中启动定时器
while(1)
{
if (count_1s >= 1000)。//可用于计时 1 秒
{
count_1s = 0; //将 1 秒计数器归零

dig_dat[4] = count / 1000;
dig_dat[5] = count % 1000 / 100;
dig_dat[6] = count % 100 / 10;
dig_dat[7] = count % 10;

count++; //数码管显示数值增加
}
}

键盘

独立按键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
void Timer0Init(void)//1毫秒@12.000MHz
{
AUXR = 0x80;//定时器时钟1T模式
TMOD &= 0xF0;//设置定时器模式
TL0 = 0x20;//设置定时初值
TH0 = 0xD1;//设置定时初值
TF0 = 0;//清除TF0标志
TR0 = 1;//定时器0开始计时
EA = 1; //打开总中断
ET0 = 1; //打开定时器0中断
}

unsigned int timer10ms = 0; //定义延迟 10ms 标志位,用于按键消抖

void Timer0Service() interrupt 1 //定时器中断服务函数,每隔 1ms 执行一次
{
display_dynamic();
timer10ms++; //10ms 标志位自加 1
}

unsigned char key_count = 0; //定义按键次数标志位,用于按键消抖
unsigned char key_value = 0; //定义具体键值,可根据按下的按键赋值进行相应操作
unsigned char key_temp = 0; //用于判断按键是否被按下(引脚是否输出低电平)

void key_scan()
{
P3 = 0x0f; //将按键引脚 P30~P33 置 1111,若 S7 按下则会变为 1110
//将 P30~P33 取反后为:0000,若 S7 被按下,则取反后为 0001
key_temp = ~P3 & 0x0f;
//与上 0x0f 用于将高四位 P34~P37 置 0,屏蔽其值,因为其为与操作,低四位不受影响
if(key_temp) //key_temp 有值即为按键被按下,进入 if 中
{
if(key_count == 0) //第一次进入 if,判断 key_count 用于按键消抖
{
key_count = 1;
}
else if(key_count == 1) //再次进入 if,key_count 为 1,说明经过 10ms 消抖,为真正被按下
{
key_count = 2; //用于确保按下一次只增加 1,数值不连增
switch(key_temp) //key_temp 中的值可用于判断具体哪个按键被按下
{
case 0x01: //S7
P0 = ~0x01;
key_value = 7;
break;
case 0x02: //S6
P0 = ~0x02;
key_value = 6;
break;
case 0x04: //S5
P0 = ~0x04;
key_value = 5;
break;
case 0x08: //S4
P0 = ~0x08;
key_value = 4;
break;
default: //多个按键同时按下
P0 = 0x00;
break;
}
}
else if(key_count == 2)
{
key_value = 0; //用于确保按下一次只增加 1,数值不连增
}
}
else //按键被松开
{
key_count = 0; //按键计数归零
key_value = 0; //按键赋值归零
P0 = 0xff;
}
Choose(4);
}

void main()
{
InitSystem();
Timer0Init(); //初始化计时器
while(1)
{
if(timer10ms >= 10) //每隔 10ms 执行一次
{
timer10ms = 0; //10ms 标识为归零
key_scan();
}
}
}

矩阵键盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
unsigned char key_count = 0;  //定义按键扫描计数,用于去抖
unsigned char key_temp = 0; //用于判断是否有按键按下
unsigned int key_pos[4][4] = //返回对应按键的值
{
00, 01, 02, 03,
10, 11, 12, 13,
20, 21, 22, 23,
30, 31, 32, 33,
};
int keyboard_scan()
{
unsigned char row = 0; //定义行
unsigned char col = 0; //定义列
//行扫描,P30~P33(行)置高电平,P34、P35、P42、P44(列)置低电平
P3 = 0x0f;
P4 = 0x00;
key_temp = ~P3 & 0x0f;
if(key_temp)
{
if(key_count == 0)
{
key_count = 1;
return 0;
}
else if(key_count == 1)
{
//key_count = 2; //矩阵键盘禁用此行,用于按住持续显示
if(P30 == 0)
{
row = 0;
}
if(P31 == 0)
{
row = 1;
}
if(P32 == 0)
{
row = 2;
}
if(P33 == 0)
{
row = 3;
}
//列扫描,P30~P33(行)置低电平,P34、P35、P42、P44(列)置高电平
P3 = 0xf0;
P4 = 0xff;
if(P44 == 0)
{
col = 0;
}
if(P42 == 0)
{
col = 1;
}
if(P35 == 0)
{
col = 2;
}
if(P34 == 0)
{
col = 3;
}
return key_pos[row][col]; //返回相应的行列所对应二维数组的值
}
else if(key_count == 2)
{
return 0;
}
}
else
{
//松开归零
key_count = 0;
return 0;
}
return 0;
}

void main()
{
unsigned char key_value = 66; //定义键盘扫描返回值
InitSystem();
Timer0Init();
while(1)
{
if(Timer10ms >= 10)
{
Timer10ms = 0;
key_value = keyboard_scan(); //调用键盘扫描函数获取返回值
}
display_buf[0] = key_value / 10; //数码管显示返回值十位
display_buf[1] = key_value % 10; //数码管显示返回值个位
}
}

DS1302 电子时钟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//日期和时间分两个数组是为了方便数码管分别显示日期和时间
//这样时间数组的元素个数和数码管位数恰好一致
unsigned char dig_time[] = //定义时间
{
2, 3, 17, 5, 9, 17, 5, 5, //恰好 8 位
};
unsigned char dig_date[] = //定义日期
{
2, 0, 2, 2,
3, 0, 3, 3, 0,
};

unsigned char *dig_show; //定义数码管显示的指针,后续可拓展切换日期和时间
unsigned char dig_pos = 0; //定义数码管位码数组下标
void display()
{
//具体显示的内容由 main 函数中对指针 *dig_show 的赋值确定
P0 = ~t_display[dig_show[dig_pos]];
Choose(7);
P0 = T_COM[dig_pos];
Choose(6);

if(++dig_pos >= 8) //注意先加和后加的问题,先加用 8,后加用 7
{
dig_pos = 0;
}
}

unsigned int timer1s = 0; //定义 1s 计数标记位,用于每隔 1s 更新一次
void Timer0Service() interrupt 1
{
timer1s++;
display();
}
#include "hardware.h"

void time_write()
{
Write_Ds1302_Byte(0x8C, dig_date[2]*0x10 + dig_date[3]); //year
Write_Ds1302_Byte(0x8A, dig_date[4]); //week
Write_Ds1302_Byte(0x88, dig_date[5]*0x10 + dig_date[6]); //month
Write_Ds1302_Byte(0x86, dig_date[7]*0x10 + dig_date[8]); //date
Write_Ds1302_Byte(0x84, dig_time[0]*0x10 + dig_time[1]); //hour
Write_Ds1302_Byte(0x82, dig_time[3]*0x10 + dig_time[4]); //min
Write_Ds1302_Byte(0x80, dig_time[6]*0x10 + dig_time[7]); //sec
}

void time_read()
{
unsigned char temp;

temp = Read_Ds1302_Byte(0x85); //hour
dig_time[0] = temp / 0x10; //十位
dig_time[1] = temp % 0x10; //个位

temp = Read_Ds1302_Byte(0x83); //min
dig_time[3] = temp / 0x10;
dig_time[4] = temp % 0x10;

temp = Read_Ds1302_Byte(0x81); //sec
dig_time[6] = temp / 0x10;
dig_time[7] = temp % 0x10;
}
#include "hardware.h"
#include "ds1302.h"

void main()
{

dig_show = dig_time; //对指针赋值,让数码管显示时间

InitSystem();
Timer0Init();
time_write(); //写入初始时间
while(1)
{
if(timer1s >= 1000)
{
timer1s = 0;
time_read(); //每隔 1s 读取一次时间,显示在数码管上
}
}
}

DS18B20 温度传感器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//单总线延时函数
void Delay_OneWire(unsigned int t) //STC89C52RC
{
t *= 12; //单片机型号差异,需将时间乘 12 倍
while(t--);
}

float rd_temperature(void)
{
unsigned char temp_low,temp_high;

init_ds18b20(); //初始化 18B29
Write_DS18B20(0xCC);
Write_DS18B20(0x44);

init_ds18b20(); //初始化 18B29
Write_DS18B20(0xCC);
Write_DS18B20(0xBE);

temp_low = Read_DS18B20(); //温度低 8 位
temp_high = Read_DS18B20(); //温度高 8 位

//0.0625 为固定的系数
//先转换为 unsigned int 类型,确保其有 16 位
//最初得到的 temp_high 位于 16 位中的低 8 位,需要将其作移 8 位到高 8 位
//与上得到的 temp_low,将低 8 位填充上
//最后强制类型转换为 float 型,用于区分温度的整数和小数部分
return 0.0625 * (float)((unsigned int)temp_high << 8 temp_low);
}
int main()
{
float temp; //定义一个用来保存温度返回值的变量

InitSystem();
Timer0Init();
while(1)
{
if(timer250ms >= 250) //每 250ms 读取一次温度
{
timer250ms = 0;
temp = rd_temperature(); //读取温度
dig_dat[0] = (unsigned int)temp / 10; //十位
dig_dat[1] = (unsigned int)temp % 10 + 32; //个位,带小数点
dig_dat[2] = (unsigned int)(temp * 10) % 10; //0.1 位
dig_dat[3] = (unsigned int)(temp * 100) % 10; //0.01 位
}
}
}

AT24C02 EEPROM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//单字节写入
void write_byte(unsigned char addr, unsigned char dat)
{
IIC_Start();
IIC_SendByte(0xA0); //要使用的 EEPROM 地址
IIC_WaitAck(); //等待回应

IIC_SendByte(addr); //要写入的地址
IIC_WaitAck();

IIC_SendByte(dat); //要写入的数据
IIC_WaitAck();

IIC_Stop();
}

//单字节读取
unsigned char read_byte(unsigned char addr)
{
unsigned char temp; //定义用于存储读取到数据的变量

IIC_Start();
IIC_SendByte(0xA0); //要使用的 EEPROM 地址
IIC_WaitAck();

IIC_SendByte(addr); //要读取的地址
IIC_WaitAck();

IIC_Start();
IIC_SendByte(0xA1);
IIC_WaitAck();

temp = IIC_RecByte(); //存储读取到的数据

return temp; //返回读取到的数据
}

//多字节写入
void write_nbyte_24c02(unsigned char addr,unsigned char *dat,unsigned char lenth)
{//dat为指针,指向数组的首地址
IIC_Start();
IIC_SendByte(0xA0);//写入0xA0,读取0xA1
IIC_WaitAck();

IIC_SendByte(addr);//addr为起始地址
IIC_WaitAck();
//lenth决定写入多少个字节,3就是3次
while(lenth--)
{
IIC_SendByte(*dat);
IIC_WaitAck();
dat++;
}

IIC_Stop();

}//读和写之间要延时10ms

//多字节读取
void read_nbyte_24c02(unsigned char addr,unsigned char *dat,unsigned char lenth)//数据保存在dat数组里
{
IIC_Start();
IIC_SendByte(0xA0);
IIC_WaitAck();

IIC_SendByte(addr);
IIC_WaitAck();

IIC_Start();
IIC_SendByte(0xA1);
IIC_WaitAck();

while(--lenth)//lenth为2则为2个字节
{
*dat = IIC_RecByte();
IIC_SendAck(0);
dat++;
}

*dat = IIC_RecByte();
//最后一位不需要应答,因此使用--lenth,使得在循环中读取lenth-1次,最后一次单独执行不做应答
IIC_Stop();

}//读和写之间要延时10ms
void main()
{
unsigned char data_24c02; //定义用于存储读取 EEPROM 返回值的变量

InitSystem();
Timer0Init();

write_byte(0x55, 0xAB); //写数据
Delay10ms();
data_24c02 = read_byte(0x55); //读数据
dig_dat[0] = data_24c02 / 0x10; //十位
dig_dat[1] = data_24c02 % 0x10; //个位

while(1)
{

}
}

FPC8591 ADC\DAC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void write_8591(unsigned char dac)
{
unsigned char i = 100;

IIC_Start();
IIC_SendByte(0x90); //ADC 是 90,EEPROM 是 A0
IIC_WaitAck();

IIC_SendByte(0x40); //写入控制字节,0100 0000 输出标识为置 1,其余置 0
IIC_WaitAck();

IIC_SendByte(dac*51); //要写入的电压值
IIC_WaitAck();

IIC_Stop();
while(i--);
}

/*
* channel_n 只能填 0、1、2、3
* 分别对应通道 0、1、2、3
* 对应二进制也刚好和 CONTROL BYTE 的最后两位一样
* 又因为其不需要自动递增,故可以直接作为控制字节发送给 8591
*/
unsigned char read_8591(unsigned char channel_n)
{
unsigned char i = 100;

IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();

IIC_SendByte(channel_n);
IIC_WaitAck();

IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();

/*
* 每次读到的都是上一次转化得到的结果
* 此处读两次就可以得到一个确定的结果
*/
channel_n = IIC_RecByte(); //第一次读
IIC_SendAck(0);
channel_n = IIC_RecByte(); //第二次读

IIC_Stop();
while(i--);

return channel_n;
//(unsignedint)channel_n / 255.0f * 500.0f 得到 0~5V 区间电压值
}
void main()
{
unsigned int temp_8591; //定义用来存储读取电压返回值的变量,16 位

InitSystem();
Timer0Init();

write_8591(255); //DAC 输出电压,范围为 0~255

while(1)
{
temp_8591 = read_8591(1); //通道 1,读取光敏电阻
temp_8591 <<= 8; //将光敏电阻左移 8 位,低 8 位用于存储电位器读到的值
Delay10ms();
temp_8591 = read_8591(3); //通道 3,读取电位器

dig_dat[0] = temp_8591 / 0x1000; //光敏十位
dig_dat[1] = temp_8591 / 0x100 % 0x10; //光敏个位
dig_dat[3] = temp_8591 / 0x10 % 0x10; //电位器十位
dig_dat[4] = temp_8591 % 0x10; //电位器个位
}
}

NE555 频率测量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
void Timer0Init(void)//1毫秒@12.000MHz
{
AUXR = 0x80;//定时器时钟1T模式
TMOD &= 0xF0;//设置定时器模式
TMOD = 0x04; //修改定时器 0 为计数模式 xxxx x1xx
TL0 = 0x00;//设置定时初值
TH0 = 0x00;//设置定时初值
TF0 = 0;//清除TF0标志
TR0 = 1;//定时器0开始计时
}

void Timer1Init(void)//1毫秒@12.000MHz
{
AUXR = 0x40;//定时器时钟1T模式
TMOD &= 0x0F;//设置定时器模式
TL1 = 0x20;//设置定时初值
TH1 = 0xD1;//设置定时初值
TF1 = 0;//清除TF1标志
TR1 = 1;//定时器1开始计时

EA = 1; //打开总中断
ET1 = 1; //打开定时器 1 中断
}

unsigned int timer1s = 0; //延迟 1 秒标志位
void Timer1Service() interrupt 3
{
unsigned int temp = 0; //储存频率临时变量,int 型,16 位
Display();
if(++timer1s >= 1000)
{
timer1s = 0; //延迟 1 秒标志位置 0
TR0 = 0; //暂停定时器 0 计数


temp = TH0; //存储频率高 8 位
temp <<= 8; //左移 8 位移向高 8 位
temp = TL0; //低 8 位赋值

TH0 = 0x00; //计数初值归零
TL0 = 0x00;

TR0 = 1;

freq = temp; //频率赋值给全局变量
}
}

void Display_freq() //数码管显示频率
{
if(freq >= 100000)
{
dig_dat[2] = freq / 100000;
}
else
{
dig_dat[2] = 16;
}
if(freq >= 10000)
{
dig_dat[3] = freq / 10000 % 10;
}
else
{
dig_dat[3] = 16;
}
if(freq >= 1000)
{
dig_dat[4] = freq / 1000 % 10;
}
else
{
dig_dat[4] = 16;
}
if(freq >= 100)
{
dig_dat[5] = freq / 100 % 10;
}
else
{
dig_dat[5] = 16;
}
if(freq >= 10)
{
dig_dat[6] = freq / 10 % 10;
}
else
{
dig_dat[6] = 16;
}
dig_dat[7] = freq % 10;
}

PWM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void LED(unsigned char led_switch)
{
P0 = ~led_switch;
Choose(4);
}

void Timer0Service() interrupt 1
{
if(++count > T)
{
count = 0;
}
if(count <= duty)
{
LED(0xff);
}
else
{
LED(0x00);
}
}
void main()
{
InitSystem();
Timer0Init();
while(1)
{
if(duty >= T)
{
while(duty)
{
Delay100ms();
duty--;
}
}
Delay100ms();
duty++;
}
}

蓝桥杯 Bilibili UP 机械狗学习笔记
https://blog.tddt.cc/posts/lanqiao-mechanical-dog.html
作者
TechPANG
发布于
2022年4月12日
许可协议