Last active
March 3, 2026 08:12
-
-
Save jh4xsy/f963f9b21945f0f1727db75cc52956e5 to your computer and use it in GitHub Desktop.
OZ1JHM CW decoder for Raspberry Pi Pico
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
| /////////////////////////////////////////////////////////////////////// | |
| // 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