篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了沁恒 CH32V208: CH32V208的储存结构, 启动模式和时钟相关的知识,希望对你有一定的参考价值。
这一篇简单说明 CH32V208 的片内存储结构和时钟的特点, 以及通过 SDK 中的示例代码分析 CH32V208 的时钟设置
目录
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 的存储器地址映射
![](https://image.cha138.com/20230502/c657ed97e9204dc99e48f15d4f25a25d.jpg)
地址分配和 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 启动 |
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 的时钟
根据数据手册, 时钟树结构如下
![](https://image.cha138.com/20230502/c6f9075a1c4c4accbe6cb1c75df7ee5f.jpg)
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.
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1994
http://poj.org/problem?id=2396
题目大意:给一个m行n列的空矩阵,让你往上面填数(数为非负整数),使得这个矩阵满足:
1.每行/列和等于给定的每行/列和
2.一些限制条件。
限制条件限制了x,y,ch,v,使得x行y列的点的数满足(>or=or<,由ch决定)v。
特别的,如果x=0则代表y列全满足该条件,如果y=0则代表x行全满足该条件,如果全为0则代表全矩阵全满足该条件。
——————————————————————————
有二分图+上下界网络流想法,不难想到先建源汇点,源点到所有的行连一条上下界均为行和的边,所有的列到汇点连一条上下界均为列和的边。
然后整理限制条件,行和列之间连满足所有限制条件的边。
在那之后就是上下界网络流的活了。
注意:
1.可以先判断行和之和如果不等于列和之和的话就是IMPOSSIBLE。
2.限制条件如果不满足的话||第一条发生的话,记得把剩余的限制条件读完再输出。
3.我们其实可以简化,我们其实根本不需要源汇点,于是它其实可以转换成无源汇上下界网络流可行流。
原因:最开始建源汇点的时候我们发现它只连了上下界相同的边,相当于没连,以致到后来跑可行流的时候它根本不会做出任何贡献,所以大可以删掉。
4.IMPOSSIBLE不要打错……
5.ZOJ选手注意,不能填负数,最后一行不能有两个回车。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=405;
const int M=2000005;
const int INF=1e9;
inline int read(){
int X=0,w=0;char ch=0;
while(!isdigit(ch)){w|=ch==‘-‘;ch=getchar();}
while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
struct node{
int nxt,to,w;
}edge[M];
int head[N],du[N],up[205][205],low[205][205];
int cnt=-1,S,T;
bool die[205][205];
inline void add(int u,int v,int w){
cnt++;
edge[cnt].to=v;
edge[cnt].w=w;
edge[cnt].nxt=head[u];
head[u]=cnt;
return;
}
int lev[N],cur[N],dui[N];
bool bfs(int k){
int r=0;
for(int i=1;i<=k;i++){
lev[i]=-1;
cur[i]=head[i];
}
dui[0]=S,lev[S]=0;
int u,v;
for(int l=0;l<=r;l++){
u=dui[l];
for(int e=head[u];e!=-1;e=edge[e].nxt){
v=edge[e].to;
if(edge[e].w>0&&lev[v]==-1){
lev[v]=lev[u]+1;
r++;
dui[r]=v;
if(v==T)return 1;
}
}
}
return 0;
}
int dinic(int u,int flow,int k){
if(u==k)return flow;
int res=0,delta;
for(int &e=cur[u];e!=-1;e=edge[e].nxt){
int v=edge[e].to;
if(edge[e].w>0&&lev[u]<lev[v]){
delta=dinic(v,min(edge[e].w,flow-res),k);
if(delta>0){
edge[e].w-=delta;
edge[e^1].w+=delta;
res+=delta;
if(res==flow)break;
}
}
}
if(res!=flow)lev[u]=-1;
return res;
}
inline void init(int m,int n){
memset(head,-1,sizeof(head));
memset(du,0,sizeof(du));
memset(die,0,sizeof(die));
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
up[i][j]=INF;
low[i][j]=0;
}
}
cnt=-1;
return;
}
inline char getc(){
char ch=‘ ‘;
while(ch==‘ ‘)ch=getchar();
return ch;
}
int main(){
int t=read(),num=0;
while(t--){
num++;
if(num>1)puts("");
int m=read(),n=read();
init(m,n);
int tot=0;
for(int i=1;i<=m;i++){
int h=read();
du[i]+=h;
tot+=h;
}
for(int i=1;i<=n;i++){
int l=read();
du[i+m]-=l;
tot-=l;
}
int c=read();
bool flag=1;
for(int i=1;i<=c;i++){
int r=read(),q=read(),a,b;
char ch=getc();
int v=read();
if(ch==‘<‘){a=0;b=--v;}
else if(ch==‘>‘){a=++v;b=INF;}
else a=b=v;
if(!r&&!q){
for(int j=1;j<=m&&flag;j++){
for(int k=1;k<=n&&flag;k++){
if(die[j][k]&&(low[j][k]>b||low[j][k]<a)){
flag=0;
break;
}
if(ch==‘=‘){up[j][k]=low[j][k]=v;die[j][k]=1;}
else if(ch==‘>‘)low[j][k]=max(low[j][k],v);
else up[j][k]=min(up[j][k],v);
if(low[j][k]>up[j][k])flag=0;
}
}
}else if(!r){
for(int j=1;j<=m&&flag;j++){
if(die[j][q]&&(low[j][q]>b||low[j][q]<a)){
flag=0;
break;
}
if(ch==‘=‘){up[j][q]=low[j][q]=v;die[j][q]=1;}
else if(ch==‘>‘)low[j][q]=max(low[j][q],v);
else up[j][q]=min(up[j][q],v);
if(low[j][q]>up[j][q])flag=0;
}
}else if(!q){
for(int k=1;k<=n&&flag;k++){
if(die[r][k]&&(low[r][k]>b||low[r][k]<a)){
flag=0;
break;
}
if(ch==‘=‘){up[r][k]=low[r][k]=v;die[r][k]=1;}
else if(ch==‘>‘)low[r][k]=max(low[r][k],v);
else up[r][k]=min(up[r][k],v);
if(low[r][k]>up[r][k])flag=0;
}
}else{
if(die[r][q]&&(low[r][q]>b||low[r][q]<a)){
flag=0;
}
if(ch==‘=‘){up[r][q]=low[r][q]=v;die[r][q]=1;}
else if(ch==‘>‘)low[r][q]=max(low[r][q],v);
else up[r][q]=min(up[r][q],v);
if(low[r][q]>up[r][q])flag=0;
}
}
if(!flag||tot!=0){
puts("IMPOSSIBLE");
continue;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
add(i,j+m,up[i][j]-low[i][j]);
add(j+m,i,0);
du[i]-=low[i][j];
du[j+m]+=low[i][j];
}
}
S=m+n+1;T=S+1;
int ans=0,full=0;
for(int i=1;i<=m+n;i++){
if(du[i]>0){
add(S,i,du[i]);
add(i,S,0);
full+=du[i];
}else if(du[i]<0){
add(i,T,-du[i]);
add(T,i,0);
}
}
while(bfs(T))ans+=dinic(S,INF,T);
if(ans!=full)puts("IMPOSSIBLE");
else{
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
int id=((i-1)*n+j)*2-1;
printf("%d",low[i][j]+edge[id].w);
if(j!=n)putchar(‘ ‘);
}
puts("");
}
}
}
return 0;
}