|
|
View previous topic :: View next topic |
Author |
Message |
valkyrie.wing
Joined: 12 Oct 2017 Posts: 10
|
AT25M02 SPI EEPROM Driver |
Posted: Fri Oct 20, 2017 12:14 am |
|
|
I am planning to create a driver for this device. What is the nearest SPI drivers included in CCS C compiler to start the development?
I saw from the included drivers folder, the device 9356SPI.
Thanks. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Fri Oct 20, 2017 1:40 am |
|
|
Any of the standard SPI drivers.
All of these recent EEPROM's, use a common signalling standard. The only things you have to change are the sizes of numbers fed to them. The nearest is actually the 25640 for example, just requiring one extra address byte added, and tweaks to the maximum size etc...
This is a better fit than the 241025 driver, where there is a bank switching bit added into the command. |
|
|
valkyrie.wing
Joined: 12 Oct 2017 Posts: 10
|
|
Posted: Fri Oct 20, 2017 8:25 pm |
|
|
Hi Ttelmah,
I checked the sample source code of 25640 and yes they are similar to AT25M02 though the implementation is I think software SPI.
I will try this one although my final implementation will be using hardware SPI on PIC18F8722.
Thanks. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Sat Oct 21, 2017 12:32 am |
|
|
Yes.
I think these examples were written to be 'generic', so usable on just about any PIC without SPI hardware. Done before #USE SPI, gave the option to use hardware or software SPI existed. This makes it much easier. |
|
|
valkyrie.wing
Joined: 12 Oct 2017 Posts: 10
|
|
Posted: Sat Oct 21, 2017 9:25 pm |
|
|
Hi Ttelmah,
I ended up writing a HW-based implementation driver for AT25M02. Although I am not quite sure if I done it correctly. I just read the MSSP chapter of PIC18F8722 and the AT25M02 datasheet. I will be testing this on my coming Monday.
I have not use any interrupt #int_ssp coz I think the PIC is a master while the EEPROM is a slave would not need any interrupt handling unless I am wrong with my assumption.
Btw, I am also planning to create a routine for page_write and page_read with a boundary of 256Bytes per EEPROM datasheet. I might also create block_write and block_read with a boundary of 1KB which is a multiple of 256Bytes.
Here are the source files together with the test driver.
AT25M02.h --> EEPROM driver header file
Code: |
#if !defined(__AT25M02_H__)
#define __AT25M02_H__
//-------------------------------------------------------------------------
// Includes
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// Global Constant Macros
//-------------------------------------------------------------------------
#bit SSP2IF = 0xFA4.7 // SPI2 interrupt flag
#bit SSP2IE = 0xFA3.7 // SPI2 interrupt enable
#bit SSP2_TRISB3 = 0xF93.3 // SPI2 EEPROM chip select
#bit SSP2_PORTB3 = 0xF81.3
#bit SSP2STAT_BF = 0xF64.0 // Buffer Full Status bit
#byte SSP2CON1 = 0xF63 // SSP2 Control register
#byte SSP2STAT = 0xF64 // SSP2 Status register
#byte SSP2BUF = 0xF66 // MSSP2 Receive Buffer/Transmit Register
#define SSP2_ENABLE (0x20)
#define SSP2_MASTER (0x40)
// EEPROM Instruction Set
#define SPI_EEPROM_CMD_WRITE (0x02)
#define SPI_EEPROM_CMD_READ (0x03)
#define SPI_EEPROM_CMD_WRDI (0x04)
#define SPI_EEPROM_CMD_RDSR (0x05)
#define SPI_EEPROM_CMD_WREN (0x06)
// different byte offset
#define SPI_ADDRESS_OFFSET_0 (0x00)
#define SPI_ADDRESS_OFFSET_1 (0x01)
#define SPI_ADDRESS_OFFSET_2 (0x02)
#define SPI_ADDRESS_OFFSET_3 (0x03)
//-------------------------------------------------------------------------
// Global Type Definitions
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// Global Structure Definitions
//-------------------------------------------------------------------------
struct EepromStatRegStruct
{
unsigned char BSY :1;
unsigned char WEL :1;
unsigned char BP0 :1;
unsigned char BP1 :1;
unsigned char RFU :3;
unsigned char WPEN :1;
};
union EepromStatRegUnion
{
struct EepromStatRegStruct EepromStatReg;
unsigned char EepromStatus;
};
//-------------------------------------------------------------------------
// Function Macros
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// Global Variable Declaration (extern)
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// Function Prototype
//-------------------------------------------------------------------------
void spi_eeprom_init (void);
union EepromStatRegUnion spi_eeprom_read_status (void);
// send one byte of data and receive one back at the same time
unsigned char spi_eeprom_write (unsigned char Data);
void spi_eeprom_write_enable (void);
void spi_eeprom_write_disable (void);
void spi_eeprom_write_byte (unsigned char Data,
unsigned int32 Address);
unsigned char spi_eeprom_read_byte (unsigned int32 Address);
#endif
|
AT25M02.c --> EEPROM driver source file
Code: |
#include "AT25M02.h"
void spi_eeprom_init (void)
{
SSP2IF = 0; // clear interrupt flag
SSP2IE = 0; // disable interrupt
SSP2CON1 = SSP2_ENABLE; // enable SPI peripheral
SSP2STAT = SSP2_MASTER; // select mode
SSP2_TRISB3 = 0; // RB3 set to output for EEPROM chip select
SSP2_PORTB3 = 1; // EEPROM chip select set to high
return;
}
union EepromStatRegUnion spi_eeprom_read_status (void)
{
unsigned char Result;
SSP2_PORTB3 = 0; // EEPROM chip select set to low
// send the EEPROM read status register
Result = spi_eeprom_write(SPI_EEPROM_CMD_RDSR);
// get the EEPROM status register
Result = spi_eeprom_write(0);
SSP2_PORTB3 = 1; // EEPROM chip select set to high
return (union EepromStatRegUnion)Result;
}
unsigned char spi_eeprom_write (unsigned char Data)
{
SSP2BUF = Data; // write to buffer for TX
while (!SSP2STAT_BF); // wait for transfer to complete
return SSP2BUF; // read the received value
}
void spi_eeprom_write_enable (void)
{
unsigned char Result;
SSP2_PORTB3 = 0; // EEPROM chip select set to low
Result = spi_eeprom_write(SPI_EEPROM_CMD_WREN);
SSP2_PORTB3 = 1; // EEPROM chip select set to high
return;
}
void spi_eeprom_write_disable (void)
{
unsigned char Result;
SSP2_PORTB3 = 0; // EEPROM chip select set to low
Result = spi_eeprom_write(SPI_EEPROM_CMD_WRDI);
SSP2_PORTB3 = 1; // EEPROM chip select set to high
return;
}
void spi_eeprom_write_byte (unsigned char Data,
unsigned int32 Address)
{
unsigned char Result;
union EepromStatRegUnion Status;
// enable write operation
spi_eeprom_write_enable();
SSP2_PORTB3 = 0; // EEPROM chip select set to low
// send the EEPROM write command
Result = spi_eeprom_write(SPI_EEPROM_CMD_WRITE);
// send the EEPROM address
Result = spi_eeprom_write(make8(Address, SPI_ADDRESS_OFFSET_2));
Result = spi_eeprom_write(make8(Address, SPI_ADDRESS_OFFSET_1));
Result = spi_eeprom_write(make8(Address, SPI_ADDRESS_OFFSET_0));
// send the data to write
Result = spi_eeprom_write(Data);
SSP2_PORTB3 = 1; // EEPROM chip select set to high
// wait for completion of previous write operation
do
{
Status = spi_eeprom_read_status();
} while (Status.EepromStatReg.BSY);
// disable write operation
spi_eeprom_write_disable();
return;
}
unsigned char spi_eeprom_read_byte (unsigned int32 Address)
{
unsigned char Result;
SSP2_PORTB3 = 0; // EEPROM chip select set to low
// send the EEPROM read command
Result = spi_eeprom_write(SPI_EEPROM_CMD_READ);
// send the EEPROM address
Result = spi_eeprom_write(make8(Address, SPI_ADDRESS_OFFSET_2));
Result = spi_eeprom_write(make8(Address, SPI_ADDRESS_OFFSET_1));
Result = spi_eeprom_write(make8(Address, SPI_ADDRESS_OFFSET_0));
// get the data from EEPROM
Result = spi_eeprom_write(0);
SSP2_PORTB3 = 1; // EEPROM chip select set to high
return Result;
}
|
Here's the test driver source file -- AT25M02_test.c
Code: |
#include <18F8722.h>
#device ADC=16
#FUSES H4 // High-speed crystal clock x4 multiplier
#FUSES NOPROTECT // No code protection from reading
#FUSES NOWDT // No Watch Dog Timer
#FUSES NOBROWNOUT // No brownout reset
#FUSES NOLVP // No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES PUT // power-up timer
#use delay(clock=40000000)
#use rs232(baud=115200, xmit=PIN_C6, rcv=PIN_C7, parity=N, bits=8, errors)
#include "AT25M02.h"
#include "AT25M02.c"
void main (void)
{
unsigned int16 WrData;
unsigned int16 RdData;
// initialize HW SPI (MSSP) as master
spi_eeprom_init();
// write one byte to designated address
WrData = 0xDEAD;
// write MS byte into EEPROM address
spi_eeprom_write_byte(make8(WrData, SPI_ADDRESS_OFFSET_1), 0x10);
// write LS byte into EEPROM address
spi_eeprom_write_byte(make8(WrData, SPI_ADDRESS_OFFSET_0), 0x11);
// Now Readback one data from the serial eeprom
// read MS byte from EEPROM address
RdData = spi_eeprom_read_byte(0x10);
RdData = make16(RdData, 0x00);
// read LS byte from EEPROM address
RdData |= (spi_eeprom_read_byte(0x11) & 0x00FF);
// verify write and read SPI EEPROM (single byte)
if (RdData != WrData)
{
// error: verify failed
printf("ERROR: Data written mismatch with data read\n");
}
printf("Done\n");
while (TRUE);
return;
}
|
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Sat Oct 21, 2017 11:51 pm |
|
|
Seriously, just use #USE SPI.
All you need to do is to remove all the actual data transfers from the existing code, and for each byte replace this with xpi_xfer. It accepts mode 0 or 3.
It'll then work with different chips without having to change settings.
Half your code disappears.
The .h file
Code: |
#if !defined(__AT25M02_H__)
#define __AT25M02_H__
#USE SPI(SPI1, MODE=3, STREAM=AT25)
#define EEPROM_CS PIN_B3 //change to suit your hardware
// EEPROM Instruction Set
#define SPI_EEPROM_CMD_WRITE (0x02L)
#define SPI_EEPROM_CMD_READ (0x03L)
#define SPI_EEPROM_CMD_WRDI (0x04L)
#define SPI_EEPROM_CMD_RDSR (0x05L)
#define SPI_EEPROM_CMD_WREN (0x06L)
//-------------------------------------------------------------------------
// Global Structure Definitions
//-------------------------------------------------------------------------
struct EepromStatRegStruct
{
unsigned char BSY :1;
unsigned char WEL :1;
unsigned char BP0 :1;
unsigned char BP1 :1;
unsigned char RFU :3;
unsigned char WPEN :1;
};
union EepromStatRegUnion
{
struct EepromStatRegStruct EepromStatReg;
unsigned char EepromStatus;
};
#define COMBINE(x) Address=((Address & 0xFFFFFF) | (x<<24))
//Jut include the actual driver
#include <AT25M02.c>
#endif
|
It is only worth having a separate include without loading the driver, on compilers where you do a link compile.
Then the .c
Code: |
void spi_eeprom_init (void)
{
output_high(EEPROM_CS);
disable_interrupts(INT_SSP);
}
union EepromStatRegUnion spi_eeprom_read_status (void)
{
unsigned char Result;
output_low(EEPROM_CS);
// send the EEPROM read status register
Result = spi_xfer(AT25, SPI_EEPROM_CMD_RDSR,8);
// get the EEPROM status register
Result = spi_xfer(AT25,0,8);
output_high(EEPROM_CS);
return (union EepromStatRegUnion)Result;
}
void spi_eeprom_write_enable (void)
{
unsigned char Result;
output_low(EEPROM_CS); // EEPROM chip select set to low
Result = spi_xferAT25,SPI_EEPROM_CMD_WREN,8);
output_high(EEPROM_CS);; // EEPROM chip select set to high
}
void spi_eeprom_write_disable (void)
{
unsigned char Result;
output_low(EEPROM_CS); // EEPROM chip select set to low
Result = spi_xferAT25,SPI_EEPROM_CMD_WRDI,8);
output_high(EEPROM_CS);; // EEPROM chip select set to high
}
void spi_eeprom_write_byte (unsigned char Data,
unsigned int32 Address)
{
unsigned char Result;
union EepromStatRegUnion Status;
// enable write operation
spi_eeprom_write_enable();
output_low(EEPROM_CS); // EEPROM chip select set to low
COMBINE(SPI_EEPROM_CMD_WRITE);
// send to the EEPROM
Result = spi_xfer(AT25,Address,32);
// send the data to write
Result = spi_xfer(AT25,Data,8);
output_high(EEPROM_CS); // EEPROM chip select set to high
// wait for completion of previous write operation
do
{
Status = spi_eeprom_read_status();
} while (Status.EepromStatReg.BSY);
// disable write operation
spi_eeprom_write_disable();
return;
}
unsigned char spi_eeprom_read_byte (unsigned int32 Address)
{
unsigned char Result;
output_low(EEPROM_CS); // EEPROM chip select set to low
COMBINE(SPI_EEPROM_CMD_READ);
// send the EEPROM address
Result = spi_xfer(AT25,Address,32);
// get the data from EEPROM
Result = spi_xfer(AT25,0,8);
output_high(EEPROM_CS); // EEPROM chip select set to high
return Result;
}
|
Then just include the .h in your main.
Now, I've typed this in untried on a computer without the compiler, so probably some typing errors, but this should be very close.
The compiler should optimise the 24bit shift into a single byte move. All I do is clock the address and command out as a single 32bit transfer.
I've also added an 'L' to the defined instructions so they will be shifted as long values to ensure this works. |
|
|
valkyrie.wing
Joined: 12 Oct 2017 Posts: 10
|
|
Posted: Sun Oct 22, 2017 1:15 am |
|
|
Hi Ttelmah,
I noticed the #use spi() has MODE=3 set. Why is so? Although AT25M02 supports both mode 0 and 3.
Now on using spi_xfer, I noticed you use the code snippet as:
Code: |
Result = spi_xfer(AT25,Address,32);
|
What interests me is spi_xfer transferring 32-bits to SPI bus? Is the MSB of this 32-bit the EEPROM command while the LSB is the LSB of the Address as illustrated below or the other way around ?
Code: |
-----------------------------------------------------------------------
| EEPROM Cmd (8bit) | Addr[23..16] | Addr[15..8] | Addr[7..0] |
-----------------------------------------------------------------------
|
Basically, I want to know how spi_xfer() works.
Thanks. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Sun Oct 22, 2017 1:42 am |
|
|
I remember some other EEPROM's proving more reliable with Mode3, than Mode0. On the basis of this I went with Mode3.
The manual does tell you about spi_xfer. It can be programmed in the #USE to transfer a specific number of bits, but defaults to allowing up to 32. When setup like this, you can then specify how many bits to send with the xfer.
SPI by default always transfers stuff MSB/MSb first. So if you look at the data sheet for the chip it shows 32bits being clocked to it, comprising the command, and then 24bits of address. I decided to just do this as one transfer. It'd actually be fractionally simpler to define the commands as 32bit values, so 0x02000000 for example, but the compiler is 'smart' here and if you specify a constant value like this and then rotate it three bytes, it only takes the low one and transfers it directly into the high byte of the result, so 'nothing lost'.
It may well be wrong, as I said, not at a computer with the compiler at present, so typed it directly from your code, but it should be very close. |
|
|
valkyrie.wing
Joined: 12 Oct 2017 Posts: 10
|
|
Posted: Wed Oct 25, 2017 5:16 pm |
|
|
Hi Ttelmah,
Already tested the code and it works perfectly. Thank you for that clever suggestions. |
|
|
ece_jm
Joined: 13 Feb 2018 Posts: 1
|
|
Posted: Tue Feb 13, 2018 6:23 pm |
|
|
Thanks for this. I am working on the same chips PIC18F8722 and AT25M02 memory chip. What is the final test code that worked? |
|
|
valkyrie.wing
Joined: 12 Oct 2017 Posts: 10
|
|
Posted: Sat Mar 03, 2018 2:23 am |
|
|
The project was continued by my colleague and I do not know her progress while I was transferred to another project. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|