Trident IoT SDK
 
Loading...
Searching...
No Matches
Hardware Abstraction Layer (HAL)

Overview

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:

// from file: hal/T32CZ20/T32CZ20_gpio.h
#define DEFAULT_GPIO_OUTPUT_CONFIG \
{ \
.direction = TR_HAL_GPIO_DIRECTION_OUTPUT, \
.output_level = TR_HAL_GPIO_LEVEL_HIGH, \
.enable_open_drain = false, \
.pull_mode = TR_HAL_PULLOPT_PULL_NONE, \
.drive_strength = TR_HAL_DRIVE_STRENGTH_DEFAULT, \
}

and here is how to use the default settings struct, but changing the output to LOW

// setup a GPIO for output, using the default settings, except set the output to LOW
// setup GPIO 1 with these settings
gpio_settings);
tr_hal_status_t
Definition tr_hal_common.h:25
#define DEFAULT_GPIO_OUTPUT_CONFIG
Definition T32CM11_gpio.h:438
@ TR_HAL_GPIO_LEVEL_LOW
Definition T32CM11_gpio.h:264
tr_hal_status_t tr_hal_gpio_init(tr_hal_gpio_pin_t pin, tr_hal_gpio_settings_t *gpio_settings)
pin type
Definition tr_hal_platform.h:23
Definition T32CM11_gpio.h:400
tr_hal_level_t output_level
Definition T32CM11_gpio.h:405

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.

GPIO

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:

#define APP_LED_PIN 8
// GPIO config structure
tr_hal_gpio_settings_t led_gpio_settings;
// setup as OUTPUT, HIGH (set), pull down 100K, normal drive strength
led_gpio_settings.enable_open_drain = false;
led_gpio_settings.pull_mode = TR_HAL_PULLOPT_PULL_NONE;
// use pin defined as APP_LED_PIN for LED
tr_hal_gpio_pin_t led_pin = (tr_hal_gpio_pin_t) { APP_LED_PIN };
led_gpio_settings);
if (status != TR_HAL_SUCCESS)
{
// error handling
}
@ TR_HAL_SUCCESS
Definition tr_hal_common.h:27
@ TR_HAL_DRIVE_STRENGTH_DEFAULT
Definition T32CM11_gpio.h:333
@ TR_HAL_PULLOPT_PULL_NONE
Definition T32CM11_gpio.h:290
@ TR_HAL_GPIO_DIRECTION_OUTPUT
Definition T32CM11_gpio.h:254
@ TR_HAL_GPIO_LEVEL_HIGH
Definition T32CM11_gpio.h:265
tr_hal_drive_strength_t drive_strength
Definition T32CM11_gpio.h:411
tr_hal_direction_t direction
Definition T32CM11_gpio.h:402
bool enable_open_drain
Definition T32CM11_gpio.h:408
tr_hal_pullopt_t pull_mode
Definition T32CM11_gpio.h:421

Now, using that same GPIO, set it to LOW (clear):

tr_hal_status_t tr_hal_gpio_set_output(tr_hal_gpio_pin_t pin, tr_hal_level_t level)

Random Number (TRNG)

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:

uint32_t result32 = 0;
uint16_t result16 = 0;
uint8_t result8 = 0;
tr_hal_test_printf("4 byte random number: status %2d, number: 0x%8x\n", status32, result32);
tr_hal_test_printf("2 byte random number: status %2d, number: 0x%8x\n", status16, result16);
tr_hal_test_printf("1 byte random number: status %2d, number: 0x%8x\n", status8, result8);
tr_hal_status_t tr_hal_trng_get_uint32(uint32_t *result)
tr_hal_status_t tr_hal_trng_get_uint8(uint8_t *result)
tr_hal_status_t tr_hal_trng_get_uint16(uint16_t *result)

Real Time Clock (RTC)

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:

  • milliseconds,
  • seconds,
  • minutes,
  • hours,
  • days,
  • months,
  • and years.

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:

  1. ON_UNIT_CHANGE - whenever a time unit changes, get an event. For instance, if this is set for days, then each time the day value changes (a new day starts) then an event would be generated. One of these events can be set for each time unit. (in other words, there could be 6 of these events setup at one time, one event for each time unit)
  2. ON_SPECIFIC_VALUE - whenever a time unit gets to a specific value. For instance, if this was set for minutes=15, then this event would trigger at times 0:15:00, 1:15:00, 2:15:00, etc. One of these events can be set for each time unit.
  3. COMBO EVENT - this event is set for a specific second, minute, hour, day, month, year. If this event is set, it is the only event that can be set (no ON_UNIT_CHANGE and no ON_SPECIFIC_VALUE can also be set at the same time). This event can be set so some time units are ignored, but if a time unit is ignored, all time units greater than the ignored time unit must also be ignored. For instance, days can be ignored but then months and years must ALSO be ignored (and sec, min, hours are NOT ignored)

Example: setup the RTC for a specific time:

// create an event handler for RTC triggers
void my_rtc_event_handler(uint32_t event_bitmask,
tr_hal_rtc_date_time current_date_time)
{
if (event_bitmask == TR_HAL_RTC_EVENT_TRIGGERED_HOURS)
{
// take some action
}
}
tr_hal_rtc_date_time new_date_time;
// May the 4th, 2025
new_date_time.date.months = 5;
new_date_time.date.days = 4;
new_date_time.date.years = 2025;
// 5:20pm
new_date_time.time.hours = 17;
new_date_time.time.minutes = 20;
new_date_time.time.seconds = 0;
// trigger an event at 5pm every day
new_date_time.hours_event_trigger = TR_HAL_EVENT_TRIGGER_ON_SPECIFIC_VALUE;
new_date_time.hours_trigger_value = 17;
// call our event handler function when the trigger occurs
new_date_time.event_handler_fx = my_rtc_event_handler;
// set the new RTC time/date with the trigger
status = tr_hal_rtc_set_date_time(new_date_time);
tr_hal_status_t tr_hal_rtc_set_date_time(tr_hal_rtc_date_time new_date_time)
#define TR_HAL_RTC_EVENT_TRIGGERED_HOURS
Definition T32CM11_rtc.h:315
@ TR_HAL_EVENT_TRIGGER_ON_SPECIFIC_VALUE
Definition T32CM11_rtc.h:179
Definition T32CM11_rtc.h:135
tr_hal_rtc_time time
Definition T32CM11_rtc.h:137
tr_hal_rtc_date date
Definition T32CM11_rtc.h:136
uint16_t years
Definition T32CM11_rtc.h:109
uint8_t months
Definition T32CM11_rtc.h:112
uint8_t days
Definition T32CM11_rtc.h:115
uint8_t hours
Definition T32CM11_rtc.h:123
uint8_t minutes
Definition T32CM11_rtc.h:126
uint8_t seconds
Definition T32CM11_rtc.h:129

Timers

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:

// create a event callback function for the timer
void timer_10sec_event(tr_hal_timer_id_t expired_timer_id)
{
// this means 10 sec timer fired
// also can check expired_timer_id == TIMER_0_ID if we have more than 1 timer
}
// default timer settings for 32 MHz clock
// we want every 10 seconds
uint32_t seconds = 10;
// timer is 32 MHz so prescaler of 1K and 32K start gives 1 second
timer_settings.timer_start_value = (32000 * seconds);
// enable timer, repeat, and interrupts
timer_settings.interrupt_enabled = true;
timer_settings.timer_repeats = true;
timer_settings.timer_enabled = true;
// add the event callback here that we created above
timer_settings.event_handler_fx = timer_10sec_event;
status = tr_hal_timer_init(TIMER_0_ID, &timer_settings);
tr_hal_timer_id_t
Definition T32CM11_timers.h:35
#define DEFAULT_32MHZ_TIMER_CONFIG
Definition T32CM11_timers.h:178
@ TIMER_0_ID
Definition T32CM11_timers.h:36
@ TR_HAL_TIMER_PRESCALER_1024
Definition T32CM11_timers.h:55
tr_hal_status_t tr_hal_timer_init(tr_hal_timer_id_t timer_id, tr_hal_timer_settings_t *timer_settings)
Definition T32CM11_timers.h:149
bool timer_repeats
Definition T32CM11_timers.h:155
bool interrupt_enabled
Definition T32CM11_timers.h:159
bool timer_enabled
Definition T32CM11_timers.h:156
tr_hal_timer_callback_t event_handler_fx
Definition T32CM11_timers.h:163
uint32_t timer_start_value
Definition T32CM11_timers.h:151
tr_hal_timer_prescalar_t prescalar
Definition T32CM11_timers.h:152

Watchdog timer (WDOG)

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:

// use the defaultconfig which is:
// watchdog ENABLED
// timer set for 6 seconds
// keep reset count (don't clear it on init)
// no lockout, default min time before reset,
// no interrupts enabled, no event handler function
// init the watchdog with default settings
tr_hal_status_t status = tr_hal_wdog_init(&settings);
#define DEFAULT_WDOG_CONFIG
Definition T32CM11_wdog.h:232
tr_hal_status_t tr_hal_wdog_init(tr_hal_wdog_settings_t *wdog_settings)
Definition T32CM11_wdog.h:180

Example of setting up a watchdog with custom settings:

// wdog settings
// start it disabled, we will enable it later
settings.watchdog_enabled = false;
// set for 20 seconds
settings.reset_enabled = true;
#define TR_HAL_WDOG_1_SECOND_TIMER_VALUE
Definition T32CM11_wdog.h:150
#define TR_HAL_WDOG_1_SECOND_PRESCALAR_VALUE
Definition T32CM11_wdog.h:154
bool watchdog_enabled
Definition T32CM11_wdog.h:184
tr_hal_wdog_prescalar_t clock_prescalar
Definition T32CM11_wdog.h:190
bool reset_enabled
Definition T32CM11_wdog.h:187
uint32_t initial_value
Definition T32CM11_wdog.h:191

Example of periodically resetting the watchdog:

// starts timer over again from initial value
status = tr_hal_wdog_reset();
tr_hal_status_t tr_hal_wdog_reset(void)

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:

// enable watchdog
status = tr_hal_wdog_enable();
// disable watchdog
// is watchdog currently enabled
bool is_enabled;
status = tr_hal_is_wdog_enabled(&is_enabled);
tr_hal_status_t tr_hal_wdog_enable(void)
tr_hal_status_t tr_hal_wdog_disable(void)
tr_hal_status_t tr_hal_is_wdog_enabled(bool *is_enabled)

UART

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 —

  1. transmit using the raw TX APIs
  2. transmit using DMA TX APIs

— receive —

  1. receive automatically in the user defined receive function (this is recommended)
  2. receive using DMA RX, where the app manages the DMA RX buffer
  3. receive using the raw API calls

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:

#define DEFAULT_UART1_CONFIG
Definition T32CM11_uart.h:579
Definition T32CM11_uart.h:470

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:

tr_hal_uart_raw_tx_buffer(UART_1_ID, &bytes_to_send, send_length);
@ UART_1_ID
Definition T32CM11_uart.h:80
tr_hal_status_t tr_hal_uart_raw_tx_one_byte(tr_hal_uart_id_t uart_id, const char byte_to_send)
tr_hal_status_t tr_hal_uart_raw_tx_buffer(tr_hal_uart_id_t uart_id, const char *bytes_to_send, uint16_t num_bytes_to_send)

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 ------—

  1. transmit using the raw TX APIs
    => set tr_hal_uart_settings_t.tx_dma_enabled=0
    => use API: tr_hal_uart_raw_tx_buffer
  2. transmit using DMA TX APIs
    => set tr_hal_uart_settings_t.tx_dma_enabled=1
    => use API: tr_hal_uart_dma_tx_bytes_in_buffer

------— receive ------—

  1. receive automatically in the user defined receive function (recommended)
    => set tr_hal_uart_settings_t.rx_handler_function to an app function
    => set tr_hal_uart_settings_t.rx_dma_enabled=0
    => handle bytes in the app function
  2. receive using DMA RX, where the app manages the DMA RX buffer
  3. receive using the raw API calls
    => set tr_hal_uart_settings_t.rx_dma_enabled=0
    => call tr_hal_uart_raw_rx_available_bytes

SPI

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:

  • CS = chip select, active low, to allow a peripheral to communicate
  • SCLK = clock signal, from Controller
  • SDO = serial data out / PICO = periperal in, controller out (data goes from Controller to Peripheral)
  • SDI = serial data in / POCI = peripheral out, controller in (data goes from Peripheral to Controller)

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.

uint8_t chip_select_index_to_use,
char* bytes_to_send,
uint16_t num_bytes_to_send,
bool receive_bytes);
tr_hal_spi_id_t
Definition T32CM11_spi.h:33
tr_hal_status_t tr_hal_spi_raw_tx_buffer(tr_hal_spi_id_t spi_id, uint8_t chip_select_index_to_use, char *bytes_to_send, uint16_t num_bytes_to_send, bool receive_bytes)

Here is a sample of setting up a SPI as a Controller, using a winbond SPI Flash as the Peripheral:

spi_settings.event_handler_fx = winbond_spi_event_handler;
spi_settings.rx_handler_function = winbond_spi_receive_handler;
status = tr_hal_spi_init(spi_id, &spi_settings);
#define SPI_CONFIG_CONTROLLER_NORMAL_MODE
Definition T32CM11_spi.h:633
@ SPI_0_ID
Definition T32CM11_spi.h:34
tr_hal_status_t tr_hal_spi_init(tr_hal_spi_id_t spi_id, tr_hal_spi_settings_t *spi_settings)
Definition T32CM11_spi.h:484
tr_hal_spi_event_callback_t event_handler_fx
Definition T32CM11_spi.h:592
tr_hal_spi_receive_callback_t rx_handler_function
Definition T32CM11_spi.h:588

I2C

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:

  • SDA == serial data
  • SCL == serial clock

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.

tr_hal_i2c_settings_t i2c_settings = I2C_CONFIG_CONTROLLER;
// init the SPI Controller using default settings
status = tr_hal_i2c_init(i2c_id,
&i2c_settings);
uint8_t bytes_to_write[12];
uint16_t target_address = 0x50;
// set up the variables to write 2 bytes to the target
num_bytes_to_write = 2;
num_bytes_to_read = 0;
bytes_to_write[0] = (read_addr >> 8);
bytes_to_write[1] = (read_addr & 0xFF);
// write the 2 byte address of where to read from
status = tr_hal_i2c_transmit(I2C_CTRL_0_ID,
target_address,
bytes_to_write,
num_bytes_to_write);
// now setup to read 1 byte from the Target
num_bytes_to_write = 0;
num_bytes_to_read = 1;
// read 1 byte from target
tr_hal_i2c_receive(I2C_CTRL_0_ID,
target_address,
num_bytes_to_read);

LED Driver

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:

// application main loop
MAIN()
{
// ...
led_blink_driver_init();
while (1)
{
// ...
led_blink_driver_tick();
}
}
#define GPIO_LED_RED 1
// setup the RED LED to blink
setup_led_blink(GPIO_LED_RED,
500, // blink time
6); // number of blinks