Skip to main content
Graduate II
September 15, 2020
Solved

Using mmap in C-Code from Linux application to access GPIOs. I want to control the GPIO PF4 on the STM32MP1 with mmap() due to speed requirements, but the pin is not toggling.

  • September 15, 2020
  • 9 replies
  • 5152 views

Details:

HW: Phytec Sargas Board, which is similar to the STM32MP1-DK2.

Build environment: Cross compiling with SDK generated by Yocto.

Linux: Yocto build, based on st-image weston.

Device Tree configuration: As far as I understood not needed to access single GPIOs.

Any help is appreciated :)

Code (only partially):

#define MEM_DEV "/dev/mem"
#define GPIOF_START_ADDR 0x50007000
#define GPIOF_END_ADDR 0x500073FF
#define GPIOF_MAP_SIZE (GPIOF_END_ADDR - GPIOF_START_ADDR)
 
#define GPIO_REG_MODER 0x00 /**< GPIO port mode register */
#define GPIO_REG_OTYPER 0x04 /**< GPIO port output type register */
#define GPIO_REG_BSRR 0x18 /**< GPIO port bit set/reset register */
 
#define GPIO_PIN_4 ((uint16_t)0x0010U)
#define GPIO_PIN_OUTPUT_DIRECTION 0x01 /**< Output */
#define GPIO_PIN_OUTPUT_PUSHPULL 0x00 /**< Output Push Pull Mode */
 
int main(int argc, char **argv)
{
 int ret = EXIT_SUCCESS;
 static void *mmapBase = NULL; /* Virtual base address */
 signal(SIGINT, intHandler); /* Catch CTRL + C signal*/
 signal(SIGTERM, intHandler); /* Catch kill signal */
 
 /* Try to open the mem device special file */
 int fdMem = open(MEM_DEV, O_RDWR | O_SYNC);
 if (fdMem < 1)
 {
 exit(1);
 }
 
 /* Map memory region for the gpio registers */
 mmapBase = mmap(NULL,
 GPIOF_MAP_SIZE,
 PROT_READ | PROT_WRITE,
 MAP_SHARED,
 fdMem,
 GPIOF_START_ADDR);
 if (mmapBase == (void*) -1)
 {
 exit(1);
 }
 
 { /* MODER */
 /* Load the different gpio register pointers with its address */
 volatile uint32_t *gpioRegAddr = mmapBase + GPIO_REG_MODER;
 /* Get the value of the gpio direction register */
 uint32_t gpioRegVal = *gpioRegAddr;
 /* Clear the GPIO, write it back to the direction register */ 
 gpioRegVal &= ~(0x03 << (4 * 2)); /* mask out the 2 bits giving the direction */
 gpioRegVal |= ((GPIO_PIN_OUTPUT_DIRECTION) << (4 * 2));
 *gpioRegAddr = gpioRegVal;
 }
 
 { /* OTYPE */
 /* Load the different gpio register pointers with its address */
 volatile uint32_t *gpioRegAddr = mmapBase + GPIO_REG_OTYPER;
 /* Get the value of the gpio direction register */
 uint32_t gpioRegVal = *gpioRegAddr;
 /* Clear the GPIO, write it back to the direction register */ 
 gpioRegVal &= ~(0x0 << (4)); /* mask out */
 gpioRegVal |= ((GPIO_PIN_OUTPUT_PUSHPULL) << (4));
 *gpioRegAddr = gpioRegVal;
 }
 
 volatile uint32_t *const gpioSetClearDataOutAddr = mmapBase + GPIO_REG_BSRR;
 keepRunning = 1;
 while(keepRunning)
 {
 *gpioSetClearDataOutAddr = GPIO_PIN_4_REG_DEFINITION; /* set the pin */
 sleep_ms(500);
 *gpioSetClearDataOutAddr = (uint32_t)(GPIO_PIN_4_REG_DEFINITION << 16); /* reset the pin */
 sleep_ms(500);
 }
 return ret;
}

    This topic has been closed for replies.
    Best answer by AntonioST

    You are right, there is another effect I missed in my description.

    When the Cortex-M4 is not executing anything, it is kept in reset and then all the clocks under its control are stopped too.

    When I run the test I had one FW running on Cortex-M4 that masked this effect. Sorry for the incorrect info.

    In attachment a code derived from your, with extra changes to bypass the Cortex-M4 in reset. It is a kind of worst case settings

    It enables in rcc the clock for the debug peripheral, then sets some bit in DBGMCU. These bits keep the clocks enable even when the Cortex-M4 is in reset.

    Tested on board STM32MP15x-EVAL (aka EV1), toggling the pin 22 of connector CN21

    Anyway, if the standard and portable methods through libgpiod are fast enough for the specific use case, don't use the mmap method.

    Antonio

    9 replies

    Technical Moderator
    September 15, 2020

    Hi @Led​ 

    Did you already confirm it works with solution provided in this page ?

    https://wiki.st.com/stm32mpu/wiki/How_to_control_a_GPIO_in_userspace

    Olivier

    ST Employee
    September 15, 2020

    Hi @Led​ 

    the Linux kernel manages the clock enable of all the peripherals in order to minimize the power consumption.

    The GPIO driver enables the clock only when it needs to change or read one of the GPIO registers, then turns the clock off immediately.

    Your application fails to modify the GPIO because it always find the clock disabled. The write operations to the GPIO registers are lost!

    How to enable the clock? Actually there are few options:

    1. there have been several proposals on Linux mailing list to introduce 'hooks' in debugfs, for your example adding an 'enable' in /sys/kernel/debug/clk/gpiof/ , but none of them get merged. You can find some of them in the mailinglist archives.
    2. your application could mmap() the RCC registers and enable the clock.
    3. you can search a device in the devicetree that turns on all the clocks and never turns them off. By adding the clock of GPIOF to its list you get the clock always on. I did not check all the devices to find such lucky case.

    So, let's focus on solution 2) that looks more interesting.

    To enable the clock in RCC you can either enable the bit GPIOFEN in RCC_MP_AHB4ENSETR (address 0x50000000 + 0xA28) or in RCC_MC_AHB4ENSETR (address 0x50000000 + 0xAA8).

    The difference between the two (note '..._MP_...' and '..._MC_...') is that the first is used by Linux on MPU Cortex-A7, the second by the embedded MCU Cortex-M4.

    Using the first you risk to conflict with kernel behavior; if you enable the clock and suddenly the kernel re-enable it for its own purpose and then disable it, you loose the clock!

    Using the second would be safer; the kernel will not touch it. But you need to guarantee that no firmware running on Cortex-M4 is changing that bit under the hood.

    Finally, to disable the clock at the end of the application, you have to use RCC_MP_AHB4ENCLRR (address 0x50000000 + 0xA2C) or RCC_MC_AHB4ENCLRR (address 0x50000000 + 0xAAC), depending on which bit you have set above.

    Antonio

    LedAuthor
    Graduate II
    September 15, 2020

    Thank you @Community member​  and @AntonioST​ for the appreciated answers!

    @Olivier: I skiped this as I thought it is slow because of the sysfs. I tested it now and saw that with 1us sleep between the toggling, I achieve a period of about 163us - which is sufficient for me. There is just quiet some jitter, which I ignore for the currently developed prototype.

    @Antonio: Great explanation. I tried two options until now, but had some issues:

    1. I did not find out what file to write the 'enable' in the /sys/../gpiof/. Maybe you can point this out?
    2. I tried to enable the corresponding bit in the M4 RCC register, but no success so far. See below the added code:
    #define RCC_MC_AHB4ENSETR ((uint32_t)0x50000000) /**< RCC register */
    #define RCC_MAP_SIZE (0x50000FFF - RCC_MC_AHB4ENSETR)
    #define RCC_REG_GPIO 0xAA8 
    #define RCC_GPIOF_CLK_BIT 0x20 /**< bit of the GPIO F clock in the RCC */
     
     
     /* Map memory region for the gpio registers */
     mmpRccBase = mmap(NULL,
     RCC_MAP_SIZE,
     PROT_READ | PROT_WRITE,
     MAP_SHARED,
     fdMem,
     RCC_MC_AHB4ENSETR);
     if (mmpRccBase == (void*) -1)
     {
     printf("ERR: mmpRccBase");
     exit(1);
     }
     volatile uint32_t *gpioRegAddr = mmpRccBase + RCC_REG_GPIO;
     *gpioRegAddr |= 0xff; /* enable the clock */

    For me the issue is solve as I can use the proposal from Olivier. Anyhow maybe someone else is interested in the mmap solution.

    (I will not be able to test it before next week)

    Led

    AntonioSTAnswer
    ST Employee
    September 16, 2020

    You are right, there is another effect I missed in my description.

    When the Cortex-M4 is not executing anything, it is kept in reset and then all the clocks under its control are stopped too.

    When I run the test I had one FW running on Cortex-M4 that masked this effect. Sorry for the incorrect info.

    In attachment a code derived from your, with extra changes to bypass the Cortex-M4 in reset. It is a kind of worst case settings

    It enables in rcc the clock for the debug peripheral, then sets some bit in DBGMCU. These bits keep the clocks enable even when the Cortex-M4 is in reset.

    Tested on board STM32MP15x-EVAL (aka EV1), toggling the pin 22 of connector CN21

    Anyway, if the standard and portable methods through libgpiod are fast enough for the specific use case, don't use the mmap method.

    Antonio

    LedAuthor
    Graduate II
    September 21, 2020

    Awesome! Thank you Antonio, this rocks :)

    Toggling the pin with

        *gpioSetClearDataOutAddr = GPIO_PIN_4; /* set the pin */

        *gpioSetClearDataOutAddr = (uint32_t)(GPIO_PIN_4 << 16); /* reset the pin */

        *gpioSetClearDataOutAddr = GPIO_PIN_4; /* set the pin */

    I achieved a period of about 110ns (nanoseconds).

    ST Employee
    September 21, 2020

    @Led​ not tested with the SW we have shared, but please consider that there are 3 methods to control the GPIO output:

    • through register ODR (offset 0x14), but this controls all the GPIO of the port, requiring complex AND/OR to avoid touching the other GPIO
    • through register BSRR (offset 0x18), as you do now, that can individually set or reset the single GPIO
    • through register BRR (offset 0x28), that can individually reset the single GPIO

    If you run set GPIO through BSRR and reset GPIO through BRR you avoid the shift-left of 16 positions.

    Depending on how the code is written and how the compiler optimizes it, you could get better performance using BSRR only or alternating BSRR for set and BRR for reset.

    LedAuthor
    Graduate II
    September 21, 2020

    @AntonioST​  thanks for this clarifications. This was indeed not clear to me.

    Visitor II
    October 5, 2021

    Hi, sry for opening this old thread but I have a similar issue.

    I am using the stm32mp157c in the OSD32MP1-BRK.

    I tried to access GPIO pins according to the example provided by @AntonioST​ . In my case i used the GPIOI bank.

    I am able to write and read the GPIOx_MODER register and also see a change on the atteched LED as the pin swithes from "analog input" to output.

    But writing to the ODR, BSRR or BRR registers dose not seem to have an effect. Also reading ODR or IDR always gives me 0.

    As @Led​ mentioned in his initial post, i also assumed the Device Tree configuration does not have an effect when i do single register access. Is this assumption true in this case?

    i hope i can get a hint on this.

    tanks

    here my code:

    #define MEM_DEV "/dev/mem" // Memory map ref man page 159
    #define GPIOI_START_ADDR 0x5000A000
    #define GPIOI_END_ADDR 0x5000A3FF
    #define GPIOI_MAP_SIZE (GPIOI_END_ADDR - GPIOI_START_ADDR)
     
    // for later use
    #define DCMI_START_ADDR 0x4C006000
    #define DCMI_END_ADDR 0x4C0063FF
    #define DCMI_MAP_SIZE (DCMI_END_ADDR - DCMI_START_ADDR)
     
    // User LEDs in PI8 (red) and PI9 (green)
     
    #include <stdint.h>
    #include <iostream>
    #include <stdlib.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
     
    using namespace std;
     
    int main(int argc, char **argv)
    {
     int keepRunning;
     int ret = EXIT_SUCCESS;
     static void *mmapBase = NULL; /* Virtual base address */
     volatile uint32_t *x;
     
     /* Try to open the mem device special file */
     int fdMem = open(MEM_DEV, O_RDWR | O_SYNC);
     if (fdMem < 1)
     {
     exit(1);
     }
     
     /* rcc */
     mmapBase = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fdMem, 0x50000000);
     if (mmapBase == (void*) -1)
    	exit(1);
     x = (volatile uint32_t *)((uint32_t*)mmapBase + 0x80c); // Clock enable for debug function
     *x |= 0x100;
     x = (volatile uint32_t *)((uint32_t*)mmapBase + 0xAA8); // MCU set clock
     *x |= 0x7FF;
     printf("rcc:%x\n",*x);
     munmap(mmapBase, 4096);
     
     /* dbgmcu */
     mmapBase = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fdMem, 0x50081000);
     if (mmapBase == (void*) -1)
    	 exit(1);
     x = (volatile uint32_t *)((uint32_t*)mmapBase + 0x4);
     *x = 0x7;
     munmap(mmapBase, 4096);
     
     /* Map memory region for the gpio registers */
     mmapBase = mmap(NULL, GPIOI_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fdMem, GPIOI_START_ADDR);
     
     if (mmapBase == (void*) -1)
     {
     exit(1);
     }
     
     /* Load the different gpio register pointers with its address */
     volatile uint32_t *const gpioModerAddr = (uint32_t*)mmapBase + 0x00;
     volatile uint32_t *const gpioSpeedrAddr = (uint32_t*)mmapBase + 0x08;
     volatile uint32_t *const gpioIdrAddr = (uint32_t*)mmapBase + 0x10;
     volatile uint32_t *const gpioOdrAddr = (uint32_t*)mmapBase + 0x14;
     volatile uint32_t *const gpioBsrrAddr = (uint32_t*)mmapBase + 0x18;
     volatile uint32_t *const gpioBrrAddr = (uint32_t*)mmapBase + 0x28;
     
     *gpioModerAddr = *gpioModerAddr & 0xFFF0FFFF | 0x00050000; // PI8 and PI9 as outputs
     
     
     *gpioBsrrAddr = 0x00000300; // turn on PI8 and PI9
     
     // print register content:
     printf("GPIO_REG_MODER:%x\n",*gpioModerAddr);
     printf("GPIO_REG_SPEEDR:%x\n",*gpioSpeedrAddr);
     printf("GPIO_REG_IDR:%x\n",*gpioIdrAddr);
     printf("GPIO_REG_ODR:%x\n",*gpioOdrAddr);
     
    }

    LedAuthor
    Graduate II
    October 6, 2021

    Hi FTrie.1, I have not further investigated this issue. But I'd still be interested in a solution. Let me know if you find out more.

    ST Employee
    October 6, 2021

    Hi @FTrie.1​ ,

    I have checked the DT for OSD32MP1-BRK at line 100 and 107. I'm not sure this is what you are using now.

    The two LED connected to the board are reported as GPIO_ACTIVE_LOW. This means that the LED gets turned ON when the GPIO is at LOW level.

    In your code you should write 0x03000000 in BSRR to set LOW the GPIO PI8 and PI9 and turn ON the LEDs.

    But you also mention that you read ODR and it is zero! It should have bits 8 and 9 set, accordingly to your write in BSRR.

    Looks like the peripheral is kept in reset state.

    Can you print, between line 41 and 47, the value of register RCC_AHB4RSTCLRR at address (mmapBase + 0x9AC)?

    Visitor II
    October 7, 2021

    Hi @AntonioST​ ,

    thanks a lot for the quick and helpful answer!

    I figured out what my problem was. Apparently it was a casting problem.

    initially i used:

    volatile uint32_t *const gpioModerAddr = (uint32_t*)mmapBase + 0x00;

    but i had to use:

    volatile uint32_t * gpioModerAddr = (volatile uint32_t*)(mmapBase + 0x00);

    I also can control the DCMI registers as this is my intended application.

    Apperently without adding it to any device tree.