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)
2
  • The problem seem to be that one cannot use local variables in the PIO assembly. So my guess is using 0s and 1s of the frame explicitly would be the only way to go. In addition you may want to have a look at the examples of how PIO is used github.com/raspberrypi/pico-micropython-examples/tree/master/…
    – Vadim
    Sep 11, 2023 at 7:35
  • @Vadim, it seems local variables are fine. The pio_ws2812 example also uses local variables. Using globals seems to be the problem.
    – wovano
    Sep 13, 2023 at 20:41

2 Answers 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
3
  • Unfortunately, I need to change the speed of the motor quite regularly (many times per second), so I do need to change the variable from outside the function.
    – lpinfinity
    Sep 14, 2023 at 0:57
  • @lpinfinity, please add that information to your question, because it wasn't directly clear for me (and maybe others), especially since the question title focuses on the "global" keyword and your code does not demonstrate how you want to use it. However, I have updated my answer and hope this puts you in the right direction!
    – wovano
    Sep 14, 2023 at 5:59
  • @lpinfinity, using an online simulator I was able to write and (somewhat) test the code. Let me know if this works for you. The simulator is very useful to understand how the code works (change Pin(0) to Pin(5) to see the LED status changing accordingly). You can also step through the PIO program code.
    – wovano
    Sep 14, 2023 at 10:16
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.

1
  • 1
    I tried that, unfortunately I still get the same error as before.
    – lpinfinity
    Sep 13, 2023 at 1:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.