开始研究代码
int main(void)
{
Init_All_Periph();
......
看到这一行,开始跟踪,于是又看到了下面的内容
void Init_All_Periph(void)
{
RCC_Configuration();
......
继续跟踪
void RCC_Configuration(void)
{
SystemInit();
......
这行代码在 system_stm32f10x.c 中找到了。
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for
debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifndef STM32F10X_CL
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#else
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#endif /* STM32F10X_CL */
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 pres
calers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
}
这一长串的又是什么,如何来用呢?看来,偷懒是不成的了,只能回过头去
研究 STM32 的时钟构成了。
相当的复杂。
系统的时钟可以有 3 个来源:内部时钟 HSI,外部时钟 HSE,或者 PLL(锁相
环模块)的输出。它们由 RCC_CFGR 寄存器中的 SW 来选择。
SW(1:0):系统时钟切换
由软件置’1’或清’0’来选择系统时钟源。 在从停止或待机模式中返
回时或直接或间接作为系统时钟的 HSE 出现故障时,由硬件强制选择 HSI 作为系
统时钟(如果时钟安全系统已经启动)
00:HSI 作为系统时钟;
01:HSE 作为系统时钟;
10:PLL 输出作为系统时钟;
11:不可用。
PLL 的输出直接送到 USB 模块,经过适当的分频后得到 48M 的频率供 USB 模
块使用。
系统时钟的一路被直接送到 I2S 模块;另一路经过 AHB 分频后送出,送往各
个系统,其中直接送往 SDI,FMSC,AHB 总线;8 分频后作为系统定时器时钟;
经过 APB1 分频分别控制 PLK1、定时器 TIM2~TIM7;经过 APB2 分频分别控制 PLK
2、定时器 TIM1~TIM8、再经分频控制 ADC;
由此可知,STM32F10x 芯片的时钟比之于 51、AVR、PIC 等 8 位机要复杂复多,
因此,我们立足于对着芯片手册来解读程序,力求知道这些程序代码如何使用,
为何这么样使用,如果自己要改,可以修改哪些部分,以便自己使用时可以得心
应手。
单步执行,看一看哪些代码被执行了。
/* Reset the RCC clock configuration to the default reset state(for
debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
这是 RCC_CR 寄存器,由图可见,HSION 是其 bit 0 位。
HSION:内部高速时钟使能
由软件置’1’或清零。
当从待机和停止模式返回或用作系统时钟的外部 4-25MHz 时钟发生故障时,
该位由硬件置’1’来启动内部 8MHz 的 RC 振荡器。当内部 8MHz 时钟被直接或间
接地用作或被选择将要作为系统时钟时,该位不能被清零。
0:内部 8MHz 时钟关闭;
1:内部 8MHz 时钟开启。
/
//
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
这是 RCC_CFGR 寄存器
该行程序清零了 MC0[2:0]这三位,和 ADCPRE[1:0],ppre2[2:0],PPRE1[2:0],
HPRE[3:0],SWS[1:0]和 SW[1:0]这 16 位。
/*
MCO: 微控制器时钟输出,由软件置’1’或清零。
0xx:没有时钟输出;
100:系统时钟(SYSCLK)输出;
101:内部 8MHz 的 RC 振荡器时钟输出;
110:外部 4-25MHz 振荡器时钟输出;
111:PLL 时钟 2 分频后输出。
*/
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
清零了 PLLON,HSEBYP,HSERDY 这 3 位。
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
清零了 HSEBYP 位 ///???为什么不一次写??
HSEBYP:外部高速时钟旁路,在调试模式下由软件置’1’或清零来旁路外
部晶体振荡器。只有在外部 4-25MHz 振荡器关闭的情况下,才能写入该位。
0:外部 4-25MHz 振荡器没有旁路;
1:外部 4-25MHz 外部晶体振荡器被旁路。
所以要先清 HSEON 位,再清该位。
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
清零了:USBPRE,PLLMUL,PLLXTPR,PLLSRC 共 7 位
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
这个暂不解读
SetSysClock();
跟踪进入该函数,可见一连串的条件编译:
单步运行,执行的是:
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
为何执行该行呢,找到 SYSCLK_PREQ_**的相关定义,如下图所示。
这样就得到了我们所要的一个结论: 如果要更改系统工作频率,只需要在这里
更改就可以了。
可以继续跟踪进入这个函数来观察如何将工作频率设定为 72MHz 的。
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration --------------------
-------*/
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
//开启 HSE
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOu
t));
//等待 HSE 确实可用,这有个标志,即 RCC_CR 寄存器中的 HSERDY 位(bit 17),
这个等待不会无限长,有个超时策略,即每循环一次计数器加 1,如果计数的次
数超过 HSEStartUp_TimeOut,就退出循环,而这个 HSEStartUp_TimeOut 在 stm
32f10x.h 中定义,
#define HSEStartUp_TimeOut ((uint16_t)0x0500) /*!< Time out for HSE
start up */
/
//
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
///再次判断 HSERDY 标志位,并据此给 HSEStatus 变量赋值。
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
//找到定义: #define RCC_CFGR_HPRE_DIV1 ((uint32
_t)0x00000000) /*!< SYSCLK not divided */
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
//找到定义:#define RCC_CFGR_PPRE2_DIV1 ((uint32_t)
0x00000000) /*!< HCLK not divided */
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
//找到定义:#define RCC_CFGR_PPRE1_DIV2 ((uint32_t)0
x00000400) /*!< HCLK divided by 2 */
#ifdef STM32F10X_CL
……
#else
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PL
LXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
//以上是设定 PLL 的倍频系数为 9,也就是说,这个 72M 是在外部晶振为 8M 时
得到的。
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong cloc
k
configuration. User can add here some code to deal with this
error */
/* Go to infinite loop */
while (1)
{
}
}
}
至此,我们可以归纳几条:
(1) 时钟源有 3 个
(2) 开机时默认是 HSI 起作用,可以配置为所要求的任意一个时钟
(3) 配置时必须按一定的顺序来打开或都关闭一些位,并且各时钟起作用有
一定的时间,因此要利用芯片内部的标志位来判断是否可以执行下一步。
(4) 如果外部时钟、PLL 输出失效,系统可以自动回复到 HSI(开启时钟安
全系统)
(5) HSI 的频率准确度可以达到+/- 1%,如果有必要时,还可以用程序来调
整这个频率,可调的范围大致在 200KHz 左右。
最后让我们来感受一下劳动的果实吧--试着改改频率看有何反应。
为查看更改后的效果,先记录更改前的数据。将调试切换到仿真,在第一条:
Delay(0xAFFFF);
指令执行前后,分别记录下 Status 和 Sec
Status:2507 3606995
Sec:0.00022749 0.05028982
将振荡频率更改为 36MHz,即
...
#define SYSCLK_FREQ_36MHz 36000000 //去掉该行的注释
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
/*#define SYSCLK_FREQ_72MHz 72000000*/ //将该行加上注释
再次运行,结果如下:
Status:2506 3606994
Sec:0.00008478 0.10036276
基本上是延时时间长了一倍。改成硬件仿真,将代码写入板子,可以看到 LE
D 闪烁的频率明显变慢了。
int main(void)
{
Init_All_Periph();
......
看到这一行,开始跟踪,于是又看到了下面的内容
void Init_All_Periph(void)
{
RCC_Configuration();
......
继续跟踪
void RCC_Configuration(void)
{
SystemInit();
......
这行代码在 system_stm32f10x.c 中找到了。
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for
debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifndef STM32F10X_CL
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#else
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#endif /* STM32F10X_CL */
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 pres
calers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
}
这一长串的又是什么,如何来用呢?看来,偷懒是不成的了,只能回过头去
研究 STM32 的时钟构成了。
相当的复杂。
系统的时钟可以有 3 个来源:内部时钟 HSI,外部时钟 HSE,或者 PLL(锁相
环模块)的输出。它们由 RCC_CFGR 寄存器中的 SW 来选择。
SW(1:0):系统时钟切换
由软件置’1’或清’0’来选择系统时钟源。 在从停止或待机模式中返
回时或直接或间接作为系统时钟的 HSE 出现故障时,由硬件强制选择 HSI 作为系
统时钟(如果时钟安全系统已经启动)
00:HSI 作为系统时钟;
01:HSE 作为系统时钟;
10:PLL 输出作为系统时钟;
11:不可用。
PLL 的输出直接送到 USB 模块,经过适当的分频后得到 48M 的频率供 USB 模
块使用。
系统时钟的一路被直接送到 I2S 模块;另一路经过 AHB 分频后送出,送往各
个系统,其中直接送往 SDI,FMSC,AHB 总线;8 分频后作为系统定时器时钟;
经过 APB1 分频分别控制 PLK1、定时器 TIM2~TIM7;经过 APB2 分频分别控制 PLK
2、定时器 TIM1~TIM8、再经分频控制 ADC;
由此可知,STM32F10x 芯片的时钟比之于 51、AVR、PIC 等 8 位机要复杂复多,
因此,我们立足于对着芯片手册来解读程序,力求知道这些程序代码如何使用,
为何这么样使用,如果自己要改,可以修改哪些部分,以便自己使用时可以得心
应手。
单步执行,看一看哪些代码被执行了。
/* Reset the RCC clock configuration to the default reset state(for
debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
这是 RCC_CR 寄存器,由图可见,HSION 是其 bit 0 位。
HSION:内部高速时钟使能
由软件置’1’或清零。
当从待机和停止模式返回或用作系统时钟的外部 4-25MHz 时钟发生故障时,
该位由硬件置’1’来启动内部 8MHz 的 RC 振荡器。当内部 8MHz 时钟被直接或间
接地用作或被选择将要作为系统时钟时,该位不能被清零。
0:内部 8MHz 时钟关闭;
1:内部 8MHz 时钟开启。
/
//
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
这是 RCC_CFGR 寄存器
该行程序清零了 MC0[2:0]这三位,和 ADCPRE[1:0],ppre2[2:0],PPRE1[2:0],
HPRE[3:0],SWS[1:0]和 SW[1:0]这 16 位。
/*
MCO: 微控制器时钟输出,由软件置’1’或清零。
0xx:没有时钟输出;
100:系统时钟(SYSCLK)输出;
101:内部 8MHz 的 RC 振荡器时钟输出;
110:外部 4-25MHz 振荡器时钟输出;
111:PLL 时钟 2 分频后输出。
*/
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
清零了 PLLON,HSEBYP,HSERDY 这 3 位。
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
清零了 HSEBYP 位 ///???为什么不一次写??
HSEBYP:外部高速时钟旁路,在调试模式下由软件置’1’或清零来旁路外
部晶体振荡器。只有在外部 4-25MHz 振荡器关闭的情况下,才能写入该位。
0:外部 4-25MHz 振荡器没有旁路;
1:外部 4-25MHz 外部晶体振荡器被旁路。
所以要先清 HSEON 位,再清该位。
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
清零了:USBPRE,PLLMUL,PLLXTPR,PLLSRC 共 7 位
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
这个暂不解读
SetSysClock();
跟踪进入该函数,可见一连串的条件编译:
单步运行,执行的是:
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
为何执行该行呢,找到 SYSCLK_PREQ_**的相关定义,如下图所示。
这样就得到了我们所要的一个结论: 如果要更改系统工作频率,只需要在这里
更改就可以了。
可以继续跟踪进入这个函数来观察如何将工作频率设定为 72MHz 的。
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration --------------------
-------*/
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
//开启 HSE
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOu
t));
//等待 HSE 确实可用,这有个标志,即 RCC_CR 寄存器中的 HSERDY 位(bit 17),
这个等待不会无限长,有个超时策略,即每循环一次计数器加 1,如果计数的次
数超过 HSEStartUp_TimeOut,就退出循环,而这个 HSEStartUp_TimeOut 在 stm
32f10x.h 中定义,
#define HSEStartUp_TimeOut ((uint16_t)0x0500) /*!< Time out for HSE
start up */
/
//
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
///再次判断 HSERDY 标志位,并据此给 HSEStatus 变量赋值。
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
//找到定义: #define RCC_CFGR_HPRE_DIV1 ((uint32
_t)0x00000000) /*!< SYSCLK not divided */
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
//找到定义:#define RCC_CFGR_PPRE2_DIV1 ((uint32_t)
0x00000000) /*!< HCLK not divided */
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
//找到定义:#define RCC_CFGR_PPRE1_DIV2 ((uint32_t)0
x00000400) /*!< HCLK divided by 2 */
#ifdef STM32F10X_CL
……
#else
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PL
LXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
//以上是设定 PLL 的倍频系数为 9,也就是说,这个 72M 是在外部晶振为 8M 时
得到的。
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong cloc
k
configuration. User can add here some code to deal with this
error */
/* Go to infinite loop */
while (1)
{
}
}
}
至此,我们可以归纳几条:
(1) 时钟源有 3 个
(2) 开机时默认是 HSI 起作用,可以配置为所要求的任意一个时钟
(3) 配置时必须按一定的顺序来打开或都关闭一些位,并且各时钟起作用有
一定的时间,因此要利用芯片内部的标志位来判断是否可以执行下一步。
(4) 如果外部时钟、PLL 输出失效,系统可以自动回复到 HSI(开启时钟安
全系统)
(5) HSI 的频率准确度可以达到+/- 1%,如果有必要时,还可以用程序来调
整这个频率,可调的范围大致在 200KHz 左右。
最后让我们来感受一下劳动的果实吧--试着改改频率看有何反应。
为查看更改后的效果,先记录更改前的数据。将调试切换到仿真,在第一条:
Delay(0xAFFFF);
指令执行前后,分别记录下 Status 和 Sec
Status:2507 3606995
Sec:0.00022749 0.05028982
将振荡频率更改为 36MHz,即
...
#define SYSCLK_FREQ_36MHz 36000000 //去掉该行的注释
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
/*#define SYSCLK_FREQ_72MHz 72000000*/ //将该行加上注释
再次运行,结果如下:
Status:2506 3606994
Sec:0.00008478 0.10036276
基本上是延时时间长了一倍。改成硬件仿真,将代码写入板子,可以看到 LE
D 闪烁的频率明显变慢了。