2019-11-05-STM32时钟设置

了解一下SystemInit()函数

这个函数是我们进行系统初始化时会使用到的一个函数。它出现在main函数的第一行。可见其重要性。

库函数的工程初始化过程中调用的函数及其顺序为:startup_stm32f10x_hd.s->SystemInit()->SetSysClock()->SetSysClockTo72()

那么SystemInit()函数进行了什么操作呢?——答案是初始化内部Flash以及系统时钟。在我们默认使用该函数时系统时钟被定为72MHz。系统时钟是整个单片机时钟的核心,各种外设的时钟都依托这个系统时钟,我们可以通过一张图看到系统时钟的来源和去向:

image.png

系统时钟的来源为外部晶振,一个是频率为8MHz的椭圆形晶振,还有一个频率为12MHz的小圆柱形晶振,还有内部的两个震荡源。我们可以看到决定系统时钟的几个时钟都连接到PLL,左半边是各个时钟源经过各种分频之后连接到PLL,右边是各种外设时钟使用不同的分频比例。其中AHB有1-512等多种分频方式(2的倍数),APB1频率是系统时钟的一半,APB2频率是系统时钟。

平时使用时只需要了解这个时钟的频率,我们通常情况下会直接调用该函数,将系统时钟频率设置成72MHz,APB1的频率为36MHz,需要修改工作频率的时候,可以通过使用HSE和HSI来配置系统时钟,相关代码可见野火的书P143。

32的位带操作

32有一个很特别的东西——位带操作。我们知道在51的使用中,关于某个特定端口的输出输入我们有时会这么写:用P0.7专门指定一个引脚,输出高就setb,输出低就clr。这种使用方法在32里就不通用了,我们在IO操作那一节就可以看到,我们想要指定一个引脚,必须设置好一个完整的GPIO的结构体指明引脚号啊输入还是输出啊之类的相关信息,非常繁琐。想要更方便操作,可以使用32的位带操作。

位带操作可以简单理解成扩展位或者膨胀

我们知道,单片机的一切外设都由在它片内的某个特殊寄存器设置及控制,想要控制某个外设只需要将相关的寄存器配置好就可以了。32提供了大小为1M的外设位带区,包含了片上外设的全部寄存器,全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器的效果(膨胀后映射等效直接连接,大大降低代码的繁琐程度)。

通过这一原理我们可以将“位带地址+位序号”转换成别名地址定义成一个宏。

// 把一个地址转换成一个指针 #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))

// 把位带别名区地址转换成指针 #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))

GPIO位带操作,实现两个寄存器的地址映射后,

1 // GPIO ODR 和 IDR 寄存器地址映射 2 #define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C 3 #define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C 4 #define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C 5 #define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C 6 #define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C 7 #define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C 8 #define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C 9 10 #define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808 11 #define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08 12 #define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008 13 #define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408 14 #define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808 15 #define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08 16 #define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08

就可以通过位操作的方法来控制GPIO的输入和输出了,宏参数n表示某个IO端口。

在通过宏定义置位函数,可以将输入输出变成更加简单易懂的写法。

1 // 单独操作 GPIO 的某一个IO 口,n(0,1,2...16),n 表示具体是哪一个IO 口 2 #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出 3 #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入 4 5 #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出 6 #define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入 7 8 #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出 9 #define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入 10 11 #define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出 12 #define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入 13 14 #define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出 15 #define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入 16 17 #define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出 18 #define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入 19 20 #define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出 21 #define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入

位带操作是一个很有意义的操作,它大大降低了代码编写的难度,使得原先冗长的代码块可以用更加简单易懂的方式替代,使用方法也很简单,在写好头文件后添加即可,头文件有现成的代码集我们这里可以直接用,之后的代码可能都会用位带操作来减少工作量。

你可能感兴趣的:(2019-11-05-STM32时钟设置)