1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-18 12:52:44 +01:00

Merge pull request #11945 from benpicco/EEPROM-m95m01

drivers: add driver for AT25xxx family of EEPROMs
This commit is contained in:
Alexandre Abadie 2020-04-07 13:19:21 +02:00 committed by GitHub
commit 067b324645
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 627 additions and 0 deletions

View File

@ -55,6 +55,11 @@ ifneq (,$(filter at24mac,$(USEMODULE)))
USEMODULE += at24cxxx
endif
ifneq (,$(filter at25xxx,$(USEMODULE)))
FEATURES_REQUIRED += periph_spi
USEMODULE += xtimer
endif
ifneq (,$(filter at30tse75x,$(USEMODULE)))
USEMODULE += xtimer
FEATURES_REQUIRED += periph_i2c

View File

@ -32,6 +32,10 @@ ifneq (,$(filter at24mac,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/at24mac/include
endif
ifneq (,$(filter at25xxx,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/at25xxx/include
endif
ifneq (,$(filter at86rf2xx,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/at86rf2xx/include
endif

1
drivers/at25xxx/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

228
drivers/at25xxx/at25xxx.c Normal file
View File

@ -0,0 +1,228 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_at25xxx
* @{
*
* @file
* @brief Driver for the AT25xxx family of SPI-EEPROMs.
* This also includes M95xxx, 25AAxxx, 25LCxxx,
* CAT25xxx & BR25Sxxx.
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include "at25xxx.h"
#include "at25xxx_constants.h"
#include "at25xxx_params.h"
#include "byteorder.h"
#include "xtimer.h"
#define POLL_DELAY_US (1000)
#ifndef min
#define min(a, b) ((a) > (b) ? (b) : (a))
#endif
#define PAGE_SIZE (dev->params.page_size)
#define ADDR_LEN (AT25XXX_PARAM_ADDR_LEN)
#define ADDR_MSK ((1UL << ADDR_LEN) - 1)
#ifndef AT25XXXX_SET_BUF_SIZE
/**
* @brief Adjust to configure buffer size
*/
#define AT225XXXX_SET_BUF_SIZE (64)
#endif
static inline int getbus(const at25xxx_t *dev)
{
return spi_acquire(dev->params.spi, dev->params.cs_pin, SPI_MODE_0, dev->params.spi_clk);
}
static inline uint32_t _pos(uint8_t cmd, uint32_t pos)
{
/* first byte is CMD, then addr with MSB first */
pos = htonl((pos & ADDR_MSK) | ((uint32_t)cmd << ADDR_LEN));
pos >>= 8 * sizeof(pos) - (ADDR_LEN + 8);
return pos;
}
static inline bool _write_in_progress(const at25xxx_t *dev)
{
return spi_transfer_reg(dev->params.spi, dev->params.cs_pin, CMD_RDSR, 0) & SR_WIP;
}
static inline bool _write_enabled(const at25xxx_t *dev)
{
return spi_transfer_reg(dev->params.spi, dev->params.cs_pin, CMD_RDSR, 0) & SR_WEL;
}
static inline int _wait_until_eeprom_ready(const at25xxx_t *dev)
{
uint8_t tries = 10;
while (_write_in_progress(dev) && --tries) {
spi_release(dev->params.spi);
xtimer_usleep(POLL_DELAY_US);
getbus(dev);
}
return tries == 0 ? -ETIMEDOUT : 0;
}
static ssize_t _write_page(const at25xxx_t *dev, uint32_t pos, const void *data, size_t len)
{
/* write no more than to the end of the current page to prevent wrap-around */
size_t remaining = PAGE_SIZE - (pos & (PAGE_SIZE - 1));
len = min(len, remaining);
pos = _pos(CMD_WRITE, pos);
/* wait for previous write to finish - may take up to 5 ms */
int res = _wait_until_eeprom_ready(dev);
if (res) {
return res;
}
/* set write enable and wait for status change */
spi_transfer_byte(dev->params.spi, dev->params.cs_pin, false, CMD_WREN);
unsigned tries = 1000;
while (!_write_enabled(dev) && --tries) {}
if (tries == 0) {
return -ETIMEDOUT;
}
/* write the data */
spi_transfer_bytes(dev->params.spi, dev->params.cs_pin, true, &pos, NULL, 1 + ADDR_LEN / 8);
spi_transfer_bytes(dev->params.spi, dev->params.cs_pin, false, data, NULL, len);
return len;
}
int at25xxx_write(const at25xxx_t *dev, uint32_t pos, const void *data, size_t len)
{
int res = 0;
const uint8_t *d = data;
if (pos + len > dev->params.size) {
return -ERANGE;
}
getbus(dev);
while (len) {
ssize_t written = _write_page(dev, pos, d, len);
if (written < 0) {
res = written;
break;
}
len -= written;
pos += written;
d += written;
}
spi_release(dev->params.spi);
return res;
}
void at25xxx_write_byte(const at25xxx_t *dev, uint32_t pos, uint8_t data)
{
at25xxx_write(dev, pos, &data, sizeof(data));
}
int at25xxx_read(const at25xxx_t *dev, uint32_t pos, void *data, size_t len)
{
if (pos + len > dev->params.size) {
return -ERANGE;
}
getbus(dev);
/* wait for previous write to finish - may take up to 5 ms */
int res = _wait_until_eeprom_ready(dev);
if (res) {
return res;
}
pos = _pos(CMD_READ, pos);
spi_transfer_bytes(dev->params.spi, dev->params.cs_pin, true, &pos, NULL, 1 + ADDR_LEN / 8);
spi_transfer_bytes(dev->params.spi, dev->params.cs_pin, false, NULL, data, len);
spi_release(dev->params.spi);
return 0;
}
uint8_t at25xxx_read_byte(const at25xxx_t *dev, uint32_t pos)
{
uint8_t b;
at25xxx_read(dev, pos, &b, sizeof(b));
return b;
}
int at25xxx_set(const at25xxx_t *dev, uint32_t pos, uint8_t val, size_t len)
{
uint8_t data[AT225XXXX_SET_BUF_SIZE];
size_t total = 0;
if (pos + len > dev->params.size) {
return -ERANGE;
}
memset(data, val, sizeof(data));
getbus(dev);
while (len) {
size_t written = _write_page(dev, pos, data, min(len, sizeof(data)));
len -= written;
pos += written;
total += written;
}
spi_release(dev->params.spi);
return 0;
}
int at25xxx_clear(const at25xxx_t *dev, uint32_t pos, size_t len)
{
return at25xxx_set(dev, pos, 0, len);
}
int at25xxx_init(at25xxx_t *dev, const at25xxx_params_t *params)
{
dev->params = *params;
spi_init_cs(dev->params.spi, dev->params.cs_pin);
if (dev->params.wp_pin != GPIO_UNDEF) {
gpio_init(dev->params.wp_pin, GPIO_OUT);
gpio_set(dev->params.wp_pin);
}
if (dev->params.hold_pin != GPIO_UNDEF) {
gpio_init(dev->params.hold_pin, GPIO_OUT);
gpio_set(dev->params.hold_pin);
}
/* check if the SPI configuration is valid */
if (getbus(dev) != SPI_OK) {
return -1;
}
spi_release(dev->params.spi);
return 0;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_at25xxx
* @{
*
* @file
* @brief Commands for the AT25xxx family of SPI-EEPROMs
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#ifndef AT25XXX_CONSTANTS_H
#define AT25XXX_CONSTANTS_H
#ifdef __cplusplus
extern "C" {
#endif
#define CMD_WREN (0x6) /**< Write Enable */
#define CMD_WRDI (0x4) /**< Write Disable */
#define CMD_RDSR (0x5) /**< Read Status Register */
#define CMD_WRSR (0x1) /**< Write Status Register */
#define CMD_READ (0x3) /**< Read from Memory Array */
#define CMD_WRITE (0x2) /**< Write to Memory Array */
#define SR_WIP (0x01) /**< Write In Progress */
#define SR_WEL (0x02) /**< Write Enable Latch */
#define SR_BP0 (0x04) /**< Block Protect 0 */
#define SR_BP1 (0x08) /**< Block Protect 1 */
#define SR_SRWD (0x80) /**< Status Register Write Disable */
#ifdef __cplusplus
}
#endif
#endif /* AT25XXX_CONSTANTS_H */

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_at25xxx
* @{
*
* @file
* @brief Default configuration for the M95M01 EEPROM
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#ifndef AT25XXX_PARAMS_H
#define AT25XXX_PARAMS_H
#include "board.h"
#include "at25xxx.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Set default configuration parameters for the AT25XXX driver
* @{
*/
#ifndef AT25XXX_PARAM_SPI
#define AT25XXX_PARAM_SPI (SPI_DEV(0))
#endif
#ifndef AT25XXX_PARAM_SPI_CLK
#define AT25XXX_PARAM_SPI_CLK (SPI_CLK_5MHZ)
#endif
#ifndef AT25XXX_PARAM_CS
#define AT25XXX_PARAM_CS (GPIO_PIN(0, 0))
#endif
#ifndef AT25XXX_PARAM_WP
#define AT25XXX_PARAM_WP (GPIO_UNDEF)
#endif
#ifndef AT25XXX_PARAM_HOLD
#define AT25XXX_PARAM_HOLD (GPIO_UNDEF)
#endif
#ifndef AT25XXX_PARAM_SIZE
#define AT25XXX_PARAM_SIZE (128 * 1024UL) /* EEPROM size, in bytes */
#endif
#ifndef AT25XXX_PARAM_ADDR_LEN
#define AT25XXX_PARAM_ADDR_LEN (24) /* Address length, in bits */
#endif
#ifndef AT25XXX_PARAM_PAGE_SIZE
#define AT25XXX_PARAM_PAGE_SIZE (256) /* Page size, in bytes */
#endif
#ifndef AT25XXX_PARAMS
#define AT25XXX_PARAMS { .spi = AT25XXX_PARAM_SPI, \
.spi_clk = AT25XXX_PARAM_SPI_CLK, \
.cs_pin = AT25XXX_PARAM_CS, \
.wp_pin = AT25XXX_PARAM_WP, \
.hold_pin = AT25XXX_PARAM_HOLD, \
.size = AT25XXX_PARAM_SIZE, \
.page_size = AT25XXX_PARAM_PAGE_SIZE }
#endif
/**
* @brief AT25XXX configuration
*/
static const at25xxx_params_t at25xxx_params[] =
{
AT25XXX_PARAMS
};
#ifdef __cplusplus
}
#endif
#endif /* AT25XXX_PARAMS_H */

144
drivers/include/at25xxx.h Normal file
View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup drivers_at25xxx AT25xxx family of SPI-EEPROMs
* @ingroup drivers_misc
*
* @brief This driver also support M95xxx, 25AAxxx, 25LCxxx, CAT25xxx & BR25Sxxx families
* @{
*
* @file
* @brief Driver for the AT25xxx series of EEPROMs
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#ifndef AT25XXX_H
#define AT25XXX_H
#include <stdint.h>
#include <stdbool.h>
#include "periph/spi.h"
#include "periph/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief struct holding all params needed for device initialization
*/
typedef struct {
spi_t spi; /**< SPI bus the device is connected to */
spi_clk_t spi_clk; /**< SPI clock speed to use */
spi_cs_t cs_pin; /**< GPIO pin connected to chip select */
gpio_t wp_pin; /**< GPIO pin connected to the write-protect pin */
gpio_t hold_pin; /**< GPIO pin connected to the hold pin */
uint32_t size; /**< Size of the EEPROM in bytes */
uint16_t page_size; /**< Page Size of the EEPROM in bytes */
} at25xxx_params_t;
/**
* @brief struct that represents an AT25XXX device
*/
typedef struct {
at25xxx_params_t params; /**< parameters */
} at25xxx_t;
/**
* @brief Initialize an AT25XXX device handle with AT25XXX parameters
*
* @param[in, out] dev AT25XXX device handle
* @param[in] params AT25XXX parameters
*
* @return 0 on success, -1 on failure
*
*/
int at25xxx_init(at25xxx_t *dev, const at25xxx_params_t *params);
/**
* @brief Read a byte at a given position @p pos
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
*
* @return read byte
*/
uint8_t at25xxx_read_byte(const at25xxx_t *dev, uint32_t pos);
/**
* @brief Sequentially read @p len bytes from a given position @p pos
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[out] data read buffer
* @param[in] len requested length to be read
*
* @return 0 on success
* @return -ERANGE if pos + len > EEPROM size
*/
int at25xxx_read(const at25xxx_t *dev, uint32_t pos, void *data, size_t len);
/**
* @brief Write a byte at a given position @p pos
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[in] data value to be written
*/
void at25xxx_write_byte(const at25xxx_t *dev, uint32_t pos, uint8_t data);
/**
* @brief Sequentially write @p len bytes from a given position @p pos
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[in] data write buffer
* @param[in] len requested length to be written
*
* @return 0 on success
* @return -ERANGE if pos + len > EEPROM size
*/
int at25xxx_write(const at25xxx_t *dev, uint32_t pos, const void *data, size_t len);
/**
* @brief Set @p len bytes from a given position @p pos to the
* value @p val
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[in] val value to be set
* @param[in] len requested length to be written
*
* @return 0 on success
* @return -ERANGE if pos + len > EEPROM size
*/
int at25xxx_set(const at25xxx_t *dev, uint32_t pos, uint8_t val, size_t len);
/**
* @brief Set @p len bytes from position @p pos to 0
*
* This is a wrapper around @see at25xxx_set.
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[in] len requested length to be written
*
* @return 0 on success
* @return -ERANGE if pos + len > EEPROM size
*/
int at25xxx_clear(const at25xxx_t *dev, uint32_t pos, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* AT25XXX_H */

View File

@ -0,0 +1,6 @@
include ../Makefile.tests_common
USEMODULE += at25xxx
USEMODULE += embunit
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,8 @@
# About
This is a manual test application for the AT25XXX EEPROM driver.
# Usage
This test application will initialize the EEPROM with its default parameters.
After initialization data will be written to the EEPROM and read back to be
checked for integrity.

106
tests/driver_at25xxx/main.c Normal file
View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Test application for the AT25XXX EEPROM driver
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* @}
*/
#include <stdio.h>
#include <string.h>
#include "embUnit.h"
#include "at25xxx.h"
#include "at25xxx_params.h"
static at25xxx_t dev;
static void test_normal_write(void)
{
const char data_in_a[] = "Hello EEPROM!";
const char data_in_b[] = "This is a test.";
char data_out[32];
TEST_ASSERT_EQUAL_INT(0, at25xxx_write(&dev, 0, data_in_a, sizeof(data_in_a)));
TEST_ASSERT_EQUAL_INT(0, at25xxx_read(&dev, 0, data_out, sizeof(data_out)));
TEST_ASSERT_EQUAL_STRING(data_in_a, data_out);
TEST_ASSERT_EQUAL_INT(0, at25xxx_write(&dev, 0, data_in_b, sizeof(data_in_b)));
TEST_ASSERT_EQUAL_INT(0, at25xxx_read(&dev, 0, data_out, sizeof(data_out)));
TEST_ASSERT_EQUAL_STRING(data_in_b, data_out);
}
static void test_page_write(void)
{
const char data_in_a[] = "Hello EEPROM!";
const char data_in_b[] = "This is a test.";
char data_out[32];
TEST_ASSERT_EQUAL_INT(0, at25xxx_write(&dev, AT25XXX_PARAM_PAGE_SIZE - 5, data_in_a, sizeof(data_in_a)));
TEST_ASSERT_EQUAL_INT(0, at25xxx_read(&dev, AT25XXX_PARAM_PAGE_SIZE - 5, data_out, sizeof(data_out)));
TEST_ASSERT_EQUAL_STRING(data_in_a, data_out);
TEST_ASSERT_EQUAL_INT(0, at25xxx_write(&dev, AT25XXX_PARAM_PAGE_SIZE - 5, data_in_b, sizeof(data_in_b)));
TEST_ASSERT_EQUAL_INT(0, at25xxx_read(&dev, AT25XXX_PARAM_PAGE_SIZE - 5, data_out, sizeof(data_out)));
TEST_ASSERT_EQUAL_STRING(data_in_b, data_out);
}
static void test_page_clear(void)
{
const char data_in_a[] = "Hello EEPROM!";
char data_out[32];
char data_clr[32];
memset(data_clr, 0, sizeof(data_clr));
TEST_ASSERT_EQUAL_INT(0, at25xxx_write(&dev, AT25XXX_PARAM_PAGE_SIZE - 5, data_in_a, sizeof(data_in_a)));
TEST_ASSERT_EQUAL_INT(0, at25xxx_read(&dev, AT25XXX_PARAM_PAGE_SIZE - 5, data_out, sizeof(data_out)));
TEST_ASSERT_EQUAL_STRING(data_in_a, data_out);
TEST_ASSERT_EQUAL_INT(0, at25xxx_clear(&dev, AT25XXX_PARAM_PAGE_SIZE - 5, sizeof(data_out)));
TEST_ASSERT_EQUAL_INT(0, at25xxx_read(&dev, AT25XXX_PARAM_PAGE_SIZE - 5, data_out, sizeof(data_out)));
TEST_ASSERT_EQUAL_INT(0, memcmp(data_out, data_clr, sizeof(data_clr)));
}
static Test *tests_EEPROM_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_normal_write),
new_TestFixture(test_page_write),
new_TestFixture(test_page_clear),
};
EMB_UNIT_TESTCALLER(EEPROM_tests, NULL, NULL, fixtures);
return (Test *)&EEPROM_tests;
}
int main(void)
{
puts("AT25XXX EEPROM driver test application\n");
at25xxx_init(&dev, &at25xxx_params[0]);
printf("EEPROM size: %u kiB\n", (unsigned) (dev.params.size / 1024));
printf("EEPROM page size: %d bytes\n", AT25XXX_PARAM_PAGE_SIZE);
printf("EEPROM address length: %d bits\n\n", AT25XXX_PARAM_ADDR_LEN);
TESTS_START();
TESTS_RUN(tests_EEPROM_tests());
TESTS_END();
return 0;
}