USBからデータを受け取る

## Raspberrypi pico でデータを受信し、LCDディスプレイに表示
### 開発環境 
- Raspberrypi_pico: thonny
- PCからの送信: メモ帳

## Pin配置
![ラズベリーパイピコ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957418/76fd868f-c157-4273-a9fc-94eb57ecb0d1.jpeg)

## ✅ GPIOと物理ピン対応表
Raspberry Pi Picoでは、**GPIO番号**と**物理ピン番号**が異なるため注意が必要です。

| 物理ピン | GPIO番号 | 用途例         |
|----------|-----------|----------------|

| 40       | GPIO21    | 未使用またはI2C切替用 |
| 38       | GPIO20    | 未使用またはI2C切替用 |
| 22       | GPIO17    | SCL（I2Cクロック）   |
| 21       | GPIO16    | SDA（I2Cデータ）     |

---

## ✅ I2C 初期化コード（GPIO16 / GPIO17 使用）

```python
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000)  # GPIO17=SCL, GPIO16=SDA
print(i2c.scan())  # I2Cデバイスが見つかればアドレスが表示される


```main.py
from machine import Pin, I2C
from pico_i2c_lcd import I2cLcd
import sys

# LCDの初期化
i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000)
lcd = I2cLcd(i2c, 0x3F, 2, 16)
lcd.clear()
lcd.putstr("USB Listening...")

# ASCIIフィルター関数（英数字＋記号のみ許可）
def ascii_only(text):
    return ''.join(c for c in text if 32 <= ord(c) <= 126)

# 入力バッファ
buffer = ""

# メインループ
while True:
    try:
        char = sys.stdin.read(1)
        if char == '\n':
            clean_text = ascii_only(buffer)
            lcd.clear()
            lcd.putstr(clean_text[:16])  # 16文字以内にカット
            print("Received:", clean_text)
            buffer = ""
        else:
            buffer += char
    except Exception as e:
        lcd.clear()
        lcd.putstr("Error occurred")
        print("Error:", e)
        break
```

# lib/pico_ic2_lcd.py
Raspberrypi pico 内に `lib` フォルダを作成し、`pico_ic2_lcd.py` を作成し以下のコードを書く
```pico_ic2_lcd.py
# forked from https://github.com/T-622/RPI-PICO-I2C-LCD/
import utime
import gc

from lcd_api import LcdApi
from machine import I2C

# PCF8574 pin definitions
MASK_RS = 0x01       # P0
MASK_RW = 0x02       # P1
MASK_E  = 0x04       # P2

SHIFT_BACKLIGHT = 3  # P3
SHIFT_DATA      = 4  # P4-P7

class I2cLcd(LcdApi):
    
    #Implements a HD44780 character LCD connected via PCF8574 on I2C

    def __init__(self, i2c, i2c_addr, num_lines, num_columns):
        self.i2c = i2c
        self.i2c_addr = i2c_addr
        self.i2c.writeto(self.i2c_addr, bytes([0]))
        utime.sleep_ms(20)   # Allow LCD time to powerup
        # Send reset 3 times
        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
        utime.sleep_ms(5)    # Need to delay at least 4.1 msec
        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
        utime.sleep_ms(1)
        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
        utime.sleep_ms(1)
        # Put LCD into 4-bit mode
        self.hal_write_init_nibble(self.LCD_FUNCTION)
        utime.sleep_ms(1)
        LcdApi.__init__(self, num_lines, num_columns)
        cmd = self.LCD_FUNCTION
        if num_lines > 1:
            cmd |= self.LCD_FUNCTION_2LINES
        self.hal_write_command(cmd)
        gc.collect()

    def hal_write_init_nibble(self, nibble):
        # Writes an initialization nibble to the LCD.
        # This particular function is only used during initialization.
        byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        gc.collect()
        
    def hal_backlight_on(self):
        # Allows the hal layer to turn the backlight on
        self.i2c.writeto(self.i2c_addr, bytes([1 << SHIFT_BACKLIGHT]))
        gc.collect()
        
    def hal_backlight_off(self):
        #Allows the hal layer to turn the backlight off
        self.i2c.writeto(self.i2c_addr, bytes([0]))
        gc.collect()
        
    def hal_write_command(self, cmd):
        # Write a command to the LCD. Data is latched on the falling edge of E.
        byte = ((self.backlight << SHIFT_BACKLIGHT) |
                (((cmd >> 4) & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        byte = ((self.backlight << SHIFT_BACKLIGHT) |
                ((cmd & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        if cmd <= 3:
            # The home and clear commands require a worst case delay of 4.1 msec
            utime.sleep_ms(5)
        gc.collect()

    def hal_write_data(self, data):
        # Write data to the LCD. Data is latched on the falling edge of E.
        byte = (MASK_RS |
                (self.backlight << SHIFT_BACKLIGHT) |
                (((data >> 4) & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        byte = (MASK_RS |
                (self.backlight << SHIFT_BACKLIGHT) |
                ((data & 0x0f) << SHIFT_DATA))      
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        gc.collect()
```
### コードを書き終えたら実行して `thonny` を閉じます。
Thonnyのシェル（REPL）は、USBシリアル（仮想COMポート）を常時占有しています
つまり、他のプログラム（pyserialなど）からはCOMポートを開けなくなるのです
これは Windows のCOMポート仕様による制限で、同時に1つのアプリしかCOMポートを開けません


## PCからデータ送信するコード
PCから実行するPythonはコマンドプロンプトから実行しました
```
python usb_post_form.py
```

```usb_post_form.py
import tkinter as tk
import serial
import time

# COMポート設定（環境に合わせて変更）
PORT = 'COM6'
BAUD = 9600

def send_message():
    message = text_entry.get()
    if not message:
        status_label.config(text="メッセージが空です")
        return
    try:
        ser = serial.Serial(PORT, BAUD)
        time.sleep(2)  # 接続安定待ち
        ser.write((message + '\n').encode('utf-8'))  # 改行を忘れずに
        ser.close()
        status_label.config(text="送信成功")
    except Exception as e:
        status_label.config(text=f"送信失敗: {e}")

# ウィンドウの作成
root = tk.Tk()
root.title("LCD送信フォーム")

# メッセージ入力欄
text_entry = tk.Entry(root, width=30, font=('Arial', 14))
text_entry.pack(pady=10)

# 送信ボタン
send_button = tk.Button(root, text="送信", command=send_message, width=20, height=2)
send_button.pack(pady=10)

# ステータス表示
status_label = tk.Label(root, text="", fg="blue")
status_label.pack(pady=10)

# 実行
root.mainloop()
```
![IMG_6003.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957418/62c98d53-bacd-4616-824d-fb1ed40b01bb.jpeg)


![cmd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957418/fbe02561-d9b5-446e-a387-a0615800d6c4.png)

![IMG_6006.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957418/a3f7dd42-797b-444e-ae34-a89e01c3a23c.jpeg)