From aeec7c04905469b65dcd451ad1e8d4d24216534f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?King=20K=C3=A9vin?= Date: Sat, 15 Apr 2017 13:59:01 +0200 Subject: [PATCH] add microwire library --- lib/microwire_master.c | 315 +++++++++++++++++++++++++++++++++++++++++ lib/microwire_master.h | 68 +++++++++ 2 files changed, 383 insertions(+) create mode 100644 lib/microwire_master.c create mode 100644 lib/microwire_master.h diff --git a/lib/microwire_master.c b/lib/microwire_master.c new file mode 100644 index 0000000..bff991d --- /dev/null +++ b/lib/microwire_master.c @@ -0,0 +1,315 @@ +/* This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + * + */ +/** library to communicate using microwore as master (code) + * @file microwire_master.c + * @author King Kévin + * @date 2017 + * @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_PORT A /**< SDO output signal port (to be connected on D slave signal) */ +#define MICROWIRE_MASTER_SDO_PIN 0 /**< SDO output signal pin (to be connected on D slave signal) */ +#define MICROWIRE_MASTER_SDI_PORT A /**< SDO input signal port (to be connected on Q slave signal) */ +#define MICROWIRE_MASTER_SDI_PIN 2 /**< SDO input signal pin (to be connected on Q slave signal) */ +#define MICROWIRE_MASTER_SCK_PORT A /**< SCK output signal port (to be connected on C slave signal) */ +#define MICROWIRE_MASTER_SCK_PIN 4 /**< 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(RCC_GPIO(MICROWIRE_MASTER_SDO_PORT)); // enable clock for GPIO domain for SDO signal + gpio_set_mode(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(MICROWIRE_MASTER_SDO_PIN)); // set SDO signal as output (controlled by the master) + gpio_clear(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO(MICROWIRE_MASTER_SDO_PIN)); // SDO is idle low + rcc_periph_clock_enable(RCC_GPIO(MICROWIRE_MASTER_SDI_PORT)); // enable clock for GPIO domain for SDI signal + gpio_set_mode(GPIO(MICROWIRE_MASTER_SDI_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(MICROWIRE_MASTER_SDI_PIN)); // set SDI signal as output (controlled by the slave) + rcc_periph_clock_enable(RCC_GPIO(MICROWIRE_MASTER_SCK_PORT)); // enable clock for GPIO domain for SCK signal + gpio_set_mode(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(MICROWIRE_MASTER_SCK_PIN)); // set SCK signal as output (controlled by the master) + gpio_clear(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(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 + timer_reset(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_SEVEONPEND; // 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(MICROWIRE_MASTER_SDO_PORT), GPIO(MICROWIRE_MASTER_SDO_PIN)); // set '1' on output + } else { + gpio_clear(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO(MICROWIRE_MASTER_SDO_PIN)); // set '0' on output + } + microwire_master_wait_clock(); // wait for clock timing + gpio_set(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(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(MICROWIRE_MASTER_SCK_PORT), GPIO(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(MICROWIRE_MASTER_SCK_PORT), GPIO(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(MICROWIRE_MASTER_SDO_PORT), GPIO(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(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is idle low + gpio_clear(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO(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(MICROWIRE_MASTER_SCK_PORT), GPIO(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(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // set clock low again + return 0!=gpio_get(GPIO(MICROWIRE_MASTER_SDI_PORT), GPIO(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(MICROWIRE_MASTER_SDI_PORT), GPIO(MICROWIRE_MASTER_SDI_PIN))) { // the dummy bit wasn't '0' + goto clean; + } + + // read data + for (size_t i=0; i0; 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(MICROWIRE_MASTER_SCK_PORT), GPIO(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 +} diff --git a/lib/microwire_master.h b/lib/microwire_master.h new file mode 100644 index 0000000..6a6e47d --- /dev/null +++ b/lib/microwire_master.h @@ -0,0 +1,68 @@ +/* This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + * + */ +/** library to communicate using microwore as master (API) + * @file microwire_master.h + * @author King Kévin + * @date 2017 + * @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. + */ +#pragma once + +/** setup microwire peripheral + * @param[in] frequency clock frequency in Hz + * @param[in] organization_x16 if x16 memory organization (16-bits) is used, or x8 (8-bits) + * @param[in] address_size address size in bits + * @note frequency practically limited to 500 kHz due to the software implementation nature + */ +void microwire_master_setup(uint32_t frequency, bool organization_x16, uint8_t address_size); +/** read data from slave memory + * @param[in] address memory address of data to read + * @param[out] data array to store read data + * @param[in] length number of data bytes/words to read + */ +void microwire_master_read(uint32_t address, uint16_t* data, size_t length); +/** enable write and erase operations + * @note on slave boot write is disable to prevent corruption + */ +void microwire_master_write_enable(void); +/** disable write and erase operations + * @note this should be done after every complete write operation to protect against corruption + */ +void microwire_master_write_disable(void); +/** write data to slave memory + * @param[in] address memory address of data to read + * @param[in] data byte/word to write + * @note after each write and before the next operation user should wait for the slave to be ready + */ +void microwire_master_write(uint32_t address, uint16_t data); +/** wait until slave is ready after a write or erase */ +void microwire_master_wait_ready(void); +/** erase memory + * @param[in] address memory address of data to read + * @note after each erase and before the next operation user should wait for the slave to be ready + */ +void microwire_master_erase(uint32_t address); +/** erase all memory + * @note after each erase and before the next operation user should wait for the slave to be ready + */ +void microwire_master_erase_all(void); +/** write data to all slave memory + * @param[in] data byte/word to write + * @note after each write and before the next operation user should wait for the slave to be ready + */ +void microwire_master_write_all(uint16_t data);