[香橙派开发系列]中断?不!中断!

前言

中断这个在很多单片机中都是比较常见的,像什么51单片机,stm32单片机都是可以通过自己的设置来实现中断的。在香橙派这种比较高级的单片机上也是有中断的,但是呢,这里的中断和51或者stm的单片机有点不同。

一、什么是中断

中断这个概念老生常谈了,学过单片机的都应该知道这个,也就是说,你在这打游戏,有一个电话过来了,你就得先停止手上的游戏,然后去处理一下那件事,这个就是中断。这里就不细说中断的概念了,毕竟和香橙派上的中断不太一样。

二、普通单片机和系统单片机的区别

这里要划分一下概念,我分为普通单片机和系统单片机两种,普通单片机就是指像51和stm32那种单核单片机,就是按照一定顺序执行的单片机,然后系统单片机就是指这种能在里面运行操作系统的单片机。

这两种类型的单片机有什么不同呢?

做过单片机开发的都知道,这种单片机只能单步执行,比如你写的这些程序,它只能一条一条的执行,没办法引出一些线程或者进程的概念,这种单片机的中断我们可以自己配置的,因为我们可以接触到底层环境来直接配置。

而这种系统单片机,因为有一层系统,所以底层的一些操作都被一层叫做HAL的抽象包给封装了,我们可以使用这个抽象包给的一些接口来对底层进行一些操作,而且这种单片机的芯片都是比较强的,可以支持像什么线程和进程的操作。

三、中断的区别

在普通单片机中,我们设置好中断后,满足了中断的条件了,它就会打断它现在正在执行的操作,转向到中断处理函数中去执行中断,就如同这张图一样

img

就如图一样,每次执行中断的时候就会打断当前执行的内容。

而在系统单片机中就不一样了,因为系统单片机中它是可以支持多线程和多进程的,所以就可以不用打断当前的系统执行,而是分裂出一个进程或者线程来处理这个中断请求,就如下面这张图

img

可以看到这种就是这个过程不会影响着我们现在执行的程序,而我们的香橙派就是用到这种方式的中断。

四、配置香橙派的中断

1.进程版

我们配置香橙派的中断其实就可以不用像配置stm32的那种一样复杂了,直接分裂一个线程一直判断是否按下或者是其他内容即可实现这个过程。

比如在stm32中我们要一个按键按下了后就会执行中断处理函数实现一些内容,这里我们就可以使用一个进程一直去读取这个按键的引脚,当按键按下了,我们就让标志位置为1或者0,然后父进程就读取这个标志位,点亮一个LED灯。

代码就可以这么写:

1.先将引脚进行初始化,这里使用的是PC10作为输入引脚,PC7作为输出引脚,然后进行配置:

#include <wiringPi.h>
int main(){
    wiringPiSetup();      // 初始化
    pinMode(16, INPUT);   // PC10输入
    pinMode(13, OUTPUT);  // PC7作为输入
    pullUpDnControl(16, PUD_UP);    // 上拉输入
    return 0;
}

2.设置完成后就开始创建进程和匿名通道。这里因为要实现进程之间的数据交换,所以使用了一个匿名通道进行信息交换

#include <wiringPi.h>
#include <unistd.h>
int main(){
    int ret;       // 接收返回值
    int pd[2];     // 管道描述符
    int pid;       // 进程ID
    wiringPiSetup();      // 初始化
    pinMode(16, INPUT);   // PC10输入
    pinMode(13, OUTPUT);  // PC7作为输入
    pullUpDnControl(16, PUD_UP);    // 上拉输入
    // 创建匿名管道
    ret = pipe(pd);      // 创建管道
    if (ret < 0){
        // 错误处理
        perror("pipe");
        return -1;     // 退出程序
    }
    // 创建进程
    pid = fort();    // 创建进程
    if (pid < 0){
        // 错误处理
        close(pd[0]);      // 关闭管道读
        close(pd[1]);      // 关闭管道写
        perror("fort");
        return -2;
    }
    if (pid == 0){
        // 子进程处理
        
    }
    else{
        // 父进程处理
    }
    return 0;
}

3.在子进程中写入判断函数并将标志位发送到父进程中,并让LED灯亮起来。

#include <wiringPi.h>
#include <unistd.h>
#include <string.h>
int main(){
    int ret;       // 接收返回值
    int pd[2];     // 管道描述符
    int pid;       // 进程ID
    int flag = 1;  // 标志位
    char buf[2];   // 发送的字符串
    wiringPiSetup();      // 初始化
    pinMode(16, INPUT);   // PC10输入
    pinMode(13, OUTPUT);  // PC7作为输入
    pullUpDnControl(16, PUD_UP);    // 上拉输入
    // 创建匿名管道
    ret = pipe(pd);      // 创建管道
    if (ret < 0){
        // 错误处理
        perror("pipe");
        return -1;     // 退出程序
    }
    // 创建进程
    pid = fort();    // 创建进程
    if (pid < 0){
        // 错误处理
        close(pd[0]);      // 关闭管道读
        close(pd[1]);      // 关闭管道写
        perror("fort");
        return -2;
    }
    if (pid == 0){
        // 子进程处理
        close(pd[0]);     // 关闭读端,因为子进程只需要写入数据
        while(1){
            if (digitalRead(16) == 0){
                // 按键按下
                delay(20);
                while(digitalRead(16) == 0);   // 消抖
                flag = !flag;
                sprintf(buf, "%d", flag); // 拼接发送的字符串
                write(pd[1], buf, 2);     // 向管道写入数据
            }
        }
    }
    else{
        // 父进程处理
        close(pd[1]);     // 关闭写端,因为只用读取内容
        while(1){
            read(pd[0], buf, 2);   // 读取数据
            if (strcmp(buf, "1") == 0){
                // 高电平
                digitalWrite(13, 1);
            }
            else{
                // 低电平
                digitalWrite(13, 0);
            }
        }
    }
    return 0;
}

然后我们就可以执行一下查看一下效果了:

img

然后再按下按钮

img

为了让大家看得更清楚这个过程我特意添加了一下输出语句来给大家查看一下效果

img

这种方法就和中断一样,系统的主执行内容不会被影响,毕竟是有另一个进程在读取按键的按下和松开。

2.wiringPi库函数版

这种方法是使用wiringPi中的函数来进行实现的,但是我这一直都搞不了,我还在研究这个方法,为了弄这个我今天买了块3b来进行测试,我看看是不是因为zero的不支持还是什么原因,这里后面再补全,先介绍一下这个方法的代码如何写,这里使用的函数是wiringPiISR,函数的原型:

int wiringPiISR (int pin, int mode, void (*function)(void));

功能:该函数会在指定管脚注册一个中断事件的函数,当指定管脚发生中断事件时,会自动调用该函数。

第一个参数是你要让哪个引脚注册位中断,比如说PC7,那这就填写13。

第二个参数是触发模式

模式 解释
INT_EDGE_FALLING 下降沿触发
INT_EDGE_RISING 上升沿触发
INT_EDGE_BOTH 上升沿或下降沿触发
INT_EDGE_SETUP 不初始化

当使用最后的一种方法时,这个函数不会初始化这个引脚,它会默认是在其他敌法进行了初始化。

第三个参数是中断函数,就是当注册的引脚触发了就会触发这个函数进行执行。

返回值如果不成功就会返回一个小于0的数,可以用这个返回值来判断一下初始化是否成功。

知道了这个我们就可以利用这个函数来实现中断:

#include <wiringPi.h>
int flag = 0;
void myinterrinput(void){
    // 中断处理函数
    flag = !flag;
}

int main(){
    wiringPiSetup();
    pinMode(16, INPUT);
    pinMode(13, OUTPUT);
    pullUpDnControl(16, PUD_UP);    // 上拉输入
    wiringPiISR(16, INT_EDGE_FALLING, &myinterrinput);
    while(1){
        digitalWrite(13, flag);
    }
    return 0;
}

执行后,当按键按下后就会执行中断函数,但是我这一执行后就会出现下面的问题

img

这个问题我还在研究到底是为什么,网上也没找到合适的答案,这里我先空着,等3b到了我再试试。

3.线程版

这个版本就可以分为Linux内核线程和wiringPi库函数版的线程了。

这里先使用Linux内核的线程

3.1 Linux内核的线程

其实这个也不能称为Linux内核线程,因为之前的linux最初开发时,在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用将进程作为可调用度的实体。然后就将线程进行了改写,由NPTL进行接手,我们现在用的线程是NPTL。

我们可以通过下面的指令来下载NPTL

sudo apt-get install manpages-posix-posix-dev

通过下面的指令来查看一下NPTL的版本

getconf GNU_LIBPTHREAD_VERSION

img

我这里的版本是2.35,只要有这个即可。

这里不过多介绍线程的概念,如果大家对线程感兴趣我后面会出有关于线程的文章给大家介绍一下的。

现在开始编程

1.初始化需要的引脚

#include <wiringPi.h>
int main(){
    wiringPiSetup();      // 初始化
    pinMode(16, INPUT);   // PC10输入
    pinMode(13, OUTPUT);  // PC7作为输入
    pullUpDnControl(16, PUD_UP);    // 上拉输入
    return 0;
}

2.创建管道和线程,这里线程之间的内容是独立的,和上面的进程一样,需要我们创建一个管道来实现进程间的通讯,所以需要创建一个管道。

在写之前,我们需要了解一下线程的创建函数:

#include <pthread.h>

int pthred_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);
功能:
    创建一个线程
参数:
    thread:线程标识符地址。
    attr:线程属性结构体地址,通常设置为NULL。
    start_routine:线程函数的入口地址。
    arg:传入线程函数的参数。
返回值:
    成功:0
    失败:非0

然后就可以开始写代码:

#include <wiringPi.h>
#include <pthread.h>
void* myinterrinput(void* arg){
    // 进程执行函数
    int flag = 0;
    char buf[2];
    while(1){
        
    }
    return NULL;
}

int main(){
    int ret;
    pthread_t tid;       // 线程标识符
    int pd[2];
    char buf[2];

    wiringPiSetup();     // 初始化
    pinMode(16, INPUT);
    pinMode(13, OUTPUT);
    pullUpDnControl(16, PUD_UP);

    // 创建管道
    ret = pipe(pd);
    if (ret < 0){
        perror("pipe");
        return -1;
    }

    // 创建线程
    ret = pthread_create(&tid, NULL, &myinterrinput, (void*)&pd[1]);       // 创建进程的函数 这里将管道的描述符传递给线程处理函数
    if (ret < 0){
        close(pd[0]);      // 关闭管道读
        close(pd[1]);      // 关闭管道写
        perror("pthread_create");
        return -2;
    }
    while(1){
        // 主进程处理一些非中断的内容
    }
    return 0;
}

3.然后开始完善处理代码

#include <stdio.h>
#include <pthread.h>
#include <wiringPi.h>
#include <unistd.h>
#include <string.h>

void* myinterrinput(void* arg){
    int flag = 0;
    char buf[2];
    while(1){
        if (digitalRead(16) == 0){
            while(digitalRead(16) == 0);
            flag = !flag;
            sprintf(buf, "%d", flag);
            // 下面的代码需要注意一下,非常重要,用了两次强转,因为传递过来的参数是void类型的,长度不够,所以这里使用了两次强转将长度匹配到4
            if (write(*(int*)(long*)arg, buf, 2) < 0){
                printf("error\n");
            }
        }
    }
    return NULL;
}

int main(){
    int ret;
    pthread_t tid;       // 线程标识符
    int pd[2];
    char buf[2];

    wiringPiSetup();     // 初始化
    pinMode(16, INPUT);
    pinMode(13, OUTPUT);
    pullUpDnControl(16, PUD_UP);

    // 创建管道
    ret = pipe(pd);
    if (ret < 0){
        perror("pipe");
        return -1;
    }

    // 创建线程
    ret = pthread_create(&tid, NULL, &myinterrinput, (void*)&pd[1]);
    if (ret < 0){
        close(pd[0]);      // 关闭管道读
        close(pd[1]);      // 关闭管道写
        perror("pthread_create");
        return -2;
    }
    while(1){
        read(pd[0], buf, 2);
        if (strcmp(buf, "1") == 0){
            digitalWrite(13, 1);
            printf("1");
        }
        else if(strcmp(buf, "0") == 0){
            printf("0");
            digitalWrite(13, 0);
        }
    }
    return 0;
}

然后开始编译,这里编译的命令如下:

gcc threadButton.c -o threadButton -lwiringPi -lpthread

因为pthread是属于外部库,需要使用-l进行连接。

运行后也是一样的效果,我就懒得拍照了。

总结

中断其实很有用的,但是对于这种多线程的开发板可以用一些其他的方式来实现这个方法,比如软中断,后面我研究一下如何通过底层实现外中断,毕竟上面的这些方法消耗的资源有点大,而且反应比直接中断要慢。

posted @ 2023-12-20 21:07  Lavender·edgar  阅读(213)  评论(4编辑  收藏  举报