This is my first self written HET program that actually works. You can define a number of timers and let them create an interrupt when they expire. I've created 8 independent 1µs resolution timers in this example. Many more are possible*1.

All previous posts were to train myself for this. While porting a LoRa example from MBED to Hercules, I tried to create timers with interrupts on the Hercules just by winging it. That never worked. Frustration .

By blogging about all the previous examples and trying to understand them, I got enough skill to build this one. I needed to fully understand the count instructions, interrupt mechanism, how to set HET data from the ARM core and what happens in the HET clock, high resolution and loop resolution ticks. It payed off.

 

*1 I am using the smallest of the Hercules family available on a LaunchPad, the TMS570LS04. With this 1µs precision and the approach I've taken, you can set up 80 counters.

 

 

Functionality an API

 

This project has two API functions:

 

  • void huiInit()

Configures the ARM, so that it can accept interrupts from the HET program.

The ARM core only accepts interrupts from those HET code lines that you tell it to. This function does that.

 

  • void huiSetTimeout(uint32_t index, uint32_t us)

Configure one of the timeout timers and start it.

The function will place the expected timeout (us, in microseconds) into the HET memory for this counter (defined by index), set its count to 0 and enables its interrupt.

When the timeout is set to 0, it does all of the above, but it disables the interrupt for that counter.

 

The counter starts immediately. By default, the timer throws the interrupt at each timeout.

If you want a single timeout, call huiSetTimeout(index, 0) in the interrupt handler to stop it.

 

HET Code

 

It's simple. One line per virtual counter/timer. All the same, except for the "next code line" pointer that points to different lines to keep the flow flowing.

 

; this program only generates interrupts. No pin required
DUMMY_PIN .equ 0

; interrupt and count attributes are set on the host program
L00   CNT { next=L01,reg=A,comp=EQ,irq=OFF,control=ON,max=0};
L01   CNT { next=L02,reg=A,comp=EQ,irq=OFF,control=ON,max=0};
L02   CNT { next=L03,reg=A,comp=EQ,irq=OFF,control=ON,max=0};
L03   CNT { next=L04,reg=A,comp=EQ,irq=OFF,control=ON,max=0};
L04   CNT { next=L05,reg=A,comp=EQ,irq=OFF,control=ON,max=0};
L05   CNT { next=L06,reg=A,comp=EQ,irq=OFF,control=ON,max=0};
L06   CNT { next=L07,reg=A,comp=EQ,irq=OFF,control=ON,max=0};
L07   CNT { next=L00,reg=A,comp=EQ,irq=OFF,control=ON,max=0};

 

Each line is called once per loop resolution (lr). I've defined that, in the ARM initialisation, as 1 µs per lr (see later in the HALCoGen Configuration section)

As example, counter #3:

 

L02   CNT { next=L03,reg=A,comp=EQ,irq=OFF,control=ON,max=0};

 

The instruction is CNT, a command that creates a virtual counter in HET.

We're using register A, could be any register. The code needs it to operate, but because the next line isn't interested in the count of the previous one (each line works independent) there's no need to protect the register from being overwritten.

We set the compare to equal. The activity state will become "hot" at the very point that the line is called the number of times that we deine.

Interrupt is OFF. That's the starting state. The API documented above will set this attribute to ON when this timer is activated.

Control defines what happens to the count. It's set ON, so the count is reset to 0 when the state is "hot".

The requested timeout, in lr units (microseconds in my project) is placed in the max attribute. It is in this case 0 and only set, in the ARM code, when we want a timer active.

 

HALCoGen Configuration

 

The HET module is activated. Because I'm using two LEDs to show timer activity for demo purposes, the GIO module is activated too.

The design is interrupt based, so I activate the HET interrupt.

The HET module itself is where we set the loop resolution time:

To get 1 µs, play with the HR and LR pre-scalers. Smart people can do that by calculating.

I use trial and error. I set the Loop time to the time I want, 1000 ns.

Then I play with the HR pre-scaler, starting from the highest setting 63, down until the Actual LR Time is what I requested.

There are other ways of getting a 1µs sequence, with the pre-scalers and in the HET code.

This one makes for the simplest program.

 

The HET-compiled .h and .c files are selected on the right.

I've chosen to not set the lines that can generate an interrupt here in HALCoGen.

These settings are calculated and configured in the APIhuiInit() call, based on the number of counters you want to have.

 

Last task is to configure the two pins that are connected to the demo LEDs as output pins.

 

On the LSP570LS 04 Launchpad, they are GIO2 and NHET08. Don't get confused by the NHET08 here. It's used as a common I/O pin and controlled by the GIO driver.

It has nothing to do with the HET code above.

 

ARM Code - The API

 

In the header, you can see that it took me 6 days to come up with a working product - time spent on posts 4 and 5 in this series. I thought I was up to the task after post 3 but that was not the case.

I needed the two "getting started" ones too to fully grasp the mechanisms.

 

API header:

 

/*
 * het_interrupt_utils.h
 *
 *  Created on: 11 jan. 2020
 *      Author: jancu
 */

#ifndef HALCOGEN_INCLUDE_HET_INTERRUPT_UTILS_H_
#define HALCOGEN_INCLUDE_HET_INTERRUPT_UTILS_H_

#include "sys_common.h"


#define HUI_NUM_COUNTERS 8


void huiInit();
void huiSetTimeout(uint32_t index, uint32_t us);

#endif /* HALCOGEN_INCLUDE_HET_INTERRUPT_UTILS_H_ */

 

HUI_NUM_COUNTERS has to match the number of lines (= virtual counters) you put in the HET code. You also finfd the public API call definitions here.

 

Implementation:

 

/*
 * het_interrupt_utils.c
 *
 *  Created on: 11 jan. 2020
 *      Author: jancu
 */

#include "het_interrupt_utils.h"

#include "het.h"
#include "het_interrupt.h"


void huiInit() {
  // enable interrupt for instructions in N2HET
  uint32_t u;
  for (u = 0; ((u < HUI_NUM_COUNTERS && (u < 32)); u++) {
    hetREG1->INTENAS = (1 << u) ;
//    huiSetTimeout(u, 0); not needed. The HET code has the right initialisation attributes.
  }
}


void huiSetTimeout(uint32_t index, uint32_t us) {
  if (index >= HUI_NUM_COUNTERS) {
    while(1); // invalid index trap, should be lower as the number of available virtual counters
  }

  // interrupt off
  hetRAM1->Instruction[index].Program &= 0xFFFE;

  if (us > 0x1FFFFFE) {
    while(1); // invalid count trap, value has to fit in a 25 bit field
  }

  // reset active count
  hetRAM1->Instruction[index].Data = 0;

  // set us
  hetRAM1->Instruction[index].Control = us;

  // enable interrupt if timeout > 0
  if (us) {
    hetRAM1->Instruction[index].Program |= 0x0001;
  }
}

 

The huiInit() function just tells the ARM interrupt engine which lines of HET code it can expect an interrupt of. I put a guard when more than 32 counters are used.

LSB 1 represents line 0, 32, 64, .... Bit 2 lines 1, 65, ... and so on. For our program, as soon as you have 32 lines, all bits are occupied because we want to react on interrupts for each counter.

 

Function huiSetTimeout() is the one that sets a timer. At the begin, it disables the interrupt for the timer we're configuring, and sets its current count to 0.

Then it writes the expected count (the number of microseconds) to the control field of that timer and, if you don't pass 0 as requested timeout, enables the counter interrupt.

 

The index is 0 based. To set timer 3 to a 1 second timeout, call huiSetTimeout(2, 1000000).

 

ARM Interrupt

 

#include "gio.h"
#include "het.h"

/* USER CODE BEGIN (0) */
#include "het_interrupt.h"
#include "het_interrupt_utils.h"

volatile uint32_t uInterruptCount = 0U;
/* USER CODE END */

// ...

#pragma WEAK(hetNotification)
void hetNotification(hetBASE_t *het, uint32 offset)
{
/*  enter user code between the USER CODE BEGIN and USER CODE END. */
/* USER CODE BEGIN (39) */
  uint32_t index = offset - 1;
  if (index >= HUI_NUM_COUNTERS) {  // invalid offset trap, expecting to be not higher than number of available virtual counters
    while(1);
  }
  huiSetTimeout(index, 0); // disable the timer

  // test: blink led A for even, B for uneven, then increase a counter so that we know how many interrupts we received.
  if (index & 1) { // odd
    gioToggleBit(hetPORT1, 8);
  } else {
    gioToggleBit(gioPORTA, 2);
  }
  uInterruptCount++;
/* USER CODE END */
}

 

When the HET module throws a registered interrupt, this function is called, and the offset represents the line of HET code that caused it +1.

Because each counter is a line of code, we can find the 0 based timer as offset - 1.

I've pu a test loop to catch if there's an unexpected interrupt. This hasn't happened.

Then (this is only if you want a timeout to fire only once), I disable the timer with a huiSetTimeout(timer#, 0) call.

 

All the rest is test code to give visual and programmatic feedback.

When an even counter timed out, I toggle LED A. When an uneven counter did it, I toggle LED B.

I also increment a test counter each time an interrupt is received. The test framework (next section) can then define a number of counters and control if the right amount of timeout interrupts resulted from that.

 

ARM Test Bed

 

The main() function tests the concept.

 

int main(void) {
  gioInit();
  hetInit();

  huiInit();

  _enable_interrupt_();

  int i = 0;
  for (i = 0; i < HUI_NUM_COUNTERS; i++) {
    huiSetTimeout(i, 500000);
    while(uInterruptCount < i * 2 + 1) {}
    huiSetTimeout(i, 250000);
    while(uInterruptCount < i * 2 + 2) {}
  }

  while(uInterruptCount == 2 * HUI_NUM_COUNTERS) {
    // success.
  }
    return 0;
}

 

The first two functions init the modules we configured in HALCoGen.

Then huiInit() initialises our module.

Next the ARM interrupt engine is enabled.

 

Next, I have a loop that will test each timer, and validate the interrupt count at each step.

Led A and B will each flash 250 ms on,  500 ms off.

Then we check (because each loop generates two interrupts) that the total count is 16.

 

Voila. Project attached.

 

Related Blog
1: adapt TI example to a LaunchPad
2: Generate Dynamic Duty Cycle
3: Measure a PWM Signal and HET Interrupts
4: Getting Started
5: Getting Started with the Wave Form Simulator
6: Many Precision Timeout Counters with ARM Interrupt