注意:原文章標題為「Java/C++ 傳值/傳引用/指針」,但內容實為 TI MSP430 單片機 C 語言固件。本文依實際內容重寫。
本專案基於 TI MSP430G2553 低功耗單片機,結合 HC-SR04 超聲波測距模組 與 UART 串口,實現非阻塞式測距並將結果 (厘米,保留一位小數) 以文本格式發送至 PC 端。採用 Timer_A 捕獲模式測量回波脈衝寬度,中斷驅動 UART 發送,主循環輪詢觸發測量。
| 功能 | 腳位 | 模組/寄存器 | 說明 |
|---|---|---|---|
| LED 指示 | P1.0 | GPIO | 測量觸發指示 |
| 按鍵觸發 | P1.3 (S2) | GPIO + 上拉 | 按下持續測量,放開停止 |
| HC-SR04 Trig | P1.4 | GPIO 輸出 | 10μs 高電平觸發 |
| HC-SR04 Echo | P2.2 | Timer1_A1 捕獲 (CCI1B) | 上升沿啟動、下降沿捕獲 |
| UART TX | P1.2 | USCI_A0 | 9600 8N1 發送測距結果 |
#define SOUR_CLK 1045000L // DCO 頻率 ~1.045 MHz #define SYS_CLK SOUR_CLK // 系統時鐘 #define MIN_UNIT_TO1M 1000L // 1m = 1000mm #define ACCU_GRD 7 // 字串緩衝區 "xxx.x\0" #define DISTANCE 45 // 最大量程 45dm = 4.5m (HC-SR04 理論 4m) #define MAX_TIME (DISTANCE * 2 * SYS_CLK / 340) // 最大回波時間對應計數值
#includelong current_time; // 捕獲到的 Timer 計數值 /* 硬體定義 */ #define LED_1 BIT0 #define SW_2 BIT3 #define TA1_1 BIT2 // P2.2 - Timer1_A1 CCI1B #define TRIG BIT4 // P1.4 - HC-SR04 Trig #define UART_TXD BIT2 // P1.2 - USCI_A0 TXD /* 初始化 Timer1_A1 捕獲模式 */ void init_timer1() { P2SEL |= TA1_1; // P2.2 選為 Timer1_A1 功能 P2DIR &= ~TA1_1; // 輸入方向 TA1CTL = MC_0 + TASSEL_2; // 停止模式, SMCLK 時鐘源 TA1CCTL1 = CCIE + SCCI + CCIS_1 + SCS + CAP + CM_2; // 捕獲模式, 下降沿, 同步捕獲, 中斷使能 } /* 初始化 UART 9600 8N1 */ void init_uart() { UCA0CTL1 |= UCSWRST; // 軟體複位 UCA0CTL1 |= UCSSEL_2; // SMCLK 時鐘源 UCA0BR0 = 0x6D; // 1.045MHz / 9600 ≈ 109 = 0x6D UCA0BR1 = 0x00; P1SEL |= UART_TXD; // P1.2 功能選擇 P1SEL2 |= UART_TXD; P1DIR |= UART_TXD; // 輸出方向 UCA0CTL1 &= ~UCSWRST; // 釋放複位 } /* 計算距離 (mm) : distance = 340 m/s * (time/2) = 170 * time_us */ /* Timer 計數值 current_time 對應時間 = current_time / SYS_CLK 秒 */ long cal_distance() { return (long)((340L * (0.5 * current_time / SYS_CLK) * MIN_UNIT_TO1M)); } /* 粗略延遲 */ void delay() { unsigned char i, j; for (i = 124; i > 0; i--) for (j = 8; j > 0; j--); } /* 將長整數距離轉為 "xxx.x" 字串 (cm, 1 位小數) */ static char *translater(long distance_mm) { static char trans[ACCU_GRD]; // "xxx.x\0" long f = MIN_UNIT_TO1M; // 1000 (mm 基數) trans[0] = ' '; if (distance_mm > 0) { for (int i = 1; i < ACCU_GRD - 1; i++) { if (i == 4) { trans[i] = '.'; continue; } // 小數點位置 trans[i] = '0' + distance_mm / f; distance_mm %= f; f /= 10; } } trans[ACCU_GRD - 1] = '\0'; return trans; } /* 發送單次觸發脈衝 (10μs) 並等待捕獲完成 */ void once_pro() { // 清除捕獲溢位標誌 if (TA1CCTL1 & COV) TA1CCTL1 &= ~COV; // 按鍵按下 (低電平) 才觸發測量 if (!(P1IN & SW_2)) { TA1R = 0; // 清零計數器 P1OUT |= TRIG; // Trig 拉高 _EINT(); // 開啟總中斷 TA1CTL |= MC_2; // 連續計數模式 __delay_cycles(10); // 10μs @ 1MHz (約 10 cycles) P1OUT &= ~TRIG; // Trig 拉低 P1OUT |= LED_1; // LED 點亮 while (TA1CCTL1 & CCIFG); // 等待捕獲中斷完成 (CCIFG 由 ISR 清除) } else { P1OUT &= ~LED_1; // LED 熄滅 _DINT(); // 關閉總中斷 } } /* UART 發送字串 (過濾前導零) */ void uart_txstring(char *str) { int i = 0; while (str[i]) { // 過濾前導零: 百位、十位為零時不發送 if (i == 1 && str[i] == '0') { i++; continue; } if (i == 2 && str[i] == '0' && str[i-1] == '0') { i++; continue; } UCA0TXBUF = str[i++]; delay(); // 簡易波特率延遲,確保發送緩衝區就緒 } } /* ========== 中斷服務程式 ========== */ /* UART TX 中斷: 發送測距結果 */ #pragma vector = USCIAB0TX_VECTOR __interrupt void usci_txdistance() { uart_txstring("\n\r"); uart_txstring(" Current distance: "); uart_txstring(translater(cal_distance())); uart_txstring(" cm"); IE2 &= ~UCA0TXIE; // 關閉 TX 中斷,避免重複進入 } /* Timer1_A1 捕獲中斷: 回波下降沿 */ #pragma vector = TIMER1_A1_VECTOR __interrupt void capture() { current_time = TA1CCR1; // 讀取捕獲值 TA1CTL &= ~MC_2; // 停止計數器 TA1CCTL1 &= ~CCIFG; // 清除中斷標誌 IE2 |= UCA0TXIE; // 使能 UART TX 中斷發送結果 } /* ========== 主程式 ========== */ void main() { WDTCTL = WDTPW + WDTHOLD; // 關閉看門狗 DCOCTL = 0; BCSCTL1 = CALBC1_1MHZ; // 校準 1MHz DCO DCOCTL = CALDCO_1MHZ; P1OUT = 0; P2OUT = 0; P1REN |= SW_2; // P1.3 上拉電阻使能 P2REN |= TA1_1; // P2.2 上拉 (可選) P1OUT |= SW_2; // 上拉輸出高 P1DIR &= ~SW_2; // P1.3 輸入 P1DIR |= TRIG + LED_1; // P1.4, P1.0 輸出 init_timer1(); init_uart(); while (1) { // 簡易去抖動延遲 int c = 8; while (c--) delay(); // 清除可能的殘留捕獲標誌 if (TA1CCTL1 & CCIFG) TA1CCTL1 &= ~CCIFG; once_pro(); // 嘗試觸發測量 } }
CM_2:下降沿捕獲。Trig 觸發後,Echo 拉高 → Timer 開始計數 → Echo 拉低 (下降沿) → 硬體自動將 TA1R 值鎖存至 TA1CCR1 並觸發中斷。CCIS_1:選擇 CCI1B (P2.2) 作為捕獲輸入源。SCS:同步捕獲,避免競態。聲速 v ≈ 340 m/s = 340,000 mm/s
往返時間 t = current_time / SYS_CLK (秒)
單程距離 d = v * t / 2 = 340,000 * (current_time / SYS_CLK) / 2
= 170,000 * current_time / SYS_CLK (mm)
程式碼: 340 * (0.5 * current_time / SYS_CLK) * 1000 → 等價
while(1) 輕量輪詢按鍵狀態,發起觸發。printf/sprintf 佔用大量 Flash/RAM。Current distance: 12.3 cm Current distance: 12.4 cm Current distance: 12.3 cm
| 現象 | 可能原因 | 解決 |
|---|---|---|
| 無輸出 / 亂碼 | 波特率不匹配、TX 腳位錯誤、GND 未共地 | 核對 UCA0BR0/BR1、P1SEL/P1SEL2、共地 |
| 距離異常大/為 0 | Echo 腳位未接/接錯、Timer 捕獲配置錯誤、超時無回波 | 檢查 P2.2 連線、CM_2/CCIS_1 設定、加入超時邏輯 |
| 按鍵無反應 | 上拉電阻未使能、按鍵接法反向 | P1REN|=SW_2; P1OUT|=SW_2; 確認原理圖 |
| LED 長亮不滅 | 捕獲中斷未觸發、CCIFG 未清除導致 while 死循環 | 檢查 ISR 中 TA1CCTL1 &= ~CCIFG、Echo 信號質量 |