2019年3月2日 星期六

[micropython] pyboard Drum

從朋友那拿到一組電玩用的鼓組,原本的電路板壞了,朋友自己焊了一塊,轉接到PS搖桿再連到電腦上玩

由於想增加第八個鼓面,朋友原先的電路感覺不容易新增與維護
於是選擇了用 pyboard 去做改接 (可以直接模擬成電腦搖桿的 Arduino 板不知丟哪去了 Q_Q)
每個鼓面下面都是蜂鳴片(壓電元件),並上 1M ohm 電阻後,
一端接地,一端接 AD輸入[1][2][3]

pyboard 提供了可以當成電腦鍵盤的功能 (接上USB時,電腦辨識成鍵盤)
當成電腦鍵盤時,同時可模擬按壓六個按鍵 [4][5]

pyboard 的USB連接到電腦時,可以是 [6]
互動執行 + 隨身碟(儲存裝置) 或
互動執行 + HID裝置 (可以為滑鼠或鍵盤)

隨身碟模式方便把寫好的程式拷到板子上跑,HID鍵盤裝置是最終要玩打鼓遊戲的設置

一開始先測試 pyboard 的 AD [7] 
先連續讀值並把數值丟出來看(如下),冒號右邊為保值的最大值
   37    22    25    24    30    28    27    20 : 49 1179 120  40  42  62  46  30
   34    23    27    22    27    32    23    13 : 49 1179 120  40  42  62  46  30
   35    24    24    22    28    25    20    15 : 49 1179 120  40  42  62  46  30
上面分別為八個鼓面的 AD輸出與最大值的保值
多試個幾次去記錄最大值,以此值做 閥值的參考
當讀值超過閥值時,才當成有輸入

測試過程中可以發現透過鼓架傳遞震動,有時敲 A 鼓,B也有反應,但輸出較小,持續時間也較短
於是加入第二個判斷,當超過閥值時,計數陣列值+1
如下為 3 與 4 鼓面同時被判定有輸入,持續時間達 7 ms 與 2ms (使用每 1ms 讀一批次AD)
[0, 0, 7, 2, 0, 0, 0, 0]

當持續發生有輸入時,送出對應的按鍵值給電腦
電腦對按鍵的辨識,間距要 10ms 以上,不然會出錯

以下程式,其中 main 裡面放了
changetMode() 方便切換 Storage 與 HID
modify_threshold() 方便修改閥值(在HID模式時)並儲存 
ma() 使用前要關掉 timer 中斷( tim2.deinit());用來查看連續的AD讀值

boot.py

# boot.py -- run on boot-up
# can run arbitrary Python, but best to keep it minimal

import machine
import pyb
#pyb.main('main.py') # main script to run after this one
#pyb.usb_mode('VCP+MSC') # act as a serial and a storage device
#pyb.usb_mode('VCP+HID') # act as a serial device and a mouse
pyb.usb_mode('CDC+HID',hid=pyb.hid_keyboard)

main.py

#hardware platform: pyboard V1.1
import time
import os
import struct
import micropython
import machine
import pyb

def changetMode(m='f'):
 '''
 parameter mode
 m = 'f' : flash
 m = other: HID_KB
 '''
 # from HID_KB mode return to Flash mode
 f=open('boot.py', 'r')
 boot=f.readlines()
 f.close()
 if m == 'f' and len(boot)>8:   # USB to flash mode
  print('back to flash mode, will reset')
  boot.pop(-1) 
  f=open('boot.py', 'w')
  for i in range(len(boot)):
   f.write(boot[i])
  f.close()
  machine.reset()
 else:          # USB to HID KB mode
  print('change to KB mode')
  f=open('boot.py', 'w')
  for i in range(len(boot)):
   f.write(boot[i])
  f.write(b"pyb.usb_mode('CDC+HID',hid=pyb.hid_keyboard)")
  f.write('\n')
  f.close()
  machine.reset()

  
hid=pyb.USB_HID()

# byte 0 Modifier keys
# byte 1 Reserved
# byte 2 Keycode 1
# byte 3 Keycode 2
# byte 4 Keycode 3
# byte 5 Keycode 4
# byte 6 Keycode 5
# byte 7 Keycode 6
# max 6 keys send at the same time 


def release_key():
 global hid
 buf = bytearray(8) # report is 8 bytes 
 #buf[2] = 0
 hid.send(buf) # key released
 pyb.delay(10) #must delay 10ms

def press_key(key = [0]):
 global hid
 buf = bytearray(8) # report is 8 bytes 
 for i in range(len(key)):
  buf[i+2] = key[i]
 #buf[i+3] = 40 # Enter; for Note test
 hid.send(buf) # key released
 pyb.delay(10) #must delay 10ms


X1 = pyb.ADC(pyb.Pin('X1')) # set X1 for analog input 
X2 = pyb.ADC(pyb.Pin('X2')) # set X2 for analog input 
X3 = pyb.ADC(pyb.Pin('X3')) # set X3 for analog input 
X4 = pyb.ADC(pyb.Pin('X4')) # set X4 for analog input 
X5 = pyb.ADC(pyb.Pin('X5')) # set X5 for analog input 
X6 = pyb.ADC(pyb.Pin('X6')) # set X6 for analog input 
X7 = pyb.ADC(pyb.Pin('X7')) # set X7 for analog input 
X8 = pyb.ADC(pyb.Pin('X8')) # set X8 for analog input 

AD = [X1, X2, X3, X4, X5, X6, X7, X8]

KBKEY = [30, 31, 32, 33, 34, 35, 36, 37] # mapping HID KB 1 ~ 8

# Timer 1 ~ 14
#Timer 3 is reserved for internal use, and 5 and 6 are used for servo and ADC/DAC control

import threshold as th

threshold = th.threshold

DRUM = [0]*8 # 
AVG = [0]*8 # for average
ADV=[0]*8 # store read AD value
flg20ms = 0

hisstat=[0]*8 # history of DRUM status

# if DRUM 0 -> 1, do nothing
# if DRUM 1 -> 0, set 1 (button up trigger)
flg_doeven = 0

def Timer2callback(t):
 global AD
 global ADV
 global DRUM
 global threshold
 global hisstat
 global flg_doeven
 global flg20ms

 if flg20ms > 0:
  flg20ms = flg20ms - 1
  
 for i in range(8):
  ADV[i] = AD[i].read()
  
  if ADV[i] > threshold[i]:
   DRUM[i] = 1
   hisstat[i] += 1
  else:
   DRUM[i] = 0
   if hisstat[i] > 2:
    if flg20ms == 0:
     print(hisstat)
     flg20ms = 20
     flg_doeven = 1

def modify_threshold(threshold):
 f = open('threshold.py', 'w')
 f.write('threshold = [')
 for i in threshold:
  f.write(str(i))
  f.write(', ')
 f.write(']')
 f.close()

def ma(op = 0):
 global AVG
 global ADV
 global DRUM
 MAXV = [0]*8
 while(1):
  AVG = [0]*8
  for j in range(20):
   for i in range(8):
    ADV[i] = AD[i].read()
    AVG[i] += ADV[i]
  for i in range(8):
   AVG[i] //= 20
   if AVG[i] > MAXV[i]:
    MAXV[i] = round(AVG[i])
  
  if op == 0:
   print('{:>4} {:>4} {:>4} {:>4} {:>4} {:>4} {:>4} {:>4}'.format(ADV[0], ADV[1], ADV[2], ADV[3], ADV[4], ADV[5], ADV[6], ADV[7]))
  elif op == 1:
   print('{:>4} {:>4} {:>4} {:>4} {:>4} {:>4} {:>4} {:>4}'.format(DRUM[0], DRUM[1], DRUM[2], DRUM[3], DRUM[4], DRUM[5], DRUM[6], DRUM[7]))
  else:
   for i in range(8):
    print('{:>5} '.format(round(AVG[i])), end='')
   print(':', end='')
   for i in range(8):
    print('{:>3} '.format(MAXV[i]), end='')
   print()

tim2 = pyb.Timer(2, freq=1000)
tim2.callback(Timer2callback)
#tim2 = pyb.Timer(2, freq=1000, callback=Timer2callback) # 1ms
flg_release = 0

while True:
 if flg_doeven == 1:
  PK=[]
  for i in range(8):
   if hisstat[i] > 3:
    PK.append(KBKEY[i]) 
  for i in range(8):
   hisstat[i] = 0
  press_key(PK)
  flg_doeven = 0
  flg_release = 1
 else:
  if flg_release == 1:
   flg_release = 0
   release_key() 
 pyb.delay(10)

threshold.py

threshold = [700, 1700, 1500, 1800, 650, 350, 400, 130]

參考:
[1] 极客DIY:利用Arduino制作电子鼓 https://www.freebuf.com/news/topnews/60389.html

[2] Arduino Leonardo/Micro As Game Controller/Joystick  https://www.instructables.com/id/Arduino-LeonardoMicro-as-Game-ControllerJoystick/

[3] [ Arduino ] Arduino電子鼓 – Arduino Drum using Hairless MIDI + pizeo disk https://yoyotechnology.wordpress.com/2015/07/15/arduino-arduino%E9%9B%BB%E5%AD%90%E9%BC%93-arduino-drum-using-hairless-midi-pizeo-disk/

[4] 什麼是 N-key 與按鍵衝突?原理說明、改善技術、選購注意完全解析https://www.techbang.com/posts/10235-keyboard-triggers-do-not-bomb-almighty-from-the-matrix-to-the-circuit-n-key-conflict-with-the-keys-in-the-end-is-what-computer-king-75-fully-understood?page=4

[5] 【micropython】用python來進行BadUSB的USB-HID測試(含無線控制) https://codertw.com/ios/58229/

[6] http://docs.micropython.org/en/latest/library/pyb.html?highlight=usb_mode#pyb.usb_mode

[7]  ADC performance of MicroPython boards https://forum.micropython.org/viewtopic.php?t=2682

注意:
1. timer 中斷裡不能使用浮點數除法,若要做移動平均,要用 // 做整數除法
2. 雖然有 14 個 timer (1~ 14),但是系統用的 (3, 5, 6)
    要避開 http://docs.micropython.org/en/v1.9.4/pyboard/pyboard/tutorial/timer.html?highlight=timer