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)方式下的延時時間精度不高,
如果對採樣的時間間隔精度有要求, 建議使用中斷的形式.