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

前言

中断这个在很多单片机中都是比较常见的,像什么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, 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, 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], 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, 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, 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, flag);
            // 下面的代码需要注意一下,非常重要,用了两次强转,因为传递过来的参数是void类型的,长度不够,所以这里使用了两次强转将长度匹配到4
            if (write(*(int*)(long*)arg, 2) < 0){
                printf("error\n");
            }
        }
    }
    return NULL;
}

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

    wiringPiSetup();     // 初始化
    pinMode(16, (void*)&pd[1]);
    if (ret < 0){
        close(pd[0]);      // 关闭管道读
        close(pd[1]);      // 关闭管道写
        perror("pthread_create");
        return -2;
    }
    while(1){
        read(pd[0], 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进行连接。

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

总结

中断其实很有用的,但是对于这种多线程的开发板可以用一些其他的方式来实现这个方法,比如软中断,后面我研究一下如何通过底层实现外中断,毕竟上面的这些方法消耗的资源有点大,而且反应比直接中断要慢。
如果图片链接失效了就到我的博客园中查看(这个破csdn关联一点用都没有):博客园文章

原文地址:https://blog.csdn.net/zagzag001/article/details/135117723

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读552次。com.mongodb.MongoQueryException: Query failed with error code 292 and error message 'Executor error during find command :: caused by :: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting.' on server 11.51.141.63:27017 _mongodb 大文件 下载失败
文章浏览阅读635次,点赞9次,收藏8次。MongoDB 是一种 NoSQL 数据库,它将每个数据存储为一个文档,这里的文档类似于 JSON/BSON 对象,具体数据结构由键值(key/value)对组成。
文章浏览阅读2.1k次。和。_mongodb 日期类型
文章浏览阅读1.7k次。Scalestack等客户期待使用MongoDB Atlas Vector Search和Amazon Bedrock构建下一代应用程序
文章浏览阅读970次。SpringBoot整合中间件mongodb、ES_springboot3 elasticsearch json数据
文章浏览阅读673次。MongoDB 简介_尚医通sql
文章浏览阅读1k次,点赞8次,收藏9次。官网下载MongoDB安装包后进行解压(因了解并不深入,故暂不进行详细说明,自行查找其他安装方法,后期了解深入后将进行该教程的完善)在bin目录下使用命令启动:./mongod --config …/mongodb.conf。该文章任然处于完善中,如果存在错误遗漏的地方,欢迎私信联系。安装相关的nuget包后即可通过以下方法连接数据。YX9010_0@的第二十篇文章。
文章浏览阅读1.2k次,点赞17次,收藏26次。社交场景, 使用 MongoDB 存储存储用户信息, 以及用户发表的朋友圈信息, 通过地理位置索引实现附近的人, 地点等功能.游戏场景, 使用 MongoDB 存储游戏用户信息, 用户的装备, 积分等直接以内嵌文档的形式存储, 方便查询, 高效率存储和访问.物流场景, 使用 MongoDB 存储订单信息, 订单状态在运送过程中会不断更新, 以 MongoDB 内嵌数组的形式来存储, 一次查询就能将订单所有的变更读取出来.物联网场景, 使用 MongoDB 存储所有接入的智能设备信息, 以及设备汇报的日
文章浏览阅读686次。您可以使用 update_one() 方法来更新 MongoDB 中调用的记录或文档。update_one() 方法的第一个参数是 query 对象,用于定义要更新的文档。注释:如果查询找到多个记录,则仅更新第一个匹配项。第二个参数是定义文档新值的对象。_python 更新 mongodb 数据
文章浏览阅读1.3k次。首先来学习一下nosql这里安装就不进行介绍 只记录一下让自己了解mongodb。_nosql注入
文章浏览阅读4.1k次,点赞8次,收藏7次。在data的目录下,创建一个db文件。因为启动MongoDB服务之前必须创建数据库文件的存放文件夹,否则命令不会自动创建,而且不能启动成功。第一步:安装时,Custom是指可以自定义安装路径,然后傻瓜式安装即可(注意:先不要安装图形化工具,否则安装时间会特别长):如果要想连接成功,必须要开服务,即mongod -dbpath C:MongoDBdatadb的cmd要一直开着。然后回车,ctrl+F输入port找到端口号,一般为:27017。打开命令行,然后找到bin文件地址,并输入。_mongodb windows安装
文章浏览阅读5.1k次,点赞3次,收藏43次。详细介绍MongoDB数据库的基本知识,安装方法,基本操作,_mongodb数据库
文章浏览阅读3.2k次。安装教程翻看以往文章。_navicat 连接mongodb
文章浏览阅读426次,点赞9次,收藏12次。win10开放端口:https://blog.csdn.net/m0_43605481/article/details/119255256。我的是阿里云服务器,所以直接在安全组中加入规则,端口范围:27017,授权对象:0.0.0.0。windows在mongodb安装文件夹的bin文件夹中的mongod.cfg。数据库名字是test,打算创建一个用户,账号aaa,密码bbb,权限readWrite。因为该用户是创建在test数据库的,所以在最后要加上test。O了,然后恢复了test的数据。
文章浏览阅读1.1k次。聚合操作主要用于处理数据并返回计算结果。聚合操作将来自多个文档的值组合在一起,按条件分组后,再进行一系列操作(如求和、平均值、最大值、最小值)以返回单个结果。MongoDB的聚合查询​聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。MongoDB中聚合(aggregate)主要用于处理数据(例如分组统计平均值、求和、最大值等),并返回计算后的数据结果,有点类似sql语句中的count(*)、groupby。..._如何将几个db的数据统整在一起做查询
文章浏览阅读680次,点赞7次,收藏8次。(2)application.properties配置文件。(4)UserService类。(5)测试和测试结果。
文章浏览阅读1k次,点赞17次,收藏25次。Studio 3T 2023.9 (macOS, Linux, Windows) - MongoDB 的专业 GUI、IDE 和 客户端,支持自然语言查询_mongodb客户端
文章浏览阅读1.1k次,点赞32次,收藏27次。插件式的存储引擎架构可以实现 Server 层和存储引擎层的解耦,可以支持多种存储引擎,如 MySQL 既可以支持 B-Tree 结构的 InnoDB 存储引擎,还可以支持 LSM 结构的 RocksDB 存储引擎。MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。的简称,是 JSON 文档的二进制表示,支持将文档和数组嵌入到其他文档和数组中,还包含允许表示不属于 JSON 规范的数据类型的扩展。
文章浏览阅读5.1k次,点赞6次,收藏96次。本文设计了一种基于智能室内温度控制的自动调速风扇。以STM32系列单片机为核心主控板,通过程序代码驱动和使用温度传感器模块实现对环境温度的实时监测,并可以实时显示环境温度。同时,可以设置温度检测的上下警告值,根据需求自行调节。_stm32 温控风扇
文章浏览阅读898次,点赞13次,收藏21次。在MongoDB中,我们使用find()和find_one()方法来在集合中查找数据,就像在MySQL数据库中使用SELECT语句来在表中查找数据一样。_pymongo find_one