/////////////////////////////////////////////////////////////////////////// // 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 // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Changes made by DL6NBS / Bernhard Hochstätter in april 2020: // // Adaptation for OLED-Display-SH1106 1,3" I2C 128 x 64 Pixel // // Now 125 small characters on 8 lines or 61 large characters on 4 lines // // taken out: serial-output, special characters // // taken in: OLED, overwrite if small or scroll mode if large characters,// // button for font-select large/small characters and soft restart. // // Works reliably up to 35wpm (small font). Tested on UNO and MICRO-pro. // // Be sure to use fast and memory-friendly library // // More at: https://github.com/olikraus/u8g2/wiki/fntlist8x8 // /////////////////////////////////////////////////////////////////////////// /* Display pins Arduino Uno or Nano 5V -> VCC GND -> GND A4 -> SDA / SCK A5 -> SCL Display pins Arduino Micro-pro VCC -> VCC (3,3V) GND -> GND D2 -> SDA / SCK D3 -> SCL */ #include #include #include U8X8_SH1106_128X64_NONAME_HW_I2C oled(/* reset=*/ U8X8_PIN_NONE); //////////////////////////////// // OLED initial values // //////////////////////////////// int mode; // mode small or large or characters int lcdindex = 3; // 0 to 2 reserved for WPM display int row = 0; // line-counter small characters const int colums = 16; // OLED = 16 small characters const int rows = 7; // OLED = 7 small characters int x; // x-cursor pointer large characters int y; // y-cursor pointer large characters const int xmax = 15; // x-limit large characters const int ymax = 6; // y-limit large characters char line1[17]; // 16 large character Array 1 char line2[17]; // 16 large character Array 2 char line3[17]; // 16 large character Array 3 char line4[17]; // 16 large character Array 4 //////////////////////////////// // Define 8 specials letters // //////////////////////////////// byte U_umlaut[8] = {B01010, B00000, B10001, B10001, B10001, B10001, B01110, B00000}; // 'Ü' byte O_umlaut[8] = {B01010, B00000, B01110, B10001, B10001, B10001, B01110, B00000}; // 'Ö' byte A_umlaut[8] = {B01010, B00000, B01110, B10001, B11111, B10001, B10001, B00000}; // 'Ä' byte AE_capital[8] = {B01111, B10100, B10100, B11110, B10100, B10100, B10111, B00000}; // 'Æ' byte OE_capital[8] = {B00001, B01110, B10011, B10101, B11001, B01110, B10000, B00000}; // 'Ø' byte fullblock[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111}; byte AA_capital[8] = {B00100, B00000, B01110, B10001, B11111, B10001, B10001, B00000}; // 'Å' byte emtyblock[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000}; int audioInPin = A0; // CW-input audio // int audioOutPin = 10; // SW-Beeper out (optional) // int beepPin = 12; // HW-Beeper out (optional) // int ledPin = 13; // CW-Signal(optional) int RXLED = 17; // The RX LED has a defined Arduino pin int TXLED = 30; // The TX LED has a defined Arduino pin byte selPin = 8; // Selector and reset button float magnitude ; int magnitudelimit = 100; int magnitudelimit_low = 100; 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 sine; float cosine; float sampling_freq = 8928.0; float target_freq = 558.0; /// adjust for your needs see above float n = 48.0; //// if you change her please change next line also int testData[48]; ////////////////////////////// // Noise Blanker time which // // shall be computed so // // this is initial // ////////////////////////////// int nbtime = 6; /// ms noise blanker long starttimehigh; long highduration; long lasthighduration; long hightimesavg; long lowtimesavg; long startttimelow; long lowduration; long laststarttime = 0; char code[20]; int stop = LOW; int wpm; //////////////// // init setup // //////////////// void setup() { //////////////////////////////////// // The basic goertzel calculation // //////////////////////////////////// int k; float omega; k = (int) (0.5 + ((n * target_freq) / sampling_freq)); omega = (2.0 * PI * k) / n; sine = sin(omega); cosine = cos(omega); coeff = 2.0 * cosine; /////////////////////////////// // define special characters // // DL6NBS: non-essential // /////////////////////////////// /* lcd.createChar(0, U_umlaut); // German lcd.createChar(1, O_umlaut); // German, Swedish lcd.createChar(2, A_umlaut); // German, Swedish lcd.createChar(3, AE_capital); // Danish, Norwegian lcd.createChar(4, OE_capital); // Danish, Norwegian lcd.createChar(5, fullblock); lcd.createChar(6, AA_capital); // Danish, Norwegian, Swedish lcd.createChar(7, emtyblock); */ // Serial.begin(115200); // DL6NBS: non-essential // pinMode(ledPin, OUTPUT); // external LED if needed pinMode(selPin, INPUT); // Font selector digitalWrite(selPin, HIGH); // turn on pullup resistors pinMode(RXLED, OUTPUT); // Set RX LED as an output (Arduino Micro) pinMode(TXLED, OUTPUT); // Set TX LED as an output (Arduino Micro) digitalWrite(RXLED, HIGH); // Set RX LED off (Arduino Micro) digitalWrite(TXLED, HIGH); // Set TX LED off (Arduino Micro) ////////////////////////////////////////////////////////// // Select the display-font with the Selector-button. // // Small font = performace up to 35 wpm or // // large font = performace up to 27 wpm // // The selected font ist memorized in the EEPROM. // // To change the font, push the selector button while // // powering on the decoder. Pushing the button while // // the decoder is running, causes a decoder restart // // with initial values, vy useful for signal changes. // // More font-information at: // // https://github.com/olikraus/u8g2/wiki/fntlist8x8 // ////////////////////////////////////////////////////////// if (digitalRead(selPin) == LOW) { // check if font-selector is pushed if (EEPROM.read(0) >= 1) { EEPROM.write(0, 0); // save large font as new standard } else { EEPROM.write(0, 1); // save small font as new standard } } if (EEPROM.read(0) == 0) { // read font-selector standard oled.setFont(u8x8_font_7x14_1x2_r); // set large font mode = 2; // set mode for large font } else { oled.setFont(u8x8_font_chroma48medium8_r); // set small font mode = 1; // set mode for small font } oled.begin(); oled.clear(); oled.print(" CW-Dekoder"); oled.setCursor(0, 2); oled.print(" OZ1JHM"); oled.setCursor(0, 4); oled.print(" OLED-Display"); oled.setCursor(0, 6); oled.print(" DL6NBS"); delay(2500); oled.clear(); } // end setup /////////////// // main loop // /////////////// void loop() { ///////////////////////////////////// // The basic where we get the tone // ///////////////////////////////////// for (char index = 0; index < n; index++) { testData[index] = analogRead(audioInPin); } for (char index = 0; index < n; index++) { float Q0; 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; //Serial.print(magnitude); Serial.println(); //// here you can measure magnitude for setup.. /////////////////////////////////////////////////////////// // 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 // //////////////////////////////////// if (magnitude > magnitudelimit * 0.6) // just to have some space up realstate = HIGH; else realstate = 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() - startttimelow); } if (filteredstate == LOW) { startttimelow = millis(); highduration = (millis() - starttimehigh); if (highduration < (2 * hightimesavg) || hightimesavg == 0) { hightimesavg = (highduration + hightimesavg + hightimesavg) / 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, "."); // Serial.print("."); // DL6NBS: non-essential } if (highduration > (hightimesavg * 2) && highduration < (hightimesavg * 6)) { strcat(code, "-"); // Serial.print("-"); // DL6NBS: non-essential wpm = (wpm + (1200 / ((highduration) / 3))) / 2; //// the most precise we can do ;o) } } if (filteredstate == HIGH) { //// we did end a LOW float lacktime = 1; if (wpm > 25)lacktime = 1.0; /// when high speeds we have to have a little more pause before new letter or new word if (wpm > 30)lacktime = 1.2; if (wpm > 35)lacktime = 1.5; if (lowduration > (hightimesavg * (2 * lacktime)) && lowduration < hightimesavg * (5 * lacktime)) { // letter space docode(); code[0] = '\0'; // Serial.print("/"); // DL6NBS: non-essential } if (lowduration >= hightimesavg * (5 * lacktime)) { // word space docode(); code[0] = '\0'; printascii(32); // Serial.println(); // DL6NBS: non-essential } } } ////////////////////////////// // write if no more letters // ////////////////////////////// if ((millis() - startttimelow) > (highduration * 6) && stop == LOW) { docode(); code[0] = '\0'; stop = HIGH; } /////////////////////////////////////////////// // we will turn on and off the selected LEDs // // and the speaker (if you need this) // /////////////////////////////////////////////// if (filteredstate == HIGH) { //digitalWrite(ledPin, HIGH); //digitalWrite(TXLED, LOW); // use this or digitalWrite(RXLED, LOW); // this for Arduino Micro-pro // digitalWrite(beepPin, HIGH); // use this for a beeper // tone(audioOutPin,target_freq); // use this for a speaker } else { //digitalWrite(ledPin, LOW); digitalWrite(TXLED, HIGH); digitalWrite(RXLED, HIGH); // digitalWrite(beepPin, LOW); // noTone(audioOutPin); } ////////////////////////////////// // the end of main loop clean up// ///////////////////////////////// updateinfolinelcd(); realstatebefore = realstate; lasthighduration = highduration; filteredstatebefore = filteredstate; ///////////////////////////////////// // Soft-restart if button pushed // ///////////////////////////////////// if (digitalRead(selPin) == LOW) { magnitudelimit = 100; magnitudelimit_low = 100; realstate = LOW; realstatebefore = LOW; filteredstate = LOW; filteredstatebefore = LOW; oled.clear(); row = 0; lcdindex = 3; wpm = 0; x = 0; y = 0; delay (500); } } //////////////////////////////// // translate cw code to ascii // //////////////////////////////// void docode() { if (strcmp(code, ".-") == 0) printascii(65); if (strcmp(code, "-...") == 0) printascii(66); if (strcmp(code, "-.-.") == 0) printascii(67); if (strcmp(code, "-..") == 0) printascii(68); if (strcmp(code, ".") == 0) printascii(69); if (strcmp(code, "..-.") == 0) printascii(70); if (strcmp(code, "--.") == 0) printascii(71); if (strcmp(code, "....") == 0) printascii(72); if (strcmp(code, "..") == 0) printascii(73); if (strcmp(code, ".---") == 0) printascii(74); if (strcmp(code, "-.-") == 0) printascii(75); if (strcmp(code, ".-..") == 0) printascii(76); if (strcmp(code, "--") == 0) printascii(77); if (strcmp(code, "-.") == 0) printascii(78); if (strcmp(code, "---") == 0) printascii(79); if (strcmp(code, ".--.") == 0) printascii(80); if (strcmp(code, "--.-") == 0) printascii(81); if (strcmp(code, ".-.") == 0) printascii(82); if (strcmp(code, "...") == 0) printascii(83); if (strcmp(code, "-") == 0) printascii(84); if (strcmp(code, "..-") == 0) printascii(85); if (strcmp(code, "...-") == 0) printascii(86); if (strcmp(code, ".--") == 0) printascii(87); if (strcmp(code, "-..-") == 0) printascii(88); if (strcmp(code, "-.--") == 0) printascii(89); if (strcmp(code, "--..") == 0) printascii(90); if (strcmp(code, ".----") == 0) printascii(49); if (strcmp(code, "..---") == 0) printascii(50); if (strcmp(code, "...--") == 0) printascii(51); if (strcmp(code, "....-") == 0) printascii(52); if (strcmp(code, ".....") == 0) printascii(53); if (strcmp(code, "-....") == 0) printascii(54); if (strcmp(code, "--...") == 0) printascii(55); if (strcmp(code, "---..") == 0) printascii(56); if (strcmp(code, "----.") == 0) printascii(57); if (strcmp(code, "-----") == 0) printascii(48); if (strcmp(code, "..--..") == 0) printascii(63); if (strcmp(code, ".-.-.-") == 0) printascii(46); if (strcmp(code, "--..--") == 0) printascii(44); if (strcmp(code, "-.-.--") == 0) printascii(33); if (strcmp(code, ".--.-.") == 0) printascii(64); if (strcmp(code, "---...") == 0) printascii(58); if (strcmp(code, "-....-") == 0) printascii(45); if (strcmp(code, "-..-.") == 0) printascii(47); if (strcmp(code, "-...-") == 0) printascii(61); if (strcmp(code, "-.--.") == 0) printascii(40); if (strcmp(code, "-.--.-") == 0) printascii(41); if (strcmp(code, ".-...") == 0) printascii(95); if (strcmp(code, "...-..-") == 0) printascii(36); if (strcmp(code, "...-.-") == 0) printascii(62); if (strcmp(code, ".-.-.") == 0) printascii(60); if (strcmp(code, "...-.") == 0) printascii(126); ////////////////// // The specials // ////////////////// if (strcmp(code, ".-.-") == 0) printascii(3); if (strcmp(code, "---.") == 0) printascii(4); if (strcmp(code, ".--.-") == 0) printascii(6); } /////////////////////////////////////// // print the ascii code to the OLED // /////////////////////////////////////// void printascii(int asciinumber) { if (mode == 1) { ////////////////////////////////////////////// // print small characers in overwrite mode // ////////////////////////////////////////////// if (lcdindex > colums - 1) { // if the line is full lcdindex = 0; // back to 1. character row++; // pointer to next row oled.setCursor(lcdindex, row); // set cursor left oled.clearLine(row); // clear active line for new print oled.clearLine(row + 1); // clear next line too for better reading } if (row > rows) { // if the page is full row = 0; // back to the first row lcdindex = 3; // place 0 to 2 reserved for WPM oled.setCursor(lcdindex, row); // place 0 to 2 reserved for WPM-display if (mode == 1) { // if small font selected, oled.clearLine(row); // clear active line for new print oled.clearLine(row + 1); // clear next line too for better reading } } oled.setCursor(lcdindex, row); // set cursor-pointer to place next ASCII oled.write(asciinumber); // and put it out // Serial.write(asciinumber); // for testing lcdindex += 1; } else { ////////////////////////////////////////////// // print large characers in scroll mode // ////////////////////////////////////////////// if (y < ymax) { // run all lines for the first page oled.setCursor(x, y); if (y == 0) line1[x] = asciinumber; // remember all 4 lines for later scroll if (y == 2) line2[x] = asciinumber; if (y == 4) line3[x] = asciinumber; if (y == 6) line4[x] = asciinumber; } else { // after this run with scrolling oled.setCursor(x, ymax); // set cursor line4[x] = asciinumber; // remember last line } oled.write(asciinumber); // print ASCII x++; // next x-place if (x > xmax) { // end of line reached x = 0; if (y == ymax) y++; // end of page reached else (y = y + 2); } if (y > ymax and x == 0) { strcpy(line1, line2); // shift line 2 up strcpy(line2, line3); // shift line 3 up strcpy(line3, line4); // shift line 4 up oled.setCursor(2, 0); // fix cursor line 1 oled.print(&line1[2]); // do not overwrite WPM oled.setCursor(0, 2); // fix cursor line 2 oled.print(line2); oled.setCursor(0, 4); // fix cursor line 3 oled.print(line3); oled.setCursor(x, 6); // fix cursor actve line 4 oled.print(" "); // clear it } } } void updateinfolinelcd() { ///////////////////////////////////// // here we update the upper line // // with the speed. // ///////////////////////////////////// oled.inverse(); // highlight the WPM place oled.setCursor(0, 0); if (wpm < 10) { oled.print(" "); // clear left place } oled.print(wpm); oled.noInverse(); // highlight off for normal display }