From 30f4047b7c0ebed240af07fae87dfc95645189b2 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 24 Jan 2026 16:43:07 -0600 Subject: [PATCH] Reworking the AVR specific stuff into an AVRInterface class. --- AVR Working Controller/AVR.cpp | 26 +-- AVR Working Controller/AVR.vcxproj | 2 + AVR Working Controller/AVR.vcxproj.filters | 6 + AVR Working Controller/AVRInterface.cpp | 159 ++++++++++++++++++ AVR Working Controller/AVRInterface.h | 111 ++++++++++++ .../SerialPort/SerialPort.cpp | 8 +- .../SerialPort/SerialPort.h | 6 +- .../Yamaha RX-V2400_RS232C_Standard.pdf | Bin 179906 -> 179925 bytes 8 files changed, 298 insertions(+), 20 deletions(-) create mode 100644 AVR Working Controller/AVRInterface.cpp create mode 100644 AVR Working Controller/AVRInterface.h diff --git a/AVR Working Controller/AVR.cpp b/AVR Working Controller/AVR.cpp index 5944992..c09ec08 100644 --- a/AVR Working Controller/AVR.cpp +++ b/AVR Working Controller/AVR.cpp @@ -24,7 +24,7 @@ CSerialPort avr; int avrOnPort = COM_NO_PORT; // Serial Port 1, 2, ... taking note that some USB adapters can be up toward channel 11, 12, ... unsigned avrBaud = 9600; -DWORD const retryInterval = 1000; // ms +const uint32_t retryInterval = 1000; // ms // Each DT is the hex-character from the stream // @@ -219,13 +219,13 @@ int DetachSerialPort(); // @param[in] len is the count of bytes in the message // @returns true if the serial interface accepted it. // -bool SerialSend(const uint8_t *p, DWORD len); +bool SerialSend(const uint8_t *p, uint16_t len); // Just big enough to hold an OSD message which is a 1 message command, 4 messages with text #define SERIALQUEUESIZE 5 typedef struct { uint8_t *messageToSend; - DWORD len; + uint16_t len; } SerialQueue_T; static SerialQueue_T serialQueue[SERIALQUEUESIZE]; @@ -241,7 +241,7 @@ static int serialQueueCount = 0; // @param[in] len is the count of bytes in the message // @returns false if the queue (which is a fixed size) is full. // -bool ProcessSerialQueue(const uint8_t *p = NULL, DWORD len = 0); +bool ProcessSerialQueue(const uint8_t *p = NULL, uint16_t len = 0); // ProcessSerialReceive @@ -257,11 +257,11 @@ unsigned long Hex2Dec(uint8_t *p, int dig); void ShowAllStatusInfo(); bool UserWantsToExitCanSniff = false; -DWORD progStartTime; +DWORD progStartTime_ms; typedef struct { const char *pMsg; - DWORD MsgLen; + uint16_t MsgLen; } Message_T; typedef struct { @@ -531,11 +531,11 @@ void ProcessWindowsMessage(void) { void EmitBuffer(const char *prefix, const uint8_t *buf, size_t len = 0, bool appendReturn = false) { int i = 0; const char *p = (const char *)buf; - DWORD now = timeGetTime(); + uint32_t now_ms = timeGetTime(); char txtBuf[MAXTEXTLEN] = ""; if (len == 0) len = strlen((const char *)buf); - sprintf_s(txtBuf, MAXTEXTLEN, "%7.3f: [%3d]%s", (float)(now - progStartTime)/1000.0f, (int)strlen(p), prefix); + sprintf_s(txtBuf, MAXTEXTLEN, "%7.3f: [%3d]%s", (float)(now_ms - progStartTime_ms)/1000.0f, (int)strlen(p), prefix); Console_Write(txtBuf); while (*p && ((unsigned)(p - (const char *)buf) < len)) { if (isprint(*p)) { @@ -575,7 +575,7 @@ void EchoSerialRecv(const uint8_t *pMsg) { Console_ScrollBottomRegion(); } -bool SerialSend(const uint8_t *p, DWORD len) { +bool SerialSend(const uint8_t *p, uint16_t len) { bool retVal = false; Console_SetCursor(0, -1); EmitBuffer("> ", p, len); @@ -590,7 +590,7 @@ bool SerialSend(const uint8_t *p, DWORD len) { return retVal; } -bool ProcessSerialQueue(const uint8_t *p, DWORD len) { +bool ProcessSerialQueue(const uint8_t *p, uint16_t len) { bool retVal = false; // assume fail static bool freshData = false; @@ -920,7 +920,7 @@ int __cdecl main(int argc, char *argv[]) { short consoleHeight = 80; short consoleScrollHeight = 30; - progStartTime = timeGetTime(); + progStartTime_ms = timeGetTime(); MessageHandlerSanityCheck(); // If the table is bad, we exit here UserCommandsSanityCheck(); // If the table is bad, we exit here @@ -1174,7 +1174,7 @@ void EnumerateComPorts() { bool portFound = false; HANDLE port = CreateFile(cBuf, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); if (port == INVALID_HANDLE_VALUE) { - DWORD dwError = GetLastError(); + uint32_t dwError = GetLastError(); if ((dwError == ERROR_ACCESS_DENIED) || (dwError == ERROR_GEN_FAILURE) || (dwError == ERROR_SHARING_VIOLATION) || (dwError == ERROR_SEM_TIMEOUT)) { foundPorts++; @@ -1208,7 +1208,7 @@ int AttachToSerialPort() { char buf[20]; // generously sized. sprintf_s(buf, sizeof(buf), "\\\\.\\COM%d", avrOnPort); - DWORD Access = GENERIC_WRITE | GENERIC_READ; + uint32_t Access = GENERIC_WRITE | GENERIC_READ; if (avr.Open(buf, avrBaud, 8, NOPARITY, ONESTOPBIT, Access)) { success = true; avr.Set_RTS_State(false); diff --git a/AVR Working Controller/AVR.vcxproj b/AVR Working Controller/AVR.vcxproj index d08249e..bf2740a 100644 --- a/AVR Working Controller/AVR.vcxproj +++ b/AVR Working Controller/AVR.vcxproj @@ -127,11 +127,13 @@ + + diff --git a/AVR Working Controller/AVR.vcxproj.filters b/AVR Working Controller/AVR.vcxproj.filters index b43dc3a..8c750f6 100644 --- a/AVR Working Controller/AVR.vcxproj.filters +++ b/AVR Working Controller/AVR.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + @@ -38,5 +41,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/AVR Working Controller/AVRInterface.cpp b/AVR Working Controller/AVRInterface.cpp new file mode 100644 index 0000000..13afe5e --- /dev/null +++ b/AVR Working Controller/AVRInterface.cpp @@ -0,0 +1,159 @@ + +#include +#include +#include "AVRInterface.h" + +//AVRInterface::AVRInterface(int (*SendMessage)(const void *buffer, unsigned int bufferSize)) { +//} +AVRInterface::~AVRInterface() { + FreeMemory(); +} + +void AVRInterface::Tick(uint32_t now_ms) { + uint32_t elapsed_ms = now_ms - sentAtTime_ms; + + switch (state) { + default: + case AVRState_T::stPoweringUp: + readyResponsReceived = false; + ProcessSerialQueue((uint8_t *)"\x11" "000" "\x03", 5); // Send the Ready Command + sentAtTime_ms = now_ms; + readyTries++; + state = stAwaitingReadyResponse; + break; + case stAwaitingReadyResponse: + if (readyResponsReceived) { + state = stReady; + } else if (elapsed_ms > RETRY_INTERVAL_ms) { + if (readyTries > MAXTRIES) { + // fail + + } else { + state = stPoweringUp; + } + } + break; + case AVRState_T::stInitializing: + break; + case AVRState_T::stRetryInitializing: + break; + case AVRState_T::stAwaitingResponse: + break; + case AVRState_T::stReady: + ProcessSerialQueue(); + break; + } +} + +bool AVRInterface::HandleMessage(const uint8_t *buffer, uint16_t len) { + switch (state) { + case stAwaitingReadyResponse: + switch (buffer[0]) { + case 0x02: // STX ETX + break; + case 0x11: // DC1 ETX + break; + case 0x12: // DC2 ETX + if (CheckTheChecksum(buffer, len)) { + readyResponsReceived = true; + } + break; + case 0x14: // DC4 ETX + break; + default: + break; + } + readyResponsReceived = true; + break; + case stReady: + break; + } + return true; +} + +bool AVRInterface::Initialize() { + return true; +} + +bool AVRInterface::IsOnline() { + return true; +} +bool AVRInterface::Power(AVRPower_T cmd) { + return true; +} + +bool AVRInterface::MasterVolumeButton(AVRVolumeButton_T cmd) { + return true; +} +bool AVRInterface::Mute(AVRMute_T cmd) { + return true; +} + +bool AVRInterface::ProcessSerialQueue(const uint8_t *p, uint16_t len) { + bool retVal = false; // assume fail + static bool freshData = false; + + if (p && len) { + if (serialQueueCount < SERIALQUEUESIZE) { + serialQueue[serialQueueCount].messageToSend = (uint8_t *)malloc(len); + if (serialQueue[serialQueueCount].messageToSend) { + memcpy(serialQueue[serialQueueCount].messageToSend, p, len); + serialQueue[serialQueueCount].len = len; + serialQueueCount++; + retVal = true; + freshData = true; + } + } + } + if (serialQueueCount) { + if ((*SendMethod)((const uint8_t *)serialQueue[0].messageToSend, serialQueue[0].len)) { + --serialQueueCount; + free(serialQueue[0].messageToSend); + serialQueue[0].messageToSend = NULL; + for (int i = 0; i < serialQueueCount; i++) { + serialQueue[i] = serialQueue[i + 1]; + } + retVal = true; + } + } + return retVal; +} + +void AVRInterface::FreeMemory() { + for (int i = 0; i < serialQueueCount; i++) { + if (serialQueue[0].messageToSend) + free(serialQueue[0].messageToSend); + } +} + + +bool AVRInterface::CheckTheChecksum(const uint8_t *szBuffer, uint32_t num) { + uint8_t sum = 0; + for (uint16_t i = 1; i < num - 3; i++) { + sum += szBuffer[i]; + } + uint8_t cksum = (uint8_t)Hex2Dec(&szBuffer[num - 3], 2); + //printf("CheckSum: %02X v. %c%c\n", sum, szBuffer[num - 3], szBuffer[num - 2]); + return (sum == cksum); +} + +// Hex2Dec +// +// All responses are pretty much Hex-ASCII, so +// we sometimes want to convert it to decimal +// This takes a buffer and converts the specified +// number of characters. +// +unsigned long AVRInterface::Hex2Dec(const uint8_t *p, int dig) { + unsigned long x = 0; + while (dig--) { + if (*p >= '0' && *p <= '9') + x = x * 16 + *p - '0'; + else if (*p >= 'a' && *p <= 'f') + x = x * 16 + 0x0a + *p - 'a'; + else if (*p >= 'A' && *p <= 'F') + x = x * 16 + 0x0a + *p - 'A'; + p++; + } + return x; +} diff --git a/AVR Working Controller/AVRInterface.h b/AVR Working Controller/AVRInterface.h new file mode 100644 index 0000000..f2d2ee0 --- /dev/null +++ b/AVR Working Controller/AVRInterface.h @@ -0,0 +1,111 @@ +#pragma once +#include + +class AVRInterface { +public: + /// @brief + /// @param SendMessage is the function this AVRInterface calls to send a message to the device + /// + AVRInterface(int (*SendMessage)(const uint8_t *buffer, uint16_t len)) + : SendMethod(SendMessage) {}; + + ~AVRInterface(); + + /// @brief When the system receives something from the device, give it to this function to handle it + /// @param buffer + /// @param len + /// @return true if it was handled + /// + bool HandleMessage(const uint8_t *buffer, uint16_t len); + + /// @brief Call this periodically so timed activities can be handled. + /// + /// Every 1 or even 50 to 100 msec is ok. + /// + /// @param millisec + /// + void Tick(uint32_t millisec); + + /// @brief Initialize the AVR interface and issue the ready command. + /// + /// This is temporarily blocking since nothing else should run until ready + /// + /// @return true if initialized + /// + bool Initialize(); + + /// @brief determine if the device is ready + /// + /// @return true if ready + /// + bool IsOnline(); + + /// @brief Power on and off commands + /// + typedef enum { + avrPowerOff, + avrPowerOn, + } AVRPower_T; + + /// @brief Send the power command + /// @param cmd + /// @return true if sent + /// + bool Power(AVRPower_T cmd); + + // MasterVolumeButton + // Issues volume up and down commands to the AVR. + // + typedef enum { + avrVolumeUp, + avrVolumeDown, + } AVRVolumeButton_T; + bool MasterVolumeButton(AVRVolumeButton_T cmd); + + // Mute, UnMute + typedef enum { + avrMute, + avrUnMute, + } AVRMute_T; + bool Mute(AVRMute_T cmd); + +private: + uint32_t sentAtTime_ms; + + typedef enum { + stPoweringUp, + stAwaitingReadyResponse, + stInitializing, + stRetryInitializing, + stReady, + stAwaitingResponse, + } AVRState_T; + AVRState_T state = stPoweringUp; + + AVRState_T GetState() { + return state; + } + + bool readyResponsReceived; + int readyTries; + #define RETRY_INTERVAL_ms 500 + #define MAXTRIES 5 + + #define SERIALQUEUESIZE 5 + typedef struct { + uint8_t *messageToSend; + uint16_t len; + } SerialQueue_T; + + SerialQueue_T serialQueue[SERIALQUEUESIZE]; + int serialQueueCount = 0; + + bool ProcessSerialQueue(const uint8_t *p = NULL, uint16_t len = NULL); + + int (*SendMethod)(const uint8_t *buffer, uint16_t bufferSize); + + bool CheckTheChecksum(const uint8_t *szBuffer, uint32_t num); + unsigned long Hex2Dec(const uint8_t *p, int dig); + + void FreeMemory(); +}; diff --git a/AVR Working Controller/SerialPort/SerialPort.cpp b/AVR Working Controller/SerialPort/SerialPort.cpp index ea2b455..c790ab4 100644 --- a/AVR Working Controller/SerialPort/SerialPort.cpp +++ b/AVR Working Controller/SerialPort/SerialPort.cpp @@ -36,7 +36,7 @@ CSerialPort::~CSerialPort() Close(); } -BOOL CSerialPort::Open(LPCTSTR PortName, DWORD BaudRate, BYTE ByteSize, BYTE Parity, BYTE StopBits, DWORD DesiredAccess) +BOOL CSerialPort::Open(LPCTSTR PortName, uint32_t BaudRate, BYTE ByteSize, BYTE Parity, BYTE StopBits, uint32_t DesiredAccess) { Close(); m_PortHandle = CreateFile(PortName, DesiredAccess, 0, NULL, OPEN_EXISTING, 0, 0); @@ -72,7 +72,7 @@ BOOL CSerialPort::Open(LPCTSTR PortName, DWORD BaudRate, BYTE ByteSize, BYTE Par SetCommState(m_PortHandle, &dcb); COMMTIMEOUTS touts; - touts.ReadIntervalTimeout = MAXDWORD; // This, plus the zero timeouts causes immediate return + touts.ReadIntervalTimeout = UINT32_MAX; // This, plus the zero timeouts causes immediate return touts.ReadTotalTimeoutMultiplier = 0; touts.ReadTotalTimeoutConstant = 0; touts.WriteTotalTimeoutConstant = 1; @@ -104,7 +104,7 @@ BOOL CSerialPort::IsOpen() return (m_PortHandle != INVALID_HANDLE_VALUE); } -DWORD CSerialPort::Read(LPVOID Buffer, DWORD BufferSize) +uint32_t CSerialPort::Read(LPVOID Buffer, uint32_t BufferSize) { DWORD Res(0); if (m_PortHandle != INVALID_HANDLE_VALUE) @@ -114,7 +114,7 @@ DWORD CSerialPort::Read(LPVOID Buffer, DWORD BufferSize) return Res; } -DWORD CSerialPort::Write(const LPVOID Buffer, DWORD BufferSize) +uint32_t CSerialPort::Write(const LPVOID Buffer, uint32_t BufferSize) { DWORD Res(0); if (m_PortHandle != INVALID_HANDLE_VALUE) diff --git a/AVR Working Controller/SerialPort/SerialPort.h b/AVR Working Controller/SerialPort/SerialPort.h index 4ede3b4..28b9fd9 100644 --- a/AVR Working Controller/SerialPort/SerialPort.h +++ b/AVR Working Controller/SerialPort/SerialPort.h @@ -32,12 +32,12 @@ public: BOOL Get_DSR_State(); BOOL Get_CTS_State(); BOOL Get_CD_State(); - virtual DWORD Write(const LPVOID Buffer, DWORD BufferSize); - virtual DWORD Read(LPVOID Buffer, DWORD BufferSize); + virtual uint32_t Write(const LPVOID Buffer, uint32_t BufferSize); + virtual uint32_t Read(LPVOID Buffer, uint32_t BufferSize); virtual BOOL IsOpen(); virtual void Close(); // Use PortName usually "COM1:" ... "COM4:" note that the name must end by ":" - virtual BOOL Open(LPCTSTR PortName, DWORD BaudRate, BYTE ByteSize, BYTE Parity, BYTE StopBits, DWORD DesiredAccess = GENERIC_READ|GENERIC_WRITE); + virtual BOOL Open(LPCTSTR PortName, uint32_t BaudRate, BYTE ByteSize, BYTE Parity, BYTE StopBits, uint32_t DesiredAccess = GENERIC_READ|GENERIC_WRITE); CSerialPort(); virtual ~CSerialPort(); diff --git a/DataSheets/Yamaha RX-V2400_RS232C_Standard.pdf b/DataSheets/Yamaha RX-V2400_RS232C_Standard.pdf index 313d0c4240b85d75ea1ea2829d92a1a4ea1fabf3..941acbca3e2b4a677344657a28da44f143ac6495 100644 GIT binary patch delta 6879 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 P3pg