CW Decoder (Morse code decoder) 1st release version. mbed = 131 revision (Not latest) is used. Only run on DISCO-F746NG mbed board.

Dependencies:   BSP_DISCO_F746NG F746_GUI F746_SAI_IO LCD_DISCO_F746NG TS_DISCO_F746NG UIT_FFT_Real mbed

Base on F746_Spectrogram program created by 不韋 呂-san.
/users/MikamiUitOpen/code/F746_Spectrogram/
Thanks 不韋 呂-san to use fundamental parts such as FFT, SAI, GUI and other useful subroutines.
You do NOT need any modification for mbed hardware and NO additional circuits.
The mbed board read CW tone from your receiver speaker via MEMES microphone (on board) and show it on the screen.

main.cpp

Committer:
kenjiArai
Date:
2017-02-05
Revision:
0:e608fc311e4e

File content as of revision 0:e608fc311e4e:

/*
 * mbed program / cwdecoder using the FFT Algorithm
 *   tested on DISCO-F746G mbed board
 *
 *  Modified by Kenji Arai
 *      http://www.page.sannet.ne.jp/kenjia/index.html
 *      http://mbed.org/users/kenjiArai/
 *
 *      Started:  January  28th, 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/
 *  2)  http://skovholm.com/cwdecoder
 *      by Hjalmar Skovholm Hansen OZ1JHM
 */
 
//  Include --------------------------------------------------------------------
#include "mbed.h"
#include "SAI_InOut.hpp"
#include "F746_GUI.hpp"
#include "Matrix.hpp"
#include "NumericLabel.hpp"
#include "FFT_Analysis.hpp"
#include "MethodCollection.hpp"

#warning "If you would like to run this program, you need modify mbed revision."
#warning "Please set mbed revision 131 (dated on Dec. 15, 2016)"

//  Definition -----------------------------------------------------------------
//#define METHOD_COLLECTION_HPP   // MethodCollection.hpp will NOT use

#define HIGH            1
#define LOW             0
#define MILLIS()        t.read_ms()

#define ONE_LINE        34
#define LINES           11
#define WORK_LINE       LINES
#define TOTAL_CHAR      (ONE_LINE * LINES)

#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;

//  ROM / Constant data --------------------------------------------------------
const int FS     = I2S_AUDIOFREQ_32K;
const int N_FFT  = 512;
#define SLOT_750HZ 12       // 32KHz /512 * 12 = 750Hz

char *const open_msg[] = {
    "Center freq.:       750 Hz",
    "Sampling freq.       32 kHz",   
    "FFT / # of points:  512",
    "Two modes ",
    "(1) CW / MORSE Decoder",
    "(2) Show Spectram"
};

//  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;
uint8_t     msg_lcd[LINES + 1][48];
uint8_t     num_last_line       = 0;
Array<float> sn(N_FFT+1);
Array<float> db(N_FFT/2+1);
LCD_DISCO_F746NG *lcd = GuiBase::GetLcdPtr();

//  Object ---------------------------------------------------------------------
Timer       t;
DigitalOut  myled(LED1);
DigitalOut  data_in(D5);
DigitalOut  loop_trg(D6);
DigitalOut  out_code(D7);
Serial      pc(USBTX, USBRX);
SaiIO       mySai(SaiIO::INPUT, N_FFT+1, FS, INPUT_DEVICE_DIGITAL_MICROPHONE_2);
FftAnalyzer fftAnalyzer(N_FFT+1, N_FFT);

//  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);
void    cw_decoder(void);
void    spectrogram(void);
uint8_t mode_slect(void);
void    spectrum(void);
//------------------------------------------------------------------------------
//  Control Program
//------------------------------------------------------------------------------
int main()
{
    while (true){
        lcd->Clear(GuiBase::ENUM_BACK);
        uint8_t n = mode_slect();
        switch (n){
            case 0:
                cw_decoder();
                break;
            case 1:
                spectrum();
                break;
            default:
                break;
        }
    }
}

uint8_t mode_slect()
{
    Label obj0(240, 2, "Operationg Mode",
               Label::CENTER, Font24, LCD_COLOR_LIGHTGREEN);
    Label obj1(40,  30, open_msg[0], Label::LEFT, Font20);
    Label obj2(40,  50, open_msg[1], Label::LEFT, Font20);
    Label obj3(40,  70, open_msg[2], Label::LEFT, Font20);
    Label obj4(40, 100, open_msg[3], Label::LEFT, Font20, LCD_COLOR_LIGHTGREEN);
    Label obj5(70, 120, open_msg[4], Label::LEFT, Font20, LCD_COLOR_LIGHTGREEN);
    Label obj6(70, 140, open_msg[5], Label::LEFT, Font20, LCD_COLOR_LIGHTGREEN);
    Button button0( 50, 200, 180, 50, "CW/MORSE", Font24);
    Button button1(260, 200, 180, 50, "SPECTRAM", Font24);
    while (true){
        if (button0.Touched()){     return 0UL;}
        if (button1.Touched()){     return 1UL;}
        wait(0.02f);
    }
}

void spectrogram()
{
    lcd->Clear(GuiBase::ENUM_BACK);
    PRINTF("\r\nFFT Spectroram\r\n");
    Button button10(380, 240, 80, 30, "MENUE", Font16);
        spectrum();
        if (button10.Touched()){     return;}
}

void cw_decoder()
{
    lcd->Clear(GuiBase::ENUM_BACK);
    PRINTF("\r\nCW Decoder(FFT) by JH1PJL\r\n");
    Label myLabel1(120, 250, "CW Decoder(FFT) by JH1PJL",
                    Label::CENTER, Font16);
    Button button20(380, 240, 80, 30, "MENUE", Font16);
    printf("Sys=%u\r\n", SystemCoreClock);
    lcd->SetTextColor(LCD_COLOR_GREEN);
    lcd->SetFont(&Font20);
    mySai.RecordIn();
    t.start();
    while (true){
        loop_trg = !loop_trg;
        data_in = 1;
        while (mySai.IsCaptured() == false){;}
        for (int n=0; n<mySai.GetLength(); n++){
            int16_t xL, xR;
            mySai.Input(xL, xR);
            sn[n] = (float)xL;
        }
        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;
                }
            }
        }
        // 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;
                }
            }
            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.7f;}
                if(wpm > 45){   lacktime = 1.9f;}
                if(wpm > 50){   lacktime = 2.0f;}
                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");
                }
            }
        }
        // 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){ 
            myled = HIGH;
        } else {
            myled = LOW;
        }
        // the end of main loop clean up
        realstatebefore = realstate;
        lasthighduration = highduration;
        filteredstatebefore = filteredstate;
        DEBUG("%d\r\n", t.read_ms());
        // return to menue or continue
        if (button20.Touched()){     return;}
    }
}

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);
    msg_lcd[WORK_LINE][num_last_line++] = 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[WORK_LINE][i] = ' ';
        }
        num_last_line = 0;
        for (i =0; i < (WORK_LINE + 1); i++){
            lcd->DisplayStringAtLine( i, &msg_lcd[i][0]);
        }
    } else {
        lcd->DisplayStringAtLine(WORK_LINE, &msg_lcd[WORK_LINE][0]);
    }
    out_code = 0;
}

//------------------------------------------------
//  リアルタイム・スペクトログラム
//      入力: MEMS マイク
//
//  2016/10/02, Copyright (c) 2016 MIKAMI, Naoki
//  modified by JH1PJL
//------------------------------------------------
void spectrum()
{
    const int FS = I2S_AUDIOFREQ_16K;   // 標本化周波数: 16 kHz
    const int N_FFT = 512;              // FFT の点数
    const float FRAME = (N_FFT/(float)FS)*1000.0f;  // 1 フレームに対応する時間(単位:ms)

    const int X0    = 40;           // 表示領域の x 座標の原点
    const int Y0    = 200;          // 表示領域の y 座標の原点
    const int H0    = 160;          // 表示する際の周波数軸の長さ(5 kHz に対応)
    const uint16_t PX_1KHZ = H0/5;  // 1 kHz に対応するピクセル数
    const int W0    = 360;          // 横方向の全体の表示の幅(単位:ピクセル)
    const int H_BAR = 2;            // 表示する際の 1 フレームに対応する横方向のピクセル数
    const int SIZE_X = W0/H_BAR;
    const uint16_t MS100 = 100*H_BAR/FRAME; // 100 ms に対応するピクセル数
    const uint32_t AXIS_COLOR = LCD_COLOR_WHITE;//0xFFCCFFFF;
    
    Matrix<uint32_t> spectra(SIZE_X, H0+1, GuiBase::ENUM_BACK);

    SaiIO mySai(SaiIO::INPUT, N_FFT+1, FS,
                INPUT_DEVICE_DIGITAL_MICROPHONE_2);

    LCD_DISCO_F746NG *lcd = GuiBase::GetLcdPtr();   // LCD 表示器のオブジェクト
    lcd->Clear(GuiBase::ENUM_BACK);
    Label myLabel1(240, 2, "Real-time spectrogram", Label::CENTER, Font16);

    // ButtonGroup の設定
    const uint16_t B_W = 50;
    const uint16_t B_Y = 242;
    const uint16_t B_H = 30;
    const string RUN_STOP[3] = {"MENUE","RUN", "STOP"};
    ButtonGroup runStop(285, B_Y, B_W, B_H, 3, RUN_STOP, 0, 0, 3, 1);
    
    Button clear(430, B_Y, B_W, B_H, "CLEAR");
    clear.Inactivate();
    // ButtonGroup の設定(ここまで)

    // 座標軸
    DrawAxis(X0, Y0, W0, H0, AXIS_COLOR, MS100, PX_1KHZ, lcd);

    Array<float> sn(N_FFT+1);
    Array<float> db(N_FFT/2+1);

    // 色と dB の関係の表示
    ColorDb(Y0, AXIS_COLOR, lcd);
        
    FftAnalyzer fftAnalyzer(N_FFT+1, N_FFT);

    // ループ内で使う変数の初期化
    int stop = 1;       // 0: run, 1: stop

    while(!runStop.GetTouchedNumber(stop)) {}
    // データ読み込み開始
    mySai.RecordIn();
  
    while (true)
    {
        runStop.GetTouchedNumber(stop);

        if (stop == 0){
            NVIC_SystemReset();
        }
        else if (stop == 1)
        {
            clear.Inactivate();
            if (mySai.IsCaptured())
            {
                // 1フレーム分の信号の入力
                for (int n=0; n<mySai.GetLength(); n++)
                {
                    int16_t xL, xR;
                    mySai.Input(xL, xR);
                    sn[n] = (float)xL;
                }
                //mySai.ResetCaptured();

                // スペクトルの更新
                SpectrumUpdate(spectra, fftAnalyzer, sn, db);
                // スペクトルの表示
                DisplaySpectrum(spectra, X0, Y0, H_BAR, lcd);
            }
        }
        else if (stop == 2)
        {
            if (!clear.IsActive()) clear.Activate();
            if (clear.Touched())
            {
                spectra.Fill(GuiBase::ENUM_BACK);   // スペクトルの表示をクリア
                DisplaySpectrum(spectra, X0, Y0, H_BAR, lcd);
                clear.Draw();
            }
        }
    }
}