CH32V103应用教程——RCC—HSE/HSI时钟配置
本章教程主要讲述如何使用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[7:0]中。 用户调整:基于不同的电压或环境温度,应用程序可以通过 RCC_CTLR 寄存器里的 HSITRIM[4:0]位来调整 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[2:0]决定。除了对外提供时钟这个作用之外,我们还可以通过示波器监控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:[2,3,...16]
* 举例: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:[2,3,...16]
*
* 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_2 1 //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_InitTypeDef GPIO_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_InitTypeDef GPIO_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:[2,3,...16]
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时钟输出频率,结果如下。
|