2020-09-15 02:46 AM
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;
}
Solved! Go to Solution.
2020-09-16 02:54 AM
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
2020-09-15 06:48 AM
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
2020-09-15 08:04 AM
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:
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
2020-09-15 10:12 AM
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:
#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
2020-09-16 02:54 AM
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
2020-09-21 01:39 AM
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).
2020-09-21 02:09 AM
@Led not tested with the SW we have shared, but please consider that there are 3 methods to control the GPIO output:
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.
2020-09-21 02:18 AM
@AntonioST thanks for this clarifications. This was indeed not clear to me.
2021-10-05 02:34 AM
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);
}
2021-10-06 03:47 AM
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.