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.

Revision:
6:5e21ac9f0550
Parent:
5:503bd366fd73
diff -r 503bd366fd73 -r 5e21ac9f0550 main.cpp
--- 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;
+}