CH32V003在MRS中的初始化过程

发布时间 2023-08-13 21:18:56作者: fxzq

在MRS的默认配置中,在main函数执行之前,就已经执行了时钟的初始化配置程序,这部分程序被放在了一个名为system_ch32v00x.c的文件中,这个文件默认被加载到MRS的User目录下(可双击打开它)。在该文件中,最重要的一个函数就是SystemInit,它负责系统的初始化工作,其代码如下所示。 

void SystemInit (void)
{
  RCC->CTLR |= (uint32_t)0x00000001;
  RCC->CFGR0 &= (uint32_t)0xFCFF0000;
  RCC->CTLR &= (uint32_t)0xFEF6FFFF;
  RCC->CTLR &= (uint32_t)0xFFFBFFFF;
  RCC->CFGR0 &= (uint32_t)0xFFFEFFFF;
  RCC->INTR = 0x009F0000;
  SetSysClock();
} 

在末尾调用了一个设置系统时钟的函数SetSysClock,其代码如下。

static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_8MHz_HSI
    SetSysClockTo_8MHz_HSI();
#elif defined SYSCLK_FREQ_24MHZ_HSI
    SetSysClockTo_24MHZ_HSI();
#elif defined SYSCLK_FREQ_48MHZ_HSI
    SetSysClockTo_48MHZ_HSI();
#elif defined SYSCLK_FREQ_8MHz_HSE
    SetSysClockTo_8MHz_HSE();
#elif defined SYSCLK_FREQ_24MHz_HSE
    SetSysClockTo_24MHz_HSE();
#elif defined SYSCLK_FREQ_48MHz_HSE
    SetSysClockTo_48MHz_HSE();
#endif
}

从上可看出,该函数主要依据宏定义SYSCLK_FREQ的值来决定使用哪一个配置函数,并没有具体内容。这里不妨选取最后一个函数SetSysClockTo_48MHz_HSE(即使用外部晶振的48MHz主频模式)来展开讨论,其代码如下所示。

static void SetSysClockTo_48MHz_HSE(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    /* Close PA0-PA1 GPIO function */
    RCC->APB2PCENR |= RCC_AFIOEN;
    AFIO->PCFR1 |= (1<<15);

    RCC->CTLR |= ((uint32_t)RCC_HSEON);

    /* Wait till HSE is ready and if Time out is reached exit */
    do
    {
        HSEStatus = RCC->CTLR & RCC_HSERDY;
        StartUpCounter++;
    } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    if ((RCC->CTLR & RCC_HSERDY) != RESET)
    {
        HSEStatus = (uint32_t)0x01;
    }
    else
    {
        HSEStatus = (uint32_t)0x00;
    }

    if (HSEStatus == (uint32_t)0x01)
    {
        /* Flash 0 wait state */
        FLASH->ACTLR &= (uint32_t)((uint32_t)~FLASH_ACTLR_LATENCY);
        FLASH->ACTLR |= (uint32_t)FLASH_ACTLR_LATENCY_1;

        /* HCLK = SYSCLK = APB1 */
        RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;

        /* PLL configuration: PLLCLK = HSE * 2 = 48 MHz */
        RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC));
        RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE_Mul2);

        /* Enable PLL */
        RCC->CTLR |= RCC_PLLON;
        /* Wait till PLL is ready */
        while((RCC->CTLR & RCC_PLLRDY) == 0)
        {
        }
        /* Select PLL as system clock source */
        RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
        RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
        /* Wait till PLL is used as system clock source */
        while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
        {
        }
    }
    else
    {
        /*
         * If HSE fails to start-up, the application will have wrong clock
     * configuration. User can add here some code to deal with this error
         */
    }
}

以上就是MRS中系统初始化的具体流程,看起来是不是有些绕(仅为了方便开发者),能不能自己实现一个精简的初始化过程呢?答案是肯定的。以下就一个是非常精简的初始化代码实例。

void SetSysClockTo_48MHz_HSE(void)
{
    RCC->CFGR0 = 0;   //AHB时钟不分频
    RCC->APB2PCENR |= 0x00000001;  //使能I/O辅助功能模块时钟
    AFIO->PCFR1 |= (1<<15);  //引脚PA1&PA2重映射位,改为接外部晶振引脚
    RCC->CTLR |= 0x00010000;  //使能外部振荡器
    while(!(RCC->CTLR & 0x00020000));  //等待外部晶振稳定
    FLASH->ACTLR &= 0x00000003;
    FLASH->ACTLR |= 0x00000001; //设置FLASH等待
    RCC->CFGR0 |= 0x00010000;  //选择外部晶振
    RCC->CTLR |= 0x01000000;  //开启PLL
    while(!(RCC->CTLR & 0x02000000));  //等待PLL稳定
    RCC->CFGR0 &= ~0x00000003;
    RCC->CFGR0 |= 0x00000002;  //选择PLL作为系统时钟
}

上面的函数可直接放在main.c文件中,提供给main函数调用。为此,还可以移出默认在User目录下的系统配置文件system_ch32v00x.c及其头文件system_ch32v00x.h,只留下一个main.c文件,同时把头文件ch32v00x.h中的“#include <system_ch32v00x.h>”一句注释掉,让开发环境做到极简。但是,文件移出后编译会报错,原因是缺失了系统初始化函数SystemInit。如何解决?有两种方法。其一,把上面的函数名称改为“void SystemInit (void)”,让系统在复位后直接执行初始化,这就不需要在main函数中来调用它了。其二,更改系统在复位后的程序入口,直接执行main函数,这需要修改系统启动文件startup_ch32v00x.S。启动文件是用汇编写成的,下面是它的具体内容。 

    .section  .init, "ax", @progbits
    .globl  _start
    .align  2
_start:
    .option   norvc;
    j       handle_reset
    .word   0
    .word   NMI_Handler                  /* NMI Handler */
    .word   HardFault_Handler            /* Hard Fault Handler */
    .word   0
    .word   0
    .word   0
    .word   0
    .word   0
    .word   0
    .word   0
    .word   0
    .word   SysTick_Handler             /* SysTick Handler */
    .word   0
    .word   SW_Handler                  /* SW Handler */
    .word   0
    /* External Interrupts */
    .word   WWDG_IRQHandler             /* Window Watchdog */
    .word   PVD_IRQHandler              /* PVD through EXTI Line detect */
    .word   FLASH_IRQHandler            /* Flash */
    .word   RCC_IRQHandler              /* RCC */
    .word   EXTI7_0_IRQHandler           /* EXTI Line 7..0 */
    .word   AWU_IRQHandler              /* AWU */
    .word   DMA1_Channel1_IRQHandler       /* DMA1 Channel 1 */
    .word   DMA1_Channel2_IRQHandler       /* DMA1 Channel 2 */
    .word   DMA1_Channel3_IRQHandler       /* DMA1 Channel 3 */
    .word   DMA1_Channel4_IRQHandler       /* DMA1 Channel 4 */
    .word   DMA1_Channel5_IRQHandler       /* DMA1 Channel 5 */
    .word   DMA1_Channel6_IRQHandler       /* DMA1 Channel 6 */
    .word   DMA1_Channel7_IRQHandler       /* DMA1 Channel 7 */
    .word   ADC1_IRQHandler              /* ADC1 */
    .word   I2C1_EV_IRQHandler             /* I2C1 Event */
    .word   I2C1_ER_IRQHandler             /* I2C1 Error */
    .word   USART1_IRQHandler              /* USART1 */
    .word   SPI1_IRQHandler                /* SPI1 */
    .word   TIM1_BRK_IRQHandler            /* TIM1 Break */
    .word   TIM1_UP_IRQHandler             /* TIM1 Update */
    .word   TIM1_TRG_COM_IRQHandler        /* TIM1 Trigger and Commutation */
    .word   TIM1_CC_IRQHandler             /* TIM1 Capture Compare */
    .word   TIM2_IRQHandler                /* TIM2 */
    .option rvc;
    .section  .text.vector_handler, "ax", @progbits
    .weak   NMI_Handler
    .weak   HardFault_Handler
    .weak   SysTick_Handler
    .weak   SW_Handler
    .weak   WWDG_IRQHandler
    .weak   PVD_IRQHandler
    .weak   FLASH_IRQHandler
    .weak   RCC_IRQHandler
    .weak   EXTI7_0_IRQHandler
    .weak   AWU_IRQHandler
    .weak   DMA1_Channel1_IRQHandler
    .weak   DMA1_Channel2_IRQHandler
    .weak   DMA1_Channel3_IRQHandler
    .weak   DMA1_Channel4_IRQHandler
    .weak   DMA1_Channel5_IRQHandler
    .weak   DMA1_Channel6_IRQHandler
    .weak   DMA1_Channel7_IRQHandler
    .weak   ADC1_IRQHandler
    .weak   I2C1_EV_IRQHandler
    .weak   I2C1_ER_IRQHandler
    .weak   USART1_IRQHandler
    .weak   SPI1_IRQHandler
    .weak   TIM1_BRK_IRQHandler
    .weak   TIM1_UP_IRQHandler
    .weak   TIM1_TRG_COM_IRQHandler
    .weak   TIM1_CC_IRQHandler
    .weak   TIM2_IRQHandler
NMI_Handler:              1: j 1b
HardFault_Handler:        1: j 1b
SysTick_Handler:          1: j 1b
SW_Handler:               1: j 1b
WWDG_IRQHandler:          1: j 1b
PVD_IRQHandler:           1: j 1b
FLASH_IRQHandler:         1: j 1b
RCC_IRQHandler:           1: j 1b
EXTI7_0_IRQHandler:       1: j 1b
AWU_IRQHandler:           1: j 1b
DMA1_Channel1_IRQHandler: 1: j 1b
DMA1_Channel2_IRQHandler: 1: j 1b
DMA1_Channel3_IRQHandler: 1: j 1b
DMA1_Channel4_IRQHandler: 1: j 1b
DMA1_Channel5_IRQHandler: 1: j 1b
DMA1_Channel6_IRQHandler: 1: j 1b
DMA1_Channel7_IRQHandler: 1: j 1b
ADC1_IRQHandler:          1: j 1b
I2C1_EV_IRQHandler:       1: j 1b
I2C1_ER_IRQHandler:       1: j 1b
USART1_IRQHandler:        1: j 1b
SPI1_IRQHandler:          1: j 1b
TIM1_BRK_IRQHandler:      1: j 1b
TIM1_UP_IRQHandler:       1: j 1b
TIM1_TRG_COM_IRQHandler:  1: j 1b
TIM1_CC_IRQHandler:       1: j 1b
TIM2_IRQHandler:          1: j 1b
    .section  .text.handle_reset, "ax", @progbits
    .weak     handle_reset
    .align    1
handle_reset:
.option push
.option norelax
    la gp, __global_pointer$
.option pop
1:
    la sp, _eusrstack
2:
    /* Load data section from flash to RAM */
    la a0, _data_lma
    la a1, _data_vma
    la a2, _edata
    bgeu a1, a2, 2f
1:
    lw t0, (a0)
    sw t0, (a1)
    addi a0, a0, 4
    addi a1, a1, 4
    bltu a1, a2, 1b
2:
    /* clear bss section */
    la a0, _sbss
    la a1, _ebss
    bgeu a0, a1, 2f
1:
    sw zero, (a0)
    addi a0, a0, 4
    bltu a0, a1, 1b
2:
    li t0, 0x80
    csrw mstatus, t0  
    li t0, 0x3
    csrw 0x804, t0  
    la t0, _start
    ori t0, t0, 3
    csrw mtvec, t0  
    jal   SystemInit
    la t0, main
    csrw mepc, t0
    mret 

在系统复位后,程序指针从handle_reset的地方开始执行。这里只需要把下面的“jal SystemInit”一句(倒数第4行处)注释掉就可以了,不让程序指针跳转到SystemInit函数去,而是直接跳转到main函数去执行。