kopia lustrzana https://github.com/micropython/micropython
552 wiersze
15 KiB
C
552 wiersze
15 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2018 Damien P. George
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "py/mperrno.h"
|
|
#include "py/mphal.h"
|
|
#include "py/runtime.h"
|
|
#include "i2c.h"
|
|
|
|
#if MICROPY_HW_ENABLE_HW_I2C
|
|
|
|
#if defined(STM32F4)
|
|
|
|
STATIC uint16_t i2c_timeout_ms[MICROPY_HW_MAX_I2C];
|
|
|
|
int i2c_init(i2c_t *i2c, mp_hal_pin_obj_t scl, mp_hal_pin_obj_t sda, uint32_t freq, uint16_t timeout_ms) {
|
|
uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
|
|
|
|
// Init pins
|
|
if (!mp_hal_pin_config_alt(scl, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, AF_FN_I2C, i2c_id + 1)) {
|
|
return -MP_EPERM;
|
|
}
|
|
if (!mp_hal_pin_config_alt(sda, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, AF_FN_I2C, i2c_id + 1)) {
|
|
return -MP_EPERM;
|
|
}
|
|
|
|
// Save timeout value
|
|
i2c_timeout_ms[i2c_id] = timeout_ms;
|
|
|
|
// Force reset I2C peripheral
|
|
RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST << i2c_id;
|
|
RCC->APB1RSTR &= ~(RCC_APB1RSTR_I2C1RST << i2c_id);
|
|
|
|
// Enable I2C peripheral clock
|
|
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN << i2c_id;
|
|
volatile uint32_t tmp = RCC->APB1ENR; // delay after RCC clock enable
|
|
(void)tmp;
|
|
|
|
uint32_t PCLK1 = HAL_RCC_GetPCLK1Freq();
|
|
|
|
// Initialise I2C peripheral
|
|
i2c->CR1 = 0;
|
|
i2c->CR2 = PCLK1 / 1000000;
|
|
i2c->OAR1 = 0;
|
|
i2c->OAR2 = 0;
|
|
|
|
freq = MIN(freq, 400000);
|
|
|
|
// SM: MAX(4, PCLK1 / (F * 2))
|
|
// FM, 16:9 duty: 0xc000 | MAX(1, (PCLK1 / (F * (16 + 9))))
|
|
if (freq <= 100000) {
|
|
i2c->CCR = MAX(4, PCLK1 / (freq * 2));
|
|
} else {
|
|
i2c->CCR = 0xc000 | MAX(1, PCLK1 / (freq * 25));
|
|
}
|
|
|
|
// SM: 1000ns / (1/PCLK1) + 1 = PCLK1 * 1e-6 + 1
|
|
// FM: 300ns / (1/PCLK1) + 1 = 300e-3 * PCLK1 * 1e-6 + 1
|
|
if (freq <= 100000) {
|
|
i2c->TRISE = PCLK1 / 1000000 + 1; // 1000ns rise time in SM
|
|
} else {
|
|
i2c->TRISE = PCLK1 / 1000000 * 3 / 10 + 1; // 300ns rise time in FM
|
|
}
|
|
|
|
#if defined(I2C_FLTR_ANOFF)
|
|
i2c->FLTR = 0; // analog filter on, digital filter off
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
STATIC int i2c_wait_sr1_set(i2c_t *i2c, uint32_t mask) {
|
|
uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
|
|
uint32_t t0 = HAL_GetTick();
|
|
while (!(i2c->SR1 & mask)) {
|
|
if (HAL_GetTick() - t0 >= i2c_timeout_ms[i2c_id]) {
|
|
i2c->CR1 &= ~I2C_CR1_PE;
|
|
return -MP_ETIMEDOUT;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
STATIC int i2c_wait_stop(i2c_t *i2c) {
|
|
uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
|
|
uint32_t t0 = HAL_GetTick();
|
|
while (i2c->CR1 & I2C_CR1_STOP) {
|
|
if (HAL_GetTick() - t0 >= i2c_timeout_ms[i2c_id]) {
|
|
i2c->CR1 &= ~I2C_CR1_PE;
|
|
return -MP_ETIMEDOUT;
|
|
}
|
|
}
|
|
i2c->CR1 &= ~I2C_CR1_PE;
|
|
return 0;
|
|
}
|
|
|
|
// For write: len = 0, 1 or N
|
|
// For read: len = 1, 2 or N; stop = true
|
|
int i2c_start_addr(i2c_t *i2c, int rd_wrn, uint16_t addr, size_t next_len, bool stop) {
|
|
if (!(i2c->CR1 & I2C_CR1_PE) && (i2c->SR2 & I2C_SR2_MSL)) {
|
|
// The F4 I2C peripheral can sometimes get into a bad state where it's disabled
|
|
// (PE low) but still an active master (MSL high). It seems the best way to get
|
|
// out of this is a full reset.
|
|
uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
|
|
RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST << i2c_id;
|
|
RCC->APB1RSTR &= ~(RCC_APB1RSTR_I2C1RST << i2c_id);
|
|
}
|
|
|
|
// It looks like it's possible to terminate the reading by sending a
|
|
// START condition instead of STOP condition but we don't support that.
|
|
if (rd_wrn) {
|
|
if (!stop) {
|
|
return -MP_EINVAL;
|
|
}
|
|
}
|
|
|
|
// Repurpose OAR1 to hold stop flag
|
|
i2c->OAR1 = stop;
|
|
|
|
// Enable peripheral and send START condition
|
|
i2c->CR1 |= I2C_CR1_PE;
|
|
i2c->CR1 |= I2C_CR1_START;
|
|
|
|
// Wait for START to be sent
|
|
int ret;
|
|
if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_SB))) {
|
|
return ret;
|
|
}
|
|
|
|
// Send the 7-bit address with read/write bit
|
|
i2c->DR = addr << 1 | rd_wrn;
|
|
|
|
// Wait for address to be sent
|
|
if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_AF | I2C_SR1_ADDR))) {
|
|
return ret;
|
|
}
|
|
|
|
// Check if the slave responded or not
|
|
if (i2c->SR1 & I2C_SR1_AF) {
|
|
// Got a NACK
|
|
i2c->CR1 |= I2C_CR1_STOP;
|
|
i2c_wait_stop(i2c); // Don't leak errors from this call
|
|
return -MP_ENODEV;
|
|
}
|
|
|
|
if (rd_wrn) {
|
|
// For reading, set up ACK/NACK control based on number of bytes to read (at least 1 byte)
|
|
if (next_len <= 1) {
|
|
// NACK next received byte
|
|
i2c->CR1 &= ~I2C_CR1_ACK;
|
|
} else if (next_len <= 2) {
|
|
// NACK second received byte
|
|
i2c->CR1 |= I2C_CR1_POS;
|
|
i2c->CR1 &= ~I2C_CR1_ACK;
|
|
} else {
|
|
// ACK next received byte
|
|
i2c->CR1 |= I2C_CR1_ACK;
|
|
}
|
|
}
|
|
|
|
// Read SR2 to clear SR1_ADDR
|
|
uint32_t sr2 = i2c->SR2;
|
|
(void)sr2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// next_len = 0 or N (>=2)
|
|
int i2c_read(i2c_t *i2c, uint8_t *dest, size_t len, size_t next_len) {
|
|
if (len == 0) {
|
|
return -MP_EINVAL;
|
|
}
|
|
if (next_len == 1) {
|
|
return -MP_EINVAL;
|
|
}
|
|
|
|
size_t remain = len + next_len;
|
|
if (remain == 1) {
|
|
// Special case
|
|
i2c->CR1 |= I2C_CR1_STOP;
|
|
int ret;
|
|
if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_RXNE))) {
|
|
return ret;
|
|
}
|
|
*dest = i2c->DR;
|
|
} else {
|
|
for (; len; --len) {
|
|
remain = len + next_len;
|
|
int ret;
|
|
if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_BTF))) {
|
|
return ret;
|
|
}
|
|
if (remain == 2) {
|
|
// In this case next_len == 0 (it's not allowed to be 1)
|
|
i2c->CR1 |= I2C_CR1_STOP;
|
|
*dest++ = i2c->DR;
|
|
*dest = i2c->DR;
|
|
break;
|
|
} else if (remain == 3) {
|
|
// NACK next received byte
|
|
i2c->CR1 &= ~I2C_CR1_ACK;
|
|
}
|
|
*dest++ = i2c->DR;
|
|
}
|
|
}
|
|
|
|
if (!next_len) {
|
|
// We sent a stop above, just wait for it to be finished
|
|
return i2c_wait_stop(i2c);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// next_len = 0 or N
|
|
int i2c_write(i2c_t *i2c, const uint8_t *src, size_t len, size_t next_len) {
|
|
int ret;
|
|
if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_AF | I2C_SR1_TXE))) {
|
|
return ret;
|
|
}
|
|
|
|
// Write out the data
|
|
int num_acks = 0;
|
|
while (len--) {
|
|
i2c->DR = *src++;
|
|
if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_AF | I2C_SR1_BTF))) {
|
|
return ret;
|
|
}
|
|
if (i2c->SR1 & I2C_SR1_AF) {
|
|
// Slave did not respond to byte so stop sending
|
|
break;
|
|
}
|
|
++num_acks;
|
|
}
|
|
|
|
if (!next_len) {
|
|
if (i2c->OAR1) {
|
|
// Send a STOP and wait for it to finish
|
|
i2c->CR1 |= I2C_CR1_STOP;
|
|
if ((ret = i2c_wait_stop(i2c))) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
return num_acks;
|
|
}
|
|
|
|
#elif defined(STM32F0) || defined(STM32F7) || defined(STM32H7)
|
|
|
|
#if defined(STM32H7)
|
|
#define APB1ENR APB1LENR
|
|
#define RCC_APB1ENR_I2C1EN RCC_APB1LENR_I2C1EN
|
|
#endif
|
|
|
|
STATIC uint16_t i2c_timeout_ms[MICROPY_HW_MAX_I2C];
|
|
|
|
int i2c_init(i2c_t *i2c, mp_hal_pin_obj_t scl, mp_hal_pin_obj_t sda, uint32_t freq, uint16_t timeout_ms) {
|
|
uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
|
|
|
|
// Init pins
|
|
if (!mp_hal_pin_config_alt(scl, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, AF_FN_I2C, i2c_id + 1)) {
|
|
return -MP_EPERM;
|
|
}
|
|
if (!mp_hal_pin_config_alt(sda, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, AF_FN_I2C, i2c_id + 1)) {
|
|
return -MP_EPERM;
|
|
}
|
|
|
|
// Save timeout value
|
|
i2c_timeout_ms[i2c_id] = timeout_ms;
|
|
|
|
// Enable I2C peripheral clock
|
|
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN << i2c_id;
|
|
volatile uint32_t tmp = RCC->APB1ENR; // delay after RCC clock enable
|
|
(void)tmp;
|
|
|
|
// Initialise I2C peripheral
|
|
i2c->CR1 = 0;
|
|
i2c->CR2 = 0;
|
|
i2c->OAR1 = 0;
|
|
i2c->OAR2 = 0;
|
|
|
|
// These timing values are for f_I2CCLK=54MHz and are only approximate
|
|
if (freq >= 1000000) {
|
|
i2c->TIMINGR = 0x50100103;
|
|
} else if (freq >= 400000) {
|
|
i2c->TIMINGR = 0x70330309;
|
|
} else if (freq >= 100000) {
|
|
i2c->TIMINGR = 0xb0420f13;
|
|
} else {
|
|
return -MP_EINVAL;
|
|
}
|
|
|
|
i2c->TIMEOUTR = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
STATIC int i2c_wait_cr2_clear(i2c_t *i2c, uint32_t mask) {
|
|
uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
|
|
uint32_t t0 = HAL_GetTick();
|
|
while (i2c->CR2 & mask) {
|
|
if (HAL_GetTick() - t0 >= i2c_timeout_ms[i2c_id]) {
|
|
i2c->CR1 &= ~I2C_CR1_PE;
|
|
return -MP_ETIMEDOUT;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
STATIC int i2c_wait_isr_set(i2c_t *i2c, uint32_t mask) {
|
|
uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
|
|
uint32_t t0 = HAL_GetTick();
|
|
while (!(i2c->ISR & mask)) {
|
|
if (HAL_GetTick() - t0 >= i2c_timeout_ms[i2c_id]) {
|
|
i2c->CR1 &= ~I2C_CR1_PE;
|
|
return -MP_ETIMEDOUT;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// len = 0, 1 or N
|
|
int i2c_start_addr(i2c_t *i2c, int rd_wrn, uint16_t addr, size_t len, bool stop) {
|
|
// Enable the peripheral and send the START condition with slave address
|
|
i2c->CR1 |= I2C_CR1_PE;
|
|
i2c->CR2 = (len > 1) << I2C_CR2_RELOAD_Pos
|
|
| (len > 0) << I2C_CR2_NBYTES_Pos
|
|
| rd_wrn << I2C_CR2_RD_WRN_Pos
|
|
| (addr & 0x7f) << 1;
|
|
i2c->CR2 |= I2C_CR2_START;
|
|
|
|
// Wait for address to be sent
|
|
int ret;
|
|
if ((ret = i2c_wait_cr2_clear(i2c, I2C_CR2_START))) {
|
|
return ret;
|
|
}
|
|
|
|
// Check if the slave responded or not
|
|
if (i2c->ISR & I2C_ISR_NACKF) {
|
|
// If we get a NACK then I2C periph unconditionally sends a STOP
|
|
i2c_wait_isr_set(i2c, I2C_ISR_STOPF); // Don't leak errors from this call
|
|
i2c->CR1 &= ~I2C_CR1_PE;
|
|
return -MP_ENODEV;
|
|
}
|
|
|
|
// Configure automatic STOP if needed
|
|
if (stop) {
|
|
i2c->CR2 |= I2C_CR2_AUTOEND;
|
|
}
|
|
|
|
// Repurpose OAR1 to indicate that we loaded CR2
|
|
i2c->OAR1 = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
STATIC int i2c_check_stop(i2c_t *i2c) {
|
|
if (i2c->CR2 & I2C_CR2_AUTOEND) {
|
|
// Wait for the STOP condition and then disable the peripheral
|
|
int ret;
|
|
if ((ret = i2c_wait_isr_set(i2c, I2C_ISR_STOPF))) {
|
|
return ret;
|
|
}
|
|
i2c->CR1 &= ~I2C_CR1_PE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// next_len = 0 or N
|
|
int i2c_read(i2c_t *i2c, uint8_t *dest, size_t len, size_t next_len) {
|
|
if (i2c->OAR1) {
|
|
i2c->OAR1 = 0;
|
|
} else {
|
|
goto load_cr2;
|
|
}
|
|
|
|
// Read in the data
|
|
while (len--) {
|
|
int ret;
|
|
if ((ret = i2c_wait_isr_set(i2c, I2C_ISR_RXNE))) {
|
|
return ret;
|
|
}
|
|
*dest++ = i2c->RXDR;
|
|
load_cr2:
|
|
if (len) {
|
|
i2c->CR2 = (i2c->CR2 & I2C_CR2_AUTOEND)
|
|
| (len + next_len > 1) << I2C_CR2_RELOAD_Pos
|
|
| 1 << I2C_CR2_NBYTES_Pos;
|
|
}
|
|
}
|
|
|
|
if (!next_len) {
|
|
int ret;
|
|
if ((ret = i2c_check_stop(i2c))) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// next_len = 0 or N
|
|
int i2c_write(i2c_t *i2c, const uint8_t *src, size_t len, size_t next_len) {
|
|
int num_acks = 0;
|
|
|
|
if (i2c->OAR1) {
|
|
i2c->OAR1 = 0;
|
|
} else {
|
|
goto load_cr2;
|
|
}
|
|
|
|
// Write out the data
|
|
while (len--) {
|
|
int ret;
|
|
if ((ret = i2c_wait_isr_set(i2c, I2C_ISR_TXE))) {
|
|
return ret;
|
|
}
|
|
i2c->TXDR = *src++;
|
|
if ((ret = i2c_wait_isr_set(i2c, I2C_ISR_TCR | I2C_ISR_TC | I2C_ISR_STOPF))) {
|
|
return ret;
|
|
}
|
|
uint32_t isr = i2c->ISR;
|
|
if (isr & I2C_ISR_NACKF) {
|
|
// Slave did not respond to byte so stop sending
|
|
if (!(isr & I2C_ISR_TXE)) {
|
|
// The TXDR is still full so the byte previous to that wasn't actually ACK'd
|
|
--num_acks;
|
|
}
|
|
break;
|
|
}
|
|
++num_acks;
|
|
load_cr2:
|
|
if (len) {
|
|
i2c->CR2 = (i2c->CR2 & I2C_CR2_AUTOEND)
|
|
| (len + next_len > 1) << I2C_CR2_RELOAD_Pos
|
|
| 1 << I2C_CR2_NBYTES_Pos;
|
|
}
|
|
}
|
|
|
|
if (!next_len) {
|
|
int ret;
|
|
if ((ret = i2c_check_stop(i2c))) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return num_acks;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(STM32F0) || defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
|
|
|
|
int i2c_readfrom(i2c_t *i2c, uint16_t addr, uint8_t *dest, size_t len, bool stop) {
|
|
int ret;
|
|
if ((ret = i2c_start_addr(i2c, 1, addr, len, stop))) {
|
|
return ret;
|
|
}
|
|
return i2c_read(i2c, dest, len, 0);
|
|
}
|
|
|
|
int i2c_writeto(i2c_t *i2c, uint16_t addr, const uint8_t *src, size_t len, bool stop) {
|
|
int ret;
|
|
if ((ret = i2c_start_addr(i2c, 0, addr, len, stop))) {
|
|
return ret;
|
|
}
|
|
return i2c_write(i2c, src, len, 0);
|
|
}
|
|
|
|
#endif
|
|
|
|
STATIC const uint8_t i2c_available =
|
|
0
|
|
#if defined(MICROPY_HW_I2C1_SCL)
|
|
| 1 << 1
|
|
#endif
|
|
#if defined(MICROPY_HW_I2C2_SCL)
|
|
| 1 << 2
|
|
#endif
|
|
#if defined(MICROPY_HW_I2C3_SCL)
|
|
| 1 << 3
|
|
#endif
|
|
#if defined(MICROPY_HW_I2C4_SCL)
|
|
| 1 << 4
|
|
#endif
|
|
;
|
|
|
|
int i2c_find_peripheral(mp_obj_t id) {
|
|
int i2c_id = 0;
|
|
if (mp_obj_is_str(id)) {
|
|
const char *port = mp_obj_str_get_str(id);
|
|
if (0) {
|
|
#ifdef MICROPY_HW_I2C1_NAME
|
|
} else if (strcmp(port, MICROPY_HW_I2C1_NAME) == 0) {
|
|
i2c_id = 1;
|
|
#endif
|
|
#ifdef MICROPY_HW_I2C2_NAME
|
|
} else if (strcmp(port, MICROPY_HW_I2C2_NAME) == 0) {
|
|
i2c_id = 2;
|
|
#endif
|
|
#ifdef MICROPY_HW_I2C3_NAME
|
|
} else if (strcmp(port, MICROPY_HW_I2C3_NAME) == 0) {
|
|
i2c_id = 3;
|
|
#endif
|
|
#ifdef MICROPY_HW_I2C4_NAME
|
|
} else if (strcmp(port, MICROPY_HW_I2C4_NAME) == 0) {
|
|
i2c_id = 4;
|
|
#endif
|
|
} else {
|
|
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%s) doesn't exist"), port);
|
|
}
|
|
} else {
|
|
i2c_id = mp_obj_get_int(id);
|
|
if (i2c_id < 1 || i2c_id >= 8 * sizeof(i2c_available) || !(i2c_available & (1 << i2c_id))) {
|
|
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id);
|
|
}
|
|
}
|
|
|
|
// check if the I2C is reserved for system use or not
|
|
if (MICROPY_HW_I2C_IS_RESERVED(i2c_id)) {
|
|
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) is reserved"), i2c_id);
|
|
}
|
|
|
|
return i2c_id;
|
|
}
|
|
|
|
#endif // MICROPY_HW_ENABLE_HW_I2C
|