/** library to communicate using microwore as master * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2017-2020 * @note peripherals used: GPIO @ref microwire_master_gpio, timer @ref microwire_master_timer * microwire is a 3-Wire half-duplex synchronous bus. It is very similar to SPI without fixed length messages (bit-wised). * @note the user has to handle the slave select pin (high during operations) so to be able to handle multiple slaves. * @warning this library implements the M93Cx8 EEPROM operation codes. Other microwire-based ICs might use different ones. */ /* standard libraries */ #include // standard integer types #include // general utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // real-time control clock library #include // general purpose input output library #include // timer utilities #include "global.h" // global utilities #include "microwire_master.h" // microwire header and definitions /** @defgroup microwire_master_gpio GPIO peripheral used to communicate * @{ */ #define MICROWIRE_MASTER_SDO_PIN PA0 /**< SDO output signal pin (to be connected on D slave signal) */ #define MICROWIRE_MASTER_SDI_PIN PA2 /**< SDO input signal pin (to be connected on Q slave signal) */ #define MICROWIRE_MASTER_SCK_PIN PA4 /**< SCK output signal pin (to be connected on C slave signal) */ /** @} */ /** @defgroup microwire_master_timer timer peripheral used to generate timing for the signal * @{ */ #define MICROWIRE_MASTER_TIMER 4 /**< timer peripheral */ /** @} */ /** address size used in operations (slave specific) */ uint8_t mirowire_master_address_size = 0; /** organization used (true=x16, false=x8) */ bool mirowire_master_organization_x16 = true; void microwire_master_setup(uint32_t frequency, bool organization_x16, uint8_t address_size) { // sanity checks if (0 == frequency || 0 == address_size) { return; } mirowire_master_address_size = address_size; // save address size mirowire_master_organization_x16 = organization_x16; // save organisation // setup GPIO rcc_periph_clock_enable(GPIO_RCC(MICROWIRE_MASTER_SDO_PIN)); // enable clock for GPIO domain for SDO signal gpio_set_mode(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // set SDO signal as output (controlled by the master) gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // SDO is idle low rcc_periph_clock_enable(GPIO_RCC(MICROWIRE_MASTER_SDI_PIN)); // enable clock for GPIO domain for SDI signal gpio_set_mode(GPIO_PORT(MICROWIRE_MASTER_SDI_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(MICROWIRE_MASTER_SDI_PIN)); // set SDI signal as output (controlled by the slave) rcc_periph_clock_enable(GPIO_RCC(MICROWIRE_MASTER_SCK_PIN)); // enable clock for GPIO domain for SCK signal gpio_set_mode(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // set SCK signal as output (controlled by the master) gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // SCK is idle low // setup timer to generate timing for the signal rcc_periph_clock_enable(RCC_TIM(MICROWIRE_MASTER_TIMER)); // enable clock for timer domain rcc_periph_reset_pulse(RST_TIM(MICROWIRE_MASTER_TIMER)); // reset timer state timer_set_mode(TIM(MICROWIRE_MASTER_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up uint16_t prescaler = rcc_ahb_frequency / (frequency * 2) / (uint32_t)(1 << 16) + 1; // calculate prescaler for most accurate timing for this speed timer_set_prescaler(TIM(MICROWIRE_MASTER_TIMER), prescaler - 1); // set calculated prescaler uint16_t period = (rcc_ahb_frequency / prescaler) / (frequency * 2); // calculate period to get most accurate timing based on the calculated prescaler timer_set_period(TIM(MICROWIRE_MASTER_TIMER), period - 1); // set calculated period timer_update_on_overflow(TIM(MICROWIRE_MASTER_TIMER)); // only use counter overflow as UEV source (use overflow as timeout) SCB_SCR |= SCB_SCR_SEVONPEND; // enable wake up on event (instead of using ISR) timer_enable_irq(TIM(MICROWIRE_MASTER_TIMER), TIM_DIER_UIE); // enable update interrupt for timer } /** wait for clock tick used to synchronise communication */ static void microwire_master_wait_clock(void) { while ( !timer_get_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF)) { // wait for timer overflow event for clock change __asm__("wfe"); // go to sleep and wait for event } timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up) } /** send bit over microwire * @param[in] bit bit to send (true = '1', false = '0') */ static void microwire_master_send_bit(bool bit) { if (bit) { gpio_set(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // set '1' on output } else { gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // set '0' on output } microwire_master_wait_clock(); // wait for clock timing gpio_set(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // make rising edge for slave to sample microwire_master_wait_clock(); // keep output signal stable while clock is high gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // put clock back to idle } /** initialize microwire communication and send header (with leading start bit '1') * @param[in] operation operation code to send (2 bits) * @param[in] address slave memory address to select */ static void microwire_master_start(uint8_t operation, uint32_t address) { // to sanity checks if (0==mirowire_master_address_size) { // can't send address return; } // initial setup gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is low (to sample on rising edge) timer_set_counter(TIM(MICROWIRE_MASTER_TIMER), 0); // reset timer counter timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up) timer_enable_counter(TIM(MICROWIRE_MASTER_TIMER)); // start timer to generate timing // send '1' start bit microwire_master_send_bit(true); // send start bit // send two bits operation code if (operation & 0x2) { // send first bit (MSb first) microwire_master_send_bit(true); // send '1' } else { microwire_master_send_bit(false); // send '2' } if (operation & 0x1) { // send second bit (LSb last) microwire_master_send_bit(true); // send '1' } else { microwire_master_send_bit(false); // send '2' } // send address for (uint8_t bit = mirowire_master_address_size; bit > 0; bit--) { if ((address >> (bit - 1)) & 0x01) { microwire_master_send_bit(true); // send '1' address bit } else { microwire_master_send_bit(false); // send '0' address bit } } gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // ensure output is idle low (could be floating) } /** stop microwire communication and end all activities */ static void microwire_master_stop(void) { timer_disable_counter(TIM(MICROWIRE_MASTER_TIMER)); // disable timer timer_set_counter(TIM(MICROWIRE_MASTER_TIMER), 0); // reset timer counter timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is idle low gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // ensure output is idle low } /** read bit from microwire communication * @return bit value (true = '1', false = '0') */ static bool microwire_master_read_bit(void) { microwire_master_wait_clock(); // wait for clock timing gpio_set(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // make rising edge for slave to output data microwire_master_wait_clock(); // wait for signal to be stable gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // set clock low again return 0 != gpio_get(GPIO_PORT(MICROWIRE_MASTER_SDI_PIN), GPIO_PIN(MICROWIRE_MASTER_SDI_PIN)); // read input signal } void microwire_master_read(uint32_t address, uint16_t* data, size_t length) { // to sanity checks if (NULL == data || 0 == length || 0 == mirowire_master_address_size) { // can't save data return; } microwire_master_start(0x02, address); // send '10' READ instruction and memory address // there should already be a '0' dummy bit if (0!=gpio_get(GPIO_PORT(MICROWIRE_MASTER_SDI_PIN), GPIO_PIN(MICROWIRE_MASTER_SDI_PIN))) { // the dummy bit wasn't '0' goto clean; } // read data for (size_t i = 0; i < length; i++) { for (uint8_t b = (mirowire_master_organization_x16 ? 16 : 8); b > 0; b--) { if (microwire_master_read_bit()) { // read bit, MSb first data[i] |= (1 << (b - 1)); // set bit } else { data[i] &= ~(1 << (b - 1)); // clear bit } } } clean: microwire_master_stop(); // stop communication and clean up } void microwire_master_write_enable(void) { // to sanity checks if (mirowire_master_address_size < 2) { // can't send '11...' address return; } microwire_master_start(0x0, 0x3 << (mirowire_master_address_size - 2)); // send '00' WEN operation code and '11...' address microwire_master_stop(); // clean up } void microwire_master_write_disable(void) { // to sanity checks if (mirowire_master_address_size < 2) { // can't send '00...' address return; } microwire_master_start(0x0, 0); // send '00' WDS operation code and '00...' address microwire_master_stop(); // clean up } void microwire_master_write(uint32_t address, uint16_t data) { // to sanity checks if (0==mirowire_master_address_size) { // can't send address return; } microwire_master_start(0x01, address); // send '01' WRITE operation code and memory address // write data (MSb first) for (uint8_t b=(mirowire_master_organization_x16 ? 16 : 8); b>0; b--) { if (data&(1<<(b-1))) { // bit is set microwire_master_send_bit(true); // send '1' data bit } else { microwire_master_send_bit(false); // send '0' data bit } } microwire_master_stop(); // clean up } void microwire_master_wait_ready(void) { // initial setup gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is low (to sample on rising edge) timer_set_counter(TIM(MICROWIRE_MASTER_TIMER), 0); // reset timer counter timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up) timer_enable_counter(TIM(MICROWIRE_MASTER_TIMER)); // start timer to generate timing // SDI low on busy, high on ready, clock is ignored while (!microwire_master_read_bit()); // wait until slave is ready microwire_master_stop(); // clean up } void microwire_master_erase(uint32_t address) { // sanity checks if (0==mirowire_master_address_size) { // can't send address return; } microwire_master_start(0x03, address); // send '11' ERASE operation code and memory address microwire_master_stop(); // clean up } void microwire_master_erase_all(void) { // sanity checks if (mirowire_master_address_size < 2) { // can't send '11...' address return; } microwire_master_start(0x00, 0x2 << (mirowire_master_address_size - 2)); // send '00' ERAL operation code and '10...' address microwire_master_stop(); // clean up } void microwire_master_write_all(uint16_t data) { // sanity checks if (0 == mirowire_master_address_size) { // can't send address return; } microwire_master_start(0x00, 0x1 << (mirowire_master_address_size - 2)); // send '00' WRAL operation code and '01...' address // write data (MSb first) for (uint8_t b = (mirowire_master_organization_x16 ? 16 : 8); b > 0; b--) { if (data&(1 << (b - 1))) { // bit is set microwire_master_send_bit(true); // send '1' data bit } else { microwire_master_send_bit(false); // send '0' data bit } } microwire_master_stop(); // clean up }