一个外部中断的练手项目:基于STM32F103C8T6的红外对射计数器

本文作者分享了从零开始使用STM32F103C8T6单片机的实践经历,包括硬件选型(OLED屏幕和红外传感器)、软件部分的工程模板搭建、库函数集成以及中断处理流程,展示了如何配置RCC、GPIO、AFIO和NVIC来实现基本功能。
摘要由CSDN通过智能技术生成

前言

在学校里学了一些单片机有关的知识之后,发现单靠这些实践起来非常的艰难......。单片机的开发也有很多种方式,有的用STM32MxCube用Hal库开发、有的寄存器开发。我因为想着从最基础做起,把单片机的原理能理解的更加细致和深刻,用于是打算自己从零开始,用库函数开发的方式做一个小项目来把单片机开发的流程走一遍,真的是个非常简单的小项目,只能说献丑了。如果对文章有任何问题都可以评论或者私信问我,探讨和解答问题也是高效学习的方法之一。

硬件设计

初步设想是用一块面包板,一块STM32F103C8T6的核心板,一块0.96英寸OLED屏幕,一个对射式红外传感器组成整个设计。传感器和OLED屏幕的接口参考ST官方的参考手册去选择。

对射式红外传感器:

VCC接3.3V;

GND接GND;

DO口(数字信号口)接PB14;

OLED屏幕:

VCC接3.3V;

GND接GND;

SCL接PB8;

SDA接PB9;

软件部分设计:

既然是从零开始,那就从工程模板建起吧。

工程模板的建立

建个新的文件夹:

打开keil5,新建Project,选择芯片和路径:

建立好的工程是缺很多东西的,我们需要在项目文件夹内新建Start文件夹存放必要配置文件、函数Library文件夹来存放库函数和他们的头文件、User文件夹放main函数、Hardware文件夹存放外设的配置函数。

然后打开st官方固件库,按路径找到这个文件夹,里面是初始化的文件,复制到工程模板的Start下面:

还有这几个:

这样我们STM32的必备文件就准备好了,之后

把Source Group 1改名为Start,然后右键选择添加已存在的文件,文件类型选择all files,把所有的.h和.c格式的文件都Add一下,.s格式的文件,这个芯片选用的是md.s结尾的那一个。

完成之后:

当然别忘记在工程选项(就是那个魔法棒)里添加上文件夹的路径,不然会无法获取到正确的文件信息。

下一步,在工程里面添加User文件,写个main函数来验证一下。

选择c语言,name:main,路径也别忘记更改,选择一下,放在第一步建好的User下面:

在User里面添加一个主函数main,插入头文件,写一个循环,试着编译运行一下:

没有报错,说明目前到这里还没有出错。下一步就是添加库函数了。

建立Library,ST官方提供了很多已经封装好的库函数供开发者使用,在固件库里就能找到,我就把他们都复制过来了:

在固件库里找到st官方封装好的库函数,.h和.c文件都复制过来:

与之前一样的步骤,把库函数也都添加进工程里。

当然还有一些必要的文件需要添加到User目录下。这里我一开始以为是添加进Library里面,卡了好一会儿,属实呆。

找到以下文件:

con.h是用来配置库函数头文件的包含关系以及用参数来检查函数定义的,两个it是用来存放中断函数的。找到之后复制到User目录下,并且也添加进工程里。最后还需要进行宏定义,在"stm32f10x.h"  里找到:

这是个条件编译,需要定义了才有用,所以我们需要把USE_STDPERIPH_DRIVER复制到这里:

然后把所有新建的文件路径都添加进我们的工程里,再编译一下:

是没错的,妙啊,工程文件就建立好了。接下来就是写函数了。

围绕中断的函数编译

整个流程涉及到RCC时钟控制、GPIO配置、AFIO配置、EXTI中断、NVIC配置。

通过STM32F103的参考手册可以知道它一共有60个中断通道,也就是有60个中断源可以使用。包括EXTI外部中断、ADC数模转换器、TIM定时器,还有USART、SPI、I2C、RTC实时时钟之类的。几乎所有的模块都能申请中断。

GPIO连接到AFIO(中断引脚选择器)之上,每个GPIO有16条线可以选择,而AFIO是可以在GPIO的16个引脚里面选择其中一个,连接到后面的EXTI通道里。

EXTI(边沿检测)可以检测通过AFIO选择之后的GPIO口信号,检测到产生的变化之后,可以向后面的NVIC发出中断申请。

NVIC(嵌套中断向量控制器)就像个管理员,它是负责统一管理中断、分配优先级的,这里就涉及到中断优先级了,在本项目用到的芯片里,每个中断都有16个可以编程的优先级,还可以进行围绕抢占优先级和响应优先级的分组。

所以函数设计部分整体的流程就是:先通过RCC开启需要开启的时钟,然后配置GPIO、AFIO、EXTI、NVIC。然后再写一个OLED显示屏的程序,内容是抓取中断触发的次数并且显示在屏幕上。

我们可以把所有传感器的模块封装在一个函数里,先建立一下这个,取名为Counter,添加到Hardware文件下,流程和之前建立工程模板时一样,右键添加,选择c语言,重命名并且把路径放在Hardware文件下。在把OLED屏幕的函数添加进来,也别忘了添加路径,更新后的Hardware文件如图所示:

Counter.c的代码我也发在下面,做一些简单的介绍吧,具体怎么写的我就不一步步阐述了。

因为我们选择的是把红外对射接在PB14上,那RCC时钟的开启的时候参数就要设置为APB2的GPIOB,开启当然就是ENABLE。AFIO也是一样,EXTI和NVIC不用开启时钟。

对GPIO的配置去到每个函数的定义里都有很详细的说明。先是结构体,然后结构体内的三个参数:输入模式、引脚选择以及数据传输速度一一配置就好了。

这个结构体的配置方法在后面的EXTI和NVIC里都会用到,步骤非常一致。然后用GPIO_Init对配置好的结构体取地址进行调用。AFIO就是很简单的选择一下PB14就行。

EXTI和NVIC配置也是和GPIO一样,具体的配置在注释里面也都标明了。值得一提的是我在NVIC的响应优先级和抢占优先级的模块分组上选择了PriorityGroup_2,按照定义,抢占优先级和响应优先级的取值范围都是0-3,所以我在后面的NVIC_IRQChannelPreemptionPriorityNVIC_IRQChannelSubPriority后面都设置为1。

关于第一个指定通道NVIC_IRQChannel 的选择,还记得之前配置工程时候选择的md.s吗?这里需要去到stm32f10x.h里面,找到这个:

在这里面找到EXTI15_10_IRQn。因为我们用的是14,所以参数选择这个。

关于结构体内的参数是如何选择的,如果还有问题可以评论或者私信。

外部中断流程配置完毕之后,脑子里会有一个具象的信号流向,一个中断信号从GPIO到AFIO到EXTI到NVIC,最后肯定是走向CPU,那么还要写一个通向CPU的中断函数来执行中断程序就能走完整个流程,而在STM32中,中断函数是已经确定了的,函数名是已经设置好了的,每个中断通道都对应一个中断函数,这个可以在startup_stm32f10x_md.s里面找,我们选择的是14,所以函数名就是EXTI15_10_IRQHandler。注意这个不能自己去更改,不然CPU就无法获取到中断。

因为这个函数里面,10到15号引脚都能触发中断,所以为了确保是我们想要的中断源触发的中断函数我们需要再在中断函数里进行中断标志位的判断,而这个判断的函数,ST也给我们封装好了(感恩)。叫EXTI_GetITStatus。参数就是你选用的那条中断源,这里就是14。写一个判断,判断返回值是不是为set,如果说返回值能对上,说明确实是14号引脚触发的中断,程序就可以执行中断函数。

然后,写一个Counter_Get函数来获取中断触发的次数。先写一个参数,类型是uint16_t 参数名为Counter_Count。中断触发一次,它就自加1。然后通过Get函数返回这个变量。

最后呢,在主函数里面,先include一下我们写的Counder.h,再在主循环前面调用一下我们的Counter_Init。最后在while循环里的OLED_ShowNum里面调用一下这个返回值,就行了。

我把Counter.c和.h还主函数都放在下面:

```c
#include "stm32f10x.h"                  // Device header


void Counter_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIO的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //开启AFIO的时钟
	//EXTI的时钟不用开启(但是不知道为什么) NVIC作为内核外设,也不用开启时钟,时钟开启完毕
	
	//接下来配置GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//对于外部中断一般是三个输入之一,不知道的可以看st官方的参考手册
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	//接下来配置AFIO
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//两个参数是选择哪个GPIOpin作为外部中断线
	
	//现在走的线路就是从GPIOB_PIN_14到AFIO_PIN_14,之后输出端再固定EXTI的第14个中断线路
	//接下来初始化EXTI,配置外部中断
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;//开启中断
	EXTI_InitStructure.EXTI_Line = EXTI_Line14; //第14个线路
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//作为外部中断	
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	//现在走的线路就是从GPIOB_PIN_14到AFIO_PIN_14,走EXTI的第14个中断线路,走到NVIC了
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	//这个是配置抢占优先级和响应优先级的分组。
	//这个分组可以在模块里分组也可以在主函数里分组
	//如果在主函数里分组了就不用在每个模块函数里分组,如果没有的话就要每个函数都进行分组且需要统一
	NVIC_InitTypeDef NVIC_InitSturcture;
	NVIC_InitSturcture.NVIC_IRQChannel = EXTI15_10_IRQn;
	NVIC_InitSturcture.NVIC_IRQChannelCmd = ENABLE ;
	NVIC_InitSturcture.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitSturcture.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitSturcture);
}

//需要一个数字来统计中断函数触发的次数
uint16_t Counter_Count;
//写一个Get模块函数
uint16_t Counter_Get(void)
{
	return  Counter_Count;
}

void EXTI15_10_IRQHandler(void)//函数名是确定的,在Start的startup...里面找,这个写错了就进不来中断了。
{
	if (EXTI_GetITStatus(EXTI_Line14) == SET)
	{
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
		{
			Counter_Count ++;
		}
		EXTI_ClearITPendingBit(EXTI_Line14);//一定要清除中断标志位,不然运行一次就会执行一次中断,卡在中断里出不去了。
	}
}


	
	
	
	
	
	

头文件Counter.h

```c
#ifndef __COUNTER_H
#define __COUNTER_H

void Counter_Init(void);
uint16_t Counter_Count;
uint16_t Counter_Get(void);


#endif

主函数:

```c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Counter.h"

int main(void)
{
	OLED_Init();
	Counter_Init();
	
	OLED_ShowString(1, 1, "Count:");
	
	while (1)
	{
		OLED_ShowNum(1, 7, Counter_Get(), 5);
	}
}

做好之后大概是这样的:

结尾

或许大佬都能看出来,我的很多措辞和表达都充满了浓浓的小白气息,而且全文也肯定存在一些问题。我也是在自己慢慢学习和摸索,如果有什么问题都可以指正,只要能进步,我就不玻璃心。

大概就是这样。

  • 23
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值