沁恆 CH32V208(二): CH32V208的儲存結構, 啟動模式和時鐘

2023-05-02 06:00:23

目錄

CH32V 儲存容量命名方式

在介紹下面的內容前, 先看一下CH32V系列和儲存相關的命名格式, 以CH32V203為例, 前面的CH32V203代表一個系列, 後面的字元分別代表了Pin腳數量, Flash大小, 封裝和工作溫度範圍

CH32V203G6U6
        ||||
        |||`-> Temperature range
        ||`--> Package: QFN
        |`---> Flash Size
        `----> Pin Count

其中的Flash大小表示為

4 = 16K
6 = 32K
8 = 64K
B = 128K
C = 256K

以及以D開頭的容量表示形式(在使用者手冊中會出現)

D6 32KB or 64KB, Low-and-medium-density general
D8 128KB or 256KB, High-density general
D8C 128KB or 256KB, Connectivity or interconnectivity
D8W 128KB or 256KB, Wireless

這些容量型別與型號的對應關係為

  • CH32V20x_D6
    CH32V203F6, CH32V203G6, CH32V203K6, CH32V203F8, CH32V203G8, CH32V203K8, CH32V203C6, CH32V203C8
  • CH32V20x_D8
    CH32V203RB
  • CH32V20x_D8W
    CH32V208GB, CH32V208CB, CH32V208RB, CH32V208WB
  • CH32V30x_D8
    CH32V303CB, CH32V303RB, CH32V303RC, CH32V303VC
  • CH32V30x_D8C
    CH32V305FB, CH32V305RB, CH32V307RC, CH32V307WC, CH32V307VC

可以看到 CH32V208 全系列屬於 CH32V20x_D8W 容量型別

CH32V208 的儲存

資料手冊中對儲存部分的說明為

  • 內建最大 64K 位元組 SRAM 區, 用於存放資料, 掉電後資料丟失. 具體容量要對應晶片型號.
  • 內建最大 480K 位元組程式快閃記憶體儲存區(Code FLASH), 用於使用者的應用程式和常數資料儲存. 其中包括零等待程式執行區域和非零等待區域.
  • 內建 28K 位元組系統儲存區(System FLASH)用於系統載入程式儲存(廠家固化自舉載入程式).
  • 128 位元組用於系統非易失設定資訊儲存區, 128 位元組用於使用者選擇字儲存區

CH32V208 的記憶體地址對映

下圖是 CH32V208 的記憶體地址對映

地址分配和 ARM Cortex M 幾乎是一樣的

  • Flash地址從 0x0800 0000 開始
  • RAM地址從 0x2000 0000 開始
  • 根據 BOOT pin 的設定, 啟動時將對應的地址對映到 0x0000 0000

其中 Flash 大小是 480KB, 而 RAM 是可以設定的(應該是一塊總計192KB的RAM), 根據零等待Flash的大小不同, 有三種劃分選項 128KF + 64KR, 144KF + 48KR, 160KF + 32KR. 當啟動時, 對應大小的code從 Flash 載入到 RAM 中執行, 實現零等待.

Flash RAM 對映關係

CH32V208 的 Flash 分為三塊: 最開始的128KB固定對映到RAM, 在復位後複製到RAM; 之後的32KB是可設定區域; 除了前面的160KB, 後面的320KB是固定的非零等待程式碼區域.

        | Fixed    | Dynamic |
| ----- | -------- | ------- | -------------------- | ------ |
| Flash | 128KB    | 32KB    | 320KB                | 32KB   |
          ------------------------------------------
                                                 └───480K 使用者可擦寫可執行
| ----- | -------- | ------- | ------ | -------------------- |
| RAM   | 128KB    | 32KB    | 32KB   |
          --------   -------   ------
             |          |       └─── 32K固定RAM
             |          └───32K可設定為RAM或Flash對映
             └───128K固定Flash對映, 復位後硬體拷貝

在LD檔案中設定可用 Flash 大小

編輯專案中的 link.ld, 在 MEMORY 部分修改, 下面的例子將 Flash 設定為 448KB

MEMORY
{  
  FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 448K
  RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}

注意 Flash 的 ORIGIN 從 0x0000 0000 開始, 不是 0x0800 0000, 因為執行時 Flash 會被對映到 0 地址, 連線時程式碼的地址都以0地址為偏移量.

啟動模式

在啟動時, 通過自舉引腳(BOOT0 和 BOOT1), 可以選擇三種自舉模式中的一種

BOOT0 BOOT1 啟動模式
0 X 從Code Flash 啟動
1 0 從System FLASH 啟動
1 1 從內部 SRAM 啟動
  • BOOT0 為獨立的pin
  • BOOT1 為PB2

QFN28封裝的 CH32V208GBU6 比較特殊, 一是沒有引出 BOOT1, 預設接地, 二是 BOOT0 與 PB8 共用同一個物理PIN腳, 在手冊第19頁有單獨說明:

BOOT0引腳引出, 但BOOT1/PB2引腳未引出的晶片, 內部BOOT1/PB2引腳將下拉到GND. 此時如果進入低功耗模式設定IO口狀態時, 建議BOOT1/PB2引腳使用輸入下拉模式防止產生額外電流.

BOOT0和PB8引腳合封晶片, 建議外接500K下拉電阻, 保證晶片上電穩定進入程式快閃記憶體記憶體自舉模式. 另外, 此PB8引腳及其複用功能只保留了輸出驅動功能, 所有輸入功能已被禁止.

這個500K下拉可以保證BOOT0不浮空的同時, 對PB8作為輸出不造成影響.

28引腳封裝晶片有許多合封引腳(至少2個IO功能引腳物理合為一個引腳), 此時驅動不要同時設定輸出功能, 否則可能損壞引腳. 有功耗要求的注意引腳狀態.

簡單說就是合封的pin腳, 不要同時設為輸出模式

CH32V208 的時鐘

根據資料手冊, 時鐘樹結構如下

CH32V208 的時鐘相對於 CH32V307 的不同點: 在CH307中沒有 ETH-PHY

對於 CH32F20x_D8C 和 CH32V30x_D8C, 當使用 USB 功能時,CPU 的頻率必須是48MHz、96MHz 或 144MH

而在 CH32V208中, ETH-PHY 的時鐘通過 HCLK 提供

CH32F20x_D8W, CH32V20x_D8 和 CH32V20x_D8W 若同時使用 USB 和 ETH 功能, 需將 USBPRE[1:0]置為 11b

對於 USBPRE[1:0] 這個暫存器值為 0B11 時的說明

5 分頻, 且 PLL 的源為 HSE 二分頻(適用於PLLCLK=240MHz , 僅 適 用 於 CH32V20x_D8W/ CH32F20x_D8W) 注: CH32V20x_D8W、CH32F20x_D8W 具有 11b 選項, 其餘型號該選項保留

可以看到, CH32V208 如果要同時使用 USB 和 ETH, 為了同時滿足 USB 的48MHz, ETH-PHY 的60MHz, 需要將 PLLCLK 升至240MHz, 5分頻後輸出給 USB, 而 ETH-PHY 則從 120MHz 的 HCLK 通過2分頻得到 60MHz

另一個需要注意的點是, BLE的 RFCLK 時鐘是由 HSE 提供的, 如果時鐘樹沒錯的話, 可以理解為只有外接時鐘源才能使用 BLE.

時鐘設定程式碼

在沁恆提供的 SDK 和程式碼範例中, 與時鐘相關的程式碼主要是這兩個檔案

ch32v20x.h

檔案中定義了外接時鐘源的頻率 HSE_VALUE, CH32V208 預設使用的是 32MHz, 如果使用其他頻率的晶振, 需要在這裡修改

#if defined(CH32V20x_D8) || defined(CH32V20x_D8W)
  #define HSE_VALUE    ((uint32_t)32000000) /* Value of the External oscillator in Hz */
#else
  #define HSE_VALUE    ((uint32_t)8000000) /* Value of the External oscillator in Hz */
#endif

而內建時鐘源是固定的 8MHz

#define HSI_VALUE              ((uint32_t)8000000) /* Value of the Internal oscillator in Hz */

system_ch32v20x.c

這個檔案存在於每個範例專案的 User 目錄下, 已經實現了常用的頻率值函數, 通過修改宏設定可以切換不同的系統頻率

//#define SYSCLK_FREQ_HSE    HSE_VALUE
//#define SYSCLK_FREQ_48MHz_HSE  48000000
//#define SYSCLK_FREQ_56MHz_HSE  56000000
//#define SYSCLK_FREQ_72MHz_HSE  72000000
//#define SYSCLK_FREQ_96MHz_HSE  96000000
//#define SYSCLK_FREQ_120MHz_HSE  120000000
#define SYSCLK_FREQ_144MHz_HSE  144000000
//#define SYSCLK_FREQ_HSI    HSI_VALUE
//#define SYSCLK_FREQ_48MHz_HSI  48000000
//#define SYSCLK_FREQ_56MHz_HSI  56000000
//#define SYSCLK_FREQ_72MHz_HSI  72000000
//#define SYSCLK_FREQ_96MHz_HSI  96000000
//#define SYSCLK_FREQ_120MHz_HSI  120000000
//#define SYSCLK_FREQ_144MHz_HSI  144000000

在裡面搜尋(3<<22), 對應 RCC->CFGR0, (3<<22)就是 USBPRE 暫存器, 可以看到在設定系統頻率為 120MHz 時的特殊處理.

void SystemCoreClockUpdate (void)
{
  uint32_t tmp = 0, pllmull = 0, pllsource = 0, Pll_6_5 = 0;

  tmp = RCC->CFGR0 & RCC_SWS;

  switch (tmp)
  {
    case 0x00:
      SystemCoreClock = HSI_VALUE;
      break;
    case 0x04:
      SystemCoreClock = HSE_VALUE;
      break;
    case 0x08:
      pllmull = RCC->CFGR0 & RCC_PLLMULL;
      pllsource = RCC->CFGR0 & RCC_PLLSRC;
      pllmull = ( pllmull >> 18) + 2;

      if(pllmull == 17) pllmull = 18;

      if (pllsource == 0x00)
      {
          if(EXTEN->EXTEN_CTR & EXTEN_PLL_HSI_PRE){
              SystemCoreClock = HSI_VALUE * pllmull;
          }
          else{
              SystemCoreClock = (HSI_VALUE >> 1) * pllmull;
          }
      }
      else
      {
#if defined (CH32V20x_D8W)                                 // 對應 CH32V208 額外的處理邏輯
        if((RCC->CFGR0 & (3<<22)) == (3<<22))              // 如果 USBPRE 為 11, 僅出現在 120MHz的設定函數中
        {
          SystemCoreClock = ((HSE_VALUE>>1)) * pllmull;    // 系統時鐘為 32 / 2 * 15 = 240MHz
        }
        else
#endif
        if ((RCC->CFGR0 & RCC_PLLXTPRE) != (uint32_t)RESET)
        {
#if defined (CH32V20x_D8) || defined (CH32V20x_D8W)
          SystemCoreClock = ((HSE_VALUE>>2) >> 1) * pllmull;
#else
          SystemCoreClock = (HSE_VALUE >> 1) * pllmull;
#endif
        }
        else
        {
#if defined (CH32V20x_D8) || defined (CH32V20x_D8W)
            SystemCoreClock = (HSE_VALUE>>2) * pllmull;
#else
          SystemCoreClock = HSE_VALUE * pllmull;
#endif
        }
      }

      if(Pll_6_5 == 1) SystemCoreClock = (SystemCoreClock / 2);

      break;
    default:
      SystemCoreClock = HSI_VALUE;
      break;
  }

  tmp = AHBPrescTable[((RCC->CFGR0 & RCC_HPRE) >> 4)];             // 通過 AHBPrescTable 對應的分頻係數, 降回 120MHz
  SystemCoreClock >>= tmp;
}

AHBPrescTable 的分頻係數陣列為

__I uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};

在 SetSysClockTo120_HSE(void) 中, 設定了 RCC_HPRE_DIV2

RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV2;

而 RCC_HPRE_DIV2 的值對應的是 0x00000080, RCC_HPRE 的值是 0x000000F0

#define RCC_HPRE                                ((uint32_t)0x000000F0) /* HPRE[3:0] bits (AHB prescaler) */
#define RCC_HPRE_0                              ((uint32_t)0x00000010) /* Bit 0 */
#define RCC_HPRE_1                              ((uint32_t)0x00000020) /* Bit 1 */
#define RCC_HPRE_2                              ((uint32_t)0x00000040) /* Bit 2 */
#define RCC_HPRE_3                              ((uint32_t)0x00000080) /* Bit 3 */

#define RCC_HPRE_DIV1                           ((uint32_t)0x00000000) /* SYSCLK not divided */
#define RCC_HPRE_DIV2                           ((uint32_t)0x00000080) /* SYSCLK divided by 2 */
#define RCC_HPRE_DIV4                           ((uint32_t)0x00000090) /* SYSCLK divided by 4 */

通過 RCC->CFGR0 & RCC_HPRE, 可以還原回 0x00000080, 再右移4位元, 就變成 0x00000008, 對應 AHBPrescTable 中的第9個, 值為1, SystemCoreClock 右移1位, 相當於除以2, 值從240MHz變回120MHz.