Add Raspberry Pi Pico port (#1232)
* Add Raspberry Pi Pico port - Adds BACnet/IP with abstracted network layer - Adds BACnet MS/TP support using RS485/UART - Includes README with examples * Add Pico 2 MS/TP demo, CI, and docs
This commit is contained in:
@@ -238,6 +238,7 @@ jobs:
|
|||||||
arm-none-eabi-gcc --version
|
arm-none-eabi-gcc --version
|
||||||
make stm32f4xx-cmake
|
make stm32f4xx-cmake
|
||||||
make at91sam7s-cmake
|
make at91sam7s-cmake
|
||||||
|
make pico-cmake
|
||||||
|
|
||||||
ports-avr:
|
ports-avr:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -395,6 +395,12 @@ dlmstp-linux: ports/linux/dlmstp.mak
|
|||||||
lwip: ports/lwip/Makefile
|
lwip: ports/lwip/Makefile
|
||||||
$(MAKE) -s -C ports/lwip clean all
|
$(MAKE) -s -C ports/lwip clean all
|
||||||
|
|
||||||
|
.PHONY: pico-cmake
|
||||||
|
pico-cmake:
|
||||||
|
cd ports/pico && ./configure.sh
|
||||||
|
[ -d ports/pico/build ] || mkdir -p ports/pico/build
|
||||||
|
cd ports/pico/build && cmake .. -DPICO_SDK_PATH=../external/pico-sdk && cmake --build . --clean-first -- -j$(shell nproc 2>/dev/null || echo 1)
|
||||||
|
|
||||||
.PHONY: pretty
|
.PHONY: pretty
|
||||||
pretty:
|
pretty:
|
||||||
find ./src -iname *.h -o -iname *.c -exec \
|
find ./src -iname *.h -o -iname *.c -exec \
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
if (NOT DEFINED PICO_SDK_PATH AND DEFINED ENV{PICO_SDK_PATH})
|
||||||
|
set(PICO_SDK_PATH "$ENV{PICO_SDK_PATH}")
|
||||||
|
endif()
|
||||||
|
if (DEFINED PICO_SDK_PATH)
|
||||||
|
get_filename_component(
|
||||||
|
PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH
|
||||||
|
BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
|
set(ENV{PICO_SDK_PATH} "${PICO_SDK_PATH}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Default this port to Pico 2.
|
||||||
|
if (NOT PICO_BOARD)
|
||||||
|
set(PICO_BOARD pico2 CACHE STRING "Pico SDK board for the BACnet Pico port" FORCE)
|
||||||
|
endif()
|
||||||
|
if (NOT DEFINED ENV{PICO_BOARD})
|
||||||
|
set(ENV{PICO_BOARD} "${PICO_BOARD}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
|
||||||
|
|
||||||
|
project(bacnet_pico_port C CXX ASM)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
set(BACNET_ROOT ${CMAKE_CURRENT_LIST_DIR}/../..)
|
||||||
|
set(BACNET_SRC ${BACNET_ROOT}/src)
|
||||||
|
set(BACNET_CORE ${BACNET_SRC}/bacnet)
|
||||||
|
set(BACNET_BASIC ${BACNET_SRC}/bacnet/basic)
|
||||||
|
|
||||||
|
set(BACNET_COMMON_SOURCES
|
||||||
|
${BACNET_BASIC}/binding/address.c
|
||||||
|
${BACNET_BASIC}/object/client/device-client.c
|
||||||
|
${BACNET_BASIC}/npdu/h_npdu.c
|
||||||
|
${BACNET_BASIC}/service/h_apdu.c
|
||||||
|
${BACNET_BASIC}/service/h_cov.c
|
||||||
|
${BACNET_BASIC}/service/h_dcc.c
|
||||||
|
${BACNET_BASIC}/service/h_noserv.c
|
||||||
|
${BACNET_BASIC}/service/h_rd.c
|
||||||
|
${BACNET_BASIC}/service/h_rp.c
|
||||||
|
${BACNET_BASIC}/service/h_rpm.c
|
||||||
|
${BACNET_BASIC}/service/h_whohas.c
|
||||||
|
${BACNET_BASIC}/service/h_whois.c
|
||||||
|
${BACNET_BASIC}/service/h_wp.c
|
||||||
|
${BACNET_BASIC}/service/s_iam.c
|
||||||
|
${BACNET_BASIC}/service/s_ihave.c
|
||||||
|
${BACNET_BASIC}/sys/datetime_mstimer.c
|
||||||
|
${BACNET_BASIC}/sys/days.c
|
||||||
|
${BACNET_BASIC}/sys/dst.c
|
||||||
|
${BACNET_BASIC}/sys/debug.c
|
||||||
|
${BACNET_BASIC}/sys/ringbuf.c
|
||||||
|
${BACNET_BASIC}/sys/fifo.c
|
||||||
|
${BACNET_BASIC}/sys/mstimer.c
|
||||||
|
${BACNET_BASIC}/tsm/tsm.c
|
||||||
|
${BACNET_CORE}/abort.c
|
||||||
|
${BACNET_CORE}/bacaction.c
|
||||||
|
${BACNET_CORE}/bacaddr.c
|
||||||
|
${BACNET_CORE}/bacapp.c
|
||||||
|
${BACNET_CORE}/bacdcode.c
|
||||||
|
${BACNET_CORE}/bacdest.c
|
||||||
|
${BACNET_CORE}/bacdevobjpropref.c
|
||||||
|
${BACNET_CORE}/bacerror.c
|
||||||
|
${BACNET_CORE}/bacint.c
|
||||||
|
${BACNET_CORE}/bacreal.c
|
||||||
|
${BACNET_CORE}/bacstr.c
|
||||||
|
${BACNET_CORE}/datalink/cobs.c
|
||||||
|
${BACNET_CORE}/datalink/crc.c
|
||||||
|
${BACNET_CORE}/datalink/dlmstp.c
|
||||||
|
${BACNET_CORE}/datalink/mstp.c
|
||||||
|
${BACNET_CORE}/datalink/mstptext.c
|
||||||
|
${BACNET_CORE}/datetime.c
|
||||||
|
${BACNET_CORE}/dcc.c
|
||||||
|
${BACNET_CORE}/hostnport.c
|
||||||
|
${BACNET_CORE}/iam.c
|
||||||
|
${BACNET_CORE}/ihave.c
|
||||||
|
${BACNET_CORE}/indtext.c
|
||||||
|
${BACNET_CORE}/memcopy.c
|
||||||
|
${BACNET_CORE}/npdu.c
|
||||||
|
${BACNET_CORE}/proplist.c
|
||||||
|
${BACNET_CORE}/rd.c
|
||||||
|
${BACNET_CORE}/reject.c
|
||||||
|
${BACNET_CORE}/rp.c
|
||||||
|
${BACNET_CORE}/rpm.c
|
||||||
|
${BACNET_CORE}/timestamp.c
|
||||||
|
${BACNET_CORE}/whohas.c
|
||||||
|
${BACNET_CORE}/whois.c
|
||||||
|
${BACNET_CORE}/wp.c
|
||||||
|
)
|
||||||
|
|
||||||
|
set(BACNET_MSTP_SOURCES
|
||||||
|
${BACNET_COMMON_SOURCES}
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/dlenv.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/rs485.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/mstimer_init.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/main.c
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(bacnet-pico-mstp ${BACNET_MSTP_SOURCES})
|
||||||
|
|
||||||
|
target_include_directories(bacnet-pico-mstp PRIVATE
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
|
${BACNET_SRC}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(bacnet-pico-mstp PRIVATE
|
||||||
|
BACDL_MSTP
|
||||||
|
MAX_APDU=480
|
||||||
|
BIG_ENDIAN=0
|
||||||
|
PRINT_ENABLED=0
|
||||||
|
MAX_TSM_TRANSACTIONS=1
|
||||||
|
CRC_USE_TABLE
|
||||||
|
BACAPP_MINIMAL
|
||||||
|
BACAPP_TIMESTAMP
|
||||||
|
BACNET_STACK_DEPRECATED_DISABLE
|
||||||
|
BACNET_PROTOCOL_REVISION=16
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(bacnet-pico-mstp
|
||||||
|
pico_stdlib
|
||||||
|
hardware_uart
|
||||||
|
hardware_gpio
|
||||||
|
hardware_timer
|
||||||
|
)
|
||||||
|
|
||||||
|
pico_enable_stdio_usb(bacnet-pico-mstp 1)
|
||||||
|
pico_enable_stdio_uart(bacnet-pico-mstp 0)
|
||||||
|
pico_add_extra_outputs(bacnet-pico-mstp)
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
# BACnet Stack Port for Raspberry Pi Pico
|
||||||
|
|
||||||
|
This directory contains the Raspberry Pi Pico port of the BACnet stack, supporting both BACnet/IP and BACnet MS/TP.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This port provides:
|
||||||
|
- **BACnet/IP** support with abstracted network layer (Developer must provide Ethernet implementation)
|
||||||
|
- **BACnet MS/TP** support using UART (RS485)
|
||||||
|
|
||||||
|
## Hardware Used
|
||||||
|
|
||||||
|
The port is written against the Raspberry Pi Pico SDK. The same code is intended to build for boards that the Pico SDK exposes with compatible UART/GPIO support, such as:
|
||||||
|
|
||||||
|
- `pico`
|
||||||
|
- `pico_w`
|
||||||
|
- `pico2`
|
||||||
|
- `pico2_w`
|
||||||
|
|
||||||
|
The current `make pico-cmake` flow defaults to the Pico SDK board `pico2` for this port unless `-DPICO_BOARD=...` is passed to CMake.
|
||||||
|
|
||||||
|
For the MS/TP side, the code currently assumes:
|
||||||
|
|
||||||
|
- UART1 on GPIO 8 (`TX`) and GPIO 9 (`RX`)
|
||||||
|
- GPIO 10 as the RS485 transceiver `DE`/`RE` control pin
|
||||||
|
- An external 3.3 V RS485 transceiver module, for example a MAX3485- or SN65HVD-based module
|
||||||
|
|
||||||
|
Typical wiring is:
|
||||||
|
|
||||||
|
- Pico/Pico 2 GPIO 8 -> transceiver `DI`
|
||||||
|
- Pico/Pico 2 GPIO 9 -> transceiver `RO`
|
||||||
|
- Pico/Pico 2 GPIO 10 -> transceiver `DE` and `RE`
|
||||||
|
- Pico/Pico 2 `3V3` -> transceiver `VCC`
|
||||||
|
- Pico/Pico 2 `GND` -> transceiver `GND`
|
||||||
|
- Transceiver `A/B` -> BACnet MS/TP trunk
|
||||||
|
|
||||||
|
No external hardware is needed for the automated CI build because the CI job only verifies that the Pico MS/TP target compiles.
|
||||||
|
|
||||||
|
## Software Dependencies
|
||||||
|
|
||||||
|
The external dependency required for this port is cloned by `ports/pico/configure.sh` into `ports/pico/external/`:
|
||||||
|
|
||||||
|
- Raspberry Pi Pico SDK 2.2.0
|
||||||
|
URL: <https://github.com/raspberrypi/pico-sdk>
|
||||||
|
|
||||||
|
The script checks out that SDK tag and initializes the SDK submodules needed by the build.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make pico-cmake
|
||||||
|
```
|
||||||
|
|
||||||
|
That target runs `ports/pico/configure.sh`, configures the CMake build in `ports/pico/build`, and builds the Pico port for `pico2`.
|
||||||
|
|
||||||
|
To select another supported board, configure manually and pass `PICO_BOARD`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ports/pico
|
||||||
|
./configure.sh
|
||||||
|
cmake -S . -B build -DPICO_SDK_PATH="$PWD/external/pico-sdk" -DPICO_BOARD=pico2
|
||||||
|
cmake --build build -- -j"$(nproc)"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using This Port
|
||||||
|
|
||||||
|
### Implement Network Functions
|
||||||
|
|
||||||
|
Developer must implement the following functions declared in `bip.h`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
bool bip_socket_init(uint16_t port);
|
||||||
|
|
||||||
|
int bip_socket_send(
|
||||||
|
const uint8_t *dest_addr,
|
||||||
|
uint16_t dest_port,
|
||||||
|
const uint8_t *mtu,
|
||||||
|
uint16_t mtu_len);
|
||||||
|
|
||||||
|
int bip_socket_receive(
|
||||||
|
uint8_t *buf,
|
||||||
|
uint16_t buf_len,
|
||||||
|
uint8_t *src_addr,
|
||||||
|
uint16_t *src_port);
|
||||||
|
|
||||||
|
void bip_socket_cleanup(void);
|
||||||
|
|
||||||
|
bool bip_get_local_network_info(uint8_t *local_addr, uint8_t *netmask);
|
||||||
|
```
|
||||||
|
|
||||||
|
These BACnet/IP hooks are examples of the interface you need to provide. The repository does not yet include a complete in-tree Pico Ethernet or Wi-Fi transport implementation, so BACnet/IP still requires board-specific network code behind `bip.h`.
|
||||||
|
|
||||||
|
### Example Usage
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/uart.h"
|
||||||
|
#include "mstimer_init.h"
|
||||||
|
#include "bip.h"
|
||||||
|
/* BACnet Stack Includes */
|
||||||
|
#include "bacnet/datalink/datalink.h"
|
||||||
|
#include "bacnet/basic/services.h"
|
||||||
|
#include "bacnet/basic/object/device.h"
|
||||||
|
#include "bacnet/basic/tsm/tsm.h"
|
||||||
|
#include "bacnet/dcc.h"
|
||||||
|
|
||||||
|
static struct mstimer DCC_Timer;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Initialize Pico
|
||||||
|
stdio_init_all();
|
||||||
|
systimer_init();
|
||||||
|
|
||||||
|
// Initialize BIP datalink
|
||||||
|
Device_Set_Object_Instance_Number(12345);
|
||||||
|
Device_Init(NULL);
|
||||||
|
address_init();
|
||||||
|
|
||||||
|
// Initialize BACnet/IP on port 0xBAC0 (47808)
|
||||||
|
if (!bip_init(0xBAC0)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set up confirmed service unrecognized service handler - required! */
|
||||||
|
Send_I_Am(&Handler_Transmit_Buffer[0]);
|
||||||
|
|
||||||
|
mstimer_set(&DCC_Timer, 1000); // 1 second timer
|
||||||
|
printf("BACnet BIP Initialized");
|
||||||
|
|
||||||
|
BACNET_ADDRESS src = {0};
|
||||||
|
uint8_t rx_buf[480];
|
||||||
|
const uint16_t max_pdu = sizeof(rx_buf);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
if (mstimer_expired(&DCC_Timer))
|
||||||
|
{
|
||||||
|
mstimer_reset(&DCC_Timer);
|
||||||
|
dcc_timer_seconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t pdu_len = bip_receive(&src, rx_buf, max_pdu, 0);
|
||||||
|
if (pdu_len > 0)
|
||||||
|
{
|
||||||
|
npdu_handler(&src, rx_buf, pdu_len);
|
||||||
|
printf("%u bytes received from MAC %u\r\n", pdu_len, src.mac[0]);
|
||||||
|
for (int i=0;i<pdu_len;i++) printf("%02X ", rx_buf[i]);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## BACnet MS/TP (RS485)
|
||||||
|
|
||||||
|
For a minimal MS/TP example for this port, see `main.c`.
|
||||||
|
The RS485 driver is ready to use without modification. It uses the Pico SDK UART functions.
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 Steve Karg
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "bacnet/bacdcode.h"
|
||||||
|
#include "bip.h"
|
||||||
|
|
||||||
|
/** @file pico/bip-init.c Initializes BACnet/IP interface (Pico). */
|
||||||
|
|
||||||
|
static bool BIP_Debug = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enabled debug printing of BACnet/IPv4
|
||||||
|
*/
|
||||||
|
void bip_debug_enable(void)
|
||||||
|
{
|
||||||
|
BIP_Debug = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disable debug printing of BACnet/IPv4
|
||||||
|
*/
|
||||||
|
void bip_debug_disable(void)
|
||||||
|
{
|
||||||
|
BIP_Debug = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* gets an IP address by name, where name can be a
|
||||||
|
string that is an IP address in dotted form, or
|
||||||
|
a name that is a domain name
|
||||||
|
returns 0 if not found, or
|
||||||
|
an IP address in network byte order */
|
||||||
|
long bip_getaddrbyname(const char *host_name)
|
||||||
|
{
|
||||||
|
(void)host_name;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the local IP address and local broadcast address from the system,
|
||||||
|
* and saves it into the BACnet/IP data structures.
|
||||||
|
*/
|
||||||
|
void bip_set_interface(void)
|
||||||
|
{
|
||||||
|
uint8_t local_address[] = { 0, 0, 0, 0 };
|
||||||
|
uint8_t broadcast_address[] = { 0, 0, 0, 0 };
|
||||||
|
uint8_t netmask[] = { 0, 0, 0, 0 };
|
||||||
|
uint8_t invertedNetmask[] = { 0, 0, 0, 0 };
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Get network info from platform-specific function */
|
||||||
|
if (!bip_get_local_network_info(local_address, netmask)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bip_set_addr(local_address);
|
||||||
|
|
||||||
|
/* setup local broadcast address */
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
invertedNetmask[i] = ~netmask[i];
|
||||||
|
broadcast_address[i] = (local_address[i] | invertedNetmask[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bip_set_broadcast_addr(broadcast_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialize the BACnet/IP services at the given interface.
|
||||||
|
* @ingroup DLBIP
|
||||||
|
* -# Gets the local IP address and local broadcast address from the system,
|
||||||
|
* and saves it into the BACnet/IP data structures.
|
||||||
|
* -# Opens a UDP socket
|
||||||
|
* -# Configures the socket for sending and receiving
|
||||||
|
* -# Configures the socket so it can send broadcasts
|
||||||
|
* -# Binds the socket to the local IP address at the specified port for
|
||||||
|
* BACnet/IP (by default, 0xBAC0 = 47808).
|
||||||
|
*
|
||||||
|
* @note For Linux, ifname is eth0, ath0, arc0, and others.
|
||||||
|
*
|
||||||
|
* @param port [in] UDP port to use for BACnet/IP
|
||||||
|
* @return True if the socket is successfully opened for BACnet/IP,
|
||||||
|
* else False if the socket functions fail.
|
||||||
|
*/
|
||||||
|
bool bip_init(uint16_t port)
|
||||||
|
{
|
||||||
|
/* Initialize the socket using platform-specific function */
|
||||||
|
if (!bip_socket_init(port)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bip_set_interface();
|
||||||
|
bip_set_port(port);
|
||||||
|
|
||||||
|
/* Mark socket as valid (0 is a valid socket ID for Pico) */
|
||||||
|
bip_set_socket(0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cleanup and close out the BACnet/IP services by closing the socket.
|
||||||
|
* @ingroup DLBIP
|
||||||
|
*/
|
||||||
|
void bip_cleanup(void)
|
||||||
|
{
|
||||||
|
if (bip_valid()) {
|
||||||
|
bip_socket_cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 Steve Karg
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "bacnet/bacdcode.h"
|
||||||
|
#include "bacnet/bacint.h"
|
||||||
|
#include "bacnet/datalink/bvlc.h"
|
||||||
|
#include "bvlc-pico.h"
|
||||||
|
#include "bip.h"
|
||||||
|
|
||||||
|
#if PRINT_ENABLED || DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** @file bip.c Configuration and Operations for BACnet/IP */
|
||||||
|
|
||||||
|
#define MAX_SOCK_NUM 8
|
||||||
|
|
||||||
|
static uint8_t BIP_Socket = MAX_SOCK_NUM;
|
||||||
|
/* port to use - stored in network byte order */
|
||||||
|
static uint16_t BIP_Port = 0;
|
||||||
|
/* IP Address - stored in network byte order */
|
||||||
|
static uint8_t BIP_Address[4] = { 0, 0, 0, 0 };
|
||||||
|
/* Broadcast Address - stored in network byte order */
|
||||||
|
static uint8_t BIP_Broadcast_Address[4] = { 0, 0, 0, 0 };
|
||||||
|
|
||||||
|
/** Converter from uint8_t[4] type address to uint32_t
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
uint32_t convertBIP_Address2uint32(const uint8_t *bip_address)
|
||||||
|
{
|
||||||
|
return ((uint32_t)bip_address[0] << 24) | ((uint32_t)bip_address[1] << 16) |
|
||||||
|
((uint32_t)bip_address[2] << 8) | ((uint32_t)bip_address[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert from uint32_t IPv4 address to uint8_t[4] address
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void convertUint32Address_2_uint8Address(uint32_t ip, uint8_t *address)
|
||||||
|
{
|
||||||
|
address[0] = (uint8_t)(ip >> 24);
|
||||||
|
address[1] = (uint8_t)(ip >> 16);
|
||||||
|
address[2] = (uint8_t)(ip >> 8);
|
||||||
|
address[3] = (uint8_t)(ip >> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Setter for the BACnet/IP socket handle.
|
||||||
|
*
|
||||||
|
* @param sock_fd [in] Handle for the BACnet/IP socket.
|
||||||
|
*/
|
||||||
|
void bip_set_socket(uint8_t sock_fd)
|
||||||
|
{
|
||||||
|
BIP_Socket = sock_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Getter for the BACnet/IP socket handle.
|
||||||
|
*
|
||||||
|
* @return The handle to the BACnet/IP socket.
|
||||||
|
*/
|
||||||
|
uint8_t bip_socket(void)
|
||||||
|
{
|
||||||
|
return BIP_Socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bip_valid(void)
|
||||||
|
{
|
||||||
|
return (BIP_Socket < MAX_SOCK_NUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bip_set_addr(const uint8_t *net_address)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
BIP_Address[i] = net_address[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returns network byte order */
|
||||||
|
uint8_t *bip_get_addr(void)
|
||||||
|
{
|
||||||
|
return BIP_Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bip_set_broadcast_addr(const uint8_t *net_address)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
BIP_Broadcast_Address[i] = net_address[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *bip_get_broadcast_addr(void)
|
||||||
|
{
|
||||||
|
return BIP_Broadcast_Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bip_set_port(uint16_t port)
|
||||||
|
{
|
||||||
|
BIP_Port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returns network byte order */
|
||||||
|
uint16_t bip_get_port(void)
|
||||||
|
{
|
||||||
|
return BIP_Port;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bip_decode_bip_address(
|
||||||
|
const BACNET_ADDRESS *bac_addr, uint8_t *address, uint16_t *port)
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
|
||||||
|
if (bac_addr) {
|
||||||
|
memcpy(address, &bac_addr->mac[0], 4);
|
||||||
|
memcpy(port, &bac_addr->mac[4], 2);
|
||||||
|
len = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Function to send a packet out the BACnet/IP socket (Annex J).
|
||||||
|
* @ingroup DLBIP
|
||||||
|
*
|
||||||
|
* @param dest [in] Destination address (may encode an IP address and port #).
|
||||||
|
* @param npdu_data [in] The NPDU header (Network) information (not used).
|
||||||
|
* @param pdu [in] Buffer of data to be sent - may be null (why?).
|
||||||
|
* @param pdu_len [in] Number of bytes in the pdu buffer.
|
||||||
|
* @return Number of bytes sent on success, negative number on failure.
|
||||||
|
*/
|
||||||
|
int bip_send_pdu(
|
||||||
|
BACNET_ADDRESS *dest,
|
||||||
|
BACNET_NPDU_DATA *npdu_data,
|
||||||
|
uint8_t *pdu,
|
||||||
|
unsigned pdu_len)
|
||||||
|
{
|
||||||
|
uint8_t mtu[BIP_MPDU_MAX] = { 0 };
|
||||||
|
int mtu_len = 0;
|
||||||
|
int bytes_sent = 0;
|
||||||
|
uint8_t address[] = { 0, 0, 0, 0 };
|
||||||
|
uint16_t port = 0;
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
(void)npdu_data;
|
||||||
|
/* assumes that the driver has already been initialized */
|
||||||
|
if (BIP_Socket >= MAX_SOCK_NUM) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtu[0] = BVLL_TYPE_BACNET_IP;
|
||||||
|
if ((dest->net == BACNET_BROADCAST_NETWORK) ||
|
||||||
|
((dest->net > 0) && (dest->len == 0)) || (dest->mac_len == 0)) {
|
||||||
|
/* broadcast */
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
address[i] = BIP_Broadcast_Address[i];
|
||||||
|
}
|
||||||
|
port = BIP_Port;
|
||||||
|
mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(
|
||||||
|
stderr, "Send Broadcast NPDU to %d.%d.%d.%d:%d\n", address[0],
|
||||||
|
address[1], address[2], address[3], port);
|
||||||
|
#endif
|
||||||
|
} else if (dest->mac_len == 6) {
|
||||||
|
bip_decode_bip_address(dest, address, &port);
|
||||||
|
mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU;
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(
|
||||||
|
stderr, "Send Unicast NPDU to %d.%d.%d.%d:%d\n", address[0],
|
||||||
|
address[1], address[2], address[3], port);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
/* invalid address */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtu_len = 2;
|
||||||
|
mtu_len += encode_unsigned16(
|
||||||
|
&mtu[mtu_len], (uint16_t)(pdu_len + 4 /*inclusive */));
|
||||||
|
memcpy(&mtu[mtu_len], pdu, pdu_len);
|
||||||
|
mtu_len += pdu_len;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(stderr, "MTU size %d\n", mtu_len);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Send the packet using platform-specific function */
|
||||||
|
bytes_sent = bip_socket_send(address, port, mtu, mtu_len);
|
||||||
|
|
||||||
|
return bytes_sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Implementation of the receive() function for BACnet/IP; receives one
|
||||||
|
* packet, verifies its BVLC header, and removes the BVLC header from
|
||||||
|
* the PDU data before returning.
|
||||||
|
*
|
||||||
|
* @param src [out] Source of the packet - who should receive any response.
|
||||||
|
* @param pdu [out] A buffer to hold the PDU portion of the received packet,
|
||||||
|
* after the BVLC portion has been stripped off.
|
||||||
|
* @param max_pdu [in] Size of the pdu[] buffer.
|
||||||
|
* @param timeout [in] The number of milliseconds to wait for a packet.
|
||||||
|
* @return The number of octets (remaining) in the PDU, or zero on failure.
|
||||||
|
*/
|
||||||
|
uint16_t bip_receive(
|
||||||
|
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout)
|
||||||
|
{
|
||||||
|
int received_bytes = 0;
|
||||||
|
int max = 0;
|
||||||
|
uint16_t pdu_len = 0;
|
||||||
|
uint8_t src_addr[] = { 0, 0, 0, 0 };
|
||||||
|
uint16_t src_port = 0;
|
||||||
|
uint16_t i = 0;
|
||||||
|
int function = 0;
|
||||||
|
|
||||||
|
(void)timeout; /* unused parameter */
|
||||||
|
|
||||||
|
/* Make sure the socket is open */
|
||||||
|
if (BIP_Socket >= MAX_SOCK_NUM) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Receive packet using platform-specific function */
|
||||||
|
received_bytes = bip_socket_receive(pdu, max_pdu, src_addr, &src_port);
|
||||||
|
|
||||||
|
/* No data available */
|
||||||
|
if (received_bytes <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the signature of a BACnet/IP packet */
|
||||||
|
if (pdu[0] != BVLL_TYPE_BACNET_IP) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Erase up to 16 bytes after the received bytes as safety margin */
|
||||||
|
max = (int)max_pdu - received_bytes;
|
||||||
|
if (max > 0) {
|
||||||
|
if (max > 16) {
|
||||||
|
max = 16;
|
||||||
|
}
|
||||||
|
memset(&pdu[received_bytes], 0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bvlc_for_non_bbmd(src_addr, &src_port, pdu, received_bytes) > 0) {
|
||||||
|
/* Handled, usually with a NACK. */
|
||||||
|
#if PRINT_ENABLED
|
||||||
|
fprintf(stderr, "BIP: BVLC discarded!\n");
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function = pico_bvlc_get_function_code();
|
||||||
|
if ((function == BVLC_ORIGINAL_UNICAST_NPDU) ||
|
||||||
|
(function == BVLC_ORIGINAL_BROADCAST_NPDU)) {
|
||||||
|
/* ignore messages from me */
|
||||||
|
if ((convertBIP_Address2uint32(src_addr) ==
|
||||||
|
convertBIP_Address2uint32(BIP_Address)) &&
|
||||||
|
(src_port == BIP_Port)) {
|
||||||
|
pdu_len = 0;
|
||||||
|
} else {
|
||||||
|
/* data in src->mac[] is in network format */
|
||||||
|
src->mac_len = 6;
|
||||||
|
memcpy(&src->mac[0], &src_addr, 4);
|
||||||
|
memcpy(&src->mac[4], &src_port, 2);
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(
|
||||||
|
stderr, "BIP receive from %d.%d.%d.%d\n", src->mac[0],
|
||||||
|
src->mac[1], src->mac[2], src->mac[3]);
|
||||||
|
#endif
|
||||||
|
/* decode the length of the PDU - length is inclusive of BVLC */
|
||||||
|
(void)decode_unsigned16(&pdu[2], &pdu_len);
|
||||||
|
/* subtract off the BVLC header */
|
||||||
|
pdu_len -= 4;
|
||||||
|
if (pdu_len < max_pdu) {
|
||||||
|
/* shift the buffer to return a valid PDU */
|
||||||
|
for (i = 0; i < pdu_len; i++) {
|
||||||
|
pdu[i] = pdu[4 + i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* ignore packets that are too large */
|
||||||
|
pdu_len = 0;
|
||||||
|
#if PRINT_ENABLED
|
||||||
|
fprintf(stderr, "BIP: PDU too large. Discarded!.\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (function == BVLC_FORWARDED_NPDU) {
|
||||||
|
memcpy(&src_addr, &pdu[4], 4);
|
||||||
|
memcpy(&src_port, &pdu[8], 2);
|
||||||
|
if ((convertBIP_Address2uint32(src_addr) ==
|
||||||
|
convertBIP_Address2uint32(BIP_Address)) &&
|
||||||
|
(src_port == BIP_Port)) {
|
||||||
|
/* ignore messages from me */
|
||||||
|
pdu_len = 0;
|
||||||
|
} else {
|
||||||
|
/* data in src->mac[] is in network format */
|
||||||
|
src->mac_len = 6;
|
||||||
|
memcpy(&src->mac[0], &src_addr, 4);
|
||||||
|
memcpy(&src->mac[4], &src_port, 2);
|
||||||
|
/* decode the length of the PDU - length is inclusive of BVLC */
|
||||||
|
(void)decode_unsigned16(&pdu[2], &pdu_len);
|
||||||
|
/* subtract off the BVLC header */
|
||||||
|
pdu_len -= 10;
|
||||||
|
if (pdu_len < max_pdu) {
|
||||||
|
/* shift the buffer to return a valid PDU */
|
||||||
|
for (i = 0; i < pdu_len; i++) {
|
||||||
|
pdu[i] = pdu[4 + 6 + i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* ignore packets that are too large */
|
||||||
|
pdu_len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pdu_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bip_get_my_address(BACNET_ADDRESS *my_address)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
if (my_address) {
|
||||||
|
my_address->mac_len = 6;
|
||||||
|
memcpy(&my_address->mac[0], &BIP_Address, 4);
|
||||||
|
memcpy(&my_address->mac[4], &BIP_Port, 2);
|
||||||
|
my_address->net = 0; /* local only, no routing */
|
||||||
|
my_address->len = 0; /* no SLEN */
|
||||||
|
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||||
|
/* no SADR */
|
||||||
|
my_address->adr[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bip_get_broadcast_address(BACNET_ADDRESS *dest)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
if (dest) {
|
||||||
|
dest->mac_len = 6;
|
||||||
|
memcpy(&dest->mac[0], &BIP_Broadcast_Address, 4);
|
||||||
|
memcpy(&dest->mac[4], &BIP_Port, 2);
|
||||||
|
dest->net = BACNET_BROADCAST_NETWORK;
|
||||||
|
dest->len = 0; /* no SLEN */
|
||||||
|
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||||
|
/* no SADR */
|
||||||
|
dest->adr[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bip_send_mpdu(
|
||||||
|
const BACNET_IP_ADDRESS *dest, const uint8_t *mtu, uint16_t mtu_len)
|
||||||
|
{
|
||||||
|
int bytes_sent = 0;
|
||||||
|
|
||||||
|
/* Send using platform-specific function */
|
||||||
|
bytes_sent = bip_socket_send(dest->address, dest->port, mtu, mtu_len);
|
||||||
|
|
||||||
|
return bytes_sent;
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
#ifndef BIP_H
|
||||||
|
#define BIP_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "bacnet/bacdef.h"
|
||||||
|
#include "bacnet/npdu.h"
|
||||||
|
#include "bacnet/datalink/bvlc.h"
|
||||||
|
|
||||||
|
/* specific defines for BACnet/IP over Ethernet */
|
||||||
|
#define BIP_HEADER_MAX (1 + 1 + 2)
|
||||||
|
#ifndef BIP_MPDU_MAX
|
||||||
|
#define BIP_MPDU_MAX (BIP_HEADER_MAX + MAX_PDU) /* 1506 */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BVLL_TYPE_BACNET_IP (0x81)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
|
||||||
|
/* User must implement these platform-specific network functions */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the UDP socket for BACnet/IP
|
||||||
|
* @param port - UDP port to bind to
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool bip_socket_init(uint16_t port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send UDP packet
|
||||||
|
* @param dest_addr - destination IP address (4 bytes, network byte order)
|
||||||
|
* @param dest_port - destination port (host byte order)
|
||||||
|
* @param mtu - data buffer to send
|
||||||
|
* @param mtu_len - length of data
|
||||||
|
* @return number of bytes sent, or negative on error
|
||||||
|
*/
|
||||||
|
int bip_socket_send(
|
||||||
|
const uint8_t *dest_addr,
|
||||||
|
uint16_t dest_port,
|
||||||
|
const uint8_t *mtu,
|
||||||
|
uint16_t mtu_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Receive UDP packet (non-blocking)
|
||||||
|
* @param buf - buffer to store received data
|
||||||
|
* @param buf_len - maximum buffer size
|
||||||
|
* @param src_addr - pointer to store source IP (4 bytes, network byte order)
|
||||||
|
* @param src_port - pointer to store source port (host byte order)
|
||||||
|
* @return number of bytes received, 0 if no data, negative on error
|
||||||
|
*/
|
||||||
|
int bip_socket_receive(
|
||||||
|
uint8_t *buf, uint16_t buf_len, uint8_t *src_addr, uint16_t *src_port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Close/cleanup the UDP socket
|
||||||
|
*/
|
||||||
|
void bip_socket_cleanup(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get local network information
|
||||||
|
* @param local_addr - buffer for local IP (4 bytes)
|
||||||
|
* @param netmask - buffer for netmask (4 bytes)
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
bool bip_get_local_network_info(uint8_t *local_addr, uint8_t *netmask);
|
||||||
|
|
||||||
|
/* BACnet/IP port functions */
|
||||||
|
bool bip_init(uint16_t port);
|
||||||
|
void bip_set_interface(void);
|
||||||
|
void bip_cleanup(void);
|
||||||
|
|
||||||
|
/* Convert uint8_t IPv4 to uint32 */
|
||||||
|
uint32_t convertBIP_Address2uint32(const uint8_t *bip_address);
|
||||||
|
void convertUint32Address_2_uint8Address(uint32_t ip, uint8_t *address);
|
||||||
|
|
||||||
|
/* common BACnet/IP functions */
|
||||||
|
void bip_set_socket(uint8_t sock_fd);
|
||||||
|
uint8_t bip_socket(void);
|
||||||
|
bool bip_valid(void);
|
||||||
|
void bip_get_broadcast_address(BACNET_ADDRESS *dest);
|
||||||
|
void bip_get_my_address(BACNET_ADDRESS *my_address);
|
||||||
|
|
||||||
|
/* function to send a packet out the BACnet/IP socket */
|
||||||
|
int bip_send_pdu(
|
||||||
|
BACNET_ADDRESS *dest,
|
||||||
|
BACNET_NPDU_DATA *npdu_data,
|
||||||
|
uint8_t *pdu,
|
||||||
|
unsigned pdu_len);
|
||||||
|
|
||||||
|
/* Function to send a packet out the BACnet/IP socket (Annex J) */
|
||||||
|
int bip_send_mpdu(
|
||||||
|
const BACNET_IP_ADDRESS *dest, const uint8_t *mtu, uint16_t mtu_len);
|
||||||
|
|
||||||
|
/* receives a BACnet/IP packet */
|
||||||
|
uint16_t bip_receive(
|
||||||
|
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout);
|
||||||
|
|
||||||
|
/* use host byte order for setting */
|
||||||
|
void bip_set_port(uint16_t port);
|
||||||
|
uint16_t bip_get_port(void);
|
||||||
|
|
||||||
|
/* use network byte order for setting */
|
||||||
|
void bip_set_addr(const uint8_t *net_address);
|
||||||
|
uint8_t *bip_get_addr(void);
|
||||||
|
|
||||||
|
/* use network byte order for setting */
|
||||||
|
void bip_set_broadcast_addr(const uint8_t *net_address);
|
||||||
|
uint8_t *bip_get_broadcast_addr(void);
|
||||||
|
|
||||||
|
/* gets an IP address by name */
|
||||||
|
long bip_getaddrbyname(const char *host_name);
|
||||||
|
|
||||||
|
void bip_debug_enable(void);
|
||||||
|
void bip_debug_disable(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
|
||||||
|
#endif /* BIP_H */
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @authors Miguel Fernandes <miguelandre.fernandes@gmail.com> Testimony Adams
|
||||||
|
* <adamstestimony@gmail.com>
|
||||||
|
* @date 6 de Jun de 2013
|
||||||
|
* @brief BACnet Virtual Link Control for Pico
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "bvlc-pico.h"
|
||||||
|
#include "bip.h"
|
||||||
|
#include "bacnet/bacint.h"
|
||||||
|
|
||||||
|
#define BVLC_RESULT 0
|
||||||
|
#define BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE 1
|
||||||
|
#define BVLC_READ_BROADCAST_DIST_TABLE 2
|
||||||
|
#define BVLC_READ_BROADCAST_DIST_TABLE_ACK 3
|
||||||
|
#define BVLC_FORWARDED_NPDU 4
|
||||||
|
#define BVLC_REGISTER_FOREIGN_DEVICE 5
|
||||||
|
#define BVLC_READ_FOREIGN_DEVICE_TABLE 6
|
||||||
|
#define BVLC_READ_FOREIGN_DEVICE_TABLE_ACK 7
|
||||||
|
#define BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY 8
|
||||||
|
#define BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK 9
|
||||||
|
#define BVLC_ORIGINAL_UNICAST_NPDU 10
|
||||||
|
#define BVLC_ORIGINAL_BROADCAST_NPDU 11
|
||||||
|
#define BVLC_SECURE_BVLL 12
|
||||||
|
#define BVLC_INVALID 255
|
||||||
|
|
||||||
|
#define BVLC_RESULT_SUCCESSFUL_COMPLETION 0x0000
|
||||||
|
#define BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK 0x0010
|
||||||
|
#define BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK 0x0020
|
||||||
|
#define BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK 0X0030
|
||||||
|
#define BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK 0x0040
|
||||||
|
#define BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK 0x0050
|
||||||
|
#define BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK 0x0060
|
||||||
|
#define BVLC_RESULT_INVALID 0xFFFF
|
||||||
|
|
||||||
|
#define BVLL_TYPE_BACNET_IP (0x81)
|
||||||
|
|
||||||
|
/** result from a client request */
|
||||||
|
BACNET_BVLC_RESULT BVLC_Result_Code = BVLC_RESULT_SUCCESSFUL_COMPLETION;
|
||||||
|
/** The current BVLC Function Code being handled. */
|
||||||
|
BACNET_BVLC_FUNCTION BVLC_Function_Code = BVLC_RESULT; /* A safe default */
|
||||||
|
|
||||||
|
/** Encode the BVLC Result message
|
||||||
|
*
|
||||||
|
* @param pdu - buffer to store the encoding
|
||||||
|
* @param result_code - BVLC result code
|
||||||
|
*
|
||||||
|
* @return number of bytes encoded
|
||||||
|
*/
|
||||||
|
static int bvlc_encode_bvlc_result(uint8_t *pdu, BACNET_BVLC_RESULT result_code)
|
||||||
|
{
|
||||||
|
if (pdu) {
|
||||||
|
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||||
|
pdu[1] = BVLC_RESULT;
|
||||||
|
/* The 2-octet BVLC Length field is the length, in octets,
|
||||||
|
of the entire BVLL message, including the two octets of the
|
||||||
|
length field itself, most significant octet first. */
|
||||||
|
encode_unsigned16(&pdu[2], 6);
|
||||||
|
encode_unsigned16(&pdu[4], (uint16_t)result_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The common send function for bvlc functions, using b/ip.
|
||||||
|
*
|
||||||
|
* @param dest_addr - Points to an address containing the
|
||||||
|
* destination address (4 bytes, network byte order)
|
||||||
|
* @param dest_port - Destination port number
|
||||||
|
* @param mtu - the bytes of data to send
|
||||||
|
* @param mtu_len - the number of bytes of data to send
|
||||||
|
* @return Upon successful completion, returns the number of bytes sent.
|
||||||
|
* Otherwise, -1 shall be returned to indicate the error.
|
||||||
|
*/
|
||||||
|
static int bvlc_send_mpdu(
|
||||||
|
const uint8_t *dest_addr,
|
||||||
|
const uint16_t *dest_port,
|
||||||
|
uint8_t *mtu,
|
||||||
|
uint16_t mtu_len)
|
||||||
|
{
|
||||||
|
int bytes_sent = 0;
|
||||||
|
|
||||||
|
/* assumes that the driver has already been initialized */
|
||||||
|
if (!bip_valid()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send using platform-specific socket function */
|
||||||
|
bytes_sent = bip_socket_send(dest_addr, *dest_port, mtu, mtu_len);
|
||||||
|
|
||||||
|
return bytes_sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sends a BVLC Result
|
||||||
|
*
|
||||||
|
* @param dest_addr - destination address
|
||||||
|
* @param dest_port - destination port
|
||||||
|
*/
|
||||||
|
static void bvlc_send_result(
|
||||||
|
const uint8_t *dest_addr,
|
||||||
|
const uint16_t *dest_port,
|
||||||
|
BACNET_BVLC_RESULT result_code)
|
||||||
|
{
|
||||||
|
uint8_t mtu[BIP_MPDU_MAX] = { 0 };
|
||||||
|
uint16_t mtu_len = 0;
|
||||||
|
|
||||||
|
mtu_len = (uint16_t)bvlc_encode_bvlc_result(&mtu[0], result_code);
|
||||||
|
bvlc_send_mpdu(dest_addr, dest_port, mtu, mtu_len);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Note any BVLC_RESULT code, or NAK the BVLL message in the unsupported cases.
|
||||||
|
* Use this handler when you are not a BBMD.
|
||||||
|
* Sets the BVLC_Function_Code in case it is needed later.
|
||||||
|
*
|
||||||
|
* @param addr [in] Socket address to send any NAK back to.
|
||||||
|
* @param port [in] Socket port
|
||||||
|
* @param npdu [in] The received buffer.
|
||||||
|
* @param received_bytes [in] How many bytes in npdu[].
|
||||||
|
* @return Non-zero BVLC_RESULT_ code if we sent a response (NAK) to this
|
||||||
|
* BVLC message. If zero, may need further processing.
|
||||||
|
*/
|
||||||
|
uint16_t bvlc_for_non_bbmd(
|
||||||
|
uint8_t *addr, uint16_t *port, uint8_t *npdu, uint16_t received_bytes)
|
||||||
|
{
|
||||||
|
uint16_t result_code = 0; /* aka, BVLC_RESULT_SUCCESSFUL_COMPLETION */
|
||||||
|
|
||||||
|
/* To check the BVLC-function code, the buffer of received
|
||||||
|
* bytes has to be at least one byte long. */
|
||||||
|
if (received_bytes >= 1) {
|
||||||
|
BVLC_Function_Code = npdu[1]; /* The BVLC function */
|
||||||
|
switch (BVLC_Function_Code) {
|
||||||
|
case BVLC_RESULT:
|
||||||
|
if (received_bytes >= 6) {
|
||||||
|
/* This is the result of our foreign device registration */
|
||||||
|
(void)decode_unsigned16(&npdu[4], &result_code);
|
||||||
|
BVLC_Result_Code = (BACNET_BVLC_RESULT)result_code;
|
||||||
|
fprintf(stderr, "BVLC: Result Code=%d\n", BVLC_Result_Code);
|
||||||
|
/* But don't send any response */
|
||||||
|
result_code = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE:
|
||||||
|
result_code =
|
||||||
|
BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK;
|
||||||
|
break;
|
||||||
|
case BVLC_READ_BROADCAST_DIST_TABLE:
|
||||||
|
result_code = BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK;
|
||||||
|
break;
|
||||||
|
case BVLC_REGISTER_FOREIGN_DEVICE:
|
||||||
|
result_code = BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK;
|
||||||
|
break;
|
||||||
|
case BVLC_READ_FOREIGN_DEVICE_TABLE:
|
||||||
|
result_code = BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK;
|
||||||
|
break;
|
||||||
|
case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY:
|
||||||
|
result_code = BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK;
|
||||||
|
break;
|
||||||
|
case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK:
|
||||||
|
result_code = BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result_code > 0) {
|
||||||
|
bvlc_send_result(addr, port, result_code);
|
||||||
|
fprintf(stderr, "BVLC: NAK code=%d\n", result_code);
|
||||||
|
}
|
||||||
|
return result_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the current BVLL Function Code we are processing.
|
||||||
|
* We have to store this higher layer code for when the lower layers
|
||||||
|
* need to know what it is, especially to differentiate between
|
||||||
|
* BVLC_ORIGINAL_UNICAST_NPDU and BVLC_ORIGINAL_BROADCAST_NPDU.
|
||||||
|
*
|
||||||
|
* @return A BVLC_ function code, such as BVLC_ORIGINAL_UNICAST_NPDU.
|
||||||
|
*/
|
||||||
|
BACNET_BVLC_FUNCTION pico_bvlc_get_function_code(void)
|
||||||
|
{
|
||||||
|
return BVLC_Function_Code;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @authors Miguel Fernandes <miguelandre.fernandes@gmail.com> Testimony Adams
|
||||||
|
* <adamstestimony@gmail.com>
|
||||||
|
* @date 6 de Jun de 2013
|
||||||
|
* @brief BACnet Virtual Link Control for Pico
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BVLC_PICO_H
|
||||||
|
#define BVLC_PICO_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "bacnet/datalink/bvlc.h"
|
||||||
|
|
||||||
|
#define BACNET_BVLC_RESULT uint8_t
|
||||||
|
#define BACNET_BVLC_FUNCTION uint8_t
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Maximum packet size for BACnet/IP */
|
||||||
|
#ifndef BIP_MPDU_MAX
|
||||||
|
#define BIP_MPDU_MAX 1506
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint16_t bvlc_for_non_bbmd(
|
||||||
|
uint8_t *addr, uint16_t *port, uint8_t *npdu, uint16_t received_bytes);
|
||||||
|
|
||||||
|
BACNET_BVLC_FUNCTION pico_bvlc_get_function_code(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* BVLC_PICO_H */
|
||||||
Executable
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# configure.sh - Clone external dependencies required to build the Pico port
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
EXTERNAL_DIR="${SCRIPT_DIR}/external"
|
||||||
|
|
||||||
|
mkdir -p "${EXTERNAL_DIR}"
|
||||||
|
|
||||||
|
PICO_SDK_DIR="${EXTERNAL_DIR}/pico-sdk"
|
||||||
|
PICO_SDK_URL="${PICO_SDK_URL:-https://github.com/raspberrypi/pico-sdk.git}"
|
||||||
|
PICO_SDK_REF="${PICO_SDK_REF:-2.2.0}"
|
||||||
|
|
||||||
|
if [ -d "${PICO_SDK_DIR}/.git" ]; then
|
||||||
|
echo "Pico SDK already present at ${PICO_SDK_DIR}."
|
||||||
|
echo "Requested SDK ref: ${PICO_SDK_REF}"
|
||||||
|
else
|
||||||
|
echo "Cloning Raspberry Pi Pico SDK ${PICO_SDK_REF} ..."
|
||||||
|
git clone --branch "${PICO_SDK_REF}" --depth 1 "${PICO_SDK_URL}" \
|
||||||
|
"${PICO_SDK_DIR}"
|
||||||
|
git -C "${PICO_SDK_DIR}" submodule update --init --depth 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "External dependencies are ready in ${EXTERNAL_DIR}/"
|
||||||
|
echo ""
|
||||||
|
echo "To build:"
|
||||||
|
echo " cmake -S . -B build -DPICO_SDK_PATH=${PICO_SDK_DIR}"
|
||||||
|
echo " # Optional: override the default Pico 2 board selection"
|
||||||
|
echo " # cmake -S . -B build -DPICO_SDK_PATH=${PICO_SDK_DIR} -DPICO_BOARD=pico2_w"
|
||||||
|
echo " cmake --build build -- -j$(nproc)"
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#include "dlenv.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
volatile struct mstp_port_struct_t MSTP_Port;
|
||||||
|
|
||||||
|
/* Internal state associated with that port */
|
||||||
|
static struct dlmstp_user_data_t MSTP_User_Data;
|
||||||
|
static uint8_t Input_Buffer[DLMSTP_MPDU_MAX];
|
||||||
|
static uint8_t Output_Buffer[DLMSTP_MPDU_MAX];
|
||||||
|
|
||||||
|
static struct dlmstp_rs485_driver RS485_Driver = {
|
||||||
|
.init = rs485_init,
|
||||||
|
.send = rs485_bytes_send,
|
||||||
|
.read = rs485_byte_available,
|
||||||
|
.transmitting = rs485_rts_enabled,
|
||||||
|
.baud_rate = rs485_baud_rate,
|
||||||
|
.baud_rate_set = rs485_baud_rate_set,
|
||||||
|
.silence_milliseconds = rs485_silence_milliseconds,
|
||||||
|
.silence_reset = rs485_silence_reset
|
||||||
|
};
|
||||||
|
|
||||||
|
// TO BE IMPROVED
|
||||||
|
volatile bool g_mstp_have_token = false;
|
||||||
|
volatile uint8_t g_last_frame_type = 0;
|
||||||
|
volatile uint8_t g_last_src = 0;
|
||||||
|
volatile uint8_t g_last_dst = 0;
|
||||||
|
|
||||||
|
static void mstp_frame_rx_complete_cb(
|
||||||
|
uint8_t src, uint8_t dst, uint8_t frame_type, uint8_t *buf, uint16_t len)
|
||||||
|
{
|
||||||
|
(void)buf;
|
||||||
|
(void)len;
|
||||||
|
|
||||||
|
g_last_src = src;
|
||||||
|
g_last_dst = dst;
|
||||||
|
g_last_frame_type = frame_type;
|
||||||
|
|
||||||
|
// TO BE IMPROVED (For other frame types as needed)
|
||||||
|
if (frame_type == FRAME_TYPE_TOKEN) {
|
||||||
|
g_mstp_have_token = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pico_dlenv_init(uint8_t mac_address)
|
||||||
|
{
|
||||||
|
/* RS-485 low-level init (pins, UART config, etc.) */
|
||||||
|
RS485_Driver.init();
|
||||||
|
|
||||||
|
/* Configure MSTP port structure that the core stack will drive */
|
||||||
|
MSTP_Port.Nmax_info_frames = BACNET_MSTP_MAX_INFO_FRAMES;
|
||||||
|
MSTP_Port.Nmax_master = BACNET_MSTP_MAX_MASTER;
|
||||||
|
|
||||||
|
MSTP_Port.InputBuffer = Input_Buffer;
|
||||||
|
MSTP_Port.InputBufferSize = sizeof(Input_Buffer);
|
||||||
|
MSTP_Port.OutputBuffer = Output_Buffer;
|
||||||
|
MSTP_Port.OutputBufferSize = sizeof(Output_Buffer);
|
||||||
|
|
||||||
|
/* No ZeroConfig / slaves / auto-baud for this Pico port (you can tweak) */
|
||||||
|
MSTP_Port.ZeroConfigEnabled = false;
|
||||||
|
MSTP_Port.SlaveNodeEnabled = false;
|
||||||
|
MSTP_Port.CheckAutoBaud = false;
|
||||||
|
|
||||||
|
MSTP_Zero_Config_UUID_Init((struct mstp_port_struct_t *)&MSTP_Port);
|
||||||
|
|
||||||
|
/* Hook the RS-485 driver into the MS/TP layer */
|
||||||
|
MSTP_User_Data.RS485_Driver = &RS485_Driver;
|
||||||
|
MSTP_Port.UserData = &MSTP_User_Data;
|
||||||
|
|
||||||
|
/* Start the MS/TP state machine using our port */
|
||||||
|
if (!dlmstp_init((char *)&MSTP_Port)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply logical MS/TP parameters */
|
||||||
|
dlmstp_set_mac_address(mac_address);
|
||||||
|
dlmstp_set_baud_rate(RS485_BAUD_RATE);
|
||||||
|
// dlmstp_slave_mode_enabled_set(true);
|
||||||
|
dlmstp_set_max_master(BACNET_MSTP_MAX_MASTER);
|
||||||
|
dlmstp_set_max_info_frames(BACNET_MSTP_MAX_INFO_FRAMES);
|
||||||
|
dlmstp_set_frame_rx_complete_callback(mstp_frame_rx_complete_cb);
|
||||||
|
// TO BE IMPROVED (Add other callbacks as needed)
|
||||||
|
dlmstp_set_frame_not_for_us_rx_complete_callback(NULL);
|
||||||
|
dlmstp_set_invalid_frame_rx_complete_callback(NULL);
|
||||||
|
dlmstp_set_frame_rx_start_callback(NULL);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The stack may call this periodically; for this Pico MS/TP-only port
|
||||||
|
we don't need extra maintenance, so it's just a no-op. */
|
||||||
|
void dlenv_maintenance_timer(uint16_t seconds)
|
||||||
|
{
|
||||||
|
(void)seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Likewise, nothing special to clean up on embedded target */
|
||||||
|
void dlenv_cleanup(void)
|
||||||
|
{
|
||||||
|
/* no action */
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#ifndef DLENV_H
|
||||||
|
#define DLENV_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "bacnet/bacdef.h"
|
||||||
|
#include "bacnet/datalink/dlenv.h"
|
||||||
|
#include "bacnet/datalink/dlmstp.h"
|
||||||
|
#include "bacnet/datalink/mstp.h"
|
||||||
|
#include "bacnet/basic/sys/mstimer.h"
|
||||||
|
|
||||||
|
#include "rs485.h"
|
||||||
|
|
||||||
|
#define BACNET_MSTP_MAX_MASTER 5
|
||||||
|
#define BACNET_MSTP_MAX_INFO_FRAMES 1
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
|
||||||
|
bool pico_dlenv_init(uint8_t mac_address);
|
||||||
|
void dlenv_maintenance_timer(uint16_t seconds);
|
||||||
|
void dlenv_cleanup(void);
|
||||||
|
|
||||||
|
// TO BE IMPROVED
|
||||||
|
extern volatile bool g_mstp_have_token;
|
||||||
|
extern volatile uint8_t g_last_frame_type;
|
||||||
|
extern volatile uint8_t g_last_src;
|
||||||
|
extern volatile uint8_t g_last_dst;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
|
||||||
|
#include "dlenv.h"
|
||||||
|
#include "mstimer_init.h"
|
||||||
|
|
||||||
|
#include "bacnet/basic/binding/address.h"
|
||||||
|
#include "bacnet/basic/object/device.h"
|
||||||
|
#include "bacnet/basic/services.h"
|
||||||
|
#include "bacnet/basic/tsm/tsm.h"
|
||||||
|
#include "bacnet/datalink/datalink.h"
|
||||||
|
#include "bacnet/npdu.h"
|
||||||
|
|
||||||
|
static uint8_t PDUBuffer[MAX_MPDU + 16];
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
uint16_t pdu_len = 0;
|
||||||
|
BACNET_ADDRESS src = { 0 };
|
||||||
|
|
||||||
|
stdio_init_all();
|
||||||
|
systimer_init();
|
||||||
|
|
||||||
|
address_init();
|
||||||
|
Device_Set_Object_Instance_Number(12345);
|
||||||
|
Device_Init(NULL);
|
||||||
|
|
||||||
|
if (!pico_dlenv_init(2)) {
|
||||||
|
while (true) {
|
||||||
|
tight_loop_contents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
|
||||||
|
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
|
||||||
|
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has);
|
||||||
|
apdu_set_confirmed_handler(
|
||||||
|
SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
|
||||||
|
apdu_set_confirmed_handler(
|
||||||
|
SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple);
|
||||||
|
apdu_set_confirmed_handler(
|
||||||
|
SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property);
|
||||||
|
apdu_set_confirmed_handler(
|
||||||
|
SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL,
|
||||||
|
handler_device_communication_control);
|
||||||
|
|
||||||
|
Send_I_Am(&Handler_Transmit_Buffer[0]);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
pdu_len = datalink_receive(&src, &PDUBuffer[0], MAX_MPDU, 0);
|
||||||
|
if (pdu_len) {
|
||||||
|
npdu_handler(&src, &PDUBuffer[0], pdu_len);
|
||||||
|
}
|
||||||
|
tsm_timer_milliseconds(1);
|
||||||
|
tight_loop_contents();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#include "mstimer_init.h"
|
||||||
|
|
||||||
|
void systimer_init(void)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long mstimer_now(void)
|
||||||
|
{
|
||||||
|
return to_ms_since_boot(get_absolute_time());
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SYSTIMER_H
|
||||||
|
#define SYSTIMER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "pico/time.h"
|
||||||
|
#include "bacnet/basic/sys/mstimer.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
void systimer_init(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
/* BACnet Stack defines - first */
|
||||||
|
#include "bacnet/bacdef.h"
|
||||||
|
/* BACnet Stack API */
|
||||||
|
#include "bacnet/basic/sys/mstimer.h"
|
||||||
|
#include "bacnet/datalink/dlmstp.h"
|
||||||
|
#include "bacnet/basic/sys/fifo.h"
|
||||||
|
#include "rs485.h"
|
||||||
|
#include "mstimer_init.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// --- Static State Variables ---
|
||||||
|
static bool Rs485_RTS_Enabled = false;
|
||||||
|
static uint32_t Rs485_Baud_Rate = RS485_BAUD_RATE;
|
||||||
|
static volatile uint32_t Rs485_Bytes_Tx = 0;
|
||||||
|
static volatile uint32_t Rs485_Bytes_Rx = 0;
|
||||||
|
|
||||||
|
/* amount of silence on the wire */
|
||||||
|
static struct mstimer Silence_Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the RS-485 UART and GPIO pins.
|
||||||
|
*/
|
||||||
|
void rs485_init(void)
|
||||||
|
{
|
||||||
|
// 1. Initialize GPIO pins
|
||||||
|
gpio_set_function(RS485_TX_PIN, GPIO_FUNC_UART);
|
||||||
|
gpio_set_function(RS485_RX_PIN, GPIO_FUNC_UART);
|
||||||
|
|
||||||
|
// Initialize DE/RE pin (RTS) as an output
|
||||||
|
gpio_init(RS485_DE_PIN);
|
||||||
|
gpio_set_dir(RS485_DE_PIN, GPIO_OUT);
|
||||||
|
rs485_rts_enable(false); // Set to receive (DE/RE low)
|
||||||
|
|
||||||
|
// 2. Initialize UART
|
||||||
|
uart_init(RS485_UART_ID, Rs485_Baud_Rate);
|
||||||
|
uart_set_format(RS485_UART_ID, DATA_BIT, STOP_BIT, UART_PARITY_NONE);
|
||||||
|
uart_set_hw_flow(RS485_UART_ID, false, false);
|
||||||
|
uart_set_fifo_enabled(RS485_UART_ID, false);
|
||||||
|
|
||||||
|
// 3. Reset silence timer
|
||||||
|
rs485_silence_reset();
|
||||||
|
|
||||||
|
// 4. Flush any junk data in the RX buffer from startup
|
||||||
|
while (uart_is_readable(RS485_UART_ID)) {
|
||||||
|
uart_getc(RS485_UART_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enables or disables the Request To Send (RTS) / Driver Enable (DE/RE)
|
||||||
|
* pin.
|
||||||
|
* @param enable True to enable transmitter (TX), False to enable receiver (RX).
|
||||||
|
*/
|
||||||
|
void rs485_rts_enable(bool enable)
|
||||||
|
{
|
||||||
|
// DE=1: Driver Enable (TX mode)
|
||||||
|
// DE=0: Receiver Enable (RX mode)
|
||||||
|
gpio_put(RS485_DE_PIN, enable ? 1 : 0);
|
||||||
|
Rs485_RTS_Enabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the current state of the RTS/DE/RE pin.
|
||||||
|
* @return True if transmitter is enabled, False if receiver is enabled.
|
||||||
|
*/
|
||||||
|
bool rs485_rts_enabled(void)
|
||||||
|
{
|
||||||
|
return Rs485_RTS_Enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Attempts to read a single byte from the UART.
|
||||||
|
* @param data_register Pointer to store the received byte.
|
||||||
|
* @return True if a byte was received, False otherwise.
|
||||||
|
*/
|
||||||
|
bool rs485_byte_available(uint8_t *data_register)
|
||||||
|
{
|
||||||
|
if (!uart_is_readable(RS485_UART_ID)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!data_register) {
|
||||||
|
// only checking availability — DO NOT CONSUME
|
||||||
|
// Edit to consume if needed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
*data_register = uart_getc(RS485_UART_ID);
|
||||||
|
Rs485_Bytes_Rx++;
|
||||||
|
rs485_silence_reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks the UART hardware for a receive error (Framing, Parity,
|
||||||
|
* Overrun).
|
||||||
|
* @return True if a receive error is present, False otherwise.
|
||||||
|
*/
|
||||||
|
bool rs485_receive_error(void)
|
||||||
|
{
|
||||||
|
// The RSR (Receive Status Register) holds error flags.
|
||||||
|
// It is part of the raw hardware registers, accessed via uart_get_hw().
|
||||||
|
uart_hw_t *uart_hw = uart_get_hw(RS485_UART_ID);
|
||||||
|
|
||||||
|
// RSR bits: 0: FE (Framing Error), 1: PE (Parity Error), 2: BE (Break
|
||||||
|
// Error), 3: OE (Overrun Error)
|
||||||
|
uint32_t rsr = uart_hw->rsr;
|
||||||
|
|
||||||
|
// Clearing the RSR register is crucial after reading errors.
|
||||||
|
// Writing anything to it clears all error flags.
|
||||||
|
uart_hw->rsr = 0;
|
||||||
|
|
||||||
|
// Check if any error bit is set
|
||||||
|
return (rsr != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends a buffer of bytes over the UART.
|
||||||
|
* @param buffer Pointer to the data buffer.
|
||||||
|
* @param nbytes Number of bytes to send.
|
||||||
|
*/
|
||||||
|
void rs485_bytes_send(const uint8_t *buffer, uint16_t nbytes)
|
||||||
|
{
|
||||||
|
// The BACnet stack will call rs485_rts_enable(true) before this function,
|
||||||
|
// and wait T_prop before sending.
|
||||||
|
// printf("TX %u bytes: ", nbytes);
|
||||||
|
// for (uint16_t i = 0; i < nbytes; i++) {
|
||||||
|
// printf("%02X ", buffer[i]);
|
||||||
|
// }
|
||||||
|
// printf("\r\n");
|
||||||
|
rs485_rts_enable(true);
|
||||||
|
// TO BE IMPROVED (Implement custom_sleep)
|
||||||
|
sleep_us(200);
|
||||||
|
uart_write_blocking(RS485_UART_ID, buffer, nbytes);
|
||||||
|
uart_tx_wait_blocking(RS485_UART_ID);
|
||||||
|
sleep_us(200);
|
||||||
|
|
||||||
|
// Update count and silence timer.
|
||||||
|
Rs485_Bytes_Tx += nbytes;
|
||||||
|
rs485_rts_enable(false);
|
||||||
|
rs485_silence_reset();
|
||||||
|
|
||||||
|
// The BACnet stack handles the post-transmit delay (T_prop + T_frame_gap)
|
||||||
|
// and calls rs485_rts_enable(false) to turn off the driver.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the currently configured baud rate.
|
||||||
|
* @return The current baud rate.
|
||||||
|
*/
|
||||||
|
uint32_t rs485_baud_rate(void)
|
||||||
|
{
|
||||||
|
return Rs485_Baud_Rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets a new baud rate for the UART.
|
||||||
|
* @param baud The new baud rate to set.
|
||||||
|
* @return True if the baud rate was successfully set, False otherwise.
|
||||||
|
*/
|
||||||
|
bool rs485_baud_rate_set(uint32_t baud)
|
||||||
|
{
|
||||||
|
if (baud == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// uart_set_baudrate returns the actual configured rate, which may be
|
||||||
|
// slightly off. We only care that the function executed successfully and we
|
||||||
|
// store the requested rate.
|
||||||
|
switch (baud) {
|
||||||
|
case 9600:
|
||||||
|
case 19200:
|
||||||
|
case 38400:
|
||||||
|
case 57600:
|
||||||
|
case 76800:
|
||||||
|
case 115200:
|
||||||
|
Rs485_Baud_Rate = baud;
|
||||||
|
uart_set_baudrate(RS485_UART_ID, Rs485_Baud_Rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Measures the duration of silence on the bus since the last byte (Tx or
|
||||||
|
* Rx).
|
||||||
|
* @return The duration of silence in milliseconds.
|
||||||
|
*/
|
||||||
|
uint32_t rs485_silence_milliseconds(void)
|
||||||
|
{
|
||||||
|
return mstimer_elapsed(&Silence_Timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resets the silence timer to the current time.
|
||||||
|
*/
|
||||||
|
void rs485_silence_reset(void)
|
||||||
|
{
|
||||||
|
mstimer_set(&Silence_Timer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the total number of bytes transmitted.
|
||||||
|
* @return The byte count.
|
||||||
|
*/
|
||||||
|
uint32_t rs485_bytes_transmitted(void)
|
||||||
|
{
|
||||||
|
return Rs485_Bytes_Tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the total number of bytes received.
|
||||||
|
* @return The byte count.
|
||||||
|
*/
|
||||||
|
uint32_t rs485_bytes_received(void)
|
||||||
|
{
|
||||||
|
return Rs485_Bytes_Rx;
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Testimony Adams <adamstestimony@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||||
|
*
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#ifndef RS485_H
|
||||||
|
#define RS485_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/uart.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/regs/uart.h"
|
||||||
|
|
||||||
|
#define RS485_UART_ID uart1
|
||||||
|
#define RS485_BAUD_RATE 38400
|
||||||
|
#define RS485_TX_PIN 8
|
||||||
|
#define RS485_RX_PIN 9
|
||||||
|
#define RS485_DE_PIN 10
|
||||||
|
#define DATA_BIT 8
|
||||||
|
#define STOP_BIT 1
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
|
||||||
|
void rs485_rts_enable(bool enable);
|
||||||
|
bool rs485_rts_enabled(void);
|
||||||
|
bool rs485_byte_available(uint8_t *data_register);
|
||||||
|
bool rs485_receive_error(void);
|
||||||
|
|
||||||
|
void rs485_bytes_send(const uint8_t *buffer, uint16_t nbytes);
|
||||||
|
|
||||||
|
uint32_t rs485_baud_rate(void);
|
||||||
|
bool rs485_baud_rate_set(uint32_t baud);
|
||||||
|
|
||||||
|
uint32_t rs485_silence_milliseconds(void);
|
||||||
|
void rs485_silence_reset(void);
|
||||||
|
|
||||||
|
uint32_t rs485_bytes_transmitted(void);
|
||||||
|
uint32_t rs485_bytes_received(void);
|
||||||
|
|
||||||
|
void rs485_init(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user