{"id":5000,"date":"2025-09-28T08:00:38","date_gmt":"2025-09-28T15:00:38","guid":{"rendered":"https:\/\/www.cloudacm.com\/?p=5000"},"modified":"2025-09-27T21:16:16","modified_gmt":"2025-09-28T04:16:16","slug":"esp8266-audio-decoding","status":"publish","type":"post","link":"https:\/\/www.cloudacm.com\/?p=5000","title":{"rendered":"ESP8266 Audio Decoding"},"content":{"rendered":"<p>In the last post, I covered how to generate audio natively with the ESP8266 module.\u00a0 The hardware to do this is rudimentary.\u00a0 I demonstrated how to generate CW (Morse Code), DTMF, synthesized speech, and audio.\u00a0 This post will expand on some of those concepts by decoding CW and DTMF signals.\u00a0 The following Fritzing wiring diagram will be the foundation for all of the code covered in this post.<\/p>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2025\/09\/Wemos_DTMF.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-5004\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2025\/09\/Wemos_DTMF-1024x896.png\" alt=\"\" width=\"640\" height=\"560\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2025\/09\/Wemos_DTMF-1024x896.png 1024w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2025\/09\/Wemos_DTMF-300x263.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2025\/09\/Wemos_DTMF-768x672.png 768w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2025\/09\/Wemos_DTMF-1536x1344.png 1536w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2025\/09\/Wemos_DTMF-309x270.png 309w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2025\/09\/Wemos_DTMF.png 1600w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><\/a><\/p>\n<p>The Encode Module will be the signal source for the Decoding modules.\u00a0 I&#8217;ve included a MT8870 Decode module as well as an Arduino Uno to highlight the simplicity of the ESP8266 Decode module.<\/p>\n<p>For the first demonstration, I&#8217;ll cover CW signaling (Morse Code).\u00a0 The Encode Module code is as follows and is similar to what was provided in an earlier post.\u00a0 I do want to point out that the timing of the encoder and decode modules need to match.\u00a0 I&#8217;m using a 35ms timing base for the dot and gap duration.\u00a0 Dashes, letter gaps, and word gaps use standard 3, 3, and 7 multiples respectively.<\/p>\n<pre>int SignalPin = 0; \/\/ D3 on NodeMCU GPIO0\r\n\r\nint unit = 35; \/\/ base time unit in ms\r\nint dot = unit;\r\nint dash = 3 * unit;\r\nint gap = unit; \/\/ between elements\r\nint letterGap = 3 * unit; \/\/ between letters\r\nint wordGap = 7 * unit; \/\/ between words\r\n\r\n\/\/ ---- Helpers ----\r\nvoid playDot() {\r\ndigitalWrite(SignalPin, HIGH);\r\ndelay(dot);\r\ndigitalWrite(SignalPin, LOW);\r\ndelay(gap);\r\n}\r\n\r\nvoid playDash() {\r\ndigitalWrite(SignalPin, HIGH);\r\ndelay(dash);\r\ndigitalWrite(SignalPin, LOW);\r\ndelay(gap);\r\n}\r\n\r\n\/\/ ---- Morse Table ----\r\nstruct MorseMap {\r\nchar letter;\r\nconst char *pattern;\r\n};\r\n\r\nMorseMap morseTable[] = {\r\n\/\/ Letters\r\n{'A', \".-\"}, {'B', \"-...\"}, {'C', \"-.-.\"}, {'D', \"-..\"},\r\n{'E', \".\"}, {'F', \"..-.\"}, {'G', \"--.\"}, {'H', \"....\"},\r\n{'I', \"..\"}, {'J', \".---\"}, {'K', \"-.-\"}, {'L', \".-..\"},\r\n{'M', \"--\"}, {'N', \"-.\"}, {'O', \"---\"}, {'P', \".--.\"},\r\n{'Q', \"--.-\"}, {'R', \".-.\"}, {'S', \"...\"}, {'T', \"-\"},\r\n{'U', \"..-\"}, {'V', \"...-\"}, {'W', \".--\"}, {'X', \"-..-\"},\r\n{'Y', \"-.--\"}, {'Z', \"--..\"},\r\n\r\n\/\/ Digits\r\n{'0', \"-----\"}, {'1', \".----\"}, {'2', \"..---\"},\r\n{'3', \"...--\"}, {'4', \"....-\"}, {'5', \".....\"},\r\n{'6', \"-....\"}, {'7', \"--...\"}, {'8', \"---..\"},\r\n{'9', \"----.\"},\r\n\r\n\/\/ Punctuation\r\n{'.', \".-.-.-\"}, {',', \"--..--\"}, {'?', \"..--..\"}, {'\\'', \".----.\"},\r\n{'!', \"-.-.--\"}, {'\/', \"-..-.\"}, {'(', \"-.--.\"}, {')', \"-.--.-\"},\r\n{'&amp;', \".-...\"}, {':', \"---...\"}, {';', \"-.-.-.\"}, {'=', \"-...-\"},\r\n{'+', \".-.-.\"}, {'-', \"-....-\"}, {'_', \"..--.-\"}, {'\\\"', \".-..-.\"},\r\n{'$', \"...-..-\"},{'@', \".--.-.\"}\r\n};\r\nint tableSize = sizeof(morseTable) \/ sizeof(MorseMap);\r\n\r\n\/\/ ---- Play one letter ----\r\nvoid playLetter(char c) {\r\nif (c &gt;= 'a' &amp;&amp; c &lt;= 'z') c -= 32; \/\/ uppercase\r\n\r\n\/\/ find in table\r\nfor (int i = 0; i &lt; tableSize; i++) {\r\nif (morseTable[i].letter == c) {\r\nconst char *p = morseTable[i].pattern;\r\nwhile (*p) {\r\nif (*p == '.') playDot();\r\nelse if (*p == '-') playDash();\r\np++;\r\n}\r\ndelay(letterGap - gap); \/\/ inter-letter spacing\r\nreturn;\r\n}\r\n}\r\n\r\nif (c == ' ') {\r\ndelay(wordGap); \/\/ word gap\r\n}\r\n}\r\n\r\n\/\/ ---- Play a full message ----\r\nvoid playMessage(const char *msg) {\r\nwhile (*msg) {\r\nplayLetter(*msg);\r\nmsg++;\r\n}\r\n}\r\n\r\nvoid setup() {\r\npinMode(SignalPin, OUTPUT);\r\ndigitalWrite(SignalPin, LOW);\r\n\r\n}\r\n\r\nvoid loop() {\r\n\r\nplayMessage(\"However difficult life may seem, there is always something you can do and succeed at.\");\r\ndelay(2000);\r\nplayMessage(\"Remember to look up at the stars and not down at your feet.\");\r\ndelay(10000);\r\n}<\/pre>\n<p>The Decode Module code is as follows.\u00a0 It outputs the decoded Morse code to the serial monitor.<\/p>\n<pre>const uint8_t telegraphPin = 5; \/\/ D1 on NodeMCU = GPIO5\r\n\r\nbool telegraphActive = false;\r\nbool telegraphWasPressed = false;\r\nunsigned long pulseStartTime = 0;\r\nunsigned long pulseEndTime = 0;\r\n\r\n\/\/ timing units (ms)\r\nconst int unit = 35; \r\nconst int dotThresh = unit; \/\/ \u22641 unit \u21d2 dot\r\nconst int dashThresh = 3 * unit; \/\/ \u22643 units \u21d2 dash\r\nconst int letterGap = 3 * unit; \/\/ \u22653 units \u21d2 new letter\r\nconst int wordGap = 7 * unit; \/\/ \u22657 units \u21d2 new word\r\n\r\nString codeBuffer = \"\";\r\n\r\n\/\/ full map: letter, digit, and period\r\nstruct MorseMap {\r\nconst char *pattern;\r\nchar decoded;\r\n};\r\n\r\nMorseMap table[] = {\r\n\/\/ Letters\r\n{\".-\", 'A'}, {\"-...\", 'B'}, {\"-.-.\", 'C'}, {\"-..\", 'D'},\r\n{\".\", 'E'}, {\"..-.\", 'F'}, {\"--.\", 'G'}, {\"....\", 'H'},\r\n{\"..\", 'I'}, {\".---\", 'J'}, {\"-.-\", 'K'}, {\".-..\", 'L'},\r\n{\"--\", 'M'}, {\"-.\", 'N'}, {\"---\", 'O'}, {\".--.\", 'P'},\r\n{\"--.-\", 'Q'}, {\".-.\", 'R'}, {\"...\", 'S'}, {\"-\", 'T'},\r\n{\"..-\", 'U'}, {\"...-\", 'V'}, {\".--\", 'W'}, {\"-..-\", 'X'},\r\n{\"-.--\", 'Y'}, {\"--..\", 'Z'},\r\n\r\n\/\/ Digits\r\n{\"-----\", '0'}, {\".----\", '1'}, {\"..---\", '2'}, {\"...--\", '3'},\r\n{\"....-\", '4'}, {\".....\", '5'}, {\"-....\", '6'}, {\"--...\", '7'},\r\n{\"---..\", '8'}, {\"----.\", '9'},\r\n\r\n\/\/ Punctuation\r\n{\".-.-.-\", '.'}, {\"--..--\", ','}, {\"..--..\", '?'}, {\".----.\", '\\''},\r\n{\"-.-.--\", '!'}, {\"-..-.\", '\/'}, {\"-.--.\", '('}, {\"-.--.-\", ')'},\r\n{\".-...\", '&amp;'}, {\"---...\", ':'}, {\"-.-.-.\", ';'}, {\"-...-\", '='},\r\n{\".-.-.\", '+'}, {\"-....-\", '-'}, {\"..--.-\", '_'}, {\".-..-.\", '\\\"'},\r\n{\"...-..-\", '$'}, {\".--.-.\", '@'}\r\n};\r\n\r\nconst int tableSize = sizeof(table) \/ sizeof(MorseMap);\r\n\r\nvoid setup() {\r\npinMode(telegraphPin, INPUT);\r\nSerial.begin(9600);\r\n}\r\n\r\nvoid loop() {\r\nint state = digitalRead(telegraphPin);\r\n\r\n\/\/ \u2014\u2014\u2014 Key down \u21d2 start measuring \u201con\u201d pulse \u2014\u2014\u2014\r\nif (state == HIGH) {\r\nif (!telegraphActive) {\r\ntelegraphActive = true;\r\ntelegraphWasPressed = false;\r\npulseStartTime = millis();\r\n}\r\nreturn;\r\n}\r\n\r\n\/\/ \u2014\u2014\u2014 Key up \u21d2 end of pulse \u21d2 classify dot\/dash \u2014\u2014\u2014\r\nif (telegraphActive) {\r\ntelegraphActive = false;\r\npulseEndTime = millis();\r\n\r\nunsigned long onLen = pulseEndTime - pulseStartTime;\r\nif (onLen &gt;= dotThresh) {\r\n\/\/ dot or dash?\r\nif (onLen &lt; dashThresh) codeBuffer += '.';\r\nelse codeBuffer += '-';\r\n\r\ntelegraphWasPressed = true;\r\n}\r\nreturn;\r\n}\r\n\r\n\/\/ \u2014\u2014\u2014 After a pulse, watch for gap \u21d2 letter or word boundary \u2014\u2014\u2014\r\nif (telegraphWasPressed) {\r\nunsigned long offLen = millis() - pulseEndTime;\r\n\r\n\/\/ letter gap (just over 3 units)\r\nif (offLen &gt;= letterGap &amp;&amp; offLen &lt; wordGap) {\r\ndecodeAndPrint(codeBuffer);\r\ncodeBuffer = \"\";\r\ntelegraphWasPressed = false;\r\n}\r\n\/\/ word gap (7 units)\r\nelse if (offLen &gt;= wordGap) {\r\ndecodeAndPrint(codeBuffer);\r\ncodeBuffer = \"\";\r\nSerial.print(' ');\r\ntelegraphWasPressed = false;\r\n}\r\n}\r\n}\r\n\r\n\/\/ \u2014\u2014\u2014 Lookup and print one symbol \u2014\u2014\u2014\r\nvoid decodeAndPrint(const String &amp;code) {\r\nfor (int i = 0; i &lt; tableSize; i++) {\r\nif (code == table[i].pattern) {\r\nSerial.print(table[i].decoded);\r\nreturn;\r\n}\r\n}\r\n\/\/ if not found:\r\nSerial.print('?');\r\n}<\/pre>\n<p>I modified the Encoder Module code to allow detection from signal state or tone sensing devices.<\/p>\n<pre>int SignalPin = 0; \/\/ D3 on NodeMCU GPIO0\r\nint TonePin = 5; \/\/ D1 on NodeMCU GPIO5\r\nint freq = 1000; \/\/ Hz\r\n\r\nint unit = 35; \/\/ base time unit in ms\r\nint dot = unit;\r\nint dash = 3 * unit;\r\nint gap = unit; \/\/ between elements\r\nint letterGap = 3 * unit; \/\/ between letters\r\nint wordGap = 7 * unit; \/\/ between words\r\n\r\n\/\/ ---- Helpers ----\r\nvoid playDot() {\r\ndigitalWrite(SignalPin, HIGH);\r\ntone(TonePin, freq);\r\ndelay(dot);\r\ndigitalWrite(SignalPin, LOW);\r\nnoTone(TonePin);\r\ndelay(gap);\r\n}\r\n\r\nvoid playDash() {\r\ndigitalWrite(SignalPin, HIGH);\r\ntone(TonePin, freq);\r\ndelay(dash);\r\ndigitalWrite(SignalPin, LOW);\r\nnoTone(TonePin);\r\ndelay(gap);\r\n}\r\n\r\n\/\/ ---- Morse Table ----\r\nstruct MorseMap {\r\nchar letter;\r\nconst char *pattern;\r\n};\r\n\r\nMorseMap morseTable[] = {\r\n\/\/ Letters\r\n{'A', \".-\"}, {'B', \"-...\"}, {'C', \"-.-.\"}, {'D', \"-..\"},\r\n{'E', \".\"}, {'F', \"..-.\"}, {'G', \"--.\"}, {'H', \"....\"},\r\n{'I', \"..\"}, {'J', \".---\"}, {'K', \"-.-\"}, {'L', \".-..\"},\r\n{'M', \"--\"}, {'N', \"-.\"}, {'O', \"---\"}, {'P', \".--.\"},\r\n{'Q', \"--.-\"}, {'R', \".-.\"}, {'S', \"...\"}, {'T', \"-\"},\r\n{'U', \"..-\"}, {'V', \"...-\"}, {'W', \".--\"}, {'X', \"-..-\"},\r\n{'Y', \"-.--\"}, {'Z', \"--..\"},\r\n\r\n\/\/ Digits\r\n{'0', \"-----\"}, {'1', \".----\"}, {'2', \"..---\"},\r\n{'3', \"...--\"}, {'4', \"....-\"}, {'5', \".....\"},\r\n{'6', \"-....\"}, {'7', \"--...\"}, {'8', \"---..\"},\r\n{'9', \"----.\"},\r\n\r\n\/\/ Punctuation\r\n{'.', \".-.-.-\"}, {',', \"--..--\"}, {'?', \"..--..\"}, {'\\'', \".----.\"},\r\n{'!', \"-.-.--\"}, {'\/', \"-..-.\"}, {'(', \"-.--.\"}, {')', \"-.--.-\"},\r\n{'&amp;', \".-...\"}, {':', \"---...\"}, {';', \"-.-.-.\"}, {'=', \"-...-\"},\r\n{'+', \".-.-.\"}, {'-', \"-....-\"}, {'_', \"..--.-\"}, {'\\\"', \".-..-.\"},\r\n{'$', \"...-..-\"},{'@', \".--.-.\"}\r\n};\r\nint tableSize = sizeof(morseTable) \/ sizeof(MorseMap);\r\n\r\n\/\/ ---- Play one letter ----\r\nvoid playLetter(char c) {\r\nif (c &gt;= 'a' &amp;&amp; c &lt;= 'z') c -= 32; \/\/ uppercase\r\n\r\n\/\/ find in table\r\nfor (int i = 0; i &lt; tableSize; i++) {\r\nif (morseTable[i].letter == c) {\r\nconst char *p = morseTable[i].pattern;\r\nwhile (*p) {\r\nif (*p == '.') playDot();\r\nelse if (*p == '-') playDash();\r\np++;\r\n}\r\ndelay(letterGap - gap); \/\/ inter-letter spacing\r\nreturn;\r\n}\r\n}\r\n\r\nif (c == ' ') {\r\ndelay(wordGap); \/\/ word gap\r\n}\r\n}\r\n\r\n\/\/ ---- Play a full message ----\r\nvoid playMessage(const char *msg) {\r\nwhile (*msg) {\r\nplayLetter(*msg);\r\nmsg++;\r\n}\r\n}\r\n\r\nvoid setup() {\r\npinMode(SignalPin, OUTPUT);\r\ndigitalWrite(SignalPin, LOW);\r\n\r\n}\r\n\r\nvoid loop() {\r\n\r\nplayMessage(\"However difficult life may seem, there is always something you can do and succeed at.\");\r\ndelay(2000);\r\nplayMessage(\"Remember to look up at the stars and not down at your feet.\");\r\ndelay(10000);\r\n}<\/pre>\n<p>The DTMF Encoding was done with this code.\u00a0 I decided to change up the typical 0-9, A-D, * and # for a hexidecimal range instead.\u00a0 It replaces E for * and F for #.\u00a0 I wanted to see if I could decode a hex dump of the song &#8220;A Mind Is Born&#8221; from Linus Akesson.<\/p>\n<pre>\/* \r\nA sketch to demonstrate the tone() function with a character\u2010map and PlayTone()\r\nThis is a hex dump of \"A Mind Is Born\" (xxd -p a_mind_is_born.prg), https:\/\/linusakesson.net\/scene\/a-mind-is-born\/\r\nBased on a 4 x 4 keypad membrane, 0-9, A-D, * and #\r\n* represents E and # represents F\r\n\r\n01080D08##D39*323232350000001941\r\n1CD000DC000011D0*00B10330*6190#5\r\n0700##1#1441D524152515531561D529\r\n1B0#*613*613D002*620A961851CA720\r\n*03##008900C4*11D06C#C##A06D8422\r\n84D74A4B1CA8A5132930D002C61C*02#\r\n#011B002A202C910#0098A2903AAB5#3\r\n850A2DAB00B011B722B6219500A5134B\r\n0*AACB#886CC4907850BA513290#D00#\r\nA9B84714900285142907AAB5#78512A0\r\n08B70D910#8810#9A8B709910388D0#9\r\n4C7**A788*86028*21D02044*5A2#DBD\r\n02089502CAD0#88*15034CCC00A9508D\r\n11D058AD04DCA0C30D1CD4484B04A030\r\n8C18D071CB*6CB71CB6A0520A05805D5\r\n91CBD0D#2BAA02620018262012241310\r\n\r\n*\/\r\n\r\n\r\n\/\/ Specify pins that lead to positive terminal of piezo buzzer.\r\nconst int LoTone = 5; \/\/ GPIO5 - D1\r\nconst int HiTone = 4; \/\/ GPIO4 - D2\r\n\r\n\/\/ Durations\r\nconst int OnDuration = 50;\r\nconst int OffDuration = OnDuration * 2;\r\n\r\n\/\/ DTMF frequencies\r\nconst int LoFreq1 = 697;\r\nconst int LoFreq2 = 770;\r\nconst int LoFreq3 = 852;\r\nconst int LoFreq4 = 941;\r\n\r\nconst int HiFreq1 = 1209;\r\nconst int HiFreq2 = 1336;\r\nconst int HiFreq3 = 1477;\r\nconst int HiFreq4 = 1633;\r\n\r\n\/\/ Keypad layout: 1 2 3 A\r\n\/\/ 4 5 6 B\r\n\/\/ 7 8 9 C\r\n\/\/ * 0 # D\r\nconst char mapKeys[] = \"123A456B789C*0#D\";\r\n\r\n\/\/ Parallel frequency arrays for quick lookup\r\nconst int loFreqMap[16] = {\r\nLoFreq1, LoFreq1, LoFreq1, LoFreq1,\r\nLoFreq2, LoFreq2, LoFreq2, LoFreq2,\r\nLoFreq3, LoFreq3, LoFreq3, LoFreq3,\r\nLoFreq4, LoFreq4, LoFreq4, LoFreq4\r\n};\r\n\r\nconst int hiFreqMap[16] = {\r\nHiFreq1, HiFreq2, HiFreq3, HiFreq4,\r\nHiFreq1, HiFreq2, HiFreq3, HiFreq4,\r\nHiFreq1, HiFreq2, HiFreq3, HiFreq4,\r\nHiFreq1, HiFreq2, HiFreq3, HiFreq4\r\n};\r\n\r\n\/\/ Play a sequence of DTMF characters\r\nvoid PlayTone(const char* seq) {\r\nfor (int i = 0; seq[i] != '\\0'; ++i) {\r\n\/\/ find index in mapKeys\r\nconst char c = seq[i];\r\nchar* p = (char*)strchr(mapKeys, c);\r\nif (p != nullptr) {\r\nint idx = p - mapKeys;\r\ntone(LoTone, loFreqMap[idx], OnDuration);\r\ntone(HiTone, hiFreqMap[idx], OnDuration);\r\n}\r\n\/\/ pause between tones\r\ndelay(OffDuration);\r\n}\r\n}\r\n\r\nvoid setup() {\r\n\r\npinMode(LoTone, OUTPUT);\r\npinMode(HiTone, OUTPUT);\r\n\r\n}\r\n\r\nvoid loop() {\r\n\r\n\/\/ define your DTMF string here\r\nPlayTone(\"01080D08##D39*323232350000001941\");\r\nPlayTone(\"1CD000DC000011D0*00B10330*6190#5\");\r\nPlayTone(\"0700##1#1441D524152515531561D529\");\r\nPlayTone(\"1B0#*613*613D002*620A961851CA720\");\r\nPlayTone(\"*03##008900C4*11D06C#C##A06D8422\");\r\nPlayTone(\"84D74A4B1CA8A5132930D002C61C*02#\");\r\nPlayTone(\"#011B002A202C910#0098A2903AAB5#3\");\r\nPlayTone(\"850A2DAB00B011B722B6219500A5134B\");\r\nPlayTone(\"0*AACB#886CC4907850BA513290#D00#\");\r\nPlayTone(\"A9B84714900285142907AAB5#78512A0\");\r\nPlayTone(\"08B70D910#8810#9A8B709910388D0#9\");\r\nPlayTone(\"4C7**A788*86028*21D02044*5A2#DBD\");\r\nPlayTone(\"02089502CAD0#88*15034CCC00A9508D\");\r\nPlayTone(\"11D058AD04DCA0C30D1CD4484B04A030\");\r\nPlayTone(\"8C18D071CB*6CB71CB6A0520A05805D5\");\r\nPlayTone(\"91CBD0D#2BAA02620018262012241310\");\r\n\r\n\/\/ wait before repeating (optional)\r\ndelay(60000);\r\n\r\n}<\/pre>\n<p>Here is the DTMF Decoding Module code.<\/p>\n<pre>#include &lt;Arduino.h&gt;\r\n\r\n\/\/ sample parameters\r\nconst uint16_t SAMPLE_RATE = 8000; \/\/ Hz\r\nconst uint16_t N = 205; \/\/ samples per block (~25.6 ms)\r\n\r\n\/\/ Goertzel target frequencies\r\nconst float freqsLow[4] = { 697.0, 770.0, 852.0, 941.0 };\r\nconst float freqsHigh[4] = {1209.0,1336.0,1477.0,1633.0 };\r\n\r\n\/\/ Goertzel state arrays\r\nfloat coeffsLow[4], coeffsHigh[4];\r\nfloat sLow_prev[4], sLow_prev2[4];\r\nfloat sHigh_prev[4], sHigh_prev2[4];\r\n\r\n\/\/ mapping row\/col to digit\r\nconst char dtmfMap[4][4] = {\r\n{ '1','2','3','A' },\r\n{ '4','5','6','B' },\r\n{ '7','8','9','C' },\r\n{ 'E','0','F','D' }\r\n};\r\n\r\nchar lastDigit = '\\0';\r\n\r\nvoid setup() {\r\nSerial.begin(9600);\r\nfor (int i = 0; i &lt; 4; i++) {\r\nfloat \u03c9L = 2.0 * PI * freqsLow[i] \/ SAMPLE_RATE;\r\ncoeffsLow[i] = 2.0 * cos(\u03c9L);\r\nfloat \u03c9H = 2.0 * PI * freqsHigh[i] \/ SAMPLE_RATE;\r\ncoeffsHigh[i] = 2.0 * cos(\u03c9H);\r\n}\r\n}\r\n\r\nvoid loop() {\r\n\/\/ reset Goertzel states\r\nfor (int i = 0; i &lt; 4; i++) {\r\nsLow_prev[i] = sLow_prev2[i] = 0;\r\nsHigh_prev[i] = sHigh_prev2[i] = 0;\r\n}\r\n\r\nunsigned long startMicros = micros();\r\n\/\/ collect N samples\r\nfor (uint16_t n = 0; n &lt; N; n++) {\r\nwhile (micros() - startMicros &lt; (unsigned long)(n * (1000000.0 \/ SAMPLE_RATE)));\r\nfloat sample = (analogRead(A0) \/ 1024.0) - 0.5;\r\nfor (int i = 0; i &lt; 4; i++) {\r\nfloat s = sample + coeffsLow[i] * sLow_prev[i] - sLow_prev2[i];\r\nsLow_prev2[i] = sLow_prev[i];\r\nsLow_prev[i] = s;\r\ns = sample + coeffsHigh[i] * sHigh_prev[i] - sHigh_prev2[i];\r\nsHigh_prev2[i] = sHigh_prev[i];\r\nsHigh_prev[i] = s;\r\n}\r\n}\r\n\r\n\/\/ compute magnitudes\r\nfloat magLow[4], magHigh[4];\r\nfor (int i = 0; i &lt; 4; i++) {\r\nmagLow[i] = sLow_prev2[i]*sLow_prev2[i] + sLow_prev[i]*sLow_prev[i]\r\n- coeffsLow[i]*sLow_prev[i]*sLow_prev2[i];\r\nmagHigh[i] = sHigh_prev2[i]*sHigh_prev2[i] + sHigh_prev[i]*sHigh_prev[i]\r\n- coeffsHigh[i]*sHigh_prev[i]*sHigh_prev2[i];\r\n}\r\n\r\n\/\/ find best and second-best bins\r\nint bestLowIdx=-1, bestHighIdx=-1;\r\nfloat bestLow=0, secondLow=0, bestHigh=0, secondHigh=0;\r\nfor (int i = 0; i &lt; 4; i++) {\r\nfloat v = magLow[i];\r\nif (v &gt; bestLow) { secondLow = bestLow; bestLow = v; bestLowIdx = i; }\r\nelse if (v &gt; secondLow) secondLow = v;\r\nv = magHigh[i];\r\nif (v &gt; bestHigh) { secondHigh = bestHigh; bestHigh = v; bestHighIdx = i; }\r\nelse if (v &gt; secondHigh) secondHigh = v;\r\n}\r\n\r\n\/\/ threshold + ratio test\r\nconst float THRESH = 0.01;\r\nconst float RATIO = 5.0; \/\/ require best &gt; RATIO * second-best\r\nbool validLow = (bestLow &gt; THRESH &amp;&amp; bestLow &gt; secondLow * RATIO);\r\nbool validHigh = (bestHigh &gt; THRESH &amp;&amp; bestHigh &gt; secondHigh * RATIO);\r\n\r\nif (validLow &amp;&amp; validHigh) {\r\nchar digit = dtmfMap[bestLowIdx][bestHighIdx];\r\nif (digit != lastDigit) {\r\nSerial.print(\"DTMF: \");\r\nSerial.println(digit);\r\nlastDigit = digit;\r\n}\r\n} else {\r\nlastDigit = '\\0';\r\n}\r\n\r\ndelay(10);\r\n}<\/pre>\n<p>Not anywhere near as impressive as Linus&#8217;s C64 work, but I was able to get the hex dump data on the serial monitor.\u00a0 I did want to include the process of decoding DTMF with the Arduino Uno using the MT8870 module.\u00a0 It isn&#8217;t as elegant as a native ESP8266 approach, more hardware, pins used, and overall costs.\u00a0 The examples I found online contained mistakes so here is the code that worked for me.<\/p>\n<pre>\/*Define input pins for DTMF Decoder pins connection *\/\r\nvoid setup() {\r\nSerial.begin(9600); \r\npinMode(8, INPUT); \/\/ connect to Std pin\r\npinMode(9, INPUT); \/\/ connect to Q4 pin\r\npinMode(10, INPUT); \/\/ connect to Q3 pin\r\npinMode(11, INPUT); \/\/ connect to Q2 pin\r\npinMode(12, INPUT); \/\/ connect to Q1 pin\r\n}\r\n\r\nvoid loop() {\r\nuint8_t number_pressed;\r\nbool signal ; \r\nsignal = digitalRead(8);\r\nif(signal == HIGH) \/* If new pin pressed *\/\r\n{\r\ndelay(250);\r\nnumber_pressed = ( 0x00 | (digitalRead(9)&lt;&lt;0) | (digitalRead(10)&lt;&lt;1) | (digitalRead(11)&lt;&lt;2) | (digitalRead(12)&lt;&lt;3) );\r\nswitch (number_pressed)\r\n{\r\ncase 0x01:\r\nSerial.println(\"Button Pressed = 1\");\r\nbreak;\r\ncase 0x02:\r\nSerial.println(\"Button Pressed = 2\");\r\nbreak;\r\ncase 0x03:\r\nSerial.println(\"Button Pressed = 3\");\r\nbreak;\r\ncase 0x04:\r\nSerial.println(\"Button Pressed = 4\");\r\nbreak;\r\ncase 0x05:\r\nSerial.println(\"Button Pressed = 5\");\r\nbreak;\r\ncase 0x06:\r\nSerial.println(\"Button Pressed = 6\");\r\nbreak;\r\ncase 0x07:\r\nSerial.println(\"Button Pressed = 7\");\r\nbreak;\r\ncase 0x08:\r\nSerial.println(\"Button Pressed = 8\");\r\nbreak;\r\ncase 0x09:\r\nSerial.println(\"Button Pressed = 9\");\r\nbreak;\r\ncase 0x0A:\r\nSerial.println(\"Button Pressed = 0\");\r\nbreak;\r\ncase 0x0B:\r\nSerial.println(\"Button Pressed = *\");\r\nbreak;\r\ncase 0x0C:\r\nSerial.println(\"Button Pressed = #\");\r\nbreak; \r\n}\r\n}\r\n}<\/pre>\n<p><iframe loading=\"lazy\" title=\"The last Morse code maritime radio station in North America | Bartell&#039;s Backroads\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/GzN-D0yIkGQ?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>Decoding Morse code is becoming a lost art done by humans.\u00a0 It and DTMF are primarily used between machines for connections that have extreme hardware and bandwidth limitations.\u00a0 It has a rich history as the foundation for our modern digital age.\u00a0 I would recommend further reading on the subject if this interests you.<\/p>\n<p>Morse code &#8211; <a href=\"https:\/\/en.wikipedia.org\/wiki\/Morse_code\">https:\/\/en.wikipedia.org\/wiki\/Morse_code<\/a><br \/>\nThe Morse Code (CW) Radio Center of NW7US &#8211; <a href=\"https:\/\/cw.hfradio.org\/\">https:\/\/cw.hfradio.org\/<\/a><\/p>\n<p>DTMF signaling &#8211; <a href=\"https:\/\/en.wikipedia.org\/wiki\/DTMF_signaling\">https:\/\/en.wikipedia.org\/wiki\/DTMF_signaling<\/a><br \/>\nGoertzel algorithm &#8211; <a href=\"https:\/\/en.wikipedia.org\/wiki\/Goertzel_algorithm\">https:\/\/en.wikipedia.org\/wiki\/Goertzel_algorithm<\/a><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the last post, I covered how to generate audio natively with the ESP8266 module.\u00a0 The hardware to do this is rudimentary.\u00a0 I demonstrated how to generate CW (Morse Code), DTMF, synthesized speech, and audio.\u00a0 This post will expand on some of those concepts by decoding CW and DTMF signals.\u00a0 The following Fritzing wiring diagram will be the foundation for all of the code covered in this post. The Encode Module will be the signal source for the Decoding modules.\u00a0&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.cloudacm.com\/?p=5000\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-5000","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/5000","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5000"}],"version-history":[{"count":10,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/5000\/revisions"}],"predecessor-version":[{"id":5002,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/5000\/revisions\/5002"}],"wp:attachment":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5000"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5000"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5000"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}