时钟树
时钟树很重要,必须梳理清楚,详细参考《STM32中文参考手册》第6章。

系统刚上电时用的是系团内部8M RC时钟。
我使用的开发板外部晶振频率8M。
时钟配置
首先把一些常用的宏抽出来单独放在common.h中,当前主要包含位操作和位带操作,代码如下:
common.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #ifndef COMMON_H #define COMMON_H #include "stm32f10x.h"
#define SETUP_BIT(data, bit) ((data) |= 1UL << (bit)) #define CLR_BIT(data, bit) ((data) &= ~(1UL << (bit))) #define GET_BIT(data, bit) ((data >> (bit)) & 1UL) #define BITS_MASK(startbit, endbit) ((1UL << ((startbit)-(endbit)+1)) - 1) #define SET_BITS(data, startbit, endbit, val) (data)&=~(BITS_MASK(startbit, endbit)<<endbit);data|=((val&BITS_MASK(startbit, endbit))<<endbit); #define GET_BITS(data, startbit, endbit) (((data) >> (endbit)) & BITS_MASK(startbit, endbit))
#define BITBAND(addr, bit) (((addr) & 0xE0000000) + 0x2000000 + ((addr) & 0x1FFFFFFF) * 8 * 4 + (bit) * 4) #define MEM_ADDR(addr) (*((volatile unsigned long *)(addr))) #define BIT_ADDR(addr, bit) MEM_ADDR(BITBAND(addr, bit))
#define GPIOA_ODR_Addr (GPIOA_BASE+12) #define GPIOB_ODR_Addr (GPIOB_BASE+12) #define GPIOC_ODR_Addr (GPIOC_BASE+12) #define GPIOD_ODR_Addr (GPIOD_BASE+12) #define GPIOE_ODR_Addr (GPIOE_BASE+12) #define GPIOF_ODR_Addr (GPIOF_BASE+12) #define GPIOG_ODR_Addr (GPIOG_BASE+12) #define GPIOA_IDR_Addr (GPIOA_BASE+8) #define GPIOB_IDR_Addr (GPIOB_BASE+8) #define GPIOC_IDR_Addr (GPIOC_BASE+8) #define GPIOD_IDR_Addr (GPIOD_BASE+8) #define GPIOE_IDR_Addr (GPIOE_BASE+8) #define GPIOF_IDR_Addr (GPIOF_BASE+8) #define GPIOG_IDR_Addr (GPIOG_BASE+8)
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr, n) #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr, n) #define PEout(n) BIT_ADDR(GPIOE_ODR_Addr, n) #define PEin(n) BIT_ADDR(GPIOE_IDR_Addr, n)
#endif
|
因为STM官方的库文件中已经定义了SET_BIT宏,且含义跟我定义的不同,我只能先改成SETUP_BIT了。
接下来是时钟初始化代码,位于sys.c中,sys.c目前只包含时钟初始化一个函数:
sys.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include "common.h"
void Stm32_Clock_Init(void) { CLR_BIT(RCC->CFGR, 17); SETUP_BIT(RCC->CR, 16); while (!GET_BIT(RCC->CR, 17)) ;
SET_BITS(RCC->CFGR, 10, 8, 0x4); SET_BITS(RCC->CFGR, 13, 11, 0x0); SET_BITS(RCC->CFGR, 15, 14, 0x2); SET_BITS(RCC->CFGR, 7, 4, 0x0);
SET_BITS(RCC->CFGR, 21, 18, 0x7); SETUP_BIT(RCC->CFGR, 16); FLASH->ACR|=0x32; SETUP_BIT(RCC->CR, 24); while (!GET_BIT(RCC->CR, 25)) ;
SET_BITS(RCC->CFGR, 1, 0, 0x2); while (GET_BITS(RCC->CFGR, 3, 2) != 0x2) ;
SETUP_BIT(RCC->BDCR, 0); while (!GET_BIT(RCC->BDCR, 1)) ; SET_BITS(RCC->BDCR, 9, 8, 1); SETUP_BIT(RCC->BDCR, 15);
}
|
RTC和看门狗后续章节会讲到。
SysTick
由于后续的章节里面都会用到延时这一功能,所以我们这里先实现出来,延时基于SysTick。
SysTick其实是一个捆绑在NVIC中的24位定时器,并且具有专用的中断号,一般用来给操作系统提供“时基”,或者说驱动操作系统。由于我们还没有用到操作系统,所以这里只用它的定时器功能,不开启中断。
在《Cortex-M3权威指南》第13.1节中有说:SysTick有两个时钟源:“内核自由运行时钟”FCLK和外部时钟。
在《STM32中文参考手册》的第6.2节有说明:RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。通过对SysTick控制与状态寄存器的设置,可选择上述时钟或Cortex(HCLK)时钟作为SysTick时钟。
延时功能的初始化即SysTick的初始化,我们把延时相关的功能放在delay.c这个单独的文件中。在delay_init()函数中,选择SysTick的时钟源,并定义了一个变量tick_per_us,在后续的延时函数中会使用这个变量将延时时间转换为tick数。由于SysTick的时钟频率为9MHz,所以tick_per_us=9,以下是delay.c代码
delay.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include "common.h"
unsigned char tick_per_us; void delay_init(unsigned char SYSCLK) { tick_per_us = SYSCLK / 8; CLR_BIT(SysTick->CTRL, 2); }
void delay_us(unsigned us) { unsigned tick = us * tick_per_us; unsigned tmp, temp1; while (tick) { tmp = (tick >= 0xFFFFFF ? 0xFFFFFF : tick); SysTick->LOAD = tmp; SysTick->VAL = 0; SysTick->CTRL = 1; do { temp1=SysTick->CTRL; }while((temp1&0x01)&&!(temp1&(1<<16))); SysTick->CTRL = 0; SysTick->VAL = 0; tick -= tmp; } }
void delay_ms(unsigned ms) { delay_us(ms * 1000); }
|
这里分享一个我调试过程中遇到的问题,在我下一章节GPIO中,使用这个延时函数进行LED闪烁测试的时候,发现延时不准确,比实际的时间长,并且忽快忽慢。经过一番排查,终于找到问题所在了,下面放上问题代码和正确代码的对比:
错误代码
1 2
| // 等待减到0,加入使能判断的原因是防止定时器在其他地方被关掉而卡死在这里 while (GET_BIT(SysTick->CTRL, 0) && !GET_BIT(SysTick->CTRL, 16)) ;
|
正确代码
1 2 3 4 5
| // 等待减到0,加入使能判断的原因是防止定时器在其他地方被关掉而卡死在这里 do { temp1=SysTick->CTRL; }while((temp1&0x01)&&!(temp1&(1<<16))); //等待时间到达
|
这两段代码的区别是,错误代码对CTRL寄存器访问了两次,而此寄存器中的COUNTFLAG位是读清的,所以第一次读取的时候此位已经被清掉了。按理来说代码应该永远卡在这个延时函数里才对,因为COUNTFLAG位在被读之前已经清掉了,但是我还是观察到了LED的闪烁,应该是因为读清需要一段时间,如果第二次读取足够快的话还是有一定几率能够读到COUNTFLAG位被置位的。