runpatrick/sa-sim-pcb
This code defines PCB footprints for various electronic modules, including BG95 cellular modem, BNO086 sensor, BQ25180 battery charger, and BQ27220 power management IC, detailing their physical pad layouts and key external features.
- Version
- 1.0.6
- License
- unset
- Stars
- 0
firmware/smoketest.c
/*
* SA-SIM v1 — Production smoketest (Zephyr / nRF Connect SDK)
* ============================================================
*
* Verifies all critical buses and devices on the assembled board:
* 1. I²C0 bus (SDA=P0.28, SCL=P0.27)
* - 0x6A BQ25180 charger
* - 0x55 BQ27220 fuel gauge
* - 0x6E BQ51013 Qi receiver
* - 0x4A BNO086 IMU
* 2. UART1 to BG95 modem (TX=P0.31, RX=P0.30, 115200 8N1)
* - sends "AT\r" and waits ≤5s for "OK"
* 3. Status LED on P0.13 (PASS = solid on, FAIL = blink error code)
*
* Build:
* west build -b nrf52840dk/nrf52840 -- -DBOARD_ROOT=$PWD
*
* Run on Renode (HIL):
* renode firmware/sa-sim.repl
*
* Error codes (LED blink count):
* 1 = I²C bus init failed
* 2 = BQ25180 charger missing
* 3 = BQ27220 fuel gauge missing
* 4 = BQ51013 Qi receiver missing
* 5 = BNO086 IMU missing
* 6 = UART1 init failed
* 7 = BG95 did not respond OK to AT
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>
#include <string.h>
/* ─────────────── I²C devices on bus 0 ──────────────────────────────────────── */
#define I2C0_NODE DT_NODELABEL(i2c0)
#define ADDR_BQ25180 0x6A
#define ADDR_BQ27220 0x55
#define ADDR_BQ51013 0x6E
#define ADDR_BNO086 0x4A
/* ─────────────── UART1 to BG95 ─────────────────────────────────────────────── */
#define UART1_NODE DT_NODELABEL(uart1)
#define BG95_AT_TIMEOUT_MS 5000
/* ─────────────── Status LED on P0.13 ────────────────────────────────────────── */
#define LED_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec status_led = GPIO_DT_SPEC_GET(LED_NODE, gpios);
/* ───────── Probe an I²C address with a 0-byte write; ACK = present ─────────── */
static int probe_i2c(const struct device *dev, uint8_t addr, const char *name)
{
uint8_t dummy = 0;
int rc = i2c_write(dev, &dummy, 0, addr);
if (rc == 0) {
printk("I2C device 0x%02X (%s) found\n", addr, name);
} else {
printk("I2C device 0x%02X (%s) MISSING (err=%d)\n", addr, name, rc);
}
return rc;
}
/* ───────── Send "AT\r" and wait up to BG95_AT_TIMEOUT_MS for "OK" ──────────── */
static int probe_bg95(const struct device *uart)
{
const char at_cmd[] = "AT\r";
char rxbuf[64] = {0};
size_t rxlen = 0;
int64_t deadline = k_uptime_get() + BG95_AT_TIMEOUT_MS;
/* Drain any boot chatter */
uint8_t junk;
while (uart_poll_in(uart, &junk) == 0) { /* discard */ }
/* Transmit AT */
for (size_t i = 0; i < sizeof(at_cmd) - 1; i++) {
uart_poll_out(uart, at_cmd[i]);
}
/* Wait for OK */
while (k_uptime_get() < deadline && rxlen < sizeof(rxbuf) - 1) {
uint8_t ch;
if (uart_poll_in(uart, &ch) == 0) {
rxbuf[rxlen++] = (char)ch;
if (strstr(rxbuf, "OK")) {
printk("BG95 AT/OK round-trip: PASS (%zu bytes)\n", rxlen);
return 0;
}
} else {
k_msleep(10);
}
}
printk("BG95 AT/OK round-trip: TIMEOUT (got %zu bytes: '%.*s')\n",
rxlen, (int)rxlen, rxbuf);
return -ETIMEDOUT;
}
/* ───────── LED helper: blink error code N times, pause 1s, repeat ──────────── */
static void blink_error(int code)
{
while (1) {
for (int i = 0; i < code; i++) {
gpio_pin_set_dt(&status_led, 1);
k_msleep(200);
gpio_pin_set_dt(&status_led, 0);
k_msleep(200);
}
k_msleep(1000);
}
}
int main(void)
{
printk("\n========================================\n");
printk("SA-SIM v1 production smoketest\n");
printk("========================================\n");
/* Init LED */
if (!gpio_is_ready_dt(&status_led)) {
printk("FATAL: status LED not ready\n");
return -ENODEV;
}
gpio_pin_configure_dt(&status_led, GPIO_OUTPUT_INACTIVE);
/* Init I²C0 */
const struct device *i2c0 = DEVICE_DT_GET(I2C0_NODE);
if (!device_is_ready(i2c0)) {
printk("FATAL: I2C0 not ready\n");
blink_error(1);
}
/* Probe each I²C device. Do not blink_error individually — collect all
failures, report, then blink the first failure code at end. */
int fail_code = 0;
if (probe_i2c(i2c0, ADDR_BQ25180, "BQ25180 charger") != 0 && !fail_code) fail_code = 2;
if (probe_i2c(i2c0, ADDR_BQ27220, "BQ27220 fuel gauge") != 0 && !fail_code) fail_code = 3;
if (probe_i2c(i2c0, ADDR_BQ51013, "BQ51013 Qi receiver") != 0 && !fail_code) fail_code = 4;
if (probe_i2c(i2c0, ADDR_BNO086, "BNO086 IMU") != 0 && !fail_code) fail_code = 5;
/* Init UART1 to BG95 */
const struct device *uart1 = DEVICE_DT_GET(UART1_NODE);
if (!device_is_ready(uart1)) {
printk("FATAL: UART1 not ready\n");
if (!fail_code) fail_code = 6;
} else if (probe_bg95(uart1) != 0 && !fail_code) {
fail_code = 7;
}
/* Verdict */
printk("========================================\n");
if (fail_code == 0) {
printk("RESULT: ALL PASS\n");
printk("========================================\n");
gpio_pin_set_dt(&status_led, 1); /* LED solid on = PASS */
while (1) k_msleep(1000);
} else {
printk("RESULT: FAIL — error code %d\n", fail_code);
printk("========================================\n");
blink_error(fail_code);
}
return 0; /* unreachable */
}