蓝桥杯第16届单片机

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() 后面,延时函数的前面)。

下面是具体的原理解释:

  1. 为什么会有这4个 I2C_Delay(255)

当你调用 I2CStop() 后,单片机确实已经把数据发给 EEPROM (AT24C02) 了。但是,EEPROM 把接收到的数据搬运并烧写到它内部的存储介质中,是需要物理时间的(这个时间叫"页写周期",通常是 5 毫秒左右)。

这4个 I2C_Delay(255) 就是为了让单片机"傻等"这 5 毫秒,保证 EEPROM 写完,以免紧接着进行下一次读写时失败。

  1. 为什么 EA=1 要放在延时前面?
  • 保护通信时序: EA=0 关闭中断是为了保护前面 I2CStartI2CStop 的通信过程。因为软件模拟 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。

  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 个字节。


  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=0EA=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)发射超声波的。

超声波测距的原理是:发射头发出声波,遇到障碍物反弹,接收头收到声波。为了让这个过程顺利进行,发射的声波必须满足特定的物理和硬件要求。

  1. 为什么是 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=1Tx=0 以及 for 循环本身也需要耗费零点几微秒的时间。加起来刚好约等于 12.5us 的高电平和 12.5us 的低电平,凑成完美的 25us(40kHz)。

  1. 为什么是循环 8 次?(for(i=0;i<8;i++)

这是超声波测距的行业标准做法:

  1. 为什么计算公式是 time * 0.017?(重点!)

这是一个非常经典的物理公式简化:距离 = 速度 × 时间 ÷ 2(因为声音是一去一回,算单程距离要除以 2)。

  1. 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,代表测距失败
  	
  	}

  }
  1. 它是如何"掐秒表"的?
  • 清零秒表: 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) 里面死机

相关推荐
LCG元1 小时前
STM32实战:基于STM32F103的HC-SR04超声波测距与OLED显示
stm32·单片机·嵌入式硬件
somi71 小时前
ARM-04-驱动-Misc ,Platform ,DTS
arm开发·单片机·嵌入式硬件·自用
never forget shyang2 小时前
CCS20.2.0使用教程
c语言·git·单片机
Chockmans2 小时前
2026年3月青少年软件编程(Python)等级考试试卷(六级)
开发语言·python·青少年编程·蓝桥杯·pycharm·python3.11·python六级
zjeweler2 小时前
网安护网面试-1-长亭护网面试
web安全·网络安全·面试·职场和发展
wfbcg11 小时前
每日算法练习:LeetCode 209. 长度最小的子数组 ✅
算法·leetcode·职场和发展
yoyobravery12 小时前
蓝桥杯第15届单片机满分
单片机·职场和发展·蓝桥杯
June bug13 小时前
全链路测试
功能测试·面试·职场和发展
倦王13 小时前
力扣日刷47
算法·leetcode·职场和发展