蓝桥杯笔记


“免责声明” ( •̀ ω •́ )✧

代码未全部验证,也许存在BUG,如发现错误欢迎指正,不愿意指正那就当作没看见也行
所有说明文字仅代表笔者个人想法

修正日志

从2023-02开始的修正日志:

date brief
2023-02-02 22:41 IIC/AT24C02 : 读取函数误写为IIC_Write,修正为IIC_Read
2023-02-02 22:53 IIC/AT24C02 : 发送函数数据参数unsigned char data不建议命名为data,data为C51保留字,使用会报错,建议改为dat
2023-03-15 21:45 修正超声波代码中#inlcude "intrins.h" 中写错的include;修正超声波代码中的管脚定义命名错误(sbit ULTX = P1^1; to ULRX)

文章目录

  • 蓝桥杯笔记
    • “免责声明” ( •̀ ω •́ )✧
    • 修正日志
    • CT107D硬件概况
    • 程序
      • 基础设备控制
      • 数码管驱动
      • 按键
        • § 独立按键
        • § 矩阵按键
      • IIC
        • § AT24C02
        • § AD/DA
      • 18B20--One Wire
      • DS1302
      • Uart串口
      • 超声波
      • 频率测量
    • 系统结构
      • Project ψ(._. )>
      • Main函数 ψ(._. )>
      • 时间控制 ψ(._. )>

CT107D硬件概况

首先是国信长天CT107D开发板的硬件概况,怎么说呢,一言难尽,268软妹币,血亏

其中虽然板上留有一些外设的拓展口,但实际上是不存在附带模块的,没错,268的板子连LED点阵都不带。 ̄へ ̄


程序

基础设备控制

由于开发板的设计,这块板上点个灯稍微有一丢丢复杂,根据电路结构,8颗LED需要通过74HC138去操作。

#mermaid-svg-atNvH6SDivDU7TSE {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-atNvH6SDivDU7TSE .error-icon{fill:#552222;}#mermaid-svg-atNvH6SDivDU7TSE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-atNvH6SDivDU7TSE .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-atNvH6SDivDU7TSE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-atNvH6SDivDU7TSE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-atNvH6SDivDU7TSE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-atNvH6SDivDU7TSE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-atNvH6SDivDU7TSE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-atNvH6SDivDU7TSE .marker.cross{stroke:#333333;}#mermaid-svg-atNvH6SDivDU7TSE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-atNvH6SDivDU7TSE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-atNvH6SDivDU7TSE .cluster-label text{fill:#333;}#mermaid-svg-atNvH6SDivDU7TSE .cluster-label span{color:#333;}#mermaid-svg-atNvH6SDivDU7TSE .label text,#mermaid-svg-atNvH6SDivDU7TSE span{fill:#333;color:#333;}#mermaid-svg-atNvH6SDivDU7TSE .node rect,#mermaid-svg-atNvH6SDivDU7TSE .node circle,#mermaid-svg-atNvH6SDivDU7TSE .node ellipse,#mermaid-svg-atNvH6SDivDU7TSE .node polygon,#mermaid-svg-atNvH6SDivDU7TSE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-atNvH6SDivDU7TSE .node .label{text-align:center;}#mermaid-svg-atNvH6SDivDU7TSE .node.clickable{cursor:pointer;}#mermaid-svg-atNvH6SDivDU7TSE .arrowheadPath{fill:#333333;}#mermaid-svg-atNvH6SDivDU7TSE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-atNvH6SDivDU7TSE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-atNvH6SDivDU7TSE .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-atNvH6SDivDU7TSE .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-atNvH6SDivDU7TSE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-atNvH6SDivDU7TSE .cluster text{fill:#333;}#mermaid-svg-atNvH6SDivDU7TSE .cluster span{color:#333;}#mermaid-svg-atNvH6SDivDU7TSE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-atNvH6SDivDU7TSE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

STC15
74HC138
74HC02
P2
STC15F2
Y0-Y7
P25+P26+P27
Y4C-Y7C
Y4-Y7
Y4C
LED灯
Y5C
SETP/RELAY...
Y6C
7SEG位选
Y7C
7SEG段选
P2 = (P2&0x1F)|0xA0;
/*
0x80: 100 --4-->Y4C: LED灯
0xA0: 101 --5-->Y5C: 挂在ULN2003上的外设
0xC0: 110 --6-->Y6C: 数码管位选
0xE0: 111 --7-->Y7C: 数码管段选
*/
P0 = ctrl;
P2 &= 0x1F;

[目录](# “免责声明” ( •̀ ω •́ )✧)


数码管驱动

位选为Y6C选定的575,段选则为Y7C,即P25-P27组成6和7;

即110和111,那P2就是C0H和E0H;

C0位选,E0段选;

void Display(void)
{static dispcom;/*消隐*/P0 = 0xFF;                                   //芯片选定到P0复制完成的间隙会产生残影,故先赋值P2 = (P2&0x1F)|0xE0;                      // 控制选定的575且不改变P2其他管脚状态P0 = 0xFF;P2 &= 0x1F;                                  // 关闭选定/*位选*/P2 = (P2&0x1F)|0xC0;P0 = 0x01<<dispcom;                            // 循环显示P2 &= 0x1F;/*段选*/P0 = 0xFF;P2 = (P2&0x1F)|0xE0;P0 = DispTab[DispBuf[dispcom]];               // DispTab : 共阳数码管段选码  DispBuf : 显示缓冲区P2 &= 0x1F;dispcom++;if(dispcom==8)dispcom = 0;                 // 防止dispcom超出范围
}

按键

§ 独立按键

J5接至BTN,P3低4位控制4个按键;

unsigned char KeyValue = 0xFF;          //全局变量记录按键值
------------------------------------------------------------
void BTN(void)
{/*变量*/static unsigned char keyvalue;       //临时记录键值static unsigned char keypress;      //记录扫描按下次数static bit keyfree = 1;              //按键按下与否unsigned char temp;                 //为方便判断/*扫描*/P3 |= 0x0F;                           //将P3口低4位设为高temp = P3&0x0F;                        //取P3口低4位,其余为0,赋给temp,便于判断/*消抖*/if(temp!=0x0F)keypress++;           //如果temp不等于0x0F,说明有键按下else keypress = 0;                    //如果在keypress加到5之前temp回归0x0F,就不算作按下了/*识别*/if(keypress==5&&keyfree)         //如果按键按下持续5个扫描,且按键在未按下的状态,就算按键按下了{keypress = 0;                  //归0keypresskeyfree = 0;                   //将按键状态设为按下,即不自由(free)(~ ̄▽ ̄)~switch(temp)                  //按键识别{case 0x07:keyvalue = 4;break;case 0x0B:keyvalue = 5;break;case 0x0D:keyvalue = 6;break;case 0x0E:keyvalue = 7;break;}}/*松手检测*/if(temp==0x0F&&keyfree==0)         //若temp回归0x0F,且按键状态为按下,说明松手了,返回键值{keyfree = 1;                    //松手后将keyfree改为1,它免费了(~ ̄▽ ̄)~KeyValue = keyvalue;}else Keyvalue = 0xFF;             //其他情况返回0xFF
}

§ 矩阵按键

J5接至KBD,P3(不存在P36、P37)和P42、P44共同控制按键;

unsigned char KeyValue = 0xFF;          //全局变量记录按键值
------------------------------------------------------------
void KBD(void)
{/*变量*/unsigned char S1=0x00,S2=0x00;     //按键键值的行,列数据static unsigned char keyvalue;       //临时记录键值static unsigned char keypress;      //记录扫描按下次数static bit keyfree = 1;              //按键按下与否unsigned char temp = 0xFF;         //临时存放扫描数据/*扫描*/P3 = 0x0F;                         //将P3口低4位设为高P42 = 0;P44 = 0;                  //P3高2位和P42P44组成高4位设为低temp = (P3&0x0F);/*消抖*/if(temp!=0x0F)keypress++;              //如果P3低4位不等于0x0F,说明疑似有键按下//此处容易出现P3&0x0F!=0x0F这类错误else keypress = 0;                   //如果在keypress加到5之前P3低4位回归0x0F,就不算作按下了/*识别*/if(keypress==5&&keyfree)            //如果按键按下持续5个扫描,且按键在未按下的状态,就算按键按下了{keypress = 0;                  //归0keypresskeyfree = 0;                   //将按键状态设为按下,即不自由(free)(~ ̄▽ ̄)~S1 = temp;                   //记录按键行值P3 = 0xF0;P42 = 1;P44 = 1;               //反转扫描,确定列if(!P42)       S2 = 0xB0;     //如果是P42=0;说明按下的键就在这列else if(!P44)  S2 = 0x70;     //同上/*这一句一定要用else if 否则S8-S11失效如果不用else if,!P42确实为1,但是!P44为0会导致else的执行覆盖S2*/else          S2 = temp;     //否则数据在P3中,记录列switch(S1|S2)                  //按键识别{/*S4~S7*/case 0x77:keyvalue = 4;break;case 0x7B:keyvalue = 5;break;case 0x7D:keyvalue = 6;break;case 0x7E:keyvalue = 7;break;/*S8~S11*/case 0xB7:keyvalue = 8;break;case 0xBB:keyvalue = 9;break;case 0xBD:keyvalue = 10;break;case 0xBE:keyvalue = 11;break;/*S12~S15*/case 0xD7:keyvalue = 12;break;case 0xDB:keyvalue = 13;break;case 0xDD:keyvalue = 14;break;case 0xDE:keyvalue = 15;break;/*S15~S19*/case 0xE7:keyvalue = 16;break;case 0xEB:keyvalue = 17;break;case 0xED:keyvalue = 18;break;case 0xEE:keyvalue = 19;break;}}/*松手检测*/if(temp==0x0F&&keyfree==0)          //若P3回归0x0F,且按键状态为按下,说明松手了,返回键值{keyfree = 1;KeyValue = keyvalue;}else Keyvalue = 0xFF;              //其他情况返回0xFF
}

IIC

§ AT24C02

不用从头写起,但是需要自己写最后使用的发送和接收函数;

数据包给出了启动停止应答等操作的函数,只需要知道IIC通信的时序或者步骤即可;

我们需要从数据手册中得到这个时序;

在AT24C02的数据手册中,我们可以在Read Operation下面找到上面这张图。乍一看看不出到底Byte Write,有多少个步骤,但实际上重点有两个图表,还包括上面那个;如下:

Figure 7表明:MSB,R/W,LSB都属于同一个字节,而在赛方给出的IIC参考程序中,有两种操作函数,电平变化和字节传输;

所以将Figure 8划分一下也变得非常简单:

易得它的顺序是

IIC_Start();
IIC_SendByte(?);
IIC_WaitAck();
IIC_SendByte(?);
IIC_WaitAck();
IIC_SendByte(?);
IIC_WaitAck();
IIC_Stop();

接下来需要知道“?”里填啥?

首先是DEVICE ADDRESS,其实甚至可以从图中出答案(当然,给出的数据里也有),图中的就是正确的(必须的呀),即0xA0;板上AT24C02芯片地址为000;

第二个发送的字节是WORD ADDRESS,即数据要写在AT24C02的哪里?这个位置是由使用情况决定的,于是设置一个输入参数,add,最后是发送的数据,自然,也是参数;

所以IIC写函数最后是:

void IIC_Write(unsigned char add,unsigned char dat)
{IIC_Start();IIC_SendByte(0xA0);                //或者写给出的SlaveAddrWIIC_WaitAck();IIC_SendByte(add); IIC_WaitAck();    IIC_SendByte(dat);IIC_WaitAck();IIC_Stop();
}

那么读函数亦是如此啦;

当然,我们看到读取并不简单,它有三种模式;即 Current Address Read、 Random Read、 Sequential Read.

类型 描述
Current Address Read 未断电时,读上一次读的地址(即当前地址)。断电后,地址归为0x00
Random Read 指定地址读取(这是我们需要的)
Sequential Read 连续读,先这样,再这样,再那样,就可以一个地址接下一个地址连续读

显然,传输步骤是:

IIC_Start();
IIC_SendByte(?);            //看图,"?"应该是0xA0(或SlaveAddrW);
IIC_WaitAck();
IIC_SendByte(?);            //需要读出的地址,设置形参add
IIC_WaitAck();
IIC_Start();
IIC_SendByte(?);            //0xA1
IIC_WaitAck();
IIC_RecByte();              //这是数据,设置形参RecData
IIC_SendAck(?);             //主机应答(是1哦)
IIC_Stop();

所以,最后是:

unsigned char IIC_Read(unsigned char add)
{unsigned char RecData;/*伪写*/IIC_Start();IIC_SendByte(0xA0);            IIC_WaitAck();IIC_SendByte(add);            IIC_WaitAck();/*读取*/IIC_Start();IIC_SendByte(0xA1);     IIC_WaitAck();RecData = IIC_RecByte();         IIC_SendAck(1);         IIC_Stop();return RecData;
}

AT24C02的读写差不多就是这样了。

§ AD/DA

对于DA过程,S直接是start;而对于AD(对应IIC的读)需要包含伪写;

首先看图中各个字节如何划分;

对于板上PCF8591来讲,地址位字节的高7位都是固定的,不固定的只有读写位,看图也可知写为低位有效。所以对写来说地址字节就是0x90;那么在上面的流程图中,ADDRESS和后面那个数字位肯定是在1个字节中,属于同一步操作;

控制字:

最低两位:通道,AD输入只能在0通道,DA输出可以选择0-3通道 DA输出只能在0通道,AD输入可以选择0-3通道;

2号位:自动递增位,这里用不到,不管,设为0;

3号位:固定为0;(最高位同)

4,5号位:输入模式,AD输入时的输入模式,我们需要一一对应,直接设为00;

6号位:输出使能,DA输出时设为1;

了解这个之后就可以直接看最上面两张图写出大致步骤了;

IIC_Start(void);
IIC_Stop(void);
IIC_WaitAck(void);
IIC_SendAck(bit ackbit);
IIC_SendByte(unsigned char byt);
IIC_RecByte(void);
//DA输出
IIC_Start();
IIC_SendByte(0x90);             //输出对应写,低电平有效,故为90
IIC_WaitAck();
IIC_SendByte(0x40);             //输出使能,通道0输出
IIC_WaitAck();
IIC_SendByte(DA_data);          //输出大小由参数确定
IIC_WaitAck();
IIC_Stop();

同理也可得到AD转换的程序;

//AD输入
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(channal);          //关闭输出使能,选择通道输入
IIC_WaitAck();
/*以上为伪写*/
IIC_Start();
IIC_SendByte(0x91);             //输出对应读,高电平有效,故为91
IIC_WaitAck();
AD_data = IIC_RecByte();       //接收数据,存入参数AD_data
IIC_SendAck(1);                 //接收时主机发送应答,和IIC不同,此时应答为1结束
IIC_Stop();

补全函数头和相关的变量定义可以得到完整的AD/DA 程序。

PS:调用AD/DA转换函数时,注意读取的是上一次转换的值。所以必要的时候要调用两遍。


18B20–One Wire

对于18B20,它使用的是onewire总线,由于只有一根线进行通信,其含义基本都是通过电平持续时间来表示的。所以对时间的把控相当严格。

这时要做一件非常重要的事,将原来资料中的延时函数改一下,否则时间就不对了;

//单总线延时函数
//void Delay_OneWire(unsigned int t)  //STC89C52RC
//{//    while(t--);
//}
------------------------------------------------------------------
void Delay_OneWire(unsigned int t)  //STC15F2K60S2
{t *= 12;while(t--);
}

即“简单的硬件条件需要相对复杂的软件来补充”

所以软件会相对复杂;

给出的资料就是这么多了。接下来看数据手册;

温度在18B20中的数据存储位置等信息,但是这些对我们帮助不大,主要是相关的驱动程序都已经给出来了,我们需要知道的是它的读取步骤;

而在这个例子中,步骤就非常明显了;

我们只想让它做两件事,将温度转换为数据,读出数据;

所以在这个例子中有些东西是不需要的,比如Match ROM(匹配ROM),因为我们板上就只有一个18B20而已。自然,send DS18B20 ROM code也是不必要的,取而代之的是Skip ROM (跳过ROM);

于是有如下步骤:

init_ds18b20();
Write_DS18B20(0xCC);            //跳过ROM
Write_DS18B20(0x44);            //转换命令
Delay_OneWire(20);              //等待转换init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0xBE);            //读取命令Temp_L = Read_DS18B20();     //从低位读起
Temp_H = Read_DS18B20();

然后是对数据的处理了,前面说到的“用处不大”Figure2现在用处大了,18B20用多种温度的分辨率,默认的是12位分辨率的,也就是11位数据和其余的符号位;在下面这段话中有明确的表述

​ The sign bits (S) indicate if the temperature is positive or negative: for positive numbers S = 0 and for negative numbers S = 1.
If the DS18B20 is configured for 12-bit resolution, all bits in the temperature register will contain valid data.

​ For 11-bit resolution, bit 0 is undefined. For 10-bit resolution, bits 1 and 0 are undefined, and for 9-bit
resolution bits 2, 1, and 0 are undefined.
​ Table 1 gives examples of digital output data and the corresponding temperature reading for 12-bit resolution conversions.

在默认12位情况下,数据值是实际温度的16倍;值得一提的是,温度数据在18B20中的存储可以理解为是以补码的形式存储的;

所以,最后,程序可以写成这个样子

unsigned int T_Value = 0x00;
bit T_Symbol = 0;
--------------------------------------------------------------------------
void rd_temperature(void)
{unsigned char Temp_L,Temp_H;unsigned int Temp = 0x00;init_ds18b20();Write_DS18B20(0xCC);          Write_DS18B20(0x44);        Delay_OneWire(20);          init_ds18b20();Write_DS18B20(0xCC);Write_DS18B20(0xBE);         Temp_L = Read_DS18B20();       //先低位再高位Temp_H = Read_DS18B20();Temp = Temp_H;                    //将2byte温度数据组成一个温度值Temp <<= 8;Temp += Temp_L;if(Temp_H&0x80){T_Symbol = 1;Temp = ~Temp + 1;         //T_Symbol == 1:负温度}else {T_Symbol = 0;               //T_Symbol == 0:正温度}T_Value = Temp/16.0*100+0.5; //除以16.0,或者*0.0625,不能是/16,保留两位小数*100,但T_Value会是真实值的100倍
}

注意点:

  1. 温度值数据类型一定至少得是unsigned int,不要顺手写成了unsigned char。
  2. 读取温度数据时是先低位,再高位。

PS:调用温度转换函数时,注意读取的是上一次转换的值。所以必要的时候要调用两遍。


DS1302

DS1302;先看给出的程序资料;

打开下面两个函数,就可以知道Write_Ds1302();是为Write_Ds1302_Byte()和Read_Ds1302_Byte()服务的,知道这点很重要,因为这样一来,我们只需要考虑怎么用这两个函数组成我们使用的函数;(两个函数而已,步骤一定不会太难,事实也正是如此)

实际上,直接用给出的函数读寄存器就可以了;

unsigned char SetRTC[3] = {0x12,0x50,0x59};
unsigned char ReadRTC[3] = {0x00,0x00,0x00};
------------------------------------------------------------------------
void Set_RTC(void)
{Write_Ds1302_Byte(0x8E,0x00);              //关闭写保护Write_Ds1302_Byte(0x84,SetRTC[0]);           //设置时Write_Ds1302_Byte(0x82,SetRTC[1]);         //设置分Write_Ds1302_Byte(0x80,SetRTC[2]);         //设置秒Write_Ds1302_Byte(0x8E,0x80);              //打开写保护
}void Read_RTC(void)
{/*注意!读和写寄存器是不一样的*/ReadRTC[0] = Read_Ds1302_Byte(0x85);         //读时ReadRTC[1] = Read_Ds1302_Byte(0x83);           //读分ReadRTC[2] = Read_Ds1302_Byte(0x81);           //读秒
}

需要注意的是,需要在数码管上显示的时候,取位数得/16而不是/10;

并且,在使用时,初始化中要先写一次初始值,否则会有意想不到的惊喜效果。即DS1302使用起来应该是这样的:

/*初始化*/
Set_RTC();while(1)
{//在该获取时间时Read_RTC();
}

PS:

头文件声明数组时不需要带上数组长度,这样就可以了。

unsigned char SetRTCData[];
unsigned char ReadRTCData[];

Uart串口

串口使用

串口最简使用方法

  1. 初始化
  2. 中断服务函数
  3. 发送数据/接收数据
/*
STC15F2K60S2  定时器2用作串口1的波特率发生器
*/// 串口1中断服务函数
void UartInit(void)     //9600bps@12.000MHz
{SCON = 0x50;      //8位数据,可变波特率AUXR |= 0x01;      //串口1选择定时器2为波特率发生器AUXR |= 0x04;        //定时器2时钟为Fosc,即1TT2L = 0xC7;           //设定定时初值T2H = 0xFE;            //设定定时初值AUXR |= 0x10;      //启动定时器2ES = 1;EA = 1;
}// UART 中断服务程序
void Uart() interrupt 4
{uchar i = 0;              //i用于存储接收数据if (RI){RI = 0;                 //清除RI位i = SBUF;               //P0显示串口数据}
}// 发送字符串
void SendString(char *s)
{while (*s != '\0')          //检测字符串结束标志{SBUF = *s++;while(TI==0);TI = 0;}
}
  1. 利用stc-isp软件生成初始化程序:

  1. RI为接收中断标志位,即RI==1时表示有被数据写入SBUF。

  2. TI为发送标志位,TI==1时表示数据已发送。(RI=1或者TI=1都可以触发串口中断,进入中断函数)

SCON寄存器详情:

D7 D6 D5 D4 D3 D2 D1 D0
SCON SM0 SM1 SM2 REN TB8 RB8 TI RI 98H
位地址 9FH 9EH 9DH 9CH 9BH 9AH 99H 98H

超声波

超声波测距过程:发送声波,计时,接收声波,计算距离;

没有关于超声波有用的信息给出,需要记住超声波模块大致的使用过程;

#mermaid-svg-UnRWQigXFfEzYO1h {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-UnRWQigXFfEzYO1h .error-icon{fill:#552222;}#mermaid-svg-UnRWQigXFfEzYO1h .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UnRWQigXFfEzYO1h .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-UnRWQigXFfEzYO1h .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UnRWQigXFfEzYO1h .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UnRWQigXFfEzYO1h .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UnRWQigXFfEzYO1h .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UnRWQigXFfEzYO1h .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UnRWQigXFfEzYO1h .marker.cross{stroke:#333333;}#mermaid-svg-UnRWQigXFfEzYO1h svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UnRWQigXFfEzYO1h .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UnRWQigXFfEzYO1h .cluster-label text{fill:#333;}#mermaid-svg-UnRWQigXFfEzYO1h .cluster-label span{color:#333;}#mermaid-svg-UnRWQigXFfEzYO1h .label text,#mermaid-svg-UnRWQigXFfEzYO1h span{fill:#333;color:#333;}#mermaid-svg-UnRWQigXFfEzYO1h .node rect,#mermaid-svg-UnRWQigXFfEzYO1h .node circle,#mermaid-svg-UnRWQigXFfEzYO1h .node ellipse,#mermaid-svg-UnRWQigXFfEzYO1h .node polygon,#mermaid-svg-UnRWQigXFfEzYO1h .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UnRWQigXFfEzYO1h .node .label{text-align:center;}#mermaid-svg-UnRWQigXFfEzYO1h .node.clickable{cursor:pointer;}#mermaid-svg-UnRWQigXFfEzYO1h .arrowheadPath{fill:#333333;}#mermaid-svg-UnRWQigXFfEzYO1h .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UnRWQigXFfEzYO1h .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UnRWQigXFfEzYO1h .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-UnRWQigXFfEzYO1h .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-UnRWQigXFfEzYO1h .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UnRWQigXFfEzYO1h .cluster text{fill:#333;}#mermaid-svg-UnRWQigXFfEzYO1h .cluster span{color:#333;}#mermaid-svg-UnRWQigXFfEzYO1h div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UnRWQigXFfEzYO1h :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

Measure()
TR0 = 1 开始计时/数
SendUltra()-发送声波
if(返回信号)
TR0 = 0 停止计时/数
读出TL0,TH0
1. 定义发送和接收管脚
2. 初始化Timer0
3. 定义发送8个40MHz信号函数
4. 定义测距函数
5. 发送信号,计时,接收信号,计算距离
#include "intrins.h"#define Nops {_nop_();_nop_();_nop_();_nop_();_nop_();\
_nop_();_nop_();_nop_();_nop_();_nop_();\
_nop_();_nop_();_nop_();_nop_();_nop_();}sbit ULTX = P1^0;
sbit ULRX = P1^1;unsigned int Distance = 0;
---------------------------------------------------------------
void Timer0Init(void)
{//STC-ISP直接获取任意一个16位计时器初始化函数//需要是12T时钟的;并且将TH0,TL0都设为0;
}void SendUltra()
{unsigned char fre;for(fre=0;fre<8;fre++){ULTX = 1;Nops;Nops;Nops;Nops;Nops;Nops;Nops;Nops;Nops;Nops;ULTX = 0;Nops;Nops;Nops;Nops;Nops;Nops;Nops;Nops;Nops;Nops;}
}void Measure(void)
{unsigned int time = 0;/*发送*/SendUltra();                  //发送信号TR0 = 1;                     //定时器0开始计时/*接收*/while(ULRX==1&&TF0==0);          //收到信号且计时器未溢出,对应两种可能--收到回声(ULRX==0)和超时未收到回音(TF==1)TR0 = 0;                     //定时器0关闭计时/*分类计算*/if(TF0)                           //如果导致中断了,说明在很长时间内(大概65ms)没有收到回声{TF0 = 0;Distance = 9999;            //超时直接加到最远距离}else{time = TH0;time <<= 8;time |= TL0;Distance = (uint)time*0.017;}/*复位*/TL0 = 0x00;                       //重新设置定时初值TH0 = 0x00;
}

程序中各种时间乱七八糟,要理解这些必须了解的一个东西就是:单片机的时间和速度到底是怎么样的?

时钟周期:又称为震荡周期,是为单片机提供定时信号的震荡源的周期,是单片机最基本的时间单位;状态周期:CPU从一个状态转换到另一状态所需要的时间。简单地说每个状态周期分为两个震荡周期(时钟周期);机器周期:一个机器周期包含六个状态例如,取指令、存储器读、存储器写等。机器周期 = 6个状态周期 = 12个时钟周期。指令周期: 顾名思义,指令周期就是执行一条指令所需的全部时间。程序中用到的nop()函数就只需要一个指令周期;

① 产生信号

用其他语句来对发送的脉冲电平计时肯定不如nop()来得准确,于是,要产生40KHz的方波,就要确定发送信号引脚处于一个状态的时间;

对于STC15F2K60S2(1T高速芯片),一个指令周期就是一个时钟周期,即 T n o p = 1 / 12 M T_{nop} = 1/12M Tnop​=1/12M(s);

而 T U L T X = 1 / 0 = 1 / 40 K = 300 T n o p T_{ULTX = 1/0} = 1/40K = 300T_{nop} TULTX=1/0​=1/40K=300Tnop​ ;

即产生40KHz方波需要ULTX处于高电平150个NOP,低电平150个NOP;

② 计时

12T计时器每12个时钟周期计数+1;即T = 12time/12M (s) = time us;


频率测量

大致步骤:

① 设置计时器0;设置为P34触发的计数器;

② 用计时器0计算500ms内的P34脉冲数;

③ 计算频率

其中TMOD寄存器中 C / T ‾ C/\overline{T} C/T就是控制计数器和定时器切换的“开关”;

和STC-ISP软件中的定时器代码唯一的区别就是打开了这个开关,并且计数槽清零。

//计时器0初始化函数
void Timer0Init(void)
{AUXR &= 0x7F;                     TMOD &= 0xF0;                      //保持计时器1的设置不变  11110000 & 计时器1设置TMOD |= 0x04;                      //设置定时器模式为计数TL0 = 0x00;                            TH0 = 0x00;                            TF0 = 0;                           TR0 = 0;
}//频率测量函数
void FreMeasure(void)
{if(G_Time_1ms%1000==0){Timer0Init();TR0 = 1;}else if(G_Time_1ms%1000==500){TR0 = 0;Frequency = TH0;Frequency <<= 8;Frequency += TL0;Frequency *= 2;           //0.5s这么多,1s就是*2TH0 = 0;                //重置计数槽TL0 = 0;}
}

注意:测量函数放在计时器1的中断函数中;不然可能在还没执行到测量函数时时间点就过去了;


系统结构

Project ψ(._. )>

小项目来说,笔者倾向于尽量放在同一个源文件,一是自己写着省时省力,不容易出错。二是分太多文件没有必要,很容易出现一个c文件和对应的头文件加起来都没几行代码,另外,如果系统中功能相互勾连,一个c文件中的函数要用到另一个c文件中的变量,头文件包含来包含去,整个项目结构更加混乱。

相反,如果在一个c文件中,你可以将各部分写得模块分明,那也是相当漂亮的。

而对于一个文件显得冗长,则可以在coding过程中将暂时不用管的函数收起(绝大部分编辑器都可以做到这点)。如此一来,coding高效且代码漂亮。

下面给出个人习惯的代码结构参考:

/*------------------------------------------------------------------------------
----------------------        属于自己的个性区域        -------------------------
------------------------------------------------------------------------------*//*----------------------------头文件及宏定义----------------------------------*/
#include
#define
/*-------------------------------变量定义-------------------------------------*/
uchar
uint
/*-------------------------------函数声明-------------------------------------*/
void Func(void);
/*---------------------------------主函数-------------------------------------*/
void main(void)
{//
}
/*-------------------------------函数定义-------------------------------------*/
// balabalabala
void Func(void);

Main函数 ψ(._. )>

个人认为主函数是一个项目代码结构的体现,一个好的结构其主函数一定是层次分明,一目了然的。(也可能是我个人执念吧o((>ω< ))o)

在下面的主函数控制代码中,以定时器1作为系统的时间管理,利用一系列代表不同时间的变量作为时间标志,类似于定了一个闹钟,闹钟响的时候就去做该做的事,否则就休息(空转while(1));

void main()
{/*初始化*/Init();/*主循环*/while(1){if(TimeFlag_10ms){TimeFlag_10ms = 0;Function();if(TimeFlag_200ms){TimeFlag_200ms = 0;Function();}}}
}

时间控制 ψ(._. )>

void Timer1Sr(void) interrupt 3
{/*时间控制*/Time_1ms++;if(Time_1ms%10==0){TimeFlag_10ms = 1;if(Time_1ms%20==0){TimeFlag_20ms = 1;if(Time_1ms%200==0){TimeFlag_200ms = 1;}}}/*频率测量*///Measure();/*显示刷新*/Display();
}

[蓝桥杯单片机] - 蓝桥杯单片机CT107D竞赛板各模块代码分析相关推荐

  1. 《蓝桥杯备赛》CT117E嵌入式竞赛板LCD驱动库的使用(带完整源码)

    声明:开发板为蓝桥杯CT117E Rev 1.1,资源只用于学习用途 1.蓝桥杯LCD驱动库(官方提供) lcd.c /*程序说明: CT117E嵌入式竞赛板LCD驱动程序软件环境: Keil uVi ...

  2. 《蓝桥杯CT107D单片机竞赛板》:蜂鸣器模块

    蜂鸣器模块 实验简介 实验原理图 实验原理 实验程序 关闭蜂鸣器与继电器 简易报警器原理 实验简介 采用软件方式,使得CT107D单片机竞赛板上的蜂鸣器和继电器分别不发声和不吸附. 实验原理图 实验原 ...

  3. 【蓝桥杯单片机最全备考资料】真题、代码、原理图、指导手册、资源包

    目录 前言 一.第一~十三届省/国赛真题 二.第八~十二届省/国赛客观题参考答案与解析 三.<"蓝桥杯"全国软件和信息技术专业人才大赛实训指导书> 四.<51单片 ...

  4. 2020蓝桥杯之单片机设计与开发(1)——CT107D开发板了解与准备

    在快放假期间开始准备蓝桥杯,也感谢我的好朋友带我一起去了解这个比赛. 首先从单片机开始,比赛的时候使用的开发板是官方指定的CT107D单片机竞赛板V20. 先看上面的官方指定开发版原理图,包含了开发板 ...

  5. 蓝桥杯单片机学习过程记录(二十七)超声波模块

    蓝桥杯单片机学习过程记录(二十七)超声波模块 超声波模块的学习,未验证. /* ------------------- 超声波模块 没模块未验证 2020.3.16 ----------------- ...

  6. 蓝桥杯单片机模块代码(AT24C02)(代码+注释)

    本模块是上电可擦除EEPROM,用于存储需要的数据.与上一节使用的底层代码相同,运用同一个总线,其操做顺序与PCF8591除第二步几乎完全一样.相同部分具体可看:蓝桥杯单片机模块代码(PCF8591) ...

  7. **决战2021年单片机蓝桥杯笔记(1)**IIC PCF8591 AT24C02

    **决战2021年单片机蓝桥杯笔记(1)**IIC PCF8591 AT24C02 I2C作为一种多用于板内同步串行通信方式,有一根SCL时钟线负责收发双方的时钟节拍,和一根SDA数据线负责传输数据, ...

  8. 基于STM32G431嵌入式竞赛板HAL库的程序设计——备赛蓝桥杯

    LED的程序设计 8个LED在该嵌入式板子上分别与PC8-PC15相连,因为LED一段连接着VCC,高电平,所以低电平LED亮,高电平LED灭,LED的另一端连接着锁存器,而锁存器在高电平时不锁存数据 ...

  9. 递增三元组蓝桥杯c语言,蓝桥-递增三元组-蓝桥

    蓝桥-递增三元组-蓝桥 蓝桥-递增三元组-蓝桥 手动求解一下会发现,B数组是关键 若固定b = B[i] a中的可能的取值是:a0 ----- at小于等于b的元素下标(小于b的个数) c中的可能取值 ...

最新文章

  1. plsql developer导出csv乱码问题
  2. Swift3.0语言教程获取C字符串
  3. 一个vue加egg.js的博客
  4. Zookeeper的一些Bugs
  5. 国内 RISC-V 产学研基地成立,Intel、Arm、RISC-V 将三分天下?
  6. 使用javaGUI编写检测是否有网
  7. 《计算机组网试验-DNS域名服务协议 》杭州电子科技大学
  8. 2021-12-11 根据单词首字母查找单词
  9. 【线性规划】投资的收益和风险
  10. DELL T7600工作站重新安装WIN7系统
  11. 1万元左右理财方法有那些
  12. 拼多多商品id怎么查看 拼多多店铺ID怎样看
  13. Android学习笔记之如何使用圆形菜单实现旋转效果...
  14. ARM架构SMMU驱动详解
  15. 处理textarea的空格和换行
  16. alin42490怎样解除_我们应该如何思维42490
  17. 习题5-6 对称轴 UVa1595
  18. Yapi测试插件--cross-request
  19. x3daudio1 7.dll怎么修复?修复方法推荐
  20. js sdk 一键分享 微信_微信JSSdk实现分享功能

热门文章

  1. VirtualBox+Vagrant
  2. selinux关闭后mysql_CentOS7中关闭selinux
  3. iMindMap教你如何熟悉自考驾照
  4. mysql8 主从配置
  5. 零基础转行从事云计算运维工作,不得不掌握的几项技能
  6. mongodb 分组聚合_MongoDB聚合嵌套分组
  7. JQuery定时器和轮播图
  8. LabVIEW视觉尺寸测量 范例包含尺寸测量和数据库工具带三菱plc通讯
  9. day14-面向对象作业
  10. Java通信方式总结