1
votes

I'm trying to write and read from an external EEPROM. There is a start bit (SB) followed by an opcode, then a 6-bit address and then the actual data. I've combined the SB and opcode into one byte that I can send as a start condition. I'm able to enable, erase and then write to the EEPROM. I'm assuming this is working since the HAL functions return HAL_OK and I can see the valid waveforms on the scope.

What I can't seem to do is read the data back. For the READ operation I don't see any waveforms on the scope. The number of clock cycles required is odd-numbered and not in multiples of 8. I don't know how I can send odd number of clock cycles since all the data is either 8, 16 or 32-bit. Wherever there are 25 or 29 clock cycles need, I seem to be sending 32 and where the required cycles are 9, I seem to be sending 16. I'm really hoping to avoid bit-banging as suggested in this thread.

Here is the main code:

int main(void)
{
  HAL_Init();
  MX_GPIO_Init();
  MX_SPI1_Init();
  __HAL_SPI_ENABLE(&hspi1);

  // pull the CS pin high to select the EEPROM (active HIGH)
  HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
  HAL_Delay(10);

  // Enable the EEPROM
  enable_status = Enable_EEPROM(&EEPROM_SPI_PORT);
  HAL_Delay(10);

  // Erase the value at address 0x00
  erase_status = Erase_EEPROM(&EEPROM_SPI_PORT, addr);
  HAL_Delay(10);

  // Write data 0xABCD at addr 0x00
  write_status = Write_EEPROM(&EEPROM_SPI_PORT, addr, tx_data);
  HAL_Delay(10);

  // Disabling the EEPROM (with an EWDS) after a WRITE as described in the datasheet
  disable_status = Disable_EEPROM(&EEPROM_SPI_PORT);
  HAL_Delay(10);

  // Re-enabling it
  enable_status = Enable_EEPROM(&EEPROM_SPI_PORT);
  HAL_Delay(10);

  // Read from the EEPROM. This part isn't working.
  read_status = Read_EEPROM(&EEPROM_SPI_PORT, addr, rx_data);
  HAL_Delay(10);

  // Pull the CS pin low to deselect the chip again.
  HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);


  while (1)
  {

  }
}

The SPI is initialized to handle 16-bit data values

SPI_HandleTypeDef hspi1;

/* SPI1 init function */
void MX_SPI1_Init(void)
{

  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

These are the EEPROM functions

#define ERASE   0x07 // erase specific memory location. This is followed by the 8-bit address and then by the 16-bit data.
#define READ    0x06 // read the memory location.
#define WRITE   0x05 // write to the memory location

#define EEPROM_SPI_PORT hspi1
extern SPI_HandleTypeDef EEPROM_SPI_PORT;


//Enable the EEPROM
//Accepts: SPI handle
//Returns: Success or failure of the enable operation
uint8_t Enable_EEPROM (SPI_TypeDef *spi_handle) {
    uint16_t ewen = (0x04 << 8) | 0b00110000;
    if (HAL_SPI_Transmit(spi_handle, &ewen, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
    else return FALSE;
}

//Disable the EEPROM
//Accepts: SPI handle
//Returns: Success or failure of the disable operation
uint8_t Disable_EEPROM (SPI_TypeDef *spi_handle) {
    uint16_t ewds = (0x04 << 8) | 0b00000000;
    if (HAL_SPI_Transmit(spi_handle, &ewds, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
    else return FALSE;
}

//Read from the EEPROM
//Accepts: SPI handle, memory address and data buffer where the read value will be stored
//Returns: Success or failure of read operation
uint8_t Read_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint16_t data) {
    uint16_t write_package;
    write_package = (READ << 8 | addr);
//  if (HAL_SPI_Transmit(spi_handle, &write_package, 1, HAL_MAX_DELAY) == HAL_OK) {
//      HAL_Delay(10);
//      if (HAL_SPI_Receive(spi_handle, &data, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
//      else return FALSE;
//  }
    if (HAL_SPI_TransmitReceive(spi_handle, &write_package, &data, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
    else return FALSE;
}

//Write to the EEPROM
//Accepts: SPI handle, memory address and data to be written
//Returns: Success or failure of write operation
uint8_t Write_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint16_t data) {
    uint16_t write_package[2];
    write_package[0] = (WRITE << 8 | addr);
    write_package[1] = data;
    if (HAL_SPI_Transmit(spi_handle, write_package, 2, HAL_MAX_DELAY) == HAL_OK) return TRUE;
    else return FALSE;
}

//Erase a specific memory address from the EEPROM
//Accepts: SPI handle and the memory address to be erased
//Returns: Success or failure of erase operation
uint8_t Erase_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr) {
    uint16_t write_package;
    write_package = (ERASE << 8 | addr);
    if (HAL_SPI_Transmit(spi_handle, &write_package, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
    else return FALSE;
}

EDIT: I’ve attached waveforms here as well.

Enable

enable

Erase

erase

Write

enter image description here

1

1 Answers

0
votes

Without looking through your code in detail, I've spotted a possible problem: In order to complete an SPI operation, the chip select (CS) line usually needs to be pulled low before and set high again after every operation.

So, the EEPROM functions in your driver code probably need to first set the CS pin low, do some SPI operation, and set it high again after that.

For convenience, I usually add some simple helper functions to the driver source file:

static GPIO_TypeDef *_cs_port;
static uint16_t _cs_pin;

static void _chip_select(void)
{
    HAL_GPIO_WritePin(_cs_port, _cs_pin, GPIO_PIN_RESET);
}

static void _chip_deselect(void)
{
    HAL_GPIO_WritePin(_cs_port, _cs_pin, GPIO_PIN_SET);
}

In that case, I usually intialize the driver and and keep track of the peripheral instance and chip select GPIO, similar to this:

static SPI_HandleTypeDef *_spi;
static uint8_t _init = 0;

int8_t eeprom_init(
    SPI_HandleTypeDef *spi,
    GPIO_TypeDef *gpio_cs_port,
    uint16_t gpio_cs_pin)
{
    if (_init)
        return -1;

    _spi = spi;
    _cs_port = gpio_cs_port;
    _cs_pin = gpio_cs_pin;

    /* do initialization here */

    _chip_deselect();

    _init = 1;

    return 0;
}

int8_t eeprom_clear(void)
{
    if (!_init)
        return -1;

    /* do de-initialization here */

    _spi = 0;
    _cs_port = 0;
    _cs_pin = 0;
    _init = 0;

    return 0;
}

int8_t eeprom_op_x(void)
{
    if (!_init)
        return -1;

    _chip_select();

    op_x(); /* todo */

    _chip_deselect();

    return 0;
}

I hope this helps :) ! There might be other issues in your hardware/software; this is probably not the full solution to your problem.

BTW: There are also ways to use hardware chip select (STM32 SPI peripheral), which I've never used (SPI / NSS in the reference manual). As far as I can tell, you also used SPI_NSS_SOFT in your SPI configuration, which requires you to manually set the chip select line.

BTW: Unrelated, but maybe of interest: ST provides simple HAL functions to access external I2C flash (HAL_I2C_Mem_*() functions).


edit 0 (more findings by skimming through code / datasheet):

  • Read_EEPROM() will not work like this, the data read from the bus isn't accessible outside the function's scope (C issue). Instead, a pointer to a read buffer could be passed to the function (or the read data could be returned as return value). For example like this: uint8_t Read_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint8_t *data, uint8_t byte_count)

  • In Read_EEPROM(): HAL_SPI_TransmitReceive() won't read the incoming bytes, when used like this. It receives and transmits at the same time. So it would make sense to first write the read / address command, and then start reading the incoming bytes (like in your code that has been commented out).

  • In Enable_/Disable_/Read_/Erase_EEPROM(): The number of bytes (size) seems to be wrong, it should be 2 instead of 1, in order to make HAL_SPI_Transmit() / HAL_SPI_TransmitReceive() transmit/receive the right number of bytes.

  • This IC does not seem to be well suited to be used with normal SPI, since it requires a very specific bit sequence which is not byte aligned (like you said). It might make sense to bit bang the communication (like you've mentioned), and pay attention to every little bit stated in the datasheet...

Since this seems to be an early test, I'd try to keep it as simple as possible, and get a first enable/write/read operation going, by bit-twiddling the same SPI pins by hand (reconfigured as normal GPIOs), so that the problems with the STM32's byte oriented SPI HAL functions won't get in your way. And then work towards a nice little driver... Maybe the STM32's SPI can still be used in some way, it's hard to tell for me right now...