見出し画像

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()


いいなと思ったら応援しよう!