CW Decoder (Morse code decoder) 1st release version. Only run on Nucleo-F446RE mbed board.
Dependencies: Array_Matrix F446_AD_DA ST7565_SPI_LCD TextLCD UIT_FFT_Real
Fork of F446_MySoundMachine by
Base on F446_MySoundMachine program created by 不韋 呂-san.
Thanks to 不韋 呂-san making fundamental part such as FFT and ADC high speed interrupt driven program.
I just combined LCD and show CW code.
Diff: main.cpp
- Revision:
- 6:5e21ac9f0550
- Parent:
- 5:503bd366fd73
--- a/main.cpp Tue Jan 31 12:52:35 2017 +0000 +++ b/main.cpp Sun Feb 05 08:02:54 2017 +0000 @@ -1,111 +1,479 @@ -//-------------------------------------------------------- -// STM32F446 と信号処理用ボードによる信号処理のデモプログラム -// -// SW: 偶数 入力をそのまま出力 -// 1: 遮断周波数可変 LPF -// 3: 遮断周波数可変 HPF -// 5: ボーカルキャンセラ -// 7: ピッチシフタ -// 9: 残響生成器 -// -// 2017/01/31, Copyright (c) 2017 MIKAMI, Naoki -//-------------------------------------------------------- +/* + * mbed program / cwdecoder using the FFT Algorithm + * tested on Nucleo-F446RE mbed board + * + * Modified by Kenji Arai + * http://www.page.sannet.ne.jp/kenjia/index.html + * http://mbed.org/users/kenjiArai/ + * + * Started: Feburary 1st, 2017 + * Revised: Feburary 5th, 2017 + * + * Reference program: + * 1) 2016/10/02, Copyright (c) 2016 MIKAMI, Naoki + * https://developer.mbed.org/users/MikamiUitOpen/code/F746_Spectrogram/ + * 2017/01/31, Copyright (c) 2017 MIKAMI, Naoki + * https://developer.mbed.org/users/MikamiUitOpen/code/F446_MySoundMachine/ + * 2) http://skovholm.com/cwdecoder + * by Hjalmar Skovholm Hansen OZ1JHM + */ + +// Include -------------------------------------------------------------------- +#include "mbed.h" +#include "Matrix.hpp" +#include "FFT_Analysis.hpp" +#include "F446_ADC_Interrupt.hpp" +#include "TextLCD.h" +#include "ST7565_SPI_LCD.h" + +// Definition ----------------------------------------------------------------- +#define METHOD_COLLECTION_HPP // MethodCollection.hpp will NOT use -#include "F446_ADC_Interrupt.hpp" // AD, DA -#include "AQM1602.hpp" // LCD 表示器 -#include "MyFunctions.hpp" // グローバル関数 -#include "SignalProcessing.hpp" // 信号処理の抽象基底クラスなど -#include "VariableLpHp.hpp" // 遮断周波数可変フィルタ -#include "WeaverModulator.hpp" // ピッチシフタ -#include "Reverbrator.hpp" // 残響生成器用 +#define HIGH 1 +#define LOW 0 +#define MILLIS() t.read_ms() + +#define ONE_LINE 20 +#define LINES 4 + +#define USE_COM +//#define USE_DEBUG + +#ifdef USE_COM +#define BAUD(x) pc.baud(x) +#define GETC(x) pc.getc(x) +#define PUTC(x) pc.putc(x) +#define PRINTF(...) pc.printf(__VA_ARGS__) +#define READABLE(x) pc.readable(x) +#else +#define BAUD(x) {;} +#define GETC(x) {;} +#define PUTC(x) {;} +#define PRINTF(...) {;} +#define READABLE(x) {;} +#endif + +#ifdef USE_DEBUG +#define DEBUG(...) pc.printf(__VA_ARGS__) +#else +#define DEBUG(...) {;} +#endif + using namespace Mikami; -const int FS_ = 24000; // 標本化周波数: 24 kHz -AdcDual_Intr myAdc_(FS_); // 参照:"F446_ADC_Interrupt.hpp" -DacDual myDac_; // 参照:"F446_DAC.hpp" +// ROM / Constant data -------------------------------------------------------- +const int FS = 48000; +const int N_FFT = 512; +#define SLOT_750HZ 8 // 48KHz /512 * 8 = 750Hz + +/* Tried other conditions +#if 0 +const int FS = 24000; +const int N_FFT = 256; +#define SLOT_750HZ 8 // 24KHz /256 * 8 = 750Hz +#endif +#if 0 +const int FS = 48000; +const int N_FFT = 256; +#define SLOT_750HZ 4 // 48KHz /256 * 4 = 750Hz +#endif +*/ -Through through_; // そのまま出力 -VariableLpHp filter_(10, FS_); // 遮断周波数可変 IIR フィルタ -VocalCanceller vCancel_; // ボーカルキャンセラ -FrqShifter fShifter_(FS_); // ピッチシフタ -EchoSystem echo_; // 残響生成器 -// 割り込みが有効になる前にポインタに割り当てておく必要がある -SignalProcessing *spPtr_ = &through_; +// RAM ------------------------------------------------------------------------ +float magnitude ; +int16_t magnitudelimit = 100; +int16_t magnitudelimit_low = 100; +int16_t realstate = LOW; +int16_t realstatebefore = LOW; +int16_t filteredstate = LOW; +int16_t filteredstatebefore = LOW; +int16_t nbtime = 2; // ms noise blanker +int32_t starttimehigh; +int32_t highduration; +int32_t lasthighduration; +int32_t hightimesavg; +int32_t lowtimesavg; +int32_t startttimelow; +int32_t lowduration; +int32_t laststarttime = 0; +char code[32]; +int16_t stop = LOW; +int16_t wpm; +uint32_t cycle = 0; +Array<float> sn(N_FFT+1); +Array<float> db(N_FFT/2+1); +uint16_t adc_bf0[N_FFT + 8]; +uint16_t adc_bf1[N_FFT + 8]; +uint8_t adc_select = 0; +uint16_t bf0_n = 0; +uint16_t bf1_n = 0; +volatile bool adc_bf0_full = false; +volatile bool adc_bf1_full = false; +volatile bool adc_data_full = false; +uint8_t msg_lcd[LINES][36]; +uint8_t num_last_line = 0; -// ADC 変換終了割り込みに対する割り込みサービス・ルーチン + +// Object --------------------------------------------------------------------- +Timer t; +DigitalOut myled(LED1); +DigitalOut morse(PC_4); +DigitalOut irq_job(D4); +DigitalOut data_in(D5); +DigitalOut loop_trg(D6); +DigitalOut out_code(D7); +Serial pc(USBTX, USBRX); +FftAnalyzer fftAnalyzer(N_FFT+1, N_FFT); +AdcSingle_Intr Adc_in(FS); +// rs, e, d4, d5, d6, d7 +TextLCD lcd(PB_0, PH_1, PC_0, PC_1, PC_2, PC_3, TextLCD::LCD20x4); +// mosi, sck, reset, a0, ncs +ST7565 glcd(PB_5, PB_3, PB_13, PB_14, PB_15, ST7565::AD12864SPI); + +// Function prototypes -------------------------------------------------------- +void setup(void); +void loop(void); +void docode(void); +void printascii(char); +void adc_convert(void); +float SpectrumUpdate(FftAnalyzer &analyzer, + const Array<float> &sn, const Array<float> &db); + +//------------------------------------------------------------------------------ +// Control Program +//------------------------------------------------------------------------------ void AdcIsr() { - float xn1, xn2, yn; - myAdc_.Read(xn1, xn2); // 入力 - yn = spPtr_->Execute(xn1, xn2); // 信号処理の実行 - myDac_.Write(yn, yn); // 出力 + irq_job = 1; + if (adc_select == 0){ + Adc_in.Read(adc_bf0[bf0_n]); + bf0_n++; + if (bf0_n >= N_FFT){ + adc_bf0_full = true; + adc_select = 1; + bf1_n = 0; + } + } else { + Adc_in.Read(adc_bf1[bf1_n]); + bf1_n++; + if (bf1_n >= N_FFT){ + adc_bf1_full = true; + adc_select = 0; + bf0_n = 0; + } + } + irq_job = 0; } int main() { - printf("\r\nDemonstration for digital signal processing\r\n"); - - BusIn sws(D6, D7, D8, D9); // ロータリ・ディップ・スイッチ用 - sws.mode(PullDown); - - AnalogIn a3In(A3); // VR からの電圧読み取り用 - Aqm1602 lcd; // LCD 表示器 - - // 出力の LPF の遮断周波数を 10 kHz に設定 - myDac_.ScfClock(10000*100); - - // ADC 変換終了割り込みに対する割り込みサービス・ルーチン割り当て - myAdc_.SetIntrVec(&AdcIsr); - - int kind = -1; // 処理の種類 - float frq; // VR で設定された周波数 - - while (true) - { - int sw; // 現在の機能切り替えスイッチの状態 - do - { - sw = sws.read(); - wait_ms(50); - } while (sw != sws.read()); - - switch (sw) - { - case 1: // 遮断周波数可変 LPF - if (FrChange(a3In, 200, 2000, 10, frq) || (sw != kind)) - { - filter_.Design(frq, BilinearDesign::LPF); - AssignDisplay(filter_, lcd, "LPF", frq); + wait(1.0); + lcd.locate(0, 0); + lcd.printf("CW DECODER(FFT) V0.1"); + lcd.locate(0, 1); + lcd.printf(" by JH1PJL Feb. 2017"); + lcd.locate(0, 2); + lcd.printf("Center Freq = 750Hz "); + lcd.locate(0, 3); + lcd.printf(" "); + glcd.cls(); + glcd.locate(0, 0); + glcd.printf(" ----- CW DECODER -----\r\n"); + glcd.printf(" "__DATE__"("__TIME__")\r\n"); + glcd.printf(" Center freq. = 750Hz\r\n"); + glcd.printf(" mbed Nucleo-F446RE\r\n"); + glcd.printf(" Base: Demo_F446_AD_DA\r\n"); + glcd.printf(" Kenji Arai / JH1PJL\r\n" ); + glcd.printf(" kenjia@sannet.ne.jp "); + PRINTF("\r\nCW Decoder(FFT) by JH1PJL\r\n"); + printf("Sys=%u\r\n", SystemCoreClock); + Adc_in.SetIntrVec(&AdcIsr); + t.start(); + while (true){ + loop_trg = !loop_trg; + data_in = 1; + adc_data_full = false; + while (adc_data_full == false){ + if (adc_bf0_full == true){ + for (int n=0; n < N_FFT; n++){ + int32_t xData; + xData = (int32_t)adc_bf0[n] - 0x00007fff; + sn[n] = (float)xData; + } + adc_bf0_full = false; + adc_data_full = true; + } else if (adc_bf1_full == true){ + for (int n=0; n < N_FFT; n++){ + int32_t xData; + xData = (int32_t)adc_bf1[n] - 0x00007fff; + sn[n] = (float)xData; } - break; - case 3: // 遮断周波数可変 HPF - if (FrChange(a3In, 200, 2000, 10, frq) || (sw != kind)) - { - filter_.Design(frq, BilinearDesign::HPF); - AssignDisplay(filter_, lcd, "HPF", frq); + adc_bf1_full = false; + adc_data_full = true; + } + } + data_in = 0; + //magnitude = SpectrumUpdate(spectra, fftAnalyzer, sn, db); + magnitude = SpectrumUpdate(fftAnalyzer, sn, db); + //printf("%f\r\n", magnitude); + if (magnitude > magnitudelimit_low){ // magnitude limit automatic + magnitudelimit = // moving average filter + (magnitudelimit +((magnitude - magnitudelimit) / 6.0f)); + } + if (magnitudelimit < magnitudelimit_low){ + magnitudelimit = magnitudelimit_low; + } + // check for the magnitude + if(magnitude > magnitudelimit * 0.9f){ // just to have some space up + realstate = HIGH; + } else { + realstate = LOW; + } + // clean up the state with a noise blanker + if (realstate != realstatebefore){ + laststarttime = MILLIS(); + } + if ((MILLIS()-laststarttime)> nbtime){ + if (realstate != filteredstate){ + filteredstate = realstate; + } + } + // 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.0f *hightimesavg) || hightimesavg == 0.0f){ + // now we know avg dit time ( rolling 3 avg) + hightimesavg = (highduration+hightimesavg+hightimesavg) / 3.0f; + } + if (highduration > (5.0f * hightimesavg) ){ + // if speed decrease fast .. + hightimesavg = highduration+hightimesavg; } - break; - case 5: // ボーカルキャンセラ - if (sw != kind) - AssignDisplay(vCancel_, lcd, "Vocal Calceller"); - break; - case 7: // ピッチシフタ - if (FrChange(a3In, 0, 200, 1, frq) || (sw != kind)) - { - fShifter_.SetFrequensy(frq); - AssignDisplay(fShifter_, lcd, "Pitch Shifter", frq); + } + } + // 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 + // 0.6 filter out false dits + if (highduration < (hightimesavg * 2.0f) + && highduration > (hightimesavg * 0.6f)){ + strcat(code,"."); + DEBUG("."); + } + if (highduration > (hightimesavg*2) + && highduration < (hightimesavg * 6.0f)){ + strcat(code,"-"); + DEBUG("-"); + wpm = (wpm + (1200/((highduration)/3)))/2; + DEBUG("<%dwpm>", wpm); } - break; - case 9: // 残響生成器 - if (sw != kind) - AssignDisplay(echo_, lcd, "Reverbrator"); - break; - default: - if (sw != kind) - AssignDisplay(through_, lcd, "Through"); - break; + } + if (filteredstate == HIGH){ // we did end a LOW + float lacktime = 1; + // when high speeds we have to have a little more pause + // before new letter or new word + if(wpm > 25){ lacktime = 1.0f;} + if(wpm > 30){ lacktime = 1.2f;} + if(wpm > 35){ lacktime = 1.5f;} + if(wpm > 40){ lacktime = 1.8f;} + if(wpm > 45){ lacktime = 2.2f;} + if(wpm > 50){ lacktime = 2.5f;} + if (lowduration > (hightimesavg*(2.0f * lacktime)) + && lowduration < hightimesavg*(5.0f * lacktime)){ + docode(); + code[0] = '\0'; + DEBUG("/"); + } + if (lowduration >= hightimesavg*(5.0f * lacktime)){ // word space + docode(); + code[0] = '\0'; + printascii(' '); + DEBUG("\r\n"); + } + } } - kind = sw; - wait(0.2f); + // write if no more letters + if ((MILLIS() - startttimelow) > (highduration * 6.0f) && stop == LOW){ + docode(); + code[0] = '\0'; + stop = HIGH; + } + // we will turn on and off the LED + // and the speaker + if(filteredstate == HIGH){ + morse = HIGH; + } else { + morse = LOW; + } + // the end of main loop clean up + realstatebefore = realstate; + lasthighduration = highduration; + filteredstatebefore = filteredstate; + //DEBUG("%d\r\n", t.read_ms()); } } + +float SpectrumUpdate(FftAnalyzer &analyzer, + const Array<float> &sn, const Array<float> &db) +{ + analyzer.Execute(sn, db); + return (db[SLOT_750HZ] - 20) * 2; +} + +// translate cw code to ascii +void docode() +{ + //PRINTF("decording<%s>", code); + if (code[0] == '.'){ // . + if (code[1] == '.'){ // .. + if (code[2] == '.'){ // ... + if (code[3] == '.'){ // .... + if (strcmp(code,"...." ) == 0){ printascii('H'); return;} + if (strcmp(code,"....." ) == 0){ printascii('5'); return;} + if (strcmp(code,"....-" ) == 0){ printascii('4'); return;} + } else if (code[3] == '-'){ // ...- + if (code[4] == '.'){ // ...-. + if (strcmp(code,"...-." ) == 0) + { printascii(126); return;} + if (strcmp(code,"...-.-" ) == 0) + { printascii(62); return;} + if (strcmp(code,"...-..-") == 0) + { printascii(36); return;} + } else if (code[4] == '-'){ // ...-- + if (strcmp(code,"...--" ) == 0) + { printascii('3'); return;} + } else { + if (strcmp(code,"...-" ) == 0) + { printascii('V'); return;} + } + } else { // ... + if (strcmp(code,"..." ) == 0){ printascii('S'); return;} + } + } else if (code[2] == '-'){ // ..- + if (strcmp(code,"..-" ) == 0){ printascii('U'); return;} + if (strcmp(code,"..-." ) == 0){ printascii('F'); return;} + if (strcmp(code,"..---" ) == 0){ printascii('2'); return;} + if (strcmp(code,"..--.." ) == 0){ printascii(63); return;} + } else { // .. + if (strcmp(code,".." ) == 0){ printascii('I'); return;} + } + } else if (code[1] == '-'){ // .- + if (code[2] == '.'){ // .-. + if (code[3] == '.'){ // .-.. + if (strcmp(code,".-.." ) == 0){ printascii('L'); return;} + if (strcmp(code,".-..." ) == 0){ printascii(95); return;} + } else if (code[3] == '-'){ // .-.- + if (strcmp(code,".-.-" ) == 0){ printascii(3); return;} + if (strcmp(code,".-.-." ) == 0){ printascii(60); return;} + if (strcmp(code,".-.-.-" ) == 0){ printascii(46); return;} + } else { // .-. + if (strcmp(code,".-." ) == 0){ printascii('R'); return;} + } + } else if (code[2] == '-'){ // .-- + if (code[3] == '.'){ // .--. + if (strcmp(code,".--." ) == 0){ printascii('P'); return;} + if (strcmp(code,".--.-" ) == 0){ printascii('-'); return;} + if (strcmp(code,".--.-." ) == 0){ printascii(64); return;} + } else if (code[3] == '-'){ // .--- + if (strcmp(code,".---" ) == 0){ printascii('J'); return;} + if (strcmp(code,".----" ) == 0){ printascii('1'); return;} + } else { // .-- + if (strcmp(code,".--" ) == 0){ printascii('W'); return;} + } + } else { // .- + if (strcmp(code,".-") == 0){ printascii('A'); return;} + } + } else { // . + if (strcmp(code,".") == 0){ printascii('E'); return;} + } + } else if (code[0] == '-'){ // - + if (code[1] == '.'){ // -. + if (code[2] == '.'){ // -.. + if (code[3] == '.'){ // -... + if (strcmp(code,"-..." ) == 0){ printascii('B'); return;} + if (strcmp(code,"-...." ) == 0){ printascii('6'); return;} + if (strcmp(code,"-....-" ) == 0){ printascii('-'); return;} + } else if (code[3] == '-'){ // -..- + if (strcmp(code,"-..-" ) == 0){ printascii('X'); return;} + if (strcmp(code,"-..-." ) == 0){ printascii(47); return;} + } else { + if (strcmp(code,"-.." ) == 0){ printascii('D'); return;} + } + } else if (code[2] == '-'){ // -.- + if (code[3] == '.'){ // -.-. + if (strcmp(code,"-.-." ) == 0){ printascii('C'); return;} + if (strcmp(code,"-.-.--" ) == 0){ printascii(33); return;} + } else if (code[3] == '-'){ // -.-- + if (strcmp(code,"-.--" ) == 0){ printascii('Y'); return;} + if (strcmp(code,"-.--." ) == 0){ printascii(40); return;} + if (strcmp(code,"-.--.-" ) == 0){ printascii(41); return;} + } else { // -.- + if (strcmp(code,"-.-" ) == 0){ printascii('K'); return;} + } + } else { // -. + if (strcmp(code,"-.") == 0){ printascii('N'); return;} + } + } else if (code[1] == '-'){ // - + if (code[2] == '.'){ // --. + if (strcmp(code,"--." ) == 0){ printascii('G'); return;} + if (strcmp(code,"--.." ) == 0){ printascii('Z'); return;} + if (strcmp(code,"--.-" ) == 0){ printascii('Q'); return;} + if (strcmp(code,"--..." ) == 0){ printascii('7'); return;} + if (strcmp(code,"--..--" ) == 0){ printascii(44); return;} + } else if (code[2] == '-'){ // --- + if (code[3] == '.'){ // ---. + if (strcmp(code,"---.." ) == 0){ printascii('8'); return;} + if (strcmp(code,"---." ) == 0){ printascii(4); return;} + if (strcmp(code,"---..." ) == 0){ printascii(58); return;} + } else if (code[3] == '-'){ // ---- + if (strcmp(code,"----." ) == 0){ printascii('9'); return;} + if (strcmp(code,"-----" ) == 0){ printascii('0'); return;} + } else { // --- + if (strcmp(code,"---" ) == 0){ printascii('O'); return;} + } + } else { // -- + if (strcmp(code,"--") == 0){ printascii('M'); return;} + } + } else { // - + if (strcmp(code,"-") == 0){ printascii('T'); return;} + } + } +} + +void printascii(char c) +{ + uint8_t i,j; + + out_code = 1; + PRINTF("%c", c); + if (num_last_line == ONE_LINE){ + for (j = 0; j < LINES; j++){ // scroll one line + for (i =0; i < ONE_LINE; i++){ + msg_lcd[j][i] = msg_lcd[j+1][i]; + } + } + for (i =0; i < ONE_LINE; i++){ // Clear last line + msg_lcd[3][i] = ' '; + } + num_last_line = 0; + for (i =0; i < 4; i++){ + lcd.locate(0, i); + lcd.printf("%s", &msg_lcd[i][0]); + } + } + if (!(num_last_line == 0 && c == ' ')){ + msg_lcd[3][num_last_line++] = c; + lcd.locate(0, 3); + lcd.printf("%s", &msg_lcd[3][0]); + } + out_code = 0; +}