Skip to content

Instantly share code, notes, and snippets.

@RickKimball
Last active March 11, 2024 14:01
Show Gist options
  • Save RickKimball/7b74a61268ca7cc23045660223006662 to your computer and use it in GitHub Desktop.
Save RickKimball/7b74a61268ca7cc23045660223006662 to your computer and use it in GitHub Desktop.
NCO c++ template
// vim: set ts=2 sw=2 expandtab list
/*
* File: nco.cpp
* Description: NCO c++ template and testdriver
*
* Author: rick kimball
*
* g++ -DDEBUG -Wall -Os -g nco.cpp -o nco
*/
#include <stdio.h>
//const long clk_in_freq = 16000000; // a 16MHz clock
const long clk_in_freq = 1000; // a 1k clock
/*
* default_signal_handler - minimum signal_out functions for NCO
*
* void toggle() - called each time an NCO edge is reached
* occurs at 2x the NCO desired frequency out
*/
typedef struct {
void toggle(void) {} /* called by NCO each edge of our oscillator */
} default_signal_handler;
/*
* NCO - numerical controlled oscillator
* freq - desired output frequency
* clk_in_freq - frequency of the driving clock
*/
template<const long freq, const long clk_in_freq, typename signal_out>
struct NCO {
long phase_increment;
long phase_accumulator;
signal_out clk_out;
#ifdef DEBUG
long counter; // optional for verifying frequency
#endif
NCO() {
phase_increment = freq - clk_in_freq;
phase_accumulator = 0;
#ifdef DEBUG
counter = 0;
#endif
}
void rising_edge(void) {
phase_accumulator = phase_accumulator + phase_increment;
if( phase_accumulator < 0 ) {
phase_accumulator = phase_accumulator + clk_in_freq;
}
else {
clk_out.toggle();
#ifdef DEBUG
counter++;
#endif
}
}
};
/*
* spew something on signal toggle
*/
template<const int sym_rising, const int sym_falling>
struct signal_print_ {
unsigned x;
signal_print_() : x(0) {}
void toggle(void) {
x = x ^ 1;
putchar(((x) ? sym_rising : sym_falling ));
}
};
/*
* clock in tick /
* clk_80_Hz + -
* clk_160Hz 1 0
*/
int main(void)
{
NCO<80, clk_in_freq, signal_print_<'+','-'> > clk_freq1;
NCO<160, clk_in_freq, signal_print_<'1','0'> > clk_freq2;
for(long x=0; x < (clk_in_freq);++x) {
#ifdef DEBUG
putchar((x&1)?'\\':'/');
#endif
clk_freq1.rising_edge();
clk_freq2.rising_edge();
}
#ifdef DEBUG
putchar('\n');
printf("clk_freq1.counter=%ld\n", clk_freq1.counter);
printf("clk_freq2.counter=%ld\n", clk_freq2.counter);
#endif
return 0;
}
#if EXPECTED_OUTPUT
$ ./nco
/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0/\/\/\/1\/\/\/+0\/\/\/1\/\/\/-0\/\/\/\1/\/\/\+0/\/\/\1/\/\/\-0
clk_freq1.counter=80
clk_freq2.counter=160
#endif
/* vim: set ts=2 sw=2 expandtab */
/*
* File: nco.h - numerical control with arbritrary tick handler
* Description: create an arbitrary frequency from a higher clock signal
* Author: rick kimball
* Date: 10/18/2017
*/
#ifndef NCO_H
#define NCO_H
/*
* NCO - numerical controlled oscillator
* freq - desired output frequency
* clk_in_freq - frequency of the driving clock
* signal_out - expects a structure or class with a void tick()
* function that is called once for each clock tick
* of freq.
*/
template<const long freq, const long clk_in_freq, typename signal_out>
struct NCO {
long phase_increment;
long phase_accumulator;
signal_out clk_out;
/*
* NCO ctor
*/
NCO():
phase_increment(freq - clk_in_freq)
,phase_accumulator(0) {
}
/*
* rising_edge - compute new accumulator value and conditional trigger tick
*/
void rising_edge(void) {
phase_accumulator = phase_accumulator + phase_increment;
if ( phase_accumulator < 0 ) {
phase_accumulator = phase_accumulator + clk_in_freq;
}
else {
clk_out.tick();
}
}
};
/*
* default_signal_handler - minimum signal_out functions for NCO
*
* void tick() - called each time frequency out clock tick is reached
*/
typedef struct {
void tick(void) { } /* called by NCO each tick of our oscillator */
} default_signal_handler;
#endif
/*
* arduino uno NCO sample sketch
*
* toggle 2 pins at different rates 10Hz and 3Hz
*/
#include "nco.h"
/*
* our tick handler that toggles a pin
* implemented as a c++ template to make it
* easy to create custom
*/
template<const unsigned pin_mask>
struct signal_toggleb {
void tick(void) {
PORTB ^= pin_mask;
}
};
typedef signal_toggleb<(1<<4)> pin12_handler; // convenience typedef
// two clocks at different frequencies
// multiple by 2 to allow us to toggle pin for a high period and a low period
NCO < 60*2, 1000, pin12_handler > clk_freq1; // toggle PB4 60Hz using a 1000Hz
NCO < 10*2, 1000, signal_toggleb<(1<<5)> > clk_freq2; // toggle PB5 10Hz aka D13 LED
void setup() {
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
}
void loop() {
static unsigned long prev;
unsigned long curr = millis();
/* use the millis() tick as our 1000Hz clock input signal */
if ( prev != curr) {
// catch up for any lost ticks to prevent frequency drift
do {
// send a pulse in to each NCO
clk_freq1.rising_edge();
clk_freq2.rising_edge();
prev++;
} while ( prev != curr);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment