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:
Testimony
2026-03-25 14:22:09 +01:00
committed by GitHub
parent 5eda68d381
commit 17e94092a1
17 changed files with 1720 additions and 0 deletions
+1
View File
@@ -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
+6
View File
@@ -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 \
+131
View File
@@ -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)
+162
View File
@@ -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.
+116
View File
@@ -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;
}
+373
View File
@@ -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;
}
+131
View File
@@ -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 */
+191
View File
@@ -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;
}
+36
View File
@@ -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 */
+32
View File
@@ -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)"
+108
View File
@@ -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 */
}
+42
View File
@@ -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
+66
View File
@@ -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();
}
}
+19
View File
@@ -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());
}
+25
View File
@@ -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
+229
View File
@@ -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;
}
+52
View File
@@ -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