Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions radio/src/boards/generic_stm32/i2c_bus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,29 @@
#include "hal/i2c_driver.h"
#include "hal.h"

#if defined(FREE_RTOS)
#include "os/task.h"
#endif

#define I2C_DEFAULT_TIMEOUT 10
#define I2C_DEFAULT_RETRIES 2

#define MAX_I2C_BUSES 2

#if defined(FREE_RTOS)
static mutex_handle_t _i2c_mutex[MAX_I2C_BUSES];
static bool _i2c_mutex_initialized[MAX_I2C_BUSES] = {};

static void i2c_ensure_mutex(uint8_t bus)
{
if (bus < MAX_I2C_BUSES && !_i2c_mutex_initialized[bus] &&
scheduler_is_running()) {
mutex_create(&_i2c_mutex[bus]);
_i2c_mutex_initialized[bus] = true;
}
}
#endif

#if defined(I2C_B1)
static const stm32_i2c_hw_def_t _i2c1 = {
.I2Cx = I2C_B1,
Expand Down Expand Up @@ -92,6 +112,33 @@ int i2c_deinit(etx_i2c_bus_t bus)
return -1;
}

void i2c_lock(etx_i2c_bus_t bus)
{
#if defined(FREE_RTOS)
i2c_ensure_mutex(bus);
if (scheduler_is_running())
mutex_lock(&_i2c_mutex[bus]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#endif
}

bool i2c_trylock(etx_i2c_bus_t bus)
{
#if defined(FREE_RTOS)
i2c_ensure_mutex(bus);
if (scheduler_is_running())
return mutex_trylock(&_i2c_mutex[bus]);
#endif
return true;
}

void i2c_unlock(etx_i2c_bus_t bus)
{
#if defined(FREE_RTOS)
if (bus < MAX_I2C_BUSES && scheduler_is_running())
mutex_unlock(&_i2c_mutex[bus]);
#endif
}

int i2c_dev_ready(etx_i2c_bus_t bus, uint16_t addr)
{
return stm32_i2c_is_dev_ready(bus, addr, I2C_DEFAULT_RETRIES,
Expand Down
3 changes: 3 additions & 0 deletions radio/src/boards/jumper-h750/touch_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ TouchState touchPanelRead()
{
if (!touchEventOccured) return internalTouchState;

i2c_lock(TOUCH_I2C_BUS);

touchEventOccured = false;

tmr10ms_t now = timersGetMsTick();
Expand Down Expand Up @@ -285,6 +287,7 @@ TouchState touchPanelRead()
if (internalTouchState.event == TE_UP ||
internalTouchState.event == TE_SLIDE_END)
internalTouchState.event = TE_NONE;
i2c_unlock(TOUCH_I2C_BUS);
return ret;
}

Expand Down
5 changes: 5 additions & 0 deletions radio/src/boards/rm-h750/bsp_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "stm32_rgbleds.h"
#include "stm32_switch_driver.h"
#include "stm32_i2c_driver.h"
#include "hal/i2c_driver.h"
#include "hal/switch_driver.h"
#include "drivers/pca95xx.h"
#include "timers_driver.h"
Expand Down Expand Up @@ -112,8 +113,12 @@ static void _poll_switches(void *param1, uint32_t trigger_source)
// Suspend hardware reads when required
if (suspendI2CTasks) return;

if (!i2c_trylock(IO_EXPANDER_I2C_BUS)) return;

_read_io_expander(&_io_switches);
_read_io_expander(&_io_fs_switches);

i2c_unlock(IO_EXPANDER_I2C_BUS);
}

static void _io_int_handler()
Expand Down
5 changes: 5 additions & 0 deletions radio/src/boards/rm-h750/tp_gt911.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ struct TouchState touchPanelRead()

if (!touchEventOccured) return internalTouchState;

i2c_lock(TOUCH_I2C_BUS);

touchEventOccured = false;

uint32_t startReadStatus = timersGetMsTick();
Expand All @@ -241,6 +243,7 @@ struct TouchState touchPanelRead()
touchGT911hiccups++;
TRACE("GT911 I2C read XY error");
if (!I2C_ReInit()) TRACE("I2C B1 ReInit failed");
i2c_unlock(TOUCH_I2C_BUS);
return internalTouchState;
}

Expand All @@ -266,6 +269,7 @@ struct TouchState touchPanelRead()
touchGT911hiccups++;
TRACE("GT911 I2C data read error");
if (!I2C_ReInit()) TRACE("I2C B1 ReInit failed");
i2c_unlock(TOUCH_I2C_BUS);
return internalTouchState;
}

Expand Down Expand Up @@ -316,6 +320,7 @@ struct TouchState touchPanelRead()
}

TRACE("touch event = %s", event2str(internalTouchState.event));
i2c_unlock(TOUCH_I2C_BUS);
return internalTouchState;
}

Expand Down
36 changes: 36 additions & 0 deletions radio/src/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1845,6 +1845,41 @@ int cliResetGT911(const char** argv)
}
#endif

#include "hal/i2c_driver.h"
#include "stm32_i2c_driver.h"

int cliI2C(const char** argv)
{
if (!argv[1]) {
cliSerialPrint("Usage: i2c scan <bus>");
return 0;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

if (!strcmp(argv[1], "scan")) {
int bus = 0;
if (toInt(argv, 2, &bus) < 0) {
cliSerialPrint("Usage: i2c scan <bus>");
return -1;
}

cliSerialPrint("Scanning I2C bus %d...", bus);
i2c_lock(bus);
int found = 0;
for (int addr = 0x08; addr < 0x78; addr++) {
if (i2c_dev_ready(bus, addr) >= 0) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
cliSerialPrint(" 0x%02X: ACK", addr);
found++;
}
}
i2c_unlock(bus);
cliSerialPrint("Found %d device(s)", found);
return 0;
}

cliSerialPrint("Unknown subcommand: %s", argv[1]);
return -1;
}

const CliCommand cliCommands[] = {
{ "beep", cliBeep, "[<frequency>] [<duration>]" },
{ "ls", cliLs, "<directory>" },
Expand All @@ -1871,6 +1906,7 @@ const CliCommand cliCommands[] = {
{ "repeat", cliRepeat, "<interval in ms> <command>" },
{ "testfatfs", cliTestFatFsSD, "" },
#endif
{ "i2c", cliI2C, "lockup <bus> <addr> | scan <bus> | recover <bus>" },
{ "help", cliHelp, "[<command>]" },
#if defined(JITTER_MEASURE)
{ "jitter", cliShowJitter, "" },
Expand Down
185 changes: 185 additions & 0 deletions radio/src/drivers/csd203.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

#include "csd203.h"

#include "delays_driver.h"
#include "hal/i2c_driver.h"
#include "os/timer.h"

// CSD203 register map
#define CSD203_REG_CONFIG 0x00
#define CSD203_REG_SHUNT_V 0x01
#define CSD203_REG_BUS_V 0x02
#define CSD203_REG_POWER 0x03
#define CSD203_REG_CURRENT 0x04
#define CSD203_REG_CALIBRATION 0x05
#define CSD203_REG_MFR_ID 0xFE

#define CSD203_MFR_ID_EXPECTED 0x4153

// Calibration parameter: gain * 10000K
#define CSD203_CAL_PARAM 51200

// Config register fields (matching CSD203 datasheet)
#define CFG_RST (1 << 15)
#define CFG_AVG_16 (2 << 9)
#define CFG_VBUS_CT_1MS (4 << 5)
#define CFG_VSHT_CT_1MS (4 << 2)
#define CFG_MODE_CONT 7

extern bool suspendI2CTasks;

static int csd203_read_reg(csd203_t* dev, uint8_t reg, uint16_t* value)
{
uint8_t buf[2];
if (i2c_read(dev->bus, dev->addr, reg, 1, buf, 2) < 0)
return -1;
*value = (buf[0] << 8) | buf[1];
return 0;
}

static int csd203_write_reg(csd203_t* dev, uint8_t reg, uint16_t value)
{
uint8_t buf[2] = {(uint8_t)(value >> 8), (uint8_t)(value & 0xFF)};
return i2c_write(dev->bus, dev->addr, reg, 1, buf, 2);
}

int csd203_init(csd203_t* dev, etx_i2c_bus_t bus, uint16_t addr,
uint16_t rshunt, uint16_t current_lsb)
{
dev->bus = bus;
dev->addr = addr;
dev->initialized = false;

uint16_t config = CFG_RST | CFG_AVG_16 | CFG_VBUS_CT_1MS |
CFG_VSHT_CT_1MS | CFG_MODE_CONT;
if (csd203_write_reg(dev, CSD203_REG_CONFIG, config) < 0)
return -1;

uint16_t cal = CSD203_CAL_PARAM / (current_lsb * rshunt);
if (csd203_write_reg(dev, CSD203_REG_CALIBRATION, cal) < 0)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return -1;

delay_ms(1);

uint16_t mfr_id;
if (csd203_read_reg(dev, CSD203_REG_MFR_ID, &mfr_id) < 0)
return -1;

if (mfr_id != CSD203_MFR_ID_EXPECTED)
return -1;

// Dummy current read to clear conversion-ready flag
uint16_t dummy;
csd203_read_reg(dev, CSD203_REG_CURRENT, &dummy);

dev->initialized = true;
return 0;
}

uint16_t csd203_read_voltage(csd203_t* dev)
{
uint16_t val = 0;
csd203_read_reg(dev, CSD203_REG_BUS_V, &val);
return val;
}

uint16_t csd203_read_current(csd203_t* dev)
{
uint16_t val = 0;
csd203_read_reg(dev, CSD203_REG_CURRENT, &val);
return val;
}

// Board-level: 3 CSD203 sensors (main, internal, external)
// Addresses: A1=VS/A0=GND (0x44), A1=VS/A0=VS (0x45), A1=GND/A0=VS (0x41)
#define CSD203_ADDR_MAIN 0x44
#define CSD203_ADDR_INTERNAL 0x45
#define CSD203_ADDR_EXTERNAL 0x41

// Shunt resistor 10 mOhm, current LSB 1 mA
#define CSD203_RSHUNT 10
#define CSD203_CURRENT_LSB 10

static csd203_t csd203_main;
static csd203_t csd203_internal;
static csd203_t csd203_external;

static etx_i2c_bus_t csd203_bus;
static uint16_t csd203_ext_vbus;
static timer_handle_t csd203_timer = TIMER_INITIALIZER;

#define CSD203_POLL_PERIOD_MS 10

static void csd203TimerCb(timer_handle_t* timer)
{
(void)timer;
static uint16_t step = 0;

if (suspendI2CTasks) return;
if (!i2c_trylock(csd203_bus)) return;

if (step == 0 && csd203_main.initialized) {
csd203_read_current(&csd203_main);
csd203_read_voltage(&csd203_main);
} else if (step == 1 && csd203_internal.initialized) {
csd203_read_current(&csd203_internal);
csd203_read_voltage(&csd203_internal);
} else if (step == 2 && csd203_external.initialized) {
csd203_read_current(&csd203_external);
csd203_ext_vbus = (uint16_t)(csd203_read_voltage(&csd203_external) * 1.25f);
}

i2c_unlock(csd203_bus);
if (++step >= 3) step = 0;
}

void csd203_start(etx_i2c_bus_t bus)
{
csd203_bus = bus;

if (i2c_init(bus) < 0)
return;

csd203_init(&csd203_main, bus, CSD203_ADDR_MAIN,
CSD203_RSHUNT, CSD203_CURRENT_LSB);

delay_ms(5);
csd203_init(&csd203_internal, bus, CSD203_ADDR_INTERNAL,
CSD203_RSHUNT, CSD203_CURRENT_LSB);

delay_ms(5);
csd203_init(&csd203_external, bus, CSD203_ADDR_EXTERNAL,
CSD203_RSHUNT, CSD203_CURRENT_LSB);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (csd203_main.initialized || csd203_internal.initialized ||
csd203_external.initialized) {
timer_create(&csd203_timer, csd203TimerCb, "csd203",
CSD203_POLL_PERIOD_MS, true);
timer_start(&csd203_timer);
}
}

uint16_t getBatteryVoltage()
{
return csd203_ext_vbus / 10;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@

#pragma once

void IICcsd203init(void);
void initCSD203(void);
void readCSD203(void);
#include "hal/i2c_driver.h"

typedef struct {
etx_i2c_bus_t bus;
uint16_t addr;
bool initialized;
} csd203_t;

int csd203_init(csd203_t* dev, etx_i2c_bus_t bus, uint16_t addr,
uint16_t rshunt, uint16_t current_lsb);

uint16_t csd203_read_voltage(csd203_t* dev);
uint16_t csd203_read_current(csd203_t* dev);
Comment on lines +35 to +36

@coderabbitai coderabbitai Bot Jun 28, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return read status instead of encoding failures as zero.

The implementation returns 0 on failed I2C reads, so callers cannot distinguish an error from a real zero reading. Make these APIs return a status and write the sample through an output parameter.

Proposed API shape
-uint16_t csd203_read_voltage(csd203_t* dev);
-uint16_t csd203_read_current(csd203_t* dev);
+int csd203_read_voltage(csd203_t* dev, uint16_t* value);
+int csd203_read_current(csd203_t* dev, uint16_t* value);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uint16_t csd203_read_voltage(csd203_t* dev);
uint16_t csd203_read_current(csd203_t* dev);
int csd203_read_voltage(csd203_t* dev, uint16_t* value);
int csd203_read_current(csd203_t* dev, uint16_t* value);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@radio/src/drivers/csd203.h` around lines 35 - 36, The csd203_read_voltage and
csd203_read_current APIs currently return the sampled value directly, which
hides I2C failures when they fall back to zero. Update the csd203_t driver
interface to return a status code and move the measured voltage/current into an
output parameter, then adjust the implementations and all call sites accordingly
so callers can distinguish read errors from legitimate zero readings.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged, deferring: the battery path consumes a cached value and tolerates a transient 0. Converting these to status-returning out-param APIs is a broader change, better done as a follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raphaelcoeffic, makes sense — thanks for the clarification. Given the cached battery path, I agree this is reasonable to defer rather than widen the API in this PR.

If you'd like, I can open a follow-up issue to track the status-returning API cleanup separately.


void csd203_start(etx_i2c_bus_t bus);
Loading