【STM32学习_凯斯2】

【STM32学习_凯斯2】

  • STM32F1系统架构
  • STM32F1系统时钟
    • 系统时钟简介
    • 系统时钟配置
    • 端口复用下的时钟
  • STM32F1NVIC 中断优先级管理
  • HAL 库中寄存器地址名称映射分析

本文为原子哥hal库开发手册学习笔记

STM32F1系统架构

【STM32学习_凯斯2】_第1张图片
ICode 总线:该总线将 M3 内核指令总线和闪存指令接口相连,指令的预取在该总线上面完成。
DCode 总线:该总线将 M3 内核的 DCode 总线与闪存存储器的数据接口相连接,常量加载和调试访问在该总线上面完成。
系统总线:该总线连接 M3 内核的系统总线到总线矩阵,总线矩阵协调内核和 DMA 间访问。
DMA 总线:该总线将 DMA 的 AHB 主控接口与总线矩阵相连,总线矩阵协调 CPU 的DCode 和 DMA 到 SRAM,闪存和外设的访问。
总线矩阵:总线矩阵协调内核系统总线和 DMA 主控总线之间的访问仲裁,仲裁利用轮换算法。
AHB/APB 桥:这两个桥在 AHB 和 2 个 APB 总线间提供同步连接, APB1 操作速度限于36MHz,APB2 操作速度全速。一般AHB总线时钟72MHz,APB2总线速度72MHz,APB1总线速度36MHz,两条APB总线分别负责不同外设的时钟供应。

系统架构像学习51单片机一样记录就行。

STM32F1系统时钟

系统时钟简介

单片机的系统时钟就像人的“脉搏”一样,提供工作标准时间,什么时间干什么事,要以下图为中心去理解系统时钟。
【STM32学习_凯斯2】_第2张图片
系统时钟主要分为:高速时钟和低速时钟,内部时钟和外部时钟。

图中共5个时钟源:
① HSI 是高速内部时钟, RC 振荡器, 频率为 8MHz。
②HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为 4MHz~16MHz。我们的开发板接的是 8M 的晶振。
③ LSI 是低速内部时钟, RC 振荡器,频率为 40kHz。 独立看门狗的时钟源只能是 LSI,同时 LSI 还可以作为 RTC 的时钟源。
④ LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。 这个主要是 RTC 的时钟源。
⑤、 PLL 为锁相环倍频输出,其时钟输入源可选择为 HSI/2、 HSE 或者 HSE/2。倍频可选择为2~16 倍,但是其输出频率最大不得超过 72MHz。

图中需要时钟的部分有:
A. MCO 是 STM32 的一个时钟输出 IO(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. D 处就是 STM32 的系统时钟 SYSCLK,它是供 STM32 中绝大部分部件工作的时钟源。系统时钟可选择为 PLL 输出、HSI 或者HSE。系统时钟最大频率为 72MHz,当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。
E. 这里的 E 处是指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK。SYSCLK 通过 AHB 分频器分频后送给各模块使用。这些模块包括:
①、 AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟
②、通过 8 分频后送给 Cortex 的系统定时器时钟,也就是 systick 了。
③、直接送给 Cortex 的空闲运行时钟 FCLK。
④、送给 APB1 分频器。 APB1 分频器输出一路供 APB1 外设使用(PCLK1,最大频率 36MHz),另一路送给定时器(Timer)2、 3、 4 倍频器使用。
⑤、送给 APB2 分频器。 APB2 分频器分频输出一路供 APB2 外设使用(PCLK2,最大频率 72MHz),另一路送给定时器(Timer)1 倍频器使用。

总结:整体来说就是5个时钟源经过分频、倍频、选择器为不同的模块提供时钟,注意图中的时钟使能,就是简单的与门,0/1控制控制外设需要的时钟是否有效,写驱动的时候注意开启使能。
SYSCLK时钟线 ——> AHB 分频器
APB1 分频器 ——> PCLK1时钟线
APB2 分频器 ——> PCLK2时钟线
各个外设都需要时钟,他们分别从这几条时钟线中挑选一条作为自己的时钟

系统时钟配置

(RCC下很多寄存器配置)
HAL库中的SystemInit 主要做了如下三个方面工作:
1) 复位 RCC 时钟配置为默认复位值(默认开始了 HIS)
2) 外部存储器配置
3) 中断向量表地址配置
所以我们需要自己配置时钟,心中要有上边儿那个图,各个时钟源的走向,记不住就经常来看看。
时钟配置是每个单片机的标配,跟原子哥学习,把配置文件存放在sys组中是个不错的选择,配置代码如下:

//时钟系统配置函数
//PLL:选择的倍频数, RCC_PLL_MUL2~RCC_PLL_MUL16
//返回值:0,成功;1,失败
void Stm32_Clock_Init(u32 PLL)
{
HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef RCC_OscInitStructure;
RCC_ClkInitTypeDef RCC_ClkInitStructure;
RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //时钟源为 HSE
RCC_OscInitStructure.HSEState=RCC_HSE_ON; //打开 HSE
RCC_OscInitStructure.HSEPredivValue=RCC_HSE_PREDIV_DIV1; //HSE 预分频
RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON; //打开 PLL
RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;
//PLL 时钟源选择 HSE
RCC_OscInitStructure.PLL.PLLMUL=PLL; //主 PLL 倍频因子
ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化
if(ret!=HAL_OK) while(1);
//选中 PLL 作为系统时钟源并且配置 HCLK,PCLK1 和 PCLK2
RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK|
RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|
RCC_CLOCKTYPE_PCLK2);
//设置系统时钟时钟源为 PLL
RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1; //AHB 分频系数为 1
RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV2; //APB1 分频系数为 2
RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV1; //APB2 分频系数为 1
ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_2);
//同时设置 FLASH 延时周期为 2WS,也就是 3 个 CPU 周期。
if(ret!=HAL_OK) while(1);
}

以上代码不必慌,主要思想就是:
1、从5个时钟源中一系列分频倍频选择了PLL时钟源作为SYSCLK
2、配置AHB、APB1、APB2分频系数为主要外设HCLK、PCLK1、PLCK2提供时钟
结果如下:

SYSCLK(系统时钟) =72MHz
PLL 主时钟 =72MHz
AHB 总线时钟(HCLK=SYSCLK/1=72MHz
APB1 总线时钟(PCLK1=HCLK/2=36MHz
APB2 总线时钟(PCLK2=HCLK/1=72MHz

之后在使用外设时,还需要使能外设时钟,不用时就关闭,如:

__HAL_RCC_GPIOA_CLK_ENABLE();//使能 GPIOA 时钟
__HAL_RCC_DMA1_CLK_ENABLE();//使能 DMA1 时钟
__HAL_RCC_USART2_CLK_ENABLE();//使能串口 2 时钟
__HAL_RCC_TIM1_CLK_ENABLE();//使能 TIM1 时钟

__HAL_RCC_GPIOA_CLK_DISABLE();//禁止 GPIOA 时钟
__HAL_RCC_DMA1_CLK_DISABLE();//禁止 DMA1 时钟
__HAL_RCC_USART2_CLK_DISABLE();//禁止串口 2 时钟
__HAL_RCC_TIM1_CLK_DISABLE();//禁止 TIM1 时钟

以上这些不用记代码,咱要记住的是这个步骤和思想,怎么操作的寄存器,知道去哪找就行(hal库中rcc头文件)。

端口复用下的时钟

复用:一个端口除了具有普通I/O功能还具有UART、ADC、DAC等功能。如PA9-GPIO/USART1_TX。
USART使用复用时钟配置:
① 首先,我们要使用 IO 复用功能,必须先打开对应的 IO 时钟和复用功能外设时钟。

__HAL_RCC_GPIOA_CLK_ENABLE(); //使能 GPIOA 时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能 USART1 时钟
__HAL_RCC_AFIO_CLK_ENABLE(); //使能辅助功能 IO 时钟

② 然后,我们在 GIPOx_MODER 寄存器中将所需 IO(对于串口 1 是 PA9,PA10) 配置为复用功能。
③ 最后,我们还需要对 IO 口的其他参数,例如上拉/下拉以及输出速度等进行配置。

GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_9; //PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9

STM32F1NVIC 中断优先级管理

CM3 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,而F103中只用到了16个内核中断和60个外部中断,不同的芯片中断利用率不同。
NVIC 中断管理相关函数主要在 HAL 库关键文stm32f1xx_hal_cortex.c 中定义。一般某个外设配置都是通过寄存器控制,NVIC_Type结构体内包含了很多组寄存器,如下:
ISER[8]中断使能寄存器组,8 个 32 位寄存器来控制,ISER[0]的 bit0~bit31 分别对应中断 0~31, ISER[1]的 bit0~27 对应中断 32~59,这样总共 60 个中断就分别对应上了。
ICER[8]中断除能寄存器组,8 个 32 位寄存器来控制。
ISPR[8]中断挂起控制寄存器组,8 个 32 位寄存器来控制,就是中断嵌套使用。
ICPR[8]中断解挂控制寄存器组,8 个 32 位寄存器来控制。
IABR[8]中断激活标志位寄存器组,8 个 32 位寄存器来控制。
IP[240]中断优先级控制的寄存器组,由 240 个 8bit 的寄存器组成,代表240个中断,8bit中只用了高4位,又分为抢占优先级和子优先级。 SCB->AIRCR寄存器控制具体情况,如下图:
【STM32学习_凯斯2】_第3张图片
经常设置第2组,抢占优先级0-3,响应优先0-3,0为最高优先级,3为最低。

中断优先级设置总结:
①系统运行开始的时候设置中断分组。

HAL_NVIC_SetPriorityGrouping (NVIC_PriorityGroup_2);

② 设置单个中断的中断优先级别并使能相应中断通道。

void HAL_NVIC_SetPriority(IRQn_Type IRQn,uint32_t PreemptPriority, uint32_t SubPriority);
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);

HAL 库中寄存器地址名称映射分析

(写程序就是控制寄存器)
51中有reg51.h头文件中sfr扩充数据类型,可以很方便的访问寄存器,而32中寄存器异常多,所以采用结构体方式访问寄存器,且结构体存储的寄存器地址是连续的
简单说说GPIOA,GPIOA 的 7 个寄存器都是 32 位的,所以每个寄存器占有 4个地址,一共占用 28 个地址。
各个寄存器地址=GPIOA基地址+寄存器偏移地址
GPIOA的地址=APB2基地址+GPIOA偏移地址

如此套娃即可计算具体地址。
往寄存器对应的地址写数据就可以操作GPIO口啦!
【STM32学习_凯斯2】_第4张图片
中间穿插一段介绍一下:
断言函数assert_param(),它的作用主要是对入口参数的有效性进行判断。有效则返回0,无效则提示错误位置对应的文件和行数,该函数对于固件库是经常使用的。
预处理宏定义

#define GPIO_PIN_0 ((uint16_t)0x0001)

宏定义常用于定义地址或数值,编译器执行时是完全替换。

#define IS_GPIO_PULL(PULL) (((PULL) == GPIO_NOPULL)\
|| ((PULL) == GPIO_PULLUP) || \ ((PULL) == GPIO_PULLDOWN))

宏定义下的函数,编译器还是替换,只不过还把形参替换成了输入的实参。

GPIOx配置很简单,如下图:

void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
-------------
如何使用
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9

过程总结:
1.GPIO_InitTypeDef 结构体定义一个对象GPIO_Initure,配置相应的Pin、Mode等数据。
2.GPIOA是个结构体指针,将GPIOA指向的地址传给初始化函数;GPIO_Initure取地址传给初始化函数。
3.函数将配置完值得那块内存数据传到GPIOA指向的地址各个寄存器中。
这种标准库,HAL库都是用结构体指针访问寄存器并配置数据的。

你可能感兴趣的:(嵌入式linux系列,stm32,单片机)