嵌入式人工智能(29-基于树莓派4B的按键中断--多线程实现)

1、由呼吸灯引发的问题

我们先看一个呼吸灯代码,按键按下退出程序。

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BOARD)       # 采用实际的物理管脚给GPIO口
# 关闭警告
GPIO.setwarnings(False)
PWM_LED = 11
# 设置输入引脚
channel = 13

def GPIO_Init():
    GPIO.setmode(GPIO.BOARD)       # 采用实际的物理管脚给GPIO口
    # 将11号引脚设置为输出模式
    GPIO.setup(PWM_LED, GPIO.OUT)
    # 设置GPIO输入模式, 使用GPIO内置的上拉电阻, 即开关断开情况下输入为HIGH
    GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)  
    # 初始化中断函数,当SW管脚为0,使能中断
    #GPIO.add_event_detect(channel, GPIO.FALLING, callback=btnISR)

def btnISR():
    pass

def PWM_LED_Breath():
    # 电流从小到大,LED由暗到亮
    for i in range(0, 101, 1):
        # 更改占空比,
        pwm.ChangeDutyCycle(i)
        time.sleep(0.02)
    # 再让电流从大到小,LED由亮变暗
    for i in range(100, -1, -1):
        pwm.ChangeDutyCycle(i)
        time.sleep(0.02)



GPIO_Init()   
pwm = GPIO.PWM(PWM_LED, 80)
pwm.start(0)

try:
    while True:
        if GPIO.input(channel)==GPIO.LOW:
            pwm.stop()
            GPIO.cleanup()
            exit()
        else:
            PWM_LED_Breath()    
except Exception as e:
    print(e)
    exit()

实际运行情况:如果按键不按下,呼吸灯一直由暗变亮,再由亮变暗。在函数PWM_LED_Breath()里由于存在time.sleep(0.02)的时间延时,才有呼吸灯效果,但是又因为存在这个time.sleep,导致按键的低电平判断时间总是在CPU的sleep时间内,即便按键按下也不能立刻中断呼吸灯。只有长按按键使引脚持续产生低电平,总有一刻该语句的执行在CPU清醒的时间内,这样才会退出程序。而且sleep时间越长,按键越不灵,这个很好理解,就像我整天都在睡觉,醒的时间很短,所以有客人来敲门总是没人开门,便认为我不在家。

2、按键中断

实际这个问题在51/32单片机的解决非常方便,这类单片机都是有外部中断的,即便CPU在delay中(delay和sleep一样),也一样有外部中断可以打断。(或者类似需要delay的地方,将程序放到定时器里面去运行,不用delay函数)。树莓派按键也有外部中断。

GPIO.add_event_detect(channel, GPIO.FALLING, callback=btnISR,bouncetime=300)

这个按键中断函数是很好的,既有消抖处理,又有回调函数,就是如果按键产生低电平,则进入按键中断服务函数btnISR,这里面任你发挥。

但是我这边执行的时候,出现问题。

我快为这个问题疯了。连续2天百度去解决这个问题。首先考虑导入GPIO文件权限问题,其次又

考虑版本问题,反正是一顿操作最终没有解决。这里种个草,回头我再试试,我的开发环境VsCode使用的Rpi.GPIO库文件找不到这个函数的具体实现,不知道为什么,是不是这个调用有问题,我的/home/pi/.local/lib/python3.11/site-packages/目录下面没有这个库?这里放一放。如果有哪位同学有解决办法,可以私聊我。

3、进程与线程

进程和线程是计算机中两个重要的概念。它们都是用于执行程序的执行单元,但在实际应用中有一些不同之处。

进程是操作系统分配资源的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源,包括内存空间、文件句柄和其他系统资源。每个进程都有独立的地址空间,因此进程之间的数据不能直接共享,需要通过进程间通信进行数据传输。每个进程都有自己的程序计数器、寄存器集合和堆栈。

线程是进程中的一个执行单元,一个进程可以包含多个线程。线程是操作系统调度的最小单位,多个线程可以同时执行不同的任务。线程共享进程的资源,可以直接访问进程的地址空间,因此线程之间可以共享数据。线程包含自己的程序计数器、寄存器集合和堆栈,但共享同一进程的其他资源。

4、多线程解决sleep问题

传统的51单片机与STM32基本是裸机跑C代码,都是单进程处理,CPU并不支持多进程多线程。这Python代码是解释执行的,解释器本来就是需要操作系统支持,没有操作系统还真不行,因此Python强大的功能必须支持多任务多线程。(不要拿microPython来和Python比较)

多个进程相当于多个py程序文件,可以同时运行,系统要给每个运行的Python程序分配CPU、内存等资源。而一个进程里面的多线程相当于还是运行的单py程序,程序里要同时执行多个任务,这里的任务并非操作系统的多任务,而是多个程序功能函数。CPU一会执行A函数,一会执行B函数,一会时间很短,与CPU的机器周期有关。所以当开启了多线程之后,虽然有sleep函数了,但是它只是在一个线程中sleep,另一个线程并没有sleep。这就相当于我虽然在睡觉,但是我睡1秒醒1秒,这样即便有人来找我,我也能知道。

下面是一个完整的通过多线程用按键解决呼吸灯退出的问题。

import RPi.GPIO as GPIO
import time
import threading

PWM_LED = 11
Button = 13
global Light_Flag
Light_Flag=True


def GPIO_Init():
    GPIO.setmode(GPIO.BOARD)       # 采用实际的物理管脚给GPIO口
    # 关闭警告
    GPIO.setwarnings(False)
    GPIO.setup(PWM_LED, GPIO.OUT)
    # 设置GPIO输入模式, 使用GPIO内置的上拉电阻, 即开关断开情况下输入为HIGH
    GPIO.setup(Button, GPIO.IN, pull_up_down=GPIO.PUD_UP)  


def Buttom_check():
    global Light_Flag
    while True:
        if GPIO.input(Button)==GPIO.LOW:
            time.sleep(0.2)
            if GPIO.input(Button)==GPIO.LOW:
                print('Button pressed.')
                Light_Flag=not Light_Flag
                break

def PWM_LED_Breath():
    pwm = GPIO.PWM(PWM_LED, 80)
    pwm.start(0)
    while True:
        if Light_Flag==True:
            # 电流从小到大,LED由暗到亮
            for i in range(0, 101, 1):
                # 更改占空比,
                pwm.ChangeDutyCycle(i)
                time.sleep(0.02)
            # 再让电流从大到小,LED由亮变暗
            for i in range(100, -1, -1):
                pwm.ChangeDutyCycle(i)
                time.sleep(0.02)
        else:
            break
    pwm.stop()
    GPIO.cleanup()
    exit()

if __name__=='__main__':
    GPIO_Init()
    try:
        #创建并启动线程t0
        t0 = threading.Thread(target=PWM_LED_Breath)
        t0.start()
        # 创建并启动线程t1
        t1 = threading.Thread(target=Buttom_check)
        t1.start()
    except Exception as e:
        print(e)
    finally:
        exit()

程序几点说明:

(1)导入threading模块,线程的开启很简单,taget后面是函数名称,如果函数有参数,后面加args=()。 t0 = threading.Thread(target=PWM_LED_Breath)。

(2) 线程之间的变量传递,注意定义为global全局变量,如果多个进程都会改变同一个变量值,那么注意采取锁机制,我们这里只有按键才会改变Light_Flag,所以不用考虑。

(3)代码基本都封装成函数了,主程序实现也比较简单,便于阅读。

(4)Python中的取反不是~,这个~是按位取反运算符,如果定义的变量值为1,按位取反后的值是-2。如果要逻辑取反,用关键字not,那么True就和Flase对应上了。

(5)Python语言并不是真正的硬件语言,相比C来做实时控制的话还是有欠缺的,如果项目中对实时控制要求很高的话,不建议用Python写。

最后还是上一张效果图,依稀可见LED灯正在呼吸...........

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值