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.

EK-TM4C123GXL: SSI to Serial Flash interface

Part Number: EK-TM4C123GXL

Hi

I am trying to talk to a SPI Flash Memory AT45DQ321 using the TM4C123 Launchpad.. I couldn't get the ID. Could someone please give me feedback on my code below. I'm using SSI2 on port B.

Thanks. 

AJ

void InitializeFlash(void)
{
    uint32_t dumpByte, *p;

    SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2))
    {
    }//end while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2))

    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB))
    {
    }//end while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB))

    GPIOPinConfigure(GPIO_PB4_SSI2CLK);
    //GPIOPinConfigure(GPIO_PB5_SSI2FSS);
    GPIOPinConfigure(GPIO_PB6_SSI2RX);
    GPIOPinConfigure(GPIO_PB7_SSI2TX);

    GPIOPinTypeSSI(GPIO_PORTB_BASE, GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7);

    SSIConfigSetExpClk(SSI2_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0,
                       SSI_MODE_MASTER, 1000000, 8);

    GPIOPinTypeGPIOOutput(GPIO_PORTB_BASE, GPIO_PIN_5);
    GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_5, GPIO_PIN_5);

    SSIEnable(SSI2_BASE);

    p = (uint32_t*)&dumpByte;


    while(SSIDataGetNonBlocking(SSI2_BASE, p))
    {
    }

}//end void InitializeFlash(void)
/***********************************************/

void ReadID(void)
{
    uint32_t x, *p;

    p = (uint32_t*)&x;

    GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_5, 0x00);

    SSIDataPut(SSI2_BASE, 0x9F);
    while(SSIBusy(SSI2_BASE))
    {
    }
    SSIDataGet(SSI2_BASE, p);
    FlashBuffer[0] = 0x000000FF & x;

    SSIDataPut(SSI2_BASE, 0x00);
    while(SSIBusy(SSI2_BASE))
    {
    }
    SSIDataGet(SSI2_BASE, p);
    FlashBuffer[1] = 0x000000FF & x;

    SSIDataPut(SSI2_BASE, 0x00);
    while(SSIBusy(SSI2_BASE))
    {
    }
    SSIDataGet(SSI2_BASE, p);
    FlashBuffer[2] = 0x000000FF & x;

    SSIDataPut(SSI2_BASE, 0x00);
    while(SSIBusy(SSI2_BASE))
    {
    }
    SSIDataGet(SSI2_BASE, p);
    FlashBuffer[3] = 0x000000FF & x;

    SSIDataPut(SSI2_BASE, 0x00);
    while(SSIBusy(SSI2_BASE))
    {
    }
    SSIDataGet(SSI2_BASE, p);
    FlashBuffer[4] = 0x000000FF & x;

    GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_5, GPIO_PIN_5);

}//end void ReadID(void)

  • By the way, I keep on reading zeros for all the bytes
  • Hi AJ,

    I took the liberty of updating your post. I changed the subject line and I used the "Insert Code, Attach Files and more..." button </> to paste your C code. Changing the subject makes it easier for someone else to understand what the post is about. Using the </> button to paste the code makes it much more readable. The more people in the community that can easily read your post, the more help you are likely to get.

    Did you look at the SSI2 signals with a scope? I built a project using your code and I see SSICLK, SSITX and PB5 used as CS coming out OK. I would double check the hardware connections to the AT45DQ321.
  • Hi Bob,

    Thanks for the response. I appreciate it. I actually figured out what was going on. But the real problem was R9 and R10 on the launchpad. Turns out there is a jumper connected to PB7 and PB6 to port D pins for MSP compatibility. I took those resistors out and I was able to get bytes in. The only thing that bugs me is that the first ssiget right after sending the op code returns 0x00. I get the first actual ID byte 1F after that after sending the first dummy byte. I was under the impression that id get 1F right after sending op code. Any thoughts to why this is the case?

    Thanks once again

    Regards,

    AJ

  • AJ_ee said:
    I get the first actual ID byte 1F after sending the first dummy byte.   I was under the impression that id get 1F right after sending op code.

    As you, "Put" to Output the "Op Code Byte" (0X9F) - the Slave's output is "high impedance."    Yet this "non-data" does arrive at the MCU's SPI Port Input.    And then - at your first "Get" - it is this "garbage data" (0X00) - which your MCU store's w/in its FIFO & presents via "Get."    Your next "Put" (dummy data) then properly acquires your Slave's "Manuf. ID (0X1F)" - placing it too w/in the FIFO.     Thus it appears that you may either "Accept this single byte offset - or discover the means to "clear the SPI FIFO" prior to your first "Get."

    I have been employing another vendor's ARM MCU - thus cannot recall the exacting procedure to "clear the FIFO" - or even - if such exists.     (others here should be able to fill that gap.)

    Many here have "long noted" (and loudly protested) that dreadful choice to "dead short" 2 pairs of MCU pins - much responsible for your "excess effort & frustration!"     "Plauge-Istors" (R9/10) should have been included in a bag - and installed by those (very FEW) who may actually seek such "compatibility."

    BTW - that Serial Flash is really "top cabin" - we especially like the presence of TWO 512 Byte Rams - which enable rapid chip loads (while) the data is being transferred from Ram to Flash - ideal...

  • cb1_mobile said:
    I have been employing another vendor's ARM MCU - thus cannot recall the exacting procedure to "clear the FIFO" - or even - if such exists.     (others here should be able to fill that gap.)

    AFAIK the only method to clear the FIFO is to read it until it is empty. It should be a matter of routine to do that as a matter of the initialization code.

    Robert

  • Initializing the FIFO - just as you suggest - makes great sense.

    Yet - it is suspected that (both) poster and this reporter prove "unable to recognize WHEN it is empty?"      Might you advise?      (perhaps marker bytes are to be pre-loaded - and upon their absence (achieved via repeated "PUT/GETs" - we have thus proceeded one FIFO byte past empty?    What then?)

    As poster very well noted - and I confirmed - device outputs "Hi-Z" as the Op Code is entered.   (via the MCU's PUT)     And then - during the subsequent 8 SPI Clocks - the Manuf. ID (0x1F) is emitted.    Yet our poster reports receipt of "first 0x00" not that proper (0x1F)!

    Most always the advice here is to "PUT and then GET" - does this prevent the "back to back" use of GETs - minus their leading PUTs?     (i.e. is it not possible for a 2nd "GET" immediately following an earlier one - minus ANY "PUT" - to output SPI Clocks - which should return external chip data?    

    (External chip's data sheet notes that the device outputs needed data upon the receipt of subsequent SPI clocks - pays no attention to MCU Data Output - during those clockings...)

  • cb1_mobile said:

    Initializing the FIFO - just as you suggest - makes great sense.

    Yet - it is suspected that (both) poster and this reporter prove "unable to recognize WHEN it is empty?"      Might you advise?

    You may be overthinking this.

    From the user manual for SSIDataGetNonBlocking

    TIVAWare User Manual said:

    Description:
    This function gets received data from the receive FIFO of the specified SSI module and places
    that data into the location specified by the ui32Data parameter. If there is no data in the FIFO,
    then this function returns a zero.

    My memory is that the TIVAWare examples use this approach.

    Robert

  • OK - thanks much for that - yet is, "Returning a zero" somewhat "Less than Comforting?"

    Is not "real data" sometimes of "zero" value?      (Note - I get "interference" from working w/other ARM MCUs (180MHz & up) - so cannot recall the particulars of those here...)

    Your efforts are appreciated - yet is not my concern valid?    (i.e. zero is a terribly common, received, SPI value!)   Again thank you, Robert...

    [edit/added] Does it not appear that the ability of "GET" to generate SPI Clocks - by itself - is the real issue here?      The intrusion of the "8 excess clocks" - created by the SPI "PUT" - proves of no value in this external device's case - yet causes the arrival of "nonsense" UNWANTED excess data...    Poster should check the source code to determine if "GET" emits SPI Clocks...

  • cb1_mobile said:
    Your efforts are appreciated - yet is not my concern valid?

    It would be if the return value contained the data. The return value is only the status (technically it's the number of data items but since it can only read one item the return value effectively must be zero or one).

    The data is actually returned via a pointer passed in the call. A fairly common calling strategy to allow testing the status in a simple inline fashion. The other way to do in C* would be to pass a pointer to a status flag and return the data but that approach usually turns out to be more awkward since you can no longer do a simple inline test and you must always assign a data value even if there wasn't valid data to return. It doesn't make a lot of difference but the first approach generally produces easier to follow code and a number of style guides recommend only using the return value for status (or less restrictively only using the return value for a value if there is no status value to return).

    Robert

    * C++ has additional options, whether they are suitable for embedded systems is another question.

  • Thank you - yet I suspect that poster (and surely I) remain confused.

    Is not, "SSIDataGetNonBlocking()" as you've graciously (earlier) supplied/defined - the function used to "receive SPI Data?"       Yet the manual (via your presentation) introduces the (weasel word) "Returned" - and by my (now repeated) read - both Data & Return - emanate from "SSIDataGetNonBlocking()."

    That cannot be correct - can it?     It would appear advantageous to employ separate functions to "Test for Empty" AND to "Extract valid SPI received data from the SPI FIFO."      Is this the case?    Is it (only) that SPI "GET" receives the valid SPI Data from the SPI FIFO?       Again thanks.

  • cb1_mobile said:
    Is not, "SSIDataGetNonBlocking()" as you've graciously (earlier) supplied/defined - the function used to "receive SPI Data?"

    Yes

    cb1_mobile said:
     Yet the manual (via your presentation) introduces the (weasel word) "Returned" - and by my (now repeated) read - both Data & Return - emanate from "SSIDataGetNonBlocking()."

    Let me quote the whole description

    TIVAWare User manual said:

    24.2.2.11 SSIDataGetNonBlocking


    Gets a data element from the SSI receive FIFO.


    Prototype:


    int32_t SSIDataGetNonBlocking(uint32_t ui32Base, uint32_t *pui32Data);

    Parameters:

    • ui32Base specifies the SSI module base address.
    • pui32Data is a pointer to a storage location for data that was received over the SSI interface.


    Description:


    This function gets received data from the receive FIFO of the specified SSI module and places that data into the location specified by the ui32Data parameter. If there is no data in the FIFO,
    then this function returns a zero.

    Note: Only the lower N bits of the value written to pui32Data contain valid data, where N is the data width as configured by SSIConfigSetExpClk(). For example, if the interface is configured for 8-bit data width, only the lower 8 bits of the value written to pui32Data contain valid data.


    Returns:

    Returns the number of elements read from the SSI receive FIFO.

    The description does not emphasize (and probably should) that it only reads a single item from the FIFO. Note that the description does not use the word return to describe the storing of the read data. Note that this is a very common calling technique.

    cb1_mobile said:
      It would appear advantageous to employ separate functions to "Test for Empty" AND to "Extract valid SPI received data from the SPI FIFO."      Is this the case?

    I'd say not*. I don't think there is much advantage to using two calls where the single call works cleanly (note the caveat).

    cb1_mobile said:
     Is it (only) that SPI "GET" receives the valid SPI Data from the SPI FIFO? 

    If I understand the question the non-blocking get is the only call that needs a status. The blocking get doesn't return until there is data and so doesn't need the status. Note that a flushing loop might be implemented something like (uncommented, untested, just to show the structure)

    void flush_spi(void)
    {
        uint32_t data;
    
        while(SSIDataGetNonBlocking(SPI_Base, &data) != 0){
             }
    }
    

    While reading into a fixed buffer might look like (you'd obviously want to improve on this but it gets the idea across)

    void read_spi_device_buffer(uint32_t *data, size_t buffersize)
    {
    
        while(buffersize > 0) { 
             if(SSIDataGetNonBlocking(SPI_Base, &data) != 0){
                  data++;
                  buffersize--; 
                  }
             }
    }
    

    So I don't think adding a second call clarifies the code here. I don't think it's the case for the SSI but for some hardware the only way to ascertain if there is data is to attempt to read it. In that case breaking this into separate calls would make the called code considerably more complex for little advantage.

    Robert

    * I do have an issue with the read into pointer being a standard fixed 32 bit type but that doesn't come into this discussion. The same is true of the base argument.

  • Thank you Robert - that was a terrific - highly caring, superbly detailed, and extensive tech explanation.    I am (again) very much in your debt.

    I may be (especially thick) today (staff notes "every day") yet still I'm impaled by the chance that "Valid SPI Data of value zero" may appear w/in the FIFO - and such may be improperly interpreted (instead of as data) as "FIFO Empty!"     (Specifically - many of our Display Controllers employ 16 bit registers - accessed via SPI - and it is not rare that one of those 2 bytes (making up the 16 bit register) contains zero!)

    I've read (and reread) the manual descriptions you've graciously supplied - yet still I find "No Answer" to the appearance of "zero" w/in valid SPI FIFO data - as "Other than FIFO Empty Signal!"    It appears to me that such eventuality has "not been properly/fully considered" - thus the "Check for zero" is flawed.     I hope I've explained this sufficiently.

    Here's a key extract from poster's (very desirable) device (again it contains over 1KB of fast static RAM - which may be "auto-transferred" to EEProm - without slowing the "Write to RAM!"

    My point in introducing this device's timing diagram is to show that, "Should both "PUTs" and "GETs" output 8 SPI Clocks - we are (needlessly interweaving) "Garbage bytes" - w/in otherwise pristine SPI Received Data.   That cannot be good - and may be avoided if an "SPI GET" is able - on its own (minus use of the earlier PUT) - to generate the 8 SPI Clocks - as required by the diagram, above.

    So two issues remain:

    • How does one distinguish the "zero" resulting from "SSIDataGetNonBlocking()" from Valid SPI Received Data ... OR ... the "Signal" that the SPI is Empty?      Such remains unclear.
    • Can use of "GET" alone prove sufficient to "pull data from the SPI Slave" - avoiding the input of "meaningless data" caused by the 8 "excess clocks" introduced by the interwoven "PUT?"

    I note that the device (just described) proves "reasonably standard" - in presenting multiple "back to back" data bytes - which appear both "Delayed and Needlessly Interwoven w/Garbage Data - caused by the interwoven yet meaningless "PUTs."

  • cb1_mobile said:
    I'm impaled by the chance that "Valid SPI Data of value zero" may appear w/in the FIFO - and such may be improperly interpreted (instead of as data) as "FIFO Empty!"

    I think the issue is you are confusing two different meanings of the word return. The user manual carefully avoids using the word return as a synonym for reading the SPI FIFO, I was not so careful.

    Expanding a little on my previous example

    void readfunc(void)
    {
        unit32_t dev_dat[6];
    
        read_spi_device_buffer( dev_dat, (size_t)4):
    }
    
    void read_spi_device_buffer(uint32_t *data, size_t buffersize)
    {
    
        while(buffersize > 0) { 
             if(SSIDataGetNonBlocking(SPI_Base, &data) != 0){
                  data++;
                  buffersize--; 
                  }
             }
    }

    Let's assume for the moment that you call readfunc and there are four values in the FIFO. The first two are zero, the third is one and the fourth is two. Now let's walk through the execution of the read_spi_buffer function and note

    1. the return of the SSIDataGetNonBlocking calls and
    2. the values put into the dev_dat array in the calling function.

    First call to SSIDataGetNonBlocking

    1. returns 1 since data is read
    2. dev_dat[0] is set to 0

    Second call

    1. returns 1 since data is read
    2. dev_dat[1] is set to 0

    Third call

    1. returns 1 since data is read
    2. dev_dat[2] is set to 1

    Fourth call

    1. returns 1 since data is read
    2. dev_dat[3] is set to 2

    At this point there will be no fourth call since the routine has read all the values it has been asked to read.

    Let's do a slightly different example where the FIFO has for some reason not filled until the third call but at that point the FIFO has the same values mentioned earlier

    First call to SSIDataGetNonBlocking

    1. returns 0 since there is no data to read
    2. It's not documented whether dev_dat[0] is changed. Best to assume it has but the value is not valid so it should be ignored.

    Second call

    1. returns 0 since there is no data to read
    2. It's not documented whether dev_dat[0] is changed. Best to assume it has but the value is not valid so it should be ignored.

    Third

    1. returns 1 since data is read
    2. dev_dat[0] is set to 0

    Fourth call

    1. returns 1 since data is read
    2. dev_dat[1] is set to 0

    Fifth call

    1. returns 1 since data is read
    2. dev_dat[2] is set to 1

    Sixth call

    1. returns 1 since data is read
    2. dev_dat[3] is set to 2

    At this point there will be no fourth call since the routine has read all the values it has been asked to read.

    Does that help clear things up and show how data and status are not intermingled?

    Robert

    Note that in my clear FIFO example I deliberately ignore any values read from the FIFO by continuously writing over previously read values.

  • Robert,

    Again - great thanks - I sense that I need to dig up one of our many LPads - and enter the code - and observe under IAR.

    I remain "impaled" right here:

    if (SSIDataGetNonBlocking(SPI_Base, &data) != 0)

    Forgive me - yet (still) I cannot see how - when "&data" extracts the value of "zero" - that "IF test is failed" and the two "conditional operations w/in" this "IF" are bypassed.

    Perhaps the earlier "read func()" enables the code to distinguish the difference between "FIFO Empty" and "Legitmate FIFO zero value data."

    With other ARM devices we employ - a separate bit informs, "when the FIFO is empty" - thus there is no, "Possibility of Conflict" between valid FIFO Data (even @ zero value) and the "FIFO Empty Signal" (ALSO zero!)

    The use of "GET" - but without the (always) linked/earlier "PUT" - and "GET's" ability to generate SPI clocks - also remains "useful" here - particularly to thread's creator...

    Again great thanks - I remain unable to properly understand how "zero" is "matched" to its cause agent...

  • Hmm, there's a syntax error there that should be

    if(SSIDataGetNonBlocking(SPI_Base, data) != 0){

    Hopefully that isn't affecting you're interpretation.

    Notice that the value of *data is never tested, only the return value of SSIDataGetNonBlocking which is different entirely.

    cb1_mobile said:
    With other ARM devices we employ - a separate bit informs, "when the FIFO is empty" - thus there is no, "Possibility of Conflict" between valid FIFO Data (even @ zero value) and the "FIFO Empty Signal" (ALSO zero!)

    There is also no possibility of that happening here. That data and status are always separate.

    Robert

    let me see if I can come up with a simplified standalone example

  • Eureka!     Even while "home sick" today - maybe now this sentence (alone) "helps me to get this."       (love when that happens!)

    "Notice that the value of *data is never tested, only the return value of SSIDataGetNonBlocking which is different entirely."

    Is it true then that the "GET" is the function which "extracts the SPI Data from the FIFO?"        And - if that proves the case - the use of the "enforced linkage" (between PUT and GET) causes unwanted nonsense bytes to be transferred) does this meet your agreement?

  • Getting rid of the I/O baggage, see if this makes it any clearer. Note that in this case I've additionally made the status and the calculated (read) values different types which might help show that they travel distinct and completely separate paths.

    #include <math.h>
    #include <stdio.h>
    #include <stdbool.h>
    
    bool restricted_sine(double angle, double *result)
    {
        if((angle < 0.0) || (angle > 3.0)) {
             return false;
             }
        else {
            *result = sin(angle);
            return true;
            }    
    
    }
    
    int main()
    {
        double res;
    
        if( restricted_sine(2.0, &res)){
             printf("Successful, result = %lf\n", res);
             }
        else {
             printf("Unsuccessful\n");
             }
        if( restricted_sine(4.0, &res)){
             printf("Successful, result = %lf\n", res);
             }
        else {
             printf("Unsuccessful\n");
             }
    }

    This is again completely untested but should run if the inevitable typos are cleaned up. It should produce something like

    Successful, result = 0.909
    Unsuccessful

    If that makes sense do you see how the same thing happens with the non blocking read to keep status and data separate so that 0 data is never interpreted as a status (and indeed cannot be).

    If not then hand execute it. If there is cross contamination show me where and how it occurs.

    Robert

  • cb1_mobile said:
    Even while "home sick" today - maybe now this sentence (alone) "helps me to get this."

    We suffer the same fate. I had a two hour tooth extraction yesterday, not at my most alert today.

    cb1_mobile said:
    Is it true then that the "GET" is the function which "extracts the SPI Data from the FIFO?"        

    Yes

    cb1_mobile said:
       And - if that proves the case - the use of the "enforced linkage" (between PUT and GET) causes unwanted nonsense bytes to be transferred) does this meet your agreement?

    Yes, you still have to do the usual pruning to get just the data you want. That is a device specific action. The FIFO does allow you to put out a command string and then read the whole batch back in rather than needed to do it 'byte by byte'. That is as long as the transaction will fit in the FIFO. And in the case of clearing the FIFO the entire transaction (if any) has already occurred, you're just reading and throwing away any junk that may be in the FIFO, whatever the original cause.

    Robert

  • Again thanks - my condition is "nausea/dizziness" 12 hours in bed last 2 days. Yours sounds (very) non-fun.

    Is it then true that a, "Sequence of "JUST GETS" (minus their accompanying PUTS) will not succeed? (for this to be true - I'd expect that GET (alone) does not yield the required SPI Clocks - instead GET just "extracts from the FIFO.") Maybe?
  • cb1_mobile said:
    Again thanks - my condition is "nausea/dizziness" 12 hours in bed last 2 days

    And the rest on the couch? Sounds uncomfortable and even annoying.

    cb1_mobile said:
    Yours sounds (very) non-fun.

    Can't say as I'm enjoying it but at least it seems (maybe?) to be improving. Towards the end the dentist kept saying "I know you're tired but can you open your mouth wider" I was thinking "I'm tired? You're the one that's been wielding the carpentry and masonry tools for that last hour and a half"

    cb1_mobile said:
    Is it then true that a, "Sequence of "JUST GETS" (minus their accompanying PUTS) will not succeed? (for this to be true - I'd expect that GET (alone) does not yield the required SPI Clocks - instead GET just "extracts from the FIFO.")

    True as long as the FIFO was previously empty, that's why you can use them to empty the FIFO and why you must empty the FIFO when initializing. Clocks are only generated by the put.

    That's also why you have to be careful about using the blocking form or spinning on the get. Trying to read one too many entries will result in the thread stopping and spinning waiting futilely for another entry.

    Robert

  • Yours sounds "worse than mine" - yet shelter dog & cat sense "something's up" - torment me "less than normal."
    As to "dental commands" - I always love the one to, "Relax!" Really - you've got that 20K RPM BLDC drill - headed deep into my "pain center" - and I'm to relax?

    It appears then that what the poster notes IS normal - and he'll receive, "Garbage bytes interspersed w/real data" courtesy of the PUT command.

    Great clarity & your "above/beyond" detail have made even a "sick person" understand more... Again much thanks...