老实说,用过很多的芯片,但是都没有太仔细的研究过芯片的细节,只要做的工作还是停留在调用函数的层面,但是要想更深入的学习一款芯片不了解细节是不行的。于是,我就拿阿波罗来班门弄斧了。粗略看了阿波罗的时钟体系,做一些笔记,记录下来(如果说的有错误,希望大家积极指正,我将不胜感激):从时钟源的角度,分为两类外部时钟(E)和内部时钟(I)。
从时钟速率的角度,分为两类高速时钟(HS)和低速时钟(LS)。
而把它们组合起来就有四种时钟:HSE、HIS、LSE、LSI。至于为什么会有这么复杂的时钟配置,主要是考虑到系统的性能和功耗两个方面的因素吧。单一时钟的话可能会导致性能过剩并且功耗过高。多个时钟的话可以平衡功耗和性能之间的平衡。 特此说明一下,系统复位后,默认初始化的是HIS时钟提供sysclock。也就是16MHZ。为了提示系统性能,我们需要使能外部时钟晶振(板载25MHz)。使能后也明显可以看出来芯片的温度升高了(使用内部的温度传感器测试,在后续有图片为证)。 这四类时钟在芯片内部通过配置,完成对各个外设的驱动。到了芯片内部,对应到那么多的外设,时钟的分类就更多了,但是主要考虑到桥的存在,分为五类:AHB3、AHB2、AHB1、APB2、APB1。芯片内的所有外设都分别挂载在这五个总线上,至于哪个外设挂歪在哪个总线上,我们就需要查看芯片的RM0410 Reference manual(Page74)了。 今天为了测试时钟的配置,我就用通用定时器 tiM2做下示范操作: 首先查看TIM2挂载在哪条总线:
可知挂载在APB1总线上。
接着就可以查看TIM的时钟回路:
从上述截图中可以看出,最后TIM时钟的周期是54MHZ。具体配置关键代码如下:
- void SystemClock_Config(void)
- {
- RCC_ClkInitTypeDef RCC_ClkInitStruct;
- RCC_OscInitTypeDef RCC_OscInitStruct;
- /* Enable HSE Oscillator and activate PLL with HSE as source */
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
- RCC_OscInitStruct.HSEState = RCC_HSE_ON;
- RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
- RCC_OscInitStruct.PLL.PLLM = 25;
- RCC_OscInitStruct.PLL.PLLN = 432;
- RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
- RCC_OscInitStruct.PLL.PLLQ = 9;
- RCC_OscInitStruct.PLL.PLLR = 7;
- if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
- {
- while(1) {};
- }
- /* Activate the OverDrive to reach the 216 Mhz Frequency */
- if(HAL_PWREx_EnableOverDrive() != HAL_OK)
- {
- while(1) {};
- }
- /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2
- clocks dividers */
- RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
- if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK)
- {
- while(1) {};
- }
- }
复制代码
考虑到ST的代码很清晰明了,对照着我作的标记大家应该可以看明白吧。
接着就进行配置TIM2的时钟分频,以及计数周期。由于TIM2的时钟可以由多个来源,既然我们用的是APB1时钟,那么就输入内部时钟也就是:
贴出对应的关键代码
- void TIM_init(TIM_HandleTypeDef *ITIMX)
- {
- ITIMX->Instance=TIM3;
- ITIMX->Init.Period=999;
- ITIMX->Init.Prescaler=53999;
- ITIMX->Init.CounterMode=TIM_COUNTERMODE_UP;
- HAL_TIM_Base_Init(ITIMX);
- HAL_TIM_Base_Start_IT(ITIMX);
- }
复制代码
539999对应上图中的PSC Prescaler,分频后时钟频率为54MHz/(53999+1)=1KHz。接下来就要注意一点了,在TIM2采样时钟之前,有
如果APB的分频比是1,那么频率不变,否则频率 乘以2,这样一来可以确定TIM2的采样频率是2kHz了。
接下来配置计数周期为999。也就是对应0.5s。在中断函数中又如下代码:
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- led_flag++;
- if(led_flag%2)
- LED_on();
- else
- LED_off();
- }
复制代码
这样一来,就可以实现DS0和DS1交替1s闪烁,通过计时测试,成功了。
由此,使用TIM2演示时钟配置的任务已经完成了。
声明一下,此次阿波罗的试用,我会把我各个阶段的完整的代码托管到github上,如需查看欢迎fork。 https://github.com/iysheng/apollo
|