The Sensor Is Fine. The Board Cannot Understand It
Recovering the Baud Rate from Garbled Characters
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
- Set up the time axis on the sample sequence — Turn sample numbers into times with the header's sample rate and leave a summary.
- Raising the threshold moves the edges — Implement rising_edges and measure the difference in detection times between the two thresholds.
- Write the frame decoder — Implement decode, which reads the samples (k + 0.5) bit times away from the start edge.
- Read the normal capture — Get the sentence with 0 framing errors.
- Thin out the samples to find where reading collapses — Decode again with spacings of 1, 4, 10, 25, and 50.
- The symptom when read at the nominal baud rate — Measure the framing errors and the shortest pulse of the misaligned capture.
- Re-measure the bit time to revive the sentence — Start from a coarse value and narrow it with longer baselines.
- Count what parity missed — Count how many frames were silently wrong.
Notes
- The materials are three files under
/opt/fixtures/serialbus/uart/:hello-9600.csv(normal),drift-8n1.csv, anddrift-8e1.csv. They are read-only, so do not modify them. The format description is in/opt/fixtures/serialbus/README.mdin the folder above. - Put the function that reads the capture file in one file such as
capture.pyand do not rewrite it at every step. The answer key does it that way too. - The
ncolumn of the file is the sample number. The time isn / sample_rate_hzseconds, and there is no time column in the file. - Common mistake one: collecting the data bits from the high-order bit. UART goes from the low-order bit.
- Common mistake two: rounding the number of samples per bit to an integer. If you capture 9600 Bd at 500 kHz it is 52.083, and if you round, it drifts by the end of the frame.
- No extra installation or internet is needed. You use only python3.
- The expected time is 70 minutes, so extend with +time before the default 60 minutes end (maximum 180 minutes). When the session ends,
/rootdisappears, so keep separately what you want to keep.
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.