本文档介绍了蓝桥杯单片机开发板的硬件控制函数,包括P0口扩展、按键处理、以及其他外设。
P0口和按键部分
选择74HC138译码器通道
函数:void set138(unsigned char channel)
参数:4 - LED,5 - 蜂鸣器、继电器,6 - 数码管位选,7 - 数码管段选,0 - 关闭
返回:无
void set138(unsigned char channel)
{
// 先将P2高三位清零,再将channel左移五位
P2 = P2 & 0x1F | (channel << 5);
}
例:
set138(4); // 选择控制LED灯
set138(6); // 选择控制数码管位选
set138(0); // 都不选择,关闭
向P0口写入数据
函数:void write_channel(unsigned char channel, unsigned char dat)
参数:channel表示需要写入的通道,dat表示需要写入的数据
返回:无
void write_channel(unsigned char channel, unsigned char dat)
{
set138(channel); // 选择通道
P0 = dat; // 向P0写入数据
set138(0); // 关闭138译码器
}
例:
write_channel(4, 0xFF); // 关闭所有LED灯
write_channel(5, 0x00); // 关闭蜂鸣器和继电器
定时器2初始化函数
函数:void Timer2_Init(void)
参数:无
返回:无
备注:使用ISP软件生成,时钟频率12MHz,定时器2(STC15系列),模式为16位自动重载,定时器时钟12T,使能定时器中断
void Timer2_Init(void) //2毫秒@12.000MHz
{
AUXR &= 0xFB; //定时器时钟12T模式
T2L = 0x30; //设置定时初始值
T2H = 0xF8; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
IE2 |= 0x04; //使能定时器2中断
}
中断初始化
函数:void interrupts_init()
参数:无
返回:无
void interrupts_init()
{
EA = 1;
}
数码管扫描
函数:void scanseg()
参数:无
返回:无
备注:menu为当前要显示的界面;digit为数码管的位;display[][]这个二维数组应该为一个全局变量,储存每个界面的内容。
void scanseg()
{
static unsigned char digit = 0;
write_channel(6, 1 << digit);
write_channel(7, display[menu][digit]);
digit = digit >= 7 ? 0 : digit + 1;
}
按键扫描
函数:void scankey()
参数:无
返回:无
备注:ROW1、ROW2、ROW3、ROW4表示一到四行,COL1、COL2、COL3、COL4表示一到四列,在引入STC15F2K60S2.H的头文件后,直接使用#define宏定义如下。keycount[],keystate[]为全局变量。MAXCOUNT为最大计数值,也使用#define宏定义。
#define ROW1 P30
#define ROW2 P31
#define ROW3 P32
#define ROW4 P33
#define COL1 P44
#define COL2 P42
#define COL3 P35
#define COL4 P34
#define MAXCOUNT 50 // 消抖时间为50 * 2ms = 100ms
void scankeys()
{
unsigned char i;
unsigned char tmpstate[16] = 0; // tmpstate == 1 表示触发
ROW2 = ROW3 = ROW4 = 1; ROW1 = 0;
COL1 = COL2 = COL3 = COL4 = 1;
if(COL1) tmpstate[0] = 0; else tmpstate[0] = 1; // 如果COL1为0,则表示按键S7被按下,将tmpstate[0]置1
if(COL2) tmpstate[1] = 0; else tmpstate[1] = 1; // 同上……
if(COL3) tmpstate[2] = 0; else tmpstate[2] = 1;
if(COL4) tmpstate[3] = 0; else tmpstate[3] = 1;
ROW1 = ROW3 = ROW4 = 1; ROW2 = 0;
COL1 = COL2 = COL3 = COL4 = 1;
if(COL1) tmpstate[4] = 0; else tmpstate[4] = 1;
if(COL2) tmpstate[5] = 0; else tmpstate[5] = 1;
if(COL3) tmpstate[6] = 0; else tmpstate[6] = 1;
if(COL4) tmpstate[7] = 0; else tmpstate[7] = 1;
ROW1 = ROW2 = ROW4 = 1; ROW3 = 0;
COL1 = COL2 = COL3 = COL4 = 1;
if(COL1) tmpstate[8] = 0; else tmpstate[8] = 1;
if(COL2) tmpstate[9] = 0; else tmpstate[9] = 1;
if(COL3) tmpstate[10] = 0; else tmpstate[10] = 1;
if(COL4) tmpstate[11] = 0; else tmpstate[11] = 1;
ROW1 = ROW2 = ROW3 = 1; ROW4 = 0;
COL1 = COL2 = COL3 = COL4 = 1;
if(COL1) tmpstate[12] = 0; else tmpstate[12] = 1;
if(COL2) tmpstate[13] = 0; else tmpstate[13] = 1;
if(COL3) tmpstate[14] = 0; else tmpstate[14] = 1;
if(COL4) tmpstate[15] = 0; else tmpstate[15] = 1;
for(i = 0; i < 16; i++) // 循环扫描16个按键的状态
{
if(tmpstate[i] == 0){
keycount[i] = 0; // 如果松手,计数值清零
} else {
keycount[i] += (keycount[i] >= MAXCOUNT) ? 0 : 1; // 如果当前计数值达到最大计数值,+0,否则+1
}
if(keycount[i] >= MAXCOUNT) { // 判断当前计数值是否达到最大计数值
keystate[i] = 1; // keystate[i]置1,表示按键确认按下
} else {
keystate[i] = 0;
}
}
}
按键上升沿判断
函数:void risingedge()
参数:无
返回:无
备注:keystate[]为全局变量
void risingedge()
{
static unsigned char pristate[16] = { 0 }; //原来按键状态
static unsigned char i = 0;
if(pristate[0] == 0 && keystate[0] == 1) // 原来状态是0,现在状态(keystate)是1
{
// 按键S7按下后要做的任务
}
if(pristate[1] == 0 && keystate[1] == 1)
{
// 按键S11按下后要做的任务
}
if(pristate[2] == 0 && keystate[2] == 1)
{
// 按键S15按下后要做的任务
}
if(pristate[3] == 0 && keystate[3] == 1)
{
// 按键S19按下后要做的任务
}
// …………
for(i = 0; i < 16; i++)
{
pristate[i] = keystate[i]; // 更新状态,防止重复触发
}
}
DS18B20
需要用到的指令,数据手册Page 11:
- SKIP ROM [CCh]
当多个DS18B20并联时,需要读取ROM内的序列号来单独操作每一个DS18B20,但蓝桥杯开发板上只有一个DS18B20,故跳过读取ROM。
- CONVERT T [44h]
开始温度转换。
- READ SCRATCHPAD [BEh]
读取温度寄存器。
温度寄存器格式,其中S表示符号位,数据手册Page 4
DS18B20温度读取
函数:unsigned long Temp_Read()
参数:无
返回:读取到的温度,unsigned long类型
备注:
unsigned long Temp_Read()
{
unsigned char LSB, MSB;
unsigned long temp = 0;
init_ds18b20();
Write_DS18B20(0xCC); // 跳过ROM指令
Write_DS18B20(0x44); // 开始温度转换
init_ds18b20();
Write_DS18B20(0XCC); // 跳过ROM指令
Write_DS18B20(0XBE); // 读取温度寄存器
LSB = Read_DS18B20(); // 先读低八位
MSB = Read_DS18B20(); // 再读高八位
temp = 0x0000;
temp = MSB; // temp是16位的,先将MSB高八位幅值给temp
temp <<= 8; // 再左移八位
temp |= LSB; // temp与LSB低八位进行或操作,得到完整数据
if((temp & 0xf800) == 0x0000) // 如果温度是正的
{
temp >>= 4; // temp右移四位,去除小数部分
temp &= ~0xf800; // temp高五位清零,也就是符号位清零
// 传感器精度是0.0625摄氏度,所以小数部分需要乘0.0625
// 温度数据为 temp + (LSB & 0x0F) * 0.0625;
// 但是这带有小数部分,乘10000是为了方便数码管显示
temp = temp * 10000 + (LSB & 0x0F) * 625;
}
return temp;
}
PCF8591
设备地址如图所示,数据手册 Page 5
高四位为固定地址1001
A2、A1、A0为可编程地址,蓝桥杯开发板上全部接地,故都为0
最后一位是区分读还是写,高电平有效(1)表示读,低电平有效(0)表示写
写入数据时,设备地址为0x90
读取数据时,设备地址为0x91
控制字如图所示,数据手册 Page 6
- 第8位必须为0
- 第7位表示是否开启DA输出,1:开启;0:关闭
- 第6位、第5位表示ADC输入方式,选择单端输入还是差分输入;蓝桥杯开发板上为四个单独的输入,所以这里选择00,四个单端输入
- 第4位必须为0
- 第3位表示转换标志是否自增(读取多个通道),不需要,关闭,选择0
- 第2位和第1位是选择ADC通道,由蓝桥杯原理图可知,00:J3上的外部输入;01:光敏电阻RD1;10:LM324差分输入;11:可调电阻Rb2
PCF8591读取Rb2电压
函数:unsigned char Read_Rb2()
参数:无
返回:读取到的电压,unsigned char类型
备注:控制字和地址看上面的说明
unsigned char Read_Rb2()
{
unsigned char Rb2;
IIC_Start(); // IIC开始信号
IIC_SendByte(0x90); // 表示向PCF8591的寄存器中写入数据
IIC_WaitAck(); // 等待PCF8591应答
IIC_SendByte(0x03); // 写入控制字0x03
IIC_WaitAck(); // 等待PCF8591应答
IIC_Stop(); // IIC结束信号
IIC_Start(); // IIC开始信号
IIC_SendByte(0x91); // 表示从PCF8591中读数据
IIC_WaitAck(); // 等待PCF8591应答
Rb2 = IIC_RecByte(); // 读取数据
IIC_SendAck(0); // 收到数据,发送应答
IIC_Stop(); // IIC结束信号
return Rb2;
}
PCF8591设置DAC输出电压
函数:void DAC_output(unsigned char value)
参数:要输出的电压值,unsigned char类型
返回:无
备注:控制字和地址看上面的说明
void DAC_output(unsigned char value)
{
IIC_Start(); // IIC开始信号
IIC_SendByte(0x90); // 表示向PCF8591的寄存器中写入数据
IIC_WaitAck(); // 等待PCF8591应答
IIC_SendByte(0x43); // 写入控制字0x43
IIC_WaitAck(); // 等待PCF8591应答
IIC_SendByte(value); // 写入DAC输出的电压值
IIC_WaitAck(); // 等待PCF8591应答
IIC_Stop(); // IIC结束信号
}
AT24C02
设备地址如图所示,这是2K的EEPROM,看图第一行,数据手册Page 11
时序部分在数据手册Page 11、12
AT24C02写入一个字节
函数:void Write_AT24C02(unsigned char address, unsigned char dat)
参数:要写入的地址,要写入的数据
返回:无
备注:地址看上面的说明
void Write_AT24C02(unsigned char address, unsigned char dat)
{
IIC_Start(); // IIC开始信号
IIC_SendByte(0xA0); // 表示向AT24C02的寄存器中写入数据
IIC_WaitAck(); // 等待AT24C02应答
IIC_SendByte(address); // 需要写入的地址
IIC_WaitAck(); // 等待AT24C02应答
IIC_SendByte(dat); // 需要写入的数据
IIC_WaitAck(); // 等待AT24C02应答
IIC_Stop(); // IIC结束信号
}
AT24C02读取一个字节
函数:unsigned char Read_AT24C02(unsigned char address)
参数:要读取的地址
返回:读到的数据
备注:地址看上面的说明
unsigned char Read_AT24C02(unsigned char address)
{
unsigned char dat;
IIC_Start(); // IIC开始信号
IIC_SendByte(0xA0); // 表示向AT24C02的寄存器中写入数据
IIC_WaitAck(); // 等待AT24C02应答
IIC_SendByte(address); // 需要读取的地址
IIC_WaitAck(); // 等待AT24C02应答
IIC_Stop(); // IIC结束信号
IIC_Start(); // IIC开始信号
IIC_SendByte(0xA1); // 表示从AT24C02中读数据
IIC_WaitAck(); // 等待AT24C02应答
dat = IIC_RecByte(); // 读取数据
IIC_SendAck(0); // 收到数据,发送应答
IIC_Stop(); // IIC结束信号
return dat;
}
例:
unsigned char val_24c02 = 0;
Write_AT24C02(0x00, 0x0C); // 向地址0x00写入0x0C(十进制12)
Delay20ms(); // 等待20毫秒(写完之后不能立即读)
val_24c02 = Read_AT24C02(0x00); //读取0x00地址数据
val_24c02 += 8; // 读到的值 + 8
超声波
超声波发射
函数:void SendWave()
参数:无
返回:无
备注:Delay12us()函数使用ISP生成,频率12MHz
void SendWave() // 发送超声波
{
unsigned char i;
for(i = 0; i < 8;i++)
{
TX = 1;
Delay12us();
TX = 0;
Delay12us();
}
}
距离计算
函数:void Distance_Process()
参数:无
返回:无
备注:distance、time均为unsigned int类型的全局变量,RX、TX使用宏定义如下
#define TX P10
#define RX P11
void Distance_Process() // Use Timer1
{
AUXR &= 0xBF; // 设置定时器12T模式
TMOD &= 0x0f; // 定时器1 TMOD高四位清零
TH1 = 0x00;
TL1 = 0x00; // 清除计数值
TF1 = 0; // 清除中断标志
TR1 = 0;
SendWave();
TR1 = 1; //开始计时
while(RX == 1 && TF1 == 0); // 直到收到回波或溢出
TR1 = 0;
if(TF1 == 1) // 如果溢出
{
TF1 = 0;
distance = 999;
}
else
{
time = (TH1 << 8) | TL1;
distance = time * 0.0172;
}
}
NE555
需要将定时器0设置为计数模式,计数初值设为最大值,配置如下
void Timer0_Init()
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x06; //设置定时器模式 // 计数模式
TL0 = 0xFF; //设置定时初始值 // 再次加一就会溢出触发中断
TH0 = 0xFF; //设置定时重载值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
}
DS1302
初始化DS1302
函数:void ds1302_init()
参数:无
返回:无
备注:写入原始数据
// Datasheet Page 9
// Table 3. Register Address/Definition
void ds1302_init()
{
// 12-54-06
Write_Ds1302_Byte(0x8E, 0X00); // 关闭写保护
Write_Ds1302_Byte(0x80, 0x06); // Second Write
Write_Ds1302_Byte(0x82, 0x54); // Minute Write
Write_Ds1302_Byte(0x84, 0x12); // Hour Write
Write_Ds1302_Byte(0x8E, 0X80); // 开启写保护
}
从DS1302中读取数据
函数:void menu()
参数:无
返回:无
备注:需要进行BCD码的转换;同样,display[]是一个全局变量的数组,存放要显示的内容
void menu()
{
display[0] = Seg_Table[Read_Ds1302_Byte(0x85) / 16]; // BCD码转换 十位 /16
display[1] = Seg_Table[Read_Ds1302_Byte(0x85) & 0x0F]; // BCD码转换 个位 &0x0F
display[2] = 0xBF; // -
display[3] = Seg_Table[Read_Ds1302_Byte(0x83) / 16];
display[4] = Seg_Table[Read_Ds1302_Byte(0x83) & 0x0F];
display[5] = 0xBF;
display[6] = Seg_Table[Read_Ds1302_Byte(0x81) / 16];
display[7] = Seg_Table[Read_Ds1302_Byte(0x81) & 0x0F];
}