diff --git a/.github/workflows/arduino.yml b/.github/workflows/arduino.yml index e1598bd..5c2f11d 100644 --- a/.github/workflows/arduino.yml +++ b/.github/workflows/arduino.yml @@ -24,11 +24,12 @@ jobs: fqbn: esp32:esp32:esp32:EventsCore=0 platforms: | - name: "esp32:esp32" - version: "2.0.17" + version: "3.0.5" sketch-paths: | - microcontroller-src/kv4p_ht_esp32_wroom_32 libraries: | - name: EspSoftwareSerial version: 8.1.0 - source-url: https://github.com/fatpat/arduino-dra818/archive/refs/tags/v1.0.1.zip + - source-url: https://github.com/pschatzmann/arduino-audio-tools/archive/5214dec10ebaab57d6eb485cb96b4180da476b54.zip verbose: true diff --git a/.gitignore b/.gitignore index f5dd401..81c5ac4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ android-src/KV4PHT/app/debug/* android-src/KV4PHT/app/release/* android-src/KV4PHT/usbSerialForAndroid/build/* build/ +.idea/ +.vscode/ backups/ diff --git a/android-src/KV4PHT/app/src/main/java/com/vagell/kv4pht/radio/RadioAudioService.java b/android-src/KV4PHT/app/src/main/java/com/vagell/kv4pht/radio/RadioAudioService.java index f55fa60..2350982 100644 --- a/android-src/KV4PHT/app/src/main/java/com/vagell/kv4pht/radio/RadioAudioService.java +++ b/android-src/KV4PHT/app/src/main/java/com/vagell/kv4pht/radio/RadioAudioService.java @@ -125,6 +125,21 @@ public byte getByte() { } } + private enum ESP32MsgType { + DATA((byte) 0xF0), + CMD((byte) 0x0F); + + private byte commandByte; + ESP32MsgType(byte commandByte) { + this.commandByte = commandByte; + } + + public byte getByte() { + return commandByte; + } + } + + public static final byte SILENT_BYTE = -128; // Callbacks to the Activity that started us @@ -134,7 +149,7 @@ public byte getByte() { public static final int AUDIO_SAMPLE_RATE = 44100; public static final int channelConfig = AudioFormat.CHANNEL_IN_MONO; public static final int audioFormat = AudioFormat.ENCODING_PCM_8BIT; - public static final int minBufferSize = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, channelConfig, audioFormat) * 2; + public static final int MIN_BUFFER_SIZE = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, channelConfig, audioFormat) * 8; private UsbManager usbManager; private UsbDevice esp32Device; private static UsbSerialPort serialPort; @@ -144,16 +159,13 @@ public byte getByte() { // For receiving audio from ESP32 / radio private AudioTrack audioTrack; - private static final int PRE_BUFFER_SIZE = 1000; + private static final int PRE_BUFFER_SIZE = 4096; private byte[] rxBytesPrebuffer = new byte[PRE_BUFFER_SIZE]; private int rxPrebufferIdx = 0; private boolean prebufferComplete = false; private static final float SEC_BETWEEN_SCANS = 0.5f; // how long to wait during silence to scan to next frequency in scan mode private LiveData> channelMemoriesLiveData = null; - // Delimiter must match ESP32 code - private static final byte[] COMMAND_DELIMITER = new byte[] {(byte)0xFF, (byte)0x00, (byte)0xFF, (byte)0x00, (byte)0xFF, (byte)0x00, (byte)0xFF, (byte)0x00}; - // AFSK modem private Afsk1200Modulator afskModulator = null; private PacketDemodulator afskDemodulator = null; @@ -532,7 +544,7 @@ private void initAudioTrack() { .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) .build()) .setTransferMode(AudioTrack.MODE_STREAM) - .setBufferSizeInBytes(minBufferSize) + .setBufferSizeInBytes(MIN_BUFFER_SIZE) .build(); restartAudioPrebuffer(); @@ -824,7 +836,19 @@ public void nextScan() { public void sendAudioToESP32(byte[] audioBuffer, boolean dataMode) { if (audioBuffer.length <= TX_AUDIO_CHUNK_SIZE) { - sendBytesToESP32(audioBuffer); + int numberOfBytesToSend = audioBuffer.length; + // TODO: Make this support packets of size greater that 2^8 +// byte[] commandInfo = { ESP32MsgType.DATA.getByte(), 0, numberOfBytesToSend}; +// byte[] combined = new byte[commandInfo.length + numberOfBytesToSend]; +// System.arraycopy(commandInfo, 0, combined, 0, commandInfo.length); +// System.arraycopy(audioBuffer, 0, combined, commandInfo.length, numberOfBytesToSend); +// sendBytesToESP32(combined); +// numberOfBytesToSend = 10; + byte[] commandInfo = { ESP32MsgType.DATA.getByte(), (byte)(numberOfBytesToSend >> 8), (byte)numberOfBytesToSend}; + sendBytesToESP32(commandInfo); +// byte[] fakeAudioData = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 }; +// sendBytesToESP32(fakeAudioData); + sendBytesToESP32(Arrays.copyOfRange(audioBuffer, 0, numberOfBytesToSend)); } else { // If the audio is fairly long, we need to send it to ESP32 at the same rate // as audio sampling. Otherwise, we'll overwhelm its DAC buffer and some audio will @@ -840,8 +864,23 @@ public void run() { android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_BACKGROUND + android.os.Process.THREAD_PRIORITY_MORE_FAVORABLE); - sendBytesToESP32(Arrays.copyOfRange(audioBuffer, chunkStart, - Math.min(audioBuffer.length, chunkStart + TX_AUDIO_CHUNK_SIZE))); + int numberOfBytesToSend = Math.min(audioBuffer.length - chunkStart, TX_AUDIO_CHUNK_SIZE); + if (numberOfBytesToSend <= 0) { + // Something has occurred + } else { + // TODO: Make this support packets of size greater that 2^8 +// byte[] commandInfo = { ESP32MsgType.DATA.getByte(), (byte)(numberOfBytesToSend >> 8), (byte)numberOfBytesToSend}; +// byte[] combined = new byte[commandInfo.length + numberOfBytesToSend]; +// System.arraycopy(commandInfo, 0, combined, 0, commandInfo.length); +// System.arraycopy(audioBuffer, chunkStart, combined, commandInfo.length, numberOfBytesToSend); +// sendBytesToESP32(combined); +// numberOfBytesToSend = 10; + byte[] commandInfo = { ESP32MsgType.DATA.getByte(), (byte)(numberOfBytesToSend >> 8), (byte)numberOfBytesToSend}; + sendBytesToESP32(commandInfo); +// byte[] fakeAudioData = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 }; +// sendBytesToESP32(fakeAudioData); + sendBytesToESP32(Arrays.copyOfRange(audioBuffer, chunkStart, chunkStart + numberOfBytesToSend)); + } } }, (int) nextSendDelay); @@ -864,17 +903,13 @@ public void run() { } public void sendCommandToESP32(ESP32Command command) { - byte[] commandArray = { COMMAND_DELIMITER[0], COMMAND_DELIMITER[1], - COMMAND_DELIMITER[2], COMMAND_DELIMITER[3], COMMAND_DELIMITER[4], COMMAND_DELIMITER[5], - COMMAND_DELIMITER[6], COMMAND_DELIMITER[7], command.getByte() }; + byte[] commandArray = { ESP32MsgType.CMD.getByte(), command.getByte() }; sendBytesToESP32(commandArray); Log.d("DEBUG", "Sent command: " + command); } public void sendCommandToESP32(ESP32Command command, String paramsStr) { - byte[] commandArray = { COMMAND_DELIMITER[0], COMMAND_DELIMITER[1], - COMMAND_DELIMITER[2], COMMAND_DELIMITER[3], COMMAND_DELIMITER[4], COMMAND_DELIMITER[5], - COMMAND_DELIMITER[6], COMMAND_DELIMITER[7], command.getByte() }; + byte[] commandArray = { ESP32MsgType.CMD.getByte(), command.getByte() }; byte[] combined = new byte[commandArray.length + paramsStr.length()]; ByteBuffer buffer = ByteBuffer.wrap(combined); buffer.put(commandArray); @@ -944,28 +979,27 @@ public static UsbSerialPort getUsbSerialPort() { private void handleESP32Data(byte[] data) { // Log.d("DEBUG", "Got bytes from ESP32: " + Arrays.toString(data)); - /* try { - String dataStr = new String(data, "UTF-8"); - if (dataStr.length() < 100 && dataStr.length() > 0) - Log.d("DEBUG", "Str data from ESP32: " + dataStr); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } */ + // try { + // String logDataStr = new String(data, StandardCharsets.UTF_8); + // if (logDataStr.length() < 100 && logDataStr.length() > 0) + // Log.d("DEBUG", "Str data from ESP32: " + logDataStr); + // } catch (UnsupportedEncodingException e) { + // throw new RuntimeException(e); + // } // Log.d("DEBUG", "Num bytes from ESP32: " + data.length); if (mode == MODE_STARTUP) { - try { - String dataStr = new String(data, "UTF-8"); - versionStrBuffer += dataStr; - if (versionStrBuffer.contains(VERSION_PREFIX)) { - int startIdx = versionStrBuffer.indexOf(VERSION_PREFIX) + VERSION_PREFIX.length(); - String verStr = ""; - try { - verStr = versionStrBuffer.substring(startIdx, startIdx + VERSION_LENGTH); - } catch (IndexOutOfBoundsException iobe) { - return; // Version string not yet fully received. - } - int verInt = Integer.parseInt(verStr); + String dataStr = new String(data, StandardCharsets.UTF_8); + versionStrBuffer += dataStr; + if (versionStrBuffer.contains(VERSION_PREFIX)) { + int startIdx = versionStrBuffer.indexOf(VERSION_PREFIX) + VERSION_PREFIX.length(); + String verStr = ""; + try { + verStr = versionStrBuffer.substring(startIdx, startIdx + VERSION_LENGTH); + } catch (IndexOutOfBoundsException iobe) { + return; // Version string not yet fully received. + } + int verInt = Integer.parseInt(verStr); if (verInt < FirmwareUtils.PACKAGED_FIRMWARE_VER) { Log.d("DEBUG", "Error: ESP32 app firmware " + verInt + " is older than latest firmware " + FirmwareUtils.PACKAGED_FIRMWARE_VER); if (callbacks != null) { @@ -977,10 +1011,7 @@ private void handleESP32Data(byte[] data) { versionStrBuffer = ""; // Reset the version string buffer for next USB reconnect. initAfterESP32Connected(); } - return; - } - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); + return; } } @@ -1015,10 +1046,15 @@ private void handleESP32Data(byte[] data) { synchronized (audioTrack) { audioTrack.write(rxBytesPrebuffer, 0, PRE_BUFFER_SIZE); } + + synchronized (audioTrack) { + // write the remaining audio bytes from data[] so we don't drop them + audioTrack.write(data, i+1, data.length - i); + } } rxPrebufferIdx = 0; - break; // Might drop a few audio bytes from data[], should be very minimal + break; } } } diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/.gitignore b/microcontroller-src/kv4p_ht_esp32_wroom_32/.gitignore new file mode 100644 index 0000000..94f90d1 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/.gitignore @@ -0,0 +1 @@ +doxygen/ diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/CommandValueEnum.hpp b/microcontroller-src/kv4p_ht_esp32_wroom_32/CommandValueEnum.hpp new file mode 100644 index 0000000..03dfda0 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/CommandValueEnum.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +// Commands defined here must match the Android app +enum class CommandValue : uint8_t +{ + COMMAND_PTT_DOWN = 1, // start transmitting audio that Android app will send + COMMAND_PTT_UP = 2, // stop transmitting audio, go into RX mode + COMMAND_TUNE_TO = 3, // change the frequency + COMMAND_FILTERS = 4, // toggle filters on/off + COMMAND_STOP = 5, // stop everything, just wait for next command + COMMAND_GET_FIRMWARE_VER = 6 // report FIRMWARE_VER in the format '00000001' for 1 (etc.) +}; diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/Constants.cpp b/microcontroller-src/kv4p_ht_esp32_wroom_32/Constants.cpp new file mode 100644 index 0000000..e69de29 diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/Constants.hpp b/microcontroller-src/kv4p_ht_esp32_wroom_32/Constants.hpp new file mode 100644 index 0000000..534afd5 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/Constants.hpp @@ -0,0 +1,45 @@ +#pragma once + +// Audio sampling rate, must match what Android app expects (and sends). +#define AUDIO_SAMPLE_RATE 44100 + +// Offset to make up for fact that sampling is slightly slower than requested, and we don't want underruns. +// But if this is set too high, then we get audio skips instead of underruns. So there's a sweet spot. +#define SAMPLING_RATE_OFFSET 218 + +// Buffer for outgoing audio bytes to send to radio module +#define TX_TEMP_AUDIO_BUFFER_SIZE 4096 // Holds data we already got off of USB serial from Android app +#define TX_CACHED_AUDIO_BUFFER_SIZE 1024 // MUST be smaller than DMA buffer size specified in i2sTxConfig, because we dump this cache into DMA buffer when full. + +#define TX_AUDIO_CHUNK_SIZE 512 + +// Max data to cache from USB (1024 is ESP32 max) +#define USB_BUFFER_SIZE 1024 + +// ms to wait before issuing PTT UP after a tx (to allow final audio to go out) +#define MS_WAIT_BEFORE_PTT_UP 40 + +// Connections to radio module +#define RXD2_PIN 16 +#define TXD2_PIN 17 +#define DAC_PIN 25 // This constant not used, just here for reference. GPIO 25 is implied by use of I2S_DAC_CHANNEL_RIGHT_EN. +#define ADC_PIN 34 // If this is changed, you may need to manually edit adc1_config_channel_atten() below too. +#define PTT_PIN 18 +#define PD_PIN 19 +#define SQ_PIN 32 + +// Built in LED +#define LED_PIN 2 + +// Tx runaway detection stuff +#define RUNAWAY_TX_SEC 200 + +// I2S audio sampling stuff +#define I2S_READ_LEN 1024 +#define I2S_WRITE_LEN 1024 +#define I2S_ADC_UNIT ADC_UNIT_1 +#define I2S_ADC_CHANNEL ADC1_CHANNEL_6 + +// Squelch parameters (for graceful fade to silence) +#define FADE_SAMPLES 256 // Must be a power of two +#define ATTENUATION_MAX 256 diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/Doxyfile b/microcontroller-src/kv4p_ht_esp32_wroom_32/Doxyfile new file mode 100644 index 0000000..0654418 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/Doxyfile @@ -0,0 +1,409 @@ +# Doxyfile 1.9.8 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "My Project" +PROJECT_NUMBER = +PROJECT_BRIEF = +PROJECT_LOGO = +OUTPUT_DIRECTORY = doxygen/ +CREATE_SUBDIRS = NO +CREATE_SUBDIRS_LEVEL = 8 +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +JAVADOC_BANNER = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +PYTHON_DOCSTRING = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +OPTIMIZE_OUTPUT_SLICE = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +TOC_INCLUDE_HEADINGS = 5 +MARKDOWN_ID_STYLE = DOXYGEN +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +NUM_PROC_THREADS = 1 +TIMESTAMP = NO +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_PRIV_VIRTUAL = YES +EXTRACT_PACKAGE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = YES +EXTRACT_ANON_NSPACES = YES +RESOLVE_UNNAMED_PARAMS = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = SYSTEM +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_HEADERFILE = YES +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_IF_INCOMPLETE_DOC = YES +WARN_NO_PARAMDOC = NO +WARN_IF_UNDOC_ENUM_VAL = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LINE_FORMAT = "at line $line of file $file" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = +INPUT_ENCODING = UTF-8 +INPUT_FILE_ENCODING = +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cxxm \ + *.cpp \ + *.cppm \ + *.c++ \ + *.c++m \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.ixx \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = +FORTRAN_COMMENT_AFTER = 72 +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_ADD_INC_PATHS = YES +CLANG_OPTIONS = +CLANG_DATABASE_PATH = +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE = AUTO_LIGHT +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_DYNAMIC_MENUS = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_CODE_FOLDING = YES +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_FEEDURL = +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +SITEMAP_URL = +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +FULL_SIDEBAR = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +OBFUSCATE_EMAILS = YES +HTML_FORMULA_FORMAT = png +FORMULA_FONTSIZE = 10 +FORMULA_MACROFILE = +USE_MATHJAX = NO +MATHJAX_VERSION = MathJax_2 +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = +MAKEINDEX_CMD_NAME = makeindex +LATEX_MAKEINDEX_CMD = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_BIB_STYLE = plain +LATEX_EMOJI_DIRECTORY = +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +XML_NS_MEMB_FILE_SCOPE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- +GENERATE_SQLITE3 = NO +SQLITE3_OUTPUT = sqlite3 +SQLITE3_RECREATE_DB = YES +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +#--------------------------------------------------------------------------- +# Configuration options related to diagram generator tools +#--------------------------------------------------------------------------- +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +DOT_NUM_THREADS = 0 +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = YES +UML_LIMIT_NUM_FIELDS = 10 +DOT_UML_DETAILS = YES +DOT_WRAP_THRESHOLD = 80 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = YES +CALLER_GRAPH = YES +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DIR_GRAPH_MAX_DEPTH = 1 +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +DIA_PATH = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_CFG_FILE = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +MSCGEN_TOOL = +MSCFILE_DIRS = diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/ModeEnum.hpp b/microcontroller-src/kv4p_ht_esp32_wroom_32/ModeEnum.hpp new file mode 100644 index 0000000..bfc5b85 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/ModeEnum.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +enum class Mode : u_int8_t +{ + MODE_TX = 0, + MODE_RX = 1, + MODE_STOPPED = 2 +}; \ No newline at end of file diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/MsgTypeEnum.hpp b/microcontroller-src/kv4p_ht_esp32_wroom_32/MsgTypeEnum.hpp new file mode 100644 index 0000000..bb50630 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/MsgTypeEnum.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +enum class MsgType : uint8_t +{ + DATA = 0xF0, + CMD = 0x0F, + DEFAULT_CMD = 0 +}; diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/README.md b/microcontroller-src/kv4p_ht_esp32_wroom_32/README.md new file mode 100644 index 0000000..5796b5c --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/README.md @@ -0,0 +1,39 @@ +# This intends to refactor KV4P-HT to use the AudioTools library + +## Todo: + +### Rx Todo + +- [ ] Configure both the input and output DACs to switch between input and output +- [ ] Configure the input sound to come from the DAC + +### Tx Dodo + +- [ ] Configure the output sound to come from the 8 bit stream from Serial + +## Requirements + +We want to create an ESP32 program that can either transmit or receive audio data over a serial connection, using the AudioTools library for audio processing. Here are the key points: + +- **Hardware**: ESP32 DevKit V1 with built-in ADC and DAC. +- **Audio**: Mono, 44100Hz sample rate, 8-bit PCM format. +- **Serial**: 921600 baud rate. +- **Modes**: + - **RX Mode**: Capture audio from the ADC and send it over serial. + - **TX Mode**: Receive audio data over serial and play it through the DAC. +- **Serial Protocol**: Simple command/data protocol with length indication. + +## Implementation + +We'll use the AudioTools library to manage the audio streams and conversions. Here's the general approach: + +### 1. Initialization + +1. Configure the Serial port between the ESP32 and the App +2. Setup the Watchdog timer +3. Setup the status LED +4. Setup the DRA818 control interface on Serial2 +5. Setup the AudioTools + 1. Setup the Rx streams + 2. Setup the Tx streams + 3. Initialize the buffered audio stream diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/README.md b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/README.md new file mode 100644 index 0000000..54a7289 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/README.md @@ -0,0 +1,25 @@ +# KV4P-HT - ESP32 Firmware + +## Brief + +This firmware has been developed for use on the ESP32 as part of the KV4P-HT project. + +The goal of this firmware is to only be an interface for the app to send and receive an audio signal to and from the DRA818 module. + +## How to Use This Documentation + +This documentation will make use of PLantUML diagrams. You can generate these diagrams by using any PlantUML tool such as [planttext.com](https://www.planttext.com/). + +## Design + +Here is the activity diagram for the ESP32 code: + +![Total Activity Diagram](diagrams/AD_Total.png) + +## Doxygen + +You can get class diagrams and technical documentation in Doxygen. Run the following command to generate the documenation: + +```bash +doxygen +``` diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_RX.uml b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_RX.uml new file mode 100644 index 0000000..eb1ad61 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_RX.uml @@ -0,0 +1,21 @@ +@startuml + +|Radio| +start +:loop; +:ReadByte; + +if (MessageType is MSG ?) then (yes) +|CommandProcessor| +:Read CMD MSG Length; +:Determine Command Type; +|Radio| +:ChangeState; +else (no) +|AudioProcessor| +:Copy available samples to the Encoded Output: +endif +|Radio| +stop + +@enduml diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_STOPPED.uml b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_STOPPED.uml new file mode 100644 index 0000000..624b7c7 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_STOPPED.uml @@ -0,0 +1,19 @@ +@startuml + +|Radio| +start +:loop; +:ReadByte; + +if (MessageType is MSG ?) then (yes) +|CommandProcessor| +:Read CMD MSG Length; +:Determine Command Type; +|Radio| +:ChangeState; +else (no) +endif +|Radio| +stop + +@enduml diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_TX.uml b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_TX.uml new file mode 100644 index 0000000..04b5f8f --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_TX.uml @@ -0,0 +1,29 @@ +@startuml + +|Radio| +start +:loop; +:ReadByte; + +if (MessageType is MSG ?) then (yes) +|CommandProcessor| +:Read CMD MSG Length; +:Determine Command Type; +|Radio| +:ChangeState; +else (no) +|AudioProcessor| +:Determine DataLength; + +repeat + :Read Serial into Buffer; +repeat while (Data Read < DataLength) + +|AudioProcessor| +:write Buffer to Encoded Input Stream; +:Copy available bytes to outputStream; +endif +|Radio| +stop + +@enduml diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_Total.png b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_Total.png new file mode 100644 index 0000000..d655198 Binary files /dev/null and b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_Total.png differ diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_Total.uml b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_Total.uml new file mode 100644 index 0000000..faa2eb8 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/AD_Total.uml @@ -0,0 +1,44 @@ +@startuml + +|Radio| +start +:loop; +:ReadByte; + +if (MessageType is MSG ?) then (yes) +|CommandProcessor| +:Read CMD MSG Length; +:Determine Command Type; +|Radio| +:ChangeState; +else (no) + +|Radio| +if (Mode is RX?) then (yes) +|AudioProcessor| + +repeat + :Read Data from DRA818 into Buffer; +repeat while (Data Read < Desired RX Packet Length) + +|AudioProcessor| +:write Buffer to Serial; +else(no) +if ( Mode is TX? ) then (yes) +|AudioProcessor| +:Determine DataLength; + +repeat + :Read Serial into Buffer; +repeat while (Data Read < DataLength) + +|AudioProcessor| +:write Buffer to DRA818; +else (no) +endif +endif +endif +|Radio| +stop + +@enduml diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/Msg_Data.uml b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/Msg_Data.uml new file mode 100644 index 0000000..b7c51d7 --- /dev/null +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/docs/diagrams/Msg_Data.uml @@ -0,0 +1,67 @@ +@startuml + +enum MsgType { + DATA = 0xF0 + CMD = 0x0F +} + +enum CommandType { + COMMAND_PTT_DOWN + COMMAND_PTT_UP + COMMAND_TUNE_TO + COMMAND_FILTERS + COMMAND_STOP + COMMAND_GET_FIRMWARE_VER +} + +object MsgHeader { + MsgType (1 Byte) +} + +object "DataMsg **Byte => Field**" as DataMsg { + 0 => MsgHeader = MsgType.DATA + 1..2 => DataLengthInBytes + 3..(3+DataLengthInBytes) => Data[DataLengthInBytes] +} + +object "Max Msg Length" as note { + 4096 Bytes +} + +object "TXCmdMsg **Byte => Field**" as TxCmdMsg { + 0 => MsgHeader = MsgType.CMD + 1 => CommandType = CommandType.COMMAND_PTT_DOWN +} + +object "RXCmdMsg **Byte => Field**" as RxCmdMsg { + 0 => MsgHeader = MsgType.CMD + 1 => CommandType = CommandType.COMMAND_PTT_UP +} + +object "TuneCmdMsg **Byte => Field**" as TuneCmdMsg { + 0 => MsgHeader = MsgType.CMD + 1 => CommandType = CommandType.COMMAND_TUNE_TO + 2..9 => TXFreq + 10..17 => RXFreq +} + +object "FiltersCmdMsg **Byte => Field**" as FiltersCmdMsg { + 0 => MsgHeader = MsgType.CMD + 1 => CommandType = CommandType.COMMAND_FILTERS + 2 => Emphasis + 3 => HighPass + 4 => LowPass +} + +object "StopCmdMsg **Byte => Field**" as StopCmdMsg { + 0 => MsgHeader = MsgType.CMD + 1 => CommandType = CommandType.COMMAND_STOP +} + +object "GetFirmwareVerCmdMsg **Byte => Field**" as GetFirmwareVerCmdMsg { + 0 => MsgHeader = MsgType.CMD + 1 => CommandType = CommandType.COMMAND_GET_FIRMWARE_VER +} + + +@enduml diff --git a/microcontroller-src/kv4p_ht_esp32_wroom_32/kv4p_ht_esp32_wroom_32.ino b/microcontroller-src/kv4p_ht_esp32_wroom_32/kv4p_ht_esp32_wroom_32.ino index cfdf0da..2d29230 100644 --- a/microcontroller-src/kv4p_ht_esp32_wroom_32/kv4p_ht_esp32_wroom_32.ino +++ b/microcontroller-src/kv4p_ht_esp32_wroom_32/kv4p_ht_esp32_wroom_32.ino @@ -20,555 +20,468 @@ along with this program. If not, see . #include #include +#if defined(ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0 , 0) #include #include +#endif #include -const byte FIRMWARE_VER[8] = {'0', '0', '0', '0', '0', '0', '0', '2'}; // Should be 8 characters representing a zero-padded version, like 00000001. -const byte VERSION_PREFIX[7] = {'V', 'E', 'R', 'S', 'I', 'O', 'N'}; // Must match RadioAudioService.VERSION_PREFIX in Android app. +// Headers +#include "CommandValueEnum.hpp" +#include "ModeEnum.hpp" +#include "MsgTypeEnum.hpp" +#include "Constants.hpp" -// Commands defined here must match the Android app -const uint8_t COMMAND_PTT_DOWN = 1; // start transmitting audio that Android app will send -const uint8_t COMMAND_PTT_UP = 2; // stop transmitting audio, go into RX mode -const uint8_t COMMAND_TUNE_TO = 3; // change the frequency -const uint8_t COMMAND_FILTERS = 4; // toggle filters on/off -const uint8_t COMMAND_STOP = 5; // stop everything, just wait for next command -const uint8_t COMMAND_GET_FIRMWARE_VER = 6; // report FIRMWARE_VER in the format '00000001' for 1 (etc.) +// https://github.com/pschatzmann/arduino-audio-tools/ +#include "AudioTools.h" -// Delimeter must also match Android app -#define DELIMITER_LENGTH 8 -const uint8_t delimiter[DELIMITER_LENGTH] = {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}; -int matchedDelimiterTokens = 0; +// #define SERIAL_TRACE_LOGGING +// #define START_IN_RX_MODE -// Mode of the app, which is essentially a state machine -#define MODE_TX 0 -#define MODE_RX 1 -#define MODE_STOPPED 2 -int mode = MODE_STOPPED; - -// Audio sampling rate, must match what Android app expects (and sends). -#define AUDIO_SAMPLE_RATE 44100 - -// Offset to make up for fact that sampling is slightly slower than requested, and we don't want underruns. -// But if this is set too high, then we get audio skips instead of underruns. So there's a sweet spot. -#define SAMPLING_RATE_OFFSET 218 - -// Buffer for outgoing audio bytes to send to radio module -#define TX_TEMP_AUDIO_BUFFER_SIZE 4096 // Holds data we already got off of USB serial from Android app -#define TX_CACHED_AUDIO_BUFFER_SIZE 1024 // MUST be smaller than DMA buffer size specified in i2sTxConfig, because we dump this cache into DMA buffer when full. -uint8_t txCachedAudioBuffer[TX_CACHED_AUDIO_BUFFER_SIZE] = {0}; -int txCachedAudioBytes = 0; -boolean isTxCacheSatisfied = false; // Will be true when the DAC has enough cached tx data to avoid any stuttering (i.e. at least TX_CACHED_AUDIO_BUFFER_SIZE bytes). - -// Max data to cache from USB (1024 is ESP32 max) -#define USB_BUFFER_SIZE 1024 - -// ms to wait before issuing PTT UP after a tx (to allow final audio to go out) -#define MS_WAIT_BEFORE_PTT_UP 40 - -// Connections to radio module -#define RXD2_PIN 16 -#define TXD2_PIN 17 -#define DAC_PIN 25 // This constant not used, just here for reference. GPIO 25 is implied by use of I2S_DAC_CHANNEL_RIGHT_EN. -#define ADC_PIN 34 // If this is changed, you may need to manually edit adc1_config_channel_atten() below too. -#define PTT_PIN 18 -#define PD_PIN 19 -#define SQ_PIN 32 - -// Built in LED -#define LED_PIN 2 - -// Object used for radio module serial comms -DRA818* dra = new DRA818(&Serial2, DRA818_VHF); - -// Tx runaway detection stuff -long txStartTime = -1; -#define RUNAWAY_TX_SEC 200 +//////////////////////////////////////////////////////////////////////////////// +/// AudioTools Globals +//////////////////////////////////////////////////////////////////////////////// +// #define AUDIO_USE_SIN_FOR_TESTING -// have we installed an I2S driver at least once? -bool i2sStarted = false; +#if defined(ESP32) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0 , 0) +AudioInfo info(44300, 1, 16); +#else +AudioInfo info(44318, 1, 16); +#endif -// I2S audio sampling stuff -#define I2S_READ_LEN 1024 -#define I2S_WRITE_LEN 1024 -#define I2S_ADC_UNIT ADC_UNIT_1 -#define I2S_ADC_CHANNEL ADC1_CHANNEL_6 +#ifndef AUDIO_USE_SIN_FOR_TESTING +AnalogAudioStream in; +#else +SineWaveGenerator sineWave(32000); +GeneratedSoundStream in(sineWave); +#endif -// Squelch parameters (for graceful fade to silence) -#define FADE_SAMPLES 256 // Must be a power of two -#define ATTENUATION_MAX 256 -int fadeCounter = 0; -int fadeDirection = 0; // 0: no fade, 1: fade in, -1: fade out -int attenuation = ATTENUATION_MAX; // Full volume -bool lastSquelched = false; +auto &serial = Serial; +EncoderL8 enc; +EncodedAudioStream enc_stream(&serial, &enc); +// Throttle throttle(enc_stream); +StreamCopy copierOut(enc_stream, in); // copies sound into Serial +Task rxCopyTask("rxCopy", 3000, 10); //////////////////////////////////////////////////////////////////////////////// -/// Forward Declarations +/// Application Globals //////////////////////////////////////////////////////////////////////////////// -void initI2SRx(); -void initI2STx(); -void tuneTo(float freqTx, float freqRx, int tone, int squelch); -void setMode(int newMode); -void processTxAudio(uint8_t tempBuffer[], int bytesRead); - +const byte FIRMWARE_VER[8] = {'0', '0', '0', '0', '0', '0', '0', '1'}; // Should be 8 characters representing a zero-padded version, like 00000001. +const byte VERSION_PREFIX[7] = {'V', 'E', 'R', 'S', 'I', 'O', 'N'}; // Must match RadioAudioService.VERSION_PREFIX in Android app. +// Mode of the app, which is essentially a state machine +Mode mode = Mode::MODE_STOPPED; -void setup() { - // Communication with Android via USB cable - Serial.begin(921600); - Serial.setRxBufferSize(USB_BUFFER_SIZE); - Serial.setTxBufferSize(USB_BUFFER_SIZE); +// Object used for radio module serial comms +DRA818 *dra = new DRA818(&Serial2, DRA818_VHF); - // Configure watch dog timer (WDT), which will reset the system if it gets stuck somehow. - esp_task_wdt_init(10, true); // Reboot if locked up for a bit - esp_task_wdt_add(NULL); // Add the current task to WDT watch +//////////////////////////////////////////////////////////////////////////////// +/// Forward Declarations +//////////////////////////////////////////////////////////////////////////////// - // Debug LED - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); +/// Setup Functions +void setInitialState(); - // Set up radio module defaults - pinMode(PD_PIN, OUTPUT); - digitalWrite(PD_PIN, HIGH); // Power on - pinMode(SQ_PIN, INPUT); - pinMode(PTT_PIN, OUTPUT); - digitalWrite(PTT_PIN, HIGH); // Rx +void setupSerial(); +void setupWDT(); +void setupLED(); +void setupDRA818(); +void setupAudioTools(); - // Communication with DRA818V radio module via GPIO pins - Serial2.begin(9600, SERIAL_8N1, RXD2_PIN, TXD2_PIN); +/// State Transition Functions +void setMode(Mode newMode); +void handleCMD(CommandValue command); +void handleDATA(); +int16_t getLengthOfDataToRead(); +void tuneTo(float freqTx, float freqRx, int tone, int squelch); +void stopTx(); +void startTx(); +void stopRx(); +void startRx(); + +void setup() +{ + setupSerial(); + // AudioLogger::instance().begin(Serial, AudioLogger::Debug); + AudioLogger::instance().begin(Serial, AudioLogger::Error); + setupWDT(); + setupLED(); + setupDRA818(); + setInitialState(); + setupAudioTools(); + +#ifdef START_IN_RX_MODE + tuneTo(146.700, 146.700, 0, 0); + startRx(); +#endif // START_IN_RX_MODE +} - int result = -1; - while (result != 1) { - result = dra->handshake(); // Wait for module to start up +MsgType msgType; + +uint8_t cmdByte[1]; + +void handleIncomingSerial() +{ + Serial.readBytes(cmdByte, 1); + msgType = static_cast(cmdByte[0]); +#ifdef SERIAL_TRACE_LOGGING + Serial.println("Message Received: " + String(static_cast(msgType))); +#endif + switch (msgType) + { + case MsgType::CMD: + handleCMD(static_cast(Serial.read())); + break; + case MsgType::DATA: + handleDATA(); + break; + default: + Serial.flush(); + break; } - // Serial.println("handshake: " + String(result)); - // tuneTo(146.700, 146.700, 0, 0); - result = dra->volume(8); - // Serial.println("volume: " + String(result)); - result = dra->filters(false, false, false); - // Serial.println("filters: " + String(result)); - - // Begin in STOPPED mode - setMode(MODE_STOPPED); } -void initI2SRx() { - // Remove any previous driver (rx or tx) that may have been installed. - if (i2sStarted) { - i2s_driver_uninstall(I2S_NUM_0); +void loop() +{ + if (Serial.available()) + { + handleIncomingSerial(); } - i2sStarted = true; - - // Initialize ADC - adc1_config_width(ADC_WIDTH_BIT_12); - adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_0); - - static const i2s_config_t i2sRxConfig = { - .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), - .sample_rate = AUDIO_SAMPLE_RATE + SAMPLING_RATE_OFFSET, - .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, - .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, - .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, - .dma_buf_count = 4, - .dma_buf_len = I2S_READ_LEN, - .use_apll = true, - .tx_desc_auto_clear = false, - .fixed_mclk = 0 - }; - - ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &i2sRxConfig, 0, NULL)); - ESP_ERROR_CHECK(i2s_set_adc_mode(I2S_ADC_UNIT, I2S_ADC_CHANNEL)); + copierOut.copy(); + +#if defined(ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0 , 0) + esp_task_wdt_reset(); +#endif } -void initI2STx() { - // Remove any previous driver (rx or tx) that may have been installed. - if (i2sStarted) { - i2s_driver_uninstall(I2S_NUM_0); +void setMode(Mode newMode) +{ +#ifdef SERIAL_TRACE_LOGGING + Serial.println("SetMode " + String(static_cast(newMode))); +#endif + if (mode == newMode) + { + return; + } + if (Mode::MODE_TX == mode) + { + stopTx(); + } + else if (Mode::MODE_RX == mode) + { + stopRx(); + } + mode = newMode; + switch (mode) + { + case Mode::MODE_STOPPED: + digitalWrite(LED_PIN, LOW); + digitalWrite(PTT_PIN, HIGH); + break; + case Mode::MODE_RX: + digitalWrite(LED_PIN, LOW); + digitalWrite(PTT_PIN, HIGH); + startRx(); + break; + case Mode::MODE_TX: + digitalWrite(LED_PIN, HIGH); + digitalWrite(PTT_PIN, LOW); + startTx(); + break; } - i2sStarted = true; - - static const i2s_config_t i2sTxConfig = { - .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), - .sample_rate = AUDIO_SAMPLE_RATE, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, - .intr_alloc_flags = 0, - .dma_buf_count = 8, - .dma_buf_len = I2S_WRITE_LEN, - .use_apll = true - }; - - i2s_driver_install(I2S_NUM_0, &i2sTxConfig, 0, NULL); - i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN); } -void loop() { - try { - if (mode == MODE_STOPPED) { - // Read a command from Android app - uint8_t tempBuffer[100]; // Big enough for a command and params, won't hold audio data - int bytesRead = 0; - - while (bytesRead < (DELIMITER_LENGTH + 1)) { // Read the delimiter and the command byte only (no params yet) - if (Serial.available()) { - tempBuffer[bytesRead++] = Serial.read(); - } - } - switch (tempBuffer[DELIMITER_LENGTH]) { - case COMMAND_STOP: - { - Serial.flush(); - } - break; - case COMMAND_GET_FIRMWARE_VER: - { - Serial.write(VERSION_PREFIX, sizeof(VERSION_PREFIX)); - Serial.write(FIRMWARE_VER, sizeof(FIRMWARE_VER)); - Serial.flush(); - esp_task_wdt_reset(); - return; - } - break; - // TODO get rid of the code duplication here and in MODE_RX below to handle COMMAND_TUNE_TO and COMMAND_FILTERS. - // Should probably just have one standardized way to read any incoming bytes from Android app here, and handle - // commands appropriately. Or at least extract the business logic from them to avoid that duplication. - case COMMAND_TUNE_TO: - { - // Example: - // 145.450144.850061 - // 8 chars for tx, 8 chars for rx, 2 chars for tone, 1 char for squelch (19 bytes total for params) - setMode(MODE_RX); - - // If we haven't received all the parameters needed for COMMAND_TUNE_TO, wait for them before continuing. - // This can happen if ESP32 has pulled part of the command+params from the buffer before Android has completed - // putting them in there. If so, we take byte-by-byte until we get the full params. - int paramBytesMissing = 19; - String paramsStr = ""; - if (paramBytesMissing > 0) { - uint8_t paramPartsBuffer[paramBytesMissing]; - for (int j = 0; j < paramBytesMissing; j++) { - unsigned long waitStart = micros(); - while (!Serial.available()) { - // Wait for a byte. - if ((micros() - waitStart) > 500000) { // Give the Android app 0.5 second max before giving up on the command - esp_task_wdt_reset(); - return; - } - } - paramPartsBuffer[j] = Serial.read(); - } - paramsStr += String((char *)paramPartsBuffer); - paramBytesMissing--; - } - - float freqTxFloat = paramsStr.substring(0, 8).toFloat(); - float freqRxFloat = paramsStr.substring(8, 16).toFloat(); - int toneInt = paramsStr.substring(16, 18).toInt(); - int squelchInt = paramsStr.substring(18, 19).toInt(); - - // Serial.println("PARAMS: " + paramsStr.substring(0, 16) + " freqTxFloat: " + String(freqTxFloat) + " freqRxFloat: " + String(freqRxFloat) + " toneInt: " + String(toneInt)); - - tuneTo(freqTxFloat, freqRxFloat, toneInt, squelchInt); - } - break; - case COMMAND_FILTERS: +void handleCMD(CommandValue command) +{ +#ifdef SERIAL_TRACE_LOGGING + Serial.println("Handle CMD " + String(static_cast(command))); +#endif + // TODO: Convert this to be a Metadata Callback from Binary + switch (command) + { + case CommandValue::COMMAND_PTT_DOWN: + { + // output->start(I2S_NUM_0, i2sPins, sampleSource); + setMode(Mode::MODE_TX); + } + break; + case CommandValue::COMMAND_PTT_UP: + { + // output->stop(I2S_NUM_0); + setMode(Mode::MODE_RX); + } + break; + case CommandValue::COMMAND_TUNE_TO: + { + // Example: + // 145.450144.850061 + // 7 chars for tx, 7 chars for rx, 2 chars for tone, 1 char for squelch (17 bytes total for params) + setMode(Mode::MODE_RX); + + // If we haven't received all the parameters needed for COMMAND_TUNE_TO, wait for them before continuing. + // This can happen if ESP32 has pulled part of the command+params from the buffer before Android has completed + // putting them in there. If so, we take byte-by-byte until we get the full params. + int paramBytesMissing = 17; + String paramsStr = ""; + if (paramBytesMissing > 0) + { + uint8_t paramPartsBuffer[paramBytesMissing]; + for (int j = 0; j < paramBytesMissing; j++) + { + unsigned long waitStart = micros(); + while (!Serial.available()) { - int paramBytesMissing = 3; // e.g. 000, in order of emphasis, highpass, lowpass - String paramsStr = ""; - if (paramBytesMissing > 0) { - uint8_t paramPartsBuffer[paramBytesMissing]; - for (int j = 0; j < paramBytesMissing; j++) { - unsigned long waitStart = micros(); - while (!Serial.available()) { - // Wait for a byte. - if ((micros() - waitStart) > 500000) { // Give the Android app 0.5 second max before giving up on the command - esp_task_wdt_reset(); - return; - } - } - paramPartsBuffer[j] = Serial.read(); - } - paramsStr += String((char *)paramPartsBuffer); - paramBytesMissing--; + // Wait for a byte. + if ((micros() - waitStart) > 500000) + { // Give the Android app 0.5 second max before giving up on the command +#if defined(ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0 , 0) + esp_task_wdt_reset(); +#endif + return; } - bool emphasis = (paramsStr.charAt(0) == '1'); - bool highpass = (paramsStr.charAt(1) == '1'); - bool lowpass = (paramsStr.charAt(2) == '1'); - - dra->filters(emphasis, highpass, lowpass); } - break; + paramPartsBuffer[j] = Serial.read(); } + paramsStr += String((char *)paramPartsBuffer); + paramBytesMissing--; + } + float freqTxFloat = paramsStr.substring(0, 7).toFloat(); + float freqRxFloat = paramsStr.substring(7, 14).toFloat(); + int toneInt = paramsStr.substring(14, 16).toInt(); + int squelchInt = paramsStr.substring(16, 17).toInt(); - esp_task_wdt_reset(); - return; - } else if (mode == MODE_RX) { - if (Serial.available()) { - // Read a command from Android app - uint8_t tempBuffer[100]; // Big enough for a command and params, won't hold audio data - int bytesRead = 0; + // Serial.println("PARAMS: " + paramsStr.substring(0, 16) + " freqTxFloat: " + String(freqTxFloat) + " freqRxFloat: " + String(freqRxFloat) + " toneInt: " + String(toneInt)); - while (bytesRead < (DELIMITER_LENGTH + 1)) { // Read the delimiter and the command byte only (no params yet) - tempBuffer[bytesRead++] = Serial.read(); - } - switch (tempBuffer[DELIMITER_LENGTH]) { - case COMMAND_STOP: - { - setMode(MODE_STOPPED); - Serial.flush(); - esp_task_wdt_reset(); - return; - } - break; - case COMMAND_PTT_DOWN: - { - setMode(MODE_TX); + tuneTo(freqTxFloat, freqRxFloat, toneInt, squelchInt); + } + break; + case CommandValue::COMMAND_FILTERS: + { + int paramBytesMissing = 3; // e.g. 000, in order of emphasis, highpass, lowpass + String paramsStr = ""; + if (paramBytesMissing > 0) + { + uint8_t paramPartsBuffer[paramBytesMissing]; + for (int j = 0; j < paramBytesMissing; j++) + { + unsigned long waitStart = micros(); + while (!Serial.available()) + { + // Wait for a byte. + if ((micros() - waitStart) > 500000) + { // Give the Android app 0.5 second max before giving up on the command +#if defined(ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0 , 0) esp_task_wdt_reset(); +#endif return; } - break; - case COMMAND_TUNE_TO: - { - // Example: - // 145.4500144.8500061 - // 8 chars for tx, 8 chars for rx, 2 chars for tone, 1 char for squelch (19 bytes total for params) - setMode(MODE_RX); - - // If we haven't received all the parameters needed for COMMAND_TUNE_TO, wait for them before continuing. - // This can happen if ESP32 has pulled part of the command+params from the buffer before Android has completed - // putting them in there. If so, we take byte-by-byte until we get the full params. - int paramBytesMissing = 19; - String paramsStr = ""; - if (paramBytesMissing > 0) { - uint8_t paramPartsBuffer[paramBytesMissing]; - for (int j = 0; j < paramBytesMissing; j++) { - unsigned long waitStart = micros(); - while (!Serial.available()) { - // Wait for a byte. - if ((micros() - waitStart) > 500000) { // Give the Android app 0.5 second max before giving up on the command - esp_task_wdt_reset(); - return; - } - } - paramPartsBuffer[j] = Serial.read(); - } - paramsStr += String((char *)paramPartsBuffer); - paramBytesMissing--; - } - - float freqTxFloat = paramsStr.substring(0, 8).toFloat(); - float freqRxFloat = paramsStr.substring(8, 16).toFloat(); - int toneInt = paramsStr.substring(16, 18).toInt(); - int squelchInt = paramsStr.substring(18, 19).toInt(); - - tuneTo(freqTxFloat, freqRxFloat, toneInt, squelchInt); - } - break; - case COMMAND_FILTERS: - { - int paramBytesMissing = 3; // e.g. 000, in order of emphasis, highpass, lowpass - String paramsStr = ""; - if (paramBytesMissing > 0) { - uint8_t paramPartsBuffer[paramBytesMissing]; - for (int j = 0; j < paramBytesMissing; j++) { - unsigned long waitStart = micros(); - while (!Serial.available()) { - // Wait for a byte. - if ((micros() - waitStart) > 500000) { // Give the Android app 0.5 second max before giving up on the command - esp_task_wdt_reset(); - return; - } - } - paramPartsBuffer[j] = Serial.read(); - } - paramsStr += String((char *)paramPartsBuffer); - paramBytesMissing--; - } - bool emphasis = (paramsStr.charAt(0) == '1'); - bool highpass = (paramsStr.charAt(1) == '1'); - bool lowpass = (paramsStr.charAt(2) == '1'); - - dra->filters(emphasis, highpass, lowpass); - } - break; - default: - { - // Unexpected. - } - break; } + paramPartsBuffer[j] = Serial.read(); } + paramsStr += String((char *)paramPartsBuffer); + paramBytesMissing--; + } + bool emphasis = (paramsStr.charAt(0) == '1'); + bool highpass = (paramsStr.charAt(1) == '1'); + bool lowpass = (paramsStr.charAt(2) == '1'); - size_t bytesRead = 0; - uint8_t buffer32[I2S_READ_LEN * 4] = {0}; - ESP_ERROR_CHECK(i2s_read(I2S_NUM_0, &buffer32, sizeof(buffer32), &bytesRead, 100)); - size_t samplesRead = bytesRead / 4; - - byte buffer8[I2S_READ_LEN] = {0}; - bool squelched = (digitalRead(SQ_PIN) == HIGH); - - // Check for squelch status change - if (squelched != lastSquelched) { - if (squelched) { - // Start fade-out - fadeCounter = FADE_SAMPLES; - fadeDirection = -1; - } else { - // Start fade-in - fadeCounter = FADE_SAMPLES; - fadeDirection = 1; - } - } - lastSquelched = squelched; + dra->filters(emphasis, highpass, lowpass); + } + break; + case CommandValue::COMMAND_STOP: + { + Serial.flush(); + } + break; + case CommandValue::COMMAND_GET_FIRMWARE_VER: + { + Serial.write(VERSION_PREFIX, sizeof(VERSION_PREFIX)); + Serial.write(FIRMWARE_VER, sizeof(FIRMWARE_VER)); + } + break; + default: + Serial.flush(); + break; + } +} - int attenuationIncrement = ATTENUATION_MAX / FADE_SAMPLES; +void handleDATA() +{ + int16_t numberOfBytesToRead = getLengthOfDataToRead(); +#ifdef SERIAL_TRACE_LOGGING + Serial.println("Handle DATA " + String(numberOfBytesToRead)); +#endif + while (numberOfBytesToRead > 0) + { + int16_t bytesAvailableForRead = std::min(TX_AUDIO_CHUNK_SIZE, Serial.available()); + int16_t bytesToReadThisTime = std::min(numberOfBytesToRead, bytesAvailableForRead); + uint8_t *bytes = new uint8_t[bytesToReadThisTime]; + Serial.readBytes(bytes, bytesToReadThisTime); + // TODO: write these bytes to the stream + delete[] bytes; + numberOfBytesToRead -= bytesToReadThisTime; + // TODO: Determine if we need to reset the WDT here + } +#ifdef SERIAL_TRACE_LOGGING + Serial.println("DATA Read"); +#endif +} - for (int i = 0; i < samplesRead; i++) { - uint8_t sampleValue; +int16_t getLengthOfDataToRead() +{ + uint8_t bytes[2]; + while (Serial.available() < 2) + { + // Wait for two bytes + } + Serial.readBytes(bytes, 2); + return ((bytes[0] << 8) | bytes[1]); +} - // Extract 8-bit sample from 32-bit buffer - sampleValue = buffer32[i * 4 + 3] << 4; - sampleValue |= buffer32[i * 4 + 2] >> 4; +void tuneTo(float freqTx, float freqRx, int tone, int squelch) +{ +#ifdef SERIAL_TRACE_LOGGING + Serial.println("Tune to TX: " + String(freqTx) + " RX: " + String(freqRx) + " tone: " + String(tone) + " sq: " + String(squelch)); +#endif + int result = dra->group(DRA818_25K, freqTx, freqRx, tone, squelch, 0); +} - // Adjust attenuation during fade - if (fadeCounter > 0) { - fadeCounter--; - attenuation += fadeDirection * attenuationIncrement; - attenuation = max(0, min(attenuation, ATTENUATION_MAX)); - } else { - attenuation = squelched ? 0 : ATTENUATION_MAX; - fadeDirection = 0; - } +void stopTx() +{ +#ifdef SERIAL_TRACE_LOGGING + Serial.println("stopTx"); +#endif + // TODO: Stop the Tx audio streams +} - // Apply attenuation to the sample - int adjustedSample = (((int)sampleValue - 128) * attenuation) >> 8; - adjustedSample += 128; - buffer8[i] = (uint8_t)adjustedSample; - } +void startTx() +{ +#ifdef SERIAL_TRACE_LOGGING + Serial.println("startTx"); +#endif + // TODO: Start the Tx audio streams +} - Serial.write(buffer8, samplesRead); - } else if (mode == MODE_TX) { - // Check for runaway tx - int txSeconds = (micros() - txStartTime) / 1000000; - if (txSeconds > RUNAWAY_TX_SEC) { - setMode(MODE_RX); - esp_task_wdt_reset(); - return; - } +void stopRx() +{ +#ifdef SERIAL_TRACE_LOGGING + Serial.println("stopRx"); +#endif + // // TODO: Stop the Rx audio streams + // in.end(); + // throttle.end(); + // enc_stream.end(); + // rxCopyTask.end(); +} - // Check for incoming commands or audio from Android - int bytesRead = 0; - uint8_t tempBuffer[TX_TEMP_AUDIO_BUFFER_SIZE]; - int bytesAvailable = Serial.available(); - if (bytesAvailable > 0) { - bytesRead = Serial.readBytes(tempBuffer, bytesAvailable); - - // Pre-cache transmit audio to ensure precise timing (required for any data encoding to work, such as BFSK). - if (!isTxCacheSatisfied) { - if (txCachedAudioBytes + bytesRead >= TX_CACHED_AUDIO_BUFFER_SIZE) { - isTxCacheSatisfied = true; - processTxAudio(txCachedAudioBuffer, txCachedAudioBytes); // Process cached bytes - } else { - memcpy(txCachedAudioBuffer + txCachedAudioBytes, tempBuffer, bytesRead); // Store bytes to cache - txCachedAudioBytes += bytesRead; - } - } +void startRx() +{ + static bool started = false; +#ifdef SERIAL_TRACE_LOGGING + Serial.println("startRx"); +#endif + if (!started) + { + enc.setSigned(true); +// TODO: Start the Rx audio streams +#ifndef AUDIO_USE_SIN_FOR_TESTING + + auto config = in.defaultConfig(RX_MODE); + config.copyFrom(info); +#if defined(ESP32) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0 , 0) + config.sample_rate = 53904; + config.adc_channels[0] = ADC_CHANNEL_6; +#else + config.auto_clear = false; + config.is_auto_center_read = false; + config.adc_pin = ADC_PIN; + adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_0); +#endif + in.begin(config); +#else + sineWave.begin(info, N_B4); + in.begin(info); +#endif + + // throttle.begin(info); + enc_stream.begin(info); + + copierOut.begin(); + started = true; + } +} - if (isTxCacheSatisfied) { // Note that it may have JUST been satisfied above, in which case we processed the cache, and will now process tempBuffer. - processTxAudio(tempBuffer, bytesRead); - } +//////////////////////////////////////////////////////////////////////////////// +// Setup Functions +//////////////////////////////////////////////////////////////////////////////// - for (int i = 0; i < bytesRead && i < TX_TEMP_AUDIO_BUFFER_SIZE; i++) { - // If we've seen the entire delimiter... - if (matchedDelimiterTokens == DELIMITER_LENGTH) { - // Process next byte as a command. - uint8_t command = tempBuffer[i]; - matchedDelimiterTokens = 0; - switch (command) { - case COMMAND_STOP: - { - delay(MS_WAIT_BEFORE_PTT_UP); // Wait just a moment so final tx audio data in DMA buffer can be transmitted. - setMode(MODE_STOPPED); - esp_task_wdt_reset(); - return; - } - break; - case COMMAND_PTT_UP: - { - delay(MS_WAIT_BEFORE_PTT_UP); // Wait just a moment so final tx audio data in DMA buffer can be transmitted. - setMode(MODE_RX); - esp_task_wdt_reset(); - return; - } - break; - } - } else { - if (tempBuffer[i] == delimiter[matchedDelimiterTokens]) { // This byte may be part of the delimiter - matchedDelimiterTokens++; - } else { // This byte is not consistent with the command delimiter, reset counter - matchedDelimiterTokens = 0; - } - } - } - } - } +void setupSerial() +{ + // Communication with Android via USB cable + Serial.begin(921600); + Serial.setRxBufferSize(USB_BUFFER_SIZE); + Serial.setTxBufferSize(USB_BUFFER_SIZE); +} - // Regularly reset the WDT timer to prevent the device from rebooting (prove we're not locked up). - esp_task_wdt_reset(); - } catch (int e) { - // Disregard, we don't want to crash. Just pick up at next loop().) - // Serial.println("Exception in loop(), skipping cycle."); - } +void setupWDT() +{ + // Configure watch dog timer (WDT), which will reset the system if it gets stuck somehow. +#if defined(ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0 , 0) + esp_task_wdt_init(10, true); // Reboot if locked up for a bit + esp_task_wdt_add(NULL); // Add the current task to WDT watch +#endif } -void tuneTo(float freqTx, float freqRx, int tone, int squelch) { - int result = dra->group(DRA818_25K, freqTx, freqRx, tone, squelch, 0); - // Serial.println("tuneTo: " + String(result)); +void setupLED() +{ + // Debug LED + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); } -void setMode(int newMode) { - mode = newMode; - switch (mode) { - case MODE_STOPPED: - digitalWrite(LED_PIN, LOW); - digitalWrite(PTT_PIN, HIGH); - break; - case MODE_RX: - digitalWrite(LED_PIN, LOW); - digitalWrite(PTT_PIN, HIGH); - initI2SRx(); - break; - case MODE_TX: - txStartTime = micros(); - digitalWrite(LED_PIN, HIGH); - digitalWrite(PTT_PIN, LOW); - initI2STx(); - txCachedAudioBytes = 0; - isTxCacheSatisfied = false; - break; +void setupDRA818() +{ + /////////////////////////////// Setup Radio Module + // Set up radio module defaults + pinMode(PD_PIN, OUTPUT); + digitalWrite(PD_PIN, HIGH); // Power on + pinMode(SQ_PIN, INPUT); + pinMode(PTT_PIN, OUTPUT); + digitalWrite(PTT_PIN, HIGH); // Rx + + // Communication with DRA818V radio module via GPIO pins + Serial2.begin(9600, SERIAL_8N1, RXD2_PIN, TXD2_PIN); + + int result = -1; + while (result != 1) + { + result = dra->handshake(); // Wait for module to start up } + + // Serial.println("handshake: " + String(result)); + // tuneTo(146.700, 146.700, 0, 0); + result = dra->volume(8); + // Serial.println("volume: " + String(result)); + result = dra->filters(false, false, false); + // Serial.println("filters: " + String(result)); } -void processTxAudio(uint8_t tempBuffer[], int bytesRead) { - if (bytesRead == 0) { - return; - } +void setInitialState() +{ + // Begin in STOPPED mode + setMode(Mode::MODE_STOPPED); +} - // Convert the 8-bit audio data to 16-bit - uint8_t buffer16[bytesRead * 2] = {0}; - for (int i = 0; i < bytesRead; i++) { - buffer16[i * 2 + 1] = tempBuffer[i]; // Move 8-bit audio into top 8 bits of 16-bit byte that I2S expects. - } +void setupAudioTools() +{ - size_t totalBytesWritten = 0; - size_t bytesWritten; - size_t bytesToWrite = sizeof(buffer16); - do { - ESP_ERROR_CHECK(i2s_write(I2S_NUM_0, buffer16 + totalBytesWritten, bytesToWrite, &bytesWritten, 100)); - totalBytesWritten += bytesWritten; - bytesToWrite -= bytesWritten; - } while (bytesToWrite > 0); + // rxCopyTask.begin( + // []() + // { + // copierOut.copy(); + // esp_task_wdt_reset(); + // } + // ); } diff --git a/microcontroller-src/platformio.ini b/microcontroller-src/platformio.ini index 3282113..8e67161 100644 --- a/microcontroller-src/platformio.ini +++ b/microcontroller-src/platformio.ini @@ -12,11 +12,15 @@ src_dir = kv4p_ht_esp32_wroom_32 [env:esp32dev] -platform = espressif32 +platform = https://github.com/platformio/platform-espressif32.git +platform_packages = + framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5 + framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.1/esp32-arduino-libs-idf-release_v5.1-33fbade6.zip board = esp32dev framework = arduino monitor_speed = 921600 lib_deps = ; fatpat/DRA818@^1.0.1 https://github.com/fatpat/arduino-dra818.git#v1.0.1 ; v1.0.1 is the latest release, but has not been pushed to registry. Update later - plerup/EspSoftwareSerial@^8.2.0 \ No newline at end of file + plerup/EspSoftwareSerial@^8.2.0 + arduino-audio-tools=https://github.com/pschatzmann/arduino-audio-tools/archive/5214dec10ebaab57d6eb485cb96b4180da476b54.zip diff --git a/microcontroller-src/tools/WriteSamples.py b/microcontroller-src/tools/WriteSamples.py new file mode 100644 index 0000000..80c6794 --- /dev/null +++ b/microcontroller-src/tools/WriteSamples.py @@ -0,0 +1,36 @@ +import serial +import time +import math + +# Configure the serial port +ser = serial.Serial('/dev/ttyUSB0', 921600) + +# Sine wave parameters +frequency = 300 # Hz +amplitude = 127 # 0-255 +offset = 128 # Center the sine wave around 128 +sampling_frequency = 44100 # Hz + +try: + while True: + # Calculate the time for the current sample + t = time.time() + + # Calculate the sine wave value + value = int(amplitude * math.sin(2 * math.pi * frequency * t) + offset) + + # Ensure the value is within the 0-255 range + value = max(0, min(value, 255)) + + # Write the value to the serial port as a single byte + ser.write(bytes([value])) # Convert to byte array with a single element + + # Calculate the time to sleep for the next sample + time_to_sleep = 1/sampling_frequency - (time.time() - t) + + # Wait for the calculated time + if time_to_sleep > 0: + time.sleep(time_to_sleep) +except KeyboardInterrupt: + print("Exiting...") + ser.close()