diff --git a/.gitmodules b/.gitmodules index 78eb385..a55b0ec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,3 +24,6 @@ [submodule "LittleFs_SDCard/src/AzureSphere/SDCard_RealTimeApp/lib"] path = LittleFs_SDCard/src/AzureSphere/SDCard_RealTimeApp/lib url = https://github.com/CodethinkLabs/mt3620-m4-drivers.git +[submodule "RS485Driver/RTApp/lib"] + path = RS485Driver/RTApp/lib + url = https://github.com/CodethinkLabs/mt3620-m4-drivers.git diff --git a/README.md b/README.md index 39b5638..0c25567 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Each folder within this repository contains a README.md file that describes the | [MutableStorageKVP](MutableStorageKVP) | Provides a set of functions that expose Key/Value pair functions (write, read, delete) over Azure Sphere Mutable Storage. | | [OSNetworkRequirementChecker-HLApp](OSNetworkRequirementChecker-HLApp) | A sample app that performs DNS resolver and custom NTP test for diagnosing networking connectivity problems. | | [OSNetworkRequirementChecker-PC](OSNetworkRequirementChecker-PC) | A PC command line utility for diagnosing networking connectivity issues. | +| [RS485Driver](RS485Driver) | An RS-485 real-time driver with HL-Core interfacing API. | | [ServiceAPIDeviceCodeAuth](ServiceAPIDeviceCodeAuth) | Code snippet to access public rest api using device code flow from a web app. | | [SetIoTCentralPropsForDeviceGroup](SetIoTCentralPropsForDeviceGroup) | Utility that makes it easy to set an Azure IoT Central Device Twin property for all devices in an Azure Sphere Device Group. | | [SetTimeFromLocation](SetTimeFromLocation) | Project that shows how to use Reverse IP lookup to get location information, then obtain time for location, and set device time. | diff --git a/RS485Driver/.gitignore b/RS485Driver/.gitignore new file mode 100644 index 0000000..b8ca4e4 --- /dev/null +++ b/RS485Driver/.gitignore @@ -0,0 +1,4 @@ +# Ignore output directories +/.vs/ +/out-*/ +/install-*/ \ No newline at end of file diff --git a/RS485Driver/HLApp/.gitignore b/RS485Driver/HLApp/.gitignore new file mode 100644 index 0000000..56e1e50 --- /dev/null +++ b/RS485Driver/HLApp/.gitignore @@ -0,0 +1,4 @@ +# Ignore output directories +/.vs/ +/out/ +/install/ \ No newline at end of file diff --git a/RS485Driver/HLApp/.vscode/launch.json b/RS485Driver/HLApp/.vscode/launch.json new file mode 100644 index 0000000..f985129 --- /dev/null +++ b/RS485Driver/HLApp/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch for Azure Sphere High-Level Applications (gdb)", + "type": "azurespheredbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "partnerComponents": [ "1CCE66F1-28E9-4DA4-AD25-D247FD362DE7" ], + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/RS485Driver/HLApp/.vscode/settings.json b/RS485Driver/HLApp/.vscode/settings.json new file mode 100644 index 0000000..9958017 --- /dev/null +++ b/RS485Driver/HLApp/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "cmake.generator": "Ninja", + "cmake.buildDirectory": "${workspaceRoot}/out/ARM-${buildType}", + "cmake.buildToolArgs": [ "-v" ], + "cmake.configureSettings": { + "CMAKE_TOOLCHAIN_FILE": "${command:azuresphere.AzureSphereSdkDir}/CMakeFiles/AzureSphereToolchain.cmake", + "AZURE_SPHERE_TARGET_API_SET": "latest-lts" + }, + "cmake.configureOnOpen": true, + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" +} \ No newline at end of file diff --git a/RS485Driver/HLApp/CMakeLists.txt b/RS485Driver/HLApp/CMakeLists.txt new file mode 100644 index 0000000..a6bb057 --- /dev/null +++ b/RS485Driver/HLApp/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.10) + +project(RS_485_HighLevelApp C) + +azsphere_configure_tools(TOOLS_REVISION "21.07") +azsphere_configure_api(TARGET_API_SET "10") + +add_executable(${PROJECT_NAME} main.c eventloop_timer_utilities.c rs485_hl_driver.c) +target_link_libraries(${PROJECT_NAME} applibs gcc_s c) + +azsphere_target_add_image_package(${PROJECT_NAME}) diff --git a/RS485Driver/HLApp/CMakeSettings.json b/RS485Driver/HLApp/CMakeSettings.json new file mode 100644 index 0000000..64bc4b5 --- /dev/null +++ b/RS485Driver/HLApp/CMakeSettings.json @@ -0,0 +1,48 @@ +{ + "environments": [ + { + "environment": "AzureSphere", + "BuildAllBuildsAllRoots": "true" + } + ], + "configurations": [ + { + "name": "ARM-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ + "AzureSphere" + ], + "buildRoot": "${projectDir}\\out\\${name}", + "installRoot": "${projectDir}\\install\\${name}", + "cmakeToolchain": "${env.AzureSphereDefaultSDKDir}CMakeFiles\\AzureSphereToolchain.cmake", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "AZURE_SPHERE_TARGET_API_SET", + "value": "latest-lts" + } + ] + }, + { + "name": "ARM-Release", + "generator": "Ninja", + "configurationType": "Release", + "inheritEnvironments": [ + "AzureSphere" + ], + "buildRoot": "${projectDir}\\out\\${name}", + "installRoot": "${projectDir}\\install\\${name}", + "cmakeToolchain": "${env.AzureSphereDefaultSDKDir}CMakeFiles\\AzureSphereToolchain.cmake", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "AZURE_SPHERE_TARGET_API_SET", + "value": "latest-lts" + } + ] + } + ] +} \ No newline at end of file diff --git a/RS485Driver/HLApp/LICENSE.txt b/RS485Driver/HLApp/LICENSE.txt new file mode 100644 index 0000000..2d6f90d --- /dev/null +++ b/RS485Driver/HLApp/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/RS485Driver/HLApp/README.md b/RS485Driver/HLApp/README.md new file mode 100644 index 0000000..9a14e9e --- /dev/null +++ b/RS485Driver/HLApp/README.md @@ -0,0 +1,3 @@ +# Sample: Inter-core comms - High-level app + +Please see the [parent project README](../README.md) for more information. diff --git a/RS485Driver/HLApp/app_manifest.json b/RS485Driver/HLApp/app_manifest.json new file mode 100644 index 0000000..13b4673 --- /dev/null +++ b/RS485Driver/HLApp/app_manifest.json @@ -0,0 +1,10 @@ +{ + "SchemaVersion": 1, + "Name": "RS-485 HighLevelApp", + "ComponentId": "96ACA524-8113-4171-9C76-6FBDBB441131", + "EntryPoint": "/bin/app", + "Capabilities": { + "AllowedApplicationConnections": [ "1CCE66F1-28E9-4DA4-AD25-D247FD362DE7" ] + }, + "ApplicationType": "Default" +} diff --git a/RS485Driver/HLApp/eventloop_timer_utilities.c b/RS485Driver/HLApp/eventloop_timer_utilities.c new file mode 100644 index 0000000..4a35055 --- /dev/null +++ b/RS485Driver/HLApp/eventloop_timer_utilities.c @@ -0,0 +1,139 @@ +/* Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "eventloop_timer_utilities.h" + +static int SetTimerPeriod(int timerFd, const struct timespec *initial, + const struct timespec *repeat); + +static int SetTimerPeriod(int timerFd, const struct timespec *initial, + const struct timespec *repeat) +{ + static const struct timespec nullTimeSpec = {.tv_sec = 0, .tv_nsec = 0}; + struct itimerspec newValue = {.it_value = initial ? *initial : nullTimeSpec, + .it_interval = repeat ? *repeat : nullTimeSpec}; + + if (timerfd_settime(timerFd, /* flags */ 0, &newValue, /* old_value */ NULL) == -1) { + Log_Debug("ERROR: Could not set timer period: %s (%d).\n", strerror(errno), errno); + return -1; + } + + return 0; +} + +struct EventLoopTimer { + EventLoop *eventLoop; + EventLoopTimerHandler handler; + int fd; + EventRegistration *registration; +}; + +// This satisfies the EventLoopIoCallback signature. +static void TimerCallback(EventLoop *el, int fd, EventLoop_IoEvents events, void *context) +{ + EventLoopTimer *timer = (EventLoopTimer *)context; + + timer->handler(timer); +} + +EventLoopTimer *CreateEventLoopPeriodicTimer(EventLoop *eventLoop, EventLoopTimerHandler handler, + const struct timespec *period) +{ + if (handler == NULL) { + errno = EINVAL; + return NULL; + } + + EventLoopTimer *timer = malloc(sizeof(EventLoopTimer)); + if (timer == NULL) { + return NULL; + } + + timer->eventLoop = eventLoop; + timer->handler = handler; + + // Initialize to unused values in case have to clean up partially initialized object. + timer->fd = -1; + timer->registration = NULL; + + timer->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + if (timer->fd == -1) { + Log_Debug("ERROR: Unable to create timer: %s (%d).\n", strerror(errno), errno); + goto failed; + } + + if (SetTimerPeriod(timer->fd, /* initial */ period, /* repeat */ period) == -1) { + goto failed; + } + + timer->registration = + EventLoop_RegisterIo(eventLoop, timer->fd, EventLoop_Input, TimerCallback, timer); + if (timer->registration == NULL) { + Log_Debug("ERROR: Unable to register timer event: %s (%d).\n", strerror(errno), errno); + goto failed; + } + + return timer; + +failed: + DisposeEventLoopTimer(timer); + return NULL; +} + +EventLoopTimer *CreateEventLoopDisarmedTimer(EventLoop *eventLoop, EventLoopTimerHandler handler) +{ + return CreateEventLoopPeriodicTimer(eventLoop, handler, NULL); +} + +void DisposeEventLoopTimer(EventLoopTimer *timer) +{ + if (timer == NULL) { + return; + } + + EventLoop_UnregisterIo(timer->eventLoop, timer->registration); + + if (timer->fd != -1) { + close(timer->fd); + } + + free(timer); +} + +int ConsumeEventLoopTimerEvent(EventLoopTimer *timer) +{ + uint64_t timerData = 0; + + if (read(timer->fd, &timerData, sizeof(timerData)) == -1) { + Log_Debug("ERROR: Could not read timerfd %s (%d).\n", strerror(errno), errno); + return -1; + } + + return 0; +} + +int SetEventLoopTimerPeriod(EventLoopTimer *timer, const struct timespec *period) +{ + return SetTimerPeriod(timer->fd, /* initial */ period, /* repeat */ period); +} + +int SetEventLoopTimerOneShot(EventLoopTimer *timer, const struct timespec *delay) +{ + return SetTimerPeriod(timer->fd, /* initial */ delay, /* repeat */ NULL); +} + +int DisarmEventLoopTimer(EventLoopTimer *timer) +{ + return SetTimerPeriod(timer->fd, /* initial */ NULL, /* repeat */ NULL); +} diff --git a/RS485Driver/HLApp/eventloop_timer_utilities.h b/RS485Driver/HLApp/eventloop_timer_utilities.h new file mode 100644 index 0000000..fc763c7 --- /dev/null +++ b/RS485Driver/HLApp/eventloop_timer_utilities.h @@ -0,0 +1,102 @@ +/* Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +#pragma once +#include + +#include + +#include + +/// +/// Opaque handle. Obtain via +/// or and dispose of via +/// . +/// +typedef struct EventLoopTimer EventLoopTimer; + +/// +/// Applications implement a function with this signature to be +/// notified when a timer expires. +/// +/// The timer which has expired. +/// +/// +typedef void (*EventLoopTimerHandler)(EventLoopTimer *timer); + +/// +/// Create a periodic timer which is invoked on the event loop. The timer +/// will begin firing immediately. +/// +/// Event loop to which the timer will be added. +/// Callback to invoke when the timer expires. +/// Timer period. +/// On success, pointer to new EventLoopTimer, which should be disposed of +/// with . On failure, returns NULL, with more +/// information available in errno.. +EventLoopTimer *CreateEventLoopPeriodicTimer(EventLoop *eventLoop, EventLoopTimerHandler handler, + const struct timespec *period); + +/// +/// Create a disarmed timer. After the timer has been allocated, call +/// or +/// to arm the timer. +/// +/// Event loop to which the timer will be added. +/// Callback to invoke when the timer expires. +/// On success, pointer to new EventLoopTimer, which should be disposed of +/// with . On failure, returns NULL, with more +/// information available in errno.. +EventLoopTimer *CreateEventLoopDisarmedTimer(EventLoop *eventLoop, EventLoopTimerHandler handler); + +/// +/// Dispose of a timer which was allocated with +/// or . +/// It is safe to call this function with a NULL pointer. +/// +/// Successfully allocated event loop timer, or NULL. +void DisposeEventLoopTimer(EventLoopTimer *timer); + +/// +/// The timer callback should call this function to consume the timer event. +/// +/// Successfully allocated timer. +/// 0 on success, -1 on failure, in which case errno contains more information. +int ConsumeEventLoopTimerEvent(EventLoopTimer *timer); + +/// +/// Change the timer's period. This function should only be called to change an existing +/// timer's period. It does not have to be called to set the initial period - that is +/// handled by . +/// +/// Timer previously allocated with +/// or . +/// New timer period. +/// 0 on success, -1 on failure, in which case errno contains more information. +/// +/// +int SetEventLoopTimerPeriod(EventLoopTimer *timer, const struct timespec *period); + +/// +/// Set the timer to expire one after a specified period. +/// +/// 0 on succcess, -1 on failure, in which case errno contains more information. +/// Timer previously allocated with +/// or . +/// Period to wait before timer expires. +/// 0 on success, -1 on failure, in which case errno contains more +/// information. +/// +/// +int SetEventLoopTimerOneShot(EventLoopTimer *timer, const struct timespec *delay); + +/// +/// Disarm an existing event loop timer. +/// +/// Timer previously allocated with +/// or . +/// 0 on success; -1 on failure, in which case errno contains more +/// information. +/// +/// +int DisarmEventLoopTimer(EventLoopTimer *timer); diff --git a/RS485Driver/HLApp/launch.vs.json b/RS485Driver/HLApp/launch.vs.json new file mode 100644 index 0000000..77876f6 --- /dev/null +++ b/RS485Driver/HLApp/launch.vs.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.1", + "configurations": [ + { + "type": "azurespheredbg", + "name": "RS-485 App (HLCore)", + "project": "CMakeLists.txt", + "workingDirectory": "${workspaceRoot}", + "applicationPath": "${debugInfo.target}", + "imagePath": "${debugInfo.targetImage}", + "partnerComponents": [ "1CCE66F1-28E9-4DA4-AD25-D247FD362DE7" ] + } + ] +} \ No newline at end of file diff --git a/RS485Driver/HLApp/main.c b/RS485Driver/HLApp/main.c new file mode 100644 index 0000000..b1cb7e0 --- /dev/null +++ b/RS485Driver/HLApp/main.c @@ -0,0 +1,195 @@ +/* +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ + +// This sample C application for Azure Sphere sends messages to, and receives +// responses from, a real-time capable application running an RS-485 driver. +// It sends a message every second and prints the message which was sent, +// and the response which was received. +// +// It uses the following Azure Sphere libraries +// - log (displays messages in the Device Output window during debugging) +// - application (establish a connection with a real-time capable application). +// - eventloop (system invokes handlers for timer events) + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "..\common_defs.h" +#include "eventloop_timer_utilities.h" +#include "rs485_hl_driver.h" + + + +/// +/// Exit codes for this application. These are used for the +/// application exit code. They must all be between zero and 255, +/// where zero is reserved for successful termination. +/// +typedef enum { + ExitCode_Success = 0, + ExitCode_TermHandler_SigTerm = 1, + ExitCode_TimerHandler_Consume = 2, + ExitCode_SendMsg_Send = 3, + ExitCode_SocketHandler_Recv = 4, + ExitCode_Init_EventLoop = 5, + ExitCode_Init_SendTimer = 6, + ExitCode_Init_Connection = 7, + ExitCode_Init_Rs485 = 8, + ExitCode_Main_EventLoopFail = 9 +} ExitCode; + +static EventLoop *eventLoop = NULL; +static EventLoopTimer *sendTimer = NULL; +static volatile sig_atomic_t exitCode = ExitCode_Success; +static uint8_t rs485RxBuffer[2000]; + +// Handy typedef, used in SendTimerEventHandler for processing Modbus commands +typedef struct { + + uint8_t *command; + uint8_t length; + +} message_t; + +const char rtAppComponentId[] = "1CCE66F1-28E9-4DA4-AD25-D247FD362DE7"; + +static void TerminationHandler(int signalNumber); +static void SendTimerEventHandler(EventLoopTimer *timer); +static void Rs485ReceiveHandler(int bytesReceived); +static ExitCode InitHandlers(void); +static void CloseHandlers(void); + +/// +/// Signal handler for termination requests. This handler must be async-signal-safe. +/// +static void TerminationHandler(int signalNumber) +{ + // Don't use Log_Debug here, as it is not guaranteed to be async-signal-safe. + exitCode = ExitCode_TermHandler_SigTerm; +} + + + +/// +/// Handle send timer event by sending a command sequence to the RS-485 driver. +/// +static void SendTimerEventHandler(EventLoopTimer *timer) +{ + static int currCommand = 0; + static const message_t commands[] = { + + { "\xff\xff\xff\xff\x80\x25\x00\x00", 8}, // Change baud rate on the RS-485 driver (Note: invert endianness in the baudrate value!) + { "\x01\x04\x00\x01\x00\x01\x60\x0A", 8}, // Measure temperature + { "\x01\x04\x00\x02\x00\x01\x90\x0A", 8}, // Measure humidity + }; + + if (ConsumeEventLoopTimerEvent(timer) != 0) { + exitCode = ExitCode_TimerHandler_Consume; + return; + } + + Rs485_Send(commands[currCommand].command, commands[currCommand].length); + currCommand++; + currCommand %= 3; +} + +/// +/// Handle receive callback from the RS-485 driver. +/// The received bytes will be available in the RX buffer passed to Rs485_Init(). +/// +static void Rs485ReceiveHandler(int bytesReceived) +{ + // Just re-logging the received bytes. + if (bytesReceived > 0) + { + Log_Debug("Rs485 Callback: received %d bytes: ", bytesReceived); + for (int i = 0; i < bytesReceived; ++i) { + Log_Debug("%02x", rs485RxBuffer[i]); + if (i != bytesReceived - 1) { + Log_Debug(":"); + } + } + Log_Debug("\n"); + } + + return; +} + +/// +/// Set up SIGTERM termination handler and event handlers for send timer +/// and to receive data from real-time capable application. +/// +/// +/// ExitCode_Success if all resources were allocated successfully; otherwise another +/// ExitCode value which indicates the specific failure. +/// +static ExitCode InitHandlers(void) +{ + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + action.sa_handler = TerminationHandler; + sigaction(SIGTERM, &action, NULL); + + eventLoop = EventLoop_Create(); + if (eventLoop == NULL) { + Log_Debug("Could not create event loop.\n"); + return ExitCode_Init_EventLoop; + } + + // Register a one-second timer to send a message to the real-time RS-485 driver (RTApp). + static const struct timespec sendPeriod = { .tv_sec = 1, .tv_nsec = 0 }; + sendTimer = CreateEventLoopPeriodicTimer(eventLoop, &SendTimerEventHandler, &sendPeriod); + if (sendTimer == NULL) { + return ExitCode_Init_SendTimer; + } + + // Initialize the real-time RS-485 driver (RTApp). + if (Rs485_Init(eventLoop, rs485RxBuffer, sizeof(rs485RxBuffer), Rs485ReceiveHandler) == -1) { + return ExitCode_Init_Rs485; + } + + return ExitCode_Success; +} + +/// +/// Clean up the resources previously allocated. +/// +static void CloseHandlers(void) +{ + DisposeEventLoopTimer(sendTimer); + Rs485_Close(); + EventLoop_Close(eventLoop); +} + +int main(void) +{ + Log_Debug("High-level RS-485 comms application\n"); + Log_Debug("Sends messages to, and receives messages from an RS-485 driver running on the RT-Core.\n"); + + exitCode = InitHandlers(); + + while (exitCode == ExitCode_Success) { + EventLoop_Run_Result result = EventLoop_Run(eventLoop, -1, true); + // Continue if interrupted by signal, e.g. due to breakpoint being set. + if (result == EventLoop_Run_Failed && errno != EINTR) { + exitCode = ExitCode_Main_EventLoopFail; + } + } + + CloseHandlers(); + Log_Debug("Application exiting.\n"); + return exitCode; +} diff --git a/RS485Driver/HLApp/rs485_hl_driver.c b/RS485Driver/HLApp/rs485_hl_driver.c new file mode 100644 index 0000000..cb3e429 --- /dev/null +++ b/RS485Driver/HLApp/rs485_hl_driver.c @@ -0,0 +1,149 @@ +/* +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ + +#include +#include +#include +#include +#include +#include + +#include "..\common_defs.h" +#include "eventloop_timer_utilities.h" +#include "rs485_hl_driver.h" + +static int rtAppSockFd = -1; +static EventLoop *rs485eventLoop; +static EventRegistration *socketEventReg = NULL; +static Rs485ReceiveCallback *userCallback = NULL; +static uint8_t *rs485rxBuffer = NULL; +static size_t rs485rxBufferSize = 0; + +static void RTAppSocketEventHandler(EventLoop *el, int fd, EventLoop_IoEvents events, void *context); + +int Rs485_Init(EventLoop *eventLoop, uint8_t *rxBuffer, size_t rxBufferSize, Rs485ReceiveCallback *callback) +{ + if (rtAppSockFd >= 0) { + + Log_Debug("ERROR: RS-485 driver already initialized! Call Rs485_Close() before re-initializing.\n"); + return -1; + } + else + { + // Open a connection to the RS-485 driver RTApp. + rtAppSockFd = Application_Connect(rtAppComponentId); + if (rtAppSockFd == -1) { + Log_Debug("ERROR: Unable to create socket: %d (%s)\n", errno, strerror(errno)); + return -1; + } + + // Set the socket's timeout, to handle cases where real-time capable application does not respond. + static const struct timeval recvTimeout = { .tv_sec = 5, .tv_usec = 0 }; + int result = setsockopt(rtAppSockFd, SOL_SOCKET, SO_RCVTIMEO, &recvTimeout, sizeof(recvTimeout)); + if (result == -1) { + Log_Debug("ERROR: Unable to set socket timeout: %d (%s)\n", errno, strerror(errno)); + return -1; + } + + // Register the handler for incoming messages from real-time RS-485 driver. + socketEventReg = EventLoop_RegisterIo(eventLoop, rtAppSockFd, EventLoop_Input, RTAppSocketEventHandler, /* context */ NULL); + if (socketEventReg == NULL) { + Log_Debug("ERROR: Unable to register socket event: %d (%s)\n", errno, strerror(errno)); + return -1; + } + + // Setup the user-callback + userCallback = callback; + + // Setup the RX buffer + if (NULL == rxBuffer || rxBufferSize < 32) + { + Log_Debug("ERROR: RX buffer not defined or too small: %p (%zu bytes)\n", rxBuffer, rxBufferSize); + return -1; + } + else + { + rs485rxBuffer = rxBuffer; + rs485rxBufferSize = rxBufferSize; + } + } + + return 0; +} + +void Rs485_Close(void) +{ + EventLoop_UnregisterIo(rs485eventLoop, socketEventReg); + if (rtAppSockFd >= 0) { + int result = close(rtAppSockFd); + if (result != 0) { + Log_Debug("ERROR: Could not close rtAppSockFd %s: %s (%d).\n", "RTApp socket", strerror(errno), errno); + } + } + + rtAppSockFd = -1; + userCallback = NULL; + rs485rxBuffer = NULL; + rs485rxBufferSize = 0; +} + +int Rs485_Send(const void *data, size_t dataLen) +{ + // Prepare the block + if (dataLen > MAX_HLAPP_MESSAGE_SIZE) + { + Log_Debug("ERROR: data buffer too big: %d (> %d)\n", dataLen, MAX_HLAPP_MESSAGE_SIZE); + return -1; + } + + // Log the bytes to be sent + Log_Debug("Rs485_Driver: sending %ld bytes: ", dataLen); + for (int i = 0; i < dataLen; ++i) { + Log_Debug("%02x", ((char*)data)[i]); + if (i != dataLen - 1) { + Log_Debug(":"); + } + } + Log_Debug("\n"); + + // Send the block to the RS-485 RTApp driver + int bytesSent = send(rtAppSockFd, data, dataLen, 0); + if (bytesSent == -1) { + Log_Debug("ERROR: Unable to send message to the RS-485 driver: %d (%s)\n", errno, strerror(errno)); + return -1; + } + + return bytesSent; +} + +void RTAppSocketEventHandler(EventLoop *el, int fd, EventLoop_IoEvents events, void *context) +{ + if (NULL != rs485rxBuffer) + { + // Read response from real-time capable application. + // If the RTApp has sent more than rs485rxBufferSize, then truncate. + int bytesReceived = recv(fd, rs485rxBuffer, rs485rxBufferSize, 0); + + if (bytesReceived == -1) { + Log_Debug("ERROR: Unable to receive message from the RS-485 driver: %d (%s)\n", errno, strerror(errno)); + return; + } + + // Log the received bytes + Log_Debug("Rs485_Driver: received %d bytes: ", bytesReceived); + for (int i = 0; i < bytesReceived; ++i) { + Log_Debug("%02x", rs485rxBuffer[i]); + if (i != bytesReceived - 1) { + Log_Debug(":"); + } + } + Log_Debug("\n"); + + if (NULL != userCallback) + { + userCallback(bytesReceived); + } + } +} diff --git a/RS485Driver/HLApp/rs485_hl_driver.h b/RS485Driver/HLApp/rs485_hl_driver.h new file mode 100644 index 0000000..f610855 --- /dev/null +++ b/RS485Driver/HLApp/rs485_hl_driver.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + + +typedef void Rs485ReceiveCallback(int bytesReceived); // The receive callback, called upon any receive event from the real-time RS-485 driver (RTApp). +extern const char rtAppComponentId[]; // The corresponding RTApp's ComponentId, to be defined externally (i.e. in main.c). + +/// +/// Initializes the connection to the real-time RS-485 driver (RTApp). +/// +/// A pointer to the EventLoop to which register the inter-core communication with the real-time RS-485 driver (RTApp). +/// A pointer to a byte-buffer into which the RS-485 driver will store the bytes received from the real-time RS-485 driver (RTApp). +/// The size in bytes of the given byte-buffer. +/// A pointer to a 'Rs485ReceiveCallback'-typed callback function, which the HL-Core RS-485 driver invokes upon receive events. +/// '0' +int Rs485_Init(EventLoop *eventLoop, uint8_t *rxBuffer, size_t rxBufferSize, Rs485ReceiveCallback *callback); + +/// +/// Closes the internal handles managing the connection to the real-time RS-485 driver (RTApp). +/// +/// +void Rs485_Close(void); + +/// +/// Sends a byte-buffer to the real-time RS-485 driver (RTApp). +/// The maximum size of the data buffer is defined by the MAX_HLAPP_MESSAGE_SIZE macro in common_defs.h. +/// +/// A pointer to the data buffer. +/// Size of the data buffer in bytes. +/// The number of bytes sent or -1 on error. +int Rs485_Send(const void *data, size_t dataLen); \ No newline at end of file diff --git a/RS485Driver/LICENSE.txt b/RS485Driver/LICENSE.txt new file mode 100644 index 0000000..63447fd --- /dev/null +++ b/RS485Driver/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) Microsoft Corporation. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/RS485Driver/README.md b/RS485Driver/README.md new file mode 100644 index 0000000..2bc4d2e --- /dev/null +++ b/RS485Driver/README.md @@ -0,0 +1,276 @@ +# RS-485 real-time driver with HL-Core interfacing API + +This project implements a light weight RS-485 driver running one of the M4 cores in the MT3620, which leverages the low-level access to the UART's hardware registers for reliable communications. The RS-485 real-time driver then also leverages the [mailbox inter-core communication feature of the MT3620](https://docs.microsoft.com/azure-sphere/app-development/high-level-inter-app), for receiving/sending messages from/to the HL-Core App. + +Together with the RS-485 real-time driver, the project includes an high-level RS-485 driver API and a sample App (HLApp). The sample App shows how to initialize the HL-Core RS-485 driver, and send/receive data by leveraging its simple API interface. + + The high-level & real-time RS-485 drivers and sample use the following Azure Sphere and 3rd-party libraries. + +| Library | Purpose | +|---------|---------| +| [application](https://docs.microsoft.com/en-us/azure-sphere/reference/applibs-reference/api-overview) | Communicates with and controls the real-time capable application. | +| [eventloop](https://docs.microsoft.com/azure-sphere/reference/applibs-reference/applibs-eventloop/eventloop-overview) | Invokes handlers for timer events. | +| [log](https://docs.microsoft.com/azure-sphere/reference/applibs-reference/applibs-log/log-overview) | Displays messages in the Device Output window during debugging. | +| [CodethinkLabs mt3620-m4-drivers](https://github.com/CodethinkLabs/mt3620-m4-drivers) | Open source libraries implementing peripheral drivers for MT3620's M4 real-time cores. | + + +## Contents + +| File/folder | Description | +|-------------|-------------| +| `README.md` | This README file. | +| `rs485.code-workspace` | A Visual Studio Code workspace file that allows building and debugging the real-time and the high-level app at the same time. | +| `launch.vs.json` | JSON file that tells Visual Studio how to deploy and debug the application. | +| `common_defs.h` | Contains common definitions between the HLApp and RTApp. | +| `HLApp` | Folder containing the configuration files, source code files, and other files needed for the high-level application. | +| `RTApp` | Folder containing the configuration files, source code files, and other files needed for the RS-485 driver real-time application. | +| `HLApp\rs485_hl_driver.h/c` | The RS-485 driver API implementation, isolates from direct inter-core communications by abstracting Init/Read/Write operations +| `RTApp\ringBuffer.h/c` | Generic ring-buffer implementation for storing bytes received on the UART peripheral. | +| `RTApp\rs485_driver.h/c` | The RS-485 driver implementation, isolates from direct UART operation by abstracting Init/Read/Write operations. | + + +## Prerequisites + +- [Seeed MT3620 Development Kit](https://aka.ms/azurespheredevkits) or other hardware that implements the [MT3620 Reference Development Board (RDB)](https://docs.microsoft.com/azure-sphere/hardware/mt3620-reference-board-design) design. +- (Optional) A USB-to-serial adapter (for example, [FTDI Friend](https://www.digikey.com/catalog/en/partgroup/ftdi-friend/60311)) to connect the real-time capable core UART to a USB port on your PC. +- (Optional) A terminal emulator, such as Telnet or [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/), to display the RTApp's output. +- A voltage level-shifter, to interface the 3.3V UART of the MT3620 to a TTL RS-485 transceiver (for example, [3.3V-5V 4 Channels Logic Level Converter Bi-Directional Shifter Module](https://www.amazon.com/FTCBlock-3-3V-5V-Channels-Converter-Bi-Directional/dp/B07H2C6SJJ/ref=sr_1_1_sspa?dchild=1&keywords=level+shifter&qid=1628280388&s=electronics&sr=1-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEyU0ZUTVZLRzFFOU1CJmVuY3J5cHRlZElkPUEwMzcwMjA0MkxUTTdHRTFBRjRXVyZlbmNyeXB0ZWRBZElkPUExMDE2MDY4Mzc2STZHVzBPMDRXVCZ3aWRnZXROYW1lPXNwX2F0ZiZhY3Rpb249Y2xpY2tSZWRpcmVjdCZkb05vdExvZ0NsaWNrPXRydWU=)). +- A TTL RS-485 transceiver device, for driving the RS-485 field bus (for example, [MAX485 Chip Module TTL to RS-485 Instrument Interface Module](https://www.amazon.com/MAX485-Module-RS-485-Instrument-Interface/dp/B01N8WLEV0/ref=asc_df_B01N8WLEV0/?tag=hyprod-20&linkCode=df0&hvadid=216534554317&hvpos=&hvnetw=g&hvrand=4726439281244768822&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9033288&hvtargid=pla-350870533393&psc=1)). +- An RS-485 device and its related protocol documentation (for example, [Temperature Humidity Transmitter SHT20 Sensor High Precision Monitoring Modbus RS-485](https://www.amazon.com/gp/product/B07SZSSNRP/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1)). + + **Note:** If the RS-485 device is not 5V-compatible, a separate power source could be required, to power the device-only, and its GND must be connected to a GND pin on Azure Sphere development board. + +## Setup + +1. Clone the [Azure Sphere Gallery](https://github.com/Azure/azure-sphere-gallery) repository and find the *RS-485 Driver* project in the *RS485Driver* folder or download the zip file from GitHub. + +1. Prepare your device on Windows or Linux. + + **To prepare your device on Windows:** + + 1. Open the [Azure Sphere command-line tool](https://docs.microsoft.com/azure-sphere/reference/overview) with administrator privileges. + + Administrator privileges are required for enabling real-time core debugging because it installs USB drivers for the debugger. + + 1. Enter the [**azsphere device enable-development**](https://docs.microsoft.com/azure-sphere/reference/azsphere-device#enable-development) command as follows: + + Azure Sphere CLI: + + ``` + azsphere device enable-development --enable-rt-core-debugging + ``` + + Azure Sphere classic CLI: + + ``` + azsphere device enable-development --enablertcoredebugging + ``` + + 1. Close the window after the command completes because administrator privilege is no longer required. As a best practice, you should always use the lowest privilege that can accomplish a task. + + **To prepare your device on Linux:** + + 1. Enter the [**azsphere device enable-development**](https://docs.microsoft.com/azure-sphere/reference/azsphere-device#enable-development) command as follows: + + Azure Sphere CLI: + + ``` + azsphere device enable-development --enable-rt-core-debugging + ``` + + Azure Sphere classic CLI: + + ``` + azsphere device enable-development --enablertcoredebugging + ``` + +1. (Optional) Set up the hardware to display output from the RTApp. For instructions, see [Set up hardware to display output](https://docs.microsoft.com/azure-sphere/install/qs-real-time-application#set-up-hardware-to-display-output). + +## How to use + +### Wiring diagram +The following diagram shows how to connect all the devices listed in the [Prerequisites](##Prerequisites). + +**Note** that the *FTDI Friend* and RTApp degug terminal are optional, if you are not interested in visualizing logs from the real-time RS485 driver (RTApp): + +![](./wiring-diagram.png) + +### Configuration +1. Configure the HL App's max message size in `common_defs.h` to match your communication requirements: + ```c + // This is the maximum message size that the HLApp can send + // to the RS-485 RTApp driver. This header is used by both Apps. + #define MAX_HLAPP_MESSAGE_SIZE 64 + + // This is essentially the buffering time (in milliseconds), + // after which the real-time RS485 driver will send out + // any received bytes to the HLApp. + // Bytes are any ways sent in case the received amount + // overflows DRIVER_MAX_RX_BUFFER_FILL_SIZE (defined in rs485_driver.h). + #define RTDRV_SEND_DELAY_MSEC 10 + ``` +2. Configure the driver defines in `rs485_driver.h` to match your hardware setup (i.e. the below values match what illustrated in the [Wiring diagram](###Wiring-diagram)): + ```c + ////////////////////////////////////////////////////////////////////////////////// + // GLOBAL VARIABLES + ////////////////////////////////////////////////////////////////////////////////// + #define DRIVER_ISU MT3620_UNIT_ISU0 + #define DRIVER_ISU_DEFAULT_BAURATE 9600 + #define DRIVER_DE_GPIO 42 + #define DRIVER_MAX_RX_BUFFER_SIZE 2048 + #define DRIVER_MAX_RX_BUFFER_FILL_SIZE 2000 + ``` +3. Comment/uncomment the `DEBUG_INFO` definition in `RTApp\main.c`, wither or not the App is ready for production (for saving MCU cycles): + ```c + #define DEBUG_INFO + ``` +4. In the HL App, just initialize the RS-485 driver through `Rs485_Init()`, and write & read bytes as per the protocol definitions of your RS-485 device (i.e. Modbus, RS-232, etc.). + + In the current implementation, the HL App cycles every 3 seconds and sends three commands through the `Rs485_Send()` API function: the first sets the RS-485 real-time driver's baudrate to 9600 and the subsequent two are specific Modbus commands of a popular and cheap (chosen for sourcing simplicity) RS-485 Modbus temperature/humidity device based on an SHT20 sensor: + + ```c + static void SendTimerEventHandler(EventLoopTimer *timer) + { + static int currCommand = 0; + static const message_t commands[] = { + + { "\xff\xff\xff\xff\x80\x25\x00\x00", 8}, // Change baud rate on the RS-485 driver (Note: invert endianness in the baudrate value!) + { "\x01\x04\x00\x01\x00\x01\x60\x0A", 8}, // Measure temperature + { "\x01\x04\x00\x02\x00\x01\x90\x0A", 8}, // Measure humidity + }; + ... + ... + ``` + **Note**: the RS-485 real-time driver can receive a **special byte-command to change the UART's baudrate on-the-fly**. The format is very simple: four trailing `0xFF` bytes, followed by the four bytes (`uint32_t`) representing the desired baudrate value, in **big-endian** format. + + .e. for setting the RS-485 UART to 9600 baud, the byte-command to be sent is: + + ``` + [0xff 0xff 0xff 0xff 0x80 0x25 0x00 0x00] (8 bytes) + ``` + The driver will return an 8-byte response: on success it'll return `[0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00]`, on failure it'll return `[0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff]`. + +5. Responses from the RS-485 real-time driver are received by the HL-Core APIs within the `Rs485EventHandler()` callback function and byte-buffer, with which the RS-485 driver was initialized. The callback function must be of type `Rs485ReceiveCallback`. + +### Build and run the sample + +The applications in this sample run as partners. Make sure that they're designated as partners, as described in [Mark applications as partners](https://docs.microsoft.com/azure-sphere/app-development/sideload-app#mark-applications-as-partners), so that sideloading one doesn't delete the other. + +If you're using Visual Studio or Visual Studio Code, you will need to deploy and debug both apps simultaneously. See the following instructions for building and running +the sample with Visual Studio or Visual Studio Code. + +**To build and run the sample with Visual Studio:** + +1. On the **File** menu, select **Open > Folder**. +1. Navigate to the *RS485Driver* sample directory, and click **Select Folder**. +1. On the **Select Startup Item** menu, select **RS-485 Driver Sample (All Cores)**. +1. On the **Build** menu, select **Build All**. +1. On the **Debug** menu, select **Start**, or press **F5**. + +**To build and run the sample with Visual Studio Code:** + +1. Use the [Visual Studio Code Multi-root Workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces) feature to build and debug the RTApp and high-level app at the same time. +1. On the **File** menu, **Select Open Workspace**. +1. Navigate to the *RS485Driver* root directory and select the file `rs485.code-workspace`. +1. Click **Open**. +1. After the build files have been created, right-click on either of the two `CMakeLists.txt` files and select **Build All Projects**. +1. Click the **Run** icon in the menu on the left side of the screen. +1. On the pull-down menu, that appears at the top of the window on the left side of the screen, select **Launch for azure Sphere Applications (gdb)(workspace)**. +1. On the **Run** menu, select **Start Debugging**. + +If you're running the sample from the command line you will need to build and run the RTApp before you build and run the high-level app. For more information about building real-time capable and high-level applications from the command line, go to [Build a sample application](../../BUILD_INSTRUCTIONS.md) and click on the links *Tutorial: Build a real-time capable application* and *Build and run a high-level sample with the CLI* respectively. + +## Example + +Once every second, the high-level application sends a message to the RS-485 driver to request in turn: + +- a baudrate change +- a temperature reading +- a humidity measurement + +to an RS-485 sensor (the one used is a cheap temperature/humidity sensor based on the SHT20, easily sourceable online). + +The real-time RS-485 driver (RTApp) then takes care of sending the message over UART by driving the DE/!RE pins of a MAX485 transceiver. As soon as the RS-485 driver (RTApp) receives a (reply) message over UART, it immediately buffers it on an *RX ring-buffer* (for lossless receiving). The ring buffer is then used by a separate a GPT timer callback function, which in turn sends a message to the HLApp if any bytes are stored in the *RX ring buffer* (up to the maximum defined in `MAX_HLAPP_MESSAGE_SIZE`). + +Finally, the HLApp simply prints the received message (coming from the RS-485 sensor device) within the `Rs485EventHandler()` callback function (with which the RS-485 driver was initialized). The high-level application output will be displayed in the Output window in Visual Studio or Visual Studio Code. The output will look like this (dependently of the RS-485 device used): + +```sh +Remote debugging from host 192.168.35.1, port 57375 +High-level RS-485 comms application +Sends messages to, and receives messages from an RS-485 driver running on the RT-Core. +Rs485_Driver: sending 8 bytes: ff:ff:ff:ff:80:25:00:00 +Rs485_Driver: received 8 bytes: ff:ff:ff:ff:00:00:00:00 +Rs485 Callback: received 8 bytes: ff:ff:ff:ff:00:00:00:00 +Rs485_Driver: sending 8 bytes: 01:04:00:01:00:01:60:0a +Rs485_Driver: received 7 bytes: 01:04:02:01:38:b9:72 +Rs485 Callback: received 7 bytes: 01:04:02:01:38:b9:72 +Rs485_Driver: sending 8 bytes: 01:04:00:02:00:01:90:0a +Rs485_Driver: received 7 bytes: 01:04:02:01:bc:b9:11 +Rs485 Callback: received 7 bytes: 01:04:02:01:bc:b9:11 +... +``` + +Because the HLApp and RTApp work asynchronously for the maximum efficiency, messages may not correspond in their sequence. + +The real-time capable application output will be sent to the serial terminal for display. With `DEBUG_INFO` defined, the output will look like this (with the RS-485 device used in this sample): + +```sh +RS-485 real-time driver +App built on: Aug 8 2021, 13:23:18 +Changing baud rate to 9600 --> OK +Received 8 bytes from HLApp: 01:04:00:01:00:01:60:0a --> sending to RS-485 field bus +Received 7 bytes from RS-485 bus: 01:04:02:01:38:b9:72 +Sending 7 bytes to HLApp: 01:04:02:01:38:b9:72 +Received 8 bytes from HLApp: 01:04:00:02:00:01:90:0a --> sending to RS-485 field bus +Received 7 bytes from RS-485 bus: 01:04:02:01:bc:b9:11 +Sending 7 bytes to HLApp: 01:04:02:01:bc:b9:11 +... +``` + +Again, the numbers in the messages may start from different places. + + +## Key concepts + +The RS-485 driver basically wraps M4 UART libraries and instantly buffers all UART received bytes on IRQs, in order to abstract the communications with RS-485 devices and take care of communication-timing details, which that have been specifically tuned with signal scoping at the wire level. This way developers don't have to worry about performances during the TX/RX operations that may arise from driving the RS-485 communications directly from the HLApp with UART & GPIO interactions. + +Several high-level protocols, such as Modbus/Profinet/etc. can be developed on top of the RS-485 real-time driver, directly from within the high-level application code, by just handling the TX/RX byte-streams from the RS-485 deiver APIs, without worrying about the critical wire-timings for the physical field bus. + + +References: +- For an overview of Azure Sphere, see [What is Azure Sphere](https://docs.microsoft.com/azure-sphere/product-overview/what-is-azure-sphere). +- To learn about partner-application development, see [Create partner applications](https://docs.microsoft.com/azure-sphere/app-development/create-partner-apps). +- To learn about how a high-level application communicates with an RTApp, see [Communicate with a real-time capable application](https://docs.microsoft.com/azure-sphere/app-development/high-level-inter-app). +- To learn more about the open source libraries implementing drivers for MT3620's M4 real-time cores, see [CodethinkLabs mt3620-m4-drivers](https://github.com/CodethinkLabs/mt3620-m4-drivers). + + +## Project expectations + +This library can be used when interfacing an Azure Sphere based device to an RS-485 field bus, where deterministic reading speed on the bus is required, supported by the RS-485 driver running on the MT3620's M4 real-time core and RX buffering, which decouples IRQ handling from the inter-core communication with the A7-core through the mailbox. + +### Expected support for the code + +There is no official support guarantee for this code, but we will make a best effort to respond to/address any issues you encounter. + +### How to report an issue + +If you run into an issue with this script, please open a GitHub issue against this repo. + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to +agree to a Contributor License Agreement (CLA) declaring that you have the right to, +and actually do, grant us the rights to use your contribution. For details, visit +https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need +to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the +instructions provided by the bot. You will only need to do this once across all repositories using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## License + +For information about the licenses that apply to this script, see [LICENSE.txt](./LICENSE.txt) \ No newline at end of file diff --git a/RS485Driver/RTApp/.gitignore b/RS485Driver/RTApp/.gitignore new file mode 100644 index 0000000..f2dfeee --- /dev/null +++ b/RS485Driver/RTApp/.gitignore @@ -0,0 +1,4 @@ +# Ignore output directories +/.vs/ +/out/ +/install/ diff --git a/RS485Driver/RTApp/.vscode/launch.json b/RS485Driver/RTApp/.vscode/launch.json new file mode 100644 index 0000000..275677d --- /dev/null +++ b/RS485Driver/RTApp/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Azure Sphere App (RTCore)", + "type": "azurespheredbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "partnerComponents": [ "96ACA524-8113-4171-9C76-6FBDBB441131" ], + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/RS485Driver/RTApp/.vscode/settings.json b/RS485Driver/RTApp/.vscode/settings.json new file mode 100644 index 0000000..8833fd6 --- /dev/null +++ b/RS485Driver/RTApp/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "cmake.generator": "Ninja", + "cmake.buildDirectory": "${workspaceRoot}/out/ARM-${buildType}", + "cmake.buildToolArgs": [ "-v" ], + "cmake.configureSettings": { + "CMAKE_TOOLCHAIN_FILE": "${command:azuresphere.AzureSphereSdkDir}/CMakeFiles/AzureSphereRTCoreToolchain.cmake", + "ARM_GNU_PATH": "${command:azuresphere.ArmGnuPath}" + }, + "cmake.configureOnOpen": true, + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" +} \ No newline at end of file diff --git a/RS485Driver/RTApp/CMakeLists.txt b/RS485Driver/RTApp/CMakeLists.txt new file mode 100644 index 0000000..eb6d4e4 --- /dev/null +++ b/RS485Driver/RTApp/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.11) +project(RS-485_Driver_RealTimeApp C) + +# Create executable +add_executable(${PROJECT_NAME} main.c Socket.c ringBuffer.c rs485_driver.c lib/VectorTable.c lib/GPT.c lib/GPIO.c lib/UART.c lib/Print.c lib/MBox.c) +target_link_libraries(${PROJECT_NAME}) +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_DEPENDS ${CMAKE_SOURCE_DIR}/linker.ld) + +azsphere_configure_tools(TOOLS_REVISION "21.07") + +# Add MakeImage post-build command +azsphere_target_add_image_package(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PUBLIC -L"${CMAKE_SOURCE_DIR}") diff --git a/RS485Driver/RTApp/CMakeSettings.json b/RS485Driver/RTApp/CMakeSettings.json new file mode 100644 index 0000000..9c4fbdf --- /dev/null +++ b/RS485Driver/RTApp/CMakeSettings.json @@ -0,0 +1,55 @@ +{ + "environments": [ + { + "environment": "AzureSphere" + } + ], + "configurations": [ + { + "name": "ARM-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ + "AzureSphere" + ], + "buildRoot": "${projectDir}\\out\\${name}-${env.AzureSphereTargetApiSet}", + "installRoot": "${projectDir}\\install\\${name}-${env.AzureSphereTargetApiSet}", + "cmakeCommandArgs": "--no-warn-unused-cli", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "CMAKE_TOOLCHAIN_FILE", + "value": "${env.AzureSphereDefaultSDKDir}CMakeFiles\\AzureSphereRTCoreToolchain.cmake" + }, + { + "name": "ARM_GNU_PATH", + "value": "${env.DefaultArmToolsetPath}" + } + ] + }, + { + "name": "ARM-Release", + "generator": "Ninja", + "configurationType": "Release", + "inheritEnvironments": [ + "AzureSphere" + ], + "buildRoot": "${projectDir}\\out\\${name}-${env.AzureSphereTargetApiSet}", + "installRoot": "${projectDir}\\install\\${name}-${env.AzureSphereTargetApiSet}", + "cmakeCommandArgs": "--no-warn-unused-cli", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "CMAKE_TOOLCHAIN_FILE", + "value": "${env.AzureSphereDefaultSDKDir}CMakeFiles\\AzureSphereRTCoreToolchain.cmake" + }, + { + "name": "ARM_GNU_PATH", + "value": "${env.DefaultArmToolsetPath}" + } + ] + } + ] +} diff --git a/RS485Driver/RTApp/Connection Diagram.png b/RS485Driver/RTApp/Connection Diagram.png new file mode 100644 index 0000000..d516ec7 Binary files /dev/null and b/RS485Driver/RTApp/Connection Diagram.png differ diff --git a/RS485Driver/RTApp/README.md b/RS485Driver/RTApp/README.md new file mode 100644 index 0000000..cc68f0f --- /dev/null +++ b/RS485Driver/RTApp/README.md @@ -0,0 +1 @@ +Please see the [parent project README](../README.md) for more information. \ No newline at end of file diff --git a/RS485Driver/RTApp/Socket.c b/RS485Driver/RTApp/Socket.c new file mode 100644 index 0000000..987ee3c --- /dev/null +++ b/RS485Driver/RTApp/Socket.c @@ -0,0 +1,470 @@ +/* Copyright (c) Codethink Ltd. All rights reserved. + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +// This is derivative of logical-intercore.c in +// https://github.com/Azure/azure-sphere-samples/tree/master/Samples/IntercoreComms +// but rewritten to be more consistent with other high level drivers in +// sample set + +#include +#include + +#include "lib/MBox.h" + +#include "Socket.h" + +#define FIFO_MSG_NEG_LEN 3 + +typedef struct __attribute__((__packed__)) { + // read and write index in bytes + uint32_t writeIndex; + uint32_t readIndex; + uint32_t reserved[14]; +} Socket_Ringbuffer_Header; + +typedef struct __attribute__((__packed__)) { + Socket_Ringbuffer_Header header; + uint8_t data[]; +} Socket_Ringbuffer_Shared; + +typedef struct { + Socket_Ringbuffer_Shared *sharedData; + uintptr_t capacity; +} Socket_Ringbuffer; + +#define RB_WRITE_INDEX(rb) rb.sharedData->header.writeIndex +#define RB_READ_INDEX(rb) rb.sharedData->header.readIndex + +typedef struct { + Component_Id comp_id; + uint32_t reserved; +} Socket_Msg_Header; + +/* Handle to socket connection containing state of shared ring buffer + + ringRemote state is updated by the A7 core and read by the M4 core + ringLocal state is updated by the M4 core and read by the A7 core */ +struct Socket { + bool open; + void (*rx_cb)(Socket*); + MBox *mailbox; + Socket_Ringbuffer ringRemote; + Socket_Ringbuffer ringLocal; +}; + +static Socket context = {0}; + +// Buffer descriptor commands +#define SOCKET_CMD_LOCAL_BUFFER_DESC 0xba5e0001 +#define SOCKET_CMD_REMOTE_BUFFER_DESC 0xba5e0002 +#define SOCKET_CMD_END_OF_SETUP 0xba5e0003 + +// Blocks inside the shared buffer have this alignment. +#define RB_ALIGNMENT 16 +// Maximum payload size in bytes. This does not include a header which +// is prepended by +#define RB_MAX_PAYLOAD_LEN 1040 + +static const uint8_t SOCKET_PORT_MSG_RECV = 1; +static const uint8_t SOCKET_PORT_MSG_SENT = 0; +static const uint8_t SOCKET_PORT_FLAGS = + (SOCKET_PORT_MSG_RECV + 1) | (SOCKET_PORT_MSG_SENT + 1); + +static uint32_t RoundUp(uint32_t value, uint32_t alignment) +{ + // alignment must be a power of two. + return (value + (alignment - 1)) & ~(alignment - 1); +} + +static Socket_Ringbuffer Socket_Ringbuffer__Parse_Desc(uint32_t buffer_desc) +{ + Socket_Ringbuffer buffer; + // The buffer size is encoded as a power of two in the bottom five bits. + buffer.capacity = (1U << (buffer_desc & 0x1F)) - sizeof(Socket_Ringbuffer_Header); + // The buffer header is a 32-byte aligned pointer which is stored in the + // top 27 bits. + buffer.sharedData = (Socket_Ringbuffer_Shared*)(buffer_desc & ~0x1F); + + return buffer; +} + +static void Socket__Msg_Available(void *user_data, uint8_t port) +{ + if ((port != SOCKET_PORT_MSG_RECV) || !user_data || + (port >= MBOX_SW_INT_PORT_COUNT)) + { + return; + } + + Socket *handle = (Socket*)user_data; + + handle->rx_cb(handle); +} + +Socket* Socket_Open(void (*rx_cb)(Socket*)) +{ + if (context.open) { + return NULL; + } + + // Initialise MBox and FIFO + MBox *mbox; + if ((mbox = MBox_FIFO_Open( + MT3620_UNIT_MBOX_CA7, NULL, NULL, NULL, &context, -1, -1)) == NULL) { + return NULL; + } + + context.mailbox = mbox; + if (Socket_Negotiate(&context) != ERROR_NONE) { + Socket_Close(&context); + return NULL; + } + + // Setup SW Interrupts + if (MBox_SW_Interrupt_Setup( + context.mailbox, SOCKET_PORT_FLAGS, + Socket__Msg_Available) != ERROR_NONE) + { + Socket_Close(&context); + return NULL; + } + + // Update context + context.rx_cb = rx_cb; + context.open = true; + + return &context; +} + +int32_t Socket_Close(Socket *socket) +{ + if (!socket || !socket->open) { + return ERROR_PARAMETER; + } + + MBox_SW_Interrupt_Teardown(socket->mailbox); + MBox_FIFO_Close(socket->mailbox); + socket->open = false; + + return ERROR_NONE; +} + + +bool Socket_NegotiationPending(Socket *socket) +{ + if (!socket) { + return false; + } + + return (MBox_FIFO_Reads_Available(socket->mailbox) != 0); +} + +int32_t Socket_Negotiate(Socket *socket) +{ + if (!socket) { + return ERROR_SOCKET_NEGOTIATION; + } + + // Get buffer descriptors from MBox FIFO + uint32_t cmd[FIFO_MSG_NEG_LEN], data[FIFO_MSG_NEG_LEN]; + + // Block and wait for A7 core to negotiate buffer descriptors + if (MBox_FIFO_ReadSync(socket->mailbox, cmd, data, FIFO_MSG_NEG_LEN) != ERROR_NONE) + { + MBox_FIFO_Close(socket->mailbox); + return ERROR_SOCKET_NEGOTIATION; + } + + // Parse buffer descriptors + Socket_Ringbuffer ringRemote, ringLocal; + unsigned parsed = 0; + + for (unsigned i = 0; i < FIFO_MSG_NEG_LEN; i++) { + switch (cmd[i]) { + case SOCKET_CMD_LOCAL_BUFFER_DESC: + ringLocal = Socket_Ringbuffer__Parse_Desc(data[i]); + parsed |= 1; + break; + + case SOCKET_CMD_REMOTE_BUFFER_DESC: + ringRemote = Socket_Ringbuffer__Parse_Desc(data[i]); + parsed |= 2; + break; + + case SOCKET_CMD_END_OF_SETUP: + parsed |= 4; + break; + + default: + break; + } + } + + if ((parsed != 7) || + (ringLocal.capacity == 0) || + (ringRemote.capacity == 0)) + { + return ERROR_SOCKET_NEGOTIATION; + } + + socket->ringRemote = ringRemote; + socket->ringLocal = ringLocal; + + return ERROR_NONE; +} + + +void Socket_Reset(Socket *socket) +{ + if (!socket) { + return; + } + + MBox_FIFO_Reset(socket->mailbox, true); +} + +static void Socket__Signal(Socket *socket, uint8_t port) +{ + // Ensure memory writes have completed (not just been sent) before raising interrupt. + // "no instruction that appears in program order after the DSB instruction can execute until the + // DSB completes" ARMv7M Architecture Reference Manual, ARM DDI 0403E.d S A3.7.3 + __asm__ volatile("dsb"); + MBox_SW_Interrupt_Trigger(socket->mailbox, port); +} + +// Helper function for Socket_Write. Writes data to the local ringbuffer, +// and wraps around to start of buffer if required. Returns updated write position. +static uint32_t Socket__Write_RB( + const Socket_Ringbuffer *rb, uint32_t startPos, const void *src, size_t size) +{ + uint32_t spaceToEnd = rb->capacity - startPos; + + uint32_t writeToEnd = size; + // If the new data would wrap around the end of the buffer then only write + // spaceToEnd bytes before subsequently writing to the start of the buffer. + if (size > spaceToEnd) { + writeToEnd = spaceToEnd; + } + + const uint8_t *src8 = (const uint8_t *)src; + + __builtin_memcpy(&(rb->sharedData->data[startPos]), src8, writeToEnd); + // If not enough space to write all data before end of buffer, then write remainder at start. + __builtin_memcpy(&(rb->sharedData->data[0]), src8 + writeToEnd, size - writeToEnd); + + uint32_t finalPos = startPos + size; + if (finalPos > rb->capacity) { + finalPos -= rb->capacity; + } + return finalPos; +} + +int32_t Socket_Write( + Socket *socket, + const Component_Id *recipient, + const void *data, + uint32_t size) +{ + if (!socket || !recipient || !data || (size == 0)) { + return ERROR_PARAMETER; + } + + if (size > RB_MAX_PAYLOAD_LEN) { + return ERROR_SOCKET_INSUFFICIENT_SPACE; + } + + // Last position read by HLApp. Corresponding release occurs on + // high-level core. + uint32_t remoteReadPosition; + __atomic_load(&(RB_READ_INDEX(socket->ringRemote)), + &remoteReadPosition, __ATOMIC_ACQUIRE); + // Last position written to by RTApp. + uint32_t localWritePosition = RB_WRITE_INDEX(socket->ringLocal); + + // Sanity check read and write positions. + if ((remoteReadPosition >= socket->ringLocal.capacity) || + ((remoteReadPosition % RB_ALIGNMENT) != 0) || + (localWritePosition >= socket->ringLocal.capacity) || + ((localWritePosition % RB_ALIGNMENT) != 0)) { + return ERROR_SOCKET_INSUFFICIENT_SPACE; + } + + // If the read pointer is behind the write pointer, then the free space + // wraps around, and the used space doesn't. + uint32_t availSpace; + if (remoteReadPosition <= localWritePosition) { + availSpace = remoteReadPosition - localWritePosition + + socket->ringLocal.capacity; + } else { + availSpace = remoteReadPosition - localWritePosition; + } + + // Check whether there is enough space to enqueue the next block. + uint32_t reqBlockSize = sizeof(uint32_t) + sizeof(Socket_Msg_Header) + size; + + if (availSpace < reqBlockSize + RB_ALIGNMENT) { + return ERROR_SOCKET_INSUFFICIENT_SPACE; + } + + // The value in the block size field does not include the space taken by the + // block size field itself. + uint32_t blockSizeExcSizeField = reqBlockSize - sizeof(uint32_t); + localWritePosition = Socket__Write_RB( + &(socket->ringLocal), localWritePosition, &blockSizeExcSizeField, + sizeof(blockSizeExcSizeField)); + + // Write header + Socket_Msg_Header msg_header = {0}; + msg_header.comp_id = *recipient; + localWritePosition = Socket__Write_RB( + &(socket->ringLocal), localWritePosition, + &msg_header, sizeof(Socket_Msg_Header)); + + // Write data + localWritePosition = Socket__Write_RB( + &(socket->ringLocal), localWritePosition, data, size); + + // Advance write position to start of next possible block. + localWritePosition = RoundUp(localWritePosition, RB_ALIGNMENT); + if (localWritePosition >= socket->ringLocal.capacity) { + localWritePosition -= socket->ringLocal.capacity; + } + + // Ensure write position update is seen after new content has been written. + // Corresponding acquire is on high-level core. + __atomic_store( + &(RB_WRITE_INDEX(socket->ringLocal)), + &localWritePosition, __ATOMIC_RELEASE); + + Socket__Signal(socket, SOCKET_PORT_MSG_SENT); + + return ERROR_NONE; +} + +// Helper function for Socket_Read. Reads data from the remote ring buffer, +// and wraps around to start of buffer if required. Returns updated read position. +static uint32_t Socket__Read_RB( + const Socket_Ringbuffer *rb, uint32_t startPos, void *dest, size_t size) +{ + uint32_t availToEnd = rb->capacity - startPos; + + uint32_t readFromEnd = size; + // If the available data wraps around the end of the buffer then only read + // availToEnd bytes before subsequently reading from the start of the buffer. + if (size > availToEnd) { + readFromEnd = availToEnd; + } + + uint8_t *dest8 = (uint8_t *)dest; + __builtin_memcpy(dest, &(rb->sharedData->data[startPos]), readFromEnd); + + // If block wrapped around the end of the buffer, then read remainder from start. + __builtin_memcpy(dest8 + readFromEnd, &(rb->sharedData->data[0]), size - readFromEnd); + + uint32_t finalPos = startPos + size; + if (finalPos > rb->capacity) { + finalPos -= rb->capacity; + } + return finalPos; +} + +int32_t Socket_Read( + Socket *socket, + Component_Id *sender, + void *data, + uint32_t *size) +{ + if (!socket || !sender || !data || !size) { + return ERROR_PARAMETER; + } + // Don't read message content until have seen that remote write position has been updated. + // Corresponding release occurs on high-level core. + uint32_t remoteWritePosition; + __atomic_load(&(RB_WRITE_INDEX(socket->ringRemote)), &remoteWritePosition, __ATOMIC_ACQUIRE); + // Last position read from by this RTApp. + uint32_t localReadPosition = RB_READ_INDEX(socket->ringLocal); + + // Sanity check read and write positions. + if ((remoteWritePosition >= socket->ringRemote.capacity) || + ((remoteWritePosition % RB_ALIGNMENT) != 0) || + (localReadPosition >= socket->ringRemote.capacity) || + ((localReadPosition % RB_ALIGNMENT) != 0)) { + return ERROR_SOCKET_INSUFFICIENT_SPACE; + } + + // Get the maximum amount of available data. The actual block size may be + // smaller than this. + + uint32_t availData; + // If data is contiguous in buffer then difference between write and read positions... + if (remoteWritePosition >= localReadPosition) { + availData = remoteWritePosition - localReadPosition; + } + // ...else data wraps around end and resumes at start of buffer + else { + availData = remoteWritePosition - localReadPosition + socket->ringRemote.capacity; + } + + // The amount of available data must be at least enough to hold the block size. + // If not, caller will assume that no message was available. + const size_t blockSizeSize = sizeof(uint32_t); + // The block size must be stored in four contiguous bytes before wraparound. + uint32_t dataToEnd = socket->ringRemote.capacity - localReadPosition; + if ((availData < blockSizeSize) || (blockSizeSize > dataToEnd)) { + return ERROR_SOCKET_INSUFFICIENT_SPACE; + } + + // The block size followed by the actual block can be no longer than the available data. + uint32_t blockSize; + localReadPosition = Socket__Read_RB( + &(socket->ringRemote), localReadPosition, &blockSize, sizeof(blockSize)); + uint32_t totalBlockSize; + + totalBlockSize = blockSizeSize + blockSize; + if (totalBlockSize > availData) { + return ERROR_SOCKET_INSUFFICIENT_SPACE; + } + + if (blockSize < sizeof(Socket_Msg_Header)) { + return ERROR_SOCKET_INSUFFICIENT_SPACE; + } + + // The caller-supplied buffer must be large enough to contain the + // payload in the buffer, excluding component ID and reserved word. + size_t senderPayloadSize = blockSize - sizeof(Socket_Msg_Header); + if (senderPayloadSize > *size) { + return ERROR_SOCKET_INSUFFICIENT_SPACE; + } + + // Tell the caller the actual block size. + *size = senderPayloadSize; + + // Read the sender header. This may wraparound to the start of the buffer. + localReadPosition = Socket__Read_RB( + &(socket->ringRemote), localReadPosition, + sender, sizeof(Socket_Msg_Header)); + + // Read data + localReadPosition = Socket__Read_RB( + &(socket->ringRemote), + localReadPosition, data, senderPayloadSize); + + // Align read position to next possible location for next buffer. + // This may wrap around. + localReadPosition = RoundUp(localReadPosition, RB_ALIGNMENT); + if (localReadPosition >= socket->ringRemote.capacity) { + localReadPosition -= socket->ringRemote.capacity; + } + + // The message content must have been retrieved before the high-level core + // sees the read position has been updated. Corresponding acquire occurs + // on high-level core. + __atomic_store( + &(RB_READ_INDEX(socket->ringLocal)), + &localReadPosition, __ATOMIC_RELEASE); + + Socket__Signal(socket, SOCKET_PORT_MSG_RECV); + + return ERROR_NONE; +} diff --git a/RS485Driver/RTApp/Socket.h b/RS485Driver/RTApp/Socket.h new file mode 100644 index 0000000..4927502 --- /dev/null +++ b/RS485Driver/RTApp/Socket.h @@ -0,0 +1,70 @@ +/* Copyright (c) Codethink Ltd. All rights reserved. + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +#ifndef AZURE_SPHERE_SOCKET_H_ +#define AZURE_SPHERE_SOCKET_H_ + +#include "lib/Common.h" +#include "lib/Platform.h" + +#include +#include + +// This interface is for communicating over a "socket" with a partner core. +// It supports connection with linux socket interface on the A7, which +// negotiates the connection by calling Application_Connect(Component_Id). +// Implementation depends on MBox.h. +// It is derivative of logical-intercore.h in +// https://github.com/Azure/azure-sphere-samples/tree/master/Samples/IntercoreComms + + +#ifdef __cplusplus +extern "C" { +#endif + +/// Returned when there's a space issue. +#define ERROR_SOCKET_INSUFFICIENT_SPACE (ERROR_SPECIFIC - 1) + +/// Returned when negotiation fails. +#define ERROR_SOCKET_NEGOTIATION (ERROR_SPECIFIC - 2) + +typedef struct Socket Socket; + +/// When sending a message, this is the recipient HLApp's component ID. +/// When receiving a message, this is the sender HLApp's component ID. +typedef struct { + /// 4-byte little-endian word + uint32_t seg_0; + /// 2-byte little-endian half + uint16_t seg_1; + /// 2-byte little-endian half + uint16_t seg_2; + /// 2-byte big-endian & 6-byte big-endian + uint8_t seg_3_4[8]; +} Component_Id; + +Socket* Socket_Open(void (*rx_cb)(Socket*)); +int32_t Socket_Close(Socket *socket); + +bool Socket_NegotiationPending(Socket *socket); +int32_t Socket_Negotiate(Socket *socket); + +void Socket_Reset(Socket *socket); + +int32_t Socket_Write( + Socket *socket, + const Component_Id *recipient, + const void *data, + uint32_t size); +int32_t Socket_Read( + Socket *socket, + Component_Id *sender, + void *data, + uint32_t *size); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef AZURE_SPHERE_SOCKET_H_ diff --git a/RS485Driver/RTApp/app_manifest.json b/RS485Driver/RTApp/app_manifest.json new file mode 100644 index 0000000..33fb352 --- /dev/null +++ b/RS485Driver/RTApp/app_manifest.json @@ -0,0 +1,12 @@ +{ + "SchemaVersion": 1, + "Name": "RS-485 Driver", + "ComponentId": "1CCE66F1-28E9-4DA4-AD25-D247FD362DE7", + "EntryPoint": "/bin/app", + "Capabilities": { + "AllowedApplicationConnections": [ "96ACA524-8113-4171-9C76-6FBDBB441131" ], + "Gpio": [ 42 ], + "Uart": [ "ISU0" ] + }, + "ApplicationType": "RealTimeCapable" +} diff --git a/RS485Driver/RTApp/launch.vs.json b/RS485Driver/RTApp/launch.vs.json new file mode 100644 index 0000000..f053e9c --- /dev/null +++ b/RS485Driver/RTApp/launch.vs.json @@ -0,0 +1,20 @@ +{ + "version": "0.2.1", + "defaults": {}, + "configurations": [ + { + "type": "azurespheredbg", + "name": "RS-485 Driver (RTCore)", + "project": "CMakeLists.txt", + "inheritEnvironments": [ + "AzureSphere" + ], + "customLauncher": "AzureSphereLaunchOptions", + "workingDirectory": "${workspaceRoot}", + "applicationPath": "${debugInfo.target}", + "imagePath": "${debugInfo.targetImage}", + "targetCore": "RTCore", + "partnerComponents": [ "96ACA524-8113-4171-9C76-6FBDBB441131" ] + } + ] +} \ No newline at end of file diff --git a/RS485Driver/RTApp/lib b/RS485Driver/RTApp/lib new file mode 160000 index 0000000..8f8da0e --- /dev/null +++ b/RS485Driver/RTApp/lib @@ -0,0 +1 @@ +Subproject commit 8f8da0e24a5c94abf447ee067bc18268e05e553d diff --git a/RS485Driver/RTApp/linker.ld b/RS485Driver/RTApp/linker.ld new file mode 100644 index 0000000..edd7398 --- /dev/null +++ b/RS485Driver/RTApp/linker.ld @@ -0,0 +1,4 @@ +/* Copyright (c) Codethink Ltd. All rights reserved. + Licensed under the MIT License. */ + +INCLUDE lib/linker.ld diff --git a/RS485Driver/RTApp/main.c b/RS485Driver/RTApp/main.c new file mode 100644 index 0000000..21acd6a --- /dev/null +++ b/RS485Driver/RTApp/main.c @@ -0,0 +1,295 @@ +/* +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ + +#include +#include +#include +#include + +#include "lib/mt3620/gpt.h" +#include "lib/GPT.h" +#include "lib/CPUFreq.h" +#include "lib/VectorTable.h" +#include "lib/NVIC.h" +#include "lib/Print.h" + +#include "..\common_defs.h" +#include "Socket.h" +#include "ringBuffer.h" +#include "rs485_driver.h" + +#define DEBUG_INFO +static UART *debug = NULL; +static Socket *socket = NULL; +static GPT *sendTimer = NULL; + +static const Component_Id A7ID = +{ + .seg_0 = 0x96ACA524, + .seg_1 = 0x8113, + .seg_2 = 0x4171, + .seg_3_4 = {0x9C, 0x76, 0x6F, 0xBD, 0xBB, 0x44, 0x11, 0x31} +}; + +typedef struct CallbackNode { + bool enqueued; + struct CallbackNode *next; + void *data; + void (*cb_void)(void); + void (*cb_void_ptr)(void *); +} CallbackNode; +static CallbackNode *volatile callbacks = NULL; + + +// Function prototypes +static void HandleUartRxIrq(void); + +// Callback handlers +static void EnqueueCallback(CallbackNode *node) +{ + uint32_t prevBasePri = NVIC_BlockIRQs(); + if (!node->enqueued) { + CallbackNode *prevHead = callbacks; + node->enqueued = true; + callbacks = node; + node->next = prevHead; + } + NVIC_RestoreIRQs(prevBasePri); +} +static void InvokeCallbacks(void) +{ + CallbackNode *node = NULL; + + do { + uint32_t prevBasePri = NVIC_BlockIRQs(); + node = callbacks; + if (node) { + node->enqueued = false; + callbacks = node->next; + } + NVIC_RestoreIRQs(prevBasePri); + + if (node) { + if (NULL != node->cb_void) + { + (*node->cb_void)(); + } + else if (NULL != node->cb_void_ptr) + { + (*node->cb_void_ptr)(node->data); + } + } + } while (node); +} + +// Handlers for messages received from the HLApp +static void handleRecvMsg(void *handle) +{ + Socket *socket = (Socket *)handle; + + Component_Id senderId; + static uint8_t msg[MAX_HLAPP_MESSAGE_SIZE]; + + if (Socket_NegotiationPending(socket)) { + UART_Printf(debug, "Negotiation pending, attempting renegotiation\n"); + + // NB: this is blocking, if you want to protect against hanging, add a timeout! + if (Socket_Negotiate(socket) != ERROR_NONE) { + UART_Printf(debug, "ERROR: renegotiating socket connection\n"); + } + } + + // Read the message from the HLApp mailbox socket + uint32_t bytesRead = sizeof(msg); + int32_t error = Socket_Read(socket, &senderId, &msg, &bytesRead); + if (error != ERROR_NONE) { + UART_Printf(debug, "ERROR: receiving message from HLApp - %ld\r\n", error); + } + else if (bytesRead > sizeof(msg)) { + UART_Printf(debug, "ERROR: message from HLApp too long - %ld\r\n", bytesRead); + } + + + // Is this a special command? + if (*((uint32_t *)&msg[0]) == 0xffffffffUL) + { + bool bRes = Rs485_Init(*((uint16_t *)&msg[4]), NULL); + + if (bRes) + { + memcpy(msg, "\xff\xff\xff\xff\x00\x00\x00\x00", 8); + } + else + { + memcpy(msg, "\xff\xff\xff\xff\xff\xff\xff\xff", 8); + } + +#ifdef DEBUG_INFO + UART_Printf(debug, "Changing baud rate to %d --> %s\r\n", *((uint16_t *)&msg[4]), bRes ? "OK" : "FAILED!!"); +#endif + + if (ring_buffer_push_bytes(&rs485_rxRingBuffer, msg, 8) == -1) + { + UART_Print(debug, "Message to HLApp LOST (rs485_rxRingBuffer overflow)!! "); + } + } + else + { +#ifdef DEBUG_INFO + UART_Printf(debug, "Received %ld bytes from HLApp: ", bytesRead); + for (uint32_t i = 0; i < bytesRead; ++i) { + UART_Printf(debug, "%02x", msg[i]); + if (i != bytesRead - 1) { + UART_Print(debug, ":"); + } + } + UART_Print(debug, " --> sending to RS-485 field bus\r\n"); +#endif + if ((error = Rs485_Write(msg, bytesRead)) != ERROR_NONE) + { + UART_Printf(debug, "Message from HLApp LOST (error: %ld)!!\r\n", error); + } + } +} +static void handleRecvMsgWrapper(Socket *handle) +{ + static CallbackNode cbn = { .enqueued = false, .cb_void = NULL, .cb_void_ptr = handleRecvMsg, .data = NULL }; + + if (!cbn.data) { + cbn.data = handle; + } + + EnqueueCallback(&cbn); +} + +// Handler for messages to be sent to the HLApp +static void handleSendMsgTimer(void *data) +{ + // Dequeue the bytes to be sent to the HLApp + int bytesBuffered = ring_buffer_count(&rs485_rxRingBuffer); + uint8_t buffer[bytesBuffered]; + int32_t error; + + if ((error = ring_buffer_pop_bytes(&rs485_rxRingBuffer, buffer, bytesBuffered)) == -1) + { + UART_Printf(debug, "Message from HLApp LOST (error: %ld)!!\r\n", error); + } + else + { +#ifdef DEBUG_INFO + UART_Printf(debug, "Sending %d bytes to HLApp: ", bytesBuffered); + for (uint32_t i = 0; i < bytesBuffered; ++i) { + UART_Printf(debug, "%02x", buffer[i]); + if (i != bytesBuffered - 1) { + UART_Print(debug, ":"); + } + } + UART_Print(debug, "\r\n"); +#endif + error = Socket_Write(socket, &A7ID, buffer, bytesBuffered); + if (error != ERROR_NONE) { + UART_Printf(debug, "ERROR: sending message - %ld\r\n", error); + } + } + + Socket_Reset(socket); // Simulate reboot +} +static void handleSendMsgTimerWrapper(GPT *timer) +{ + if (NULL != timer) + (void)(timer); + + static CallbackNode cbn = { .enqueued = false, .cb_void = NULL, .cb_void_ptr = handleSendMsgTimer, .data = NULL }; + EnqueueCallback(&cbn); +} + +// IRQ Handlers for the RS-485 UART +static void HandleUartRxIrqDeferred(void) +{ + uintptr_t avail = Rs485_ReadAvailable(); + if (avail == 0) { + UART_Print(debug, "ERROR: UART received interrupt for zero bytes.\r\n"); + return; + } + + uint8_t buffer[avail]; + if (Rs485_Read(buffer, avail) != ERROR_NONE) { + + UART_Printf(debug, "ERROR: Failed to read %zu bytes from UART.\r\n", avail); + return; + } + +#ifdef DEBUG_INFO + UART_Printf(debug, "Received %zu bytes from RS-485 bus: ", avail); + for (uint32_t i = 0; i < avail; ++i) { + UART_Printf(debug, "%02x", buffer[i]); + if (i != avail - 1) { + UART_Print(debug, ":"); + } + } + UART_Printf(debug, "\r\n"); +#endif + + // If the RX buffer overflows the desired limit, immediately send the bytes to the HLApp + // so to lower chances of losing bytes from the serial port in between GPT interrupts. + if (ring_buffer_count(&rs485_rxRingBuffer) > DRIVER_MAX_RX_BUFFER_FILL_SIZE) + { + handleSendMsgTimerWrapper(NULL); + } + + // Enqueue the received bytes in the ring buffer, to be sent to the HLApp upon GPT interrupts. + if (ring_buffer_push_bytes(&rs485_rxRingBuffer, buffer, avail) == -1) + { + UART_Print(debug, "Message from UART LOST (rs485_rxRingBuffer overflow)!! "); + } +} +static void HandleUartRxIrq(void) { + static CallbackNode cbn = { .enqueued = false, .cb_void = HandleUartRxIrqDeferred, .cb_void_ptr = NULL, .data = NULL }; + EnqueueCallback(&cbn); +} + +_Noreturn void RTCoreMain(void) +{ + VectorTableInit(); + //CPUFreq_Set(26000000); + + // Initialize the debug UART + debug = UART_Open(MT3620_UNIT_UART_DEBUG, 115200, UART_PARITY_NONE, 1, NULL); + UART_Print(debug, "RS-485 real-time driver\r\n"); + UART_Print(debug, "Built on: " __DATE__ " " __TIME__ "\r\n"); + + // Initialize the RS-485 driver + Rs485_Init(9600, HandleUartRxIrq); + + // Setup GPT1 as "Write to HLApp" timer + sendTimer = GPT_Open(MT3620_UNIT_GPT0, MT3620_GPT_012_HIGH_SPEED, GPT_MODE_REPEAT); + if (!sendTimer) { + UART_Printf(debug, "ERROR: GPT_Open failed\r\n"); + } + else + { + int32_t error; + if ((error = GPT_SetMode(sendTimer, GPT_MODE_REPEAT)) != ERROR_NONE) { + UART_Printf(debug, "ERROR: GPT_SetMode failed %ld\r\n", error); + } + if ((error = GPT_StartTimeout( + sendTimer, RTDRV_SEND_DELAY_MSEC, GPT_UNITS_MILLISEC, + handleSendMsgTimerWrapper)) != ERROR_NONE) { + UART_Printf(debug, "ERROR: GPT_StartTimeout failed %ld\r\n", error); + } + } + + // Setup the receive socket for the HLApp + socket = Socket_Open(handleRecvMsgWrapper); + if (!socket) { + UART_Printf(debug, "ERROR: Socket_Open failed\r\n"); + } + + for (;;) { + __asm__("wfi"); + InvokeCallbacks(); + } +} + diff --git a/RS485Driver/RTApp/ringBuffer.c b/RS485Driver/RTApp/ringBuffer.c new file mode 100644 index 0000000..89ec378 --- /dev/null +++ b/RS485Driver/RTApp/ringBuffer.c @@ -0,0 +1,56 @@ +/* +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ + +#include "ringBuffer.h" + + +void ring_buffer_init(ringBuffer_t *rb, uint8_t *bufferBase, uint32_t maxSize) +{ + rb->bufferBase = bufferBase; + rb->bufferHead = rb->bufferTail = rb->bufferCount = 0; + rb->bufferMaxSize = maxSize; +} + +inline uint32_t ring_buffer_count(ringBuffer_t *cb) +{ + return cb->bufferCount; +} + +inline bool ring_buffer_isFull(ringBuffer_t *cb) +{ + return (cb->bufferCount >= cb->bufferMaxSize); +} + +int ring_buffer_push_bytes(ringBuffer_t *rb, uint8_t *buffer, uint32_t length) +{ + if (rb->bufferCount + length < rb->bufferMaxSize) + { + for (unsigned i = 0; i < length; i++) { + + rb->bufferBase[rb->bufferHead++] = buffer[i]; + rb->bufferHead %= rb->bufferMaxSize; + } + rb->bufferCount += length; + return length; + } + + return -1; +} + +int ring_buffer_pop_bytes(ringBuffer_t *rb, uint8_t *buffer, uint32_t length) +{ + if (rb->bufferCount > 0) + { + for (unsigned i = 0; i < length; i++) { + + buffer[i] = rb->bufferBase[rb->bufferTail++]; + rb->bufferTail %= rb->bufferMaxSize; + } + rb->bufferCount -= length; + return length; + } + + return -1; +} \ No newline at end of file diff --git a/RS485Driver/RTApp/ringBuffer.h b/RS485Driver/RTApp/ringBuffer.h new file mode 100644 index 0000000..f3f2cee --- /dev/null +++ b/RS485Driver/RTApp/ringBuffer.h @@ -0,0 +1,75 @@ +/* +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ + +#pragma once + +#include +#include +#include + +/// +/// Structure defining the ring buffer handle type, to be used +/// in handling all the object-data related to a ring buffer. +/// +typedef struct { + + uint8_t *bufferBase; + + volatile uint32_t bufferHead; + volatile uint32_t bufferTail; + volatile uint32_t bufferCount; + uint32_t bufferMaxSize; + +} ringBuffer_t; + + +/// +/// +/// +/// Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained +/// The address of the memory block that will be used +/// The size of the memory block +void ring_buffer_init(ringBuffer_t *rb, uint8_t *bufferBase, uint32_t maxSize); + +/// +/// Returns the current bytes stored in the ring buffer. +/// +/// Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained +/// +/// The current byte-count stored in the ring buffer. +/// +uint32_t ring_buffer_count(ringBuffer_t *rb); + +/// +/// Checks if the current bytes stored in the ring buffer have reached the maximum capacity. +/// +/// Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained. +/// 'true' is the ring buffer is full, 'false' otherwise. +bool ring_buffer_isFull(ringBuffer_t *rb); + +/// +/// Stores a byte-buffer starting from the ring buffer's head pointer. +/// +/// Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained. +/// Pointer to a byte-buffer, from where the data will be read. +/// Length of the given byte-buffer. +/// +/// '-1' on failure (i.e. buffer overrun)' +/// 'number of bytes stored' on success. +/// +int ring_buffer_push_bytes(ringBuffer_t *rb, uint8_t *buffer, uint32_t length); + +/// +/// Retrieves a byte-buffer starting from the ring buffer's tail pointer. +/// +/// Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained. +/// Pointer to a byte-buffer, where the data will be copied. +/// Length of the given byte-buffer. +/// +/// '-1' on failure (i.e. buffer overrun)' +/// 'number of bytes read' on success. +/// +int ring_buffer_pop_bytes(ringBuffer_t *rb, uint8_t *buffer, uint32_t length); + diff --git a/RS485Driver/RTApp/rs485_driver.c b/RS485Driver/RTApp/rs485_driver.c new file mode 100644 index 0000000..6c5a364 --- /dev/null +++ b/RS485Driver/RTApp/rs485_driver.c @@ -0,0 +1,109 @@ +/* +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ + +#include +#include + +#include "lib/mt3620/uart.h" +#include "lib/GPIO.h" +#include "lib/UART.h" +#include "rs485_driver.h" + +static Platform_Unit driverISU = DRIVER_ISU; +static unsigned driverIsuBaudrate = DRIVER_ISU_DEFAULT_BAURATE; +static uint8_t driverEnableGPIO = DRIVER_DE_GPIO; +static UART *uart_handle = NULL; +static void (*uart_rxIrq_callback)(void); + +uint8_t rxBuffer[DRIVER_MAX_RX_BUFFER_SIZE]; +ringBuffer_t rs485_rxRingBuffer; + +bool Rs485_Init(uint32_t baudrate, void (*rxIrqCallback)(void)) +{ + if (baudrate == 0) + return false; + + if (NULL != uart_handle) + { + UART_Close(uart_handle); + } + + driverIsuBaudrate = baudrate; + + // Initialize the RS-485 UART + if (NULL != rxIrqCallback) + { + uart_rxIrq_callback = rxIrqCallback; + } + uart_handle = UART_Open(driverISU, driverIsuBaudrate, UART_PARITY_NONE, 1, uart_rxIrq_callback); + if (!uart_handle) { + return false; + } + + // Setup the RX message queue to be sent to the HLApp + ring_buffer_init(&rs485_rxRingBuffer, rxBuffer, sizeof(rxBuffer)); + + // Setup DE/!RE driver GPIO + GPIO_ConfigurePinForOutput(driverEnableGPIO); + GPIO_Write(driverEnableGPIO, false); + + return true; +} + +inline void Rs485_Close(void) +{ + UART_Close(uart_handle); + uart_handle = NULL; +} + +inline uintptr_t Rs485_ReadAvailable(void) +{ + return UART_ReadAvailable(uart_handle); +} + +inline int32_t Rs485_Read(void *data, uintptr_t size) +{ + return UART_Read(uart_handle, data, size); +} + +int32_t Rs485_Write(const void *data, uintptr_t size) +{ + int32_t res = ERROR_BUSY; + + bool state; + if (GPIO_Read(driverEnableGPIO, &state) == ERROR_NONE && state == false) + { + // Enable the transceiver's TX driver (DE), consequently the transceiver's RX driver (!RE) + // shall be disabled in hardware (i.e. connected together to DE as !RE is active low). + GPIO_Write(driverEnableGPIO, true); + + // Write to the RS-485 transceiver + int32_t res = UART_Write(uart_handle, data, size); + + if (ERROR_NONE == res) + { + // Wait for the UART's hardware TX buffer to empty + uint32_t retries = 0xFFFF; + while (retries && !UART_IsWriteComplete(uart_handle)) retries--; + + if (retries == 0) + { + res = ERROR_TIMEOUT; + } + else + { + // This is fine-tuned with a scope to achieve a minimal delay, + // so to fully include the STOP bit after the TX of the last byte + for (int i = 300; i > 0; i--) __asm__("nop"); + } + } + + // Disable the transceiver's TX driver (DE), consequently the transceiver's RX driver (!RE) + // shall be enabled in hardware (i.e. connected together to DE as !RE is active low).. + GPIO_Write(driverEnableGPIO, false); + } + + return res; +} \ No newline at end of file diff --git a/RS485Driver/RTApp/rs485_driver.h b/RS485Driver/RTApp/rs485_driver.h new file mode 100644 index 0000000..3f52475 --- /dev/null +++ b/RS485Driver/RTApp/rs485_driver.h @@ -0,0 +1,63 @@ +/* +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ +#pragma once + +#include +#include + +#include "..\common_defs.h" +#include "ringBuffer.h" + +////////////////////////////////////////////////////////////////////////////////// +// GLOBAL VARIABLES +////////////////////////////////////////////////////////////////////////////////// +#define DRIVER_ISU MT3620_UNIT_ISU0 +#define DRIVER_ISU_DEFAULT_BAURATE 9600 +#define DRIVER_DE_GPIO 42 +#define DRIVER_MAX_RX_BUFFER_SIZE 2048 +#define DRIVER_MAX_RX_BUFFER_FILL_SIZE 2000 + +extern ringBuffer_t rs485_rxRingBuffer; + + +/// +/// This function initializes the internal RS-485 UART handle, DE GPIO and TX ring buffer. +/// +/// The baudrate to which the UART should be configured. +/// A pointer to the function to be called upon an RX interrupt. +/// If NULL the previous setting is retained (useful when just changing the baudrate). +/// 'true' is the initialization succeeds, 'false' otherwise. +bool Rs485_Init(uint32_t baudrate, void (*rxIrqCallback)(void)); + +/// +/// Closes the internal UART handle used by the RS-485 driver. +/// +/// +/// +void Rs485_Close(void); + +/// +/// This function returns the number of bytes currently buffered for a RS-485 UART. +/// +/// Number of bytes available to be read. +uintptr_t Rs485_ReadAvailable(void); + +/// +/// This function blocks until it has read size bytes from the RS-485 UART. +/// +/// Start of buffer into which data should be written. +/// Size of data in bytes. +/// ERROR_NONE on success, or an error code. +int32_t Rs485_Read(void *data, uintptr_t size); + +/// +/// Buffers the supplied data and asynchronously writes it to the internal RS-485 UART handle. +/// If there is not enough space to buffer the data, then any unbuffered data will be discarded. +/// The size of the buffer is defined by the TX_BUFFER_SIZE macro in lib\UART.c. +/// +/// A pointer to the data buffer. +/// Size of the data buffer in bytes. +/// ERROR_NONE on success, or an error code. +int32_t Rs485_Write(const void *data, uintptr_t size); \ No newline at end of file diff --git a/RS485Driver/common_defs.h b/RS485Driver/common_defs.h new file mode 100644 index 0000000..9a19a96 --- /dev/null +++ b/RS485Driver/common_defs.h @@ -0,0 +1,15 @@ +/* Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +#pragma once + +// This is the maximum message size that the HLApp can send +// to the RS-485 RTApp driver. This header is used by both Apps. +#define MAX_HLAPP_MESSAGE_SIZE 64 + +// This is essentially the buffering time (in milliseconds), +// after which the real-time RS485 driver will send out +// any received bytes to the HLApp. +// Bytes are any ways sent in case the received amount +// overflows DRIVER_MAX_RX_BUFFER_FILL_SIZE (defined in rs485_driver.h). +#define RTDRV_SEND_DELAY_MSEC 10 \ No newline at end of file diff --git a/RS485Driver/launch.vs.json b/RS485Driver/launch.vs.json new file mode 100644 index 0000000..dde8636 --- /dev/null +++ b/RS485Driver/launch.vs.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.1", + "configurations": [ + { + "type": "azurespheredbg", + "name": "RS-485 Driver Sample (All Cores)", + "project": "HLApp/CMakeLists.txt", + "DebugBuildStepBuildAll": "true", + "workingDirectory": "${workspaceRoot}", + "applicationPath": "${debugInfo.target}", + "imagePath": "${debugInfo.targetImage}", + "targetCore": "AnyCore", + "partnerComponents": [ "96ACA524-8113-4171-9C76-6FBDBB441131", "1CCE66F1-28E9-4DA4-AD25-D247FD362DE7" ] + } + ] +} diff --git a/RS485Driver/rs485.code-workspace b/RS485Driver/rs485.code-workspace new file mode 100644 index 0000000..8249b51 --- /dev/null +++ b/RS485Driver/rs485.code-workspace @@ -0,0 +1,32 @@ +{ + "folders": [ + { + "path": "HLApp" + }, + { + "path": "RTApp" + } + ], + "settings": {}, + "launch": { + "configurations": [{ + "name": "Launch for Azure Sphere Applications (gdb)", + "type": "azurespheredbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "environment": [], + "externalConsole": true, + "partnerComponents": [], + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }], + "compounds": [] + } +} \ No newline at end of file diff --git a/RS485Driver/wiring-diagram.png b/RS485Driver/wiring-diagram.png new file mode 100644 index 0000000..2246e35 Binary files /dev/null and b/RS485Driver/wiring-diagram.png differ