~ 是 **按位取反 (Bitwise NOT)** 運算符,屬於一元運算符。對操作數的每一個二進制位執行:0 變 1,1 變 0。
// 假設 P1_0 連接 LED,初始輸出低電平 (LED 亮,假設低電平有效)
P1_0 = 0;
// 主迴圈中每隔 500ms 執行一次
while(1) {
P1_0 = ~P1_0; // 核心:讀取當前引腳輸出鎖存器值,取反,寫回
delay_ms(500);
}
| 步驟 | P1_0 當前值 (二進制) | ~P1_0 結果 | 物理電平 | LED 狀態 |
|---|---|---|---|---|
| 初始 | 0b0 (僅看最低位) |
- | 低電平 (0V) | 亮 |
| 第 1 次執行 | 0b0 |
0b1 |
高電平 (3.3V/5V) | 滅 |
| 第 2 次執行 | 0b1 |
0b0 |
低電平 | 亮 |
**關鍵點**:單片機 C 編譯器 (Keil C51, IAR, SDCC 等) 會將 P1_0 = ~P1_0; 優化為**讀-改-寫**指令序列,直接操作特殊功能寄存器 (SFR) P1 的第 0 位鎖存器。
// 利用定時器中斷翻轉引腳,產生 50% 佔空比方波
void Timer0_ISR() interrupt 1 {
TH0 = 0xFC; TL0 = 0x18; // 重裝 1ms @ 11.0592MHz
P1_0 = ~P1_0; // 翻轉電平,週期 2ms -> 頻率 500Hz
}
// 主函數僅需初始化定時器,無需處理翻轉邏輯
int main() {
TMOD = 0x01; TH0 = 0xFC; TL0 = 0x18;
ET0 = 1; EA = 1; TR0 = 1;
while(1); // 閒置
}
| 誤區/陷阱 | 正確做法/解釋 |
|---|---|
P1_0 = !P1_0; (邏輯非) 也能翻轉? |
**行為不同**!! 將任意非零值變 0,零值變 1。若 P1_0 為 0xFF (某位損壞讀出全 1),!0xFF = 0;~0xFF = 0x00。位運算用 ~,邏輯判斷用 !。 |
對整個端口取反 P1 = ~P1; 會影響其他引腳? |
**會**。若 P1.1-P1.7 連接關鍵外設 (如按鍵輸入、通訊線),盲目取反會導致系統異常。**僅操作目標位**:P1 ^= (1<<0); (異或翻轉第 0 位) 或 P1_0 = ~P1_0; (編譯器支持位寻址時)。 |
讀取引腳電平用 P1_0 可靠嗎? |
**輸出模式下讀取的是輸出鎖存器**,可靠;**輸入模式下讀取的是實際引腳電平**。若引腳配置為開漏輸出/雙向,讀回值可能非預期。**明確區分輸出鎖存器 (寫) 與輸入引腳狀態 (讀)**,部分單片機 (如 STM32) 有獨立的 ODR/IDR 寄存器。 |
#ifndef _BIT_UTILS_H_
#define _BIT_UTILS_H_
#define SET_BIT(reg, bit) ((reg) |= (1u << (bit)))
#define CLEAR_BIT(reg, bit) ((reg) &= ~(1u << (bit)))
#define TOGGLE_BIT(reg, bit) ((reg) ^= (1u << (bit)))
#define GET_BIT(reg, bit) (((reg) >> (bit)) & 1u)
#define IS_BIT_SET(reg, bit) (((reg) & (1u << (bit))) != 0)
// 使用示例
// TOGGLE_BIT(P1, 0); // 翻轉 P1.0,比 ~P1_0 更通用、可移植 (不依賴編譯器位寻址擴展)
#endif
~ 是單片機開發中**「翻轉狀態」**的核心利器。理解其**按位邏輯**、區分邏輯非 !、掌握**讀-改-寫**機制與**位帶操作/異或翻轉**等安全替代方案,是寫出健壯、可移植嵌入式代碼的基本功。