Relaxation oscillator used in the hysteresis lab. The “variable capacitor” in this schematic is a person’s finger and a touch plate made from aluminum foil and packing tape.
I spent today writing code for the KL25Z board to act as a period or frequency detector for the hysteresis lab, where they build a relaxation oscillator using a 74HC14N Schmitt trigger inverter and use it to make a capacitance touch sensor (pictures of last year’s setup in Weekend work). I had written code for the Arduino boards last year, and I started by trying to do the same thing on the KL25Z, using the MBED online development system. The Arduino code used “PulseIn()” to measure pulse duration, and the MBED system does not have an equivalent function. I could have implemented PulseIn() with a couple of busy waits and a microsecond-resolution timer, but I decided to try using “InterruptIn” to get interrupts on each rising edge instead.
The basic idea of last year’s code (and the first couple versions I wrote today) was to determine the pulse duration or period when the board is reset, finding the maximum over a few hundred cycles, and using that as a set point to create two thresholds for switching an LED on or off. I got the code working, but I was not happy with it as a tool for the students to use.
The biggest problem is that the touch plate couples in 60Hz noise from the user’s finger, so the oscillator output signal is frequency modulated. This frequency modulation can be large compared with the change in frequency from touching or not touching the plate (depending on how big C1 is), so setting the resistor and capacitor values for the oscillator got rather tricky, and the results were unreliable.
I then changed from reading instantaneous period to measuring frequency by counting edges in a 1/60th-second window. That way the 60Hz frequency modulation of the oscillator gets averaged out, and we can get a fairly stable frequency reading. The elimination of the 60Hz noise allows me to use less hysteresis in the on/off decision for the LED, making the touch sensor more sensitive without getting flicker on transitions. The code worked fairly well, but I was not happy with the maximum frequency that it could handle—the touch sensor gets more sensitive if C1 is small, which tends to result in high frequency oscillations. The problem with the code was that MBED’s InterruptIn implementation seems to have a lot of overhead, and the code missed the edge interrupts if they came more often than about every 12µsec. Because I was interrupting on both rising and falling edges, the effective maximum frequency was about 40kHz, which was much lower than I wanted.
To fix the frequency limitation, I replaced MBED’s InterruptIn with my own interrupt service routine for PortD (I was using pin PTD4 as the interrupt input). With this change, I could go to about 800kHz (1.6e6 interrupts per second), which is plenty for this lab. If I wanted to go to higher frequencies, I’d look at only rising edges, rather than rising+falling edges, to get another factor of two at the high end. I didn’t make that change, because doing so would reduce the resolution of the frequency measurement at the low end, and I didn’t think that the tradeoff was worth it here.
The code is now robust to fairly large variations in the oscillator design. It needs a 20% drop in frequency to turn on the green LED, but the initial frequency can be anywhere in the range 400Hz–800kHz.
To make it easier for students to debug their circuits, I took advantage of having an RGB LED on the board to indicate the state of the program: on reset, the LED is yellow, turning blue once a proper oscillator input has been detected, or red if the oscillator frequency is not in range. When the frequency drops sufficiently, the LED turns from blue to green, turning back to blue when the frequency goes up again.
For even more debugging help, I output the frequency that the board sees through the USB serial connection every 1/60th second, so that a program like the Arduino serial monitor can be used to see how much the frequency is changing. I took advantage of that feature to make a plot of the frequency as the touch sensor was touched.
Plot of frequency of hysteresis oscillator, as the touch pad is touched three times. Note that the thresholds are very conservatively set relative to the noise, but that the sensitivity is still much higher than needed to detect the finger touches.
Overall, I think that the code for the KL25Z is better than what I wrote last year for the Arduino—now I have to rewrite the lab handout to match! I actually need to update two lab handouts this weekend, since week 3 will have both the hysteresis lab and the sampling and aliasing lab. Unfortunately, the features needed for those labs (trigger on rising and falling edges and downsampling) are not working in PteroDAQ yet.
Here is the code that I wrote for the frequency detector:
// freq_detector_own_isr
// Kevin Karplus
// 2014 Apr 5
// This program is intended to be used as a "capacitive touch sensor"
// with an external relaxation oscillator whose frequency
// varies with the capacitance of a touch.
// The program expects a periodic square wave on pin PTD4 with a frequency between
// about 400Hz and 800kHz. (LOW_FREQ_LIMIT and HIGH_FREQ_LIMIT).
// On reset, it displays a yellow light, then measures the frequency to store as the "off" frequency.
//
// If the frequency is out of range (say for a disconnected input), then the light is set to red,
// and the off frequency checked again.
// Otherwise the LED is turned blue.
//
// After initialization, if the program detects a frequency 20% less than the initial freq,
// it turns the light green,
// turning it blue again when the the frequency increases to 90% of the original frequency.
//
// No floating-point is used, just integer arithmetic.
//
// Frequency measurements are made by counting the number of rising and falling edges
// in one cycle of the mains frequency (1/60 sec), giving somewhat poor resolution at lower
// frequencies.
// The counting time is chosen to that frequency modulation by the mains voltages is averaged out.
//
// This version of the code uses my own setup for the interrupt service routine, because InterruptIn has
// too much overhead. I can go to over 800kHz (1.6e6 interrupts/second) with this setup,
// but only about 40kHz (80e3) interrupts/sec with mbed's InterruptIn.
#include "mbed.h"
#define PCR_PORT_TO_USE (PORTD->PCR[4]) // pin PTD3 is the pin to use
#define MAINS_FREQ (60) // frequency of electrical mains in Hz
#define COUNTING_TIME (1000000/MAINS_FREQ) // duration in usec of one period of electrical mains
// off_frequency must be between LOW_FREQ_LIMIT and HIGH_FREQ_LIMIT for program to accept it
#define LOW_FREQ_LIMIT (400)
#define HIGH_FREQ_LIMIT (800000)
// on-board RGB LED
PwmOut rled(LED_RED);
PwmOut gled(LED_GREEN);
PwmOut bled(LED_BLUE);
#define PWM_PERIOD (255) // for the on-board LEDs in microseconds
// Set the RGB led color to R,G,B with 0 being off and PWM_PERIOD being full-on
void set_RGB_color(uint8_t R, uint8_t G, uint8_t B)
{
rled.pulsewidth_us(PWM_PERIOD-R);
gled.pulsewidth_us(PWM_PERIOD-G);
bled.pulsewidth_us(PWM_PERIOD-B);
}
// InterruptIn square_in(PTD4);
volatile uint32_t edges_counted;
uint32_t low_freq_threshold, high_freq_threshold; // thresholds for detecting frequency changes
extern "C"{
// interrupt routine that counts edges into edges_counted
void PORTD_IRQHandler(void)
{
edges_counted++;
PCR_PORT_TO_USE |= PORT_PCR_ISF_MASK;
}
}
// return the frequency for the square_in input in Hz
uint32_t frequency(void)
{
PCR_PORT_TO_USE &= ~PORT_PCR_IRQC_MASK; // disable interrupts on pin PTD3
edges_counted=0;
PCR_PORT_TO_USE |= PORT_PCR_ISF_MASK | PORT_PCR_IRQC(11); // clear interrupt for PTD3, and enable interrupt on either edge
wait_us(COUNTING_TIME);
PCR_PORT_TO_USE &= ~PORT_PCR_IRQC_MASK; // disable interrupts on pin PTD3
uint32_t freq=edges_counted*MAINS_FREQ/2;
return freq;
}
int main()
{
rled.period_us(PWM_PERIOD);
gled.period_us(PWM_PERIOD);
bled.period_us(PWM_PERIOD);
set_RGB_color(255,255,0); // set light to yellow
SIM->SCGC5 |= SIM_SCGC5_PORTD_MASK; // make sure port D has clocks on
PCR_PORT_TO_USE &= ~PORT_PCR_MUX_MASK; // clearing the MUX field
PCR_PORT_TO_USE |= PORT_PCR_MUX(1); // Setting pin as GPIO
FPTD->PDDR &= ~ (1<<4); // make sure pin is input pin
NVIC_EnableIRQ(PORTD_IRQn); // enable interrupts for port D
__enable_irq();
uint32_t off_frequency= frequency();
while ( off_frequency<low_freq_limit ||="" off_frequency="">HIGH_FREQ_LIMIT)
{ // timed out. set color to red and keep trying
set_RGB_color(255,0,0);
printf("FREQ out of range: %luHz\n", off_frequency);
off_frequency= frequency();
}
uint32_t low_freq= 8*off_frequency/10; // 80% of off_frequency
uint32_t high_freq= 9*off_frequency/10; // 90% of off_frequency
printf("off= %luHz lo_thresh=%luHz hi_thresh=%luHz\n",off_frequency, low_freq, high_freq);
while(1)
{ uint32_t freq=frequency();
printf("%lu Hz\n",freq);
if (freq < low_freq)
{ // low_fequency found, turn LED green
set_RGB_color(0,255,0);
}
else if (freq >= high_freq)
{ // high frequency found, turn LED blue again
set_RGB_color(0,0,255);
}
}
}