赞
踩
此处部分概念为简介,后面会根据具体 外设的使用 和 功能的实现 补充
RCC(reset clock control)
:复位和时钟控制器。
STM32F103xC, STM32F103xD and STM32F103xE performance line block diagram。
CPU上电的时候,默认情况下:
RC振荡器
(HSI),时钟供给 SYSCLK系统时钟
8M,SYSCLK时钟信号经过AHB预分频器,分频系数默认是1,
8M
8M
startup_stm32f10x_hd.s 启动文件
)会帮我们配置时钟,外部的高速时钟可以取值为 4~16M
,一般选8M晶振
,8M 经过PLL锁相环倍频器
,可以让倍频系数值为9
,这里出来PLLCLK锁相环时钟为最高72MHz
,单刀三击开关选择PLLCLK作为SYSCLK系统时钟,系统时钟信号也是 72M Hz
,AHB 和APB2预分频器分频里值是1,APB1预分频器值是2,最终:
72M
36M
72M
4个时钟源
,时钟源就是产生时钟信号(高低电平脉冲)的源头
HSI
:高速时钟 内部高速时钟源,是一个由电阻和电容构成 RC振荡器,可以产生8M HzHSE
:芯片外部的高速时钟,这个是通常接 外部石英晶振
,4~16M 之间,一般选择8M HzLSI
:芯片内部 ,40K Hz,会受温度影响LSE
:外部低速时钟,32.768K HzAHB时钟线
最高运行稳定可靠频率是72M Hz,时钟信号名字 HCLK 高速时钟
APB2时钟线
最高运行稳定可靠频率是72M Hz,时钟信号名字 PCLK2
APB1时钟线
最高运行稳定可靠频率是36M Hz,时钟信号名字 PCLK1
GPIOA GPIOB … GPIOG 所有的GPIO外设都挂在 APB2时钟线上
8个定时器 TIM1、TIM2 … TIM8 只有TIM1
、 TIM8
这2个高级定时器挂在APB2时钟线
,其余都在APB1是时钟线
5个串口 USART1、USART2 … UART5 只有USART1
挂在APB2时钟线
,其余在APB1 …
USB 时钟是由 PLLCLK 经过 USB 预分频器得到,分频因子可以是:[1,1.5],具体的由时钟配置寄存器 CFGR 的位 22:USBPRE 配置。USB 的时钟最高是 48M,根据分频因子反推 过 来 算 , PLLCLK 只 能 是 48M 或 者 是 72M 。 一 般 我 们 设 置 PLLCLK=72M ,USBCLK=48M。USB 对时钟要求比较高,所以 PLLCLK 只能是由 HSE 倍频得到,不能使用 HSI 倍频。
Cortex 系统时钟由 HCLK 8 分频得到,等于 9M,Cortex 系统时钟用来驱动内核的系统定时器 SysTick,SysTick 一般用于操作系统的时钟节拍,也可以用做普通的定时。
ADC 时钟由 PCLK2 经过 ADC 预分频器得到,分频因子可以是[2,4,6,8],具体的由时钟配置寄存器 CFGR 的位 15-14:ADCPRE[1:0]决定。很奇怪的是怎么没有 1 分频。ADC 时钟最高只能是 14M,如果采样周期设置成最短的 1.5 个周期的话,ADC 的转换时间可以达到最短的 1us。如果真要达到最短的转换时间 1us 的话,那 ADC 的时钟就得是 14M,反推PCLK2 的时钟只能是:28M、56M、84M、112M,鉴于 PCLK2 最高是 72M,所以只能取28M 和 56M。
RTC 时钟可由 HSE/128 分频得到,也可由低速外部时钟信号 LSE 提供,频率为32.768KHZ,也可由低速内部时钟信号 HSI 提供,具体选用哪个时钟由备份域控制寄存器BDCR的位9-8:RTCSEL[1:0]配置。独立看门狗的时钟由 LSI提供,且只能是由 LSI提供,LSI 是低速的内部时钟信号,频率为 30~60KHZ 直接不等,一般取 40KHZ。
MCO 是 microcontroller clock output 的缩写,是微控制器时钟输出引脚,在 STM32 F1系列中 由 PA8 复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。MCO 的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK,具体选哪个由时钟配置寄存器CFGR 的位 26-24:MCO[2:0]决定。除了对外提供时钟这个作用之外,我们还可以通过示波器监控 MCO 引脚的时钟输出来验证我们的系统时钟配置是否正确。
概念:GPIO
(General Purpose Input/Output):stm32 的 通用输入输出 端口,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能;
作用:负责采集外部器件的信息或者控制外部器件工作,即输入输出;
特性:
电气特性:
引脚类型:
电源引脚
:一般以V字母开头的可以认为是电源引脚;晶振引脚
:
复位引脚
:NRST下载引脚
:串口等BOOT引脚
:BOOT0、BOOT1GPIO引脚
:以P开头的 PA0~PA15…PG0-PG15该图从最右端看起,最右端就是代表 STM32 芯片引出的 GPIO 引脚,其余左边部件都位于芯片内部。
保护二极管
:
上拉、下拉电阻
:
TTL 施密特触发器
:
P-MOS 管和 N-MOS 管
:
控制
STM32 芯片的 I/O 引脚电平的高低来实现。GPIOA
、GPIOB
、GPIOC
… 至 GPIOG
共 7 组 GPIO,芯片一共 144 个引脚,其中 GPIO就占了 112个
,所有的 GPIO 引脚都有基本的输入输出功能。基本的输出
功能是由 STM32 控制引脚输出高、低电平,实现开关控制,如:把 GPIO引脚接入到 LED 灯,那就可以控制 LED 灯的亮灭(就是把 GPIO 的引脚连接到 LED 灯的阴极,LED 灯的阳极接电源,然后通过 STM32 控制该引脚的电平,从而实现控制 LED 灯的亮灭),引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。基本的输入
功能是检测外部输入电平,如把 GPIO 引脚连接到按键,通过电平高低区分按键是否被按下。要控制GPIO端口,就要涉及相关寄存器,参见《STM32参考手册》GPIO寄存器描述。
每个 GPIO端口
有:
关于寄存器名称如GPIOx-CRL,GPIOx_CRH上的标号x,其取值可以为图中括号内的值(A…E),表示这些寄存器也跟GPIO一样,是分组的。也就是说,对于端口GPIOA和GPIOB,它们都有互不相干的一组寄存器,如控制GPIOA的寄存器名为GPIOA-CRL,GPIOA-CRH等,而控制GPIOB的则是不同的被命名为GPIOB_CRL,GPIOB_CRH等的寄存器。
根据数据手册中列出的每个I/O端口的特定硬件特征,GPIO端口的每个位可以由软件分别配置成多种模式:
typedef enum
{
GPIO_Mode_AIN = 0x0, // 模拟输入
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入
GPIO_Mode_IPD = 0x28, // 下拉输入
GPIO_Mode_IPU = 0x48, // 上拉输入
GPIO_Mode_Out_OD = 0x14, // 开漏输出
GPIO_Mode_Out_PP = 0x10, // 推挽输出
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出
} GPIOMode_TypeDef;
每个I/O端口位可以自由编程,然而I/O端口寄存器必须按32位字被访问(不允许半字或字节访问)。GPIOx_BSRR和GPIOx_BRR寄存器允许对任何GPIO寄存器的读/更改的独立访问;
这样,在读和更改访问之间产生IRQ时不会发生危险。
电平信号
直接进入
输入数据寄存器。MCU 直接读取 I/O 口电平,I/O 的电平状态是不 确定
的,完全由外部输入决定;输出为 0
,则 P-MOS 管关闭,N-MOS管导通
,I/O 端口是低电平
,若控制 输出为 1
,则 P-MOS 管导通
, N-MOS 管关闭,I/O 端口就是高电平
,外部上拉和下拉的作用是控制在没有输出时 IO 口电平GPIO_Mode_Out_OD(out open drain),软件IIC的SDA、SCL等
在开漏输出模式时,只有 N-MOS 管工作(P-MOS管始终不被激活),
外部的上拉或者下拉决定
,如果没有上拉或者下拉 IO 口就处于悬空状态。并且此时 施密特触发器是打开
的,即 输入可用
,通过输入数据寄存器 GPIOx_IDR 可读取 I/O 的实际状态。I/O 口的电平不一定是输出的电平。一般应用在 I2C、SMBUS 通讯等需要"线与"功能的总线电路中。
GPIO_AF_OD(alternate function open drain),片上外设输出功能(硬件IIC的SDA、SCL引脚等)
输出数据寄存器 GPIOx_ODR 无效,输出的高低电平的来源于片上外设;
施密特触发器打开,输入可用,通过输入数据寄存器可获取 I/O 实际状态,除了输出信号的来源改变 其他与开漏输出功能相同。
为了使用GPIO,需要在STM32F10x代码中进行以下 步骤:
RCC_APB2PeriphClockCmd
函数 启用特定GPIO管脚的时钟信号
以使其变得可用。GPIO_InitTypeDef
结构体配置GPIO管脚的 输入/输出模式
、引脚速度
和 引脚上下拉控制
。GPIO_ReadInputDataBit
和 GPIO_SetBits
等函数读取或控制GPIO的引脚状态。#include "stm32f10x.h" //延时函数 void delay(unsigned int i) { while(i--) { int j=10000; while(j--); } } // 经过原理图分析,PE5接D1 输出低电平小灯(LED1 发光二极管)亮,高电平不亮 int main() {//RCC reset clock contrl //第一步,开相关片上外设时钟,这里GPIO,所有的GPIO外设都在APB2时钟线上 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE); //第二步,相关GPIO初始化 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //引脚号 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //高低电平输出切换速度,10M GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO工作模式。一共有8种,其中4种输入和4种输出模式,配置成通用推挽输出 GPIO_Init(GPIOE, &GPIO_InitStructure); while(1) { GPIO_ResetBits(GPIOE, GPIO_Pin_5); // 让PE5输出低电平 delay(800); GPIO_SetBits(GPIOE, GPIO_Pin_5); // 让PE5输出高电平灭 delay(800); } }
随机的事件
(外部或内部),CPU暂时停止正在运行的程序
,转去执行
一段 特殊的服务程序
(中断服务子程序或中断处理程序),以 处理该事件
,事件处理完
后 又 返回被中断的程序继续执行
,这一过程称为中断。引发中断的称为中断源。
中断的意义
:高效处理紧急程序,不会一直占用CPU资源。
STM32 中断非常强大,每个外设都可以产生中断,此处,异常就是中断,中断就是异常。
F103 在 内核
水平上搭载了一个异常响应系统, 支持为数众多的系统异常和外部中断。其中系统异常有 8 个(如果把 Reset 和 HardFault 也算上的话就是 10 个),外部中断有 60个。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。有关具体的系统异常和外部中断可在标准库文件 stm32f10x.h 这个头文件查询到,在 IRQn_Type 这个结构体里面包含了 F103 系列全部的异常声明。
NVIC
(Nested Vector Interrupt Controller)是 嵌套向量中断控制器
,控制着整个芯片中断相关的功能,
它跟内核
紧密耦合,是内核里面的一个外设;
NVIC支持:256个中断(16内核+240外部),支持256个优先级,允许裁剪,(STM32:内核10个+外部60个 中断,16个优先级);
各个芯片厂商在设计芯片的时候会对 Cortex-M3 内核里面的 NVIC 进行裁剪,把不需要的部分去掉,所以说 STM32 的 NVIC 是 Cortex-M3 的 NVIC 的一个子集。
在固件库中,NVIC的结构体定义可谓是颇有远虑,给每个寄存器都预留了很多位,恐怕为的是日后扩展功能。不过 STM32F103 可用不了这么多,只是用了部分而已,具体使用了多少可参考《 Cortex-M3 内核编程手册》-4.3.11:NVIC 寄存器映射。
NVIC 结构体定义,来自固件库头文件:core_cm3.h
typedef struct {
__IO uint32_t ISER[8]; // 中断使能寄存器
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; // 中断清除寄存器
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; // 中断使能悬起寄存器
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; // 中断清除悬起寄存器
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; // 中断有效位寄存器
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; // 中断优先级寄存器(8Bit wide)
uint32_t RESERVED5[644];
__O uint32_t STIR; // 软件触发中断寄存器
} NVIC_Type;
在配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中断,ICER 用来失能中断,IP 用来设置中断优先级。
固件库函数:
NVIC 库函数 | 描述 |
---|---|
void NVIC_EnableIRQ(IRQn_Type IRQn) | 使能中断 |
void NVIC_DisableIRQ(IRQn_Type IRQn) | 失能中断 |
void NVIC_SetPendingIRQ(IRQn_Type IRQn) | 设置中断悬起位 |
void NVIC_ClearPendingIRQ(IRQn_Type IRQn) | 清除中断悬起位 |
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn) | 获取悬起中断编号 |
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) | 设置中断优先级 |
uint32_t NVIC_GetPriority(IRQn_Type IRQn) | 获取中断优先级 |
void NVIC_SystemReset(void) | 系统复位 |
抢占优先级(pre)
:高抢占优先级可以打断正在执行的低抢占优先级中断响应优先级(sub,子)
:当抢占优先级相同时,响应优先级高的先执行,但是不能互相打断自然优先级
:中断向量表的优先级,数值越小,表示优先级越高在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx,用来配置外部中断的优先级,IPR 宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是绝大多数 CM3芯片都会精简设计,以致实际上支持的优先级数减少,在 F103中,只使用了高 4bit。
优先级的分组由内核外设 SCB 的应用程序中断及复位控制寄存器 AIRCR 的PRIGROUP[10:8]位决定,F103 分为了 5 组,具体如下:主优先级=抢占优先级
主优先级位:表示抢占优先级的位数
子优先级位:表示响应优先级的位数
特别提示:一个工程中,一般只设置一次中断优先级分组。
判断执行顺序:抢占 > 响应 > 自然优先级
设置优先级分组可调用库函数 NVIC_PriorityGroupConfig()
实现,有关 NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中。
中断优先级分组库函数 NVIC_PriorityGroupConfig() :
/** * 配置中断优先级分组:抢占优先级和子优先级 * 形参如下: * @arg NVIC_PriorityGroup_0: 0bit for 抢占优先级 * 4 bits for 子优先级 * @arg NVIC_PriorityGroup_1: 1 bit for 抢占优先级 * 3 bits for 子优先级 * @arg NVIC_PriorityGroup_2: 2 bit for 抢占优先级 * 2 bits for 子优先级 * @arg NVIC_PriorityGroup_3: 3 bit for 抢占优先级 * 1 bits for 子优先级 * @arg NVIC_PriorityGroup_4: 4 bit for 抢占优先级 * 0 bits for 子优先级 * @注意 如果优先级分组为 0,则抢占优先级就不存在,优先级就全部由子优先级控制 */ void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) { // 设置优先级分组 SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup; }
优先级分组真值表:
优先级分组 | 主优先级 | 子优先级 | 描述 |
---|---|---|---|
NVIC_PriorityGroup_0 | 0 | 0-15 | 主-0bit,子-4bit |
NVIC_PriorityGroup_1 | 0-1 | 0-7 | 主-1bit,子-3bit |
NVIC_PriorityGroup_2 | 0-3 | 03 | 主-2bit,子-2bit |
NVIC_PriorityGroup_3 | 0-7 | 0-1 | 主-3bit,子-1bit |
NVIC_PriorityGroup_4 | 0-15 | 0 | 主-4bit,子-0bit |
① 使能外设某个中断,这个具体由每个外设的相关中断使能位控制。
② 设置中断优先级分组,初始化 NVIC_InitTypeDef
结构体:
typedef struct
{
uint8_t NVIC_IRQChannel; //中断源
uint8_t NVIC_IRQChannelPreemptionPriority://抢占优先级
uint8_t NVIC_IRQChannelSubPriority://响应优先级
FunctionalStale NVIC_IRQChannelCmd://中断使能或失能
} NVIC_InitTypeDef;
③ 编写中断服务函数
startup_stm32f10x_hd.s
中我们预先为每个中断都写了一个中断服务函数(264行往下
),只是这些中断函数都是为空,为的只是初始化中断向量表。实际的中断服务函数都需要我们重新编写,为了方便管理我们可以把中断服务函数统一写在 stm32f10x_it.c
这个库文件中。接收:接收字符,当串口1每收到1个字节, CPU就会自动取调用对应的中断处理函数,将接收到的字符转小写,并发送;
发送:固定发送字符串。
#include "stm32f10x.h" void delay(unsigned int i) { while(i--) { int j = 10000; while(j--); } } void Usart1_SendStr(char *pstr) { while(*pstr != '\0') { USART_ClearFlag(USART1,USART_FLAG_TC); //清除发送完成标志,为下一个字节的发送做好判断准备工作 USART_SendData(USART1, *pstr); pstr++; while(0 == USART_GetFlagStatus(USART1, USART_FLAG_TC)); //只要没有发送完成, USART_GetFlagStatus函数返回0,CPU就卡在这里 } } void MyUsart_Init() { // 1、开相关片上外设的时钟,GPIOA USART1(串口1) PA9是TX PA10是RX RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // 2、相关GPIO口 初始化 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 AF 复用 Alternate functions GPIO_Init(GPIOA, &GPIO_InitStructure); // 3、先关片上外设初始化 USART1 // 波特率、数据位、停止位、是奇还是偶校验位、是否由硬件流构成、输入输出模式 // 115200,8个数据位,1个停止位,没有奇偶校验,没有硬件控制流 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 115200; //反应每秒传输数据的bit数目,单位 bps bit per second 比特每秒 USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 增加接收功能 USART_Init(USART1, &USART_InitStructure); // =============如果让 串口1增加接收功能=====begin===== // 设置优先级分组 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //初始化NVIC结构体 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 配置串口1为中断源 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级 NVIC_Init(&NVIC_InitStructure); //当串口1每收到1个字节, CPU就会自动取调用对应的中断处理函数 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //这个函数类似Qt connect函数把某一个中断事件和对应中断处理函数关联起来 // =============如果让 串口1增加接收功能=====end===== // USART1使能,即启动串口 USART_Cmd(USART1,ENABLE); } //串口1中断接收函数 IT(interrupt) void USART1_IRQHandler() //函数名不能乱写,一定要这个函数名,从启动文件264行往下找复制出来重写 {//当串口1关联的事件发送,CPU就会自动来调用这个函数 if(USART_GetITStatus (USART1, USART_IT_RXNE)) {//当因为 串口1 收到1个字节,CPU就会进入到这里 u16 RxData; RxData = USART_ReceiveData (USART1);//读取刚刚收到的那个字节 USART_SendData (USART1, RxData+32); //收到大写字母,把对应小写字母发送出去 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } //通过串口1 发送一个字符发送到计算机 int main() { MyUsart_Init(); while(1) { Usart1_SendStr("welcome to 嵌入式\n"); delay(1000); } }
EXTI(External interrupt/event controller)
:外部中断/事件控制器
,管理了控制器的 20个中断/事件线。
每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。
特性:
EXTI 可分为两大部分功能,产生中断、产生事件,这两个功能从硬件上就有所不同。
首先我们来看上图中红色虚线指示的电路流程。它是一个 产生中断的线路,最终信号流入到 NVIC 控制器内。
输入线
,EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件。输入线一般是存在电平变化的信号。边沿检测电路
,它会根据 上升沿触发选择寄存器
(EXTI_RTSR) 和 下降沿触发选择寄存器
(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到 有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号0。而 EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。或门电路
:
软件中断事件寄存器
(EXTI_SWIER)。与门电路
:
挂起寄存器(EXTI_PR)
内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 1。输出到 NVIC 内
,从而实现系统中断事件控制。接下来我们来看看绿色虚线指示的电路流程。它是一个产生事件的线路,最终输出一个脉冲信号。 产生事件线路是在编号 3 电路之后与中断线路有所不同,之前电路都是共用的。
与门
:
脉冲发生器电路
,
脉冲信号
,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如 定时器 TIM
、模拟数字转换器 ADC
等等,这样的脉冲信号 一般用来触发 TIM 或者 ADC 开始转换。EXTI 有 20 个中断/事件线,每个 GPIO 都可以被设置为输入线,占用 EXTI0 至 EXTI15,还有另外4根用于特定的外设事件,如下表。
4根特定外设中断/事件线由外设触发,具体用法参考《STM32F10X-中文参考手册》中对外设的具体说明。
EXTI 0 至 EXTI 15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输入源。
标准库函数对每个外设都建立了一个初始化结构体,比如 EXTI_InitTypeDef
,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 EXTI_Init()
调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。
typedef struct {
uint32_t EXTI_Line; // 中断/事件线
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式
EXTITrigger_TypeDef EXTI_Trigger; // 触发类型
FunctionalState EXTI_LineCmd; // EXTI 使能
} EXTI_InitTypeDef;
EXTI_Line
:EXTI 中断/事件线选择,可选 EXTI0 至 EXTI19,可参考上表 选择。EXTI_Mode
:EXTI 模式选择,可选为产生中断(EXTI_Mode_Interrupt)或者产生事件(EXTI_Mode_Event)。EXTI_Trigger
:EXTI 边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下降 沿 触 发 ( EXTI_Trigger_Falling) 或 者 上 升 沿 和 下 降 沿 都 触 发( EXTI_Trigger_Rising_Falling)。EXTI_LineCmd
:控制是否使能 EXTI 线,可选使能 EXTI 线(ENABLE)或禁用(DISABLE)。AFIO:Alternate Function IO,即复用功能IO,主要用于重映射
和外部中断映射配置
外部中断配置:AFIO_EXTICR1~4
,配置EXTI中断线0~15对应到哪个具体IO口
原理图:
KEY1 PE3 按钮按下低电平,设置输入线路下降沿为中断请求,即检测到下降沿触发中断,按下直至松手为一次
代码:
#include "stm32f10x.h" void delay(unsigned int i) { while(i--) { int j = 10000; while(j--); } } void Usart1_SendStr(char *pstr) { while(*pstr != '\0') { USART_ClearFlag(USART1,USART_FLAG_TC); //清除发送完成标志,为下一个字节的发送做好判断准备工作 USART_SendData(USART1, *pstr); pstr++; while(0 == USART_GetFlagStatus(USART1, USART_FLAG_TC)); //只要没有发送完成, USART_GetFlagStatus函数返回0,CPU就卡在这里 } } void MyUsart_Init() { // 1、开相关片上外设的时钟,GPIOA USART1(串口1) PA9是TX PA10是RX RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // 2、相关GPIO口 初始化 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 AF 复用 Alternate functions GPIO_Init(GPIOA, &GPIO_InitStructure); // 默认所有的管脚是 浮空输入模式 GPIO_InitTypeDef GPIO_InitStructure1; GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure1); // 3、先关片上外设初始化 USART1 // 波特率、数据位、停止位、是奇还是偶校验位、是否由硬件流构成、输入输出模式 // 115200,8个数据位,1个停止位,没有奇偶校验,没有硬件控制流 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 115200; //反应每秒传输数据的bit数目,单位 bps bit per second 比特每秒 USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 增加接收功能 USART_Init(USART1, &USART_InitStructure); // =============如果让 串口1增加接收功能=====begin===== NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 串口1中断源 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure); //当串口1每收到1个字节, CPU就会自动取调用对应的中断处理函数 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //这个函数类似Qt connect函数把某一个中断事件和对应中断处理函数关联起来 // =============如果让 串口1增加接收功能=====end===== // USART1使能,即启动串口 USART_Cmd(USART1,ENABLE); } //串口1中断接收函数 IT(interrupt) void USART1_IRQHandler() //函数名不能乱写,一定要这个函数名,从启动文件264行往下找复制出来重写 {//当串口1关联的事件发送,CPU就会自动来调用这个函数 if(USART_GetITStatus (USART1, USART_IT_RXNE)) {//当因为 串口1 收到1个字节,CPU就会进入到这里 // unsigned char Revdata= //typedef uint16_t u16; //typedef unsigned short int uint16_t; u16 RxData; RxData = USART_ReceiveData (USART1);//读取刚刚收到的那个字节 USART_SendData (USART1, RxData+32); //收到大写字母,把对应小写字母发送出去 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } // 轮询(查询)法 //WKUP PA0 按钮按下高电平 //KEY0 PE4 按钮按下低电平 unsigned char WK_Up_Key0_Poll_Init() { } //中断法 // KEY1 PE3 按钮按下低电平 void Key1_Inter_Init() { //第一步开相关片上外设时钟,GPIOE,外部引脚中断,还需要开一个AFIO时钟,AFIO这个片上外设也是在APB2时钟线上 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO,ENABLE); //第二步,相关GPIO口初始化,填参数有3种方法,通过函数注释提示、通过函数体开头部分参数检查、通过实参类型定义找到 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU; GPIO_InitStruct.GPIO_Pin=GPIO_Pin_3; GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz; GPIO_Init(GPIOE,&GPIO_InitStruct); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);//配置外部中断线 //第三步 中断源NVIC配置 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel=EXTI3_IRQn;//外部中断,外部中断源3 NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=0; NVIC_Init(&NVIC_InitStructure); //第四部相关片上外设初始化 EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.EXTI_Line=EXTI_Line3;//外部中断线3 EXTI_InitStruct.EXTI_LineCmd=ENABLE; EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;//中断模式,不是事件模式 EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;//中断触发事件,下降沿信号 EXTI_Init(&EXTI_InitStruct); } unsigned int Key1_Val=0; char Key1_buf[100]={0}; #include "stdio.h" void EXTI3_IRQHandler() { if(EXTI_GetITStatus(EXTI_Line3)) {//CPU进入到这里 delay(3);//一般超过10ms,避开抖动区 if(0 == GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)) {//如果CPU进入到这里,说明过了10ms,PE3检测到任然是按钮按下状态,说是一次真实可靠按下 Key1_Val++; sprintf(Key1_buf,"Key1_PE3 中断按钮被按下 Key1_Val=%d\n",Key1_Val); Usart1_SendStr(Key1_buf); } EXTI_ClearITPendingBit(EXTI_Line3); } } // 通过串口1 发送1个字符到计算机 int main() { MyUsart_Init(); Key1_Inter_Init(); while(1) { } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。