As of CircuitPython 7.3, it is possible to send data to one or more PIO peripherals while Python code keeps running. Depending on how the background_write function is called, it can send a block of data just once, or repeatedly until it's directed otherwise.

To introduce this capability, the following example shows morse code messages on the board's LED. It works on any RP2040 board with board.LED. You can also use it on a Raspberry Pi Pico, but you'll have to add an external LED and series resistor, then modify the code to use the right board.GP## connection.

# SPDX-FileCopyrightText: 2022 Jeff Epler, written for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""Demonstrate background writing, including loop writing, with morse code.

On any rp2040 board with board.LED, this will alternately send 'SOS' and 'TEST'
via the LED, demonstrating that Python code continues to run while the morse
code data is transmitted. Alternately, change one line below to make it send
'TEST' forever in a loop, again while Python code continues to run.

The combination of "LED status" and duration is sent to the PIO as 16-bit number:
The top bit is 1 if the LED is turned on and 0 otherwise.  The other 15 bits form a delay
value from 1 to 32767. A subset of the morse code 'alphabit' is created, with everthing
based on the 'DIT duration' of about 128ms (1MHz / 32 / 4000).

https://en.wikipedia.org/wiki/Morse_code
"""

import array
import time
from board import LED
from rp2pio import StateMachine
from adafruit_pioasm import Program

# This program turns the LED on or off depending on the first bit of the value,
# then delays a length of time given by the next 15 bits of the value.
# By correctly choosing the durations, a message in morse code can be sent.
pio_code = Program(
    """
        out x, 1
        mov pins, x
        out x, 15
    busy_wait:
        jmp x--, busy_wait [31]
        """
)


# The top bit of the command is the LED value, on or off
LED_ON = 0x8000
LED_OFF = 0x0000

# The other 15 bits are a delay duration.
# It must be the case that 4 * DIT_DURATION < 32768
DIT_DURATION = 4000
DAH_DURATION = 3 * DIT_DURATION

# Build up some elements of morse code, based on the wikipedia article.
DIT = array.array("H", [LED_ON | DIT_DURATION, LED_OFF | DIT_DURATION])
DAH = array.array("H", [LED_ON | DAH_DURATION, LED_OFF | DIT_DURATION])
# That is, two more DAH-length gaps for a total of three
LETTER_SPACE = array.array("H", [LED_OFF | (2 * DAH_DURATION)])
# That is, four more DAH-length gaps (after a letter space) for a total of seven
WORD_SPACE = array.array("H", [LED_OFF | (4 * DIT_DURATION)])

# Letters and words can be created by concatenating ("+") the elements
E = DIT + LETTER_SPACE
O = DAH + DAH + DAH + LETTER_SPACE
S = DIT + DIT + DIT + LETTER_SPACE
T = DAH + LETTER_SPACE
SOS = S + O + S + WORD_SPACE
TEST = T + E + S + T + WORD_SPACE

sm = StateMachine(
    pio_code.assembled,
    frequency=1_000_000,
    first_out_pin=LED,
    pull_threshold=16,
    auto_pull=True,
    out_shift_right=False,
)

# To simply repeat 'TEST' forever, change to 'if True':
if False:  # pylint: disable=using-constant-test
    print("Sending out TEST forever", end="")
    sm.background_write(loop=TEST)
    while True:
        print(end=".")
        time.sleep(0.1)

# But instead, let's alternate SOS and TEST, forever:

while True:
    for plain, morse in (
        ("SOS", SOS),
        ("TEST", TEST),
    ):
        print(f"Sending out {plain}", end="")
        sm.background_write(morse)
        sm.clear_txstall()
        while not sm.txstall:
            print(end=".")
            time.sleep(0.1)
        print()
        print("Message all sent to StateMachine (including emptying FIFO)")
        print()

Now to focus on some specific parts of the program. First, take a look at the PIO program itself. This program reads the new LED state first, sets it on the pin, reads a delay, then loops until the delay has been completed:

pio_code = Program(
    """
        out x, 1
        mov pins, x
        out x, 15
    busy_wait:
        jmp x--, busy_wait [31]
        """
)

Next, according to the necessary arrangement of bits, a small vocabulary in Morse code is built up:

# Letters and words can be created by concatenating ("+") the elements
E = DAH + LETTER_SPACE
O = DAH + DAH + DAH + LETTER_SPACE
S = DIT + DIT + DIT + LETTER_SPACE
T = DIT + LETTER_SPACE
SOS = S + O + S + WORD_SPACE
TEST = T + E + S + T + WORD_SPACE

Now, of course the Python code could simply write the data to the State Machine and wait for it to finish with sm.write(SOS). However, by using this method, control won't return to the Python code until all the data is handled. In the case where this is undesirable, use a background write so that the operation continues in the background while CircuitPython code continues working in the foreground:

sm.background_write(morse)
sm.clear_txstall()
while not sm.txstall:
    print(end=".")
    time.sleep(0.1)
print()
print("Message all sent to StateMachine (including emptying FIFO)")
print()

By clearing and then monitoring the txstall flag, the code waits for the whole message to be played before continuing.

Using the loop keyword argument, a single message can be made to loop forever:

print("Sending out TEST forever", end="")
sm.background_write(loop=TEST)
while True:
    print(end=".")
    time.sleep(0.1)

That's one LED, but what if you have greater ambitions? Head to the next page:  Driving 7-segment displays with Background Writes.

This guide was first published on Mar 03, 2021. It was last updated on May 10, 2022.

This page (Advanced: Generating Morse Code with Background Writes) was last updated on Jun 07, 2023.

Text editor powered by tinymce.