一、储存器映射和寄存器映射
STM32的寻址范围:
(1)32位的单片机可以有32根地址线(每根地址线有两种状态:导通或不导通)
(2)单片机内存地址访问的存储单元是按字节编址的(而不是bit)
STM32寻址大小:(字节)
STM32寻址范围:0x0000 0000 ~ 0xFFFF FFFF
1、存储器映射
存储器指可以存储数据的设备,本身没有地址信息,对存储器分配地址的过程称为存储器映射。
存储器功能划分(F1为例)
ST将4G()地址空间分成了8块
Block0(FLASH)功能划分
Block1(SRAM)功能划分
Block2(外设)功能划分
2、寄存器映射
寄存器是单片机内部一种特殊的内存,可以实现对单片机各个功能的控制(寄存器就是单片机内部的控制机构)。寄存器是特殊的存储器,给寄存器地址命名的过程,就叫寄存器映射。
STM32寄存器分类
寄存器描述:
寄存器地址计算:
将寄存器地址分为三个部分:
(1)总线基地址(BUS_BASE_ADDR)
(2)外设基于总线基地址的偏移量(PERIPH_OFFSET)
(3)寄存器相对外设基地址的偏移量(REG_OFFSET)
寄存器地址 = BUS_BASE_ADDR + PERIPH_OFFSET + REG_OFFSET
总线基地址
APB1总线的基地址,也叫外设基地址(PERIPH_BASE);此表的偏移量:是相对外设基地址(PERIPH_BASE)来说的
GPIO外设基地址及偏移量
此表的偏移量:是相对APB2外设基地址(APB2PERIPH_BASE)来说的
此表的偏移量:是相对GPIOA外设基地址(GPIOA_BASE)来说的。
例如:GPIOA_ODR寄存器地址计算过程:
二、STM32时钟系统
1.1认识时钟树
简单来说,时钟是具有周期性的脉冲信号,最常用的是占空比50%的方波(时钟就是*,/(分频))
STM32F103时钟树简图
STM32F103时钟树图
从图中可以看到,STM32共有五个时钟源,分别是HSI、HSE、LSI、LSE和PLL ,下面分别对它们进行讲解:
①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。独立看门狗的时钟源只能是 LSI ,同时LSI 还可以作为 RTC 的时钟源。
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。是主要的RTC时钟源。
⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
上面我们简要概括了STM32 的时钟源,那么这 5 个时钟源是怎么给各个外设以及系统提供时钟的呢?这里我们将一一讲解。这里我们使用官方手册提供的框图(图一)进行讲解,图中我们用A~E 标示我们要讲解的地方。
A 、 MCO 是 STM32 的一个时钟输出 IO(PA8) PA8),它可以选择一个时钟信号输出 可以
选择为 PLL 输出的 2 分频、 HSI 、 HSE 、或者 系统 时钟 。这个时钟可以用来给外
部其他系统提供时钟源。
B 、 RTC 时钟源,从图上可以看出, RTC 的时钟源可以选择 LSI LSE ,以及
HSE 的 128 分频。
C 、 从图中可以看出 C 处 USB 的时钟是来自 PLL 时钟源。 STM32 中有一个全速 功能
的 USB 模块 ,其串行 接口 引擎需要 一个频率为 48MHz 的时钟源。该时钟源只能
从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB
模块时, PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz 。
D 、 STM32 的系统时钟 SYSCLK ,它 是供 STM32 中绝大部分部件 工作 的时
钟源 。 系统时钟可选择为 PLL 输出、 HSI 或者 HSE 。系统时钟最大频率 为 72MHz
当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。
E 、 其他所有外设。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK 。 SYSCLK 通过 AHB 分频器分频后送给各模块使用 。这些模块包括:
① AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。
② 通过 8 分频后送给 Cortex 的系统 定时器 时钟 ,也就是 systick 了 。
③ 直接送给 Cortex 的空闲运行时钟 FCLK 。
④ 送给 APB1 分频器。 APB1 分频器输出一路供 APB1 外设使用 (PCLK1 ,最大频率 36MHz)36MHz),另一路送给定时器 (Timer)2 、 3 、 4 倍频器使用。
⑤送给 APB2 分频器。 APB2 分频器分频输出一路供 APB2 外设使用 (PCLK2最大频率 72MHz)72MHz),另一 路送给定时器 (Timer)1 倍频器使用。
APB1和APB2的区别: APB1上面连的是低速外设,包括电脑接口、备份接口、CAN、USB、I2C1、I2C2 、 UART2 、 UART3 等等, APB2 上面连接的是高速外设包括 UART1 、 SPI1 、 Timer1 、 ADC1 、 ADC2 、所有普通 IO 口 (PA~ 、第二功能 IO 口 等。 APB2 下面所挂的外设的时钟要比 APB1 的高 。
在以上的时钟输出中,有很多 是带使能控制的,例如 AHB 总线 时钟、内核时钟、各种 APB1外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
STM32CubeMX时钟树(F103)
时钟源名称 | 频率 | 材料 | 用途 |
高速外部振荡器(HSE) | 4~16MHz | 晶体/陶瓷 | SYSCLK/RTC |
低速外部振荡器(LSE) | 32.768KHz | 晶体/陶瓷 | RTC |
高速内部振荡器(HSI) | 8MHz | RC | SYSCLK |
低速内部振荡器(LSI) | 40KHz | RC | RTC/IWDG |
时钟源名称 | 频率 | 材料 | 用途 |
高速外部振荡器(HSE) | 4~16MHz | 晶体/陶瓷 | SYSCLK/RTC |
低速外部振荡器(LSE) | 32.768KHz | 晶体/陶瓷 | RTC |
高速内部振荡器(HSI) | 8MHz | RC | SYSCLK |
低速内部振荡器(LSI) | 40KHz | RC | RTC/IWDG |
STM32F4时钟树简图
STM32 F429时钟树图
STM32CubeMX时钟树(F429)
STM32F7时钟树简图
2.1 系统时钟配置步骤
1,配置HSE_VALUE :告诉HAL库外部晶振频率,stm32xxxx_hal_conf.h
2,调用SystemInit()函数(可选):在启动文件中调用, 在system_stm32xxxx.c定义
3,选择时钟源,配置PLL:通过HAL_RCC_OscConfig()函数设置
4,选择系统时钟源,配置总线分频器:通过HAL_RCC_ClockConfig()函数设置
5,配置扩展外设时钟(可选):通过HAL_RCCEx_PeriphCLKConfig()函数设置
STM32时钟系统的配置除了初始化的时候在 system_stm32f10x.c 中的 SystemInit 函数中外,其他的配置主要在 stm32f10x_rcc.c 文件中,里面有很多时钟设置函数,大家可以打开这个文件浏览一下,基本上看看函数的名称就知道这个函数的作用。对于系统时钟,默认情况下是SystemInit 函数的 SetSysClock() 函数中间判断的,而设置是通过宏定义设置的。我们可以看看 SetSysClock() 函数体:
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#endif
}
上述代码就是判断系统宏定义的时钟是多少,然后设置相应值。系统默认宏定义是 72MHz:
#define SYSCLK_FREQ_72MHz 72000000 //72MHZ
#define SYSCLK_FREQ_36MHz 36000000 //36MHZ
同时还要注意的是,当我们设置好系统时钟后,可以通过变量SystemCoreClock 获取系统时钟
值,如果系统是 72M 时钟,那么 SystemCoreClock =72000000 。这是在 system_stm32f10x.c 文件中设置的。
这里总结一下SystemInit() 函数中设置的系统时钟大小:
- SYSCLK (系统时钟 =72MHz
- AHB 总线时钟 使用 SYSCLK) =72MHz
- APB1 总线时钟 ( =36MHz
- APB2 总线时钟 ( =72MHz
- PLL 时钟 =72MHz
2.2时钟配置总流程
在STM32单片机复位之后,首先进入startup程序:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
可以看出,在进入main主程序之前,先触发了SystemInit()函数,这样就可以保证不需要每次都把时钟配置程序写入main.c文件了,同样,当你想要执行自定义时钟配置程序时也可以改动这个部分。首先我们看一下SystemInit()函数
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
SetSysClock();
}
SystemInit()函数的作用就是使RCC_CR寄存器最低位置1即开启内部8MHz振荡器,然后转到SetSysClock()函数中。刚刚已经讲过了,对于系统时钟,默认情况下就是SystemInit 函数的 SetSysClock() 函数中间判断的
由于把系统时钟频率通过宏定义设置为72MHz,所以这里直接进入最下面的程序SetSysClockTo72(),这也是为什么有的程序把这一段语句直接写入主函数,原则上来说这是可以的,但是没必要,因为startup启动文件已经帮我们定义好了。
下面就是SetSysClockTo72()程序部分,也就是最关键的部分
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 配置---------------------------*/
/* 使能 HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);//RCC_CR寄存器第16位置1,开启HSE
/* 等待HSE就绪 超时退出 */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY; //读取RCC_CR第17位
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET) //读取RCC_CR第17位
{
HSEStatus = (uint32_t)0x01; //HSE状态写1
}
else
{
HSEStatus = (uint32_t)0x00; //HSE状态写0
}
if (HSEStatus == (uint32_t)0x01) //FLASH寄存器部分可以不用管
{
/* 使能 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; //RCC_CFGR寄存器第4~7位写0,即AHB不分频,即HCLK使用72MHz时钟
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; //RCC_CFGR寄存器第11~13位写0,APB2不分频,即PCLK2使用72MHz时钟
/* PCLK1 = HCLK/2 */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //RCC_CFGR寄存器第8~10位写100,APB1二分频,即PCLK1使用36MHz时钟
#else
/* PLL 配置: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL)); //RCC_CFGR寄存器的16~21位清零
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);//RCC_CFGR寄存器第16位置1,使HSE作为PLL输入时钟;17位置0不变,使HSE作为PLL输入时钟时不分频。RCC_CFGR寄存器第18~21位置0111,配置位PLL 9倍频输出给PLLCLK,即PLLCLK为72MHz
#endif /* STM32F10X_CL */
/* 使能 PLL */
RCC->CR |= RCC_CR_PLLON; //RCC->CR寄存器第24位置1,使能PLL
/* 等待PLL就绪 */
while((RCC->CR & RCC_CR_PLLRDY) == 0) //读取PLL时钟就绪标志位(第25位)
{
}
/* 选择PLL作为系统时钟源 */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //RCC->CFGR寄存器第0~1位清零
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; //RCC->CFGR寄存器第0~1位(SW位)置10,即PLL输出作为系统时钟
/* 等待PLL被用作系统时钟源就绪 */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //等待RCC->CFGR寄存器第2~3位(SWS位)为10即就绪
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
}
}
#endif
三、MAP文件浅析
MAP文件是MDK编译代码后,产生的集程序、数据及IO空间的一种映射列表文件。简单说就是包括了:各种.c文件、函数、符号等的地址、大小、引用关系等信息。
MAP文件浅析
MAP文件组成
四、启动模式
STM32F1启动模式
启动过程(以FLASH为例)