M5stackで2足歩行ロボットの作成⑥「micropythonで液晶画面に表情をつけてサーボモーターと連動させる」
2021-07-23

今回は、M5stackの画面に表情を付けるプログラムを組んでいきたいと思います。
サムネイルにあるとおり質素なものですので、ずっと同じ表情だと少しあれなんでサーボモーターが動作した時に口をぱくぱくさせる感じに仕上げます。
それでは、あらかじめロボット(サーボモーター)、pca9685、m5stack、電池などのハードウェアをPCとつなげておきます。
プログラムは、前回作成したserver.pyに追加していきます。
server.py
import network
import machine
import time
import socket
import re
from machine import Pin,I2C
import servo
import pca9685
from m5stack import lcd
ESSID = 'M5stack'
PASSWORD = '11223344'
IP = '192.168.5.1'
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
sev = servo.Servos(i2c)
sv ="p"
posi = {"lx":90, "ly":90, "lr":10,
"rx":230, "ry":90, "rr":10,
"ux":110,"uy":170}
lcd.clear(lcd.BLACK)
def nomale_lcd():
lcd.circle(posi["lx"], posi["ly"],
posi["lr"], lcd.WHITE, lcd.WHITE)
lcd.circle(posi["rx"], posi["ry"],
posi["rr"], lcd.WHITE, lcd.WHITE)
lcd.rect(posi["ux"], posi["uy"],
100, 5, lcd.WHITE, lcd.WHITE)
#サーボモーター制御
def servoset():
global sv
if sv == "st":
lcd.rect(posi["ux"], posi["uy"],
100, 30, lcd.WHITE, lcd.BLACK)
sev.position(0, 97)
time.sleep_ms(1000)
sev.position(1, 90)
time.sleep_ms(1000)
sev.position(2, 100)
time.sleep_ms(1000)
sev.position(3, 90)
time.sleep_ms(1000)
elif sv == "fw":
lcd.rect(posi["ux"], posi["uy"],
100, 30, lcd.WHITE, lcd.BLACK)
sev.position(3, 70)
sev.position(1, 70)
time.sleep_ms(500)
sev.position(0, 77)
sev.position(2, 80)
time.sleep_ms(500)
sev.position(3, 90)
sev.position(1, 90)
time.sleep_ms(500)
sev.position(3, 110)
sev.position(1, 110)
time.sleep_ms(500)
sev.position(0,117)
sev.position(2, 120)
time.sleep_ms(500)
sev.position(3, 90)
sev.position(1, 90)
time.sleep_ms(500)
elif sv == "lt":
lcd.rect(posi["ux"], posi["uy"],
100, 30, lcd.WHITE, lcd.BLACK)
sev.position(3, 40)
sev.position(1, 60)
time.sleep_ms(500)
sev.position(0, 117)
time.sleep_ms(500)
sev.position(3, 90)
time.sleep_ms(500)
sev.position(1, 90)
time.sleep_ms(500)
sev.position(0, 97)
time.sleep_ms(500)
elif sv == "bk":
lcd.rect(posi["ux"], posi["uy"],
100, 30, lcd.WHITE, lcd.BLACK)
sev.position(3, 110)
sev.position(1, 110)
time.sleep_ms(500)
sev.position(0, 77)
sev.position(2, 80)
time.sleep_ms(500)
sev.position(3, 90)
sev.position(1, 90)
time.sleep_ms(500)
sev.position(3, 70)
sev.position(1, 70)
time.sleep_ms(500)
sev.position(0,117)
sev.position(2, 120)
time.sleep_ms(500)
sev.position(3, 90)
sev.position(1, 90)
time.sleep_ms(500)
elif sv == "rt":
lcd.rect(posi["ux"], posi["uy"],
100, 30, lcd.WHITE, lcd.BLACK)
sev.position(3, 120)
sev.position(1, 140)
time.sleep_ms(500)
sev.position(2, 80)
time.sleep_ms(500)
sev.position(1, 90)
time.sleep_ms(500)
sev.position(3, 90)
time.sleep_ms(500)
sev.position(2, 100)
time.sleep_ms(500)
#html
def home_page():
html = """<html lang="ja">
<head>
<meta charset="utf-8">
<title>サーボテスト</title>
<meta name="viewport" content=
"width=device-width, initial-scale=1">
<style>
* {
margin: 0px;
padding: 0px;
}
body {
max-width: 600px;
font-size: 25px;
width: 100%;
}
main {
height: 40vh;
background: skyblue;
}
ul {
display: block;
height: 40vh;
list-style: none;
padding-top: 10px;
}
.bc {
display: flex;
}
.a, .bc, .d {
height: 12vh;
}
li {
width: 100px ;
height: 90% ;
margin-left: auto;
margin-right: auto;
background: yellow;
}
.b {
margin-left: auto;
}
.c {
margin-right: auto;
}
ul li {
text-align: center;
}
.a li, .b li, .c
li, .d li {
border: solid 1px;
}
.n li {
background: yellow;
}
a:active {
color: #ff2020;
}
.a li, .b li, .c li, .d li, .n li {
border: solid 1px;
}
.n {
margin-right: 3px;
margin-left: 3px;
}
</style>
</head>
<body>
<main>
<ul>
<div class="a">
<a href="/svm_aa">
<li id="forward">前進</li>
</a></div>
<div class="bc">
<div class="b">
<a href="/svm_bb">
<li id="left">左旋回</li>
</a></div>
<div class="n">
<a href="/svm_ee">
<li id="set">SET</li>
</a></div>
<div class="c">
<a href="/svm_cc">
<li id="right">右旋回</li>
</a></div>
</div>
<div class="d">
<a href="/svm_dd">
<li id="backward">後退</li>
</a></div>
</ul>
</main>
</body>
</html>"""
return html #テキストとして値を返す
nomale_lcd()
#WIFI APの立ち上げ
ap = network.WLAN(network.AP_IF)
ap.config(essid=ESSID, authmode=3,
password=PASSWORD)
ap.ifconfig((IP,'255.255.255.0',IP,
'8.8.8.8'))
ap.active(True)
print("AP OK")
#WEBサーバーの立ち上げ
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
#クライアント接続
while True:
conn, addr = s.accept()
request = str(conn.recv(1024))
m = re.search(r'svm_(\D\D\s)', request)
#print(request)
#ボタン操作反映
if m != None :
svm = m.group(0)
if svm == 'svm_aa ':
sv = "fw"
elif svm == 'svm_bb ':
sv = "lt"
elif svm == 'svm_cc ':
sv = "rt"
elif svm == 'svm_dd ':
sv = "bk"
elif svm == 'svm_ee ':
sv = "st"
servoset() #サーボ関数呼び出し
sv ="p"
lcd.clear(lcd.BLACK)
nomale_lcd()
response = home_page()#html text格納
conn.send(response)#htmlデータ送信
conn.close() #scket破棄
print("Data Received")
from m5stack import lcd のライブラリで、表示関係の関数が使えるようになります。下記にドキュメントへのリンクを張っておくので参考にしてください。↓
https://github.com/m5stack/M5Stack_MicroPython/blob/master/README.md#lcd
posiの変数に、両目と口の表示位置を格納しています。M5stackは320×240なので、xとy(横軸と縦軸)で座標を決めておきます。今回lは左目、rは右目、uは口の座標としています。
lcd.clearのメソッドは、引数に背景色を選択できます。
続いて表情を表示させる関数nomale_lcdを定義しています。lcd.circle は、指定した半径の円状のグラフィックを表示させるメソッドになります。引数の第一、第二に座標、第三に半径、第四が枠の色で第五が塗りつぶしの色になります。lcd.recth は、四角のグラフィックを表示できます。座標を決めて、第三引数にwhidth,第四にheightを指定します。
クライアントがWEBサーバーに接続後のwhileに入る前にnomale_lcdを呼び出して、M5stackの電源投入後に表情が表示されるようにしています。
あとはサーボモーターの関数が実効された場合、口を開ける動作にしたいので、前後左右それぞれのプログラムの先頭にlcd.recthを入れています。
サーボの動作終了後に口が開きっぱなしになるので、一旦クリアして、再度デフォルトの状態のnomale.lcdを呼び出しています。
こんな感じになります。↓
次回はこのプログラムをmain.pyに変更し、ハードウェアを全部脳みそに詰め込んで、表示部だけを外側から見えるようにして完成に持っていきたいと思います。