diff --git a/conf/firmwares/demo.makefile b/conf/firmwares/demo.makefile
index c0070243a8..645aa5eab5 100644
--- a/conf/firmwares/demo.makefile
+++ b/conf/firmwares/demo.makefile
@@ -69,7 +69,7 @@ COMMON_DEMO_SRCS += $(SRC_ARCH)/led_hw.c
COMMON_DEMO_SRCS += $(SRC_ARCH)/mcu_periph/gpio_arch.c
endif
-COMMON_DEMO_SRCS += mcu_periph/i2c.c $(SRC_ARCH)/mcu_periph/i2c_arch.c
+COMMON_DEMO_SRCS += mcu_periph/i2c.c mcu_periph/softi2c.c $(SRC_ARCH)/mcu_periph/i2c_arch.c
COMMON_DEMO_SRCS += mcu_periph/uart.c
COMMON_DEMO_SRCS += $(SRC_ARCH)/mcu_periph/uart_arch.c
ifeq ($(ARCH), linux)
diff --git a/conf/firmwares/rotorcraft.makefile b/conf/firmwares/rotorcraft.makefile
index b2c6833a61..ba2600189b 100644
--- a/conf/firmwares/rotorcraft.makefile
+++ b/conf/firmwares/rotorcraft.makefile
@@ -136,6 +136,7 @@ endif # TARGET == fbw
ifneq ($(TARGET), fbw)
$(TARGET).srcs += mcu_periph/i2c.c
+$(TARGET).srcs += mcu_periph/softi2c.c
$(TARGET).srcs += $(SRC_ARCH)/mcu_periph/i2c_arch.c
endif
diff --git a/conf/firmwares/rover.makefile b/conf/firmwares/rover.makefile
index 4c592e35a5..34412ce0d5 100644
--- a/conf/firmwares/rover.makefile
+++ b/conf/firmwares/rover.makefile
@@ -120,6 +120,7 @@ endif
##
$(TARGET).srcs += mcu_periph/i2c.c
+$(TARGET).srcs += mcu_periph/softi2c.c
$(TARGET).srcs += $(SRC_ARCH)/mcu_periph/i2c_arch.c
include $(CFG_SHARED)/uart.makefile
diff --git a/conf/firmwares/subsystems/fixedwing/autopilot.makefile b/conf/firmwares/subsystems/fixedwing/autopilot.makefile
index 65cc3106c8..33b9c0ed48 100644
--- a/conf/firmwares/subsystems/fixedwing/autopilot.makefile
+++ b/conf/firmwares/subsystems/fixedwing/autopilot.makefile
@@ -91,6 +91,7 @@ endif
#
ifneq ($(TARGET),fbw)
$(TARGET).srcs += mcu_periph/i2c.c
+$(TARGET).srcs += mcu_periph/softi2c.c
$(TARGET).srcs += $(SRC_ARCH)/mcu_periph/i2c_arch.c
endif
diff --git a/conf/modules/i2c.xml b/conf/modules/i2c.xml
index b6c0b59fc3..1f0b46bc38 100644
--- a/conf/modules/i2c.xml
+++ b/conf/modules/i2c.xml
@@ -12,6 +12,7 @@
+
diff --git a/sw/airborne/arch/chibios/mcu_periph/i2c_arch.c b/sw/airborne/arch/chibios/mcu_periph/i2c_arch.c
index 51e557bd41..d25e694aae 100644
--- a/sw/airborne/arch/chibios/mcu_periph/i2c_arch.c
+++ b/sw/airborne/arch/chibios/mcu_periph/i2c_arch.c
@@ -46,6 +46,12 @@
#define I2C_THREAD_STACK_SIZE 512
#endif
+
+static bool i2c_chibios_idle(struct i2c_periph *p) __attribute__((unused));
+static bool i2c_chibios_submit(struct i2c_periph *p, struct i2c_transaction *t) __attribute__((unused));
+static void i2c_chibios_setbitrate(struct i2c_periph *p, int bitrate) __attribute__((unused));
+
+
#if USE_I2C1 || USE_I2C2 || USE_I2C3 || USE_I2C4
// private I2C init structure
@@ -209,6 +215,10 @@ static THD_WORKING_AREA(wa_thd_i2c1, I2C_THREAD_STACK_SIZE);
*/
void i2c1_hw_init(void)
{
+ i2c1.idle = i2c_chibios_idle;
+ i2c1.submit = i2c_chibios_submit;
+ i2c1.setbitrate = i2c_chibios_setbitrate;
+
i2cStart(&I2CD1, &i2cfg1);
i2c1.reg_addr = &I2CD1;
i2c1.errors = &i2c1_errors;
@@ -263,6 +273,10 @@ static THD_WORKING_AREA(wa_thd_i2c2, I2C_THREAD_STACK_SIZE);
*/
void i2c2_hw_init(void)
{
+ i2c2.idle = i2c_chibios_idle;
+ i2c2.submit = i2c_chibios_submit;
+ i2c2.setbitrate = i2c_chibios_setbitrate;
+
i2cStart(&I2CD2, &i2cfg2);
i2c2.reg_addr = &I2CD2;
i2c2.errors = &i2c2_errors;
@@ -317,6 +331,10 @@ static THD_WORKING_AREA(wa_thd_i2c3, I2C_THREAD_STACK_SIZE);
*/
void i2c3_hw_init(void)
{
+ i2c3.idle = i2c_chibios_idle;
+ i2c3.submit = i2c_chibios_submit;
+ i2c3.setbitrate = i2c_chibios_setbitrate;
+
i2cStart(&I2CD3, &i2cfg3);
i2c3.reg_addr = &I2CD3;
i2c3.init_struct = NULL;
@@ -372,6 +390,10 @@ static THD_WORKING_AREA(wa_thd_i2c4, 128);
*/
void i2c4_hw_init(void)
{
+ i2c4.idle = i2c_chibios_idle;
+ i2c4.submit = i2c_chibios_submit;
+ i2c4.setbitrate = i2c_chibios_setbitrate;
+
i2cStart(&I2CD4, &i2cfg4);
i2c4.reg_addr = &I2CD4;
i2c4.init_struct = NULL;
@@ -411,7 +433,7 @@ void i2c_event(void) {}
* Empty, for paparazzi compatibility only. Bitrate is already
* set in i2cX_hw_init()
*/
-void i2c_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __attribute__((unused))) {}
+static void i2c_chibios_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __attribute__((unused))) {}
/**
* i2c_submit() function
@@ -430,7 +452,7 @@ void i2c_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __
* @param[in] p pointer to a @p i2c_periph struct
* @param[in] t pointer to a @p i2c_transaction struct
*/
-bool i2c_submit(struct i2c_periph *p, struct i2c_transaction *t)
+static bool i2c_chibios_submit(struct i2c_periph *p, struct i2c_transaction *t)
{
#if USE_I2C1 || USE_I2C2 || USE_I2C3 || USE_I2C4
// sys lock
@@ -469,7 +491,7 @@ bool i2c_submit(struct i2c_periph *p, struct i2c_transaction *t)
*
* Empty, for paparazzi compatibility only
*/
-bool i2c_idle(struct i2c_periph *p __attribute__((unused)))
+static bool i2c_chibios_idle(struct i2c_periph *p __attribute__((unused)))
{
return FALSE;
}
diff --git a/sw/airborne/arch/linux/mcu_periph/i2c_arch.c b/sw/airborne/arch/linux/mcu_periph/i2c_arch.c
index 5254bd4a42..706a05ace3 100644
--- a/sw/airborne/arch/linux/mcu_periph/i2c_arch.c
+++ b/sw/airborne/arch/linux/mcu_periph/i2c_arch.c
@@ -42,6 +42,12 @@
#define I2C_THREAD_PRIO 10
#endif
+
+static bool i2c_linux_idle(struct i2c_periph *p __attribute__((unused))) __attribute__((unused));
+static bool i2c_linux_submit(struct i2c_periph *p, struct i2c_transaction *t) __attribute__((unused));
+static void i2c_linux_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __attribute__((unused))) __attribute__((unused));
+
+
static void *i2c_thread(void *thread_data);
// private I2C init structure
@@ -66,16 +72,16 @@ void i2c_event(void)
{
}
-void i2c_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __attribute__((unused)))
+static void i2c_linux_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __attribute__((unused)))
{
}
-bool i2c_idle(struct i2c_periph *p __attribute__((unused)))
+static bool i2c_linux_idle(struct i2c_periph *p __attribute__((unused)))
{
return true;
}
-bool i2c_submit(struct i2c_periph *p, struct i2c_transaction *t)
+static bool i2c_linux_submit(struct i2c_periph *p, struct i2c_transaction *t)
{
// get mutex lock and condition
pthread_mutex_t *mutex = &(((struct i2c_thread_t *)(p->init_struct))->mutex);
@@ -204,6 +210,10 @@ struct i2c_thread_t i2c0_thread;
void i2c0_hw_init(void)
{
+ i2c0.idle = i2c_linux_idle;
+ i2c0.submit = i2c_linux_submit;
+ i2c0.setbitrate = i2c_linux_setbitrate;
+
i2c0.reg_addr = (void *)open("/dev/i2c-0", O_RDWR);
i2c0.errors = &i2c0_errors;
@@ -224,6 +234,10 @@ struct i2c_thread_t i2c1_thread;
void i2c1_hw_init(void)
{
+ i2c1.idle = i2c_linux_idle;
+ i2c1.submit = i2c_linux_submit;
+ i2c1.setbitrate = i2c_linux_setbitrate;
+
i2c1.reg_addr = (void *)open("/dev/i2c-1", O_RDWR);
i2c1.errors = &i2c1_errors;
@@ -244,6 +258,10 @@ struct i2c_thread_t i2c2_thread;
void i2c2_hw_init(void)
{
+ i2c2.idle = i2c_linux_idle;
+ i2c2.submit = i2c_linux_submit;
+ i2c2.setbitrate = i2c_linux_setbitrate;
+
i2c2.reg_addr = (void *)open("/dev/i2c-2", O_RDWR);
i2c2.errors = &i2c2_errors;
@@ -264,6 +282,10 @@ struct i2c_thread_t i2c3_thread;
void i2c3_hw_init(void)
{
+ i2c3.idle = i2c_linux_idle;
+ i2c3.submit = i2c_linux_submit;
+ i2c3.setbitrate = i2c_linux_setbitrate;
+
i2c3.reg_addr = (void *)open("/dev/i2c-3", O_RDWR);
i2c3.errors = &i2c3_errors;
diff --git a/sw/airborne/arch/lpc21/mcu_periph/i2c_arch.c b/sw/airborne/arch/lpc21/mcu_periph/i2c_arch.c
index 5e7d024814..2b13db730d 100644
--- a/sw/airborne/arch/lpc21/mcu_periph/i2c_arch.c
+++ b/sw/airborne/arch/lpc21/mcu_periph/i2c_arch.c
@@ -33,6 +33,12 @@
#include BOARD_CONFIG
#include "armVIC.h"
+
+static bool i2c_lpc21_idle(struct i2c_periph *p) __attribute__((unused));
+static bool i2c_lpc21_submit(struct i2c_periph *p, struct i2c_transaction *t) __attribute__((unused));
+static void i2c_lpc21_setbitrate(struct i2c_periph *p, int bitrate) __attribute__((unused));
+
+
///////////////////
// I2C Automaton //
///////////////////
@@ -231,6 +237,9 @@ uint8_t i2c0_vic_channel;
/* SCL0 on P0.2 */
void i2c0_hw_init(void)
{
+ i2c0.idle = i2c_lpc21_idle;
+ i2c0.submit = i2c_lpc21_submit;
+ i2c0.setbitrate = i2c_lpc21_setbitrate;
i2c0.reg_addr = I2C0;
i2c0_vic_channel = VIC_I2C0;
@@ -316,6 +325,9 @@ uint8_t i2c1_vic_channel;
/* SCL1 on P0.11 */
void i2c1_hw_init(void)
{
+ i2c1.idle = i2c_lpc21_idle;
+ i2c1.submit = i2c_lpc21_submit;
+ i2c1.setbitrate = i2c_lpc21_setbitrate;
i2c1.reg_addr = I2C1;
i2c1_vic_channel = VIC_I2C1;
@@ -341,12 +353,12 @@ void i2c1_hw_init(void)
#endif /* USE_I2C1 */
-bool i2c_idle(struct i2c_periph *p)
+static bool i2c_lpc21_idle(struct i2c_periph *p)
{
return p->status == I2CIdle;
}
-bool i2c_submit(struct i2c_periph *p, struct i2c_transaction *t)
+static bool i2c_lpc21_submit(struct i2c_periph *p, struct i2c_transaction *t)
{
uint8_t idx;
@@ -383,7 +395,7 @@ bool i2c_submit(struct i2c_periph *p, struct i2c_transaction *t)
void i2c_event(void) { }
-void i2c_setbitrate(struct i2c_periph *p, int bitrate)
+static void i2c_lpc21_setbitrate(struct i2c_periph *p, int bitrate)
{
int period = 15000000 / 2 / bitrate;
// Max 400kpbs
diff --git a/sw/airborne/arch/sim/mcu_periph/gpio_arch.h b/sw/airborne/arch/sim/mcu_periph/gpio_arch.h
index b027593dc1..1392b3aef4 100644
--- a/sw/airborne/arch/sim/mcu_periph/gpio_arch.h
+++ b/sw/airborne/arch/sim/mcu_periph/gpio_arch.h
@@ -77,5 +77,6 @@ static inline void gpio_setup_input(uint32_t port __attribute__((unused)), uint1
static inline void gpio_set(uint32_t port __attribute__((unused)), uint16_t pin __attribute__((unused))) {}
static inline void gpio_clear(uint32_t port __attribute__((unused)), uint16_t pin __attribute__((unused))) {}
static inline void gpio_toggle(uint32_t port __attribute__((unused)), uint16_t pin __attribute__((unused))) {}
+static inline uint16_t gpio_get(uint32_t gpioport __attribute__((unused)), uint16_t gpios __attribute__((unused))) { return FALSE; }
#endif /* GPIO_ARCH_H */
diff --git a/sw/airborne/arch/sim/mcu_periph/i2c_arch.c b/sw/airborne/arch/sim/mcu_periph/i2c_arch.c
index 9dcbbcc78e..907b9e1e9b 100644
--- a/sw/airborne/arch/sim/mcu_periph/i2c_arch.c
+++ b/sw/airborne/arch/sim/mcu_periph/i2c_arch.c
@@ -27,16 +27,19 @@
#include "mcu_periph/i2c.h"
-bool i2c_idle(struct i2c_periph *p __attribute__((unused))) { return true; }
-bool i2c_submit(struct i2c_periph *p __attribute__((unused)), struct i2c_transaction *t __attribute__((unused))) { return true;}
+static bool __attribute__((unused)) i2c_sim_idle(struct i2c_periph *p __attribute__((unused))) { return true; }
+static bool __attribute__((unused)) i2c_sim_submit(struct i2c_periph *p __attribute__((unused)), struct i2c_transaction *t __attribute__((unused))) { return true;}
+static void __attribute__((unused)) i2c_sim_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __attribute__((unused))) { }
void i2c_event(void) { }
-void i2c_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __attribute__((unused))) { }
#if USE_I2C0
struct i2c_errors i2c0_errors;
void i2c0_hw_init(void)
{
+ i2c0.idle = i2c_sim_idle;
+ i2c0.submit = i2c_sim_submit;
+ i2c0.setbitrate = i2c_sim_setbitrate;
i2c0.errors = &i2c0_errors;
ZEROS_ERR_COUNTER(i2c0_errors);
}
@@ -48,6 +51,9 @@ struct i2c_errors i2c1_errors;
void i2c1_hw_init(void)
{
+ i2c1.idle = i2c_sim_idle;
+ i2c1.submit = i2c_sim_submit;
+ i2c1.setbitrate = i2c_sim_setbitrate;
i2c1.errors = &i2c1_errors;
ZEROS_ERR_COUNTER(i2c1_errors);
}
@@ -59,6 +65,9 @@ struct i2c_errors i2c2_errors;
void i2c2_hw_init(void)
{
+ i2c2.idle = i2c_sim_idle;
+ i2c2.submit = i2c_sim_submit;
+ i2c2.setbitrate = i2c_sim_setbitrate;
i2c2.errors = &i2c2_errors;
ZEROS_ERR_COUNTER(i2c2_errors);
}
diff --git a/sw/airborne/arch/stm32/mcu_periph/i2c_arch.c b/sw/airborne/arch/stm32/mcu_periph/i2c_arch.c
index 2f1d56b1e8..8337fbc100 100644
--- a/sw/airborne/arch/stm32/mcu_periph/i2c_arch.c
+++ b/sw/airborne/arch/stm32/mcu_periph/i2c_arch.c
@@ -38,6 +38,11 @@
#include "mcu_periph/gpio.h"
+static bool i2c_stm32_idle(struct i2c_periph *periph) __attribute__((unused));
+static bool i2c_stm32_submit(struct i2c_periph *periph, struct i2c_transaction *t) __attribute__((unused));
+static void i2c_stm32_setbitrate(struct i2c_periph *periph, int bitrate) __attribute__((unused));
+
+
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
@@ -845,6 +850,9 @@ struct i2c_errors i2c1_errors;
void i2c1_hw_init(void)
{
+ i2c1.idle = i2c_stm32_idle;
+ i2c1.submit = i2c_stm32_submit;
+ i2c1.setbitrate = i2c_stm32_setbitrate;
i2c1.reg_addr = (void *)I2C1;
i2c1.init_struct = NULL;
@@ -916,6 +924,9 @@ struct i2c_errors i2c2_errors;
void i2c2_hw_init(void)
{
+ i2c2.idle = i2c_stm32_idle;
+ i2c2.submit = i2c_stm32_submit;
+ i2c2.setbitrate = i2c_stm32_setbitrate;
i2c2.reg_addr = (void *)I2C2;
i2c2.init_struct = NULL;
@@ -989,6 +1000,9 @@ struct i2c_errors i2c3_errors;
void i2c3_hw_init(void)
{
+ i2c3.idle = i2c_stm32_idle;
+ i2c3.submit = i2c_stm32_submit;
+ i2c3.setbitrate = i2c_stm32_setbitrate;
i2c3.reg_addr = (void *)I2C3;
i2c3.init_struct = NULL;
@@ -1054,7 +1068,7 @@ void i2c3_er_isr(void)
// -short wires, low capacitance bus: IMU: high speed
// -long wires with a lot of capacitance: motor controller: put speed as low as possible
-void i2c_setbitrate(struct i2c_periph *periph, int bitrate)
+static void i2c_stm32_setbitrate(struct i2c_periph *periph, int bitrate)
{
// If NOT Busy
if (i2c_idle(periph)) {
@@ -1312,7 +1326,7 @@ void i2c_event(void)
/////////////////////////////////////////////////////////
// Implement Interface Functions
-bool i2c_submit(struct i2c_periph *periph, struct i2c_transaction *t)
+static bool i2c_stm32_submit(struct i2c_periph *periph, struct i2c_transaction *t)
{
if (periph->watchdog > WD_DELAY) {
return false;
@@ -1349,7 +1363,7 @@ bool i2c_submit(struct i2c_periph *periph, struct i2c_transaction *t)
return true;
}
-bool i2c_idle(struct i2c_periph *periph)
+static bool i2c_stm32_idle(struct i2c_periph *periph)
{
// This is actually a difficult function:
// -simply reading the status flags can clear bits and corrupt the transaction
diff --git a/sw/airborne/mcu.c b/sw/airborne/mcu.c
index 50afbee51c..063ff87c80 100644
--- a/sw/airborne/mcu.c
+++ b/sw/airborne/mcu.c
@@ -43,10 +43,14 @@
#define USING_UART 1
#include "mcu_periph/uart.h"
#endif
-#if USE_I2C0 || USE_I2C1 || USE_I2C2 || USE_I2C3 || USE_I2C4
+#if USE_I2C0 || USE_I2C1 || USE_I2C2 || USE_I2C3 || USE_I2C4 || USE_SOFTI2C0 || USE_SOFTI2C1
#define USING_I2C 1
#include "mcu_periph/i2c.h"
#endif
+#if USE_SOFTI2C0 || USE_SOFTI2C1
+#define USING_SOFTI2C 1
+#include "mcu_periph/softi2c.h"
+#endif
#if USE_ADC
#include "mcu_periph/adc.h"
#endif
@@ -176,6 +180,12 @@ void mcu_init(void)
#ifdef USE_I2C4
i2c4_init();
#endif
+#ifdef USE_SOFTI2C0
+ softi2c0_init();
+#endif
+#ifdef USE_SOFTI2C1
+ softi2c1_init();
+#endif
#if USE_ADC
adc_init();
#endif
@@ -250,6 +260,9 @@ void mcu_event(void)
#if USING_I2C
i2c_event();
#endif
+#if USING_SOFTI2C
+ softi2c_event();
+#endif
#if USE_USB_SERIAL
VCOM_event();
diff --git a/sw/airborne/mcu_periph/i2c.c b/sw/airborne/mcu_periph/i2c.c
index 3a79804ff6..8d0a0a20b5 100644
--- a/sw/airborne/mcu_periph/i2c.c
+++ b/sw/airborne/mcu_periph/i2c.c
@@ -249,6 +249,14 @@ static void send_i2c4_err(struct transport_tx *trans, struct link_device *dev)
#endif /* USE_I2C4 */
+#if USE_SOFTI2C0
+extern void send_softi2c0_err(struct transport_tx *trans, struct link_device *dev);
+#endif /* USE_SOFTI2C0 */
+
+#if USE_SOFTI2C1
+extern void send_softi2c1_err(struct transport_tx *trans, struct link_device *dev);
+#endif /* USE_SOFTI2C1 */
+
#if PERIODIC_TELEMETRY
static void send_i2c_err(struct transport_tx *trans __attribute__((unused)),
struct link_device *dev __attribute__((unused)))
@@ -278,13 +286,21 @@ static void send_i2c_err(struct transport_tx *trans __attribute__((unused)),
case 4:
#if USE_I2C4
send_i2c4_err(trans, dev);
+#endif
+ case 5:
+#if USE_SOFTI2C0
+ send_softi2c0_err(trans, dev);
+#endif
+ case 6:
+#if USE_SOFTI2C1
+ send_softi2c1_err(trans, dev);
#endif
break;
default:
break;
}
_i2c_nb_cnt++;
- if (_i2c_nb_cnt == 5) {
+ if (_i2c_nb_cnt == 7) {
_i2c_nb_cnt = 0;
}
}
@@ -354,6 +370,7 @@ bool i2c_blocking_transmit(struct i2c_periph *p, struct i2c_transaction *t,
// Wait for transaction to complete
float start_t = get_sys_time_float();
while (t->status == I2CTransPending || t->status == I2CTransRunning) {
+ if (p->spin) p->spin(p);
if (get_sys_time_float() - start_t > I2C_BLOCKING_TIMEOUT) {
break; // timeout after 1 second
}
@@ -375,6 +392,7 @@ bool i2c_blocking_receive(struct i2c_periph *p, struct i2c_transaction *t,
// Wait for transaction to complete
float start_t = get_sys_time_float();
while (t->status == I2CTransPending || t->status == I2CTransRunning) {
+ if (p->spin) p->spin(p);
if (get_sys_time_float() - start_t > I2C_BLOCKING_TIMEOUT) {
break; // timeout after 1 second
}
@@ -396,6 +414,7 @@ bool i2c_blocking_transceive(struct i2c_periph *p, struct i2c_transaction *t,
// Wait for transaction to complete
float start_t = get_sys_time_float();
while (t->status == I2CTransPending || t->status == I2CTransRunning) {
+ if (p->spin) p->spin(p);
if (get_sys_time_float() - start_t > I2C_BLOCKING_TIMEOUT) {
break; // timeout after 1 second
}
diff --git a/sw/airborne/mcu_periph/i2c.h b/sw/airborne/mcu_periph/i2c.h
index 352a0ac266..27ef685cde 100644
--- a/sw/airborne/mcu_periph/i2c.h
+++ b/sw/airborne/mcu_periph/i2c.h
@@ -135,7 +135,18 @@ struct i2c_transaction {
/** I2C peripheral structure.
*/
+struct i2c_periph;
+typedef bool i2c_idle_fn_t(struct i2c_periph *p);
+typedef bool i2c_submit_fn_t(struct i2c_periph *p, struct i2c_transaction *t);
+typedef void i2c_setbitrate_fn_t(struct i2c_periph *p, int bitrate);
+typedef void i2c_spin_fn_t(struct i2c_periph *p); // To update peripherals within tight loops, e.g. the blocking functions. Leave NULL if not required.
+
struct i2c_periph {
+ /* architecture-specific functions */
+ i2c_idle_fn_t *idle;
+ i2c_submit_fn_t *submit;
+ i2c_setbitrate_fn_t *setbitrate;
+ i2c_spin_fn_t *spin;
/* circular buffer holding transactions */
struct i2c_transaction *trans[I2C_TRANSACTION_QUEUE_LEN];
uint8_t trans_insert_idx;
@@ -223,6 +234,18 @@ extern void i2c4_init(void);
#endif /* USE_I2C4 */
+#if USE_SOFTI2C0
+extern struct i2c_periph softi2c0;
+extern void softi2c0_init(void);
+#endif /* USE_SOFTI2C0 */
+
+
+#if USE_SOFTI2C1
+extern struct i2c_periph softi2c1;
+extern void softi2c1_init(void);
+#endif /* USE_SOFTI2C1 */
+
+
/** Initialize I2C peripheral */
extern void i2c_init(struct i2c_periph *p);
@@ -230,7 +253,9 @@ extern void i2c_init(struct i2c_periph *p);
* @param p i2c peripheral to be used
* @return TRUE if idle
*/
-extern bool i2c_idle(struct i2c_periph *p);
+static inline bool i2c_idle(struct i2c_periph *p) {
+ return p->idle(p);
+}
/** Submit a I2C transaction.
* Must be implemented by the underlying architecture
@@ -238,13 +263,18 @@ extern bool i2c_idle(struct i2c_periph *p);
* @param t i2c transaction
* @return TRUE if insertion to the transaction queue succeeded
*/
-extern bool i2c_submit(struct i2c_periph *p, struct i2c_transaction *t);
+static inline bool i2c_submit(struct i2c_periph *p, struct i2c_transaction *t) {
+ return p->submit(p, t);
+}
/** Set I2C bitrate.
* @param p i2c peripheral to be used
* @param bitrate bitrate
*/
-extern void i2c_setbitrate(struct i2c_periph *p, int bitrate);
+static inline void i2c_setbitrate(struct i2c_periph *p, int bitrate) {
+ p->setbitrate(p, bitrate);
+}
+
extern void i2c_event(void);
/*
diff --git a/sw/airborne/mcu_periph/softi2c.c b/sw/airborne/mcu_periph/softi2c.c
new file mode 100644
index 0000000000..7d1edefb30
--- /dev/null
+++ b/sw/airborne/mcu_periph/softi2c.c
@@ -0,0 +1,668 @@
+/*
+ * Copyright (C) 2020 Tom van Dijk
+ *
+ * This file is part of paparazzi.
+ *
+ * paparazzi is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * paparazzi 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with paparazzi; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+/**
+ * @file mcu_periph/softi2c.c
+ * Platform-independent software I2C implementation.
+ * Can be used transparently in place of the hardware I2C in i2c.h.
+ *
+ * This implementation can only be used as the (only) master on the I2C bus.
+ */
+
+#include "mcu_periph/softi2c.h"
+
+#include "mcu_arch.h"
+#include "mcu_periph/gpio.h"
+#include "mcu_periph/sys_time.h"
+
+#if PERIODIC_TELEMETRY
+#include "subsystems/datalink/telemetry.h"
+#endif
+
+#include
+
+
+#ifndef SOFTI2C0_DIVIDER
+#define SOFTI2C0_DIVIDER 1
+#endif
+#ifndef SOFTI2C1_DIVIDER
+#define SOFTI2C1_DIVIDER 1
+#endif
+
+
+struct softi2c_device {
+ struct i2c_periph *periph;
+ uint32_t sda_port;
+ uint16_t sda_pin;
+ uint32_t scl_port;
+ uint16_t scl_pin;
+ /* Bit-level state machine */
+ uint8_t bit_state;
+ /* Byte-level state machine */
+ uint8_t byte_state;
+ /* Transaction-level state machine */
+ // periph->status
+ // periph->idx_buf
+};
+
+
+static bool softi2c_idle(struct i2c_periph *periph) __attribute__((unused));
+static bool softi2c_submit(struct i2c_periph *periph, struct i2c_transaction *t) __attribute__((unused));
+static void softi2c_setbitrate(struct i2c_periph *periph, int bitrate) __attribute__((unused));
+static void softi2c_spin(struct i2c_periph *p) __attribute__((unused));
+
+
+#if USE_SOFTI2C0 && ( \
+ !defined(SOFTI2C0_SDA_GPIO) || !defined(SOFTI2C0_SDA_PIN) || \
+ !defined(SOFTI2C0_SCL_GPIO) || !defined(SOFTI2C0_SCL_PIN))
+#error "SDA and SCL pins must be configured for SOFTI2C0!"
+#endif
+
+#if USE_SOFTI2C1 && ( \
+ !defined(SOFTI2C1_SDA_GPIO) || !defined(SOFTI2C1_SDA_PIN) || \
+ !defined(SOFTI2C1_SCL_GPIO) || !defined(SOFTI2C1_SCL_PIN))
+#error "SDA and SCL pins must be configured for SOFTI2C1!"
+#endif
+
+
+#if USE_SOFTI2C0
+struct i2c_periph softi2c0;
+static struct softi2c_device softi2c0_device;
+void softi2c0_init(void) {
+ i2c_init(&softi2c0);
+ softi2c0_hw_init();
+}
+#if PERIODIC_TELEMETRY
+void send_softi2c0_err(struct transport_tx *trans, struct link_device *dev)
+{
+ uint16_t i2c0_wd_reset_cnt = softi2c0.errors->wd_reset_cnt;
+ uint16_t i2c0_queue_full_cnt = softi2c0.errors->queue_full_cnt;
+ uint16_t i2c0_ack_fail_cnt = softi2c0.errors->ack_fail_cnt;
+ uint16_t i2c0_miss_start_stop_cnt = softi2c0.errors->miss_start_stop_cnt;
+ uint16_t i2c0_arb_lost_cnt = softi2c0.errors->arb_lost_cnt;
+ uint16_t i2c0_over_under_cnt = softi2c0.errors->over_under_cnt;
+ uint16_t i2c0_pec_recep_cnt = softi2c0.errors->pec_recep_cnt;
+ uint16_t i2c0_timeout_tlow_cnt = softi2c0.errors->timeout_tlow_cnt;
+ uint16_t i2c0_smbus_alert_cnt = softi2c0.errors->smbus_alert_cnt;
+ uint16_t i2c0_unexpected_event_cnt = softi2c0.errors->unexpected_event_cnt;
+ uint32_t i2c0_last_unexpected_event = softi2c0.errors->last_unexpected_event;
+ uint8_t _bus0 = 10;
+ pprz_msg_send_I2C_ERRORS(trans, dev, AC_ID,
+ &i2c0_wd_reset_cnt,
+ &i2c0_queue_full_cnt,
+ &i2c0_ack_fail_cnt,
+ &i2c0_miss_start_stop_cnt,
+ &i2c0_arb_lost_cnt,
+ &i2c0_over_under_cnt,
+ &i2c0_pec_recep_cnt,
+ &i2c0_timeout_tlow_cnt,
+ &i2c0_smbus_alert_cnt,
+ &i2c0_unexpected_event_cnt,
+ &i2c0_last_unexpected_event,
+ &_bus0);
+}
+#endif
+#endif /* USE_SOFTI2C0 */
+
+#if USE_SOFTI2C1
+struct i2c_periph softi2c1;
+static struct softi2c_device softi2c1_device;
+void softi2c1_init(void) {
+ i2c_init(&softi2c1);
+ softi2c1_hw_init();
+}
+#if PERIODIC_TELEMETRY
+void send_softi2c1_err(struct transport_tx *trans, struct link_device *dev)
+{
+ uint16_t i2c1_wd_reset_cnt = softi2c1.errors->wd_reset_cnt;
+ uint16_t i2c1_queue_full_cnt = softi2c1.errors->queue_full_cnt;
+ uint16_t i2c1_ack_fail_cnt = softi2c1.errors->ack_fail_cnt;
+ uint16_t i2c1_miss_start_stop_cnt = softi2c1.errors->miss_start_stop_cnt;
+ uint16_t i2c1_arb_lost_cnt = softi2c1.errors->arb_lost_cnt;
+ uint16_t i2c1_over_under_cnt = softi2c1.errors->over_under_cnt;
+ uint16_t i2c1_pec_recep_cnt = softi2c1.errors->pec_recep_cnt;
+ uint16_t i2c1_timeout_tlow_cnt = softi2c1.errors->timeout_tlow_cnt;
+ uint16_t i2c1_smbus_alert_cnt = softi2c1.errors->smbus_alert_cnt;
+ uint16_t i2c1_unexpected_event_cnt = softi2c1.errors->unexpected_event_cnt;
+ uint32_t i2c1_last_unexpected_event = softi2c1.errors->last_unexpected_event;
+ uint8_t _bus1 = 11;
+ pprz_msg_send_I2C_ERRORS(trans, dev, AC_ID,
+ &i2c1_wd_reset_cnt,
+ &i2c1_queue_full_cnt,
+ &i2c1_ack_fail_cnt,
+ &i2c1_miss_start_stop_cnt,
+ &i2c1_arb_lost_cnt,
+ &i2c1_over_under_cnt,
+ &i2c1_pec_recep_cnt,
+ &i2c1_timeout_tlow_cnt,
+ &i2c1_smbus_alert_cnt,
+ &i2c1_unexpected_event_cnt,
+ &i2c1_last_unexpected_event,
+ &_bus1);
+}
+#endif
+#endif /* USE_SOFTI2C1 */
+
+
+static void softi2c_gpio_highz(uint32_t port, uint16_t pin) {
+#if defined(CHIBIOS_MCU_ARCH_H) || defined(STM32_MCU_ARCH_H)
+ /* Arch's with input_pullup */
+ gpio_setup_input_pullup(port, pin);
+#else
+ /* Arch's without input_pullup */
+ // Gpio_set might enable pullup on some architectures (e.g. arduino), not sure
+ // if it works here. If not, an external pull-up resistor is required on the
+ // I2C lines.
+ gpio_setup_input(port, pin);
+ gpio_set(port, pin);
+#endif /* arch with/without input_pullup */
+}
+
+static bool softi2c_gpio_read(uint32_t port, uint16_t pin) {
+ softi2c_gpio_highz(port, pin);
+ return gpio_get(port, pin);
+}
+
+static void softi2c_gpio_drive_low(uint32_t port, uint16_t pin) {
+ gpio_setup_output(port, pin);
+ gpio_clear(port, pin);
+}
+
+static void softi2c_setup_gpio(
+ uint32_t sda_port, uint16_t sda_pin,
+ uint32_t scl_port, uint16_t scl_pin) __attribute__((unused));
+
+static void softi2c_setup_gpio(
+ uint32_t sda_port, uint16_t sda_pin,
+ uint32_t scl_port, uint16_t scl_pin) {
+#ifdef STM32_MCU_ARCH_H
+ gpio_enable_clock(sda_port);
+ gpio_enable_clock(scl_port);
+#endif /* STM32F1/F4 */
+ softi2c_gpio_highz(sda_port, sda_pin);
+ softi2c_gpio_highz(scl_port, scl_pin);
+}
+
+
+#if USE_SOFTI2C0
+struct i2c_errors softi2c0_errors;
+
+void softi2c0_hw_init(void) {
+ softi2c0.idle = softi2c_idle;
+ softi2c0.submit = softi2c_submit;
+ softi2c0.setbitrate = softi2c_setbitrate;
+ softi2c0.spin = softi2c_spin;
+ softi2c0.reg_addr = (void *) &softi2c0_device;
+ softi2c0.errors = &softi2c0_errors;
+ ZEROS_ERR_COUNTER(softi2c0_errors);
+
+ softi2c0_device.periph = &softi2c0;
+ softi2c0_device.sda_port = SOFTI2C0_SDA_GPIO;
+ softi2c0_device.sda_pin = SOFTI2C0_SDA_PIN;
+ softi2c0_device.scl_port = SOFTI2C0_SCL_GPIO;
+ softi2c0_device.scl_pin = SOFTI2C0_SCL_PIN;
+
+ /* Set up GPIO */
+ softi2c_setup_gpio(
+ SOFTI2C0_SDA_GPIO, SOFTI2C0_SDA_PIN,
+ SOFTI2C0_SCL_GPIO, SOFTI2C0_SCL_PIN);
+}
+#endif /* USE_SOFTI2C0 */
+
+#if USE_SOFTI2C1
+struct i2c_errors softi2c1_errors;
+
+void softi2c1_hw_init(void) {
+ softi2c1.idle = softi2c_idle;
+ softi2c1.submit = softi2c_submit;
+ softi2c1.setbitrate = softi2c_setbitrate;
+ softi2c1.spin = softi2c_spin;
+ softi2c1.reg_addr = (void *) &softi2c1_device;
+ softi2c1.errors = &softi2c1_errors;
+ ZEROS_ERR_COUNTER(softi2c1_errors);
+
+ softi2c1_device.periph = &softi2c1;
+ softi2c1_device.sda_port = SOFTI2C1_SDA_GPIO;
+ softi2c1_device.sda_pin = SOFTI2C1_SDA_PIN;
+ softi2c1_device.scl_port = SOFTI2C1_SCL_GPIO;
+ softi2c1_device.scl_pin = SOFTI2C1_SCL_PIN;
+
+ /* Set up GPIO */
+ softi2c_setup_gpio(
+ SOFTI2C1_SDA_GPIO, SOFTI2C1_SDA_PIN,
+ SOFTI2C1_SCL_GPIO, SOFTI2C1_SCL_PIN);
+}
+#endif /* USE_SOFTI2C1 */
+
+
+/*
+ * I2C implementation
+ */
+// Some notes about timing:
+// The I2C standard lists timing requirements in the order of microseconds. This
+// is difficult to explicitly implement for two reasons:
+// - The event loop spins at a ~20 us period.
+// - The sys time clock has a resolution of ~200 us. (Can be set higher, but
+// can cause lockups when set to ~2us or lower.)
+// This module handles timing by returning to the event loop at every pause.
+// The event loop runs slow enough to satisfy all I2C standard mode minimum
+// timings. Because the event loop takes longer than the minimum timing, the
+// maximum bitrate is reduced. However, *software*-i2c should not be used for
+// high-bitrate applications anyway.
+// (Experimental code where usleep was used for in-bit timing had only little
+// performance benefit (~20kHz vs ~15kHz). Because sleeping in the event loop
+// can have other negative consequences, this option was removed.)
+// Pauses *between* bits also use the time between event calls as a delay. As a
+// result, the bitrate is an integer fraction of the event rate and cannot be
+// set exactly. The bitrate can be coarsely adjusted per softi2c device by
+// setting SOFTI2CX_DIVIDER.
+
+
+// Bit read/write functions
+// Should be called continuously from event function.
+// Return true upon completion.
+// The bit functions start by setting SCL low and end at the last transition
+// before SCL is set low again. The time between event calls is used to space
+// this last transition and the new SCL low edge, thereby setting the bus
+// frequency.
+
+static bool softi2c_write_start(struct softi2c_device *d) {
+ softi2c_gpio_drive_low(d->sda_port, d->sda_pin);
+ return true; // > T_HD_STA_MIN
+}
+
+// Note: write_bit may also be used to write the ACK bit (T_VD_ACK == T_VD_DAT)
+static bool softi2c_write_bit(struct softi2c_device *d, bool bit) {
+ switch (d->bit_state) {
+ case 0:
+ // Start of bit
+ softi2c_gpio_drive_low(d->scl_port, d->scl_pin);
+ d->bit_state++;
+ return false;
+ case 1:
+ // After SCL fall time and data hold time
+ if (bit) {
+ softi2c_gpio_highz(d->sda_port, d->sda_pin);
+ } else {
+ softi2c_gpio_drive_low(d->sda_port, d->sda_pin);
+ }
+ d->bit_state++;
+ return false;
+ case 2:
+ // After SDA rise(/fall) and data set-up time
+ softi2c_gpio_highz(d->scl_port, d->scl_pin);
+ d->bit_state++;
+ /* Falls through. */
+ case 3:
+ if (!gpio_get(d->scl_port, d->scl_pin)) return false;
+ // After SCL rise time and confirmed high (clock stretching)
+ d->bit_state = 0;
+ return true; // > T_HIGH_MIN
+ default: return false;
+ }
+}
+
+// Note: read_bit may also be used to read the ACK bit (T_VD_ACK == T_VD_DAT)
+static bool softi2c_read_bit(struct softi2c_device *d, bool *bit) {
+ switch (d->bit_state) {
+ case 0:
+ // Start of bit
+ softi2c_gpio_drive_low(d->scl_port, d->scl_pin);
+ d->bit_state++;
+ return false;
+ case 1:
+ // After SCL fall time and data hold time
+ softi2c_gpio_highz(d->sda_port, d->sda_pin); // SDA may be driven low by slave.
+ // After SCL(!) fall time and minimum low time (== T_HD_DAT_MIN)
+ softi2c_gpio_highz(d->scl_port, d->scl_pin);
+ d->bit_state++;
+ /* Falls through. */
+ case 2:
+ if (!gpio_get(d->scl_port, d->scl_pin)) return false;
+ // After SCL rise time and confirmed high (clock stretching)
+ *bit = softi2c_gpio_read(d->sda_port, d->sda_pin);
+ d->bit_state = 0;
+ return true; // > T_HIGH_MIN
+ default: return false;
+ }
+}
+
+static bool softi2c_write_restart(struct softi2c_device *d) {
+ switch (d->bit_state) {
+ case 0:
+ // Start of bit
+ softi2c_gpio_drive_low(d->scl_port, d->scl_pin);
+ d->bit_state++;
+ return false;
+ case 1:
+ // After SCL fall time and data hold time
+ softi2c_gpio_highz(d->sda_port, d->sda_pin);
+ d->bit_state++;
+ return false;
+ case 2:
+ // After SDA rise time and data set-up time
+ softi2c_gpio_highz(d->scl_port, d->scl_pin);
+ d->bit_state++;
+ /* Falls through. */
+ case 3:
+ if (!gpio_get(d->scl_port, d->scl_pin)) return false;
+ // After SCL rise time and confirmed high (clock stretching)
+ d->bit_state++;
+ return false;
+ case 4:
+ // After restart set-up time
+ softi2c_gpio_drive_low(d->sda_port, d->sda_pin);
+ d->bit_state = 0;
+ return true; // > T_HD_STA_MIN
+ default: return false;
+ }
+}
+
+static bool softi2c_write_stop(struct softi2c_device *d) {
+ switch (d->bit_state) {
+ case 0:
+ // Start of bit
+ softi2c_gpio_drive_low(d->scl_port, d->scl_pin);
+ d->bit_state++;
+ return false;
+ case 1:
+ // After SCL fall time and data hold time
+ softi2c_gpio_drive_low(d->sda_port, d->sda_pin);
+ d->bit_state++;
+ return false;
+ case 2:
+ // After SDA fall time and data set-up time
+ softi2c_gpio_highz(d->scl_port, d->scl_pin);
+ d->bit_state++;
+ /* Falls through. */
+ case 3:
+ if (!gpio_get(d->scl_port, d->scl_pin)) return false;
+ // After SCL rise time and confirmed high (clock stretching)
+ d->bit_state++;
+ return false;
+ case 4:
+ // After stop set-up time
+ softi2c_gpio_highz(d->sda_port, d->sda_pin);
+ d->bit_state = 0;
+ return true; // > T_BUF_MIN
+ default: return false;
+ }
+}
+
+// Byte read/write functions
+// Should be called continuously from event function.
+// Return true upon completion.
+
+// write_byte:
+// starts with SCL <= low for first bit (start bit is not included!)
+// ends at SCL allowed low
+static bool softi2c_write_byte(struct softi2c_device *d, uint8_t byte, bool *ack) {
+ bool bit;
+ switch (d->byte_state) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ // Write bit
+ bit = byte & (0x80 >> d->byte_state); // MSB first
+ if (!softi2c_write_bit(d, bit)) return false; // Write bit in progress
+ d->byte_state++;
+ return false;
+ case 8:
+ // Read ACK
+ if (!softi2c_read_bit(d, ack)) return false; // Read bit in progress
+ *ack = !*ack; // Inverted, low = acknowledge
+ d->byte_state = 0;
+ return true;
+ default: return false;
+ }
+}
+
+// read_byte:
+// starts with SCL <= low for first bit (start bit is not included!)
+// ends at SCL allowed low
+// Note: ack should be false when reading last byte (UM10204 3.1.10)
+static bool softi2c_read_byte(struct softi2c_device *d, uint8_t *byte, bool ack) {
+ bool bit;
+ switch (d->byte_state) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ // Read bit
+ if (!softi2c_read_bit(d, &bit)) return false; // Read bit in progress
+ if (bit) {
+ *byte |= (0x80 >> d->byte_state); // MSB first
+ } else {
+ *byte &= ~(0x80 >> d->byte_state); // MSB first
+ }
+ d->byte_state++;
+ return false;
+ case 8:
+ // Write ACK
+ // Note: inverted logic, low = acknowledge.
+ if (!softi2c_write_bit(d, !ack)) return false; // Write bit in progress
+ d->byte_state = 0;
+ return true;
+ default: return false;
+ }
+}
+
+// Transaction handling
+// Should be called continously from event function.
+// Returns true upon completion.
+static bool softi2c_process_transaction(struct softi2c_device *d, struct i2c_transaction *t) {
+ uint8_t byte;
+ bool ack;
+ switch (d->periph->status) {
+ case I2CIdle:
+ // Start of transaction
+ t->status = I2CTransRunning;
+ d->periph->status = I2CStartRequested;
+ d->periph->idx_buf = 0;
+ return false;
+
+ case I2CStartRequested:
+ // Send start bit
+ if (!softi2c_write_start(d)) return false;
+ // Start bit sent
+ if (t->type == I2CTransRx) {
+ d->periph->status = I2CAddrRdSent;
+ } else {
+ d->periph->status = I2CAddrWrSent;
+ }
+ return false;
+
+ case I2CAddrWrSent:
+ // Send write address
+ if (!softi2c_write_byte(d, t->slave_addr, &ack)) return false;
+ // Write address sent
+ if (!ack) {
+ d->periph->errors->ack_fail_cnt++;
+ t->status = I2CTransFailed;
+ d->periph->status = I2CStopRequested;
+ return false;
+ }
+ d->periph->status = I2CSendingByte;
+ return false;
+
+ case I2CSendingByte:
+ // Check remaining bytes
+ if (d->periph->idx_buf >= t->len_w) {
+ d->periph->idx_buf = 0;
+ if (t->type == I2CTransTxRx) {
+ d->periph->status = I2CRestartRequested;
+ } else {
+ t->status = I2CTransSuccess;
+ d->periph->status = I2CStopRequested;
+ }
+ return false;
+ }
+ // Send byte
+ if (!softi2c_write_byte(d, t->buf[d->periph->idx_buf], &ack)) return false;
+ // Byte sent
+ if (!ack) {
+ d->periph->errors->ack_fail_cnt++;
+ t->status = I2CTransFailed;
+ d->periph->status = I2CStopRequested;
+ return true;
+ }
+ d->periph->idx_buf++;
+ return false;
+
+ case I2CRestartRequested:
+ // Send restart bit
+ if (!softi2c_write_restart(d)) return false;
+ // Restart bit sent
+ d->periph->status = I2CAddrRdSent;
+ return false;
+
+ case I2CAddrRdSent:
+ // Send read address
+ byte = t->slave_addr | 0x01;
+ if (!softi2c_write_byte(d, byte, &ack)) return false;
+ // Read address sent
+ if (!ack) {
+ d->periph->errors->ack_fail_cnt++;
+ t->status = I2CTransFailed;
+ d->periph->status = I2CStopRequested;
+ return true;
+ }
+ d->periph->status = I2CReadingByte;
+ return false;
+
+ case I2CReadingByte:
+ // Check remaining bytes
+ if (d->periph->idx_buf >= t->len_r - 1) {
+ d->periph->status = I2CReadingLastByte;
+ return false;
+ }
+ // Read byte
+ if (!softi2c_read_byte(d, (uint8_t *) &(t->buf[d->periph->idx_buf]), true)) return false;
+ // Byte read
+ d->periph->idx_buf++;
+ return false;
+
+ case I2CReadingLastByte:
+ // Check remaining bytes
+ if (d->periph->idx_buf >= t->len_r) {
+ t->status = I2CTransSuccess;
+ d->periph->idx_buf = 0;
+ d->periph->status = I2CStopRequested;
+ return false;
+ }
+ // Read last byte (no ACK!)
+ if (!softi2c_read_byte(d, (uint8_t *) &(t->buf[d->periph->idx_buf]), false)) return false;
+ // Byte read
+ d->periph->idx_buf++;
+ return false;
+
+ case I2CStopRequested:
+ // Send stop bit
+ if (!softi2c_write_stop(d)) return false;
+ // Stop bit sent
+ d->periph->status = I2CIdle;
+ return true;
+
+ default:
+ // Should never happen, something went wrong
+ t->status = I2CTransFailed;
+ d->periph->status = I2CIdle;
+ return true;
+ }
+}
+
+// Per-device event function
+static void softi2c_device_event(struct softi2c_device *d) __attribute__((unused));
+
+static void softi2c_device_event(struct softi2c_device *d) {
+ struct i2c_periph *p = d->periph;
+ if (p->trans_insert_idx != p->trans_extract_idx) {
+ // Transaction(s) in queue
+ struct i2c_transaction *t = p->trans[p->trans_extract_idx];
+ if (softi2c_process_transaction(d, t)) {
+ // Transaction finished
+ p->trans_extract_idx = (p->trans_extract_idx + 1) % I2C_TRANSACTION_QUEUE_LEN;
+ }
+ }
+}
+
+
+/*
+ * Paparazzi functions
+ */
+void softi2c_event(void) {
+#if USE_SOFTI2C0
+ static int softi2c0_cnt = 1;
+ if (softi2c0_cnt == SOFTI2C0_DIVIDER) {
+ softi2c_device_event(&softi2c0_device);
+ softi2c0_cnt = 1;
+ } else {
+ softi2c0_cnt++;
+ }
+#endif
+#if USE_SOFTI2C1
+ static int softi2c1_cnt = 1;
+ if (softi2c1_cnt == SOFTI2C1_DIVIDER) {
+ softi2c_device_event(&softi2c1_device);
+ softi2c1_cnt = 1;
+ } else {
+ softi2c1_cnt++;
+ }
+#endif
+}
+
+static void softi2c_spin(struct i2c_periph *p) {
+ softi2c_device_event((struct softi2c_device *) p->reg_addr);
+ sys_time_usleep(5); // For I2C timing.
+}
+
+static bool softi2c_idle(struct i2c_periph *p) {
+ return (p->status == I2CIdle) && (p->trans_insert_idx == p->trans_extract_idx);
+}
+
+static bool softi2c_submit(struct i2c_periph *p, struct i2c_transaction *t) {
+ uint8_t next_idx = (p->trans_insert_idx + 1) % I2C_TRANSACTION_QUEUE_LEN;
+ if (next_idx == p->trans_extract_idx) {
+ // queue full
+ p->errors->queue_full_cnt++;
+ t->status = I2CTransFailed;
+ return false;
+ }
+ t->status = I2CTransPending;
+ /* put transaction in queue */
+ p->trans[p->trans_insert_idx] = t;
+ p->trans_insert_idx = next_idx;
+ return true;
+}
+
+static void softi2c_setbitrate(struct i2c_periph *p __attribute__((unused)), int bitrate __attribute__((unused))) {
+ /* Do nothing */
+}
diff --git a/sw/airborne/mcu_periph/softi2c.h b/sw/airborne/mcu_periph/softi2c.h
new file mode 100644
index 0000000000..f4a832c274
--- /dev/null
+++ b/sw/airborne/mcu_periph/softi2c.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 Tom van Dijk
+ *
+ * This file is part of paparazzi.
+ *
+ * paparazzi is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * paparazzi 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with paparazzi; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+/**
+ * @file mcu_periph/softi2c.h
+ * Platform-independent software I2C implementation.
+ * Can be used transparently in place of the hardware I2C in i2c.h.
+ */
+
+#ifndef MCU_PERIPH_SOFTI2C_H
+#define MCU_PERIPH_SOFTI2C_H
+
+#include "mcu_periph/i2c.h"
+
+
+void softi2c_event(void);
+
+
+#if USE_SOFTI2C0
+extern void softi2c0_hw_init(void);
+#endif /* USE_SOFTI2C0 */
+
+#if USE_SOFTI2C1
+extern void softi2c1_hw_init(void);
+#endif /* USE_SOFTI2C1 */
+
+
+#endif // MCU_PERIPH_SOFTI2C_H