Skip to content

Conversation

@ArcaneNibble
Copy link
Collaborator

This PR implements a driver for the EV3 SPI flash memory.

@laurensvalk laurensvalk self-requested a review July 4, 2025 22:23
@ArcaneNibble
Copy link
Collaborator Author

ArcaneNibble commented Jul 5, 2025

Hmm, there seems to be a strange heisenbug here. I'm currently working on integrating the ADC into this code, but something I've done seems to have made code that (I promise!) was previously working no longer reach the REPL (hanging without any debug output).

Still investigating...

@ArcaneNibble
Copy link
Collaborator Author

With the latest commit the flash + ADC seem to both work (not at the same time but instead using the suggested hack).

I rewrote the entire ADC sampling logic to use DMA.

@dlech
Copy link
Member

dlech commented Jul 5, 2025

but something I've done seems to have made code that (I promise!) was previously working no longer reach the REPL (hanging without any debug output).

Rebasing on some changes I just pushed might help as I found and fixed a bug with the wrong entry point address in the uImage. Different builds could have the linker put the Entry at different places. See pybricks/support#2256 (comment)

Copy link
Member

@dlech dlech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't do an in-depth review since I know this is still a work in progress, but I like what I see so far. 😄

SPIModeConfigure(SOC_SPI_0_REGS, SPI_MASTER_MODE);
unsigned int spipc0 = SPI_SPIPC0_SOMIFUN | SPI_SPIPC0_SIMOFUN | SPI_SPIPC0_CLKFUN | SPI_SPIPC0_SCS0FUN0 | SPI_SPIPC0_SCS0FUN3;
SPIPinControl(SOC_SPI_0_REGS, 0, 0, &spipc0);
SPIDefaultCSSet(SOC_SPI_0_REGS, (1 << 0) | (1 << 3));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use macros here so we know what these bits are.

SPI_DATA_FORMATx are not really better though. I would introduce a new one that says SPI_BITS_PER_WORD(x)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added macros for 0 and 3

Not sure what you want to do about SPI_DATA_FORMATx? There is the large comment explaining how format 0 is allocated for the SPI flash while format 1 is allocated for the ADC.

SPIConfigClkFormat(SOC_SPI_0_REGS, SPI_CLK_POL_LOW | SPI_CLK_OUTOFPHASE, SPI_DATA_FORMAT1);
SPIShiftMsbFirst(SOC_SPI_0_REGS, SPI_DATA_FORMAT1);
SPICharLengthSet(SOC_SPI_0_REGS, 16, SPI_DATA_FORMAT1);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also set other config explicitly here even if it is the same as flash.

For example, SPIDefaultCSSet() is an interesting one. Omitting SPI_CSHOLD there is important for using the ADC with DMA so that it toggles the CS line between each word.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which other config settings were you thinking of? SPIDefaultCSSet doesn't configure the CSHOLD bit. That bit is in fact cleared by the DMA commands to SPIDAT1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSHOLD was the main one. I guess I copied and pasted the wrong fucntion name. But it is nice to see what the full config is all in once place without relying on the flash memory driver setting up some of this for us. (And could save us some trouble if we ever tweak the flash memory driver without realizing it also affects the ADC).

@laurensvalk
Copy link
Member

With the latest commit the flash + ADC seem to both work (not at the same time but instead using the suggested hack).

I rewrote the entire ADC sampling logic to use DMA.

Sounds great! Looking forward to check it out after the weekend and help you get it merged.

@ArcaneNibble ArcaneNibble marked this pull request as ready for review July 7, 2025 15:20
@ArcaneNibble ArcaneNibble changed the title [WIP] [EV3] Implement a SPI flash driver for the EV3 [EV3] Implement a SPI flash driver for the EV3 Jul 7, 2025
@ArcaneNibble
Copy link
Collaborator Author

I've cleaned up the history of this PR and marking it as ready for review.

laurensvalk and others added 14 commits July 8, 2025 13:31
We still need to add some logic to selectively enable the block device driver and the ADC driver, which both use SPI0 on EV3.
Can be used as a starting point for new contributors for the EV3 driver.
…ash driver.

This is derived from the W25Qxx_STM32 driver, but with most of the logic removed
to make room for the EV3 implementation.
…performing flash commands.

This is all of the logic needed to program the EDMA3 controller and the SPI peripheral,
but the logic to read/write flash is not implemented yet.
…riting.

This completes the storage implementation for EV3.
The ADC driver will be rewritten to use DMA, but the code for setting it up
is placed in the SPI flash driver in order to centralize the logic in one place.
This stub driver uses pbio rather than Contiki, and it is ready to be
integrated with the SPI flash driver (including waiting for the SPI bus to be available),
but the actual logic for talking to the ADC chip has not been implemented yet.
This ensures that the specified frequency is never exceeced.
@laurensvalk
Copy link
Member

Thanks! Rebased and applied ./tools/codeformat.py to each commit along the way. I've tested storage with hub.system.storage. The analog touch sensor is detected and works.

I've been working on pybricks/support#2264, but I propose to merge yours first for simplicity.

Since EV3 is under heavy development, I think we can merge it as-is and address open TODOs in follow up pull requests.

@coveralls
Copy link

Coverage Status

coverage: 57.091%. remained the same
when pulling 6f88e2d on ArcaneNibble:spiflash
into dd0dfbc on pybricks:master.

@laurensvalk laurensvalk merged commit 6f88e2d into pybricks:master Jul 8, 2025
17 checks passed
Comment on lines +59 to +62
(1 << 12) | \
(1 << 11) | \
(((x) & 0xf) << 7) | \
(1 << 6)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have some macros or comments to explain these bits.

Comment on lines +111 to +112
// XXX We probably need to figure out how atomicity works between the DMA and the CPU.
// For now, read the value twice and assume it's good (not torn) if the values are the same.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of worrying about tearing, I would do a double buffer. Just before we call pbdrv_adc_callbacks in the protothread where we drive the sampling, copy the DMA buffer to a 2nd buffer. We know we are not in the middle of a DMA operation at this point. Then have this function use the values from the 2nd buffer so that we know the data is good.

SPIConfigClkFormat(SOC_SPI_0_REGS, SPI_CLK_POL_LOW | SPI_CLK_OUTOFPHASE, SPI_DATA_FORMAT1);
SPIShiftMsbFirst(SOC_SPI_0_REGS, SPI_DATA_FORMAT1);
SPICharLengthSet(SOC_SPI_0_REGS, 16, SPI_DATA_FORMAT1);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSHOLD was the main one. I guess I copied and pasted the wrong fucntion name. But it is nice to see what the full config is all in once place without relying on the flash memory driver setting up some of this for us. (And could save us some trouble if we ever tweak the flash memory driver without realizing it also affects the ADC).

static inline void spi0_setup_for_flash() {
HWREG(SOC_SPI_0_REGS + SPI_SPIDELAY) = 0;

*(volatile uint16_t *)(SOC_SPI_0_REGS + SPI_SPIDAT1 + 2) =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use SPIDat1Config()?


// Helper functions for setting up the high control bits of a data transfer
static inline void spi0_setup_for_flash() {
HWREG(SOC_SPI_0_REGS + SPI_SPIDELAY) = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use SPIDelayConfigure()?


// SPI module data formats
SPIClkConfigure(SOC_SPI_0_REGS, SOC_SPI_0_MODULE_FREQ, SPI_CLK_SPEED_FLASH, SPI_DATA_FORMAT0);
// For reasons which have not yet been fully investigated, attempting to switch between
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the TRM on the DEFSEL field description of SPIDAT1, it says:

Note: Preselecting a Format Register. Writing to just the control field (using byte writes) does
not initiate any SPI transfer in master mode. This feature can be used to set up SPIx_CLK phase
or polarity before actually starting the transfer by just updating the DFSEL fields in the control field
to select the required phase/polarity combination.

so maybe not a glitch, but intentional?

So it sounds like we should swap the order of the config here.

SPIOutOfReset(SOC_SPI_0_REGS);
SPIModeConfigure(SOC_SPI_0_REGS, SPI_MASTER_MODE);
unsigned int spipc0 = SPI_SPIPC0_SOMIFUN | SPI_SPIPC0_SIMOFUN | SPI_SPIPC0_CLKFUN | SPI_SPIPC0_SCS0FUN0 | SPI_SPIPC0_SCS0FUN3;
SPIPinControl(SOC_SPI_0_REGS, 0, 0, &spipc0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to split SPIPinControl() into two function so we don't have this goofy pass by reference when writing.

SPIReset(SOC_SPI_0_REGS);
SPIOutOfReset(SOC_SPI_0_REGS);
SPIModeConfigure(SOC_SPI_0_REGS, SPI_MASTER_MODE);
unsigned int spipc0 = SPI_SPIPC0_SOMIFUN | SPI_SPIPC0_SIMOFUN | SPI_SPIPC0_CLKFUN | SPI_SPIPC0_SCS0FUN0 | SPI_SPIPC0_SCS0FUN3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another place where I would rather just have the ADC reconfigure things here instead. Or at least add some more comments on what parts of this are for the ADC. I.e. one chip select is for this driver and one is for the ADC driver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants