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.
News
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.