diff --git a/core/esp_spi.c b/core/esp_spi.c index 47dcd66..84b3cb0 100644 --- a/core/esp_spi.c +++ b/core/esp_spi.c @@ -297,6 +297,41 @@ size_t spi_transfer(uint8_t bus, const void *out_data, void *in_data, size_t len return len; } +static void _spi_buf_read(uint8_t bus, uint8_t b, void *in_data, + size_t len, spi_endianness_t e, spi_word_size_t word_size) +{ + _wait(bus); + size_t bytes = len * (uint8_t)word_size; + _set_size(bus, bytes); + uint32_t w = ((uint32_t)b << 24) | ((uint32_t)b << 16) | ((uint32_t)b << 8) | b; + for (uint8_t i = 0; i < _SPI_BUF_SIZE / 4; i ++) + SPI(bus).W[i] = w; + _start(bus); + _wait(bus); + _spi_buf_prepare(bus, len, e, word_size); + memcpy(in_data, (void *)SPI(bus).W, bytes); +} + +void spi_read(uint8_t bus, uint8_t out_byte, void *in_data, size_t len, spi_word_size_t word_size) +{ + spi_endianness_t e = spi_get_endianness(bus); + uint8_t buf_size = _SPI_BUF_SIZE / (uint8_t)word_size; + + size_t blocks = len / buf_size; + for (size_t i = 0; i < blocks; i++) + { + size_t offset = i * _SPI_BUF_SIZE; + _spi_buf_read(bus, out_byte, (uint8_t *)in_data + offset, buf_size, e, word_size); + _rearm_extras_bit(bus, false); + } + + uint8_t tail = len % buf_size; + if (tail) + _spi_buf_read(bus, out_byte, (uint8_t *)in_data + blocks * _SPI_BUF_SIZE, tail, e, word_size); + + if (blocks) _rearm_extras_bit(bus, true); +} + static void _repeat_send(uint8_t bus, uint32_t *dword, int32_t *repeats, spi_word_size_t size) { diff --git a/core/include/esp/spi.h b/core/include/esp/spi.h index ff77c79..9ce68fb 100644 --- a/core/include/esp/spi.h +++ b/core/include/esp/spi.h @@ -400,6 +400,16 @@ void spi_repeat_send_16(uint8_t bus, uint16_t data, int32_t repeats); */ void spi_repeat_send_32(uint8_t bus, uint32_t data, int32_t repeats); +/** + * \brief Repeatedly send byte over SPI and receive data + * \param bus Bus ID: 0 - system, 1 - user + * \param out_byte Byte to send + * \param in_data Receive buffer + * \param len Buffer size in words + * \param word_size Size of the word + */ +void spi_read(uint8_t bus, uint8_t out_byte, void *in_data, size_t len, spi_word_size_t word_size); + #ifdef __cplusplus } #endif diff --git a/extras/fram/component.mk b/extras/fram/component.mk new file mode 100644 index 0000000..3878e09 --- /dev/null +++ b/extras/fram/component.mk @@ -0,0 +1,9 @@ +# Component makefile for extras/fram + +# expected anyone using ADC driver includes it as 'fram/fram.h' +INC_DIRS += $(fram_ROOT).. + +# args for passing into compile rule generation +fram_SRC_DIR = $(fram_ROOT) + +$(eval $(call component_compile_rules,fram)) diff --git a/extras/fram/fram.c b/extras/fram/fram.c new file mode 100644 index 0000000..2192bb1 --- /dev/null +++ b/extras/fram/fram.c @@ -0,0 +1,183 @@ +/** + * Driver for serial nonvolatile ferroelectric random access + * memory or F-RAM. + * + * Part of esp-open-rtos + * Copyright (C) 2017 Ruslan V. Uss + * BSD Licensed as described in the file LICENSE + */ +#include "fram.h" + +#include + +#define CMD_WRSR 0x01 // 0b00000001 +#define CMD_WRITE 0x02 // 0b00000010 +#define CMD_READ 0x03 // 0b00000011 +#define CMD_WRDI 0x04 // 0b00000100 +#define CMD_RDSR 0x05 // 0b00000101 +#define CMD_WREN 0x06 // 0b00000110 +#define CMD_FSTRD 0x0b // 0b00001011 +#define CMD_RDID 0x9f // 0b10011111 +#define CMD_SLEEP 0xb9 // 0b10111001 +#define CMD_SNR 0xc3 // 0b11000011 + +#define SR_BIT_WEL 1 +#define SR_BIT_BP0 2 +#define SR_BIT_BP1 3 +#define SR_BIT_WPEN 7 + +#define SR_MASK_BP (0x03 << SR_BIT_BP0) + +static const spi_settings_t defaults = { + .endianness = SPI_BIG_ENDIAN, + .msb = true, + .mode = SPI_MODE0, + .minimal_pins = true, + .freq_divider = SPI_FREQ_DIV_40M +}; + +inline static void chip_select(const fram_t *dev) +{ + gpio_write(dev->cs_gpio, false); +} + +inline static void chip_unselect(const fram_t *dev) +{ + gpio_write(dev->cs_gpio, true); +} + +static uint8_t read_status_reg(const fram_t *dev) +{ + chip_select(dev); + spi_transfer_8(dev->spi_bus, CMD_RDSR); + uint8_t res = spi_transfer_8(dev->spi_bus, 0xff); + chip_unselect(dev); + return res; +} + +static void write_status_reg(const fram_t *dev, uint8_t val) +{ + chip_select(dev); + spi_transfer_8(dev->spi_bus, CMD_WREN); + chip_unselect(dev); + + chip_select(dev); + spi_transfer_8(dev->spi_bus, CMD_WRSR); + spi_transfer_8(dev->spi_bus, val); +} + +void fram_init(const fram_t *dev) +{ + gpio_enable(dev->cs_gpio, GPIO_OUTPUT); + gpio_set_pullup(dev->cs_gpio, true, true); + chip_unselect(dev); +} + +static void begin(const fram_t *dev, spi_settings_t *s, spi_settings_t *old) +{ + spi_get_settings(dev->spi_bus, &old); + memcpy(&s, &defaults, sizeof(spi_settings_t)); + s->freq_divider = dev->spi_freq_div; + spi_set_settings(dev->spi_bus, &s); + chip_select(dev); +} + +static void end(const fram_t *dev, spi_settings_t *old) +{ + chip_unselect(dev); + spi_set_settings(dev->spi_bus, &old); +} + +void fram_read(const fram_t *dev, void *to, void *from, size_t size) +{ + spi_settings_t s, old; + begin(dev, &s, &old); + + uint32_t header = ((uint32_t)CMD_READ << 24) | ((uint32_t)from & 0x00ffffff); + spi_transfer_32(dev->spi_bus, header); + + spi_set_endianness(dev->spi_bus, SPI_LITTLE_ENDIAN); + spi_read(dev->spi_bus, 0xff, to, size, SPI_8BIT); + + end(dev, &old); +} + +void fram_write(const fram_t *dev, void *from, void *to, size_t size) +{ + spi_settings_t s, old; + begin(dev, &s, &old); + + spi_transfer_8(dev->spi_bus, CMD_WREN); + chip_unselect(dev); + + chip_select(dev); + uint32_t header = ((uint32_t)CMD_WRITE << 24) | ((uint32_t)to & 0x00ffffff); + spi_transfer_32(dev->spi_bus, header); + + spi_set_endianness(dev->spi_bus, SPI_LITTLE_ENDIAN); + spi_transfer(dev->spi_bus, from, NULL, size, SPI_8BIT); + + end(dev, &old); +} + +void fram_sleep(const fram_t *dev, bool sleep) +{ + if (!sleep) + { + chip_select(dev); + chip_unselect(dev); + return; + } + + spi_settings_t s, old; + begin(dev, &s, &old); + + spi_transfer_8(dev->spi_bus, CMD_SLEEP); + + end(dev, &old); +} + +bool fram_busy(const fram_t *dev) +{ + gpio_enable(dev->cs_gpio, GPIO_INPUT); + bool res = !gpio_read(dev->cs_gpio); + fram_init(dev); + return res; +} + +void fram_id(const fram_t *dev, fram_id_t *id) +{ + spi_settings_t s, old; + begin(dev, &s, &old); + + spi_transfer_8(dev->spi_bus, CMD_RDID); + + for (uint8_t i = 0; i < FRAM_ID_LEN; i ++) + id->data[FRAM_ID_LEN - i - 1] = spi_transfer_8(dev->spi_bus, 0xff); + + end(dev, &old); +} + +void fram_set_wp_mode(const fram_t *dev, fram_wp_mode_t mode) +{ + spi_settings_t s, old; + begin(dev, &s, &old); + + write_status_reg(dev, (read_status_reg(dev) & ~SR_MASK_BP) | ((mode & 0x03) << SR_BIT_BP0)); + + end(dev, &old); +} + +fram_wp_mode_t fram_get_wp_mode(const fram_t *dev) +{ + spi_settings_t s, old; + begin(dev, &s, &old); + + fram_wp_mode_t res = (fram_wp_mode_t)((read_status_reg(dev) & SR_MASK_BP) >> SR_BIT_BP0); + + end(dev, &old); + + return res; +} + + diff --git a/extras/fram/fram.h b/extras/fram/fram.h new file mode 100644 index 0000000..d0eb429 --- /dev/null +++ b/extras/fram/fram.h @@ -0,0 +1,124 @@ +/** + * Driver for serial nonvolatile ferroelectric random access + * memory or F-RAM. + * + * Part of esp-open-rtos + * Copyright (C) 2017 Ruslan V. Uss + * BSD Licensed as described in the file LICENSE + */ +#ifndef EXTRAS_FRAM_H_ +#define EXTRAS_FRAM_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FRAM_ID_LEN 9 + +/** + * F-RAM device descriptor + */ +typedef struct +{ + uint8_t spi_bus; //!< SPI bus + uint8_t cs_gpio; //!< chip select GPIO + uint32_t spi_freq_div; //!< SPI bus frequency divider +} fram_t; + +/** + * F-RAM device ID + */ +typedef struct +{ + uint8_t data[FRAM_ID_LEN]; + union + { + uint8_t rsvd: 3; + uint8_t rev: 3; + uint8_t sub: 2; + uint8_t density: 5; + uint8_t family: 3; + uint8_t manufacturer[FRAM_ID_LEN - 2]; + }; +} fram_id_t; + + +/** + * Write protection mode + */ +typedef enum +{ + FRAM_WP_NONE = 0, //!< No write protection + FRAM_WP_UPPER_QUARTER,//!< Upper 1/4 write protection + FRAM_WP_UPPER_HALF, //!< Upper 1/2 write protection + FRAM_WP_ALL //!< All memory write protection +} fram_wp_mode_t; + +/** + * Prepare to read/write F-RAM + * @param dev Pointer to device descriptor + */ +void fram_init(const fram_t *dev); + +/** + * Read data from F-RAM + * @param dev Pointer to device descriptor + * @param to Buffer to store data + * @param from F-RAM address + * @param size Bytes to read + */ +void fram_read(const fram_t *dev, void *to, void *from, size_t size); + +/** + * Write data to F-RAM + * @param dev Pointer to device descriptor + * @param from Data buffer + * @param to F-RAM address + * @param size Bytes to write + */ +void fram_write(const fram_t *dev, void *from, void *to, size_t size); + +/** + * Set device to sleep mode or wake up + * @param dev Pointer to device descriptor + * @param sleep Set to sleep mode when true, wake up otherwise + */ +void fram_sleep(const fram_t *dev, bool sleep); + +/** + * Check if F-RAM busy with another SPI master. + * To use this method of sharing F-RAM between two masters + * you'll need to pull up CS GPIO line + * @param dev Pointer to device descriptor + * @return true when device is busy + */ +bool fram_busy(const fram_t *dev); + +/** + * Read the device ID + * @param dev Pointer to device descriptor + * @param id Poiner to device ID structure + */ +void fram_id(const fram_t *dev, fram_id_t *id); + +/** + * Set block write protection mode + * @param dev Pointer to device descriptor + * @param mode Write protection mode + */ +void fram_set_wp_mode(const fram_t *dev, fram_wp_mode_t mode); + +/** + * Get current write protection mode + * @param dev Pointer to device descriptor + * @return Write protection mode + */ +fram_wp_mode_t fram_get_wp_mode(const fram_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* EXTRAS_FRAM_H_ */