【喂饭】【速成】蓝桥杯模版手把手建立

蓝桥杯模版建立


Author:蒋永鑫(左岚)

Time:2024.6.2


前言

小小的bb几句

这里我说明一下我个人在学习单片机的时候的一些心得体会,以及大模板的最新建立,会持续更新。

最基础的模版来自于B站Up Alice_西风的个人空间-Alice_西风个人主页-哔哩哔哩视频 (bilibili.com)

我的B站为左-岚的个人空间-左-岚个人主页-哔哩哔哩视频 (bilibili.com)

这篇文章对应着我B站的视频为 【蓝桥杯】【单片机】大模板构建喂饭教程
建议搭配着视频使用,不然可能有些地方比较迷糊。
用到的压缩包和这个pdf的百度网盘链接

链接:https://pan.baidu.com/s/1LhlBR7eKAB4A4sbvkHtiJw?pwd=mvin
提取码:mvin
–来自百度网盘超级会员V4的分享

对应的CSDN链接为【喂饭】【速成】蓝桥杯模版手把手建立-CSDN博客

对应的知乎链接为【喂饭】【速成】蓝桥杯模版手把手建立 - 左岚的文章 - 知乎

这两个仓库可以删除后面的东西(一直删除到master,可以看到我的全部的东西)

Github的仓库为 lanqiaobei_study/模板/demo_zuolan at master · zuoliangyu/lanqiaobei_study (github.com)
Gitee的仓库为 模板/demo_zuolan · zuoliangyu/蓝桥杯学习 - 码云 - 开源中国 (gitee.com)

锁存器与三八译码器介绍

这里我们需要有一个基础知识,就是我们的锁存器和三八译码器介绍,这是一个比较关键的点,我们看到下面这两个图,可以看到我们的锁存器是用P25,P26,P27进行控制,即P2的高三位进行的控制,我们只需要按照如下的命令进行书写即可。

P2 = P2 & 0x1f | 0x?0;
P2 &= 0x1f;

其中的P2&=0x1f,表示我们将高三位置为0,| 0x?0的操作表明我们对高三位进行书写,比如Y4C,那么就代表我们的高三位要表示为4,即0100,我们将其放在高位,即1000_0000,翻译过来就是0x80

image-20240603102849364

image-20240603102505211

NOP延时函数

这里要注意一个问题,就是我们延时函数大部分都有NOP,包括Ds1302,IIC都使用了这个,这个函数存在于头文件intrins.h中,需要注意一下引用,一般来说,一个NOP在12MHz的单片机下是1us,其余的自己去换算一下。

LED,蜂鸣器,继电器,MOTOR模版建立

Led.c

这里虽然名称叫做Led.c,但是里面书写LED,蜂鸣器,继电器,MOTOR的代码

Led底层

我们可以看到Led的代码,我们可以注意到的是,这个地方使用了static这个关键词,因为我们需要关注内存的使用~~(这里不得不提C51的垃圾内存容量)~~,所以我们将其变为函数静态内存,可以有效节省。

传入的参数有两个,一个是addr,代表我们需要控制的Led的地址,(0-7)对应了(L1-L8),另一个是enable,用于控制选中的Led灯的亮灭。

void Led_Disp(unsigned char addr, unsigned char enable)

我们定义了两个参数,一个是temp,一个是temp_old,你们可能会好奇,为什么我要在这里用两个变量来进行编写,明明逻辑上只需要用一个变量对P0进行赋值就行了,这里我说明一下,我们一般情况下不会一直操控锁存器,否则有概率影响数码管的显示,当我们状况出现变化(temp!=temp_old)的时候,我们才对锁存器P2的高三位进行书写。当我们需要点亮的时候,就让相应的addr变为1;当我们需要熄灭的时候就让相应的addr变为0;

这里你可能会有疑问,因为我们在原理图上面如果想让Led亮,那么应该给0,这里做一个说明,我们后面会直接取反,这里用1是为了符合我们的直觉~~(因为正常人就是1亮0灭)~~

static unsigned char temp = 0x00;
static unsigned char temp_old = 0xff;
if (enable)
    temp |= 0x01 << addr;
else
    temp &= ~(0x01 << addr);

image-20240603102116772

我们通过查看原理图可以看到,用于控制的Load为Y4C,我们通过计算二进制可以发现结果为1000_0000,即0x80,所以我们在这里代码中的P2设置为P2 = P2 & 0x1f | 0x80;在运行后别忘记使用P2 &= 0x1f来进行高位清零,但是在我们操作P2锁存器之前,我们需要先对P0进行赋值(要记得先取反)

if (temp != temp_old) {
    P0 = ~temp;
    P2 = P2 & 0x1f | 0x80;
    P2 &= 0x1f;
    temp_old = temp;
}

完整代码如下

    /// @brief Led扫描
    /// @param addr 需要控制的Led的地址(0-7)
    /// @param enable 控制该地址的Led是否点亮
    void Led_Disp(unsigned char addr, unsigned char enable) {
      static unsigned char temp = 0x00;
      static unsigned char temp_old = 0xff;
      if (enable)
        temp |= 0x01 << addr;
      else
        temp &= ~(0x01 << addr);
      if (temp != temp_old) {
        P0 = ~temp;
        P2 = P2 & 0x1f | 0x80;
        P2 &= 0x1f;
        temp_old = temp;
      }
    }
蜂鸣器,继电器,MOTOR底层

这里我就一起讲了,因为他们三是一个芯片的不同输出,和上方的Led类似,但是这几个需要一个全局变量(在函数外申明)

unsigned char temp_0 = 0x00;
unsigned char temp_old_0 = 0xff;

我们可以看到,BUZZ、MOTOR、RELAY为一个芯片的不同位的输出

RELAY对应I5->Q4->D4->0001_0000->0x10

MOTOR对应I6->Q5->D5->0010_0000->0x20

BUZZ对应I7->Q6->D6->0100_0000->0x40

这里后我们就知道如果我们需要对应的工作,那么就要给P0赋相应的值,并且下方的Y5C也告诉我们,我们需要给锁存器为1010_0000->0xa0,即P2 = P2 & 0x1f | 0xa0;这个之后也别忘了使用P2 &= 0x1f来进行高位清零。

  if (temp_0 != temp_old_0) {
    P0 = temp_0;
    P2 = P2 & 0x1f | 0xa0;
    P2 &= 0x1f;
    temp_old_0 = temp_0;
  }

image-20240603110637349

总体代码如下

unsigned char temp_0 = 0x00;
unsigned char temp_old_0 = 0xff;
/// @brief 蜂鸣器控制
/// @param enable
void Beep(bit enable) {
  if (enable)
    temp_0 |= 0x40;
  else
    temp_0 &= ~(0x40);
  if (temp_0 != temp_old_0) {
    P0 = temp_0;
    P2 = P2 & 0x1f | 0xa0;
    P2 &= 0x1f;
    temp_old_0 = temp_0;
  }
}
/// @brief 继电器控制
/// @param enable
void Relay(bit enable) {
  if (enable)
    temp_0 |= 0x10;
  else
    temp_0 &= ~(0x10);
  if (temp_0 != temp_old_0) {
    P0 = temp_0;
    P2 = P2 & 0x1f | 0xa0;
    P2 &= 0x1f;
    temp_old_0 = temp_0;
  }
}
/// @brief MOTOR控制
/// @param enable
void MOTOR(bit enable) {
  if (enable)
    temp_0 |= 0x20;
  else
    temp_0 &= ~(0x20);
  if (temp_0 != temp_old_0) {
    P0 = temp_0;
    P2 = P2 & 0x1f | 0xa0;
    P2 &= 0x1f;
    temp_old_0 = temp_0;
  }
}

Led.h

申明代码如下

#include <STC15F2K60S2.H>
void Led_Disp(unsigned char addr, unsigned char enable);
void Beep(bit enable);
void Relay(bit enable);
void MOTOR(bit enable);

Key模版建立

key.c

基础代码

在这里我们可以看到原理图里面的行列显示,最新版的原理图把row和col单独拿出来了,所以可能对应有点麻烦~~(其实也不麻烦)~~,如下图所示,我们直接按照一列一列进行扫描,给列低电平后判断指定行是否为低电平,如果指定行为低电平,那么我们就认为这个按键被按下。并且我们在这里不需要实现消抖等操作,我们会在main.c里面实现我们的功能,这里只实现最基础的扫描。

P44 = 0;P42 = 1;P35 = 1;P34 = 1;
if (P33 == 0) temp = 4;
if (P32 == 0) temp = 5;
if (P31 == 0) temp = 6;
if (P30 == 0) temp = 7;

P44 = 1;P42 = 0;P35 = 1;P34 = 1;
if (P33 == 0) temp = 8;
if (P32 == 0) temp = 9;
if (P31 == 0) temp = 10;
if (P30 == 0) temp = 11;

P44 = 1;P42 = 1;P35 = 0;P34 = 1;
if (P33 == 0) temp = 12;
if (P32 == 0) temp = 13;
if (P31 == 0) temp = 14;
if (P30 == 0) temp = 15;

P44 = 1;P42 = 1;P35 = 1;P34 = 0;
if (P33 == 0) temp = 16;
if (P32 == 0) temp = 17;
if (P31 == 0) temp = 18;
if (P30 == 0) temp = 19;

image-20240603112443055

image-20240603112417611

在我们把行列扫描写完后就可以完成工作了,但是,我们要注意一个问题,局部变量一定要注意初始化为0

unsigned char temp = 0;
注意事项

在这里要注意一个问题,我们的串口进行发送的时候会占用P30和P31口,这个是硬件的冲突,所以我们在进入这个扫描函数的时候,需要对其关掉轮询(就是在main函数里面用的什么来进行各个函数定时的定时器,叫做轮询定时器)。这里我用的是定时器1,所以我进入后就要使用ET1=0关掉,结束后用P3=0xff;ET1=1开启。

ET1 = 0;
----
P3 = 0xff;
ET1 = 1;

还有一个比较重要的问题,今年(2024)不管是省赛还是国赛都考了ne555的测量,我们ne555的测量要用到P34引脚,也就是我们的按键的最后一列,那么这里就要注意一下,这是一个硬件冲突,且无法解决,所以我们将所有P34删除,就当我们用的按键为4行3列

P44 = 0;P42 = 1;P35 = 1;
if (P33 == 0) temp = 4;
if (P32 == 0) temp = 5;
if (P31 == 0) temp = 6;
if (P30 == 0) temp = 7;

P44 = 1;P42 = 0;P35 = 1;
if (P33 == 0) temp = 8;
if (P32 == 0) temp = 9;
if (P31 == 0) temp = 10;
if (P30 == 0) temp = 11;

P44 = 1;P42 = 1;P35 = 0;
if (P33 == 0) temp = 12;
if (P32 == 0) temp = 13;
if (P31 == 0) temp = 14;
if (P30 == 0) temp = 15;

整体代码如下(没有包括ne555,如果有ne555需要把按键扫描换成上面的)

#include "key.h"
unsigned char Key_Read() {
  unsigned char temp = 0;
  ET1 = 0;

  P44 = 0;P42 = 1;P35 = 1;P34 = 1;
  if (P33 == 0) temp = 4;
  if (P32 == 0) temp = 5;
  if (P31 == 0) temp = 6;
  if (P30 == 0) temp = 7;

  P44 = 1;P42 = 0;P35 = 1;P34 = 1;
  if (P33 == 0) temp = 8;
  if (P32 == 0) temp = 9;
  if (P31 == 0) temp = 10;
  if (P30 == 0) temp = 11;

  P44 = 1;P42 = 1;P35 = 0;P34 = 1;
  if (P33 == 0) temp = 12;
  if (P32 == 0) temp = 13;
  if (P31 == 0) temp = 14;
  if (P30 == 0) temp = 15;

  P44 = 1;P42 = 1;P35 = 1;P34 = 0;
  if (P33 == 0) temp = 16;
  if (P32 == 0) temp = 17;
  if (P31 == 0) temp = 18;
  if (P30 == 0) temp = 19;

  P3 = 0xff;
  ET1 = 1;
  return temp;
}

key.h

#include <STC15F2K60S2.H>
unsigned char Key_Read();

数码管模板建立

Seg.c

数码管编码推导

这里我单独开一个标题来做数码管的编码推导,因为这个很重要,我们看到下方的截图

image-20240603114712357

可以观察到每一段的数码管都被编号,这里由于我们用的是共阳极数码管,即0亮1灭,下面我给一个🌰来进行说明

image-20240603115210566

我们标好后按照dp->g->…->a的顺序写出,即1010_0100->0xa4,这个值在数码管的显示中就代表着“2”

所以我们就可以根据这个规律来编写一个段选表

code unsigned char seg_dula[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92,
                                 0x82, 0xf8, 0x80, 0x90, 0xff, 0x88};
数码管代码编写

这是我们最最重要的玩意,因为这个涉及到我们的显示,我们看原理图可以看到,我们需要对数码管进行位选(选择哪一个数码管进行显示),段选(选择数码管显示的数据)。

其中ABCDEFG和DP为我们的段选,COM1-8为我们的位选

我们需要先对P0进行段选显示后,使用P2将锁存器打开(Y7C->1110_0000->0xe0);在对P0进行位选配置后,使用P2将锁存器打开(Y6C->1100_0000->0xc0)

image-20240603113818447

经过我们的分析后我们就可以知道我们需要传入的数据为wela位选,dula段选,point是否显示小数点

void Seg_Disp(unsigned char wela, unsigned char dula, unsigned char point) 

我们为了避免闪烁,所以我们需要先对数码管进行消隐,即

P0 = 0xff;
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;

然后我们对位置进行选择后使用P2打开相应锁存器

P0 = 0x01 << wela;
P2 = P2 & 0x1f | 0xc0;
P2 &= 0x1f;

由于我们可以知道,要显示小数点的话,我们就要让DP为0,其他不变,即&0x7f即可完成

P0 = seg_dula[dula];
if (point) P0 &= 0x7f;
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;

总体代码如下

#include "seg.h"
// 0-9 灭
//  A
code unsigned char seg_dula[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92,
                                 0x82, 0xf8, 0x80, 0x90, 0xff, 0x88};
void Seg_Disp(unsigned char wela, unsigned char dula, unsigned char point) {
  // 消隐
  P0 = 0xff;
  P2 = P2 & 0x1f | 0xe0;
  P2 &= 0x1f;

  // 位选
  P0 = 0x01 << wela;
  P2 = P2 & 0x1f | 0xc0;
  P2 &= 0x1f;

  // 段选
  P0 = seg_dula[dula];
  if (point) P0 &= 0x7f;
  P2 = P2 & 0x1f | 0xe0;
  P2 &= 0x1f;
}

Seg.h

#include <STC15F2K60S2.H>
void Seg_Disp(unsigned char wela, unsigned char dula, unsigned char point);

Ds1302时钟模版建立

Ds1302.c

这里有官方提供的参考代码,所以我们上面的时序可以不用书写,我们直接对下方的时间读取/写入进行操作

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

首先是我们需要绑定SDA,RST,SCK的引脚,在原理图中可以看到,并且要注意一个问题,我们用到了NOP函数,所以需要引入头文件intrins.h

image-20240603120352094

#include "intrins.h"
sbit SDA = P2 ^ 3;
sbit RST = P1 ^ 3;
sbit SCK = P1 ^ 7;

然后就是我们的读写时钟,观察芯片手册可以看到

image-20240603120847616

我们如果需要读取时钟的话,只需要读取地址0x85->小时 0x83->分钟 0x81->秒钟

我们如果需要写入时钟的话,需要先将0x8E位置的最高位WP置0,解除写保护后才能进行写入,写入的地址为0x84->小时 0x82->分钟 0x80->秒钟

所以我们的代码编写就很简单了,我们传入数据为数组的指针

void Set_Rtc(unsigned char *ucRtc) 
void Read_Rtc(unsigned char *ucRtc) 

这里给一个调用的示例

unsigned char time[3] = {11,12,13};//11:12:13
Set_Rtc(time);
Read_Rtc(time);

我们在写入之前需要进行解锁写保护,写完后就进行写保护

Write_Ds1302_Byte(0x8e, 0x00);
-----
Write_Ds1302_Byte(0x8e, 0x80);

我们在使用的过程中可以直接使用for循环进行读取/写入,由于我们为了方便,所以我们在这里使用十进制进行存储和读取(本质上存储和读取的都是二进制BCD码,比如0x11我们读取为11,写入为11,在里面内部使用变量进行转换)

举个🌰,我们假设要写入的时间为23:59:58,那么我们实际上要写入芯片的数据为0x23 0x59 0x58

但是我们为了方便(因为有时候需要我们是设置时钟,加减的那种,直接使用bcd码不方便,需要我们判断是否达到0xa0然后进行转换),这里我们可以在里面写一个十进制转为十进制bcd码的公式就行,以写入0x23为例,我们输入给我们Set_Rtc函数的数据为23,我们要先将其转换为0x23才能进行写入,那么我们可以观察一下,本质上就是把十位和个位分开,然后再存入即可,我们分开很简单,直接/10和%10就可以取出十位和个位,然后再使用<<4来进行高位移位,将我们的十位移动到高四位去,然后|上我们的个位(低四位)就可以将其拼接完成。

23->2,3->0010,0011->0100_0011

temp = ucRtc[i] / 10 << 4 | ucRtc[i] % 10;

同理,我们读取的时候读取到的都是十进制的bcd码,那么我们将其转换后再读出

0x23->0x2,0x3->2*10+3->23

ucRtc[i] = temp >> 4 * 10 + temp & 0x0f;

读取和写入的代码如下

//写入 
for (i = 0; i < 3; i++) {
    temp = ucRtc[i] / 10 << 4 | ucRtc[i] % 10;
    Write_Ds1302_Byte(0x84 - 2 * i, temp);
}
//读取
for (i = 0; i < 3; i++) {
    temp = Read_Ds1302_Byte(0x85 - 2 * i);
    ucRtc[i] = temp >> 4 * 10 + temp & 0x0f;
}

需要补充的完整代码

// 十进制 11 -> 0x11
void Set_Rtc(unsigned char *ucRtc) {
  unsigned char i;
  unsigned char temp;
  Write_Ds1302_Byte(0x8e, 0x00);
  for (i = 0; i < 3; i++) {
    temp = ucRtc[i] / 10 << 4 | ucRtc[i] % 10;
    Write_Ds1302_Byte(0x84 - 2 * i, temp);
  }
  Write_Ds1302_Byte(0x8e, 0x80);
}
// 十进制 0x11 -> 11
void Read_Rtc(unsigned char *ucRtc) {
  unsigned char i;
  unsigned char temp;
  for (i = 0; i < 3; i++) {
    temp = Read_Ds1302_Byte(0x85 - 2 * i);
    ucRtc[i] = temp >> 4 * 10 + temp & 0x0f;
  }
}

完整代码

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

#include "intrins.h"
sbit SDA = P2 ^ 3;
sbit RST = P1 ^ 3;
sbit SCK = P1 ^ 7;
//
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);
}
// 十进制 11 -> 0x11
void Set_Rtc(unsigned char *ucRtc) {
  unsigned char i;
  unsigned char temp;
  Write_Ds1302_Byte(0x8e, 0x00);
  for (i = 0; i < 3; i++) {
    temp = ucRtc[i] / 10 << 4 | ucRtc[i] % 10;
    Write_Ds1302_Byte(0x84 - 2 * i, temp);
  }
  Write_Ds1302_Byte(0x8e, 0x80);
}
// 十进制 0x11 -> 11
void Read_Rtc(unsigned char *ucRtc) {
  unsigned char i;
  unsigned char temp;
  for (i = 0; i < 3; i++) {
    temp = Read_Ds1302_Byte(0x85 - 2 * i);
    ucRtc[i] = temp >> 4 * 10 + temp & 0x0f;
  }
}

Ds1302.h

#include <STC15F2K60S2.H>
void Set_Rtc(unsigned char *ucRtc);
void Read_Rtc(unsigned char *ucRtc);

Onewire温度模版建立

Onewire.c

官方的参考代码

这里有官方提供的参考代码,所以我们上面的时序可以不用书写,我们直接对下方的进行温度读取操作。

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

首先是我们需要绑定DQ的引脚,在原理图中可以看到

image-20240603151248313

sbit DQ = P1 ^ 4;

我们不需要管上方的时序的书写,只需要进行温度读取的代码书写就行,

下方是温度芯片的使用顺序(官方手册),首先我们需要运行init,然后再检查rom(可跳过),最后执行我们的功能函数,注意,一次只能使用一个功能。

image-20240603151648898

我们可以在官方手册上找到需要运行的指令

image-20240603151943165

当我们发送0xcc的时候,就会跳过ROM检查,并且这里也说明了,我们发送0x44就可以开启温度转换,发送0xbe就可以开始读取温度,但是要注意一个事情,我们同样可以在我们的官方的手册上面找到一段读取的语句,这里表明我们先读取低位,再读取高位,最后将其拼接后再*精度就是我们的数据了

image-20240603152342393

温度的精度在文档中对应如下

image-20240603152557565

精度为9->0.5;10->0.25;11->0.125;12(默认)->0.0625

我们就可以根据上面的描述来进行代码书写

float rd_temperature() {
    unsigned char low, high;
    //开启温度转换
    init_ds18b20();
    Write_DS18B20(0xcc);
    Write_DS18B20(0x44);
    Delay_OneWire(200);
    //开启温度读取
    init_ds18b20();
    Write_DS18B20(0xcc);
    Write_DS18B20(0xbe);
    low = Read_DS18B20();
    high = Read_DS18B20();
    return (float)(high << 8 | low) * 0.0625;
}

这里给一个调用示例

float temp = rd_temperature();

完整代码如下

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

float rd_temperature() {
  unsigned char low, high;
  init_ds18b20();
  Write_DS18B20(0xcc);
  Write_DS18B20(0x44);
  Delay_OneWire(200);

  init_ds18b20();
  Write_DS18B20(0xcc);
  Write_DS18B20(0xbe);
  low = Read_DS18B20();
  high = Read_DS18B20();
  return (float)(high << 8 | low) * 0.0625;
}

Onewire.h

#include <STC15F2K60S2.H>
float rd_temperature();

IIC(PCF8591(AD/DA)与AT24C02(EEPROM))模版建立

iic.c

官方的参考代码

这里有官方提供的参考代码,所以我们上面的时序可以不用书写,我们直接进行AD读取与DA输出还有AT24C02的存储操作

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

首先是我们需要绑定sda和scl的引脚,在原理图中可以看到,并且要注意一个问题,我们用到了NOP函数,所以需要引入头文件intrins.h

image-20240603153405955

#include "intrins.h"
sbit scl = P2 ^ 0;
sbit sda = P2 ^ 1;

我在这里需要说明一下IIC通信的方式,他是一个一个八位的数据,其中第一位是我们的发送地址,永远都是1,然后后面挂载了不同的外设,我们的蓝桥杯的板子挂了两个外设,一个是PCF8591,对应着AD/DA的功能;另一个是AT24C02,对应着EEPROM的存储功能。在我们板子上就长我们下面这个样子~~(我画的有点抽象)~~,所以我们就可以知道,如果我们要对AT24C02芯片写入数据的话,我们就需要使用地址为(1010_0000->0xa0),要读取数据的化,我们就要使用地址为(1010_0001->0xa1);要写入DA输出的数据的话,我们就需要使用地址为(1001_0000->0x90),要读取AD采样的数据的话,我们就需要使用地址为(1001_0001->0x91)

image-20240603154239060

在我们使用IIC进行通信的时候,我们要遵循下面的运行逻辑

I2CStart();
I2CSendByte(地址);
I2CWaitAck();	
I2CSendByte(功能);
I2CWaitAck();	
AD与DA

从上面说明的内容的我们可以知道,如果我们要对PCF8591进行操作,那么就需要对地址0x90和0x91进行操作

我们现在先说一下AD的代码,通过观察原理图我们可以发现,AD有4个通道可以进行读取

image-20240604090624435

其中AIN0通道是我们的外部测量通道(由于蓝桥杯现在还没有涉及到信号发生器,所以我们可以先暂时不用管这个通道)

image-20240604091125348

AIN1通道很明显就是光敏电阻的分压读取(光照越强,分压越高,采集到的结果越大),

AIN2通道为差分输入(我们也是不常用到的输入,因为没有信号发生器)

AIN3通道为滑动变阻器输入(我们测量滑动变阻器的分压,这个你左右旋钮看一下就知道哪边变大了)

image-20240604091205128

我们查看一下PCF8591的手册可以看到,我们读取的地址的书写方式,这里我统一给一个说明

位次情况说明情况
第一位固定位0
第二位是否需要开启DA输出开启DA输出(1)
不开启DA输出(0)
第三四位选择输入通道的模式
差分输入具体要看下面的图
通道与编号对应输入(常用)(00)
通道差分输入(01,10,11)
第五位固定位0
第六位自动递增标志
(用于一个一个读取AD输入口)
自我递增(没考过,因为我们一般都是单一读取)(1)
不自我递增(0)
第七八位用于指定我们读取的通道通道0(00),在我们正常的对应输入下为外部信号输入
通道1(01),在我们正常的对应输入下为光明电阻分压输入
通道2(10),在我们正常的对应输入下为差分信号输入
通道3,在我们正常的对应输入下为滑动变阻器分压输入(11)

image-20240604092329778

我们那么这个时候常考的就是下面的形式,注意一下题目中是否需求DA输出,如果需要DA输出的话,这里使用地址的时候必须使用允许DA输出,

需要的数据输入的地址
外部电压输入(开启DA)0100_0000->0x40
外部电压输入(不开启DA)0000_0000->0x00
光敏电阻分压输入(开启DA)0100_0001->0x41
光敏电阻分压输入(不开启DA)0000_0001->0x01
差分信号输入(开启DA)0100_0010->0x42
差分信号输入(不开启DA)0000_0010->0x02
滑动变阻器分压输入(开启DA)0100_0011->0x43
滑动变阻器分压输入(不开启DA)0000_0011->0x03

有了这样一个基础,我们就可以开始着手编写我们的AD代码了

我们只需要指定一下我们需要读取的通道的地址就行

unsigned char Ad_Read(unsigned char addr) 

这里给一个调用示例,这里需要注意的是,如果你同时读取光敏电阻和滑动变阻器的值,他们会地址交换,你们可以自己去试试。

unsigned char temp = Ad_Read(0x01);//读取光敏电阻结果 

首先我们需要选中我们的PCF芯片并且写入我们需要读取的地址,这里需要注意的就只有每次发送Byte后我们需要Wait一下

// 选择芯片为PCF
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();

然后重新开启一下通话(因为我们上面是进行写操作,现在要进行读操作),读取后我们需要发送一个Ack为1,表明我们这边接收数据完成,后续不用接受了,结束后就可以关闭对话Stop了

I2CStart();
I2CSendByte(0x91);
I2CWaitAck();
temp = I2CReceiveByte();
I2CSendAck(1);
I2CStop();

AD完整代码

unsigned char Ad_Read(unsigned char addr) {
  unsigned char temp;
  // 选择芯片为PCF
  I2CStart();
  I2CSendByte(0x90);
  I2CWaitAck();
  I2CSendByte(addr);
  I2CWaitAck();

  I2CStart();
  I2CSendByte(0x91);
  I2CWaitAck();
  temp = I2CReceiveByte();
  I2CSendAck(1);
  I2CStop();
  return temp;
}

说完AD我们就要说到DA了

和AD一样,我们只需要传入我们需要写入的数据就行,但是要注意一下我们的数据为数字电压,即0-255,我们输出后的电压是模拟电压,即0-5V,所以我们是一一映射过去的

void Da_Write(unsigned char dat) 

举个🌰,我们如果想要输出电压为2V,那么我们就应该

Da_Write(2*51); 

如果想要输出的电压为2.5V,那么我们就应该,这里不用关心他会出现不匹配的情况,C语言底层会自动转换为unsigned char类型的,这类有啊注意

Da_Write(2.5*51); 

首先我们需要选中我们的PCF芯片并且给他我们的地址信息(前面提到过如果要开启DA输出,那么我们应该写入的地址),这里需要注意的就只有每次发送Byte后我们需要Wait一下,这里的0x41可以更换为0x40-0x43的任何一个,但是最好和题目中要求AD需要读取的通道一致(这里也要注意在你读取AD的时候加上0x4)

void Da_Write(unsigned char dat) {
  // 选择芯片为PCF
  I2CStart();
  I2CSendByte(0x90);
  I2CWaitAck();
  I2CSendByte(0x41);
  I2CWaitAck();
  I2CSendByte(dat);
  I2CWaitAck();
}

AD与

EEPROM

从上面说明的内容的我们可以知道,如果我们要对AT24C02进行操作,那么就需要对地址0xa0和0xa1进行操作

本质上我们的EEPROM写入与读取和AD/DA是一致的,但是我们需要注意一个点,因为我们通常写入/读取的不止一个数据,我们一般是一个数组写进去和读取,所以我们就需要注意这个点,我们的操作如下,传递的数据都是数组的首地址(如果存单个数据只需要使用&name就行),传入参数为需要写入/读取数组首地址名称))str,需要写入/读取的数组地址addr,数组的大小num

void EEPROM_Write(unsigned char *str, unsigned char addr, unsigned char num)
void EEPROM_Read(unsigned char *str, unsigned char addr, unsigned char num) 

这里给一个调用的示例,需要注意一下如果写入为unsigned int,可以不用分为高低位存储,但是需要分为高低位读取

unsigned char arr_input[5]={1,2,3,4,5};
unsigned char arr_output[5];
EEPROM_Write(arr_input,0,5);
EEPROM_Read(arr_output,0,5);

unsigned char input = 5;
unsigned char output;
EEPROM_Write(&input,0,1);
EEPROM_Read(&output,0,1);

unsigned int input = 5;
unsigned int output;
unsigned char high,low;
EEPROM_Write(&input,0,2);
EEPROM_Read(&high,0,1);
EEPROM_Read(&low,1,1);
output = high<<4 | low;

现在我来说一下实现细节

先说明一下EEPROM_Read函数,和上文的AD读取一样,我们需要先选中AT24C02,并且功能为写,然后进行位置的传输,确认我们需要读取的地址

I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();

在我们读取的时候,和上文AD一样,我们需要重新开启一个对话进行读取

I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();

但是在我们读取的时候需要注意一个点,我们使用的数组进行的数据读取,并且在我们读取的时候对指针进行++操作,当我们还没有读完数据的时候,我们发送Ack为0,代表我们需要继续读取,当我们读取数据结束后,我们发送Ack为1,表示读取结束,最后也别忘记需要进行Stop停止交流。

I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
while (num--) {
    *str++ = I2CReceiveByte();
    if (num)
        I2CSendAck(0);
    else
        I2CSendAck(1);
}
I2CStop();

在这里说明一下*str++代表的含义,这是c的一个写法,等价于下面的代码

unsigned char arr[5];
unsigned char i = 0;
unsigned char num = 5;
while(num--)
{
	arr[i] = I2CReceiveByte();
i++;
-----
}

完整的代码EEPROM_READ如下

void EEPROM_Read(unsigned char *str, unsigned char addr, unsigned char num) {
  I2CStart();
  I2CSendByte(0xa0);
  I2CWaitAck();
  I2CSendByte(addr);
  I2CWaitAck();

  I2CStart();
  I2CSendByte(0xa1);
  I2CWaitAck();
  while (num--) {
    *str++ = I2CReceiveByte();
    if (num)
      I2CSendAck(0);
    else
      I2CSendAck(1);
  }
  I2CStop();
}

我们可以从上面看到,AD和EEPROM_READ可以进行类比;同理,我们的DA和EEPROM_Write也可以进行类比,但是EEPROM_Write有一个需要注意的点,我会在下面进行说明

我们第一步还是选择我们的芯片,选择我们需要写入的地址

I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();

第二步就是使用一个循环进行写入,这里需要注意一个问题,就是我们每写入一个数据,都需要I2C_Delay一下,注意不是Delay,而是I2C_Delay,经过我们测试,当这个值为200 的时候效果是可以的,并且这里有个最最重要的点,在我们Stop结束后,我们需要进行4个255时间的I2C_Delay~~(其实我在群里面说过,但你们一直觉得我在讲乐子)~~

while (num--) {
    I2CSendByte(*str++);
    I2CWaitAck();
    I2C_Delay(200);
}
I2CStop();
I2C_Delay(255);
I2C_Delay(255);
I2C_Delay(255);
I2C_Delay(255);

完整的代码EEPROM_Write如下

void EEPROM_Write(unsigned char *str, unsigned char addr, unsigned char num) {
  I2CStart();
  I2CSendByte(0xa0);
  I2CWaitAck();
  I2CSendByte(addr);
  I2CWaitAck();

  while (num--) {
    I2CSendByte(*str++);
    I2CWaitAck();
    I2C_Delay(200);
  }
  I2CStop();
  I2C_Delay(255);
  I2C_Delay(255);
  I2C_Delay(255);
  I2C_Delay(255);
}

完整代码如下

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

#include "intrins.h"
#define DELAY_TIME 5
sbit scl = P2 ^ 0;
sbit sda = P2 ^ 1;
//
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 Ad_Read(unsigned char addr) {
  unsigned char temp;
  // 选择芯片为PCF
  I2CStart();
  I2CSendByte(0x90);
  I2CWaitAck();
  I2CSendByte(addr);
  I2CWaitAck();

  I2CStart();
  I2CSendByte(0x91);
  I2CWaitAck();
  temp = I2CReceiveByte();
  I2CSendAck(1);
  I2CStop();
  return temp;
}
// 数字电压255->5V
void Da_Write(unsigned char dat) {
  // 选择芯片为PCF
  I2CStart();
  I2CSendByte(0x90);
  I2CWaitAck();
  I2CSendByte(0x41);
  I2CWaitAck();
  I2CSendByte(dat);
  I2CWaitAck();
}
void EEPROM_Write(unsigned char *str, unsigned char addr, unsigned char num) {
  I2CStart();
  I2CSendByte(0xa0);
  I2CWaitAck();
  I2CSendByte(addr);
  I2CWaitAck();

  while (num--) {
    I2CSendByte(*str++);
    I2CWaitAck();
    I2C_Delay(200);
  }
  I2CStop();
  I2C_Delay(255);
  I2C_Delay(255);
  I2C_Delay(255);
  I2C_Delay(255);
}
void EEPROM_Read(unsigned char *str, unsigned char addr, unsigned char num) {
  I2CStart();
  I2CSendByte(0xa0);
  I2CWaitAck();
  I2CSendByte(addr);
  I2CWaitAck();

  I2CStart();
  I2CSendByte(0xa1);
  I2CWaitAck();
  while (num--) {
    *str++ = I2CReceiveByte();
    if (num)
      I2CSendAck(0);
    else
      I2CSendAck(1);
  }
  I2CStop();
}

iic.h

#include <STC15F2K60S2.H>
unsigned char Ad_Read(unsigned char addr);
void Da_Write(unsigned char dat);
void EEPROM_Write(unsigned char *str, unsigned char addr, unsigned char num);
void EEPROM_Read(unsigned char *str, unsigned char addr, unsigned char num);

Uart串口模版建立

uart.c

在这里我们需要注意,串口官方是没有给底层的,我们需要自己手搓,在这里我给的代码为重定向代码。

首先是使用isp生成一个用定时器2计时的串口1的代码,波特率为9600(比赛的时候可能不是9600,要根据题目来看),定时器时钟选用12T,数据位为8位

image-20240604144711333

我们直接把他复制到我们代码里面就行,最后别忘记加一个EA=1来开启总中断,还有些isp版本可能比较老,没有使能中断的选项,所以需要自己加上ES=1

void Uart1_Init(void)  // 9600bps@12.000MHz
{
  SCON = 0x50;   // 8位数据,可变波特率
  AUXR |= 0x01;  // 串口1选择定时器2为波特率发生器
  AUXR |= 0x04;  // 定时器时钟1T模式
  T2L = 0xC7;    // 设置定时初始值
  T2H = 0xFE;    // 设置定时初始值
  AUXR |= 0x10;  // 定时器2开始计时
  ES = 1;        // 使能串口1中断
  EA = 1;
}

我们这里不需要实现复杂的函数,只需要实现一个重定向就行,使用printf直接将数据发送到串口就行,我现在说一下步骤

我们通过查看c51里面(注意是c51,不是标准的c语言函数库,直接在keil里面右键打开),我们可以看到下方有一个putchar的一个extern的函数定义,这就是我们需要重载的函数,因为我们printf本质上就是调用putchar的

/*--------------------------------------------------------------------------
STDIO.H

Prototypes for standard I/O functions.
Copyright (c) 1988-2002 Keil Elektronik GmbH and Keil Software, Inc.
All rights reserved.
--------------------------------------------------------------------------*/

#ifndef __STDIO_H__
#define __STDIO_H__

#ifndef EOF
 #define EOF -1
#endif

#ifndef NULL
 #define NULL ((void *) 0)
#endif

#ifndef _SIZE_T
 #define _SIZE_T
 typedef unsigned int size_t;
#endif

#pragma SAVE
#pragma REGPARMS
extern char _getkey (void);
extern char getchar (void);
extern char ungetchar (char);
extern char putchar (char);
extern int printf   (const char *, ...);
extern int sprintf  (char *, const char *, ...);
extern int vprintf  (const char *, char *);
extern int vsprintf (char *, const char *, char *);
extern char *gets (char *, int n);
extern int scanf (const char *, ...);
extern int sscanf (char *, const char *, ...);
extern int puts (const char *);

#pragma RESTORE

#endif


我们可以知道,串口在发送的时候,会将数据放进缓冲区SBUF(如果你用串口2那就是S2BUF,但是这里没涉及到,就不用管了),当我们正在发送的时候TI标志位为0,当我们发送完成后,他就会被置为0,然后我们需要将ch返回(这个是函数本身就需要返回的char)

extern char putchar(char ch) {
  SBUF = ch;
  while (TI == 0);
  TI = 0;
  return ch;
}

uart.h

#include <STC15F2K60S2.H>

#include "stdio.h"
void Uart1_Init(void);

Ul超声波模版建立

ul.c

在这里我们需要注意,超声波官方是没有给底层的,我们需要自己手搓。我们通过查询原理图可以看到,超声波的T为P10,R为P11,所以我们需要定义一下

sbit Tx = P1 ^ 0;
sbit Rx = P1 ^ 1;

image-20240604151041554

现在我们来进行逻辑的书写,我们首先要知道超声波测距的原理,我发送数据后,等待接收数据,那么我通过计算发送和接收中的间隔时间,就可以得到我们的距离,这里用数学公式可以进行计算,一般来说声速都是340m/s(也不排除题目里面和你说要对其进行变换啥的【比如第十四届国赛】,这里就按照声速为340m/s来进行说明)

d = v t 2 = 340 m / s ∗ t 2 = 170 m / s ∗ t = 1.7 ∗ 1 0 4 c m / s ∗ t d = {{vt} \over 2} = {{340m/s*t} \over 2} = 170m/s*t = 1.7*{10^4}cm/s*t d=2vt=2340m/st=170m/st=1.7104cm/st

我们这里有一个单位换算需要进行注意,t的单位为us,也就是 1 0 − 6 s {10^{ - 6}}s 106s,我们进行完整的单位换算就可以知道d的距离了(单位为cm), d = 1.7 ∗ 1 0 4 ∗ 1 0 − 6 t = 0.017 t d = 1.7*{10^4}*{10^{ - 6}}t = 0.017t d=1.7104106t=0.017t

图来自微信群友chmod-wrx绘制(我绘图太🌶︎🐔了)

image-20240604153314869

根据我们上方的分析,我们可以知道我们首先需要进行发送,我们在这里发送8个40kHz的方波进行测量,那么周期就是 1 40 k = 25 ⋅ 1 0 − 6 s = 25 u s {1 \over {40k}} = 25 \cdot {10^{ - 6}}s = 25us 40k1=25106s=25us,也就是我们需要 12 u s 12us 12us将发送的电平进行反转

代码如下,我们首先使用isp生成一个12us的延时函数,这里需要注意的是要正确使用系统频率为12MHz;如果这里测量结果有问题,可以尝试将33修改为38或者其他的数据,因为软件延时不准。

image-20240604153718702

void Delay12us(void)	//@12.000MHz
{
	unsigned char data i;

	_nop_();
	_nop_();
	i = 33;//可以修改为其他数据,比如38
	while (--i);
}

然后就是我们的超声波发送函数,发送8个4kHz的方波

void Ut_Wave_Init() {
  unsigned char i;
  for (i = 0; i < 8; i++) {
    Tx = 1;
    Delay12us();
    Tx = 0;
    Delay12us();
  }
}

最后是我们最重要的数据接收与转换函数的书写,在这里我们使用的pca定时器,可以节省定时器来进行其他的操作(因为ne555强制占用定时器0,串口考到就会占用一个定时器,还有一个定时器需要用来进行函数轮询,所以我们就必须用其他的定时器进行超声波的操作),其实PCA本质上还是一个定时器,所以他也存在一些定时器的参数,比如TLx->CL;THx->CH;TMOD->CMOD;TRx->CR;TFx->CF

我们首先需要对定时器的CH和CL清零,为了我们计数做准备;然后将CMOD配置为0x00,十六位不重载定时器。

CH = CL = 0;
CMOD = 0x00;

在我们发送超声波的时候,我们需要将总中断关闭,在我们发送完毕后就关掉,在我们发送完成后,就打开CR开始计时,等待我们收到回应(收到回应后Rx为0)或者计数溢出(溢出后CF为1),我们就关闭CR开始读取数据

EA = 0;
Ut_Wave_Init();
EA = 1;

CR = 1;
while (Rx && !CF);
CR = 0;

如果我们是因为收到回应而结束(那么就代表我们测量到了数据),那么我们就进行时间读取,如下所示,你们可能会好奇为什么+3,这是因为我们实际测量发现,+3后在远程数据(测量结果>10的时候)误差会比较小,而近程数据(测量数据<10)满足误差范围,我们也不可能测量得到0这个数据(题目中一般都不会测到0)(所以我们才把他视为测量错误的返回结果)

time = CH << 8 | CL;  
return (0.017 * time + 3);

如果我们是因为计数溢出(没有收到返回的信号,代表测量错误),我们就直接返回为0,表示错误结果

return 0;

转换的完整代码如下

unsigned char Ut_Wave_Data() {
  unsigned int time;
  CH = CL = 0;
  CMOD = 0x00;

  EA = 0;
  Ut_Wave_Init();
  EA = 1;

  CR = 1;
  while (Rx && !CF);
  CR = 0;
  if (!CF) {  // us -> s 10^(-6)
    // m -> cm 10^2
    //  10^(-4)
    // L = V*T/2=340*time/2=170*10^(-4)*time=0.017*time
    time = CH << 8 | CL;  
    return (0.017 * time + 3);
  } else {
    CF = 0;
    return 0;
  }
}

完整代码如下

#include "ul.h"

#include "intrins.h"
sbit Tx = P1 ^ 0;
sbit Rx = P1 ^ 1;
void Delay12us(void)  //@12.000MHz
{
  unsigned char data i;

  _nop_();
  _nop_();
  i = 33;  // 38
  while (--i);
}
void Ut_Wave_Init() {
  unsigned char i;
  for (i = 0; i < 8; i++) {
    Tx = 1;
    Delay12us();
    Tx = 0;
    Delay12us();
  }
}
unsigned char Ut_Wave_Data() {
  unsigned int time;
  CH = CL = 0;
  CMOD = 0x00;

  EA = 0;
  Ut_Wave_Init();
  EA = 1;

  CR = 1;
  while (Rx && !CF);
  CR = 0;
  if (!CF) {  // us -> s 10^(-6)
    // m -> cm 10^2
    //  10^(-4)
    // L = V*T/2=340*time/2=170*10^(-4)*time=0.017*time
    time = CH << 8 | CL;  
    return (0.017 * time + 3);
  } else {
    CF = 0;
    return 0;
  }
}

ul.h

#include <STC15F2K60S2.H>
unsigned char Ut_Wave_Data();

init初始化模版建立

init.c

我们在启动开发板的时候最好对其进行初始化,关闭蜂鸣器和继电器,MOTOR和全部LED灯,避免上电情况有误

#include "init.h"
void System_Init() {
  P0 = 0xff;
  P2 = P2 & 0x1f | 0x80;
  P2 &= 0x1f;

  P0 = 0x00;
  P2 = P2 & 0x1f | 0xa0;
  P2 &= 0x1f;
}

init.h

#include <STC15F2K60S2.H>
void System_Init();

main主函数书写

main.c

现在我们来进行主函数的编写,我也拆分为模块,但是最后还是会给一个整体的代码

全部变量的归纳

这里我把全部的全局变量放上来

  • 定义了Led的变量数组,用于控制8个灯的亮灭情况
  • 定义了Seg_Pos的变量,用于控制数码管位选情况
  • 定义了Seg_Buf的变量数组,用于控制每个数码管显示第数据
  • 定义了Seg_Point的变量数组,用于控制某一位数码管是否显示小数点
  • 定义了Uart_Buf的变量数组,用于获取串口接收的数据
  • 定义了Uart_Rx_Index的变量,用于对串口接收后的数组进行索引
  • 定义了Uart_flag的变量,用于判断当前是否正处于串口接收状态
  • 定义了Sys_Tick的变量,用于嘀嗒计时,判断串口是否接收状态超过了10ms
  • 定义了ucRtc的变量数组,用于存放时钟数据,时分秒
  • 定义了int型的计时变量time_all_1s,用于全局定时
  • 定义了int型的频率变量Freq,用于测频结果的存放
/* LED与数码管 */
unsigned char ucLed[8] = {0, 0, 0, 0, 0, 0, 0, 0};
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 Uart_Buf[10];
unsigned char Uart_Rx_Index;
bit Uart_flag;
unsigned char Sys_Tick;
/* 时间*/
unsigned char ucRtc[3] = {11, 11, 11};
unsigned int time_all_1s;

/* 数据 */
unsigned int Freq;//ne555测频
数据读取模块

我们单独写一个模块对数据进行读取,方便我们进行不同数据的操作,这里使用%的操作来进行时间的定时,比如我们的时间读取,就每隔50ms(因为我定时器是1ms)进行一次;AD读取每隔100ms一次;温度读取每隔500ms一次(这里仅作参考,后续还是要自己搓一下试试)

void Data_Proc() 
  if (time_all_1s % 50 == 0) {
    // 时间读取
  }
  if (time_all_1s % 100 == 0) {
    // AD读取
  }
  if (time_all_1s % 500 == 0) {
    // 温度读取
  }
}
Key键盘模块

这个模块是重中之重,因为用到了一个很精妙的三行代码消抖方法,这个在我以前的视频蓝桥杯单片机按键考点里面提到过,点击这行字体就可以跳转过去,我这里简单说明一下,我们的逻辑如下,Key_Val,Key_Old,Key_Down,Key_Up,这几个变量都是下面的逻辑,我们这里对其的原理进行解析

瞬时值获取-> 获取按下的值,获取抬起的值->更新⼀下值

Down和Up都是瞬时值,Old比Val延后10ms,基于这个特性我们可以进行书写代码

image-20240604200625869

对应的代码如下,我们将这四个变量设置为静态局部变量,可以有效节省内存空间,使用if语句那一段表明我们选择10ms跑一次代码(因为我们定时是1ms的)

void Key_Proc() {
  static unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
  if (time_all_1s % 10) return;
  Key_Val = Key_Read();
  Key_Down = Key_Val & (Key_Old ^ Key_Val);
  Key_Up = ~Key_Val & (Key_Old ^ Key_Val);
  Key_Old = Key_Val;
}

如果理解不了上面的图和代码,我就拿一个🌰来进行验证,比如我们按下/抬起S4来做一下验证

键盘状态Key_OldKey_ValKey_Old^Key_ValKey_DownKey_Up
未按下000000000000^0000=000000000000
按下过程中(10ms)000001000000^0100=01000100&0100=01001011&0100=0000
按下稳定(10ms后)010001000100^0100=00000100&0000=00001011&0000=0000
抬起过程(10ms)010000000100^0000=01000000&0100=00001111&0100=0100

我们可以从这里看出,当我们按下的时候,那一瞬间Down会是4(注意是一瞬间),其余时候都是0;同理Up也是一样,当我们抬起的瞬间Up会是4(注意是一瞬间),其余时候是0

那么我们如何在这个代码里面进行书写呢,其实很简单,我们一般判断按键都是判断的按键按下,即Down的数据,那么我们按照下面这样写就行,下面就是按下S4的一个示例

void Key_Proc() {
    static unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
    if (time_all_1s % 10) return;
    Key_Val = Key_Read();
    Key_Down = Key_Val & (Key_Old ^ Key_Val);
    Key_Up = ~Key_Val & (Key_Old ^ Key_Val);
    Key_Old = Key_Val;
    if(Key_Down == 4)
        //执行按下S4的函数
}
Seg数码管模块

这个和V2模版不一样,他将数据读取放出去了,所以我们的数码管里面不过多涉及到数据读取,这里选择的是20ms进行一次数码管状态更新

void Seg_Proc() {
  if (time_all_1s % 20) return;
}

如何使用呢,其实也非常简单,我们只需要按照下方的写法即可(因为一般来说我们都有多个界面进行切换,我们定义一个变量Seg_Show_Mode来进行书写,如果有子界面的话,拿我们就再定义一个mode就行)

void Seg_Proc() {
    if (time_all_1s % 20) return;
    switch(Seg_Show_Mode)
    {
        case 0:
            //xx界面
            Seg_Buf[0] = xx;
            Seg_Buf[1] = xx;
            break;
        case 1:
            //xx界面
            break;
    }
}
Led灯模块

这里面我的话经常放的是Led,蜂鸣器,继电器,MOTOR口的相关逻辑,这里不需要设置定时进入

void Led_Proc() {}

使用示例如下

void Led_Proc() {
    ucLed[0] = 1;//L1点亮
    ucLed[1] = 0;//L2熄灭
    Relay(1);//打开继电器(L10亮)
    Beep(1);//蜂鸣器开启
    MOTOR(1);//MOTOR输出高电平
    Relay(0);//关闭继电器(L10灭)
    Beep(0);//蜂鸣器关闭
    MOTOR(0);//MOTOR输出低电平
}
Uart串口模块

我们在这里使用了串口超时解析,即10ms内没有收集到串口的信息的时候我们才进入并进行解析(后续在定时器里面会提到这个Sys_Tick),在我们解析结束后使用memset函数将接收的数组置为0(为了防止后续解析被干扰),并将索引置为0,为下一次接收解析进行准备

void Uart_Proc() {
  if (Uart_Rx_Index == 0) return;
  if (Sys_Tick >= 10) {
    Sys_Tick = Uart_flag = 0;

    memset(Uart_Buf, 0, Uart_Rx_Index);
    Uart_Rx_Index = 0;
  }
}

使用示例,这里发送小写的ok返回hello,注意一下,判断的时候用的是单引号而不是双引号

void Uart_Proc() {
  if (Uart_Rx_Index == 0) return;
  if (Sys_Tick >= 10) {
    Sys_Tick = Uart_flag = 0;
	if(Uart_Buf[0]=='o'&&Uart_Buf[1]=='k')
        printf("hello");
    memset(Uart_Buf, 0, Uart_Rx_Index);
    Uart_Rx_Index = 0;
  }
}
定时器0初始化(NE555模块)

这个模块是最有意思的,首先我们需要注意一下,P34和singal的跳线帽要插在一起,并且按键方面需要删除P34,然后才能开始测,这里依然用isp生成一下初始化的定时器0,但是这里要注意,新版的不能生成0ms,我们就随便生成个时间,然后按照我这样勾选后复制粘贴就行

image-20240604202942361

我们需要将TL0和TH0的值改为0,并且在TMOD &= 0xF0;下面对其进行配置 TMOD |= 0x05;将其变为十六位不自动重载(这里具体可以参考手册里面对于定时器的配置,我之前的视频有提到蓝桥杯单片机ne555模块,点击即可观看),这里别忘了EA开启总中断(这是我的习惯),频率的读取放在定时器1那里讲

void Timer0_Init(void)  // 1毫秒@12.000MHz
{
  AUXR &= 0x7F;  // 定时器时钟12T模式
  TMOD &= 0xF0;  // 设置定时器模式
  TMOD |= 0x05;
  TL0 = 0;  // 设置定时初始值
  TH0 = 0;  // 设置定时初始值
  TF0 = 0;  // 清除TF0标志
  TR0 = 1;  // 定时器0开始计时
  EA = 1;
}
定时器1初始化

这是我们轮询的定时器的初始化代码,我们使用isp生成1ms定时器1的代码(记得勾选上使能),老版本没有使能,所以记得加上ET1 = 1开启

image-20240604203409782

当然,不要忘记EA = 1,

void Timer1_Init(void)  // 1毫秒@12.000MHz
{
  AUXR &= 0xBF;  // 定时器时钟12T模式
  TMOD &= 0x0F;  // 设置定时器模式
  TL1 = 0x18;    // 设置定时初始值
  TH1 = 0xFC;    // 设置定时初始值
  TF1 = 0;       // 清除TF1标志
  TR1 = 1;       // 定时器1开始计时
  ET1 = 1;       // 使能定时器1中断
  EA = 1;
}
定时器1中断(重点)

我们在这里放了一堆东西,这里我做一下说明,我们每个中断都有对应的中断号(可以在官方的手册查到),这里定时器1的中断号是3

然后是我们的一个全局定时器(1s,注意time_all_1s的类型要是unsigned int,不然会溢出),在这个定时器里面我们对频率进行了读取,将TH0 << 8 | TL0的结果给了Freq(注意一下Freq的类型要是unsigned int,不然会溢出),在我们读取完数据后,将TH0和TL0进行了置0手动重载,方便下一次进行测量

接着就是一个和Sys_Tick相关的代码,当我们接收到数据的时候Uart_flag会置为1,然后将Sys_Tick置为0,也就是说我们一段时间不接受数据的话,那么Sys_Tick的值会一直累加,直到达到10ms(我们上面在Uart串口模块里面的设定),Uart_flag置为0,Sys_Tick的值置为0,为下一轮串口解析做准备

然后就是我们的一个位选的代码,因为我们数码管本质上是一位一位显示,所以我们需要写一个位选的变量来进行位选,这里使用的Seg_Pos = (++Seg_Pos) % 8将Seg_Pos这个变量在0~7中间进行循环

接着就是我们的数码管显示函数,这里不需要多说了,保持他不变就行,传入为位选、位选对应的段选显示、位选对应的小数点显示

最后就是我们的Led显示函数,这里我并没有选择和数码管同步,而是选择在中断里面写了个for循环来进行显示,因为它不涉及到扫描等操作,本质上就是可以直接一起显示。

void Timer1_Isr(void) interrupt 3 {
  unsigned char i;
  if (++time_all_1s == 1000) {
    time_all_1s = 0;
    Freq = TH0 << 8 | TL0;
    TH0 = TL0 = 0;
  }
  if (Uart_flag) Sys_Tick++;
  Seg_Pos = (++Seg_Pos) % 8;
  Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], Seg_Point[Seg_Pos]);
  for (i = 0; i < 8; i++) Led_Disp(i, ucLed[i]);
}
串口中断(这里代码写死)

我们串口有一个接收中断,串口1的中断号查询后发现为4

我们第一行就是判断是否RI为1,因为RI为1则代表有信息过来了,当我们收集到信息时,我们将Uart_flag置为1,Sys_Tick置为0,开始嘀嗒计时,当我们10ms没有接收到新的数据的时候,我们就认为数据接收完成,可以开始进行串口处理,后面的接收就不需要多讲了,我们是一位一位进行接收的,而读写都是用的SBUF这个缓冲区(串口1,如果是串口2则是S2BUF,但是串口2不会考,所以这里不用管),我们接收完成后,将RI置为0,为下一次接收做准备,最后一行的>10清零,则是为了避免出现传入数据溢出,导致程序异常,这里的10取决于你的字符串数组的位数,我在最上面定的是10位,所以这里写的10

void Uart1_Isr(void) interrupt 4 {
  if (RI) {
    Uart_flag = 1;
    Sys_Tick = 0;
    Uart_Buf[Uart_Rx_Index] = SBUF;
    Uart_Rx_Index++;
    RI = 0;
  }
  if (Uart_Rx_Index > 10) Uart_Rx_Index = 0;
}
main主函数书写

这是我们的一个最初的函数,没什么好说明的,就是需要进行一些初始化而已,但是顺序不要写错了,首先是系统初始化,然后是定时器0(ne555),最后是定时器1(轮询)的初始化。

你可以发现,我在这里进行了一次温度的空读取,因为温度上电后会显示85(温度转换的时间),如果在后面才给他开始温度转换的话,那么很有可能跳85,导致测量结果错误,这里的延时函数也是用isp直接生成的,注意系统时间别选错了就行

image-20240604205532498

我们写完初始化之后就需要写一下循环了,51单片机里面必须将循环写死,所以我们用了while(1),这里的顺序倒没什么特殊的要求,但是尽量按照我这个顺序就行,不会出问题的。

void main() {
    System_Init();
    Timer0_Init();
    Timer1_Init();
    Set_Rtc(ucRtc);
    rd_temperature();
    Delay750ms();
    while (1) {
        Data_Proc();
        Key_Proc();
        Seg_Proc();
        Uart_Proc();
        Led_Proc();
    }
}
完整的main.c
#include "main.h"
/* LED与数码管 */
unsigned char ucLed[8] = {0, 0, 0, 0, 0, 0, 0, 0};
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 Uart_Buf[10];
unsigned char Uart_Rx_Index;
bit Uart_flag;
unsigned char Sys_Tick;
/* 时间*/
unsigned char ucRtc[3] = {11, 11, 11};
unsigned int time_all_1s;

/* 数据 */
unsigned int Freq;

void Data_Proc() {
  if (time_all_1s % 50 == 0) {
    // 时间读取
  }
  if (time_all_1s % 100 == 0) {
    // AD读取
  }
  if (time_all_1s % 500 == 0) {
    // 温度读取
  }
}
/* 键盘处理*/
void Key_Proc() {
  static unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
  if (time_all_1s % 10) return;
  Key_Val = Key_Read();
  Key_Down = Key_Val & (Key_Old ^ Key_Val);
  Key_Up = ~Key_Val & (Key_Old ^ Key_Val);
  Key_Old = Key_Val;
}

/* 数码管处理*/
void Seg_Proc() {
  if (time_all_1s % 20) return;
}
void Led_Proc() {}
void Uart_Proc() {
  if (Uart_Rx_Index == 0) return;
  if (Sys_Tick >= 10) {
    Sys_Tick = Uart_flag = 0;

    memset(Uart_Buf, 0, Uart_Rx_Index);
    Uart_Rx_Index = 0;
  }
}
void Timer0_Init(void)  // 1毫秒@12.000MHz
{
  AUXR &= 0x7F;  // 定时器时钟12T模式
  TMOD &= 0xF0;  // 设置定时器模式
  TMOD |= 0x05;
  TL0 = 0;  // 设置定时初始值
  TH0 = 0;  // 设置定时初始值
  TF0 = 0;  // 清除TF0标志
  TR0 = 1;  // 定时器0开始计时
  EA = 1;
}

void Timer1_Init(void)  // 1毫秒@12.000MHz
{
  AUXR &= 0xBF;  // 定时器时钟12T模式
  TMOD &= 0x0F;  // 设置定时器模式
  TL1 = 0x18;    // 设置定时初始值
  TH1 = 0xFC;    // 设置定时初始值
  TF1 = 0;       // 清除TF1标志
  TR1 = 1;       // 定时器1开始计时
  ET1 = 1;       // 使能定时器1中断
  EA = 1;
}

void Timer1_Isr(void) interrupt 3 {
  unsigned char i;
  if (++time_all_1s == 1000) {
    time_all_1s = 0;
    Freq = TH0 << 8 | TL0;
    TH0 = TL0 = 0;
  }
  if (Uart_flag) Sys_Tick++;
  Seg_Pos = (++Seg_Pos) % 8;
  Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], Seg_Point[Seg_Pos]);
  for (i = 0; i < 8; i++) Led_Disp(i, ucLed[i]);
}
void Uart1_Isr(void) interrupt 4 {
  if (RI) {
    Uart_flag = 1;
    Sys_Tick = 0;
    Uart_Buf[Uart_Rx_Index] = SBUF;
    Uart_Rx_Index++;
    RI = 0;
  }
  if (Uart_Rx_Index > 10) Uart_Rx_Index = 0;
}
void Delay750ms(void)  //@12.000MHz
{
  unsigned char data i, j, k;

  _nop_();
  _nop_();
  i = 35;
  j = 51;
  k = 182;
  do {
    do {
      while (--k);
    } while (--j);
  } while (--i);
}

void main() {
  System_Init();
  Timer0_Init();
  Timer1_Init();
  Set_Rtc(ucRtc);
  rd_temperature();
  Delay750ms();
  while (1) {
    Data_Proc();
    Key_Proc();
    Seg_Proc();
    Uart_Proc();
    Led_Proc();
  }
}

main.h

#include <STC15F2K60S2.H>

#include "ds1302.h"
#include "iic.h"
#include "init.h"
#include "intrins.h"
#include "key.h"
#include "led.h"
#include "onewire.h"
#include "seg.h"
#include "stdio.h"
#include "string.h"
#include "uart.h"
#include "ul.h"

id Key_Proc() {
static unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
if (time_all_1s % 10) return;
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Old ^ Key_Val);
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);
Key_Old = Key_Val;
}

/* 数码管处理*/
void Seg_Proc() {
if (time_all_1s % 20) return;
}
void Led_Proc() {}
void Uart_Proc() {
if (Uart_Rx_Index == 0) return;
if (Sys_Tick >= 10) {
Sys_Tick = Uart_flag = 0;

memset(Uart_Buf, 0, Uart_Rx_Index);
Uart_Rx_Index = 0;

}
}
void Timer0_Init(void) // 1毫秒@12.000MHz
{
AUXR &= 0x7F; // 定时器时钟12T模式
TMOD &= 0xF0; // 设置定时器模式
TMOD |= 0x05;
TL0 = 0; // 设置定时初始值
TH0 = 0; // 设置定时初始值
TF0 = 0; // 清除TF0标志
TR0 = 1; // 定时器0开始计时
EA = 1;
}

void Timer1_Init(void) // 1毫秒@12.000MHz
{
AUXR &= 0xBF; // 定时器时钟12T模式
TMOD &= 0x0F; // 设置定时器模式
TL1 = 0x18; // 设置定时初始值
TH1 = 0xFC; // 设置定时初始值
TF1 = 0; // 清除TF1标志
TR1 = 1; // 定时器1开始计时
ET1 = 1; // 使能定时器1中断
EA = 1;
}

void Timer1_Isr(void) interrupt 3 {
unsigned char i;
if (++time_all_1s == 1000) {
time_all_1s = 0;
Freq = TH0 << 8 | TL0;
TH0 = TL0 = 0;
}
if (Uart_flag) Sys_Tick++;
Seg_Pos = (++Seg_Pos) % 8;
Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], Seg_Point[Seg_Pos]);
for (i = 0; i < 8; i++) Led_Disp(i, ucLed[i]);
}
void Uart1_Isr(void) interrupt 4 {
if (RI) {
Uart_flag = 1;
Sys_Tick = 0;
Uart_Buf[Uart_Rx_Index] = SBUF;
Uart_Rx_Index++;
RI = 0;
}
if (Uart_Rx_Index > 10) Uart_Rx_Index = 0;
}
void Delay750ms(void) //@12.000MHz
{
unsigned char data i, j, k;

nop();
nop();
i = 35;
j = 51;
k = 182;
do {
do {
while (–k);
} while (–j);
} while (–i);
}

void main() {
System_Init();
Timer0_Init();
Timer1_Init();
Set_Rtc(ucRtc);
rd_temperature();
Delay750ms();
while (1) {
Data_Proc();
Key_Proc();
Seg_Proc();
Uart_Proc();
Led_Proc();
}
}




### main.h

```c
#include <STC15F2K60S2.H>

#include "ds1302.h"
#include "iic.h"
#include "init.h"
#include "intrins.h"
#include "key.h"
#include "led.h"
#include "onewire.h"
#include "seg.h"
#include "stdio.h"
#include "string.h"
#include "uart.h"
#include "ul.h"
  • 26
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

左岚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值