TT Lab
Get started
Learn Learning paths Courses

The Sensor Is Fine. The Board Cannot Understand It

Recovering the Baud Rate from Garbled Characters

Continue in TT Lab

Goal

Decode UART frames directly from a captured sample sequence, and from a capture whose transmit clock is off, re-measure the real bit time to revive the readable sentence. In the process you count in numbers what parity and framing errors each catch and what they miss.

Why it matters

UART has no clock line. Both sides must trust the same bit time, but the only way to check that trust is the start bit, once per frame. So when the clock is off, the frame falls apart from the back, and the symptom comes up only as "characters are garbled". This lab is practice in turning that report into a number, how many percent off it is. The key is that you fix the time axis rather than the values, and the basis for that time axis is already inside the capture file.

Steps

  1. Set up the time axis on the sample sequence — Turn sample numbers into times with the header's sample rate and leave a summary.
  2. Raising the threshold moves the edges — Implement rising_edges and measure the difference in detection times between the two thresholds.
  3. Write the frame decoder — Implement decode, which reads the samples (k + 0.5) bit times away from the start edge.
  4. Read the normal capture — Get the sentence with 0 framing errors.
  5. Thin out the samples to find where reading collapses — Decode again with spacings of 1, 4, 10, 25, and 50.
  6. The symptom when read at the nominal baud rate — Measure the framing errors and the shortest pulse of the misaligned capture.
  7. Re-measure the bit time to revive the sentence — Start from a coarse value and narrow it with longer baselines.
  8. Count what parity missed — Count how many frames were silently wrong.

Notes

Set up the time axis on the sample sequence

Create a working folder with mkdir -p /root/bus-uart, read /opt/fixtures/serialbus/uart/hello-9600.csv, and save to /root/bus-uart/survey.json sample_rate_hz (the value read from the header), sample_count (the number of sample rows), duration_us (number of samples x 1,000,000 / sample rate), min_v, max_v, and idle_level ("high" if the first sample is 1.65 V or more, otherwise "low").

The header is the lines that begin with #, and sample_rate_hz is there. The next line is the column names and below that are the samples. The time is not in the file and is calculated from the sample number and the sample rate.

Raising the threshold moves the edges

Implement rising_edges(volts, threshold_v) in /root/bus-uart/edges.py. It returns, as an ascending list, the i for which volts[i-1] is below the threshold and volts[i] is at or above it (i starts from 1). Then apply the thresholds 1.65 V and 2.90 V to hello-9600.csv and save to /root/bus-uart/threshold.json count_1v65, count_2v90, first_1v65, first_2v90, shift_samples (first_2v90 minus first_1v65), and shift_us.

If an edge has a finite slope, a higher threshold is crossed later. Check that the counts of the two thresholds are the same and only the times differ. The grader also tests this function with a short table unrelated to the capture file.

Write the frame decoder

Implement decode(volts, sample_rate_hz, baud, threshold_v=1.65, parity=None) in /root/bus-uart/uart.py. Find the edge i where the line falls from idle (high) to the start bit, and read the sample (k + 0.5) bit times away from i, truncated with int(), as bit k. The data is 8 bits, from the low-order bit. Return a list of frames, each as {"start": i, "value": byte, "framing_ok": whether the stop bit is 1, "parity_ok": whether the parity is right}, and look for the next edge starting right after the sample where the stop bit was read. parity is None or "even".

The number of samples per bit is sample_rate_hz / baud and is not an integer. If parity is None, parity_ok is always true. The grader first checks with hand-made test tables (a normal frame, a frame whose stop bit is 0, and a frame with only the parity bit flipped).

Read the normal capture

Decode hello-9600.csv at 9600 Bd and save to /root/bus-uart/message.json frame_count, framing_errors, bytes (a list of integers), and text (a string made by joining the bytes directly as characters).

It is normal if there are 0 framing errors. If not 0, recheck the number of samples per bit or the condition for finding the start edge.

Thin out the samples to find where reading collapses

Thin out the sample sequence of hello-9600.csv at spacings of 1, 4, 10, 25, and 50 (volts[::k]), set the sample rate to rate // k for each, and decode again at 9600 Bd. In /root/bus-uart/sampling.json, put cases, 5 of them exactly in this order, and in each entry put decimate, sample_rate_hz, samples_per_bit, and text_ok (whether the string is the same as the unthinned result). And write in first_failing_decimate the first spacing for which text_ok becomes false.

When the number of samples per bit approaches 1, the decoder does not raise an error but produces different characters. text_ok must be a boolean and not the string "true".

The symptom when read at the nominal baud rate

Decode /opt/fixtures/serialbus/uart/drift-8n1.csv at 9600 Bd and save to /root/bus-uart/drift.json frame_count, framing_errors, and bytes. In addition, gather the sample numbers where the level changes, find the minimum of the neighboring spacings and write it as shortest_pulse_us, write 1,000,000 / 9600 as nominal_bit_time_us, and write their ratio as ratio.

The shortest interval is one bit. How many percent that value differs from the nominal bit time is this capture's diagnosis. Check that the frame count is similar to the normal capture while only the framing errors increase.

Re-measure the bit time to revive the sentence

Re-measure the bit time from drift-8n1.csv. First take the minimum of the edge spacings as a coarse value, and then narrow it by lengthening the baseline — pick the farthest edge that falls within (current estimate x 16) samples of the first edge, divide that distance by the estimate and round to an integer, divide again by that integer, and repeat with the multiple grown by 4x each time up to the last edge. Save the result to /root/bus-uart/measured.json as bit_time_us, baud (1,000,000 divided by bit_time_us), the text decoded again at that baud rate, and framing_errors.

You must not round in one step with the whole length — if the coarse value's error times the number of bits exceeds 0.5 bit, it picks a wrong integer. The characters are readable even from the coarse value alone, but grading requires within 0.2 %, so you cannot skip the narrowing step.

Count what parity missed

Decode /opt/fixtures/serialbus/uart/drift-8e1.csv twice with even parity — once with the re-measured bit time (this one is the true value) and once at 9600 Bd. In /root/bus-uart/parity.json write frames, parity_errors, framing_errors, flagged (the number of frames caught by either one), wrong_bytes (the number of frames whose value differs from the true value), silently_wrong (the number of frames whose value differs yet that passed both parity and framing), and text_at_measured.

Parity looks only at the odd or even count of 1s. If an even number are flipped, it passes as is, so flagged can be smaller than wrong_bytes. Pair the two decode results in frame order and compare them.