This project uses the timers of a TI Hercules microcontroller to generate complex digital signals and to measure digital signals.

 

In this post: measure the the duty cycle of the previous post and take actions when it's about a certain range. The actions are:

  • put a pin high as long as the duty cycle stays outside the accepted range (error output)
  • throw an interrupt to the host microcontroller

 

 

This project is based on TI's application note spna178: Monitoring PWM Using N2HET.

That note explains the functionality and has a link to a Code Composer Studio project .

I'm using these as the source for the blog series.

 

Measure a Pulse Signal's Duty Cycle

 

The HET module is not only a signal generator. It can read inputs too (actually it does not have to do any I/O. I'm re-learning the module because I want to use it in a project as a software only timer).

It can then do calculations based on if/when the input is a high/low or just changed. You can make time measurements based on events. You can count.

In this case, we constantly measure edges, and calculate the duty cycle relation. If you measure 3 transitions. You can calculate duty cycle based on that.

Then, we compare the duty cycle with a desired range. We (for exercise reasons) consider duty cycles between 20 and 80% valid, the others outliers.

 

When the HET module detects that a duty cycle is an outlier, it pulls an error pin high.

At the same time (this is our choice here. Looking for an excuse to test HET interrupts ), it also raises an interrupt with the host controller (the ARM core).

The ARM core gets info whether the interrupt is fired for a "< 20%" situation or a "> 80%" situation. It maintains a counter for both and increments it whenever one of the two outlier cases happen.

I hope you understand this is an arbitrary example to show how measuring and taking actions based on that works.

 

 

TI has chosen to kick off two reactions when a certain situation happens: put a pin high and throw a host controller error. These are (practical and good to learn!) example reactions.

Putting the error output high and low takes no main controller clock ticks. The HET module does that all by itself

Try it: run the program in debug mode and halt the main core. The HET modules keep on generating the triangle wave and keep on measuring and driving the error output.

 

If you decide to raise an interrupt with the host ARM core, controller cycles will be consumed.

That's natural. We're handing over the handling of a situation to the ARM controller. It needs the clock ticks to manage that. That's no different from receiving a UART buffer full interrupt or anything else...

 

The HET Program

 

Like in the previous post, the defines at the begin will be overridden by calculated values in our firmware, before the logic is started.

The ones in the HET file (and the one of the previous post too) were chosen so that the simulation tool doesn't have to go trough many steps before it gets interesting.

In the C firmware, these are replaced by values that are chosen to make for a good wave and good measurements.

 

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This example code is to be loaded to N2HET2 to monitor the PWM waveform generated
; by another source. The N2HET2 expects a range of valid duty cycles (specified in MAX_DUTY
; and MIN_DUTY) to be monitored. If the PWM waveform is out of range then the N2HET2 
; will generate an interrupt to the host CPU as well as set the specified pin high. 
; N2HET2 will reset the pin if the monitored duty cycles are within the range between 
; MIN_DUTY and MAX_DUTY.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; this is the pin number to monitor the PWM duty cycle
PULSE_MONITOR_PIN_NUM  .equ 4
; this is the maximum duty cycle of a valid range
MAX_DUTY               .equ 6
; this is the minimum duty cycle of a valid range
MIN_DUTY               .equ 3
; this is the number of rising edges on the PWM input to skip
SKIP_EDGES             .equ 3
; this is the pin number to assert/de-assert error
ERROR_PIN_NUM          .equ 0
; this is the unlock key for code execution
UNLOCK_KEY             .equ 0xA


; The data field of the MOV32 instruction contains an initial value (0x5) that is not
; equal to the key to unlock the NHET program. First the MOV32 instruction moves the
; initial value to a temporaray register T
L00   MOV32 { remote=DUMMY,type=IMTOREG,reg=T,data=0x5,hr_data=0};

; Compare the register T value with the key to unlock NHET. The key to unlock is
; 0xA. If the key is not matched then go back to L00. The CPU is supposed to write
; the proper key (0xA) to unlock the NHET
L01   ECMP { next=L00,hr_lr=LOW,cond_addr=L02,pin=0,reg=T,data=UNLOCK_KEY,hr_data=0};

; Use PCNT to measure the high phase of the PWM using type=RISE2FALL
; Note that the pin selected to measure pulse is pin=PULSE_MONITOR_PIN_NUM which
; is changeable by the host CPU.    
L02   PCNT { hr_lr=HIGH,type=RISE2FALL,pin=PULSE_MONITOR_PIN_NUM};


; Use ECNT to first detect the number of rising edges. The purpose here is to throw 
; away the measurements of the first few edges becasue the period/pulse measurement 
; from PCNT will not be accurate for the first edge.
L03   ECNT { cond_addr=L05,pin=PULSE_MONITOR_PIN_NUM,event=RISE,reg=R,data=0};

; Compare with SKIP_EDGES. Only when the number of edges is greater or equal to SKIP_EDGES  
; will we take the pulse measurement from PCNTs
L04   MCMP { next=L00,hr_lr=LOW,cond_addr=L05,pin=0,order=REG_GE_DATA,reg=R,data=SKIP_EDGES,hr_data=0};

; Reset the compare value to 0 so that when ECNT counter overflows it will not need 
; to skip edges again.
L05   MOV32 { remote=L04,type=IMTOREG&REM,reg=NONE,data=0x0,hr_data=0};

; Move the pulse captured by PCNT RISE2FALL to a temp register T
L06   MOV32 { remote=L02,type=REMTOREG,reg=T};

; Subtract the captured value in T by the expected period value stored in the 
; label REM_PERIOD. Note that the expected period stored in REM_PERIOD
; is actually the expected period plus one. For example, if the expected period is
; 10 then REM_PERIOD will store 11. The PCNT can measure the period with accuracy
; of +/-1 from the expected meaning it may measure within the bound of 9 to 11. 
; If we set expected value to 11 then it eases our comparision so that the measured
; value should never be greater than REM_PERIOD. The largest difference between
; the measured value against the expected value will be between 9 and 11. To
; tolerate this difference of 2 we first do the subtraction and then perform a right
; shift of the result by two bits.
L07   SUB { src1=REM,src2=T,dest=IMM,rdest=NONE,remote=REM_MAX_DUTY,smode=LSR,scount=2,data=0};

; If the captured pulse is larger than max duty then enable interrupt to the CPU
; and set the error pin
L08   BR { next=L09,cond_addr=L11,event=LT,irq=ON};

; Subtract the min duty cycle boundary from the current captured pulse width. If
; the the min duty cycle is greater than the current pulse then it means the
; current pulse is smaller than the min duty which is out of bound. 
L09   SUB { src1=REM,src2=T,dest=IMM,rdest=NONE,remote=REM_MIN_DUTY,smode=LSR,scount=2,data=0};

; Generate an interrupt and set the pin high if the current captured pulse width is 
; out of range. 
L10   BR { next=L13,cond_addr=L11,event=GT,irq=ON};

; Move 0 to register A. The register A will be used by the subsequent ECMP instruction
; to compare with. The purpose is for the ECMP to have a compare match all the time.
L11   MOV32 { remote=DUMMY,type=IMTOREG,reg=A,data=0};

; Depending on the result of the substraction. If the subtraction result indicates that
; PWM to be monitored exceeds the expected amount, we will assert the ERROR_PIN_NUM. 
L12   ECMP { next=L00,hr_lr=LOW,en_pin_action=ON,cond_addr=L00,pin=ERROR_PIN_NUM,action=SET,reg=A,data=0};

; If the subtraction result indicates that the PWM to be monitored does not exceed
; the epxected amount, we will clear the ERROR_PIN_NUM using the below ECMP instruction.
L13   MOV32 { remote=DUMMY,type=IMTOREG,reg=A,data=0};

; Clear the ERROR_PIN_NUM pin.
L14   ECMP { next=L00,hr_lr=LOW,en_pin_action=ON,cond_addr=L00,pin=ERROR_PIN_NUM,action=CLEAR,reg=A,data=0}; 

; The max duty is stored in the data field in the below dummy ECMP instruction. This 
; instruction is never executed.
REM_MAX_DUTY   ECMP { next=REM_MAX_DUTY,cond_addr=REM_MAX_DUTY,pin=0,reg=A,data=MAX_DUTY,hr_data=0};

; The min duty is stored in the data field in the below dummy ECMP instruction. This 
; instruction is never executed.
REM_MIN_DUTY   ECMP { next=REM_MIN_DUTY,cond_addr=REM_MIN_DUTY,pin=0,reg=A,data=MIN_DUTY,hr_data=0};

; This is a dummy instruction. It is more a HET IDE issue where in L00 if a 
; remote address is not given it is throwing an error even though that in 
; L00 there is no need to move data from/to the remote field.
DUMMY   BR { next=DUMMY,cond_addr=DUMMY,event=NOCOND,irq=OFF};

 

Line L08 (overrun) and L10 (underrun) are interesting because they will throw interrupts.

Line L12 and L14 are those that assert and reset the error pin, as appropriate.

 

Firmware

 

There are a few interesting blocks here. There's an initialisation function that sets the registers and also loads real data for the defines.

 

void configNHET2()
{
  /* Set N2HET2[NHET2_MONITOR_ERROR_PIN] to output */
  hetREG2->DIR = 1 << NHET2_MONITOR_ERROR_PIN;


  /* Change the LRPFC according to user input */
  hetREG2->PFR = (LRPFC << 8) ;


  /* Enable interrupt for instructions in N2HET2*/
  hetREG2->INTENAS = (1 << pHET_L08_1) | (1 << pHET_L10_1) ;

  /* Configure the PCNT RISE2FALL to select pin NHET2_PIN_MONITOR */
  hetRAM2->Instruction[pHET_L02_1].Program = (hetRAM2->Instruction[pHET_L02_1].Program & 0xFFFFFFE0) |
                                  (uint32)(NHET2_PIN_MONITOR);
  /* Configure the ECNT to select pin NHET2_PIN_MONITOR */

  hetRAM2->Instruction[pHET_L03_1].Control = (hetRAM2->Instruction[pHET_L03_1].Control & 0xFFFFE0FF) |
                                  (NHET2_PIN_MONITOR << 8);

  /* calculate the max/min high phase duty width in terms of # of total HR cycles
   * and then write to the pulse field in the N2HET program
   * The max/min pulse is added one to compensate for the fact that
   * the  pulse measurement can have a +/- 1 HR clock of tolerance.
   */
  hetRAM2->Instruction[pHET_REM_MAX_DUTY_1].Data = (uint32)(NHET2_MAX_PULSE_MONITOR + 1);
  hetRAM2->Instruction[pHET_REM_MIN_DUTY_1].Data = (uint32)(NHET2_MIN_PULSE_MONITOR + 1);

  /* Configure the N2HET2 pin to output the error */
  hetRAM2->Instruction[pHET_L12_1].Control = (hetRAM2->Instruction[pHET_L12_0].Control & 0xFFFFE0FF) |
                                    (NHET2_MONITOR_ERROR_PIN << 8);
  hetRAM2->Instruction[pHET_L14_1].Control = (hetRAM2->Instruction[pHET_L14_0].Control & 0xFFFFE0FF) |
                                    (NHET2_MONITOR_ERROR_PIN << 8);

  /* Unlock the N2HET program. Initially after reset the N2HET program is locked */
  hetRAM2->Instruction[pHET_L00_1].Data = UNLOCK_KEY << 7;
}

 

Line 12 is a nice example that shows how you can enable that interrupts thrown by HET are handled by the ARM cortex core.

It's also possible o do that in HALCoGen, but there you have to use the HET assembler line number.

The method used here is better in my opinion, because it uses the labels of the code lines. That's more resilient to later changes.

 

The interrupt handler is called and gets info of what of those HET lines threw it.

 

#include "NHET2_PWM_Range_Monitor.h"
uint32 greater_than_max_duty = 0;
uint32 less_than_min_duty = 0;

#pragma WEAK(hetNotification)
void hetNotification(hetBASE_t *het, uint32 offset) {
  if (offset == (pHET_L08_1+1) ) {
    greater_than_max_duty++;
  } else if (offset == (pHET_L10_1+1) ) {
    less_than_min_duty++;   
  } else {  /* Should never come here */
    while (1);
  }
}

 

If you pause the program during a debug session, you can check how many duty cycles were outside the preset range. In our case, that will be 20% of all pulses lower and 20% of all pulses higher.

While you're holding the main arm core, the HET blocks will keep on spinning. They are really two autonomous controllers.The interrupts thrown during that time are ignored by the halted ARM core.

 

One of the indicators that the HETs are autonomous is the main loop of this project:

 

  while(1) {    
  }

 

Core doesn't do anything. It'll get sidetracked at each interrupt, then happily spin further just burning energy.

 

What Did this Example Try to Convey?

 

  • That the HET modules can, all by themselves, perform fairly complex exercises, in a deterministic and constant pace.
  • They can be use as timers, can drive and read hardware pins.
  • Can communicate with the ARM core and vice versa. By exchanging data, via interrupts and (not covered in this example) by the HTU module (a DMA-like engine that works without the standard DMA module to avoid contention with any other block in the package.
  • That I still have a lot of learning to do. This example helped t grow knowledge but also made me realise I'm still at the start of the curve.

 

I've attached a fresh version of the project to the first blog. In that version I've refined the adaption to the TMS570LC43 LaunchPad and corrected some bugs.

 

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