【備忘録】Raspberry Pi Pico で CircuitPython を使って DVI 出力する方法（その2）

# 0. はじめに

以前、↓こちらの記事を書きました。

https://qiita.com/nak435/items/d7173c800c511f91035c

今回、タクトスイッチ4個 と ブザー が搭載された、以前と **違うタイプ**の HDMI 出力ボードを入手したので、これを使う方法をまとめておきます。

[<img width="280" alt="PICO-HDMI-PLUS.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/1cb41af5-bff4-166f-0ca6-382963b59459.jpeg" />](https://www.microfan.jp/2024/07/pico-hdmi-plus/)

https://www.microfan.jp/2024/07/pico-hdmi-plus/



# 1. Pinout

DVI に関するピンは、以前の記事の[Adafruit DVI Breakout Board](https://qiita.com/nak435/items/d7173c800c511f91035c#2-picoとの接続)と同じです。

<table>
  <tbody>
  <tr><td colspan="3" style="text-align: center;">Pinout</td></tr>
    <tr>
      <td style="vertical-align:top;">

|	DVI	|	Pico	|
|	:-:	|	:-:	|
|	TXC-	|	GP15	|
|	TXC+	|	GP14	|
|	TX0-	|	GP13	|
|	TX0+	|	GP12	|
|	TX1-	|	GP19	|
|	TX1+	|	GP18	|
|	TX2-	|	GP17	|
|	TX2+	|	GP16	|

</td>
      <td style="vertical-align:top;">

|	Buzzer	|	Pico	|
|	:-:	|	:-:	|
|	Buzzer	|	GP8	|

<br><br><br><br><br><br><br><br>

</td>
      <td style="vertical-align:top;">

|	Switch	|	Pico	|
|	:-:	|	:-:	|
|	common	|	GP9	|
|	SW1	|	GP10	|
|	SW2	|	GP11	|
|	SW3	|	GP20	|
|	SW4	|	GP21	|

<br><br><br>

</td>
    </tr>
  </tfoot>
</table>

- 回路図

<img width="300" alt="PICO-HDMI-PLUS_circuit.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/789b5589-64ad-55d8-056a-1c2ab961bf48.jpeg" />


Switch（タクトスイッチ）回路は要注意です（後述）。



# 2. Buzzer 鳴動

次のコードで任意の周波数の音を出すことができます。

```python:CircuitPython（440Hzの音を0.5秒出力）
import time
import board
import pwmio

buzzer_OFF, buzzer_ON = 0, 0x8000

buzzer = pwmio.PWMOut(board.GP8, variable_frequency=True)

buzzer.frequency = 440  # 440Hz : A4

buzzer.duty_cycle = buzzer_ON

time.sleep(0.5) # 0.5秒

buzzer.duty_cycle = buzzer_OFF
```


# 3. Switch チェック

次のコードでボタン（タクトスイッチ）の押下状況をチェックできます。

```python:CircuitPython（SW1〜SW4をチェック）
import time
import board
import digitalio

GP9 = digitalio.DigitalInOut(board.GP9)
GP9.direction = digitalio.Direction.OUTPUT
GP9.value = True

buttonPins = [board.GP10, board.GP11, board.GP20, board.GP21]
btns = []
for pin in buttonPins:
    button = digitalio.DigitalInOut(pin) 
    #button.pull = digitalio.Pull.UP
    btns.append(button)

prev_pushed = 0
while True:
    time.sleep(0.05)
    pushed = 0
    for index, btn in enumerate(btns):
        if not btn.value: pushed |= (1 << index)
    if prev_pushed == pushed: continue
    prev_pushed = pushed
    for index in range(len(btns)):
        if pushed & (1 << index):
            print(f"sw{index + 1} ", end='')
        else:
            print(' '*4, end='')
    print()

```

:::note alert
GP9を HIGH にする必要があります。
これを忘れると、ボタンの3つ同時押しで誤動作（誤検知）します。
（GP9を HIGH にすれば外部プルアップは不要となる）
:::


# 4. SIMONゲーム

<iframe width="632" height="360" src="https://www.youtube.com/embed/R6JgujHOt3w?rel=0" title="Simon" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>


せっかくボタンとブザーが付いているので、元祖「<b><i>音</i></b> ゲー 」[サイモン](https://www.youtube.com/watch?v=4YhVyt4q5HI)を作ってみました。

ボタンと音の関係は次の通り。

| 黄色 | 青色 | 緑色 | 赤色 |
|:-:|:-:|:-:|:-:|
| SW1  | SW2  | SW3  | SW4 |
|209 Hz|252 Hz|310 Hz|415 Hz|


<!-- 





HDMIモニターに接続しない場合は、次の各ピンにLED（アノード側）と1KΩ程度の抵抗を接続してください。カソード側はGNDへ。

| GP5  | GP4  | GP3  | GP2 |
|:-:|:-:|:-:|:-:|
| 黄色LED | 青色LED | 緑色LED | 赤色LED |



HDMIモニター、LED共に接続しない場合は、<b><i>音</i></b> だけのゲームになります。

```python:simon.py
import time
import random

import board
import pwmio
import picodvi
import digitalio
import displayio
import terminalio
import framebufferio

from adafruit_display_shapes.arc import Arc
from adafruit_display_shapes.rect import Rect
from adafruit_display_text.label import Label

displayio.release_displays()

fb = picodvi.Framebuffer(
    width=320, height=240, color_depth=8,
    clk_dp=board.GP14, clk_dn=board.GP15,
    red_dp=board.GP12, red_dn=board.GP13,
    green_dp=board.GP18, green_dn=board.GP19,
    blue_dp=board.GP16, blue_dn=board.GP17)

display = framebufferio.FramebufferDisplay(fb)

group = displayio.Group()
display.root_group = group

buzzer_OFF, buzzer_ON = 0, 0x8000
buzzer = pwmio.PWMOut(board.GP8, variable_frequency=True)
buzzer.duty_cycle = buzzer_OFF

# led pin for sw1, sc_x, sw3, sw4
ledPins = [board.GP5, board.GP4, board.GP3, board.GP2]

# pin for sw1, sc_x, sw3, sw4
buttonPins = [board.GP10, board.GP11, board.GP20, board.GP21] 

#gameTones = [note.GS3, note.B3, note.DS4, note.GS4]
gameTones = [209, 252, 310, 415] # after adjustment
gameSequence = [0] * 100
gameIndex = 0
    
GP9 = digitalio.DigitalInOut(board.GP9)
GP9.direction = digitalio.Direction.OUTPUT
GP9.value = True

#random.seed(0)

class SimonPie:
    def __init__(self, group):
        
        c_x = display.width // 2
        c_y = display.height // 2
        w4, h4 = c_x // 2, c_y // 2
        r, ra = h4, 20
        w = 6; wh = w // 2

        BLACK = 0
        WHITE = 0xFFFFFF
        RED = 0xFF0000
        GREEN = 0x00FF00
        BLUE = 0x0000FF
        YELLOW = 0xFFFF00
        RED_A = 0xFFBEB4
        GREEN_A = 0xBFFEBD
        BLUE_A = 0xB9C2FF
        YELLOW_A = 0xFFFEBE

        self.pies = []
        pie_info = [(YELLOW_A, 90, 225), (BLUE_A, -90, -45), (GREEN_A, 90, 135), (RED_A, 90, 45)]
        for color, angle, direction in pie_info:
            arc = Arc(x=c_x, y=c_y, radius=r, angle=angle, direction=direction, segments=8, arc_width=r - ra, outline=color, fill=color)
            self.pies.append(arc)
        arc_info = [(YELLOW, 90, 225), (BLUE, -90, -45), (GREEN, 90, 135), (RED, 90, 45)]
        for color, angle, direction in arc_info:
            arc = Arc(x=c_x, y=c_y, radius=r, angle=angle, direction=direction, segments=10, arc_width=r - ra, outline=color, fill=color)
            group.append(arc)
            
        group.append(Rect(c_x - wh, 0, w, display.height, fill=BLACK))
        group.append(Rect(0, c_y - wh, display.width, w, fill=BLACK))

        self.center_label = Label(terminalio.FONT, text="", color=WHITE, background_color=BLACK, anchor_point=(0.5, 0.5), anchored_position=(c_x, c_y))
        group.append(self.center_label)


simon = SimonPie(group)

def delay(milliSec):
    time.sleep(milliSec / 1000)
    
btns = []
for pin in buttonPins:
    button = digitalio.DigitalInOut(pin) 
    #button.pull = digitalio.Pull.UP
    btns.append(button)

leds = []
for pin in ledPins:
    led = digitalio.DigitalInOut(pin)
    led.direction = digitalio.Direction.OUTPUT
    leds.append(led)

def tone(freq, duration=-1, wait=-1):
    buzzer.frequency = freq
    buzzer.duty_cycle = buzzer_ON
    if duration < 0: return
    delay(duration)
    buzzer.duty_cycle = buzzer_OFF
    delay(wait)
    if wait < 0: return

def displayScore():
    print(gameIndex + 1)
    simon.center_label.text = str(gameIndex + 1)

def lightLedAndPlayTone(index):
    group.append(simon.pies[index])
    leds[index].value = True
    duration = 420 if gameIndex < 5 else 320 if gameIndex < 13 else 220
    tone(gameTones[index], duration, 50)
    leds[index].value = False
    group.pop()
    
def playSequence():
    for n in range(gameIndex):
        index = gameSequence[n]
        lightLedAndPlayTone(index)
    
def readButtons():
    while True:
        for index, btn in enumerate(btns):
            if not btn.value: return index
        delay(5)

def gameOver():
    global gameIndex
    print(f"Game over! your score: {gameIndex - 1}")
    gameIndex = 0
    delay(200)    
    tone(42, 1500) #4.2Hz for original
    print("---")
    delay(500)


def checkUserSequence():
    for n in range(gameIndex):
        expectedButton = gameSequence[n]
        actualButton = readButtons()
        lightLedAndPlayTone(actualButton)
        if expectedButton != actualButton:
          return False
    return True

def startupSound():
    global gameIndex
    gameIndex = len(gameTones)
    for index in range(len(gameTones)):
        gameSequence[index] = index
    playSequence()
    gameIndex = 0  

display.refresh()
delay(600);delay(600);delay(500) #wait for refresh
startupSound()
simon.center_label.text = "Push any switch"
display.refresh()
print("Push any Switch for start games.")
readButtons()
delay(600)

while True:
    displayScore()

    gameSequence[gameIndex] = int(random.random() * 4)
    gameIndex += 1
 
    playSequence()
    if not checkUserSequence():
        gameOver()

    delay(300)

    if gameIndex > 0:
        delay(300)
```

 -->

[以前の記事と同じ環境](https://qiita.com/nak435/items/d7173c800c511f91035c#4-環境)



# 参考

### - Pico DVI

https://www.microfan.jp/2024/07/picodvi/


### - Simon

https://www.waitingforfriday.com/?p=586


### - Wokmi (Arduino)

（コードの一部を参考にさせてもらいました）

https://wokwi.com/projects/328451800839488084



<br><br><br><br>**以上**