3duilabのアイコン画像

ドットマトリクスLEDのSPI制御と非接触空間センサーへの応用例 [ラズパイピコ-Python]

3duilab 2021年10月24日に作成  (2022年01月18日に更新) 製作品 製作品

ドットマトリクスLEDのSPI制御と非接触空間センサーへの応用例 [ラズパイピコ-Python]

概要

非接触空間センサーのドットマトリクスLEDへの応用を考えました。
シリアル-パラレル変換はシフトレジスタ(74HC595)を使う簡単な方法です。SPIにGPIOを2ピン追加するだけ、非接触センサーのカラーLEDも同様にシフトレジスタを制御しています。
双方向ハンドセンサーリンク https://interactive-hand-sensor.com/root/

シフトレジスタとSPIを使ったシリアルーパラレル変換の長所

  • SPI以外の追加ピンが少ない(1〜3)
    • SS(IC選択)はRCKピンを使う
    • OE(出力ON/OFF)とCLR(リセット)は省略できる
  • プログラムが簡単
    • SPI出力の後にRCKをON/OFFするだけ

動画

ここに動画が表示されます

回路図

キャプションを入力できます

シフトレジスタ書き込み部分プログラム(Python3)

# *** 書き込み値 Rcの設定 ***
chg = lambda x: 1 << (x - 1) if x > 0 else 0
Rc = [-1, -1]
oe8(True)                                 # 出力Off
for rc in args: #r:3, c:1
    for i in range(len(rc)):
        k = chg(rc[i])
        if Rc[i] == -1:
            Rc[i] = k
        elif Rc[i] != k:
            Rc[i] |= k
Rc[0] = ~Rc[0]      # invert row
# *** 書き込み ***
spi.write( bytearray([Rc[1], Rc[0]]) )    # spi書き込み
fix8(True)                                # データ保存、IC選択
fix8(False)                               # データ保存、IC選択
oe8(False)                                # 出力On

プログラム

main.py

from machine import PWM from Sensor_CLED import * def makePwm(gpio, freq, duty16): pwm = PWM(gpio) pwm.freq(freq) pwm.duty_u16(duty16) makePwm(pwmCled_gpio, 50, 1<<11) # set pwm def makeIndi(): val = True def closure(timer): nonlocal val CLed.setIndi(val) val = not val return closure indi = makeIndi() tim = Timer() tim.init(freq=3, mode=Timer.PERIODIC, callback=indi) def makeCounter(): cnt = 0 def closure(num): nonlocal cnt cnt += 1 if cnt == num: cnt = 0 return True return False return closure counter = makeCounter() def direction(lst8): CRI = 600 ''' 1, 0, 7, 2, 6, 3, 4, 5, ''' def near(a, b): if ((a == 0) and (b == 7)) or ((a == 7) and (b == 0)): return True return abs(a - b) == 1 def ccw(a, b): if ((a == 0) and (b == 7)) or ((a == 7) and (b == 0)): return b < a return b > a top3 = sorted(lst8, reverse=True)[:3] if top3[0] < CRI: return -1 # less than CRITERIA if min(lst8) > (top3[0] >> 1): return 8 # no direction idxTop = [lst8.index(v) for v in top3] next0is1 = near(idxTop[0], idxTop[1]) next0is2 = near(idxTop[0], idxTop[2]) dif01near = (top3[0] - top3[1]) < (top3[1] >> 1) dif12near = (top3[1] - top3[2]) < (top3[2] >> 1) if (next0is1 and dif01near) and ((next0is2 and not dif12near) or (not next0is2)): rev = idxTop[0] + (0.5 if ccw(idxTop[0], idxTop[1]) else -0.5) if rev == -0.5: return 7.5 return rev return idxTop[0] # **************** led8x8 ******************* oe8 = getPinOut('OE_I8x8') fix8 = getPinOut('FIX_8x8') spi = getPinOut('SPI') # 6,7,8,9pin def writeSpi(args): # args:(row0, col0), (row1, col1) up to 2 pieces, same row or col chg = lambda x: 1 << (x - 1) if x > 0 else 0 Rc = [-1, -1] oe8(True) for rc in args: #r:3, c:1 for i in range(len(rc)): k = chg(rc[i]) if Rc[i] == -1: Rc[i] = k elif Rc[i] != k: Rc[i] |= k Rc[0] = ~Rc[0] # invert row spi.write( bytearray([Rc[1], Rc[0]]) ) #print(bin(Rc[0]), bin(Rc[1])) fix8(True) fix8(False) oe8(False) i8x8 = {-1:((0,0),),0:((4,1),(5,1)),0.5:((6,1),),1:((7,2),),1.5:((8,3),),2:((8,4),(8,5)),2.5:((8,6),),3:((7,7),),3.5:((6,8),),4:((4,8),(5,8)),4.5:((3,8),),5:((2,7),),5.5:((1,6),),6:((1,4),(1,5)),6.5:((1,3),),7:((2,2),),7.5:((3,1),),8:((4,4),(4,5),(5,4),(5,5))} # ***************** main ******************** if __name__ == '__main__': cled = CLed() Sensor.init() # *** initialize Sensor *** print('******************* START ********************') cled.turnOn(5) time.sleep(1) while True: Sensor.setAd() cled.turnOn() if counter(10): lst = Sensor.getAdLst() dir = direction(lst) writeSpi(i8x8[dir]) #print(lst) #print(dir)

Sensor_CLED.py

from impQ import * from machine import Timer import time COL = getPinOut('COL') # 3,4,5pin ROW = getPinOut('ROW') ssR = getPinOut('SS') # 6pin spi = getPinOut('SPI') # 6,7,8,9pin selSen = getPinOut('SEL_SEN') # 10pin oeIled = getPinOut('OE_ILED') # 11pin fix = getPinOut('FIX_CLED') # 12pin oeCled, oeCled_gpio = getPinOut('OE_CLED', True) # 13pin: closure, gpio pwmCled, pwmCled_gpio = getPinOut('PWM', True) # 14pin COL_LEN = 8 ROW_LEN = 1 # def make_irqHandler(): # val = True # def closure(timer): # nonlocal val # val = not val # def getVal(): # return val # return closure, getVal # irq_handler, irq_val = make_irqHandler() # tim = Timer() # tim.init(period=1000, mode=Timer.PERIODIC, callback=irq_handler) # ********************************* Sensor ************************************* class Sensor(): # cri = [90,135,195,275,390,535,720,990,1300,1750,2200,2800] cri = [90,275,535,990,1750,2800] VON_CORRECTION = 20 # for GND Level correction def __init__(self): self.__adVal = 0 self.__value = 0 @property def AD(self): return self.__adVal @property def Value6(self): # 1 ~ 6 return self.__value @Value6.setter def Value6(self, adVal): for i in range(len(self.cri)): if adVal < self.cri[i] + self.VON_CORRECTION: self.__value = i return self.__value = len(self.cri) @classmethod def init(cls): # *********** init ************* cls.adAryInit = [[0 for _ in range(COL_LEN)] for _ in range(ROW_LEN)] print('cri:', cls.cri) cls.sens = [[Sensor() for _ in range(COL_LEN)] for _ in range(ROW_LEN)] # *********** calibration *********** shift = 8 for i in range(1 << shift): # add 256 times cls.setAd(True) for r in range(ROW_LEN): # >> 8 for c in range(COL_LEN): cls.adAryInit[r][c] = cls.adAryInit[r][c] >> shift @classmethod def getAdLst(cls, shift=0): if ROW_LEN == 1: return [s.AD>>shift for s in cls.sens[0]] else: adLst = [] for r in range(ROW_LEN): adLst.extend([s.AD>>shift for s in cls.sens[r]]) return adLst @classmethod def setAd(cls, isInit=False): # ''' row -> col ''' for r in range(ROW_LEN): outNumBit(ROW, r) # set row for c in range(COL_LEN): outNumBit(COL, c) # set col cls.__spiAdc(r, c, isInit) @classmethod def __spiAdc(cls, r, c, isInit): ''' get AD-value and set it to sens[r][c] ''' ledSta = oeCled_gpio.value() # buffering oe cls.__adcOn(True, False) # oeIled:off Voff = cls.__getAdVal(c) cls.__adcOn(True) # oeIled:on Von = cls.__getAdVal(c) cls.__adcOn(False) oeCled(ledSta) # undo oe if isInit: cls.adAryInit[r][c] += noMinus(Von - Voff - (Voff>>2)) else: adVal = noMinus(Von - Voff - (Voff>>2) - cls.adAryInit[r][c]) cls.sens[r][c].__adVal = adVal cls.sens[r][c].Value6 = adVal @classmethod def __getAdVal(cls, c): wLst = bytearray([ 6 | (c >> 2), c << 6, 0]) rLst = bytearray([0,0,0]) ssR(False) # ss on spi.write_readinto(wLst,rLst) ssR(True) # ss off return ((rLst[1] & 0x0f) << 8) + rLst[2] @classmethod def __adcOn(cls, isSelSen, isIled = None): if isIled is None:isIled = isSelSen selSen(isSelSen) # selsen(10) True:sensor on oeIled(isIled) # ********************************** CLed ************************************** class CLed(): ''' Serial Parallel converter for color LED ''' indiColor = 6 __indiBlk = False # indicator blink allOff = True indiOff = False # *** indicator OFF *** def __init__(self): pass def turnOn(self, tpl2=-1):# val:int/tuple # ***** value preperation ***** ssR(False) # ss on oeCled(False) # oe off for r in range(ROW_LEN): outNumBit(ROW, r) # set row if type(tpl2) == int: if tpl2 == -1: # *** normal *** sLst = [s.Value6 for s in Sensor.sens[r]] else: # paint all surface sLst = [tpl2 for _ in range(COL_LEN)] else: # tpl2 is tuple ex:tpl = ((1,2,3,4,5,6,7,6),(1,2,3,4,5,6,7,6)) sLst = [tpl2[r][c] if tpl2[r][c] > 0 else 0 for c in range(COL_LEN)] self.allOff &= (sum(sLst) == 0) self.setSb(sLst, r) self.allOff = True ssR(True) # ss off oeCled(True) # oe on def setSb(self, lst, row): # set single board, cube-indicator, row:indicator row detection # colorTbl = (0,4,5,1,3,2,6) #IHS10 colorTbl = (0,1,3,2,6,4,5) #IHS11 val24 = 0 for c in range(COL_LEN): # col7+col6+col5+..col0 isIndi = ((lst[c] == 0) and self.allOff and self.__indiBlk) and (not self.indiOff) if (row == ROW_LEN - 1) and (c == COL_LEN - 1) and isIndi: cVal = self.indiColor else: cVal = lst[c] # sensor value val24 += colorTbl[cVal] << c * 3 wLst = bytearray([(val24 >> b * COL_LEN) & 0xff for b in range(2, -1, -1)]) ssR(False) # ss on spi.write(wLst) ssR(True) # ss off fix(True) # latch clock and output color LED fix(False) @classmethod def setIndi(cls, isOn): cls.__indiBlk = isOn @classmethod def setIndiColor(cls, color = 6): cls.indiColor = color if __name__ == '__main__': print('Sensor_CLED is running now!')

impQ.py

from machine import Pin, SPI def GPIOOUT(pin, ini = 0, isGPIO = False): gpio = Pin(pin, Pin.OUT) gpio.value(ini) def closure(val): gpio.value(val) if isGPIO: return closure, gpio else: return closure def GPIO_NUM_BIT(pin, bit): gpio = Pin(pin, Pin.OUT) gpio.value(0) sel = 1 << bit def closure(num): gpio.value(num & sel) return closure def outNumBit(nbLst, num): # nbLst:row or col for f in nbLst: f(num) def noMinus(n): if n < 0: return 0 return n pinDic = {'COL':((0,0),(1,1),(2,2)),'SS':(5,1),'SPI':0,'SEL_SEN':7,'OE_ILED':8,'OE_I8x8':12,'FIX_8x8':13, 'FIX_CLED':9,'OE_CLED':10,'PWM':11,'PWM2':18,'ROW':((19,0),(20,1),(21,2))} ''' ABC:345 SS:6 MOSI:7 MISO:8 SCK:9 SEL_SEN:10 OE_ILED:11 FIX_ILED:12 OE_CLED:13 PWM_CLED:14 ''' def getPinOut(pStr, isGPIO = False): # pin string if pStr not in pinDic:return None pn = pinDic[pStr] if pStr in ('COL','ROW'): return [GPIO_NUM_BIT(pin, bit) for (pin, bit) in pn] elif pStr == 'SPI': spi = SPI(pn, baudrate=2000000, sck=Pin(6), mosi=Pin(3), miso=Pin(4)) return spi elif type(pn) == tuple: return GPIOOUT(pn[0], pn[1]) # active-H elif type(pn) == int: return GPIOOUT(pn, 0, isGPIO) # closure, gpio else: return None if __name__ == '__main__': print('impQ is running now!')

双方向ハンドセンサーはサイエンスカフェえむしーじじょう様(https://sciencecafe-mc2.com/)で展示中、12月末まで

3duilabのアイコン画像
赤外線フォトリフレクタを利用した次世代の非接触空間センサー「双方向ハンドセンサー」を開発しています。電子回路と組込みソフトウェアのエンジニアです。事故で指先を失いました(冬山で凍傷になって)😁 動画まとめ https://imgur.com/user/3duilab/posts  website https://interactive-hand-sensor.com/root/
ログインしてコメントを投稿する