第九周笔记(合并整理版)
主题涵盖: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 注意点(你原笔记保留并更清晰化)
-
Timer0_Init(); Timer1_Init();建议先初始化 T0,否则第一次显示可能不对。 -
第一次测量频率不准:可以用
show_flag_first延迟 1 秒后再显示,滤除第一次测量。 -
必须用跳线帽接上 P34 和 SIGNAL,否则测不到。
-
按键与 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。
-
配置要点:
-
先清
CH、CL -
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 查)。 -
图:
第九周
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 则被定时器/串口等使用,会带来硬件冲突
可选处理策略:
-
逻辑判断当前是按键功能还是频率测量功能,避免冲突
-
注释掉 P34 相关按键代码(因为“定时器不能停止”这一类需求)
-
进行按键判断时,不是 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(设置时间)写法要点
你原笔记的流程(保留并补充关键点):
-
关闭写保护 WP
-
处理 CH 位:CH=1 可停止计时(便于写入),写完后通常要再恢复为 CH=0
-
写入时分秒:注意 十进制转 BCD(你写“十进制转换成十六进制”,更准确说是转 BCD)
-
再打开写保护
恢复笔记
5.3 Get_Time(读取时间)
-
读之前关中断
EA=0,避免时序被打断 -
依次读时分秒
-
再开中断
恢复笔记
6. DS18B20(温度传感器)
要点整理:
-
定义引脚 DQ
-
ROM:每个 DS18B20 有唯一 ROM;如果只有一个器件,可用 Skip ROM 简化流程
-
流程示例(你原笔记意思):
-
init → skip ROM → 温度转换
-
等待转换(你写延时 200ms)
-
再 init → skip → 读温度
-
-
读取温度通常是低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 写规则(无等待应答的写法思路):
-
START → 写 PCF 写地址
-
不用再次 START → 写通道地址
-
STOP
-
-
读规则(你强调 ACK/NACK,很关键):
-
START → 写 PCF 读地址
-
写通道地址
-
再次 START
-
读模拟量
-
发送应答(0=继续读,1=停止读)
-
STOP
恢复笔记
-
7.3 AT24C02(EEPROM)
要点整理:
-
EEPROM 掉电不丢失
-
AT24C02 按页写入:你记录“8个字节一页”

-
可存变量:用地址
&a等方式传入(本质按字节写入) -
写完成后要“停止很多次/多次延时”等待内部写周期结束(你用多次
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();//停止
}