Adding RS-485 real-time driver with HL-Core interfacing API (#59)

* Adding RS-485 real-time driver with HL-Core interfacing API

* Added global buffering time definition before sending data to the HLApp.

* Added RX buffer guard against overflowing in between GPT interrupts.
This commit is contained in:
Gianni Trevisiol 2021-09-10 09:51:44 -07:00 коммит произвёл GitHub
Родитель f376d09ce9
Коммит c333117dc6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
41 изменённых файлов: 2428 добавлений и 0 удалений

3
.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

Просмотреть файл

@ -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. |

4
RS485Driver/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
# Ignore output directories
/.vs/
/out-*/
/install-*/

4
RS485Driver/HLApp/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
# Ignore output directories
/.vs/
/out/
/install/

27
RS485Driver/HLApp/.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -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
}
]
}
]
}

11
RS485Driver/HLApp/.vscode/settings.json поставляемый Normal file
Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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})

Просмотреть файл

@ -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"
}
]
}
]
}

Просмотреть файл

@ -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.

Просмотреть файл

@ -0,0 +1,3 @@
# Sample: Inter-core comms - High-level app
Please see the [parent project README](../README.md) for more information.

Просмотреть файл

@ -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"
}

Просмотреть файл

@ -0,0 +1,139 @@
/* Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. */
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <applibs/log.h>
#include <applibs/eventloop.h>
#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);
}

Просмотреть файл

@ -0,0 +1,102 @@
/* Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. */
#pragma once
#include <time.h>
#include <unistd.h>
#include <applibs/eventloop.h>
/// <summary>
/// Opaque handle. Obtain via <see cref="CreateEventLoopPeriodicTimer" />
/// or <see cref="CreateEventLoopDisarmedTimer" /> and dispose of via
/// <see cref="DisposeEventLoopTimer" />.
/// </summary>
typedef struct EventLoopTimer EventLoopTimer;
/// <summary>
/// Applications implement a function with this signature to be
/// notified when a timer expires.
/// </summary>
/// <param name="timer">The timer which has expired.</param>
/// <seealso cref="CreateEventLoopPeriodicTimer" />
/// <seealso cref="CreateEventLoopDisarmedTimer" />
typedef void (*EventLoopTimerHandler)(EventLoopTimer *timer);
/// <summary>
/// Create a periodic timer which is invoked on the event loop. The timer
/// will begin firing immediately.
/// </summary>
/// <param name="eventLoop">Event loop to which the timer will be added.</param>
/// <param name="handler">Callback to invoke when the timer expires.</param>
/// <param name="period">Timer period.</param>
/// <returns>On success, pointer to new EventLoopTimer, which should be disposed of
/// with <see cref="DisposeEventLoopTimer" />. On failure, returns NULL, with more
/// information available in errno.</returns>.
EventLoopTimer *CreateEventLoopPeriodicTimer(EventLoop *eventLoop, EventLoopTimerHandler handler,
const struct timespec *period);
/// <summary>
/// Create a disarmed timer. After the timer has been allocated, call
/// <see cref="SetEventLoopTimerPeriod" /> or <see cref="SetEventLoopTimerOneShot" />
/// to arm the timer.
/// </summary>
/// <param name="eventLoop">Event loop to which the timer will be added.</param>
/// <param name="handler">Callback to invoke when the timer expires.</param>
/// <returns>On success, pointer to new EventLoopTimer, which should be disposed of
/// with <see cref="DisposeEventLoopTimer" />. On failure, returns NULL, with more
/// information available in errno.</returns>.
EventLoopTimer *CreateEventLoopDisarmedTimer(EventLoop *eventLoop, EventLoopTimerHandler handler);
/// <summary>
/// Dispose of a timer which was allocated with <see cref="CreateEventLoopPeriodicTimer" />
/// or <see cref="CreateEventLoopDisarmedTimer" />.
/// It is safe to call this function with a NULL pointer.
/// </summary>
/// <param name="timer">Successfully allocated event loop timer, or NULL.</param>
void DisposeEventLoopTimer(EventLoopTimer *timer);
/// <summary>
/// The timer callback should call this function to consume the timer event.
/// </summary>
/// <param name="timer">Successfully allocated timer.</param>
/// <returns>0 on success, -1 on failure, in which case errno contains more information.</returns>
int ConsumeEventLoopTimerEvent(EventLoopTimer *timer);
/// <summary>
/// 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 <see cref="CreateEventLoopPeriodicTimer" />.
/// </summary>
/// <param name="timer">Timer previously allocated with <see cref="CreateEventLoopPeriodicTimer" />
/// or <see cref="CreateEventLoopDisarmedTimer" />.</param>
/// <param name="period">New timer period.</param>
/// <returns>0 on success, -1 on failure, in which case errno contains more information.</returns>
/// <seealso cref="SetEventLoopTimerOneShot" />
/// <seealso cref="DisarmEventLoopTimer" />
int SetEventLoopTimerPeriod(EventLoopTimer *timer, const struct timespec *period);
/// <summary>
/// Set the timer to expire one after a specified period.
/// </summary>
/// <returns>0 on succcess, -1 on failure, in which case errno contains more information.</returns>
/// <param name="timer">Timer previously allocated with <see cref="CreateEventLoopPeriodicTimer" />
/// or <see cref="CreateEventLoopDisarmedTimer" />.</param>
/// <param name="delay">Period to wait before timer expires.</param>
/// <returns>0 on success, -1 on failure, in which case errno contains more
/// information.</returns>
/// <seealso cref="SetEventLoopTimerPeriod" />
/// <seealso cref="DisarmEventLoopTimer" />
int SetEventLoopTimerOneShot(EventLoopTimer *timer, const struct timespec *delay);
/// <summary>
/// Disarm an existing event loop timer.
/// </summary>
/// <param name="timer">Timer previously allocated with <see cref="CreateEventLoopPeriodicTimer" />
/// or <see cref="CreateEventLoopDisarmedTimer" />.</param>
/// <returns>0 on success; -1 on failure, in which case errno contains more
/// information.</returns>
/// <seealso cref="SetEventLoopTimerOneShot" />
/// <seealso cref="SetEventLoopTimerPeriod" />
int DisarmEventLoopTimer(EventLoopTimer *timer);

Просмотреть файл

@ -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" ]
}
]
}

195
RS485Driver/HLApp/main.c Normal file
Просмотреть файл

@ -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 <signal.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <applibs/log.h>
#include <applibs/application.h>
#include "..\common_defs.h"
#include "eventloop_timer_utilities.h"
#include "rs485_hl_driver.h"
/// <summary>
/// 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.
/// </summary>
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);
/// <summary>
/// Signal handler for termination requests. This handler must be async-signal-safe.
/// </summary>
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;
}
/// <summary>
/// Handle send timer event by sending a command sequence to the RS-485 driver.
/// </summary>
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;
}
/// <summary>
/// Handle receive callback from the RS-485 driver.
/// The received bytes will be available in the RX buffer passed to Rs485_Init().
/// </summary>
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;
}
/// <summary>
/// Set up SIGTERM termination handler and event handlers for send timer
/// and to receive data from real-time capable application.
/// </summary>
/// <returns>
/// ExitCode_Success if all resources were allocated successfully; otherwise another
/// ExitCode value which indicates the specific failure.
/// </returns>
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;
}
/// <summary>
/// Clean up the resources previously allocated.
/// </summary>
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;
}

Просмотреть файл

@ -0,0 +1,149 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <applibs/log.h>
#include <applibs/application.h>
#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);
}
}
}

Просмотреть файл

@ -0,0 +1,33 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
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).
/// <summary>
/// Initializes the connection to the real-time RS-485 driver (RTApp).
/// </summary>
/// <param name="eventLoop">A pointer to the EventLoop to which register the inter-core communication with the real-time RS-485 driver (RTApp).</param>
/// <param name="rxBuffer">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).</param>
/// <param name="rxBufferSize">The size in bytes of the given byte-buffer.</param>
/// <param name="callback">A pointer to a 'Rs485ReceiveCallback'-typed callback function, which the HL-Core RS-485 driver invokes upon receive events.</param>
/// <returns>'0'</returns>
int Rs485_Init(EventLoop *eventLoop, uint8_t *rxBuffer, size_t rxBufferSize, Rs485ReceiveCallback *callback);
/// <summary>
/// Closes the internal handles managing the connection to the real-time RS-485 driver (RTApp).
/// </summary>
/// <param name=""></param>
void Rs485_Close(void);
/// <summary>
/// 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.
/// </summary>
/// <param name="data">A pointer to the data buffer.</param>
/// <param name="size">Size of the data buffer in bytes.</param>
/// <returns>The number of bytes sent or -1 on error.</returns>
int Rs485_Send(const void *data, size_t dataLen);

21
RS485Driver/LICENSE.txt Normal file
Просмотреть файл

@ -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.

276
RS485Driver/README.md Normal file
Просмотреть файл

@ -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)

4
RS485Driver/RTApp/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
# Ignore output directories
/.vs/
/out/
/install/

27
RS485Driver/RTApp/.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -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
}
]
}
]
}

11
RS485Driver/RTApp/.vscode/settings.json поставляемый Normal file
Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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}")

Просмотреть файл

@ -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}"
}
]
}
]
}

Двоичные данные
RS485Driver/RTApp/Connection Diagram.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.1 MiB

Просмотреть файл

@ -0,0 +1 @@
Please see the [parent project README](../README.md) for more information.

470
RS485Driver/RTApp/Socket.c Normal file
Просмотреть файл

@ -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 <stdbool.h>
#include <stddef.h>
#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;
}

Просмотреть файл

@ -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 <stdbool.h>
#include <stdint.h>
// 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.</summary>
#define ERROR_SOCKET_INSUFFICIENT_SPACE (ERROR_SPECIFIC - 1)
/// Returned when negotiation fails.</summary>
#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_

Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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" ]
}
]
}

1
RS485Driver/RTApp/lib Submodule

@ -0,0 +1 @@
Subproject commit 8f8da0e24a5c94abf447ee067bc18268e05e553d

Просмотреть файл

@ -0,0 +1,4 @@
/* Copyright (c) Codethink Ltd. All rights reserved.
Licensed under the MIT License. */
INCLUDE lib/linker.ld

295
RS485Driver/RTApp/main.c Normal file
Просмотреть файл

@ -0,0 +1,295 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#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();
}
}

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -0,0 +1,75 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
/// <summary>
/// Structure defining the ring buffer handle type, to be used
/// in handling all the object-data related to a ring buffer.
/// </summary>
typedef struct {
uint8_t *bufferBase;
volatile uint32_t bufferHead;
volatile uint32_t bufferTail;
volatile uint32_t bufferCount;
uint32_t bufferMaxSize;
} ringBuffer_t;
/// <summary>
///
/// </summary>
/// <param name="rb">Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained</param>
/// <param name="bufferBase">The address of the memory block that will be used</param>
/// <param name="maxSize">The size of the memory block</param>
void ring_buffer_init(ringBuffer_t *rb, uint8_t *bufferBase, uint32_t maxSize);
/// <summary>
/// Returns the current bytes stored in the ring buffer.
/// </summary>
/// <param name="rb">Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained</param>
/// <returns>
/// The current byte-count stored in the ring buffer.
/// </returns>
uint32_t ring_buffer_count(ringBuffer_t *rb);
/// <summary>
/// Checks if the current bytes stored in the ring buffer have reached the maximum capacity.
/// </summary>
/// <param name="rb">Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained.</param>
/// <returns>'true' is the ring buffer is full, 'false' otherwise.</returns>
bool ring_buffer_isFull(ringBuffer_t *rb);
/// <summary>
/// Stores a byte-buffer starting from the ring buffer's head pointer.
/// </summary>
/// <param name="rb">Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained.</param>
/// <param name="buffer">Pointer to a byte-buffer, from where the data will be read.</param>
/// <param name="length">Length of the given byte-buffer.</param>
/// <returns>
/// '-1' on failure (i.e. buffer overrun)'
/// 'number of bytes stored' on success.
/// </returns>
int ring_buffer_push_bytes(ringBuffer_t *rb, uint8_t *buffer, uint32_t length);
/// <summary>
/// Retrieves a byte-buffer starting from the ring buffer's tail pointer.
/// </summary>
/// <param name="rb">Pointer to a 'ringBuffer_t' type, where all the ring buffer variables are maintained.</param>
/// <param name="buffer">Pointer to a byte-buffer, where the data will be copied.</param>
/// <param name="length">Length of the given byte-buffer.</param>
/// <returns>
/// '-1' on failure (i.e. buffer overrun)'
/// 'number of bytes read' on success.
/// </returns>
int ring_buffer_pop_bytes(ringBuffer_t *rb, uint8_t *buffer, uint32_t length);

Просмотреть файл

@ -0,0 +1,109 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
#include <stdint.h>
#include <stdbool.h>
#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;
}

Просмотреть файл

@ -0,0 +1,63 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#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;
/// <summary>
/// This function initializes the internal RS-485 UART handle, DE GPIO and TX ring buffer.
/// </summary>
/// <param name="baudrate">The baudrate to which the UART should be configured.</param>
/// <param name="rxIrqCallback">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).</param>
/// <returns>'true' is the initialization succeeds, 'false' otherwise.</returns>
bool Rs485_Init(uint32_t baudrate, void (*rxIrqCallback)(void));
/// <summary>
/// Closes the internal UART handle used by the RS-485 driver.
/// </summary>
/// <param name=""></param>
/// <returns></returns>
void Rs485_Close(void);
/// <summary>
/// This function returns the number of bytes currently buffered for a RS-485 UART.
/// </summary>
/// <returns>Number of bytes available to be read.</returns>
uintptr_t Rs485_ReadAvailable(void);
/// <summary>
/// This function blocks until it has read size bytes from the RS-485 UART.
/// </summary>
/// <param name="data">Start of buffer into which data should be written.</param>
/// <param name="size">Size of data in bytes.</param>
/// <returns>ERROR_NONE on success, or an error code.</returns>
int32_t Rs485_Read(void *data, uintptr_t size);
/// <summary>
/// <para>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.</para>
/// </summary>
/// <param name="data">A pointer to the data buffer.</param>
/// <param name="size">Size of the data buffer in bytes.</param>
/// <returns>ERROR_NONE on success, or an error code.</returns>
int32_t Rs485_Write(const void *data, uintptr_t size);

15
RS485Driver/common_defs.h Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -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" ]
}
]
}

Просмотреть файл

@ -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": []
}
}

Двоичные данные
RS485Driver/wiring-diagram.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.1 MiB