AP_HAL_ChibiOS: support I2Cv4 single DMA channel

The ChibiOS I2Cv4 LLD (used on STM32G0/G4/C0/U0/U3/H5/L4+) uses a
single DMA channel per I2C peripheral, shared between TX and RX,
configured via STM32_I2C_I2Cx_DMA_CHANNEL. The previous code
emitted the separate STM32_I2C_I2Cx_RX/TX_DMA_STREAM defines used
by I2Cv2/I2Cv3, which no longer satisfy the I2Cv4 driver.

Detect I2Cv4 MCUs from CHIBIOS_PLATFORM_MK, allocate a single DMA
channel per I2C peripheral in dma_resolver.py (no _RX/_TX split),
and generate HAL_I2Cn_CONFIG entries that feed the single channel
into the shared DMA slot with SHARED_DMA_NONE as the partner.

Also replace the unconditional STM32_I2C_USE_I2Cn TRUE lines in
stm32g4_mcuconf.h with #ifndef-guarded defaults to FALSE, so
boards only enable the I2C peripherals their hwdef actually uses.
Without this, the I2Cv4 driver demanded DMA channels for every
I2C peripheral on every G4 board regardless of pin usage.
This commit is contained in:
Andy Piper
2026-05-22 16:46:21 +10:00
committed by Peter Barker
parent 4c5c889b3a
commit abffee1520
3 changed files with 68 additions and 10 deletions
@@ -232,10 +232,18 @@
/*
* I2C driver system settings.
*/
#define STM32_I2C_USE_I2C1 TRUE
#define STM32_I2C_USE_I2C2 TRUE
#define STM32_I2C_USE_I2C3 TRUE
#define STM32_I2C_USE_I2C4 TRUE
#ifndef STM32_I2C_USE_I2C1
#define STM32_I2C_USE_I2C1 FALSE
#endif
#ifndef STM32_I2C_USE_I2C2
#define STM32_I2C_USE_I2C2 FALSE
#endif
#ifndef STM32_I2C_USE_I2C3
#define STM32_I2C_USE_I2C3 FALSE
#endif
#ifndef STM32_I2C_USE_I2C4
#define STM32_I2C_USE_I2C4 FALSE
#endif
#define STM32_I2C_BUSY_TIMEOUT 50
#define STM32_IRQ_I2C1_PRIORITY 5
#define STM32_IRQ_I2C2_PRIORITY 5
@@ -665,6 +665,24 @@ class ChibiOSHWDef(hwdef.HWDef):
return None
return lib.mcu[name]
def mcu_uses_I2Cv4(self):
'''return True if this MCU uses the ChibiOS I2Cv4 LLD driver, which
uses a single DMA channel per I2C peripheral (shared between TX and
RX) rather than separate RX and TX streams'''
lib = self.get_mcu_lib(self.mcu_type)
platform_mk = getattr(lib, 'build', {}).get('CHIBIOS_PLATFORM_MK', '')
# ChibiOS platform directories whose platform.mk pulls in LLD/I2Cv4
i2cv4_platforms = (
'STM32G0xx/',
'STM32G4xx/',
'STM32C0xx/',
'STM32U0xx/',
'STM32U3xx/',
'STM32H5xx/',
'STM32L4xx+/',
)
return any(p in platform_mk for p in i2cv4_platforms)
def get_ram_reserve_start(self):
'''get amount of memory to reserve for bootloader comms and the address if non-zero'''
ram_reserve_start = self.get_config('RAM_RESERVE_START', default=0, type=int)
@@ -1935,6 +1953,7 @@ INCLUDE common.ld
devlist = []
# write out config structures
uses_i2cv4 = self.mcu_uses_I2Cv4()
for dev in i2c_list:
if not dev.startswith('I2C') or dev[3] not in "1234":
self.error("Bad I2C_ORDER element %s" % dev)
@@ -1942,14 +1961,26 @@ INCLUDE common.ld
devlist.append('HAL_I2C%u_CONFIG' % n)
sda_line = self.make_line('I2C%u_SDA' % n)
scl_line = self.make_line('I2C%u_SCL' % n)
f.write('''
if uses_i2cv4:
# I2Cv4 (STM32G0/G4/C0/U0/U3/H5/L4+) uses a single DMA
# channel for both TX and RX on each I2C peripheral
f.write('''
#if defined(STM32_I2C_I2C%u_DMA_CHANNEL)
#define HAL_I2C%u_CONFIG { &I2CD%u, %u, STM32_I2C_I2C%u_DMA_CHANNEL, SHARED_DMA_NONE, %s, %s }
#else
#define HAL_I2C%u_CONFIG { &I2CD%u, %u, SHARED_DMA_NONE, SHARED_DMA_NONE, %s, %s }
#endif
'''
% (n, n, n, n, n, scl_line, sda_line, n, n, n, scl_line, sda_line))
else:
f.write('''
#if defined(STM32_I2C_I2C%u_RX_DMA_STREAM) && defined(STM32_I2C_I2C%u_TX_DMA_STREAM)
#define HAL_I2C%u_CONFIG { &I2CD%u, %u, STM32_I2C_I2C%u_RX_DMA_STREAM, STM32_I2C_I2C%u_TX_DMA_STREAM, %s, %s }
#else
#define HAL_I2C%u_CONFIG { &I2CD%u, %u, SHARED_DMA_NONE, SHARED_DMA_NONE, %s, %s }
#endif
'''
% (n, n, n, n, n, n, n, scl_line, sda_line, n, n, n, scl_line, sda_line))
% (n, n, n, n, n, n, n, scl_line, sda_line, n, n, n, scl_line, sda_line))
f.write('\n')
self.write_device_table(f, "i2c devices", "HAL_I2C_DEVICE_LIST", devlist)
@@ -2648,6 +2679,13 @@ Please run: Tools/scripts/build_bootloaders.py %s
continue
for prefix in prefixes:
if type.startswith(prefix):
if prefix == 'I2C' and self.mcu_uses_I2Cv4():
# I2Cv4 uses a single DMA channel per I2C peripheral
# shared between TX and RX, so request DMA using the
# plain peripheral name (no _RX/_TX suffix)
if type not in peripherals:
peripherals.append(type)
break
ptx = type + "_TX"
prx = type + "_RX"
if prefix in ['SPI', 'I2C']:
@@ -471,9 +471,17 @@ def write_dma_header(f, peripheral_list, mcu_type, dma_exclude=[],
shared = ' // shared %s' % ','.join(stream_assign[stream])
if stream[0] in [1,2]:
shared_set.add("(1U<<STM32_DMA_STREAM_ID(%u,%u))" % (stream[0],stream[1]))
# Plain I2C key (no _RX/_TX suffix) is used for the ChibiOS I2Cv4 LLD
# which takes a single DMA channel per peripheral via
# STM32_I2C_I2Cx_DMA_CHANNEL, and handles DMAMUX request source
# selection internally.
is_i2cv4 = (key.startswith('I2C') and
not key.endswith('_RX') and not key.endswith('_TX'))
stream_suffix = 'CHANNEL' if is_i2cv4 else 'STREAM'
if curr_dict[key] == "STM32_DMA_STREAM_ID_ANY":
f.write("#define %-30s STM32_DMA_STREAM_ID_ANY\n" % (chibios_dma_define_name(key)+'STREAM'))
f.write("#define %-30s %s\n" % (chibios_dma_define_name(key)+'CHAN', dmamux_channel(key)))
f.write("#define %-30s STM32_DMA_STREAM_ID_ANY\n" % (chibios_dma_define_name(key)+stream_suffix))
if not is_i2cv4:
f.write("#define %-30s %s\n" % (chibios_dma_define_name(key)+'CHAN', dmamux_channel(key)))
continue
else:
dma_controller = curr_dict[key][0]
@@ -484,7 +492,7 @@ def write_dma_header(f, peripheral_list, mcu_type, dma_exclude=[],
continue
else:
f.write("#define %-30s STM32_DMA_STREAM_ID(%u, %u)%s\n" %
(chibios_dma_define_name(key)+'STREAM', dma_controller,
(chibios_dma_define_name(key)+stream_suffix, dma_controller,
curr_dict[key][1], shared))
if have_DMAMUX and "_UP" in key:
# share the dma with rest of the _CH ports
@@ -495,6 +503,10 @@ def write_dma_header(f, peripheral_list, mcu_type, dma_exclude=[],
f.write("#define %-30s STM32_DMA_STREAM_ID(%u, %u)%s\n" %
(chibios_dma_define_name(chkey)+'STREAM', dma_controller,
curr_dict[key][1], shared))
if is_i2cv4:
# I2Cv4 does not need a separate CHAN define; the LLD selects
# the DMAMUX request source (STM32_DMAMUX1_I2Cx_TX/RX) itself
continue
for streamchan in sorted(dma_map[key]):
if stream == (streamchan[0], streamchan[1]):
if have_DMAMUX:
@@ -510,7 +522,7 @@ def write_dma_header(f, peripheral_list, mcu_type, dma_exclude=[],
if chkey not in timer_ch_periph:
continue
f.write("#define %-30s %s\n" %
(chibios_dma_define_name(chkey)+'CHAN',
(chibios_dma_define_name(chkey)+'CHAN',
chan.replace('_UP', '_CH{}'.format(ch))))
break