Use a general purpose input/output interface on Linux computers and laptops.
GPIO Class
The GPIO
class (GPIO.cpp
and GPIO.hpp
) does the work of the program. Its member functions count up in a variety of number systems, count down in a variety of number systems, and display 12- and 24-hour clocks. The main()
function, which is not a member of the GPIO
class, instantiates FT232H
and GPIO
objects and starts a separate thread of execution to run the readWrite()
method.
As stated before, GPIO
counts by using its member methods: countUp(base)
, countDown(base)
, and runClock(hours)
. The GPIO
non-member thread method readWrite()
employs the FT232H
object to write the counts and time to the FT232H GPIO ports. GPIO
also contains some member helper methods for readWrite()
: bool GPIO::interrupted()
, void GPIO::resetInterrupt()
, and byte GPIO::getPattern(int digit)
.
Non-member functions implement time delays. The FT232H
and GPIO
classes have static helper functions, FT232H::getFT232H()
and GPIO::getGPIO()
that return pointers to each class. Wherever you are in the program, you can call these functions and access each classes' members. For example, GPIO.cpp
has the code shown in Listing 1. Note that the static functions FT232H::getFT232H()
and GPIO::GetGPIO()
are called by a fully declared name and not by a preexisting object. You use the pointers returned by these static functions to access the member methods of the class. The file gpio.asm
is the assembly language equivalent of GPIO.hpp
and GPIO.cpp
.
Listing 1
Write to GPIO
01 void* readWrite(void* pArg) // thread function - writes digits to GPIO 02 { 03 GPIO* pGPIO = GPIO::getGPIO(); 04 FT232H* pFT = FT232H::getFT232H(); 05 int nCnt = DELAY_COUNT; 06 07 while (!pGPIO->getExitFlag()) 08 { 09 if (--nCnt == 0) // is it time to check for an interrupt? 10 { 11 if (pGPIO->interrupted()) // yes - check for interrupt 12 { 13 pGPIO->setInterruptFlag(true); // respond to interrupt 14 pGPIO->resetInterrupt(); 15 } 16 nCnt = DELAY_COUNT; // repopulate nCnt 17 } 18 19 pFT->writeACByte(0); // clear digits - turn off display 20 pFT->writeADByte(pGPIO->getPattern(3)); // write digit3 segments 21 pFT->writeACByte(DRIVE_DIGIT_3); // turn on digit3 22 sleep2_5ms(); 23 ... 24 } 25 ... 26 }
Now look at a C++ member function (Listing 2) next to its assembly language equivalent (Listing 3). The C++ version has a member function of the GPIO
class that takes no arguments and returns a boolean value. It declares a local (or automatic) variable, byte byteToRead
, that lives on the stack.
Listing 2
GPIO.cpp Snippet
bool GPIO::interrupted() // check for interrupt (GPIO.cpp line 55) { byte byteToRead FT232H::getFT232H()->readACByte(&byteToRead); // readACByte into byteToRead return (byteToRead & INTERRUPT_MASK) != 0; }
Listing 3
gpio.asm Snippet
interrupted: ; check for interrupt (gpio.asm line 343) prologue sub rsp, VAR_SIZE * NUM_VAR ; make space for local var on stack lea rdi, byte nParam ; pass addr of nParam to readACByte call readACByte ; read a byte from AC into nParam mov al, byte nParam ; get the byte we just read test al, INTERRUPT_MASK ; is the INTERRUPT_MASK bit set? setnz al ; al = 1 if yes, al = 0 if no .fin: epilogue
The static member of the FT232H
class, FT232H::getFT232H(),
gets a pointer that you can use to call FT232H::readACByte()
and then pass it the address of byteToRead
. The question is: When you AND byteToRead
with INTERRUPT_MASK
(0x80), is the result not equal to zero? Remember that AC7 is an input pin that gets the Q output pin of the S-R latch. The answer to this question is returned to the calling method.
gpio.asm
Now, it's a little hard to believe, but the assembly language function does exactly the same thing, starting with the macro definition, prologue
. All Intel assembly language functions that call other subroutines, need
%macro prologue 0 push rbp mov rbp, rsp %endmacro
to prepare the stack. (The epilogue
macro at the end of the function,
%macro epilogue 0 leave ret %endmacro
restores the stack to the way it was before prologue
was invoked.) After invoking prologue
, the line
sub rsp, VAR_SIZE * NUM_VAR
subtracts the product (8x2=16 because NUM_VAR
is rounded up to an even number) from the stack pointer to make room on the stack for the local 8-bit variable nParam
. Next, the code
lea rdi, byte nParam readACByte call readACByte nParam mov al, byte nParam test al, INTERRUPT_MASK ; is the INTERRUPT_MASK bit set? setnz al ; al = 1 if yes, al = 0 if no epilogue
loads the effective address lea
of byte nParam
into the rdi
register. In the C calling convention, the first argument to a function is always passed in the rdi
register.
The lines that follow call readACByte
, get the byte just read into the 1-byte al
register, and ANDs al
with INTERRUPT_MASK
to clear the processor's zero flag if bit 7 of the byte read is a <1>1<1>. The setnz al
instruction sets the al
register to 1
if the zero flag is cleared, or it sets al
to
if the zero flag is set. By the C calling convention, you always return the result in the rax
register (of which al
is the least significant byte). After invoking the ret
(in epilogue
), the al
register will contain 1
if there was an interrupt, or
otherwise.
The calling function (in this case, readWrite
) can then invoke test al, al
to find out whether the interrupted function found an interrupt. The test
instruction does an AND operation, which affects the flags but doesn't save the result. The relevant piece of the calling function is:
readWrite: ... call interrupted ; check for interrupt test al, al ; interrupted? jz .continue0 ; jump if no call resetInterrupt ; handle interrupt .continue0: ...
Why are label names preceded by a period? It's a YASM and NASM assembler trick that lets you reuse label names from method to method. The label .continue0
is really known to the assembler as readWrite.continue0
. Assembly language contains lots and lots of jumps (equivalent to the dreaded goto
statement). To avoid calling the resetInterrupt
method, gpio.asm
performs jz .continue0
to jump around the call to resetInterrupt
if al == 0
. Every conditional (jz .next
, i.e., jump if zero flag is 1) or unconditional (jmp .next
) jump needs a target label. Reuse of label names is a real time and energy saver.
Infos
- FT232H : https://www.adafruit.com/product/2264
- "Access Raspberry Pi GPIO with ARM64 assembly" by John Schwartzman, Linux Magazine, issue 247, June 2021, pg. 56, https://www.linuxpromagazine.com/Issues/2021/247/ARM64-Assembly-and-GPIO
- Code for this article: ftp://ftp.linux-magazine.com/pub/listings/linux-magazine.com/258/
« Previous 1 2
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
![Learn More](https://www.linux-magazine.com/var/linux_magazin/storage/images/media/linux-magazine-eng-us/images/misc/learn-more/834592-1-eng-US/Learn-More_medium.png)
News
-
NVIDIA Released Driver for Upcoming NVIDIA 560 GPU for Linux
Not only has NVIDIA released the driver for its upcoming CPU series, it's the first release that defaults to using open-source GPU kernel modules.
-
OpenMandriva Lx 24.07 Released
If you’re into rolling release Linux distributions, OpenMandriva ROME has a new snapshot with a new kernel.
-
Kernel 6.10 Available for General Usage
Linus Torvalds has released the 6.10 kernel and it includes significant performance increases for Intel Core hybrid systems and more.
-
TUXEDO Computers Releases InfinityBook Pro 14 Gen9 Laptop
Sporting either AMD or Intel CPUs, the TUXEDO InfinityBook Pro 14 is an extremely compact, lightweight, sturdy powerhouse.
-
Google Extends Support for Linux Kernels Used for Android
Because the LTS Linux kernel releases are so important to Android, Google has decided to extend the support period beyond that offered by the kernel development team.
-
Linux Mint 22 Stable Delayed
If you're anxious about getting your hands on the stable release of Linux Mint 22, it looks as if you're going to have to wait a bit longer.
-
Nitrux 3.5.1 Available for Install
The latest version of the immutable, systemd-free distribution includes an updated kernel and NVIDIA driver.
-
Debian 12.6 Released with Plenty of Bug Fixes and Updates
The sixth update to Debian "Bookworm" is all about security mitigations and making adjustments for some "serious problems."
-
Canonical Offers 12-Year LTS for Open Source Docker Images
Canonical is expanding its LTS offering to reach beyond the DEB packages with a new distro-less Docker image.
-
Plasma Desktop 6.1 Released with Several Enhancements
If you're a fan of Plasma Desktop, you should be excited about this new point release.