From f46018b2046edc7e49546db4c5b7f375c828ab46 Mon Sep 17 00:00:00 2001 From: agarof <18119032+agarof@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:54:25 +0200 Subject: [PATCH] Add extended I2C HAL functions (#3037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add extended I2C HAL functions * Rename I2CEndClockStretch to I2CEndAwaitRestart * Address review comments * Update f18 api_symbols.csv * FuriHal: check input values in cortex timer * FuriHal: cleanup I2C documentation * Properly bump api symbols * FuriHal: fix incorrect cast in I2C write_reg methods, fix spelling and naming * FuriHal: cleanup const usage in I2C, sync declaration and implementation * Format Sources * FuriHal: more i2c docs * Add I2C Restart and Pause / Resume test * Add I2C auto-reload test * UnitTests: skip furi_hal_i2c_ext_eeprom if eeprom is not connected * UnitTest: cleanup subghz test output * FuriHal: classic timeouts in i2c Co-authored-by: hedger Co-authored-by: あく --- .../unit_tests/furi_hal/furi_hal_tests.c | 115 +++++ .../debug/unit_tests/subghz/subghz_test.c | 13 +- firmware/targets/f18/api_symbols.csv | 14 +- firmware/targets/f7/api_symbols.csv | 14 +- .../targets/f7/furi_hal/furi_hal_cortex.c | 5 + firmware/targets/f7/furi_hal/furi_hal_i2c.c | 447 ++++++++++-------- .../targets/furi_hal_include/furi_hal_i2c.h | 215 ++++++--- 7 files changed, 542 insertions(+), 281 deletions(-) diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c index e942c5933..2dbaa4d86 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c +++ b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c @@ -5,6 +5,11 @@ #include "../minunit.h" #define DATA_SIZE 4 +#define EEPROM_ADDRESS 0b10101000 +#define EEPROM_ADDRESS_HIGH (EEPROM_ADDRESS | 0b10) +#define EEPROM_SIZE 512 +#define EEPROM_PAGE_SIZE 16 +#define EEPROM_WRITE_DELAY_MS 6 static void furi_hal_i2c_int_setup() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -14,6 +19,14 @@ static void furi_hal_i2c_int_teardown() { furi_hal_i2c_release(&furi_hal_i2c_handle_power); } +static void furi_hal_i2c_ext_setup() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); +} + +static void furi_hal_i2c_ext_teardown() { + furi_hal_i2c_release(&furi_hal_i2c_handle_external); +} + MU_TEST(furi_hal_i2c_int_1b) { bool ret = false; uint8_t data_one = 0; @@ -103,14 +116,116 @@ MU_TEST(furi_hal_i2c_int_1b_fail) { mu_assert(data_one != 0, "9 invalid data"); } +MU_TEST(furi_hal_i2c_int_ext_3b) { + bool ret = false; + uint8_t data_many[DATA_SIZE] = {0}; + + // 3 byte: read + data_many[0] = LP5562_CHANNEL_BLUE_CURRENT_REGISTER; + ret = furi_hal_i2c_tx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many, + 1, + FuriHalI2cBeginStart, + FuriHalI2cEndAwaitRestart, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "3 tx failed"); + + // Send a RESTART condition, then read the 3 bytes one after the other + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 1, + 1, + FuriHalI2cBeginRestart, + FuriHalI2cEndPause, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "4 rx failed"); + mu_assert(data_many[1] != 0, "4 invalid data"); + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 2, + 1, + FuriHalI2cBeginResume, + FuriHalI2cEndPause, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "5 rx failed"); + mu_assert(data_many[2] != 0, "5 invalid data"); + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 3, + 1, + FuriHalI2cBeginResume, + FuriHalI2cEndStop, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "6 rx failed"); + mu_assert(data_many[3] != 0, "6 invalid data"); +} + +MU_TEST(furi_hal_i2c_ext_eeprom) { + if(!furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, EEPROM_ADDRESS, 100)) { + printf("no device connected, skipping\r\n"); + return; + } + + bool ret = false; + uint8_t buffer[EEPROM_SIZE] = {0}; + + for(size_t page = 0; page < (EEPROM_SIZE / EEPROM_PAGE_SIZE); ++page) { + // Fill page buffer + for(size_t page_byte = 0; page_byte < EEPROM_PAGE_SIZE; ++page_byte) { + // Each byte is its position in the EEPROM modulo 256 + uint8_t byte = ((page * EEPROM_PAGE_SIZE) + page_byte) % 256; + + buffer[page_byte] = byte; + } + + uint8_t address = (page < 16) ? EEPROM_ADDRESS : EEPROM_ADDRESS_HIGH; + + ret = furi_hal_i2c_write_mem( + &furi_hal_i2c_handle_external, + address, + page * EEPROM_PAGE_SIZE, + buffer, + EEPROM_PAGE_SIZE, + 20); + + mu_assert(ret, "EEPROM write failed"); + furi_delay_ms(EEPROM_WRITE_DELAY_MS); + } + + ret = furi_hal_i2c_read_mem( + &furi_hal_i2c_handle_external, EEPROM_ADDRESS, 0, buffer, EEPROM_SIZE, 100); + + mu_assert(ret, "EEPROM read failed"); + + for(size_t pos = 0; pos < EEPROM_SIZE; ++pos) { + mu_assert_int_eq(pos % 256, buffer[pos]); + } +} + MU_TEST_SUITE(furi_hal_i2c_int_suite) { MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown); MU_RUN_TEST(furi_hal_i2c_int_1b); MU_RUN_TEST(furi_hal_i2c_int_3b); + MU_RUN_TEST(furi_hal_i2c_int_ext_3b); MU_RUN_TEST(furi_hal_i2c_int_1b_fail); } +MU_TEST_SUITE(furi_hal_i2c_ext_suite) { + MU_SUITE_CONFIGURE(&furi_hal_i2c_ext_setup, &furi_hal_i2c_ext_teardown); + MU_RUN_TEST(furi_hal_i2c_ext_eeprom); +} + int run_minunit_test_furi_hal() { MU_RUN_SUITE(furi_hal_i2c_int_suite); + MU_RUN_SUITE(furi_hal_i2c_ext_suite); return MU_EXIT_CODE; } diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 1900f2045..64e0591df 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -98,9 +98,9 @@ static bool subghz_decoder_test(const char* path, const char* name_decoder) { } subghz_file_encoder_worker_free(file_worker_encoder_handler); } - FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT) { - printf("\033[0;31mTest decoder %s ERROR TimeOut\033[0m\r\n", name_decoder); + printf("Test decoder %s ERROR TimeOut\r\n", name_decoder); return false; } else { return subghz_test_decoder_count ? true : false; @@ -137,9 +137,9 @@ static bool subghz_decode_random_test(const char* path) { } subghz_file_encoder_worker_free(file_worker_encoder_handler); } - FURI_LOG_D(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_D(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT * 10) { - printf("\033[0;31mRandom test ERROR TimeOut\033[0m\r\n"); + printf("Random test ERROR TimeOut\r\n"); return false; } else if(subghz_test_decoder_count == TEST_RANDOM_COUNT_PARSE) { return true; @@ -200,10 +200,9 @@ static bool subghz_encoder_test(const char* path) { subghz_transmitter_free(transmitter); } flipper_format_free(fff_data_file); - FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT) { - printf( - "\033[0;31mTest encoder %s ERROR TimeOut\033[0m\r\n", furi_string_get_cstr(temp_str)); + printf("Test encoder %s ERROR TimeOut\r\n", furi_string_get_cstr(temp_str)); subghz_test_decoder_count = 0; } furi_string_free(temp_str); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index bc17ff2fa..f4e990bec 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,38.0,, +Version,+,39.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1105,14 +1105,16 @@ Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 26fad6b58..d37d5f333 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,38.0,, +Version,+,39.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1176,14 +1176,16 @@ Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_ibutton_emulate_set_next,void,uint32_t diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index 3fbe384e3..6b5efc376 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -15,8 +15,11 @@ void furi_hal_cortex_init_early() { } void furi_hal_cortex_delay_us(uint32_t microseconds) { + furi_check(microseconds < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND)); + uint32_t start = DWT->CYCCNT; uint32_t time_ticks = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * microseconds; + while((DWT->CYCCNT - start) < time_ticks) { }; } @@ -26,6 +29,8 @@ uint32_t furi_hal_cortex_instructions_per_microsecond() { } FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) { + furi_check(timeout_us < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND)); + FuriHalCortexTimer cortex_timer = {0}; cortex_timer.start = DWT->CYCCNT; cortex_timer.value = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * timeout_us; diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c.c b/firmware/targets/f7/furi_hal/furi_hal_i2c.c index bdfe0b0a3..8c7b054e3 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_i2c.c +++ b/firmware/targets/f7/furi_hal/furi_hal_i2c.c @@ -5,7 +5,6 @@ #include #include -#include #include #define TAG "FuriHalI2c" @@ -27,7 +26,7 @@ void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle) { furi_hal_power_insomnia_enter(); // Lock bus access handle->bus->callback(handle->bus, FuriHalI2cBusEventLock); - // Ensuree that no active handle set + // Ensure that no active handle set furi_check(handle->bus->current_handle == NULL); // Set current handle handle->bus->current_handle = handle; @@ -51,177 +50,265 @@ void furi_hal_i2c_release(FuriHalI2cBusHandle* handle) { furi_hal_power_insomnia_exit(); } +static bool + furi_hal_i2c_wait_for_idle(I2C_TypeDef* i2c, FuriHalI2cBegin begin, FuriHalCortexTimer timer) { + do { + if(furi_hal_cortex_timer_is_expired(timer)) { + return false; + } + } while(begin == FuriHalI2cBeginStart && LL_I2C_IsActiveFlag_BUSY(i2c)); + // Only check if the bus is busy if starting a new transaction, if not we already control the bus + + return true; +} + +static bool + furi_hal_i2c_wait_for_end(I2C_TypeDef* i2c, FuriHalI2cEnd end, FuriHalCortexTimer timer) { + // If ending the transaction with a stop condition, wait for it to be detected, otherwise wait for a transfer complete flag + bool wait_for_stop = end == FuriHalI2cEndStop; + uint32_t end_mask = (wait_for_stop) ? I2C_ISR_STOPF : (I2C_ISR_TC | I2C_ISR_TCR); + + while((i2c->ISR & end_mask) == 0) { + if(furi_hal_cortex_timer_is_expired(timer)) { + return false; + } + } + + return true; +} + +static uint32_t + furi_hal_i2c_get_start_signal(FuriHalI2cBegin begin, bool ten_bit_address, bool read) { + switch(begin) { + case FuriHalI2cBeginRestart: + if(read) { + return ten_bit_address ? LL_I2C_GENERATE_RESTART_10BIT_READ : + LL_I2C_GENERATE_RESTART_7BIT_READ; + } else { + return ten_bit_address ? LL_I2C_GENERATE_RESTART_10BIT_WRITE : + LL_I2C_GENERATE_RESTART_7BIT_WRITE; + } + case FuriHalI2cBeginResume: + return LL_I2C_GENERATE_NOSTARTSTOP; + case FuriHalI2cBeginStart: + default: + return read ? LL_I2C_GENERATE_START_READ : LL_I2C_GENERATE_START_WRITE; + } +} + +static uint32_t furi_hal_i2c_get_end_signal(FuriHalI2cEnd end) { + switch(end) { + case FuriHalI2cEndAwaitRestart: + return LL_I2C_MODE_SOFTEND; + case FuriHalI2cEndPause: + return LL_I2C_MODE_RELOAD; + case FuriHalI2cEndStop: + default: + return LL_I2C_MODE_AUTOEND; + } +} + +static bool furi_hal_i2c_transfer_is_aborted(I2C_TypeDef* i2c) { + return LL_I2C_IsActiveFlag_STOP(i2c) && + !(LL_I2C_IsActiveFlag_TC(i2c) || LL_I2C_IsActiveFlag_TCR(i2c)); +} + +static bool furi_hal_i2c_transfer( + I2C_TypeDef* i2c, + uint8_t* data, + uint32_t size, + FuriHalI2cEnd end, + bool read, + FuriHalCortexTimer timer) { + bool ret = true; + + while(size > 0) { + bool should_stop = furi_hal_cortex_timer_is_expired(timer) || + furi_hal_i2c_transfer_is_aborted(i2c); + + // Modifying the data pointer's data is UB if read is true + if(read && LL_I2C_IsActiveFlag_RXNE(i2c)) { + *data = LL_I2C_ReceiveData8(i2c); + data++; + size--; + } else if(!read && LL_I2C_IsActiveFlag_TXIS(i2c)) { + LL_I2C_TransmitData8(i2c, *data); + data++; + size--; + } + + // Exit on timeout or premature stop, probably caused by a nacked address or byte + if(should_stop) { + ret = size == 0; // If the transfer was over, still a success + break; + } + } + + if(ret) { + ret = furi_hal_i2c_wait_for_end(i2c, end, timer); + } + + LL_I2C_ClearFlag_STOP(i2c); + + return ret; +} + +static bool furi_hal_i2c_transaction( + I2C_TypeDef* i2c, + uint16_t address, + bool ten_bit, + uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + bool read, + FuriHalCortexTimer timer) { + uint32_t addr_size = ten_bit ? LL_I2C_ADDRSLAVE_10BIT : LL_I2C_ADDRSLAVE_7BIT; + uint32_t start_signal = furi_hal_i2c_get_start_signal(begin, ten_bit, read); + + if(!furi_hal_i2c_wait_for_idle(i2c, begin, timer)) { + return false; + } + + do { + uint8_t transfer_size = size; + FuriHalI2cEnd transfer_end = end; + + if(size > 255) { + transfer_size = 255; + transfer_end = FuriHalI2cEndPause; + } + + uint32_t end_signal = furi_hal_i2c_get_end_signal(transfer_end); + + LL_I2C_HandleTransfer(i2c, address, addr_size, transfer_size, end_signal, start_signal); + + if(!furi_hal_i2c_transfer(i2c, data, transfer_size, transfer_end, read, timer)) { + return false; + } + + size -= transfer_size; + data += transfer_size; + start_signal = LL_I2C_GENERATE_NOSTARTSTOP; + } while(size > 0); + + return true; +} + +bool furi_hal_i2c_rx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, + uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + uint32_t timeout) { + furi_check(handle->bus->current_handle == handle); + + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); + + return furi_hal_i2c_transaction( + handle->bus->i2c, address, ten_bit, data, size, begin, end, true, timer); +} + +bool furi_hal_i2c_tx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, + const uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + uint32_t timeout) { + furi_check(handle->bus->current_handle == handle); + + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); + + return furi_hal_i2c_transaction( + handle->bus->i2c, address, ten_bit, (uint8_t*)data, size, begin, end, false, timer); +} + bool furi_hal_i2c_tx( FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* data, - uint8_t size, + size_t size, uint32_t timeout) { - furi_check(handle->bus->current_handle == handle); furi_assert(timeout > 0); - bool ret = true; - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - if(!ret) { - break; - } - - LL_I2C_HandleTransfer( - handle->bus->i2c, - address, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_WRITE); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_TXIS(handle->bus->i2c)) { - LL_I2C_TransmitData8(handle->bus->i2c, (*data)); - data++; - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); - - return ret; + return furi_hal_i2c_tx_ext( + handle, address, false, data, size, FuriHalI2cBeginStart, FuriHalI2cEndStop, timeout); } bool furi_hal_i2c_rx( FuriHalI2cBusHandle* handle, uint8_t address, uint8_t* data, - uint8_t size, + size_t size, uint32_t timeout) { - furi_check(handle->bus->current_handle == handle); furi_assert(timeout > 0); - bool ret = true; - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - if(!ret) { - break; - } - - LL_I2C_HandleTransfer( - handle->bus->i2c, - address, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_READ); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_RXNE(handle->bus->i2c)) { - *data = LL_I2C_ReceiveData8(handle->bus->i2c); - data++; - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); - - return ret; + return furi_hal_i2c_rx_ext( + handle, address, false, data, size, FuriHalI2cBeginStart, FuriHalI2cEndStop, timeout); } bool furi_hal_i2c_trx( FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* tx_data, - uint8_t tx_size, + size_t tx_size, uint8_t* rx_data, - uint8_t rx_size, + size_t rx_size, uint32_t timeout) { - if(furi_hal_i2c_tx(handle, address, tx_data, tx_size, timeout) && - furi_hal_i2c_rx(handle, address, rx_data, rx_size, timeout)) { - return true; - } else { - return false; - } + return furi_hal_i2c_tx_ext( + handle, + address, + false, + tx_data, + tx_size, + FuriHalI2cBeginStart, + FuriHalI2cEndStop, + timeout) && + furi_hal_i2c_rx_ext( + handle, + address, + false, + rx_data, + rx_size, + FuriHalI2cBeginStart, + FuriHalI2cEndStop, + timeout); } bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint32_t timeout) { furi_check(handle); - furi_check(handle->bus->current_handle == handle); furi_assert(timeout > 0); bool ret = true; FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } + if(!furi_hal_i2c_wait_for_idle(handle->bus->i2c, FuriHalI2cBeginStart, timer)) { + return false; + } - handle->bus->i2c->CR2 = - ((((uint32_t)(i2c_addr) & (I2C_CR2_SADD)) | (I2C_CR2_START) | (I2C_CR2_AUTOEND)) & - (~I2C_CR2_RD_WRN)); + LL_I2C_HandleTransfer( + handle->bus->i2c, + i2c_addr, + LL_I2C_ADDRSLAVE_7BIT, + 0, + LL_I2C_MODE_AUTOEND, + LL_I2C_GENERATE_START_WRITE); - while((!LL_I2C_IsActiveFlag_NACK(handle->bus->i2c)) && - (!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c))) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } + if(!furi_hal_i2c_wait_for_end(handle->bus->i2c, FuriHalI2cEndStop, timer)) { + return false; + } - if(LL_I2C_IsActiveFlag_NACK(handle->bus->i2c)) { - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } + ret = !LL_I2C_IsActiveFlag_NACK(handle->bus->i2c); - LL_I2C_ClearFlag_NACK(handle->bus->i2c); - - // Clear STOP Flag generated by autoend - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - - // Generate actual STOP - LL_I2C_GenerateStopCondition(handle->bus->i2c); - - ret = false; - } - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } - - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); + LL_I2C_ClearFlag_NACK(handle->bus->i2c); + LL_I2C_ClearFlag_STOP(handle->bus->i2c); return ret; } @@ -257,7 +344,7 @@ bool furi_hal_i2c_read_mem( uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, - uint8_t len, + size_t len, uint32_t timeout) { furi_check(handle); @@ -272,11 +359,12 @@ bool furi_hal_i2c_write_reg_8( uint32_t timeout) { furi_check(handle); - uint8_t tx_data[2]; - tx_data[0] = reg_addr; - tx_data[1] = data; + const uint8_t tx_data[2] = { + reg_addr, + data, + }; - return furi_hal_i2c_tx(handle, i2c_addr, (const uint8_t*)&tx_data, 2, timeout); + return furi_hal_i2c_tx(handle, i2c_addr, tx_data, 2, timeout); } bool furi_hal_i2c_write_reg_16( @@ -287,69 +375,42 @@ bool furi_hal_i2c_write_reg_16( uint32_t timeout) { furi_check(handle); - uint8_t tx_data[3]; - tx_data[0] = reg_addr; - tx_data[1] = (data >> 8) & 0xFF; - tx_data[2] = data & 0xFF; + const uint8_t tx_data[3] = { + reg_addr, + (data >> 8) & 0xFF, + data & 0xFF, + }; - return furi_hal_i2c_tx(handle, i2c_addr, (const uint8_t*)&tx_data, 3, timeout); + return furi_hal_i2c_tx(handle, i2c_addr, tx_data, 3, timeout); } bool furi_hal_i2c_write_mem( FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, - uint8_t* data, - uint8_t len, + const uint8_t* data, + size_t len, uint32_t timeout) { furi_check(handle); - furi_check(handle->bus->current_handle == handle); furi_assert(timeout > 0); - bool ret = true; - uint8_t size = len + 1; - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - if(!ret) { - break; - } - - LL_I2C_HandleTransfer( - handle->bus->i2c, - i2c_addr, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_WRITE); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_TXIS(handle->bus->i2c)) { - if(size == len + 1) { - LL_I2C_TransmitData8(handle->bus->i2c, mem_addr); - } else { - LL_I2C_TransmitData8(handle->bus->i2c, (*data)); - data++; - } - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); - - return ret; + return furi_hal_i2c_tx_ext( + handle, + i2c_addr, + false, + &mem_addr, + 1, + FuriHalI2cBeginStart, + FuriHalI2cEndPause, + timeout) && + furi_hal_i2c_tx_ext( + handle, + i2c_addr, + false, + data, + len, + FuriHalI2cBeginResume, + FuriHalI2cEndStop, + timeout); } diff --git a/firmware/targets/furi_hal_include/furi_hal_i2c.h b/firmware/targets/furi_hal_include/furi_hal_i2c.h index 566574ab8..f493655b4 100644 --- a/firmware/targets/furi_hal_include/furi_hal_i2c.h +++ b/firmware/targets/furi_hal_include/furi_hal_i2c.h @@ -1,11 +1,11 @@ /** - * @file furi_hal_i2c.h - * I2C HAL API + * @file furi_hal_i2c.h I2C HAL API */ #pragma once #include +#include #include #include @@ -13,6 +13,35 @@ extern "C" { #endif +/** Transaction beginning signal */ +typedef enum { + /*!Begin the transaction by sending a START condition followed by the + * address */ + FuriHalI2cBeginStart, + /*!Begin the transaction by sending a RESTART condition followed by the + * address + * @note Must follow a transaction ended with + * FuriHalI2cEndAwaitRestart */ + FuriHalI2cBeginRestart, + /*!Continue the previous transaction with new data + * @note Must follow a transaction ended with FuriHalI2cEndPause and + * be of the same type (RX/TX) */ + FuriHalI2cBeginResume, +} FuriHalI2cBegin; + +/** Transaction end signal */ +typedef enum { + /*!End the transaction by sending a STOP condition */ + FuriHalI2cEndStop, + /*!End the transaction by clock stretching + * @note Must be followed by a transaction using + * FuriHalI2cBeginRestart */ + FuriHalI2cEndAwaitRestart, + /*!Pauses the transaction by clock stretching + * @note Must be followed by a transaction using FuriHalI2cBeginResume */ + FuriHalI2cEndPause, +} FuriHalI2cEnd; + /** Early Init I2C */ void furi_hal_i2c_init_early(); @@ -22,78 +51,126 @@ void furi_hal_i2c_deinit_early(); /** Init I2C */ void furi_hal_i2c_init(); -/** Acquire i2c bus handle +/** Acquire I2C bus handle * - * @return Instance of FuriHalI2cBus + * @param handle Pointer to FuriHalI2cBusHandle instance */ void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle); -/** Release i2c bus handle - * - * @param bus instance of FuriHalI2cBus aquired in `furi_hal_i2c_acquire` +/** Release I2C bus handle + * + * @param handle Pointer to FuriHalI2cBusHandle instance acquired in + * `furi_hal_i2c_acquire` */ void furi_hal_i2c_release(FuriHalI2cBusHandle* handle); -/** Perform I2C tx transfer +/** Perform I2C TX transfer * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param data pointer to data buffer - * @param size size of data buffer - * @param timeout timeout in ticks + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_tx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, const uint8_t* data, - const uint8_t size, + size_t size, uint32_t timeout); -/** Perform I2C rx transfer +/** + * Perform I2C TX transfer, with additional settings. * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param data pointer to data buffer - * @param size size of data buffer - * @param timeout timeout in ticks + * @param ten_bit Whether the address is 10 bits wide + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param begin How to begin the transaction + * @param end How to end the transaction + * @param timer Timeout timer + * + * @return true on successful transfer, false otherwise + */ +bool furi_hal_i2c_tx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, + const uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + uint32_t timeout); + +/** Perform I2C RX transfer + * + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param address I2C slave address + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_rx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, uint8_t* data, - const uint8_t size, + size_t size, uint32_t timeout); -/** Perform I2C tx and rx transfers +/** Perform I2C RX transfer, with additional settings. * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param tx_data pointer to tx data buffer - * @param tx_size size of tx data buffer - * @param rx_data pointer to rx data buffer - * @param rx_size size of rx data buffer - * @param timeout timeout in ticks + * @param ten_bit Whether the address is 10 bits wide + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param begin How to begin the transaction + * @param end How to end the transaction + * @param timer Timeout timer + * + * @return true on successful transfer, false otherwise + */ +bool furi_hal_i2c_rx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, + uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + uint32_t timeout); + +/** Perform I2C TX and RX transfers + * + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param address I2C slave address + * @param tx_data Pointer to TX data buffer + * @param tx_size Size of TX data buffer + * @param rx_data Pointer to RX data buffer + * @param rx_size Size of RX data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_trx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, const uint8_t* tx_data, - const uint8_t tx_size, + size_t tx_size, uint8_t* rx_data, - const uint8_t rx_size, + size_t rx_size, uint32_t timeout); /** Check if I2C device presents on bus * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param timeout Timeout in milliseconds * * @return true if device present and is ready, false otherwise */ @@ -101,11 +178,11 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, /** Perform I2C device register read (8-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data pointer to register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Pointer to register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -118,11 +195,11 @@ bool furi_hal_i2c_read_reg_8( /** Perform I2C device register read (16-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data pointer to register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Pointer to register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -135,12 +212,12 @@ bool furi_hal_i2c_read_reg_16( /** Perform I2C device memory read * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param mem_addr memory start address - * @param data pointer to data buffer - * @param len size of data buffer - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param mem_addr Memory start address + * @param data Pointer to data buffer + * @param len Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -149,16 +226,16 @@ bool furi_hal_i2c_read_mem( uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, - uint8_t len, + size_t len, uint32_t timeout); /** Perform I2C device register write (8-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -171,11 +248,11 @@ bool furi_hal_i2c_write_reg_8( /** Perform I2C device register write (16-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -188,12 +265,12 @@ bool furi_hal_i2c_write_reg_16( /** Perform I2C device memory * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param mem_addr memory start address - * @param data pointer to data buffer - * @param len size of data buffer - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param mem_addr Memory start address + * @param data Pointer to data buffer + * @param len Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -201,8 +278,8 @@ bool furi_hal_i2c_write_mem( FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, - uint8_t* data, - uint8_t len, + const uint8_t* data, + size_t len, uint32_t timeout); #ifdef __cplusplus