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:
Родитель
f376d09ce9
Коммит
c333117dc6
|
@ -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. |
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore output directories
|
||||
/.vs/
|
||||
/out-*/
|
||||
/install-*/
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore output directories
|
||||
/.vs/
|
||||
/out/
|
||||
/install/
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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" ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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);
|
|
@ -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.
|
|
@ -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)
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore output directories
|
||||
/.vs/
|
||||
/out/
|
||||
/install/
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.1 MiB |
|
@ -0,0 +1 @@
|
|||
Please see the [parent project README](../README.md) for more information.
|
|
@ -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" ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
|
@ -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);
|
|
@ -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": []
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.1 MiB |
Загрузка…
Ссылка в новой задаче