• 合宙AIR105(二): 时钟设置和延迟函数


    目录

    Air105 的时钟

    高频振荡源

    • 芯片支持使用内部振荡源, 或使用外置12MHz晶体
      • 芯片上电复位后 ROM boot 启动过程基于内部12MHz的振荡器
      • 芯片内部集成的12MHz振荡源精度为±2%, 精度一般
      • 使用外置12MHz晶体, 需要软件切换
    • 经过PLL倍频后为系统提供输入
    • 倍频后的PLL时钟频率可通过寄存器进行配置,可选频率为:108MHz, 120MHz, 132MHz, 144MHz, 156MHz, 168MHz, 180MHz, 192MHz, 204MHz

    分频结构

    • PLL_CLK
      • 外部 XTAL12M 或 内部 OSC12M -> 直通, 或PLL产生 108MHz - 204MHz
    • FCLK / CPU_CLK
      • PLL_CLK -> 2bit分频(0, 2分频, 4分频) -> FCLK
      • FCLK就是主程序循环的时钟
    • HCLK
      • FCLK -> 1bit分频(默认=1, 2分频) -> HCLK
      • 当 FCLK 小于 102MHz 时不分频, 否则2分频
    • PCLK
      • HCLK -> 1bit分频(默认=0, 不分频) -> PCLK (外设频率)
      • PCLK 是大部分外设 TIMER, ADC, SPI, WDT, GPIO, I2C, UART 的时钟
    • QSPI
      • FCLK -> 3bit分频(默认=3, 4分频) -> QSPI

    低频振荡源

    • 芯片安全区基于内部32KHz,RTC默认基于内部OSC 32K, 使用外部XTAL 32K需要软件切换
    • 支持内部或外部32KHz输出

    时钟结构

    • (外部或内部 32K RTC OSC) -> SYSTICK
    • 内部 32K OSC -> Security

    时钟设置

    以下代码基于 air105_project 的库函数

    寄存器

    寄存器手册 Air105芯片数据手册_1.1.pdf

    寄存器的基础地址, 定义在 air105.h

    #define AIR105_FLASH_BASE                       (0x01000000UL)                /*!< (FLASH     ) Base Address */
    #define AIR105_SRAM_BASE                        (0x20000000UL)                /*!< (SRAM      ) Base Address */
    #define AIR105_PERIPH_BASE                      (0x40000000UL)                /*!< (Peripheral) Base Address */
    
    #define AIR105_AHB_BASE                         (AIR105_PERIPH_BASE)
    #define AIR105_APB0_BASE                        (AIR105_PERIPH_BASE + 0x10000)
    
    #define SYSCTRL_BASE                            (AIR105_APB0_BASE + 0xF000)
    

    SYSCTRL_BASE

    • 地址 = 外设基础地址 0x40000000UL + APB0 偏移 0x10000 + SYSCTRL 偏移 0xF000
    • 范围 [0x4001_F000, 0x4001_FFFF]

    时钟振荡源

    振荡源选择

    SYSCTRL_SYSCLKSourceSelect(SELECT_EXT12M);
    

    12MHz 时钟来源选择: 0:片外 XTAL, 1:片内 OSC

    void SYSCTRL_SYSCLKSourceSelect(SYSCLK_SOURCE_TypeDef source)
    {
        assert_param(IS_SYSCLK_SOURCE(source));
        
        switch (source)
        {
        case SELECT_EXT12M:
            // FREQ_SEL 是一个32bit的寄存器, 先与补码(清零第12位), 然后写入值(0)
            SYSCTRL->FREQ_SEL = ((SYSCTRL->FREQ_SEL & (~SYSCTRL_FREQ_SEL_CLOCK_SOURCE_Mask)) | SYSCTRL_FREQ_SEL_CLOCK_SOURCE_EXT);
            break;
        
        case SELECT_INC12M:
            // 先与补码(清零第12位), 然后写入值(1)
            SYSCTRL->FREQ_SEL = ((SYSCTRL->FREQ_SEL & (~SYSCTRL_FREQ_SEL_CLOCK_SOURCE_Mask)) | SYSCTRL_FREQ_SEL_CLOCK_SOURCE_INC);
            break;
        }
    }
    

    时钟频率

    设置使用默认的内部时钟HSI(Internal clock)

    void SystemClock_Config_HSI(void)
    {
        // 设置CPU频率, 直接选择, 不需要计算
        SYSCTRL_PLLConfig(SYSCTRL_PLL_204MHz);
        // 分频后产生 FCLK -> 这是主程序的时钟
        SYSCTRL_PLLDivConfig(SYSCTRL_PLL_Div_None);
        // 分频产生 HCLK, 如果 FCLK > 102MHz 则无论如何设置, 都会被二分频
        SYSCTRL_HCLKConfig(SYSCTRL_HCLK_Div2);
        // 分频产生 PCLK -> 这是大部分外设的时钟
        SYSCTRL_PCLKConfig(SYSCTRL_PCLK_Div2);
        QSPI_SetLatency((uint32_t)0);
    }
    

    PLL分频的选项

    #define SYSCTRL_PLL_Div_None                       ((uint32_t)0x00)
    #define SYSCTRL_PLL_Div2                           ((uint32_t)0x01)
    #define SYSCTRL_PLL_Div4                           ((uint32_t)0x10)
    

    设置 SysTick

    void Delay_Init(void)
    {
        SYSCTRL_ClocksTypeDef clocks;
    
        SYSCTRL_GetClocksFreq(&clocks);
        SysTick_Config(clocks.CPU_Frequency / 1000000);   ///< 1us
    }
    

    调用 SysTick_Config 将单个 SysTick 设置为 1 us.

    也可以直接使用SYSCTRL->HCLK_1MS_VAL * 2 / 1000这个变量代表了当前时钟配置下, 1ms需要的HCLK时钟周期, 根据当前FCLK是否大于108MHz 确定是否要乘以2.

    之后就会每隔1us调用 SysTick_Handler(void), 在这里设置 32bit g_current_tick 递增, 可以用于延时控制. 因为32bit数的限制, 1.2个小时后会溢出, 所以这里有一个延迟的极限.

    void SysTick_Handler(void)
    {
        g_current_tick++;
    }
    

    延迟函数

    为避免溢出造成的延迟错误, 需要做一个判断

    uint32_t get_diff_tick(uint32_t cur_tick, uint32_t prior_tick)
    {
        if (cur_tick < prior_tick)
        {
            // 如果当前值比前值还小, 说明发生了溢出, 用当前值加上原值取反(即原值离溢出的距离)
            return (cur_tick + (~prior_tick));
        }
        else
        {
            return (cur_tick - prior_tick);
        }
    }
    

    延迟的函数

    void Delay_us(uint32_t usec)
    {
        uint32_t old_tick;
    
        old_tick = g_current_tick;
        while (get_diff_tick(g_current_tick, old_tick) < usec);
    }
    
    void Delay_ms(uint32_t msec)
    {
        uint32_t old_tick;
    
        old_tick = g_current_tick;
        while (get_diff_tick(g_current_tick, old_tick) < (msec * 1000));
    }
    

    代码

    代码地址: https://gitee.com/iosetting/air105_project

    可以使用Keil5 MDK 直接打开 Demos 目录下的示例项目, 与Air105开发板接线参考前一篇合宙AIR105(一): Keil MDK开发环境, DAP-Link 烧录和调试

  • 相关阅读:
    深入理解C++ 11新特性:1)
    Effective Java 第三版:1)
    Java 8 实战:2)
    MyBatis Plus
    Java 8 实战:1)
    十二要素应用宣言
    Dubbo 2):源码级
    [SCOI2009]windy数 数位dp
    [ZJOI2006]物流运输 最短路 动态规划
    [ZJOI2008]骑士
  • 原文地址:https://www.cnblogs.com/milton/p/16387525.html
Copyright © 2020-2023  润新知