概要
ミニドーナツに、いろんなフレーバーのパウダーを振りかけて円グラフを描く装置です。
最大5要素、各0~5の値を入力すると、入力した値の比率に合わせてフレーバの粉が振りかけられます。
娘と原宿を歩いていたら、ドーナツ屋さんに行列ができていて「この店のドーナツ、半分は土地代だねー」ってつぶやいたら「現実に引き戻さないでよ」と言われました。
現実だって楽しくおいしく味付けして楽しめるということを伝えたくて作りました。
システム構成
フレーバパウダー押出装置×4
ターンテーブル×1
microbit×1
フレーバパウダー押出装置はシリンダ・スクリュー・ヘッドからなり、ローテーションタイプのサーボモータで駆動しています。
押出装置は90°の角度差で4機搭載されていますので、4種類のフレーバ+プレーンの5種類の味を楽しめます。
ターンテーブルはステッピングモータで動かします。
microbitで、ドーナツ円周方向座標と各パウダーの描画座標を制御してドーナツ上にグラフを描画します。
操作方法
この装置は4種類のフレーバパウダーをパウダー押出機に入れて使います。
それぞれのパウダーとパウダーを書けないプレーンの比率をセットして開始すると、それぞれの比率に合わせて円グラフを描くようにパウダーを振りかけます。
フレーバの比率はmicro:bitのA,Bボタンで入力し、タッチセンサーで描画を開始します。
選択されている列はLEDの点灯が反転します。
この画像の例では、第1列がラズベリー味で値は5、第2列はマンゴー味で値は2、第3列が抹茶で値が2、第4列が選択されており、味はパウダーシュガーで値は1、第5列はプレーンで値は0です。
この状態でタッチセンサに触ると、ラズベリー:マンゴー:抹茶:パウダーシュガーが、5:2:2:1となるようにグラフが描画されます。
メカの構造
パウダー押出機
パウダー押出機は、プレートノズル、シリンダー、スクリュー、サーボマウントの4つの部品から組み立てられています。
プレートノズルはパウダーをライン上に吐き出させるためにスクリュー回転軸から偏心させた円弧に沿ってスリットを開けています。偏心の程度やスリット幅を調整し、均等にパウダーを吐き出せるように工夫しています。
ターンテーブル
ターンテーブルは、均等な速度でドーナツを回転させるための装置です。アクチュエータにはステッピングモータを使用し、回転座標を正確に制御します。
ターンテーブルはドーナツをセットしやすくするため、リニアガイド上に設置しています。デコレーション位置のストッパーにはマグネットを設置して、位置を正確に保持できます。
ドーナツを取り出す際にはターンテーブル事引き出せます。
ベースプレート
ベースプレートは4部品を組み合せて製作します。プレート上にターンテーブル、パウダー押出機、microbitのマウンタが取り付けられています。
食品用の装置なので、洗浄しやすいようにパウダー押出機をワンタッチで着脱できるようにしています。
コード
開発はmicrobit python editorで行いました。
from microbit import *
# --- 設定セクション ---
FULL_STEPS_PER_ROTATION = 4096 # ステッピングモーター1回転のステップ数(28BYJ-48 = 4096)
NOZZLE_ANGLES = [0, 90, 180, 270] # 各カラーシュガーのノズル位置(a, b, c, d)
positionOpen = 120
positionClose = 30
trayIsOpened = True
servoDelay = 10
stepperPins = [pin13, pin14, pin15, pin16]
trayPin = pin0
color = [pin1, pin2, pin8, pin9] # カラーa, b, c, d
colorCount = [0, 0, 0, 0, 0] # 各色の割合(最大5色)
currentColumn = 0
screwSpeed = 55 #26~128
screwStop = 77
# --- ステッピングモーター制御クラス ---
class Stepper28BYJ48:
step_sequence = [
[1,0,0,0],
[1,1,0,0],
[0,1,0,0],
[0,1,1,0],
[0,0,1,0],
[0,0,1,1],
[0,0,0,1],
[1,0,0,1]
]
def __init__(self, pins, delay_ms=2):
if len(pins) != 4:
raise ValueError("4ピン指定が必要")
self.pins = pins
self.delay_ms = delay_ms
self.position = 0
def move(self, steps):
direction = 1 if steps > 0 else -1
for _ in range(abs(steps)):
self.position = (self.position + direction) % 8
self._set_pins(self.step_sequence[self.position])
sleep(self.delay_ms)
self.release()
def _set_pins(self, pin_states):
for pin, state in zip(self.pins, pin_states):
pin.write_digital(state)
def release(self):
for pin in self.pins:
pin.write_digital(0)
# --- ユーティリティ関数群 ---
def angleToSteps(angle):
return int(round(FULL_STEPS_PER_ROTATION * angle / 360))
def moveServo(start, stop):
trayPin.write_analog(stop)
#step = 1 if start <= stop else -1
#for i in range(start, stop + step, step):
# trayPin.write_analog(i)
# sleep(servoDelay)
def closeTray():
global trayIsOpened
moveServo(positionOpen, positionClose)
sleep(1000)
trayIsOpened = False
def openTray():
global trayIsOpened
moveServo(positionClose, positionOpen)
sleep(1000)
trayIsOpened = True
def colorCountToImage(counts, highlight=-1):
rows = []
for y in range(5):
row = ''
for x in range(5):
val = 9 if counts[x] >= 5 - y else 0
if x == highlight:
val = 9 - val # 反転表示
row += str(val)
rows.append(row)
return Image(':'.join(rows))
def drawPieChart(counts):
total = sum(counts)
if total == 0:
return
segment_angle = 0
angle_per_counts = 360 / total
for i in range(4):
if counts[i] == 0:
continue
segment_angle = counts[i] * angle_per_counts
if i != 0:
motor.move(angleToSteps(NOZZLE_ANGLES[i]- NOZZLE_ANGLES[i-1])) #今回ノズル位置の位置合わせ
color[i].write_analog(screwSpeed) #シュガー用モーター動作
motor.move(angleToSteps(segment_angle)) #ドーナツ回転
color[i].write_analog(screwStop) #シュガー用モーター停止
# 残りすべてゼロなら終了
if all(c == 0 for c in counts[i + 1:]):
break
# --- 初期化 ---
motor = Stepper28BYJ48(stepperPins, delay_ms=5)
trayPin.set_analog_period(positionOpen)
color[0].set_analog_period(20)
color[1].set_analog_period(20)
color[2].set_analog_period(20)
color[3].set_analog_period(20)
# --- メインループ ---
while True:
display.show(colorCountToImage(colorCount, currentColumn))
if pin_logo.is_touched():
if trayIsOpened:
closeTray()
drawPieChart(colorCount)
else:
openTray()
elif button_b.is_pressed():
currentColumn = (currentColumn + 1) % 5
sleep(200)
elif button_a.is_pressed():
colorCount[currentColumn] = (colorCount[currentColumn] + 1) % 6
sleep(200)
sleep(100)
投稿者の人気記事





-
airpocket
さんが
2025/08/01
に
編集
をしました。
(メッセージ: 初版)
-
airpocket
さんが
2025/08/01
に
編集
をしました。
-
airpocket
さんが
2025/08/01
に
編集
をしました。
ログインしてコメントを投稿する