The Trident HAL code interfaces directly with the chip registers and exposes a common interface for applications to interact with the hardware peripherals. The API interface is kept consistant as much as possible across different chips in order to make porting apps from one chip to another as smooth as possible.
The Trident HAL is organized into modules. Each module has 1 common include file that is applicable to all chips. This file lives in the include directory. Each module also has 2 files that are specific to each chip: a .H register header file that contains chip specific definitions, and a .C source file with chip-specific implementation. These live in the chip directory, for example T32CZ20 or T32CM11.
Most of the HAL modules have a settings structure and a module init function. The settings structure shows the user what options and settings are available. This structure also has a macro initializer that sets reasonable defaults for all the structure options. This structure is passed to the module init function, which initizlizes the hardware peripheral. The init function will make sure that if there are incompatible settings an error status is returned. The init function also makes sure that register settings are done in the correct order. Some modules, like Random Number (TRNG) doesn't have a settings struct/init function because it is very simple and the module init (if needed) executes the first time an API is called.
Default initializer for settings structs are kept in the register header file. For instance, here is the GPIO default initializer for using a GPIO as an OUTPUT:
and here is how to use the default settings struct, but changing the output to LOW
Applications can still access the chip registers if necessary, but if the apps can stick to using the Trident HAL APIs it will make the app more portable.
The Trident HAL also tries to make application development easier by handling interrupts for each module and passing back events to the app via an Event Handler callback. For modules that receive bytes, the Trident HAL will send these received bytes to the app via a receive callback. Both the event handler callback and receive callback are defined in the settings struct.
Trident HAL GPIO APIs allow the app to make use of the pins of the chip as General Purpose Input/Outputs. This allows the app to set pins as inputs, or outputs, control the output signal, control the pull up or pull down amount, control the drive strength, etc.
GPIOs can be setup with a settings structure, and they can also be modified with individual APIs. The normal process would be to setup a GPIO once with an init function and then change settings as needed with the individual APIs.
Here is an example of setting up a GPIO as an output, for example for an LED:
Now, using that same GPIO, set it to LOW (clear):
The Trident HAL APIs for True Random Number Generation (TRNG) allow the app to easily get a random number.
This module is simple and does not need an init function called. This has 3 APIs to get random numbers of size uint8_t, uint16_t, or uint32_t. To get a random number larger than 4 bytes, the app will need to call the API more than once.
Here is an example of the APIs being used:
The Trident HAL APIs for Real Time Clock (RTC) allow the app to set and read the RTC and also create trigger events based on dates or times.
the RTC module keeps track of 7 time units:
The RTC module also supports events (interrupts) triggered on time units There are 3 types of events (interrupts) that can be set for these time units:
Example: setup the RTC for a specific time:
The Trident HAL APIs for hardware Timers allow the app to setup chip timers that call interrupts at periodic intervals. These interrupts are handled by the Trident HAL and these create a call to the app via the event callback. The event callback for Timers is simple since the only event is "timer expired". The app could have separate event callbacks for each timer defined, or 1 event callback for all timers, since the event callback passes in the timer ID that expired.
Example of how to setup a timer:
A watchdog timer is used to monitor a system, and restart or notify the system if a failure or lockup is detected. When a system enables a watchdog timer, the timer counts down and when it hits zero it restarts the system. The app is responsible for resetting the timer periodically, so it won't reach zero. If a lockup prevents this, or a section of code takes too long, that could cause a watchdog reset.
The Trident HAL watchdog API allows the watchdog timer to be setup to restart the system and/or to send an interrupt. The watchdog keeps a running count of the number of times the system was restarted due to the watchdog.
There is only 1 watchdog timer in the system, so that is what the APIs don'take take a watchdog_id.
Example of setting up a watchdog using the default settings:
Example of setting up a watchdog with custom settings:
Example of periodically resetting the watchdog:
The watchdog can be disabled, for example if a large security calculation had to be done which could trigger the watchdog. Example of how to enable and disable the watchdog:
The Trident HAL UART APIs allow the app to interact with another device connected via UART. This module defines a struct to use for setting up a UART and APIs to transmit and receive from a UART. It also defines a struct that allows interaction with the chip registers.
before setting up a UART, pick how it will be used:
— transmit —
— receive —
UART configuration is done by setting fields in an instance of the tr_hal_uart_settings_t struct and then passing this instance to tr_hal_uart_init.
There is a define that can be used to initialize an instance to the default values for that particular UART. for instance, to init UART1 to default values set it to DEFAULT_UART1_CONFIG, like this:
if a user defined receive function is set (rx_handler_function) then the Trident HAL will manage the chip interrupts and registers and will pass received bytes to this user receive function. This is the recommended way to get UART communication working. If this method is used then the app will just handle bytes that come in to the user receive function and can send responses using any of the TX APIs, for instance:
alternatively, the UART can be setup to use the chip DMA functions to transmit or receive bytes. Set the correct fields in the tr_hal_uart_settings_t to enable DMA TX (tx_dma_enabled) or DMA RX (rx_dma_enabled).
how to receive (RX) and transmit (TX) in different modes with the UART:
------— transmit ------—
------— receive ------—
SPI is used for synchronous serial communication, between a Controller (device that controls which device uses the bus) and a number of Peripherals, devices that use the bus. SPI uses at least 4 wires:
one device is the Controller, it has at least one Peripheral if there is more than 1 peripheral there should be a different CS line for each peripheral, but common bus for the other 3 pins. This way the Controller can pull the CS line low to a particular peripheral and only that peripheral will use the bus
The Trident API currently supports standard SPI. It does not yet support Dual SPI or Quad SPI.
The Trident API currently supports using the SPI as a Controller. It does not yet support using it as a Peripheral.
SPI has a lot of different options. There are some common values for these options, which are reflected in the default initializers. Since the Controller controls access to the bus, the API is setup for the Controller to transmit bytes and also receive bytes in the same command. The Controller sends bytes even when reading, so this API sends the number of bytes specified and uses the buffer to know the byte values. The boolean is set to true when also receiving.
Here is a sample of setting up a SPI as a Controller, using a winbond SPI Flash as the Peripheral:
The Trident HAL provides an API for using the I2C Controller device.
I2C (Inter-Integrated Circuit) also known as IIC, is a 2-wire synchronous, multi-device serial communication protocol. it is a simple and low cost solution for communication, but not the fastest due to just having 2 wires. One wire is clock and the other is data for both Controller and Target.
the 2 wires for I2C are:
NXP (who created I2C in 1982 when they were Philips have changed the terminology of I2C and now use "Controller" for the device that controls the communication (instead of "Master"). And "Target" is now used instead of "Slave".
The Controller controls the communication and can transmit bytes to the Target, or read bytes from the target. The example below shows a Controller writing 2 bytes to a Target and then reading 1 byte from that Target.
The Trident HAL provides a sample LED driver, that uses the Trident HAL RTC API to determine if it is time to turn on or off a blinking LED. This driver has an init function that should be called once, a setup function that starts an LED blinking, and a tick function that gets called in the applications main loop.
Example for using the LED driver: