<think>我们使用STC12C5A60S2单片机,要求:
1. K1按键(假设接在某个IO口,比如P1.0)控制P3.2输出方波,频率范围300Hz至2400Hz,每次按键切换频率(例如按一次300Hz,再按一次600Hz,...直到2400Hz再回到300Hz,循环)。
2. 同时,P2.4输出10个脉冲周期(即10个方波)后停止,每次按键切换频率时,P2.4重新输出10个脉冲。
3. 通过P0口连接74HC245驱动数码管显示当前频率序号(1-8,对应8个频率,比如1:300Hz, 2:600Hz, ..., 8:2400Hz)。
分析:
频率范围:300Hz, 600Hz, 900Hz, 1200Hz, 1500Hz, 1800Hz, 2100Hz, 2400Hz。共8个频率点,每次按键切换一个频率点,循环。
实现思路:
1. 使用定时器中断产生方波。由于有两个引脚需要输出方波(P3.2和P2.4),且P2.4每次只输出10个脉冲,我们可以考虑使用同一个定时器,但通过软件计数来产生不同频率和脉冲个数。
2. 我们选择定时器0作为方波生成的定时器。因为STC12C5A60S2有两个定时器(定时器0和定时器1),我们可以用定时器0来产生方波,定时器1用于数码管扫描(如果需要动态扫描)或者用延时函数实现数码管显示(静态显示也可以,但一般为了节省IO口,采用动态扫描)。
3. 注意:P3.2是外部中断0的引脚,但这里我们用作普通IO输出方波。
4. 数码管显示:我们使用P0口连接74HC245(驱动数码管的段选),另外还需要位选信号(这里假设使用P2.0-P2.3作为位选,但题目没有给定位选引脚,我们假设使用P2口的部分引脚作为位选)。由于题目要求显示1-8,我们只需要一个数码管即可(或者用多个数码管中的一位),这里我们简化使用一个数码管显示数字(1-8)。
具体步骤:
1. 初始化定时器0,设置初始中断时间(比如300Hz对应的周期为1/300秒,半周期翻转一次,所以定时时间为1/(2*300)秒)。
2. 在定时器0中断服务程序中:
- 对P3.2进行翻转(产生方波)。
- 同时,对P2.4的输出进行控制:在需要输出脉冲时,也翻转P2.4,并且计数(每输出一个完整的方波,需要两次翻转,所以一个周期计数两次?)。我们可以在每次翻转时计数,当计数达到20(10个周期,因为每个周期翻转两次)时,停止P2.4的输出(将其置低或高,保持不再翻转)并清零计数。
3. 按键检测:检测K1按键(假设接在P1.0),当检测到按键按下时,切换到下一个频率点(共8个),同时重新启动P2.4输出(即允许定时器中断中再次对P2.4进行翻转,并重新计数)。
4. 数码管显示:显示当前频率序号(1-8)。这里我们采用静态显示,因为只显示一个数字,但实际中可能需要动态扫描(如果使用多位数码管,我们只显示其中一位)。为了简化,我们假设使用一个共阴数码管,将P0口连接到74HC245的输入,74HC245的输出连接数码管的段选。另外,位选我们直接选通一位(例如,如果使用4位数码管,我们只让其中一位的位选有效,其他位选无效)。这里我们假设位选使用P2.5(任意一个未使用的IO)控制,或者使用P2口其他引脚(注意P2.4已经被使用,所以避开)。
但注意:题目要求P0.0-P0.7连接74HC245驱动数码管。74HC245是双向驱动器,我们这里用作输出,所以DIR方向控制要接高电平或低电平(根据数据手册,我们假设DIR接高电平,即A端到B端,A端接P0,B端接数码管段选)。位选信号我们使用P2.1(假设)控制数码管的共阴极(或共阳极,这里假设共阴,那么位选为高电平有效?不对,共阴数码管位选是低电平有效?具体看电路连接)。这里我们假设位选使用P2.1,并且数码管为共阴,那么要显示的那一位给低电平,其他给高电平(如果有多位的话)。由于我们只显示一位,所以只需要一个位选信号。
然而,题目要求显示1-8,我们用一个数码管即可,所以位选可以固定(只接一个数码管,那么位选一直有效)。但为了防止干扰,我们最好还是控制位选。
由于题目没有给出具体的数码管连接电路,我们简化处理:
- 使用一个数码管(共阴),其段选通过74HC245连接到P0口,位选接在P2.1(假设),且需要将位选置为有效(低电平有效,所以P2.1=0)。
- 注意:P2.4已经被使用(输出脉冲),所以位选不能使用P2.4,我们使用P2.1(或其他未使用的引脚)。
5. 频率点的存储:我们可以用一个数组存储8个频率对应的定时器重装值。由于方波的周期T=1/f,而定时器中断是半周期翻转一次,所以定时器中断周期为T/2=1/(2f)。定时器采用16位自动重装模式,那么重装值 = 65536 - (T/2)* (晶振频率/12) (因为STC12C5A60S2是1T单片机,但默认是12T,具体看设置。STC12C5A60S2可以设置1T或12T,我们这里假设使用12T模式,晶振频率为11.0592MHz,这样定时准确)。
计算定时器重装值:
定时器计数周期 = 1 / (Fosc/12) = 12 / Fosc 秒。
所以,定时时间t对应的计数值N = t / (12/Fosc) = t * Fosc / 12。
对于半周期时间t_half = 1/(2*f) 秒,则:
N = t_half * Fosc / 12 = (1/(2*f)) * Fosc / 12 = Fosc/(24*f)
重装值 = 65536 - N
例如,f=300Hz, Fosc=11059200Hz
N = 11059200 / (24*300) = 11059200 / 7200 = 1536
重装值 = 65536 - 1536 = 64000 = 0xFA00
但是,由于1536是整数,所以这样计算是准确的。但其他频率呢?
我们计算一下600Hz:N=11059200/(24*600)=11059200/14400=768,重装值=65536-768=64768=0xFD00
900Hz: N=11059200/(24*900)=11059200/21600=512,重装值=65536-512=65024=0xFE00
1200Hz: N=11059200/(24*1200)=11059200/28800=384,重装值=65536-384=65152=0xFE80
注意:384*30=11520,而11059200/28800=384,所以是整数。
1500Hz: N=11059200/(24*1500)=11059200/36000=307.2,不是整数!这样就会产生误差。
因此,我们需要重新考虑定时器设置。由于STC12C5A60S2的定时器可以工作在1T模式(速度是传统51的12倍),这样定时器计数频率就是Fosc,而不是Fosc/12。我们使用1T模式,可以减小误差。
在1T模式下:
计数周期 = 1/Fosc
半周期t_half = 1/(2*f)
计数值N = t_half / (1/Fosc) = Fosc/(2*f)
重装值 = 65536 - N
使用1T模式,重新计算:
f=300: N = 11059200/(2*300)=11059200/600=18432,重装值=65536-18432=47104=0xB800
f=600: N=11059200/(2*600)=9216,重装值=65536-9216=56320=0xDC00
f=900: N=11059200/(2*900)=6144,重装值=65536-6144=59392=0xE800
f=1200: N=11059200/(2*1200)=4616,重装值=65536-4616=60920=0xEDF8 -> 注意,4616不是整数?11059200/2400=4616.333...,所以有小数,我们取整,这样会有误差。
为了减小误差,我们可以使用定时器的自动重装模式,并允许在中断中修改重装值。对于非整数的情况,我们可以采用在中断中动态调整重装值(比如,有时候用N,有时候用N+1)来平均,但这样复杂。
或者,我们选择晶振频率为12MHz,这样计算:
Fosc=12000000
1T模式:N = Fosc/(2*f) = 12000000/(2*f)=6000000/f
300Hz: N=6000000/300=20000,重装值=65536-20000=45536=0xB1E0
600Hz: N=6000000/600=10000,重装值=55536=0xD8F0
900Hz: N=6000000/900≈6666.666,取6667,重装值=65536-6667=58869=0xE5F5
1200Hz: N=6000000/1200=5000,重装值=60536=0xEC78
1500Hz: N=6000000/1500=4000,重装值=61536=0xF060
1800Hz: N=6000000/1800≈3333.333,取3333,重装值=65536-3333=62203=0xF2FB
2100Hz: N=6000000/2100≈2857.142,取2857,重装值=65536-2857=62679=0xF4D7
2400Hz: N=6000000/2400=2500,重装值=63036=0xF63C
这样,除了非整数的情况,我们取整,会有一定误差,但可以接受。
考虑到实际应用,我们使用12MHz晶振,1T模式。
6. 程序结构:
- 初始化:设置定时器0为16位自动重装模式,1T模式,设置初始重装值(对应300Hz),打开中断,启动定时器。
- 设置P3.2和P2.4为推挽输出(因为STC12C5A60S2的IO口可以设置模式,这里设置为推挽输出)。
- 设置按键(P1.0)为输入模式(准双向即可)。
- 数码管显示初始化:设置P0口和位选引脚(P2.1)为推挽输出,并显示初始序号1。
- 在定时器0中断服务程序中:
翻转P3.2(输出方波)。
如果当前需要输出P2.4脉冲(即计数使能),则翻转P2.4,并增加翻转次数计数(每翻转一次,计数一次,注意一个周期翻转两次)。当翻转次数达到20(10个周期)时,停止翻转(将P2.4保持为低电平或高电平?我们可以在停止后将其置为低电平,并禁止再翻转(直到下一次按键))。
- 主循环中检测按键,按键去抖,切换频率,并重置P2.4的输出使能和计数,同时更新数码管显示。
7. 变量定义:
- 当前频率序号(1-8):index
- 频率对应的重装值数组:timer0_reload[8] = {0xB1E0, 0xD8F0, 0xE5F5, 0xEC78, 0xF060, 0xF2FB, 0xF4D7, 0xF63C}
- P2.4输出控制:flag_pulse_enable(允许输出脉冲)和pulse_count(当前已经翻转的次数)
8. 注意:在切换频率时,要重新设置定时器0的重装值(在中断中修改也可以,但我们在主循环中修改,因为重装值在中断中会重新加载,我们在主循环中修改重装值后,下次中断就会使用新的重装值)。
9. 数码管显示:用一个数组存储0-9的段码(共阴数码管),例如:
unsigned char code seg_table[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //0-9
显示数字n时,P0=seg_table[n]; 同时位选有效(我们假设只有一位数码管,位选一直有效,或者用P2.1控制位选,我们将其置0(共阴))。
10. 按键处理:去抖,等待按键释放。
代码框架:
注意:STC12C5A60S2的特殊功能寄存器设置:
AUXR寄存器:设置T0x12(第7位)为1,定时器0为1T模式。
TMOD:设置定时器0为16位自动重装模式(实际上自动重装模式是TMOD=0x00,但通常使用模式1(16位不自动重装)?不对,自动重装模式是模式2(8位自动重装),但16位自动重装需要设置TMOD=0x00,然后使用T0的工作方式0(16位自动重装)?实际上,STC12C5A60S2的定时器0有4种模式,模式0(16位自动重装)是通过AUXR中的T0_CT(AUXR.6)和T0_GATE(AUXR.5)来设置的。我们查阅手册:STC12C5A60S2的定时器0有4种模式:
模式0:16位自动重装
模式1:16位不自动重装
模式2:8位自动重装
模式3:不可屏蔽中断的16位自动重装
我们使用模式0(16位自动重装):设置AUXR中的T0_CT=0(定时器模式),T0_GATE=0(仅由TR0启动),TMOD的低4位为0000(即TMOD&=0xF0; TMOD|=0x00;),然后设置AUXR中的T0_M0和T0_M1(在AUXR中,T0_M0是AUXR.6,T0_M1是AUXR.7?不对,手册上写:T0_CT是AUXR.6,T0_GATE是AUXR.5,而T0_M0和T0_M1在AUXR.4和AUXR.3?没有找到明确的说明,实际上STC12C5A60S2的定时器模式设置比较特殊,建议参考手册。
根据手册,设置定时器0为16位自动重装模式:
TMOD &= 0xF0; // 清除T0的控制位
TMOD |= 0x00; // 模式0(16位自动重装)?实际上,在STC12C5A60S2中,定时器0的模式0是13位定时器,模式1是16位定时器(不自动重装),模式2是8位自动重装,模式3是16位自动重装(不可屏蔽中断)?不对,在数据手册中,定时器0的模式由TMOD的M1和M0位设置:
M1=0, M0=0: 模式0,13位定时器
M1=0, M0=1: 模式1,16位定时器(不自动重装)
M1=1, M0=0: 模式2,8位自动重装
M1=1, M0=1: 模式3,两个8位定时器(只适用于T0)
我们想要16位自动重装,但标准51没有,STC12C5A60S2增加了模式0(16位自动重装)吗?实际上,STC12C5A60S2的定时器0可以设置为16位自动重装模式,但需要设置AUXR寄存器中的T0_CT和T0_GATE,以及TMOD中的M1和M0。具体如下:
设置16位自动重装模式:设置TMOD的M1=0, M0=0(即模式0)?不对,模式0是13位。所以STC12C5A60S2的定时器0没有16位自动重装模式?那我们可以使用模式2(8位自动重装)来模拟,但这样精度不够(最大255)。或者使用模式1(16位不自动重装),然后在中断中重装。为了精确,我们使用模式1(16位不自动重装),在中断中重装。
因此,我们使用定时器0模式1(16位不自动重装),在中断中重装。
步骤:
设置TMOD: TMOD &= 0xF0; TMOD |= 0x01; // 模式1,16位定时器
设置AUXR: AUXR |= 0x80; // 设置T0为1T模式(如果使用1T模式,AUXR的T0x12位(第7位)为1)
定时器重装值:TH0 = (65536 - N) >> 8; TL0 = (65536 - N) & 0xFF;
然后启动定时器:TR0=1; ET0=1; EA=1;
在中断服务程序中,需要重新装入初值(因为模式1不会自动重装)。
由于频率变化时重装值不同,我们在中断中根据当前频率的重装值来重装。
代码示例:
注意:由于中断频率较高,中断服务程序要尽量短。
我们使用全局变量来存储当前频率的重装值(timer0_reload_value),以及P2.4脉冲计数。
变量定义:
unsigned int timer0_reload[8] = {0xB1E0, 0xD8F0, 0xE5F5, 0xEC78, 0xF060, 0xF2FB, 0xF4D7, 0xF63C}; // 12MHz晶振,1T模式下的重装值(16位)
unsigned char index = 0; // 当前频率序号0-7(对应1-8)
bit pulse_enable = 0; // 是否允许P2.4输出脉冲(0:停止,1:允许)
unsigned char pulse_count = 0; // 当前已经翻转的次数(每翻转一次加1,10个周期需要20次翻转?不对,一个周期有两次翻转(从低到高,再从高到低),所以10个周期需要20次翻转?但我们计数的是翻转次数,每翻转一次,输出半个周期。所以10个周期需要20次翻转?是的。但注意,我们每次翻转都计数,当计数到20时,停止。
但是,我们也可以这样:每输出一个完整的周期(两次翻转)计一次数,这样计数10次即可。这样在中断中,每次翻转P2.4后,如果是下降沿(或上升沿)则计数?这样复杂。我们直接按翻转次数计数。
定时器0中断服务程序:
void timer0_isr() interrupt 1
{
TH0 = (unsigned char)(timer0_reload[index] >> 8);
TL0 = (unsigned char)(timer0_reload[index] & 0xFF);
// 翻转P3.2
P32 = ~P32; // 假设P3.2定义为sbit P32=P3^2;
// 处理P2.4
if (pulse_enable)
{
P24 = ~P24; // 翻转P2.4
pulse_count++;
if (pulse_count >= 20) // 10个周期(20次翻转)
{
pulse_enable = 0; // 停止翻转
P24 = 0; // 停止后置为低电平(也可以置为高,根据需求)
}
}
}
按键处理(在主循环):
if (K1按下) // 假设K1接在P1.0
{
延时去抖;
if (K1确实按下)
{
index = (index+1) % 8; // 切换到下一个频率
// 更新数码管显示:显示index+1
display_number(index+1);
// 重新启动P2.4脉冲输出
pulse_enable = 1;
pulse_count = 0;
// 等待按键释放
while(!K1); // 等待释放,这里可以加延时去抖
}
}
数码管显示函数:
void display_number(unsigned char num)
{
// 段码表,共阴数码管,0-9
unsigned char code seg_table[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
// 显示数字num(1-8),所以num-1对应0-7,但我们的表是0-9,所以直接取seg_table[num]即可?不对,num是1-8,所以seg_table[num]是1的段码?不对,1的段码是seg_table[1]。
// 所以,显示num,就是seg_table[num]
// 但是注意,我们只显示一位,所以不需要位选切换。这里我们假设数码管位选已经设置好(一直有效),或者每次显示都设置位选有效。
P0 = seg_table[num]; // 注意:这里P0输出的是段码,但74HC245是驱动,所以直接给P0赋值即可。
// 同时,确保位选有效:假设位选接P2.1(低电平有效)
P2_1 = 0; // 位选有效(共阴,低电平点亮)
}
但是,注意P2口除了P2.1(位选)和P2.4(脉冲输出)外,其他位不要影响。所以,我们设置位选时,不能影响P2.4。我们可以用位操作:
P2 = (P2 & 0xFD) | 0x00; // 将P2.1置0,其他位不变。但注意,这样会同时改变P2.4?不会,因为P2.4是输出脉冲,我们在中断中会改变它,但主循环中改变P2.1不会影响P2.4,因为P2.4在中断中翻转。
但是,在中断中翻转P2.4时,可能会和主循环中操作P2口冲突。为了避免冲突,我们使用sbit来单独控制P2.4和位选(P2.1)。这样,操作sbit不会影响其他位。
11. 初始化:
void init()
{
// 设置定时器0
TMOD &= 0xF0; // 清除T0的设置
TMOD |= 0x01; // 定时器0,模式1(16位不自动重装)
AUXR |= 0x80; // T0为1T模式(1T:1个时钟周期计数一次,12T:12个时钟周期计数一次)
TH0 = (unsigned char)(timer0_reload[index] >> 8);
TL0 = (unsigned char)(timer0_reload[index] & 0xFF);
ET0 = 1; // 允许定时器0中断
TR0 = 1; // 启动定时器0
EA = 1; // 总中断
// 设置IO口
P3M0 = 0x04; // 设置P3.2为推挽输出(因为要输出方波,需要强推挽)
P3M1 = 0x00;
P2M0 = 0x10; // 设置P2.4为推挽输出
P2M1 = 0x00;
// P0口用于数码管段选,设置为推挽输出
P0M0 = 0xFF;
P0M1 = 0x00;
// P1.0按键设置为准双向(输入)
P1M0 = 0x00;
P1M1 = 0x00; // 准双向
// 初始化变量
index = 0;
pulse_enable = 1; // 一开始就允许输出10个脉冲
pulse_count = 0;
// 初始显示
display_number(index+1); // 显示1
}
12. 主函数:
void main()
{
init();
while(1)
{
if (K1 == 0) // 假设按键按下为低电平
{
delay_ms(10); // 去抖
if (K1 == 0)
{
index = (index+1) % 8;
display_number(index+1);
pulse_enable = 1;
pulse_count = 0;
// 等待按键释放
while(K1==0); // 等待释放
delay_ms(10);
}
}
}
}
13. 注意:中断服务程序中的重装值更新:由于index是全局变量,在中断中读取,主循环中修改,所以需要防止中断中读取到不完整的值(但index是8位,在51上是原子操作,所以安全)。但是,当index在主循环中修改时,中断可能发生,此时中断中就会使用新的重装值。这样是合理的。
14. 关于P2.4的输出:在中断中,当pulse_enable为1时,每次翻转P2.4,并计数。当计数达到20时,停止并清零pulse_count?实际上,我们只将pulse_enable置0,pulse_count保留为20。下一次按键时,pulse_count会被重置为0。这样也可以。
15. 但是,注意:在切换频率时,我们重新启动P2.4输出(pulse_enable=1, pulse_count=0),这样P2.4就会重新输出10个周期的脉冲。
16. 关于数码管:我们使用静态显示,所以显示一次后,只要不改变,数码管就一直显示那个数字。所以主循环中不需要持续刷新显示。
17. 另外,注意74HC245的方向控制:我们假设DIR接高电平(A->B),A接P0,B接数码管段选。所以DIR引脚可以接VCC。
18. 位选:我们假设使用P2.1控制一个数码管的位选(共阴数码管,低电平有效),所以初始化后,将P2.1置0,然后显示数字时,只需要改变P0口即可。
19. 但是,在初始化时,我们设置了P0为推挽输出,P2.1也要设置为推挽输出(低电平驱动数码管)。在初始化中,我们设置了P2M0和P2M1,其中P2.4设置为推挽,P2.1我们还没有设置。所以需要设置P2.1为推挽:
P2M0 |= 0x02; // 0000 0010,设置P2.1为推挽
P2M1 &= ~0x02;
20. 最后,注意:在显示函数中,我们只设置了P0和P2.1,所以其他位选(如果有多个数码管)应该关闭(即置高电平,不选通)。因为我们只用一个数码管,所以其他位选(如果有)可以不管,但为了安全,最好将其他位选关闭。
由于我们只用一个数码管,所以没有问题。
21. 关于定时器中断频率:最高频率2400Hz,半周期为1/4800秒,中断周期为208.3us。在12MHz晶振1T模式下,一个机器周期1us,所以中断服务程序必须在208us内执行完毕。我们的中断服务程序很短,所以没问题。
22. 但是,在中断中重装定时器值,会引入一定的误差(因为中断响应和重装需要时间)。我们可以通过调整重装值来补偿。但这里为了简单,忽略不计。
23. 如果使用标准12T模式,中断频率高时可能来不及响应,所以使用1T模式。
总结:我们使用12MHz晶振,1T模式,定时器0模式1(16位不自动重装),在中断中重装,并翻转P3.2和P2.4(当使能时)。按键切换频率,并重新启动P2.4的10个脉冲输出。数码管显示当前频率序号。
由于计算重装值时,部分频率不是整数,所以会有轻微误差。如果需要更精确,可以使用更高频率的晶振,或者使用定时器的其他模式(如PWM模式)来输出方波,但题目要求使用普通IO。
下面给出完整代码:</think>### 实现方案:STC12C5A60S2单片机按键控制方波输出与数码管显示
#### 1. 硬件连接设计
- **单片机**:STC12C5A60S2(1T模式,12MHz晶振)
- **按键K1**:接P1.0(下降沿触发)
- **方波输出**:
- P3.2输出主方波(频率300-2400Hz)
- P2.4输出10周期脉冲
- **数码管驱动**:
- P0口 → 74HC245(A端)→ 数码管段选(共阴)
- P2.1控制数码管位选(低电平有效)
- **74HC245方向控制**:DIR接VCC(A→B方向)
#### 2. 频率计算与定时器配置
使用1T模式(12MHz晶振),定时器0模式1(16位定时),频率对应的重装值:
$$ \text{重装值} = 65536 - \frac{12000000}{2 \times f} $$
| 序号 | 频率 (Hz) | 重装值(十六进制) |
|------|-----------|-------------------|
| 1 | 300 | 0xB1E0 |
| 2 | 600 | 0xD8F0 |
| 3 | 900 | 0xE5F5 |
| 4 | 1200 | 0xEC78 |
| 5 | 1500 | 0xF060 |
| 6 | 1800 | 0xF2FB |
| 7 | 2100 | 0xF4D7 |
| 8 | 2400 | 0xF63C |
#### 3. 程序实现
```c
#include <STC12C5A60S2.H>
// 频率重装值表 (12MHz, 1T模式)
unsigned int code timer0_reload[8] = {
0xB1E0, 0xD8F0, 0xE5F5, 0xEC78,
0xF060, 0xF2FB, 0xF4D7, 0xF63C
};
unsigned char index = 0; // 当前频率序号(0-7)
bit pulse_enable = 1; // P2.4脉冲输出使能
unsigned char pulse_count = 0; // 脉冲翻转计数
// 数码管段码表 (共阴, 0-9)
unsigned char code seg_table[] = {
0x3F, 0x06, 0x5B, 0x4F, 0x66,
0x6D, 0x7D, 0x07, 0x7F, 0x6F
};
// 数码管显示函数
void display_number(unsigned char num) {
P0 = seg_table[num]; // 段码输出
P2_1 = 0; // 位选使能(低电平)
}
// 定时器0中断服务
void timer0_isr() interrupt 1 {
// 重装定时值
TH0 = (unsigned char)(timer0_reload[index] >> 8);
TL0 = (unsigned char)(timer0_reload[index] & 0xFF);
P3_2 = ~P3_2; // 翻转P3.2(主方波)
// 处理P2.4脉冲
if (pulse_enable) {
P2_4 = ~P2_4; // 翻转P2.4
pulse_count++;
if (pulse_count >= 20) { // 10周期(20次翻转)
pulse_enable = 0;
P2_4 = 0; // 停止后置低电平
}
}
}
// 初始化函数
void init() {
// 定时器0配置
TMOD &= 0xF0; // 清除设置
TMOD |= 0x01; // 模式1(16位定时)
AUXR |= 0x80; // 1T模式
TH0 = (unsigned char)(timer0_reload[index] >> 8);
TL0 = (unsigned char)(timer0_reload[index] & 0xFF);
ET0 = 1; // 允许中断
TR0 = 1; // 启动定时器
// IO口配置
P3M0 = 0x04; P3M1 = 0x00; // P3.2推挽输出
P2M0 = 0x12; P2M1 = 0x00; // P2.1/P2.4推挽输出
P0M0 = 0xFF; P0M1 = 0x00; // P0口推挽输出
P1M0 = 0x00; P1M1 = 0x00; // P1口准双向
// 外中断0配置(按键)
IT0 = 1; // 下降沿触发
EX0 = 1; // 允许中断
EA = 1; // 总中断
// 初始显示
display_number(index + 1);
}
// 按键中断服务
void ex0_isr() interrupt 0 {
delay_ms(10); // 消抖
if (P1_0 == 0) {
index = (index + 1) % 8; // 循环切换频率
display_number(index + 1);
pulse_enable = 1; // 重启脉冲
pulse_count = 0;
while(!P1_0); // 等待按键释放
}
}
void main() {
init();
while(1) { /* 主循环空转,中断处理 */ }
}
```
#### 4. 关键实现说明
1. **定时器精度**:使用1T模式提升定时精度,非整数频率取整处理
2. **脉冲控制**:每周期翻转2次,20次翻转对应10个完整周期
3. **按键响应**:外中断0实现即时响应,消抖处理防误触发
4. **数码管驱动**:
- 74HC245提升P0口驱动能力
- 静态显示降低CPU占用
5. **IO配置**:
- 方波引脚设为推挽输出
- 按键引脚准双向模式
#### 5. 调试要点
1. 测量P3.2输出频率,验证是否匹配设定值
2. 用示波器观察P2.4脉冲数是否为10个
3. 检查数码管显示值与当前频率序号一致性
4. 若频率误差较大,可微调重装值补偿中断延迟