【跟着小甲鱼学C语言】P31 局部变量和全局变量

本文深入探讨了局部变量和全局变量的区别,详细解释了全局变量的自动初始化特性以及如何使用extern关键字来处理变量的提前引用和跨文件定义。

局部变量和全局变量

1.全局变量

定义在main函数外的变量就是全局变量。与其他变量不同的是,如果不初始化全局变量,其会自动初始化为0。如果main函数中声明了同名的变量,那么全局变量会被屏蔽

2.extern关键字

有两个作用:
1.当变量在定义前就被使用的时候,可以使用extern关键字告诉编译器,这个变量已经在后边定义过了。
2.表示这个变量或者函数已经在其他文件定义过了。

<think>我们有两个问题需要解决: 1. 在代码中按P3^1(K1)时,DA1(可能是指LED1)会灭。我们需要分析为什么按K1会导致DA1灭,并修正。 2. P2^5同时连接蜂鸣器LED,我们需要通过译码器控制来分别控制它们。 首先,我们分析第一个问题:按K1(P3^1)时DA1(假设为P2^0连接的LED)会灭。 在用户提供的代码片段中,我们注意到: sbit K1 = P3^1; sbit LED1 = P2^0; 在while循环中,当检测到K1按下时,执行了LED1 = !LED1。因此,按K1会翻转LED1的状态(亮变灭,灭变亮)。这可能是设计意图,但用户提到“按P31时DA1会灭”,这似乎是用户不希望的现象。 如果用户希望按K1不要影响DA1(即LED1),那么可能是按键扫描部分编写有误,或者是硬件连接上DA1与K1有某种关联?但根据代码,确实是设计为按键K1控制LED1。 但是,用户描述为“DA1”,而代码中为“LED1”,我们需要确认DA1是否就是LED1。如果是,那么按K1翻转LED1是代码设计如此。如果用户不希望这样,我们可以修改代码,比如去掉这部分代码,或者修改为控制其他LED。 然而,用户还提到第二个问题:P2.5同时连接蜂鸣器LED。因此,我们怀疑第一个问题可能与第二个问题相关,即当操作P2.5时可能会影响LED的状态(因为P2.5也是LED锁存器的一部分?)。但实际上,在第一个问题的代码中,我们并没有操作P2.5,只是操作了P2^0(LED1)。 因此,我们重新审视第一个问题:用户希望按下P3^1(K1)时,DA1(可能是P2^0)不要熄灭。那么有两种可能: a) 用户不希望K1控制LED1,即代码需要修改。 b) 硬件上,K1LED1有直接连接(如共地等)导致按下K1时LED1电流被旁路?但这种情况很少见,通常不会这样设计。 我们先假设用户提供的代码就是导致现象的直接原因。如果用户想取消K1对LED1的控制,可以删除或注释掉相应的代码。 但是,用户可能希望K1控制其他东西(比如蜂鸣器)而不影响LED1。因此,我们可能需要修改按键处理程序。 第二个问题:P2.5同时连接蜂鸣器LED。在STC89开发板上,通常通过锁存器(如74HC573)来扩展IO,不同的设使用不同的锁存器,由P2口的高位(P2.5, P2.6, P2.7)通过74HC138译码器选择。 控制方法: - 当需要控制LED时,选择LED的锁存器(地址通常为0x80,即P2=1000 0000,高三位是100,低5位保持不变) - 当需要控制蜂鸣器时,选择蜂鸣器的锁存器(地址通常为0xA0,即P2=1010 0000,高三位是101) - 操作完成后,需要关闭锁存器(将P2的高三位清零,即P2 &= 0x1F) 因此,我们不能再直接使用sbit定义单个LED(如LED1=P2^0)来控制,因为这样会直接操作P2.0,而P2.0可能还连接在锁存器的控制线上(如74HC138的输入)。而且,当使用锁存器时,LED通常是挂在P0口上的,而不是P2口。 所以,我们需要重新确认硬件连接: - LED模块可能连接在P0口,通过锁存器控制。 - 直接操作P2.0可能会影响锁存器的选择(因为P2.0是锁存器地址线的一部分)。 根据用户描述,P2.5连接了蜂鸣器LED,说明P2.5是一个复用引脚,既作为锁存器选择线(用于选择不同的设),又作为LED的驱动线?这不太可能。更合理的解释是: - P2.5作为74HC138的输入,用于选择不同的设锁存器。 - LED蜂鸣器分别挂在不同的锁存器上,锁存器的输出再驱动LED蜂鸣器。 因此,我们不能使用sbit直接操作P2.5,而应该通过改变锁存器的选择来操作不同的设。 综上所述,我们需要: 1. 修改第一个问题:如果用户希望按下K1不要改变LED1(DA1),那么我们可以将按键处理程序中的LED1翻转代码去掉,或者修改为其他操作(如控制蜂鸣器)。 2. 对于第二个问题,使用锁存器控制方法来分别控制LED蜂鸣器。 由于用户提供的代码中LED1、LED2、LED3都定义在P2口的低三位(P2.0, P2.1, P2.2),而P2.5又是复用引脚,这显然与锁存器控制方式冲突。因此,我们需要确认硬件设计。 常见的开发板(如普中科技开发板)设计: - LED模块连接在P0口,通过锁存器(LED锁存器)控制。 - 蜂鸣器连接在P0.6(也可能是其他引脚),通过另一个锁存器(蜂鸣器锁存器)控制。 - 锁存器由74HC138控制,74HC138的输入来自P2.5、P2.6、P2.7。 因此,我们需要改变LED的控制方式,不再使用sbit直接操作P2口,而是通过锁存器操作P0口。 步骤: 1. 定义锁存器的选择地址(如前面的0x80, 0xA0等)。 2. 编写选择锁存器的函数。 3. 在操作LED时,选择LED锁存器,然后向P0口写入数据(每一位对应一个LED)。 4. 在操作蜂鸣器时,选择蜂鸣器锁存器,然后向P0口(具体某一位)写入0或1(根据硬件设计,可能是P0.6)。 因此,第一个问题(按K1时DA1灭)的根源可能是:当我们直接操作P2.0时,由于P2.0是锁存器地址线的一部分(低5位),可能会意改变锁存器的选择,导致其他设(如数码管、蜂鸣器)被误操作。所以,正确的做法是使用锁存器方式来控制所有设。 解决方案: 首先,重新定义锁存器地址(根据开发板原理图): #define LED_ADDR 0x80 // 1000 0000 -> 高三位100 #define BUZZER_ADDR 0xA0 // 1010 0000 -> 高三位101 ... // 其他锁存器地址 然后,编写锁存器选择函数: void SelectHC573(unsigned char addr) { P2 = (P2 & 0x1F) | addr; // 保留P2的低5位,高3位设置为addr } 接着,控制LED(假设8个LED连接在P0口): void LED_Control(unsigned char led_data) { SelectHC573(LED_ADDR); P0 = led_data; // 控制所有LED // 注意:操作完后,最好关闭锁存器,防止后续操作影响 SelectHC573(0x00); // 关闭锁存器,实际是让锁存器处于非选通状态?但通常开发板设计为操作后立即关闭锁存器(将P2高3位清零)来避免影响其他设备。 // 或者:P2 &= 0x1F; // 将高3位清零,即关闭所有锁存器 } 注意:关闭锁存器的方法是将锁存器选择地址清除(即P2高3位清零)。所以我们可以在操作完锁存器后执行:P2 &= 0x1F; 同样,控制蜂鸣器: void Buzzer_Control(unsigned char on) { SelectHC573(BUZZER_ADDR); if (on) P0 = 0x40; // 假设蜂鸣器由P0.6控制,则置位P0.6(高电平驱动或低电平驱动,根据硬件电路) else P0 = 0x00; // 关闭蜂鸣器 P2 &= 0x1F; // 关闭锁存器 } 但是,硬件上蜂鸣器可能是由P0.6控制,并且可能是高电平有效或低电平有效。需要根据原理图确定。常见的是高电平有效。 现在,回到第一个问题:按K1时,我们不希望DA1灭。但如果我们采用锁存器控制LED,那么之前的sbit定义方式就不适用了。因此,我们需要修改代码,不再使用sbit操作LED,而是使用锁存器控制。 修改后的主程序框架: #include "STC89.h" // 定义锁存器地址 #define LED_ADDR 0x80 #define BUZZER_ADDR 0xA0 // 定义按键引脚(保持不变) sbit K1 = P3^1; sbit K2 = P3^0; sbit K3 = P3^2; // 延时函数(保持不变) // 锁存器选择函数 void SelectHC573(unsigned char addr) { P2 = (P2 & 0x1F) | addr; } // 初始化设 void InitPeripherals() { // 关闭蜂鸣器 SelectHC573(BUZZER_ADDR); P0 = 0x00; // 关闭蜂鸣器(假设低电平关闭) P2 &= 0x1F; // 初始化LED状态(全部熄灭,假设LED共阳极,P0输出0则灭) SelectHC573(LED_ADDR); P0 = 0xFF; // 全部熄灭 P2 &= 0x1F; } void main() { unsigned char led_status = 0xFF; // 初始状态全灭 InitPeripherals(); while(1) { if (K1 == 0) { Delay5ms(); if (K1 == 0) { // 按K1,我们不再翻转P2.0,而是通过锁存器控制LED // 例如:我们只翻转LED1(即P0.0),其他LED不变 led_status ^= 0x01; // 翻转最低位(对应LED1) SelectHC573(LED_ADDR); P0 = led_status; P2 &= 0x1F; // 同时,我们也可以让蜂鸣器响一声 SelectHC573(BUZZER_ADDR); P0 = 0x40; // 开启蜂鸣器 Delay5ms(); // 短暂延时 P0 = 0x00; // 关闭蜂鸣器 P2 &= 0x1F; while (K1 == 0); // 等待按键释放 } } // 其他按键处理... } } 说明: - 这样修改后,LED1(即DA1)的状态由led_status变量控制,每次按下K1,LED1会翻转(亮灭交替),同时蜂鸣器会滴一声。 - 如果用户不希望按K1时改变LED1,那么可以将led_status翻转的代码去掉,只保留蜂鸣器提示音。 注意:LED1对应的是P0.0,而之前用户定义的是P2.0,现在改为通过P0口控制,所以硬件上LED模块应该是连接到P0口的。 总结: 1. 由于P2.5复用问题,我们需要使用锁存器控制方式操作设。 2. 直接操作P2口(如P2.0)可能会影响锁存器选择,导致设冲突。 3. 因此,我们重新编写代码,通过锁存器控制LED蜂鸣器。 4. 对于第一个问题,修改按键处理程序,不再直接操作P2.0,而是通过锁存器控制P0口,这样按K1时对LED的操作就变成了对P0.0的操作,而P0.0通过锁存器连接到LED1,就不会影响其他设。 如果用户希望按K1时DA1不熄灭,那么就不在按键处理中改变LED的状态(即去掉led_status翻转的代码)。 根据用户需求调整:如果用户希望按K1时DA1不灭,那么就不改变LED的状态。例如: if (K1 == 0) { Delay5ms(); if (K1 == 0) { // 只让蜂鸣器响,不改变LED SelectHC573(BUZZER_ADDR); P0 = 0x40; // 开启蜂鸣器 Delay5ms(); // 短暂延时 P0 = 0x00; // 关闭蜂鸣器 P2 &= 0x1F; while (K1 == 0); // 等待按键释放 } } 这样,按下K1时蜂鸣器会滴一声,LED状态保持不变。 最后,关于蜂鸣器控制,请注意: - 蜂鸣器的驱动电平(高电平或低电平)需要根据硬件电路确定。 - 以上代码假设蜂鸣器由P0.6控制,且高电平有效。如果实际是低电平有效,则开启蜂鸣器时P0=0xBF(即P0.6=0),关闭时P0=0xFF(P0.6=1)。 - 如果蜂鸣器是无源的,则需要用PWM驱动才能发出声音,而不仅仅是高低电平。但如果是开发板上的有源蜂鸣器,则只需给固定电平即可。 因此,请根据实际蜂鸣器类型调整。以上代码适用于有源蜂鸣器。 如果用户开发板的硬件连接与上述不同,请根据原理图调整。 下面给出完整的代码示例(假设有源蜂鸣器,高电平有效,LED共阳极,P0输出0亮,输出1灭): </think>### 问题分析与解决方案 #### 1. 按键P3^1(K1)触发DA1熄灭问题 **原因分析**: 当按下K1(P3^1)时,代码执行了`LED1 = !LED1`操作。如果DA1连接在P2.0(即LED1),这是正常行为。但若DA1不是P2.0控制的LED: 1. 硬件连接冲突:P3^1可能直接/间接连接到DA1控制线 2. 地址译码干扰:操作P2口时影响锁存器选择[^3] 3. 初始化状态异常:端口未正确初始化 **解决方案**: ```c // 修改按键处理逻辑 sbit DA1 = P2^3; // 假设DA1实际连接P2.3 void main() { P2 = 0xFF; // 初始化P2口全部高电平 while(1) { if(K1 == 0) { Delay5ms(); if(K1 == 0) { // 隔离操作:先选择锁存器再修改 SelectHC573(0x80); // 选择LED锁存器 DA1 = !DA1; // 仅操作DA1 P2 &= 0x1F; // 关闭锁存器 while(K1 == 0); // 等待释放 } } } } ``` #### 2. P2.5复用控制(蜂鸣器+LED) **核心原理**: 通过74HC138译码器实现设隔离控制[^1][^3]: - LED锁存器地址:`0x80` (P2=1000 0000) - 蜂鸣器锁存器地址:`0xA0` (P2=1010 0000) - 控制公式: $$P2_{\text{new}} = (P2 \space \& \space 0x1F) \space | \space \text{addr}$$ **完整实现代码**: ```c #include "STC89.h" // 地址定义 #define LED_ADDR 0x80 #define BUZZER_ADDR 0xA0 // 锁存器选择函数 void SelectHC573(unsigned char addr) { P2 = (P2 & 0x1F) | addr; // 保留低5位,设置高3位 } // 蜂鸣器控制 void BuzzerCtrl(unsigned char state) { SelectHC573(BUZZER_ADDR); // 选择蜂鸣器锁存器 P0 = state ? 0x40 : 0x00; // P0.6控制蜂鸣器(开发板通常设计) P2 &= 0x1F; // 关闭锁存器 } // LED控制(示例) void LEDCtrl(unsigned char leds) { SelectHC573(LED_ADDR); P0 = leds; // P0控制LED组 P2 &= 0x1F; } void main() { // 初始化设 BuzzerCtrl(0); // 关闭蜂鸣器 LEDCtrl(0xFF); // 关闭所有LED while(1) { if(K1 == 0) { Delay5ms(); if(K1 == 0) { BuzzerCtrl(1); // 开启蜂鸣器 Delay100ms(); BuzzerCtrl(0); // 关闭蜂鸣器 // 同时控制P2.5的LED(不影响蜂鸣器) SelectHC573(LED_ADDR); P0 &= ~(1 << 5); // 点亮P2.5对应的LED P2 &= 0x1F; } } } } ``` ### 关键配置说明 1. **蜂鸣器控制要点**: - 使用专用地址`0xA0`访问蜂鸣器 - 通过P0.6引脚输出控制信号 - 操作后立即关闭锁存器(`P2 &= 0x1F`) 2. **避免冲突的设计**: ```c // 正确操作(硬件隔离) BuzzerCtrl(1); // 仅影响蜂鸣器 LEDCtrl(0xFE); // 仅影响LED // 错误操作(直接操作P2.5) P2^5 = 1; // 同时影响LED蜂鸣器 ``` 3. **硬件连接验证**: - 确认开发板上P2.5是否同时连接LED蜂鸣器模块 - 检查74HC138译码器的输入端是否连接P2.5-P2.7 - 测量P0.6是否通向蜂鸣器控制电路 ### 常见问题排查 | 现象 | 可能原因 | 解决方案 | |------|----------|----------| | 蜂鸣器不响 | 锁存器地址错误 | 检查`BUZZER_ADDR`是否为`0xA0` | | LED异常闪烁 | 未关闭锁存器 | 每次操作后加`P2 &= 0x1F` | | 设相互干扰 | 操作间隔太短 | 设操作间添加`Delay5ms()` | | 按键响应异常 | 端口模式错误 | 初始化时设置`P3 = 0xFF`(上拉输入) | **重要提示**:STC89单片机P2口具有复用功能,操作时需严格遵循: 1. 设操作前选择对应锁存器 2. 数据通过P0口传输 3. 操作后立即释放总线(关闭锁存器) 4. 避免直接操作P2口高三位[^1][^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值