一、了解系统框图
1.1 分析系统框图结构
从图中可以看到两部分红色和蓝色,蓝色框都是外设,由公司厂商自己设计比如ST、NXP、GD等等。红色部分包括cortex内核、FLASH、总线矩阵。其中cortex内核也就是CPU由ARM公司设计,所以称为ARM架构。目前市面主流架构以及代表产品有ARM架构(单片机、linux),RISC-V架构(单片机),x86(win)。
内核通过总线与其他单元通信,这里面有四条总线:ICode、DCode、DMA总线、系统总线。
- ICode:指令总线,cortex内核不停地从flash中读取指令来执行。
- DCode:数据总线,用来读取flash中存放的定义的常量数据。
- DMA总线:DMA外号搬运助手,在CPU不介入的情况搬运FLASH和SRAM的数据。
- 系统总线:CPU与外设、SRAM连接的总线。
总线矩阵:从图中可以看出DMA和DCode有一个总线矩阵,因为它两都可以访问FLASH中的数据,所以需要一个仲裁单元。DMA、系统总线、SRAM、外设、时钟源互相连接也需要一个仲裁单元来仲裁。从图中也可以看出,这款芯片指令总线与数据总线分开的,所以它属于哈佛架构。
1.2 高速外设总线AHB总线
从名字就能够知道AHB总线的时钟频率很高,它的作用也就是高速传输数据以及同时连接多个外设提高多效率。一般AHB的时钟频率与系统时钟频率一样。挂载在AHB总线上的外设主要有APB1、APB2子总线。
注:尽管RCC挂载在AHB总线上,但是RCC不算是AHB上面的外设。只是因为RCC对速度要求很高,AHB总线提供了高速的数据传输能力,这对于时钟信号的管理至关重要。因此它被挂载在AHB总线上,以确保时钟信号的及时和准确传递。
1.3 低速总线APB总线上的外设
从上图可以看到,大部分的外设GPIO、TIM、SCI等等都挂载在APB总线上,知道外设挂载在哪条线上可以让我们找到每个外设的直接时钟源。比如TIM的时钟频率就是APB1配置。
二、了解时钟树
- 上图中四处红色框对应时钟时钟源,1:外部低速晶振为RTC模块供电(网上买的RTC模块上面的晶振就是32.768kHz)。2:外部高速晶振,为整个单片机系统提供时钟参考。3:内部低速RC振荡电路,为独立看门狗提供时钟源。4:高速内部RC振荡电路,可以代替外部高速晶振。但是由于随着芯片的工作芯片内部温度会有变化,所以会使得内部的振荡电路产生温漂,从而使得晶振频率发生变化,所以需要配置相关寄存器来进行校准。通常都用外部高速晶振作为时钟源。
以32MHz的HSE为晶振输入,有四条支路。
- 1:32M晶振128分频后为RTC提供时钟源。
- 2: 通过RCC_CFGR0寄存器PLLXTPRE位来配置4分频或8分频进如锁相环。
- 3:32M晶振经过2分频进入锁相环。
- 4:32M晶振直接接到系统时钟源上。
因为晶振频率太低,所以一般不选择支路4作为系统主频。下面以支路2为例讲解。
首先32M经过4分频后,进入PLLMUL倍频环节。此时频率为32/4=8Mhz,要想达到该芯片官方推荐的最高的144MHz主频。还需要进行18倍频,同样也是最大倍频系数。倍频后锁相环时钟PLLCLK就是144MHz,同样将开关SW选择为PLLCLK即可将系统时钟配置为144Mhz。整个过程操作的寄存器只有RCC_CFGR0。
2.1 时钟配置代码
首先打开启动文件,观察系统时钟什么时候配置的。
从图可以看到,在硬件复位中断后首先会调用SystemInit函数,随之进入main函数。所以时钟就在SystemInit函数中配置。全局搜索该函数名字发现就在system_ch32f20x.c中。
仅关注红框中的配置,在寄存器配置中通常用 &= 来进行复位,|= 来进行置为。
直接进入SetSysClock()函数。
分析SetSysClockTo144_HSE()。
到这里APB1,APB2上的外设的时钟源以及系统时钟源都配置好了,如果想要开启某个外设的时钟,只需在RCC中配置即可。
2.2 MCO引脚
时钟树中有个MCO,该引脚可以将配置的时钟源输出。最大支持25MHz。通常在以太网通信中,用MCO做时钟参考。
这里先配置用HSI内部时钟8Mhz作为MCO时钟源输出,用逻辑分析仪代替示波器观察其脉冲频率。
从图中可以看到输出脉冲频率为8Mhz。
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOA,&GPIO_InitStructure);
RCC_MCOConfig(RCC_MCO_HSI);
while(1)
{}
}