Hardware registers #
As Hardware Abstraction Layer (HAL), JeeH needs to provide access to a wide range of built-in hardware peripherals. On ARM Cortex, there are specific addresses in the 4 GB address space which map to the registers of these peripherals. These are documented in Reference Manuals 1.
Direct access from C++ #
To simplify use, JeeH creates a set of IoReg
definitions which allow reading and
writing the registers, as well as individual bits and bit ranges in them. This
example shows how to send a character to a serial port on an STM32F7xx chip,
using polled mode:
#include <jee.h>
using namespace jeeh;
void putch (char c) {
enum { ISR=0x1C, TDR=0x28 }; // USART register offsets
while (!USART1[ISR](7)) {} // wait until TXE is clear
USART1[TDR] = c; // send the byte out
}
The above code is highly µC-specific, although much of this is often the same across several different STM32 µC families. One key trick in JeeH is how it defines USART1:
constexpr IoReg<0x4001'1000> USART1;
This is automatically generated as part of the build process, from a System View
Definition (SVD) file for the specific µC currently used as build target.
These definitions are generated in the file jee-svd.h
and included by jee.h
.
Taking a closer look #
The IoReg
type is a C++ template. It defines an object which can be treated as
a subscripted array, with each element a hardware register. Thus, USART1[ISR]
refers to the 32-bit register at address 0x4001101C (i.e. 0x40011000 + 0x1C).
This register can then be read or written as 32-bit word as if it were a normal
(volatile) C++ variable. In this case we only check bit 7, using the notation
USART1[ISR](7)
. Again, this bit can be read or written as if it were a C++
variable.
Once the USART TX buffer is available, USART1[TDR] = c;
then stores a new
character in it.
There is no middleman #
Unlike other HAL libraries, JeeH does not add wrapper functions to access hardware registers. Only the base of each hardware peripheral is defined as a (constexpr) C++ variable. It is up to the driver code to specify any offsets it needs and to introduce symbolic values for them.
Since the Reference Manuals are the ultimate source of truth for each hardware peripheral in each µC, and since they need to be perused anyway, there’s no reason to wrap things up in yet another API, which would require its own documentation (and introduce its own bugs …).
The result of this choice of C++ syntax, is that direct access to all the hardware tends to require much less source code compared to other HAL designs. Which in turn makes it easier to read.
The way to use JeeH for direct access to built-in hardware, is to open up the Reference Manual!
Configure and use GPIO pins #
There is a Pin
type to easily define and configure the General Purpose I/O
(GPIO) pins. Here is what must surely be JeeH’s simplest blink example:
#include <jee.h>
using namespace jeeh;
int main () {
Pin led ("C13"); // the LED is tied to GPIO pin PC13
led.mode("P"); // set this pin in "push-pull" output mode
while (true) {
led.toggle(); // toggle the LED
msIdle(500); // wait 500 ms
}
}
There’s also a quick way to configure multiple pins, for example:
Pin::config("B0:A,B1,C13:P,G10:H7"):
This sets PB0 and PB1 to analog (in), PC13 to pull-up (out), and PG10 to alt mode 7 with a high-speed pin drive. The following variant also defines the pin objects for use in subsequent code:
Pin pins [4];
Pin::config("B0:A,B1,C13:P,G10:H7", pins):
...
pins[2] = 1; // set PC13 high
Lastly, this code defines the same pin objects, but without configuring them:
Pin pins [4];
Pin::config("B0,B1,C13,G10", pins):
...
pins[2].mode("P"); // configure PC13 as push-pull output
The following configuration modes are available for pins on STM32 µCs:
A
= analog (in)F
= floating (in)O
= open drain (out)P
= push-pull (out)
Optionally followed by these (not every combination will be meaningful):
D
= enable pull-downU
= enable pull-upL
= low speed driveN
= normal speed drive (default)H
= high speed driveV
= very high speed drive<N>
= alternate mode N
All pin configurations, reads, and writes use the direct access mechanism described earlier.
C++17 code efficiency #
The gcc
/ g++
compilers are surprisingly adept at optimising the generated
code these days. One example is with pins: the definition Pin led ("C13");
is
in fact simply a compile-time way of defining a variable, which encodes the
GPIO port and bit location in a single byte (when the argument is a string known
at compile time). It could also have been written as:
constexpr Pin led ("C13");
This means that all the code based on top of this is essentially using a small
constant, which lets the compiler perform constant folding and constant
propagation in most cases. The same goes for hardware register definitions
such as USART1
: it’s simply a compile-time constant.
Turning variables into constant values helps the compiler generate smaller and faster code.
-
Such as the 1700+ page RM0385 PDF for STM32F74x / STM32F75x µCs on STM’s site. ↩︎