蓝桥杯单片机第九周

第九周笔记(合并整理版)

主题涵盖:NE555 频率测量、超声波测距、串口收发与解析、数码管与按键冲突、DS1302、DS18B20、I2C(PCF8591 / AT24C02)等。

第九周

恢复笔记


1. NE555 模块(频率测量)

1.1 硬件与资源分配思路

  • 由于硬件连接关系,外部脉冲只能由定时器0接受:

    • T0:用作计数器(数外部脉冲个数)

    • T1:用作计时器(提供 1ms / 1s 时间基准)

  • 图示:

    第九周

1.2 计数范围与最大频率

  • 最大频率约 27 kHz(你记录为“27K左右”)。

  • 8 位计数最大 256 不够用,因此采用 16 位计数更稳妥(你原文“16 为计数”应为“16位计数”)。

  • 使用 12T模式(你在代码与说明中也有强调)。

    第九周

1.3 Timer0(计数器)初始化

要点:TL0/TH0 清零,配置为计数模式、16位、不自动重载;注意此处一般不需要开中断(你的笔记也是这么提醒)。

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

  • TL0, TH0 设置为 0,计数,同时设置十六位不自动重载将 T0 修改为计数器,TMOD =|0x05

  • 注意这里不要开中断

    第九周

1.4 Timer1(1ms 中断,用于 1s 窗口统计)底层逻辑

  • 定时器1:1ms 触发一次中断

  • 累计 1000ms(1s)后,读取 T0 计数值作为频率(每秒脉冲数≈Hz)

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

1.5 注意点(你原笔记保留并更清晰化)

  1. Timer0_Init(); Timer1_Init(); 建议先初始化 T0,否则第一次显示可能不对。

  2. 第一次测量频率不准:可以用 show_flag_first 延迟 1 秒后再显示,滤除第一次测量。

  3. 必须用跳线帽接上 P34 和 SIGNAL,否则测不到。

  4. 按键与 P34 存在硬件冲突:按键里 P34 相关要处理(见后面“按键冲突”章节)。

    第九周

    恢复笔记

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


2. 超声波模块(测距)

2.1 引脚定义

  • TX:信号发送端

  • RX:信号接收端

第九周

2.2 距离换算公式

d=vt2=340m/s⋅t2=170m/s⋅td=\frac{vt}{2}=\frac{340m/s \cdot t}{2}=170m/s \cdot td=2vt​=2340m/s⋅t​=170m/s⋅t

  • t 单位为 us(10−6s10^{-6}s10−6s),距离 d 用 cm

d=0.017td = 0.017td=0.017t

第九周

2.3 发送 8 个 40kHz 方波

  • 40kHz:周期 T=25usT=25usT=25us,电平翻转约 12.5us12.5us12.5us。

  • 你记录“stc-isp 不能产生 0.5ms 的函数,只能生成 12us 延时函数”,这里核心意思是:用现成延时函数近似实现 12.5us。

    第九周

void Delay12us(void) //@12.000MHz { unsigned char data i; _nop_(); i = 3; while (--i); }

波形发送:

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

2.4 使用 PCA 定时器测量回波时间(因为其他定时器被占用)

  • 串口占用 T2、NE555 占用 T0/T1(轮询/中断),所以超声波用 PCA

  • 配置要点:

    • 先清 CHCL

    • CMOD = 0x00(16 位、不重载)

    • 测量时建议关中断,避免被打断导致计时误差

    • CR 控制计数启停,CF 是溢出标志

第九周

完整代码(原样保留):


#include "ul.h"
#include "intrins.h"
sbit Tx = P1 ^ 0;
sbit Rx = P1 ^ 1;
void Delay12us(void)    //@12.000MHz
{
    unsigned char data i;
    _nop_();
    i = 3;
    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;        ************这里Time用int,8位合成都用int类型*************
  CH = CL = 0;
  CMOD = 0x00;
  EA = 0;   //关闭中断,防止被打断测量不准
  Ut_Wave_Init();
  EA = 1;
  CR = 1;           //CR是计数的开关
  while (Rx && !CF);//CF是溢出标志位,
                   //但没有溢出且信号返回时停止计时
  CR = 0;
  if (!CF) 
  { 
      time = CH << 8 | CL;  //数据合成
      return (0.017 * time );
  } 
  else 
  {
    CF = 0;
   return 0;
  }
}
`

`


3. 串口模块(配置、发送、接收与超时解析)

3.1 基础注意事项

  • 板子与 ISP 频率要匹配:选择 12MHz / 12T(你这里也强调“不要错”)。

  • 串口波特率要与 ISP 配置一致。

  • 串口中断使能:ES = 1

  • SBUF 是收发缓冲中转站:PC→MCU 与 MCU→PC 都经过 SBUF。

    第九周

3.2 TI / RI 标志位含义(你原笔记已很清楚)

  • TI(Transmit Interrupt):发送完成标志,硬件置 1,软件清 0

  • RI(Receive Interrupt):接收完成标志,硬件置 1,软件清 0

  • 两者都是硬件置 1,软件清 0

    第九周

3.3 串口初始化(使用定时器2作为波特率发生器)

void Uart1_Init(void)	//9600bps@12.000MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x01;		//                       **********串口1选择定时器2为波特率发生器**************
	AUXR &= 0xFB;		//定时器时钟12T模式           **********注意定时器别选错****************
	T2L = 0xE6;			//设置定时初始值
	T2H = 0xFF;			//设置定时初始值
	AUXR |= 0x10;		//定时器2开始计时
    EA  = 0;
    ES  = 1;
}

printf 需要包含 #include <stdio.h>(你已写)。

3.4 串口重定向(printf 底层)

#include "stdio.h" extern char putchar(char ch)//putchar是printf打印字节的底层 { SBUF = ch; while (TI == 0); TI = 0; return ch; }

发送示例:

unsigned char my_data=5; printf("%bu",my_data);

  • 注意 printf 格式控制符(你写的 %bu%xu 等可以在 Keil help 查)。

  • 图:
    image-20260131213846266

    第九周

3.5 串口接收:超时解析法(RI + Tick)

核心思想:每个字节到来就存入缓冲区并清零 Tick;当一段时间未继续接收(Tick 超过阈值)则认为一帧结束,进入 Uart_Proc() 处理并清空缓冲区。

中断接收:

`pdata unsigned char Uart_Buf[10] = {0}; // 串口接收缓冲区
idata unsigned char Uart_Rx_Index;      // 串口接收索引
idata unsigned char Uart_Recv_Tick;     // 串口接收时间标志
idata unsigned char Uart_Rx_Flag;
void Uart1_Isr(void) interrupt 4           //注意串口的计时器中断号是4
{
  // 若接收到数据
  if (RI)
  {
    Uart_Rx_Flag = 1;                 // 接收标志拉高
    Uart_Recv_Tick = 0;               // 清零接收时间标志
    Uart_Buf[Uart_Rx_Index++] = SBUF; // 将数据保存到缓冲区
    RI = 0;                           // 清除接收中断标志,方便下一次接收
    if (Uart_Rx_Index > 10)
    {
      Uart_Rx_Index = 0; // 防止溢出
      memset(Uart_Buf, 0, 10);
    }
  }
}
// 定时器
void Time1_Isr(void) interrupt 3 //这里中断不用写TH,TL自动配好了在STC15中
{
    if (Uart_Rx_Flag)
        Uart_Recv_Tick++;        // 串口接收计时增加,如果不再接收,Tick不会清零就会一直增加
}

处理函数框架:

void Uart_Proc() { if (Uart_Rx_Index == 0)return; // 无数据,直接返回 if (Uart_Recv_Tick >= 10) // 若接收超时,对数据读取,清空缓存区,停止计时,标志位归零 { Uart_Recv_Tick = 0; Uart_Rx_Flag = 0 ; //*************************处理函数**************************// memset(Uart_Buf, 0, Uart_Rx_Index); // 清空接收数据 //*************************处理函数**************************// Uart_Rx_Index = 0; } }

  • 处理函数提示:记得 #include "string.h"

  • sscanf 返回值:成功赋值的变量个数

    第九周


4. 数码管与按键:硬件冲突与工程组织建议

4.1 数码管锁存器分工

  • U8 连接 COM 段代表位选

  • U7 代表段选

  • 它们连接的锁存器不同

    恢复笔记

4.2 独立按键 IO 默认状态

  • 独立按键的 IO 口默认为高电平(常见上拉输入)。

    恢复笔记

4.3 P3 口冲突(重点)

你记录的冲突点与处理思路(保留并整理):

  • 按键的 P30、P31 是频率测量的 RTX / RTD

  • P34 则被定时器/串口等使用,会带来硬件冲突

可选处理策略:

  1. 逻辑判断当前是按键功能还是频率测量功能,避免冲突

  2. 注释掉 P34 相关按键代码(因为“定时器不能停止”这一类需求)

  3. 进行按键判断时,不是 P30、P31 都被赋值,此时需要 P3 | 0xef 进行复位,避免影响串口和定时器使用

    恢复笔记

4.4 工程组织建议

  • 数码管只做显示

  • 外设(DS1302、DS18B20等)尽量独立模块化,减少耦合

    恢复笔记


5. DS1302(实时时钟)注意事项

5.1 引脚定义与命名

先给 SCK、SDA、RST 赋值(注意 DS1302 的 IO 与 I2C 的 SDA 不要混):

sbit SCK = P1^7; sbit SDA = P2^3; sbit RST = P1^3;

  • SCK 对应 SCK

  • SDA 对应 IO(建议在工程里命名区分 I2C_SDA / DS1302_IO)

  • RST(reset)也叫 CE

    恢复笔记

5.2 Set_RTC(设置时间)写法要点

你原笔记的流程(保留并补充关键点):

  1. 关闭写保护 WP

  2. 处理 CH 位:CH=1 可停止计时(便于写入),写完后通常要再恢复为 CH=0

  3. 写入时分秒:注意 十进制转 BCD(你写“十进制转换成十六进制”,更准确说是转 BCD)

  4. 再打开写保护

    恢复笔记

5.3 Get_Time(读取时间)

  1. 读之前关中断 EA=0,避免时序被打断

  2. 依次读时分秒

  3. 再开中断

    恢复笔记


6. DS18B20(温度传感器)

要点整理:

  1. 定义引脚 DQ

  2. ROM:每个 DS18B20 有唯一 ROM;如果只有一个器件,可用 Skip ROM 简化流程

  3. 流程示例(你原笔记意思):

    • init → skip ROM → 温度转换

    • 等待转换(你写延时 200ms)

    • 再 init → skip → 读温度

  4. 读取温度通常是低8位/高8位合成,再按精度换算(你的意思是“最后合成一个数,除以精度”)

    恢复笔记


7. I2C(PCF8591 / AT24C02)

7.1 I2C 通用注意点(整理版)

  • 记得定义 SCL、SDA,但注意不要和 OneWire、DS1302_IO 混用命名

  • 你提到“开头宏定义时间改成5 / 或删 nop”,核心目标是:让 I2C 时序满足器件要求

    恢复笔记

说明:你笔记里“写入等待200ms左右”更像是给 EEPROM 写周期的经验值;PCF8591 通常不需要这么久(见文末纠错)。

恢复笔记

7.2 PCF8591

  • 输出:0~255 模拟量(DA)

  • 输入:0~5V 对应数字量(AD)

  • 常用地址(与你笔记一致):

    • 写地址 0x90,读地址 0x91(注意这与 A0/A1/A2 绑法有关,通常 0x90/0x91 是 A2..A0=0 的情况)
  • 通道控制字(你给了示例):

    • AIN0:0x40

    • AIN1:0x41

    • AIN2:0x42

    • AIN3:0x43

      恢复笔记

读写规则(保留你的原意)

  • AD/DA 写规则(无等待应答的写法思路):

    1. START → 写 PCF 写地址

    2. 不用再次 START → 写通道地址

    3. STOP

  • 读规则(你强调 ACK/NACK,很关键):

    1. START → 写 PCF 读地址

    2. 写通道地址

    3. 再次 START

    4. 读模拟量

    5. 发送应答(0=继续读,1=停止读

    6. STOP

      恢复笔记

7.3 AT24C02(EEPROM)

要点整理:

  1. EEPROM 掉电不丢失

  2. AT24C02 按页写入:你记录“8个字节一页”:white_check_mark:

  3. 可存变量:用地址 &a 等方式传入(本质按字节写入)

  4. 写完成后要“停止很多次/多次延时”等待内部写周期结束(你用多次 I2C_Delay(255) 实现)

    恢复笔记

代码(原样保留):


*@brief 向EEPROM写入多个字节
*@param str   指向要写入数据的数组的指针
*@param addr  EEPROM内部的起始写入地址
*@param num   要写入的数据个数(字节数)
*/
	
void EEPROM_Write(unsigned char*str,unsigned char addr,unsigned char num)
{
	I2CStart();
	I2CSendByte(0xa0);//写AT24C02地址
	I2CWaitAck();
	
	I2CSendByte(addr);//发送写入的地址
	I2CWaitAck();
  
	while(num--)
	{
		I2CSendByte(*str++);//发送写入的数据
    I2CWaitAck();
		I2C_Delay(255);//延时等待写入数据
	}
	
	I2CStop();
  I2C_Delay(255);
  I2C_Delay(255);	
  I2C_Delay(255);
  I2C_Delay(255);		
  I2C_Delay(255);
  I2C_Delay(255);	
  I2C_Delay(255);
  I2C_Delay(255);		
  I2C_Delay(255);
  I2C_Delay(255);		
	
}
void EEPROM_Read(unsigned char*str,unsigned char addr,unsigned num)
{
	I2CStart();
  I2CSendByte(0xa0);//写AT24C02
	I2CWaitAck();
	I2CSendByte(addr);//读取的起始地址
	I2CWaitAck();
	
	I2CStart();//开始读取,模式转变所以重新开始
	I2CSendByte(0xa1);//读PCF8951地址
	I2CWaitAck();
	
	while(num--)
	{
		*str++=I2CReceiveByte();//读取
	  if(num)I2CSendAck(0);//发送0表示接受应答,读取继续
		else I2CSendAck(1);//发送1表示读取停止
	}
	I2CStop();//停止
}