使用Raspberry Pi制作咖啡机控制装置 第3部分:使用Python完成控制软件

第1部分:装置概况和咖啡萃取
第2部分:硬件制作

 

本系列连载将为您介绍使用Raspberry Pi制作咖啡机控制装置的方法。本系列为我们分享的嘉宾是阿矢谷充先生,他迄今为止在各种媒体上,面向喜欢自己动手制作的群体发表过很多电子作品制作类的文章。在连载的第1部分中,介绍了装置的概况;在第2部分中,介绍了硬件的制作和安装方法;在第3部分中,将为您介绍控制程序的编写方法。

coffee-maker-with-raspberry-pi-03-01

 

目录

  1. 环境设置
  2. Python的开发和运行环境
  3. I2C液晶工作确认模块
  4. 咖啡机控制主程序(整体结构)
  5. 使用Python完成控制程序

 

1.环境设置

首先,我们来了解一下运行环境。操作系统是Raspberry Pi标准的Raspbian,我使用的是Release10:buster。在这里我们不会涉及它们的安装方法。

先为您介绍必要的设置。通过菜单打开“Raspberry pi设置”页面,点开“接口”选项卡。

coffee-maker-with-raspberry-pi-03-02

 

这里必须启用的项目是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如下:

coffee-maker-with-raspberry-pi-03-03

 

首先,关于编辑器,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. 咖啡机控制主程序(整体结构)

首先,控制程序的整体结构如下所示。

coffee-maker-with-raspberry-pi-03-04

 

左侧的蓝色部分是在开始时只运行一次的初始设置模块。绿色部分是与确定杯数的中断例程相关的模块。这也就是在第2部分中提到的硬件——黑色微动开关按钮,通过它来决定要萃取多少杯咖啡。每按一次开关,就会通过中断处理更改杯数。

右侧的橙色部分是主例程。首先是等待启动按钮(红色微动开关)输入,然后是由第一步、第二步、第三步组成的定时器循环。

第一步是“初次注水”。在进入第二步“闷蒸”之前,让热水浸润咖啡粉。咖啡机将只在这个设置时间内处于ON(通电)状态。
第二步是“闷蒸”。热水浸润咖啡粉后,保持一段时间。此时,咖啡机处于OFF(断电)状态。
第三步是“萃取”。此时,咖啡机基本上是ON(通电)状态。作为附加功能,我增加了基于PWM的ON/OFF控制功能。这可以分别设置咖啡机ON的时间(秒)和OFF的时间(秒)。我希望用这个功能尝试通过软件实现一种叫做“节奏萃取”(加入一定量的热水然后暂停,反复此操作)的方法。关于PWM功能的详细内容,将在python程序介绍部分中进行说明。

下表1中汇总了上述设置参数。

coffee-maker-with-raspberry-pi-03-05

表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功能。其工作示意图如下:

coffee-maker-with-raspberry-pi-03-06-2

 

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视频观察本装置的运行情况。

※此链接为Youtube视频

 

在下一篇也就是第4部分(剧终篇)中,我们将对这个软件进行扩展和修改,以便可以通过网络进行远程设置。我们会通过Raspberry Pi控制Web服务器功能,并从客户端Web浏览器进行设置。我们将使用html、Java Script、Ajax、PHP等Web编程来实现该功能。

最后,我还打算谈谈如何更好地使用本装置制作咖啡,敬请期待!

 

 

本系列连载的内容

第1部分:装置概况和咖啡萃取
第2部分:硬件制作
第3部分:使用Python完成控制软件(本文)
第4部分:远程设置软件

阿矢谷充

在一家测量仪器公司(网络设备供应商)担任系统工程师。目前在各媒体上,面向喜欢自己动手制作的群体撰写和发表电子作品制作类的文章。爱好是演奏古乐器和琉特琴。现任日本琉特琴协会理事。

http://lute.penne.jp/thumbunder/

相关文章

  1. basic-of-raspberrypi_01_02-1

    Raspberry Pi使用前的准备【第1篇】Raspberry Pi的基础知识

  2. 使用Raspberry Pi 4进行电子制作 入门!【第4篇】 使用Raspberry Pi 4和G…

  3. raspberrypi04_1

    使用Raspberry Pi 4进行电子制作 入门! 【第3篇】无头模式下熟练使用Raspberry…

  4. raspberrypi04_1

    使用Raspberry Pi 4进行电子制作的入门教程! 【第2篇】Raspberry Pi 4的设…

  5. raspberrypi04_1

    使用Raspberry Pi 4进行电子制作的入门教程! 第1篇: 开始使用新产品“Raspberr…

  6. i06_4a

    制作一款图形处理装置,用数字控制自然力

  7. ito_05

    制作卡通角色随风摆动就能绘制图形作品的装置

  8. i04-8

    使用传感器轻松制作有趣的项目

  9. what-is-raspberrypi_01_2

    从历史到使用方法的全面了解!电子作品创作不可或缺的“Raspberry Pi(树莓派)”究竟是何方神…

TECH INFO

  • Sugiken老师的电机驱动器课堂
  • 重点必看
  • 技术分享
  • Arduino入门指南

基础知识

  • Si功率元器件
  • IGBT功率元器件
  • 热设计
  • 电路仿真
  • 开关噪声-EMC
  • AC/DC
  • DC/DC
  • 电机
  • 传递函数

工程技巧


Sugiken老师的电机驱动器课堂

PICK UP

PAGE TOP