持续更新的基于STM32F103XX型号的STM32教程

目录

 

引言:

笔记:

STM32的三种开发方式(寄存器开发   标准库函数开发   HAL库开发):

STM32的4个独立时钟源(HSI HSE LSI LSE):

STM32的AHB总线与APB总线:

开启STM32的外设模块时钟

为什么要开启外设模块时钟?

外设模块时钟使能化处理:

GPIO的8种模式,4种数据传输速率,配置与开启:

GPIO的8种工作模式(4种输入,4种输出,3种数据传输速率):

4种输入:

4种输出:

3种数据传输速率:

GPIO口的配置:

GPIO的开启:

总结:

中断:

中断的概念:

中断系统:

中断优先级:

中断嵌套:

注意:

EXTI外部中断:

EXTI外部中断的基本结构(来自江协科技PPT):

EXTI如何工作:

EXTI外部中断支持的触发方式(4种):

EXTI支持的GPIO口:

EXTI的响应触发方式(2种):

EXTI如何开启:


引言:

我主要是根据哔哩哔哩up主江协科技进行学习,其中大部分内容为自己手写,其余来自参考官方参考手册/江协科技/野火公司等,使用的开发板系列为:STM32F108XX系列。

此处附上网课地址:STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili

我所使用的开发环境为windows系统,所用的软件为Keil uVision5。

此处附上keil5的下载地址与相应mdk的下载地址:

keil5: https://www.keil.com/download/product/

MDK525:https://pan.baidu.com/s/15ox_4y5BorNfVBh5tYThtQ  提取码:wjhd

MDK安装教程:https://blog.csdn.net/DaXiongRen/article/details/120072979

如果不想下载keil5软件,也可以使用vscode中的keil插件

如下为具体的操作过程:https://blog.csdn.net/Johnor/article/details/134353311


笔记:

STM32的三种开发方式(寄存器开发   标准库函数开发   HAL库开发):

寄存器开发:此方法需要用户自己去操作底层寄存器进行功能设计(类似于51单片机的开发),但由于STM32的寄存器过多,故不建议直接使用寄存器进行开发。

标准库函数开发:此方法即使用官方提供的标准库进行开发(函数的使用可以参照官方提供的库函数文档),且较为简单,易上手。

HAL库开发: 此方法为使用官方提供的图形化的一键生产代码的工具进行开发,在这个工具中我们只要根据你想要的功能,点点鼠标就可以生成你想要的功能,上手很快,但是性能损失比较大。


STM32的4个独立时钟源(HSI HSE LSI LSE):

HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。

HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。

LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。 

LSE是低速外部时钟,接频率为32.768kHz的石英晶体。

如果大家想深入了解STM32的时钟,大家可以去看一下此链接:https://zhuanlan.zhihu.com/p/376597895


STM32的AHB总线与APB总线:

AHB是高速总线,是一种系统总线,它主要负责连接处理器,DMA等一些内部接口。AHB 系统由主模块、从模块和基础结构3部分组成,整个AHB总线上的传输都由主模块发出,由从模块负责回应。
APB是低速总线,它主要负责连接外围设备,它又分为APB1(36MHZ)和APB2(72MHZ),它的总线架构不像 AHB支持多个主模块,在APB里面唯一的主模块就是APB 桥。APB桥就是连接AHB和APB中间的玩意。

以下分别为APB1和APB2所控制的不同外设的详细分布(来自官方的开发手册):

APB1:

#define RCC_APB1Periph_TIM2              ((uint32_t)0x00000001)
#define RCC_APB1Periph_TIM3              ((uint32_t)0x00000002)
#define RCC_APB1Periph_TIM4              ((uint32_t)0x00000004)
#define RCC_APB1Periph_TIM5              ((uint32_t)0x00000008)
#define RCC_APB1Periph_TIM6              ((uint32_t)0x00000010)
#define RCC_APB1Periph_TIM7              ((uint32_t)0x00000020)
#define RCC_APB1Periph_TIM12             ((uint32_t)0x00000040)
#define RCC_APB1Periph_TIM13             ((uint32_t)0x00000080)
#define RCC_APB1Periph_TIM14             ((uint32_t)0x00000100)
#define RCC_APB1Periph_WWDG              ((uint32_t)0x00000800)
#define RCC_APB1Periph_SPI2              ((uint32_t)0x00004000)
#define RCC_APB1Periph_SPI3              ((uint32_t)0x00008000)
#define RCC_APB1Periph_USART2            ((uint32_t)0x00020000)
#define RCC_APB1Periph_USART3            ((uint32_t)0x00040000)
#define RCC_APB1Periph_UART4             ((uint32_t)0x00080000)
#define RCC_APB1Periph_UART5             ((uint32_t)0x00100000)
#define RCC_APB1Periph_I2C1              ((uint32_t)0x00200000)
#define RCC_APB1Periph_I2C2              ((uint32_t)0x00400000)
#define RCC_APB1Periph_USB               ((uint32_t)0x00800000)
#define RCC_APB1Periph_CAN1              ((uint32_t)0x02000000)
#define RCC_APB1Periph_CAN2              ((uint32_t)0x04000000)
#define RCC_APB1Periph_BKP               ((uint32_t)0x08000000)
#define RCC_APB1Periph_PWR               ((uint32_t)0x10000000)
#define RCC_APB1Periph_DAC               ((uint32_t)0x20000000)
#define RCC_APB1Periph_CEC               ((uint32_t)0x40000000)

APB2:

#define RCC_APB2Periph_AFIO              ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOA             ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB             ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC             ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD             ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE             ((uint32_t)0x00000040)
#define RCC_APB2Periph_GPIOF             ((uint32_t)0x00000080)
#define RCC_APB2Periph_GPIOG             ((uint32_t)0x00000100)
#define RCC_APB2Periph_ADC1              ((uint32_t)0x00000200)
#define RCC_APB2Periph_ADC2              ((uint32_t)0x00000400)
#define RCC_APB2Periph_TIM1              ((uint32_t)0x00000800)
#define RCC_APB2Periph_SPI1              ((uint32_t)0x00001000)
#define RCC_APB2Periph_TIM8              ((uint32_t)0x00002000)
#define RCC_APB2Periph_USART1            ((uint32_t)0x00004000)
#define RCC_APB2Periph_ADC3              ((uint32_t)0x00008000)
#define RCC_APB2Periph_TIM15             ((uint32_t)0x00010000)
#define RCC_APB2Periph_TIM16             ((uint32_t)0x00020000)
#define RCC_APB2Periph_TIM17             ((uint32_t)0x00040000)
#define RCC_APB2Periph_TIM9              ((uint32_t)0x00080000)
#define RCC_APB2Periph_TIM10             ((uint32_t)0x00100000)
#define RCC_APB2Periph_TIM11             ((uint32_t)0x00200000)

开启STM32的外设模块时钟

为什么要开启外设模块时钟?

由于STM32十分复杂,而且在控制不同外设时,所需的时钟频率各不相同(如果控制外设都是用告诉时钟,那么无异于杀鸡焉用牛刀,过分浪费性能了),而且为了控制单片机的功率,故ST公司使用了多个时钟源的设置,因此在我们想用某个外设时就要开启相应地时钟。

外设模块时钟使能化处理:

在开启外设模块时钟之前,我们需要新进行总线的选择(如我们在使用GPIOA时,我门就需要选择APB2总线;在使用TIM2计时器时,我门就要选择APB1总线)。

外设模块时钟使能的初始化函数:

void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
//APB1总线的外设模块时钟使能初始化函数
//此处第一个参数为你所使用的外设名字
//第二个参数为 ENABLE

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
//APB2总线的外设模块时钟使能初始化函数
//此处第一个参数为你所使用的外设名字
//第二个参数为 ENABLE

假设我们使用的是GPIOA外设,其初始化代码就为:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//此处ENABLE就是使能的意思
//RCC为系统时钟

假设我们使用的TIM2计时器外设,其初始化代码就为:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

GPIO的8种模式,4种数据传输速率,配置与开启:

GPIO的8种工作模式(4种输入,4种输出,3种数据传输速率):
4种输入:

  GPIO_Mode_AIN = 0x0                         模拟输入    GPIO无效,引脚直接接入内部ADC
  GPIO_Mode_IN_FLOATING = 0x04      浮空输入   可读取引脚电平,若引脚悬空,则电平不确定
  GPIO_Mode_IPD = 0x28    下拉输入    可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
  GPIO_Mode_IPU = 0x48    上拉输入    可读取引脚电平,内部连接下拉电阻,悬空时默认低电平

4种输出:

GPIO_Mode_Out_OD = 0x14     开漏输出        可输出引脚电平,高电平为高阻态,低电平接VSS
GPIO_Mode_Out_PP = 0x10     推挽输出         可输出引脚电平,高电平接VDD,低电平接VSS
GPIO_Mode_AF_OD = 0x1C     复用开漏输出  由片上外设控制,高电平为高阻态,低电平接VSS
GPIO_Mode_AF_PP = 0x18      复用推挽输出  由片上外设控制,高电平接VDD,低电平接VSS

3种数据传输速率:

  GPIO_Speed_10MHz   
  GPIO_Speed_2MHz
  GPIO_Speed_50MHz

GPIO口的配置:

在官方提供的开发手册中,是使用一个结构体对引脚进行初始化配置:

typedef struct
{
  uint16_t GPIO_Pin;              //引脚选择
  GPIOSpeed_TypeDef GPIO_Speed;   //数据传输速率选择
  GPIOMode_TypeDef GPIO_Mode;     //引脚工作模式选择
}GPIO_InitTypeDef;                //官方提供的GPIO初始化类的定义名

假设我们使用的是引脚1,传输速率为2MHZ,模式为推挽输出,那么其初始化代码应为:

GPIO_InitTypeDef GPIO_IUse;                //此处我使用的结构体名字GPIO_IUsee 
GPIO_IUse.GPIO_Mode = GPIO_Mode_Out_PP;    //将模式设置为推挽输出模式
GPIO_IUse.GPIO_Pin = GPIO_Pin_1;           //将初始化引脚设置为我所使用的1号引脚
GPIO_IUSe.GPIO_Speed = GPIO_Speed_2MHz;    //将数据传输缩率设置为2MHZ

注意:此处此处的GPIO_Speed一般为50MHz

GPIO的开启:

GPIO的初始化函数:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
//第一个参数为自己使用的外设模块编号
//第二个参数为我们自己定义的GPIO初始化结构体

假设我们使用的为GPIOA外设模块,结构体为我们刚GPIO_IUse结构体,所以其初始化代码为:

GPIO_Init(GPIOA,GPIO_IUse);
总结:

开启GPIO的总过程:

1.开启外设模块时钟

2.GPIO口初始化结构体的配置

3.GPIO口的初始化

总流程代码展示:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//外设模块时钟使能化
GPIO_InitTypeDef GPIO_INITled;//定义GPIO初始化结构体的名字
GPIO_INITled.GPIO_Mode = GPIO_Mode_Out_PP; //定义引脚的工作模式
GPIO_INITled.GPIO_Pin = GPIO_Pin_7;//定义所使用的引脚
GPIO_INITled.GPIO_Speed = GPIO_Speed_50MHz;//定义工作频率
GPIO_Init(GPIOB,&GPIO_INITled);//GPIO引脚开启

中断:

中断的概念:

中断就是指计算机在进行某一程序的运行时,由于本身硬件或者用户的操作而不得不放弃当前的任务1去执行另一个任务2,在完成任务2后再回来继续完成任务1。

举个例子:

假设刚开始我在一旁打游戏,突然我妈喊我去打扫卫生,由于害怕挨骂,所以我不得不放弃打游戏而去打扫卫生,在打扫完卫生之后,我又回去继续打游戏。

下图为相对应的流程图:

中断系统:

实现中断作用的软/硬件系统被称为中断系统。

中断优先级:

在单片机有多个中断源同时申请中断时,为了使单片机更好的反应中断,故人为设定了几种中断优先级,使CPU根据中断事物的缓急进行中断,使单片机优先响应更加紧急的中断源,更好的完成任务。

中断嵌套:

当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。

继续以刚才的例子打比方:

在我还没打扫完卫生时,我的女神突然给我发了消息(懂得都懂),这时我肯定停止打扫卫生,去和女神聊天了,跟女神聊完之后,再回来继续打扫卫生,打扫完卫生之后,再回来打游戏。

注意:

在STM32中,共有68个可屏蔽中断通道,包含EXTI,TIM,ADC等多个外设,且这些中断由NVIC统一管理。

此处NVIC寄存器在另一篇博客中讲解:NVIC寄存器

EXTI外部中断:
EXTI外部中断的基本结构(来自江协科技PPT):

EXTI如何工作:

EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序(此处设计到上文提到的中断优先级和中断嵌套)

EXTI外部中断支持的触发方式(4种):

1.上升沿触发      引脚由低电平变为高电平时触发

2.下降沿触发      引脚由低电平变为高电平时触发

3.双边沿触发      引脚由低电平变为高电平时触发/由高电平变为低电平时也触发

4.软件触发

EXTI支持的GPIO口:

支持所有的GPIO口,但是相同的Pin不能同时触发中断

例如:

GPIOA_Pin_1  和  GPIOB_Pin_1就不能同时触发,因为GPIO的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个。

EXTI的响应触发方式(2种):

1.中断响应

2.事件响应

EXTI如何开启:

1.开启外设模块时钟和AFIO时钟。

2.配置与开启GPIO引脚。

3.初始化中断线程。

4.配置外部中断的优先级。

5.选择外部中断处理函数。

注意:

其中步骤二与先前所讲方法一样

步骤四讲解的链接:NVIC寄存器详解(基于STM32F103XX型号讲解)-CSDN博客

因此步骤二,四在此处就不多讲了。

步骤一:开启外设模块时钟。

在使用EXTI外设时,不仅要打开外设模块的时钟,同时也要打开AFIO时钟;

相应代码:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);    //打开外设模块时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);    //打开AFIO时钟
步骤三:初始化中断线程。

GPIO_EXTILineConfig()函数:

GPIO_EXTILineConfig()函数用于配置外部中断与特定GPIO引脚之间的映射关系。

假设此处我使用的GPIOA的0引脚,其初始化代码为:

GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);

中断初始化结构体的内容以及其含义:

typedef struct
{
  uint32_t EXTI_Line;       //所使用的中断线
  EXTIMode_TypeDef EXTI_Mode;     //所使用的中断模式
  EXTITrigger_TypeDef EXTI_Trigger;  //所使用的中断触发方式
  FunctionalState EXTI_LineCmd;      //中断使能控制
}EXTI_InitTypeDef;

中断初始化结构体的配置过程:

EXTI_InitTypeDef EXTI_extiINIT;    //定义EXTI初始化的名字为EXTI_extiINIT
EXTI_extiINIT.EXTI_Line = EXTI_Line0;    //此时我使用0号中断线
EXTI_extiINIT.EXTI_Mode = EXTI_Mode_Interrupt;    //此处我使用的模式为中断模式
EXTI_extiINIT.EXTI_Trigger = EXTI_Trigger_Rising;    //此处我是用上升沿触发
/*
  EXTI_Trigger_Falling     下降沿触发
  EXTI_Trigger_Rising_Falling     双沿触发(上升沿和下降沿都可触发)
*/
EXTI_extiNIT.EXTI_LineCmd = ENABLE;    //EXTI使能开启
EXTI_Init(&EXTI_extiINIT);    //对EXTI进行初始化
步骤五:选择外部中断处理函数。

根据中断线进行选择:

在使用不同的GPIO口时,就用相应的IRQHandler。

注意:

在使用5-9引脚时,使用EXTI9_5_IRQHandler;

在使用10-15引脚时,使用EXTI15_10_IRQHandler;

EXTI外部中断总结过程以及代码:

1.开启外设模块时钟和AFIO时钟。

2.配置与开启GPIO引脚。

3.初始化中断线程。

4.配置外部中断的优先级(NVIC寄存器的选组)。

5.选择外部中断处理函数(NVIC寄存器的配置以及初始化)。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);    //开启外设模块时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);    //开启AFIO时钟

//对GPIO进行配置
GPIO_InitTypeDef GPIO_PBDINIT;
GPIO_PBDINIT.GPIO_Mode = GPIO_Mode_IPU;
GPIO_PBDINIT.GPIO_Pin = GPIO_Pin_0;
GPIO_PBDINIT.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_PBDINIT);

//初始化中断线程设计
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource8);
	
//EXTI外部中断初始化配置
EXTI_InitTypeDef EXTI_PBDINIT;
EXTI_PBDINIT.EXTI_Line = EXTI_Line8;
EXTI_PBDINIT.EXTI_LineCmd = ENABLE;
EXTI_PBDINIT.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_PBDINIT.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_PBDINIT);
	
//配置外部中断的优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	

//NVIC寄存器的配置以及初始化
NVIC_InitTypeDef NVIC_PBDINIT;
NVIC_PBDINIT.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_PBDINIT.NVIC_IRQChannelCmd = ENABLE;
NVIC_PBDINIT.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_PBDINIT.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_PBDINIT);

物联沃分享整理
物联沃-IOTWORD物联网 » 持续更新的基于STM32F103XX型号的STM32教程

发表评论