1.目标
通过以前的学习,学习到了基础的GPIO输入和输出以及串口,接下来,通过一个小demo来学习引入外部中断。也就是通过按键设置外部中断来进行按键状态的读取,按键按下,LED灯亮起,按键松开,LED熄灭。
2.中断
2.1什么是中断
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
2.2中断的流程
1.设置中断源,允许指定的中断发生;
2.设置中断控制器,例如屏蔽指定中断,例如设置允许发生的中断的优先等级;
3.设置CPU中断总开关,使能中断; 执行一般程序;
4.产生中断,硬件将中断信号发送给中断控制器,中断控制器再发给CPU让CPU知道发生了什么中断;
5.CPU每执行完一句指令,都会去检查有无异常中断产生; 如果发现有异常中断发生,那么就会去处理异常中断;
2.3ARM对中断的处理
1.保存现场:将一般程序的当前执行地址、变量等保存到栈中;
2.处理异常:通过异常中断信号跳转到保存在异常向量表中的中断服务函数开始执行中断;
3.恢复现场:执行完中断处理程序之后,从栈中读取之前保存的数据,跳转到原本的执行地址开始执行一般程序;
2.4 中断优先级
1.优先级数值越低,被执行的优先等级就越高;
2.优先级分为:抢占优先级和子优先级; 抢占优先级数值低的中断可以打断正在执行的抢占优先级数值高的中断;
3.子优先级只能决定同一时间产生的多个相同抢占优先级中断的执行顺序;
4.当两个优先级数值一样的(不管是抢占还是子优先级)中断同时产生,那么则根据在异常向量表中的位置决定执行先后,越靠前的中断先被执行;
3.分析
首先,在这个demo里,需要涉及到的是LED和按键,按键涉及到外部中断,其中外部中断有多种模式,因为需要实现的目的是按下和松开都变化状态,所以采用双边沿触发,也就是上升沿和下降沿都触发。虽然这个demo里,单片机只处理了这一件事情,但是我们要想象当单片机的处理器在运行时,双边沿触发外部中断,外部中断有对应的回调函数,这个demo里要做的就是,触发中断,读取按键的状态,然后控制LED的亮灭,按键按下,LED亮,按键松开,LED灭。
4.创建工程
4.1配置老三样
4.2配置LED和按键
设置PA0-PA3为外部中断模式,然后双边沿触发,设置PB12-PB15为输出模式,默认输出高电平,也就是默认LED初始是熄灭的状态。
4.3配置NVIC
使能外部中断。
5.代码
5.1重构按键的代码
引入外部中断,所有我们需要重新写Read函数,将正常的按键Read函数修改成GPIODrvIrqRead函数,写一个全局变量state作为标志位。
#define K1 \
{ \
.name = "K1", \
.port = GPIOA,\
.pin = GPIO_PIN_0, \
.Init = GPIODrvInit, \
.Write = NULL, \
.Read = GPIODrvIrqRead, \
.next = NULL \
}
static GPIO_PinState state = 1;
static int GPIODrvIrqRead(struct GPIODev *ptdev)
{
if(NULL == ptdev) return -EINVAL;
return state;
}
5.2回调函数
在外部中断的函数里,所有的外部中断公用一个回调函数,但是是通过输入的GPIO_Pin来区分是哪个外部中断,以及执行什么样的中断处理。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case GPIO_PIN_0: // PA0--K1
{
state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
break;
}
case GPIO_PIN_1: // PA1--K2
{
break;
}
case GPIO_PIN_2: // PA2--K3
{
break;
}
case GPIO_PIN_3: // PA3--K4
{
break;
}
default:break;
}
}
5.3 测试函数
#include "devices.h"
#include "errno.h"
#include "mytype.h"
void app_key_test(void)
{
IODevicesRegister();
GPIODevice *pK1 = IODeviceFind("K1");
if(NULLDEV == pK1) return;
GPIODevice *pD1 = IODeviceFind("D1");
if(NULLDEV == pD1) return;
if(pK1->Init(pK1) != ESUCCESS) return;
if(pD1->Init(pD1) != ESUCCESS) return;
while(1)
{
int status = pK1->Read(pK1);
pD1->Write(pD1, status);
}
}
5.4 main函数
#include "main.h"
#include "usart.h"
#include "gpio.h"
void SystemClock_Config(void);
extern void app_key_test(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
app_key_test();
while (1)
{
}
}
5.5逻辑分析
首先初始化HAL库,初始化系统时钟,初始化GPIO,初始化串口(可以忽略不计)。然后调用app_key_test(),在app_key_test()函数中,首先寻找K1和D1设备,也就是按键1和LED1,接着对D1和K1进行初始化,然后轮询读取K1的状态,写一个全局变量state作为标志位,默认为1,也就是LED熄灭,当按键按下的时候,PA0口的状态为0,则LED亮,当按键松开的时候,PA0口的状态为1,则LED熄灭。
6.环形缓冲区
6.1什么是环形缓冲区
环形缓冲区是一个先进先出(FIFO)的闭环的存储空间。通俗的理解为,在内存中规划了一块“圆形”的地,将该“圆形”进行N(Ring Buffer的大小)等分。
6.2 ring_buffer.c
#include "errno.h"
#include "libs.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
static int RingBufferWrite(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length);
static int RingBufferRead(struct RingBuffer *ptbuf, unsigned char *dst, unsigned int length);
static int RingBufferClear(struct RingBuffer *ptbuf);
static int RingBufferFree(struct RingBuffer *ptbuf);
/*
函数名:RingBufferNew
功能:初始化一个指定的环形缓冲区
输入参数:ength->表示缓冲区分配的内存大小,单位是字节
输出参数:无
返回值:NULL->表示错误;ptbuf->表示成功得到一个buffer
*/
struct RingBuffer *RingBufferNew(unsigned int length)
{
struct RingBuffer *ptbuf;
if(0 == length) return NULL;
ptbuf = (struct RingBuffer*)malloc(sizeof(struct RingBuffer));
if(NULL == ptbuf) return NULL;
if(NULL != ptbuf->info.pHead)
{
free(ptbuf->info.pHead);
}
ptbuf->info.pHead = (unsigned char*)malloc(length);
if(NULL == ptbuf->info.pHead)
{
xprintf("Error. Malloc %d bytes failed.\r\n", length);
return NULL;
}
ptbuf->info.pValid = ptbuf->info.pValidEnd = ptbuf->info.pHead;
ptbuf->info.pEnd = ptbuf->info.pHead + length;
ptbuf->info.nValidLength = 0;
ptbuf->info.nBufferLength = length;
ptbuf->Write = RingBufferWrite;
ptbuf->Read = RingBufferRead;
ptbuf->Clear = RingBufferClear;
ptbuf->Free = RingBufferFree;
return ptbuf;
}
static int RingBufferFree(struct RingBuffer *ptbuf)
{
if(ptbuf == NULL) return -EINVAL;
if(ptbuf->info.pHead==NULL) return -EINVAL;
free((unsigned char*)ptbuf->info.pHead);
ptbuf->info.pHead = NULL;
ptbuf->info.pValid = NULL;
ptbuf->info.pValidEnd = NULL;
ptbuf->info.pEnd = NULL;
ptbuf->info.nValidLength = 0;
free((struct RingBuffer *)ptbuf);
return ESUCCESS;
}
static int RingBufferWrite(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length)
{
unsigned int len1 = 0, len2 = 0;
unsigned int move_len = 0;
if(length > ptbuf->info.nBufferLength)
{
return -EINVAL;
}
if(ptbuf->info.pHead==NULL)
{
return -EINVAL;
}
// copy buffer to pValidEnd
if( (ptbuf->info.pValidEnd + length) > ptbuf->info.pEnd ) // 超过了Buffer范围需要分为两段
{
len1 = (unsigned)(ptbuf->info.pEnd - ptbuf->info.pValidEnd);
len2 = length - len1;
memcpy((unsigned char*)ptbuf->info.pValidEnd, src, len1);
memcpy((unsigned char*)ptbuf->info.pHead, src + len1, len2);
ptbuf->info.pValidEnd = ptbuf->info.pHead + len2; // 更新有效数据区尾地址
}
else
{
memcpy((unsigned char*)ptbuf->info.pValidEnd, src, length);
ptbuf->info.pValidEnd = ptbuf->info.pValidEnd + length;
}
// 重新计算已使用区的起始位置
if( (ptbuf->info.nValidLength + length) > ptbuf->info.nBufferLength ) // 要写入的数据超过了缓冲区总长度,分为两段写
{
move_len = ptbuf->info.nValidLength + length - ptbuf->info.nBufferLength;
if( (ptbuf->info.pValid + move_len) > ptbuf->info.pEnd )
{
len1 = (unsigned)(ptbuf->info.pEnd - ptbuf->info.pValid);
len2 = move_len - len1;
ptbuf->info.pValid = ptbuf->info.pHead + len2;
}
else
{
ptbuf->info.pValid = ptbuf->info.pValid + move_len;
}
ptbuf->info.nValidLength = ptbuf->info.nBufferLength;
}
else
{
ptbuf->info.nValidLength = ptbuf->info.nValidLength + length;
}
return (int)length;
}
static int RingBufferRead(struct RingBuffer *ptbuf, unsigned char *dst, unsigned int length)
{
unsigned int len1 = 0, len2 = 0;
if(ptbuf->info.pHead==NULL) return -EINVAL;
if(ptbuf->info.nValidLength==0) return -ENOMEM;
if(length > ptbuf->info.nValidLength)
{
length = ptbuf->info.nValidLength;
}
if( (ptbuf->info.pValid + length) > ptbuf->info.pEnd )
{
len1 = (unsigned int)(ptbuf->info.pEnd - ptbuf->info.pValid);
len2 = length - len1;
memcpy(dst, (unsigned char*)ptbuf->info.pValid, len1);
memcpy(dst + len1, (unsigned char*)ptbuf->info.pHead, len2);
ptbuf->info.pValid = ptbuf->info.pHead + len2;
}
else
{
memcpy(dst, (unsigned char*)ptbuf->info.pValid, length);
ptbuf->info.pValid = ptbuf->info.pValid + length;
}
ptbuf->info.nValidLength -= length;
return (int)length;
}
static int RingBufferClear(struct RingBuffer *ptbuf)
{
if(ptbuf == NULL) return -EINVAL;
if(ptbuf->info.pHead==NULL) return -EINVAL;
if(ptbuf->info.pHead != NULL)
{
memset(ptbuf->info.pHead, 0, ptbuf->info.nBufferLength);
}
ptbuf->info.pValid = ptbuf->info.pValidEnd = ptbuf->info.pHead;
ptbuf->info.nValidLength = 0;
return ESUCCESS;
}
6.2 ring_buffer.h
#ifndef __RING_BUFFER_H
#define __RING_BUFFER_H
typedef struct RingBuffInfo{
unsigned char *pHead;
unsigned char *pEnd;
unsigned char *pValid;
unsigned char *pValidEnd;
unsigned int nBufferLength;
unsigned int nValidLength;
}RingBuffInfo;
typedef struct RingBuffer{
RingBuffInfo info;
int (*Write)(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length);
int (*Read)(struct RingBuffer *ptbuf, unsigned char *dst, unsigned int length);
int (*Clear)(struct RingBuffer *ptbuf);
int (*Free)(struct RingBuffer *ptbuf);
struct RingBuffer *next;
}RingBuffer;
struct RingBuffer *RingBufferNew(unsigned int length);
#endif /* __DRV_BUFFER_H */
7.采用了环形缓冲区后的代码
7.1重构代码
在GPIODev里加入value代表GPIO当时的状态。
typedef struct GPIODev{
char *name;
void *port;
unsigned int pin;
unsigned char value;
int (*Init)(struct GPIODev *ptdev);
int (*Write)(struct GPIODev *ptdev, unsigned char status);
int (*Read)(struct GPIODev *ptdev);
struct GPIODev *next;
}GPIODevice;
static RingBuffer *gK1Buffer = NULL;
static int GPIODrvInit(struct GPIODev *ptdev)
{
if(NULL == ptdev) return -EINVAL;
if(strstr(ptdev->name, "K1"))
{
gK1Buffer = RingBufferNew(sizeof(unsigned char) * 10);
if(NULL == gK1Buffer) return -EIO;
}
return ESUCCESS;
}
static int GPIODrvIrqRead(struct GPIODev *ptdev)
{
if(NULL == ptdev) return -EINVAL;
if(strstr(ptdev->name, "K1"))
{
if(NULL != gK1Buffer)
{
int ret = gK1Buffer->Read(gK1Buffer, (unsigned char*)&ptdev->value, 1);
if(ret != 1) return -EIO;
}
}
return ESUCCESS;
}
7.2 回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case GPIO_PIN_0: // PA0--K1
{
GPIO_PinState state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
if(NULL != gK1Buffer)
gK1Buffer->Write(gK1Buffer, (unsigned char*)&state, 1);
break;
}
case GPIO_PIN_1: // PA1--K2
{
}
case GPIO_PIN_2: // PA2--K3
{
}
case GPIO_PIN_3: // PA3--K4
{
}
default:break;
}
}
7.3 测试函数
#include "devices.h"
#include "errno.h"
#include "mytype.h"
void app_key_test(void)
{
IODevicesRegister();
GPIODevice *pK1 = IODeviceFind("K1");
if(NULLDEV == pK1) return;
GPIODevice *pD1 = IODeviceFind("D1");
if(NULLDEV == pD1) return;
if(pK1->Init(pK1) != ESUCCESS) return;
if(pD1->Init(pD1) != ESUCCESS) return;
while(1)
{
int status = pK1->Read(pK1);
if(ESUCCESS == status)
pD1->Write(pD1, pK1->value);
}
}