Read NAND Flash device signature with Arduino

 Author:   Posted on:   Updated on:  2020-12-19T18:56:23Z

Implement easy routines to get data from a NAND flash memory using Arduino Pro Mini (ATmega328p running on 3.3 V)

NAND Flash chips are widely used non-volatile memory devices. They have high storage capacity, fast access time and are reliable, usually being able to withstand 100,000 erase/program cycles. Such chips are available with parallel or serial interface (commonly SPI). While the latter can be easily interfaced to any SPI port and can be read/programmed even by slow microcontrollers, parallel chips are faster and require more data lines (connections) to host microcontroller.

In the previous post I described the way I connected a NAND flash to Arduino (a Pro mini compatible board running at 3.3 V). This time, I'll deal with the limitation of the small MCU and read the NAND signature.

Read NAND Flash device signature with Arduino

The 8-bit I/O port

With the ATmega328 based Arduino, getting a full 8-bit usable port is not that easy. The only viable option is PORTD, mapped to Arduino digital pins D0 to D7. Unfortunately, pins D0 and D1 are the hardware UART (the serial port). Yes, I could reuse them for I/O and implement a software serial port on other pins. In this way, it will be possible to read data from memory at a faster rate. But what do I do with that data? Software serial ports are even more limited in speed than the hardware port on D0 and D1. SPI interface could be an option that I’m thinking to explore sometimes.

For now, as you have seen from the schematic in the previous post, I split the 8-bit port on two hardware ports of Arduino.

The 8-bit I/O port on Arduino ATmega328p

The 8-bit I/O port on Arduino ATmega328p

This requires a bit of coding to get the right data on the port. I will use direct port writing to get things done. Here is the function that outputs data to port:

void portWrite(uint8_t by) {
  PORTC = (PORTC & 0xC0) | (by & 0x3F);
  PORTB = (PORTB & 0xFC) | (by >> 6);
}

Let me explain a bit: PORTC & 0xC0 saves the state of the bits that we don’t need [7:6]. I do not intend to change these bits therefore, when I output data to port, these should remain unchanged. I don’t know if some sort of undefined behavior will occur if the state of any of these pins changes while performing the current operation. In the same way, by & 0x3F keeps only bits [5:0] of the data to be written. The two bytes (one with the current status of the port and the other with data to be written) are OR’ed and assigned directly to port register.

The same thing for PORTB. Mask 0xFC keeps set bits [7:2]. Our data needs to be shifted to get only bits [7:6] at positions [1:0] ready for being OR’ed with the current port state and assigned to port register.

So, what do we get if we call in an endless loop portWrite(0x00); portWrite(0xFF);? Well, not quite a fast signal. Only 0.5 MHz and not a 50 % duty cycle. We can also see how bits [7:6], which are outputted on PORTB are slightly delayed in time.

Speed of 8-bit port direct writing (ATmega328p)

Speed of 8-bit port direct writing (ATmega328p)

Port reading is quite simple. I take the first 6 bits from PORTC and OR them with the shifted two bits from PORTB.

uint8_t portRead() {
  return (PINC & 0x3F) | (((PINB & 0x03) << 6));
}

The same port will be used for both data output and input. So, I must write some routines to switch between input and output. I'll do this by writing to data direction registers. It is done easily, using the same bit masks. Output mode sets the bits we need, while in input mode, the same bits are cleared.

void portModeOutput() {
  DDRC |= 0x3F;
  DDRB |= 0x03;
}

void portModeInput() {
  DDRC &= ~0x3F;
  DDRB &= ~0x03;
}

But when should the port be set as input and when as output? Well, from the following table (source: datasheet) we can see that only when reading data from NAND, the port should be previously set to input.

Bus operations of NAND (source: ST datasheet)

Bus operations of NAND (source: ST datasheet)

Or, to state this in a different way: as long as R stays HIGH, host MCU port can be set to output, because NAND port is input.

The control signals

Although slower, I'll use Arduino digitalWrite() for all control signals. Control signals RB, R, E, W and WP are active low. With the exception of RB which should be set to input, all are outputs and their initial state have to be HIGH (inactive). CL and AL are also outputs, but these are active HIGH. I wrote a simple function which sets all control signals to default state:

void nandIdleBus() {
  digitalWrite(NAND_R, HIGH);
  digitalWrite(NAND_W, HIGH);
  digitalWrite(NAND_CL, LOW);
  digitalWrite(NAND_AL, LOW);
  digitalWrite(NAND_E, HIGH);
}

This is called in setup() after setting the I/O mode for these pins. Before any attempt to communicate with the memory, it must be enabled.

Sending commands

As far I can read from the datasheet, commands can contain 1 to 3 bytes. I'm interested in this, because after I'm done sending commands, I will disable CL (set to LOW). My routine will only take care of CL, W and I/O port. E and AL should be previously set to default state (disabled). By previously we can safely assume a long time ago reported to common NAND timings.

Command latch waveform (adapted from datasheet)

Command latch waveform (adapted from datasheet)

Setup times? Long enough! Hold times? The same. No need to worry. The code looks like this:

void nandSendCommand(uint8_t cmd, bool last = true) {
  digitalWrite(NAND_CL, HIGH);
  digitalWrite(NAND_W, LOW);
  portWrite(cmd);
  digitalWrite(NAND_W, HIGH); // this triggers data capture
  if (last) digitalWrite(NAND_CL, LOW);
}

There is no time constraint between W falling and data output (so which should be first? I guess it doesn't matter). But the data should be present on MCU port at least 20 ns before W rises and must stay there at least 10 ns. Sure! No delays needed with ATmega328.

How can I send address? In the same way, the only difference being the use of AL instead of CL. But because now I'm more interested in knowing if the NAND is still... alive, I'll go straight to device signature reading.

Read signature

Addresses of this NAND are 4 bytes wide. For the purpose of reading device signature, after the specific command, the address is a single byte: 0x00.

Read signature waveforms (adapted from datasheet)

Read signature waveforms (adapted from datasheet)

You know the writing waveforms. Data to be read from NAND is made available in no more than 35 ns after R falls. It is latched on the rising edge of R, yet it must be read before this (no more than 30 ns pass after R rises and data port goes Hi-Z). After the signature is read, you can send other commands to NAND.

void nandReadSignature() {
  uint8_t sm = 0x00, sd = 0x00;

  portModeOutput();
  nandSendCommand(0x90);
  digitalWrite(NAND_AL, HIGH);
  digitalWrite(NAND_W, LOW);
  portWrite(0x00); // address 0x00
  digitalWrite(NAND_W, HIGH); // this triggers data capture
  digitalWrite(NAND_AL, LOW);

  portModeInput();
  digitalWrite(NAND_R, LOW);
  sm = portRead();
  digitalWrite(NAND_R, HIGH);
  digitalWrite(NAND_R, LOW);
  sd = portRead();
  digitalWrite(NAND_R, HIGH);

  Serial.print("NAND manufacturer: ");
  Serial.println(sm, HEX);
  Serial.print("NAND device ID:    ");
  Serial.println(sd, HEX);
}

Note how I set the port as output at first, then I switched to input before setting R LOW. This implementation leaves I/O port to input (remember to set it back to output if you call another function). And... it works!

NAND device signature read by Arduino

NAND device signature read by Arduino

The code is quite simple, and it could have been even better if I had available a full 8-bit port. This being said I'll attempt to read data in a future post. In the sketch I also implemented a function to read the status register (which informs me Program/ Erase/ Read Controller is inactive). The sketch, at this time, does not check the read busy signal nor does it take control of write protect pin.

Sketch download: nand_read_test.ino. To be continued...

No comments :

Post a Comment

Please read the comments policy before publishing your comment.