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

蓝桥杯笔记


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

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

修正日志

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

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


CT107D硬件概况

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

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


程序

基础设备控制

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

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;					//归0keypress
        keyfree = 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;					//归0keypress
        keyfree = 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);				//或者写给出的SlaveAddrW
    IIC_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,即1T
	T2L = 0xC7;			//设定定时初值
	T2H = 0xFE;			//设定定时初值
	AUXR |= 0x10;		//启动定时器2
	
	ES = 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寄存器详情:

D7D6D5D4D3D2D1D0
SCONSM0SM1SM2RENTB8RB8TIRI98H
位地址9FH9EH9DH9CH9BH9AH99H98H

超声波

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

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

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就是*2
		
		TH0 = 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();
}

  • 16
    点赞
  • 140
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

川九-EveRYouNg

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

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

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

打赏作者

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

抵扣说明:

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

余额充值