【STM32】江科大STM32小白养成记
本文章主要记录本人在学习stm32过程中的笔记,我是看的江科大的视频,其中也插入了不少的例程代码。绝大多数内容为本人手写,代码部分大多来自江科大的例程,注释是我自己添加,如果有错误,欢迎提出!
(PS:目前我还是个小菜鸡,有没有一起学习的呀?哈哈哈哈哈哈)
Part 1 空白项目创建
1.右上角”Project”->”New uVision Project”
2.新建文件,创建项目名称
3.进入新建文件夹,下面“文件名”叫做”Project”,保存
4.选择芯片型号,”STMicroelectronics”->”STM32F1 Series”->”STM32F103”->”STM32F103C8”,点击“OK”,关闭自动弹出的新建工程小助手
5.打开“资料”->“固件库”->“STM32F10x_StdPeriph_Lib_V3.5.0”->“Libraries”->“CMSIS”->“CM3”->“DeviceSupport”->“ST”->“STM32F10x”->“startup”->“arm”,内含文件为启动文件,复制所有文件
6.找到刚才的新建工程项目文件夹,在里面新建文件夹“Start”,把刚才复制的所有文件全部粘贴到“Start”文件夹
7.然会返回固件库的文件夹,到“STM32F10x”,复制“stm32f10x.h”、“system_stm32f10x.c”、“system_stm32f10x.h”,粘贴到新建的“Start”文件夹下
“stm32f10x.h”:外设寄存器描述文件
“system_stm32f10x.c”、“system_stm32f10x.h”:配置时钟
8.添加内核寄存器的描述文件,打开“资料”->“固件库”->“STM32F10x_StdPeriph_Lib_V3.5.0”->“Libraries”->“CMSIS”->“CM3”->“CoreSupport”,全选复制“core_cm3.c”、“core_cm3.h”,粘贴到“Start”文件夹下
9.打开Keil软件,点击选中“Source Group 1”,单击,重命名为“Start”,然后右键,选择“Add Existing Files to Group ‘ Start’… ”,打开“Start”文件夹,将下面的“文件类型(T)”选择“All Files(*.*)”,然后添加启动文件,F103C8T6型号选择“md.s”的启动文件,然后将文件里所有以“.c”和“.h”的文件全部添加,然后点击“Add”,然后“Close”
10.点击Keil软件的“”,打开工程选项,选择“C/C++”,选择下面“InClude Paths”的右边“…”,新建路径,然后再点“…”,选择“Start”文件夹,点击“OK”
11.打开新建的项目文件夹,新建项目文件夹“User”。在Keil左边选择“Target”->“Add Group”,把“New Group”重命名为“User”,右键“User”,点击“Add New Item to Group ‘User’”,选择“C file(.c)”,“Name”那一栏叫“main”,“Location”选择“User”文件夹,点击“Add”
12.在“main.c”的文件中右键,选择“Insert ‘#include file ’”->“stm32f10x.h”。将main函数初始化。文件最后一行必须是空行,否则会报错
13.配置调试器,点击“”,选择“Debug”,右上角选择驱动“ST-Link Debugger”,点击右边的“Settings”,选择“Flash DownLoad”,勾选“Reset and Run”,点击“确认”->“OK”
14.打开工程文件夹,新建文件夹“Library”用来存放库函数,然后打开“资料”->“固件库”->“STM32F10x_StdPeriph_Lib_V3.5.0”->“Libraries”->“STM32F10x_StdPeriph_Driver”->“src”,全选复制到“Library”文件夹粘贴,然后打开固件库的“inc”文件夹,全选复制到“Library”文件夹粘贴
15.回到Keil软件,在Keil左边选择“Target”->“Add Group”,把“New Group”重命名为“Library”,右键“Library”,点击“Add Existing Files to Group ‘Library’”,打开“Library”文件夹,全选,然后“Add”,“Close”
16.打开“资料”->“固件库”->“STM32F10x_StdPeriph_Lib_V3.5.0”->“Project”->“STM32F10x_Stdperiph_Template”,复制“stm32f10x_conf.h”、“stm32f10x_it.c”、“stm32f10x_it.h”到新建工程的“User”目录之下,回到Keil软件,右键“User”选择“Add Existing Files to Group ‘User’”,选择“stm32f10x_conf.h”、“stm32f10x_it.c”、“stm32f10x_it.h”,点击“Add”,“Close”
17.点击Keil软件的“”,选择“C/C++”,选择“Define”栏,粘贴“USE_STDPERIPH_DRIVER”,选择下面“InClude Paths”的右边“…”,新建路径,然后再点“…”,选择“User”、“Library”文件夹,点击“OK”
如此,项目就建好啦!!!但是后面更多的是复制之前的项目,毕竟新建空项目可太麻烦了!
Part 2 GPIO输出
RCC寄存器用来使能GPIO的时钟,GPIO都是APB2的外设。
第一步:配置使能时钟:RCC_APB2PeriphClockCmd(选择外设,ENABLE/DISENABLE)
第二步:配置端口模式:GPIO_Init(选择GPIO,&参数的结构体)
初始化结构体:GPIO_InitTypeDef GIOP_InitStructure
GIOP_InitStructure.GPIO_Mode = 输出模式
GIOP_InitStructure.GPIO_Pin = 输出引脚
GIOP_InitStructure.GPIO_Speed = 输出速度
第三步:设置GPIO的高低电平
端口的8种模式:
GPIO_DeInit:复位外设
GPIO_AFIOInit:复位AFIO外设
GPIO_Init:用结构体的参数来初始化GPIOk口
GPIO_StructInit:将结构体变量赋一个默认值
GPIO输出函数:
GPIO_SetBits:将指定端口设置为高电平
GPIO_ResetBits:将指定端口设置为低电平
GPIO_WriteBit:根据第三个参数的值来设置指定端口
GPIO_Write:同时对16个端口进行写入操作
ADC:从引脚直接接入片上外设
推挽输出和开漏输出的选择
使用推挽
1.驱动能力需求较高的场合
2.高速信号传输
3.无需共用信号线的场合
使用开漏
1.多个设备共用信号线
2.不同电压系统之间的接口
3.需要外部上拉电阻来确定逻辑高电平的场合
接下来‘就是我们第一个点灯程序(本人当时超级兴奋的,好吧?)
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;//初始化结构体
//-------------------------------------------------------------
//初始化结构体内容
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//A0口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度
//-------------------------------------------------------------
GPIO_Init(GPIOA,&GPIO_InitStructure);
//GPIO_ResetBits(GPIOA, GPIO_Pin_0);//A0口
while(1)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_0,Bit_RESET);//点亮
Delay_ms(500);//延迟500ms
GPIO_WriteBit(GPIOA, GPIO_Pin_0,Bit_SET);//熄灭
Delay_ms(500);//延迟500ms
}
}
接下来是流水灯,超级好看!!!!我用的红黄蓝绿交替的,一共是八个灯珠,当然,大家要是有时间可以发挥想象,成为 新一代点灯大师,哈哈哈哈~~~~~~~
但是流水灯需要用到Delay函数,有心的江老师(江科大)已经为我们准备好了!这是源文件
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
还有头文件
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
以及最主要的流水灯程序
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;//初始化结构体
//-------------------------------------------------------------
//初始化结构体内容
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;//所有接口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度
//-------------------------------------------------------------
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
//使用低八位
GPIO_Write(GPIOA, ~0x0001);//0000 0000 0000 0001点亮第一个 低电平点亮
Delay_ms(100);//延迟500ms
GPIO_Write(GPIOA, ~0x0002);//0000 0000 0000 0010点亮第二个 低电平点亮
Delay_ms(100);//延迟500ms
GPIO_Write(GPIOA, ~0x0004);//0000 0000 0000 0100点亮第三个 低电平点亮
Delay_ms(100);//延迟500ms
GPIO_Write(GPIOA, ~0x0008);//0000 0000 0000 1000点亮第四个 低电平点亮
Delay_ms(100);//延迟500ms
GPIO_Write(GPIOA, ~0x0010);//0000 0000 0001 0000点亮第五个 低电平点亮
Delay_ms(100);//延迟500ms
GPIO_Write(GPIOA, ~0x0020);//0000 0000 0010 0000点亮第六个 低电平点亮
Delay_ms(100);//延迟500ms
GPIO_Write(GPIOA, ~0x0040);//0000 0000 0100 0000点亮第七个 低电平点亮
Delay_ms(100);//延迟500ms
GPIO_Write(GPIOA, ~0x0080);//0000 0000 1000 0000点亮第八个 低电平点亮
Delay_ms(100);//延迟500ms
}
}
这里运用到了一点点数电的知识,二进制码,逢1进0哈!比如10就是2,11是3,100是4,以此类推。
Part 3 GPIO输入
大家可以看看,在单片机里,咱们不用C语言那一套,我们定义变量看的是stdint关键字,大家可以在后面的代码中看到。
浅浅解释一下函数哈!
GPIO_ReadInputDataBit:读取数据寄存器某一端口的输入值
GPIO_ReadInputData:读取整个输入寄存器
GPIO_ReadOutputDataBit:读取数据寄存器某一端口的输出值(用于输出模式)
GPIO_ReadOutputData:读取整个输出寄存器
调用这些函数前,我们要分清楚一个项目输入是哪些,输出是哪些。比如,在我们按键控制LED的项目中,输入是按键,而输出是LED,对于按键的状态,我们就能够调用GPIO_ReadInputDataBit函数,对于灯的状态,我们就调用GPIO_ReadOutputDataBit函数。再比如我们光线传感器控制蜂鸣器项目中,光纤传感器的传感数据就是输入,而输出是我们的蜂鸣器。
下面是蜂鸣器的代码:
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;//初始化结构体
//-------------------------------------------------------------
//初始化结构体内容
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//B12输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度
//-------------------------------------------------------------
GPIO_Init(GPIOB,&GPIO_InitStructure);
//GPIO_ResetBits(GPIOA, GPIO_Pin_0);//A0口
while(1)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_12,Bit_RESET);
Delay_ms(100);//延迟100ms
GPIO_WriteBit(GPIOB, GPIO_Pin_12,Bit_SET);
Delay_ms(100);//延迟100ms
GPIO_WriteBit(GPIOB, GPIO_Pin_12,Bit_RESET);
Delay_ms(100);//延迟100ms
GPIO_WriteBit(GPIOB, GPIO_Pin_12,Bit_SET);
Delay_ms(700);//延迟700ms
}
}
学过数电的同学都知道,Set是置高电平,Reset是置低电平。当GPIO引脚为低电平时,电流会通过蜂鸣器,使其发出声音;当GPIO引脚为高电平时,蜂鸣器停止发声。
下面是按键控制LED,大家可以看到,江科大的代码都是使用多个部分,这样的模块化编程在后面十分方便,遇到相似的问题就可以直接调用模块,省时省力哈~
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
uint8_t KeyNum;
int main(void)
{
LED_Init();
KEY_Init();
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
LED1_Turn();
}
if(KeyNum == 2)
{
LED2_Turn();
}
}
}
#include "stm32f10x.h" // Device header
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
//-----------------------------------------------------
//初始化结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//-----------------------------------------------------
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化GPIO口
GPIO_SetBits(GPIOA,GPIO_Pin_1 | GPIO_Pin_2);//初始化高电平,LED是熄灭状态
}
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
void LED1_Off(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
void LED1_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0)//GPIOA为低电平
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);//设置为高电平
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);//设置为低电平
}
}
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}
void LED2_Off(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
void LED2_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2) == 0)//GPIOA为低电平
{
GPIO_SetBits(GPIOA,GPIO_Pin_2);//设置为高电平
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_2);//设置为低电平
}
}
#include "stm32f10x.h" // Device header
#include "Delay.h"
void KEY_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)//代表按下按键
{
Delay_ms(10);//暂停10ms,避免按键抖动来带的误触
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0) ;//按键一直按下,一直在循环
Delay_ms(10);//暂停10ms,避免按键抖动来带的误触
KeyNum = 1;
}
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0)//代表按下按键
{
Delay_ms(10);//暂停10ms,避免按键抖动来带的误触
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0) ;//按键一直按下,一直在循环
Delay_ms(10);//暂停10ms,避免按键抖动来带的误触
KeyNum = 2;
}
return KeyNum;
}
接下来是光线传感器控制蜂鸣器的程序
#include "stm32f10x.h" // Device header
void LightSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
uint8_t LightSensor_Get(void)
{
return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13);
}
#include "stm32f10x.h" // Device header
void Buzzer_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
//-----------------------------------------------------
//初始化结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//-----------------------------------------------------
GPIO_Init(GPIOB,&GPIO_InitStructure);//初始化GPIO口
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
void Buzzer_ON(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_OFF(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_12) == 0)//GPIOB为低电平
{
GPIO_SetBits(GPIOB,GPIO_Pin_12);//设置为高电平
}
else
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);//设置为低电平
}
}
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "BUZZER.h"
#include "LightSensor.h"
int main(void)
{
Buzzer_Init();
LightSensor_Init();
while(1)
{
if(LightSensor_Get() == 1)
{
Buzzer_ON();
}
else
{
Buzzer_OFF();
}
}
}
Part 4 OLED显示屏
OLED(Organic Light Emitting Diode):有机发光二极管
OLED显示屏:性能优异的新型显示屏,具有功耗低、相应速度快、宽视角、轻薄柔韧等特点
0.96寸OLED模块:小巧玲珑、占用接口少、简单易用,是电子设计中非常常见的显示屏模块
供电:3~5.5V,通信协议:I2C/SPI,分辨率:128*64
江科大用的是4引脚的OLED屏幕。
调试方式:
串口调试:通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息
显示屏调试:直接将显示屏连接到单片机,将调试信息打印在显示屏上
Keil调试模式:借助Keil软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能
下面是调用的代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
int main(void)
{
OLED_Init();
OLED_ShowChar(1,1,'A');//字符
OLED_ShowString(1,3,"Hello World!");//字符串
OLED_ShowNum(2,1,12345,5);//十进制
OLED_ShowSignedNum(2,7,-12345,5);//有符号的十进制
OLED_ShowHexNum(3,1,0xAA55,4);//16进制
OLED_ShowBinNum(4,1,0xAA55,16);//二进制数
while(1)
{
}
}
上面ShowHexNum是显示十六进制,范围是0~0xFFFFFFFF,演示代码只显示了四位,我们需要注意的就是OLED显示屏只显示最后四位哈。ShowBinNum是显示二进制,范围:0~1111 1111 1111 1111,具体的大家可以看看下面的真值表。
Part 5 EXTI外部中断
EXTI(外部中断/事件控制器)
EXTI专注于外部信号的中断触发。
功能:用于检测外部引脚的信号变化(如上升沿、下降沿或双边沿),并将这些变化作为中断请求传递给NVIC。
特点:EXTI可以将多个外部信号映射到有限的中断线上,增加了系统的灵活性。
AFIO(高级功能I/O)
AFIO负责将GPIO引脚与EXTI中断线连接。
功能:用于管理GPIO引脚的重映射和中断线的连接。它将GPIO引脚与EXTI的中断线连接起来。
特点:AFIO负责将多个GPIO引脚选择到特定的EXTI线上,但同一时间只能选择一个引脚。
NVIC(嵌套向量中断控制器)
NVIC作为中断管理的核心,负责调度和处理所有中断请求。
功能:管理整个系统的中断请求,包括EXTI、定时器中断、串口中断等。NVIC根据中断优先级决定何时响应中断请求。
特点:NVIC支持中断嵌套、中断优先级分组和中断屏蔽。
TIM(定时器)
TIM用于定时功能,其中断请求通过NVIC管理。
功能:用于定时功能,如定时中断、PWM输出、输入捕获等。TIM本身是一个计数器,需要配置时钟源。
特点:TIM的中断功能需要单独使能,例如通过TIM_ITConfig()函数。
协同工作
EXTI与NVIC:EXTI检测到外部信号变化后,向NVIC发送中断请求。NVIC根据优先级决定是否响应并执行中断服务例程(ISR)。
AFIO与EXTI:AFIO负责将GPIO引脚连接到EXTI的中断线,EXTI再将中断请求传递给NVIC。
TIM与NVIC:TIM的中断请求同样通过NVIC进行管理。TIM的中断功能需要使能后,NVIC才会接收并处理其中断。
中断处理流程
GPIO → AFIO → EXTI → NVIC:GPIO引脚的信号变化通过AFIO映射到EXTI的中断线,EXTI检测到信号变化后向NVIC发送请求,NVIC根据优先级处理中断。
TIM → NVIC:TIM的中断请求直接发送给NVIC,NVIC根据优先级处理。
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
抢占式优先级:
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,高抢占式优先级的中断可以嵌套在低抢占式优先级的中断中。
响应式优先级:
当两个抢占式优先级同时来时,先处理响应式优先级高的(谁优先级高先响应谁)。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。
总结:抢占式优先级>响应优先级>中断表中的排位顺序
NVIC(中断矢量控制器)优先级分组
NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
中断事件是一种可以导致中断发生的事件,中断则是因为中断事件的发生而导致的后续行为过程。事件与中断事件是包含关系,即事件可分为中断事件或非中断事件。而中断事件与中断之间属于前后关联的因果关系,虽有关联,但二者在时序上、行为上并不一样。
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
支持的触发方式:上升沿/下降沿/双边沿/软件触发
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(例如PA0和PB0,但是如果有多个中断引脚,要选择不同Pin的引脚,类似于PB0和 PB1,PA9和PA7,PA6和PB15之类)
通道数:16个GPIO_Pin(GPIO_Pin_0—GPIO_Pin_15),外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
触发响应方式:中断响应/事件响应
中断响应:申请中断,CPU执行中断函数。
事件响应:当外部中断检测到引脚电平变化,选择触发事件,外部中断信号不通向CPU,通向其他外设。
外部中断的功能之一:从低功耗模式的停止模式下唤醒STM32。
PVD电源电压监测:当电源从电压过低恢复时,需要PVD借助外部中断退出停止模式。
RTC闹钟:为了省电,RTC定闹钟之后STM32会进入停止模式,等到闹钟响的时候借助外部中断唤醒。
EXTI基本结构:
每个GPIO外设有16个引脚,
AFIO就是数据选择器,可以在前面3个GPIO外设中选择其中一个GPIO外设的16个引脚连接到后面的EXTI的通道。
Patience:外部中断的9~5会触发同一个中断函数和15~10会触发同一个中断函数,所以要设立标志位来区分是哪一个中断函数进入的NVIC。
AFIO复用IO口
AFIO主要用于引脚复用功能的选择和重定义。
在STM32中,AFIO主要完成两个任务:复用功能引脚重映射和中断引脚选择。
一共16个数据选择器,每个数据选择器选择一个接到后面的EXTI上。
使用AFIO将“默认复用功能”换到“重定义功能”的位置
EXTI框图
配置GPIO AFIO
void GPIO_AFIODeInit(void);复位AFIO外设
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);锁定GPIO配置,防止更改
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);配置GPIO事件输出功能
void GPIO_EventOutputCmd(FunctionalState NewState);配置GPIO事件输出功能
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);配置引脚重映射
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);配置AFIO的数据选择器,选择中断引脚
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);配置以太网相关
配置EXTI
void EXTI_DeInit(void);恢复默认状态
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);根据结构体初始化EXTI外设
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);将结构体变量赋值
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);软件触发外部中断
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);获取指定标志位是否置1
void EXTI_ClearFlag(uint32_t EXTI_Line);对置1 的标志位进行清除
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);获取中断标志位是否置1
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);清除中断挂起标志位
主程序查看和清除标志位:FlagStatus EXTI_GetFlagStatus、void EXTI_ClearFlag
中断函数查看和清除标志位:ITStatus EXTI_GetITStatus、void EXTI_ClearITPendingBit
E.g.
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;//指定配置中断线
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//指定选择中断线的新状态
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//指定外部中断线的模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//指定触发信号的有效边沿
EXTI_Init(&EXTI_InitStructure);
配置NVIC
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);中断分组
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);利用结构体初始化NVIC
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);设置中断向量表
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);系统低功耗配置
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
E.g.
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//只能使用一种分组方式
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//指定中断通道来开启或关闭
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定中断通道是使能还是失能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定所选通道的抢占优先级和响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
void EXTI15_10_IRQHandler(void)//中断函数
//中断函数都是无参无返回值的
旋转编码器
旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
类型:机械触点式/霍尔传感器式/光栅式
Part 6 TIM定时中断
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
所有定时器的时钟都是72MHz
STM32有16位计数器、预分频器、自动重装寄存器的时基单元(核心部分),在72MHz计数时钟下可以实现最大59.65s的定时
计数器:执行技术定时的寄存器,每来一个时钟,计数器+1
预分频器:对计数器的时钟进行分频,使计数更加灵活
自动重装寄存器:计数的目标值,即及多少个时钟申请中断
当预分频器设置最大,自动重装寄存器设置最大,定时器的最大定时时间就是59.65s,接近1min
72M/65536/65536 = 中断频率,再取倒数就是59.65s (2^16 = 65536)
STM32不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
STM32的定时器根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
1MHz = 1 us
1KHz = 1ms
定时器类型
STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
基本定时器(仅支持 向上计数模式)
时基单元:计数器、预分频器、自动重装寄存器
预分频器:基本定时器只能选择内部时钟CK_INT,内部时钟的来源是RCC_TIMxCLK,这里的频率值一般都是系统的主频72MHZ,因此通向时基单元的技术基准频率就是72MHz。预分频器对72MHz的时钟进行预分频,
当预分频器写0,那就是不分频,或者是1分频,输出频率 = 输入频率 = 72MHz
当预分频器写1,那就是2分频,输出频率 = 输入频率 / 2 = 36MHz;
当预分频器写2,那就是3分频,输出频率 = 输入频率 / 3;
…….
总之,实际分频系数 = 预分频器的值 + 1
16位预分频器最大值是65535,也就是65536分频
计数器:对分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就 + 1.
16位计数器的值可以从0到65535。所以计数器的值在计数过程中会不断地自增运行,当自增运行到目标值时,产生中断,完成定时任务。
自动重装载寄存器:存写入计数的固定目标
当计数值 = 自动重装值,产生中断信号,并清零计数器,计数器自动开始下一次的计数计时。
更新中断:计数值等于自动重装值产生的中断
产生更新中断,更新中断之后就通往NVIC,我们配置好NVIC的定时器通道,那定时器的更新中断就能够得到CPU响应
产生事件,这里产生的事件叫更新事件,更新事件不会触发中断,但可以触发内部其他电路工作
主模式触发DAC的功能:让内部硬件不受主程序的控制下实现自动运行
通用计时器(向上计数、向下计数,中央对齐三种模式)
外部时钟模式二(ETR外部引脚提供时钟,或者对ETR时钟进行计数,把定时器当作计数器):TIMx_ETR引脚(PA0), 配置内部的极性选择、边沿检测额预分频器电路,再配置输入滤波电路,对外部时钟进行整形。滤波后的信号兵分两路,上一路ETRF进入触发控制器,然后选择作为时基单元的时钟。
外部时钟模式一(将TRGI当作外部时钟来使用):
ITR0到ITR3分别来自其他4个定时器的TRGO输出。外部时钟模式一的输入可以是ETR引脚、其他定时器、TIMx_CH1引脚的边沿(上升沿、下降沿均有效)、TIMx_CH1引脚和TIMx_CH2引脚。
TRGI提供时钟()
输出比较电路
四个通道,分别对应CH1到CH4的引脚,可用于输出PWM波形、驱动电机
输入比较电路
四个通道,分别对应CH1到CH4的引脚,可用于测量输入方波的频率
捕获/比较寄存器
输入捕获和输出比较共用的,输入捕获和输出比较不能同时使用
高级定时器
可实现每隔几个计数周期才发生一次更新事件和更新中断
定时中断基本结构
预分频器时序
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
溢出时间:溢出频率取倒数
计数器无预装时序
计数器有预装时序
影子寄存器的目的:让值的变化和更新事件同时发生,防止在运行图中更改造成错误
RCC时钟树
8 MHz HSI RC和4-16 MHz HSE OSC是告诉晶振,提供系统时钟,AHB、APB1、APB2都来自于这两个高速晶振。外部石英振荡器比内部RC振荡器更稳定。
CSS(Clock Security System)时钟安全系统,负责切换时钟。监测外部时钟运行状态,一旦外部时钟失效,自动把外部时钟切换回内部时钟,保证系统时钟的运行,防止程序卡死造成事故。
AHB总线有预分频器,在System_Init函数里配置的分配系数为1,即分配的时钟为72MHz
APB1总线,分配系数为2,APB1总线的时钟为72MHz / 2 = 36MHz
所有定时器内部基准时钟都是72MHz
void TIM_DeInit(TIM_TypeDef* TIMx);恢复缺省配置
Void TIM_TimeBaseInit(TIM_TypeDef* TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);时基单元初始化(时基单元:计数器、预分频器、自动重装寄存器)
TIMx:选择某个定时器
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);给结构体变量赋默认值
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);使能计数器,对应图上的“运行控制”
TIMx:选择计数器
FunctionalState NewState:使能/失能
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);使能中断输出信号,对应图中“中断输出控制”
TIMx:选择计时器
TIM_IT:选择配置哪个中断输出
FunctionalState NewState:使能/失能
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);选择内部时
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);选择ITRx其他定时器的时钟
TIMx:选择要配置的定时器
TIM_InputTriggerSource:选择要接入哪个的定时
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);选择ITx捕获通道的时钟
TIM_TIxExternalCLKSource:选择TIx具体的引脚
TIM_ICPolarity:输入的极性
ICFilter:滤波器
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);选择ETR通过外部时钟模式1输入的时钟
TIM_ExtTRGPrescaler:外部触发预分频器,对ETR的外部时钟再提前做一个分频
TIM_ICPolarity:输入的极性
ICFilter:滤波器
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);选择ETR通过外部时钟模式2输入的时钟
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);单独配置ETR引脚的预分频器、极性、滤波器这些参数
作者:我都学计算机了,让让我吧