This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C123G gets stuck when reading AT25D SPI Flash



Hi,

I'm having a strange issue when reading from SPI flash, in some situation reading the SSI bus gets stuck and never recovers.


I wrote the a rather generic SPI routine and implemented flash read/write functions on top of it

// responses to cmd_buffer are ignored, responses to data_buffer are stored in data_buffer
void spi_tx(const spi_cfg_t cfg, const uint8_t *cmd_buffer, int cmd_buffer_len, uint8_t *data_buffer, int data_buffer_len, uint8_t flags) {
  uint8_t count_tx = 0;
  uint8_t count_rx = 0;

  const uint8_t *tx_data = cmd_buffer;
  uint8_t *rx_data = NULL;

  // Wait until the transmit is finished
  while ((HWREG(cfg.base + SSI_O_SR) & SSI_SR_TFE) != SSI_SR_TFE);
  // clear rx
  while ((HWREG(cfg.base + SSI_O_SR) & SSI_SR_RNE)) {
    *rx_data = HWREG(cfg.base + SSI_O_DR);
  }

  // CS low
  HWREG(cfg.port_cs + (cfg.pin_cs << 2)) = 0;

  while (count_tx < cmd_buffer_len + data_buffer_len) {
    // wait for free spot in TX fifo
    while((HWREG(cfg.base + SSI_O_SR) & SSI_SR_TNF) == 0);

    HWREG(cfg.base + SSI_O_DR) = *tx_data;
    tx_data++;
    count_tx++;

    // we are done transmitting cmd_buffer
    if (tx_data - cmd_buffer == cmd_buffer_len) {
      tx_data = data_buffer;
    }

    // wait for spi
    while (HWREG(cfg.base + SSI_O_SR) & SSI_SR_BSY);

    // read rx
    while ((HWREG(cfg.base + SSI_O_SR) & SSI_SR_RNE) && count_rx < cmd_buffer_len + data_buffer_len) {
      *rx_data = HWREG(cfg.base + SSI_O_DR);
      // ignore responses to cmd_buffer
      if (rx_data != NULL)
        rx_data++;
      // switch to data_buffer
      else if (count_rx + 1 == cmd_buffer_len && !(flags & SPI_DATA_IMMUTABLE))
        rx_data = data_buffer;
      count_rx++;
    }
  }

  // SSI busy
  while (HWREG(cfg.base + SSI_O_SR) & SSI_SR_BSY);

  // CS high
  HWREG(cfg.port_cs + (cfg.pin_cs << 2)) = cfg.pin_cs;

  // should never happen
  if (count_rx < cmd_buffer_len + data_buffer_len) {
    printf("%d bytes missing\n", cmd_buffer_len + data_buffer_len - count_rx);
  }
}

int flash_write_page(uint32_t address, uint32_t len, const uint8_t *data) {
	printf("write(%x, %d, %x)\n", address, len, data);
	uint8_t cmd_buffer[4];

	// enable write
	cmd_buffer[0] = CMD_WREN;
	spi_tx(config, cmd_buffer, 1, 0, 0, 0);

	// write page
	cmd_buffer[0] = CMD_PP;
	cmd_buffer[1] = (address >> 16) & 0xff;
	cmd_buffer[2] = (address >>  8) & 0xff;
	cmd_buffer[3] = (address >>  0) & 0xff;

	spi_tx(config, cmd_buffer, 4, (uint8_t *) data, len, SPI_DATA_IMMUTABLE);

	while(flash_busy());

	return 0;
}

int flash_read_page(uint32_t address, uint32_t len, uint8_t *data) {
	printf("read(%x, %d, %x)\n", address, len, data);
	uint8_t cmd_buffer[5];
	cmd_buffer[0] = CMD_FREAD;
	cmd_buffer[1] = (address >> 16) & 0xff;
	cmd_buffer[2] = (address >>  8) & 0xff;
	cmd_buffer[3] = (address >>  0) & 0xff;
	cmd_buffer[4] = 0;

	spi_tx(config, cmd_buffer, 5, data, len, 0);

	return 0;
}

This works quite well, there are however situations where I get stuck on something like read(0, 128, 2000536c) quite consistently.
 (It always seems to be a read from the 0st address on the flash, but in other situations that works just fine)


What am I missing here?

Is there some way I can add a timeout to my SPI transfer so I could at least recover from that?

  • Hello Benjamin,

    Where does the code get stuck? There is no timeout facility in the SPI Module. The only method to do a timeout would be to use a SysTick or Watchdog timer to count for a value greater than the expected transfer time and if it expires before transfer, you may begin a recovery sequence.

    The one potential issue I see is when you read the SSIDR (comment "read rx"). For evrry push of a data you will get one data in the Data register. You do no need to put that in a while statement. But a simple check of SSI Not being busy can be used in the main while loop.

    Regards
    Amit
  • Benjamin Valentin said:
    situations where I get stuck on something like read(0, 128, 2000536c) quite consistently... always a read from 0st adr...

    That's your point of most interest - I believe - and does it not seem that such (apparent) "address sensitivity" falls "outside" of a simple (peripheral) delay or busy check?   

    Might this be a "sensitivity or issue" of your external device - and not a program code issue?    Have you really (thoroughly) read/review that device's datasheet - especially its most recent errata?

    As always - does this issue visit beyond one single device - upon one single board?    Single board anomalies always over-tax - bad solder joints have been noted to cause such "address violations."   (especially @ addresses of "0" or "FF" - when a bad, IC joint may confound such "special address" recognition...)    As I recall - such devices (usually) bring out 2 address pins - so that 4 such devices may co-exist upon the bus...

  • This works quite well, there are however situations where I get stuck on something like read(0, 128, 2000536c) quite consistently.
    (It always seems to be a read from the 0st address on the flash, but in other situations that works just fine)"

    I presume there's a typo since that third argument should not even compile, I think.

    "Is there some way I can add a timeout to my SPI transfer so I could at least recover from that?"
    SPI shouldn't stop since there are no checks that would cause it to even pause much less stop.

    Robert
  • Note poster's slight modification to classic, "Does not Work!"    Poster, "situations where I get stuck" and he's surely smart & experienced enough to know better.

    We've no idea if the code "hangs" (as Amit proposes) or if the data returned is in error - or...?

    Great detail (otherwise) by the poster - unfortunately the key/critical "proper description of his issue" was sorely lacking...

  • Unfortunately this only happened when the flash/spiffs was in a peculiar state (remove operation on a certain file) and during debugging I accidentally triggered an automatic reformat so this currently doesn't happen anymore - I see if it occurs again, for now I followed Amit's advice and removed the while loop around the SPI read.

    Btw, do you think I could get an increase in speed if I first filled the tx buffer (write till SSI_SR_TNF == 0) and then read the rx buffer in a loop?

    (But for now I guess I should not introduce new bugs ;))

  • Hello Benjamin,

    Keep it simple. Loop the following and work on the data extraction after getting all the data.

    HWREG(cfg.base + SSI_O_DR) = *tx_data;
    while (HWREG(cfg.base + SSI_O_SR) & SSI_SR_BSY);
    *rx_data = HWREG(cfg.base + SSI_O_DR);

    Regards
    Amit