How to access GPIO (and other peripherals) using C++

(Yes I searched this forum about this)

Hi,
I just started my journey with the Jetson Nano. I have a lot to learn because a lot is new for me. So I want to dot this step by step.

Firstly I have installed the needed tools to write and run my first ‘hello world’ program. So far so good.

Now I want to access the GPIO pins. I connected a LED and want to control the LED. For all platforms I worked on so far you simply include a .h file and call the correct gpio function. I searched this forum and there is a lot of info. But it is all to confusing for me. I do not see anything like a gpio function or SDK for the peripherals.
My main questions are:

  • how to access the GPIO peripheral (and other peripherals)
  • what to include and what are the functions?
  • where can I find information or examples about this?

Hi mechamania,

Are you using the devkit or custom board for Jetson Nano?

You can simply control GPIO through sysfs.

$echo <GPIO Num> > /sys/class/gpio/export

Yes I use the Devkit.
Using sysfs is way to slow. I need to control motors and read encoders.
I also need to access other peripherals like the SPI, and I2S.
And I would like to use C++ to access the peripherals.

Can I access all peripheral registers like done in this project:

Thus for instance:

static void *baseCNF;
baseCNF = mmap(0, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_GPIO, base_CNF);
static volatile GPIO_CNF *pin3;
pin3 = (GPIO_CNF volatile *)((char *)baseCNF + CNF_3);

So that I can have full control of a peripheral?

Hi @mechamania

Since you mentioned my lib I can tell you about my own experience playing with the Nano. Sure you can mmap() CPU registers as I did, not everything though, depending of what you are trying to map you are going to get a seg fault because IOMMU and such, the kernel is going to block you.

Then other challenge would be, how to capture interruptions from user space e.g. how to know when you finish an spi transfer or the like, that’s going to be a problem and actually for the i2c and spi ports, I changed my approach and I just ioctl() the character devices (used the driver that comes with the OS) but changing pinmux/cntrl registers programmatically instead of fighting the device tree.

Anyways, if what you want is just manipulate the pins, use the spi and i2c buses you can use my lib, just check the examples on github and take it from there, you can use c++. Feedback from other people has been good so far, of course you can also modify it or whatever.

If what you want is do your own thing from scratch, keep in mind all of the above, this is not an MCU, there is an OS/kernel between you and the metal and so to achieve certain things e.g. improve the spi behaviour like allowing continuous transmission or whatever, you will have to modify/create your own driver or character device and replace what is already there.

One thing that might happen is that you do your own thing in user space and you are fighting with the driver in the background, that is something to keep in mind.

Also essential to know the hardware, register addresses and so forth, get the Technical Reference Manual, you can find it in the download area.

This is kind of a brief all over the place, but there is a lot to it if you never programmed hardware on a linux environment.

2 Likes

Hi Rubberazer. Thanks for this comprehensive answer.
It is true that I want to use part of the system as a MCU and I realize that this is not how Linux is ment to be used.
I want to use the most of the system for video analysis and communication. In a way as it is ment to be. But I would like to reserve one core to do some low level stuff with some peripherals for motor control and such things. I do not like that Linus will sit in between and want full control.
The peripherals I want to control this way are:

  • GPIO (at least a few ports)
  • SPI
  • I2C
  • I2S
    If interrupts are a problem, I have no problem using a polling technique. Not the best solution but for what I need it will be ok.

So do you think I can maket the kernel not touching these devices and have full control over it?

B.T.W. I tried your code and could not get it working. I’m using Visual Studio Code for the development and I did not succeed yet in running the code in root mode. There are several solutions for this on the internet, but none seem to work.

Do you have a suggestion for that?

1 Like

Related to my code, you have to install the library in your Nano, git clone+make+sudo make install and then create code based on the examples and compile that. Or just take the library source code itself, take the parts that you find useful and do your own thing, I think if you read that code along the Technical Reference Manual you will see it through. I don’t do cross compiling in this platform by the way, is powerful enough for my emacs, so I just SSH and code into it.

Related to polling, I didn’t do it because unless you disable the system drivers, they might catch the interrupt before your program do, I tried, say you are polling to catch a rising edge on a GPIO, the driver will arrive before you do half of the time and clear the bit, your program will miss out.

The way you talk about, it sounds more like some SOCs that have dedicated parts to do specific stuff, Texas Instruments style, maybe? with a couple of MCU cores and then another couple more CPU like plus DSPs and other stuff?

Again in this machine I believe that whatever you do, you have to count with a kernel between you and the machine, you don’t have to use sysfs (i didn’t) but still you have to count on that.

Maybe other people have a different experience and can chip in, not sure I can help beyond that.

1 Like

Isn’t it possible to disable the peripherals for the Linux OS? For instance the UART: can we disable it so that Linux does not touch it at all?

1 Like

Yes you can, haven’t tried for all of them but blacklisting should work in general, check this out: https://linuxconfig.org/how-to-blacklist-a-module-on-ubuntu-debian-linux

Be careful disabling stuff,

1 Like

@Rubberazer , Do you have defines for the GPIO of a Nano Orin board?
If not, how can I find these myself?

I only need to do some simple GPIO IO. But I had to switch to the Nano Orin.

For header pin 32 for instance, I can find out that this is the PG.06 GPIO pin. But how to translate this to the hardware addresses. Or the #define CNF_32 in your code.

Nope, unfortunately I couldn’t put my hands on the Orin yet, to find register addresses for the Orin and so forth you will have to check the Technical Reference Manual, it is in the download area.

Yes, I did look at the TRM. But I can not find any indication of PG.06 (or some PG port).

I guess I need tree things:

  • base address (like #define base_CNF 0x6000d000 in your code)
  • port offset for pin 32 (is Port G?) (like #define PINMUX_32 0x1fc in your code)
  • bit numer for bit masks

I assume bit number is 6.
Is the base address the same as Nano?
Can you tell me how to find the port offset in the TRM?

1 Like

Hi @mechamania

I am also trying to using Jetson Orin Nano’s GPIO through mmap as in @Rubberazer’s library, you got any lead in that?

No, sorry.
The TRM is complex. I could not figure out what addresses and constants to use.
So I ended up using the sysfs approach.

1 Like

Okay, thanks for the update @mechamania. I am not able to find register addresses of Orin Nano SOM in any of it’s documents. Is TRM is available exclusively for Orin Nano or common across Jetson Orin series?

How is the GPIO response/latency when using sysfs approach?

When filter with TRM in the Jetson Download center (Jetson Download Center | NVIDIA Developer)
you can find the document: Jetson Orin Series SoC Technical Reference Manual
So yes, I think there is a common TRM for all Orin SOCs.

When using this loop:

#define SET_PIN32                    \
    if (gpioPG06FileDescriptor >= 0) \
        write(gpioPG06FileDescriptor, "1", 1);
#define CLR_PIN32                    \
    if (gpioPG06FileDescriptor >= 0) \
        write(gpioPG06FileDescriptor, "0", 1);
int main()
{
   // setting up for GPIO pin32 /sys/class/gpio file system
   int gpioPG06FileDescriptor = open("/sys/class/gpio/PG.06/value", O_RDWR);
   if (gpioPG06FileDescriptor < 0)
   {
       cout << "Unable to open /sys/class/gpio/PG.06/value" << endl;
   }

   while (true) {
       SET_PIN32
       CLR_PIN32
   }
}

pin 32 will be about 5 uSec high and 5 uSec low. So the time for a pin change is about 5 uSec.

Note that the pin need to be configured to be output first.

1 Like

The CPU runs at 1.5GHz. So it takes around 7000 clock cycles to change the state!

I think direct access would be at least 100 times faster. That is why I don’t like this kind of IO.

1 Like

Thanks for the details @mechamania. Anyway, will give a try to direct mmap method. I need fast response irrespective of CPU load for my application to work properly.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.