Raspberry Pi Pico timer priority

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
Post Reply
Leonti
Posts: 14
Joined: Sun Mar 28, 2021 12:29 pm

Raspberry Pi Pico timer priority

Post by Leonti » Sun Mar 28, 2021 12:40 pm

Hi,

I'm trying to control a robot that has Nema stepper motors, UART to communicate with the host and an SSD1306 I2C display.

I'd like the wheels to run as stable and smooth as possible, so I found this code: https://github.com/jeffmer/micropython- ... stepper.py

The idea is that a timer is used to execute a stepper callback which is executed at a very high frequency and then the callback decided whether to send a pulse to a stepper diver board.
Every second I also read current and voltage from an INA219 board using I2C, display results on a display and also send them to UART.

The problem is that every second the motors "stutter" so it seems like LCD/UART code has a priority over the timer.
My limited understanding was that the timer would pause everything else going on, execute, and then return control to the main loop, but it seems like as long as the main loop is busy the timer is not called or delayed.

Here is a snippet of the code:

Code: Select all

m1 = Stepper(18, 19)
m2 = Stepper(20, 21)
motors_en = Pin(22, Pin.OUT)
motors_en.high()

def step_cb(t):
  global m1, m2
  m1.do_step()
  m2.do_step()

tim = Timer()
tim.init(freq=10000, mode=Timer.PERIODIC, callback=step_cb)

def update_power():
  global screen, uart
  voltage = ina.bus_voltage
  current = ina.current
 # screen.battery_status(voltage, current) # screen is a wrapper around SSD1306 library using I2C
 # send('P:' + str(voltage) + ',' + str(current)) # just sends data over UART

report_deadline = utime.ticks_add(utime.ticks_ms(), 200)
while True:
  if uart.any() > 0:
    c = uart.read(1)
    if c == b'\n' and len(buffer) > 0:
      on_command(buffer.decode('utf-8'))
      buffer = b''
    elif len(buffer) > 0 or (len(buffer) == 0 and c == b'C'):
      buffer += c

  if utime.ticks_diff(report_deadline, utime.ticks_ms()) < 0:
    report_deadline = utime.ticks_add(utime.ticks_ms(), 1000)
    update_power()
Anything I can do to give the timer a higher priority?
Or restructure the code in a way that allows for "step_cb" callback to be always executed on schedule?

Cheers,
Leonti

rafl
Posts: 10
Joined: Tue Mar 23, 2021 7:15 am

Re: Raspberry Pi Pico timer priority

Post by rafl » Sun Mar 28, 2021 2:13 pm

It's my understanding that timers in the rp2 port are implemented using alarm pools described at https://raspberrypi.github.io/pico-sdk- ... ml#details. The C-level callback for those alarms does get executed from the timer interrupt handler, but the C callback that micropython provides to the alarm pool doesn't immediately run the associated python callback. It instead just schedules the python-level callback to be executed "soon" using mp_sched_schedule.

mp_sched_schedule is also exposed from python as machine.schedule, so all the documentation at https://docs.micropython.org/en/latest/ ... t=schedule should also apply to timer callbacks on the rp2 platform.

Based on that, I think it might be worth to have a look at what's being executed by the scheduler at the time the hardware timer interrupt runs. If it's something that won't yield control back to the scheduler reasonably quickly, your python timer callback will have to wait until it does.

I don't think it's currently possible to execute python callbacks in the interrupt handler context on the rp2 port.

If whatever is blocking the timer callback from being executed earlier is hard to identify or hard to adapt to not be blocking for an extended period of time, perhaps using the PIO mechanism would be an option for your application. With that you'd have very accurate timing for your outputs independently of anything that's going on in the ARM core.

Leonti
Posts: 14
Joined: Sun Mar 28, 2021 12:29 pm

Re: Raspberry Pi Pico timer priority

Post by Leonti » Mon Mar 29, 2021 7:03 am

Thanks for your answer @rafl!

Both UART communication and I2C(ssd1306 panel) communication are causing this issue, I'm just using the built-in UART and an external ssd1306 library, so I don't think I can affect timings there.

I tried another approach, since Pico has 2 cores, I thought I'd try replacing the timer with a loop running on another core, like this:

Code: Select all

def steppers_thread(m1, m2):
  while True:
    m1.do_step()
    m2.do_step()
    utime.sleep_us(100)

_thread.start_new_thread(steppers_thread, (m1, m2))
The code works, but it has exact same issue as before.
Shouldn't the second core be completely free for this task or does i2c/uart affect execution on both cores?

Post Reply