M5Stack Color Maker

![all pic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496179/d9175232-0b41-d864-7d91-653577226afa.png)

#はじめに
M5Stack Color Makerは、[M5Stack Japan Creativty Contest 2021](https://info.switch-science.com/m5stack-creativity-contest-2021)に応募するために制作しました。
一年前の第一回目のコンテスト時には電子工作歴1年ほどでで作品を作るアイデアも技術もありませんでしたが、今年はぜひ応募したく急遽企画、制作してみました。

#1.完成品
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">M5Stack Color Maker 完成！<br>カラーセンサーで色をスキャンして、絵の具を調合して出力する装置。<br>調整無しでも結構いい色出してます😁 <a href="https://t.co/69oX6V0px9">pic.twitter.com/69oX6V0px9</a></p>&mdash; AIRPOCKET＠rastaman vibration (@AirpocketRobot) <a href="https://twitter.com/AirpocketRobot/status/1428987707804438528?ref_src=twsrc%5Etfw">August 21, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
M5Stack Color Makerは次の三つ機能を持っています。
①色情報のスキャン
②色情報の編集
③色情報に基づいて絵の具を調色

これらの機能を用いて、欲しい色の絵の具を調色したり、現実のアイテムの色を絵の具で再現できます。
#2.構成
![構成3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496179/75f096bc-492b-1534-7d8a-bdddbc6fbd6a.png)


### コントローラー
今回作成した装置は、**M5Stack Core2**でUIとセンサーを、**Raspberry Pi PICO**で5つのステッピングモーターを制御しています。
M5Stackに接続されたカラーセンサーで色情報を読み取りUIに表示、色はUI上のスライダーで調整可能で、出力ボタンを押すとセットされた絵の具を自動で計量して調色できます。
色情報はUART通信でM5StackからRaspberry Pi PICOへ送信しています。
M5Stack Core2は**UIFlow**、RaspBerry Pi PICOは**Micropython**でコーディングしました。

### カラーセンサー
カラーセンサーTCS34725搭載の開発ボードを使用しています。M5Stackの**Color Sensor RGB Unit**と同等品で、Core2とI2C接続を行いUIFlowの標準ライブラリを使用しています。
カラーセンサーは外乱光により測定情報が狂うため、3Dプリンタでセンサーカバーを製作し、取り付けています。

### チューブポンプ（ステッピングモーター駆動）
絵の具を吐出するために、チューブポンプ（ローラーポンプ）を製作しました。
チューブポンプとは、チューブをローラーでしごいて液体を輸送するポンプで少量の薬液を計量・輸送する際に使用されます。
チューブポンプのアクチュエーターには**28BJY-48**というユニポーラ式ステッピングモーターを使用しています。
もともとはエアコンのルーバーを駆動させるためのモーターらしく非常に非力なので使い方を選びますが、ポンプ構造を工夫して実装しました。
非常に安価な商品ですので、ステッピングモーターの実験や勉強には適しているかと思います。

### モータードライバー
28BJY-48には**UL2003**というドライバーが付属しているものが多く流通していますが、今回はこれを用いず**A4988**というステッピングモーター用ドライバーを使用しました。理由はGPIOの削減のためです。28BJY-48が「ユニポーラ」式のステッピングモーターであるのに対し、A4988は「バイポーラ」方式のステッピングモーター用ドライバーですが、とくに改造などせずに使用できます。
処理速度の問題からRaspberry Pi PICOを採用したため、結果的にGPIO削減は不要になりましたが、企画当初の名残でA4988を使用しています。

#3.動作概要
光の三原色はRGB（赤、緑、青）、色の三原色はCMY（シアン、マゼンダ、イエロー）です。
プリンターなどではCMYにK（クロ）を加えた4色を調色してフルカラーを実現しています。

M5Stack Color Makerは、カラーセンサーで検出したRGBデータをCMYKデータに変換し、CMYKの絵具と水を自動で計量して調色します。
UI上にCMYK4色の配合比率をスライダーバーで表示しているため、スライダーを直接操作することでも色情報を編集できます。

### 光と色の関係
私たちの目にはRGBを感じる細胞があり、RGBの強さの組み合わせで幅広い色を認識しています。
Rの波長の光だけがあれば赤、Bだけであれば青、RとBが組み合わさると紫、という具合です。
RGBすべての光があると白色に感じます。

色の三原色CMYは、白色の光からRGBそれぞの波長の光を取り除く（吸収する）効果があります。
C（シアン）は、RGB（白色）の光からR（赤）だけを吸収し、GBのみを反射するため、GBの混ざった色に見えます。同様に、M（マゼンダ）はG（緑）を吸収してRBの混ざった色に見えます。Y（イエロー）はB（青）を吸収してRGの混ざった色に見えます。
たとえば、CとMを混ぜた絵具を画用紙に塗り白色光をあてると、RとGが吸収されて残ったBのみが反射するため、青色に見えます。

多くのプリンターなどでは、CMYにK（黒）を加えることで効率的に光を吸収して、暗い色を表現しています。

### 絵の具による色の再現
カラーセンサーでスキャンした色データはRGB値で取り込まれます。RGB値は次の計算によりCMYK値に変換できます。

**K = Min(1 – R, 1 – G, 1 – B)**

**C = ( 1 – R – K ) / ( 1 – K )**

**M = ( 1 – G – K ) / ( 1 – K )**

**Y = ( 1 – B – K ) / ( 1 – K )**

絵の具の調色を行うためには、色ごとの隠ぺい力や発色に合わせてさらに調整が必要となるため、上式でみちびかれたCMYK値のそれぞれの係数を掛け合わせて最終的な配合比率を決定します。
また本機では、水彩絵の具は水を加えて画用紙の白色を透過させて発色を調整するため、CMYKにW(Water)を加えた5色の混色で色表現を行っています。
[こちらのサイト](https://optica.cocolog-nifty.com/blog/2012/04/post-ab6f.html)の説明が判りやすくお勧めです。

#4.プログラム
###4-1.M5Stack Core2 のUIFlow
![uiflowcode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496179/6533e8de-a1c5-351d-76fe-a2e8d075fa4f.png)

```python
from m5stack import *
from m5stack_ui import *
from uiflow import *
from easyIO import *
import unit

screen = M5Screen()
screen.clean_screen()
screen.set_screen_bg_color(0xFFFFFF)
color3 = unit.get(unit.COLOR, unit.PORTA)


rgbR = None
rgbG = None
cmykC = None
enable = None
rgbB = None
cmykM = None
Dir = None
R = None
cmykY = None
CStep = None
G = None
cmykK = None
MStep = None
B = None
YStep = None
K = None
KStep = None
C = None
WStep = None
M = None
Cx = None
Y = None
Mx = None
Yx = None
Kx = None
Wx = None
Stepx = None
slider = None
cmykW = None
count = None
fontR = None
fontG = None
fontB = None
i = None


label0 = M5Label('R=', x=0, y=220, color=0x000, font=FONT_MONT_20, parent=None)
label1 = M5Label('G=', x=75, y=220, color=0x000, font=FONT_MONT_20, parent=None)
label2 = M5Label('B=', x=147, y=220, color=0x000, font=FONT_MONT_20, parent=None)
label9 = M5Label('255', x=26, y=220, color=0x000, font=FONT_MONT_20, parent=None)
label10 = M5Label('255', x=102, y=220, color=0x000, font=FONT_MONT_20, parent=None)
label11 = M5Label('255', x=173, y=220, color=0x000, font=FONT_MONT_20, parent=None)
label3 = M5Label('step', x=233, y=220, color=0x000, font=FONT_MONT_14, parent=None)
label4 = M5Label('i', x=279, y=220, color=0x000, font=FONT_MONT_14, parent=None)
label16 = M5Label('255', x=260, y=24, color=0x000, font=FONT_MONT_20, parent=None)
label17 = M5Label('255', x=260, y=59, color=0x000, font=FONT_MONT_20, parent=None)
label18 = M5Label('255', x=260, y=95, color=0x000, font=FONT_MONT_20, parent=None)
label19 = M5Label('255', x=260, y=131, color=0x000, font=FONT_MONT_20, parent=None)
slider0 = M5Slider(x=40, y=12, w=240, h=12, min=0, max=100, bg_c=0xa0a0a0, color=0x00ffff, parent=None)
slider1 = M5Slider(x=40, y=48, w=240, h=12, min=0, max=100, bg_c=0xa0a0a0, color=0xff00ff, parent=None)
slider2 = M5Slider(x=40, y=84, w=240, h=12, min=0, max=100, bg_c=0xa0a0a0, color=0xffff00, parent=None)
slider3 = M5Slider(x=40, y=120, w=240, h=12, min=0, max=100, bg_c=0xa0a0a0, color=0x000000, parent=None)
touch_button0 = M5Btn(text='SCAN', x=14, y=154, w=120, h=60, bg_c=0xFFFFFF, text_c=0x000000, font=FONT_MONT_14, parent=None)
touch_button1 = M5Btn(text='MAKE COLOR', x=179, y=154, w=120, h=60, bg_c=0xFFFFFF, text_c=0x000000, font=FONT_MONT_14, parent=None)

import math


def upRange(start, stop, step):
  while start <= stop:
    yield start
    start += abs(step)

def downRange(start, stop, step):
  while start >= stop:
    yield start
    start -= abs(step)


def touch_button0_pressed():
  global rgbR, rgbG, cmykC, enable, rgbB, cmykM, Dir, R, cmykY, CStep, G, cmykK, MStep, B, YStep, K, KStep, C, WStep, M, Cx, Y, Mx, Yx, Kx, Wx, Stepx, slider, cmykW, count, fontR, fontG, fontB, i
  rgbR = color3.red
  rgbG = color3.green
  rgbB = color3.blue
  R = rgbR / 255
  G = rgbG / 255
  B = rgbB / 255
  K = 1 - max([R, G, B])
  C = (1 - R - K) / (1 - K)
  M = (1 - G - K) / (1 - K)
  Y = (1 - B - K) / (1 - K)
  cmykC = round(C * 100)
  cmykM = round(M * 100)
  cmykY = round(Y * 100)
  cmykK = round(K * 100)
  slider0.set_value(cmykC)
  slider1.set_value(cmykM)
  slider2.set_value(cmykY)
  slider3.set_value(cmykK)
  slider = True
  pass
touch_button0.pressed(touch_button0_pressed)

def touch_button1_pressed():
  global rgbR, rgbG, cmykC, enable, rgbB, cmykM, Dir, R, cmykY, CStep, G, cmykK, MStep, B, YStep, K, KStep, C, WStep, M, Cx, Y, Mx, Yx, Kx, Wx, Stepx, slider, cmykW, count, fontR, fontG, fontB, i
  uart1.write(str(cmykC)+"\r\n")
  uart1.write(str(cmykM)+"\r\n")
  uart1.write(str(cmykY)+"\r\n")
  uart1.write(str(cmykK)+"\r\n")
  pass
touch_button1.pressed(touch_button1_pressed)


uart1 = machine.UART(1, tx=25, rx=27)
uart1.init(115200, bits=8, parity=None, stop=1)
enable = 2
Dir = 13
CStep = 14
MStep = 19
YStep = 25
KStep = 26
WStep = 27
Cx = 1
Mx = 1
Yx = 1
Kx = 1
Wx = 0.5
Stepx = 1
cmykC = 0
cmykM = 0
cmykY = 0
cmykK = 0
cmykW = 100
rgbR = 0
rgbG = 0
rgbB = 0
slider = False
count = False
digitalWrite(enable, 1)
digitalWrite(Dir, 0)
while True:
  rgbR = round(255 * (1 - cmykC / 100) * (1 - cmykK / 100))
  rgbG = round(255 * (1 - cmykM / 100) * (1 - cmykK / 100))
  rgbB = round(255 * (1 - cmykY / 100) * (1 - cmykK / 100))
  fontR = 255 - rgbR
  fontG = 255 - rgbG
  fontB = 255 - rgbB
  if (slider0.get_value()) != cmykC:
    cmykC = slider0.get_value()
    slider = True
  if (slider1.get_value()) != cmykM:
    cmykM = slider1.get_value()
    slider = True
  if (slider2.get_value()) != cmykY:
    cmykY = slider2.get_value()
    slider = True
  if (slider3.get_value()) != cmykK:
    cmykK = slider3.get_value()
    cmykW = 100 - cmykK
    slider = True
  if slider == True:
    touch_button1.set_bg_color((rgbR << 16) | (rgbG << 8) | rgbB)
    touch_button1.set_btn_text_color((fontR << 16) | (fontG << 8) | fontB)
    slider = False
  label9.set_text(str(rgbR))
  label10.set_text(str(rgbG))
  label11.set_text(str(rgbB))
  label16.set_text(str(cmykC))
  label17.set_text(str(cmykM))
  label18.set_text(str(cmykY))
  label19.set_text(str(cmykK))
  if count == True:
    label3.set_text('              ')
    digitalWrite(enable, 0)
    digitalWrite(CStep, 0)
    digitalWrite(MStep, 0)
    digitalWrite(YStep, 0)
    digitalWrite(KStep, 0)
    digitalWrite(WStep, 0)
    label3.set_text(str(round(max([cmykC * Stepx * Cx, cmykM * Stepx * Mx, cmykY * Stepx * Yx, cmykK * Stepx * Kx, cmykW * Stepx * Wx]))))
    i_end = float(round(max([cmykC * Stepx * Cx, cmykM * Stepx * Mx, cmykY * Stepx * Yx, cmykK * Stepx * Kx, cmykW * Stepx * Wx])))
    for i in (0 <= i_end) and upRange(0, i_end, 1) or downRange(0, i_end, 1):
      if i < round(cmykC * Stepx * Mx):
        digitalWrite(CStep, 1)
      if i < round(cmykM * Stepx * Mx):
        digitalWrite(MStep, 1)
      if i < round(cmykY * Stepx * Yx):
        digitalWrite(YStep, 1)
      if i < round(cmykK * Stepx * Kx):
        digitalWrite(KStep, 1)
      if i < round(cmykW * Stepx * Wx):
        digitalWrite(WStep, 1)
      digitalWrite(CStep, 0)
      digitalWrite(MStep, 0)
      digitalWrite(YStep, 0)
      digitalWrite(KStep, 0)
      digitalWrite(WStep, 0)
    count = False
  wait_ms(2)
```

###4-2.Raspberry Pi PICOのMicropythonコード
Raspberry Pi PICOのステッピングモーター制御用プログラムは追って公開します。

#5.配線図
配線図は追って公開予定です。基板はユニバーサル基板を利用していますので、基板図面はありません。
###5-1.センサー
###5-2.UART
###5-3.A4988
###5-4.28BJY-48

#6.3Dデータ
3Dデータは追って公開予定です。
###6-1.センサーカバー
###6-2.筐体
###6-3.チューブポンプ

#7.開発小話 ～チューブポンプの新規開発～
今回、絵の具の微量計量及び送液にチューブポンプを採用しました。チューブポンプは、ローラーのついた回転子をモーターで回転させ、ローラーと筐体の間に挟んだチューブをしごいて送液します。モーターの回転量を制御すると送液量をコントロールできるため、計量と送液を同時に行えます。
一方で、ローラーをしごく構造上、送液に脈動が発生します。今回の様に微量の液体を計量する場合、この脈動が計量精度を大幅に悪化させるため脈動の少ないローラーポンプを開発しました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ちなみに、一般的な構造のチューブポンプはこんな感じで脈動する。 <a href="https://t.co/vpRI4CEudi">pic.twitter.com/vpRI4CEudi</a></p>&mdash; AIRPOCKET＠rastaman vibration (@AirpocketRobot) <a href="https://twitter.com/AirpocketRobot/status/1422917186218123266?ref_src=twsrc%5Etfw">August 4, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
一般的なチューブポンプは脈動により進む→止まる→進む→止まるの繰り返しです。
これに対して、開発したチューブポンプは液体が進み続けています。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">最終調整後。まだ多少脈動が残るけど、これ以上はモーター側のグレードアップが必要。 <a href="https://t.co/BINvCd8sAl">pic.twitter.com/BINvCd8sAl</a></p>&mdash; AIRPOCKET＠rastaman vibration (@AirpocketRobot) <a href="https://twitter.com/AirpocketRobot/status/1430724561163591684?ref_src=twsrc%5Etfw">August 26, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

この技術は新規性がありそうなため、特許申請を考え既存技術を調査中です。

ちなみにより高精度な計量、送液が必要なインクジェットプリンターなどでは、圧電ダイヤフラム式のポンプが使用されています。自作が難しそうなため今回は採用を見送っています。

#8.さいごに
M5Stack Color Makerは電子工作歴２年の集大成の一つです。技術的に難しいことはしていませんが、だれでも楽しめるガジェットを目指して企画しました。完成後、Twitterで公開したところ予想を超える反応をいただいたため、とても満足しています。
最後に、今後のメイカー活動の方向性に大きな影響を受けたツイートをご紹介したいと思います。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">色盲の私には夢の様な装置！<br>赤金ルアー作ったつもりなのに緑金だったなんて事がなくなるんですね！<br><br>文字通り人生が変わる人もいるかもしれん装置よこれ（泣） <a href="https://t.co/EYGHexy3vc">https://t.co/EYGHexy3vc</a></p>&mdash; 🆃🅾🅼🅼🆈 (@Tomy_cn) <a href="https://twitter.com/Tomy_cn/status/1429056717698920459?ref_src=twsrc%5Etfw">August 21, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">色を混ぜるマシンの動画をみて即反応してしまいました！<br><br>学校では絵を描くたびに個性的な色使いだといわれ、地図帳の色分けの区別が出来ず<br>後に自分が色覚異常と知り、色を使う事、色名を口に出す事すら怖くなりました<br><br>大袈裟に聞こえるかもしれませんが、同意してくれる方は少くないと思います</p>&mdash; 🆃🅾🅼🅼🆈 (@Tomy_cn) <a href="https://twitter.com/Tomy_cn/status/1429214702911901696?ref_src=twsrc%5Etfw">August 21, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

@Tomy_cnさんのツイートですが、私がまったく想像していなかった言葉をいただきました。
恥ずかしなら私は、これまでの生活のなかで「障害者」について深く考える機会がありませんでした。しかしこのツイートをいただき、障害が作る壁に傷ついたり、無用の苦労をしている人の力になれるのであれば、そのために私の電子工作のスキルやアイデアが生かせるのであれば、こんなに幸せなことはないと感じました。

以前音を可視化する装置を開発した際には、片耳が聞こえないという方から、「片耳しか聞こえない＝音源の方向がわからない、私にとっては、とってもありがたい物です。実用化、製品化されたらいいのにと思いました」という言葉をいただいたこともあります。

おそらく「障害の壁をこわす装置」の開発が、今後私のメイカー活動の大きなテーマになるだろうと感じています。