草庐IT

沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟

Milton 2024-07-09 原文

目录

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.

有关沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  7. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  8. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  9. Ruby:标准递归模式 - 2

    我经常迷上ruby​​的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情

  10. ruby - 在 Ruby 中查找多个正则表达式匹配的模式和位置 - 2

    这应该是一个简单的问题,但我找不到任何相关信息。给定一个Ruby中的正则表达式,对于每个匹配项,我需要检索匹配的模式$1、$2,但我还需要匹配位置。我知道=~运算符为我提供了第一个匹配项的位置,而string.scan(/regex/)为我提供了所有匹配模式。如果可能,我需要在同一步骤中获得两个结果。 最佳答案 MatchDatastring.scan(regex)do$1#Patternatfirstposition$2#Patternatsecondposition$~.offset(1)#Startingandendingpo

随机推荐