[RISC-V MCU 应用开发] 第一百零三章、CH32V103应用教程——RCC—HSE/HSI时钟配置

[复制链接]
 楼主| RISCVLAR 发表于 2021-7-14 16:05 | 显示全部楼层 |阅读模式
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 位,将产生相应中断。
图片1.png

注:负载电容需要尽可能地靠近振荡器引脚,并根据晶体厂家参数选择容值。
外部高速时钟源(HSE 旁路):此模式从外部直接送入时钟源到 OSC_IN 引脚,OSC_OUT 引脚悬空。最高支持 25MHz 频率。应用程序需在 HSEON 位为 0 情况下,置位 HSEBYP 位,打开 HSE 旁路功能,然后再置
位 HSEON 位。
图片2.png
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文件中查看。
图片3.png
当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时钟是否分频,具体如下图所示:
图片4.png
关于扩展配置寄存器,其功能描述具体可参考CH32V103应用手册。
关于CH32V103 RCC具体信息,可参考CH32V103应用手册。

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

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

  3. #include  "ch32v10x.h"

  4. void HSE_SetSysClock(uint32_t pllmul);
  5. void HSI_SetSysClock(uint32_t pllmul);

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

  1. #include "clk.h"

  2. /*
  3. * 使用HSE时,设置系统时钟的步骤
  4. * 1、开启HSE ,并等待 HSE 稳定
  5. * 2、设置 AHB、APB2、APB1的预分频因子
  6. * 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置
  7. * 4、开启PLL,并等待PLL稳定
  8. * 5、把PLLCK切换为系统时钟SYSCLK
  9. * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟
  10. */

  11. /* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1
  12. * PCLK2 = HCLK = SYSCLK
  13. * PCLK1 = HCLK
  14. * 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16]
  15. * 举例:User_SetSysClock(RCC_PLLMul_9);  则设置系统时钟为:8MHZ * 9 = 72MHZ
  16. * HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法
  17. */

  18. void HSE_SetSysClock(uint32_t pllmul)
  19. {
  20.     __IO uint32_t HSEStartUpStatus = 0;

  21.   // 把RCC外设初始化成复位状态
  22.   RCC_DeInit();

  23.   //使能HSE,开启外部晶振,野火开发板用的是8M
  24.   RCC_HSEConfig(RCC_HSE_ON);

  25.   // 等待 HSE 启动稳定
  26.   HSEStartUpStatus = RCC_WaitForHSEStartUp();

  27.     // 只有 HSE 稳定之后则继续往下执行
  28.   if (HSEStartUpStatus == SUCCESS)
  29.   {
  30. //----------------------------------------------------------------------//
  31.     // 这两句是操作FLASH闪存用到的,如果不操作FLASH的话,这两个注释掉也没影响
  32.     // 使能FLASH 预存取缓冲区
  33.     FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

  34.     // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2
  35.     // 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,
  36.     // 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了
  37.     // 0:0 < SYSCLK <= 24M
  38.     // 1:24< SYSCLK <= 48M
  39.     // 2:48< SYSCLK <= 72M
  40.     FLASH_SetLatency(FLASH_Latency_2);
  41. //----------------------------------------------------------------------//

  42.     // AHB预分频因子设置为1分频,HCLK = SYSCLK
  43.     RCC_HCLKConfig(RCC_SYSCLK_Div1);

  44.     // APB2预分频因子设置为1分频,PCLK2 = HCLK
  45.     RCC_PCLK2Config(RCC_HCLK_Div1);

  46.     // APB1预分频因子设置为1分频,PCLK1 = HCLK/2
  47.     RCC_PCLK1Config(RCC_HCLK_Div2);

  48. //-----------------设置各种频率主要就是在这里设置-------------------//
  49.     // 设置PLL时钟来源为HSE,设置PLL倍频因子
  50.     // PLLCLK = 8MHz * pllmul
  51.     RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);
  52. //------------------------------------------------------------------//

  53.     // 开启PLL
  54.     RCC_PLLCmd(ENABLE);

  55.     // 等待 PLL稳定
  56.     while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
  57.     {
  58.     }

  59.     // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
  60.     RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

  61.     // 读取时钟切换状态位,确保PLLCLK被选为系统时钟
  62.     while (RCC_GetSYSCLKSource() != 0x08)
  63.     {
  64.     }
  65.   }
  66.   else
  67.   {
  68.     // 如果HSE开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
  69.     // 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,HSI是内部的高速时钟,8MHZ
  70.     while (1)
  71.     {
  72.     }
  73.   }
  74. }


  75. /*
  76. * 使用HSI时,设置系统时钟的步骤
  77. * 1、开启HSI ,并等待 HSI 稳定
  78. * 2、设置 AHB、APB2、APB1的预分频因子
  79. * 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置
  80. * 4、开启PLL,并等待PLL稳定
  81. * 5、把PLLCK切换为系统时钟SYSCLK
  82. * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟
  83. */

  84. /* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:HCLK2, APB1总线时钟:PCLK1
  85. * PCLK2 = HCLK = SYSCLK
  86. * PCLK1 = HCLK
  87. * 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16]
  88. *
  89. * HSI作为时钟来源,经过PLL倍频作为系统时钟,这是在HSE故障的时候才使用的方法
  90. * HSI会因为温度等原因会有漂移,不稳定,一般不会用HSI作为时钟来源,除非是迫不得已的情况
  91. * 如果HSI要作为PLL时钟的来源的话,可选择二分频或者不分频直接输入PLL。芯片可以在使用HSI时工作在72MHz主频
  92. * HSI作为PLL输入时钟,演示系统时钟为 24MHz、48MHz、72MHz。
  93. * HSI/2作为PLL输入时钟,演示系统时钟为 24MHz、48MHz
  94. */
  95. void HSI_SetSysClock(uint32_t pllmul)
  96. {

  97. #if (PLL_Source == HSI)
  98.   EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;  //HSI时钟作为PLL输入时钟
  99. #endif

  100.   __IO uint32_t HSIStartUpStatus = 0;

  101.   // 把RCC外设初始化成复位状态
  102.   RCC_DeInit();

  103.   //使能HSI
  104.   RCC_HSICmd(ENABLE);

  105.   // 等待 HSI 就绪
  106.   HSIStartUpStatus = RCC->CTLR & RCC_HSIRDY;

  107.   // 只有 HSI就绪之后则继续往下执行
  108.   if (HSIStartUpStatus == RCC_HSIRDY)
  109.   {
  110. //----------------------------------------------------------------------//
  111.     // 这两句是操作FLASH闪存用到的,如果不操作FLASH的话,这两个注释掉也没影响
  112.     // 使能FLASH 预存取缓冲区
  113.     FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

  114.     // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2
  115.     // 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,
  116.     // 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了
  117.     // 0:0 < SYSCLK <= 24M
  118.     // 1:24< SYSCLK <= 48M
  119.     // 2:48< SYSCLK <= 72M
  120.     FLASH_SetLatency(FLASH_Latency_2);
  121. //----------------------------------------------------------------------//

  122.     // AHB预分频因子设置为1分频,HCLK = SYSCLK
  123.     RCC_HCLKConfig(RCC_SYSCLK_Div1);

  124.     // APB2预分频因子设置为1分频,PCLK2 = HCLK
  125.     RCC_PCLK2Config(RCC_HCLK_Div1);

  126.     // APB1预分频因子设置为1分频,PCLK1 = HCLK/2
  127.     RCC_PCLK1Config(RCC_HCLK_Div2);

  128. //-----------------设置各种频率主要就是在这里设置-------------------//
  129.     // 若设置PLL时钟来源为HSI,设置PLL倍频因子
  130.     // PLLCLK = 8MHz * pllmul
  131.     // 若设置PLL时钟来源为HSI/2,设置PLL倍频因子
  132.     // PLLCLK = 4MHz * pllmul
  133.     RCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);
  134. //------------------------------------------------------------------//

  135.     // 开启PLL
  136.     RCC_PLLCmd(ENABLE);

  137.     // 等待 PLL稳定
  138.     while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
  139.     {
  140.     }

  141.     // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
  142.     RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

  143.     // 读取时钟切换状态位,确保PLLCLK被选为系统时钟
  144.     while (RCC_GetSYSCLKSource() != 0x08)
  145.     {
  146.     }
  147.   }
  148.   else
  149.   {
  150.     // 如果HSI开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
  151.     // 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,HSI是内部的高速时钟,8MHZ
  152.     while (1)
  153.     {
  154.     }
  155.   }
  156. }

clk.c文件主要介绍使用HSE和HSI配置系统时钟,具体说明见程序注释。此处需要注意的是,当我们单独使用HSE和HSI配置系统时钟时,需要将system_ch32v10x.c文件的时钟全部注释掉,具体如下图:
图片5.png
Main.c文件
  1. /********************************** (C) COPYRIGHT *******************************
  2. * File Name          : main.c
  3. * Author             : WCH
  4. * Version            : V1.0.0
  5. * Date               : 2020/04/30
  6. * Description        : Main program body.
  7. *******************************************************************************/

  8. // 程序来到main函数之前,启动文件:statup_ch32v10x.S已经调用SystemInit()函数把系统时钟初始化成72MHZ
  9. // SystemInit()在system_ch32v10x.c中定义
  10. // 如果用户想修改系统时钟,可自行编写程序修改,本章教程主要介绍用户如何编写程序修改系统时钟以及通过PA8引脚输出相关时钟

  11. #include "debug.h"
  12. #include "clk.h"

  13. #define HSI      0   //HSI
  14. #define HSI_1_2  1   //HSI/2

  15. #define PLL_Source   HSI
  16. //#define PLL_Source   HSI_1_2

  17. //  软件延时函数,使用不同的系统时钟,延时不一样
  18. void Delay(__IO uint32_t nCount)
  19. {
  20.     for(; nCount != 0; nCount--);
  21. }

  22. void MCO_GPIO_Init(void)
  23. {
  24.     GPIO_InitTypeDef  GPIO_InitStructure;
  25.     // 开启GPIOA的时钟
  26.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

  27.     // 选择GPIO8引脚
  28.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

  29.     //设置为复用功能推挽输出
  30.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

  31.     //设置IO的翻转速率为50M
  32.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  33.     // 初始化GPIOA8
  34.     GPIO_Init(GPIOA, &GPIO_InitStructure);
  35. }

  36. void LED_Init(void)
  37. {

  38.     GPIO_InitTypeDef  GPIO_InitStructure;                     //定义一个GPIO_InitTypeDef类型的结构体

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

  40.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;      //配置GPIO引脚
  41.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;          //设置GPIO模式为推挽输出
  42.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;         //设置GPIO口输出速度
  43.     GPIO_Init(GPIOA, &GPIO_InitStructure);                    //调用库函数,初始化GPIOA

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

  45. }

  46. /*******************************************************************************
  47. * Function Name  : main
  48. * Description    : Main program.
  49. * Input          : None
  50. * Return         : None
  51. *******************************************************************************/
  52. int main(void)
  53. {


  54.     USART_Printf_Init(115200);
  55.         MCO_GPIO_Init();
  56.         LED_Init();

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

  58.     // 重新设置系统时钟,根据需要修改,CH32V103最高可设置为80MH,但通常使用72M作为默认设置
  59.     // SYSCLK = 8M * RCC_PLLMul_x, x:[2,3,...16]
  60.     HSE_SetSysClock(RCC_PLLMul_9);
  61.     //HSI_SetSysClock(RCC_PLLMul_9);

  62.     // 设置MCO引脚输出时钟,用示波器即可在PA8测量到输出的时钟信号
  63.     // MCO引脚输出可以是HSE,HSI,PLLCLK/2,SYSCLK
  64.     //RCC_MCOConfig(RCC_MCO_HSE);             // 8M
  65.     //RCC_MCOConfig(RCC_MCO_HSI);             // 8M
  66.     //RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);     // 36M
  67.     RCC_MCOConfig(RCC_MCO_SYSCLK);            // 72M

  68.     while (1)
  69.     {
  70.         GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1);        // 亮
  71.         Delay(0x0FFFFF);
  72.         GPIO_ResetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1);      // 灭
  73.         Delay(0x0FFFFF);
  74.     }
  75. }

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

4下载验证
将编译好的程序下载到开发板并复位,可通过LED灯闪烁查看系统时钟大小,系统时钟越大,闪烁越快。
利用示波器检测MCO引脚PA8时钟输出频率,结果如下。
图片6.png

102、RCC-HSE、HSI时钟配置.rar

458.86 KB, 下载次数: 81

您需要登录后才可以回帖 登录 | 注册

本版积分规则

133

主题

296

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部

133

主题

296

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部