74HC573

原理

由于蓝桥杯板子上的外设很多, 所以开发板上使用了4个74HC573锁存器来复用引脚, 操控573上的外设由3个步骤组成

image-20241201085625536

  1. 138译码器

    将3位输入地址(A0, A1, A2)译码为8个独立的输出(D0 ~ D7)。这里我们只需要高4位也就是D4~D7的输出, 所以真值表为:

    单片机P2 A0 A1 A2 D
    0x80 1 0 0 1110 1111
    0xA0 1 0 1 1101 1111
    0xC0 1 1 0 1011 1111
    0xD0 1 1 1 0111 1111
  2. 或非门 (NOR)

因为锁存器使能(Load)为高时,Q 输出将随数据(D)输入而变。当使能为低时,将输出锁存在已建立的数据电平上。我们需要锁存器默认为锁存状态, 即被38译码器选中时才进入透明状态, 所以开发板上使用一端接地的或非门来实现对38译码器选中信号取反
NOR真值表:

A0 A1 Y
0 0 1
0 1 0
1 0 0
1 1 0
  1. 74HC573锁存器

    锁存器使能(Load)为高时,Q 输出将随数据(D)输入而变。当使能为低时,将输出锁存在已建立的数据电平上。输出控制不影响锁存器的内部工作,即老数据可以保持.

程序设计

在将Load置高前, 改变P0为我们想要的值, 然后拉高对应Load再马上拉低Load, 这样就完成了一次数据更改与锁存操作

拉高Load时, 锁存器就已经变为透明状态了, 即输出严格等于P0, 所以可以在拉高Load之后马上拉低Load进行锁存, 但是前提是需要提前改变P0的值

1
2
3
4
void sel_573(char num){
if(num>=4 && num<=7) P2 = (P2 & 0x1f) | (num << 5); //选择
P2 &= 0X1F; //锁存
}

矩阵键盘

原理

程序设计

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <STC15F2K60S2.H>
#include "start.h"

unsigned char key_num(){
unsigned int temp=0;
unsigned char i;
unsigned int sign = 0x8000;
//往每行中输入低电平
//L1:P44 L2:P42 L3:P35 L4:P34

P44=0;P42=1;P35=1;P34=1;//第一列
temp = temp | (P3&0x0f);//只保存低四位数据

P44=1;P42=0;P35=1;P34=1;//第二列
temp = temp<<4 | (P3&0x0f);//只保存低四位数据

P44=1;P42=1;P35=0;P34=1;//第三列
temp = temp<<4 | (P3&0x0f);//只保存低四位数据

P44=1;P42=1;P35=1;P34=0;//第四列
temp = temp<<4 | (P3&0x0f);//只保存低四位数据
for(i=0;i<16;i++) if((~temp) == (sign>>i)) return i+4;//从最高位检测到最低位
return 0;
}

数码管

原理

程序设计

完整代码

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
#include <STC15F2K60S2.H>
#include "seg.h"
#include "start.h"
code unsigned char Seg_Table[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};

void seg_show_char(char wei,char duan){
P0 = 0x01 << wei - 1;
sel_573(6);

if(duan == '.') P0 = 0x7f;
else if(duan == '-') P0 = 0xBF | 0x80;
else if(duan == 'P') P0 = 0x0C | 0x80;
else P0 = Seg_Table[duan] | 0x80;
sel_573(7);
/*消隐操作*/
delay_ms(1);
P0 = 0xff;
sel_573(7);
}

void seg_show_num(char wei,long int num,char len){
char i=0;
int digits[8] = {0};
if (num < 0) {
seg_show_char(wei++, '-'); // 显示负号
num = -num; // 取正数用于显示
}
while(len--) digits[i++] = num%10,num/=10;
if(num<0) seg_show_char(wei++,'-');
while(i--) seg_show_char(wei++,digits[i]);
}

DS1302

原理

程序设计

  1. 查看数据手册关于寄存器地址的定义

    image-20241206002645680

    将表中的读写地址记为数组方便使用, 同时新建一个Timer数组来存放时间

    1
    2
    3
    unsigned char Timer[7]={0,0,0,1,1,1,1};//存放时间的数组,从低到高分别是 [秒, 分, 时, 天, 月, 星期, 年]
    unsigned char Write_DS1302_adrr[7]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};//存放写入地址
    unsigned char Read_DS1302_adrr[7]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};//存放读出地址
  2. 定义iic端口

    1
    2
    3
    sbit SCK=P1^7;
    sbit SDA=P2^3;
    sbit RST=P1^3;
  3. 写入Timer数组的时间

    由于DS1302在0x8E的BIT7上有写保护, 所以在写入数据前需要给BIT7写入0关闭写保护, 之后调用官方给的Write_Ds1302_Byte()函数对指定位置写入数据, 操作完成之后再给BIT7写入1开启写保护

    1
    2
    3
    4
    5
    6
    void DS1302_Config(){
    char i;
    Write_Ds1302_Byte(0x8e, 0x00);//关闭写保护
    for(i = 0; i < 7; i++) Write_Ds1302_Byte(Write_DS1302_adrr[i], Timer[i]);//依次写入时间
    Write_Ds1302_Byte(0x8e, 0x80);//开启写保护
    }
  4. 读取时间

    1
    2
    3
    4
    void Read_DS1302_Timer(){
    unsigned char i;
    for(i = 0; i < 7; i++) Timer[i] = Read_Ds1302_Byte(Read_DS1302_adrr[i]);
    }

完整代码

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
/*    # 	DS1302代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <STC15F2K60S2.H>
#include <intrins.h>
#include "ds1302.h"
unsigned char Timer[7]={0,0,0,1,1,1,1};//用来存放时间的数组,从低到高分别是 秒,分,时 ,天
unsigned char Write_DS1302_adrr[7]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};//用来存放写入地址
unsigned char Read_DS1302_adrr[7]={0x80+1,0x82+1,0x84+1,0x86+1,0x88+1,0x8a+1,0x8c+1};//用来存放读出地址

sbit SCK=P1^7;
sbit SDA=P2^3;
sbit RST=P1^3;

void Write_Ds1302(unsigned char temp)
{
unsigned char i;
for (i=0;i<8;i++)
{
SCK = 0;
SDA = temp&0x01;
temp>>=1;
SCK=1;
}
}

//
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )
{
RST=0; _nop_();
SCK=0; _nop_();
RST=1; _nop_();
Write_Ds1302(address);
Write_Ds1302(dat);
RST=0;
}

//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
unsigned char i,temp=0x00;
RST=0; _nop_();
SCK=0; _nop_();
RST=1; _nop_();
Write_Ds1302(address);
for (i=0;i<8;i++)
{
SCK=0;
temp>>=1;
if(SDA)
temp|=0x80;
SCK=1;
}
RST=0; _nop_();
SCK=0; _nop_();
SCK=1; _nop_();
SDA=0; _nop_();
SDA=1; _nop_();
return (temp);
}




void DS1302_Config(){
char i;
Write_Ds1302_Byte(0x8e, 0x00);
for(i = 0; i < 7; i++) Write_Ds1302_Byte(Write_DS1302_adrr[i], Timer[i]);
Write_Ds1302_Byte(0x8e, 0x80);
}


void Read_DS1302_Timer(){
unsigned char i;
for(i = 0; i < 7; i++) Timer[i] = Read_Ds1302_Byte(Read_DS1302_adrr[i]);
}

DS18B20

原理

程序设计

DS18B20读取温度的基本流程

  1. DS18B20复位。
  2. 写入字节0xCC,跳过ROM指令。
  3. 写入字节0x44,开始温度转换。
  4. DS18B20复位。
  5. 写入字节0xCC,跳过ROM指令。
  6. 写入字节0xBE,读取高速暂存器。
  7. 读取暂存器的第0字节,即温度数据的LSB。
  8. 读取暂存器的第1字节,即温度数据的MSB。
  9. DS18B20复位。表示读取数据结束。
  10. 将LSB和MSB整合成为一个16位数据。
  11. 判断读取结果的符号,进行正负温度的数据处理
  1. 查阅数据手册的ROM COMMANDSDS18B20 FUNCTION COMMANDS获取到所需的指令码

    常用指令码:

    • 0xCC: 跳过ROM指令。忽略64位ROM地址,直接向DS18B20发起各种执行指令。
    • 0x44:温度转换指令。启动DS18B20进行温度转换。
    • 0xBE:读取暂存器指令。DS18B20收到该指令后,会逐个输出高速暂存器中字节0到字节9的内容。如果要停止读取,必须进行复位操作。如果只需要读取温度数据,那么,在读完第0个字节和第1个字节数据后,不再理会DS18B20后面发出的数据即可。
  2. 实现读取函数

    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
    sbit DQ=P1^4;
    int DS18B20_Get_Tempreature(){
    unsigned char LSB,MSB;
    int DS18B20_Tempreature;
    init_ds18b20(); //复位
    Write_DS18B20(0xCC); //跳过ROM指令
    Write_DS18B20(0x44); //开始温度转换
    init_ds18b20(); //复位
    Write_DS18B20(0xCC); //跳过ROM指令
    Write_DS18B20(0xBE); //读取温度
    LSB = Read_DS18B20();
    MSB = Read_DS18B20();
    init_ds18b20(); //复位

    DS18B20_Tempreature = (MSB<<8) | LSB;//对读取的温度进行处理
    if((DS18B20_Tempreature & 0xF800) == 0x000) //判断温度为正
    {
    /*保留1位小数
    将读取的温度放大10倍,假设为26.5度,则读出的温度为265*/
    DS18B20_Tempreature = (DS18B20_Tempreature >> 4) *10;
    DS18B20_Tempreature = DS18B20_Tempreature + (LSB & 0x0f)*0.0625*10;

    /*保留两位小数
    将读取的温度放大100倍,假设为26.53度,则读出的温度为2653
    DS18B20_Tempreature = (DS18B20_Tempreature >> 4) *10;
    DS18B20_Tempreature = DS18B20_Tempreature + (LSB & 0x0f)*0.0625*10;*/

    }
    return DS18B20_Tempreature;
    }

完整代码

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
/*    # 	单总线代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/

//
#include <STC15F2K60S2.H>
#include "onewire.h"
sbit DQ=P1^4;

void Delay_OneWire(unsigned int t)
{
unsigned char i;
while(t--) for(i=0;i<12;i++);
}

//
void Write_DS18B20(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
DQ = 0;
DQ = dat&0x01;
Delay_OneWire(5);
DQ = 1;
dat >>= 1;
}
Delay_OneWire(5);
}

//
unsigned char Read_DS18B20(void)
{
unsigned char i;
unsigned char dat;

for(i=0;i<8;i++)
{
DQ = 0;
dat >>= 1;
DQ = 1;
if(DQ)
{
dat |= 0x80;
}
Delay_OneWire(5);
}
return dat;
}

//
bit init_ds18b20(void)
{
bit initflag = 0;

DQ = 1;
Delay_OneWire(12);
DQ = 0;
Delay_OneWire(80);
DQ = 1;
Delay_OneWire(10);
initflag = DQ;
Delay_OneWire(5);

return initflag;
}
int DS18B20_Get_Tempreature(){
unsigned char LSB,MSB;
int DS18B20_Tempreature;
init_ds18b20(); //复位
Write_DS18B20(0xCC); //跳过ROM指令
Write_DS18B20(0x44); //开始温度转换
init_ds18b20(); //复位
Write_DS18B20(0xCC); //跳过ROM指令
Write_DS18B20(0xBE); //读取温度
LSB = Read_DS18B20();
MSB = Read_DS18B20();
init_ds18b20(); //复位

DS18B20_Tempreature = (MSB<<8) | LSB;//对读取的温度进行处理
if((DS18B20_Tempreature & 0xF800) == 0x000) //判断温度为正
{
/*保留1位小数
将读取的温度放大10倍,假设为26.5度,则读出的温度为265*/
DS18B20_Tempreature = (DS18B20_Tempreature >> 4) *10;
DS18B20_Tempreature = DS18B20_Tempreature + (LSB & 0x0f)*0.0625*10;

/*保留两位小数
将读取的温度放大100倍,假设为26.53度,则读出的温度为2653
DS18B20_Tempreature = (DS18B20_Tempreature >> 4) *10;
DS18B20_Tempreature = DS18B20_Tempreature + (LSB & 0x0f)*0.0625*10;*/
}
return DS18B20_Tempreature;
}

PCF8591

原理

程序设计

  1. 读取AD值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    unsigned char PCF8951_AD_Converter(unsigned char channel){
    unsigned char AD=0;
    I2CStart();
    I2CSendByte(0x90);I2CWaitAck();//写入从机PCF8591的写地址,
    I2CSendByte(0x00|channel);I2CWaitAck();//写入控制指令,AD转换,选择通道channel
    I2CStop();//结束本次通信,改变收发方向

    I2CStart();
    I2CSendByte(0X91);I2CWaitAck();
    AD = I2CReceiveByte();
    return AD;
    }
  2. DA输出

    1
    2
    3
    4
    5
    6
    7
    void PCF8951_DA_Out(unsigned char dat){
    I2CStart(); //iic通信开始
    I2CSendByte(0x90);I2CWaitAck(); //写入从机PCF8591的写地址,
    I2CSendByte(0x40);I2CWaitAck();
    I2CSendByte(dat);I2CWaitAck();
    I2CStop();
    }

完整代码

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*    #   I2C代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include "iic.h"
#define DELAY_TIME 10
sbit sda=P2^1;
sbit scl=P2^0;
//
static void I2C_Delay(unsigned char n)
{
do
{
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
}
while(n--);
}

//
void I2CStart(void)
{
sda = 1;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 0;
I2C_Delay(DELAY_TIME);
scl = 0;
}

//
void I2CStop(void)
{
sda = 0;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 1;
I2C_Delay(DELAY_TIME);
}

//
void I2CSendByte(unsigned char byt)
{
unsigned char i;

for(i=0; i<8; i++){
scl = 0;
I2C_Delay(DELAY_TIME);
if(byt & 0x80){
sda = 1;
}
else{
sda = 0;
}
I2C_Delay(DELAY_TIME);
scl = 1;
byt <<= 1;
I2C_Delay(DELAY_TIME);
}

scl = 0;
}

//
unsigned char I2CReceiveByte(void)
{
unsigned char da;
unsigned char i;
for(i=0;i<8;i++){
scl = 1;
I2C_Delay(DELAY_TIME);
da <<= 1;
if(sda)
da |= 0x01;
scl = 0;
I2C_Delay(DELAY_TIME);
}
return da;
}

//
unsigned char I2CWaitAck(void)
{
unsigned char ackbit;

scl = 1;
I2C_Delay(DELAY_TIME);
ackbit = sda;
scl = 0;
I2C_Delay(DELAY_TIME);

return ackbit;
}

//
void I2CSendAck(unsigned char ackbit)
{
scl = 0;
sda = ackbit;
I2C_Delay(DELAY_TIME);
scl = 1;
I2C_Delay(DELAY_TIME);
scl = 0;
sda = 1;
I2C_Delay(DELAY_TIME);
}

unsigned char PCF8951_AD_Converter(unsigned char channel){
unsigned char AD=0;
I2CStart(); //iic通信开始
I2CSendByte(0x90);I2CWaitAck(); //写入从机PCF8591的写地址,
I2CSendByte(0x00|channel);I2CWaitAck(); //写入控制指令,单端输入,AD转换,选择通道channel
I2CStop(); //结束本次通信,改变收发方向

I2CStart();
I2CSendByte(0X91);I2CWaitAck();
AD = I2CReceiveByte();
return AD;
}

void PCF8951_DA_Out(unsigned char dat){
I2CStart(); //iic通信开始
I2CSendByte(0x90);I2CWaitAck(); //写入从机PCF8591的写地址,
I2CSendByte(0x40);I2CWaitAck();
I2CSendByte(dat);I2CWaitAck();
I2CStop();
}