感谢风哥,让我能拿到30多名成绩❥(^_-)
代码具体讲解可以看b站up西风和左岚的视频,我在这里只是大概总结一下
这里有个链接是基于目前文章的模版编写的模块集合并运用
总概
第一 模板底层是比赛的基础
第二 代码逻辑是比赛拿奖的关键
第三 低频考点 ≠ 不考(超声波,串口)
毕竟比赛的时候会不会考到谁都说不准
第四 考试时不要因为一个小的模块就丢失了整体代码的逻辑
如: led没写出来也不要影响到数码管按键等模块的代码
备注:因为15届官方新出的赛点资源包有改变,所以我也修改了文章内容
如有错误还请指出,不胜感激!
还需要注意这个引脚鼠标左键点一下就会出来具体信息
一、基础底层模板
1.1 main.c 主函数模板
现在主函数优先使用定时器1
因为这样就不用管什么题,直接无脑定时器1就行了
/* 头文件声明区 */
#include <STC15F2K60S2.H>
#include "Init.h"
#include "Key.h"
#include "Seg.h"
#include "LED.h"
/* 基础变量创建区 */
unsigned int Slow_Down; //减速扫描专用
bit Key_Slow_Flag, Seg_Slow_Flag, LED_Slow_Flag, Infor_Slow_Flag; //按键数码管LED信息处理减速标志位
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_Buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; //LED显示缓冲区
unsigned char i; //循环专用变量
/* 变量创建区 */
/* 按键处理函数 */
void Key_Deal()
{
static unsigned char Key_New = 0, Key_Old = 0; //按键获取
static unsigned char Key_UP = 0, Key_Down = 0; //专用变量
if (Key_Slow_Flag) return; //减速
Key_Slow_Flag = 1;
Key_New = Key_Read(); //获取键码
Key_UP = ~Key_New & (Key_New ^ Key_Old); //捕获上升沿
Key_Down = Key_New & (Key_New ^ Key_Old); //捕获下降沿
Key_Old = Key_New; //辅助扫描
switch (Key_Down)
{
case 4:
break;
}
}
/* 数码管处理函数 */
void Seg_Deal()
{
if (Seg_Slow_Flag) return;
Seg_Slow_Flag = 1;
}
/* LED处理函数 */
void LED_Deal()
{
if (LED_Slow_Flag) return; //LED减速,可以不要,但题目如果出现流水灯之类的
LED_Slow_Flag = 1; //大家灵活应变,把减速去掉
}
/* 信息处理函数 */
void Infor_Deal()
{
/* 信息输出区 */
/* 减速 */
if (Infor_Slow_Flag) return;
Infor_Slow_Flag = 1;
/* 信息处理区 */
}
/* 定时器初始化函数 */
void Timer1_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初值
TH1 = 0xFC; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
EA = 1;
ET1 = 1;
}
/* 中断服务函数 */
void Timer1_Serve() interrupt 3
{
if (++Slow_Down == 400) Slow_Down = Infor_Slow_Flag = 0;
if (Slow_Down % 10 == 0) Key_Slow_Flag = 0;
if (Slow_Down % 20 == 0) LED_Slow_Flag = 0;
if (Slow_Down % 100 == 0) Seg_Slow_Flag = 0;
Seg_Show(Slow_Down%8, Seg_Buf[Slow_Down%8], Seg_Point[Slow_Down%8]);
LED_Show(Slow_Down%8, LED_Buf[Slow_Down%8]);
}
/* 主函数 */
void main()
{
System_Init();
Timer1_Init();
while (1)
{
Infor_Deal();
Key_Deal();
Seg_Deal();
LED_Deal();
}
}
1.2 Init.c 系统底层
#include <STC15F2K60S2.H>
#include "Init.h"
void System_Init()
{
P0 = 0x00; //关闭蜂鸣器等
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
P0 = 0xff; //关闭蜂鸣器等
P2 = P2 & 0x1f | 0x80;
P2 &= 0x1f;
}
1.3 Key.c 按键底层
#include <STC15F2K60S2.H>
#include "Key.h"
/* 矩阵按键KBD */
unsigned char Key_Read()
{
unsigned char temp = 0;
ET0 = 0;
P34 = 0; P35 = 1; P42 = 1; P44 = 1;
if (P30 == 0) temp = 19;
if (P31 == 0) temp = 18;
if (P32 == 0) temp = 17;
if (P33 == 0) temp = 16;
P34 = 1; P35 = 0; P42 = 1; P44 = 1;
if (P30 == 0) temp = 15;
if (P31 == 0) temp = 14;
if (P32 == 0) temp = 13;
if (P33 == 0) temp = 12;
P34 = 1; P35 = 1; P42 = 0; P44 = 1;
if (P30 == 0) temp = 11;
if (P31 == 0) temp = 10;
if (P32 == 0) temp = 9;
if (P33 == 0) temp = 8;
P34 = 1; P35 = 1; P42 = 1; P44 = 0;
if (P30 == 0) temp = 7;
if (P31 == 0) temp = 6;
if (P32 == 0) temp = 5;
if (P33 == 0) temp = 4;
P3 = 0xff;
ET0 = 1;
return temp;
}
/* 独立按键BTN */
/*
unsigned char Key_Read()
{
unsigned char temp = 0;
if (P30 == 0) temp = 7;
if (P31 == 0) temp = 6;
if (P32 == 0) temp = 5;
if (P33 == 0) temp = 4;
return temp;
}
*/
1.4 Seg.c 数码管底层
#include <STC15F2K60S2.H>
#include "Seg.h"
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xff, //熄灭
0xbf, //-
0x88, //A
0x83, //b
0xc6, //C
0xa1, //d
0x86, //E
0x8e //F
};
void Seg_Show(unsigned char addr, Table, Point)
{
P0 = 0xff; //消影
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;
P0 = 0x01 << addr; //位选
P2 = P2 & 0x1f | 0xc0;
P2 &= 0x1f;
P0 = Seg_Table[Table]; //段选
if (Point)
P0 &= 0x7f; //小数点
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;
}
1.5 LED.c LED底层
注意LED不要和蜂鸣器继电器共用变量
#include <STC15F2K60S2.H>
#include "LED.h"
/* LED显示函数 */
void LED_Show(unsigned char addr, enable)
{
static unsigned char temp_new_LED = 0x00;
static unsigned char temp_old_LED = 0xFF;
if (enable)
temp_new_LED |= 0x01 << addr;
else
temp_new_LED &= ~(0x01 << addr);
if (temp_new_LED != temp_old_LED)
{
P0 = ~temp_new_LED;
P2 = P2 & 0x1f | 0x80;
P2 &= 0x1f;
temp_old_LED = temp_new_LED;
}
}
/* 蜂鸣器继电器专用 */
unsigned char temp_new = 0x00;
unsigned char temp_old = 0xFF;
// 0-关闭 1-开启
/* 蜂鸣器控制函数 */
void Beep(unsigned char flag)
{
if (flag)
temp_new |= 0x40;
else
temp_new &= ~(0x40);
if (temp_new != temp_old)
{
P0 = temp_new;
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
temp_old = temp_new;
}
}
/* 继电器控制函数 */
void Relay(unsigned char flag)
{
if (flag)
temp_new |= 0x10;
else
temp_new &= ~(0x10);
if (temp_new != temp_old)
{
P0 = temp_new;
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
temp_old = temp_new;
}
}
二、模块内容
2.0 模块总概
我这里只有我们需要自己编写的函数
在15届的底层驱动代码的有点改变但是不多,对于我们的底层代码是不需要改变的
官方在驱动代码中并没有给出对应模块的.h和引脚定义
所以在比赛时我们需要自己写一个.h文件,
而引脚定义因为15届原理图的改变可以很方便的找到对应引脚
但我们需要注意超声波的引脚定义,这里不用像15届之前的原理图一样和对应的相反
我在下面超声波板块会详细说明
2.1 DS1302 时钟芯片(十进制版)
这样写底层,在主程序中就就可以定义10进制的时钟数组eg:Rtc[3] = {23, 59, 55};
/* # DS1302代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <STC15F2K60S2.H>
#include <intrins.h>
#include "ds1302.h"
sbit SCK = P1^7;
sbit SDA = P2^3;
sbit RST = P1^3;
//
void DS1302_Write_Rtc(unsigned char *pRtc)
{
unsigned char i = 0; //循环专用变量
unsigned char temp = 0x00; //写入中间变量
Write_Ds1302_Byte(0x8e, 0x00); //关闭写保护
for(i = 0; i < 3; i++)
{
temp = ((pRtc[i]/10) << 4) | (pRtc[i] % 10);//转化为16进制
Write_Ds1302_Byte(0x84 - 2*i, temp);//写入时钟
}
Write_Ds1302_Byte(0x8e, 0x80); //开启写保护
}
//
void DS1302_Read_Rtc(unsigned char *pRtc)
{
unsigned char i = 0; //循环专用变量
for(i = 0; i < 3; i++)
pRtc[i] = (Read_Ds1302_Byte(0x85-2*i)/16*10) + (Read_Ds1302_Byte(0x85-2*i)%16);//读取时间,转化为10进制
}
2.2 DS18B20 温度转换芯片
/* # 单总线代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <STC15F2K60S2.H>
#include "onewire.h"
sbit DQ = P1^4;
float DS18B20_Read_T()
{
unsigned char low, high;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
low = Read_DS18B20();
high = Read_DS18B20();
return (((high << 8) | low) / 16.0);
}
2.3 PCF8591 AD-DA转换
/* # I2C代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <STC15F2K60S2.H>
#include <intrins.h>
#include "iic.h"
sbit sda = P2^1;
sbit scl = P2^0;
//
unsigned char AD_Read(unsigned char addr)
{
unsigned char Temp;
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0x91);
I2CWaitAck();
Temp = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return Temp;
}
//
void DA_Write(unsigned char dat)
{
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(0x41);
I2CWaitAck();
I2CSendByte(dat);
I2CWaitAck();
I2CStop();
}
2.4 NE555 频率测量
ne555会用到定时器0,所以当考到ne555的时候,主程序要选择其他定时器使用
还有要注意用跳线帽将P34和SIGNAL连接,顺便提醒一下大家,如果将P34和SIGNAL连接了
那按键的S16,S17,S18,S19是不能用的,所以题目也不会这样出,大家注意一点就好
当考到NE555的时候,我们需要将Key.c内,对P34操作为0的那一行屏蔽掉
2.4.1 定时器0初始化
我们只需要stc-isp生成一个定时器0 12MHz,1ms,16位自动重装载值代码,12T
然后将TL0和TH0都赋为0x00,并加上 TMOD |= 0x05
而我们只是需要用到它的计数功能,用不上中断,
所以不用加上EA = 1; ET0 = 1;
/* 定时器初始化函数 */
void Timer0_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x05; //设置定时器模式
TL0 = 0x00; //设置定时初始值
TH0 = 0x00; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
2.4.2 NE555在main.c内的程序
注意用的是unsigned int!!!不要用成char了
#include <STC15F2K60S2.H>
unsigned int Freq, Timer1_1000ms; //实时频率测量, 定时器1000ms计时专用变量
/* 定时器初始化函数 */
void Timer0_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x05; //设置定时器模式
TL0 = 0x00; //设置定时初始值
TH0 = 0x00; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
void Timer1_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初始值
TH1 = 0xFC; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1;
EA = 1;
}
/* 定时器中断服务函数 */
void Timer1_Serve() interrupt 3
{
if (++Timer1_1000ms == 1000) //1秒计时
{
Freq = (TH0 << 8) | TL0; //获取频率
Timer1_1000ms = TH0 = TL0 = 0;
}
}
2.5 AT24C02 EEPROM写入读取
为了防止在第一次上电时读出数据错误,则可以加一个PIN验证
这个是我写第九届国赛前面的PIN验证,算是实例了
虽然i2c的底层驱动代码有点变化,但是咱的底层是不用改变的
/* # I2C代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <STC15F2K60S2.H>
#include <intrins.h>
#include "iic.h"
sbit sda = P2^1;
sbit scl = P2^0;
//
void EEPROM_Write(unsigned char *pAT, addr, num)
{
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
while(num--)
{
I2CSendByte(*pAT++);
I2CWaitAck();
I2C_Delay(200);
}
I2CStop();
}
//
void EEPROM_Read(unsigned char *pAT, addr, num)
{
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0xA1);
I2CWaitAck();
while(num--)
{
*pAT++ = I2CReceiveByte();
if(num) I2CSendAck(0);
else I2CSendAck(1);
}
I2CStop();
}
2.6 超声波(PCA)
2.6.1 引脚定义(P10,P11)
注意15届原理图是在这个位置给的是对我们用户来说的
所以超声波的引脚对应对应不用再和之前一样要tx对rx
即引脚对应为
2.6.2 超声波底层代码(Uwave.c)
#include <STC15F2K60S2.H>
#include <intrins.h>
#include "Uwave.h"
sbit Tx = P1^0;
sbit Rx = P1^1;
void Delay12us() //@12.000MHz
{
unsigned char i;
_nop_();
_nop_();
i = 33;
while (--i);
}
void Uwave_Init()
{
unsigned char i = 0;
for (i = 0; i < 8; i++)
{
Tx = 1;
Delay12us();
Tx = 0;
Delay12us();
}
}
unsigned char Uwave_Read()
{
unsigned int time = 0;
CMOD = 0x00;
CH = CL = 0;//计数清零
Uwave_Init();
CR = 1; //开始计数
while ((Rx == 1) && (CF == 0));
CR = 0; //停止计数
if (CF == 1)
{
CF = 0;
return 0;
}
else
{
time = (CH << 8) | CL;
return (unsigned char)(time * 0.017);
}
}
2.7 Uart串口(重定向版)
串口使用重定向
在main.c中就只需要使用printf函数即和打印数据
但是一定要把串口中断写出来
不管你是否要接收数据,否则在打印之后程序卡死
2.7.1 Uart.c
#include <STC15F2K60S2.H>
#include "Uart.h"
#include <stdio.h>
void Uart_Init(void) //9600bps@12.000MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x01; //串口1选择定时器2为波特率发生器
AUXR &= 0xFB; //定时器时钟12T模式
T2L = 0xE6; //设置定时初始值
T2H = 0xFF; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
ES = 1; //串口中断开启
EA = 1; //总中断开启
}
/* 重定向 */
extern char putchar (char ch)
{
SBUF = ch;
while(TI == 0);
TI = 0;
return ch;
}
2.7.2 main.c
/* 变量创建区 */
unsigned char Uart_Rx_Index; //串口接收指针
unsigned char Uart_Rx_Buf[20]; //串口接收缓冲区
unsigned char Uart_Ticks; //串口计时
bit Uart_Rx_Flag; //串口接收数据标志位
/* 串口处理函数 */
void Uart_Deal()
{
if (!Uart_Rx_Index) return;
if (Uart_Ticks > 10)
{
Uart_Ticks = Uart_Rx_Flag = 0;
/* 逻辑处理区 */
/* 逻辑处理结束 */
memset(Uart_Rx_Buf, 0, Uart_Rx_Index);
Uart_Rx_Index = 0;
}
}
/* 中断服务函数 */
//定时器1中断服务函数
void Timer1_Serve() interrupt 3
{
if (++Slow_Down == 400) Slow_Down = Infor_Slow_FLag = 0;
if (Slow_Down % 10 == 0) Key_Slow_FLag = 0;
if (Slow_Down % 20 == 0) LED_Slow_FLag = 0;
if (Slow_Down % 100 == 0) Seg_Slow_FLag = 0;
Seg_Show(Slow_Down%8, Seg_Buf[Slow_Down%8], Seg_Point[Slow_Down%8]);
LED_Show(Slow_Down%8, LED_Buf[Slow_Down%8]);
//串口空闲处理
if (Uart_Rx_Flag && ++Uart_Ticks > 10)
Uart_Ticks = 11;
}
//串口中断服务函数
void Uart_Serve() interrupt 4
{
/* 数据接收区 */
if (RI == 1)
{
Uart_Ticks = RI = 0;
Uart_Rx_Flag = 1;
Uart_Rx_Buf[Uart_Rx_Index++] = SBUF;
}
/* 数据接收错误处理 */
if (Uart_Rx_Index > 15) //最好是比数组的小,以防越界访问
{
memset(Uart_Rx_Buf, 0, 20);
/* 接收错误处理区 */
/* 处理结束 */
Uart_Rx_Index = 0;
Uart_Ticks = Uart_Rx_Flag = 0;
}
}