rpi2040 gives correct output on logic analizer for 8bit but not for 9bit communication
Port, board and/or hardware
rpi2040
MicroPython version
MicroPython v1.23.0 on 2024-06-02; Raspberry Pi Pico with RP2040
Reproduction
import machine
import utime
# Configure UART1 on GPIO8 (TX) and GPIO9 (RX)
uart = machine.UART(1, tx=machine.Pin(8), rx=machine.Pin(9), baudrate=25000, bits=9, parity=0, stop=1)
# Function to send the known pattern
def send_pattern():
#test bytes
uart.write(bytes([0x55])) # 0x55 (binary 01010101)
uart.write(bytes([0xAA])) # 0xAA (binary 10101010)
uart.write("AAA")
#what I want to actually send
uart.write(bytes.fromhex('0102'))
uart.write(bytes.fromhex('0104'))
uart.write(bytes.fromhex('00F9'))
#uart.write(b'\xAA')
#uart.write(b'\xFF')
# Send the known pattern every 50ms
while True:
send_pattern()
utime.sleep_ms(50)
Expected behaviour
expected to see those values in the logic analyzer
Observed behaviour
the logic analyzer sees this:
| name | type | start_time | duration | data | error |
|---|---|---|---|---|---|
| Async Serial | data | 0.0087 | 0.00042 | 0x0075 | |
| Async Serial | data | 0.00914 | 0.00042 | 0x0029 | framing |
| Async Serial | data | 0.00966 | 0.00042 | 0x0161 | framing |
| Async Serial | data | 0.0103 | 0.00042 | 0x0061 | |
| Async Serial | data | 0.01074 | 0.00042 | 0x002C | framing |
| Async Serial | data | 0.01126 | 0.00042 | 0x0064 | framing |
| Async Serial | data | 0.0119 | 0.00042 | 0x01F9 | |
| Async Serial | data | 0.05908 | 0.00042 | 0x0175 | framing |
| Async Serial | data | 0.05958 | 0.00042 | 0x010A | |
| Async Serial | data | 0.06002 | 0.00042 | 0x0161 | framing |
Additional Information
If I change the program and analyzer to use 8bits instead, things look better. I may have some issues there but the 0x55 and 0xAA are picked up for example.
uart = machine.UART(1, tx=machine.Pin(8), rx=machine.Pin(9), baudrate=25000, bits=8, parity=0, stop=1)
| name | type | start_time | duration | data | error |
|---|---|---|---|---|---|
| Async Serial | data | 0.01646 | 0.00038 | 0x55 | framing |
| Async Serial | data | 0.0169 | 0.00038 | 0xAA | framing |
| Async Serial | data | 0.01734 | 0.00038 | 0x41 | framing |
| Async Serial | data | 0.01778 | 0.00038 | 0x41 | framing |
| Async Serial | data | 0.01822 | 0.00038 | 0x41 | framing |
| Async Serial | data | 0.01866 | 0.00038 | 0x01 | |
| Async Serial | data | 0.0191 | 0.00038 | 0x02 | |
| Async Serial | data | 0.01954 | 0.00038 | 0x01 | |
| Async Serial | data | 0.01998 | 0.00038 | 0x04 |
Code of Conduct
Yes, I agree
9 Bit is not supported by the RP2040 MCU. At least an error should be raised. AFAIK, the only port that supports 9 bit is the STM32 port.
fantabulous. Do you have some recommended boards from mamazone for this? This is, I'm not kidding, the 4th board I try to get this very simple task done and some how I get one more face smash at the wall. I have a raspberry py zero lying around, do you think that might do it? I'll go see if I can find it in the mean time.
You can get a PyBoard V1.1 from the MicroPython store https://store.micropython.org/product/PYBv1.1 The Adafruit Feather STM32F405 Express should work as well https://www.adafruit.com/product/4382, sold on Amazon as well. If you search for PyBoard on Amazon, you get links to other STM32F405VG boards, which may work as well with the MicroPython firmware. Only that the board names for the Pins will not match.
Just bit-bang your serial, with PIO 25000bauds should be fairly doable.
this sort of worked:
from machine import Pin
import utime
class UART:
def __init__(self, tx_pin, baud_rate=2500):
self.tx_pin = Pin(tx_pin, Pin.OUT)
self.baud_rate = baud_rate
self.bit_period_us = int(1000000 / baud_rate)
def send_data(self, data):
for byte in data:
# Start bit
self.tx_pin.value(0)
utime.sleep_us(self.bit_period_us) # Start bit duration
for _ in range(8): # 8 data bits
self.tx_pin.value(byte & 1)
byte >>= 1
utime.sleep_us(self.bit_period_us) # Data bit duration
# Stop bit
self.tx_pin.value(1)
utime.sleep_us(self.bit_period_us) # Stop bit duration
# Ensure total byte transmission duration
utime.sleep_us(self.bit_period_us * 2 - 4) # Total byte transmission duration
# Example usage:
if __name__ == "__main__":
uart = UART(tx_pin=8, baud_rate=2500) # Initialize UART with TX pin connected to GPIO pin 8 and baud rate of 2500
data = bytearray([102, 104, 0x0F9]) # Data to be transmitted
uart.send_data(data) # Send data
the timing seems correct:
but it doesn't look exactly like the original signal from the projector i'm trying to reverse engineer:
Over in raspberry pi land, I got this to work but the timing seems really unstable, the waveform looks exactly as it should be but some times the timing is 400us, some times its 530us so you get framing errors:
import pigpio
import time
# Set up GPIO mode
pi = pigpio.pi() # Use default BCM numbering
# UART settings
BAUD_RATE = 2500 # Change baud rate to 2500 bps
BIT_PERIOD = 400000/ BAUD_RATE # Time for one bit in microseconds
START_BIT = 0
STOP_BIT = 1
pin = 14
# Data to send
DATA = [
(0x02, 1),
(0x04, 1),
(0xF9, 0)
]
# Function to send a single byte
def send_byte(byte, parity_bit, pin):
pi.set_mode(pin, pigpio.OUTPUT)
# Start bit
pi.write(pin, START_BIT)
time.sleep(BIT_PERIOD / 1000000) # Sleep for the bit period in microseconds
# Data bits
for i in range(8):
bit = (byte >> i) & 0x01
pi.write(pin, bit)
time.sleep(BIT_PERIOD / 1000000) # Sleep for the bit period in microseconds
# Parity bit
pi.write(pin, parity_bit)
time.sleep(BIT_PERIOD / 1000000) # Sleep for the bit period in microseconds
# Stop bit
pi.write(pin, STOP_BIT)
time.sleep(BIT_PERIOD / 1000000) # Sleep for the bit period in microseconds
try:
while True:
# Send data on pins 8, 10, and 27 (BCM numbering)
#for pin in [14,8, 10, 27]:
for byte, parity_bit in DATA:
send_byte(byte, parity_bit, pin)
#time.sleep(0.000681) # Sleep for the bit period in microseconds
#time.sleep(0.000450) # Sleep for the bit period in microseconds
time.sleep(0.371) # Sleep for the bit period in microseconds
except KeyboardInterrupt:
pass
# Clean up GPIO
pi.stop()
Using GPIO for UART has no reliable timing. The suggestion was to use the PIO state machine, which runs at exact timing. An example for TX is here: https://github.com/raspberrypi/pico-micropython-examples/blob/master/pio/pio_uart_tx.py You can easily expand that to any bit length. Data is supplied to the state machine by sm.put, which may be a word of up to 32 bit. A parity bit could be added to the bit pattern before pushing them to the state machine. Alternative PIO code for RX canbe found at https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_rx
Even if that is using C code, you can take the PIO script from the uart_rx.pio file.
Robert-hh, you're so cool! Look I got it to work! its a baby step, I gotta see if the projector actually gets fooled so simply, but man this gives me the tool to replicate the responses the lamp is supposed to give. Thanks for the tip!
from machine import Pin, Timer
from rp2 import PIO, StateMachine, asm_pio
UART_BAUD = 2500
PIN_BASE = 8
NUM_UARTS = 1
# Define the hexadecimal values to be sent
hex_values = [0x102, 0x104, 0x0F9]
# Assembly code for UART transmission
@asm_pio(sideset_init=PIO.OUT_HIGH, out_init=PIO.OUT_HIGH, out_shiftdir=PIO.SHIFT_RIGHT)
def uart_tx():
# Block with TX deasserted until data available
pull()
# Initialise bit counter, assert start bit for 8 cycles
set(x, 8) .side(0) [7]
# Shift out 9 data bits, 9 execution cycles per bit
label("bitloop")
out(pins, 1) [7]
jmp(x_dec, "bitloop")
# Assert stop bit for 8 cycles total (incl 1 for pull())
nop() .side(1) [6]
# Initialize StateMachine for UART
sm = StateMachine(
0, uart_tx, freq=9 * UART_BAUD, sideset_base=Pin(PIN_BASE), out_base=Pin(PIN_BASE)
)
sm.active(1)
# Function to print the hexadecimal values
def pio_uart_hex(sm, hex_values):
for hex_val in hex_values:
sm.put(hex_val)
# Insert a slight delay between bytes
for _ in range(1550): # Adjust this number for the desired delay
pass
# Print the hexadecimal values from the UART
pio_uart_hex(sm, hex_values)
# Function to repeat the transmission every 200ms
def repeat_send(timer):
pio_uart_hex(sm, hex_values)
# Initialize timer for repeating transmission
tim = Timer(-1)
tim.init(period=275, mode=Timer.PERIODIC, callback=repeat_send)
I had to play around with the delays 1550 gave me around the correct delay between bytes. 275 gave me around the correct delay between messages.
best of all, you plug it and it runs, no 2 minutes booting up delay from a lazy raspberry pi :)
Glad that it works. To be honest, the hint for using PIO came from @Blafy. I just pointed to suitable examples.