2

I am working on a project in which I need to control a brushless motor with a microcontroller. I have chosen the Pico as I already have some on hand and there is a high chance of it being completely destroyed, so the low price is nice.

To control the motor, I have connected one of the Pico's GPIO pins to the input of a dshot brushless ESC. I am using micropython as I do not have any experience with C or C++. I am able to get the motor to spin, but I am having some trouble changing the speed. Dshot uses a 16 bit digital frame for communication. More information on dshot can be found here: https://brushlesswhoop.com/dshot-and-bidirectional-dshot/.

When I set the frame variable to 1000001011000110 (and frameinverse to the inverse) inside of the square() function, the motor spins as expected. However, when I try to pull that variable from outside of the function as shown below, I get the error NameError: name 'frame' isn't defined.

Is there something I'm doing wrong, or should I try to find another way of achieving this goal? (I know the code has too much repetition, I want to get this working before I try to optimize anything)

from machine import Pin
from rp2 import PIO, StateMachine, asm_pio

frame = "1000001011000110" frameinverse = ''.join('0' if c=='1' else ('1' if c=='0' else c) for c in frame)

@asm_pio(set_init=PIO.OUT_LOW) def square(): global frame global frameinverse wrap_target() set(pins, 1) [int(str(frame)[0], 2)] set(pins, 0) [int(str(frameinverse)[0], 2)]

set(pins, 1) [int(str(frame)[1], 2)]
set(pins, 0) [int(str(frameinverse)[1], 2)]

set(pins, 1) [int(str(frame)[2], 2)]
set(pins, 0) [int(str(frameinverse)[2], 2)]

set(pins, 1) [int(str(frame)[3], 2)]
set(pins, 0) [int(str(frameinverse)[3], 2)]

set(pins, 1) [int(str(frame)[4], 2)]
set(pins, 0) [int(str(frameinverse)[4], 2)]

set(pins, 1) [int(str(frame)[5], 2)]
set(pins, 0) [int(str(frameinverse)[5], 2)]

set(pins, 1) [int(str(frame)[6], 2)]
set(pins, 0) [int(str(frameinverse)[6], 2)]

set(pins, 1) [int(str(frame)[7], 2)]
set(pins, 0) [int(str(frameinverse)[7], 2)]

set(pins, 1) [int(str(frame)[8], 2)]
set(pins, 0) [int(str(frameinverse)[8], 2)]

set(pins, 1) [int(str(frame)[9], 2)]
set(pins, 0) [int(str(frameinverse)[9], 2)]

set(pins, 1) [int(str(frame)[10], 2)]
set(pins, 0) [int(str(frameinverse)[10], 2)]

set(pins, 1) [int(str(frame)[11], 2)]
set(pins, 0) [int(str(frameinverse)[11], 2)]

set(pins, 1) [int(str(frame)[12], 2)]
set(pins, 0) [int(str(frameinverse)[12], 2)]

set(pins, 1) [int(str(frame)[13], 2)]
set(pins, 0) [int(str(frameinverse)[13], 2)]

set(pins, 1) [int(str(frame)[14], 2)]
set(pins, 0) [int(str(frameinverse)[14], 2)]

set(pins, 1) [int(str(frame)[15], 2)]
set(pins, 0) [int(str(frameinverse)[15], 2)]

wrap()

sm = rp2.StateMachine(0, square, freq=200000, set_base=Pin(0))

sm.active(1)

Bodo
  • 283
  • 1
  • 8
lpinfinity
  • 23
  • 3

2 Answers2

0

The global keyword is not used to pull variables from the outside into the function - it does the opposite, make variables defined in a function available to the outside code. Therefore, an error is thrown correctly, as you didn't define the globalized variables within the function. But this isn't necessary at all. Variables from outside code are always global and available in functions. When you just remove both lines containing global, your code should work fine.

Romy
  • 11
  • 2
0

Normally the global would not be needed in this case, since you only read the frame and frameinverse variables. You would only need the global keyword if you also write to the variable inside the function, because that would otherwise create a local variable and not update the global variable.

In this case it seems related to the asm_pio decorator. I'm not sure how this works exactly, but I guess it does some special "tricks" that do not work well with your global variables.

In case you want to control the pattern in the PIO program based on the Python variable frame, you would have to follow a different approach. You can write data to the TX FIFO of the PIO, and read that data from the program. Writing to the TX FIFO can be done using Statemachine.put(). The data can be read in the program using the pull instruction.

The following references explain everything you need to know to be able to do this:

  • The RP2040 datasheet, Chapter 3, which explains the PIO and all its functions.

  • The rp2 and rp2.StateMachine documentation, which explain how to use this from Python.

  • The WS2812 LEDs example in Chapter 3.6.2 of the RP2040 datasheet and this WS2812 Python example from the "Raspberry Pi Pico Python SDK" book. This example uses the FIFO to write from the Python application code to the PIO assembly program.

    Note that the protocol that is described in the link from your question is actually very similar to the WS2812 protocol, so this should be a really useful example for your situation.

Since I do not have your hardware, I'm not able to test the code, but using an online simulator, the following code seems to work as intended. You might have to double-check the timing and probably adjust the frequency accordingly.

from machine import Pin
from rp2 import PIO, StateMachine, asm_pio

@asm_pio(sideset_init=PIO.OUT_LOW, out_shiftdir=PIO.SHIFT_LEFT) def program(): wrap_target() pull(block) set(y, 16) label('loop') out(x, 1) .side(0) jmp(not_x, 'zero') .side(1) [1] jmp('endloop') .side(1) [1] label('zero') nop() .side(0) [1] label('endloop') jmp(y_dec, 'loop') .side(0) wrap()

sm = StateMachine(0, program, freq=200000, sideset_base=Pin(0)) sm.active(1)

def transmit_frame(frame: str): sm.put(int(frame, 2), 16)

print('Running') while True: transmit_frame('1000001011000110') # update with any value you like

wovano
  • 181
  • 3
  • 13