概述
在前两篇的开发笔记中,在 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 的功能。谬误之处,欢迎指正。