蓝桥杯代码例程详解

  本文档介绍了蓝桥杯单片机开发板的硬件控制函数,包括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:

  1. SKIP ROM [CCh]

  当多个DS18B20并联时,需要读取ROM内的序列号来单独操作每一个DS18B20,但蓝桥杯开发板上只有一个DS18B20,故跳过读取ROM。

  1. CONVERT T [44h]

  开始温度转换。

  1. 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

地址看数据手册Page 9

初始化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];
}

添加新评论