本系列连载将为您介绍使用Raspberry Pi制作咖啡机控制装置的方法。本系列为我们分享的嘉宾是阿矢谷充先生,他迄今为止在各种媒体上,面向喜欢自己动手制作的群体发表过很多电子作品制作类的文章。在连载的第1部分中,介绍了装置的概况;在第2部分中,介绍了硬件的制作和安装方法;在第3部分中,将为您介绍控制程序的编写方法。
目录
1.环境设置
首先,我们来了解一下运行环境。操作系统是Raspberry Pi标准的Raspbian,我使用的是Release10:buster。在这里我们不会涉及它们的安装方法。
先为您介绍必要的设置。通过菜单打开“Raspberry pi设置”页面,点开“接口”选项卡。
这里必须启用的项目是SSH、VNC和I2C。此次制作的装置中没有使用SPI,但因其是一个像I2C一样常用的外部接口,所以我将其设为“启用”状态。串行端口和串行控制台也不会用到,但因为它们可在发生Wi-Fi等故障时用来进行设置,所以我也将它们设为“启用”了。
关于SSH和VNC,由于本装置一旦将零部件收纳进外壳后,就无法再接入HDMI等外部端口,因此,它们对于通过远程进行所有设置而言至关重要。VNC需要在PC端或Mac端安装“VNC Viewer”等客户端软件,所以请安装所需软件。要想通过VNC进行远程操作,当然需要通过Wi-Fi进行IP连接。
2.Python的开发和运行环境
我使用“Python”作为控制软件,并选择了最常用的RPi.GPIO库来控制GPIO。关于这个RPi.GPIO的基础知识,请参考Dvice Plus曾经发表过的一篇非常通俗易懂的介绍文章《通过RPi.GPIO Python库使用Raspberry Pi GPIO引脚》。
通过RPi.GPIO Python库使用Raspberry Pi GPIO引脚
如果您还不了解RPi.GPIO,建议您务必先阅读此文。
使用Python开发Raspberry Pi用的程序时,方法有很多。编写源代码时,也有多种方法可选,比如是在Raspberry Pi端编写还是在PC或Mac上使用熟悉的编辑器编写。
此次,我尝试使用了一个在Raspberry Pi上运行的名为“Thonny”的软件。Thonny是一个集成了编辑器、执行环境和调试环境的IDE(集成开发环境)。Thonny在Raspbian操作系统中已经默认安装好了,属于开箱即用的功能,可以通过菜单直接调用。Thonny的GUI如下:
首先,关于编辑器,Thonny是Python专用的软件,具有重要的缩进功能,会自动缩进,可以放心使用。还有,保留字是用不同颜色区分的,CTRL+Space支持自动补全(显示保留字候选),用起来非常方便。
Thonny还配有调试器,可以单步执行和显示变量的值。Thonny的功能仅限于初学者的基本需要,所以使用方法很简单,不过我认为它足以应对中小型程序的需求。
我是用VNC连接Raspberry Pi上的Thonny来编辑和运行程序的。用VNC连接后,Raspbian的GUI只是虚拟的,因此即使您断开连接,正在执行的任务和环境也会继续保持。本装置的Python控制程序也是通过Thonny执行的,可以直接使用。
关于Raspberry Pi的VNC连接,请参考以下链接中介绍的详细设置方法。
VNC远程桌面连接Raspberry Pi(兼容Windows/Mac/Linux)
3.I2C液晶工作确认模块
在第2部分中,我们了解了硬件相关的内容,在这里我们先来确认一下I2C液晶的工作情况。测试是使用Raspberry Pi命令和通过Python编写的程序进行的。另外,由于该测试程序是模块化的,因此可以通过咖啡机控制程序来导入和使用。
首先,使用Raspberry Pi命令确认I2C的连接是否正常。通过Raspbian上的LXTerminal或通过SSH连接的终端上的shell输入“i2cdetect -y 1”命令。
pi@raspberrypi:~ $ i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 3e -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
如果I2C连接正常,会显示LCD模块的地址:3e。如果不显示,需要检查一下看看是否有接线错误等问题。
接下来,使用Thonny创建以下工作确认模块。我在/home/pi下创建了一个名为“python”的目录,用来保存程序,这方面您可以自由决定。程序名称为“lcdtest_2x8.py”。
import smbus import time i2c = smbus.SMBus(1) addr = 0x3e def initlcd(): data = [0x38, 0x39, 0x14, 0x78, 0x5f, 0x6a, 0x0c, 0x01, 0x06] i2c.write_i2c_block_data(addr, 0, data) time.slee p(0.3) def write_data_to_lcd(upper,lower): #write upper i2c.write_byte_data(addr, 0, 0x80) data1 =["" for j in range(len(upper))] for i in range(len(upper)): data1[i] = ord(upper[i]) i2c.write_byte_data(addr, 0, 0x80) i2c.write_i2c_block_data(addr, 0x40, data1) #write lower data2 =["" for j in range(len(lower))] for i in range(len(lower)): data2[i] = ord(lower[i]) i2c.write_byte_data(addr, 0, 0xc0) i2c.write_i2c_block_data(addr, 0x40, data2) def clsc(): i2c.write_byte_data(addr, 0, 0x01) time.slee p(0.3) if __name__ == '__main__': initlcd() write_data_to_lcd("line1","line2") time.slee p(2) clsc() write_data_to_lcd("--LCD---","**Test**") time.slee p(2) clsc() write_data_to_lcd("Test","==end===")
运行程序,如果程序正常工作,会在液晶显示屏上显示三个测试信息。这个程序中导入了python的I2C控制用的“smbus”和时间用的“time”两个模块。另外,还设置了以下三个函数:
initlcd()用来初始化LCD模块。该函数在程序开始时会运行一次,发送模块中使用的“AQM0802A”芯片的控制命令。
write_data_to_lcd(upper,lower)用来将字符串写入LCD模块。这个程序用于8个字符×2行的LCD,参数upper显示上一行,参数upper显示下一行。使用ord()函数将字符串转换为ASCII码并发送给模块。
clsc()用来清除LCD显示。将这些函数作为模块导入另一个程序,即可引用和使用这些函数。
4. 咖啡机控制主程序(整体结构)
首先,控制程序的整体结构如下所示。
左侧的蓝色部分是在开始时只运行一次的初始设置模块。绿色部分是与确定杯数的中断例程相关的模块。这也就是在第2部分中提到的硬件——黑色微动开关按钮,通过它来决定要萃取多少杯咖啡。每按一次开关,就会通过中断处理更改杯数。
右侧的橙色部分是主例程。首先是等待启动按钮(红色微动开关)输入,然后是由第一步、第二步、第三步组成的定时器循环。
第一步是“初次注水”。在进入第二步“闷蒸”之前,让热水浸润咖啡粉。咖啡机将只在这个设置时间内处于ON(通电)状态。
第二步是“闷蒸”。热水浸润咖啡粉后,保持一段时间。此时,咖啡机处于OFF(断电)状态。
第三步是“萃取”。此时,咖啡机基本上是ON(通电)状态。作为附加功能,我增加了基于PWM的ON/OFF控制功能。这可以分别设置咖啡机ON的时间(秒)和OFF的时间(秒)。我希望用这个功能尝试通过软件实现一种叫做“节奏萃取”(加入一定量的热水然后暂停,反复此操作)的方法。关于PWM功能的详细内容,将在python程序介绍部分中进行说明。
下表1中汇总了上述设置参数。
表1 设置参数一览表
设置时间单位为“秒”。此次使用的咖啡机为Kalita ET-102,最大容量为5杯。我让该程序最多允许设置6杯,设置为6杯时,是将闷蒸时间设置为“0”,快速冲泡咖啡的“最快模式”。
目前,我们已经可以使用表1中的参数通过电脑或智能手机上的Web浏览器来设置参数了,具体设置方法将在本系列连载的第4部分进行介绍。
5.使用Python完成控制程序
针对在第2部分中提到的功能,我用Raspberry Pi的Python编写了程序,下面对该程序进行说明。
程序内容如下:
# -*- coding: utf-8 -*- #咖啡机控制程序 rev 1.0 #导入库 import RPi.GPIO as GPIO import time import lcdtest_2x8 #设置变量 BUTTON1 = 24 BUTTON2 = 25 START_STOP_LED = 23 AC_OUT_LED = 17 SSR = 18 CUPIDX = 1 #Parameters:初次注水时间、闷蒸时间、萃取时间、ON时间、OFF时间 wtimer = [[60,40,360,60,10],[68,50,480,60,20],[70,50,600,60,20],[74,50,600,60,20],[0,0,600,60,0]] #GPIO Setup GPIO.setmode(GPIO.BCM) GPIO.setup(BUTTON1,GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(BUTTON2,GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(START_STOP_LED,GPIO.OUT,initial=GPIO.LOW) GPIO.setup(AC_OUT_LED,GPIO.OUT,initial=GPIO.LOW) GPIO.setup(SSR,GPIO.OUT) ssr_pwm=GPIO.PWM(SSR,1) ssr_pwm.start(0) #杯数设置中断例程 def button_pushed1(BUTTON1): GPIO.remove_event_detect(BUTTON1) global CUPIDX if CUPIDX == 1: print("Pressed!! Cups=3") lcdtest_2x8.write_data_to_lcd("Cups=3","") CUPIDX = CUPIDX+1 elif CUPIDX == 2: print("Pressed!! Cups=4") lcdtest_2x8.write_data_to_lcd("Cups=4","") CUPIDX = CUPIDX+1 elif CUPIDX == 3: print("Pressed!! Cups=5") lcdtest_2x8.write_data_to_lcd("Cups=5","") CUPIDX = CUPIDX+1 elif CUPIDX == 4: print("Pressed!! Cups=6") lcdtest_2x8.write_data_to_lcd("Cups=6","") CUPIDX = CUPIDX+1 elif CUPIDX == 5: print("Pressed!! Cups=2") lcdtest_2x8.write_data_to_lcd("Cups=2","") CUPIDX = 1 time.slee p(0.3) GPIO.add_event_detect(BUTTON1, GPIO.RISING, callback=button_pushed1, bouncetime=200) #初始设置 GPIO.add_event_detect(BUTTON1, GPIO.RISING, callback=button_pushed1, bouncetime=200) lcdtest_2x8.initlcd() lcdtest_2x8.write_data_to_lcd("Coffee","Timer") time.slee p(2) lcdtest_2x8.clsc() lcdtest_2x8.write_data_to_lcd("Cups=2","") #主循环 try: while True: print("Waiting for rising edge on BUTTON2") GPIO.wait_for_edge(BUTTON2, GPIO.RISING) print("Rising edge detected on BUTTON2.") GPIO.remove_event_detect(BUTTON1) #Set Freqency and DutyCycle freq_int = 1/(wtimer[CUPIDX-1][3] + wtimer[CUPIDX-1][4]) duty = wtimer[CUPIDX-1][3]/(wtimer[CUPIDX-1][3] + wtimer[CUPIDX-1][4])*100 print("Frequency(Hz) =",freq_int) print("Duty cycle(%) =",duty) #1st step current_time = time.time() stop_time = current_time + wtimer[CUPIDX-1][0] ssr_pwm.ChangeDutyCycle(100) GPIO.output(AC_OUT_LED,1) ledflag = True while time.time() < stop_time: ctime = int(stop_time - time.time()) print(ctime) GPIO.output(START_STOP_LED,ledflag) lcdtest_2x8.write_data_to_lcd("","1st:"+str(ctime)+" ") ledflag = not ledflag time.slee p(1) print("Timer1 out") print('Time: ' + str(time.time() - current_time)) #2nd step: Murashi current_time = time.time() stop_time = current_time + wtimer[CUPIDX-1][1] ssr_pwm.ChangeDutyCycle(0) GPIO.output(AC_OUT_LED,0) ledflag = True while time.time() < stop_time: ctime = int(stop_time - time.time()) print(ctime) GPIO.output(START_STOP_LED,ledflag) lcdtest_2x8.write_data_to_lcd("","2nd:"+str(ctime)+" ") ledflag = not ledflag time.slee p(1) print("Timer2 out") print('Time: ' + str(time.time() - current_time)) #3rd step: current_time = time.time() stop_time = current_time + wtimer[CUPIDX-1][2] ssr_pwm.ChangeFrequency(freq_int) ssr_pwm.ChangeDutyCycle(duty) GPIO.output(AC_OUT_LED,1) ledflag = True while time.time() < stop_time: ctime = int(stop_time - time.time()) print(ctime) GPIO.output(START_STOP_LED,ledflag) lcdtest_2x8.write_data_to_lcd("","3rd:"+str(ctime)+" ") ledflag = not ledflag time.slee p(1) ssr_pwm.ChangeFrequency(1) ssr_pwm.ChangeDutyCycle(0) GPIO.output(AC_OUT_LED,0) GPIO.output(START_STOP_LED,False) print("Timer3 out") print('Time: ' + str(time.time() - current_time)) lcdtest_2x8.write_data_to_lcd("","Sto p ") GPIO.add_event_detect(BUTTON 1, GPIO.RISING, call back=button_pushed1, bouncetime=200) except KeyboardInterrupt: GPIO.remove_event_detect(BUTTON1) ssr_pwm.stop() GPIO.cleanup() # clean up GPIO on CTRL+C exit print("STOP")
下面介绍各程序块的运行要点。这个程序的名称是“coffee_control_rev1.py”。
#导入库
在这里,先导入“RPi.gpio”和“time”标准库。然后将“I2C液晶工作确认模块”一节中提到的“lcdtest_2x8.py”作为库加载进来。请将“lcdtest_2x8.py”放在与这个主程序相同的目录中(我是放在/home/pi/python/中)。
#设置变量
我将GPIO使用的BCM编号放在命名变量中了。这是为了在设置GPIO时更容易理解(在下面的介绍说明中,会有类似“BUTTON1(24)”这样的带有BCM编号的表述)。最后的“CUPIDX”用作全局变量,用来保存当前的杯数。
#Parameters:设置参数列表
前面提到的“表1 设置参数一览表”中的值在二维列表wtimer中。值通过wtimer[杯数][项目]索引来引用。在这个程序中,设置值是以编程方式描述的,在第4部分中,我们将对其进行更新,以便可以通过Web浏览器更改这些值。
# GPIO Setup
首先,通过“GPIO.setmode(GPIO.BCM)”将模式设置为使用BCM编号的模式。连接微动开关的BUTTON1(24)和BUTTON2(25)为“GPIO.IN”输入模式,将内部上拉电阻指定为下拉“GPIO.PUD_DOWN”。将START_STOP_LED(23)、AC_OUT_LED(17)、SSR(18)设置为“GPIO.OUT”模式。
SSSR(18)使用PWM功能。Raspberry Pi有两个由硬件控制的PWM端口,端口(18)是其中之一。
通过以下语句来启动PWM。
ssr_pwm=GPIO.PWM(SSR,1)
ssr_pwm.start(0)
上面参数中的数字1是频率,单位是Hz。可以用实数来指定这个数字,所以也支持1以下的低频。下面参数中的数字0是占空比,用%来指定。这个数值也可以用实数来设置。初始状态的占空比=0,输出为OFF。
#杯数设置中断例程
咖啡杯数用黑色微动开关“BUTTON1(24)”进行设置。需要检测到此按钮被按下并启动中断处理设置所定义的函数。其中断动作例程——函数为“def button_pushed1”。每按一下黑色的微动开关,该例程——函数就会被调用。
中断设置是通过下面的“#初始设置”开头的语句来完成的:
GPIO.add_event_detect(BUTTON1, GPIO.RISING, callback=button_pushed1, bouncetime=200)
通过“GPIO.RISING”指定应在从L到H的上升沿检测输入。“callback=button_pushed1”是检测到输入时要调用的函数名。“bouncetime=200”是非检测窗口时间,用来防止由于开关抖动而导致的误检测。在这个案例中,设置为在第一次检测后200ms内不工作。
另外,在这个中断函数例程的开头,插入了用来禁用中断的“GPIO.remove_event_detect”。这是防止在中断处理过程中发生多重中断的措施。在该例程的最后,加上“GPIO.add_event_detect”来重新启用中断。
接下来,为保存当前杯数的变量“CUPIDX”指定“global”。如果不进行这个指定,就会被视为其函数中的局部变量,将无法在主程序中引用该值。
然后,由if语句在每次按下开关时递增杯数。在shell和液晶显示器上将会显示杯数。
#初始设置
进行前述的中断设置和液晶显示器中的启动消息显示设置。
#主循环
我在介绍整体结构时提到过,主循环由等待启动按钮输入和三个定时器循环组成。
首先是等待启动按钮——BUTTON2(25)的输入。
GPIO.wait_for_edge(BUTTON2, GPIO.RISING)
使用“GPIO.RISING”检测上升沿。启动时,首先禁用杯数输入的中断处理。
GPIO.remove_event_detect(BUTTON1)
这是为了禁止在萃取咖啡过程中改变杯数。在三个定时器循环结束后,会再次启用中断,以便我们能够设置杯数。
#Set Freqency and DutyCycle:PWM参数设置
我在主循环中添加了基于PWM的咖啡机ON/OFF功能。其工作示意图如下:
PWM(Pulse Width Modulation=脉冲宽度调制)是一种周期性地改变脉冲ON/OFF宽度的信号方式,常被用于改变LED的亮度和控制电机转速等应用。在这类应用中,频率为几十Hz以上,导通脉冲宽度为毫秒级。在Raspberry Pi的PWM中,是通过频率和占空比设置的,可以用实数代入,因此,即使在1Hz以下的低频条件下也可以正常运行。
于是,我以“秒”为单位设置了咖啡机的热水注入时间和暂停时间,并尝试将其应用于注入热水注入时间(ON)=60秒、暂停时间(OFF)=20秒等萃取方法。在本案例中,我将频率设置为0.0125Hz,占空比设置为75%,虽然频率非常低,但PWM还是能工作正常的。
#1st step:初次注水
这是先用热水浸润咖啡粉的初次注水定时器循环。使用“time.time()”函数获取当前时间。以秒为单位输入变量。将初次注水时间添加到这里成为“stop_time”。在当前时间超过这个“stop_time”时,定时器的while循环结束。
while time.time() < stop_time:
该方法与后续的第二步和第三步相同。
至于启动咖啡机的方法,SSR(18)使用PWM设置。因此,要使电源连续ON,请将占空比设置为100%。
ssr_pwm.ChangeDutyCycle(100)
然后是要使LED点亮来表示工作中这个状态。
GPIO.output(AC_OUT_LED,1)
GPIO.output(START_STOP_LED,ledflag)
“AC_OUT_LED”表示咖啡机已启动(ON)。“START_STOP_LED”表示定时器正在运行,每隔1秒点亮一次。我是将“time.slee p(1)”加入循环中,以“ledflag = not ledflag”来实现间隔闪烁的。在LCD上,每隔1秒会显示定时器的剩余时间。
#2nd step: 闷蒸
程序结构与#1st step基本相同。由于咖啡机在闷蒸过程中处于OFF状态,所以将SSR(18)的PWM占空比设置为0%。
之后,让“AC_OUT_LED”熄灭。
#3rd step:萃取
第三步的结构也与#1st step 和#2nd step相同。通过执行以下语句来启用前述的基于PWM的ON/OFF功能。
ssr_pwm.ChangeFrequency(freq_int)
ssr_pwm.ChangeDutyCycle(duty)
“freq_int”和“duty”变量中含有前述的#Set Freqency and DutyCycle计算值。定时器循环结束后,将PWM设置如下,关闭咖啡机(频率=1Hz、duty=0%):
ssr_pwm.ChangeFrequency(1)
ssr_pwm.ChangeDutyCycle(0)
最后,允许设置杯数的中断并恢复开始按钮输入。
“except KeyboardInterrupt:”部分是当程序被CTRL+C中止时的异常处理,会清理GPIO并初始化所有的端口。
对python程序列表的说明就到这里。您可以通过下面的YouTube视频观察本装置的运行情况。
在下一篇也就是第4部分(剧终篇)中,我们将对这个软件进行扩展和修改,以便可以通过网络进行远程设置。我们会通过Raspberry Pi控制Web服务器功能,并从客户端Web浏览器进行设置。我们将使用html、Java Script、Ajax、PHP等Web编程来实现该功能。
最后,我还打算谈谈如何更好地使用本装置制作咖啡,敬请期待!
本系列连载的内容
第1部分:装置概况和咖啡萃取
第2部分:硬件制作
第3部分:使用Python完成控制软件(本文)
第4部分:远程设置软件