![](/media/cache/profiles/f0fcf351df4eb6786e9bb6fc4e2dee02.jpg.50x50_q85.jpg)
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.
Diff: main.cpp
- Revision:
- 0:e608fc311e4e
diff -r 000000000000 -r e608fc311e4e main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Sun Feb 05 07:49:54 2017 +0000 @@ -0,0 +1,564 @@ +/* + * 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(); + } + } + } +}