Connection
| Setting | Value |
|---|---|
| Port (TX) | PA9 |
| Port (RX) | PA10 |
| Baud rate | 9600 |
| Format | 8N1 |
| Flow control | None |
Connect a USB-UART adapter: PA9 → RX, PA10 → TX, GND → GND.
Protocol
All messages are JSON objects terminated by \n. Max command length: 127 bytes.
Command: {"cmd": "GAS", "data": ""}
Response: {"cmd": "GAS", "data": "12.50"}
On power-on the firmware sends:
{"cmd": "FW", "data": "0.1.0"}
Command Reference
Readings
| Command | Response data | Notes |
|---|---|---|
{"cmd":"GAS","data":""} | "12.50" (ppm) | Stability mean if available, else raw code |
{"cmd":"TEMP","data":""} | "23.4" (°C) | SHT45 |
{"cmd":"HUM","data":""} | "52.1" (%RH) | SHT45 |
{"cmd":"FW","data":""} | "0.1.0" | Returns ACK cmd |
Status & Diagnostics
| Command | Response data | Notes |
|---|---|---|
{"cmd":"STATUS","data":""} | "2048:CALIBRATED" | raw_adc:state |
{"cmd":"STABILITY","data":""} | "1300:30:1" | mean_mv:sample_count:is_stable |
Calibration states: UNCALIBRATED, ZERO_CALIBRATED, CALIBRATED.
Calibration
| Command | Response data | Notes |
|---|---|---|
{"cmd":"ZERO","data":""} | "1010" (baseline ADC) | Auto-zero. Requires is_stable=1. |
{"cmd":"ZERO","data":"1010"} | "1010" | Manual zero at given ADC code. |
{"cmd":"SPAN","data":"25"} | "25.0:71%" | Auto-span. Requires ZERO first. |
{"cmd":"SPAN","data":"25:1420"} | "25.0:71%" | Manual span at given ADC code. |
SPAN response format: "<ppm>:<cha_percent>%"
Error Responses
All errors use {"cmd":"ERR","data":"<code>"}:
| Data | Cause |
|---|---|
NOT_STABLE | ZERO attempted before sensor is stable |
ZERO_FIRST | SPAN attempted without prior zero |
INVALID_PPM | PPM value ≤ 0 in SPAN command |
JSON_PARSE | Malformed JSON received |
UTF8 | Non-UTF8 bytes received |
UNKNOWN_CMD | Unrecognised cmd field |
Example Session
← {"cmd":"FW","data":"0.1.0"}
→ {"cmd":"STATUS","data":""}
← {"cmd":"STATUS","data":"1253:UNCALIBRATED"}
→ {"cmd":"STABILITY","data":""}
← {"cmd":"STABILITY","data":"1253:12:0"}
(wait ~20 more seconds)
→ {"cmd":"STABILITY","data":""}
← {"cmd":"STABILITY","data":"1250:30:1"}
→ {"cmd":"ZERO","data":""}
← {"cmd":"ZERO","data":"1250"}
(apply 25 ppm cylinder, wait T90 ~35 s)
→ {"cmd":"SPAN","data":"25"}
← {"cmd":"SPAN","data":"25.0:71%"}
→ {"cmd":"GAS","data":""}
← {"cmd":"GAS","data":"24.87"}
→ {"cmd":"TEMP","data":""}
← {"cmd":"TEMP","data":"23.6"}
Testing with Python
import serial, json, time
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
time.sleep(1.5) # wait for MCU boot
def cmd(command, data=""):
msg = json.dumps({"cmd": command, "data": data}) + "\n"
ser.reset_input_buffer()
ser.write(msg.encode())
ser.flush()
raw = ser.readline()
return json.loads(raw.decode().strip()) if raw else None
print(cmd("FW")) # {'cmd': 'ACK', 'data': '0.1.0'}
print(cmd("STABILITY")) # {'cmd': 'STABILITY', 'data': '1250:30:1'}
print(cmd("GAS")) # {'cmd': 'GAS', 'data': '24.87'}
print(cmd("ZERO")) # {'cmd': 'ZERO', 'data': '1250'}
print(cmd("SPAN", "25")) # {'cmd': 'SPAN', 'data': '25.0:71%'}