Skip to content

Instantly share code, notes, and snippets.

@jh4xsy
Last active March 3, 2026 08:12
Show Gist options
  • Select an option

  • Save jh4xsy/f963f9b21945f0f1727db75cc52956e5 to your computer and use it in GitHub Desktop.

Select an option

Save jh4xsy/f963f9b21945f0f1727db75cc52956e5 to your computer and use it in GitHub Desktop.
OZ1JHM CW decoder for Raspberry Pi Pico
///////////////////////////////////////////////////////////////////////
// CW Decoder made by Hjalmar Skovholm Hansen OZ1JHM VER 1.01 //
// Feel free to change, copy or what ever you like but respect //
// that license is http://www.gnu.org/copyleft/gpl.html //
// Discuss and give great ideas on //
// https://groups.yahoo.com/neo/groups/oz1jhm/conversations/messages //
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// Read more here http://en.wikipedia.org/wiki/Goertzel_algorithm //
// if you want to know about FFT the http://www.dspguide.com/pdfbook.htm //
///////////////////////////////////////////////////////////////////////////
// ported to Raspberry Pi Pico by JH4XSY/1
#include <Arduino.h>
#include "hardware/timer.h"
// Picoピン設定
const int audioInPin = 26; // ADC0
const int audioOutPin = 15; // GP15
const int ledPin = LED_BUILTIN; // Pico内蔵LEDを光らせる★
// Goertzel 演算用変数
float magnitude;
int magnitudelimit = 60;
const int magnitudelimit_low = 60; // ノイズフロアの測定から仮置き
int realstate = LOW;
int realstatebefore = LOW;
int filteredstate = LOW;
int filteredstatebefore = LOW;
///////////////////////////////////////////////////////////
// The sampling frq will be 8928 on a 16 mhz //
// without any prescaler etc //
// because we need the tone in the center of the bins //
// you can set the tone to 496, 558, 744 or 992 //
// then n the number of samples which give the bandwidth //
// can be (8928 / tone) * 1 or 2 or 3 or 4 etc //
// init is 8928/558 = 16 *4 = 64 samples //
// try to take n = 96 or 128 ;o) //
// 48 will give you a bandwidth around 186 hz //
// 64 will give you a bandwidth around 140 hz //
// 96 will give you a bandwidth around 94 hz //
// 128 will give you a bandwidth around 70 hz //
// BUT remember that high n take a lot of time //
// so you have to find the compromice - i use 48 //
///////////////////////////////////////////////////////////
float coeff;
float Q1 = 0;
float Q2 = 0;
float cosine;
float sampling_freq = 8928.0;
float target_freq = 744.0; // 744Hzに変更★
const float n = 96.0; // 96サンプルに変更★
int testData[96];
//////////////////////////////
// Noise Blanker time which //
// shall be computed so //
// this is initial //
//////////////////////////////
int nbtime = 12; /// ms noise blanker 12msに変更★
long starttimehigh;
long highduration;
long lasthighduration;
long hightimesavg = 0;
long lowtimesavg;
long starttimelow;
long lowduration;
long laststarttime = 0;
char code[20];
int stop = LOW;
int wpm = 20;
void docode();
void printchar(int asciinumber);
// タイマー割り込み用変数★
volatile int sampleIndex = 0;
volatile bool bufferReady = false;
// タイマー割り込みコールバック(112µsごと)★
bool sample_callback(struct repeating_timer *t) {
if (sampleIndex < n) {
testData[sampleIndex] = analogRead(audioInPin);
sampleIndex++;
} else {
bufferReady = true;
sampleIndex = 0;
}
return true;
}
////////////////
// init setup //
////////////////
void setup() {
// PicoのADC解像度を10bit(0-1023)に設定して元のコードと互換性を保つ
analogReadResolution(10);
////////////////////////////////////
// The basic goertzel calculation //
////////////////////////////////////
int k = (int) (0.5 + ((n * target_freq) / sampling_freq));
float omega = (2.0 * PI * k) / n;
float cosine = cos(omega);
coeff = 2.0 * cosine;
Serial.begin(115200);
delay(1000);
Serial.println("--- CW Decoder(OZ1JHM VER 1.01+) Started ---");
pinMode(ledPin, OUTPUT);
// タイマー割り込み開始(112µs周期)★
static struct repeating_timer timer;
add_repeating_timer_us(-112, sample_callback, NULL, &timer);
}
///////////////
// main loop //
///////////////
void loop() {
/////////////////////////////////////
// The basic where we get the tone //
/////////////////////////////////////
// サンプルが揃うまで待つ★
if (!bufferReady) return;
// 信号強度計算 (Goertzel)
for (char index = 0; index < n; index++){
float Q0 = coeff * Q1 - Q2 + (float) testData[index];
Q2 = Q1;
Q1 = Q0;
}
float magnitudeSquared = (Q1*Q1)+(Q2*Q2)-Q1*Q2*coeff; // we do only need the real part //
magnitude = sqrt(magnitudeSquared);
Q2 = 0;
Q1 = 0;
// 次のサンプリングを許可★
bufferReady = false;
///////////////////////////////////////////////////////////
// here we will try to set the magnitude limit automatic //
///////////////////////////////////////////////////////////
if (magnitude > magnitudelimit_low) {
magnitudelimit = (magnitudelimit +((magnitude - magnitudelimit)/6)); /// moving average filter
}
if (magnitudelimit < magnitudelimit_low)
magnitudelimit = magnitudelimit_low;
////////////////////////////////////
// now we check for the magnitude //
////////////////////////////////////
realstate = (magnitude > magnitudelimit * 0.6) ? HIGH : LOW;
/////////////////////////////////////////////////////
// here we clean up the state with a noise blanker //
/////////////////////////////////////////////////////
if (realstate != realstatebefore) {
laststarttime = millis();
}
if ((millis() - laststarttime) > nbtime) {
if (realstate != filteredstate) {
filteredstate = realstate;
}
}
////////////////////////////////////////////////////////////
// Then we do want to have some durations on high and low //
////////////////////////////////////////////////////////////
if (filteredstate != filteredstatebefore) {
if (filteredstate == HIGH) {
starttimehigh = millis();
lowduration = (millis() - starttimelow);
} else {
starttimelow = millis();
highduration = (millis() - starttimehigh);
if (highduration < (2 * hightimesavg) || hightimesavg == 0) {
hightimesavg = (highduration + hightimesavg * 2) / 3; // now we know avg dit time ( rolling 3 avg)
}
if (highduration > (5 * hightimesavg)) {
hightimesavg = highduration + hightimesavg; // if speed decrease fast ..
}
}
}
///////////////////////////////////////////////////////////////
// now we will check which kind of baud we have - dit or dah //
// and what kind of pause we do have 1 - 3 or 7 pause //
// we think that hightimeavg = 1 bit //
///////////////////////////////////////////////////////////////
if (filteredstate != filteredstatebefore) {
stop = LOW;
if (filteredstate == LOW) { //// we did end a HIGH
if (highduration < (hightimesavg * 2) && highduration > (hightimesavg * 0.6)) { /// 0.6 filter out false dits
strcat(code, ".");
} else if (highduration > (hightimesavg * 2) && highduration < (hightimesavg * 6)) {
strcat(code, "-");
wpm = (wpm + (1200 / (highduration / 3))) / 2; //// the most precise we can do ;o)
}
}
if (filteredstate == HIGH) { //// we did end a LOW
float lacktime = 1.0;
if (wpm > 25) lacktime = 1.0; /// when high speeds we have to have a little more pause before new letter or new word
else if (wpm > 30) lacktime = 1.2;
else if (wpm > 35) lacktime = 1.5;
if (lowduration > (hightimesavg * 2 * lacktime) && lowduration < (hightimesavg * 5 * lacktime)) {
docode(); // 文字確定
code[0] = '\0';
} else if (lowduration >= (hightimesavg * 5 * lacktime)) {
docode(); // 文字確定
code[0] = '\0';
printchar(32); // 単語間のスペース出力
}
}
}
//////////////////////////////
// write if no more letters //
//////////////////////////////
if ((millis() - starttimelow) > (highduration * 6) && stop == LOW) {
docode();
code[0] = '\0';
stop = HIGH;
}
/////////////////////////////////////
// we will turn on and off the LED //
// and the speaker //
/////////////////////////////////////
if(filteredstate == HIGH){
digitalWrite(ledPin, HIGH);
tone(audioOutPin, target_freq);
} else {
digitalWrite(ledPin, LOW);
noTone(audioOutPin);
}
//////////////////////////////////
// the end of main loop clean up//
/////////////////////////////////
realstatebefore = realstate;
lasthighduration = highduration;
filteredstatebefore = filteredstate;
}
////////////////////////////////
// translate cw code to ascii //
////////////////////////////////
void docode() {
if (code[0] == '\0') return; // codeが空っぽなら、何もしない!★
// (A-Z, 0-9 などのデコードテーブルはオリジナルと同じ)
if (strcmp(code,".-") == 0) printchar(65);
else if (strcmp(code,"-...") == 0) printchar(66);
else if (strcmp(code,"-.-.") == 0) printchar(67);
else if (strcmp(code,"-..") == 0) printchar(68);
else if (strcmp(code,".") == 0) printchar(69);
else if (strcmp(code,"..-.") == 0) printchar(70);
else if (strcmp(code,"--.") == 0) printchar(71);
else if (strcmp(code,"....") == 0) printchar(72);
else if (strcmp(code,"..") == 0) printchar(73);
else if (strcmp(code,".---") == 0) printchar(74);
else if (strcmp(code,"-.-") == 0) printchar(75);
else if (strcmp(code,".-..") == 0) printchar(76);
else if (strcmp(code,"--") == 0) printchar(77);
else if (strcmp(code,"-.") == 0) printchar(78);
else if (strcmp(code,"---") == 0) printchar(79);
else if (strcmp(code,".--.") == 0) printchar(80);
else if (strcmp(code,"--.-") == 0) printchar(81);
else if (strcmp(code,".-.") == 0) printchar(82);
else if (strcmp(code,"...") == 0) printchar(83);
else if (strcmp(code,"-") == 0) printchar(84);
else if (strcmp(code,"..-") == 0) printchar(85);
else if (strcmp(code,"...-") == 0) printchar(86);
else if (strcmp(code,".--") == 0) printchar(87);
else if (strcmp(code,"-..-") == 0) printchar(88);
else if (strcmp(code,"-.--") == 0) printchar(89);
else if (strcmp(code,"--..") == 0) printchar(90);
else if (strcmp(code,".----") == 0) printchar(49);
else if (strcmp(code,"..---") == 0) printchar(50);
else if (strcmp(code,"...--") == 0) printchar(51);
else if (strcmp(code,"....-") == 0) printchar(52);
else if (strcmp(code,".....") == 0) printchar(53);
else if (strcmp(code,"-....") == 0) printchar(54);
else if (strcmp(code,"--...") == 0) printchar(55);
else if (strcmp(code,"---..") == 0) printchar(56);
else if (strcmp(code,"----.") == 0) printchar(57);
else if (strcmp(code,"-----") == 0) printchar(48);
else if (strcmp(code,"..--..") == 0) printchar(63); // ?
else if (strcmp(code,".-.-.-") == 0) printchar(46); // .
else if (strcmp(code,"--..--") == 0) printchar(44); // ,
else if (strcmp(code,"-.-.--") == 0) printchar(33); // !
else if (strcmp(code,".--.-.") == 0) printchar(64); // @
else if (strcmp(code,"---...") == 0) printchar(58); // :
else if (strcmp(code,"-....-") == 0) printchar(45); // -
else if (strcmp(code,"-..-.") == 0) printchar(47); // /
else if (strcmp(code,"-.--.") == 0) printchar(40); // ( <KN>
else if (strcmp(code,"-.--.-") == 0) printchar(41); // ) <KK>
else if (strcmp(code,".-...") == 0) printchar(45); // - <AS>
else if (strcmp(code,"...-..-") == 0) printchar(36); // $
else if (strcmp(code,"...-.-") == 0) printchar(62); // > <SK>
else if (strcmp(code,".-.-.") == 0) printchar(43); // + <AR>★
else if (strcmp(code,"-...-") == 0) printchar(61); // = <BT>★
else if (strcmp(code,"...-.") == 0) printchar(126); // ~ <SN>
else {
printchar(95); // どの符号にも当てはまらない場合 '_' を表示★
}
}
// シリアルポートに文字出力★
void printchar(int asciinumber) {
Serial.write((char)asciinumber);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment