embeddedstm32bare-metalstm32f7mcu

STM32F7, Bare Metal C, QSPI Indirect Mode Hangs or Writes Same Gibberish


Win 10, STM32CubeIDE 1.8.0
STM32F746 Discovery board

MCU: STM32F746 (Datasheet, Reference Manual, Errata)
Flash: MT25QL128ABA1EW9-0SIT (Datasheet)

Equipment: Low end oscilloscope, low end logic analyzer with decoder

What I'm trying to achieve: I want to be able to send command via indirect write (works OK), read register with indirect read (fails miserably with consistent garbage on the lines), haven't even tried to read/write actual memory.

Connections (from the discovery board schematic):

enter image description here

Interestingly enough, the example provided by STMicoelectronics themselves also doesn't work as expected. More on that later.

Initially, I read the reference manual and tried to figure the stuff out on my own, as I always do when I learn to operate new peripheral. It didn't exactly work out, so I used TouchGFX-generated code to compare configuration (it's using memory mapped mode, but I could at least check clock and GPIO setup, and it was correct), then I also found pretty much the only other article that does what I do. I was close, but a few unexplained (not covered in reference manual) bits from the article made it work. (Article)

I used only the early code from there. Up to and including the first bit of code under "Initialization" section, but not anything further. I adjusted it for my Flash size (128Mbit).

I will safely assume my clock and GPIO initialization is correct, as it matches TouchGFX code, which utilizes QSPI Flash, as well as example program from STM32F7 package.

I'm configuring QSPI with 1MHz clock. While it's not exactly covered in specs of the Flash IC, it's easier on my scope and logic analyzer, both of which have 100MHz as documented upper bound, but they're not really usable there. I also tried to use 108MHz, which is a documented Flash IC spec, I still get garbage there (found via debugging output).

QSPI setup:

void qspi_setup_indirect_mode(void) {

    /* ------------ QSPI Initialization --------------- */

    /*
     * Make sure QSPI is disabled
     * */
    QUADSPI->CR &= ~(QUADSPI_CR_EN);

    /*
     * Flash size 128Mb=16MB=2^24 bytes
     * 24-bit Address
     *
     * */
    QUADSPI->DCR = 0x00; //reset
    QUADSPI->CCR = 0x00; //reset
    QUADSPI->DCR |= (23U << QUADSPI_DCR_FSIZE_Pos);
    QUADSPI->CCR |= (2U << QUADSPI_CCR_ADSIZE_Pos);

    /*
     * Sample shift 1/2 clock cycle
     * Prescaler = 2 (216MHz/216 = 1MHz)
     *
     * */
    QUADSPI->CR = 0x00; //reset
    QUADSPI->CR |= (QUADSPI_CR_SSHIFT | (215U << QUADSPI_CR_PRESCALER_Pos));

    /*
     * Make sure all flags are cleared
     *
     * */
    QUADSPI->FCR = QUADSPI_FCR_CTOF | QUADSPI_FCR_CSMF | QUADSPI_FCR_CTCF | QUADSPI_FCR_CTEF;

    /*
     * Enable peripheral
     * */
    //QUADSPI->CR |= (QUADSPI_CR_EN); (enable later for every transmission)
}

Then there is function, that sets command mode. It sets access mode (indirect write, read, polling, memory mapped), as well as on how many datalines instruction, address and so on are transmitted (from none to 4), and dummy cycles. Nothing fancy, very similar to the one from the example.

void qspi_set_command_mode(uint8_t fmode, uint8_t imode, uint8_t admode, uint8_t abmode, uint8_t dcyc, uint8_t dmode) {
    /*
     * Make sure QSPI is disabled
     * */
    QUADSPI->CR &= ~(QUADSPI_CR_EN);

    /*
     * Communication configuration register
     * First, reset all mode values
     * Set new values
     * */
    QUADSPI->CCR = QUADSPI->CCR & ~(QUADSPI_CCR_FMODE) & ~(QUADSPI_CCR_IMODE) & ~(QUADSPI_CCR_ADMODE) & ~(QUADSPI_CCR_ABMODE) & ~(QUADSPI_CCR_DCYC)
            & ~(QUADSPI_CCR_DMODE);
    QUADSPI->CCR = QUADSPI->CCR | (fmode << QUADSPI_CCR_FMODE_Pos) | (imode << QUADSPI_CCR_IMODE_Pos) | (admode << QUADSPI_CCR_ADMODE_Pos)
            | (abmode << QUADSPI_CCR_ABMODE_Pos) | (dcyc << QUADSPI_CCR_DCYC_Pos) | (dmode << QUADSPI_CCR_DMODE_Pos);
} 

I tried various minor changes to these functions, and write works if and only if I disable peripheral, configure the thing, enable, set the instruction. If I enable peripheral in the setup section, write doesn't work. This is not covered in reference manual, I found it in the article (where it's not pointed out).

void qspi_sendCommandIndirectWrite(uint8_t command) {
    QUADSPI->CR &= ~(QUADSPI_CR_EN); //disable qspi to configure

    QUADSPI->FCR = QUADSPI_FCR_CTOF | QUADSPI_FCR_CSMF | QUADSPI_FCR_CTCF | QUADSPI_FCR_CTEF; //clear all flags

    qspi_set_command_mode(0x00, 0x01, 0x00, 0x00, 0x00, 0x00); //Set indirect write, only instruction on 1 line, everything else off

    QUADSPI->CCR &= ~(0xFF << QUADSPI_CCR_INSTRUCTION_Pos); //clear instruction field

    QUADSPI->CR |= (QUADSPI_CR_EN);

    QUADSPI->CCR |= (command << QUADSPI_CCR_INSTRUCTION_Pos); //writing instruction starts communication

    while (QUADSPI->SR & QUADSPI_SR_BUSY); // Wait for the transaction to complete, and disable the peripheral.

    QUADSPI->CR &= ~(QUADSPI_CR_EN);
}

void qspi_sendCommandIndirectRead(uint8_t command, uint8_t receiveBuffer[], uint32_t length) {
    QUADSPI->CR &= ~(QUADSPI_CR_EN); //disable qspi to configure

    QUADSPI->FCR = QUADSPI_FCR_CTOF | QUADSPI_FCR_CSMF | QUADSPI_FCR_CTCF | QUADSPI_FCR_CTEF; //clear all flags
    qspi_set_command_mode(0x01, 0x01, 0x00, 0x00, 0x01, 0x01); //Set indirect write, only instruction on 1 line, , data on 1 line, 1 dummy cycle, everything else off
    QUADSPI->CCR &= ~(0xFF << QUADSPI_CCR_INSTRUCTION_Pos); //clear instruction field
    QUADSPI->DLR = length;
    QUADSPI->CR |= (QUADSPI_CR_EN);
    QUADSPI->CCR |= (command << QUADSPI_CCR_INSTRUCTION_Pos); //writing instruction starts communication
    uint32_t counter = 0x00;
    while (counter < length) {
        while (!(QUADSPI->SR & QUADSPI_SR_TCF)); //wait while data arrives to FIFO
        receiveBuffer[counter] = (uint8_t) (0xFF & QUADSPI->DR);
        counter++;
    }
    while (QUADSPI->SR & QUADSPI_SR_BUSY); // Wait for the transaction to complete, and disable the peripheral.

    QUADSPI->CR &= ~(QUADSPI_CR_EN);
}

Finally, all of that is called in the main the following way:

#include "main.h"

void system_hw_setup(void);
void qspi_example(void);

int main(void) {

    system_hw_setup(); //initialize hardware
    system_msdelay(100U);
    //qspi_sendCommandIndirectWrite(MT25QL128ABA1EW9_COMMAND_ENTER_QUAD_IO_MODE); //works OK

    //qspi_example(); //example provided by STM32 w clock and GPIO setup
    system_msdelay(100U);

    uint8_t test[1];
    while (1) {
        qspi_sendCommandIndirectRead(MT25QL128ABA1EW9_COMMAND_READ_STATUS_REGISTER, test, 1);
        //qspi_sendCommandIndirectRead(MT25QL128ABA1EW9_COMMAND_READ_ENHANCED_VOLATILE_CONFIGURATION_REGISTER, test, 1);

        system_msdelay(100U);
        toggle_stm32f746disco_ld1();
        /*
         test[0] = 0x00;
         if (test[0] == 0x00) {
         test[0] = (uint8_t) '0';
         }
         /usart_dma_sendArray(USART1, test, 1); */

    }
}

void system_hw_setup(void) {
    rcc_setup(); //clock for peripheral, clock will not be altered; therefore default HSI 16MHz
    systick_setup(SYSTEM_FREQUENCY); //activate systick
    gpio_setup(); //set pin modes and functions
    dma_reset_flags(DMA2); //clear DMA2 flags for USART1
    dma_reset_flags(DMA1); //clear DMA1 flags for I2C3
    usart_dma_setup(USART1); //set control registers and settings for USART1 and its DMA connected to st-link
    usart_enable(USART1); //enable uart1
    usart_enable_tx(USART1); //enable tx line (wrapper)
    usart_enable_rx(USART1); //enable rx line (wrapper)
    qspi_setup_indirect_mode(); //enable qspi in indirect mode
    nvic_setup(); //set interrupts and their priorities

}

which gives the following:

QSPI Logic analyzer

enter image description here

As per reference manual, data from the IC should come on DQ1, but it's not happening. Also, sometimes DQ3 randomly goes up for some time. The number of clock cycles is strange. Also, I have no idea why there is some 0x80 packet there, I'm sending only the instruction and nothing else. It could be related to my artificially lowered clock speed, but the same configuration also miserably fails if I set QSPI clock to proper value.

I'm pretty lost at what I'm doing wrong, and the reference manual section of the MCU is not much help at this point, and there are next to no resources on the internet that cover it in a meaningful (or any, at this point) way.

I would appreciate any help or advice with making QSPI work!


Solution

  • The main problem was the access to data register. QUADSPI->DR is a volatile uint32_t. So whenever I access QUADSPI->DR, even if I received 1 byte, it reads 4 bytes from the register, and it also produces gibberish with FIFO threshold because of that. The correct solution is to explicitly specify byte, half-word or word access to the QUADSPI->DR. I take address of the data register, cast it as a pointer to uint8_t or uint16_t, and dereference it:

    uint32_t mydata = QUADSPI->DR;
    uint16_t mydata = *(uint16_t*)(&QUADSPI->DR);
    uint8_t mydata = *(uint8_t*)(&QUADSPI->DR);