Faster SDMMC bus speed?

after using system_clock_override.cpp from this thread How to set up the Portenta with 480MHz Clock (at least more than 4MHz)

my SDMMC bus got a little faster 6.67Mhz to 8Mhz but i would like to go the the full 25Mhz. how do i do this?

Hi, could you share:

  • Which SD library you're using on your Portenta?
  • The specific code you used for system_clock_override.cpp?

This would help me provide more specific guidance for reaching the full 25MHz speed.

#include "SDMMCBlockDevice.h" for the block device lib and the system_clock_override.cpp file is already linked in the the thread i mentioned above.
Thanks!

Here's what you can do:

  1. First, locate the SDMMCBlockDevice implementation. This is typically in the mbed-os directory structure.
  2. You'll need to modify the SDMMC configuration in the initialization code. The key is to adjust the clock divider that determines the SD card clock frequency.

Try creating a custom version of the SDMMC initialization by adding this code to your project:

#include "SDMMCBlockDevice.h"
#include "mbed.h"

// Custom initialization for SDMMC with higher clock speed
bool initHighSpeedSDMMC() {
    // Get handle to the SDMMC peripheral
    SDMMC_TypeDef *SDMMCx = SDMMC1;
    
    // Save current CR register value
    uint32_t cr_reg = SDMMCx->CLKCR;
    
    // Clear clock divider bits
    cr_reg &= ~SDMMC_CLKCR_CLKDIV;
    
    // Set clock divider for 25MHz (assuming 200MHz SDMMC kernel clock)
    // Divider = (kernel_clock / target_freq) - 2
    // For 25MHz: (200MHz / 25MHz) - 2 = 6
    cr_reg |= (6 << SDMMC_CLKCR_CLKDIV_Pos);
    
    // Enable high-speed mode
    cr_reg |= SDMMC_CLKCR_NEGEDGE;
    
    // Write back the modified register
    SDMMCx->CLKCR = cr_reg;
    
    return true;
}
  1. Call this function after initializing your SDMMCBlockDevice but before performing any operations:
SDMMCBlockDevice bd;

void setup() {
    Serial.begin(9600);
    while (!Serial) delay(10);
    
    Serial.println("Initializing SD card...");
    int err = bd.init();
    
    if (err) {
        Serial.print("SD card initialization failed: ");
        Serial.println(err);
        return;
    }
    
    // Apply high-speed configuration
    if (initHighSpeedSDMMC()) {
        Serial.println("High-speed SDMMC configuration applied");
    } else {
        Serial.println("Failed to apply high-speed SDMMC configuration");
    }
    
    // Your code continues...
}
  1. You might need to adjust the divider value (6 in this example) based on the actual SDMMC kernel clock frequency after your system_clock_override.cpp changes.
  2. As a diagnostic, you can measure the actual SDMMC clock frequency before and after your modifications:
void printSDMMCFreq() {
    SDMMC_TypeDef *SDMMCx = SDMMC1;
    uint32_t divider = ((SDMMCx->CLKCR & SDMMC_CLKCR_CLKDIV) >> SDMMC_CLKCR_CLKDIV_Pos) + 2;
    uint32_t kernel_clock = 200000000; // Estimate based on system clock
    float sdmmc_freq = (float)kernel_clock / divider / 1000000;
    
    Serial.print("SDMMC frequency: ");
    Serial.print(sdmmc_freq);
    Serial.println(" MHz");
}

Thanks you, i will give this a try. i am struggling with another issue at this time. i need to have a 32bit timer with and ETR pin available to produce a high accuracy microsecond counter based on a CSAC's 10Mhz output. i had this working but now after integrating all my M7 testing with some of my M4 testing i am now seeing conflicts with i think the builtin us_ticker code and possibly the sdmmcblockdevice implementation. my sd card writing is being handled by the the M4 core and my ADC input and microsecond timing was being handled by the M7 with TIM2 but there was an issue with that when the sdcard was initialized and so i tried to change to TIM5 and am running into conflicts there as well. below is my timer code for both TIM2 and TIM5

volatile uint32_t secondsCounter = 0;
volatile uint8_t topSec = 0;

void setupTIM2() {
  RCC->APB1LENR |= RCC_APB1LENR_TIM2EN;  // Enable TIM2 clock
  TIM2->CR1 = 0;                         // Reset control register
  TIM2->PSC = 4;                         // Prescaler set to 5 (PSC + 1)
  TIM2->ARR = 0xFFFFFFFF;                // Max auto-reload value (free-running)
  TIM2->CNT = 0;                         // Reset counter
  TIM2->DIER |= TIM_DIER_UIE;            // Enable update interrupt

  // Configure PA0 as Alternate Function (AF1 for TIM2_ETR)
  GPIOA->MODER &= ~(0b11 << (0 * 2));  // Clear mode bits
  GPIOA->MODER |= (0b10 << (0 * 2));   // Set to Alternate Function mode
  GPIOA->AFR[0] &= ~(0xF << (0 * 4));  // Clear AF selection
  GPIOA->AFR[0] |= (0x1 << (0 * 4));   // Set AF1 (TIM2_ETR)

  // Configure TIM2 to use external clock mode 1 on ETR (PA0)
  TIM2->SMCR = (7 << TIM_SMCR_SMS_Pos) |  // External Clock Mode 1
               (7 << TIM_SMCR_TS_Pos) |   // Trigger source = ETR (PA0)
               (1 << 12);                 // Set ETR prescaler to divide by 2

  // Ensure ETR is active high
  TIM2->SMCR &= ~(1 << 15);  // Clear ETP bit to select active high

  // Disable digital filtering on ETR
  //TIM2->SMCR &= ~(0b1111 << 8);  // Clear ETR filter bits

  TIM2->ARR = 999999;  // Set rollover to 1,000,000 counts
  TIM2->CR1 |= TIM_CR1_ARPE;

  //TIM2->EGR = TIM_EGR_UG;    // Manually trigger an update event
  TIM2->CR1 |= TIM_CR1_CEN;   // Enable the counter
  NVIC_EnableIRQ(TIM2_IRQn);  // Enable TIM2 interrupt in NVIC
}

extern "C" void TIM2_IRQHandler() {
  if (TIM2->SR & TIM_SR_UIF) {  // Check if update interrupt flag is set
    TIM2->SR &= ~TIM_SR_UIF;    // Clear the interrupt flag
    secondsCounter++;           // Increment seconds counter
    topSec = 1;
  }
}

void setupTIM5() {
  RCC->APB1LENR |= RCC_APB1LENR_TIM5EN;  // Enable TIM5 clock
  TIM5->CR1 = 0;                         // Reset control register
  TIM5->PSC = 4;                         // Prescaler set to 5 (PSC + 1)
  TIM5->ARR = 0xFFFFFFFF;                // Max auto-reload value (free-running)
  TIM5->CNT = 0;  // Reset counter
  TIM5->DIER |= TIM_DIER_UIE;  // Enable update interrupt

  // Configure PA4 as Alternate Function (AF2 for TIM5_ETR)
  GPIOA->MODER &= ~(0b11 << (4 * 2));  // Clear mode bits for PA4
  GPIOA->MODER |= (0b10 << (4 * 2));   // Set to Alternate Function mode
  GPIOA->AFR[0] &= ~(0xF << (4 * 4));  // Clear AF selection for PA4
  GPIOA->AFR[0] |= (0x2 << (4 * 4));   // Set AF2 (TIM5_ETR)

  // Configure TIM5 to use external clock mode 1 on ETR (PA4)
  TIM5->SMCR = (7 << TIM_SMCR_SMS_Pos) |  // External Clock Mode 1
               (7 << TIM_SMCR_TS_Pos) |   // Trigger source = ETR (PA4)
               (1 << 12);                 // Set ETR prescaler to divide by 2

  // Ensure ETR is active high
  TIM5->SMCR &= ~(1 << 15);  // Clear ETP bit to select active high

  TIM5->ARR = 999999;  // Set rollover to 1,000,000 counts
  TIM5->CR1 |= TIM_CR1_ARPE;

  //TIM5->EGR = TIM_EGR_UG;  // Manually trigger an update event
  TIM5->CR1 |= TIM_CR1_CEN;  // Enable the counter
  NVIC_EnableIRQ(TIM5_IRQn);  // Enable TIM5 interrupt in NVIC
}

extern "C" void TIM5_IRQHandler(void) {
  if (TIM5->SR & TIM_SR_UIF) {
    TIM5->SR &= ~TIM_SR_UIF;
    secondsCounter++;
    topSec = 1;
  }
}

I've created a program that has this changes:

  1. Timer Selection: Instead of using TIM2 or TIM5 (which are likely used by the Arduino core or mbed), I've switched to TIM3 which is less likely to have conflicts with the built-in libraries.
  2. Pin Assignment: TIM3 has an ETR input on pin PD2, which gives you another option that shouldn't conflict with SDMMC or other peripherals.
  3. Interrupt Priority Management: By explicitly setting the timer interrupt priority, we can ensure it doesn't interfere with the SDMMC operations.
  4. Initialization Order: The code suggests initializing the SD card first, then applying the high-speed configuration, and only then setting up your custom timer. This order helps avoid initialization conflicts.
  5. Microsecond Reading: I've included a getMicroseconds() function that handles potential rollover situations correctly when reading the timer.

Notes

  1. Double-check that PD2 is accessible on your Portenta board and not already used for something else.
  2. You may need to adapt the pin assignments if PD2 is not suitable - TIM3 has other alternate function pins that could be used instead.
#include "mbed.h"
#include "SDMMCBlockDevice.h"

// Global variables for timing
volatile uint32_t secondsCounter = 0;
volatile uint8_t topSec = 0;

// Timer selection - TIM3 is less likely to conflict with us_ticker and SDMMC
// TIM3 has ETR on PD2, which is an alternative pin you can use

void setupTimerForCSAC() {
    // Use TIM3 which is less likely to conflict with built-in functions
    RCC->APB1LENR |= RCC_APB1LENR_TIM3EN;  // Enable TIM3 clock
    TIM3->CR1 = 0;                         // Reset control register
    
    // Configure PD2 as Alternate Function (AF2 for TIM3_ETR)
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIODEN;   // Enable GPIOD clock
    GPIOD->MODER &= ~(0b11 << (2 * 2));    // Clear mode bits for PD2
    GPIOD->MODER |= (0b10 << (2 * 2));     // Set to Alternate Function mode
    GPIOD->AFR[0] &= ~(0xF << (2 * 4));    // Clear AF selection for PD2
    GPIOD->AFR[0] |= (0x2 << (2 * 4));     // Set AF2 (TIM3_ETR)
    
    // Configure TIM3 to use external clock mode 1 on ETR (PD2)
    TIM3->SMCR = (7 << TIM_SMCR_SMS_Pos) |  // External Clock Mode 1
                 (7 << TIM_SMCR_TS_Pos) |   // Trigger source = ETR
                 (1 << 12);                 // Set ETR prescaler to divide by 2
    
    // Ensure ETR is active high
    TIM3->SMCR &= ~(1 << 15);  // Clear ETP bit to select active high
    
    // Set rollover to 1,000,000 counts for 1 second timing with 10MHz input รท 2
    TIM3->ARR = 999999;  
    TIM3->CR1 |= TIM_CR1_ARPE;
    
    // Set interrupt priority higher than default but below critical system interrupts
    // This helps avoid conflicts with SDMMC operations
    NVIC_SetPriority(TIM3_IRQn, 2);
    
    // Enable counter and interrupt
    TIM3->DIER |= TIM_DIER_UIE;
    TIM3->CR1 |= TIM_CR1_CEN;
    NVIC_EnableIRQ(TIM3_IRQn);
}

// TIM3 interrupt handler
extern "C" void TIM3_IRQHandler(void) {
    if (TIM3->SR & TIM_SR_UIF) {
        TIM3->SR &= ~TIM_SR_UIF;  // Clear the interrupt flag
        secondsCounter++;         // Increment seconds counter
        topSec = 1;
    }
}

// Function to get microsecond time (combining seconds and microseconds)
uint64_t getMicroseconds() {
    uint32_t seconds = secondsCounter;
    uint32_t micropart = TIM3->CNT;  // Current count represents microseconds
    
    // Handle potential rollover during read
    if (topSec && TIM3->CNT < 500000) {
        seconds = secondsCounter;  // Re-read after potential interrupt
        topSec = 0;
    }
    
    return ((uint64_t)seconds * 1000000) + micropart;
}

// Enhanced SDMMC configuration for higher speed
void configureHighSpeedSDMMC() {
    // Get handle to the SDMMC peripheral
    SDMMC_TypeDef *SDMMCx = SDMMC1;
    
    // Save current CR register value
    uint32_t cr_reg = SDMMCx->CLKCR;
    
    // Clear clock divider bits
    cr_reg &= ~SDMMC_CLKCR_CLKDIV;
    
    // Set clock divider for 25MHz (assuming 200MHz SDMMC kernel clock)
    // Divider = (kernel_clock / target_freq) - 2
    // For 25MHz: (200MHz / 25MHz) - 2 = 6
    cr_reg |= (6 << SDMMC_CLKCR_CLKDIV_Pos);
    
    // Enable high-speed mode
    cr_reg |= SDMMC_CLKCR_NEGEDGE;
    
    // Write back the modified register
    SDMMCx->CLKCR = cr_reg;
}

// Example of how to initialize everything in your application
void setupTimingAndStorage() {
    // Initialize SD card first
    SDMMCBlockDevice sd;
    int err = sd.init();
    if (err) {
        // Handle SD initialization error
        return;
    }
    
    // Apply high-speed configuration to SDMMC
    configureHighSpeedSDMMC();
    
    // Set up timer for CSAC after SD initialization
    // This ordering helps avoid conflicts
    setupTimerForCSAC();
    
    // Now both systems should work without conflicts
}

wow, thank you very much. however, isn't TIM3 a 16 bit timer? will it be able to count to 1,000,000?

My mistake there! I was thinking of a different STM32 chip, only tim2 an tim5 are 32 bit timers.
I hope the provided code can help you to develop the script report anyway

no worries and thanks for trying. i think the answer to my problem is going to be figuring out how to take back or disable what arduino or mbed is trying to use these timers for. like the us_ticker. i just don't know how far the rabbit hole goes. how many other bits of code and libraries may depend on the us_ticker functionality???