JJYDecoder.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. from machine import Pin
  2. import rp2
  3. from micropython import schedule
  4. import utime as time
  5. from Debug import Debug
  6. from TimeSource import TimeSource
  7. # インジケーターLEDのGPIO
  8. INDICATOR_PIN = 25
  9. @rp2.asm_pio(
  10. sideset_init=(rp2.PIO.OUT_LOW),
  11. )
  12. def jjy_capture_p():
  13. wrap_target()
  14. wait(1, pin, 0) # 立ち上がりを待つ
  15. set(x, 0).side(1) # カウンタのリセット
  16. label("loop")
  17. # --- 1ms 待機ブロック (10kHz動作時、10クロック消費) ---
  18. nop() [7] # 8クロック
  19. nop() # 1クロック
  20. jmp(x_dec, "next") # 1クロック (xをカウントダウン)
  21. label("next")
  22. jmp(pin, "loop") # まだHighならループ継続(+1クロック)
  23. mov(isr, x).side(0) # カウント結果をISRへ
  24. push() # Pythonへ送信
  25. irq(rel(0))
  26. wrap()
  27. @rp2.asm_pio(
  28. sideset_init=(rp2.PIO.OUT_LOW),
  29. )
  30. def jjy_capture_n():
  31. wrap_target()
  32. wait(0, pin, 0) # 立ち下がりを待つ
  33. set(x, 0).side(1) # カウンタのリセット
  34. label("loop")
  35. # --- 1ms 待機ブロック (10kHz動作時、10クロック消費) ---
  36. nop() [7] # 8クロック
  37. jmp(x_dec, "next") # 1クロック (xをカウントダウン)
  38. label("next")
  39. jmp(pin, "success") # Highならループ脱出(+1クロック)
  40. jmp("loop") # Lowなのでループ継続(+1クロック)
  41. label("success")
  42. mov(isr, x).side(0) # カウント結果をISRへ
  43. push() # Pythonへ送信
  44. irq(rel(0))
  45. wrap()
  46. # 定数定義
  47. JJY_ERROR = 3 # エラー
  48. JJY_P_MARK = 2 # ポジションマーカー
  49. MARK_POSITIONS = (0, 9, 19, 29, 39, 49, 59) # ポジションマーカーの位置
  50. class JJYDecoder(Debug,TimeSource):
  51. """
  52. JJYデコーダークラス
  53. """
  54. def __init__(self, callback=None, smid = 0, input_port=15, input_pol=1, indicator_port=INDICATOR_PIN):
  55. """
  56. コンストラクタ
  57. Args:
  58. callback: コールバック関数 callback((jjy_time, received_ticks_ms)):
  59. jjy_time: 受信した日本標準時(UNIXエポックタイム)
  60. received_ticks_ms: jjy_timeを受信したtime.ticks_ms()
  61. smid: このクラスで使用するステートマシン番号
  62. input_port: JJY受信ユニットの出力が接続されているGPIO番号
  63. input_pol: JJY受信ユニットの出力極性、1=正論理、0=負論理
  64. idicator_port: インジケーターLEDのGPIO番号
  65. """
  66. # 60秒分のビットを格納するバッファ
  67. self.bit_buffer = [0] * 60
  68. # 現在の秒位置
  69. self.pos = 0
  70. # 同期が始まったならTrue
  71. self.synced = False
  72. # マーカーを識別したらTrue
  73. self.last_was_marker = False
  74. # 同期に成功した回数
  75. self.sync_counter = 0
  76. # 受信ステートマシンの割り込みが発生したticks_ms
  77. self.ticks_ms = 0
  78. # コールバック関数
  79. self.callback_funcs = []
  80. # ステートマシン番号
  81. self.sm_id = smid
  82. # JJY受信ステートマシンのロードと起動
  83. in_pin = Pin(input_port, Pin.IN, pull=-1)
  84. self.sm: rp2.StateMachine
  85. if input_pol == 1: # 正論理
  86. self.sm = rp2.StateMachine(self.sm_id, jjy_capture_p, freq=10000, in_base=in_pin, jmp_pin=in_pin,sideset_base=Pin(indicator_port))
  87. else: # 負論理
  88. self.sm = rp2.StateMachine(self.sm_id, jjy_capture_n, freq=10000, in_base=in_pin, jmp_pin=in_pin,sideset_base=Pin(indicator_port))
  89. self.sm.irq(self._pio_handler,1)
  90. self.add_callback(callback)
  91. # self.sm.active(1)
  92. @micropython.native
  93. def _pio_handler(self, p):
  94. """
  95. PIO割り込みハンドラ
  96. """
  97. schedule(self._jjy_interrupt, (p, time.ticks_ms()))
  98. @micropython.native
  99. def _jjy_interrupt(self, args):
  100. """
  101. JJY受信割り込みハンドラ
  102. """
  103. p, ticks = (args)
  104. pulse_width = 0x100000000 - int(self.sm.get())
  105. self.ticks_ms = ticks - pulse_width # この信号の推定立ち上がり時間
  106. bit = JJY_ERROR # 3 はエラー
  107. if pulse_width < 100: pass # おそらくはノイズ、無視する
  108. elif pulse_width < 350: bit = JJY_P_MARK # ポジションマーカー
  109. elif pulse_width < 600: bit = 1
  110. elif pulse_width < 950: bit = 0
  111. else: pass # なんらかの異常
  112. self.dprint("pulse width=%d, bit=%d" % (pulse_width, bit))
  113. if bit == JJY_ERROR: # エラーが起きたらいったんご破算にする
  114. self.synced = False
  115. self.pos = 0
  116. if bit == JJY_P_MARK: # ポジションマーカー
  117. if self.last_was_marker: # ポジションマーカーが2つ続いたら1フレーム開始
  118. self.dprint("--- Frame Sync ---")
  119. self.pos = 0
  120. self.synced = True # 同期開始
  121. self.last_was_marker = True
  122. if self.synced: # ポジションマーカーの位置をチェック
  123. if self.pos not in MARK_POSITIONS:
  124. self.dprint("-- Error: Out of sync --") # 同期が外れているのでご破算にする
  125. self.synced = False
  126. self.pos = 0
  127. else:
  128. self.last_was_marker = False
  129. if self.synced:
  130. self.bit_buffer[self.pos] = bit
  131. self.pos += 1
  132. # 1フレーム受信完了
  133. if self.pos >= 60:
  134. self.__decode_frame()
  135. self.pos = 0
  136. self.synced = False # 継続せず
  137. @micropython.native
  138. def __decode_frame(self):
  139. """
  140. 1フレーム60個分のデータを日本標準時にデコードするプライベート関数
  141. """
  142. # パリティチェック
  143. pa2 = (self.bit_buffer[1]+self.bit_buffer[2]+self.bit_buffer[3]+self.bit_buffer[5]+self.bit_buffer[6]+self.bit_buffer[7]+self.bit_buffer[8]) % 2
  144. pa1 = (self.bit_buffer[12]+self.bit_buffer[13]+self.bit_buffer[15]+self.bit_buffer[16]+self.bit_buffer[17]+self.bit_buffer[18]) % 2
  145. if pa1 != self.bit_buffer[36] or pa2 != self.bit_buffer[37]: # エラー、デコードを諦める
  146. self.dprint("-- Parity Error --")
  147. return
  148. # minute
  149. m10 = self.bit_buffer[1] << 2 | self.bit_buffer[2] << 1 | self.bit_buffer[3]
  150. m1 = self.bit_buffer[5] << 3 | self.bit_buffer[6] << 2 | self.bit_buffer[7] << 1 | self.bit_buffer[8]
  151. minute = m10 * 10 + m1
  152. # hour
  153. h10 = self.bit_buffer[12] << 1 | self.bit_buffer[13]
  154. h1 = self.bit_buffer[15] << 3 | self.bit_buffer[16] << 2 | self.bit_buffer[17] << 1 | self.bit_buffer[18]
  155. hour = h10 * 10 + h1
  156. # yearday
  157. yd100 = self.bit_buffer[22] << 1 | self.bit_buffer[23]
  158. yd10 = self.bit_buffer[25] << 3 | self.bit_buffer[26] << 2 | self.bit_buffer[27] << 1 | self.bit_buffer[28]
  159. yd1 = self.bit_buffer[30] << 3 | self.bit_buffer[31] << 2 | self.bit_buffer[32] << 1 | self.bit_buffer[33]
  160. yearday = yd100 * 100 + yd10 * 10 + yd1
  161. # year
  162. y10 = self.bit_buffer[41] << 3 | self.bit_buffer[42] << 2 | self.bit_buffer[43] << 1 | self.bit_buffer[44]
  163. y1 = self.bit_buffer[45] << 3 | self.bit_buffer[46] << 2 | self.bit_buffer[47] << 1 | self.bit_buffer[48]
  164. year = y10 * 10 + y1 + 2000 # 2000年代は決め打ち
  165. # month, mday
  166. starting_sec = time.mktime((year,1,1,0,0,0,0,0)) # 起点=本年1月1日0時0分
  167. target_sec = starting_sec + (yearday - 1) * 86400 # 経過秒を加算
  168. current_dt = time.localtime(target_sec)
  169. mday = current_dt[2]
  170. month = current_dt[1]
  171. # weekday
  172. jjy_weekday = self.bit_buffer[50] << 2 | self.bit_buffer[51] << 1 | self.bit_buffer[52]
  173. weekday = (jjy_weekday + 6) % 7
  174. # JJYから得た時刻を通知する
  175. jjy_time = time.mktime((year,month,mday,hour,minute,59,weekday,yearday))
  176. data = (jjy_time, self.ticks_ms)
  177. try:
  178. for callback in self.callback_funcs:
  179. if callback is not None:
  180. schedule(callback, data)
  181. except RuntimeError:
  182. self.dprint("micropython.schedule queue full")
  183. def add_callback(self, callback):
  184. """
  185. 時刻を通知するコールバック関数を登録する
  186. Args:
  187. callback: コールバック関数 callback((jjy_time, received_ticks_ms)):
  188. jjy_time: 受信した日本標準時(UNIXエポックタイム)
  189. received_ticks_ms: jjy_timeを受信したtime.ticks_ms()
  190. """
  191. if callback is not None:
  192. self.callback_funcs.append(callback)
  193. def stop(self):
  194. """ステートマシンの実行を一時停止する"""
  195. self.sm.active(0)
  196. def restart(self):
  197. """ステートマシンをリセットして再起動する"""
  198. self.sm.restart()
  199. self.sm.active(1)
  200. def release(self):
  201. """
  202. 終了処理。
  203. ステートマシンを停止し、割り込みを解除した上でPIOコードをメモリから除去する。
  204. """
  205. # SM停止
  206. self.sm.active(0)
  207. # 割り込みハンドラ解除
  208. self.sm.irq(None, 1)
  209. # PIOコード除去
  210. pio_no = 0
  211. if self.sm_id > 3:
  212. pio_no = 1
  213. pio = rp2.PIO(pio_no)
  214. pio.remove_program()