What do I misunderstand about rgbled()?



  • I'm new to Python and new to Pycom (lopy4) so this might turn out to be a silly question, but if I run this code fragment (LoRa init taken care of in boot.py and working)

    red=0x330000
    green=0x003300
    blue=0x000033
    
    senddata = 0xaa
    switch = Pin('P10', mode = Pin.IN)
    
    while True:  
        if(switch() == 0):
            rgbled(red+green+blue)  # white    
            #print("Button pressed")
            retdata=loraSendReceive(senddata)
            print("Data transmitted: ", senddata)
            if(len(retdata) != 0):
                print("Data received: ", retdata)
            time.sleep(1)
        #print("Button not pressed")
        rgbled(green)  # Green
    

    I would expect the rgb led to turn green immediately and white for as long as it takes to send a byte plus the 1 sec sleep as soon as the button is pressed. That does not happen. The led turns green and stays green after the first button press. Messages are sent and received via LoRaWan.

    If I uncomment the 'button' print statements those are working as expected. It makes a mess for printing a lot of messages but at least the program shows it is going where it is expected to go. The led stays green.

    What am I missing in turning on the rgb led?



  • @robert-hh

    And precise enough to create the timing signals for a WS2812! I remember writing a piece of C-code for a PIC microcontroller controlling a whole string of these leds and wrestling with that for a bit.

    Thanks for your support. I was getting a little bit frustrated with the lopy on my bench but am starting to realize now that the difficult bit here is not in the networking but in the way a high-level language like Python interfaces with low level hardware. Just more stuff to learn :-)



  • @Paul-Holthuizen That is the Remote Controller Unit of the ESP32. Intended to send and receive messages for remote controllers. But it is also fine for creating and timing bit patterns with high precision. See https://docs.pycom.io/firmwareapi/pycom/machine/rmt/#app



  • @robert-hh

    For my benefit: what's the RMT?



  • @Gijs said in What do I misunderstand about rgbled()?:

    I would not have expected that and figured the method to be blocking until the LED color actually changed. Do you think this is a bug, or a feature?

    Using the RMT is definitly a feature. The code actually waits until a previous transmission is finished. And that's the heart of my PR. I just insert another wait pulse cycle pair to the RMT chain, which causes a sufficient delay. I was not able to find the place causing the 50µs delay which is initially there.



  • @Gijs Not really. It is not that a follow up call to rgbled() squashes the previous one. It is the opposite. If the follwow-up call is too early, it will be ignored. And in the example it was the rgbled(white) which came too soon, only ~50 µs after the previous one.
    The messages themselves had proper structure, only the time between them was too short.
    shortburst.png
    Adding more time (here 144 µs) made it work.
    shortburst_145_pass.png
    I made a PR ensuring at least 280 µs, because that's what I found in the datasheet of the WS2812B-V4 (opposed to 50µs in the WS2812B datasheet).


  • Global Moderator

    So if I understand this correctly, the loop

    pycom.heartbeat(False)
    While True:
       rgbled(green)
    

    does not show a green color on the led because we call it too fast, leaving no time to actually change the led as we queue another task to change the led color right after the first one, squashing the first one? That would make sense.

    Thanks for the thorough explanation! I would not have expected that and figured the method to be blocking until the LED color actually changed. Do you think this is a bug, or a feature?

    Gijs



  • @Paul-Holthuizen Adding a small sleep time into a busy loop is anyhow recommended.
    In this case, pycom.rgbled() does not execute asynchronously as a function. It's the sending process that is started. I'm not sure if it is operating in hardware of software. Maybe similar to the UART, where a 128 byte queue is located in the UART hardware, which can be sent or received w/o any software/firmware interaction.
    The RTOS is documented on the espressif web pages. https://docs.espressif.com/projects/esp-idf/en/release-v4.1/ The only complication may be, that Pycom does not use the latest release, and also a tailored version. But the previous versions should be available to at espressif.

    Edit: I also use a small test script along your code for verification:

    import pycom
    import time
    from machine import Pin
    pycom.heartbeat(False)
    white=0x333333
    green=0x003300
    p = Pin("P9", Pin.IN, Pin.PULL_UP)
    time.sleep(0.5)
    
    while(True):
        if(p() == 0):
            pycom.rgbled(white)
            print("Data verzonden: ")
            time.sleep(1)
        pycom.rgbled(green)
        time.sleep_us(200)
    


  • @robert-hh

    This seems a reasonable explanation. And it gives me an idea of how to handle this particular problem. However, it also gives me something to think about. How do I know what functions are executed asynchronously?

    This is by no means meant to be disapproving but how am I going to use inputs and outputs if I do not know beforehand how these are being handled by Pycom? Are these handled by an (interrupt driven) RTOS or not? If so, is there a description of the RTOS that handles all of this?

    I am really impressed with the ability to make a solid network connection with only a couple of lines of code, but I still struggle with the connect-to-hardware side of things with the Lopy device. No doubt a learning curve.



  • @tuftec The calls to the led function are pretty simple. The function mod_pycom_rgb_led() in modpycom.c calls
    bool led_set_color() in modled.c, which then calls rmt_write_items() to transfer the data bit pattern to the RGB led, AFTER waiting that the previous transmission has finished. Maybe the wait time is too short. The WS2812 requires reset time of 50µs between two transmissions. I see no wait period for that in the code, but in the actual timing on P2 I see that delay.
    But it still only works with my test code, if I increase the delay to 200µs.

    Edit: Making a few trials it turns out, that up to a time gap of about 125µs between two commands to the RGB led, the second command is ignored. From about 150µs on, the second command will be executed. I do not know which RGB led is on the board. The OEM baseboard schematics show the WS2812B which is specified for 3.5V min, and I replaced once successfully a defective RGB led with a WS2812B. So it might work to some extend. But the mandatory reset time may be longer than 50 µs in that case.



  • I have been following this thread as it could explain some issues I have been chasing in other areas. If the rgb led is in fact controlled by the underlying RTOS then that would explain a lot. You can not just simply look at your python code and work out what is happening. Calls to the led function could get queued or dropped without you knowing. I have observed strange timing behaviour when dealing with the LTE component on the FiPy which is handled by a totally seperate processor/modem. Am I correct in reading somewhere that some of the pycom modules (like LoRa) run on the second core, which call also introducing timing issues, particularly when not blocking.
    Peter.



  • @Paul-Holthuizen I have an possible reason. In this version of the code:

    while(True):  
        if(switch() == 0):
            rgbled(white)
            retdata=loraSendReceive(senddata)
            print("Data verzonden: ", senddata)
            if(len(retdata) != 0):
                print("Data ontvangen: ", retdata)
        rgbled(green)
    

    You are sending rgbled(green) very fast, when the button is NOT pressed. That may lock up the RGB, such that other calls to rgbled(white) are ignore. You could try to insert a small delay, like 1 ms, into the while(True) loop, just to give rgbled(green) time to complete.
    Note: The signal pattern for the RGB led is created by the RMT unit. You load it once with a bit pattern, and then the transmission is done while the calling code can already return. So it's asynchronous to the code, which makes this kind interference possible.



  • @robert-hh

    No, I do not debounce the switch at all. For the loraSendReceive() takes always the same 2135 ms to finish which is a debounce in itself.
    I also see only one print statement on the console after pressing the switch, and only one message gets sent out.



  • @Paul-Holthuizen Another question: do you debounce the switch reading in the function switch(). And is there a code path by which the function loraSendReceive() return very fast?



  • @robert-hh

    Interesting, and thanks for thinking along on this!

    However, in my case the time between setting the led white and then setting it back to green again is always at least 2135 ms or the time it takes loraSendReceive() to finish. This time is timed using Timer.chrono().

    So I do not think I simply color switch the led too fast to be seen or to be executed by rgbled() unless something weird happens (like subsequent code executing before loraSendReceive() is finished.

    Furthermore it is weird that the place of rgbled(green) seems to have an impact; when placed under if(len(retdata) != 0): it functions, and when placed under if(switch() == 0): it does not. Not even when I insert a time.sleep(1) right before it to prevent rgbled(green) from being called too often. In the latter case I have to hold the switch for at least a second, after which it does send the message, but still refuses to set the led to white.



  • @Paul-Holthuizen I wrote a little program to test wioth the RGB led, how short can pulses be made such that they can still be seen.

    • If it is just a on/off pulse, it can go down to 100µs and may less
    • if it is a switch between green and short white, it requires about 20 ms and more to be clearly noticeable.

    BUT. There is an interesting phenomenon. If the time between two RGB Led calls is very short, like 20µ or less, the LED may stick in one state and requires a long on/of cycle to recover.
    Test code below. I borrowed the color names & values from your code.

    import pycom
    pycom.heartbeat(False)
    import time
    red=0x330000
    green=0x003300
    blue=0x000033
    white = red + green + blue
    black = 0
    
    pycom.rgbled(black)
    
    while True:
        q = input("Time")
        if q == "q": break
        t = int(q)
        pycom.rgbled(white)
        time.sleep_us(t)
        pycom.rgbled(black)
    


  • @Paul-Holthuizen I woudl not expect the MicroPython core to be have wrong. It has been thoroughly tested. I never ran into an error at that program logic level.
    Two seconds it the time LoRaWAN waits for a downlink packet from the host. So it could be that sometimes the send in loraSendReceive() blocks for the 2 + x seconds, and sometimes it returns immediately. That may be related to timing differences between the MicroPython task and the LoRa task.
    If you have a logic analyzer, you could set and clear a GPIO pin immediately before and after the loraSendReceive() call. You could also connect a LED to it. A simple LED may work better than the RGB led, which requires a complex message. I do not know if this message is sent immediately or e.g. upon the next tick of a timer



  • @jcaron

    No threads, callbacks or triggers, at least not that I am aware of. And I changed the led colors as part of the debugging, no changes.

    There is one thing that strikes me as odd. If I upload the code as per sample 1 (the working one) everything works fine. I press the button, the led lights white, the message gets sent, the led lights green again.

    Now, if I remove the USB cable and attach a LiPo battery the Lopy 4 reboots, the led lights white once, then goes and stays green. All the while messages are sent every couple of seconds, as if the switch is held in (which it is not).

    Remove the battery, reconnect the USB, and after the boot (without me uploading anything) everything works as expected again. This is reproducibly.

    I am starting to wonder if I might have a defective device. Or if the firmware upgrade I did upon receiving the lopy went south somehow. There were a couple of choices that did not make much sense at the time, e.g. the 'type' parameter (which I set to 'development')

    I will re-program the fw and see if that somehowmakes a difference.



  • @Paul-Holthuizen that is indeed weird. I wondered if maybe you had an issue mixing spaces and tabs which could have caused the indentation to not be what you thought, but I don’t think you could get that result in that case anyway (you should probably still double-check, just in case).

    Do you use any threads? Are there any callbacks/triggers?

    Can you try using two other colours there just to be sure it isn’t something else changing the LED colour?



  • @jcaron said in What do I misunderstand about rgbled()?:

    .

    The socket is set to blocking right before sending off the data and reset to non-blocking right after.

    Data sent is only one byte; this takes ~50ms airtime but a lot longer in the Pycom code; measuring it with Timer I found it takes 2135 ms to execute the retdata=loraSendReceive(senddata) statement.

    What I do not understand is that my first snippet

    while(True):  
        if(switch() == 0):
            rgbled(white)
            retdata=loraSendReceive(senddata)
            print("Data verzonden: ", senddata)
            if(len(retdata) != 0):
                print("Data ontvangen: ", retdata)
            rgbled(green)
    

    works as expected, while the second

    while(True):  
        if(switch() == 0):
            rgbled(white)
            retdata=loraSendReceive(senddata)
            print("Data verzonden: ", senddata)
            if(len(retdata) != 0):
                print("Data ontvangen: ", retdata)
        rgbled(green)
    

    does not.

    In both snippets the program should show a white led while waiting for the data being sent off. But in the second snippet it looks like the if(switch() == 0): statement continues before the program returns from sending the data.

    Either something in Pycom is broken or I completely miss the way IF statements are handled in Python / Pycom :-)


Log in to reply
 

Pycom on Twitter