SPI with 24C040 EEPROM

Thread Starter

ArakelTheDragon

Joined Nov 18, 2016
1,366
I am trying to implement SPI protocol with an EEPROM chip. But I think I am missing something, can anyone give me an opinion on what should I add? Also the EEPROM has 3MHz max input, if I make it on 500Hz, will it be ok?

Code:
void SPI_EEPROM ()
{
   PWM_function ();
   CHIP_SELECT ();
   WREN_INSTRUCTION ();
   WRITE_INSTRUCTION ();
  
   CHIP_SELECT ();
   READ_INSTRUCTION ();

}

void CHIP_SELECT ()
{
   output_high (PIN_B0);                    /* After the EEPROM is selected by "chip select" going low, on the falling front, a control byte must be sent, A8 bit is the address bit */
   output_low  (PIN_B0);
  
}

void WREN_INSTRUCTION ()
{
                                   /* WREN instruction = the first byte transmitted is the control byte, with the MSB A8 being transmitted first, Byte = 0000 X110 */
   output_low (PIN_B2);                        /* "0" */
   output_high (PIN_B2);                    /* "1" */
   output_high (PIN_B2);                    /* "1" */
   output_low (PIN_B2);                        /* A8 */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */
  
   output_high (PIN_B0);                    /* The real programming will start after the pin "CS" is turned high */
}

void WRITE_INSTRUCTION ()
{
                                  /* WRITE instruction = 0000 A010, A is the MSB A8 */
   output_low (PIN_B2);                        /* "0" */
   output_high (PIN_B2);                    /* "1" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "A8" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */
  
   output_high (PIN_B0);                    /* Pull the chip selet pin high when the operation is done, because after its held high, the information is written to the EEPROM */
}

void READ_INSTRUCTION ()
{
   unsigned int8 Data;
                                      /* READ instruction = 0000 A011, A is the MSB A8 */
   output_high (PIN_B2);                        /* "0" */
   output_high (PIN_B2);                    /* "1" */
   output_low (PIN_B2);                        /* "1" */
   output_low (PIN_B2);                        /* "A8" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */
   output_low (PIN_B2);                        /* "0" */

   Data = input (PIN_B4);
   output_d (DATA);
   output_high (PIN_B0);                    /* Pull the chip selet pin high when the operation is done, because after its held high, the information is written to the EEPROM */

}


   /* PWM program from PCM programmers's post */
/* Try the following program. Copy and paste it into MPLAB. */
/* Remember that the PWM duty value should not be set greater */
/* than the middle number in the setup_timer_2() function. */
/* If you set it greater than that value, then you will get a constant */
/* high level out. (100% duty cycle) */

void PWM_function ()
{
output_low(PIN_C1);   // Set CCP2 output low
output_low(PIN_C2);   // Set CCP1 output low

setup_ccp1(CCP_PWM);  // Configure CCP1 as a PWM
setup_ccp2(CCP_PWM);  // Configure CCP2 as a PWM

setup_timer_2(T2_DIV_BY_16, 124, 1);  // 500 Hz,     
set_pwm1_duty(31);                    // 25% duty cycle on pin C2, 25% of 124
set_pwm2_duty(62);                    // 50% duty cycle on pin C1, 50% of 124

//while(1);  // Prevent PIC from going to sleep  (Important !)
}
 

JohnInTX

Joined Jun 26, 2012
4,787
It looks to me as if you are wiggling the data line PIN_B2 (MOSI) but not the clock (SCLK). SPI is a clocked serial interface so you need to set the data bit, clock the bit, set the next data bit, clock the bit etc.
For this part, data is clocked in on the rising edge of the clock and can change when the clock is low so:
SCLK=0
for data bits 1-8
set data out
SCLK=1
SCLK=0
next data bit
 

Thread Starter

ArakelTheDragon

Joined Nov 18, 2016
1,366
I am using the PWM signal to generate a 500Hz clock, but I may not have synced it. For I2C the procedure is:
"Start"
"High address"
"Low address"
"Data"
"Stop"

Yes, I know SPI is a synchronious interface, but I am trying to make the procedure properly. Let me see if I understand:
  1. CHIP_SELECT ();, No clocking is needed here?
  2. WREN_INSTRUCTION ();, I understand that first I need to send the "WREN" byte taken from the datasheet, and clock it on every bit? If I put a delay after the sending of every bit will that be enough? I read in the datasheet that the EEPROM has "5ms" read and write time, but a max "3MHz" clock, does that mean I should be clocking at "3MHz" for "5ms" and can I clock at "500Hz" for "5ms"? I am using a PWM signal at "500Hz" therefore if the clock pin is connected to the PWM pin and I delay after sending the bit for "5ms" will that be enough? "1/500Hz = "2ms", 50% duty cycle.
  3. WRITE_INSTRUCTION (); The write control byte taken from the datasheet, sends only 8 bits with "A8" being the first bit of the address (0b "0", "0", "0", "0", "A8", "0", "0", "0"), however if only 4 bits are used for the address and that means 16 addresses? Do I have to send only 8 bits and packets of 8 bits, or should I send 11 bits and there will be no problem? The control byte in the datasheet is 8 bits? I have to clock on every bit? I read that the EEPROM has an automatically incremented counter, so I am guessing that if it starts from adress "1000" it will keep reading, but how will the reading be done, at what speed and how exactly do I read bit by bit and put it in 1 whole "address byte" or variable.
  4. READ_INSTRUCTION (); Send the control byte from the datasheet for reading, again only 4 bits and 16 addresses? Clock at every bit?
 

JohnInTX

Joined Jun 26, 2012
4,787
Forget I2C. It's not applicable here.

You can't do clocking with the PWM because of the sync problem you noted. Use an output line for SCK.
Specific answers:
0) You can run the SPI interface as slow as you want. Max clock frequency is 3MHz. The SPI speed is unrelated to the EEPROM write speed.

1) Correct. Be sure SCK is low before setting CS/ low.

2) The WP/ pin must be high to enable writing. Yes, you must send WREN 06h to the chip as a single transaction i.e. no other data read/write, framed by CE/. That sets the WRiteENable latch inside the chip. The latch stays set until one of the conditions in section 3.4 of the datasheet is satisfied. These are
Power up
WRiteDIsable instruction sent.
WRiteStatusRegister instruction sent.
Taking the WP/ line low
.. and most important to this discussion
WRITE instruction successfully executed- this means that WREN must be sent once for each write operation to 'unlock' the chip. It doesn't have to be executed at every bit or byte (and couldn't be if you think about it).

3)The memory is organized as 512x8 meaning you need a 9bit address. The 9th bit is contained in the WRITE instruction itself. The WRITE instruction is
0000x010
where x is the 9th address bit - 0 for addressing the first 256 bytes, 1 for addressing the 2ed 256 bytes. The lower 8 bits of the instruction are sent next byte following the WRITE instruction. The data byte(s) follow those 2 first ones. See Figure 3-2 and 3-3 in the datasheet. A8 is the 9th address bit and you can see the lower 8 bits of the address in the byte immediately following the WRITE instruction.

4)The addressing in the READ instruction is exactly the same as in the WRITE. The difference is that instead of clocking out the data to write, you clock in the data to read. Fig. 3-1.

In all cases standard SPI applies:
Drop CE/, raise when done with entire transaction (not each byte)
SCK is 0 on entry and exit

To WRITE any byte:
for 8 bits{
Set the data out (MOSI) to the first bit (MSbit) you want to write
SCK =1
SCK =0 to clock the data out
shift the data byte left by 1 to put the next bit into the MSbit
}


To READ a byte:
for 8 bits{
SCK=1
Read MSbit in from MISO
Left-shift bit into receiving byte
SCK=0
}

Note how both of these routines use a loop and data shift rather than brute-force bit setting/clearing like your code above. The prototypes might look like:

void WriteByte(char byte); // pass a character to be written, assumes CE/ and SCK==0
char ReadByte(void); // clocks EEPROM 8 bits and returns the character read. Assumes CE/ and SCK==0.

A complete write transaction will consist of sending the WREN byte as a separate instruction then sending a WRITE instruction with address and data to write. Note that each byte write or read below means a complete 8 bit byte using the sub-routines above.
CE/ =0
WriteByte 06h // enable writing for the following transaction
CE/=1


Then address the chip and send the data to be written
CE/ = 0
Write 02h or 0ah depending on the 9th address bit
Write the lower 8 address bits
Write the data byte(s)
CE/ = 1


A complete READ transaction will consist of sending the READ instruction, the address to read from and then reading byte(s) of data.
CE/ = 0
Write 03h or 0bh depending on 9th address bit.
Write the lower 8 address bits
Read data byte(s)
CE/ = 1


As far as timing, the SPI clock and data timing requirements are unrelated to the write time of the EEPROM. The SPI is fast but each WRITE CYCLE takes up to 5ms to complete. A write cycle begins when you raise CE/ after the write instruction, address and data are sent. You have to wait 5ms before sending the next write. During that time you cannot write or read any data but you can check the status register to see if the write is complete. Or you can just delay awhile.

Note that you can write up to 16 bytes per write cycle i.e. send the WRITE instruction, address and up to 16 bytes of data that will be written all at once. Note that the memory is organized in 16 byte pages and you can't cross a page boundary in a single write. That's common. See Sec 3.3

To approach this, write routines that send a byte and read a byte. CE/ must be 0 before calling. Each of those routines will assume SCK == 0 on entry and leave SCK==0 on exit. They will toggle SCK 8 times and either serially shift out data to write or shift in data to read.

Then use these as described to construct full transactions, one for write, one for read. Transactions control CE/. For example to write 2 bytes:
CE/=0
WriteByte(WREN)
WriteByte(02h) // addresses lower 256 bytes
WriteByte(address)
WriteByte(data)
WriteByte(next_data)
CE/=1 // starts write cycle, wait 5ms.


Write the routines to take a source pointer, starting address and byte count i.e.
WriteBytes(char *source_data, int address, int count);

Read is left as an exercise as is polling STATUS to see if any previous write cycle is complete and the memory is again ready to read or write.

SPI is pretty standard stuff, avoid creative pioneering like PWMs for the clocking and you'll get there faster.

That's how I do it anyway.
Good luck.
 

Attachments

Last edited:

Thread Starter

ArakelTheDragon

Joined Nov 18, 2016
1,366
char byte
This will be very difficult!

I start with this this:
Code:
void init_ext_eeprom(int8 EEPROM_SELECT)
{
   output_high(EEPROM_SELECT); /* EEPROM chip select, this function outputs "high" 1 pin */
   output_low(EEPROM_DI);              /* Why do  I need this? */
   output_low(EEPROM_CLK);         /* I clock the EEPROM, so the falling front will start the process? */
}
Next its a blurr:
Code:
BOOLEAN ext_eeprom_ready(int8 EEPROM_SELECT) {
   BYTE cmd[1], i, data; /* Why do we use a "BYTE" variable?, I am guessing this is why the function is of "BOOLEAN" type? */

   cmd[0] = 0x05;                  /* rdsr opcode */

   output_low(EEPROM_SELECT); /* Deselect the CS pin */

   for(i=1; i<=8; ++i)
  {
      output_bit(EEPROM_DI, shift_left(cmd,1,0)); /* e.g output_bit (pin, value), [B]EDIT: */
                                                                                   /* I dont quite get this "shift" */
                                                                                   /* function */[/B]
      output_high(EEPROM_CLK);   /* We clock the data latches */
      output_low(EEPROM_CLK);    /* Back to idle state so on the falling front the process is started */
   }

   for(i=1; i<=8; ++i)
  {
        output_high(EEPROM_CLK);   /* We clock the data latches */
        shift_left(&data,1,input(EEPROM_DO)); /* I dont understand this one, I think it takes the 8 bits and shifts them 1 posisiton to the left with every */                                                                                    /* new bit */
        output_low(EEPROM_CLK);                   /* back to idle, setting the clock low at the end of the 8 bits operation */
   }
   output_high(EEPROM_SELECT);               /* Chip select the EEPROM */
   return !bit_test(data, 0);                                /* I think we test if "bit 0" of "data" is "0" or "1" and we return the inverse value? */                    
}
EDIT: I dont quite get this "shift" function "output_bit(EEPROM_DI, shift_left(cmd,1,0));" */
When I am done understanding this much, I will move forward.
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
In the first listing, You are actually de-selecting the EEPROM by taking CE/ high. De-select means it is not enabled for use. You want to set up the clock and data to known states then drop CE/ to enable the chip. You don't have to init the data at this point but you do have to start with SCK low.

In the second listing, the reason you are shifting is that you are in a loop. Each clock reads or writes a bit. For the loop to work, it uses the same bit addressing so the data byte (8 bits) is shifted to bring the next bit into the place.
The second listing is problematic since you are reading and writing at the same time. SPI actually does that if you want but you clock only once per bit. The routines I described use two different routines for reading and writing since the EEPROM is always reading or writing a byte and does not take advantage of SPI's ability to write a byte and get one back at the same time.

It looks like you are copying code from some other source. That's why you don't understand it. I don't either.
 

Thread Starter

ArakelTheDragon

Joined Nov 18, 2016
1,366
I am, I am trying to uderstand it, I thought it would be easier, I will focus on your routines and see what I can do if I code it by myself. I understand that we shift once for every count, I just didnt understand how exactly does that shifting works and what do the variables do.
 
Top