cancel
Showing results for 
Search instead for 
Did you mean: 

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.

Led
Senior

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;
}

1 ACCEPTED SOLUTION

Accepted Solutions
AntonioST
ST Employee

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

View solution in original post

11 REPLIES 11
Olivier GALLIEN
ST Employee

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

Olivier GALLIEN
In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question.
AntonioST
ST Employee

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

Led
Senior

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

AntonioST
ST Employee

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

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).

AntonioST
ST Employee

@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.

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

FTrie.1
Associate II

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);
 
}

Led
Senior

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.