Last active
March 11, 2024 14:01
-
-
Save RickKimball/7b74a61268ca7cc23045660223006662 to your computer and use it in GitHub Desktop.
NCO c++ template
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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