Changed ATmega168 port to ATmega328 on Uno R3 with DFR0259 RS485 shield (#784)
* Changed ATmega168 example for ATmega328 on Arduino Uno R3 with DFR0259 RS485 shield. Added ADC interface from BDK port and mapped to some AV objects. Removed MS/TP MAC address DIP switch GPIO and moved MS/TP configuration to AV objects. Added AV units property. Added some Uno R3 Digital Inputs and outputs mapped to some BV. Added AVR EEPROM from BDK port and mapped some non-volatile data including MAC address and max manager and baud rate, device ID and names and description and location.
This commit is contained in:
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "MinGW64",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/../../src",
|
||||
"C:/msys64/mingw64/avr/include/"
|
||||
],
|
||||
"defines": [
|
||||
"__GNUC__",
|
||||
"__AVR_ATmega328P__"
|
||||
],
|
||||
"compilerPath": "C:/msys64/mingw64/bin/avr-gcc.exe",
|
||||
"cStandard": "gnu11",
|
||||
"cppStandard": "gnu++14",
|
||||
"intelliSenseMode": "windows-gcc-x64",
|
||||
"compilerArgs": [
|
||||
"-mmcu=atmega328p",
|
||||
// Will ensure MCU defines are set correctly
|
||||
"-DF_CPU=16000000UL",
|
||||
// Will ensure F_CPU is set correctly
|
||||
"-Os" // Will avoid optimization warnings re: _delay
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/../../src",
|
||||
"/usr/lib/avr/include"
|
||||
],
|
||||
"defines": [
|
||||
"__GNUC__",
|
||||
"__AVR_ATmega328P__"
|
||||
],
|
||||
"compilerPath": "/usr/bin/avr-gcc",
|
||||
"cStandard": "gnu11",
|
||||
"cppStandard": "gnu++14",
|
||||
"intelliSenseMode": "${default}",
|
||||
"compilerArgs": [
|
||||
"-mmcu=atmega328p",
|
||||
// Will ensure MCU defines are set correctly
|
||||
"-DF_CPU=16000000UL",
|
||||
// Will ensure F_CPU is set correctly
|
||||
"-Os" // Will avoid optimization warnings re: _delay
|
||||
]
|
||||
}
|
||||
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
###############################################################################
|
||||
# Makefile for BACnet
|
||||
###############################################################################
|
||||
|
||||
## General Flags
|
||||
MCU = atmega328p
|
||||
AVRDUDE_MCU = m328p
|
||||
TARGET = bacnet
|
||||
## Tools
|
||||
CC = avr-gcc
|
||||
AR = avr-ar
|
||||
OBJCOPY = avr-objcopy
|
||||
OBJDUMP = avr-objdump
|
||||
SIZE = avr-size
|
||||
AVRDUDE = avrdude
|
||||
|
||||
# programmer id--check the avrdude for complete list
|
||||
# # of available opts. These should include stk500,
|
||||
# # avr910, avrisp, bsd, pony and more. Set this to
|
||||
# # one of the valid "-c PROGRAMMER-ID" values
|
||||
# # described in the avrdude info page.
|
||||
# #
|
||||
AVRDUDE_PROGRAMMERID = arduino
|
||||
#
|
||||
# # port--serial or parallel port to which your
|
||||
# # hardware programmer is attached
|
||||
# #
|
||||
ifeq ($(OS),Windows_NT)
|
||||
AVRDUDE_PORT = COM21
|
||||
else
|
||||
AVRDUDE_PORT = /dev/ttyUSB0
|
||||
endif
|
||||
|
||||
# Source locations
|
||||
BACNET_SRC = ../../src
|
||||
BACNET_CORE = $(BACNET_SRC)/bacnet
|
||||
BACNET_BASIC = $(BACNET_CORE)/basic
|
||||
|
||||
# local files for this project
|
||||
CSRC = main.c \
|
||||
adc.c \
|
||||
timer.c \
|
||||
stack.c \
|
||||
rs485.c \
|
||||
dlmstp.c \
|
||||
apdu.c \
|
||||
h_rp.c \
|
||||
eeprom.c \
|
||||
nvdata.c \
|
||||
device.c \
|
||||
av.c \
|
||||
bv.c \
|
||||
h_whois.c \
|
||||
h_wp.c
|
||||
|
||||
# common demo files needed
|
||||
BASICSRC = $(BACNET_BASIC)/tsm/tsm.c \
|
||||
$(BACNET_BASIC)/npdu/h_npdu.c \
|
||||
$(BACNET_BASIC)/sys/bigend.c \
|
||||
$(BACNET_BASIC)/sys/debug.c \
|
||||
$(BACNET_BASIC)/sys/mstimer.c \
|
||||
$(BACNET_BASIC)/service/s_iam.c \
|
||||
$(BACNET_BASIC)/service/h_noserv.c
|
||||
|
||||
# core BACnet stack files
|
||||
CORESRC = \
|
||||
$(BACNET_CORE)/datalink/crc.c \
|
||||
$(BACNET_CORE)/npdu.c \
|
||||
$(BACNET_CORE)/bacdcode.c \
|
||||
$(BACNET_CORE)/bacint.c \
|
||||
$(BACNET_CORE)/bacreal.c \
|
||||
$(BACNET_CORE)/bacstr.c \
|
||||
$(BACNET_CORE)/iam.c \
|
||||
$(BACNET_CORE)/rp.c \
|
||||
$(BACNET_CORE)/wp.c \
|
||||
$(BACNET_CORE)/whois.c \
|
||||
$(BACNET_CORE)/bacaddr.c \
|
||||
$(BACNET_CORE)/abort.c \
|
||||
$(BACNET_CORE)/reject.c \
|
||||
$(BACNET_CORE)/bacerror.c \
|
||||
$(BACNET_CORE)/bacapp.c
|
||||
|
||||
## Include Directories
|
||||
INCLUDES = -I. -I$(BACNET_SRC) -I$(BACNET_CORE)
|
||||
|
||||
# Source to Object conversion
|
||||
COBJ = $(CSRC:.c=.o)
|
||||
BASICOBJ = $(BASICSRC:.c=.o)
|
||||
COREOBJ = $(CORESRC:.c=.o)
|
||||
|
||||
LIBRARY = lib$(TARGET).a
|
||||
|
||||
## Options common to compile, link and assembly rules
|
||||
COMMON = -mmcu=$(MCU)
|
||||
|
||||
OPTIMIZE_FLAGS = -mcall-prologues
|
||||
#OPTIMIZE_FLAGS += -finline-functions
|
||||
OPTIMIZE_FLAGS += -finline-functions-called-once
|
||||
#OPTIMIZATION = -O0
|
||||
#OPTIMIZATION = -Os
|
||||
OPTIMIZATION = -Os $(OPTIMIZE_FLAGS)
|
||||
#OPTIMIZATION = -O3 $(OPTIMIZE_FLAGS)
|
||||
|
||||
## Compile options common for all C compilation units.
|
||||
BFLAGS = -DBACDL_MSTP
|
||||
BFLAGS += -DMAX_APDU=50
|
||||
BFLAGS += -DBIG_ENDIAN=0
|
||||
BFLAGS += -DMAX_TSM_TRANSACTIONS=0
|
||||
BFLAGS += -DBACNET_SVC_SERVER
|
||||
BFLAGS += -DBACAPP_REAL
|
||||
BFLAGS += -DBACAPP_OBJECT_ID
|
||||
BFLAGS += -DBACAPP_UNSIGNED
|
||||
BFLAGS += -DBACAPP_ENUMERATED
|
||||
BFLAGS += -DBACAPP_CHARACTER_STRING
|
||||
BFLAGS += -DWRITE_PROPERTY
|
||||
BFLAGS += -DBACNET_PROTOCOL_REVISION=9
|
||||
ifeq (${LEGACY},true)
|
||||
# disable deprecated function warnings for legacy builds
|
||||
BFLAGS += -DBACNET_STACK_DEPRECATED_DISABLE
|
||||
endif
|
||||
|
||||
CFLAGS = $(COMMON)
|
||||
# dead code removal
|
||||
CFLAGS += -ffunction-sections -fdata-sections
|
||||
CFLAGS += -Wall -gdwarf-2 $(BFLAGS) $(OPTIMIZATION) -fsigned-char
|
||||
CFLAGS += -MD -MP -MT $(*F).o -MF dep/$(@F).d
|
||||
# fix warnings about 'array subscript 0 is outside array bounds' new in GCC 12
|
||||
GCCVERSIONGTEQ12 := $(shell expr `avr-gcc -dumpversion | cut -f1 -d.` \>= 12)
|
||||
ifeq "$(GCCVERSIONGTEQ12)" "1"
|
||||
CFLAGS += --param=min-pagesize=0
|
||||
endif
|
||||
CFLAGS += -DF_CPU=16000000UL
|
||||
# silence some warnings
|
||||
CFLAGS += -Wno-switch
|
||||
|
||||
## Assembly specific flags
|
||||
ASMFLAGS = $(COMMON)
|
||||
ASMFLAGS += $(CFLAGS)
|
||||
ASMFLAGS += -x assembler-with-cpp -Wa,-gdwarf2
|
||||
|
||||
## Linker flags
|
||||
LDFLAGS = $(COMMON)
|
||||
#dead code removal
|
||||
#LDFLAGS += -Wl,-nostartfiles,-nostdlib
|
||||
LDFLAGS += -Wl,--gc-sections,-static
|
||||
LDFLAGS += -Wl,-Map=$(TARGET).map,-L.,-l$(TARGET)
|
||||
#LDFLAGS += -Wl,-Map=$(TARGET).map
|
||||
|
||||
## Intel Hex file production flags
|
||||
HEX_FLASH_FLAGS = -R .eeprom
|
||||
HEX_EEPROM_FLAGS = -j .eeprom
|
||||
HEX_EEPROM_FLAGS += --set-section-flags=.eeprom="alloc,load"
|
||||
HEX_EEPROM_FLAGS += --change-section-lma .eeprom=0 --no-change-warnings
|
||||
|
||||
## Objects that must be built in order to link
|
||||
OBJECTS = $(COBJ) $(BASICOBJ)
|
||||
#OBJECTS = $(COBJ)
|
||||
|
||||
## Build
|
||||
TARGET_ELF=$(TARGET).elf
|
||||
|
||||
all: $(LIBRARY) $(TARGET_ELF) $(TARGET).hex $(TARGET).eep $(TARGET).lst \
|
||||
size Makefile
|
||||
|
||||
##Link
|
||||
$(TARGET_ELF): $(OBJECTS) $(LIBRARY)
|
||||
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
|
||||
|
||||
%.hex: $(TARGET_ELF)
|
||||
$(OBJCOPY) -O ihex $(HEX_FLASH_FLAGS) $< $@
|
||||
|
||||
%.eep: $(TARGET_ELF)
|
||||
-$(OBJCOPY) $(HEX_EEPROM_FLAGS) -O ihex $< $@ || exit 0
|
||||
|
||||
%.lst: $(TARGET_ELF)
|
||||
$(OBJDUMP) -h -S $< > $@
|
||||
|
||||
lib: $(LIBRARY)
|
||||
|
||||
$(LIBRARY): $(COREOBJ) Makefile
|
||||
$(AR) rcs $@ $(COREOBJ)
|
||||
$(OBJDUMP) --syms $@ > $(LIBRARY:.a=.lst)
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(INCLUDES) $(CFLAGS) $*.c -o $@
|
||||
|
||||
size: ${TARGET_ELF}
|
||||
@echo
|
||||
@${SIZE} ${TARGET_ELF}
|
||||
|
||||
install: $(TARGET_ELF)
|
||||
$(AVRDUDE) -c $(AVRDUDE_PROGRAMMERID) \
|
||||
-p $(AVRDUDE_MCU) -P $(AVRDUDE_PORT) -e \
|
||||
-U flash:w:$(TARGET).hex
|
||||
|
||||
## Clean target
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -rf $(OBJECTS) $(TARGET_ELF) dep/*
|
||||
-rm -rf $(LIBRARY) $(COREOBJ) $(LIBRARY:.a=.lst)
|
||||
-rm -rf $(TARGET).hex $(TARGET).eep $(TARGET).lst $(TARGET).map
|
||||
|
||||
## Other dependencies
|
||||
-include $(shell mkdir dep 2>/dev/null) $(wildcard dep/*)
|
||||
@@ -0,0 +1,116 @@
|
||||
### BACnet MS/TP on Arduino Uno R3 Platform - Atmega328p
|
||||
|
||||
The Arduino Uno R3 platform for this example uses the following peripherals:
|
||||
|
||||
1) USB bootloader using AVR-119 protocol
|
||||
|
||||
2) 7-position DIP switch for MS/TP MAC address
|
||||
|
||||
3) Arduino Uno V3 compatible RS485 shield (DFR0259)
|
||||
|
||||
### Arduino Uno V3 Pin Mapping
|
||||
|
||||
| ATmega328p | Arduino | RS485 DFR0259 | BACnet Object |
|
||||
|:------------|:------------|:---------------|:----------------|
|
||||
| NC | NC | | |
|
||||
| +5V | IOREF | | |
|
||||
| RESET | RESET | RST BUTTON | |
|
||||
| +3V3 | +3V3 | | |
|
||||
| +5V | +5V | +5V | |
|
||||
| GND | GND | GND | |
|
||||
| GND | GND | GND | |
|
||||
| VIN | VIN | | |
|
||||
| | | | |
|
||||
| ADC0/PC0 | A0 | | AV 0 Millivolts |
|
||||
| ADC1/PC1 | A1 | | AV 1 Millivolts |
|
||||
| ADC2/PC2 | A2 | | AV 2 Millivolts |
|
||||
| ADC3/PC3 | A3 | | AV 3 Millivolts |
|
||||
| ADC4/PC4 | A4* | | |
|
||||
| ADC5/PC5 | A5* | | |
|
||||
| | | | |
|
||||
| ADC5/PC5 | SCL* | I2C | |
|
||||
| ADC4/PC4 | SDA* | I2C | |
|
||||
| AVDD | AREF | | |
|
||||
| GND | GND | | |
|
||||
| SCK/PB5 | D13 | LED-L ANODE (+)| BV 99 (output) |
|
||||
| MISO/PB4 | D12 | | BV 9 (output) |
|
||||
| MOSI/PB3 | D11 | | BV 8 (output) |
|
||||
| SS/PB2 | D10 | | BV 7 (output) |
|
||||
| OC1/PB1 | D9 | | BV 6 (output) |
|
||||
| ICP/PB0 | D8 | | BV 5 (output) |
|
||||
| | | | |
|
||||
| AIN1/PD7 | D7 | | BV 4 (input) |
|
||||
| AIN0/PD6 | D6 | | BV 3 (input) |
|
||||
| T1/PD5 | D5 | | BV 2 (input) |
|
||||
| T0/PD4 | D4 | | BV 1 (input) |
|
||||
| INT1/PD3 | D3 | | BV 0 (input) |
|
||||
| INT0/PD2 | D2 | CE DE /RE RTS | RS485 |
|
||||
| TXD/PD1 | D1/Tx** | TXD | RS485 |
|
||||
| RXD/PD0 | D0/Rx** | RXD | RS485 |
|
||||
|
||||
\* ADC4/PC4: A4 and SDA are shared I/O
|
||||
|
||||
\* ADC5/PC5: A5 and SCL are shared I/O
|
||||
|
||||
\** shared with Uno R3 USB Tx/Rx. DFR0259 switch ON to disable.
|
||||
|
||||
### Building this Project
|
||||
|
||||
#### GNU Makefile
|
||||
|
||||
There is a GNU Makefile that uses avr-gcc and avr-libc to build the project.
|
||||
avrdude can be used to program the Arduino Uno R3 board via USB.
|
||||
|
||||
The GNU Makefile is used in the continuous integration pipeline to validate
|
||||
the build is not broken. The Makefile is called from an Ubuntu image
|
||||
container after installing the necessary tools:
|
||||
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq build-essential
|
||||
sudo apt-get install -qq gcc-avr avr-libc binutils-avr avrdude
|
||||
|
||||
To add the build and debug tools to MinGW64 environment:
|
||||
|
||||
pacman --noconfirm -S mingw-w64-x86_64-gcc-avr
|
||||
pacman --noconfirm -S mingw-w64-x86_64-avr-libc
|
||||
pacman --noconfirm -S mingw-w64-x86_64-binutils-avr
|
||||
pacman --noconfirm -S mingw-w64-x86_64-avrdude
|
||||
|
||||
The build sequence is usually:
|
||||
|
||||
make clean all
|
||||
|
||||
The Makefile includes a recipe to use avrdude to program the Uno R3 via USB
|
||||
|
||||
make install
|
||||
|
||||
#### GCC and CStack Usage
|
||||
|
||||
CStack check for GCC is included in the device object as property 512.
|
||||
The compile shows 352 (.bss) + 136 (.data) = 488 bytes of RAM used, and
|
||||
the ATmega328 has 2048 bytes of RAM, leaving 1560 bytes for the CStack.
|
||||
BACnet Device object proprietary property 512 is mapped to the CStack Size
|
||||
and returns 1376 using ReadProperty request. After some ReadProperty and
|
||||
WriteProperty requests, the CStack shows 1159 CStack bytes free.
|
||||
Note that the value 0xC5 (197) was used to paint the CStack.
|
||||
Keep this in mind when developing.
|
||||
|
||||
##### Shield option
|
||||
|
||||
The DFR0259 shield for RS485 was used, but any RS485 circuit could be
|
||||
attached to the Arduino Uno R3 using the same pins for Tx, Rx, CE RE/DE RTS.
|
||||
|
||||
A 7-position DIP switch was connected to GPIO for the MS/TP MAC address,
|
||||
but the value could also be stored in EEPROM.
|
||||
|
||||
### BACnet Capabilities
|
||||
|
||||
The BACnet Capabilities include WhoIs, I-Am, ReadProperty, and
|
||||
WriteProperty support. The BACnet objects include a Device object,
|
||||
10 Binary Value objects, and 10 Analog Value objects. An GPIO output
|
||||
is controlled by Binary Value object instance 0. All required object
|
||||
properties can be retrieved using ReadProperty. The Present_Value
|
||||
property of the Analog Value and Binary Value objects can be
|
||||
written using WriteProperty. The Object_Identifier, Object_Name,
|
||||
Max_Info_Frames, Max_Master, and baud rate (property 9600) of the
|
||||
Device object can be written using WriteProperty.
|
||||
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* @brief This module manages the Analog to Digital Converter (ADC)
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "hardware.h"
|
||||
/* me */
|
||||
#include "adc.h"
|
||||
|
||||
/* prescale select bits */
|
||||
#if (F_CPU >> 1) < 1000000
|
||||
#define ADPS_8BIT (1)
|
||||
#define ADPS_10BIT (3)
|
||||
#elif (F_CPU >> 2) < 1000000
|
||||
#define ADPS_8BIT (2)
|
||||
#define ADPS_10BIT (4)
|
||||
#elif (F_CPU >> 3) < 1000000
|
||||
#define ADPS_8BIT (3)
|
||||
#define ADPS_10BIT (5)
|
||||
#elif (F_CPU >> 4) < 1000000
|
||||
#define ADPS_8BIT (4)
|
||||
#define ADPS_10BIT (6)
|
||||
#elif (F_CPU >> 5) < 1000000
|
||||
#define ADPS_8BIT (5)
|
||||
#define ADPS_10BIT (7)
|
||||
#else
|
||||
#error "ADC: F_CPU too large for accuracy."
|
||||
#endif
|
||||
|
||||
/* full scale ADC voltage constant in millivolts */
|
||||
#ifndef ADC_MILLIVOLTS_MAX
|
||||
#define ADC_MILLIVOLTS_MAX 5000L
|
||||
#endif
|
||||
/* full scale ADC value */
|
||||
#ifndef ADC_VALUE_MAX
|
||||
#define ADC_VALUE_MAX 1024L
|
||||
#endif
|
||||
|
||||
/* Array of ADC results */
|
||||
#define ADC_CHANNELS_MAX 8
|
||||
static volatile uint16_t Sample_Result[ADC_CHANNELS_MAX];
|
||||
static volatile uint8_t Enabled_Channels;
|
||||
|
||||
/**
|
||||
* @brief ADC interrupt based acquisition ISR
|
||||
*/
|
||||
ISR(ADC_vect)
|
||||
{
|
||||
uint8_t index;
|
||||
uint8_t mask;
|
||||
uint8_t channels;
|
||||
uint16_t value = 0;
|
||||
|
||||
/* determine which conversion finished */
|
||||
index = BITMASK_CHECK(ADMUX, ((1 << MUX2) | (1 << MUX1) | (1 << MUX0)));
|
||||
/* read the results */
|
||||
value = ADCL;
|
||||
value |= (ADCH << 8);
|
||||
Sample_Result[index] = value;
|
||||
channels = Enabled_Channels;
|
||||
__enable_interrupt();
|
||||
/* clear the mux */
|
||||
BITMASK_CLEAR(ADMUX, ((1 << MUX2) | (1 << MUX1) | (1 << MUX0)));
|
||||
/* find the next enabled channel */
|
||||
while (channels) {
|
||||
index++;
|
||||
if (index >= ADC_CHANNELS_MAX) {
|
||||
index = 0;
|
||||
}
|
||||
mask = 1 << index;
|
||||
if (channels & mask) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* configure the next channel */
|
||||
BITMASK_SET(ADMUX, ((index) << MUX0));
|
||||
/* Start the next conversion */
|
||||
BIT_SET(ADCSRA, ADSC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable the ADC channel for interrupt based acquisition
|
||||
* @param index - 0..8 = ADC0..ADC8, respectively
|
||||
*/
|
||||
void adc_enable(uint8_t index)
|
||||
{
|
||||
if (Enabled_Channels) {
|
||||
/* ADC interupt is already started */
|
||||
BIT_SET(Enabled_Channels, index);
|
||||
} else {
|
||||
if (index < ADC_CHANNELS_MAX) {
|
||||
/* not running yet */
|
||||
BIT_SET(Enabled_Channels, index);
|
||||
/* clear the mux */
|
||||
BITMASK_CLEAR(ADMUX, ((1 << MUX2) | (1 << MUX1) | (1 << MUX0)));
|
||||
/* configure the channel */
|
||||
BITMASK_SET(ADMUX, ((index) << MUX0));
|
||||
/* Start the next conversion */
|
||||
BIT_SET(ADCSRA, ADSC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the latest ADC channel value (8-bit)
|
||||
* @param index - 0..8 = ADC0..ADC8, respectively
|
||||
*/
|
||||
uint8_t adc_result_8bit(uint8_t index)
|
||||
{
|
||||
uint8_t result = 0;
|
||||
uint8_t sreg;
|
||||
|
||||
if (index < ADC_CHANNELS_MAX) {
|
||||
adc_enable(index);
|
||||
sreg = SREG;
|
||||
__disable_interrupt();
|
||||
result = (uint8_t)(Sample_Result[index] >> 2);
|
||||
SREG = sreg;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the latest ADC channel value (10-bit)
|
||||
* @param index - 0..8 = ADC0..ADC8, respectively
|
||||
*/
|
||||
uint16_t adc_result_10bit(uint8_t index)
|
||||
{ /* 0..7 = ADC0..ADC7, respectively */
|
||||
uint16_t result = 0;
|
||||
uint8_t sreg;
|
||||
|
||||
if (index < ADC_CHANNELS_MAX) {
|
||||
adc_enable(index);
|
||||
sreg = SREG;
|
||||
__disable_interrupt();
|
||||
result = Sample_Result[index];
|
||||
SREG = sreg;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the latest ADC channel value in millivolts
|
||||
* @param index - 0..8 = ADC0..ADC8, respectively
|
||||
* @return millivolts
|
||||
*/
|
||||
uint16_t adc_millivolts(uint8_t index)
|
||||
{
|
||||
uint32_t value;
|
||||
|
||||
value = adc_result_10bit(index);
|
||||
value = (value * ADC_MILLIVOLTS_MAX) / ADC_VALUE_MAX;
|
||||
|
||||
return (uint16_t)(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the ADC for interrupt based acquisition
|
||||
*/
|
||||
void adc_init(void)
|
||||
{
|
||||
/* Initial channel selection */
|
||||
/* ADLAR = Left Adjust Result
|
||||
REFSx = hardware setup: cap on AREF
|
||||
*/
|
||||
ADMUX = (0 << ADLAR) | (0 << REFS1) | (1 << REFS0);
|
||||
/* ADEN = Enable
|
||||
ADSC = Start conversion
|
||||
ADIF = Interrupt Flag - write 1 to clear!
|
||||
ADIE = Interrupt Enable
|
||||
ADATE = Auto Trigger Enable
|
||||
*/
|
||||
ADCSRA =
|
||||
(1 << ADEN) | (1 << ADIE) | (1 << ADIF) | (0 << ADATE) | ADPS_10BIT;
|
||||
/* trigger selection bits
|
||||
0 0 0 Free Running mode
|
||||
0 0 1 Analog Comparator
|
||||
0 1 0 External Interrupt Request 0
|
||||
0 1 1 Timer/Counter0 Compare Match
|
||||
1 0 0 Timer/Counter0 Overflow
|
||||
1 0 1 Timer/Counter1 Compare Match B
|
||||
1 1 0 Timer/Counter1 Overflow
|
||||
1 1 1 Timer/Counter1 Capture Event
|
||||
*/
|
||||
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0);
|
||||
/* disable ADC power reduction */
|
||||
PRR &= ((uint8_t) ~(1 << PRADC));
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @brief This module manages the Analog to Digital Converter (ADC)
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef ADC_H
|
||||
#define ADC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
void adc_enable(uint8_t index);
|
||||
uint8_t adc_result_8bit(uint8_t index);
|
||||
uint16_t adc_result_10bit(uint8_t index);
|
||||
uint16_t adc_millivolts(uint8_t index);
|
||||
void adc_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @brief This module manages the BACnet Application Protocol Data Unit (APDU)
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
/* BACnet Stack defines - first */
|
||||
#include "bacnet/bacdef.h"
|
||||
/* BACnet Stack API */
|
||||
#include "bacnet/apdu.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
|
||||
static uint8_t Local_Network_Priority; /* Fixing test 10.1.2 Network priority */
|
||||
|
||||
/**
|
||||
* @brief get the local network priority
|
||||
* @return local network priority
|
||||
*/
|
||||
uint8_t apdu_network_priority(void)
|
||||
{
|
||||
return Local_Network_Priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set the local network priority
|
||||
* @param net - local network priority
|
||||
*/
|
||||
void apdu_network_priority_set(uint8_t pri)
|
||||
{
|
||||
Local_Network_Priority = pri & 0x03;
|
||||
}
|
||||
|
||||
bool apdu_service_supported(BACNET_SERVICES_SUPPORTED service_supported)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
if (service_supported == SERVICE_SUPPORTED_READ_PROPERTY) {
|
||||
status = true;
|
||||
}
|
||||
if (service_supported == SERVICE_SUPPORTED_WHO_IS) {
|
||||
status = true;
|
||||
}
|
||||
#ifdef WRITE_PROPERTY
|
||||
if (service_supported == SERVICE_SUPPORTED_WRITE_PROPERTY) {
|
||||
status = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint16_t apdu_decode_confirmed_service_request(
|
||||
uint8_t *apdu, /* APDU data */
|
||||
uint16_t apdu_len,
|
||||
BACNET_CONFIRMED_SERVICE_DATA *service_data,
|
||||
uint8_t *service_choice,
|
||||
uint8_t **service_request,
|
||||
uint16_t *service_request_len)
|
||||
{
|
||||
uint16_t len = 0; /* counts where we are in PDU */
|
||||
|
||||
service_data->segmented_message = (apdu[0] & BIT(3)) ? true : false;
|
||||
service_data->more_follows = (apdu[0] & BIT(2)) ? true : false;
|
||||
service_data->segmented_response_accepted =
|
||||
(apdu[0] & BIT(1)) ? true : false;
|
||||
service_data->max_segs = decode_max_segs(apdu[1]);
|
||||
service_data->max_resp = decode_max_apdu(apdu[1]);
|
||||
service_data->invoke_id = apdu[2];
|
||||
service_data->priority = apdu_network_priority();
|
||||
len = 3;
|
||||
if (service_data->segmented_message) {
|
||||
service_data->sequence_number = apdu[len++];
|
||||
service_data->proposed_window_number = apdu[len++];
|
||||
}
|
||||
*service_choice = apdu[len++];
|
||||
*service_request = &apdu[len];
|
||||
*service_request_len = apdu_len - len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void apdu_handler(
|
||||
BACNET_ADDRESS *src,
|
||||
uint8_t *apdu, /* APDU data */
|
||||
uint16_t apdu_len)
|
||||
{
|
||||
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
|
||||
uint8_t service_choice = 0;
|
||||
uint8_t *service_request = NULL;
|
||||
uint16_t service_request_len = 0;
|
||||
uint16_t len = 0; /* counts where we are in PDU */
|
||||
|
||||
if (apdu) {
|
||||
/* PDU Type */
|
||||
switch (apdu[0] & 0xF0) {
|
||||
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
|
||||
len = apdu_decode_confirmed_service_request(
|
||||
&apdu[0], /* APDU data */
|
||||
apdu_len, &service_data, &service_choice, &service_request,
|
||||
&service_request_len);
|
||||
if (len == 0) {
|
||||
/* service data unable to be decoded - simply drop */
|
||||
break;
|
||||
}
|
||||
if (service_choice == SERVICE_CONFIRMED_READ_PROPERTY) {
|
||||
handler_read_property(
|
||||
service_request, service_request_len, src,
|
||||
&service_data);
|
||||
}
|
||||
#ifdef WRITE_PROPERTY
|
||||
else if (service_choice == SERVICE_CONFIRMED_WRITE_PROPERTY) {
|
||||
handler_write_property(
|
||||
service_request, service_request_len, src,
|
||||
&service_data);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
handler_unrecognized_service(
|
||||
service_request, service_request_len, src,
|
||||
&service_data);
|
||||
}
|
||||
(void)len;
|
||||
break;
|
||||
case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST:
|
||||
service_choice = apdu[1];
|
||||
service_request = &apdu[2];
|
||||
service_request_len = apdu_len - 2;
|
||||
if (service_choice == SERVICE_UNCONFIRMED_WHO_IS) {
|
||||
handler_who_is(service_request, service_request_len, src);
|
||||
}
|
||||
break;
|
||||
case PDU_TYPE_SIMPLE_ACK:
|
||||
case PDU_TYPE_COMPLEX_ACK:
|
||||
case PDU_TYPE_SEGMENT_ACK:
|
||||
case PDU_TYPE_ERROR:
|
||||
case PDU_TYPE_REJECT:
|
||||
case PDU_TYPE_ABORT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,598 @@
|
||||
/**
|
||||
* @brief This module manages the BACnet Analog Value objects
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "hardware.h"
|
||||
#include "adc.h"
|
||||
#include "nvdata.h"
|
||||
#include "rs485.h"
|
||||
#include "stack.h"
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacenum.h"
|
||||
#include "bacnet/bacapp.h"
|
||||
#include "bacnet/config.h" /* the custom stuff */
|
||||
#include "bacnet/wp.h"
|
||||
#include "bacnet/basic/object/av.h"
|
||||
|
||||
/* functions to get the present value when requested */
|
||||
typedef float (*object_present_value_read_callback)(void);
|
||||
/* functions to get the present value when requested */
|
||||
typedef bool (*object_present_value_write_callback)(float value);
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the ADC0 object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float adc0_value(void)
|
||||
{
|
||||
return (float)adc_millivolts(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the ADC1 object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float adc1_value(void)
|
||||
{
|
||||
return (float)adc_millivolts(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the ADC2 object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float adc2_value(void)
|
||||
{
|
||||
return (float)adc_millivolts(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the ADC3 object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float adc3_value(void)
|
||||
{
|
||||
return (float)adc_millivolts(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the stack size object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float stack_size_value(void)
|
||||
{
|
||||
return (float)stack_size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the stack unused object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float stack_unused_value(void)
|
||||
{
|
||||
return (float)stack_unused();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the MS/TP baud rate object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float mstp_baud(void)
|
||||
{
|
||||
uint8_t kbaud;
|
||||
float value;
|
||||
|
||||
kbaud = nvdata_unsigned8(NV_EEPROM_MSTP_BAUD_K);
|
||||
value = (float)RS485_Baud_Rate_From_Kilo(kbaud);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the present value for the MS/TP baud rate object.
|
||||
* @param value - The new value.
|
||||
* @return true if the value was in range and written.
|
||||
*/
|
||||
static bool mstp_baud_write(float value)
|
||||
{
|
||||
bool status = false;
|
||||
uint8_t kbaud;
|
||||
int32_t baud;
|
||||
|
||||
baud = (int32_t)value;
|
||||
if ((baud >= 9600L) && (baud <= 1152000L)) {
|
||||
kbaud = (uint8_t)(baud / 1000UL);
|
||||
nvdata_unsigned8_set(NV_EEPROM_MSTP_BAUD_K, kbaud);
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the MS/TP MAC address object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float mstp_mac(void)
|
||||
{
|
||||
return (float)nvdata_unsigned8(NV_EEPROM_MSTP_MAC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the present value for the MS/TP address object.
|
||||
* @param value - The new value.
|
||||
* @return true if the value was in range and written.
|
||||
*/
|
||||
static bool mstp_mac_write(float value)
|
||||
{
|
||||
bool status = false;
|
||||
uint8_t mac = 0;
|
||||
int32_t value32;
|
||||
|
||||
value32 = (int32_t)value;
|
||||
if ((value32 >= 0L) && (value32 <= 127L)) {
|
||||
mac = (uint8_t)value32;
|
||||
nvdata_unsigned8_set(NV_EEPROM_MSTP_MAC, mac);
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the MS/TP max manager object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float mstp_manager(void)
|
||||
{
|
||||
return (float)nvdata_unsigned8(NV_EEPROM_MSTP_MAX_MASTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the present value for the MS/TP max manager object.
|
||||
* @param value - The new value.
|
||||
* @return true if the value was in range and written.
|
||||
*/
|
||||
static bool mstp_manager_write(float value)
|
||||
{
|
||||
bool status = false;
|
||||
uint8_t manager = 0;
|
||||
int32_t value32;
|
||||
|
||||
value32 = (int32_t)value;
|
||||
if ((value32 >= 0) && (value32 <= 127)) {
|
||||
manager = (uint8_t)value32;
|
||||
nvdata_unsigned8_set(NV_EEPROM_MSTP_MAX_MASTER, manager);
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the present value for the Device ID object.
|
||||
* @return The present value.
|
||||
*/
|
||||
static float device_id(void)
|
||||
{
|
||||
return (float)nvdata_unsigned24(NV_EEPROM_DEVICE_0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the present value for the Device ID object.
|
||||
* @param value - The new value.
|
||||
* @return true if the value was in range and written.
|
||||
*/
|
||||
static bool device_id_write(float value)
|
||||
{
|
||||
bool status = false;
|
||||
int32_t value32;
|
||||
|
||||
value32 = (int32_t)value;
|
||||
if ((value32 >= 0) && (value32 <= BACNET_MAX_INSTANCE)) {
|
||||
nvdata_unsigned24_set(NV_EEPROM_DEVICE_0, value32);
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
struct object_data {
|
||||
const uint8_t object_id;
|
||||
const char *object_name;
|
||||
uint16_t units;
|
||||
object_present_value_read_callback read_callback;
|
||||
object_present_value_write_callback write_callback;
|
||||
float present_value;
|
||||
};
|
||||
/* clang-format off */
|
||||
static struct object_data Object_List[] = {
|
||||
/* device ADC inputs */
|
||||
{ 0, "ADC0", UNITS_MILLIVOLTS, adc0_value, NULL, 0.0f },
|
||||
{ 1, "ADC1", UNITS_MILLIVOLTS, adc1_value, NULL, 0.0f },
|
||||
{ 2, "ADC2", UNITS_MILLIVOLTS, adc2_value, NULL, 0.0f },
|
||||
{ 3, "ADC3", UNITS_MILLIVOLTS, adc3_value, NULL, 0.0f },
|
||||
/* device configuration */
|
||||
{ 92, "Device ID", UNITS_NO_UNITS,
|
||||
device_id, device_id_write, 0.0f },
|
||||
{ 93, "MS/TP Baud", UNITS_BITS_PER_SECOND,
|
||||
mstp_baud, mstp_baud_write, 0.0f },
|
||||
{ 94, "MS/TP MAC", UNITS_NO_UNITS,
|
||||
mstp_mac, mstp_mac_write, 0.0f },
|
||||
{ 95, "MS/TP Max Manager", UNITS_NO_UNITS,
|
||||
mstp_manager, mstp_manager_write, 0.0f },
|
||||
/* device status */
|
||||
{ 96, "MCU Frequency", UNITS_HERTZ, NULL, NULL, (float)F_CPU },
|
||||
{ 97, "CStack Size", UNITS_NO_UNITS, stack_size_value, NULL, 0.0f },
|
||||
{ 98, "CStack Unused", UNITS_NO_UNITS, stack_unused_value, NULL, 0.0f },
|
||||
{ 99, "Uptime", UNITS_HOURS, NULL, NULL, 0.0f }
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
/* number of objects */
|
||||
static const unsigned Objects_Max =
|
||||
sizeof(Object_List) / sizeof(Object_List[0]);
|
||||
|
||||
/**
|
||||
* @brief Return the object or NULL if not found.
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return object if the instance is found, and NULL if not
|
||||
*/
|
||||
static struct object_data *Object_List_Element(uint32_t object_instance)
|
||||
{
|
||||
unsigned index;
|
||||
uint32_t object_id;
|
||||
|
||||
for (index = 0; index < Objects_Max; index++) {
|
||||
object_id = Object_List[index].object_id;
|
||||
if (object_id == object_instance) {
|
||||
return &Object_List[index];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines if a given Analog Value instance is valid
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return true if the instance is valid, and false if not
|
||||
*/
|
||||
bool Analog_Value_Valid_Instance(uint32_t object_instance)
|
||||
{
|
||||
if (Object_List_Element(object_instance)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines the number of objects
|
||||
* @return Number of objects
|
||||
*/
|
||||
unsigned Analog_Value_Count(void)
|
||||
{
|
||||
return Objects_Max;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines the object instance-number for a given 0..N index
|
||||
* @param index - 0..N value
|
||||
* @return object instance-number for the given index
|
||||
*/
|
||||
uint32_t Analog_Value_Index_To_Instance(unsigned index)
|
||||
{
|
||||
uint32_t object_instance = UINT32_MAX;
|
||||
|
||||
if (index < Objects_Max) {
|
||||
object_instance = Object_List[index].object_id;
|
||||
}
|
||||
|
||||
return object_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, determines a 0..N index
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return index for the given instance-number, or N if not valid.
|
||||
*/
|
||||
unsigned Analog_Value_Instance_To_Index(uint32_t object_instance)
|
||||
{
|
||||
unsigned index = 0;
|
||||
|
||||
for (index = 0; index < Objects_Max; index++) {
|
||||
if (Object_List[index].object_id == object_instance) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, sets the object-name
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param new_name - holds the object-name to be set
|
||||
* @return true if object-name was set
|
||||
*/
|
||||
bool Analog_Value_Name_Set(uint32_t object_instance, const char *value)
|
||||
{
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
object->object_name = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the object name C string
|
||||
* @param object_instance [in] BACnet object instance number
|
||||
* @return object name or NULL if not found
|
||||
*/
|
||||
const char *Analog_Value_Name_ASCII(uint32_t object_instance)
|
||||
{
|
||||
const char *object_name = "AV-X";
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
object_name = object->object_name;
|
||||
}
|
||||
|
||||
return object_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, determines the present-value
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return present-value of the object
|
||||
*/
|
||||
float Analog_Value_Present_Value(uint32_t object_instance)
|
||||
{
|
||||
float value = 0.0;
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
if (object->read_callback) {
|
||||
value = object->read_callback();
|
||||
} else {
|
||||
value = object->present_value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, sets the present-value
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param value - value to set
|
||||
* @return true if value is within range and present-value is set.
|
||||
*/
|
||||
bool Analog_Value_Present_Value_Set(
|
||||
uint32_t object_instance, float value, uint8_t priority)
|
||||
{
|
||||
bool status = false;
|
||||
struct object_data *object;
|
||||
|
||||
(void)priority;
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
if (object->write_callback) {
|
||||
status = object->write_callback(value);
|
||||
} else {
|
||||
object->present_value = value;
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, determines the units
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return units from the given object, or UNITS_NO_UNITS if not found
|
||||
*/
|
||||
uint16_t Analog_Value_Units(uint32_t object_instance)
|
||||
{
|
||||
uint16_t units = UNITS_NO_UNITS;
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
units = object->units;
|
||||
}
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, sets the units
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param units - property value to set
|
||||
* @return true if valid instance and property was set
|
||||
*/
|
||||
bool Analog_Value_Units_Set(uint32_t object_instance, uint16_t units)
|
||||
{
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
object->units = units;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ReadProperty handler for this object. For the given ReadProperty
|
||||
* data, the application_data is loaded or the error flags are set.
|
||||
* @param rpdata - BACNET_READ_PROPERTY_DATA data, including
|
||||
* requested data and space for the reply, or error response.
|
||||
* @return number of APDU bytes in the response, or
|
||||
* BACNET_STATUS_ERROR on error.
|
||||
*/
|
||||
int Analog_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
int apdu_len = 0; /* return value */
|
||||
BACNET_BIT_STRING bit_string;
|
||||
BACNET_CHARACTER_STRING char_string;
|
||||
uint8_t *apdu;
|
||||
|
||||
apdu = rpdata->application_data;
|
||||
switch (rpdata->object_property) {
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
apdu_len = encode_application_object_id(
|
||||
&apdu[0], OBJECT_ANALOG_VALUE, rpdata->object_instance);
|
||||
break;
|
||||
case PROP_OBJECT_NAME:
|
||||
characterstring_init_ansi(
|
||||
&char_string, Analog_Value_Name_ASCII(rpdata->object_instance));
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_OBJECT_TYPE:
|
||||
apdu_len =
|
||||
encode_application_enumerated(&apdu[0], OBJECT_ANALOG_VALUE);
|
||||
break;
|
||||
case PROP_PRESENT_VALUE:
|
||||
apdu_len = encode_application_real(
|
||||
&apdu[0], Analog_Value_Present_Value(rpdata->object_instance));
|
||||
break;
|
||||
case PROP_STATUS_FLAGS:
|
||||
bitstring_init(&bit_string);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false);
|
||||
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
|
||||
break;
|
||||
case PROP_EVENT_STATE:
|
||||
apdu_len =
|
||||
encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
|
||||
break;
|
||||
case PROP_OUT_OF_SERVICE:
|
||||
apdu_len = encode_application_boolean(&apdu[0], false);
|
||||
break;
|
||||
case PROP_UNITS:
|
||||
apdu_len = encode_application_enumerated(
|
||||
&apdu[0], Analog_Value_Units(rpdata->object_instance));
|
||||
break;
|
||||
default:
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
/* only array properties can have array options */
|
||||
if ((apdu_len >= 0) && (rpdata->array_index != BACNET_ARRAY_ALL)) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief WriteProperty handler for this object. For the given WriteProperty
|
||||
* data, the application_data is loaded or the error flags are set.
|
||||
* @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including
|
||||
* requested data and space for the reply, or error response.
|
||||
* @return false if an error is loaded, true if no errors
|
||||
*/
|
||||
bool Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
int len = 0;
|
||||
BACNET_APPLICATION_DATA_VALUE value;
|
||||
|
||||
if (!Analog_Value_Valid_Instance(wp_data->object_instance)) {
|
||||
wp_data->error_class = ERROR_CLASS_OBJECT;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return false;
|
||||
}
|
||||
/* decode the some of the request */
|
||||
len = bacapp_decode_application_data(
|
||||
wp_data->application_data, wp_data->application_data_len, &value);
|
||||
/* FIXME: len < application_data_len: more data? */
|
||||
if (len < 0) {
|
||||
/* error while decoding - a value larger than we can handle */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
return false;
|
||||
}
|
||||
if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
|
||||
(wp_data->array_index != BACNET_ARRAY_ALL)) {
|
||||
/* only array properties can have array options */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
return false;
|
||||
}
|
||||
switch (wp_data->object_property) {
|
||||
case PROP_PRESENT_VALUE:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_REAL) {
|
||||
status = Analog_Value_Present_Value_Set(
|
||||
wp_data->object_instance, value.type.Real,
|
||||
wp_data->priority);
|
||||
if (!status) {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
|
||||
}
|
||||
break;
|
||||
case PROP_UNITS:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
|
||||
status = Analog_Value_Units_Set(
|
||||
wp_data->object_instance, value.type.Enumerated);
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
|
||||
}
|
||||
break;
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
case PROP_OBJECT_NAME:
|
||||
case PROP_OBJECT_TYPE:
|
||||
case PROP_STATUS_FLAGS:
|
||||
case PROP_EVENT_STATE:
|
||||
case PROP_OUT_OF_SERVICE:
|
||||
case PROP_DESCRIPTION:
|
||||
case PROP_PRIORITY_ARRAY:
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
||||
break;
|
||||
default:
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure some analog pins for ADC operation
|
||||
*/
|
||||
void Analog_Value_Init(void)
|
||||
{
|
||||
adc_enable(0);
|
||||
adc_enable(1);
|
||||
adc_enable(2);
|
||||
adc_enable(3);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @brief This module manages effective C bit manipulation for AVR
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
* @note From AVR035: Efficient C Coding for AVR
|
||||
*/
|
||||
#ifndef AVR035_H
|
||||
#define AVR035_H
|
||||
|
||||
/* a=register, b=bit number to act upon */
|
||||
#define BIT_SET(a, b) ((a) |= (1 << (b)))
|
||||
#define BIT_CLEAR(a, b) ((a) &= ~(1 << (b)))
|
||||
#define BIT_FLIP(a, b) ((a) ^= (1 << (b)))
|
||||
#define BIT_CHECK(a, b) ((a) & (1 << (b)))
|
||||
|
||||
/* x=target variable, y=mask */
|
||||
#define BITMASK_SET(x, y) ((x) |= (y))
|
||||
#define BITMASK_CLEAR(x, y) ((x) &= (~(y)))
|
||||
#define BITMASK_FLIP(x, y) ((x) ^= (y))
|
||||
#define BITMASK_CHECK(x, y) ((x) & (y))
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* @brief This module manages the BACnet Binary Value objects
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "hardware.h"
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacenum.h"
|
||||
#include "bacnet/config.h" /* the custom stuff */
|
||||
#include "bacnet/rp.h"
|
||||
#include "bacnet/wp.h"
|
||||
#include "bacnet/basic/object/bv.h"
|
||||
|
||||
struct object_data {
|
||||
const uint8_t object_id;
|
||||
const char *object_name;
|
||||
volatile uint8_t *port;
|
||||
volatile uint8_t *pin;
|
||||
volatile uint8_t *ddr;
|
||||
const uint8_t bit;
|
||||
};
|
||||
/* clang-format off */
|
||||
static struct object_data Object_List[] = {
|
||||
{ 0, "D3", NULL, &PIND, &DDRD, PD3 },
|
||||
{ 1, "D4", NULL, &PIND, &DDRD, PD4 },
|
||||
{ 2, "D5", NULL, &PIND, &DDRD, PD5 },
|
||||
{ 3, "D6", NULL, &PIND, &DDRD, PD6 },
|
||||
{ 4, "D7", NULL, &PIND, &DDRD, PD7 },
|
||||
{ 5, "D8", &PORTB, &PINB, &DDRB, PB0 },
|
||||
{ 6, "D9", &PORTB, &PINB, &DDRB, PB1 },
|
||||
{ 7, "D10", &PORTB, &PINB, &DDRB, PB2 },
|
||||
{ 8, "D11", &PORTB, &PINB, &DDRB, PB3 },
|
||||
{ 9, "D12", &PORTB, &PINB, &DDRB, PB4 },
|
||||
{ 99, "LED", &PORTB, &PINB, &DDRB, PB5 }
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
/* number of objects */
|
||||
static const unsigned Objects_Max =
|
||||
sizeof(Object_List) / sizeof(Object_List[0]);
|
||||
|
||||
/**
|
||||
* @brief Return the object or NULL if not found.
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return object if the instance is found, and NULL if not
|
||||
*/
|
||||
static struct object_data *Object_List_Element(uint32_t object_instance)
|
||||
{
|
||||
unsigned index;
|
||||
uint32_t object_id;
|
||||
|
||||
for (index = 0; index < Objects_Max; index++) {
|
||||
object_id = Object_List[index].object_id;
|
||||
if (object_id == object_instance) {
|
||||
return &Object_List[index];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines if a given Analog Value instance is valid
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return true if the instance is valid, and false if not
|
||||
*/
|
||||
bool Binary_Value_Valid_Instance(uint32_t object_instance)
|
||||
{
|
||||
if (Object_List_Element(object_instance)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines the number of objects
|
||||
* @return Number of objects
|
||||
*/
|
||||
unsigned Binary_Value_Count(void)
|
||||
{
|
||||
return Objects_Max;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines the object instance-number for a given 0..N index
|
||||
* @param index - 0..N value
|
||||
* @return object instance-number for the given index
|
||||
*/
|
||||
uint32_t Binary_Value_Index_To_Instance(unsigned index)
|
||||
{
|
||||
uint32_t object_instance = UINT32_MAX;
|
||||
|
||||
if (index < Objects_Max) {
|
||||
object_instance = Object_List[index].object_id;
|
||||
}
|
||||
|
||||
return object_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, determines a 0..N index
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return index for the given instance-number, or N if not valid.
|
||||
*/
|
||||
unsigned Binary_Value_Instance_To_Index(uint32_t object_instance)
|
||||
{
|
||||
unsigned index = 0;
|
||||
|
||||
for (index = 0; index < Objects_Max; index++) {
|
||||
if (Object_List[index].object_id == object_instance) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, sets the object-name
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param new_name - holds the object-name to be set
|
||||
* @return true if object-name was set
|
||||
*/
|
||||
bool Binary_Value_Name_Set(uint32_t object_instance, const char *value)
|
||||
{
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
object->object_name = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the object name C string
|
||||
* @param object_instance [in] BACnet object instance number
|
||||
* @return object name or NULL if not found
|
||||
*/
|
||||
const char *Binary_Value_Name_ASCII(uint32_t object_instance)
|
||||
{
|
||||
const char *object_name = "BV-X";
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
object_name = object->object_name;
|
||||
}
|
||||
|
||||
return object_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, determines the present-value
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return present-value of the object
|
||||
*/
|
||||
BACNET_BINARY_PV Binary_Value_Present_Value(uint32_t object_instance)
|
||||
{
|
||||
BACNET_BINARY_PV value = BINARY_INACTIVE;
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
if (BIT_CHECK(*object->pin, object->bit)) {
|
||||
value = BINARY_ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For a given object instance-number, sets the present-value
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param value - value to set
|
||||
* @return true if value is within range and present-value is set.
|
||||
*/
|
||||
bool Binary_Value_Present_Value_Set(
|
||||
uint32_t object_instance, BACNET_BINARY_PV value)
|
||||
{
|
||||
struct object_data *object;
|
||||
|
||||
object = Object_List_Element(object_instance);
|
||||
if (object) {
|
||||
if (object->port) {
|
||||
if (value == BINARY_ACTIVE) {
|
||||
BIT_SET(*object->port, object->bit);
|
||||
} else {
|
||||
BIT_CLEAR(*object->port, object->bit);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ReadProperty handler for this object. For the given ReadProperty
|
||||
* data, the application_data is loaded or the error flags are set.
|
||||
* @param rpdata - BACNET_READ_PROPERTY_DATA data, including
|
||||
* requested data and space for the reply, or error response.
|
||||
* @return number of APDU bytes in the response, or
|
||||
* BACNET_STATUS_ERROR on error.
|
||||
*/
|
||||
int Binary_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
int apdu_len = 0; /* return value */
|
||||
BACNET_BIT_STRING bit_string;
|
||||
BACNET_CHARACTER_STRING char_string;
|
||||
BACNET_BINARY_PV present_value = BINARY_INACTIVE;
|
||||
BACNET_POLARITY polarity = POLARITY_NORMAL;
|
||||
uint8_t *apdu;
|
||||
|
||||
apdu = rpdata->application_data;
|
||||
switch (rpdata->object_property) {
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
apdu_len = encode_application_object_id(
|
||||
&apdu[0], OBJECT_BINARY_VALUE, rpdata->object_instance);
|
||||
break;
|
||||
/* note: Name and Description don't have to be the same.
|
||||
You could make Description writable and different */
|
||||
case PROP_OBJECT_NAME:
|
||||
characterstring_init_ansi(
|
||||
&char_string, Binary_Value_Name_ASCII(rpdata->object_instance));
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_OBJECT_TYPE:
|
||||
apdu_len =
|
||||
encode_application_enumerated(&apdu[0], OBJECT_BINARY_VALUE);
|
||||
break;
|
||||
case PROP_PRESENT_VALUE:
|
||||
present_value = Binary_Value_Present_Value(rpdata->object_instance);
|
||||
apdu_len = encode_application_enumerated(&apdu[0], present_value);
|
||||
break;
|
||||
case PROP_STATUS_FLAGS:
|
||||
/* note: see the details in the standard on how to use these */
|
||||
bitstring_init(&bit_string);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false);
|
||||
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
|
||||
break;
|
||||
case PROP_EVENT_STATE:
|
||||
/* note: see the details in the standard on how to use this */
|
||||
apdu_len =
|
||||
encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
|
||||
break;
|
||||
case PROP_OUT_OF_SERVICE:
|
||||
apdu_len = encode_application_boolean(&apdu[0], false);
|
||||
break;
|
||||
case PROP_POLARITY:
|
||||
/* FIXME: figure out the polarity */
|
||||
apdu_len = encode_application_enumerated(&apdu[0], polarity);
|
||||
break;
|
||||
default:
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
/* only array properties can have array options */
|
||||
if ((apdu_len >= 0) && (rpdata->array_index != BACNET_ARRAY_ALL)) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* returns true if successful */
|
||||
bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
int len = 0;
|
||||
BACNET_APPLICATION_DATA_VALUE value;
|
||||
|
||||
if (!Binary_Value_Valid_Instance(wp_data->object_instance)) {
|
||||
wp_data->error_class = ERROR_CLASS_OBJECT;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return false;
|
||||
}
|
||||
/* decode the some of the request */
|
||||
len = bacapp_decode_application_data(
|
||||
wp_data->application_data, wp_data->application_data_len, &value);
|
||||
/* FIXME: len < application_data_len: more data? */
|
||||
if (len < 0) {
|
||||
/* error while decoding - a value larger than we can handle */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
return false;
|
||||
}
|
||||
if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
|
||||
(wp_data->array_index != BACNET_ARRAY_ALL)) {
|
||||
/* only array properties can have array options */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
return false;
|
||||
}
|
||||
switch (wp_data->object_property) {
|
||||
case PROP_PRESENT_VALUE:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
|
||||
if ((value.type.Enumerated == BINARY_ACTIVE) ||
|
||||
(value.type.Enumerated == BINARY_INACTIVE)) {
|
||||
status = Binary_Value_Present_Value_Set(
|
||||
wp_data->object_instance,
|
||||
(BACNET_BINARY_PV)value.type.Enumerated);
|
||||
if (!status) {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
||||
}
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
|
||||
}
|
||||
break;
|
||||
case PROP_OUT_OF_SERVICE:
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
case PROP_OBJECT_NAME:
|
||||
case PROP_OBJECT_TYPE:
|
||||
case PROP_STATUS_FLAGS:
|
||||
case PROP_EVENT_STATE:
|
||||
case PROP_POLARITY:
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
||||
break;
|
||||
default:
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure some digital pins as inputs or outputs
|
||||
*/
|
||||
void Binary_Value_Init(void)
|
||||
{
|
||||
unsigned index = 0;
|
||||
|
||||
for (index = 0; index < Objects_Max; index++) {
|
||||
if (Object_List[index].port) {
|
||||
/* Configure the pin as an output */
|
||||
BIT_CLEAR(*Object_List[index].port, Object_List[index].bit);
|
||||
BIT_SET(*Object_List[index].ddr, Object_List[index].bit);
|
||||
/* Turn off the pin */
|
||||
BIT_CLEAR(*Object_List[index].port, Object_List[index].bit);
|
||||
} else {
|
||||
/* Configure the pin as an input */
|
||||
BIT_CLEAR(*Object_List[index].ddr, Object_List[index].bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
/**
|
||||
* @brief This module manages the BACnet Device object
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
/* stack */
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacstr.h"
|
||||
#include "bacnet/bacenum.h"
|
||||
#include "bacnet/apdu.h"
|
||||
#include "bacnet/dcc.h"
|
||||
#include "bacnet/datalink/dlmstp.h"
|
||||
#include "bacnet/version.h"
|
||||
/* platform */
|
||||
#include "rs485.h"
|
||||
#include "nvdata.h"
|
||||
/* objects */
|
||||
#include "bacnet/basic/services.h"
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include "bacnet/basic/object/av.h"
|
||||
#include "bacnet/basic/object/bv.h"
|
||||
#include "bacnet/wp.h"
|
||||
|
||||
/* note: you really only need to define variables for
|
||||
properties that are writable or that may change.
|
||||
The properties that are constant can be hard coded
|
||||
into the read-property encoding. */
|
||||
static uint32_t Object_Instance_Number = 260001;
|
||||
static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL;
|
||||
static const char *Model_Name = "ATmega328 Uno R3 Device";
|
||||
|
||||
/* methods to manipulate the data */
|
||||
uint32_t Device_Object_Instance_Number(void)
|
||||
{
|
||||
return Object_Instance_Number;
|
||||
}
|
||||
|
||||
bool Device_Set_Object_Instance_Number(uint32_t object_id)
|
||||
{
|
||||
bool status = true; /* return value */
|
||||
|
||||
if (object_id <= BACNET_MAX_INSTANCE) {
|
||||
Object_Instance_Number = object_id;
|
||||
/* FIXME: Write the data to the eeprom */
|
||||
/* I2C_Write_Block(
|
||||
EEPROM_DEVICE_ADDRESS,
|
||||
(char *)&Object_Instance_Number,
|
||||
sizeof(Object_Instance_Number),
|
||||
EEPROM_BACNET_ID_ADDR); */
|
||||
} else {
|
||||
status = false;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool Device_Valid_Object_Instance_Number(uint32_t object_id)
|
||||
{
|
||||
/* BACnet allows for a wildcard instance number */
|
||||
return (Object_Instance_Number == object_id);
|
||||
}
|
||||
|
||||
uint16_t Device_Vendor_Identifier(void)
|
||||
{
|
||||
return BACNET_VENDOR_ID;
|
||||
}
|
||||
|
||||
unsigned Device_Object_List_Count(void)
|
||||
{
|
||||
unsigned count = 1; /* at least 1 for device object */
|
||||
|
||||
/* FIXME: add objects as needed */
|
||||
count += Analog_Value_Count();
|
||||
count += Binary_Value_Count();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool Device_Object_List_Identifier(
|
||||
uint32_t array_index, BACNET_OBJECT_TYPE *object_type, uint32_t *instance)
|
||||
{
|
||||
bool status = false;
|
||||
uint32_t object_index = 0;
|
||||
uint32_t object_count = 0;
|
||||
|
||||
/* device object */
|
||||
if (array_index == 1) {
|
||||
*object_type = OBJECT_DEVICE;
|
||||
*instance = Object_Instance_Number;
|
||||
status = true;
|
||||
}
|
||||
/* normalize the index since
|
||||
we know it is not the previous objects */
|
||||
/* array index starts at 1 */
|
||||
object_index = array_index - 1;
|
||||
/* 1 for the device object */
|
||||
object_count = 1;
|
||||
/* FIXME: add objects as needed */
|
||||
/* analog value objects */
|
||||
if (!status) {
|
||||
/* array index starts at 1, and 1 for the device object */
|
||||
object_index -= object_count;
|
||||
object_count = Analog_Value_Count();
|
||||
if (object_index < object_count) {
|
||||
*object_type = OBJECT_ANALOG_VALUE;
|
||||
*instance = Analog_Value_Index_To_Instance(object_index);
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
/* binary value objects */
|
||||
if (!status) {
|
||||
object_index -= object_count;
|
||||
object_count = Binary_Value_Count();
|
||||
/* is it a valid index for this object? */
|
||||
if (object_index < object_count) {
|
||||
*object_type = OBJECT_BINARY_VALUE;
|
||||
*instance = Binary_Value_Index_To_Instance(object_index);
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encode a BACnetARRAY property element
|
||||
* @param object_instance [in] BACnet network port object instance number
|
||||
* @param array_index [in] array index requested:
|
||||
* 0 to N for individual array members
|
||||
* @param apdu [out] Buffer in which the APDU contents are built, or NULL to
|
||||
* return the length of buffer if it had been built
|
||||
* @return The length of the apdu encoded or
|
||||
* BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX
|
||||
*/
|
||||
int Device_Object_List_Element_Encode(
|
||||
uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu)
|
||||
{
|
||||
int apdu_len = BACNET_STATUS_ERROR;
|
||||
BACNET_OBJECT_TYPE object_type;
|
||||
uint32_t instance;
|
||||
bool found;
|
||||
|
||||
if (object_instance == Device_Object_Instance_Number()) {
|
||||
/* single element is zero based, add 1 for BACnetARRAY which is one
|
||||
* based */
|
||||
array_index++;
|
||||
found =
|
||||
Device_Object_List_Identifier(array_index, &object_type, &instance);
|
||||
if (found) {
|
||||
apdu_len =
|
||||
encode_application_object_id(apdu, object_type, instance);
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* return the length of the apdu encoded or -1 for error */
|
||||
int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
uint8_t *apdu;
|
||||
int apdu_len = 0; /* return value */
|
||||
int apdu_max = 0;
|
||||
BACNET_BIT_STRING bit_string;
|
||||
BACNET_CHARACTER_STRING char_string;
|
||||
uint32_t i = 0;
|
||||
uint32_t count = 0;
|
||||
|
||||
apdu = rpdata->application_data;
|
||||
apdu_max = rpdata->application_data_len;
|
||||
switch (rpdata->object_property) {
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
apdu_len = encode_application_object_id(
|
||||
&apdu[0], OBJECT_DEVICE, Object_Instance_Number);
|
||||
break;
|
||||
case PROP_OBJECT_NAME:
|
||||
nvdata_name(NV_EEPROM_DEVICE_NAME, &char_string, "BACnet Device");
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_DESCRIPTION:
|
||||
nvdata_name(
|
||||
NV_EEPROM_DEVICE_DESCRIPTION, &char_string, "Description");
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_LOCATION:
|
||||
nvdata_name(NV_EEPROM_DEVICE_LOCATION, &char_string, "Location");
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_OBJECT_TYPE:
|
||||
apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE);
|
||||
break;
|
||||
case PROP_SYSTEM_STATUS:
|
||||
apdu_len = encode_application_enumerated(&apdu[0], System_Status);
|
||||
break;
|
||||
case PROP_VENDOR_NAME:
|
||||
characterstring_init_ansi(&char_string, BACNET_VENDOR_NAME);
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_VENDOR_IDENTIFIER:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Vendor_Identifier());
|
||||
break;
|
||||
case PROP_MODEL_NAME:
|
||||
characterstring_init_ansi(&char_string, Model_Name);
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_FIRMWARE_REVISION:
|
||||
characterstring_init_ansi(&char_string, BACNET_VERSION_TEXT);
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_APPLICATION_SOFTWARE_VERSION:
|
||||
characterstring_init_ansi(&char_string, "1.0");
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_PROTOCOL_VERSION:
|
||||
apdu_len =
|
||||
encode_application_unsigned(&apdu[0], BACNET_PROTOCOL_VERSION);
|
||||
break;
|
||||
case PROP_PROTOCOL_REVISION:
|
||||
apdu_len =
|
||||
encode_application_unsigned(&apdu[0], BACNET_PROTOCOL_REVISION);
|
||||
break;
|
||||
case PROP_PROTOCOL_SERVICES_SUPPORTED:
|
||||
/* Note: list of services that are executed, not initiated. */
|
||||
bitstring_init(&bit_string);
|
||||
for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) {
|
||||
/* automatic lookup based on handlers set */
|
||||
bitstring_set_bit(
|
||||
&bit_string, (uint8_t)i,
|
||||
apdu_service_supported((BACNET_SERVICES_SUPPORTED)i));
|
||||
}
|
||||
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
|
||||
break;
|
||||
case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
|
||||
/* Note: this is the list of objects that can be in this device,
|
||||
not a list of objects that this device can access */
|
||||
bitstring_init(&bit_string);
|
||||
/* must have the bit string as big as it can be */
|
||||
for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) {
|
||||
/* initialize all the object types to not-supported */
|
||||
bitstring_set_bit(&bit_string, (uint8_t)i, false);
|
||||
}
|
||||
/* FIXME: indicate the objects that YOU support */
|
||||
bitstring_set_bit(&bit_string, OBJECT_DEVICE, true);
|
||||
bitstring_set_bit(&bit_string, OBJECT_ANALOG_VALUE, true);
|
||||
bitstring_set_bit(&bit_string, OBJECT_BINARY_VALUE, true);
|
||||
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
|
||||
break;
|
||||
case PROP_OBJECT_LIST:
|
||||
count = Device_Object_List_Count();
|
||||
apdu_len = bacnet_array_encode(
|
||||
rpdata->object_instance, rpdata->array_index,
|
||||
Device_Object_List_Element_Encode, count, apdu, apdu_max);
|
||||
if (apdu_len == BACNET_STATUS_ABORT) {
|
||||
rpdata->error_code =
|
||||
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
|
||||
} else if (apdu_len == BACNET_STATUS_ERROR) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
|
||||
}
|
||||
break;
|
||||
case PROP_MAX_APDU_LENGTH_ACCEPTED:
|
||||
apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU);
|
||||
break;
|
||||
case PROP_SEGMENTATION_SUPPORTED:
|
||||
apdu_len =
|
||||
encode_application_enumerated(&apdu[0], SEGMENTATION_NONE);
|
||||
break;
|
||||
case PROP_APDU_TIMEOUT:
|
||||
apdu_len = encode_application_unsigned(&apdu[0], 60000);
|
||||
break;
|
||||
case PROP_NUMBER_OF_APDU_RETRIES:
|
||||
apdu_len = encode_application_unsigned(&apdu[0], 0);
|
||||
break;
|
||||
case PROP_DEVICE_ADDRESS_BINDING:
|
||||
/* FIXME: encode the list here, if it exists */
|
||||
break;
|
||||
case PROP_DATABASE_REVISION:
|
||||
apdu_len = encode_application_unsigned(&apdu[0], 0);
|
||||
break;
|
||||
case PROP_MAX_INFO_FRAMES:
|
||||
apdu_len =
|
||||
encode_application_unsigned(&apdu[0], dlmstp_max_info_frames());
|
||||
break;
|
||||
case PROP_MAX_MASTER:
|
||||
apdu_len =
|
||||
encode_application_unsigned(&apdu[0], dlmstp_max_master());
|
||||
break;
|
||||
case (BACNET_PROPERTY_ID)9600:
|
||||
apdu_len =
|
||||
encode_application_unsigned(&apdu[0], RS485_Get_Baud_Rate());
|
||||
break;
|
||||
default:
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
||||
apdu_len = -1;
|
||||
break;
|
||||
}
|
||||
/* only array properties can have array options */
|
||||
if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) &&
|
||||
(rpdata->array_index != BACNET_ARRAY_ALL)) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
int len = 0;
|
||||
BACNET_APPLICATION_DATA_VALUE value;
|
||||
size_t name_length;
|
||||
|
||||
if (!Device_Valid_Object_Instance_Number(wp_data->object_instance)) {
|
||||
wp_data->error_class = ERROR_CLASS_OBJECT;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return false;
|
||||
}
|
||||
/* decode the some of the request */
|
||||
len = bacapp_decode_application_data(
|
||||
wp_data->application_data, wp_data->application_data_len, &value);
|
||||
/* FIXME: len < application_data_len: more data? */
|
||||
if (len < 0) {
|
||||
/* error while decoding - a value larger than we can handle */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
return false;
|
||||
}
|
||||
if ((wp_data->object_property != PROP_OBJECT_LIST) &&
|
||||
(wp_data->array_index != BACNET_ARRAY_ALL)) {
|
||||
/* only array properties can have array options */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
return false;
|
||||
}
|
||||
switch (wp_data->object_property) {
|
||||
case PROP_OBJECT_NAME:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) {
|
||||
name_length =
|
||||
characterstring_length(&value.type.Character_String);
|
||||
if (name_length == 0) {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
} else {
|
||||
status = nvdata_name_write(
|
||||
NV_EEPROM_DEVICE_NAME, &value.type.Character_String,
|
||||
&wp_data->error_class, &wp_data->error_code);
|
||||
}
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
|
||||
}
|
||||
break;
|
||||
case PROP_DESCRIPTION:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) {
|
||||
name_length =
|
||||
characterstring_length(&value.type.Character_String);
|
||||
if (name_length == 0) {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
} else {
|
||||
status = nvdata_name_write(
|
||||
NV_EEPROM_DEVICE_DESCRIPTION,
|
||||
&value.type.Character_String, &wp_data->error_class,
|
||||
&wp_data->error_code);
|
||||
}
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
|
||||
}
|
||||
break;
|
||||
case PROP_LOCATION:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) {
|
||||
name_length =
|
||||
characterstring_length(&value.type.Character_String);
|
||||
if (name_length == 0) {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
} else {
|
||||
status = nvdata_name_write(
|
||||
NV_EEPROM_DEVICE_LOCATION, &value.type.Character_String,
|
||||
&wp_data->error_class, &wp_data->error_code);
|
||||
}
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
|
||||
}
|
||||
break;
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
case PROP_MAX_INFO_FRAMES:
|
||||
case PROP_MAX_MASTER:
|
||||
case PROP_NUMBER_OF_APDU_RETRIES:
|
||||
case PROP_APDU_TIMEOUT:
|
||||
case PROP_VENDOR_IDENTIFIER:
|
||||
case PROP_SYSTEM_STATUS:
|
||||
case PROP_MODEL_NAME:
|
||||
case PROP_VENDOR_NAME:
|
||||
case PROP_FIRMWARE_REVISION:
|
||||
case PROP_APPLICATION_SOFTWARE_VERSION:
|
||||
case PROP_LOCAL_TIME:
|
||||
case PROP_UTC_OFFSET:
|
||||
case PROP_LOCAL_DATE:
|
||||
case PROP_DAYLIGHT_SAVINGS_STATUS:
|
||||
case PROP_PROTOCOL_VERSION:
|
||||
case PROP_PROTOCOL_REVISION:
|
||||
case PROP_PROTOCOL_SERVICES_SUPPORTED:
|
||||
case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
|
||||
case PROP_OBJECT_LIST:
|
||||
case PROP_MAX_APDU_LENGTH_ACCEPTED:
|
||||
case PROP_SEGMENTATION_SUPPORTED:
|
||||
case PROP_DEVICE_ADDRESS_BINDING:
|
||||
case PROP_DATABASE_REVISION:
|
||||
case PROP_ACTIVE_COV_SUBSCRIPTIONS:
|
||||
case 9600:
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
||||
break;
|
||||
default:
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
# Introduction
|
||||
|
||||
This is an Arduino RS485 shield, especially designed for the Arduino
|
||||
controller board. It can easily convert the UART to the RS485.
|
||||
This shield integrates:
|
||||
|
||||
* a standard RS485 port
|
||||
* a mini RS485 port (PH2.0 interface)
|
||||
* RS485 headers
|
||||
* provides the prototyping areas
|
||||
* configurable automatic or manual transmission (RTS) mode
|
||||
* configurable operation or programming mode
|
||||
|
||||
# Specification
|
||||
|
||||
* Power module: +5.0V
|
||||
* Module size: 55mmx53mm
|
||||
* 16 digital IO port (including a I2C interface)
|
||||
* 6 analog IO port and power
|
||||
* Operation/programming mode switch
|
||||
* Automatic/manual transceiver mode switch (When in manual transceiver mode, the enable end is the 2nd digital IO port)
|
||||
* Transceiver indicating LED
|
||||
* Standard RS485 interface, mini RS485 interface(PH2.0) and RS485 pins
|
||||
* prototyping area
|
||||
* Reset button
|
||||
|
||||
# Pin Definition
|
||||
|
||||

|
||||
|
||||
## Operation and programming mode switch:
|
||||
|
||||
* To OFF, you can download the program for the Arduino controller.
|
||||
* To ON, the shield will convert the UART to the RS485.
|
||||
|
||||
## Transceiver mode switch:
|
||||
|
||||
* To AUTO, the shield is automatically sending and receiving data so you
|
||||
do not care about operation timing
|
||||
* To MANU, the shield is in manual transceiver status, and the enable end
|
||||
is the 2nd digital IO port. Enabling high-side power Ping to sending
|
||||
state while the low level of the receiving state.
|
||||
|
||||
# Usage Setup
|
||||
|
||||
When the RS485 shield is installed on your Arduino controller,
|
||||
and then you will wire the RS485 shiled with USB-RS485 converter
|
||||
to connect terminals, namely: A pair of A, B on B, GND on GND,
|
||||
then download the following sample code.
|
||||
|
||||
Note: Downloading code, run / program mode switch must hit the OFF position,
|
||||
otherwise it will lead to the download fails. When downloaded, it is complete
|
||||
to ON, and the shield to normal use).
|
||||
|
||||
# Schematic
|
||||
|
||||

|
||||
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1020 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 175 KiB |
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @brief This module manages the internal AVR non-volatile data storage
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "hardware.h"
|
||||
#include "eeprom.h"
|
||||
|
||||
/**
|
||||
* @brief Read a block of bytes from the EEPROM
|
||||
* @param eeaddr - EEPROM starting memory address (offset of zero)
|
||||
* @param buf - where to store the data that is read
|
||||
* @param len - number of bytes of data to read
|
||||
* @return The number of bytes read
|
||||
*/
|
||||
int eeprom_bytes_read(uint16_t eeaddr, uint8_t *buf, int len)
|
||||
{
|
||||
int count = 0; /* return value */
|
||||
|
||||
while (len) {
|
||||
__EEGET(buf[count], eeaddr);
|
||||
count++;
|
||||
eeaddr++;
|
||||
len--;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a block of bytes to the EEPROM
|
||||
* @param eeaddr - EEPROM starting memory address (offset of zero)
|
||||
* @param buf - data to write to the EEPROM
|
||||
* @param len - number of bytes of data
|
||||
* @return The number of bytes written
|
||||
*/
|
||||
int eeprom_bytes_write(uint16_t eeaddr, const uint8_t *buf, int len)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while (len) {
|
||||
__EEPUT(eeaddr, buf[count]);
|
||||
count++;
|
||||
eeaddr++;
|
||||
len--;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @brief This module contains the API for internal AVR non-volatile data
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef EEPROM_H
|
||||
#define EEPROM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef EEPROM_BYTES_MAX
|
||||
#define EEPROM_BYTES_MAX (1 * 1024)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
int eeprom_bytes_read(
|
||||
uint16_t ee_address, /* EEPROM starting memory address */
|
||||
uint8_t *buffer, /* data to store */
|
||||
int nbytes); /* number of bytes of data to read */
|
||||
int eeprom_bytes_write(
|
||||
uint16_t ee_address, /* EEPROM starting memory address */
|
||||
const uint8_t *buffer, /* data to send */
|
||||
int nbytes); /* number of bytes of data */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @brief This module manages the BACnet ReadProperty handler
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2005
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "bacnet/config.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
#include "bacnet/datalink/datalink.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacerror.h"
|
||||
#include "bacnet/apdu.h"
|
||||
#include "bacnet/npdu.h"
|
||||
#include "bacnet/abort.h"
|
||||
#include "bacnet/rp.h"
|
||||
/* demo objects */
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include "bacnet/basic/object/av.h"
|
||||
#include "bacnet/basic/object/bv.h"
|
||||
|
||||
/* Encodes the property APDU and returns the length,
|
||||
or sets the error, and returns -1 */
|
||||
static int Encode_Property_APDU(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
int apdu_len = -1;
|
||||
|
||||
/* handle each object type */
|
||||
switch (rpdata->object_type) {
|
||||
case OBJECT_DEVICE:
|
||||
/* Test for case of indefinite Device object instance */
|
||||
if (rpdata->object_instance == BACNET_MAX_INSTANCE) {
|
||||
rpdata->object_instance = Device_Object_Instance_Number();
|
||||
}
|
||||
if (Device_Valid_Object_Instance_Number(rpdata->object_instance)) {
|
||||
apdu_len = Device_Read_Property(rpdata);
|
||||
}
|
||||
break;
|
||||
case OBJECT_ANALOG_VALUE:
|
||||
if (Analog_Value_Valid_Instance(rpdata->object_instance)) {
|
||||
apdu_len = Analog_Value_Read_Property(rpdata);
|
||||
}
|
||||
break;
|
||||
case OBJECT_BINARY_VALUE:
|
||||
if (Binary_Value_Valid_Instance(rpdata->object_instance)) {
|
||||
apdu_len = Binary_Value_Read_Property(rpdata);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
rpdata->error_class = ERROR_CLASS_OBJECT;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
break;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
void handler_read_property(
|
||||
uint8_t *service_request,
|
||||
uint16_t service_len,
|
||||
BACNET_ADDRESS *src,
|
||||
BACNET_CONFIRMED_SERVICE_DATA *service_data)
|
||||
{
|
||||
BACNET_READ_PROPERTY_DATA data;
|
||||
int len = 0;
|
||||
int ack_len = 0;
|
||||
int property_len = 0;
|
||||
int pdu_len = 0;
|
||||
int ack_end_len = 0;
|
||||
BACNET_NPDU_DATA npdu_data;
|
||||
int bytes_sent = 0;
|
||||
BACNET_ADDRESS my_address;
|
||||
|
||||
/* encode the NPDU portion of the packet */
|
||||
datalink_get_my_address(&my_address);
|
||||
npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL);
|
||||
pdu_len = npdu_encode_pdu(
|
||||
&Handler_Transmit_Buffer[0], src, &my_address, &npdu_data);
|
||||
if (service_data->segmented_message) {
|
||||
/* we don't support segmentation - send an abort */
|
||||
len = abort_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
|
||||
ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true);
|
||||
goto RP_ABORT;
|
||||
}
|
||||
len = rp_decode_service_request(service_request, service_len, &data);
|
||||
if (len < 0) {
|
||||
/* bad decoding - send an abort */
|
||||
len = abort_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
|
||||
ABORT_REASON_OTHER, true);
|
||||
goto RP_ABORT;
|
||||
}
|
||||
/* default case will be error */
|
||||
ack_len = rp_ack_encode_apdu_init(
|
||||
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id, &data);
|
||||
ack_end_len = rp_ack_encode_apdu_object_property_end(NULL);
|
||||
data.application_data = &Handler_Transmit_Buffer[pdu_len + ack_len];
|
||||
data.application_data_len =
|
||||
sizeof(Handler_Transmit_Buffer) - (pdu_len + ack_len + ack_end_len);
|
||||
data.error_class = ERROR_CLASS_OBJECT;
|
||||
data.error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
property_len = Encode_Property_APDU(&data);
|
||||
if (property_len >= 0) {
|
||||
len = rp_ack_encode_apdu_object_property_end(
|
||||
&Handler_Transmit_Buffer[pdu_len + property_len + ack_len]);
|
||||
len += ack_len + property_len;
|
||||
} else if (property_len == BACNET_STATUS_ABORT) {
|
||||
/* BACnet APDU too small to fit data,
|
||||
so proper response is Abort */
|
||||
len = abort_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
|
||||
ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true);
|
||||
} else {
|
||||
len = bacerror_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
|
||||
SERVICE_CONFIRMED_READ_PROPERTY, data.error_class, data.error_code);
|
||||
}
|
||||
RP_ABORT:
|
||||
pdu_len += len;
|
||||
bytes_sent = datalink_send_pdu(
|
||||
src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len);
|
||||
(void)bytes_sent;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @brief This module manages the BACnet Who-Is handler
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2005
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "bacnet/config.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/whois.h"
|
||||
#include "bacnet/iam.h"
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
|
||||
bool Send_I_Am_Flag = true;
|
||||
|
||||
void handler_who_is(
|
||||
uint8_t *service_request, uint16_t service_len, BACNET_ADDRESS *src)
|
||||
{
|
||||
int len = 0;
|
||||
int32_t low_limit = 0;
|
||||
int32_t high_limit = 0;
|
||||
int32_t target_device;
|
||||
|
||||
(void)src;
|
||||
len = whois_decode_service_request(
|
||||
service_request, service_len, &low_limit, &high_limit);
|
||||
if (len == 0) {
|
||||
Send_I_Am_Flag = true;
|
||||
} else if (len != BACNET_STATUS_ERROR) {
|
||||
/* is my device id within the limits? */
|
||||
target_device = Device_Object_Instance_Number();
|
||||
if ((target_device >= low_limit) && (target_device <= high_limit)) {
|
||||
Send_I_Am_Flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @brief This module manages the BACnet WriteProperty handler
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2005
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "bacnet/config.h"
|
||||
#include "bacnet/datalink/datalink.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacerror.h"
|
||||
#include "bacnet/apdu.h"
|
||||
#include "bacnet/npdu.h"
|
||||
#include "bacnet/abort.h"
|
||||
#include "bacnet/wp.h"
|
||||
/* demo objects */
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include "bacnet/basic/object/av.h"
|
||||
#include "bacnet/basic/object/bv.h"
|
||||
|
||||
/* too big to reside on stack frame for PIC */
|
||||
static BACNET_WRITE_PROPERTY_DATA wp_data;
|
||||
|
||||
void handler_write_property(
|
||||
uint8_t *service_request,
|
||||
uint16_t service_len,
|
||||
BACNET_ADDRESS *src,
|
||||
BACNET_CONFIRMED_SERVICE_DATA *service_data)
|
||||
{
|
||||
int len = 0;
|
||||
int pdu_len = 0;
|
||||
BACNET_NPDU_DATA npdu_data;
|
||||
int bytes_sent = 0;
|
||||
BACNET_ADDRESS my_address;
|
||||
|
||||
/* decode the service request only */
|
||||
len = wp_decode_service_request(service_request, service_len, &wp_data);
|
||||
/* encode the NPDU portion of the packet */
|
||||
datalink_get_my_address(&my_address);
|
||||
npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL);
|
||||
pdu_len = npdu_encode_pdu(
|
||||
&Handler_Transmit_Buffer[0], src, &my_address, &npdu_data);
|
||||
/* bad decoding or something we didn't understand - send an abort */
|
||||
if (len <= 0) {
|
||||
len = abort_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
|
||||
ABORT_REASON_OTHER, true);
|
||||
} else if (service_data->segmented_message) {
|
||||
len = abort_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
|
||||
ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true);
|
||||
} else {
|
||||
wp_data.error_class = ERROR_CLASS_OBJECT;
|
||||
wp_data.error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
switch (wp_data.object_type) {
|
||||
case OBJECT_DEVICE:
|
||||
if (Device_Write_Property(&wp_data)) {
|
||||
len = encode_simple_ack(
|
||||
&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id,
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY);
|
||||
} else {
|
||||
len = bacerror_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id,
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY, wp_data.error_class,
|
||||
wp_data.error_code);
|
||||
}
|
||||
break;
|
||||
case OBJECT_ANALOG_VALUE:
|
||||
if (Analog_Value_Write_Property(&wp_data)) {
|
||||
len = encode_simple_ack(
|
||||
&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id,
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY);
|
||||
} else {
|
||||
len = bacerror_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id,
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY, wp_data.error_class,
|
||||
wp_data.error_code);
|
||||
}
|
||||
break;
|
||||
case OBJECT_BINARY_VALUE:
|
||||
if (Binary_Value_Write_Property(&wp_data)) {
|
||||
len = encode_simple_ack(
|
||||
&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id,
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY);
|
||||
} else {
|
||||
len = bacerror_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id,
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY, wp_data.error_class,
|
||||
wp_data.error_code);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
len = bacerror_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY, wp_data.error_class,
|
||||
wp_data.error_code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pdu_len += len;
|
||||
bytes_sent = datalink_send_pdu(
|
||||
src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len);
|
||||
(void)bytes_sent;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @brief This module manages the project hardware defines
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef HARDWARE_H
|
||||
#define HARDWARE_H
|
||||
|
||||
#if !defined(F_CPU)
|
||||
#error Set F_CPU in the Makefile
|
||||
#endif
|
||||
|
||||
/* IAR compiler specific configuration */
|
||||
#if defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ASM__)
|
||||
#include <ioavr.h>
|
||||
#include <iom328p.h>
|
||||
#endif
|
||||
|
||||
/* AVR-GCC compiler specific configuration */
|
||||
#if defined(__GNUC__)
|
||||
#include <avr/io.h>
|
||||
#include <avr/wdt.h>
|
||||
#include <avr/power.h>
|
||||
#endif
|
||||
|
||||
#if !defined(__AVR_ATmega328P__)
|
||||
#error Firmware is configured for ATmega328P only (-mmcu=atmega328p)
|
||||
#endif
|
||||
|
||||
#include "iar2gcc.h"
|
||||
#include "bacnet/basic/sys/bits.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* @brief This module manages the common IAR and GCC compiler differences
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef IAR2GCC_H
|
||||
#define IAR2GCC_H
|
||||
|
||||
#if !defined(F_CPU)
|
||||
#error You must define F_CPU - clock frequency!
|
||||
#endif
|
||||
/* IAR */
|
||||
#if defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ASM__)
|
||||
#include <inavr.h>
|
||||
#include <ioavr.h>
|
||||
#include <intrinsics.h>
|
||||
#include <stdint.h>
|
||||
/* BitValue is used alot in GCC examples */
|
||||
#ifndef _BV
|
||||
#define _BV(bit_num) (1 << (bit_num))
|
||||
#endif
|
||||
|
||||
/* inline function */
|
||||
static inline void _delay_us(uint8_t microseconds)
|
||||
{
|
||||
do {
|
||||
__delay_cycles(F_CPU / 1000000UL);
|
||||
} while (microseconds--);
|
||||
}
|
||||
|
||||
#if (__VER__ > 700)
|
||||
#define DDA0 DDRA0
|
||||
#define DDA1 DDRA1
|
||||
#define DDA2 DDRA2
|
||||
#define DDA3 DDRA3
|
||||
#define DDA4 DDRA4
|
||||
#define DDA5 DDRA5
|
||||
#define DDA6 DDRA6
|
||||
#define DDA7 DDRA7
|
||||
|
||||
#define DDB0 DDRB0
|
||||
#define DDB1 DDRB1
|
||||
#define DDB2 DDRB2
|
||||
#define DDB3 DDRB3
|
||||
#define DDB4 DDRB4
|
||||
#define DDB5 DDRB5
|
||||
#define DDB6 DDRB6
|
||||
#define DDB7 DDRB7
|
||||
|
||||
#define DDC0 DDRC0
|
||||
#define DDC1 DDRC1
|
||||
#define DDC2 DDRC2
|
||||
#define DDC3 DDRC3
|
||||
#define DDC4 DDRC4
|
||||
#define DDC5 DDRC5
|
||||
#define DDC6 DDRC6
|
||||
#define DDC7 DDRC7
|
||||
|
||||
#define DDD0 DDRD0
|
||||
#define DDD1 DDRD1
|
||||
#define DDD2 DDRD2
|
||||
#define DDD3 DDRD3
|
||||
#define DDD4 DDRD4
|
||||
#define DDD5 DDRD5
|
||||
#define DDD6 DDRD6
|
||||
#define DDD7 DDRD7
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#include <util/delay.h>
|
||||
#endif
|
||||
|
||||
/* adjust some definitions to common versions */
|
||||
#if defined(__CROSSWORKS_AVR)
|
||||
#if (__TARGET_PROCESSOR == ATmega644P)
|
||||
#define PRR PRR0
|
||||
#define UBRR0 UBRR0W
|
||||
#define UBRR1 UBRR1W
|
||||
|
||||
#define PA0 PORTA0
|
||||
#define PA1 PORTA1
|
||||
#define PA2 PORTA2
|
||||
#define PA3 PORTA3
|
||||
#define PA4 PORTA4
|
||||
#define PA5 PORTA5
|
||||
#define PA6 PORTA6
|
||||
#define PA7 PORTA7
|
||||
|
||||
#define PB0 PORTB0
|
||||
#define PB1 PORTB1
|
||||
#define PB2 PORTB2
|
||||
#define PB3 PORTB3
|
||||
#define PB4 PORTB4
|
||||
#define PB5 PORTB5
|
||||
#define PB6 PORTB6
|
||||
#define PB7 PORTB7
|
||||
|
||||
#define PC0 PORTC0
|
||||
#define PC1 PORTC1
|
||||
#define PC2 PORTC2
|
||||
#define PC3 PORTC3
|
||||
#define PC4 PORTC4
|
||||
#define PC5 PORTC5
|
||||
#define PC6 PORTC6
|
||||
#define PC7 PORTC7
|
||||
|
||||
#define PD0 PORTD0
|
||||
#define PD1 PORTD1
|
||||
#define PD2 PORTD2
|
||||
#define PD3 PORTD3
|
||||
#define PD4 PORTD4
|
||||
#define PD5 PORTD5
|
||||
#define PD6 PORTD6
|
||||
#define PD7 PORTD7
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Input/Output Registers */
|
||||
#if defined(__GNUC__)
|
||||
#include <avr/io.h>
|
||||
|
||||
typedef struct {
|
||||
unsigned char bit0 : 1;
|
||||
unsigned char bit1 : 1;
|
||||
unsigned char bit2 : 1;
|
||||
unsigned char bit3 : 1;
|
||||
unsigned char bit4 : 1;
|
||||
unsigned char bit5 : 1;
|
||||
unsigned char bit6 : 1;
|
||||
unsigned char bit7 : 1;
|
||||
} BitRegisterType;
|
||||
|
||||
#ifndef true
|
||||
#define true 1
|
||||
#endif
|
||||
|
||||
#ifndef false
|
||||
#define false 0
|
||||
#endif
|
||||
|
||||
#define GPIO_BITREG(port, bitnum) \
|
||||
((volatile BitRegisterType *)_SFR_MEM_ADDR(port))->bit##bitnum
|
||||
|
||||
#define PINA_Bit0 GPIO_BITREG(PINA, 0)
|
||||
#define PINA_Bit1 GPIO_BITREG(PINA, 1)
|
||||
#define PINA_Bit2 GPIO_BITREG(PINA, 2)
|
||||
#define PINA_Bit3 GPIO_BITREG(PINA, 3)
|
||||
#define PINA_Bit4 GPIO_BITREG(PINA, 4)
|
||||
#define PINA_Bit5 GPIO_BITREG(PINA, 5)
|
||||
#define PINA_Bit6 GPIO_BITREG(PINA, 6)
|
||||
#define PINA_Bit7 GPIO_BITREG(PINA, 7)
|
||||
|
||||
#define PORTA_Bit0 GPIO_BITREG(PORTA, 0)
|
||||
#define PORTA_Bit1 GPIO_BITREG(PORTA, 1)
|
||||
#define PORTA_Bit2 GPIO_BITREG(PORTA, 2)
|
||||
#define PORTA_Bit3 GPIO_BITREG(PORTA, 3)
|
||||
#define PORTA_Bit4 GPIO_BITREG(PORTA, 4)
|
||||
#define PORTA_Bit5 GPIO_BITREG(PORTA, 5)
|
||||
#define PORTA_Bit6 GPIO_BITREG(PORTA, 6)
|
||||
#define PORTA_Bit7 GPIO_BITREG(PORTA, 7)
|
||||
|
||||
#define PINB_Bit0 GPIO_BITREG(PINB, 0)
|
||||
#define PINB_Bit1 GPIO_BITREG(PINB, 1)
|
||||
#define PINB_Bit2 GPIO_BITREG(PINB, 2)
|
||||
#define PINB_Bit3 GPIO_BITREG(PINB, 3)
|
||||
#define PINB_Bit4 GPIO_BITREG(PINB, 4)
|
||||
#define PINB_Bit5 GPIO_BITREG(PINB, 5)
|
||||
#define PINB_Bit6 GPIO_BITREG(PINB, 6)
|
||||
#define PINB_Bit7 GPIO_BITREG(PINB, 7)
|
||||
|
||||
#define PORTB_Bit0 GPIO_BITREG(PORTB, 0)
|
||||
#define PORTB_Bit1 GPIO_BITREG(PORTB, 1)
|
||||
#define PORTB_Bit2 GPIO_BITREG(PORTB, 2)
|
||||
#define PORTB_Bit3 GPIO_BITREG(PORTB, 3)
|
||||
#define PORTB_Bit4 GPIO_BITREG(PORTB, 4)
|
||||
#define PORTB_Bit5 GPIO_BITREG(PORTB, 5)
|
||||
#define PORTB_Bit6 GPIO_BITREG(PORTB, 6)
|
||||
#define PORTB_Bit7 GPIO_BITREG(PORTB, 7)
|
||||
|
||||
#define PINC_Bit0 GPIO_BITREG(PINC, 0)
|
||||
#define PINC_Bit1 GPIO_BITREG(PINC, 1)
|
||||
#define PINC_Bit2 GPIO_BITREG(PINC, 2)
|
||||
#define PINC_Bit3 GPIO_BITREG(PINC, 3)
|
||||
#define PINC_Bit4 GPIO_BITREG(PINC, 4)
|
||||
#define PINC_Bit5 GPIO_BITREG(PINC, 5)
|
||||
#define PINC_Bit6 GPIO_BITREG(PINC, 6)
|
||||
#define PINC_Bit7 GPIO_BITREG(PINC, 7)
|
||||
|
||||
#define PORTC_Bit0 GPIO_BITREG(PORTC, 0)
|
||||
#define PORTC_Bit1 GPIO_BITREG(PORTC, 1)
|
||||
#define PORTC_Bit2 GPIO_BITREG(PORTC, 2)
|
||||
#define PORTC_Bit3 GPIO_BITREG(PORTC, 3)
|
||||
#define PORTC_Bit4 GPIO_BITREG(PORTC, 4)
|
||||
#define PORTC_Bit5 GPIO_BITREG(PORTC, 5)
|
||||
#define PORTC_Bit6 GPIO_BITREG(PORTC, 6)
|
||||
#define PORTC_Bit7 GPIO_BITREG(PORTC, 7)
|
||||
|
||||
#define PIND_Bit0 GPIO_BITREG(PIND, 0)
|
||||
#define PIND_Bit1 GPIO_BITREG(PIND, 1)
|
||||
#define PIND_Bit2 GPIO_BITREG(PIND, 2)
|
||||
#define PIND_Bit3 GPIO_BITREG(PIND, 3)
|
||||
#define PIND_Bit4 GPIO_BITREG(PIND, 4)
|
||||
#define PIND_Bit5 GPIO_BITREG(PIND, 5)
|
||||
#define PIND_Bit6 GPIO_BITREG(PIND, 6)
|
||||
#define PIND_Bit7 GPIO_BITREG(PIND, 7)
|
||||
|
||||
#define PORTD_Bit0 GPIO_BITREG(PORTD, 0)
|
||||
#define PORTD_Bit1 GPIO_BITREG(PORTD, 1)
|
||||
#define PORTD_Bit2 GPIO_BITREG(PORTD, 2)
|
||||
#define PORTD_Bit3 GPIO_BITREG(PORTD, 3)
|
||||
#define PORTD_Bit4 GPIO_BITREG(PORTD, 4)
|
||||
#define PORTD_Bit5 GPIO_BITREG(PORTD, 5)
|
||||
#define PORTD_Bit6 GPIO_BITREG(PORTD, 6)
|
||||
#define PORTD_Bit7 GPIO_BITREG(PORTD, 7)
|
||||
|
||||
#define GPIOR0_Bit0 GPIO_BITREG(GPIOR0, 0)
|
||||
#define GPIOR0_Bit1 GPIO_BITREG(GPIOR0, 1)
|
||||
#define GPIOR0_Bit2 GPIO_BITREG(GPIOR0, 2)
|
||||
#define GPIOR0_Bit3 GPIO_BITREG(GPIOR0, 3)
|
||||
#define GPIOR0_Bit4 GPIO_BITREG(GPIOR0, 4)
|
||||
#define GPIOR0_Bit5 GPIO_BITREG(GPIOR0, 5)
|
||||
#define GPIOR0_Bit6 GPIO_BITREG(GPIOR0, 6)
|
||||
#define GPIOR0_Bit7 GPIO_BITREG(GPIOR0, 7)
|
||||
|
||||
#define GPIOR1_Bit0 GPIO_BITREG(GPIOR1, 0)
|
||||
#define GPIOR1_Bit1 GPIO_BITREG(GPIOR1, 1)
|
||||
#define GPIOR1_Bit2 GPIO_BITREG(GPIOR1, 2)
|
||||
#define GPIOR1_Bit3 GPIO_BITREG(GPIOR1, 3)
|
||||
#define GPIOR1_Bit4 GPIO_BITREG(GPIOR1, 4)
|
||||
#define GPIOR1_Bit5 GPIO_BITREG(GPIOR1, 5)
|
||||
#define GPIOR1_Bit6 GPIO_BITREG(GPIOR1, 6)
|
||||
#define GPIOR1_Bit7 GPIO_BITREG(GPIOR1, 7)
|
||||
|
||||
#define GPIOR2_Bit0 GPIO_BITREG(GPIOR2, 0)
|
||||
#define GPIOR2_Bit1 GPIO_BITREG(GPIOR2, 1)
|
||||
#define GPIOR2_Bit2 GPIO_BITREG(GPIOR2, 2)
|
||||
#define GPIOR2_Bit3 GPIO_BITREG(GPIOR2, 3)
|
||||
#define GPIOR2_Bit4 GPIO_BITREG(GPIOR2, 4)
|
||||
#define GPIOR2_Bit5 GPIO_BITREG(GPIOR2, 5)
|
||||
#define GPIOR2_Bit6 GPIO_BITREG(GPIOR2, 6)
|
||||
#define GPIOR2_Bit7 GPIO_BITREG(GPIOR2, 7)
|
||||
|
||||
#endif
|
||||
|
||||
/* Global Interrupts */
|
||||
#if defined(__GNUC__)
|
||||
#define __enable_interrupt() sei()
|
||||
#define __disable_interrupt() cli()
|
||||
#endif
|
||||
|
||||
/* Interrupts */
|
||||
#if defined(__ICCAVR__)
|
||||
#define PRAGMA(x) _Pragma(#x)
|
||||
#define ISR(vec) \
|
||||
/* function prototype for use with "require protoptypes" option. */ \
|
||||
PRAGMA(vector = vec) __interrupt void handler_##vec(void); \
|
||||
PRAGMA(vector = vec) __interrupt void handler_##vec(void)
|
||||
#elif defined(__GNUC__)
|
||||
#include <avr/interrupt.h>
|
||||
#endif
|
||||
|
||||
/* Flash */
|
||||
#if defined(__ICCAVR__)
|
||||
#define FLASH_DECLARE(x) __flash x
|
||||
#elif defined(__GNUC__)
|
||||
#define FLASH_DECLARE(x) x __attribute__((__progmem__))
|
||||
#endif
|
||||
|
||||
/* EEPROM */
|
||||
#if defined(__ICCAVR__)
|
||||
#define EEPROM_DECLARE(x) __eeprom x
|
||||
#endif
|
||||
#if defined(__GNUC__)
|
||||
#include <avr/eeprom.h>
|
||||
#define EEPROM_DECLARE(x) x __attribute__((section(".eeprom")))
|
||||
#endif
|
||||
|
||||
/* IAR intrinsic routines */
|
||||
#if defined(__GNUC__)
|
||||
/* FIXME: intrinsic routines: map to assembler for size/speed */
|
||||
#define __multiply_unsigned(x, y) ((x) * (y))
|
||||
/* FIXME: __root means to not optimize or strip */
|
||||
#define __root
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* @brief This module manages the main C code for the BACnet demo
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "hardware.h"
|
||||
#include "adc.h"
|
||||
#include "nvdata.h"
|
||||
#include "rs485.h"
|
||||
#include "stack.h"
|
||||
#include "bacnet/datalink/datalink.h"
|
||||
#include "bacnet/npdu.h"
|
||||
#include "bacnet/dcc.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
#include "bacnet/basic/sys/mstimer.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
#include "bacnet/iam.h"
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include "bacnet/basic/object/av.h"
|
||||
#include "bacnet/basic/object/bv.h"
|
||||
|
||||
/* From the WhoIs hander - performed by the DLMSTP module */
|
||||
extern bool Send_I_Am_Flag;
|
||||
/* main loop task timer */
|
||||
static struct mstimer Task_Timer;
|
||||
/* uptime */
|
||||
static uint32_t Uptime_Seconds;
|
||||
|
||||
/* dummy function - so we can use default demo handlers */
|
||||
bool dcc_communication_enabled(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void hardware_init(void)
|
||||
{
|
||||
/* Initialize the Clock Prescaler for ATmega48/88/168 */
|
||||
/* The default CLKPSx bits are factory set to 0011 */
|
||||
/* Enbable the Clock Prescaler */
|
||||
CLKPR = _BV(CLKPCE);
|
||||
/* CLKPS3 CLKPS2 CLKPS1 CLKPS0 Clock Division Factor
|
||||
------ ------ ------ ------ ---------------------
|
||||
0 0 0 0 1
|
||||
0 0 0 1 2
|
||||
0 0 1 0 4
|
||||
0 0 1 1 8
|
||||
0 1 0 0 16
|
||||
0 1 0 1 32
|
||||
0 1 1 0 64
|
||||
0 1 1 1 128
|
||||
1 0 0 0 256
|
||||
1 x x x Reserved
|
||||
*/
|
||||
/* Set the CLKPS3..0 bits to Prescaler of 1 */
|
||||
CLKPR = 0;
|
||||
/* Initialize I/O ports */
|
||||
/* For Port DDRx (Data Direction) Input=0, Output=1 */
|
||||
/* For Port PORTx (Bit Value) TriState=0, High=1 */
|
||||
DDRB = 0;
|
||||
PORTB = 0;
|
||||
DDRC = 0;
|
||||
PORTC = 0;
|
||||
DDRD = 0;
|
||||
PORTD = 0;
|
||||
|
||||
/* Configure the watchdog timer - Disabled for testing */
|
||||
BIT_CLEAR(MCUSR, WDRF);
|
||||
WDTCSR = 0;
|
||||
|
||||
/* Configure Specialized Hardware */
|
||||
RS485_Initialize();
|
||||
mstimer_init();
|
||||
adc_init();
|
||||
|
||||
/* Enable global interrupts */
|
||||
__enable_interrupt();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief process some values once per second
|
||||
*/
|
||||
static void one_second_task(void)
|
||||
{
|
||||
BACNET_BINARY_PV value;
|
||||
float hours;
|
||||
|
||||
/* LED toggling */
|
||||
value = Binary_Value_Present_Value(99);
|
||||
if (value == BINARY_ACTIVE) {
|
||||
value = BINARY_INACTIVE;
|
||||
} else {
|
||||
value = BINARY_ACTIVE;
|
||||
}
|
||||
Binary_Value_Present_Value_Set(99, value);
|
||||
/* uptime */
|
||||
Uptime_Seconds += 1;
|
||||
hours = (float)Uptime_Seconds / 3600.0f;
|
||||
Analog_Value_Present_Value_Set(99, hours, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the device's non-volatile data
|
||||
*/
|
||||
static void device_nvdata_init(void)
|
||||
{
|
||||
uint8_t value8;
|
||||
uint16_t value16;
|
||||
uint32_t value32;
|
||||
const char *default_name = "AVR Device";
|
||||
const char *default_description = "Description";
|
||||
const char *default_location = "Location";
|
||||
|
||||
value16 = nvdata_unsigned16(NV_EEPROM_TYPE_0);
|
||||
if (value16 != NV_EEPROM_TYPE_ID) {
|
||||
/* set to defaults */
|
||||
nvdata_unsigned16_set(NV_EEPROM_TYPE_0, NV_EEPROM_TYPE_ID);
|
||||
nvdata_unsigned8_set(NV_EEPROM_VERSION, NV_EEPROM_VERSION_ID);
|
||||
nvdata_unsigned8_set(NV_EEPROM_MSTP_MAC, 123);
|
||||
nvdata_unsigned8_set(NV_EEPROM_MSTP_BAUD_K, 38);
|
||||
nvdata_unsigned8_set(NV_EEPROM_MSTP_MAX_MASTER, 127);
|
||||
nvdata_unsigned24_set(NV_EEPROM_DEVICE_0, 260123);
|
||||
nvdata_name_set(
|
||||
NV_EEPROM_DEVICE_NAME, CHARACTER_UTF8, default_name,
|
||||
strlen(default_name));
|
||||
nvdata_name_set(
|
||||
NV_EEPROM_DEVICE_DESCRIPTION, CHARACTER_UTF8, default_description,
|
||||
strlen(default_description));
|
||||
nvdata_name_set(
|
||||
NV_EEPROM_DEVICE_LOCATION, CHARACTER_UTF8, default_location,
|
||||
strlen(default_location));
|
||||
}
|
||||
value8 = nvdata_unsigned8(NV_EEPROM_MSTP_MAC);
|
||||
dlmstp_set_mac_address(value8);
|
||||
value8 = nvdata_unsigned8(NV_EEPROM_MSTP_BAUD_K);
|
||||
value32 = RS485_Baud_Rate_From_Kilo(value8);
|
||||
RS485_Set_Baud_Rate(value32);
|
||||
value8 = nvdata_unsigned8(NV_EEPROM_MSTP_MAX_MASTER);
|
||||
if (value8 > 127) {
|
||||
value8 = 127;
|
||||
}
|
||||
dlmstp_set_max_master(value8);
|
||||
dlmstp_set_max_info_frames(1);
|
||||
Send_I_Am_Flag = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static receive buffer,
|
||||
* initialized with zeros by the C Library Startup Code.
|
||||
* Note: Added a little safety margin to the buffer,
|
||||
* so that in the rare case, the message would be filled
|
||||
* up to MAX_MPDU and some decoding functions would overrun,
|
||||
* these decoding functions will just end up in a safe field
|
||||
* of static zeros. */
|
||||
static uint8_t PDUBuffer[MAX_MPDU + 16];
|
||||
|
||||
/**
|
||||
* Main function
|
||||
* @return 0, should never reach this
|
||||
*/
|
||||
int main(void)
|
||||
{
|
||||
uint16_t pdu_len = 0;
|
||||
BACNET_ADDRESS src; /* source address */
|
||||
|
||||
hardware_init();
|
||||
Analog_Value_Init();
|
||||
Binary_Value_Init();
|
||||
device_nvdata_init();
|
||||
dlmstp_init(NULL);
|
||||
mstimer_set(&Task_Timer, 1000);
|
||||
for (;;) {
|
||||
/* process */
|
||||
if (mstimer_expired(&Task_Timer)) {
|
||||
mstimer_reset(&Task_Timer);
|
||||
one_second_task();
|
||||
}
|
||||
/* BACnet handling */
|
||||
pdu_len = dlmstp_receive(&src, &PDUBuffer[0], MAX_MPDU, 0);
|
||||
if (pdu_len) {
|
||||
npdu_handler(&src, &PDUBuffer[0], pdu_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief Handle Get/Set of non-volatile data
|
||||
* @date July 8, 2015
|
||||
* @author Steve Karg
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "bacnet/bacint.h"
|
||||
#include "bacnet/bacstr.h"
|
||||
#include "nvdata.h"
|
||||
#include "eeprom.h"
|
||||
|
||||
/**
|
||||
* Gets 8-byte value from non-volatile memory
|
||||
*
|
||||
* @return device ID
|
||||
*/
|
||||
uint64_t nvdata_unsigned64(uint32_t ee_address)
|
||||
{
|
||||
uint64_t long_value = 0;
|
||||
uint8_t buffer[8];
|
||||
|
||||
if (eeprom_bytes_read(ee_address, buffer, 8) == 8) {
|
||||
decode_unsigned64(buffer, &long_value);
|
||||
}
|
||||
|
||||
return long_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a 8-byte value in non-volatile memory
|
||||
*
|
||||
* @param long_value 8-byte value
|
||||
*/
|
||||
int nvdata_unsigned64_set(uint32_t ee_address, uint64_t long_value)
|
||||
{
|
||||
uint8_t buffer[8];
|
||||
|
||||
encode_unsigned64(buffer, long_value);
|
||||
return eeprom_bytes_write(ee_address, buffer, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a 4-byte value from non-volatile memory
|
||||
*
|
||||
* @return device ID
|
||||
*/
|
||||
uint32_t nvdata_unsigned32(uint32_t ee_address)
|
||||
{
|
||||
uint32_t long_value = 0;
|
||||
uint8_t buffer[4];
|
||||
|
||||
if (eeprom_bytes_read(ee_address, buffer, 4) == 4) {
|
||||
decode_unsigned32(buffer, &long_value);
|
||||
}
|
||||
|
||||
return long_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a 4-byte value in non-volatile memory
|
||||
*
|
||||
* @param long_value 4-byte value
|
||||
*/
|
||||
int nvdata_unsigned32_set(uint32_t ee_address, uint32_t long_value)
|
||||
{
|
||||
uint8_t buffer[4];
|
||||
|
||||
encode_unsigned32(buffer, long_value);
|
||||
return eeprom_bytes_write(ee_address, buffer, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a 3-byte value from non-volatile memory
|
||||
*
|
||||
* @return device ID
|
||||
*/
|
||||
uint32_t nvdata_unsigned24(uint32_t ee_address)
|
||||
{
|
||||
uint32_t long_value = 0;
|
||||
uint8_t buffer[3];
|
||||
|
||||
if (eeprom_bytes_read(ee_address, buffer, 3) == 3) {
|
||||
decode_unsigned24(buffer, &long_value);
|
||||
}
|
||||
|
||||
return long_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a 3-byte value in non-volatile memory
|
||||
*
|
||||
* @param long_value 3-byte value
|
||||
*/
|
||||
int nvdata_unsigned24_set(uint32_t ee_address, uint32_t long_value)
|
||||
{
|
||||
uint8_t buffer[3];
|
||||
|
||||
encode_unsigned24(buffer, long_value);
|
||||
return eeprom_bytes_write(ee_address, buffer, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a 2-byte value from non-volatile memory
|
||||
*
|
||||
* @return device ID
|
||||
*/
|
||||
uint16_t nvdata_unsigned16(uint32_t ee_address)
|
||||
{
|
||||
uint16_t short_value = 0;
|
||||
uint8_t buffer[2];
|
||||
|
||||
if (eeprom_bytes_read(ee_address, buffer, 2) == 2) {
|
||||
decode_unsigned16(buffer, &short_value);
|
||||
}
|
||||
|
||||
return short_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a 2-byte value in non-volatile memory
|
||||
*
|
||||
* @param short_value 2-byte value
|
||||
*/
|
||||
int nvdata_unsigned16_set(uint32_t ee_address, uint16_t short_value)
|
||||
{
|
||||
uint8_t buffer[2];
|
||||
|
||||
encode_unsigned16(buffer, short_value);
|
||||
return eeprom_bytes_write(ee_address, buffer, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a 1-byte value from non-volatile memory
|
||||
*
|
||||
* @return device ID
|
||||
*/
|
||||
uint8_t nvdata_unsigned8(uint32_t ee_address)
|
||||
{
|
||||
uint8_t buffer = 0;
|
||||
|
||||
if (eeprom_bytes_read(ee_address, &buffer, 1) == 1) {
|
||||
/* something useful */
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a 1-byte value in non-volatile memory
|
||||
*
|
||||
* @param byte_value 1-byte value
|
||||
*/
|
||||
int nvdata_unsigned8_set(uint32_t ee_address, uint8_t byte_value)
|
||||
{
|
||||
return eeprom_bytes_write(ee_address, &byte_value, 1);
|
||||
}
|
||||
|
||||
bool nvdata_name_isvalid(uint8_t encoding, uint8_t length, const char *str)
|
||||
{
|
||||
bool valid = false;
|
||||
|
||||
if ((encoding < MAX_CHARACTER_STRING_ENCODING) &&
|
||||
(length <= NV_EEPROM_NAME_SIZE)) {
|
||||
if (encoding == CHARACTER_UTF8) {
|
||||
valid = utf8_isvalid(str, length);
|
||||
} else {
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a name to the EEPROM
|
||||
* @param offset - starting memory address
|
||||
* @param encoding - BACnet CharacterString encoding
|
||||
* @param str - string to write
|
||||
* @param length - number of bytes to write
|
||||
* @return true if successful
|
||||
*/
|
||||
bool nvdata_name_set(
|
||||
uint16_t offset, uint8_t encoding, const char *str, uint8_t length)
|
||||
{
|
||||
uint8_t buffer[NV_EEPROM_NAME_SIZE] = { 0 };
|
||||
uint8_t i = 0;
|
||||
|
||||
if (nvdata_name_isvalid(encoding, length, str)) {
|
||||
eeprom_bytes_write(NV_EEPROM_NAME_LENGTH(offset), &length, 1);
|
||||
eeprom_bytes_write(
|
||||
NV_EEPROM_NAME_ENCODING(offset), (uint8_t *)&encoding, 1);
|
||||
for (i = 0; i < length; i++) {
|
||||
buffer[i] = str[i];
|
||||
}
|
||||
eeprom_bytes_write(
|
||||
NV_EEPROM_NAME_STRING(offset), &buffer[0], NV_EEPROM_NAME_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Save the BACnet CharacterString to non-volatile memory at offset
|
||||
* @param offset - starting memory address
|
||||
* @param char_string - BACnet CharacterString value
|
||||
* @param error_class - BACnet error class
|
||||
* @param error_code - BACnet error code
|
||||
* @return true if name is a valid set of characters, valid length, and
|
||||
* valid character set encoding.
|
||||
* error_code and error_class are filled if false.
|
||||
*/
|
||||
bool nvdata_name_write(
|
||||
uint32_t offset,
|
||||
BACNET_CHARACTER_STRING *char_string,
|
||||
BACNET_ERROR_CLASS *error_class,
|
||||
BACNET_ERROR_CODE *error_code)
|
||||
{
|
||||
bool status = false;
|
||||
size_t length;
|
||||
uint8_t encoding;
|
||||
const char *str;
|
||||
|
||||
length = characterstring_length(char_string);
|
||||
if (length <= NV_EEPROM_NAME_SIZE) {
|
||||
encoding = characterstring_encoding(char_string);
|
||||
if (encoding < MAX_CHARACTER_STRING_ENCODING) {
|
||||
str = characterstring_value(char_string);
|
||||
status = nvdata_name_set(offset, encoding, str, length);
|
||||
if (!status) {
|
||||
*error_class = ERROR_CLASS_PROPERTY;
|
||||
*error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
*error_class = ERROR_CLASS_PROPERTY;
|
||||
*error_code = ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED;
|
||||
}
|
||||
} else {
|
||||
*error_class = ERROR_CLASS_PROPERTY;
|
||||
*error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a name from the EEPROM
|
||||
* @param offset - starting memory address
|
||||
* @param char_string - BACnet CharacterString value
|
||||
* @param default_string - default string to use if not valid
|
||||
*/
|
||||
void nvdata_name(
|
||||
uint32_t offset,
|
||||
BACNET_CHARACTER_STRING *char_string,
|
||||
const char *default_string)
|
||||
{
|
||||
uint8_t encoding = 0;
|
||||
uint8_t length = 0;
|
||||
char name[NV_EEPROM_NAME_SIZE + 1] = "";
|
||||
|
||||
eeprom_bytes_read(NV_EEPROM_NAME_ENCODING(offset), &encoding, 1);
|
||||
eeprom_bytes_read(NV_EEPROM_NAME_LENGTH(offset), &length, 1);
|
||||
eeprom_bytes_read(
|
||||
NV_EEPROM_NAME_STRING(offset), (uint8_t *)&name, NV_EEPROM_NAME_SIZE);
|
||||
if (nvdata_name_isvalid(encoding, length, name)) {
|
||||
characterstring_init(char_string, encoding, &name[0], length);
|
||||
} else if (default_string) {
|
||||
(void)nvdata_name_set(
|
||||
offset, CHARACTER_UTF8, default_string, strlen(default_string));
|
||||
characterstring_init_ansi(char_string, default_string);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from SEEPROM memory at address
|
||||
*
|
||||
* @param start_address - SEEPROM starting memory address
|
||||
* @param buffer - place to store data that has been read
|
||||
* @param length - number of bytes to read
|
||||
*
|
||||
* @return number of bytes read, or 0 on error
|
||||
*/
|
||||
uint32_t nvdata_get(uint32_t ee_address, uint8_t *buffer, uint32_t nbytes)
|
||||
{
|
||||
return eeprom_bytes_read(ee_address, buffer, nbytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write some data and wait until it is sent
|
||||
*
|
||||
* @param ee_address - SEEPROM starting memory address
|
||||
* @param buffer - data to send
|
||||
* @param length - number of bytes of data
|
||||
*
|
||||
* @return number of bytes written, or 0 on error
|
||||
*/
|
||||
uint32_t
|
||||
nvdata_set(uint32_t ee_address, uint8_t *buffer, uint32_t buffer_length)
|
||||
{
|
||||
return eeprom_bytes_write(ee_address, buffer, buffer_length);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @brief This module contains the map for internal AVR non-volatile data
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef NVDATA_H
|
||||
#define NVDATA_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacerror.h"
|
||||
#include "bacnet/bacstr.h"
|
||||
#include "eeprom.h"
|
||||
|
||||
/*=============== EEPROM ================*/
|
||||
#define NV_EEPROM_TYPE_ID 0xBAC0U
|
||||
#define NV_EEPROM_VERSION_ID 1
|
||||
#define NV_EEPROM_BYTES_MAX EEPROM_BYTES_MAX
|
||||
|
||||
/* note to developers: define each byte,
|
||||
even if they are not used explicitly */
|
||||
|
||||
/* EEPROM Type ID to indicate initialized or not */
|
||||
#define NV_EEPROM_TYPE_0 0
|
||||
#define NV_EEPROM_TYPE_1 1
|
||||
/* EEPROM Version ID to indicate schema change */
|
||||
#define NV_EEPROM_VERSION 2
|
||||
|
||||
/* EEPROM free space - 3..9 */
|
||||
|
||||
/* BACnet MS/TP datalink layer */
|
||||
#define NV_EEPROM_MSTP_MAC 10
|
||||
/* 9=9.6k, 19=19.2k, 38=38.4k, 57=57.6k, 76=76.8k, 115=115.2k */
|
||||
#define NV_EEPROM_MSTP_BAUD_K 11
|
||||
#define NV_EEPROM_MSTP_MAX_MASTER 12
|
||||
/* device instance is only 22 bits */
|
||||
#define NV_EEPROM_DEVICE_0 13
|
||||
#define NV_EEPROM_DEVICE_1 14
|
||||
#define NV_EEPROM_DEVICE_2 15
|
||||
|
||||
/* EEPROM free space - 16..31 */
|
||||
|
||||
/* BACnet Names - 32 bytes of data each */
|
||||
#define NV_EEPROM_NAME_LENGTH(n) ((n) + 0)
|
||||
#define NV_EEPROM_NAME_ENCODING(n) ((n) + 1)
|
||||
#define NV_EEPROM_NAME_STRING(n) ((n) + 2)
|
||||
#define NV_EEPROM_NAME_SIZE 30
|
||||
#define NV_EEPROM_NAME_OFFSET (1 + 1 + NV_EEPROM_NAME_SIZE)
|
||||
/* Device Name - starting offset */
|
||||
#define NV_EEPROM_DEVICE_NAME 32
|
||||
/* Device Description - starting offset */
|
||||
#define NV_EEPROM_DEVICE_DESCRIPTION \
|
||||
(NV_EEPROM_DEVICE_NAME + NV_EEPROM_NAME_OFFSET)
|
||||
/* Device Location - starting offset */
|
||||
#define NV_EEPROM_DEVICE_LOCATION \
|
||||
(NV_EEPROM_DEVICE_DESCRIPTION + NV_EEPROM_NAME_OFFSET)
|
||||
|
||||
/* EEPROM free space 128..1024 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
uint64_t nvdata_unsigned64(uint32_t ee_address);
|
||||
int nvdata_unsigned64_set(uint32_t ee_address, uint64_t long_value);
|
||||
int nvdata_unsigned32_set(uint32_t ee_address, uint32_t long_value);
|
||||
uint32_t nvdata_unsigned32(uint32_t ee_address);
|
||||
int nvdata_unsigned24_set(uint32_t ee_address, uint32_t long_value);
|
||||
uint32_t nvdata_unsigned24(uint32_t ee_address);
|
||||
uint16_t nvdata_unsigned16(uint32_t ee_address);
|
||||
int nvdata_unsigned16_set(uint32_t ee_address, uint16_t short_value);
|
||||
uint8_t nvdata_unsigned8(uint32_t ee_address);
|
||||
int nvdata_unsigned8_set(uint32_t ee_address, uint8_t byte_value);
|
||||
|
||||
bool nvdata_name_isvalid(uint8_t encoding, uint8_t length, const char *str);
|
||||
bool nvdata_name_set(
|
||||
uint16_t offset, uint8_t encoding, const char *str, uint8_t length);
|
||||
|
||||
bool nvdata_name_write(
|
||||
uint32_t offset,
|
||||
BACNET_CHARACTER_STRING *char_string,
|
||||
BACNET_ERROR_CLASS *error_class,
|
||||
BACNET_ERROR_CODE *error_code);
|
||||
void nvdata_name(
|
||||
uint32_t offset,
|
||||
BACNET_CHARACTER_STRING *char_string,
|
||||
const char *default_string);
|
||||
|
||||
uint32_t nvdata_get(uint32_t ee_address, uint8_t *buffer, uint32_t nbytes);
|
||||
uint32_t nvdata_set(uint32_t ee_address, uint8_t *buffer, uint32_t nbytes);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* @brief The module handles sending data out the RS-485 port
|
||||
* and handles receiving data from the RS-485 port.
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* This file has been customized for use with ATMEGA168 */
|
||||
#include "hardware.h"
|
||||
#include "rs485.h"
|
||||
#include "bacnet/basic/sys/mstimer.h"
|
||||
|
||||
/* baud rate */
|
||||
static uint32_t RS485_Baud = 9600;
|
||||
/* amount of silence on the wire */
|
||||
static struct mstimer Silence_Timer;
|
||||
|
||||
/* Public access to the Silence Timer */
|
||||
unsigned long RS485_Timer_Silence(void)
|
||||
{
|
||||
return mstimer_elapsed(&Silence_Timer);
|
||||
}
|
||||
|
||||
/* Public reset of the Silence Timer */
|
||||
void RS485_Timer_Silence_Reset(void)
|
||||
{
|
||||
mstimer_set(&Silence_Timer, 0);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Initializes the RS485 hardware and variables, and starts in
|
||||
* receive mode.
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
void RS485_Initialize(void)
|
||||
{
|
||||
/* enable Transmit and Receive */
|
||||
UCSR0B = _BV(TXEN0) | _BV(RXEN0);
|
||||
|
||||
/* Set USART Control and Status Register n C */
|
||||
/* Asynchronous USART 8-bit data, No parity, 1 stop */
|
||||
/* Set USART Mode Select: UMSELn1 UMSELn0 = 00 for Asynchronous USART */
|
||||
/* Set Parity Mode: UPMn1 UPMn0 = 00 for Parity Disabled */
|
||||
/* Set Stop Bit Select: USBSn = 0 for 1 stop bit */
|
||||
/* Set Character Size: UCSZn2 UCSZn1 UCSZn0 = 011 for 8-bit */
|
||||
/* Clock Polarity: UCPOLn = 0 when asynchronous mode is used. */
|
||||
UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
|
||||
/* Clear Power Reduction USART0 */
|
||||
BIT_CLEAR(PRR, PRUSART0);
|
||||
/* Use port PD2 for RTS - enable and disable of Transceiver Tx/Rx */
|
||||
/* Set port bit as Output - initially receiving */
|
||||
BIT_CLEAR(PORTD, PD2);
|
||||
BIT_SET(DDRD, DDD2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Returns the baud rate that we are currently running at
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
uint32_t RS485_Get_Baud_Rate(void)
|
||||
{
|
||||
return RS485_Baud;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts kili-baud to baud
|
||||
* @param baud_k - baud rate in approximate kilobaud
|
||||
* @return: baud rate in bps
|
||||
*/
|
||||
uint32_t RS485_Baud_Rate_From_Kilo(uint8_t baud_k)
|
||||
{
|
||||
uint32_t baud = 0;
|
||||
|
||||
if (baud_k == 255) {
|
||||
baud = 38400;
|
||||
} else if (baud_k >= 115) {
|
||||
baud = 115200;
|
||||
} else if (baud_k >= 76) {
|
||||
baud = 76800;
|
||||
} else if (baud_k >= 57) {
|
||||
baud = 57600;
|
||||
} else if (baud_k >= 38) {
|
||||
baud = 38400;
|
||||
} else if (baud_k >= 19) {
|
||||
baud = 19200;
|
||||
} else if (baud_k >= 9) {
|
||||
baud = 9600;
|
||||
} else {
|
||||
baud = 38400;
|
||||
}
|
||||
|
||||
return baud;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Sets the baud rate for the chip USART
|
||||
* RETURN: true if valid baud rate
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
bool RS485_Set_Baud_Rate(uint32_t baud)
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
switch (baud) {
|
||||
case 9600:
|
||||
case 19200:
|
||||
case 38400:
|
||||
case 57600:
|
||||
case 76800:
|
||||
case 115200:
|
||||
RS485_Baud = baud;
|
||||
/* 2x speed mode */
|
||||
BIT_SET(UCSR0A, U2X0);
|
||||
/* configure baud rate */
|
||||
UBRR0 = (F_CPU / (8UL * RS485_Baud)) - 1;
|
||||
break;
|
||||
default:
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Enable or disable the transmitter
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
void RS485_Transmitter_Enable(bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
BIT_SET(PORTD, PD2);
|
||||
} else {
|
||||
BIT_CLEAR(PORTD, PD2);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Waits on the SilenceTimer for 40 bits.
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
void RS485_Turnaround_Delay(void)
|
||||
{
|
||||
uint8_t nbytes = 4;
|
||||
|
||||
RS485_Transmitter_Enable(false);
|
||||
while (nbytes) {
|
||||
while (!BIT_CHECK(UCSR0A, UDRE0)) {
|
||||
/* do nothing - wait until Tx buffer is empty */
|
||||
}
|
||||
/* Send the data byte */
|
||||
UDR0 = 0xff;
|
||||
nbytes--;
|
||||
}
|
||||
/* was the frame sent? */
|
||||
while (!BIT_CHECK(UCSR0A, TXC0)) {
|
||||
/* do nothing - wait until the entire frame in the
|
||||
Transmit Shift Register has been shifted out */
|
||||
}
|
||||
/* Clear the Transmit Complete flag by writing a one to it. */
|
||||
BIT_SET(UCSR0A, TXC0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send some data and wait until it is sent
|
||||
* @param buffer - data to send
|
||||
* @param nbytes - number of bytes of data
|
||||
*/
|
||||
void RS485_Send_Data(const uint8_t *buffer, uint16_t nbytes)
|
||||
{
|
||||
while (nbytes) {
|
||||
while (!BIT_CHECK(UCSR0A, UDRE0)) {
|
||||
/* do nothing - wait until Tx buffer is empty */
|
||||
}
|
||||
/* Send the data byte */
|
||||
UDR0 = *buffer;
|
||||
buffer++;
|
||||
nbytes--;
|
||||
}
|
||||
/* was the frame sent? */
|
||||
while (!BIT_CHECK(UCSR0A, TXC0)) {
|
||||
/* do nothing - wait until the entire frame in the
|
||||
Transmit Shift Register has been shifted out */
|
||||
}
|
||||
/* Clear the Transmit Complete flag by writing a one to it. */
|
||||
BIT_SET(UCSR0A, TXC0);
|
||||
/* per MSTP spec, sort of */
|
||||
RS485_Timer_Silence_Reset();
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Return true if a framing or overrun error is present
|
||||
* RETURN: true if error
|
||||
* ALGORITHM: autobaud - if there are a lot of errors, switch baud rate
|
||||
* NOTES: Clears any error flags.
|
||||
*****************************************************************************/
|
||||
bool RS485_ReceiveError(void)
|
||||
{
|
||||
bool ReceiveError = false;
|
||||
volatile uint8_t dummy_data;
|
||||
|
||||
/* check for framing error */
|
||||
#if 0
|
||||
if (BIT_CHECK(UCSR0A, FE0)) {
|
||||
/* FIXME: how do I clear the error flags? */
|
||||
BITMASK_CLEAR(UCSR0A, (_BV(FE0) | _BV(DOR0) | _BV(UPE0)));
|
||||
ReceiveError = true;
|
||||
}
|
||||
#endif
|
||||
/* check for overrun error */
|
||||
if (BIT_CHECK(UCSR0A, DOR0)) {
|
||||
/* flush the receive buffer */
|
||||
do {
|
||||
dummy_data = UDR0;
|
||||
} while (BIT_CHECK(UCSR0A, RXC0));
|
||||
ReceiveError = true;
|
||||
}
|
||||
(void)dummy_data;
|
||||
|
||||
return ReceiveError;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Return true if data is available
|
||||
* RETURN: true if data is available, with the data in the parameter set
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
bool RS485_DataAvailable(uint8_t *data)
|
||||
{
|
||||
bool DataAvailable = false;
|
||||
|
||||
/* check for data */
|
||||
if (BIT_CHECK(UCSR0A, RXC0)) {
|
||||
*data = UDR0;
|
||||
DataAvailable = true;
|
||||
}
|
||||
|
||||
return DataAvailable;
|
||||
}
|
||||
|
||||
#ifdef TEST_RS485
|
||||
int main(void)
|
||||
{
|
||||
unsigned i = 0;
|
||||
uint8_t DataRegister;
|
||||
|
||||
RS485_Set_Baud_Rate(38400);
|
||||
RS485_Initialize();
|
||||
/* receive task */
|
||||
for (;;) {
|
||||
if (RS485_ReceiveError()) {
|
||||
fprintf(stderr, "ERROR ");
|
||||
} else if (RS485_DataAvailable(&DataRegister)) {
|
||||
fprintf(stderr, "%02X ", DataRegister);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* TEST_RS485 */
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @brief The module handles API for the RS-485 port
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef RS485_H
|
||||
#define RS485_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
unsigned long RS485_Timer_Silence(void);
|
||||
|
||||
void RS485_Timer_Silence_Reset(void);
|
||||
|
||||
void RS485_Initialize(void);
|
||||
|
||||
void RS485_Transmitter_Enable(bool enable);
|
||||
|
||||
void RS485_Send_Data(
|
||||
const uint8_t *buffer, /* data to send */
|
||||
uint16_t nbytes); /* number of bytes of data */
|
||||
|
||||
bool RS485_ReceiveError(void);
|
||||
bool RS485_DataAvailable(uint8_t *data);
|
||||
|
||||
void RS485_Turnaround_Delay(void);
|
||||
uint32_t RS485_Get_Baud_Rate(void);
|
||||
bool RS485_Set_Baud_Rate(uint32_t baud);
|
||||
uint32_t RS485_Baud_Rate_From_Kilo(uint8_t baud_k);
|
||||
|
||||
void RS485_LED_Timers(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @brief The module handles CSTACK monitoring
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2009
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include "hardware.h"
|
||||
#include "stack.h"
|
||||
|
||||
/* stack checking */
|
||||
#if defined(__GNUC__)
|
||||
extern uint8_t _end;
|
||||
extern uint8_t __stack;
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define STACK_CANARY (0xC5)
|
||||
void stack_init(void) __attribute__((naked)) __attribute__((section(".init1")));
|
||||
#endif
|
||||
|
||||
void stack_init(void)
|
||||
{
|
||||
#if defined(__GNUC__)
|
||||
#if 0
|
||||
uint8_t *p = &_end;
|
||||
|
||||
while (p <= &__stack) {
|
||||
*p = STACK_CANARY;
|
||||
p++;
|
||||
}
|
||||
#else
|
||||
__asm volatile(" ldi r30,lo8(_end)\n"
|
||||
" ldi r31,hi8(_end)\n"
|
||||
" ldi r24,lo8(0xc5)\n" /* STACK_CANARY = 0xc5 */
|
||||
" ldi r25,hi8(__stack)\n"
|
||||
" rjmp .cmp\n"
|
||||
".loop:\n"
|
||||
" st Z+,r24\n"
|
||||
".cmp:\n"
|
||||
" cpi r30,lo8(__stack)\n"
|
||||
" cpc r31,r25\n"
|
||||
" brlo .loop\n"
|
||||
" breq .loop" ::);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned stack_size(void)
|
||||
{
|
||||
#if defined(__GNUC__)
|
||||
return (&__stack) - (&_end);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t stack_byte(unsigned offset)
|
||||
{
|
||||
#if defined(__GNUC__)
|
||||
return *(&_end + offset);
|
||||
#else
|
||||
offset = offset;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned stack_unused(void)
|
||||
{
|
||||
unsigned count = 0;
|
||||
#if defined(__GNUC__)
|
||||
uint8_t *p = &_end;
|
||||
|
||||
while (p <= &__stack) {
|
||||
if ((*p) != STACK_CANARY) {
|
||||
count = p - (&_end);
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
#endif
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @brief The module handles the API for the CSTACK monitoring
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2009
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef STACK_H
|
||||
#define STACK_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* C stack checking */
|
||||
void stack_init(void);
|
||||
|
||||
unsigned stack_size(void);
|
||||
|
||||
uint8_t stack_byte(unsigned offset);
|
||||
|
||||
unsigned stack_unused(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @brief This module handles the 1 millisecond timer for the mstimer library
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2007
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include "hardware.h"
|
||||
#include "bacnet/basic/sys/mstimer.h"
|
||||
|
||||
/* Prescaling: 1, 8, 64, 256, 1024 */
|
||||
#define TIMER_MICROSECONDS 1000UL
|
||||
#define TIMER_TICKS(p) \
|
||||
(((((F_CPU) / (p)) / 1000UL) * (TIMER_MICROSECONDS)) / 1000UL)
|
||||
#define TIMER_TICKS_MAX 255UL
|
||||
/* adjust the prescaler for the processor clock */
|
||||
#if (TIMER_TICKS(1) <= TIMER_TICKS_MAX)
|
||||
#define TIMER0_PRESCALER 1
|
||||
#elif (TIMER_TICKS(8) <= TIMER_TICKS_MAX)
|
||||
#define TIMER0_PRESCALER 8
|
||||
#elif (TIMER_TICKS(64) <= TIMER_TICKS_MAX)
|
||||
#define TIMER0_PRESCALER 64
|
||||
#elif (TIMER_TICKS(256) <= TIMER_TICKS_MAX)
|
||||
#define TIMER0_PRESCALER 256
|
||||
#elif (TIMER_TICKS(1024) <= TIMER_TICKS_MAX)
|
||||
#define TIMER0_PRESCALER 1024
|
||||
#else
|
||||
#error "TIMER0: F_CPU too large for timer prescaler."
|
||||
#endif
|
||||
#define TIMER0_TICKS TIMER_TICKS(TIMER0_PRESCALER)
|
||||
/* Timer counts up from count to TIMER_TICKS_MAX and then signals overflow */
|
||||
#define TIMER0_COUNT (TIMER_TICKS_MAX - TIMER0_TICKS)
|
||||
|
||||
/* millisecond time counter */
|
||||
static volatile unsigned long Millisecond_Counter;
|
||||
|
||||
/* Configure the Timer */
|
||||
void mstimer_init(void)
|
||||
{
|
||||
/* Normal Operation */
|
||||
TCCR1A = 0;
|
||||
/* CSn2 CSn1 CSn0 Description
|
||||
---- ---- ---- -----------
|
||||
0 0 0 No Clock Source
|
||||
0 0 1 No prescaling
|
||||
0 1 0 CLKio/8
|
||||
0 1 1 CLKio/64
|
||||
1 0 0 CLKio/256
|
||||
1 0 1 CLKio/1024
|
||||
1 1 0 Falling Edge of T0 (external)
|
||||
1 1 1 Rising Edge of T0 (external)
|
||||
*/
|
||||
#if (TIMER0_PRESCALER == 1)
|
||||
TCCR0B = _BV(CS00);
|
||||
#elif (TIMER0_PRESCALER == 8)
|
||||
TCCR0B = _BV(CS01);
|
||||
#elif (TIMER0_PRESCALER == 64)
|
||||
TCCR0B = _BV(CS01) | _BV(CS00);
|
||||
#elif (TIMER0_PRESCALER == 256)
|
||||
TCCR0B = _BV(CS02);
|
||||
#elif (TIMER0_PRESCALER == 1024)
|
||||
TCCR0B = _BV(CS02) | _BV(CS00);
|
||||
#else
|
||||
#error Timer0 Prescale: Invalid Value
|
||||
#endif
|
||||
/* Clear any TOV1 Flag set when the timer overflowed */
|
||||
BIT_CLEAR(TIFR0, TOV0);
|
||||
/* Initial value */
|
||||
TCNT0 = TIMER0_COUNT;
|
||||
/* Enable the overflow interrupt */
|
||||
BIT_SET(TIMSK0, TOIE0);
|
||||
/* Clear the Power Reduction Timer/Counter0 */
|
||||
BIT_CLEAR(PRR, PRTIM0);
|
||||
}
|
||||
|
||||
/* Timer interupt */
|
||||
/* note: Global interupts must be enabled - sei() */
|
||||
/* Timer Overflowed! Increment the time. */
|
||||
ISR(TIMER0_OVF_vect)
|
||||
{
|
||||
/* Set the counter for the next interrupt */
|
||||
TCNT0 = TIMER0_COUNT;
|
||||
/* Overflow Flag is automatically cleared */
|
||||
/* Update the global timer */
|
||||
Millisecond_Counter++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current time in milliseconds
|
||||
* @return milliseconds
|
||||
*/
|
||||
unsigned long mstimer_now(void)
|
||||
{
|
||||
unsigned long milliseconds;
|
||||
|
||||
BIT_CLEAR(TIMSK0, TOIE0);
|
||||
milliseconds = Millisecond_Counter;
|
||||
BIT_SET(TIMSK0, TOIE0);
|
||||
|
||||
return milliseconds;
|
||||
}
|
||||
Reference in New Issue
Block a user