范例程序?qū)崙?zhàn)指南:從點(diǎn)燈到多外設(shè)工程搭建)
簡(jiǎn)介這是一份面向嵌入式初學(xué)者與單片機(jī)開(kāi)發(fā)者的STC單片機(jī)C語(yǔ)言范例程序合集圍繞ADC采樣、串口通信、EEPROM讀寫、ISP在系統(tǒng)編程、時(shí)鐘中斷、端口測(cè)試及優(yōu)化編程思想等核心功能展開(kāi)適合用于課程實(shí)驗(yàn)、自學(xué)練手與項(xiàng)目參考。資源包共612個(gè)文件約14.88MB以plg、uv2、hex、opt、lnp等工程與燒錄文件為主配合h頭文件、c源碼、asm匯編及pdf說(shuō)明文檔覆蓋ADC、PCA模擬全雙工串口、紅外收發(fā)、PWM、LCD驅(qū)動(dòng)、按鍵掃描、看門狗等典型模塊并附實(shí)驗(yàn)箱布局圖與使用說(shuō)明。目前已有5855人學(xué)習(xí)下載讀者可從中獲得可直接編譯運(yùn)行的完整工程、寄存器配置思路與硬件調(diào)試參考便于快速理解STC單片機(jī)各外設(shè)的編程方法提升實(shí)際開(kāi)發(fā)效率。1. 從一塊“點(diǎn)不亮”的板子說(shuō)起STC 單片機(jī)范例程序到底能幫你省下多少試錯(cuò)時(shí)間很多人第一次接觸 STC 單片機(jī)卡住的地方往往不是寫不出代碼而是代碼寫完了燒不進(jìn)去、燈不亮、串口沒(méi)反應(yīng)。你手里可能有一塊最小系統(tǒng)板、一根 USB 轉(zhuǎn)串口線還有一份不知道從哪下載的例程打開(kāi)一看全是寄存器操作注釋寥寥。STC 單片機(jī)范例程序這份資源解決的就是這個(gè)最實(shí)際的問(wèn)題它把 GPIO 點(diǎn)燈、定時(shí)器、串口收發(fā)、ADC 采樣、PWM 輸出、EEPROM 讀寫這些常用外設(shè)的完整工程打包在一起每個(gè)工程都是可以直接編譯、直接燒錄的獨(dú)立項(xiàng)目。適合誰(shuí)適合剛上手 STC 系列、不想從零搭工程模板的嵌入式新手也適合需要快速驗(yàn)證某個(gè)外設(shè)功能的熟手。你不需要先去啃幾百頁(yè)的數(shù)據(jù)手冊(cè)先把例程跑通再回頭對(duì)照寄存器理解會(huì)快得多。2. 工程結(jié)構(gòu)拆解拿到壓縮包后先看哪幾個(gè)文件2.1 目錄組織與工程模板的復(fù)用邏輯一份典型的 STC 單片機(jī)范例程序包解壓后通常不是一個(gè)大雜燴而是按外設(shè)功能分目錄。常見(jiàn)做法是每個(gè)功能一個(gè)獨(dú)立文件夾比如01_GPIO_LED、02_Timer0、03_UART1、04_ADC、05_PWM、06_EEPROM每個(gè)文件夾里放一個(gè)完整的 Keil 工程。這樣設(shè)計(jì)的好處是你不需要在同一個(gè)工程里靠宏定義切換功能直接打開(kāi)對(duì)應(yīng)文件夾就能編譯燒錄互不干擾。我一般拿到這種包第一件事不是急著打開(kāi).uvproj而是先看根目錄有沒(méi)有Common或Library文件夾。如果有說(shuō)明作者把延時(shí)函數(shù)、串口打印、類型定義這些公共代碼抽出來(lái)了各個(gè)例程通過(guò)相對(duì)路徑引用。這種結(jié)構(gòu)在二次開(kāi)發(fā)時(shí)最省事你改一處公共代碼所有例程都能受益。如果沒(méi)有公共文件夾每個(gè)例程都是自包含的那就意味著代碼有重復(fù)改的時(shí)候要逐個(gè)同步。# 典型的目錄結(jié)構(gòu)示意 STC_Examples/ ├── 01_GPIO_LED/ │ ├── main.c │ ├── led.c │ ├── led.h │ └── project.uvproj ├── 02_Timer0/ │ ├── main.c │ ├── timer.c │ ├── timer.h │ └── project.uvproj ├── 03_UART1/ │ ├── main.c │ ├── uart.c │ ├── uart.h │ └── project.uvproj ├── Common/ │ ├── delay.c │ ├── delay.h │ ├── type.h │ └── uart_printf.c └── README.md上面這個(gè)結(jié)構(gòu)里Common文件夾是關(guān)鍵。delay.c提供毫秒和微秒級(jí)延時(shí)type.h做類型重定義uart_printf.c把printf重定向到串口。這些在每個(gè)例程里都會(huì)被引用。你打開(kāi)01_GPIO_LED的工程在 Keil 的工程樹(shù)里應(yīng)該能看到Common組里面包含這些文件。如果看不到說(shuō)明路徑?jīng)]配好需要手動(dòng)添加。2.2 Keil 工程配置里最容易忽略的三個(gè)選項(xiàng)打開(kāi).uvproj之后先別急著點(diǎn)編譯。STC 單片機(jī)的工程配置有幾個(gè)地方和 STM32 不一樣配錯(cuò)了就是“編譯通過(guò)但跑不起來(lái)”。第一個(gè)是芯片型號(hào)選擇。STC 的 Keil 支持包安裝后在 Device 列表里能搜到具體型號(hào)比如 STC89C52RC、STC12C5A60S2、STC15W4K56S4 等。選錯(cuò)型號(hào)最直接的后果是寄存器地址不對(duì)程序跑飛。第二個(gè)是時(shí)鐘頻率設(shè)置。在Target選項(xiàng)卡的Xtal里填的數(shù)值會(huì)影響 Keil 仿真時(shí)的時(shí)序計(jì)算但真正決定串口波特率的是你代碼里寫的定時(shí)器初值。很多人在這里填 11.0592MHz代碼里卻按 12MHz 算結(jié)果串口出來(lái)全是亂碼。常見(jiàn)做法是晶振用 11.0592MHz串口波特率才準(zhǔn)如果板子上焊的是 12MHz那串口波特率就得用定時(shí)器 2 或者手動(dòng)算誤差。第三個(gè)是輸出 HEX 文件的勾選。在Output選項(xiàng)卡里Create HEX File必須勾上否則編譯完沒(méi)有可燒錄文件。STC-ISP 下載軟件只認(rèn).hex或.bin不認(rèn).axf。// Common/delay.h 里常見(jiàn)的宏定義 #ifndef __DELAY_H #define __DELAY_H #include type.h // 假設(shè)系統(tǒng)時(shí)鐘為 11.0592MHz // 這兩個(gè)宏在 STC 例程里出現(xiàn)頻率極高 #define MAIN_Fosc 11059200L // 主頻單位 Hz #define DELAY_MS(ms) delay_ms(ms) #define DELAY_US(us) delay_us(us) void delay_ms(u16 ms); void delay_us(u16 us); #endif這段代碼里MAIN_Fosc這個(gè)宏很關(guān)鍵。很多例程的延時(shí)函數(shù)、串口波特率計(jì)算、定時(shí)器初值都依賴它。如果你板子上的晶振不是 11.0592MHz第一件事就是改這個(gè)宏然后重新編譯。改完還不算完串口波特率的定時(shí)器初值也要跟著重算否則通信還是有問(wèn)題。2.3 從點(diǎn)燈例程看 STC 的 GPIO 模式配置STC 單片機(jī)和傳統(tǒng) 8051 最大的區(qū)別之一就是 GPIO 多了模式寄存器。以 STC15 系列為例每個(gè)端口都有PxM0和PxM1兩個(gè)寄存器組合起來(lái)決定引腳是準(zhǔn)雙向、推挽、高阻還是開(kāi)漏。很多新手從 STM32 轉(zhuǎn)過(guò)來(lái)習(xí)慣性地直接給端口賦值結(jié)果 LED 亮度不對(duì)或者根本驅(qū)動(dòng)不了。// 01_GPIO_LED/led.c 中的初始化片段 #include led.h void LED_Init(void) { // 配置 P1.0 為推挽輸出驅(qū)動(dòng) LED P1M1 ~0x01; // P1.0 模式位 M1 清零 P1M0 | 0x01; // P1.0 模式位 M0 置一 // 組合結(jié)果M10, M01 - 推挽輸出 P1_0 1; // 初始熄滅取決于硬件接法 } void LED_Toggle(void) { P1_0 !P1_0; // 翻轉(zhuǎn)電平 }這段代碼里P1M1和P1M0的位操作是 STC 特有的。推挽輸出模式下引腳能輸出較大電流直接驅(qū)動(dòng) LED 沒(méi)問(wèn)題。如果是準(zhǔn)雙向模式輸出高電平時(shí)驅(qū)動(dòng)能力很弱LED 可能微亮或者不亮。這就是為什么有些人說(shuō)“代碼明明一樣我的燈就是不亮”——大概率是 GPIO 模式?jīng)]配對(duì)。3. 編譯與燒錄從 Keil 到 STC-ISP 的完整鏈路3.1 編譯前必須確認(rèn)的芯片型號(hào)與頭文件在 Keil 里打開(kāi)工程后左側(cè)Project窗口里會(huì)列出所有源文件。先看Target名稱通常作者會(huì)寫成芯片型號(hào)。右鍵Options for Target在Device標(biāo)簽頁(yè)確認(rèn)選中的芯片和實(shí)際板子一致。如果列表里找不到你的型號(hào)說(shuō)明 STC 的 Keil 支持包沒(méi)裝需要去 STC 官網(wǎng)下載安裝。頭文件方面STC 例程通常包含stc15.h、stc8.h或reg51.h。reg51.h是標(biāo)準(zhǔn) 8051 頭文件只包含基本寄存器沒(méi)有 STC 擴(kuò)展的PxM0、PxM1、AUXR等。如果你在代碼里用了這些擴(kuò)展寄存器但頭文件是reg51.h編譯會(huì)報(bào)“未定義標(biāo)識(shí)符”。常見(jiàn)做法是STC12 系列用stc12.hSTC15 系列用stc15.hSTC8 系列用stc8.h。這些頭文件在 STC-ISP 軟件里自帶也可以從官網(wǎng)下載。// main.c 中常見(jiàn)的頭文件包含順序 #include stc15.h // 芯片寄存器定義 #include type.h // 類型重定義 #include delay.h // 延時(shí)函數(shù) #include uart.h // 串口驅(qū)動(dòng) #include led.h // LED 驅(qū)動(dòng) void main(void) { UART_Init(9600); // 串口初始化波特率 9600 LED_Init(); // LED 初始化 while (1) { LED_Toggle(); delay_ms(500); // 延時(shí) 500ms printf(LED toggled\r\n); // 串口輸出調(diào)試信息 } }這段main函數(shù)的結(jié)構(gòu)很典型先初始化外設(shè)然后進(jìn)死循環(huán)。UART_Init(9600)里的參數(shù)是波特率改這個(gè)值的時(shí)候要同步改定時(shí)器初值。printf能用的前提是uart_printf.c里重定向了putchar或者用了TI中斷發(fā)送。如果串口助手收不到數(shù)據(jù)先檢查波特率、數(shù)據(jù)位、停止位是否和代碼一致再檢查硬件接線STC 的TXD接 USB 轉(zhuǎn)串口的RXDRXD接TXD共地。3.2 STC-ISP 下載軟件的參數(shù)設(shè)置與冷啟動(dòng)編譯出.hex文件后打開(kāi) STC-ISP 軟件。第一步選芯片型號(hào)必須和 Keil 里選的一致。第二步選串口號(hào)插上 USB 轉(zhuǎn)串口線后軟件會(huì)自動(dòng)識(shí)別如果沒(méi)識(shí)別到檢查驅(qū)動(dòng)是否安裝。第三步打開(kāi).hex文件。第四步設(shè)置硬件選項(xiàng)比如是否使用內(nèi)部晶振、復(fù)位腳是否用作 IO。點(diǎn)擊“下載/編程”按鈕后軟件會(huì)提示“正在檢測(cè)目標(biāo)單片機(jī)”。這時(shí)候需要給單片機(jī)上電冷啟動(dòng)——也就是先斷電點(diǎn)下載再上電。很多新手卡在這里因?yàn)榘遄右恢蓖ㄖ娷浖z測(cè)不到。冷啟動(dòng)是 STC 單片機(jī) ISP 下載的固定流程因?yàn)樾酒想姇r(shí)會(huì)先運(yùn)行 Bootloader檢測(cè)串口是否有下載命令。提示如果一直檢測(cè)不到先檢查串口線是否插緊再檢查板子上的晶振是否起振。有些最小系統(tǒng)板需要手動(dòng)按復(fù)位鍵有些則完全斷電再上電。下載成功后軟件會(huì)顯示“操作成功”。這時(shí)候板子上的 LED 應(yīng)該開(kāi)始閃爍串口助手也能看到打印信息。如果 LED 不閃先量一下 LED 引腳電壓是否在翻轉(zhuǎn)再檢查 LED 極性是否接反。3.3 串口調(diào)試用 printf 定位問(wèn)題的最快方式在嵌入式開(kāi)發(fā)里串口打印是最樸素的調(diào)試手段但也是最有效的。STC 例程里通常會(huì)把printf重定向到串口這樣你就能用printf(變量值%d\r\n, val)這種方式輸出中間變量。重定向的代碼一般長(zhǎng)這樣// Common/uart_printf.c 中的重定向?qū)崿F(xiàn) #include stc15.h #include uart.h // 重定向 putchar使 printf 輸出到串口 char putchar(char c) { while (!TI); // 等待上一個(gè)字節(jié)發(fā)送完成 TI 0; // 清除發(fā)送完成標(biāo)志 SBUF c; // 寫入發(fā)送緩沖區(qū) return c; }這段代碼里TI是串口發(fā)送完成標(biāo)志位SBUF是發(fā)送/接收緩沖區(qū)。while(!TI)會(huì)阻塞等待直到上一個(gè)字節(jié)發(fā)完。這種寫法簡(jiǎn)單但如果在中斷里調(diào)用printf可能會(huì)死鎖。常見(jiàn)做法是主循環(huán)里用printf沒(méi)問(wèn)題中斷里盡量只置標(biāo)志位不要直接打印。串口助手的設(shè)置要和代碼一致波特率 9600、數(shù)據(jù)位 8、停止位 1、無(wú)校驗(yàn)。如果收到亂碼先檢查晶振頻率和波特率是否匹配。11.0592MHz 晶振配 9600 波特率誤差為 012MHz 晶振配 9600 誤差約 0.16%短距離通信沒(méi)問(wèn)題長(zhǎng)距離可能丟包。4. 核心外設(shè)例程精講定時(shí)器、ADC 與 EEPROM4.1 定時(shí)器中斷從寄存器配置到中斷服務(wù)函數(shù)定時(shí)器是單片機(jī)最常用的外設(shè)之一STC 的定時(shí)器和標(biāo)準(zhǔn) 8051 兼容但多了AUXR寄存器來(lái)控制分頻。以定時(shí)器 0 為例工作模式 1 是 16 位定時(shí)器不自動(dòng)重裝模式 2 是 8 位自動(dòng)重裝。做精確延時(shí)或者波特率發(fā)生器通常用模式 2。// 02_Timer0/timer.c 中的初始化代碼 #include stc15.h #include timer.h void Timer0_Init(void) { AUXR ~0x80; // 定時(shí)器 0 時(shí)鐘為 12T 模式即 12 分頻 TMOD ~0x0F; // 清除定時(shí)器 0 模式位 TMOD | 0x01; // 定時(shí)器 0 工作模式 116 位定時(shí)器 // 假設(shè)晶振 11.0592MHz定時(shí) 50ms // 12T 模式下定時(shí)器計(jì)數(shù)頻率 11.0592MHz / 12 921600Hz // 50ms 需要計(jì)數(shù) 921600 * 0.05 46080 次 // 初值 65536 - 46080 19456 0x4C00 TH0 0x4C; TL0 0x00; ET0 1; // 使能定時(shí)器 0 中斷 EA 1; // 使能總中斷 TR0 1; // 啟動(dòng)定時(shí)器 0 } // 中斷服務(wù)函數(shù)中斷號(hào) 1 對(duì)應(yīng)定時(shí)器 0 void Timer0_ISR(void) interrupt 1 { TH0 0x4C; // 重新加載初值 TL0 0x00; // 用戶代碼比如翻轉(zhuǎn) LED P1_0 !P1_0; }這段代碼里AUXR寄存器的T0x12位決定定時(shí)器時(shí)鐘是 12T 還是 1T。12T 模式下定時(shí)器計(jì)數(shù)頻率是晶振頻率的 1/12和傳統(tǒng) 8051 一致。1T 模式下計(jì)數(shù)頻率等于晶振頻率定時(shí)更精確但初值計(jì)算方式不同。TMOD的低 4 位控制定時(shí)器 00x01表示模式 1。中斷服務(wù)函數(shù)后面的interrupt 1是 Keil C51 的語(yǔ)法數(shù)字 1 對(duì)應(yīng)定時(shí)器 0 中斷向量。初值計(jì)算是新手最容易出錯(cuò)的地方。TH0和TL0組成 16 位計(jì)數(shù)器從初值開(kāi)始往上數(shù)數(shù)到 65536 溢出觸發(fā)中斷。所以初值 65536 - 需要計(jì)數(shù)的次數(shù)。需要計(jì)數(shù)的次數(shù) 定時(shí)時(shí)間 × 計(jì)數(shù)頻率。把這三個(gè)量搞清楚定時(shí)器就不會(huì)配錯(cuò)。4.2 ADC 采樣通道選擇與參考電壓STC 的 ADC 模塊在 STC12、STC15、STC8 系列上都有通常是 10 位精度8 通道。用 ADC 的時(shí)候除了配置寄存器還要注意參考電壓。有些型號(hào)的參考電壓是內(nèi)部固定的有些可以外接。如果測(cè)出來(lái)的值一直不準(zhǔn)先檢查參考電壓是否穩(wěn)定。// 04_ADC/adc.c 中的采樣函數(shù) #include stc15.h #include adc.h u16 ADC_Read(u8 channel) { // 選擇通道P1ASF 寄存器對(duì)應(yīng) P1 口 P1ASF 1 channel; ADC_CONTR 0x80; // 使能 ADC 模塊 ADC_CONTR | channel; // 選擇通道 ADC_CONTR | 0x08; // 啟動(dòng)轉(zhuǎn)換 while (!(ADC_CONTR 0x10)); // 等待轉(zhuǎn)換完成 ADC_CONTR ~0x10; // 清除完成標(biāo)志 // 返回 10 位結(jié)果高 8 位在 ADC_RES低 2 位在 ADC_RESL return ((u16)ADC_RES 2) | (ADC_RESL 0x03); }這段代碼里P1ASF寄存器把 P1 口的某個(gè)引腳設(shè)為模擬輸入。ADC_CONTR的0x80位使能 ADC0x08位啟動(dòng)轉(zhuǎn)換0x10位是轉(zhuǎn)換完成標(biāo)志。結(jié)果拼接的時(shí)候ADC_RES是高 8 位ADC_RESL的低 2 位是低 2 位拼起來(lái)就是 10 位結(jié)果。如果只需要 8 位精度直接讀ADC_RES就行。實(shí)際使用中ADC 的輸入電壓范圍是 0 到 VCC。如果 VCC 是 5V10 位 ADC 的分辨率就是 5V / 1024 ≈ 4.88mV。測(cè)出來(lái)的值乘以 4.88mV 就是實(shí)際電壓。如果 VCC 不穩(wěn)定測(cè)出來(lái)的值會(huì)跳。常見(jiàn)做法是在 VCC 和 GND 之間并一個(gè) 0.1uF 電容濾掉高頻噪聲。4.3 EEPROM 讀寫掉電保存參數(shù)的實(shí)現(xiàn)STC 的 EEPROM 其實(shí)是 Data Flash用來(lái)保存掉電后需要保留的參數(shù)比如校準(zhǔn)值、用戶設(shè)置。操作 EEPROM 需要先擦除扇區(qū)再寫入。擦除和寫入都需要時(shí)間期間 CPU 會(huì)暫停。// 06_EEPROM/eeprom.c 中的讀寫函數(shù) #include stc15.h #include eeprom.h void EEPROM_Erase(u16 addr) { IAP_CONTR 0x80; // 使能 IAP IAP_CMD 0x03; // 扇區(qū)擦除命令 IAP_ADDRH addr 8; IAP_ADDRL addr 0xFF; IAP_TRIG 0x5A; // 觸發(fā)命令 IAP_TRIG 0xA5; IAP_CONTR 0x00; // 關(guān)閉 IAP } void EEPROM_Write(u16 addr, u8 dat) { IAP_CONTR 0x80; IAP_CMD 0x02; // 字節(jié)寫入命令 IAP_ADDRH addr 8; IAP_ADDRL addr 0xFF; IAP_DATA dat; IAP_TRIG 0x5A; IAP_TRIG 0xA5; IAP_CONTR 0x00; } u8 EEPROM_Read(u16 addr) { u8 dat; IAP_CONTR 0x80; IAP_CMD 0x01; // 字節(jié)讀取命令 IAP_ADDRH addr 8; IAP_ADDRL addr 0xFF; IAP_TRIG 0x5A; IAP_TRIG 0xA5; dat IAP_DATA; IAP_CONTR 0x00; return dat; }這段代碼里IAP_CONTR的0x80位使能 IAP 操作IAP_CMD選擇命令0x01讀、0x02寫、0x03擦除。IAP_TRIG需要依次寫入0x5A和0xA5才能觸發(fā)操作這是 STC 的固定流程順序不能反。擦除是按扇區(qū)進(jìn)行的一個(gè)扇區(qū)通常 512 字節(jié)擦除后整個(gè)扇區(qū)變成0xFF。寫入只能把1變成0所以寫之前必須先擦除。注意EEPROM 的擦寫次數(shù)有限通常 10 萬(wàn)次左右。不要在主循環(huán)里頻繁寫 EEPROM否則壽命很快耗盡。常見(jiàn)做法是只在參數(shù)變化時(shí)寫或者加一個(gè)延時(shí)幾秒內(nèi)不重復(fù)寫。5. 避坑與排查STC 例程跑不起來(lái)時(shí)先查這五條5.1 編譯通過(guò)但下載失敗現(xiàn)象Keil 編譯無(wú)報(bào)錯(cuò)生成.hex文件但 STC-ISP 一直提示“正在檢測(cè)目標(biāo)單片機(jī)”。原因通常是冷啟動(dòng)流程不對(duì)或者串口被占用。解決先關(guān)閉其他占用串口的軟件比如串口助手然后給單片機(jī)斷電點(diǎn)擊下載按鈕再上電。如果還是不行換一根 USB 轉(zhuǎn)串口線有些線質(zhì)量差下載不穩(wěn)定。5.2 LED 不亮或亮度異?,F(xiàn)象程序下載成功但 LED 不亮或者微亮。原因大概率是 GPIO 模式?jīng)]配對(duì)。STC 的準(zhǔn)雙向模式輸出高電平驅(qū)動(dòng)能力弱推挽模式才能正常驅(qū)動(dòng) LED。解決檢查PxM0和PxM1寄存器配置確保 LED 引腳設(shè)為推挽輸出。另外檢查 LED 極性有些板子是低電平點(diǎn)亮代碼里初始電平要對(duì)應(yīng)。5.3 串口亂碼或收不到數(shù)據(jù)現(xiàn)象串口助手收到亂碼或者完全沒(méi)數(shù)據(jù)。原因通常是波特率不匹配、晶振頻率和代碼不一致、接線錯(cuò)誤。解決先確認(rèn)代碼里的MAIN_Fosc和實(shí)際晶振一致再確認(rèn)串口助手波特率和代碼一致最后檢查TXD和RXD是否交叉連接共地是否接好。如果用的是內(nèi)部晶振波特率誤差可能較大建議用外部晶振。5.4 定時(shí)器中斷不觸發(fā)現(xiàn)象定時(shí)器初始化了但中斷服務(wù)函數(shù)不執(zhí)行。原因可能是總中斷沒(méi)開(kāi)、定時(shí)器沒(méi)啟動(dòng)、中斷號(hào)寫錯(cuò)。解決檢查EA 1和ET0 1是否都寫了檢查TR0 1是否啟動(dòng)檢查中斷服務(wù)函數(shù)的interrupt編號(hào)是否和定時(shí)器對(duì)應(yīng)。定時(shí)器 0 是 1定時(shí)器 1 是 3定時(shí)器 2 是 5。5.5 ADC 采樣值跳動(dòng)大現(xiàn)象ADC 讀出來(lái)的值不穩(wěn)定跳動(dòng)幾十個(gè)數(shù)值。原因可能是參考電壓不穩(wěn)、模擬輸入引腳懸空、電源噪聲大。解決在 VCC 和 GND 之間加濾波電容模擬輸入引腳接一個(gè)下拉電阻避免懸空采樣時(shí)多讀幾次取平均值。如果對(duì)精度要求高外接一個(gè)穩(wěn)定的參考電壓源。6. 二次開(kāi)發(fā)改造把例程變成自己的項(xiàng)目模板6.1 從單個(gè)例程到多外設(shè)協(xié)同的工程重組例程跑通之后下一步就是做自己的項(xiàng)目。你不可能把 LED 例程、串口例程、定時(shí)器例程各燒一遍而是要把它們合并到一個(gè)工程里。常見(jiàn)做法是新建一個(gè)工程文件夾把Common目錄復(fù)制過(guò)來(lái)再把需要的外設(shè)驅(qū)動(dòng)文件按功能分組添加。比如drivers/led.c、drivers/uart.c、drivers/timer.c每個(gè)驅(qū)動(dòng)配一個(gè)頭文件main.c只負(fù)責(zé)初始化和主循環(huán)邏輯。// 重組后的 main.c 結(jié)構(gòu) #include stc15.h #include type.h #include delay.h #include led.h #include uart.h #include timer.h #include adc.h void System_Init(void) { UART_Init(9600); // 串口初始化 LED_Init(); // LED 初始化 Timer0_Init(); // 定時(shí)器初始化 ADC_Init(); // ADC 初始化 } void main(void) { System_Init(); while (1) { u16 adc_val ADC_Read(0); // 讀通道 0 printf(ADC: %d\r\n, adc_val); delay_ms(1000); } }這種結(jié)構(gòu)的好處是每個(gè)驅(qū)動(dòng)模塊獨(dú)立改一個(gè)不影響其他。System_Init集中初始化主循環(huán)只處理業(yè)務(wù)邏輯。如果你要做低功耗可以在主循環(huán)里加PCON | 0x01進(jìn)空閑模式定時(shí)器中斷喚醒。6.2 用宏定義做功能裁剪與參數(shù)配置例程里的參數(shù)比如波特率、定時(shí)時(shí)間、ADC 通道最好用宏定義抽出來(lái)放在一個(gè)config.h里。這樣換板子或者改需求的時(shí)候只改一個(gè)文件不用滿工程搜數(shù)字。// config.h 中的可配置參數(shù) #ifndef __CONFIG_H #define __CONFIG_H #define MAIN_Fosc 11059200L // 主頻 #define UART_BAUD 9600 // 串口波特率 #define TIMER0_MS 50 // 定時(shí)器 0 周期單位 ms #define ADC_CHANNEL 0 // ADC 通道 #define LED_PIN P1_0 // LED 引腳 #endif改MAIN_Fosc的時(shí)候延時(shí)函數(shù)和串口波特率都會(huì)跟著變前提是驅(qū)動(dòng)代碼里用的是這個(gè)宏而不是硬編碼的數(shù)字。我見(jiàn)過(guò)很多例程延時(shí)函數(shù)里直接寫for(i0;i1000;i)這種延時(shí)不準(zhǔn)換晶振就廢了。合格的做法是用定時(shí)器做延時(shí)或者用_nop_()配合循環(huán)但循環(huán)次數(shù)要根據(jù)主頻算。6.3 驗(yàn)證改造是否成功的三個(gè)檢查點(diǎn)改造完工程燒錄之前先做三個(gè)檢查。第一編譯無(wú)警告。Keil 的警告往往藏著問(wèn)題比如未使用變量、類型不匹配別忽略。第二串口打印正常。在main開(kāi)頭打印一行版本信息確認(rèn)程序跑起來(lái)了。第三外設(shè)逐個(gè)驗(yàn)證。先點(diǎn)燈再串口再定時(shí)器最后 ADC。每加一個(gè)外設(shè)燒一次確認(rèn)沒(méi)問(wèn)題再加下一個(gè)。這樣出問(wèn)題的時(shí)候范圍小好定位。從那以后我每次拿到新的例程包都強(qiáng)制走一遍“先點(diǎn)燈、再串口、后中斷”的流程確認(rèn)基礎(chǔ)鏈路通了再往上堆功能。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取