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灯正在呼吸...........