cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F746-Disco QuadSPI Bare Metal Indirect Read No Communication

ilyus
Senior II

Win 10, STM32CubeIDE 1.8.0

STM32F746-Disco

I'm learning QuadSPI and doing everything in bare metal with C, it gives me understanding of hardware.

QSPI Flash IC, mouser, with datasheet

Short summary of the problem: indirect read from QuadSPI Flash, no communication on datalines, Fifo treshold flag always set.

I've never worked with QuadSPI before, so I read the reference manual of STM32F746, seemed more or less clear, at least I was ready to give it a shot, it usually works this way.

I used TouchGFX generated code, which I opened in STM32CubeIDE, to grab some parameters of QuadSPI, and I also used a breakpoint while running TouchGFX program to check if my QuadSPI registers have same or similar values and if there is nothing important missing (since TouchGFX uses QuadSPI Flash differently, for one, not in indirect mode). I made sure my GPIO are configured identically, everything is clocked, etc.

So, here is how I setup the stuff:

void qspi_setup_indirect_mode(void){
 
	//NOR Flash MT25QL128ABA 128Mb=16MB
 
	/*
	 * Control Register
	 * Prescaler: Fclock = AHBclock/2
	 * FIFO Threshold: FTF is set when 4 or more bytes available to be written to FIFO
	 *
	 * */
	QUADSPI->CR |= QUADSPI_CR_ABORT; //abort ongoing commands
	while(QUADSPI->CR & QUADSPI_CR_ABORT); //wait while aborted
	QUADSPI->CR = 0x00; //reset register
	QUADSPI->CR |= QUADSPI_CR_SSHIFT | (0x03 << QUADSPI_CR_FTHRES_Pos) | (0x01 << QUADSPI_CR_PRESCALER_Pos);
 
	/*
	 * Device Configuraion register
	 * Clock Mode 0 (default)
	 * Chip Select High Time 6 Cycles
	 * Flash Size 16MB = 2^24 bytes
	 *
	 * */
	QUADSPI->DCR = 0x00; //reset
	QUADSPI->DCR |= (0x05 << QUADSPI_DCR_CSHT_Pos) | (23U << QUADSPI_DCR_FSIZE_Pos);
 
	/*
	 * Communication configuration register
	 * Address size 24 bits
	 *
	 * */
	QUADSPI->CCR = ((QUADSPI->CCR) & (~(0x03 << QUADSPI_CCR_ADSIZE_Pos))) | (0x02 << QUADSPI_CCR_ADSIZE_Pos);
 
	QUADSPI->CR |= QUADSPI_CR_EN; //enable QUADSPI
}

This seems to mirror TouchGFX QuadSPI parameters.

Next, I setup instruction and data on a single line of QuadSPI, while address, alternate bytes, and dummy cycles are off. Then I set indirect read mode, set that I expect to receive 20 bytes of Flash ID as per the datasheet of the Flash IC, and I clear QSPI flags. Next I load instruction itself - the command to read ID - intro the INSTRUCTION bits of the communication configuration register. As per reference manual, this is supposed to trigger communication immediately. Next my code just waits until fifo threshold is reached - 4 bytes - to arrive so I can read them all at once as uint32_t from the Data Register. I don't care what happens to the next bytes for now, I'm not handling them, I'll worry about it when I see I that receive anything at all.

void qspi_read_id(void){
 
	qspi_set_command_mode(QSPI_IMODE_1, QSPI_ADMODE_0, QSPI_ABMODE_0, 0x00, QSPI_DMODE_1);//set command mode, 1 data line for instruction, 1 data line for data
 
	QUADSPI->CCR &= ~QUADSPI_CCR_FMODE | (0x01 << QUADSPI_CCR_FMODE_Pos); //indirect read mode
 
	QUADSPI->DLR = (uint32_t) 19; //expect to receive 20 bytes
 
	QUADSPI->FCR |= QUADSPI_FCR_CTOF | QUADSPI_FCR_CSMF | QUADSPI_FCR_CTCF | QUADSPI_FCR_CTEF;
 
	QUADSPI->CCR = (MT25QL128ABA1EW9_COMMAND_READ_ID_1 << QUADSPI_CCR_INSTRUCTION_Pos); //must trigger command transmission
 
	while(!(QUADSPI->SR & QUADSPI_SR_FTF)); //wait until there are 4 bytes of data to read
 
 
 
}
void qspi_set_command_mode(uint8_t imode, uint8_t admode, uint8_t abmode, uint8_t dcyc, uint8_t dmode){
	/*
	 * Communication configuration register
	 * First, reset all mode values
	 * Set new values
	 * */
	QUADSPI->CCR = QUADSPI->CCR & ~(QUADSPI_CCR_IMODE) & ~(QUADSPI_CCR_ADMODE) & ~(QUADSPI_CCR_ABMODE) & ~(QUADSPI_CCR_DCYC) & ~(QUADSPI_CCR_DMODE);
	QUADSPI->CCR = QUADSPI->CCR | (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);
}

Finally, my simple tiny main() that is supposed to test all of this:

#include "main.h"
 
void system_hw_setup(void);
 
int main(void) {
 
	system_hw_setup(); //initialize hardware
 
	uint8_t receivedQSPIdata[4];
 
	while (1) {
		system_msdelay(1000U);
                //BREAKPOINT HERE
		qspi_read_id();
		uint32_t temp = QUADSPI->DR;
		receivedQSPIdata[0] = temp & 0xFF;
		receivedQSPIdata[1] = (temp >> 8) & 0xFF;
		receivedQSPIdata[2] = (temp >> 16) & 0xFF;
		receivedQSPIdata[3] = (temp >> 24) & 0xFF;
		usart_dma_sendArray(USART1, receivedQSPIdata, sizeof(receivedQSPIdata));
 
	}
}

system_hw_setup initializes clocks and all hardware, UART, DMA, NVIC, Systick etc., including qspi_setup_indirect_mode(). All other functions work 100%, including uart dma.

If I put a breakpoint in the loop so that the program pauses before every qspi_read_id() call. Before read_id is called the very first time, the only flag in QuadSPI->SR status register is Fifo Threshold Flag. If I continue the program, 1 second later I'm paused right before calling read_id again, and I already have flags "busy", "transfer complete" and the same "fifo threshold".

The D0 line is definitely firmly stuck at 0v, the scope never triggers. Chip select goes crazy after the first time read_id is called and never stops even while MCU is paused on a breakpoint:

0693W00000NqZBFQA3.jpg 

I have obviously misconfigured something, but I'm out of ideas. I read some materials on the topic, will keep researching too, but I was hoping maybe someone could point me in the right direction.

1 ACCEPTED SOLUTION

Accepted Solutions
ilyus
Senior II

I still don't know, what I did wrong, it's clearly some misconfiguration somewhere, but I managed to solve it. I took parts from this article. I skipped clock enable and GPIO configuration, I'm sure they're correct, and I only copied a pair of code in the initialization part, after all, I needed only to see the first transmission to know it's working. I had to modify that code to fit my Flash IC. This is my code that worked (I scoped a regular communication):

	// QSPI peripheral initialization.
	// Set Flash size; 128Mb = 16MB = 2^(23+1) bytes.
	QUADSPI->DCR |= (23 << QUADSPI_DCR_FSIZE_Pos);
	// Set 1-wire data mode with 24-bit addressing.
	QUADSPI->CCR |= ((2 << QUADSPI_CCR_ADSIZE_Pos) | (1 << QUADSPI_CCR_IMODE_Pos));
 
	// Wait an extra half-cycle to read, and set a clock prescaler of 2+1=3.
	QUADSPI->CR |= ( QUADSPI_CR_SSHIFT | (2 << QUADSPI_CR_PRESCALER_Pos));
	// Enable the peripheral.
	QUADSPI->CR |= ( QUADSPI_CR_EN);
 
	// Set the 'enter QSPI mode' instruction.
	QUADSPI->CCR |= (MT25QL128ABA1EW9_COMMAND_ENTER_QUAD_IO_MODE << QUADSPI_CCR_INSTRUCTION_Pos);
	// Wait for the transaction to complete, and disable the peripheral.
	while ( QUADSPI->SR & QUADSPI_SR_BUSY) {
	};
	QUADSPI->CR &= ~( QUADSPI_CR_EN);

It's poorly commented because it's a copy-paste from the article. But it actually works, I call it regularly in the loop and I see stuff jumping on my scope, no crazy waveforms on chip select. I guess I'll take a step back now and think about what I did wrong initially, then style the code up a bit.

Now the real work begings. Interrupts, DMA and other pretty stuff.

View solution in original post

7 REPLIES 7
ilyus
Senior II

I have found a reasonable bare metal QSPI indirect mode article here. But I don't see any difference in principle comparing to what I'm doing. I have a feeling the problem could be buried someone in gpio/clock setup, but I have access to QuadSPI registers. Anyway, I'm searching further.

ilyus
Senior II

I've compared GPIO configuration of working QSPI from TouchGFX and my QSPI. Used breakpoints and compared registers. QSPI pins are configured identically in my setup and in TouchGFX working QSPI example.

Pavel A.
Evangelist III

Here you can find simple (no GUI) QSPI examples for the board.

Run one of these and verify that it works as expected.

ilyus
Senior II

Well, first of all, GUI example works OK, I can scope the communication. Second of all, I couldn't find any non-gui QSPI examples for any disco boards.

STM32Cube_FW_F7_V1.16.0\Drivers\BSP\STM32746G-Discovery\stm32746g_discovery_qspi.c

STM32Cube_FW_F7_V1.16.0\Projects\STM32746G-Discovery\Examples\QSPI\QSPI_PreInitConfig

STM32Cube_FW_F7_V1.16.0\Projects\STM32746G-Discovery\Examples\QSPI\QSPI_ReadWrite_IT

#WhereAreMyPants

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..

interestingly enough, I tried the preinitconfig example, and it didn't work. Compiled it ok, but no communication. Communication only on TouchGFX project visible. As for _discovery_qspi.c, it's a part of TouchGFX project too, I studied it from top to bottom even before opening this question, it was there where I took params for initialization.

Anyway, for now everything has failed (except TouchGFX-generated project), so I will be comparing it to my project again, maybe put TouchGFX-generated code into my project and see when it starts working. And then putting my code back piece by piece to see where it breaks.

ilyus
Senior II

I still don't know, what I did wrong, it's clearly some misconfiguration somewhere, but I managed to solve it. I took parts from this article. I skipped clock enable and GPIO configuration, I'm sure they're correct, and I only copied a pair of code in the initialization part, after all, I needed only to see the first transmission to know it's working. I had to modify that code to fit my Flash IC. This is my code that worked (I scoped a regular communication):

	// QSPI peripheral initialization.
	// Set Flash size; 128Mb = 16MB = 2^(23+1) bytes.
	QUADSPI->DCR |= (23 << QUADSPI_DCR_FSIZE_Pos);
	// Set 1-wire data mode with 24-bit addressing.
	QUADSPI->CCR |= ((2 << QUADSPI_CCR_ADSIZE_Pos) | (1 << QUADSPI_CCR_IMODE_Pos));
 
	// Wait an extra half-cycle to read, and set a clock prescaler of 2+1=3.
	QUADSPI->CR |= ( QUADSPI_CR_SSHIFT | (2 << QUADSPI_CR_PRESCALER_Pos));
	// Enable the peripheral.
	QUADSPI->CR |= ( QUADSPI_CR_EN);
 
	// Set the 'enter QSPI mode' instruction.
	QUADSPI->CCR |= (MT25QL128ABA1EW9_COMMAND_ENTER_QUAD_IO_MODE << QUADSPI_CCR_INSTRUCTION_Pos);
	// Wait for the transaction to complete, and disable the peripheral.
	while ( QUADSPI->SR & QUADSPI_SR_BUSY) {
	};
	QUADSPI->CR &= ~( QUADSPI_CR_EN);

It's poorly commented because it's a copy-paste from the article. But it actually works, I call it regularly in the loop and I see stuff jumping on my scope, no crazy waveforms on chip select. I guess I'll take a step back now and think about what I did wrong initially, then style the code up a bit.

Now the real work begings. Interrupts, DMA and other pretty stuff.