草帽王子 发表于 2021-7-31 19:17:40

第一百零三章:CH32V103应用教程——RCC—HSE/HSI时钟配置

本帖最后由 草帽王子 于 2021-7-31 19:17 编辑

本章教程主要讲述如何使用HSE或HSI配置系统时钟,可配合CH32V103应用手册第三章学习本教程。

1、HSI、HSE简介

HSI是系统内部 8MHz 的 RC 振荡器产生的高速时钟信号。HSI RC 振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间很短但时钟频率精度较差。HSI 通过设置 RCC_CTLR 寄存器中的 HSION 位被启动和关闭,HSIRDY 位指示 HSI RC 振荡器是否稳定。系统默认 HSION 和 HSIRDY 置 1(建议不要关闭)。如果设置了 RCC_INTR 寄存器的 HSIRDYIE 位,将产生相应中断。

出厂校准:制造工艺的差异会导致每个芯片的 RC 振荡频率不同,所以在芯片出厂前,会为每颗芯片进行 HSI 校准。系统复位后,工厂校准值被装载到 RCC_CTLR 寄存器的 HSICAL中。

用户调整:基于不同的电压或环境温度,应用程序可以通过 RCC_CTLR 寄存器里的 HSITRIM位来调整 HSI 频率。

注:如果 HSE 晶体振荡器失效,HSI 时钟会被作为备用时钟源(时钟安全系统)。

HSE 是外部的高速时钟信号,包括外部晶体/陶瓷谐振器产生或者外部高速时钟送入。

外部晶体/陶瓷谐振器(HSE 晶体):外接 4-16MHz 外部振荡器为系统提供更为精确的时钟源。进一步信息可参考数据手册的电气特性部分。HSE 晶体可以通过设置 RCC_CTLR 寄存器中的 HSEON位被启动和关闭,HSERDY 位指示 HSE 晶体振荡是否稳定,硬件在 HSERDY 位置 1 后才将时钟送入系统。如果设置了 RCC_INTR 寄存器的 HSERDYIE 位,将产生相应中断。

注:负载电容需要尽可能地靠近振荡器引脚,并根据晶体厂家参数选择容值。

外部高速时钟源(HSE 旁路):此模式从外部直接送入时钟源到 OSC_IN 引脚,OSC_OUT 引脚悬空。最高支持 25MHz 频率。应用程序需在 HSEON 位为 0 情况下,置位 HSEBYP 位,打开 HSE 旁路功能,然后再置
位 HSEON 位。
MCO是microcontroller clock output的缩写,是微控制器时钟输出引脚,在 CH32V103系列中由PA8复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。MCO的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK,具体选哪个由时钟配置寄存器0的位 26-24:MCO决定。除了对外提供时钟这个作用之外,我们还可以通过示波器监控MCO引脚的时钟输出来验证我们的系统时钟配置是否正确。

通常在程序中,我们都是使用HSE经过PLL倍频之后作为系统时钟。HSE作为外部晶振大小通常为8M,PLL的倍频因子设置为9,因此系统时钟大小为72M。CH32V103系统时钟最高是80M。在CH32V103标准库函数中,可以在启动文件startup_ch32v10x.S中看到,在跳转到main函数之前,会先调用SystemInit()函数把系统始终初始化成72MHz,如下图。关于SystemInit()函数具体配置,可在system_ch32v10x.c文件中查看。


当HSE出现故障不能使用的时候,如果开启了时钟安全系统,将切换到HSI时钟下。时钟安全系统是控制器的一种运行保护机制,它可以在 HSE 时钟发送故障的情况下,切换到 HSI时钟下,并产生中断通知,允许应用程序软件完成营救操作。

通过设置 RCC_CTLR 寄存器的 CSSON 位置 1,激活时钟安全系统。此时,时钟监测器将在 HSE 振荡器启动(HSERDY=1)延迟后被使能,并在 HSE 时钟关闭后关闭。一旦系统运行过程中 HSE 时钟发生故障,HSE 振荡器将被关闭,时钟失效事件将被送到高级定时器(TIM1 和 TIM8)的刹车输入端,并产生时钟安全中断,CSSF 位置 1,并且应用程序进入 NMI 不可屏蔽中断,通过置位 CSSC 位,可以清除CSSF 位标志,可撤销 NMI 中断挂起位。

如果当前 HSE 作为系统时钟,或者当前 HSE 作为 PLL 输入时钟,PLL 作为系统时钟,时钟安全系统将在 HSE 故障时自动将系统时钟切换到 HSI 振荡器,并关闭 HSE 振荡器和 PLL。

在此处需要注意的是:CH32与STM32不同,STM32F103内部8M时钟输入PLL需要2分频,CH32内部8M经过增强配置,可不分频输入PLL。芯片可以在使用HSI时工作于72MHz主频。为了兼容STM32标准库,同时又为了保证CH32内部8M可不分频输入PLL,CH32提供了一个配置扩展寄存器,通过配置扩展寄存器(EXTEND_CTR)位4,可设置HSI时钟是否分频,具体如下图所示:


关于扩展配置寄存器,其功能描述具体可参考CH32V103应用手册。

关于CH32V103 RCC具体信息,可参考CH32V103应用手册。

2、硬件设计

RCC为单片机内部资源,无需进行硬件连接。但本章教程需要通过LED闪烁快慢现象观察系统主频高低,因此需要将PA0和PA1与开发板LED灯引脚连接起来。

3、软件设计

本章教程主要讲述如何使用HSE和HSI配置系统时钟,具体程序如下:
clk.h文件
#ifndef __CLK_H

#define __CLK_H



#include"ch32v10x.h"



void HSE_SetSysClock(uint32_t pllmul);

void HSI_SetSysClock(uint32_t pllmul);



#endif
clk.h文件主要进行函数声明;
clk.c文件


#include "clk.h"



/*

* 使用HSE时,设置系统时钟的步骤

* 1、开启HSE ,并等待 HSE 稳定

* 2、设置 AHB、APB2、APB1的预分频因子

* 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置

* 4、开启PLL,并等待PLL稳定

* 5、把PLLCK切换为系统时钟SYSCLK

* 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟

*/



/* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1

* PCLK2 = HCLK = SYSCLK

* PCLK1 = HCLK

* 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:

* 举例:User_SetSysClock(RCC_PLLMul_9);则设置系统时钟为:8MHZ * 9 = 72MHZ

* HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法

*/



void HSE_SetSysClock(uint32_t pllmul)

{

    __IO uint32_t HSEStartUpStatus = 0;



// 把RCC外设初始化成复位状态

RCC_DeInit();



//使能HSE,开启外部晶振,野火开发板用的是8M

RCC_HSEConfig(RCC_HSE_ON);



// 等待 HSE 启动稳定

HSEStartUpStatus = RCC_WaitForHSEStartUp();



    // 只有 HSE 稳定之后则继续往下执行

if (HSEStartUpStatus == SUCCESS)

{

//----------------------------------------------------------------------//

    // 这两句是操作FLASH闪存用到的,如果不操作FLASH的话,这两个注释掉也没影响

    // 使能FLASH 预存取缓冲区

    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);



    // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2

    // 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,

    // 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了

    // 0:0 < SYSCLK <= 24M

    // 1:24< SYSCLK <= 48M

    // 2:48< SYSCLK <= 72M

    FLASH_SetLatency(FLASH_Latency_2);

//----------------------------------------------------------------------//



    // AHB预分频因子设置为1分频,HCLK = SYSCLK

    RCC_HCLKConfig(RCC_SYSCLK_Div1);



    // APB2预分频因子设置为1分频,PCLK2 = HCLK

    RCC_PCLK2Config(RCC_HCLK_Div1);



    // APB1预分频因子设置为1分频,PCLK1 = HCLK/2

    RCC_PCLK1Config(RCC_HCLK_Div2);



//-----------------设置各种频率主要就是在这里设置-------------------//

    // 设置PLL时钟来源为HSE,设置PLL倍频因子

    // PLLCLK = 8MHz * pllmul

    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);

//------------------------------------------------------------------//



    // 开启PLL

    RCC_PLLCmd(ENABLE);



    // 等待 PLL稳定

    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)

    {

    }



    // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK

    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);



    // 读取时钟切换状态位,确保PLLCLK被选为系统时钟

    while (RCC_GetSYSCLKSource() != 0x08)

    {

    }

}

else

{

    // 如果HSE开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理

    // 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,HSI是内部的高速时钟,8MHZ

    while (1)

    {

    }

}

}





/*

* 使用HSI时,设置系统时钟的步骤

* 1、开启HSI ,并等待 HSI 稳定

* 2、设置 AHB、APB2、APB1的预分频因子

* 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置

* 4、开启PLL,并等待PLL稳定

* 5、把PLLCK切换为系统时钟SYSCLK

* 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟

*/



/* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:HCLK2, APB1总线时钟:PCLK1

* PCLK2 = HCLK = SYSCLK

* PCLK1 = HCLK

* 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:

*

* HSI作为时钟来源,经过PLL倍频作为系统时钟,这是在HSE故障的时候才使用的方法

* HSI会因为温度等原因会有漂移,不稳定,一般不会用HSI作为时钟来源,除非是迫不得已的情况

* 如果HSI要作为PLL时钟的来源的话,可选择二分频或者不分频直接输入PLL。芯片可以在使用HSI时工作在72MHz主频

* HSI作为PLL输入时钟,演示系统时钟为 24MHz、48MHz、72MHz。

* HSI/2作为PLL输入时钟,演示系统时钟为 24MHz、48MHz

*/

void HSI_SetSysClock(uint32_t pllmul)

{



#if (PLL_Source == HSI)

EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;//HSI时钟作为PLL输入时钟

#endif



__IO uint32_t HSIStartUpStatus = 0;



// 把RCC外设初始化成复位状态

RCC_DeInit();



//使能HSI

RCC_HSICmd(ENABLE);



// 等待 HSI 就绪

HSIStartUpStatus = RCC->CTLR & RCC_HSIRDY;



// 只有 HSI就绪之后则继续往下执行

if (HSIStartUpStatus == RCC_HSIRDY)

{

//----------------------------------------------------------------------//

    // 这两句是操作FLASH闪存用到的,如果不操作FLASH的话,这两个注释掉也没影响

    // 使能FLASH 预存取缓冲区

    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);



    // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2

    // 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,

    // 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了

    // 0:0 < SYSCLK <= 24M

    // 1:24< SYSCLK <= 48M

    // 2:48< SYSCLK <= 72M

    FLASH_SetLatency(FLASH_Latency_2);

//----------------------------------------------------------------------//



    // AHB预分频因子设置为1分频,HCLK = SYSCLK

    RCC_HCLKConfig(RCC_SYSCLK_Div1);



    // APB2预分频因子设置为1分频,PCLK2 = HCLK

    RCC_PCLK2Config(RCC_HCLK_Div1);



    // APB1预分频因子设置为1分频,PCLK1 = HCLK/2

    RCC_PCLK1Config(RCC_HCLK_Div2);



//-----------------设置各种频率主要就是在这里设置-------------------//

    // 若设置PLL时钟来源为HSI,设置PLL倍频因子

    // PLLCLK = 8MHz * pllmul

    // 若设置PLL时钟来源为HSI/2,设置PLL倍频因子

    // PLLCLK = 4MHz * pllmul

    RCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);

//------------------------------------------------------------------//



    // 开启PLL

    RCC_PLLCmd(ENABLE);



    // 等待 PLL稳定

    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)

    {

    }



    // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK

    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);



    // 读取时钟切换状态位,确保PLLCLK被选为系统时钟

    while (RCC_GetSYSCLKSource() != 0x08)

    {

    }

}

else

{

    // 如果HSI开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理

    // 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,HSI是内部的高速时钟,8MHZ

    while (1)

    {

    }

}

}



clk.c文件主要介绍使用HSE和HSI配置系统时钟,具体说明见程序注释。此处需要注意的是,当我们单独使用HSE和HSI配置系统时钟时,需要将system_ch32v10x.c文件的时钟全部注释掉,具体如下图:
Main.c文件
/********************************** (C) COPYRIGHT *******************************

* File Name          : main.c

* Author             : WCH

* Version            : V1.0.0

* Date               : 2020/04/30

* Description      : Main program body.

*******************************************************************************/



// 程序来到main函数之前,启动文件:statup_ch32v10x.S已经调用SystemInit()函数把系统时钟初始化成72MHZ

// SystemInit()在system_ch32v10x.c中定义

// 如果用户想修改系统时钟,可自行编写程序修改,本章教程主要介绍用户如何编写程序修改系统时钟以及通过PA8引脚输出相关时钟



#include "debug.h"

#include "clk.h"



#define HSI      0   //HSI

#define HSI_1_21   //HSI/2



#define PLL_Source   HSI

//#define PLL_Source   HSI_1_2



//软件延时函数,使用不同的系统时钟,延时不一样

void Delay(__IO uint32_t nCount)

{

    for(; nCount != 0; nCount--);

}



void MCO_GPIO_Init(void)

{

    GPIO_InitTypeDefGPIO_InitStructure;

    // 开启GPIOA的时钟

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);



    // 选择GPIO8引脚

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;



    //设置为复用功能推挽输出

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;



    //设置IO的翻转速率为50M

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;



    // 初始化GPIOA8

    GPIO_Init(GPIOA, &GPIO_InitStructure);

}



void LED_Init(void)

{



    GPIO_InitTypeDefGPIO_InitStructure;                     //定义一个GPIO_InitTypeDef类型的结构体



    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //使能与LED相关的GPIO端口时钟



    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;      //配置GPIO引脚

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;          //设置GPIO模式为推挽输出

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;         //设置GPIO口输出速度

    GPIO_Init(GPIOA, &GPIO_InitStructure);                  //调用库函数,初始化GPIOA



    GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1);                //设置引脚输出高电平



}



/*******************************************************************************

* Function Name: main

* Description    : Main program.

* Input          : None

* Return         : None

*******************************************************************************/

int main(void)

{





    USART_Printf_Init(115200);

      MCO_GPIO_Init();

      LED_Init();



      printf("This is HSE&HSI example\r\n");



    // 重新设置系统时钟,根据需要修改,CH32V103最高可设置为80MH,但通常使用72M作为默认设置

    // SYSCLK = 8M * RCC_PLLMul_x, x:

    HSE_SetSysClock(RCC_PLLMul_9);

    //HSI_SetSysClock(RCC_PLLMul_9);



    // 设置MCO引脚输出时钟,用示波器即可在PA8测量到输出的时钟信号

    // MCO引脚输出可以是HSE,HSI,PLLCLK/2,SYSCLK

    //RCC_MCOConfig(RCC_MCO_HSE);             // 8M

    //RCC_MCOConfig(RCC_MCO_HSI);             // 8M

    //RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);   // 36M

    RCC_MCOConfig(RCC_MCO_SYSCLK);            // 72M



    while (1)

    {

      GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1);      // 亮

      Delay(0x0FFFFF);

      GPIO_ResetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1);      // 灭

      Delay(0x0FFFFF);

    }

}Main.c文件主要进行相关函数初始化以及通过MCO输出时钟,此外可通过LED闪烁情况查看系统时钟大小。

4、下载验证

将编译好的程序下载到开发板并复位,可通过LED灯闪烁查看系统时钟大小,系统时钟越大,闪烁越快。

利用示波器检测MCO引脚PA8时钟输出频率,结果如下。



页: [1]
查看完整版本: 第一百零三章:CH32V103应用教程——RCC—HSE/HSI时钟配置