前言
经过多届蓝桥杯赛事实战验证的代码框架,主打高复用性与快速适配。本模板基于官方CT107D开发板设计,包含LED、数码管、按键、定时器、串口、超声波、555定时器、DS18B20、DS1302、PCF8591、AT24C02等核心模块驱动,助你快速搭建比赛系统。
比赛中会给出onewire.c、ds1302.c、iic.c三个文件,直接调用到自己的工程文件即可,最新版的onewire.c不需要修改延时,但注意加上单片机和包含_nop_函数的头文件及总线引脚定义!
#include <STC15F2K60S2.H>
#include <intrins.h>
//iic总线引脚定义
sbit sda = P2^1; /* 数据线 */
sbit scl = P2^0; /* 时钟线 */
//onewire总线引脚定义
sbit DQ = P1^4; //单总线接口
//ds1302总线引脚定义
sbit SCK=P1^7;
sbit SDA=P2^3;
sbit RST = P1^3; // DS1302复位
目录
一、延时函数 Delay
-
功能:通过循环空操作实现毫秒级延时,系统频率为12MHz
-
应用场景:按键消抖、传感器时序控制、简单任务调度
-
注意:可以使用定时器替代循环延时以提高系统实时性
void Delay(unsigned int x)
{
unsigned int i, j;
for (i = 0; i < x; i++)
{
for (j = 0; j < 123; j++);
}
}
二、锁存器通道选择 SelectHC573
-
功能:74HC573锁存器通道选择(如控制LED、数码管位选等)
-
应用场景:复用IO口时切换外设控制权(P0口复用问题)
-
注意:操作后需及时关闭锁存器防止信号干扰
#include "reg52.h"
sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;
void SelectHC573(unsigned char Channel) //选中Y(n)
{
switch(Channel)
{
case(4): //用于控制LED灯,通过锁存器控制LED的亮灭
HC138_C = 1; HC138_B = 0; HC138_A = 0;
break;
case(5): //用于控制蜂鸣器、继电器等功能
HC138_C = 1; HC138_B = 0; HC138_A = 1;
break;
case(6): //用于控制数码管的位选,决定数码管的哪一位被选中
HC138_C = 1; HC138_B = 1; HC138_A = 0;
break;
case(7): //用于控制数码管的段选,决定数码管的显示内容
HC138_C = 1; HC138_B = 1; HC138_A = 1;
break;
case(0):
HC138_C = 0; HC138_B = 0; HC138_A = 0;
break;
}
}
三、系统初始化 InitSystem
-
功能:系统初始化(关闭蜂鸣器/继电器、同时LED保持熄灭状态)
#include "reg52.h"
#include "SelectHC573.h"
void InitSystem()
{
SelectHC573(5); //上电初始化,关闭蜂鸣器和继电器
P0 = 0x00;
SelectHC573(4); //上电初始化,LED保持熄灭状态
P0 = 0xFF;
}
四、独立按键控制LED
-
检测方式:直连式检测(单个IO对应单个按键)
-
特点:硬件电路简单,但占用较多IO资源
-
典型应用:功能切换键、模式选择键
-
注意:将开发板上J5处的跳帽接到2~3引脚(BTN模式)
#include "reg52.h"
#include "Delay.h"
#include "SelectHC573.h"
sbit S5 = P3^2;
sbit S4 = P3^3;
sbit L7 = P0^6;
sbit L8 = P0^7;
void Keys_Ctrl()
{
SelectHC573(4);
if(S5 == 0)
{
Delay(20);
if(S5 == 0)
{
L7 = 0;
while(S5 == 0);
L7 = 1;
}
}
if(S4 == 0)
{
Delay(20);
if(S4 == 0)
{
L8 = 0;
while(S4 == 0);
L8 = 1;
}
}
}
五、矩阵键盘
-
检测方式:行列扫描法
-
核心算法:消抖+ 键值映射
-
功能:按下按键返回对应键码值
-
注意:将开发板上J5处的跳帽接到1~2引脚(KBD模式),若使用NE555进行频率测量需要将与P3^4引脚相关的代码注释掉
#include "reg52.h"
#include "Delay.h"
sfr P4 = 0xC0;
sbit R1 = P3^0; //行
sbit R2 = P3^1;
sbit R3 = P3^2;
sbit R4 = P3^3;
sbit C4 = P3^4; //列
sbit C3 = P3^5;
sbit C2 = P4^2;
sbit C1 = P4^4;
unsigned char MatrixKey()
{
unsigned char KeyNumber = 0xFF;
// 第一行(S7, S11, S15, S19)
R1 = 0;
R2 = R3 = R4 = 1;
C1 = C2 = C3 = C4 = 1;
if (C1 == 0)
{ // S7
Delay(20);
if (C1 == 0)
{
while (C1 == 0);
KeyNumber = 7;
}
}
else if (C2 == 0)
{ // S11
Delay(20);
if (C2 == 0)
{
while (C2 == 0);
KeyNumber = 11;
}
}
else if (C3 == 0)
{ // S15
Delay(20);
if (C3 == 0) {
while (C3 == 0);
KeyNumber = 15;
}
}
else if (C4 == 0)
{ // S19
Delay(20);
if (C4 == 0)
{
while (C4 == 0);
KeyNumber = 19;
}
}
// 第二行(S6, S10, S14, S18)
R2 = 0;
R1 = R3 = R4 = 1;
C1 = C2 = C3 = C4 = 1;
if (C1 == 0)
{ // S6
Delay(20);
if (C1 == 0)
{
while (C1 == 0);
KeyNumber = 6;
}
}
else if (C2 == 0)
{ // S10
Delay(20);
if (C2 == 0)
{
while (C2 == 0);
KeyNumber = 10;
}
}
else if (C3 == 0)
{ // S14
Delay(20);
if (C3 == 0)
{
while (C3 == 0);
KeyNumber = 14;
}
}
else if (C4 == 0)
{ // S18
Delay(20);
if (C4 == 0)
{
while (C4 == 0);
KeyNumber = 18;
}
}
// 第三行(S5, S9, S13, S17)
R3 = 0;
R2 = R1 = R4 = 1;
C1 = C2 = C3 = C4 = 1;
if (C1 == 0)
{ // S5
Delay(20);
if (C1 == 0)
{
while (C1 == 0);
KeyNumber = 5;
}
}
else if (C2 == 0)
{ // S9
Delay(20);
if (C2 == 0)
{
while (C2 == 0);
KeyNumber = 9;
}
}
else if (C3 == 0)
{ // S13
Delay(20);
if (C3 == 0)
{
while (C3 == 0);
KeyNumber = 13;
}
}
else if (C4 == 0)
{ // S17
Delay(20);
if (C4 == 0)
{
while (C4 == 0);
KeyNumber = 17;
}
}
// 第四行(S4, S8, S12, S16)
R4 = 0;
R2 = R3 = R1 = 1;
C1 = C2 = C3 = C4 = 1;
if (C1 == 0)
{ // S4
Delay(20);
if (C1 == 0)
{
while (C1 == 0);
KeyNumber = 4;
}
}
else if (C2 == 0)
{ // S8
Delay(20);
if (C2 == 0)
{
while (C2 == 0);
KeyNumber = 8;
}
}
else if (C3 == 0)
{ // S12
Delay(20);
if (C3 == 0)
{
while (C3 == 0);
KeyNumber = 12;
}
}
else if (C4 == 0)
{ // S16
Delay(20);
if (C4 == 0)
{
while (C4 == 0);
KeyNumber = 16;
}
}
return KeyNumber;
}
六、数码管显示
-
驱动原理:动态扫描(74HC573锁存段选/位选信号)
-
关键技术:
-
段码表设计(本开发板为数码管为共阳极)
-
小数点处理与显示缓冲区分层管理
-
-
优化方向:定时中断刷新显示(避免闪烁)
#include "reg52.h"
#include "SelectHC573.h"
#include "Delay.h"
// 0 1 2 3 4 5 6 7 8 9 A B C D E F . -
unsigned char code SMG_duanma[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90,
0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E, 0x7F, 0xBF};
//数码管显示带小数点 0-9
unsigned char code SMG_duanmaDot[] = {0x40, 0x79, 0x24, 0x30, 0x19, 0x12, 0x02, 0x78, 0x00, 0x10};
void ShowNixie(unsigned char Data, unsigned char Location) //按位显示数码管
{
SelectHC573(6);
P0 = 0x01 << Location;
SelectHC573(7);
P0 = Data;
}
void CloseNixie() //关闭全部数码管
{
SelectHC573(6);
P0 = 0xFF;
SelectHC573(7);
P0 = 0xFF;
}
或直接使用以下文件,包括对锁存器、LED、蜂鸣器、继电器、数码管底层的编写
Init.c
#include "reg52.h"
unsigned char temp_1 = 0x00;
unsigned char temp_old_1 = 0xff;
// 0 1 2 3 4 5 6 7 8 9 A B C D E F . -
code unsigned char seg_dula[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90,
0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E, 0x7F, 0xBF};
code unsigned char seg_wela[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
void Led_Disp(unsigned char *ucLed)
{
unsigned char temp = 0x00;
static unsigned char temp_old = 0xff;
unsigned char i = 0;
for(i = 0; i < 8; i++)
{
temp |= (ucLed[i] << i);
}
if(temp != temp_old)
{
P0 = ~temp;
P2 = P2 & 0x1f | 0x80;
P2 &= 0x1f;
temp_old = temp;
}
}
void Beep(unsigned char flag)
{
if(flag)
temp_1 |= 0x40;
else
temp_1 &= ~0x40;
if(temp_1 != temp_old_1)
{
P0 = temp_1;
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
temp_old_1 = temp_1;
}
}
void Relay(unsigned char flag)
{
if(flag)
{
temp_1 |= 0x10;
}
else
{
temp_1 &= ~0x10;
}
if(temp_1 != temp_old_1)
{
P0 = temp_1;
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
temp_old_1 = temp_1;
}
}
void Seg_Disp(unsigned char wela, unsigned char dula, unsigned char point)
{
P0 = 0xff;
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;
P0 = seg_wela[wela];
P2 = P2 & 0x1f | 0xc0;
P2 &= 0x1f;
P0 = seg_dula[dula];
if(point)
P0 &= 0x7f;
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;
}
七、定时器+数码管显示时间
-
实现方案:
-
定时器中断维护系统时钟
-
数码管显示时分秒
-
-
难点:计时精度校准与显示数据同步
#include "reg52.h"
#include "Delay.h"
#include "Nixie.h"
#include "SelectHC573.h"
void InitTimer0() //初始化,16位,定时50ms
{
TMOD = 0x01;
TH0 = (65535 - 50000) / 256;
TL0 = (65535 - 50000) % 256;
ET0 = 1;
EA = 1;
TR0 = 1;
}
unsigned char state = 0;
unsigned char hour = 0;
unsigned char min = 0;
unsigned char sec = 0;
void ServiceTimer0() interrupt 1 //中断服务函数
{
TH0 = (65535 - 50000) / 256;
TL0 = (65535 - 50000) % 256;
state++;
if(state == 20)
{
sec++;
state = 0;
}
if(sec == 60)
{
min++;
sec = 0;
}
if(min == 60)
{
hour++;
min = 0;
}
if(hour == 99)
{
hour = 0;
}
}
void ShowTime() //显示系统运行时间
{
ShowNixie(SMG_duanma[sec % 10], 7);
Delay(1);
ShowNixie(SMG_duanma[sec / 10], 6);
Delay(1);
ShowNixie(SMG_duanma[17], 5);
Delay(1);
ShowNixie(SMG_duanma[min % 10], 4);
Delay(1);
ShowNixie(SMG_duanma[min / 10], 3);
Delay(1);
ShowNixie(SMG_duanma[17], 2);
Delay(1);
ShowNixie(SMG_duanma[hour % 10], 1);
Delay(1);
ShowNixie(SMG_duanma[hour / 10], 0);
Delay(1);
}
八、串口通信 Uart
-
典型用途:调试信息输出、与上位机数据交互
-
关键配置:
-
波特率计算(常用9600/115200)
-
工作模式设置(模式1:8位UART)
-
-
注意:避免在中断中执行耗时操作
main.c
#include <STC15F2K60S2.H>
#include "Init.h"
#include "Uart.h"
#include <stdio.h>
unsigned char Seg_Pos; //数码管扫描专用变量
unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10}; //数码管显示数据存放数组
unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0}; //数码管小数点数据存放数组
unsigned char Led_Pos; //Led扫描专用变量
unsigned char LED_state[8] = {0,0,0,0,0,0,0,0}; //LED的亮灭
//定时器初始化函数
void Timer0Init() //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //开启定时器中断
EA = 1; //开启总中断
}
//中断服务函数
void Timer0Server() interrupt 1
{
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
if(++Seg_Pos == 8) Seg_Pos = 0; //刷新数码管
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
Led_Disp(LED_state); //刷新LED
}
void InitSystem() //系统上电初始化
{
unsigned char i;
for(i = 0; i < 8; i++)
{
LED_state[i] = 0; //关闭LED
Seg_Buf[i] = 10; //关闭数码管
Seg_Point[i] = 0;
}
Beep(0); //关闭蜂鸣器
Relay(0); //关闭继电器
}
float t = 23.56;
void main()
{
InitSystem();
Timer0Init();
UartInit();
sprintf(Uart_Send, "t = %.2f\r\n", t);
SendString(Uart_Send);
while(1)
{
Uart_Ctrl();
}
}
Uart.c
#include <STC15F2K60S2.H>
#include "main.h"
#include "Init.h"
unsigned char Uart_Recv[10]; //串口接收数组
unsigned char Uart_Recv_Index; //串口接受指针
unsigned char Uart_Send[10]; //串口发送数组
unsigned char command; //指令
//串口初始化,直接生成后加上开启中断的代码
void UartInit(void) //9600bps@12.000MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x01; //串口1选择定时器2为波特率发生器
AUXR &= 0xFB; //定时器2时钟为Fosc/12,即12T
T2L = 0xE6; //设定定时初值
T2H = 0xFF; //设定定时初值
AUXR |= 0x10; //启动定时器2
ES = 1; //开启串口中断
EA = 1; //开启总中断
}
void ServiceUart() interrupt 4
{
if(RI == 1) //串口接收数据
{
Uart_Recv[Uart_Recv_Index] = SBUF; //将数据保存到数组
Uart_Recv_Index ++;
RI = 0;
}
}
void SendByte(unsigned char Data) //串口发送一个字节
{
SBUF = Data;
while(TI == 0);
TI = 0;
}
void SendString(unsigned char *str) //串口发送一个字符串
{
while(*str != '\0')
{
SendByte(*str++);
}
}
void Uart_Ctrl() //串口控制
{
if(Uart_Recv_Index == 1) //接收一位并放到数组里
{
Uart_Recv_Index = 0;
command = Uart_Recv[0] - 48; //数组里存放的是字符,根据Ascall表-48得到数字
Seg_Buf[0] = command / 10; //在数码管上显示指令
Seg_Buf[1] = command % 10;
SendByte(Uart_Recv[0]); //串口接收显示字符
if(Uart_Recv[0] == 'A') //根据指令控制LED
{
LED_state[0] = 1;
LED_state[1] = 0;
}
else if(Uart_Recv[0] == 'B')
{
LED_state[0] = 0;
LED_state[1] = 1;
}
}
}
九、DS18B20温度传感器
-
协议特点:单总线通信(严格时序要求)
-
开发要点:
-
初始化序列(存在脉冲检测)
-
温度数据读取与浮点转换(注意符号位处理)
-
-
常见问题:总线冲突导致读取失败
-
注意:蓝桥杯比赛过程中会给出onewire文件代码,注意修改文件中的Delay_OneWire函数,与系统频率保持一致,然后在DS18B20中直接调用即可
//单总线内部延时函数
void Delay_OneWire(unsigned int t)
{
t *= 12; //这句要在原文件加上,否则无法正确读取温度数据!!!
while(t--);
}
#include "reg52.h"
#include "onewire.h"
// 0 1 2 3 4 5 6 7 8 9 A B C D E F . -
unsigned char code SMG_duanma[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90,
0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E, 0x7F, 0xBF};
//数码管显示带小数点 0-9
unsigned char code SMG_duanmaDot[] = {0x40, 0x79, 0x24, 0x30, 0x19, 0x12, 0x02, 0x78, 0x00, 0x10};
unsigned int Tem = 0;
sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;
void SelectHC573(unsigned char Channel) //选中Y(n)
{
switch(Channel)
{
case(4): //用于控制LED灯,通过锁存器控制LED的亮灭
HC138_C = 1; HC138_B = 0; HC138_A = 0;
break;
case(5): //用于控制蜂鸣器、继电器等功能
HC138_C = 1; HC138_B = 0; HC138_A = 1;
break;
case(6): //用于控制数码管的位选,决定数码管的哪一位被选中
HC138_C = 1; HC138_B = 1; HC138_A = 0;
break;
case(7): //用于控制数码管的段选,决定数码管的显示内容
HC138_C = 1; HC138_B = 1; HC138_A = 1;
break;
}
}
void InitSystem()
{
SelectHC573(5); //上电初始化,关闭蜂鸣器和继电器
P0 = 0x00;
SelectHC573(4); //上电初始化,LED保持熄灭状态
P0 = 0xFF;
}
void ShowNixie(unsigned char Data, unsigned char Location)
{
SelectHC573(6);
P0 = 0x01 << Location;
SelectHC573(7);
P0 = Data;
}
void CloseNixie()
{
SelectHC573(6);
P0 = 0xFF;
SelectHC573(7);
P0 = 0xFF;
}
void Delay_SMG(unsigned int t)
{
while(t--);
}
void ShowTem()
{
ShowNixie(SMG_duanma[Tem % 10], 7);
Delay_SMG(100);
ShowNixie(SMG_duanmaDot[Tem % 100 / 10], 6);
Delay_SMG(100);
ShowNixie(SMG_duanma[Tem / 100], 5);
Delay_SMG(100);
ShowNixie(0xFF, 4);
Delay_SMG(100);
ShowNixie(0xFF, 3);
Delay_SMG(100);
ShowNixie(0xFF, 2);
Delay_SMG(100);
ShowNixie(0xFF, 1);
Delay_SMG(100);
ShowNixie(0xFF, 0);
Delay_SMG(100);
CloseNixie();
}
//延时函数,同时保证数码管持续显示
void Delay_1(unsigned int t)
{
while(t--)
{
ShowTem();
}
}
void Read_DS18B20_Tem()
{
unsigned char LSB, MSB;
init_ds18b20(); //初始化
Write_DS18B20(0xCC); //跳过ROM指令
Write_DS18B20(0x44); //开启转换
// Delay_1(1000); //等待转换(不建议加,会阻塞主循环)
init_ds18b20(); //复位
Write_DS18B20(0xCC); //跳过ROM指令
Write_DS18B20(0xBE); //读取温度转化数据
LSB = Read_DS18B20(); //提取数据低8位
MSB = Read_DS18B20(); //提取数据高8位
Tem = MSB;
Tem = (Tem << 8) | LSB; //将转换数据合并位16位的Tem
if((Tem & 0xF800) == 0x0000) //判断Tem是否位正数,Tem高5位为1表示正
{
Tem >>= 4; //移出小数部分,Tem低4位为小数部分
Tem = Tem * 10; //整数部分乘10,实现保留一位小数
Tem = Tem + (LSB & 0x0F) * 0.625; //将小数部分加到整数部分上,得到最终的温度值
}
}
void main()
{
InitSystem();
while(1)
{
Read_DS18B20_Tem();
ShowTem();
}
}
十、DS1302实时时钟
-
功能特性:SPI三线通信、后备电池供电
-
核心操作:
-
时钟寄存器读写
-
写保护位控制
-
-
注意:蓝桥杯比赛过程中会给出ds1302示例代码,以下代码是对该文件的延申
#include "ds1302.h"
//DS1302写地址
unsigned char Write_DS1302_Addr[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C};
//DS1302读地址
unsigned char Read_DS1302_Addr[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8B, 0x8D};
//存放时间数组,25年3月11日,星期二,23点59分31秒
unsigned char Timer[7] = {0x31, 0x59, 0x23, 0x11, 0x03, 0x02, 0x25};
void DS1302_Config() //将时间数据写入DS1302
{
unsigned char i;
Write_Ds1302_Byte(0x8E, 0x00); //关闭写保护
for(i=0; i<7; i++)
{
Write_Ds1302_Byte(Write_DS1302_Addr[i], Timer[i]);
}
Write_Ds1302_Byte(0x8E, 0x80); //开启写保护
}
void Read_DS1302() //将DS1302中的数据读出
{
unsigned char i;
for(i=0; i<7; i++)
{
Timer[i] = Read_Ds1302_Byte(Read_DS1302_Addr[i]);
}
}
十一、555定时器与频率测量
-
测量原理:在蓝桥杯中,利用555定时器产生方波信号,通过单片机的定时器进行频率测量。具体方法是将555定时器输出的方波信号连接到单片机的定时器计数输入引脚,然后使用定时器0进行计数,定时器1进行定时。当定时器1经过1秒后,定时器0的计数值即为555定时器的信号频率
-
硬件连接:将555定时器的输出信号引脚SIGNAL通过跳线帽连接到单片机的P3.4引脚
-
调节方式:在蓝桥杯单片机板子上,555定时器主要作为信号发生电路,通过调节电位器Rb3可产生频率不同的方波
#include "reg52.h"
#include "InitSystem.h"
#include "Nixie.h"
#include "Delay.h"
unsigned int count_f = 0; //用于计算频率
unsigned int data_f = 0; //用于缓存频率数据
unsigned char count_t = 0; //用于定时时间控制
void Init_Timer() //定时器初始化
{
//定时器0用作计数,8位重装方式
TH0 = 0xFF;
TL0 = 0xFF;
//定时器1用作定时,时间为50ms
TH1 = (65535 - 50000 + 1) / 256;
TL1 = (65535 - 50000 + 1) % 256;
TMOD = 0x16; //设置定时器模式,低4位为定时器0模式,高4位为定时器1模式
//0x16表示:定时器0为模式1(8位自动重装方式),定时器1为模式2(16位定时器模式)
ET0 = 1; //使能定时器0中断
ET1 = 1; //使能定时器1中断
EA = 1; //使能全局中断
TR0 = 1; //启动定时器0
TR1 = 1; //启动定时器1
}
void ServiceTimer0() interrupt 1 //定时器0中断服务函数,计数加一进入中断
{
count_f++;
}
void ServiceTimer1() interrupt 3 //定时器1中断服务函数
{
TH1 = (65535 - 50000 + 1) / 256;
TL1 = (65535 - 50000 + 1) % 256;
count_t++;
if(count_t == 20) //定时1s
{
data_f = count_f; //将count_f的值保存到data_f中
count_f = 0; //count_f清零重新开始计数
count_t = 0; //count_t清零重新开始定时
}
}
void ShowFreq() //在数码管上显示频率测量值
{
ShowNixie(SMG_duanma[15], 0); //在第一位数码管显示 F
Delay(1);
ShowNixie(0xFF, 1); //第二位数码管不作显示
Delay(1);
ShowNixie(0xFF, 2); //第二位数码管不作显示
Delay(1);
if(data_f > 9999) //后五位用于频率值显示
{
ShowNixie(SMG_duanma[data_f / 10000], 3);
Delay(1);
}
if(data_f > 999)
{
ShowNixie(SMG_duanma[data_f / 1000 % 10], 4);
Delay(1);
}
if(data_f > 99)
{
ShowNixie(SMG_duanma[data_f / 100 % 10], 5);
Delay(1);
}
if(data_f > 9)
{
ShowNixie(SMG_duanma[data_f / 10 % 10], 6);
Delay(1);
}
ShowNixie(SMG_duanma[data_f % 10], 7);
Delay(1);
CloseNixie();
}
void main()
{
InitSystem();
Init_Timer();
while(1)
{
ShowFreq();
}
}
十二、PCF8591的DAC模拟电压输出
-
基本原理:DAC功能是将数字信号转换为模拟电压。PCF8591内部有一个数模转换器,它接收输入的数字信号(通常是8位),并将其转换为对应的模拟电压值输出。
-
实现功能:模式1中,数码管左边显示“-1-”,DAC输出固定电压2.00V。
模式2中,数码管左边显示“-2-”,DAC输出固定电压4.00V。
模式3中,数码管左边显示“-3-”,数码管右边显示PCF8591芯片AIN3通道的实时输入电压,并将该电压作为DAC的输出参数,使DAC的输出电压和PCF8591芯片AIN3通道的输入电压保持同步变化,通过改变Rb2可调电阻可以改变DAC的输出电压。
#include "reg52.h"
#include "InitSystem.h"
#include "Delay.h"
#include "Nixie.h"
#include "iic.h"
#include "Delay.h"
sbit S4 = P3^3;
unsigned char adc_value = 0; //AIN3的采样数据
float adc_volt = 0; //AIN3的换算电压
unsigned int smg_volt = 0; //AIN3的显示电压
unsigned char mode = 1; //模式(1:2V输出,2:4V输出,3:AIN3输入)
void ShowADC() //数码管显示函数
{
ShowNixie(SMG_duanma[17], 0);
Delay(1);
ShowNixie(SMG_duanma[mode], 1);
Delay(1);
ShowNixie(SMG_duanma[17], 2);
Delay(1);
ShowNixie(0xFF, 3);
Delay(1);
ShowNixie(0xFF, 4);
Delay(1);
ShowNixie(SMG_duanmaDot[smg_volt / 100], 5);
Delay(1);
ShowNixie(SMG_duanma[smg_volt / 10 % 10], 6);
Delay(1);
ShowNixie(SMG_duanma[smg_volt % 10], 7);
Delay(1);
CloseNixie();
}
void Read_PCF8591_AIN3() // 电压采样处理函数,用于读取PCF8591的AIN3通道电压
{
IIC_Start(); // 发起I2C总线的起始信号
IIC_SendByte(0x90); // 发送设备地址和写操作位
IIC_WaitAck(); // 等待设备响应ACK信号
IIC_SendByte(0x43); // 发送控制字节0x43,选择AIN3通道并配置其他相关参数
IIC_WaitAck();
IIC_Stop(); // 发送I2C总线的停止信号
ShowADC(); // 等待电压转换完成
IIC_Start();
IIC_SendByte(0x91); // 发送设备地址和读操作位
IIC_WaitAck();
adc_value = IIC_RecByte(); // 读取AD采样信号
IIC_SendAck(1); // 产生非应答信号
IIC_Stop();
//将ADC采样到的数据换算成对应的电压值
adc_volt = adc_value / 255.0 * 5.0;
smg_volt = adc_volt * 100;
}
void Set_PCF8591(unsigned char Data) //PCF8591电压输出设置函数
{
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(0x43);
IIC_WaitAck();
IIC_SendByte(Data); //设置DAC电压输出参数
IIC_WaitAck();
IIC_Stop();
}
void Scan_Keys() //按键扫描函数
{
static bit key_lock = 0; //按键锁标志(减少按键误判)
if((S4 == 0) && (key_lock == 0)) //检测到按键按下
{
key_lock = 1; //加锁
Delay(20); //按键消抖
if(S4 == 0)
{
mode++; //模式切换逻辑
if(mode > 3) mode = 1;
// 根据模式设置输出
switch(mode)
{
case 1: //模式1中,数码管左边显示“-1-”,DAC输出固定电压2.00V
Set_PCF8591(102);
smg_volt = 200;
break;
case 2: //模式2中,数码管左边显示“-2-”,DAC输出固定电压4.00V
Set_PCF8591(204);
smg_volt = 400;
break;
case 3: //模式3中,数码管左边显示“-3-”,显示PCF8591芯片AIN3通道的实时输入电压
break;
}
}
}
else if((S4 == 1) && (key_lock == 1)) //按键未按下时,key_lock恢复为0
{
key_lock = 0;
}
}
void main()
{
InitSystem();
while(1)
{
if(mode == 3) //当处于模式3时
{
Read_PCF8591_AIN3(); //读取PCF8591的AIN3通道电压
}
Scan_Keys();
ShowADC();
}
}
十三、AT24C02基本读写操作
-
存储容量与组织:24C02是一款2K位的串行EEPROM存储器,总容量为256字节。存储空间被组织为8位宽的字节,每个字节都有唯一的地址,地址范围从0x00到0xFF。
-
接口与通信:24C02采用I²C总线接口,通过两条线(SDA和SCL)与微控制器或其他设备通信。SDA为串行数据线,SCL为串行时钟线。
-
实现功能:AT24C02读写按键触发数
#include "reg52.h"
#include "Nixie.h"
#include "Delay.h"
#include "InitSystem.h"
#include "iic.h"
sbit S4 = P3^3;
sbit S5 = P3^2;
sbit S6 = P3^1;
unsigned char data1 = 0, data2 = 0, data3 = 0;
void Show_AT24C02() //在数码管上显示存储在AT24C02中的数据
{
ShowNixie(SMG_duanma[data1 / 10], 0); //显示data1
Delay(1);
ShowNixie(SMG_duanma[data1 % 10], 1);
Delay(1);
ShowNixie(SMG_duanma[17], 2); //显示 -
Delay(1);
ShowNixie(SMG_duanma[data2 / 10], 3); //显示data2
Delay(1);
ShowNixie(SMG_duanma[data2 % 10], 4);
Delay(1);
ShowNixie(SMG_duanma[17], 5); //显示 -
Delay(1);
ShowNixie(SMG_duanma[data3 / 10], 6); //显示data3
Delay(1);
ShowNixie(SMG_duanma[data3 % 10], 7);
Delay(1);
}
void Write_AT24C02(unsigned char Addr, unsigned char Data) // 向AT24C02写入数据的函数
{
IIC_Start(); // 发送IIC起始信号
IIC_SendByte(0xA0); // 发送设备地址字节(写操作),0xA0是AT24C02的设备地址加上写操作位
IIC_WaitAck(); // 等待设备响应ACK信号
IIC_SendByte(Addr); // 发送存储器地址字节,指定要写入的存储器位置
IIC_WaitAck(); // 等待设备响应ACK信号
IIC_SendByte(Data); // 发送要写入的数据字节
IIC_WaitAck(); // 等待设备响应ACK信号
IIC_Stop(); // 发送IIC停止信号
}
unsigned char Read_AT24C02(unsigned char Addr) // 从AT24C02读取数据的函数
{
unsigned char Temp; // 用于存储读取到的数据
IIC_Start(); // 发送IIC起始信号
IIC_SendByte(0xA0); // 发送设备地址字节(写操作),用于指定存储器地址
IIC_WaitAck(); // 等待设备响应ACK信号
IIC_SendByte(Addr); // 发送存储器地址字节,指定要读取的存储器位置
IIC_WaitAck(); // 等待设备响应ACK信号
IIC_Start(); // 再次发送IIC起始信号,进入读操作
IIC_SendByte(0xA1); // 发送设备地址字节(读操作),0xA1是AT24C02的设备地址加上读操作位
IIC_WaitAck(); // 等待设备响应ACK信号
Temp = IIC_RecByte(); // 读取一个字节的数据,存储到Temp变量中
IIC_SendAck(1); // 发送NACK信号(1表示NACK),表示不再读取更多数据
IIC_Stop(); // 发送IIC停止信号
return Temp; // 返回读取到的数据
}
void Scan_Keys()
{
//按键S4的扫描处理
if(S4 == 0)
{
Delay(20); //去抖动
if(S4 == 0)
{
data1++; //按键次数增加
if(data1 > 13) //大于13恢复0
{
data1 = 0;
}
Write_AT24C02(0x00, data1); //写入24C02
while(S4 == 0) //松手检测
{
Show_AT24C02(); //保持数码管动态刷新
}
}
}
//按键S5的扫描处理
if(S5 == 0)
{
Delay(20);
if(S5 == 0)
{
data2++;
if(data2 > 13)
{
data2 = 0;
}
Write_AT24C02(0x01, data2);
while(S5 == 0)
{
Show_AT24C02();
}
}
}
//按键S6的扫描处理
if(S6 == 0)
{
Delay(20);
if(S6 == 0)
{
data3++;
if(data3 > 13)
{
data3 = 0;
}
Write_AT24C02(0x02, data3);
while(S6 == 0)
{
Show_AT24C02();
}
}
}
}
void main()
{
InitSystem();
while(1)
{
Show_AT24C02();
Scan_Keys();
}
}
十四、超声波测距
-
测距原理
超声波测距的基本原理是利用超声波在空气中的传播速度相对稳定这一特性。具体步骤如下:
-
发射超声波:超声波模块通过单片机控制,从发射端(如P1.0端口)向某一方向发射超声波,通常发射频率为40kHz。
-
接收反射波:发射出去的超声波在传播过程中遇到障碍物后会反射回来,被接收端(如P1.1端口)接收。
-
计算时间差:从发射超声波开始计时,到接收到反射波停止计时,这段时间就是超声波从发射到返回的总时间。
-
计算距离:根据公式“距离 = 声速 × 时间 / 2”计算出距离,其中声速在空气中约为340m/s。
-
硬件连接
在蓝桥杯单片机竞赛综合平台CT107D中,超声波模块的TX引脚接到单片机的P1.0端口,RX引脚接到单片机的P1.1端口。使用前需正确配置跳线帽,将J2 2x3的排针的A1和A相连,B1与B相连。
ultrasonic.c
#include <STC15F2K60S2.H>
#include <intrins.h>
#include "Init.h"
#include "main.h"
sbit Tx = P1^0;
sbit Rx = P1^1;
unsigned char Wave_Data;
void ShowWave() //超声波测距显示
{
Seg_Point[5] = 0;
Seg_Point[6] = 0;
Seg_Buf[0] = 7; // 显示 7
Seg_Buf[1] = 10; // 不显示
Seg_Buf[2] = 10; // 不显示
Seg_Buf[3] = 10; // 不显示
Seg_Buf[4] = 10; // 不显示
if (Wave_Data > 99)
Seg_Buf[5] = Wave_Data / 100; // 显示距离的百位
else
Seg_Buf[5] = 10;
Seg_Buf[6] = Wave_Data % 100 / 10; // 显示距离的十位
Seg_Buf[7] = Wave_Data % 10; // 显示距离的个位
}
void Delay12us() //@12.000MHz,软件延时12us
{
unsigned char i;
_nop_();
_nop_();
i = 33;
while (--i);
}
void Wave_Init() //超声波初始化函数,产生八个40MHz的方波信号
{
unsigned char i;
for(i=0; i<8; i++)
{
Tx = 1;
Delay12us();
Tx = 0;
Delay12us();
}
}
unsigned char Wave_Read() // 超声波距离读取函数
{
unsigned int time; // 用于存储计时结果的时间变量
TMOD &= 0xF0; // 设置定时器模式寄存器,清除定时器0的模式位,保留定时器1的模式
TH0 = TL0 = 0; // 清除定时器0的高字节和低字节寄存器,将定时器0的计数值清零
Wave_Init(); // 调用Wave_Init函数,发送脉冲信号启动超声波模块
TR0 = 1; // 启动定时器0开始计时
while((Rx == 1) && (TF0 == 0)); // 等待接收引脚Rx变为低电平,表示超声波模块开始发送回波信号,或者等待定时器0溢出(TF0=1)
TR0 = 0; // 停止定时器0计时
if(TF0 == 0) // 如果定时器0没有溢出,说明成功接收到回波信号
{
time = TH0 << 8 | TL0; // 将定时器0的高字节和低字节合并,得到完整的计时结果
return (time * 0.017); // 根据超声波测距公式计算距离,返回结果
}
else // 如果定时器0溢出,说明超声波模块没有在规定时间内接收到回波信号,可能是超出测量范围或出现错误
{
TF0 = 0; // 清除定时器0的溢出标志
return 0; // 返回0表示测距失败
}
}
main.c
#include "reg52.h"
#include "ultrasonic.h"
#include "InitSystem.h"
#include "Nixie.h"
#include "Delay.h"
unsigned char Wave_Data;
void ShowData()
{
ShowNixie(SMG_duanma[Wave_Data / 100], 0);
Delay(1);
ShowNixie(SMG_duanma[Wave_Data / 10 % 10], 1);
Delay(1);
ShowNixie(SMG_duanma[Wave_Data % 10], 2);
Delay(1);
ShowNixie(0xFF, 3);
Delay(1);
ShowNixie(0xFF, 4);
Delay(1);
ShowNixie(0xFF, 5);
Delay(1);
ShowNixie(0xFF, 6);
Delay(1);
ShowNixie(0xFF, 7);
Delay(1);
CloseNixie();
}
void main()
{
InitSystem();
while(1)
{
Wave_Data = Wave_Read();
ShowData();
}
}
十五、主函数完整代码 main
包含所有考点,掌握即拿奖!
#include <STC15F2K60S2.H> //包含STC15F2K60S2单片机的头文件
#include "Init.h" //初始化配置,包括LED、数码管等
#include "Delay.h" //延时函数
#include "MatrixKey.h" //矩阵键盘
#include "DS18B20.h" //温度数据处理
#include "Parameter.h" //参数设置
#include "My1302.h" //时间数据处理
#include "ADC.h" //ADC转换
#include "NE555.h" //频率测量
#include "ultrasonic.h" //超声波测距
#include "Uart.h" //串口
#include "AT24C02.h" //EEPROM保存
unsigned char Seg_Pos; //数码管扫描专用变量
unsigned char Seg_count = 0; //数码管刷新计数
unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10}; //数码管显示数据存放数组
unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0}; //数码管小数点数据存放数组
unsigned char Led_Pos; //Led扫描专用变量
unsigned char LED_state[8] = {0,0,0,0,0,0,0,0}; //LED的亮灭
unsigned int count = 0; //定时器计数
unsigned char state = 0; //界面状态(0为温度界面,1为参数界面,2为时间界面)
unsigned char flag_eeprom = 0xAA;//EEPROM标志位
unsigned char count_pwm = 0; //pwm计数
unsigned char pwm_duty = 0; //pwm占空比
unsigned char pwm_mode = 0; //pwm模式
//定时器初始化函数
void Timer1Init() //1毫秒@12.000MHz
{
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x18; //设置定时初始值
TH1 = 0xFC; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1; //开启定时器中断
EA = 1; //开启总中断
}
//中断服务函数
void Timer1Server() interrupt 3
{
TL1 = 0x18; //设置定时初始值
TH1 = 0xFC; //设置定时初始值
if(++Seg_count == 3) //计时3ms
{
//数码管、LED刷新
if(++Seg_Pos == 8) Seg_Pos = 0; //刷新数码管
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
Led_Disp(LED_state); //刷新LED
Seg_count = 0;
}
//NE555频率测量
count_t ++;
if(count_t == 1000) //定时1s
{
data_f = count_f; //将count_f的值保存到data_f中
count_f = 0; //count_f清零重新开始计数
count_t = 0; //count_t清零重新开始定时
}
//PWM(这里需要改成0.1ms定时)
count_pwm ++;
if(count_pwm <= pwm_duty)
{
LED_state[3] = 1; //开启L4
}
else if(count_pwm < 100)
{
LED_state[3] = 0; //熄灭L4
}
else if(count_pwm == 100)
{
count_pwm = 0;
}
}
void InitSystem() //系统上电初始化
{
unsigned char i;
for(i = 0; i < 8; i++)
{
LED_state[i] = 0; //关闭LED
Seg_Buf[i] = 10; //关闭数码管
Seg_Point[i] = 0;
}
Beep(0); //关闭蜂鸣器
Relay(0); //关闭继电器
}
void LED_Ctrl() //LED控制
{
switch(state) //根据状态控制灯光
{
case 0:
LED_state[0] = 1;
LED_state[1] = 0;
LED_state[2] = 0;
break;
case 1:
LED_state[0] = 0;
LED_state[1] = 1;
LED_state[2] = 0;
break;
case 2:
LED_state[0] = 0;
LED_state[1] = 0;
LED_state[2] = 1;
break;
case 3:
LED_state[0] = 0;
LED_state[1] = 0;
LED_state[2] = 0;
break;
}
}
void Keys_Scan() //按键扫描
{
unsigned char KeyNum; //存放矩阵键盘返回值
KeyNum = MatrixKey();
if(KeyNum != 0xFF) //读取到有效数据
{
if(KeyNum == 4) //按下S4切换显示界面
{
state ++;
if(state > 7) state = 0;
}
if(state == 1) //在参数设置界面下
{
if(KeyNum == 8) //按下S8参数+1
{
parameter ++;
}
if(KeyNum == 9) //按下S9参数-1
{
parameter --;
}
if(KeyNum == 10)//按下S10保存参数值
{
Write_AT24C02(0x00, parameter); //向AT24C02的0地址写入参数值
Delay(100); //注意连续的写入需要加入延时
flag_eeprom = 0x55; //标志位置0x55说明不是首次上电
Write_AT24C02(0x01, flag_eeprom);//向AT24C02的1地址写入标志位
Delay(100);
}
}
/* 按键控制PWM输出模式
if(KeyNum == 7)
{
switch(pwm_mode)
{
case 0:
LED_state[3] = 1;
TR0 = 1;
pwm_duty = 10;
pwm_mode = 1;
break;
case 1:
pwm_duty = 50;
pwm_mode = 2;
break;
case 2:
pwm_duty = 90;
pwm_mode = 3;
break;
case 3:
LED_state[3] = 0;
TR0 = 0;
pwm_mode = 0;
break;
}
}
*/
}
}
void main()
{
InitSystem(); //系统上电初始化
// Timer0Init(); //定时器0初始化(NE555计数、超声波测距,不可同时使用)
Timer1Init(); //定时器1初始化(LED、数码管刷新、NE555计时)
UartInit(); //串口初始化(使用定时器2)
DS1302_Config();//DS1302初始配置,写入初始时间
flag_eeprom = Read_AT24C02(0x01);
if(flag_eeprom == 0x55)
parameter = Read_AT24C02(0x00); //从AT24C02中读取参数值
while(1)
{
Read_DS18B20_Tem(); //读取温度数据
Read_DS1302(); //读取时间数据
//注意这里需要连续读取两次
Read_PCF8591_AIN1();//读取通道1返回值
Read_PCF8591_AIN1();
Read_PCF8591_AIN3();//读取通道3返回值
Read_PCF8591_AIN3();
Wave_Data = Wave_Read();//读取超声波测距数据
Uart_Ctrl(); //串口控制
LED_Ctrl(); //LED控制
Keys_Scan(); //按键扫描
switch(state) //根据状态切换显示界面
{
case 0:
if(Tem != 850) //跳过上电显示85℃
ShowTem(); //显示温度界面
break;
case 1:
ShowParameter();//显示参数设置界面
break;
case 2:
ShowTime(); //显示时间数据
break;
case 3:
ShowADC_1();//显示ADC1数据
break;
case 4:
ShowADC_3();//显示ADC3数据
break;
case 5:
ShowFreq();//显示频率
break;
case 6:
ShowPeriod();//显示周期
break;
case 7:
ShowWave();//显示测距
break;
}
}
}
总结
本文梳理的模块覆盖蓝桥杯单片机赛题90%以上的核心考点,通过模块化设计可快速适配不同赛题需求。建议将模板作为开发基石,根据具体任务灵活组合功能模块。
📌 创作声明:
-
代码部分参考官方示例代码
-
开发环境:Keil uVision5 + IAP15F2K61S2
-
原创内容转载请注明出处
愿每一份代码都能燃烧出梦想的火花,我们赛场见! ✨