-
-
Notifications
You must be signed in to change notification settings - Fork 1
Backend Documentation
Many names are given to digital procedures that occur on a system in the context of multitasking. FreeRTOS calls them "tasks", POSIX calls them "threads" and so on. To not confuse a FreeRTOS task with the processes that happen in jescore, they are called "jobs". These jobs are connected to one or more FreeRTOS tasks, but they are more than just wrappers. A job exists as a struct, holding descriptions of what to do in case of invocation. Below is such a job struct:
typedef struct job_struct_t{
char name[__MAX_JOB_NAME_LEN_BYTE];
TaskHandle_t handle;
uint32_t mem_size;
uint8_t priority;
void (*function) (void* p);
char args[__MAX_JOB_ARGS_LEN_BYTE];
uint8_t is_loop;
uint8_t is_singleton;
uint8_t instances;
e_role_t role;
origin_t caller;
void* param;
jes_err_t error;
QueueHandle_t notif_queue;
SemaphoreHandle_t lock;
uint32_t timing_begin;
uint32_t timing_end;
struct job_struct_t* pn;
}job_struct_t;Notice the emphasis on a dedicated string buffer. Not only does FreeRTOS require each task to have a name for debugging and tracing, but jescore gives it a second purpose. If desired, the job can be launched via the mentioned CLI by this exact name, making CLI integration native.
This is the same handle you would expect from FreeRTOS. It has exactly that same usage.
Allocated dynamic memory for the job in bytes. Is used by FreeRTOS.
Priority of the task. Be careful to give tasks that are always active the same priority, otherwise they won't be executed in parallel! This is also used by FreeRTOS.
This is the user job you define, see your_function().
Arguments in string format. This field can be set in 3 different ways:
- Evoking the CLI with the name of a job with additional strings behind the job name will store these additional strings here.
- Launching a job with
jes_launch_job_args(). This mimics the CLI behavior, but stays purely in memory. - Calling
jes_job_set_args(). However, this can only be done by the calling job to the calling job.
These args can then be retrieved withjes_job_get_args()from inside a job and be parsed accordingly.
This is a boolean variable that the user has to set truthfully. The reason for this is CLI interaction. If a job is finite, its optional output (such as a processing message) can be printed, before the header of the CLI is reprinted, ending the transaction. If a job is infinite, ergo contains while(1), the CLI would never return if this job were to be evoked. For this reason, the header is reprinted before the infinite job is evoked.
This is a boolean variable that controls whether only one instance of this job can run at a time. If set to 1 (true), launching the job while it's already running will fail with e_err_duplicate. If set to 0 (false), multiple instances of the same job can run concurrently.
A job can be started multiple times concurrently, as long as they don't create resource sharing conflicts and is_singleton is set to 0. This variable counts how many instances of the same registered job exist at any given point in time.
This is an internal variable that describes what kind of role the job fulfills in a hierarchical context. Core jobs are listed as core-role, base jobs have their own role and finally, user jobs as well. This is important for access rights.
The caller describes the origin of the job invocation. It can take on these values:
typedef enum origin_t{
e_origin_undefined,
e_origin_interrupt,
e_origin_cli,
e_origin_core,
e_origin_api,
}origin_t;Anything called by the API functions will have the e_origin_api role, anything called by the CLI e_origin_cli and so on.
This is the content of the optional param, see jes_job_set_param().
If a system error occurs in the job that can be caught by jescore, the job will be cancelled and the error will be stored here. This is helpful for tracing issues.
Notification queue handle for inter-job communication.
Task semaphore handle for locking the job in multitasking context.
Timestamp for the beginning of a job execution. This can be used for benchmarking. Use __job_set_timing_begin() to set this manually in your job code.
Timestamp for the end of a job execution. This can be used for benchmarking. Use __job_set_timing_end() to set this manually in your job code.
Now for the coolest thing: Job-structs themselves are arranged in a linked list. As soon as you register them, they become a member of the list, which gives jescore the ability to create a journal of the specified jobs. You can search through this journal and get every job, status, resource... Whatever you wish to get. If this reminds you somewhat of system-daemons on Linux, then I have reached my goal!
jescore runs in C, that was my requirement. This means, no existing Arduino FW unifications. For this reason, I wrote my own small abstraction package for UART peripherals of the ESP32 and STM32 MCUs. The driver code for that can be found in lib/unified/uart_unif.c. This file abstracts the differences in UART peripheral config from all mentioned MCUs. It compiles differently according to the macros BUILD_FOR_STM32 and BUILD_FOR_ESP32, which are set by the detection of the automatically selected platform. Support for "generic" Arduino and AVR is planned.
For a complete and up-to-date list of all supported boards and platforms, see the dedicated Board Support page.
How a board is supported depends on its macro implementation in include/board_parser.h. Here, the hardware specific interfaces are hooked up to generic macros that then enable jescore and CLI support on the MCU. If the CLI is disabled, jescore just needs FreeRTOS. If the CLI is enabled, it needs access to the UART stream that is in some way connected to a client PC. This is different for various boards. You can add your own UART board support for these platforms.
See Adding Board Support for more verbose details on how to add a new board.
For the ESP32, this is straight forward, as the platform's UART peripheral shares ESP-IDF-framework code among the ESP32 families. If you want to use a custom UART on the ESP32, you only need to set the UART number, which is set in BASE_UART. Here's an example:
# in compile command or platformio.ini
-DJES_UART_CUSTOM
-DBASE_UART=UART_NUM_<num>where <num> is the UART peripheral index (often index 0) and JES_UART_CUSTOM is a macro that tells the linker to not use the default UART for that board.
For the STM MCUs, this is more complicated. The following macros need to be set:
#define BUILD_PLATFORM_NAME "<board type/name>"
#define USART_RCC_PERIPH RCC_PERIPHCLK_USART<num>
#define USART_CLK_SRC_DEFAULT(PeriphClkInit_struct) PeriphClkInit_struct.Usart<num>ClockSelection = __HAL_RCC_GET_USART<num>_SOURCE()
#define USART_CLK_ENABLE() __HAL_RCC_USART<num>_CLK_ENABLE()
#define USART_CLK_GPIO_ENABLE() __HAL_RCC_GPIO<port>_CLK_ENABLE()
#define USART_CLK_GPIO_DISABLE() __USART<num>_CLK_DISABLE();
#define USART_NUM USART<num>
#define USART_GPIO_TX_PORT GPIO<port>
#define USART_GPIO_RX_PORT GPIO<port>
#define USART_GPIO_TX_NUM GPIO_PIN_<pin>
#define USART_GPIO_RX_NUM GPIO_PIN_<pin>
#define USART_GPIO_TX_ALT GPIO_AF<AF num>_USART<num>
#define USART_GPIO_RX_ALT GPIO_AF<AF num>_USART<num>
#define USART_IRQn_NUM USART<num>_IRQnwhere <num> is the UART peripheral index (often index 2), <port> is the GPIO bank of the UART TX and RX <pin>s. <AF num> denotes the alternate function index and is specific to the MCU. If BUILD_PLATFORM_NAME is not set, it defaults to "STM32" without the specific family number.
Note:
uart_cfg.his only required for STM32 boards. For ESP32, theJES_UART_CUSTOMflag only requires definingBASE_UART(e.g.,-DBASE_UART=UART_NUM_1), and nouart_cfg.hfile is needed.
You can define these macros in a file called uart_cfg.h, which you include in one of your source files and build process. If you then build with the flag -DJES_UART_CUSTOM or you define that macro in your main code, the file uart_cfg.h will be included. Here is an example:
#ifndef _UART_CFG_H_
#define _UART_CFG_H_
#define USART_RCC_PERIPH RCC_PERIPHCLK_USART2
#define USART_CLK_SRC_DEFAULT(PeriphClkInit_struct) PeriphClkInit_struct.Usart2ClockSelection = __HAL_RCC_GET_USART2_SOURCE()
#define USART_CLK_ENABLE() __HAL_RCC_USART2_CLK_ENABLE()
#define USART_CLK_GPIO_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define USART_CLK_GPIO_DISABLE() __USART2_CLK_DISABLE();
#define USART_NUM USART2
#define USART_GPIO_TX_PORT GPIOA
#define USART_GPIO_RX_PORT GPIOA
#define USART_GPIO_TX_NUM GPIO_PIN_2
#define USART_GPIO_RX_NUM GPIO_PIN_15
#define USART_GPIO_TX_ALT GPIO_AF7_USART2
#define USART_GPIO_RX_ALT GPIO_AF3_USART2
#define USART_IRQn_NUM USART2_IRQn
#endif // _UART_CFG_H_