普冉(PUYA)单片机开发笔记(3): 使用PY32F003的外部时钟

本文详细介绍了PY32F003的时钟系统结构,包括HSI和HSE时钟树,以及如何配置系统时钟以使用不同频率。重点强调了外部时钟对MCU性能的影响,指出PY32F003缺乏PLL且HSI频率偏差较大,推荐使用HSE提高精度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

在前两篇的开发笔记中,在 PY32F003F8P 的开发板上使用厂家自带的例程完成了中断式非阻塞 UART 通信的实验,顺便完成了对板载 LED 的开关控制,熟悉了如何重构一个 Keil uVision 项目,对 PY32F003 有了一个初步的认识。仅仅完成单一功能是不够的,例如在任何一个实用项目中,总会用到 ADC,TIM,WDG,GPIO,PWM,RTC 等功能组合在一起,实现应用需求。而这些功能无一不利用到了 MCU 的时钟系统。

除非运行在最小系统,实用开发——尤其是稍微复杂一点的实用开发——都会配备外部时钟,RTC时钟是否配备则要看项目的具体需求。外部高速时钟总是需要加以关注的,不掌握好这个“心脏”,MCU 也许会“贫血”的呢。

PY32F003的时钟树

如上图,PY32F003 的时钟树还是比较简单的。

HSI 时钟树

  • 如果硬件上没有连接外部晶振的话,PY32F003使用内部的24MHz HSI RC 和 32.768KHz 的 LSI。
  • HSI 可以通过分频复用器输出到 MCO。
  • HSI 经过分频 HSIDIV 得到 HSISYS,复用后获得 SYSCLK,作为驱动内核与总线的时钟源。
  • 有 AHB 和 APB 两条总线,AHB 经过分频后,得到 APB,驱动 AHB 总线、内核和内核定时器。
  • APB 经过分频后驱动外设,包括GPIO,定时器,比较器和 ADC等。
  • 即使硬件上接了外部高速晶振(HSE),也依旧可以只使用 HSI。

HSE时钟树

  • 通过 HAL_RCC_OscConfig 函数选择时钟源。外部时钟源的最高频率是 32MHz。
  • HSE 也有 MCO 输出,实际上不怎么需要:如果想要测量 HSE 的话,用示波器直接测量晶振管脚好了。
  • HSE 经复用后,直接得到 SYSCLK。后续的时钟分配和 HSI 相同。

需要注意的是 PY32F003 没有 PLL,只有 HSIDIV。所以可以推测 PY32F003 的最高时钟频率就是 32MHz(看来文档上的说明是没错的,前文中我说到的“笔误”是我的理解错误)。为了是 MCU 工作在 32MHz,HSIDIV 要设置为 DIV1。

PY32F003 没有 LSE

PY32F003总是使用LSI,硬件上没有看到 LSE 的管脚。

使用 24MHz 外部时钟的实验

HSI 和 HSE 的切换

编写如下的 main.c 代码,测试一下 PY32F003开发板的 HSI 和 HSE。

/********************************************************************************************************
**函数信息 :int main(void)
**功能描述 :main函数
**输入参数 :
**输出参数 :
**    备注 :
********************************************************************************************************/
int main(void)
{
    HAL_Init();                                                         // 初始化systick
    HAL_SuspendTick();
    SystemClock_Config();                                               // 配置系统时钟为默认HSI 8MHz,之后切换为LSI时钟
                                                                        // 配置PA01引脚为MCO功能,输出系统时钟
    HAL_RCC_MCOConfig(RCC_MCO2, RCC_MCO1SOURCE_SYSCLK, RCC_MCODIV_1);   // 配置PA01引脚为MCO功能,输出系统时钟
    while (BSP_PB_GetState(BUTTON_KEY) == 1) {}                         // 等待用户按下开发板的按钮
    SetSysClock(RCC_SYSCLKSOURCE_HSE);                                  // 配置系统时钟为HSE
    while(1) {}                                                         // 主循环
}

其中 HAL_RCC_MCOConfig 函数指令 MCU 从 PA0 管脚输出时钟信号。

SystemClock_Config 函数和 SetSysClock 函数的代码如下。

/********************************************************************************************************
**函数信息 :void SystemClock_Config(void)
**功能描述 :系统时钟配置
**输入参数 :
**输出参数 :
**    备注 :
********************************************************************************************************/
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE | 
                                       RCC_OSCILLATORTYPE_HSI | 
                                       RCC_OSCILLATORTYPE_LSI;              //配置时钟源HSE/HSI/LSE/LSI
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;                                //开启HSI
    RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;                                //不分频
      
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_8MHz;        //配置HSI输出时钟为8MHz
    //RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_4MHz;      //配置HSI输出时钟为4MHz
    //RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_16MHz;     //配置HSI输出时钟为16MHz
    //RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_22p12MHz;  //配置HSI输出时钟为22.12MHz
    //RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_24MHz;     //配置HSI输出时钟为24MHz
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                //开启HSE
    RCC_OscInitStruct.HSEFreq = RCC_HSE_16_32MHz;                           //HSE工作频率范围16M~32M
    RCC_OscInitStruct.LSIState = RCC_LSI_ON;                                //开启LSI

    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)                    //初始化RCC振荡器
    {
        Error_Handler();
    }

    //初始化CPU,AHB,APB总线时钟
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | 
                                  RCC_CLOCKTYPE_SYSCLK | 
                                  RCC_CLOCKTYPE_PCLK1;                      //RCC系统时钟类型
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_LSI;                  //SYSCLK的源选择为LSI
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                      //APH时钟不分频
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                       //APB时钟不分频

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) //初始化RCC系统时钟(FLASH_LATENCY_0=24M以下;FLASH_LATENCY_1=48M)
    {
        Error_Handler();
    }
}
/********************************************************************************************************
**函数信息 :void SetSysClock(uint32_t SYSCLKSource)
**功能描述 :设置系统时钟
**输入参数 :uint32_t SYSCLKSource
**           (可选RCC_SYSCLKSOURCE_LSI/RCC_SYSCLKSOURCE_LSE
**            /RCC_SYSCLKSOURCE_HSE/RCC_SYSCLKSOURCE_HSI/
**           RCC_SYSCLKSOURCE_PLLCLK)
**输出参数 :
**    备注 :
********************************************************************************************************/
void SetSysClock(uint32_t SYSCLKSource)
{
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | 
                                  RCC_CLOCKTYPE_HCLK | 
                                  RCC_CLOCKTYPE_PCLK1;          //RCC系统时钟类型
    RCC_ClkInitStruct.SYSCLKSource = SYSCLKSource;              //系统时钟源选择
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;          //APH时钟不分频
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;           //APB时钟2分频

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, 
                            FLASH_LATENCY_1) != HAL_OK)         //初始化RCC系统时钟(FLASH_LATENCY_0=24M以下;FLASH_LATENCY_1=48M)
    {
        Error_Handler();
    }
}

编译,烧录。用示波器测量 GND 和 PA0 管脚。刚启动时可以从 PA0 测得 8MHz 的时钟波形,然后按一下板子上的按钮,PA0 的输出是 24MHz 的 HSE 波形

从示波器上观察波形,可以看到芯片的 8MHz HSI 不是很稳定,测量频率在 8.007~8.012MHz 范围内波动,计算得到其频率误差在 0.005/8,约为625PPM,这个误差相当大了。外部晶振很稳定的,示波器的读数稳定在 24.0006MHz 不动。所以,实用系统中,是需要给 PY32F003 配备外部晶振的。

使用 HSE 工作在 24MHz

编写 SystemClock_Config 函数如下。

/********************************************************************************************************
**函数信息 :void SystemClock_Config(void)
**功能描述 :系统时钟配置
**输入参数 :
**输出参数 :
**    备注 :
********************************************************************************************************/
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  //配置时钟源HSE/HSI/LSE/LSI
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE | RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_LSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;                                                    //开启HSI
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;                                                    //不分频

  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_8MHz;                            //配置HSI输出时钟为8MHz
  //RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_4MHz;                          //配置HSI输出时钟为4MHz
  //RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_16MHz;                         //配置HSI输出时钟为16MHz
  //RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_22p12MHz;                      //配置HSI输出时钟为22.12MHz
  //RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_24MHz;                         //配置HSI输出时钟为24MHz
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                                    //开启HSE
  RCC_OscInitStruct.HSEFreq = RCC_HSE_16_32MHz;                                               //HSE工作频率范围16M~32M
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;                                                    //开启LSI

  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)                                        //初始化RCC振荡器
  {
    Error_Handler();
  }

  //初始化CPU,AHB,APB总线时钟
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1; //RCC系统时钟类型
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE;                                      //SYSCLK的源选择为HSE
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          //APH时钟不分频
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;                                             //APB时钟2分频

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)                      //初始化RCC系统时钟(FLASH_LATENCY_0=24M以下;FLASH_LATENCY_1=48M)
  {
    Error_Handler();
  }
}

在 main.c 中使用 SystemClock_Config。

/*************************************************************************
**函数信息 :void main(void)
**功能描述 :main函数
**输入参数 :
**输出参数 :
**    备注 :
**************************************************************************/
int main(void)
{
    HAL_Init(); // systick初始化

    SystemClock_Config();
    GPIO_Config();  // Intialize GPIO PB5
    USART_Config(); // USART初始化
#if(0)
    MX_DMA_Init();  // Intialize DMA
#endif

    printf("\r\n+---------------------------------------+"
           "\r\n|        PY32F003 MCU is ready.         |"
           "\r\n+---------------------------------------+"
           "\r\n");

    /*通过中断方式发送数据*/
    if (HAL_UART_Transmit_IT(&UartHandle, (uint8_t *)aTxBuffer, 12) != HAL_OK)
    {
        Error_Handler();
    }

    while (1)
    {
        /**
         *  For testing GPIO output
         *  2023-11-24
         *  Hard coder Luoyuan
        */
        HAL_Delay(1000);
        HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
    }
}

SystemClock_Config() 函数要放在 HAL_Init() 之后,其它外设初始化之前。这个实验中的 SystemClock_Config() 函数其实就是前面一个实验中两个函数的组合。

其余代码和我的笔记(1)相同,拷贝过来即可。运行结果如下,一切如设计预期。

总结

  • PY32F003 的时钟没有 PLL,最高工作频率是 32MHz。有网友称其可以工作在 48MHz,还不知道是如何配置的。
  • 在 main.c 中自定义 SystemClock_Config() 可以灵活选择时钟源。
  • PY32F003 的 HSI 的频率偏差高达 625PPM,如果需要较为准确的计时的话,需要为其配备 HSE。

厂家软件包中的例程有一些 BUG,例如对板载 Push Button (PB)的初始化和对 LED3 的初始化中都用到了

GPIO_InitStruct.Pull = GPIO_NOPULL; 

但对于 PB,应该设置成 GPIO_PULLUP 才可以。

另一个 BUG 就是在 py32f0xx_hal_conf.h 中,有些变量没有 define,初次编译时成功的不多。

在逐渐熟悉 PY32F003,根据自己实验慢慢全面掌握这颗 MCU 的功能。谬误之处,欢迎指正。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

硬核老骆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值