Due to some shortcomings with the wiringTB library which Asus provides and my need for direct SPI access without the spidev subsystem I decided to write this minimalistic IO library for the Tinker Board. This is done by the consistent utilization of direct memory access (via virtual memory mapping). The need for direct access was based on the requirements of my project. So I implemented the features I needed plus some nice to have ones for the completeness of feature groups. E.g. including reading from gpios even when I just use them for their output functionality.
In my project I am using a RT Preempt enabled linux kernel (4.4.79) with the Armbian distribution. Due to problems of rt-patching the kernel provided by Asus (tag/1.9 at that time, haven't tried since) I started with a vanilla kernel and backported a lot of changes to re-enable all functionalities I needed (see the following link for additional information). The forked and updated package of armbian can be found here: https://github.com/arne48/armbian_build
This might be helpful for you too. And interfaces that might interfere with the gpio header are disabled.
- Input & Output
The library provides easy to use functions to setup any non-power pin to either an Input or an Output.
- Pullup & Down settings for Inputs
Functions are provided to set the pulling resistors of pins. The supported types are normal, pullup, pulldown and buskeeper.
- Drivestrength (mA) for Outputs
For pins operating as an output the max. drive strength can be set to 2, 4, 8 or 12 mA.
- SPI0 and SPI2 usable
The image provided by Asus just enables SPI2 which then can be used by spidev via ioctl. This library allows it to utilize both SPI controllers.
- Up to 66.7MHz clockspeed
The clock-sources of the clock-reset-unit for the SPI controllers are set in a way that they don't depend on the general PLL clock but are using the codec PLL which should be always stable. Further the divider setting allows a maximum clockspeed for the SPI controllers of up to 66.7MHz.
- Slave Select can be set to any free gpio or none
One functionality which I already missed in wiringPi was to disable the hardware slave select so it can be set by software to a wide variety of pins without sacrificing the still active hardware pin to be disconnected. To allow the most possible flexibility the slave selects were decided to be set by software internally alltogether. By implementing it like this it is possible to use any pin as a slave select which is then automatically operated by the library. Or it is also possible to set no slave select at all and handle it by yourself with as much slaves as needed.
- SPI modes 0, 1, 2, 3 are supported
The SPI controllers themself support all 4 available SPI modes and so does the library.
- MSB first and LSB first mode can be set
Via the configuration provided by the library it is possible to set the byte-order if needed.
The examples are located in the examples directory. They provide a reference to the usage of the library.
- GPIO Input
In this example all pins are set as inputs with pullups. Once a pin gets toggled by bridging them to GND or releasing them from GND a message will be prompted to the command-line. (End with any key)
- GPIO Output
In this examples all pins are set to ouput mode and are toggled in an infinite loop. (End with any key)
- SPI
This example is meant to be a loopback test and sends 64kb via SPI while checking the received data for corruptions.
Pin numbering:
The pins are numbered intuitively by their position on the header from 1 (3.3V) to 40 (last gpio). For an overview of the header please check this image from Asus: Link
Error Handling:
To prevent spamming of the console errors are handled quiete and gracefully. If an operation is illegal it will neither crash or print an error message in those cases no operation will be executed and in case of a non-void function a default value will be returned.
Maps the hardware into virtual memory and resets header pins to inputs
returns: 1 if initialization was successful otherwise returns 0
int tinkerboard_init(void);
If gpio memory is available it resets the header and finally will unmap the virtual memory of gpio, cru and spi
void tinkerboard_end(void);
Resets all pins of header to inputs using pullups
void tinkerboard_reset_header(void);
Reads the current configuration value of the requested pin from the corresponding grf register. If requested pin is a power-pin it returns 0
pin_number: number of pin to read the grf config of
returns: configuration (IOMUX) of pin. Contact appropriate documentation for further details
uint32_t tinkerboard_get_grf_config(uint32_t pin_number);
Reads the data direction register for specified pin and returns its mode as enum. If requested pin is a power-pin it returns Input
pin_number: number of pin to read the data direction config of
returns: data direction of gpio. This can be either Input or Output
enum IOMode tinkerboard_get_gpio_mode(uint32_t pin_number);
Sets the data direction of specified pin to Input or Output and also the grf register to gpio
pin_number: number of pin to set the data direction for
mode: direction (Input or Output) to set the pin to
void tinkerboard_set_gpio_mode(uint32_t pin_number, enum IOMode mode);
If gpio is set as an Output this function will set its voltage level to either HIGH or LOW
pin_number: number of output pin to set the voltage level for
state: state to set the pin to (HIGH or LOW)
void tinkerboard_set_gpio_state(uint32_t pin_number, enum IOState state);
If gpio is set as an Input this function will return the current state sensed on Input
pin_number: number of pin to request the current state for
returns: current state that was sensed on Input (HIGH or LOW)
enum IOState tinkerboard_get_gpio_state(uint32_t pin_number);
This function allows to configure a pulling behaviour for a pin to prevent it from floating eventually. Available configurations are NORMAL_Z, PULLUP, PULLDOWN, BUSKEEPER.
pin_number: number of pin to set the pulling behaviour for
mode: pulling mode to set the specified pin to
void tinkerboard_set_gpio_pud(uint32_t pin_number, enum PUDMode mode);
Sets the output drive strength of the specified pin. Available options are DRV2MA, DRV4MA, DRV8MA, DRV12MA.
pin_number: pin to set the output driving strength of
strength: strength to set the specified pin to
void tinkerboard_set_gpio_drive_strength(uint32_t pin_number, enum DriveStrength strength);
Initializes the requested SPI controller with the configuration passed.
controller: enum of SPI controller to initialize (SPI0 or SPI2)
mode_config: configuration for the SPI controller
void tinkerboard_spi_init(enum SPIController controller, struct spi_mode_config_t mode_config);
Disables specified SPI controller
controller: enum of SPI controller to disable (SPI0 or SPI2)
void tinkerboard_spi_end(enum SPIController controller);
Transfers a passed buffer of specified length by using the requested SPI controller and writes received data into receive buffer.
controller: enum of SPI controller to transfer data with (SPI0 or SPI2)
tx_buff: pointer to transfer buffer
rx_buff: pointer to receive buffer
length: length of transfer buffer
mode_config: configuration the SPI controller was initialized with
void tinkerboard_spi_transfer(enum SPIController controller, uint8_t* tx_buff, uint8_t* rx_buff, uint32_t length, struct spi_mode_config_t mode_config);
This struct contains the configurations to initialize the SPI controller with.
Important: data_frame_size and transfer_mode are exposed for eventual customizations. If their handling is not added the defaults DFS_8 and TRANSMIT_RECEIVE must be used.
clk_divider: defines any even divider >=2 (66.7MHz) to reduce the SPI clock with
clk_mode: defines the SPI clock mode (0, 1, 2 or 3)
data_frame_size: specifies the size of the data frame (DFS_4, DFS_8 or DFS_16)
slave_select: specifies the pin to use as slave select. This can be any free pin. Or if no slave select is needed use define NO_SS.
transfer_mode: transfer mode of SPI controller. (TRANSMIT_RECEIVE, TRANSMIT, RECEIVE). Please read important note.
byte_order: Sets the order the controller transfers bytes by (MSB_FIRST, LSB_FIRST)
struct spi_mode_config_t {
uint32_t clk_divider;
uint32_t clk_mode;
enum SPIDataFrameSize data_frame_size;
uint32_t slave_select;
enum SPITransferMode transfer_mode;
enum SPIByteOrder byte_order;
};
For compilation and installation cmake is used. Besides building the library this also supports its installation. By installing the library it is possible to use the provided cmake module to easily make use of the library within your own projects.
$mkdir build && cd build
$cmake ..
$make
$sudo make install
- Place the cmake module (FindTINKERBOARD_IO.cmake) found in the cmake_module directory somewhere inside of your project.
- add
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/PATH_TO_CMAKE_MODULE/")
where "PATH_TO_CMAKE_MODULE" is the relative path to the directory containing the module within your project. - now you can use
find_package(TINKERBOARD_IO REQUIRED)
to get the ${TINKERBOARD_IO_LIBRARIES} and ${TINKERBOARD_IO_INCLUDE_DIRS} variables for further usage.