# UARTPeriodicTx Hardware Periodic Transmission

`UARTPeriodicTx` uses a hardware timer to periodically send prepared UART frames. It suits periodic status frame and control frame transmission tasks: Python code is responsible for preparing and updating data, while the hardware timer is responsible for triggering the native send path at the configured period.

This tutorial corresponds to the source example `resources/examples/03-Machine/uart_periodic_tx.py`. The example sends 8-byte frames at a 50 ms period and uses UART3 local loopback to verify send/receive results.

## Build Configuration

This module is not compiled into the firmware by default. Before building, execute:

```bash
# When using k230-builder
k230 make menuconfig

# When building natively
make menuconfig
```

Enable `Enable UART periodic TX module` in the following menu, then rebuild and flash the firmware:

```text
CanMV Micropython Components Configurations
    Enable UART periodic TX module
```

## Wiring

The example uses UART3:

```text
IO50 (UART3_TXD) ---- IO51 (UART3_RXD)
```

Power off the board before completing the loopback wiring, and confirm that the IO50 and IO51 levels and pin multiplexing of this board match the example. UART levels must match; do not connect 5 V UART signals. See [UART Levels and Adapter Module Safety](./uart.md) for detailed requirements. You may also connect a logic analyzer to IO50 to measure the transmission period.

```{warning}
UART3, IO50, and IO51 in the example are the board-level mapping for the current example. In real applications, select the UART and FPIOA pins based on the development board schematic, REPL configuration, and other peripherals already in use.
```

## How the Example Works

The example first maps IO50/IO51 to UART3 TX/RX:

```python
from machine import FPIOA, UART

fpioa = FPIOA()
fpioa.set_function(50, FPIOA.UART3_TXD)
fpioa.set_function(51, FPIOA.UART3_RXD)
```

Then create and start the transmitter:

```python
from uart_periodic_tx import UARTPeriodicTx

transmitter = UARTPeriodicTx(
    UART.UART3,
    0,
    50,
    max_len=8,
    baudrate=115200,
    bits=UART.EIGHTBITS,
    parity=UART.PARITY_NONE,
    stop=UART.STOPBITS_ONE,
    repeat_last=True,
)

transmitter.update(b"\xA5\x5A\x00\xFF\x3C\xC3\xFF\x0D")
transmitter.start()
```

The example uses `machine.UART` only to read the data looped back to UART3 RX in order to check the frame content. In business code, the running `UARTPeriodicTx` already owns the send path of the same UART, so do not call `machine.UART.write()`, `init()`, or `deinit()` on it.

`repeat_last` controls the behavior when no new data has been updated:

- `repeat_last=True` (default): Each hardware timer trigger sends the most recent frame passed to `update()`.
- `repeat_last=False`: Each non-empty frame from `update()` is sent only once; triggers without new data do not send and are counted into `skipped`.

The example calls `update()` every `UPDATE_PERIOD_MS`, writing the sequence number into the transmit frame. The frame format is as follows:

```text
A5 5A <sequence> <sequence ^ FF> 3C C3 <checksum> 0D
```

For example, when the sequence number is `0x0F`, the frame content is:

```text
A5 5A 0F F0 3C C3 FF 0D
```

When `repeat_last=True`, the hardware timer will repeatedly send the most recently published frame and will not generate sequence numbers on its own. Therefore, seeing the same frame consecutively on the logic analyzer means that no new `update()` took effect during the capture period, or that a new frame was published after the current hardware trigger. When `repeat_last=False`, a sequence-numbered frame will appear only once after a new `update()`. Capture a sufficiently long time window, and confirm that the script is still running, that `UPDATE_PERIOD_MS` has elapsed, and that `update()` did not raise an error.

## Running and Results

Copy the example to the device and run `uart_periodic_tx.py`. The default configuration is:

| Item | Value |
| --- | --- |
| UART | UART3 |
| Baud rate | 115200, 8N1 |
| Hardware timer | 0 |
| Send period | 50 ms |
| Test duration | 5000 ms |
| Frame length | 8 bytes |

The output gives send, receive, and error statistics, for example:

```text
UARTPeriodicTx loopback test: period=50 ms, duration=5000 ms, repeat_last=True
frames: sent=100, received=100, invalid=0
tx stats: short_write=0, errors=0, skipped=0
PASS
```

Receive timestamps are used only for software loopback sanity checks. To measure the real on-wire period and jitter, use a logic analyzer on IO50 and decode at `115200`, 8 data bits, no parity, 1 stop bit.

## Frequently Asked Questions

| Symptom | Troubleshooting |
| --- | --- |
| `ImportError: no module named 'uart_periodic_tx'` | Enable `Enable UART periodic TX module` in `menuconfig` and rebuild the firmware. |
| `OSError: [Errno 16] EBUSY` | Check whether `timer_id` is already used by `machine.Timer` or another `UARTPeriodicTx`; multiple periodic transmitters on the same UART must also share the same configuration. |
| Logic analyzer always shows the same sequence number | Confirm the capture duration covers multiple `UPDATE_PERIOD_MS`, and check whether the application keeps calling `update()`. The timer repeating the last frame between updates is expected behavior. |
| No loopback data received or `FAIL` is shown | Check the IO50-to-IO51 connection, FPIOA mapping, whether UART3 is occupied by another function, and whether both ends use 115200 8N1. |
| `short_write` or `errors` is non-zero | Reduce the frame length or increase the baud rate, lengthen the send period, and ensure that no overlapping send tasks share the same UART. |
| `skipped` is non-zero | In `repeat_last=False` mode, `skipped` when no data is updated is expected behavior; otherwise, check UART busy state, timer resources, and frame length. |

Release resources when finished:

```python
try:
    transmitter.start()
    # In business code, call transmitter.update(...) as needed
finally:
    transmitter.deinit()
```

See the [`uart_periodic_tx` Module API Manual](../../api/machine/k230_canmv_uart_periodic_tx_api_manual.md) for more API parameters and resource constraints.
