Raspberry Pi Picoのマルチコアと割り込み処理の問題
Raspberry Pi Picoをmicropythonで組み込みを開発した時、割り込みの処理でフリーズする時がある。
Picoがフリーズする条件は、COREをデュアルで動作させ、タイマー割り込みとGPIOの割り込みを使うと、何かしらのタイミングでフリーズが発生する。
タイマー割り込みの間隔を短くすると、そのフリーズが顕著になる。しかしCOREをシングルで使用するとフリーズしない。
デュアルコア動作でタイマー割り込みを使用せず、GPIOの割り込みだけならフリーズしない。
デュアルコア動作でタイマー割り込みの代わりにPIOで定期的に割り込みを発生させ、GPIOの割り込みも使用するとフリーズする。
割り込みが発生したら、machine.disable_irq()を呼び出して、割り込み処理中は他の割り込みを停止としても効果がない。
それらの検証から、割り込みが同時に複数の発生した時、割り込み処理がCORE0、CORE1の両方で分担した時にフリーズしているような気がする。
解決方法:
GPIOの割り込みは使用せず、PIOを使ってGPIOの状態をバッファリング(FIFO)する。メインのプログラムからは必要に応じて、FIFOからGPIOの状態を取り出す。
シングルコアでの動作も確認してみたが、7SEGの表示とLCD表示を一つのコアで処理すると、LCDの文字列に時間がかかってしまい、カウントダウン表示が一瞬止まったように見える。(カウントダウンがスムーズに表示されない)
from rp2 import PIO, StateMachine, asm_pio
# キー入力
@asm_pio(in_shiftdir=PIO.SHIFT_LEFT)
def sw_status():
set(x,0)
label("start")
in_(pins, 3) # 3つのGPIOの状態を読み込む
mov(y,isr)
jmp(x_not_y, "pushval") # 前回の入力と今回の入力が違ったらプッシュ
in_(null, 32) # 32ビットシフトさせてisrをクリアー状態にする。
jmp("start")
label("pushval")
mov(x,isr) # スイッチ情報を保存
push() # スイッチ情報をプッシュ(isrはクリアされる)
jmp("start")
class KeyStatus:
def __init__(self, smno, pinno):
self.counter = 0
self.sm = StateMachine(smno, sw_status, freq = 2_000, in_base = pinno)
# コールバック関数の登録
def set_active(self):
self.sm.active(1)
def set_inactive(self):
self.sm.active(0)
def has_value(self):
return self.sm.rx_fifo()
def getkey(self):
if self.sm.rx_fifo() > 0:
return self.sm.get()
return 0
キッチンタイマーのメインのプログラム。
作りかけ20%。
# Source: Electrocredible.com, Language: MicroPython
#
# CORE0,CORE1の両方を有効にして、タイマー割り込みとGPIOの割り込みの両方を使うと、GPIOの割り込みタイミングでフリーズする時がある。
# machine.disable_irq() 割り込み停止をつかても状態は変わらない。
# PIOをタイマー替わりに使用してもダメ
# 多分、デュアルコアが有効で、かつ、2つ以上割り込みが同時に発生するとフリーズするかも
import utime
import time
import micropython
import _thread
from rp2 import PIO, StateMachine, asm_pio
from machine import Pin,I2C, SoftSPI, Timer, RTC
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
from time import sleep
import max7219_8digit
from m8026s import m8026s
from pico_temp_sensor import Temperature
from pico_eeprom import eeprominfo
from quere import msg_quere
from encoder_pio import RotaryEncoder
from pio_keyin import KeyStatus
rtc = RTC()
# 割り込み処理中の例外を作成するための設定です
micropython.alloc_emergency_exception_buf(100)
spi = SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
ss = Pin(5, Pin.OUT)
display = max7219_8digit.Display(spi, ss)
I2C_ADDR = 0x27
I2C_NUM_ROWS = 2
I2C_NUM_COLS = 16
i2c = I2C(0, sda=machine.Pin(0), scl=machine.Pin(1), freq=400000)
lcd = I2cLcd(i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS)
#led25 = machine.Pin(25, machine.Pin.OUT)
#led25.value(0)
#led17 = machine.Pin(17, machine.Pin.OUT)
#led17.value(0)
#led16 = machine.Pin(16, machine.Pin.OUT)
#led16.value(0)
timertalbe = [-1,-1,-1,-1,-1,-1]
TIMER_ALARM_IDX = 0
TIMER_LCD_BACKLIGHT_IDX = 1
TIMER_TEMPERTURE_IDX = 2
TIMER_MUSIC_SIGNAL = 3
TIMER_ROTARY_SPEED_IDX = 4
TIMER_PRIODE_TEMP = 10 * 1 # 0.1s
TIMER_PRIODE_BACKLIGHT = 10 * 60 * 5 # 5 mis
ROTARY_STEP = 100 # 10s
# timertalbeは0.1s タイマーテーブル
#タイマー割り込みが0.1秒なら TIMER_DECREMENTは 1に設定
#タイマー割り込みが1秒なら TIMER_DECREMENTは 10に設定
TIMER_PERIOD = 100 # TIMER IRQ 1s
TIMER_DECREMENT = 1 # TIMER TABLE DEC VAL 1=0.1s 10=1s
clearPIN = 18
startStopPIN = 19
rotarySwPIN_A = 6
rotarySwPIN_B = 7
rotarySwitchB = Pin(rotarySwPIN_B, Pin.IN)
playMusicSignal_PIN = 14
playMusicCommand_PIN = 15
melody = m8026s(playMusicCommand_PIN)
#EEPROM
EEPROM_ID = 1
EEPROM_ADDR = 0x50
EEPROM_SCL_PIN = 11
EEPROM_SDA_PIN = 10
eeprom = eeprominfo(EEPROM_ID, EEPROM_ADDR, EEPROM_SCL_PIN, EEPROM_SDA_PIN)
#message キュー
m_quere_core0 = msg_quere();
m_quere_core1 = msg_quere();
FOCUS_LCD = 1
FOCUS_7SEG = 2
processFocus = FOCUS_7SEG
# 100ミリ秒単位でタイマー割り込み
def decTimerFunc(timer):
#state = machine.disable_irq()
for num in range(5):
if timertalbe[num] > 0:
timertalbe[num] -= TIMER_DECREMENT
# 書き換え後に割り込みを再開します。
#machine.enable_irq(state)
# スイッチ入力
Keyin = KeyStatus(1, pinno=Pin(18))
Keyin.set_active()
#ロータリーエンコーダー
REncoder = RotaryEncoder(0, clock=Pin(rotarySwPIN_A),signal=Pin(rotarySwPIN_B))
REncoder.set_active()
def SwitchPIO():
val = REncoder.get_leftRight()
if val == 1 :
m_quere_core0.push(m_quere_core1.MSG_ROTARY_LEFT)
m_quere_core1.push(m_quere_core1.MSG_ROTARY_LEFT)
elif val == 2 :
m_quere_core0.push(m_quere_core1.MSG_ROTARY_RIGHT)
m_quere_core1.push(m_quere_core1.MSG_ROTARY_RIGHT)
val = Keyin.getkey()
if val == 1 :
m_quere_core0.push(m_quere_core1.MSG_BUTTON_CLEAR)
m_quere_core1.push(m_quere_core1.MSG_BUTTON_CLEAR)
elif val == 2 :
m_quere_core0.push(m_quere_core1.MSG_BUTTON_START_STOP)
m_quere_core1.push(m_quere_core1.MSG_BUTTON_START_STOP)
if m_quere_core1.has():
timertalbe[TIMER_LCD_BACKLIGHT_IDX] = TIMER_PRIODE_BACKLIGHT
lcd.backlight_on()
display.set_intensity(8)
lcd_wakeup = True
# メロディーの終了監視タイマーセット
def playMusicSignal(pin):
state = machine.disable_irq()
timertalbe[TIMER_MUSIC_SIGNAL] = 5 # 0.5s
machine.enable_irq(state)
# 秒数から時間文字列に変換する
def makeTimeFromSerial(second_value):
val = int(second_value / 10) # 秒数に変換
# hour_val = int(val / 3600)
# val = val - hour_val * 3600
min_val = int(val / 60)
sec_val = val - min_val * 60
mil_val = int(second_value % 10)
timestr = ""
if min_val > 0:
timestr = timestr + "%3d" % min_val + "-"
else:
timestr = timestr + " "
if min_val > 0 :
timestr = timestr + "%02d" % sec_val + "."
else:
timestr = timestr + "%2d" % sec_val + "."
timestr = timestr + "%1d" % mil_val
return timestr
# LCD, 7SEG 省電力
def lcd_disp_power():
if not m_quere_core1.has() and timertalbe[TIMER_LCD_BACKLIGHT_IDX] == 0 :
timertalbe[TIMER_LCD_BACKLIGHT_IDX] = -1
lcd.backlight_off()
display.set_intensity(1)
lcd_wakeup = False
# 7SEG 処理
def task7SEG():
global timertalbe
global processFocus
rcounter = 0
old_rcounter = rcounter
old_alarm_val = 0
step7segview = 0
count_s = makeTimeFromSerial(rcounter)
display.write_to_buffer(count_s)
display.display()
while True:
lcd_disp_power()
SwitchPIO()
q_msg = m_quere_core1.pop()
if step7segview == 0:
if q_msg == m_quere_core1.MSG_BUTTON_START_STOP:
if rcounter > 0:
timertalbe[TIMER_ALARM_IDX] = rcounter
timertalbe[TIMER_LCD_BACKLIGHT_IDX] = rcounter + TIMER_PRIODE_BACKLIGHT
rcounter = 0
step7segview = 1
else:
old_rcounter = rcounter
if q_msg == m_quere_core1.MSG_ROTARY_RIGHT:
if timertalbe[TIMER_ROTARY_SPEED_IDX] > 0:
rcounter = rcounter - ROTARY_STEP * 3
else:
rcounter = rcounter - ROTARY_STEP
if rcounter < 0:
rcounter = 0
timertalbe[TIMER_ROTARY_SPEED_IDX] = 1
elif q_msg == m_quere_core1.MSG_ROTARY_LEFT:
if timertalbe[TIMER_ROTARY_SPEED_IDX] > 0:
rcounter = rcounter + ROTARY_STEP * 3
else:
rcounter = rcounter + ROTARY_STEP
timertalbe[TIMER_ROTARY_SPEED_IDX] = 1
#変化あり
if old_rcounter != rcounter:
count_s = makeTimeFromSerial(rcounter)
display.write_to_buffer(count_s)
display.display()
if q_msg == m_quere_core1.MSG_BUTTON_CLEAR:
rcounter = 0
count_s = makeTimeFromSerial(rcounter)
display.write_to_buffer(count_s)
display.display()
elif step7segview == 1:
if processFocus == FOCUS_7SEG:
if q_msg == m_quere_core1.MSG_BUTTON_START_STOP: # 一時停止
m_quere_core1.clear()
rcounter = timertalbe[TIMER_ALARM_IDX]
step7segview=0
# カウントダウン中にクリアボタン押下で、LCDへフォーカス移動
if q_msg == m_quere_core1.MSG_BUTTON_CLEAR:
m_quere_core0.clear()
processFocus = FOCUS_LCD
m_quere_core0.push(m_quere_core0.MSG_BUTTON_CLEAR)
if old_alarm_val != timertalbe[TIMER_ALARM_IDX]:
count_s = makeTimeFromSerial(timertalbe[TIMER_ALARM_IDX])
display.write_to_buffer(count_s)
display.display()
old_alarm_val = timertalbe[TIMER_ALARM_IDX]
if timertalbe[TIMER_ALARM_IDX] == 0:
count_s = makeTimeFromSerial(timertalbe[TIMER_ALARM_IDX])
display.write_to_buffer(count_s)
display.display()
timertalbe[TIMER_ALARM_IDX] = -1
timertalbe[TIMER_MUSIC_SIGNAL] = 0
step7segview = 2
elif step7segview == 2:
if timertalbe[TIMER_MUSIC_SIGNAL] == 0:
timertalbe[TIMER_MUSIC_SIGNAL] = 10
melody.sendCommand(melody.MUSICNUMBER_BASE + 3);
if q_msg == m_quere_core1.MSG_BUTTON_START_STOP:
melody.sendCommand(melody.COMMAND_STOP);
rcounter = 0
step7segview = 0
# メインタスク処理
def mainTask():
global timertalbe
global lcd_wakeup
global step7segview
global processFocus
lcd_wakeup = True
old_second = 0
#温度 3.3vは高すぎるので3.23v
sensor_temp = Temperature(3.27)
temperature_celcius = 0
configure_mode = False
while True:
if processFocus == FOCUS_LCD:
q_msg = m_quere_core0.pop()
if q_msg == m_quere_core0.MSG_BUTTON_CLEAR:
if configure_mode == True:
# Configureモードで再度クリアーボタンを押されたら、フォーカスを7SEGに移す
configure_mode = False
m_quere_core1.clear()
processFocus = FOCUS_7SEG
else:
configure_mode = True
lcd.clear()
lcd.move_to(0,0)
lcd.putstr("CONFIGURE")
else:
# 1s毎に温度値を取得する
if timertalbe[TIMER_TEMPERTURE_IDX] == 0:
temperature_celcius = sensor_temp.ReadTemperature()
timertalbe[TIMER_TEMPERTURE_IDX] = TIMER_PRIODE_TEMP
# 省電力
if timertalbe[TIMER_LCD_BACKLIGHT_IDX] == 0:
if (timertalbe[TIMER_ALARM_IDX] > 0):
timertalbe[TIMER_LCD_BACKLIGHT_IDX] = TIMER_PRIODE_BACKLIGHT
else:
timertalbe[TIMER_LCD_BACKLIGHT_IDX] = -1
lcd.backlight_off()
display.set_intensity(1)
if lcd_wakeup:
datetime = rtc.datetime()
if old_second != datetime[6]:
lcd.move_to(0,0)
lcd.putstr("{year:>04d}/{month:>02d}/{day:>02d}".format(
year=datetime[0], month=datetime[1], day=datetime[2]) )
lcd.move_to(0,1)
lcd.putstr("{HH:>02d}:{MM:>02d}:{SS:>02d} {cc:>2.1f}C".format(HH=datetime[4]
, MM=datetime[5]
, SS=datetime[6]
, cc=temperature_celcius))
old_second = datetime[6]
def main():
timertalbe[TIMER_LCD_BACKLIGHT_IDX] = TIMER_PRIODE_BACKLIGHT
timertalbe[TIMER_TEMPERTURE_IDX] = TIMER_PRIODE_TEMP
localtime = utime.localtime()
rtc.datetime((localtime[0], localtime[1], localtime[2], 0, localtime[3], localtime[4], localtime[5], 0))
decTimer = Timer(period=TIMER_PERIOD, callback=decTimerFunc )
#decTimer.init(mode=Timer.PERIODIC, freq=10, callback=decTimerFunc)
#decTimer.init(period=100, callback=decTimerFunc )
playMusicSignalIO = machine.Pin(playMusicSignal_PIN, machine.Pin.IN, machine.Pin.PULL_UP)
playMusicSignalIO.irq(trigger=Pin.IRQ_FALLING, handler=playMusicSignal)
# CORE 1
_thread.stack_size(2048)
_thread.start_new_thread(task7SEG, ())
# CORE 0
#task7SEG() # 7SEG
mainTask() # LCD
if __name__ == "__main__":
main()