STC8H開發(三): 基於FwLib_STC8的模數轉換ADC介紹和演示用例說明
- 2022 年 1 月 9 日
- 筆記
- ADC, FwLib_STC8, Mobile/Embed, STC8G, STC8H
目錄
- STC8H開發(一): 在Keil5中配置和使用FwLib_STC8封裝庫(圖文詳解)
- STC8H開發(二): 在Linux VSCode中配置和使用FwLib_STC8封裝庫(圖文詳解)
- STC8H開發(三): 基於FwLib_STC8的模數轉換ADC介紹和演示用例說明
前面介紹了在Keil5和PlatformIO環境下使用FwLib_STC8, 接下來以STC8H系列為主, 結合demo中的演示用例介紹ADC(模數轉換)
STC8G和STC8H的ADC模數轉換
STC8G和STC8H的ADC部分在寄存器設置上基本上一致, 但是不同型號對應的通道編號, 通道數量和精度有區別
通道數量和精度
對應STC8G/STC8H的各個系列的通道數量和精度如下.
產品線 | ADC 分辨率 | ADC 通道數 |
---|---|---|
STC8H1K08 系列 | 10 位 | 9 通道 |
STC8H1K28 系列 | 10 位 | 12 通道 |
STC8H3K64S4 系列 | 12 位 | 12 通道 |
STC8H3K64S2 系列 | 12 位 | 12 通道 |
STC8H8K64U 系列 | 12 位 | 15 通道 |
STC8H2K64T 系列 | 12 位 | 15 通道 |
STC8H4K64TLR 系列 | 12 位 | 15 通道 |
STC8H4K64TLCD 系列 | 12 位 | 15 通道 |
STC8H4K64LCD 系列 | 12 位 | 15 通道 |
通道的選擇使用寄存器ADC_CONTR
的低4位, 對應STC8G/STC8H的各個系列, 這個寄存器的數值對應的通道如下
STC8H1K28 | STC8H1K08 | STC8H3K64S4 STC8H3K64S2 |
STC8H8K64U STC8H2K64T STC8H4K64TLR |
STC8H4K64TLCD STC8H4K64LCD |
STC8G1K08A | STC8G1K08 STC8G1K08T |
STC8G2K64S4 STC8G2K64S2 |
|
---|---|---|---|---|---|---|---|---|
0000 | P1.0/ADC0 | P1.0/ADC0 | P1.0/ADC0 | P1.0/ADC0 | P1.0/ADC0 | P3.0/ADC0 | P1.0/ADC0 | P1.0/ADC0 |
0001 | P1.1/ADC1 | P1.1/ADC1 | P1.1/ADC1 | P1.1/ADC1 | P1.1/ADC1 | P3.1/ADC1 | P1.1/ADC1 | P1.1/ADC1 |
0010 | P1.2/ADC2 | N/A | P1.2/ADC2 | P5.4/ADC2 | P5.4/ADC2 | P3.2/ADC2 | P1.2/ADC2 | P1.2/ADC2 |
0011 | P1.3/ADC3 | N/A | N/A | P1.3/ADC3 | P1.3/ADC3 | P3.3/ADC3 | P1.3/ADC3 | P1.3/ADC3 |
0100 | P1.4/ADC4 | N/A | N/A | P1.4/ADC4 | P1.4/ADC4 | P5.4/ADC4 | P1.4/ADC4 | P1.4/ADC4 |
0101 | P1.5/ADC5 | N/A | N/A | P1.5/ADC5 | P1.5/ADC5 | P5.5/ADC5 | P1.5/ADC5 | P1.5/ADC5 |
0110 | P1.6/ADC6 | N/A | P1.6/ADC6 | P1.6/ADC6 | P6.2/ADC6 | N/A | P1.6/ADC6 | P1.6/ADC6 |
0111 | P1.7/ADC7 | N/A | P1.7/ADC7 | P1.7/ADC7 | P6.3/ADC7 | N/A | P1.7/ADC7 | P1.7/ADC7 |
1000 | P0.0/ADC8 | P3.0/ADC8 | P0.0/ADC8 | P0.0/ADC8 | P0.0/ADC8 | N/A | P3.0/ADC8 | P0.0/ADC8 |
1001 | P0.1/ADC9 | P3.1/ADC9 | P0.1/ADC9 | P0.1/ADC9 | P0.1/ADC9 | N/A | P3.1/ADC9 | P0.1/ADC9 |
1010 | P0.2/ADC10 | P3.2/ADC10 | P0.2/ADC10 | P0.2/ADC10 | P0.2/ADC10 | N/A | P3.2/ADC10 | P0.2/ADC10 |
1011 | P0.3/ADC11 | P3.3/ADC11 | P0.3/ADC11 | P0.3/ADC11 | P0.3/ADC11 | N/A | P3.3/ADC11 | P0.3/ADC11 |
1100 | N/A | P3.4/ADC12 | P0.4/ADC12 | P0.4/ADC12 | P0.4/ADC12 | N/A | P3.4/ADC12 | P0.4/ADC12 |
1101 | N/A | P3.5/ADC13 | P0.5/ADC13 | P0.5/ADC13 | P0.5/ADC13 | N/A | P3.5/ADC13 | P0.5/ADC13 |
1110 | N/A | P3.6/ADC14 | P0.6/ADC14 | P0.6/ADC14 | P0.6/ADC14 | N/A | P3.6/ADC14 | P0.6/ADC14 |
1111 | 1.19Vref | 1.19Vref | 1.19Vref | 1.19Vref | 1.19Vref | 1.19Vref | 1.19Vref | 1.19Vref |
轉換結果的對齊格式
ADC採樣的精度實際上是不能設置的, 採樣都是用的當前型號的最大精度, 結果存儲在[ADC_RES, ADC_RESL]這兩個寄存器. 為方便不同場合使用不同精度的結果, 可以將結果設置為左對齊或右對齊.
- 當設置為左對齊時, 可以只取ADC_RES的值(8位), 忽略最後兩位.
- 當設置位右對齊時, 根據實際的精度, 可以取ADC_RES的低4位(12位精度)或低2位(10位精度), 加上ADC_RESL得到最終結果.
轉換的時間消耗
一個完整的 ADC 轉換時間為 = Tsetup + Tduty + Thold + Tconvert
- Tsetup: 轉換的通道切換時間, 可以設置為1個或2個ADC時鐘周期
- Tduty: 轉換的採樣時間, 默認是最低的11個ADC時鐘, 最高為32個ADC時鐘周期
- Thold: 通道選擇的保持時間, 可以選擇1, 2, 3, 4個ADC時鐘周期
- Tconvert: 轉換時間是固定的, 10bit精度是10個ADC時鐘, 12bit精度是12個ADC時鐘
以上的時間單位都是ADC時鐘周期, 每個ADC時鐘周期佔用系統時鐘(SYSCLK)的數量是可以設置的, 使用ADCCFG
寄存器的低三位, 可以設置為最低2個系統時鐘周期到最高32個系統時鐘周期
對於轉換的最高頻率, DS上寫了全局限制
- 10 位 ADC 的速度不能高於 500KHz
- 12 位 ADC 的速度不能高於 800KHz
- 轉換的採樣時間不能小於 10,建議設置為 15
硬件連線
STC8G/STC8H的ADC硬件連線有兩種: 帶AVcc,AGrnd和不帶AVcc,AGrnd
帶 AVcc,AGrnd
高端型號STC8H3K64S2系列, 例如會帶這兩個pin腳, 分別對應的是轉換目標的電壓參考值和對地參考值. 對於普通使用, 這兩個可以直接接到VCC和GND, 連線為
AGrnd -> GND
AVcc -> VCC
AVref -> VCC
Vcc -> VCC
Gnd -> GND
ADC1 -> 採樣點
不帶 AVcc,AGrnd
低端型號以及STC8G系列不帶這兩個pin, 只需要接AVref, 採樣點與MCU共地連接, 連線為
AVref -> VCC
Vcc -> VCC
Gnd -> GND
ADC1 -> Test voltage
演示用例說明
以下演示用例, 基於 FwLib_STC8, 源代碼位於 FwLib_STC8/demo/adc 目錄, 可以自行下載或查看. 因為版本演變, 代碼可能與倉庫中的代碼有出入, 以倉庫中的最新版本為準.
關於如何運行演示用例, 可以參考前面介紹的Keil C51和VSCode PlatformIO的配置說明
使用ADC1進行8位ADC轉換, 主動查詢(polling)方式
下面的例子, 使用主動查詢的方式每隔0.1秒對P1.1口進行ADC轉換, 精度8位, 將結果輸出至串口
main.c代碼
#include "fw_hal.h"
void main(void)
{
uint8_t res;
// 調整系統頻率, 如果使用STC-ISP設定頻率, 需要將這行注釋掉
SYS_SetClock();
// 用於結果輸出
UART1_Config8bitUart(UART1_BaudSource_Timer2, HAL_State_ON, 115200);
// 將 ADC1(GPIO P1.1) 設為高阻輸入
GPIO_P1_SetMode(GPIO_Pin_1, GPIO_Mode_Input_HIP);
// 使用通道: ADC1
ADC_SetChannel(0x01);
// 設置ADC時鐘 = SYSCLK / 2 / (1+1) = SYSCLK / 4
ADC_SetClockPrescaler(0x01);
// 設置結果左對齊, 只需要取值 ADC_RES
ADC_SetResultAlignmentLeft();
// 開啟ADC電源
ADC_SetPowerState(HAL_State_ON);
while(1)
{
// 開始轉換
ADC_Start();
// 等待兩個系統時鐘
NOP();
NOP();
// 檢查轉換結果標誌位是否置位
while (!ADC_SamplingFinished());
// 清除結果標誌位
ADC_ClearInterrupt();
// 讀取結果
res = ADC_RES;
// 通過串口1輸出
UART1_TxString("Result: ");
UART1_TxHex(res);
UART1_TxString("\r\n");
// 等待100ms後再次進行轉換
SYS_Delay(100);
}
}
使用ADC1進行10位/12位ADC轉換, 中斷(interrupt)方式
下面的例子, 使用中斷的方式對P1.1口進行ADC連續轉換, 精度10位(或12位, MCU型號不同精度不同), 每隔0.1秒將結果輸出至串口
#include "fw_hal.h"
// 16位變量用於記錄轉換結果
uint16_t res;
// 處理中斷的方法, 使用宏定義保證Keil C51和SDCC的兼容性
INTERRUPT(ADC_Routine, EXTI_VectADC)
{
// 先清除中斷位
ADC_ClearInterrupt();
// 結果低8位
res = ADC_RESL;
// 結果高8位
res |= (ADC_RES & 0x0F) << 8;
// 再次啟動, 使得ADC連續轉換,
ADC_Start();
}
void main(void)
{
// 設置系統頻率
SYS_SetClock();
// 結果輸出
UART1_Config8bitUart(UART1_BaudSource_Timer2, HAL_State_ON, 115200);
// 設置P11高阻輸入模式
GPIO_P1_SetMode(GPIO_Pin_1, GPIO_Mode_Input_HIP);
// 使用通道: ADC1
ADC_SetChannel(0x01);
// ADC時鐘 = SYSCLK / 2 / (1+15) = SYSCLK / 32
ADC_SetClockPrescaler(0x0F);
// 右對齊, 方便轉換為雙位元組的結果
ADC_SetResultAlignmentRight();
// 開啟全局中斷和ADC中斷
EXTI_Global_SetIntState(HAL_State_ON);
EXTI_ADC_SetIntState(HAL_State_ON);
// 開啟ADC電源
ADC_SetPowerState(HAL_State_ON);
// 開始ADC轉換
ADC_Start();
while(1)
{
// 轉換結果輸出
UART1_TxString("Result: ");
UART1_TxHex(res >> 8);
UART1_TxHex(res & 0xFF);
UART1_TxString("\r\n");
SYS_Delay(100);
}
}
使用ADC1, ADC2雙通道進行轉換, 中斷(interrupt)方式
下面介紹一個更實用的例子, 中斷形式進行多通道ADC轉換, 可以用於無線小車遙控, 雙聲道音頻採樣等
#include "fw_hal.h"
// 用於記錄當前採樣的通道編號
uint8_t pos;
// 記錄各通道的採樣結果
uint16_t res[2];
// 中斷處理方法
INTERRUPT(ADC_Routine, EXTI_VectADC)
{
ADC_ClearInterrupt();
// 記錄採樣結果
res[pos] = ADC_RESL;
res[pos] |= (ADC_RES & 0x0F) << 8;
// 切換到下一個通道
pos = (pos+1) & 0x1;
if (pos == 0)
{
/**
* 在採樣頻率較高時, 加上這兩句能提高精度. 其機制是切換到開漏模式清除採樣口上的殘留電壓
GPIO_P1_SetMode(GPIO_Pin_1, GPIO_Mode_InOut_OD);
GPIO_P1_SetMode(GPIO_Pin_1, GPIO_Mode_Input_HIP);
*/
ADC_SetChannel(0x01);
}
else
{
/**
* Uncomment these lines in high speed ADC
GPIO_P1_SetMode(GPIO_Pin_2, GPIO_Mode_InOut_OD);
GPIO_P1_SetMode(GPIO_Pin_2, GPIO_Mode_Input_HIP);
*/
ADC_SetChannel(0x02);
}
ADC_Start();
}
// 下面的代碼和前面的基本上是一樣的, 就不詳細注釋了
void main(void)
{
SYS_SetClock();
// For debug print
UART1_Config8bitUart(UART1_BaudSource_Timer2, HAL_State_ON, 115200);
// Channel: ADC1
ADC_SetChannel(0x01);
// ADC Clock = SYSCLK / 2 / (1+15) = SYSCLK / 32
ADC_SetClockPrescaler(0x0F);
// Right alignment, high 2-bit in ADC_RES, low 8-bit in ADC_RESL
ADC_SetResultAlignmentRight();
// Enable interrupts
EXTI_Global_SetIntState(HAL_State_ON);
EXTI_ADC_SetIntState(HAL_State_ON);
// Turn on ADC power
ADC_SetPowerState(HAL_State_ON);
// Set ADC1(P1.1), ADC2(P1.2) HIP
GPIO_P1_SetMode(GPIO_Pin_1|GPIO_Pin_2, GPIO_Mode_Input_HIP);
// Start ADC
ADC_Start();
while(1)
{
UART1_TxString("Result: ");
UART1_TxHex(res[0] >> 8);
UART1_TxHex(res[0] & 0xFF);
UART1_TxChar(' ');
UART1_TxHex(res[1] >> 8);
UART1_TxHex(res[1] & 0xFF);
UART1_TxString("\r\n");
SYS_Delay(100);
}
}
結束
以上就是STC8H使用FwLib_STC8封裝庫進行ADC轉換的演示用例說明. 在實際使用中, 主動查詢(polling)方式下的延時時間精度不高,
如果對採樣的時間間隔精度有要求, 建議使用中斷的形式.