M5Stack Cardputer で RFID2ユニットを使ってみた

# この記事の目的
M5Stack Cardputer（ただし、Cardputer使用に拘らない）でRFID2を使える様にするための勘所を把握する。
基本的に自分のための備忘録です。😁

# この記事に関する注意
ここに掲載するプログラムはあくまで動作確認、動作を理解するためのものでありそのままの状態でなにかに役立てるアプリケーションではありません。
RFID2がうまく使えないという投稿が散見されていたので載せてます。
参考までに、ということでご理解ください。

# システム概要
　今回のM5Stack Cardputerに接続しているシステムは次の通りです。![CM241130-104059005.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/b8a3c559-425a-46f5-57fe-72979111f00a.jpeg)
左上から
Grove I2Cハブ（６ポート）

https://www.switch-science.com/products/6170?pr_prod_strat=e5_desc&pr_rec_id=7627e84af&pr_rec_pid=7675793440966&pr_ref_pid=7675351957702&pr_seq=uniform

M5Stack用WS1850S搭載 RFID 2ユニット

https://www.switch-science.com/products/8301?_pos=1&_sid=7e0cf4536&_ss=r

M5Stack用 I2Cジョイスティックユニット RGB LED付き (STM32G030)

https://www.switch-science.com/products/9922?_pos=17&_sid=47c5725d0&_ss=r

M5Stack用計量ユニット 5kgレンジ（HX711）

https://www.switch-science.com/products/9509?variant=43144263827654

※今回、計量ユニットは使っていません。

そして、M5Stack Cardputerキット（M5StampS3付属）

https://www.switch-science.com/products/9277?variant=42805209366726

いずれもスイッチサイエンスさんで購入しました、お世話になってます🙇‍♂️
今回はRFID2とジョイスティックユニットを使用しています。
ジョイスティックユニットはRFIDタグのデータを消去する時に使ってます。
キーボードを使えばそれでも良いのだけど今回は使いませんでした。

# 開発環境
開発は UiFlow2を使いました。
長年C言語、アセンブラなどテキストでプログラムを書いていましたが、いまはシンボルを組み立ててプログラムができてしまう、凄い世の中になったものです。
最終的には pythonで実行される様ですが、コマンドを覚える必要がないので便利です。
プログラムの流れを作るのはC言語もアセンブラも同じですからね😁
UiFlow2の紹介や使い方はここでは扱いません。

# 使用するRFIDタグ
今回使用するタグは以下のものです。
![CM241130-104312023-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/221cd0bd-56ea-3b5d-0eb4-6944cd2262f5.jpeg)
タグはアマゾンで買いました。

https://www.amazon.co.jp/dp/B07CRZD2VG?ref=ppx_yo2ov_dt_b_fed_asin_title

タグでもカードでもよいのですがポイントは
ISO14443Aです！
FeliCaとかでは読めません。タグ、カードを購入する際は要注意です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/51cfd6ee-ac4c-dd37-9568-d1cb01638282.png)
このスクショはAndroidスマホアプリ(RFIDカードリーダー)で読み取ったものです。
このタグのメモリは16文字×64ブロックで1024バイトあります。
実は20年ほど前、Mifareカードで設定情報を保存する計測器を開発したことがあります。
当時はカードのデータシート、リーダライターのデータシート首っ引きで何日もかけて理解し、プログラムを開発した記憶があるのですが、今は簡単になりました😁
とりあえず、覚えておくことは「最初のブロックは読み出しのみでUIDが保存されている」
ユーザーが使えるのは次のブロック以降、ということです。
知識として・・
RFIDタグやカードは「単なるメモリ」ではありません。
電波を使って通信し、コマンドを理解してメモリ操作するマイコンが入っています。
つまりカード内のメモリと通信するのではなく、それを管理するタグやカード内のマイコンと通信していると思ってください。

# 画面構成
画面構成は次の通り
TAG UID(lbl_ID) : タグのUIDを表示
label1(lbl_Data) : 読み取ったデータ(bytearray)を表示
Status 0(lbl_St_Cide) : 書き込み命令時の返り値を表示
Massage or Data(lbl_message) : 読み取ったデータを表示（余分な文字を省く）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/850d7147-053e-c262-4cb0-101ff40a9102.png)
となっています。
ま、要は読み取ったUIDやデータが表示され、書き込んだデータを確認できれば良いので適当です😁

# 初期設定
初期設定のプログラムです。プログラムと呼ぶべきか、コードと呼ぶべきか😁
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/2c5a1d77-eb43-ba6c-c00c-17339e726d11.png)

UiFlow2上で「ユニット」を追加すると自動でそのユニットの初期化コマンドが挿入されているので、あとは変数をここで初期化しておきます。(ユニットとは、RFID2やjoistick2等のこと)
まず、作成した変数は
・ID_bArray : UIDを格納するBytearrayです。RFID2への読み書きはBytearray単位で行います。
・DATA_bArray : 読み書きするデータを格納するBytearrayです。
・test_str : 書き込みするデータを格納するBytearrayです。
・in_name_str : キーボードからの入力を格納するつもりで作成しましたが今回未使用。
Pythonコードにすると↓こんな感じ
```python:setup
def setup():
  global lbl_message, lbl_ID, lbl_Status, lbl_Data, lbl_st_Code, kb, i2c0, rfid_0, miniscales_0, joystick2_0, ID_bArray, DATA_bArray, reader_status, in_name_str, txt_message, test_str, id_str

  M5.begin()
  lbl_message = Widgets.Label("Message or Data", 3, 102, 1.0, 0xffffff, 0x222222, Widgets.FONTS.EFontJA24)
  lbl_ID = Widgets.Label("TAG UID", 2, 25, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18)
  lbl_Status = Widgets.Label("Status", 2, 76, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18)
  lbl_Data = Widgets.Label("label1", 2, 55, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu12)
  lbl_st_Code = Widgets.Label("0", 68, 77, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18)

  kb = MatrixKeyboard()
  i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
  joystick2_0 = Joystick2Unit(i2c0, 0x63)
  rfid_0 = RFIDUnit(i2c0)
  in_name_str = ''
  miniscales_0 = MiniScaleUnit(i2c0)
  joystick2_0.fill_color(0x33cc00)
  ID_bArray = bytearray(16)
  DATA_bArray = bytearray(16)
  DATA_bArray = '                '.encode()
  test_str = 'Hello RFID!'
```
初期化後は、カードを検出したらそのUIDを読み取って表示、そしてデータを書き込む（上書き）です。
この流れをLoopに作成します。

# Loop部
先頭でカードがあるか無いかを判定し、カードがあれば中を実行します。
その時ジョイスティックが押されていればカードをクリアする関数を呼び出します。（後述）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/71c39d64-326e-a091-f5b3-e4efa5a9ffb0.png)
流れとしては UIDとデータを読み取りそれぞれ ID_bArrayとDATA_bArrayに格納します。
その後、 「bytearray('なんちゃら～」を省いたデータを作って表示させるという感じです。
UIDは全部16進数で表示したかったのですが、文字コードが存在しているところでは文字になってしまいます。
ここはなんとかしたいとも思ったのですが、さほど重要ではないので追求しませんでした😁
実際にアプリケーションで運用する時、UIDを表示する意味はあまり無いし、検証等で使えれば「よし」なので。
↓返り値のある「write buffer」コマンドで返ってくるのは「書き込んだ文字数」でした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/d49fb4f9-10ef-8b36-1df9-846e6a9730f6.png)

（独り言）そういえば20年ほど前にMifareカードの読み書きにもこんなのあったなぁ
↓この部分で読み取ったデータを変数に格納した後、test_strをDATA_bArrayに書き込んでいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/373574b3-804b-7f6f-8023-31124dc4b076.png)
次にタグのデータをクリアします。
今回、クリアと言っても「空白」を16文字分埋めるということにしました。
# Card_Clar部
↓がクリア部です。空白で隠しているところは後で・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/7bf65f36-1260-8205-92b9-a6d918e108c5.png)
流れは、カードがあれば空白16も自分を書き込むというものです。
で、なぜか消去できていない、消去した後で再読み込みすると消去する前と変わらない状態でした。
ここはちょっとハマったのですが、20年ほど前のMifareカード読み書きのプログラムを作った時
「消去や書き込みする場合はその前にそのブロックを読み取る」というルールがあった様な～なんて思い出しました。
で↓の様にしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/2c93b6d2-fa07-90fc-2fe6-ee1364babce9.png)
この2行を足したところ、正常に消去（空白16文字で書き込んでいる）できました。
なぜ「空白16文字」としたか、というと
書き込むデータが16バイトに満たない場合、余った範囲は上書きされない。というのが自分的に好きじゃなかったので、こういう形をとりました😁
ところで↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/00fde6e1-1a9e-dcd1-1435-a53e2fb8f2bf.png)
この赤丸で囲った部分がブロックの指定の数値になります。
「0」はUIDが入っている領域でユーザー書き換えができないので「１」以降となります。
他にもユーザーが使えない領域、があった様な気がするのですがここでは触れません(^_^;)
最後にプログラム全ブロックとpythonコードを載せておきます。
全ブロックのイメージは UiFlow2の画面上の何もないところで右クリックすると以下のポップアップメニューが表示されるので、下から２番目の、「Export all block to image」を左クリックするとダウンロードフォルダにpngイメージが保存されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/13bcdff1-d93f-10e1-0a65-b76a6b620c91.png)
# 動作確認画面
↓実行直後の画面
![CM241201-222836001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/e0133e67-c482-e91a-d51d-812710cf4172.jpeg)
↓データが書き込まれた後のタグを読み取った時の画面
![CM241201-223217004.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/8eedece3-3bad-0efb-3af7-f3d7244d31e4.jpeg)
データ消去時の画面は省略
↓実行中のWebTerminal画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/2b9df6e3-bdc0-31d5-2d45-dc539a46b04c.png)
WebTerminalには「シリアル出力する」としたところが表示されます。
# 全ブロック
![uiflow2_block_1733103579137.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118608/9c14349c-732b-321f-a603-50ff4eabd06e.png)
pythonコード
```python:RFID_test
import os, sys, io
import M5
from M5 import *
from unit import RFIDUnit
from hardware import *
from unit import Joystick2Unit
from unit import MiniScaleUnit
import time



lbl_message = None
lbl_ID = None
lbl_Status = None
lbl_Data = None
lbl_st_Code = None
kb = None
i2c0 = None
rfid_0 = None
miniscales_0 = None
joystick2_0 = None


ID_bArray = None
DATA_bArray = None
reader_status = None
in_name_str = None
txt_message = None
test_str = None
id_str = None

# Describe this function...
def Card_Clear():
  global ID_bArray, DATA_bArray, reader_status, in_name_str, txt_message, test_str, id_str, lbl_message, lbl_ID, lbl_Status, lbl_Data, lbl_st_Code, kb, i2c0, rfid_0, miniscales_0, joystick2_0
  # RFIDデータの書き換えは、一旦書き換えるアドレスを読み取り、その後書き込みをしないと更新されない。
  if rfid_0.is_new_card_present():
    ID_bArray = rfid_0.read_card_uid()
    DATA_bArray = rfid_0.read(1)
    DATA_bArray = '                '.encode()
    reader_status = rfid_0.write(1, DATA_bArray)
    lbl_st_Code.setText(str(reader_status))
    print('カードをクリアしました！')
    lbl_message.setText(str('データ消去完了'))
    print(DATA_bArray)


def setup():
  global lbl_message, lbl_ID, lbl_Status, lbl_Data, lbl_st_Code, kb, i2c0, rfid_0, miniscales_0, joystick2_0, ID_bArray, DATA_bArray, reader_status, in_name_str, txt_message, test_str, id_str

  M5.begin()
  lbl_message = Widgets.Label("Message or Data", 3, 102, 1.0, 0xffffff, 0x222222, Widgets.FONTS.EFontJA24)
  lbl_ID = Widgets.Label("TAG UID", 2, 25, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18)
  lbl_Status = Widgets.Label("Status", 2, 76, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18)
  lbl_Data = Widgets.Label("label1", 2, 55, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu12)
  lbl_st_Code = Widgets.Label("0", 68, 77, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18)

  kb = MatrixKeyboard()
  i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
  joystick2_0 = Joystick2Unit(i2c0, 0x63)
  rfid_0 = RFIDUnit(i2c0)
  in_name_str = ''
  miniscales_0 = MiniScaleUnit(i2c0)
  joystick2_0.fill_color(0x33cc00)
  ID_bArray = bytearray(16)
  DATA_bArray = bytearray(16)
  DATA_bArray = '                '.encode()
  test_str = 'Hello RFID!'


def loop():
  global lbl_message, lbl_ID, lbl_Status, lbl_Data, lbl_st_Code, kb, i2c0, rfid_0, miniscales_0, joystick2_0, ID_bArray, DATA_bArray, reader_status, in_name_str, txt_message, test_str, id_str
  M5.update()
  if rfid_0.is_new_card_present():
    lbl_message.setText(str(' '))
    if joystick2_0.get_button_status():
      Card_Clear()
      rfid_0.close()
      time.sleep_ms(500)
    else:
      ID_bArray = rfid_0.read_card_uid()
      DATA_bArray = rfid_0.read(1)
      print(ID_bArray)
      print(DATA_bArray)
      lbl_Data.setText(str(DATA_bArray))
      txt_message = str(DATA_bArray)
      DATA_bArray = test_str.encode()
      id_str = str(ID_bArray)
      id_str = id_str.replace('\\x', '')
      id_str = id_str.replace("bytearray(b'", 'UID:0x')
      id_str = id_str.replace("')", '')
      id_str = id_str.replace('000000000000', '')
      lbl_ID.setText(str(id_str))
      print(id_str)
      txt_message = txt_message.replace("bytearray(b'", '')
      txt_message = txt_message.replace("')", '')
      txt_message = txt_message.replace("'", '')
      txt_message = txt_message.replace('b', '')
      lbl_message.setText(str(txt_message))
      reader_status = rfid_0.write(1, DATA_bArray)
      lbl_st_Code.setText(str(reader_status))
      joystick2_0.fill_color(0xffffff)
      rfid_0.close()
      time.sleep_ms(500)
  joystick2_0.fill_color(0x6600cc)


if __name__ == '__main__':
  try:
    setup()
    while True:
      loop()
  except (Exception, KeyboardInterrupt) as e:
    try:
      from utility import print_error_msg
      print_error_msg(e)
    except ImportError:
      print("please update to latest firmware")
```
今回はCardputerのキーボードは使っていないので、他のM5Stackシリーズでも使えると思います。
今回の投稿は以上です！では(^.^)/~~~