Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic SDRAM FMC driver #194

Closed
wants to merge 8 commits into from
Closed

Basic SDRAM FMC driver #194

wants to merge 8 commits into from

Conversation

iostat
Copy link

@iostat iostat commented Jul 30, 2020

Title says it all. This is a basic driver allowing one to use the FMC peripheral to drive an SDRAM module which can then be accessed directly in the memory space of the MCU. It /should/ work on F427, F429, F437, and F439 devices. However, I only have a STM32F429I-Discovery board to test it against. Mostly ported from the STM32 HAL/LL libraries. Included is a simple RAM test in examples/ targeting the STM32F429I-Discovery board

It'd be nice if this could be expanded to support NOR/PSRAM and all the other goodies that the FMC offers, but I don't have any hardware to test it with. I believe the 429I-EVAL includes a NOR Flash and SDRAM on a single bus, so that may be interesting to ultimately target.

One particular divergence from the usual patterns in this module is that we Peripherals::steal() to get access to the GPIOs to set up their AF/Speed/Pullup. This is mainly due to the sheer number of pins that would otherwise have to be passed by the user, which seemed inconvenient at best. The majority of the FMC pins aren't swappable, so it made the most sense ergonomically to just manipulate them directly using steal(). The few pins which /are/ swappable (and whose usage of swapping affects behavior) are captured in the type system.

@therealprof
Copy link
Member

Thanks for the PR. I was planning to add FSMC support at some point and since they're very similar we should probably try to add them in one go and in a consolidated way.

I need to look at your proposal at more detail but I'll add a few things I've noticed right away.

Cargo.toml Outdated Show resolved Hide resolved
$gpioX
.$pXY
.into_alternate_af12()
.set_speed(crate::gpio::Speed::High)
Copy link
Member

Choose a reason for hiding this comment

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

Why only High instead of VeryHigh?

Copy link
Author

@iostat iostat Jul 30, 2020

Choose a reason for hiding this comment

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

Primarily because it's unnecessary. Using a higher Speed means using more GPIO drivers (more drive strength), which means more potential EMI emissions and power consumption (best practice is to use the lowest Speed suitable for the application). The FMC runs the memory at half of HCLK, which puts you in the ballpark of 90MHz SDCLK frequency on a maxed out F429 running at 180MHz. Speed::High the lowest setting which reliably gives us the bandwidth we need (good for 50MHz - 100MHz).

sdnwe: SDNWE,
) {
let peripherals = unsafe { crate::stm32::Peripherals::steal() };
let gpiod = peripherals.GPIOD.split();
Copy link
Member

Choose a reason for hiding this comment

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

Those operations are risky as they do RMW operations. At the very least we need to somehow protect these. Also I think those GPIOs don't belong into something called SDRAMConfig as the GPIOs are completely independent of what exactly you're attaching to your parallel bus.

Probably better to move the GPIO configuration out into it's own thing. Also an interesting approach to explore would be to have the traditional new function where you have to pass in all the correct pins and a (unsafe) new_unchecked function which assumes that the application has properly set up everything.

Copy link
Author

@iostat iostat Jul 30, 2020

Choose a reason for hiding this comment

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

Those operations are risky as they do RMW operations. At the very least we need to somehow protect these.

Agree with the risk, I was hoping it's mitigated by the fact that the operations they do are idempotent (i.e., they never disable any GPIO peripherals or pins, only additively enable them), and banking on the fact that the RegisterBlocks are guarded by VolatileCells in the upstream API

Also I think those GPIOs don't belong into something called SDRAMConfig as the GPIOs are completely independent of what exactly you're attaching to your parallel bus. Probably better to move the GPIO configuration out into it's own thing.

Do you mean the GPIOs which are passed into configure, or generally the notion of impl SDRAMConfig being ultimately responsible for setting up the GPIOs. If the former -- those GPIOs are necessary for SDRAM functionality. The are passed into SDRAMConfig.configure(...) which consumes the SDRAMConfig and returns an SDRAM. This is deliberately done so that at least ownership of the swappable SDRAM control pins is taken in this case. If the latter, it's only done that way to separate out the logic of the different steps (i.e., so we don't have a gigantic configure() function)

Also an interesting approach to explore would be to have the traditional new function where you have to pass in all the correct pins and a (unsafe) new_unchecked function which assumes that the application has properly set up everything.

A new function was my initial approach, but was abandoned due to the potential combinatorial explosion in future support for all device families and FMC configurations (i.e., device family <-> gpio ports mappings/AFs <-> possible FMC configurations w/r/t bus widths and other pins). OTOH, I can totally see a separate FMCAddressBus, FMCDataBus, etc. set of traits which /could/ encapsulate the different non-swappable pins, but again, their construction would involve significant boilerplate and doesn't seem to me to bring much value unless the rest of FMC's more sophisticated capabilities are supported (e.g., NOR/PSRAM mux, etc.)

Copy link
Member

Choose a reason for hiding this comment

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

Agree with the risk, I was hoping it's mitigated by the fact that the operations they do are idempotent (i.e., they never disable any GPIO peripherals or pins, only additively enable them), and banking on the fact that the RegisterBlocks are guarded by VolatileCells in the upstream API

VolatileCell is not providing any protection against racy RMW operations.

Do you mean the GPIOs which are passed into configure, or generally the notion of impl SDRAMConfig being ultimately responsible for setting up the GPIOs.

Both. The pins used by FMC/FSMC are the same, independent of the mode or devices connected to the bus. Probably we should even separate the bus setup altogether from the configuration of the channels.

A new function was my initial approach, but was abandoned due to the potential combinatorial explosion in future support for all device families and FMC configurations (i.e., device family <-> gpio ports mappings/AFs <-> possible FMC configurations w/r/t bus widths and other pins).

It's not too bad, really. We'll have to deal with that anyway at some point.

I can test e.g. with a STM32F413H Disco (FSMC) and would certainly like to see the display work with this driver. I do have it running (in a hackish way) and can certainly provide some details about that.

Copy link
Author

@iostat iostat Jul 30, 2020

Choose a reason for hiding this comment

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

VolatileCell is not providing any protection against racy RMW operations.

Someone would be using this code with ownership of the peripheral register blocks anyway when doing their GPIO setup, and the VolatileCell guards against races due to the compiler reordering the accesses. I suspect we might not be on the same page with what "racy" means, could you elaborate?

Both. The pins used by FMC/FSMC are the same, independent of the mode or devices connected to the bus. Probably we should even separate the bus setup altogether from the configuration of the channels.

I just started doing this, but this does mean a separate type for each possible width of address bus (+3 types for data buses). To enforce stuff like valid values of Column vs Row bits, how do you feel about requiring const_generics to support that?

Copy link
Member

Choose a reason for hiding this comment

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

Someone would be using this code with ownership of the peripheral register blocks anyway when doing their GPIO setup

How do you mean? You're conjuring the PeripheralBlocks out of thin air with your steal(). Stealing != owning.

I just started doing this, but this does mean a separate type for each possible width of address bus (+3 types for data buses).

Not necessarily. Depending on how we want to achieve that we could do a lot of different things, including automatically generating enums with all possible variants or specififying the maximum possible and allowing dummy pins.

how do you feel about requiring const_generics to support that?

Anything not requiring nightly is fair game. 😅

Copy link
Member

Choose a reason for hiding this comment

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

#[inline] has no effect in dev mode. Need to go #[inline(always)] for that.

Copy link
Author

@iostat iostat Aug 2, 2020

Choose a reason for hiding this comment

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

Just peppered #[inline(always)] everywhere, same result.

Copy link
Author

Choose a reason for hiding this comment

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

I've pushed an fmc-support-owned branch if you want to see if you can make it fit. I'm going to make this PR rely less on unsafe in the interim w/r/t owning FMC and taking ownership of as many pins as possible.

Copy link
Member

Choose a reason for hiding this comment

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

Need to go deeper into the rabbit hole and figure out where the bloat is coming from. It's unfortunate that dev mode is pretty much unusable for embedded but it's also not worth working around it in weird ways because it's quite likely that you'll hit the next road block right around the corner (usually in the form of code executing so slow due a massive amount of silly function calls that all essential timing goals are missed).

Copy link
Member

Choose a reason for hiding this comment

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

I've pushed an fmc-support-owned branch if you want to see if you can make it fit.

Making code work in dev mode is a tertiary goal for me at best. As I mentioned before I'd be okay with having two ways of initialising, one "regular" new() with all pins and one new_unchecked(), e.g. like done here: https://github.com/stm32-rs/stm32h7xx-hal/blob/master/src/spi.rs. Though I will insist on that being unsafe because I very much disagree on the unchecked postfix providing a clear enough message about the real unsafety (and that unsafe is only applicable to memory safety) of this.

src/fmc.rs Outdated Show resolved Hide resolved
@therealprof therealprof marked this pull request as draft July 30, 2020 16:10
@iostat
Copy link
Author

iostat commented Jul 30, 2020

Thanks for taking a look at this and giving valuable feedback! I've replied to a few of these with my motivations/thought process, and will fix the remainder later tonight. Happy to keep discussing and refining this until its something everyone's OK with! I originally wanted to kickstart a discussion of how an FMC API would work as an issue, but it seemed more fruitful to start with an MVP and refine it with something that can be tested for ergonomics and functionality -- hence the PR.

iostat and others added 2 commits July 30, 2020 12:44
Co-authored-by: Daniel Egger <daniel@eggers-club.de>
Co-authored-by: Daniel Egger <daniel@eggers-club.de>
@therealprof
Copy link
Member

@iostat I absolutely prefer the MVP approach!

@richardeoin
Copy link
Member

richardeoin commented Aug 1, 2020

I've started a crate to provide FMC/FSMC drivers that can be used for all the stm32 parts. It currently only supports some F7, some H7, and F469/F479, and also only has SDRAM support so far.

Would you be interested in combining this PR into the crate? I think some of the tasks would be:

  • Looking at the register defintions and deciding if they apply to F427/F429/F437/F439, otherwise creating another file.
  • Creating a memory device definition for the SDRAM on the STM32F429I-Discovery
  • Adding anything else that's needed to the stm32-fmc crate
  • Finally creating an implementation here in the F4 HAL

Let me know your thoughts. I will be adding more support for the H7, but happy to help here as well.

bors bot added a commit that referenced this pull request Apr 18, 2021
297: Add support for some LCD controllers using the FMC/FSMC r=therealprof a=samcrow

# Introduction

This pull request adds code that uses the Flexible Memory Controller or Flexible Static Memory Controller to interface with some types of LCD controllers.

We have [another FMC pull request](#194) for non-LCD applications that has been inactive for a while. I'm hoping that this pull request will be easier to review because it does not try to implement everything that the hardware supports.

# Features

* Controls up to 4 independent LCDs
* Compatible with LCD controllers that use the Intel 8080 protocol with a 16-bit bidirectional data bus
* Includes an example that works on the STM32F412G-DISCO board
* Provides a low-level interface that can be used to write drivers for specific LCD controllers

# Limitations

* No support for bus widths other than 16 bits
* I have only tested this code with Sitronix ST7789-series LCD controllers. Are there other LCD controllers that use an Intel 8080-like interface that this code cannot support?
* The code uses some trait trickery to ensure that the client code can only access the display(s) that correspond to the chip select pin(s) in use. However, this may make the API too difficult for people to understand.

# Future work

* Support an 8-bit data bus (in addition to the currently supported 16-bit bus)
* Potentially support an 18-bit data bus, on microcontroller models that have more than 16 data pins

Co-authored-by: Sam Crow <scrow@eng.ucsd.edu>
@burrbull
Copy link
Contributor

burrbull commented Aug 3, 2024

Closing in favor of stm32-fmc.

@burrbull burrbull closed this Aug 3, 2024
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