code
code 是一个特定的扩展关键字。
它的意思是:将这些数据存储在程序存储器(ROM / Flash)中,而不是数据存储器(RAM)中。
为什么要用 code?
节省宝贵的RAM:51单片机的RAM空间非常小,而ROM空间相对较大。
你代码里的 seg_wei(数码管位选)和 seg_duan(数码管段选)都是查表用的固定数据,在程序运行过程中绝对不会被修改。
EEPROM的底层
//1+2+2
////while+5
//void ee_write(unsigned char *str,unsigned char addr,unsigned char num)
//{
// EA=0;
// I2CStart();
// I2CSendByte(0xa0);
// I2CWaitAck();
//
// I2CSendByte(addr);
// I2CWaitAck();
//
// while(num--)
// {
// I2CSendByte(*str++);
// I2CWaitAck();
// I2C_Delay(200);
// }
// I2CStop();
//
// EA=1;
// I2C_Delay(255);
// I2C_Delay(255);
// I2C_Delay(255);
// I2C_Delay(255);
//}
最好还是保持第一种写法(把 EA=1 放在 I2CStop() 后面,延时函数的前面)。
下面是具体的原理解释:
- 为什么会有这4个
I2C_Delay(255)?
当你调用 I2CStop() 后,单片机确实已经把数据发给 EEPROM (AT24C02) 了。但是,EEPROM 把接收到的数据搬运并烧写到它内部的存储介质中,是需要物理时间的(这个时间叫"页写周期",通常是 5 毫秒左右)。
这4个 I2C_Delay(255) 就是为了让单片机"傻等"这 5 毫秒,保证 EEPROM 写完,以免紧接着进行下一次读写时失败。
- 为什么
EA=1要放在延时前面?
-
保护通信时序:
EA=0关闭中断是为了保护前面I2CStart到I2CStop的通信过程。因为软件模拟 I2C 的时序很严格,不能被定时器中断打断,否则会导致通信失败。 -
释放 CPU 响应能力: 通信一旦结束(
I2CStop执行完),时序就不再需要保护了!此时立刻EA=1打开总中断,那么在单片机"傻等"这 5 毫秒的过程中,定时器中断依然可以正常工作。
如果你把 EA=1 放到了最后面:
-
意味着在这长达 5 毫秒的延时等待中,单片机的总中断
EA依然是关闭状态。 -
后果: 所有的定时器中断、串口中断全都会被挂起卡住。如果是做蓝桥杯比赛或者带有数码管的开发板,你会发现每次往 EEPROM 写数据时,数码管都会明显闪烁或抖动一下(因为数码管有 5 毫秒没被点亮或没被切换),同时这期间如果串口有数据发过来,也可能会漏接。
//void ee_read(unsigned char *str,unsigned char addr,unsigned char num)
//{
// EA=0;
//
// I2CStart();
// I2CSendByte(0xa0);
// I2CWaitAck();
//
// I2CSendByte(addr);
// I2CWaitAck();
//
// I2CStart();
// I2CSendByte(0xa1);
// I2CWaitAck();
//
// while(num--)
// {
// *str++=I2CReceiveByte();
// if(num)I2CSendAck(0);
// else I2CSendAck(1);
// }
// I2CStop();
//
// EA=1;
//}
这完全取决于你在代码里是把 35 当作"数字"存,还是当作"字符(文本)"存。
但在单片机编程(尤其是蓝桥杯等比赛)中,99% 的情况下 num 应该等于 1。
- 为什么
num = 1是对的?(当作数字存)
在C语言中,一个 unsigned char(无符号字符型/单字节)可以存储的大小范围是 0 到 255。 你的数字是 35,它完全可以被塞进一个字节里。
unsigned char val = 35;
ee_write(&val, 0x00, 1); // 存的时候,num写1,只占用EEPROM的一个地址
ee_read(&val, 0x00, 1); // 读的时候,num写1,直接读出35
当 num=1 时,循环体刚好只执行了 1 次,完美读出 1 个字节。
- 网课博主为什么说
num = 2?(当作字符存)
博主的情况是把 35 当成了字符串 "35",也就是把它拆成了字符 '3' 和字符 '5'。 在计算机里,字符 '3' 的ASCII码是十进制的 51,字符 '5' 的ASCII码是 53。
unsigned char str[2] = {'3', '5'}; // 这是一个数组,占2个字���
ee_write(str, 0x00, 2); // num等于2,占用EEPROM的两个地址
这种写法每次存入一个字符,循环体内执行 2 次。???
总结建议
如果你只是想记录一个传感器数据、设定阈值(比如温度上限 35 度),绝对应该用 num=1,把它当数字存。这样不仅省空间,读出来以后可以直接用来做加减乘除计算。如果拆成字符存,读出来还得自己写代码把 '3' 和 '5' 拼成数字 35,纯属脱裤子放屁。
sys_init() 不需要加时序保护(EA=0 和 EA=1)
关键在于你调用它的位置。
你把 sys_init() 放在了 main() 函数的开头,并且在 Timer1_Init() 和 Timer0_Init() 之前。
这个时候,你的定时器根本还没有开始工作,单片机的总中断(EA)通常也还是默认关闭状态。既然没有任何中断会打断它,自然就不存在"保护"的需求,它安安静静地按顺序执行完就行了。
超声波底层
//产生8个40kHZ的脉冲
void wave_init()
{//50%的占空比,25us就是40khz
unsigned char i;
EA=0;//关闭总中断,防止中断打断
for(i=0;i<8;i++)
{
Tx=1;
Delay12us();
Tx=0;
Delay12us();
}
EA=1;
}
这段代码是专门用来驱动超声波发送探头(Tx)发射超声波的。
超声波测距的原理是:发射头发出声波,遇到障碍物反弹,接收头收到声波。为了让这个过程顺利进行,发射的声波必须满足特定的物理和硬件要求。
- 为什么是 40kHz?为什么延时 12us?
你板子上的超声波发射探头,它的硬件物理共振频率是 40kHz。也就是说,只有你给它提供 40kHz 的方波电信号,它才能把电能最大化地转换成超声波发出去(偏高或偏低都会导致发不出声音或距离极短)。
-
计算周期:频率 f=40,000 Hz,那么一个完整的周期时间 T=1/40000=0.000025 秒=25 微秒(us)。
-
50%占空比:一个周期内,高电平和低电平的时间各占一半,即 25 us/2=12.5 us。
-
代码实现:代码里写了
Tx=1; Delay12us(); Tx=0; Delay12us(); -
虽然延时函数是 12us,但是单片机执行
Tx=1、Tx=0以及for循环本身也需要耗费零点几微秒的时间。加起来刚好约等于 12.5us 的高电平和 12.5us 的低电平,凑成完美的 25us(40kHz)。
- 为什么是循环 8 次?(
for(i=0;i<8;i++))
这是超声波测距的行业标准做法:
- 为什么计算公式是
time * 0.017?(重点!)
这是一个非常经典的物理公式简化:距离 = 速度 × 时间 ÷ 2(因为声音是一去一回,算单程距离要除以 2)。
- CF标志位是干什么的?(硬件超时保护)
CF 是这个定时器(PCA)的溢出标志位。
-
如果发的脉冲太少(比如只发1、2个):声波能量太弱,跑不远,接收头可能听不到回声。
-
如果发的脉冲太多(比如发20个):发射时间太长,导致前面的声波已经撞到近处障碍物弹回来了,你探头还在发,这叫"余震盲区变大",会导致测不到近距离的物体。
unsigned int distance(void)
{
unsigned int time;//16位用int
CH = CL=0;//定时器从0开始计时
CMOD=0X00;//配置成16位不自动重装模式
wave_init();//发送一个超声波脉冲
//超声波模块发完波后,RX会由低电平变为高电平
//如果硬件响应较慢,RX还没来得及拉高
CR=1;//开始计时
while((Rx==1)&&(CF==0));//等待超声波接收信号
//RX==1没收到回波
//CF==0没超时
CR=0;//关闭定时器,停止计时
if(CF==0)//CF==0没超时,成功收到回波
{
time = (CH<<8)|CL;
return (time*0.017);
}
else//CF==1超时了,没收到回波
{
CF = 0;//把超时标志清零
return 0;//返回0,代表测距失败
}
}
- 它是如何"掐秒表"的?
-
清零秒表:
CH = CL = 0;和CMOD = 0x00;就是把单片机内部的一个 16 位硬件定时器(在这里用的是 PCA 模块)清零,并配置好工作模式,准备开始计"滴答"声。 -
发令枪响:
wave_init()发出超声波,相当于开枪。 -
按下秒表:
CR = 1;让定时器开始疯狂计数(每过 1 微秒,也就是 1us,定时器里的数字就加 1)。 -
声音的速度: 在常温空气中,声速大约是 340m/s。把它换算成厘米和微秒(因为单片机计时的单位是微秒):340m/s=34000cm/s=0.034cm/us。
-
计算推导:
-
总距离 =
time(微秒) × 0.034 (厘米/微秒) -
单程距离 = (
time× 0.034) ÷ 2 -
简化后 =
time * 0.017
-
-
结论: 只要你把计时器计出来的数字,乘以
0.017,得出的结果直接就是厘米(cm)。 -
这个定时器是 16 位的,它能计数的最大范围是 0 到 65535。
-
如果超声波探头前面完全没有障碍物,或者对着天空发,声波一去不复返。
Rx就会一直持高电平(1)。 -
如果没有
CF==0这个限制,单片机就会永远卡在while(Rx==1)里面死机