tm1637.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import rp2
  2. from machine import Pin
  3. import time
  4. class TM1637:
  5. # 割り当て可能なステートマシンID管理
  6. _available_ids = list(range(8))
  7. # セグメントデータ定義 (Cコードの digit_seg / letter_seg に対応)
  8. _DIGIT_SEG = [0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f]
  9. _LETTER_SEG = {
  10. 'A': 0x77, 'b': 0x7c, 'C': 0x39, 'd': 0x5e, 'E': 0x79, 'F': 0x71,
  11. 'G': 0x3d, 'H': 0x76, 'i': 0x04, 'J': 0x0e, 'L': 0x38, 'n': 0x54,
  12. 'o': 0x5c, 'P': 0x73, 'q': 0x67, 'r': 0x50, 'S': 0x6d, 't': 0x78,
  13. 'u': 0x1c, 'y': 0x6e, '-': 0x40, ' ': 0x00
  14. }
  15. # PIOプログラム定義
  16. @rp2.asm_pio(
  17. out_init=(rp2.PIO.IN_HIGH),
  18. set_init=(rp2.PIO.IN_HIGH),
  19. sideset_init=(rp2.PIO.OUT_HIGH),
  20. out_shiftdir=rp2.PIO.SHIFT_RIGHT,
  21. autopull=False,
  22. fifo_join=rp2.PIO.JOIN_TX
  23. )
  24. def _tm1637_pio():
  25. wrap_target()
  26. # --- Start Condition ---
  27. pull(block)
  28. set(pindirs, 0b00) # SDA/SCL input
  29. nop() [7]
  30. set(pindirs, 0b01) # SDA output/SCL input
  31. set(pins, 0) # SDA is Low
  32. nop() [7]
  33. set(pindirs, 0b11) # SDA/SCL output
  34. set(x, 7) .side(0) # CLK is Low
  35. jmp("output")
  36. label("byte_loop")
  37. set(x, 7) .side(0) # CLK is Low
  38. pull(block)
  39. label("output")
  40. nop() [3]
  41. out(pins, 1) [4] # LSB output
  42. nop() .side(1) # CLK is High
  43. nop() [7]
  44. nop() .side(0) # CLK is Low
  45. jmp(x_dec, "output")
  46. # --- ACK Check ---
  47. set(pindirs, 0b10) [7] # SDA input/SCL output
  48. nop() .side(1) # SCL = High
  49. jmp(pin, "nack") # SDA High (NACK) なら分岐
  50. jmp("ack")
  51. label("nack")
  52. irq(rel(0)) # NACK通知
  53. label("ack")
  54. nop() [6]
  55. nop() .side(0) # CLK is Low (ACK cycle end)
  56. set(pindirs, 0b11) [7] # SDA/SCL Output
  57. out(x, 1) # 9bit目を読み取って判定
  58. jmp(not_x, "byte_loop") # 0なら次のバイトへ
  59. # --- Stop Condition ---
  60. set(pins, 0) [7] # SDA is Low
  61. nop() .side(1) # SCL is High
  62. nop() [7]
  63. set(pindirs, 0b00) [7] # SDA/SCL Input (STOP)
  64. wrap()
  65. def __new__(cls, *args, **kwargs):
  66. if not cls._available_ids:
  67. raise RuntimeError("Maximum TM1637 instances (8) reached")
  68. instance = super().__new__(cls)
  69. instance.sm_id = cls._available_ids.pop(0)
  70. return instance
  71. def __init__(self, sda_pin, columns=6, contrast=3):
  72. # SDAの次のピンをSCLとする仕様を再現 (sda_base_pin + 1)
  73. self.sda_pin = Pin(sda_pin, Pin.IN, pull=Pin.PULL_UP)
  74. self.scl_pin = Pin(sda_pin + 1, Pin.IN, pull=Pin.PULL_UP)
  75. self.columns = columns
  76. # PIO初期化 (2MHz)
  77. self.sm = rp2.StateMachine(
  78. self.sm_id,
  79. self._tm1637_pio,
  80. freq=2000000,
  81. sideset_base=self.scl_pin,
  82. set_base=self.sda_pin, # SDA(bit0) & SCL(bit1) をまとめて制御
  83. out_base=self.sda_pin,
  84. jmp_pin=self.sda_pin
  85. )
  86. self.sm.active(1)
  87. # 初期表示設定
  88. self.clear()
  89. self.set_contrast(contrast)
  90. def _send_cmd(self, data, keep_going=False):
  91. # 9ビット目に「終了(1)」か「継続(0)」のフラグを立てる
  92. val = (data & 0xFF) | (0 if keep_going else 0x100)
  93. self.sm.put(val)
  94. def set_contrast(self, level):
  95. # 0x88 (Display ON) | 0~7
  96. cmd = 0x88 | (level & 0x07)
  97. self._send_cmd(cmd)
  98. def _chr_to_seg(self, char):
  99. if '0' <= char <= '9':
  100. return self._DIGIT_SEG[int(char)]
  101. return self._LETTER_SEG.get(char, 0x00)
  102. def show_str(self, s):
  103. # 文字列を解析してセグメントデータのリストを作成
  104. segments = []
  105. i = 0
  106. s_len = len(s)
  107. # カラム数または文字列が終わるまでループ
  108. while i < s_len and len(segments) < self.columns:
  109. char = s[i]
  110. base_seg = self._chr_to_seg(char)
  111. # 次の文字が存在し、かつドットかコロンの場合
  112. if (i + 1) < s_len and s[i + 1] in ('.', ':'):
  113. base_seg |= 0x80 # 8ビット目(ドット)を立てる
  114. i += 1 # ドット文字分インデックスを進める(スキップ)
  115. segments.append(base_seg)
  116. i += 1
  117. # 2. 生成したデータを出力
  118. # データセット(自動アドレスインクリメント)
  119. self._send_cmd(0x40) # TM1637_AUTOINC
  120. # アドレス設定 (0)
  121. self._send_cmd(0xC0, keep_going=True) # TM1637_ADDRSET
  122. # リストの内容を送信
  123. for j, seg in enumerate(segments):
  124. # 最後かどうかを判定 (リストの最後 かつ カラム最大)
  125. is_last = (j == len(segments) - 1)
  126. self._send_cmd(seg, keep_going=not is_last)
  127. def clear(self):
  128. self.show_str(' ' * self.columns)
  129. def release(self):
  130. self.sm.active(0)
  131. if self.sm_id not in self.__class__._available_ids:
  132. self.__class__._available_ids.append(self.sm_id)
  133. self.__class__._available_ids.sort()