639 lines
18 KiB
C
639 lines
18 KiB
C
/**
|
|
* @file
|
|
* @brief Example uBASIC-Plus porting layer for the uBASIC-Plus interpreter
|
|
* for STM32F4xx NUCLIO board
|
|
* @author Steve Karg
|
|
* @date 2025
|
|
* @copyright SPDX-License-Identifier: MIT
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#include <ctype.h>
|
|
#include "bacnet/basic/object/ai.h"
|
|
#include "bacnet/basic/object/ao.h"
|
|
#include "bacnet/basic/object/av.h"
|
|
#include "bacnet/basic/object/bi.h"
|
|
#include "bacnet/basic/object/bo.h"
|
|
#include "bacnet/basic/object/bv.h"
|
|
#include "bacnet/basic/object/device.h"
|
|
#include "bacnet/basic/object/ms-input.h"
|
|
#include "bacnet/basic/object/mso.h"
|
|
#include "bacnet/basic/object/msv.h"
|
|
#include "bacnet/basic/program/ubasic/ubasic.h"
|
|
#include "bacnet/basic/sys/mstimer.h"
|
|
#include "bacnet/wp.h"
|
|
#include "led.h"
|
|
|
|
#if defined(UBASIC_SCRIPT_HAVE_PRINT_TO_SERIAL)
|
|
/**
|
|
* @brief Write a buffer to the serial port
|
|
* @param msg Pointer to the buffer to write
|
|
* @param n Number of bytes to write
|
|
*/
|
|
static void serial_write(const char *msg, uint16_t n)
|
|
{
|
|
(void)msg;
|
|
(void)n;
|
|
}
|
|
|
|
/**
|
|
* @brief Gather key presses until new-line is recieved or buffer is full
|
|
* @return return the next byte from the input stream, or EOF(-1) if no byte is
|
|
* available
|
|
*/
|
|
static int serial_getc(void)
|
|
{
|
|
int ch = -1;
|
|
|
|
return ch;
|
|
}
|
|
#endif
|
|
|
|
#if defined(UBASIC_SCRIPT_HAVE_HARDWARE_EVENTS)
|
|
static uint32_t Event_Mask;
|
|
|
|
/**
|
|
* @brief Hardware event status bit
|
|
* @param bit Event bit
|
|
* @return 1 if the event is set, 0 otherwise
|
|
*/
|
|
static int8_t hw_event(uint8_t bit)
|
|
{
|
|
if (bit < 32) {
|
|
if (Event_Mask & (1UL << bit)) {
|
|
return 1; // Event is set
|
|
}
|
|
}
|
|
|
|
return 0; // Event is not set
|
|
}
|
|
|
|
/**
|
|
* @brief Clear a hardware event state bit
|
|
* @param bit Event bit
|
|
*/
|
|
static void hw_event_clear(uint8_t bit)
|
|
{
|
|
if (bit < 32) {
|
|
Event_Mask &= ~(1UL << bit);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(UBASIC_SCRIPT_HAVE_STORE_VARS_IN_FLASH)
|
|
static uint8_t EEPROM_Buffer[256];
|
|
|
|
/**
|
|
* @brief Read some data from the EEPROM
|
|
* @param start_address EEPROM starting memory address
|
|
* @param buffer data to store
|
|
* @param length number of bytes of data to read
|
|
*/
|
|
static size_t
|
|
eepromRead(uint16_t start_address, uint8_t *buffer, uint16_t length)
|
|
{
|
|
size_t bytes_read = 0;
|
|
uint16_t i = 0;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
if (start_address + i < sizeof(EEPROM_Buffer)) {
|
|
buffer[i] = EEPROM_Buffer[start_address + i];
|
|
bytes_read++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bytes_read;
|
|
}
|
|
|
|
/**
|
|
* @brief Write some data to the EEPROM
|
|
* @param start_address EEPROM starting memory address
|
|
* @param buffer data to send
|
|
* @param length number of bytes of data
|
|
*/
|
|
static size_t
|
|
eepromWrite(uint16_t start_address, uint8_t *buffer, uint16_t length)
|
|
{
|
|
size_t bytes_written = 0;
|
|
uint16_t i = 0;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
if (start_address + i < sizeof(EEPROM_Buffer)) {
|
|
EEPROM_Buffer[start_address + i] = buffer[i];
|
|
bytes_written++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
#define UBASIC_FLASH_PAGE_SIZE 256
|
|
|
|
/**
|
|
* @brief Write a variable to the EEPROM
|
|
* @param Name Variable name
|
|
* @param Vartype Variable type
|
|
* @param datalen_bytes Data length in bytes
|
|
* @param dataptr Pointer to the data
|
|
*/
|
|
static void variable_write(
|
|
uint8_t Name, uint8_t vartype, uint8_t datalen_bytes, uint8_t *dataptr)
|
|
{
|
|
// Calculate the starting address based on variable name
|
|
uint16_t start_address = Name * UBASIC_FLASH_PAGE_SIZE;
|
|
uint8_t buffer[UBASIC_FLASH_PAGE_SIZE];
|
|
|
|
// Prepare the buffer with the variable type and data length
|
|
buffer[0] = vartype; // First byte is the variable type
|
|
buffer[1] = datalen_bytes; // Second byte is the data length
|
|
for (uint8_t i = 0; i < datalen_bytes; i++) {
|
|
buffer[i + 2] = dataptr[i]; // Copy the actual data into the buffer
|
|
}
|
|
|
|
// Write the buffer to EEPROM
|
|
eepromWrite(start_address, buffer, datalen_bytes + 2);
|
|
}
|
|
|
|
/**
|
|
* @brief Read a variable from the EEPROM
|
|
* @param Name Variable name
|
|
* @param Vartype Variable type
|
|
* @param dataptr Pointer to store the data
|
|
* @param datalen Pointer to store the data length
|
|
*/
|
|
static void
|
|
variable_read(uint8_t Name, uint8_t vartype, uint8_t *dataptr, uint8_t *datalen)
|
|
{
|
|
// Calculate the starting address based on variable name
|
|
uint8_t buffer[UBASIC_FLASH_PAGE_SIZE] = { 0 };
|
|
uint16_t start_address = Name * UBASIC_FLASH_PAGE_SIZE;
|
|
|
|
// Read the data from EEPROM
|
|
eepromRead(start_address, buffer, UBASIC_FLASH_PAGE_SIZE);
|
|
// Check if the variable type matches
|
|
if (buffer[0] == vartype) {
|
|
*datalen = buffer[1]; // Get the data length
|
|
for (uint8_t i = 0; i < *datalen; i++) {
|
|
dataptr[i] =
|
|
buffer[i + 2]; // Copy the actual data into the provided pointer
|
|
}
|
|
} else {
|
|
*datalen = 0; // If type does not match, set length to 0
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if ( \
|
|
defined(UBASIC_SCRIPT_HAVE_TICTOC_CHANNELS) || \
|
|
defined(UBASIC_SCRIPT_HAVE_SLEEP) || \
|
|
defined(UBASIC_SCRIPT_HAVE_INPUT_FROM_SERIAL))
|
|
/* defined in mstimer.h */
|
|
#endif
|
|
|
|
#if defined(UBASIC_SCRIPT_HAVE_RANDOM_NUMBER_GENERATOR)
|
|
/**
|
|
* @brief Generate a random number
|
|
* @param size Size of the random number in bits
|
|
* @return Random number size-bits wide
|
|
*/
|
|
static uint32_t random_uint32(uint8_t size)
|
|
{
|
|
uint32_t value = 0, grains = 0;
|
|
uint8_t k, i;
|
|
static bool initialized = false;
|
|
|
|
if (!initialized) {
|
|
srand(0);
|
|
initialized = true;
|
|
}
|
|
for (k = 0; k < 4; k++) {
|
|
grains = 0;
|
|
for (i = 0; i < (size >> 1); i++) {
|
|
/* Two LS bits are most likely most random */
|
|
grains |= (rand() & 0x00000003) << (2 * i);
|
|
}
|
|
value ^= grains;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
#endif
|
|
|
|
#if defined(UBASIC_SCRIPT_HAVE_PWM_CHANNELS)
|
|
static int32_t dutycycle_pwm_ch[UBASIC_SCRIPT_HAVE_PWM_CHANNELS];
|
|
|
|
/**
|
|
* @brief Configure the PWM
|
|
* @param psc Prescaler
|
|
* @param per Period
|
|
*/
|
|
static void pwm_config(uint16_t psc, uint16_t per)
|
|
{
|
|
(void)psc;
|
|
(void)per;
|
|
}
|
|
|
|
/**
|
|
* @brief Write a value to the PWM
|
|
* @param ch Channel
|
|
* @param dutycycle Duty cycle
|
|
*/
|
|
static void pwm_write(uint8_t ch, int32_t dutycycle)
|
|
{
|
|
if (ch < UBASIC_SCRIPT_HAVE_PWM_CHANNELS) {
|
|
dutycycle_pwm_ch[ch] = dutycycle;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Read a value from the PWM
|
|
* @param ch Channel
|
|
* @return Duty cycle
|
|
*/
|
|
static int32_t pwm_read(uint8_t ch)
|
|
{
|
|
if (ch < UBASIC_SCRIPT_HAVE_PWM_CHANNELS) {
|
|
return dutycycle_pwm_ch[ch];
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(UBASIC_SCRIPT_HAVE_ANALOG_READ)
|
|
/**
|
|
* @brief Configure the ADC
|
|
* @param sampletime Sample time
|
|
* @param nreads Number of reads
|
|
*/
|
|
static void adc_config(uint8_t sampletime, uint8_t nreads)
|
|
{
|
|
(void)sampletime;
|
|
(void)nreads;
|
|
}
|
|
|
|
/**
|
|
* @brief Read a value from the ADC
|
|
* @param channel Channel
|
|
* @return ADC value
|
|
*/
|
|
static int32_t adc_read(uint8_t channel)
|
|
{
|
|
return (int32_t)random_uint32(12);
|
|
}
|
|
#endif
|
|
|
|
#if defined(UBASIC_SCRIPT_HAVE_GPIO_CHANNELS)
|
|
/**
|
|
* @brief Configure the GPIO
|
|
* @param ch Channel
|
|
* @param mode Mode
|
|
* @param freq Frequency
|
|
*/
|
|
static void gpio_config(uint8_t ch, int8_t mode, uint8_t freq)
|
|
{
|
|
(void)ch;
|
|
(void)mode;
|
|
(void)freq;
|
|
}
|
|
|
|
/**
|
|
* @brief Write a value to the GPIO
|
|
* @param ch Channel
|
|
* @param pin_state Pin state
|
|
*/
|
|
static void gpio_write(uint8_t ch, uint8_t pin_state)
|
|
{
|
|
unsigned int led_index;
|
|
|
|
if (ch == 0) {
|
|
return;
|
|
}
|
|
/* adjust for zero based index */
|
|
led_index = ch - 1;
|
|
if (pin_state) {
|
|
led_on(led_index);
|
|
} else {
|
|
led_off(led_index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Read a value from the GPIO
|
|
* @param ch Channel
|
|
* @return GPIO value
|
|
*/
|
|
static int32_t gpio_read(uint8_t ch)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(UBASIC_SCRIPT_HAVE_BACNET)
|
|
/**
|
|
* @brief Create a BACnet object
|
|
* @param object_type Object type
|
|
* @param instance Object instance
|
|
* @param object_name Object name
|
|
*/
|
|
static void
|
|
bacnet_create_object(uint16_t object_type, uint32_t instance, char *object_name)
|
|
{
|
|
uint32_t r;
|
|
|
|
switch (object_type) {
|
|
case OBJECT_ANALOG_INPUT:
|
|
if (!Analog_Input_Valid_Instance(instance)) {
|
|
r = Analog_Input_Create(instance);
|
|
if (r == instance) {
|
|
Analog_Input_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
case OBJECT_ANALOG_OUTPUT:
|
|
if (!Analog_Output_Valid_Instance(instance)) {
|
|
r = Analog_Output_Create(instance);
|
|
if (r == instance) {
|
|
Analog_Output_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
case OBJECT_ANALOG_VALUE:
|
|
if (!Analog_Value_Valid_Instance(instance)) {
|
|
r = Analog_Value_Create(instance);
|
|
if (r == instance) {
|
|
Analog_Value_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_INPUT:
|
|
if (!Binary_Input_Valid_Instance(instance)) {
|
|
r = Binary_Input_Create(instance);
|
|
if (r == instance) {
|
|
Binary_Input_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_OUTPUT:
|
|
if (!Binary_Output_Valid_Instance(instance)) {
|
|
r = Binary_Output_Create(instance);
|
|
if (r == instance) {
|
|
Binary_Output_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_VALUE:
|
|
if (!Binary_Value_Valid_Instance(instance)) {
|
|
r = Binary_Value_Create(instance);
|
|
if (r == instance) {
|
|
Binary_Value_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_INPUT:
|
|
if (!Multistate_Input_Valid_Instance(instance)) {
|
|
r = Multistate_Input_Create(instance);
|
|
if (r == instance) {
|
|
Multistate_Input_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_OUTPUT:
|
|
if (!Multistate_Output_Valid_Instance(instance)) {
|
|
r = Multistate_Output_Create(instance);
|
|
if (r == instance) {
|
|
Multistate_Output_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_VALUE:
|
|
if (!Multistate_Value_Valid_Instance(instance)) {
|
|
r = Multistate_Value_Create(instance);
|
|
if (r == instance) {
|
|
Multistate_Value_Name_Set(instance, strdup(object_name));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Write a property to a BACnet object
|
|
* @param object_type Object type
|
|
* @param instance Object instance
|
|
* @param property_id Property ID
|
|
* @param value Property value
|
|
*/
|
|
static void bacnet_write_property(
|
|
uint16_t object_type,
|
|
uint32_t instance,
|
|
uint32_t property_id,
|
|
UBASIC_VARIABLE_TYPE value)
|
|
{
|
|
BACNET_BINARY_PV value_binary = BINARY_INACTIVE;
|
|
|
|
switch (object_type) {
|
|
case OBJECT_ANALOG_INPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
Analog_Input_Present_Value_Set(
|
|
instance, fixedpt_tofloat(value));
|
|
}
|
|
break;
|
|
case OBJECT_ANALOG_OUTPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
Analog_Output_Present_Value_Set(
|
|
instance, fixedpt_tofloat(value), BACNET_MAX_PRIORITY);
|
|
}
|
|
break;
|
|
case OBJECT_ANALOG_VALUE:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
Analog_Value_Present_Value_Set(
|
|
instance, fixedpt_tofloat(value), BACNET_MAX_PRIORITY);
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_INPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
if (fixedpt_toint(value) != 0) {
|
|
value_binary = BINARY_ACTIVE;
|
|
}
|
|
Binary_Input_Present_Value_Set(instance, value_binary);
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_OUTPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
if (fixedpt_toint(value) != 0) {
|
|
value_binary = BINARY_ACTIVE;
|
|
}
|
|
Binary_Output_Present_Value_Set(
|
|
instance, value_binary, BACNET_MAX_PRIORITY);
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_VALUE:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
if (fixedpt_toint(value) != 0) {
|
|
value_binary = BINARY_ACTIVE;
|
|
}
|
|
Binary_Value_Present_Value_Set(instance, value_binary);
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_INPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
Multistate_Input_Present_Value_Set(
|
|
instance, fixedpt_toint(value));
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_OUTPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
Multistate_Output_Present_Value_Set(
|
|
instance, fixedpt_toint(value), BACNET_MAX_PRIORITY);
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_VALUE:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
Multistate_Value_Present_Value_Set(
|
|
instance, fixedpt_toint(value));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Read a property from a BACnet object
|
|
* @param object_type Object type
|
|
* @param instance Object instance
|
|
* @param property_id Property ID
|
|
* @return Property value
|
|
*/
|
|
static UBASIC_VARIABLE_TYPE bacnet_read_property(
|
|
uint16_t object_type, uint32_t instance, uint32_t property_id)
|
|
{
|
|
UBASIC_VARIABLE_TYPE value = 0;
|
|
float value_float = 0.0;
|
|
BACNET_BINARY_PV value_binary = BINARY_INACTIVE;
|
|
|
|
switch (object_type) {
|
|
case OBJECT_ANALOG_INPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value_float = Analog_Input_Present_Value(instance);
|
|
value = fixedpt_fromfloat(value_float);
|
|
}
|
|
break;
|
|
case OBJECT_ANALOG_OUTPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value_float = Analog_Output_Present_Value(instance);
|
|
value = fixedpt_fromfloat(value_float);
|
|
}
|
|
break;
|
|
case OBJECT_ANALOG_VALUE:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value_float = Analog_Value_Present_Value(instance);
|
|
value = fixedpt_fromfloat(value_float);
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_INPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value_binary = Binary_Input_Present_Value(instance);
|
|
value =
|
|
fixedpt_fromint((value_binary == BINARY_ACTIVE) ? 1 : 0);
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_OUTPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value_binary = Binary_Output_Present_Value(instance);
|
|
value =
|
|
fixedpt_fromint((value_binary == BINARY_ACTIVE) ? 1 : 0);
|
|
}
|
|
break;
|
|
case OBJECT_BINARY_VALUE:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value_binary = Binary_Value_Present_Value(instance);
|
|
value =
|
|
fixedpt_fromint((value_binary == BINARY_ACTIVE) ? 1 : 0);
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_INPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value =
|
|
fixedpt_fromint(Multistate_Input_Present_Value(instance));
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_OUTPUT:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value =
|
|
fixedpt_fromint(Multistate_Output_Present_Value(instance));
|
|
}
|
|
break;
|
|
case OBJECT_MULTI_STATE_VALUE:
|
|
if (property_id == PROP_PRESENT_VALUE) {
|
|
value =
|
|
fixedpt_fromint(Multistate_Value_Present_Value(instance));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief Initialize the hardware drivers
|
|
* @param data Pointer to the ubasic data structure
|
|
*/
|
|
void ubasic_port_init(struct ubasic_data *data)
|
|
{
|
|
#if ( \
|
|
defined(UBASIC_SCRIPT_HAVE_TICTOC_CHANNELS) || \
|
|
defined(UBASIC_SCRIPT_HAVE_SLEEP) || \
|
|
defined(UBASIC_SCRIPT_HAVE_INPUT_FROM_SERIAL))
|
|
data->mstimer_now = mstimer_now;
|
|
#endif
|
|
data->variable_write = variable_write;
|
|
data->variable_read = variable_read;
|
|
#if defined(UBASIC_SCRIPT_HAVE_HARDWARE_EVENTS)
|
|
data->hw_event = hw_event;
|
|
data->hw_event_clear = hw_event_clear;
|
|
#endif
|
|
#if defined(UBASIC_SCRIPT_HAVE_PWM_CHANNELS)
|
|
data->pwm_config = pwm_config;
|
|
data->pwm_write = pwm_write;
|
|
data->pwm_read = pwm_read;
|
|
#endif
|
|
#if defined(UBASIC_SCRIPT_HAVE_PWM_CHANNELS)
|
|
data->adc_config = adc_config;
|
|
data->adc_read = adc_read;
|
|
#endif
|
|
#if defined(UBASIC_SCRIPT_HAVE_GPIO_CHANNELS)
|
|
data->gpio_config = gpio_config;
|
|
data->gpio_write = gpio_write;
|
|
data->gpio_read = gpio_read;
|
|
#endif
|
|
#if defined(UBASIC_SCRIPT_HAVE_RANDOM_NUMBER_GENERATOR)
|
|
data->random_uint32 = random_uint32;
|
|
#endif
|
|
#if defined(UBASIC_SCRIPT_HAVE_PRINT_TO_SERIAL)
|
|
data->serial_write = serial_write;
|
|
#endif
|
|
#if defined(UBASIC_SCRIPT_HAVE_INPUT_FROM_SERIAL)
|
|
data->ubasic_getc = serial_getc;
|
|
#endif
|
|
#if defined(UBASIC_SCRIPT_HAVE_BACNET)
|
|
data->bacnet_create_object = bacnet_create_object;
|
|
data->bacnet_write_property = bacnet_write_property;
|
|
data->bacnet_read_property = bacnet_read_property;
|
|
#endif
|
|
}
|