From b5b2fd5b7b6c1014e3788b7dd0f50d00a755f95d Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Mon, 19 May 2025 14:57:43 -0500 Subject: [PATCH] Added multiple uBASIC program objects to stm32f4xx example port. (#995) --- ports/stm32f4xx/bacnet.c | 4 - ports/stm32f4xx/main.c | 68 ++++++++++-- ports/stm32f4xx/program-ubasic.c | 131 +++++++++++++++-------- ports/stm32f4xx/program-ubasic.h | 5 +- ports/stm32f4xx/ubasic-port.c | 28 ++--- src/bacnet/basic/object/program.c | 16 +++ src/bacnet/basic/object/program.h | 2 + src/bacnet/basic/program/ubasic/ubasic.c | 33 +++++- src/bacnet/basic/program/ubasic/ubasic.h | 11 ++ 9 files changed, 218 insertions(+), 80 deletions(-) diff --git a/ports/stm32f4xx/bacnet.c b/ports/stm32f4xx/bacnet.c index 7dd7e0ba..7ab8529e 100644 --- a/ports/stm32f4xx/bacnet.c +++ b/ports/stm32f4xx/bacnet.c @@ -21,8 +21,6 @@ #include "bacnet/iam.h" /* BACnet objects */ #include "bacnet/basic/object/device.h" -#include "bacnet/basic/object/program.h" -#include "bacnet/basic/program/ubasic/ubasic.h" /* me */ #include "bacnet.h" @@ -39,7 +37,6 @@ void bacnet_init(void) { /* initialize objects */ Device_Init(NULL); - Program_UBASIC_Init(BACNET_MAX_INSTANCE); /* set up our confirmed service unrecognized service handler - required! */ apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); /* we need to handle who-is to support dynamic device binding */ @@ -93,7 +90,6 @@ void bacnet_task(void) mstimer_reset(&DCC_Timer); dcc_timer_seconds(DCC_CYCLE_SECONDS); } - Program_UBASIC_Task(); /* handle the messaging */ pdu_len = datalink_receive(&src, &PDUBuffer[0], sizeof(PDUBuffer), 0); if (pdu_len) { diff --git a/ports/stm32f4xx/main.c b/ports/stm32f4xx/main.c index b046e3ad..f0bd661e 100644 --- a/ports/stm32f4xx/main.c +++ b/ports/stm32f4xx/main.c @@ -24,6 +24,7 @@ #include "rs485.h" #include "led.h" #include "bacnet.h" +#include "program-ubasic.h" /* MS/TP port */ static struct mstp_port_struct_t MSTP_Port; @@ -40,6 +41,53 @@ static struct dlmstp_rs485_driver RS485_Driver = { static struct dlmstp_user_data_t MSTP_User_Data; static uint8_t Input_Buffer[DLMSTP_MPDU_MAX]; static uint8_t Output_Buffer[DLMSTP_MPDU_MAX]; +static const char *UBASIC_Program_1 = + /* program listing with either \0, \n, or ';' at the end of each line. + note: indentation is not required */ + "println 'Demo - GPIO';" + ":loop;" + " dwrite(3, 1);" + " sleep (0.3);" + " dwrite(3, 0);" + " sleep (0.3);" + "goto loop;" + "end;"; +static const char *UBASIC_Program_2 = + /* program listing with either \0, \n, or ';' at the end of each line. + note: indentation is not required */ + "println 'Demo - GPIO';" + ":loop;" + " dwrite(2, 1);" + " sleep (0.5);" + " dwrite(2, 0);" + " sleep (0.1);" + "goto loop;" + "end;"; +static const char *UBASIC_Program_3 = + /* program listing with either \0, \n, or ';' at the end of each line. + note: indentation is not required */ + "println 'Demo - BACnet & GPIO';" + "bac_create(0, 1, 'ADC-1-AVG');" + "bac_create(0, 2, 'ADC-2-AVG');" + "bac_create(0, 3, 'ADC-1-RAW');" + "bac_create(0, 4, 'ADC-3-RAW');" + "bac_create(4, 1, 'LED-1');" + ":startover;" + " a = aread(1);" + " bac_write(0, 3, 85, a);" + " c = avgw(a, c, 10);" + " bac_write(0, 1, 85, c);" + " b = aread(2);" + " bac_write(0, 4, 85, b);" + " d = avgw(b, d, 10);" + " bac_write(0, 2, 85, d);" + " h = bac_read(4, 1, 85);" + " dwrite(1, (h % 2));" + " sleep (0.2);" + "goto startover;" + "end;"; +/* uBASIC data tree for each program running */ +static struct ubasic_data UBASIC_Data[3]; /** * @brief Called from _write() function from printf and friends @@ -57,8 +105,6 @@ int __io_putchar(int ch) */ int main(void) { - struct mstimer Blink_Timer; - /*At this stage the microcontroller clock setting is already configured, this is done through SystemInit() function which is called from startup file (startup_stm32f4xx.s) before to branch to application main. @@ -72,7 +118,6 @@ int main(void) mstimer_init(); led_init(); rs485_init(); - mstimer_set(&Blink_Timer, 500); /* FIXME: get the device ID from EEPROM */ Device_Set_Object_Instance_Number(103); /* seed stdlib rand() with device-id to get pseudo consistent @@ -99,10 +144,10 @@ int main(void) MSTP_Port.OutputBuffer = Output_Buffer; MSTP_Port.OutputBufferSize = sizeof(Output_Buffer); /* choose from non-volatile configuration for zero-config or slave mode */ - MSTP_Port.ZeroConfigEnabled = true; + MSTP_Port.ZeroConfigEnabled = false; MSTP_Port.Zero_Config_Preferred_Station = 0; MSTP_Port.SlaveNodeEnabled = false; - MSTP_Port.CheckAutoBaud = true; + MSTP_Port.CheckAutoBaud = false; /* user data */ MSTP_User_Data.RS485_Driver = &RS485_Driver; MSTP_Port.UserData = &MSTP_User_Data; @@ -112,7 +157,7 @@ int main(void) dlmstp_set_mac_address(255); } else { /* FIXME: get the address from hardware DIP or from EEPROM */ - dlmstp_set_mac_address(1); + dlmstp_set_mac_address(63); } if (!MSTP_Port.CheckAutoBaud) { /* FIXME: get the baud rate from hardware DIP or from EEPROM */ @@ -120,13 +165,14 @@ int main(void) } /* initialize application layer*/ bacnet_init(); + /* configure a program */ + Program_UBASIC_Init(10); + Program_UBASIC_Create(1, &UBASIC_Data[0], UBASIC_Program_1); + Program_UBASIC_Create(2, &UBASIC_Data[1], UBASIC_Program_2); + Program_UBASIC_Create(3, &UBASIC_Data[2], UBASIC_Program_3); for (;;) { - if (mstimer_expired(&Blink_Timer)) { - mstimer_reset(&Blink_Timer); - led_toggle(LED_LD3); - led_toggle(LED_RS485); - } led_task(); bacnet_task(); + Program_UBASIC_Task(); } } diff --git a/ports/stm32f4xx/program-ubasic.c b/ports/stm32f4xx/program-ubasic.c index 70a1ea9e..d381605a 100644 --- a/ports/stm32f4xx/program-ubasic.c +++ b/ports/stm32f4xx/program-ubasic.c @@ -8,37 +8,15 @@ #include #include #include +#include #include #include "bacnet/basic/object/program.h" #include "bacnet/basic/program/ubasic/ubasic.h" #include "bacnet/basic/sys/mstimer.h" -/* uBASIC-Plus program object */ -static struct ubasic_data UBASIC_DATA = { 0 }; +/* timer for the task */ static struct mstimer UBASIC_Timer; -static uint32_t UBASIC_Instance = 0; -static const char *UBASIC_Program = - /* program listing with either \0, \n, or ';' at the end of each line. - note: indentation is not required */ - "println 'Demo - BACnet & GPIO';" - "bac_create(0, 1, 'ADC-1');" - "bac_create(0, 2, 'ADC-2');" - "bac_create(4, 1, 'LED-1');" - "bac_create(4, 2, 'LED-2');" - ":startover;" - " a = aread(1);" - " c = avgw(a, c, 10);" - " bac_write(0, 1, 85, c);" - " b = aread(2);" - " d = avgw(b, d, 10);" - " bac_write(0, 2, 85, d);" - " h = bac_read(4, 1, 85);" - " dwrite(1, (h % 2));" - " i = bac_read(4, 2, 85);" - " dwrite(2, (i % 2));" - " sleep (0.2);" - "goto startover;" - "end;"; +/* context structure */ /** * @brief Load the program into the uBASIC interpreter @@ -47,9 +25,14 @@ static const char *UBASIC_Program = */ static int Program_Load(void *context) { + if (!context) { + return -1; + } struct ubasic_data *data = (struct ubasic_data *)context; - ubasic_load_program(data, UBASIC_Program); + ubasic_load_program(data, NULL); + (void)ubasic_program_location(data); + return 0; } @@ -61,6 +44,9 @@ static int Program_Load(void *context) */ static int Program_Run(void *context) { + if (!context) { + return -1; + } struct ubasic_data *data = (struct ubasic_data *)context; int result = 0; @@ -68,6 +54,7 @@ static int Program_Run(void *context) if (result <= 0) { return -1; } + (void)ubasic_program_location(data); return 0; } @@ -79,9 +66,12 @@ static int Program_Run(void *context) */ static int Program_Halt(void *context) { + if (!context) { + return -1; + } struct ubasic_data *data = (struct ubasic_data *)context; - data->status.bit.isRunning = 0; + ubasic_halt_program(data); return 0; } @@ -93,10 +83,14 @@ static int Program_Halt(void *context) */ static int Program_Restart(void *context) { + if (!context) { + return -1; + } struct ubasic_data *data = (struct ubasic_data *)context; ubasic_clear_variables(data); - ubasic_load_program(data, UBASIC_Program); + ubasic_load_program(data, NULL); + (void)ubasic_program_location(data); return 0; } @@ -108,9 +102,13 @@ static int Program_Restart(void *context) */ static int Program_Unload(void *context) { + if (!context) { + return -1; + } struct ubasic_data *data = (struct ubasic_data *)context; ubasic_clear_variables(data); + return 0; } @@ -119,29 +117,74 @@ static int Program_Unload(void *context) */ void Program_UBASIC_Task(void) { + size_t index, max_index; + uint32_t instance; + if (mstimer_expired(&UBASIC_Timer)) { mstimer_reset(&UBASIC_Timer); - Program_Timer(UBASIC_Instance, mstimer_interval(&UBASIC_Timer)); + max_index = Program_Count(); + for (index = 0; index < max_index; index++) { + instance = Program_Index_To_Instance(index); + Program_Timer(instance, mstimer_interval(&UBASIC_Timer)); + } } } /** - * @brief Initialize the uBASIC program object - * @param instance Instance number of the program object + * @brief Create one uBASIC program object + * @param requested_instance Instance number of the program object + * @param context Pointer to the uBASIC data structure + * @param program Pointer to the uBASIC program * @return 0 on success, non-zero on error */ -void Program_UBASIC_Init(uint32_t instance) +int Program_UBASIC_Create( + uint32_t requested_instance, + struct ubasic_data *context, + const char *program) { - ubasic_port_init(&UBASIC_DATA); - UBASIC_Instance = Program_Create(instance); - Program_Context_Set(UBASIC_Instance, &UBASIC_DATA); - Program_Load_Set(UBASIC_Instance, Program_Load); - Program_Run_Set(UBASIC_Instance, Program_Run); - Program_Halt_Set(UBASIC_Instance, Program_Halt); - Program_Restart_Set(UBASIC_Instance, Program_Restart); - Program_Unload_Set(UBASIC_Instance, Program_Unload); - /* auto-run the program */ - Program_Change_Set(UBASIC_Instance, PROGRAM_REQUEST_RUN); - /* start the cyclic 10ms run timer for the program object */ - mstimer_set(&UBASIC_Timer, 10); + uint32_t instance = 0; + + if (!context) { + return -1; + } + if (Program_Valid_Instance(requested_instance)) { + instance = requested_instance; + if (program) { + context->program = program; + } + Program_Change_Set(instance, PROGRAM_REQUEST_RESTART); + } else { + instance = Program_Create(requested_instance); + if (instance == BACNET_MAX_INSTANCE) { + return -1; + } + if (program) { + context->program = program; + } else { + context->program = "end;"; + } + Program_Context_Set(instance, context); + Program_Load_Set(instance, Program_Load); + Program_Run_Set(instance, Program_Run); + Program_Halt_Set(instance, Program_Halt); + Program_Restart_Set(instance, Program_Restart); + Program_Unload_Set(instance, Program_Unload); + Program_Location_Set(instance, context->location); + ubasic_port_init(context); + /* auto-run the program */ + Program_Change_Set(instance, PROGRAM_REQUEST_RUN); + } + + return 0; +} + +/** + * @brief Initialize the uBASIC program object + * @param requested_instance Instance number of the program object + * @return 0 on success, non-zero on error + */ +void Program_UBASIC_Init(unsigned long task_ms) +{ + /* start the cyclic 10ms run timer for the program object */ + mstimer_set(&UBASIC_Timer, task_ms); } diff --git a/ports/stm32f4xx/program-ubasic.h b/ports/stm32f4xx/program-ubasic.h index f095202f..32d7e5ba 100644 --- a/ports/stm32f4xx/program-ubasic.h +++ b/ports/stm32f4xx/program-ubasic.h @@ -9,13 +9,16 @@ #define PROGRAM_UBASIC_H #include #include +#include "bacnet/basic/program/ubasic/ubasic.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ void Program_UBASIC_Task(void); -void Program_UBASIC_Init(uint32_t instance); +void Program_UBASIC_Create( + uint32_t instance, struct ubasic_data *data, const char *program); +void Program_UBASIC_Init(unsigned long task_ms); #ifdef __cplusplus } diff --git a/ports/stm32f4xx/ubasic-port.c b/ports/stm32f4xx/ubasic-port.c index 4b859a74..0f64cbee 100644 --- a/ports/stm32f4xx/ubasic-port.c +++ b/ports/stm32f4xx/ubasic-port.c @@ -312,23 +312,17 @@ static void gpio_config(uint8_t ch, int8_t mode, uint8_t freq) */ static void gpio_write(uint8_t ch, uint8_t pin_state) { - switch (ch) { - case 1: - if (pin_state) { - led_on(LED_LD1); - } else { - led_off(LED_LD1); - } - break; - case 2: - if (pin_state) { - led_on(LED_LD2); - } else { - led_off(LED_LD2); - } - break; - default: - break; + 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); } } diff --git a/src/bacnet/basic/object/program.c b/src/bacnet/basic/object/program.c index 312f762c..38981f6b 100644 --- a/src/bacnet/basic/object/program.c +++ b/src/bacnet/basic/object/program.c @@ -942,6 +942,22 @@ bool Program_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) return status; } +/** + * @brief Set the context used with load, unload, run, halt, and restart + * @param object_instance [in] BACnet object instance number + * @param context [in] pointer to the context + */ +void *Program_Context_Get(uint32_t object_instance) +{ + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + return pObject->Context; + } + + return NULL; +} + /** * @brief Set the context used with load, unload, run, halt, and restart * @param object_instance [in] BACnet object instance number diff --git a/src/bacnet/basic/object/program.h b/src/bacnet/basic/object/program.h index 8fd3b826..ad48a0d7 100644 --- a/src/bacnet/basic/object/program.h +++ b/src/bacnet/basic/object/program.h @@ -114,6 +114,8 @@ void Program_Init(void); note: return value is 0 for success, non-zero for failure */ BACNET_STACK_EXPORT +void *Program_Context_Get(uint32_t object_instance); +BACNET_STACK_EXPORT void Program_Context_Set(uint32_t object_instance, void *context); BACNET_STACK_EXPORT void Program_Load_Set(uint32_t object_instance, int (*load)(void *context)); diff --git a/src/bacnet/basic/program/ubasic/ubasic.c b/src/bacnet/basic/program/ubasic/ubasic.c index a711a1df..72307691 100644 --- a/src/bacnet/basic/program/ubasic/ubasic.c +++ b/src/bacnet/basic/program/ubasic/ubasic.c @@ -562,11 +562,15 @@ void ubasic_load_program(struct ubasic_data *data, const char *program) } data->status.byte = 0x00; if (program) { - data->program_ptr = program; - tokenizer_init(&data->tree, program); + data->program = program; + } + if (data->program) { + data->program_ptr = data->program; + tokenizer_init(&data->tree, data->program_ptr); data->status.bit.isRunning = 1; } } + /*---------------------------------------------------------------------------*/ static void token_error_print(struct ubasic_data *data, UBASIC_VARIABLE_TYPE token) @@ -2809,7 +2813,7 @@ static void subsequent_statement(struct ubasic_data *data) return; } /*---------------------------------------------------------------------------*/ -static bool ubasic_program_finished(struct ubasic_data *data) +bool ubasic_program_finished(struct ubasic_data *data) { struct ubasic_tokenizer *tree = &data->tree; @@ -3015,6 +3019,29 @@ uint8_t ubasic_finished(struct ubasic_data *data) return (ubasic_program_finished(data) || data->status.bit.isRunning == 0); } +void ubasic_halt_program(struct ubasic_data *data) +{ + data->status.bit.isRunning = 0; +} + +const char *ubasic_program_location(struct ubasic_data *data) +{ + struct ubasic_tokenizer *tree = &data->tree; + char *statement_end; + if (tree->ptr) { + snprintf(data->location, sizeof(data->location), "%s", tree->ptr); + /* only return the statement until end-of-line */ + statement_end = strpbrk(data->location, ";\n"); + if (statement_end) { + *statement_end = 0; + } + } else { + snprintf(data->location, sizeof(data->location), "end"); + } + + return data->location; +} + /*---------------------------------------------------------------------------*/ void ubasic_set_variable( diff --git a/src/bacnet/basic/program/ubasic/ubasic.h b/src/bacnet/basic/program/ubasic/ubasic.h index 28261859..206ed803 100644 --- a/src/bacnet/basic/program/ubasic/ubasic.h +++ b/src/bacnet/basic/program/ubasic/ubasic.h @@ -116,7 +116,12 @@ struct ubasic_data { int16_t free_arrayptr; int16_t arrayvariable[UBASIC_VARNUM_MAX]; #endif + /* entire program */ + const char *program; + /* points to current statement */ const char *program_ptr; + /* copy of the current statement until end-of-line */ + char location[UBASIC_STATEMENT_SIZE]; uint16_t gosub_stack[UBASIC_GOSUB_STACK_DEPTH]; uint8_t gosub_stack_ptr; @@ -220,7 +225,13 @@ int32_t ubasic_run_program(struct ubasic_data *data); BACNET_STACK_EXPORT uint8_t ubasic_execute_statement(struct ubasic_data *data, char *statement); BACNET_STACK_EXPORT +void ubasic_halt_program(struct ubasic_data *data); +BACNET_STACK_EXPORT +bool ubasic_program_finished(struct ubasic_data *data); +BACNET_STACK_EXPORT uint8_t ubasic_finished(struct ubasic_data *data); +BACNET_STACK_EXPORT +const char *ubasic_program_location(struct ubasic_data *data); BACNET_STACK_EXPORT uint8_t ubasic_waiting_for_input(struct ubasic_data *data);