From c97a43dd6f6252db794ee74f77d9895ce15203b9 Mon Sep 17 00:00:00 2001 From: Lamdonn Date: Tue, 18 Mar 2025 21:39:48 +0800 Subject: [PATCH] Add the source code of the initial version slup and cant --- source/06_performance/cant.c | 489 ++++++++++++++++++++++ source/06_performance/cant.h | 126 ++++++ source/06_performance/slup.c | 780 +++++++++++++++++++++++++++++++++++ source/06_performance/slup.h | 205 +++++++++ source/07_math/floatl.c | 4 +- test/test.mk | 2 + test/test_cant.c | 377 +++++++++++++++++ test/test_slup.c | 294 +++++++++++++ 8 files changed, 2275 insertions(+), 2 deletions(-) create mode 100644 source/06_performance/cant.c create mode 100644 source/06_performance/cant.h create mode 100644 source/06_performance/slup.c create mode 100644 source/06_performance/slup.h create mode 100644 test/test_cant.c create mode 100644 test/test_slup.c diff --git a/source/06_performance/cant.c b/source/06_performance/cant.c new file mode 100644 index 0000000..2aab7ff --- /dev/null +++ b/source/06_performance/cant.c @@ -0,0 +1,489 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file cant.c + * \unit cant + * \brief This is a can test for C language + * \author Lamdonn + * \version v0.1.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "cant.h" + +/* Frame data base length. */ +// Constant representing the base number of bits for a CAN standard frame's data. +// It is set to 47 bits and is used to define the basic length of a CAN standard frame. +static const uint32_t CanStdFrmBaseBits = 47; /**< CAN standard frame data base length. */ +// Constant representing the base number of bits for a CAN extended frame's data. +// It is set to 67 bits and is used to define the basic length of a CAN extended frame. +static const uint32_t CanExtFrmBaseBits = 67; /**< CAN extended frame data base length. */ +// Constant representing the base number of bits for a CANFD (Controller Area Network Flexible Data Rate) standard frame's data. +// Calculated as the sum of different parts of the frame (17(SOF~BRS) + 12(ACK~IFS) + 5(ESI~DLC) + 18(CRC~DEL) + dlc * 8). +// This defines the basic length of a CANFD standard frame. +static const uint32_t CanFDStdFrmBaseBits = 17 + 12 + 5 + 18; /**< CANFD standard frame data base length, 17(SOF~BRS) + 12(ACK~IFS) + 5(ESI~DLC) + 18(CRC~DEL) + dlc * 8 */ +// Constant representing the base number of bits for a CANFD extended frame's data. +// Calculated as the sum of different parts of the frame (36(SOF~BRS) + 12(ACK~IFS) + 5(ESI~DLC) + 18(CRC~DEL) + dlc * 8). +// This defines the basic length of a CANFD extended frame. +static const uint32_t CanFDExtFrmBaseBits = 36 + 12 + 5 + 18; /**< CANFD extended frame data base length, 36(SOF~BRS) + 12(ACK~IFS) + 5(ESI~DLC) + 18(CRC~DEL) + dlc * 8 */ +// Constant representing the additional number of bits for CANFD when the Data Length Code (DLC) is greater than 16. +// In this case, the CRC changes from 17 bits to 23 bits, and this value represents that additional length. +static const uint32_t CanFDAppendBits = 4; /**< CANFD append length, when DLC is greater than 16, crc changes from 17bits to 23bits. */ + +// Array of baud rates available for the CAN bus. +// It contains different baud rate values such as 125000 (125K baud), 250000 (250K baud), etc. +// The size of the array is determined by CANT_BAUDRATE_MAX (although it's not fully clear from this code what that is exactly). +static const uint32_t baudrate_list[CANT_BAUDRATE_MAX] = +{ + 125000, // CANT_BAUDRATE_125K + 250000, // CANT_BAUDRATE_250K + 500000, // CANT_BAUDRATE_500K + 800000, // CANT_BAUDRATE_800K + 1000000, // CANT_BAUDRATE_1000K + // CANT_BAUDRATE_MAX, +}; + +/** + * \brief Calculates the CRC8 (Cyclic Redundancy Check with 8 bits) value for a given data buffer. + * \param data: Pointer to the data buffer for which the CRC8 is to be calculated. + * \param len: The length of the data buffer. + * \return The calculated CRC8 value as a uint8_t. + * + * This function calculates the CRC8 value for the input data buffer. It iterates through each byte of the data. + * For each byte, it XORs the byte with the current CRC value and then performs a series of shift and XOR operations + * based on the most significant bit of the CRC value to update the CRC. After processing all bytes, it returns the final CRC8 value. + */ +static uint8_t crc8(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 0x80) crc = (crc << 1) ^ 0x07; + else crc <<= 1; + } + } + return crc; +} + +/** + * \brief Updates the bus load statistics for a CAN (Controller Area Network) device. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \param length: The length of the data frame in bytes. + * \return CANT_E_OK on success, indicating that the statistics have been updated. + * + * This function calculates the number of bits transmitted in a CAN FD (Flexible Data Rate) standard frame + * and adds it to the `periodbits` member of the CANT structure. If the data length is greater than 16 bytes, + * it also adds the additional bits due to the change in CRC length from 17 bits to 23 bits. + */ +static int cant_statistics_busload(CANT *cant, uint16_t length) +{ + // Check if the configured baud rate is within the valid range + if (cant->config.baundrate < CANT_BAUDRATE_MAX) + { + // Calculate the total number of bits in the frame and add it to the periodbits + cant->periodbits += (CanFDStdFrmBaseBits + length * 8); + + /* When DLC is greater than 16, crc changes from 17bits to 23bits. */ + if (length > 16) + { + // Add the additional bits due to the CRC change + cant->periodbits += CanFDAppendBits; + } + } + + return CANT_E_OK; +} + +/** + * \brief Calculates the bus load percentage for a CAN device. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \return CANT_E_OK on success, indicating that the bus load has been calculated. + * + * This function calculates the bus load percentage based on the total number of bits transmitted + * during a period and the configured baud rate. It then updates the `busload` member of the CANT structure. + * If the calculated bus load exceeds 100%, it is capped at 100%. Finally, it resets the `periodbits` member. + */ +static int cant_calculate_busload(CANT *cant) +{ + // Check if the configured baud rate is within the valid range + if (cant->config.baundrate < CANT_BAUDRATE_MAX) + { + /* Count the amount of data transmitted during this cycle, including sent and received. */ + // Calculate the bus load as a percentage and scale it by 10000 + cant->busload = (uint16_t)((double)cant->periodbits / baudrate_list[cant->config.baundrate] * 10000); + // Cap the bus load at 100% + if (cant->busload > 10000) cant->busload = 10000; + // Reset the periodbits for the next cycle + cant->periodbits = 0; + } + + return CANT_E_OK; +} + +/** + * \brief Updates the dummy data parameters based on the bus load in the CAN device. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \return CANT_E_OK if the update is successful, CANT_E_DCANID if the CAN ID in the dummy data is 0. + * + * This function first checks if the CAN ID in the dummy data is valid (not 0). If it is valid, + * it compares the current bus load with the target load in the dummy data. If the bus load is less + * than the target load, it increases the 'gapcount' by a fixed resolution value. If the bus load + * is greater than the target load, it decreases the 'gapcount' by the same resolution value. + * Then it calculates the 'gapbase' as the integer part of 'gapcount' divided by 100, + * and the 'compression' as the fractional part of 'gapcount' divided by 100 minus 'gapbase'. + * Finally, it sets the 'rate' to the 'compression' value. + */ +static int cant_dummy_update(CANT *cant) +{ + const uint32_t resolution = 5; + + if (cant->dummy.canid == 0) return CANT_E_DCANID; + + /* Update the deviation value and record it in 'cant->dummy.gapcount' */ + if (cant->busload < cant->dummy.tarload) + { + if (cant->dummy.gapcount + resolution > cant->dummy.gapcount) + { + cant->dummy.gapcount += resolution; + } + } + else if (cant->busload > cant->dummy.tarload) + { + if (cant->dummy.gapcount >= resolution) + { + cant->dummy.gapcount -= resolution; + } + } + + /* Update the basic sending number and compression rate of the sending point */ + cant->dummy.gapbase = (uint32_t)(cant->dummy.gapcount / 100); + cant->dummy.compression = (double)cant->dummy.gapcount / 100.0 - cant->dummy.gapbase; + cant->dummy.rate = cant->dummy.compression; + + return CANT_E_OK; +} + +/** + * \brief Executes the dummy data sending process in the CAN device. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \return CANT_E_OK if the execution is successful. + * + * This function first calculates the number of messages to send based on the 'gapbase' + * and 'compression' values in the dummy data. It adds the 'compression' value to the 'rate'. + * If the 'rate' exceeds 1.0, it subtracts 1.0 from the 'rate' and increments the number of messages to send. + * If there are messages to send, it constructs the dummy data buffer with relevant information such as + * the current count and bus load, calculates the CRC8 value for a part of the data buffer, + * and then calls the 'cant_transmit' function to send the dummy data. After sending each message, + * it increments the current count in the dummy data. + */ +static int cant_dummy_execute(CANT *cant) +{ + uint32_t send = 0; + + /* Calculate how many messages the current sending point needs to send */ + send = cant->dummy.gapbase; + cant->dummy.rate += cant->dummy.compression; + if (cant->dummy.rate > 1.0) + { + cant->dummy.rate -= 1.0; + send++; + } + + /* Evenly 'gapCount' the difference to each sending point for sending */ + if (send > 0) + { + for (int i = 0; i < send; i++) + { + cant->dummy.data[2] = (cant->dummy.curcount >> 24) & 0xFF; + cant->dummy.data[3] = (cant->dummy.curcount >> 16) & 0xFF; + cant->dummy.data[4] = (cant->dummy.curcount >> 8) & 0xFF; + cant->dummy.data[5] = (cant->dummy.curcount) & 0xFF; + + cant->dummy.data[6] = (cant->busload >> 8) & 0xFF; + cant->dummy.data[7] = (cant->busload) & 0xFF; + + cant->dummy.data[1] = crc8(&(cant->dummy.data[2]), sizeof(cant->dummy.data) - 2); + + cant_transmit(cant, cant->dummy.canid, cant->dummy.data, sizeof(cant->dummy.data)); + + cant->dummy.curcount++; + } + } + + return CANT_E_OK; +} + +/** + * \brief Verifies the received dummy data in the CAN device. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \param canid: The CAN ID of the received data. + * \param data: Pointer to the received data buffer. + * \param length: The length of the received data buffer. + * \return CANT_E_OK if the verification is successful, CANT_E_DCANID if the CAN ID is invalid. + * + * This function first checks if the CAN ID in the verification settings is valid (not 0) + * and if it matches the received CAN ID. If not, it returns an error code. Then it extracts + * the type of the received data. If the type is 0x00, it extracts the expected CRC value, + * the count value, and the loading value from the data buffer. It calculates the CRC value + * for the relevant part of the data buffer and compares it with the expected CRC value. + * If they don't match, it increments the error count. It also checks if the received count + * value is consistent with the current count value in the verification settings. If not, + * it updates the last count value. If the verification is successful (no errors), it increments + * the verification count and updates the current count value. + */ +static int cant_dummy_verify(CANT *cant, uint32_t canid, uint8_t *data, uint16_t length) +{ + uint8_t type = 0; + uint8_t calcrc = 0; + uint8_t expcrc = 0; + uint8_t verifyfail = 0; + uint16_t loadding = 0; + uint32_t count = 0; + + if (cant->verify.canid == 0 || canid != cant->verify.canid) return CANT_E_DCANID; + + type = data[0]; + + if (type == 0x00) + { + expcrc = data[1]; + count = data[2] << 24 | data[3] << 16 | data[4] << 8 | data[5]; + loadding = data[6] << 8 | data[7]; + + calcrc = crc8(&data[2], length - 2); + if (calcrc != expcrc) + { + cant->verify.errcount++; + verifyfail++; + } + + if (count != cant->verify.curcount + 1 && cant->verify.vercount != 0) + { + int32_t lstcount = count - cant->verify.curcount - 1; + if (lstcount > 0) cant->verify.lstcount += lstcount; + } + + if (verifyfail == 0) cant->verify.vercount++; + + cant->verify.curcount = count; + } + + return CANT_E_OK; +} + +/** + * \brief Sets the target bus load for the CAN device's dummy data. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \param load: The target bus load value to be set. + * \return CANT_E_OK if the setting is successful, CANT_E_INVALID if the pointer is NULL. + * + * This function checks if the provided pointer is valid. If so, it ensures that the load + * value is within the valid range (less than or equal to 10000) and then sets the target + * bus load value in the dummy data part of the CANT structure. + */ +int cant_set_busload(CANT *cant, uint16_t load) +{ + if (!cant) return CANT_E_INVALID; + if (load >= 10000) load = 10000; + + cant->dummy.tarload = load; + + return CANT_E_OK; +} + +/** + * \brief Retrieves the target bus load for the CAN device's dummy data. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \param load: Pointer to a variable where the target bus load will be stored. + * \return CANT_E_OK if the retrieval is successful, CANT_E_INVALID if the pointer is NULL, + * CANT_E_LOAD if the provided load pointer is NULL. + * + * This function checks if the provided pointers are valid. If so, it copies the target + * bus load value from the dummy data part of the CANT structure to the provided variable. + */ +int cant_get_busload(CANT *cant, uint16_t *load) +{ + if (!cant) return CANT_E_INVALID; + if (!load) return CANT_E_LOAD; + + *load = cant->dummy.tarload; + + return CANT_E_OK; +} + +/** + * \brief Sets the CAN ID for the CAN device's dummy data. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \param canid: The CAN ID value to be set. + * \return CANT_E_OK if the setting is successful, CANT_E_INVALID if the pointer is NULL, + * CANT_E_ECANID if the provided CAN ID is 0. + * + * This function checks if the provided pointer is valid. If so, it checks if the provided + * CAN ID is not 0. If valid, it sets the CAN ID in the dummy data part of the CANT structure. + */ +int cant_set_dummy_canid(CANT *cant, uint32_t canid) +{ + if (!cant) return CANT_E_INVALID; + if (canid == 0) return CANT_E_ECANID; + + cant->dummy.canid = canid; + + return CANT_E_OK; +} + +/** + * \brief Sets the CAN ID for the verification settings in the CAN device. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \param canid: The CAN ID value to be set. + * \return CANT_E_OK if the setting is successful, CANT_E_INVALID if the pointer is NULL, + * CANT_E_ECANID if the provided CAN ID is 0. + * + * This function checks if the provided pointer is valid. If so, it checks if the provided + * CAN ID is not 0. If valid, it sets the CAN ID in the verification part of the CANT structure. + */ +int cant_set_verify_canid(CANT *cant, uint32_t canid) +{ + if (!cant) return CANT_E_INVALID; + if (canid == 0) return CANT_E_ECANID; + + cant->verify.canid = canid; + + return CANT_E_OK; +} + +/** + * \brief Transmits data over the CAN bus using the provided CAN device configuration. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \param canid: The CAN ID to which the data will be transmitted. + * \param data: Pointer to the data buffer to be transmitted. + * \param length: The length of the data buffer. + * \return CANT_E_OK if the transmission is successful, appropriate error codes otherwise. + * CANT_E_INVALID if the pointer to the CANT structure is NULL. + * CANT_E_DATA if the data pointer is NULL. + * CANT_E_TRANSMIT if the transmission function pointer in the configuration is NULL. + * CANT_E_TRANSFAIL if the actual transmission function returns a non-zero value. + * + * This function first checks the validity of the input parameters. If all parameters are valid, + * it calls the configured transmission function to send the data. If the transmission function + * returns a non-zero value, it indicates a transmission failure. After a successful transmission, + * it updates the bus load statistics for the CAN device. + */ +int cant_transmit(CANT *cant, uint32_t canid, uint8_t *data, uint16_t length) +{ + if (!cant) return CANT_E_INVALID; + if (!data) return CANT_E_DATA; + if (!cant->config.transmit) return CANT_E_TRANSMIT; + + if (cant->config.transmit(canid, data, length) != 0) return CANT_E_TRANSFAIL; + + cant_statistics_busload(cant, length); + + return CANT_E_OK; +} + +/** + * \brief Receives data over the CAN bus and performs related operations. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \param canid: The CAN ID from which the data is received. + * \param data: Pointer to the buffer where the received data will be stored. + * \param length: The length of the received data. + * \return CANT_E_OK if the operation is successful, CANT_E_INVALID if the pointer to the CANT structure is NULL. + * + * This function first checks the validity of the input pointer. If valid, it verifies the received + * dummy data using the 'cant_dummy_verify' function. Then it updates the bus load statistics. + * If the receive function pointer in the configuration is valid, it calls the receive function + * to handle the received data. + */ +int cant_receive(CANT *cant, uint32_t canid, uint8_t *data, uint16_t length) +{ + if (!cant) return CANT_E_INVALID; + cant_dummy_verify(cant, canid, data, length); + cant_statistics_busload(cant, length); + if (cant->config.receive) cant->config.receive(canid, data, length); + return CANT_E_OK; +} + +/** + * \brief Initializes the CANT structure representing the CAN device. + * \param cant: Pointer to the CANT structure to be initialized. + * \return CANT_E_OK if the initialization is successful, CANT_E_INVALID if the pointer is NULL. + * + * This function initializes various fields in the CANT structure. It sets the bus load, period bits, + * and timestamp to 0. It also initializes the dummy data and verification related fields in the + * CANT structure to their default values. For the dummy data, it sets the CAN ID, compression, + * rate, target load, gap base, gap count, current count, and initializes the data buffer. For + * the verification part, it sets the CAN ID, error count, current count, verification count, + * and last count to 0. + */ +int cant_init(CANT *cant) +{ + if (!cant) return CANT_E_INVALID; + + cant->busload = 0; + cant->periodbits = 0; + cant->timestamp = 0; + + /* Dummy data init */ + cant->dummy.canid = 0; + cant->dummy.compression = 0.0; + cant->dummy.rate = 0.0; + cant->dummy.tarload = 0; + cant->dummy.gapbase = 0; + cant->dummy.gapcount = 0; + cant->dummy.curcount = 0; + cant->dummy.data[0] = 0x00; + for (uint8_t i = 8; i < sizeof(cant->dummy.data); i++) + { + cant->dummy.data[i] = i; + } + + cant->verify.canid = 0; + cant->verify.errcount = 0; + cant->verify.curcount = 0; + cant->verify.vercount = 0; + cant->verify.lstcount = 0; + + return CANT_E_OK; +} + +/** + * \brief The main task function for the CAN device. + * \param cant: Pointer to the CANT structure representing the CAN device. + * \return CANT_E_OK if the task is executed successfully, CANT_E_INVALID if the pointer is NULL. + * + * This function is the main task handler for the CAN device. It updates the timestamp of the + * CAN device. Based on the timestamp value, it performs different operations at specific intervals. + * Every 1000 units of the timestamp, it calculates the bus load using the 'cant_calculate_busload' + * function. Every 10 units of the timestamp, it executes the dummy data sending operation using + * the 'cant_dummy_execute' function. Every 1000 units of the timestamp, it updates the dummy + * data parameters using the 'cant_dummy_update' function. + */ +int cant_task(CANT *cant) +{ + if (!cant) return CANT_E_INVALID; + + cant->timestamp += cant->config.period; + if (cant->timestamp >= 252000000) cant->timestamp = 0; + + if (cant->timestamp % 1000 == 0) + { + cant_calculate_busload(cant); + } + + if (cant->timestamp % 10 == 0) + { + cant_dummy_execute(cant); + } + + if (cant->timestamp % 1000 == 0) + { + cant_dummy_update(cant); + } + + return CANT_E_OK; +} diff --git a/source/06_performance/cant.h b/source/06_performance/cant.h new file mode 100644 index 0000000..ad17373 --- /dev/null +++ b/source/06_performance/cant.h @@ -0,0 +1,126 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file cant.h + * \unit cant + * \brief This is a can test for C language + * \author Lamdonn + * \version v0.1.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __cant_H +#define __cant_H + +#include +#include +#include +#include + +/* Version infomation */ +#define CANT_V_MAJOR 0 +#define CANT_V_MINOR 1 +#define CANT_V_PATCH 0 + +#define CANT_BAUDRATE_125K 0 +#define CANT_BAUDRATE_250K 1 +#define CANT_BAUDRATE_500K 2 +#define CANT_BAUDRATE_800K 3 +#define CANT_BAUDRATE_1000K 4 +#define CANT_BAUDRATE_MAX 5 + +#define CANT_E_OK 0 // Success +#define CANT_E_INVALID 1 // Invalid `cant` pointer +#define CANT_E_DATA 2 // Invalid `data` pointer +#define CANT_E_LEN 3 // Invalid legth +#define CANT_E_NTEST 4 // CANT load testing has not started +#define CANT_E_OVER 5 // CANT load exceeds range +#define CANT_E_LINK 6 // Invalid `link` pointer +#define CANT_E_RATE 7 // Invalid `rate` pointer +#define CANT_E_TRANSMIT 8 // Invalid `transmit` pointer +#define CANT_E_TRANSFAIL 9 // Invalid `transmit` pointer +#define CANT_E_LOAD 10 // Invalid `load` pointer +#define CANT_E_DCANID 11 // Dummy canids that do not match +#define CANT_E_ECANID 12 // Error canid + +// Function pointer type for functions related to CAN transfer. +// It takes a CAN ID (uint32_t), a pointer to data (uint8_t), and the length of the data (uint16_t) as parameters +// and returns an int. It can be used for functions like CAN transmission or reception. +typedef int (*cant_transfer_t)(uint32_t canid, uint8_t *data, uint16_t length); + +// Structure definition for the CANT (presumably related to CAN bus) structure. +typedef struct +{ + // Inner structure for configuration related to the CAN functionality. + struct + { + // Channel number of the CAN bus, represented as a uint8_t. + uint8_t channel; + // Baud rate of the CAN bus, represented as a uint8_t. + uint8_t baundrate; + // Call period for the 'cant_task()' function, represented as a uint16_t. + uint16_t period; // `cant_task()` call period + // Function pointer for CAN transmission. It points to a function that can transmit data on the CAN bus. + cant_transfer_t transmit; // can transmit the drive function + // Function pointer for CAN reception. It points to a function that can handle received data on the CAN bus. + cant_transfer_t receive; // can receive hook functions + } config; + + // Bus load value of the CAN bus, represented as a uint16_t. + uint16_t busload; + // Value related to period bits (specific meaning depends on the context), represented as a uint32_t. + uint32_t periodbits; + // Timestamp value, represented as a uint32_t. + uint32_t timestamp; + + // Inner structure for dummy data related to CAN operations. + struct + { + // CAN ID, represented as a uint32_t. + uint32_t canid; + // Compression factor, represented as a float. + float compression; + // Rate value, represented as a float. + float rate; + // Target load value, represented as a uint16_t. + uint16_t tarload; + // Base value for the gap (specific meaning depends on the context), represented as a uint32_t. + uint32_t gapbase; + // Count value for the gap (specific meaning depends on the context), represented as a uint32_t. + uint32_t gapcount; + // Current count value, represented as a uint32_t. + uint32_t curcount; + // Array to store data, with a size of 64 bytes. + uint8_t data[64]; + } dummy; + + // Inner structure for verification related data in CAN operations. + struct + { + // CAN ID, represented as a uint32_t. + uint32_t canid; + // Error count value, represented as a uint32_t. + uint32_t errcount; + // Current count value, represented as a uint32_t. + uint32_t curcount; + // Verification count value, represented as a uint32_t. + uint32_t vercount; + // Last count value, represented as a uint32_t. + uint32_t lstcount; + } verify; +} CANT; + +int cant_set_dummy_canid(CANT *cant, uint32_t canid); +int cant_set_verify_canid(CANT *cant, uint32_t canid); + +int cant_set_busload(CANT *cant, uint16_t load); +int cant_get_busload(CANT *cant, uint16_t *load); + +int cant_transmit(CANT *cant, uint32_t canid, uint8_t *data, uint16_t length); +int cant_receive(CANT *cant, uint32_t canid, uint8_t *data, uint16_t length); + +int cant_init(CANT *cant); +int cant_task(CANT *cant); + +#endif diff --git a/source/06_performance/slup.c b/source/06_performance/slup.c new file mode 100644 index 0000000..448fc5b --- /dev/null +++ b/source/06_performance/slup.c @@ -0,0 +1,780 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file slup.c + * \unit slup + * \brief This is a simple serial link universal protocol for C language + * \author Lamdonn + * \version v0.1.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "slup.h" + +/* +| HEAD | SN | FrameType | Length | Data | Check | TAIL | +*/ + +// Macro to send a character and perform some additional operations. +// It calls the putc function pointer in the slup structure to send the character 'c'. +// It also logs the character in hexadecimal format using SLUP_DEBUG and updates the upload statistics. +#define sputc(c) do { (slup->putc)(c); SLUP_DEBUG("%02x ", (c)); slup_statis_upload(slup); } while (0) + +// Macro to access the i-th element of the head mask in the SLUP configuration. +#define head(i) (slup->cfg.head[(i)]) + +// Macro to access the i-th element of the tail mask in the SLUP configuration. +#define tail(i) (slup->cfg.tail[(i)]) + +// Macro to access the i-th byte of the sequence number (sn) in the slup structure. +#define sn(i) (((uint8_t *)(&(slup->sn)))[(i)]) + +// Macro to access the i-th byte of the length variable. +#define len(i) (((uint8_t *)(&(length)))[(i)]) + +// Define states for the SLUP receive state machine. +#define SLUP_RX_STATE_HEAD 0 +#define SLUP_RX_STATE_SN 1 +#define SLUP_RX_STATE_FRAMETYPE 2 +#define SLUP_RX_STATE_LENGTH 3 +#define SLUP_RX_STATE_DATA 4 +#define SLUP_RX_STATE_CHECK 5 +#define SLUP_RX_STATE_TAIL 6 + +// Debug macro, currently defined as an empty operation. +// Can be redefined to use printf for debugging purposes. +#define SLUP_DEBUG(...) +// #define SLUP_DEBUG printf + +/** + * \brief Pushes a character into the SLUP queue. + * \param queue: Pointer to the SLUP queue structure. + * \param data: The character to be pushed into the queue. + * \return 1 if the push operation is successful, 0 otherwise. + * + * This function adds a character to the SLUP queue. If the queue is not full, + * it simply adds the character to the tail of the queue and updates the tail index and size. + * If the queue is full, it overwrites the oldest element and updates both the head and tail indices. + */ +static int slup_queue_push(SLUP_QUEUE *queue, char data) +{ + if (!queue) return 0; + + if (queue->size < SLUP_RXQUE_SIZE) + { + queue->base[queue->tail] = data; + queue->tail = (queue->tail + 1) % SLUP_RXQUE_SIZE; + queue->size++; + } + else + { + queue->base[queue->tail] = data; + queue->tail = (queue->tail + 1) % SLUP_RXQUE_SIZE; + queue->head = (queue->head + 1) % SLUP_RXQUE_SIZE; + } + + return 1; +} + +/** + * \brief Pops a specified number of elements from the SLUP queue. + * \param queue: Pointer to the SLUP queue structure. + * \param count: The number of elements to be popped from the queue. + * \return 1 if the pop operation is successful, 0 otherwise. + * + * This function removes a specified number of elements from the SLUP queue. + * If the number of elements to be popped is greater than the current size of the queue, + * it will only pop the existing elements in the queue. + */ +static int slup_queue_pop(SLUP_QUEUE *queue, uint32_t count) +{ + if (!queue) return 0; + if (queue->size == 0) return 0; + if (count == 0) return 0; + + if (count > queue->size) count = queue->size; + + queue->head = (queue->head + count) % SLUP_RXQUE_SIZE; + queue->size -= count; + + return 1; +} + +/** + * \brief Retrieves an element at a specified index from the SLUP queue. + * \param queue: Pointer to the SLUP queue structure. + * \param index: The index of the element to be retrieved. + * \return The character at the specified index if the queue is valid and not empty, 0 otherwise. + * + * This function returns the element at a given index in the SLUP queue. + * The index is calculated relative to the head of the queue. + */ +static char slup_queue_at(SLUP_QUEUE *queue, uint32_t index) +{ + if (!queue) return 0; + if (queue->size == 0) return 0; + return queue->base[((queue->head + index) % SLUP_RXQUE_SIZE)]; +} + +/** + * \brief Update the upload statistics of the SLUP structure. + * \param slup: Pointer to the SLUP structure. + * + * This function increments the 'upbits' field in the SLUP structure by 8, + * indicating that 8 bits of data have been uploaded. It is called within the 'sputc' macro + * when sending data to keep track of the amount of uploaded data. + */ +static void slup_statis_upload(SLUP *slup) +{ + slup->upbits += 8; +} + +/** + * \brief Update the download statistics of the SLUP structure. + * \param slup: Pointer to the SLUP structure. + * + * This function increments the 'downbits' field in the SLUP structure by 8, + * indicating that 8 bits of data have been downloaded. It is used to track + * the amount of data received in the SLUP communication process. + */ +static void slup_statis_download(SLUP *slup) +{ + slup->downbits += 8; +} + +/** + * \brief Receive and process an SLUP package. + * \param slup: Pointer to the SLUP structure. + * \return SLUP_E_OK: Indicates that the package has been successfully received and processed. + * + * This function first checks the 'frame' field in the 'parser' of the SLUP structure. + * If the 'frame' is 0x00, it extracts the link information from the first byte of the 'buffer'. + * Depending on the result of the bitwise AND operation between the link information and SLUP_LINK_RX, + * it updates the 'link' field in the SLUP structure. If the 'frame' is not 0x00 and the 'receive' + * function pointer in the SLUP structure is valid, it calls the 'receive' function to handle + * the received data in the 'buffer' with the specified size 'bsize'. + */ +static int slup_receive_package(SLUP *slup) +{ + if (slup->parser.frame == 0x00) + { + uint8_t link = 0; + + link = slup->buffer[0]; + + if (link & SLUP_LINK_RX) + { + slup->link |= SLUP_LINK_TX; + } + else + { + slup->link &= (~SLUP_LINK_TX); + } + } + else + { + if (slup->receive) + { + (slup->receive)(slup->buffer, slup->bsize); + } + } + return SLUP_E_OK; +} + +/** + * \brief Send an SLUP package. + * \param slup: Pointer to the SLUP structure. + * \param frame: The frame type of the package. + * \param data: Pointer to the data buffer of the package. + * \param length: The length of the data in the package. + * \return SLUP_E_OK: Indicates that the package has been successfully sent. + * SLUP_E_INVALID: Returned if the 'slup' pointer is NULL. + * SLUP_E_DATA: Returned if the 'data' pointer is NULL. + * SLUP_E_LEN: Returned if the 'length' is 0. + * + * This function constructs and sends an SLUP package. It first validates the input parameters. + * Then it sends each part of the package in order: the head, sequence number (SN), frame type, + * data length, data, checksum, and tail. The sequence number is incremented after being sent. + * The checksum is calculated using the 'check' function pointer in the SLUP structure. + */ +static int slup_send_package(SLUP *slup, uint8_t frame, uint8_t *data, uint16_t length) +{ + uint32_t i = 0; + uint32_t check = 0; + + if (!slup) return SLUP_E_INVALID; + if (!data) return SLUP_E_DATA; + if (length == 0) return SLUP_E_LEN; + + /* Send the HEAD part of the package */ + for (i = 0; i < slup->cfg.hsize; i++) + { + sputc(head(i)); + } + + /* Send the SN (Sequence Number) part of the package */ + sputc(sn(0)); + sputc(sn(1)); + sputc(sn(2)); + sputc(sn(3)); + slup->sn++; + + /* Send the FrameType part of the package */ + sputc(frame); + + /* Send the Length part of the package */ + sputc(len(0)); + sputc(len(1)); + + /* Calculate the checksum of the data */ + check = (slup->check)(data, length); + + /* Send the Data part of the package */ + for (i = 0; i < length; i++) + { + sputc(data[i]); + } + + /* Send the Check part of the package */ + for (i = 0; i < slup->cfg.csize; i++) + { + sputc((((uint8_t *)(&(check)))[(i)])); + } + + /* Send the TAIL part of the package */ + for (i = 0; i < slup->cfg.tsize; i++) + { + sputc(tail(i)); + } + + return SLUP_E_OK; +} + +/** + * \brief Parses the received data in the SLUP queue and processes it according to the SLUP protocol. + * \param slup: Pointer to the SLUP structure. + * + * This function is responsible for parsing the data in the SLUP queue. It iterates through all the + * data in the queue when there is data available. For each byte of data, it uses a state machine + * (implemented with a switch statement) to process the data based on the current state of the parser. + * The states represent different parts of the SLUP package such as the head, sequence number (SN), + * frame type, length, data, checksum, and tail. Once a complete package is parsed and verified, + * it calls the slup_receive_package function to handle the received package and resets the buffer + * and parser state for the next package. + */ +static void slup_parse_task(SLUP *slup) +{ + uint32_t i = 0; + char data = 0; + + if (slup->queue.size > 0) + { + for (i = 0; i < slup->queue.size; i++) + { + data = slup_queue_at(&slup->queue, i); + + SLUP_DEBUG("%02x ", data); + + switch (slup->parser.state) + { + case SLUP_RX_STATE_HEAD: + { + // If the current index for the head (hindex) is less than the configured head size + if (slup->parser.hindex < slup->cfg.hsize) + { + // If the received data byte matches the expected head byte at the current index + if (data == slup->cfg.head[slup->parser.hindex]) + { + // Increment the head index + slup->parser.hindex++; + + // If the head index reaches the configured head size, it means the head is fully received + if (slup->parser.hindex == slup->cfg.hsize) + { + slup->parser.hindex = 0; + SLUP_DEBUG(" [SLUP_RX_STATE_HEAD] OK\r\n"); + // Move to the next state which is to receive the sequence number (SN) + slup->parser.state = SLUP_RX_STATE_SN; + } + } + else + { + // If the received byte doesn't match, reset the head index + slup->parser.hindex = 0; + } + } + else + { + + } + } break; + case SLUP_RX_STATE_SN: + { + // If the current index for the sequence number (sindex) is less than the configured SN size + if (slup->parser.sindex < slup->cfg.ssize) + { + // Store the received data byte into the appropriate position of the parser's SN + ((uint8_t *)(&(slup->parser.sn)))[slup->parser.sindex] = data; + + // Increment the sequence number index + slup->parser.sindex++; + + // If the sequence number index reaches the configured SN size, it means the SN is fully received + if (slup->parser.sindex == slup->cfg.ssize) + { + slup->parser.sindex = 0; + SLUP_DEBUG(" [SLUP_RX_STATE_SN] OK\r\n"); + // Move to the next state which is to receive the frame type + slup->parser.state = SLUP_RX_STATE_FRAMETYPE; + } + } + else + { + + } + } break; + case SLUP_RX_STATE_FRAMETYPE: + { + // Store the received data byte as the frame type + slup->parser.frame = data; + SLUP_DEBUG(" [SLUP_RX_STATE_FRAMETYPE] OK\r\n"); + // Move to the next state which is to receive the length + slup->parser.state = SLUP_RX_STATE_LENGTH; + } break; + case SLUP_RX_STATE_LENGTH: + { + // If the current index for the length (lindex) is less than the size of a uint16_t (since length is uint16_t) + if (slup->parser.lindex < sizeof(uint16_t)) + { + // Store the received data byte into the appropriate position of the parser's length + ((uint8_t *)(&(slup->parser.length)))[slup->parser.lindex] = data; + + // Increment the length index + slup->parser.lindex++; + + // If the length index reaches the size of a uint16_t, it means the length is fully received + if (slup->parser.lindex == sizeof(uint16_t)) + { + slup->parser.lindex = 0; + SLUP_DEBUG(" [SLUP_RX_STATE_LENGTH] OK\r\n"); + // Move to the next state which is to receive the data + slup->parser.state = SLUP_RX_STATE_DATA; + } + } + else + { + + } + } break; + case SLUP_RX_STATE_DATA: + { + // Store the received data byte into the buffer and increment the buffer size + slup->buffer[slup->bsize++] = data; + // If the current index for the data (dindex) is less than the received length + if (slup->parser.dindex < slup->parser.length) + { + // Increment the data index + slup->parser.dindex++; + + // If the data index reaches the received length, it means the data is fully received + if (slup->parser.dindex == slup->parser.length) + { + slup->parser.dindex = 0; + SLUP_DEBUG(" [SLUP_RX_STATE_DATA] OK\r\n"); + // Move to the next state which is to receive the checksum + slup->parser.state = SLUP_RX_STATE_CHECK; + } + } + else + { + + } + } break; + case SLUP_RX_STATE_CHECK: + { + // If the current index for the checksum (cindex) is less than the configured checksum size + if (slup->parser.cindex < slup->cfg.csize) + { + // Store the received data byte into the appropriate position of the parser's checksum + ((uint8_t *)(&(slup->parser.check)))[slup->parser.cindex] = data; + + // Increment the checksum index + slup->parser.cindex++; + + // If the checksum index reaches the configured checksum size, it means the checksum is fully received + if (slup->parser.cindex == slup->cfg.csize) + { + slup->parser.cindex = 0; + + uint32_t cvalue = 0; + + // Calculate the checksum of the received data in the buffer + cvalue = (slup->check)(slup->buffer, slup->bsize); + + // Compare the calculated checksum with the received checksum + if (memcmp(&cvalue, &slup->parser.check, slup->cfg.csize) == 0) + { + SLUP_DEBUG(" [SLUP_RX_STATE_CHECK] OK\r\n"); + // Move to the next state which is to receive the tail + slup->parser.state = SLUP_RX_STATE_TAIL; + } + else + { + // If the checksums don't match, reset the buffer size for the next reception + slup->bsize = 0; + // Reset the parser state to start receiving a new package from the head + slup->parser.state = SLUP_RX_STATE_HEAD; + } + } + } + else + { + + } + } break; + case SLUP_RX_STATE_TAIL: + { + // If the current index for the tail (tindex) is less than the configured tail size + if (slup->parser.tindex < slup->cfg.tsize) + { + // If the received data byte matches the expected tail byte at the current index + if (data == slup->cfg.tail[slup->parser.tindex]) + { + // Increment the tail index + slup->parser.tindex++; + + // If the tail index reaches the configured tail size, it means the tail is fully received + if (slup->parser.tindex == slup->cfg.tsize) + { + slup->parser.tindex = 0; + SLUP_DEBUG(" [SLUP_RX_STATE_TAIL] OK\r\n"); + // Call the function to handle the received package + slup_receive_package(slup); + // Reset the buffer size for the next reception + slup->bsize = 0; + // Reset the parser state to start receiving a new package from the head + slup->parser.state = SLUP_RX_STATE_HEAD; + } + } + else + { + // If the received byte doesn't match, reset the tail index and the parser state to start from the head + slup->parser.tindex = 0; + slup->parser.state = SLUP_RX_STATE_HEAD; + } + } + else + { + + } + } break; + default: + break; + } + } + + // Pop all the data from the queue after processing + slup_queue_pop(&slup->queue, 0xFFFFF); + } +} + +/** + * \brief Calculate the upload and download rates and reset the bit counters. + * \param slup: Pointer to the SLUP structure. + * + * This function is responsible for calculating the upload and download rates. + * It copies the current values of 'upbits' and 'downbits' to 'uprate' and 'downrate' respectively. + * Then it resets 'upbits' and 'downbits' to zero, preparing for the next rate calculation. + */ +static void slup_calrate_task(SLUP *slup) +{ + slup->uprate = slup->upbits; + slup->downrate = slup->downbits; + slup->upbits = 0; + slup->downbits = 0; +} + +/** + * \brief Update the dummy data parameters based on the upload rate. + * \param slup: Pointer to the SLUP structure. + * + * This function updates the 'gapcount', 'gapbase', 'compression', and 'rate' fields + * in the 'dummy' structure of the SLUP object. It first calculates the deviation + * between the upload rate and the target rate. If the upload rate is less than the target, + * it increases the 'gapcount' by a fixed resolution value. If the upload rate is greater + * than the target, it decreases the 'gapcount' by the same resolution value. + * Then it calculates the 'gapbase' as the integer part of 'gapcount' divided by 100, + * and the 'compression' as the fractional part of 'gapcount' divided by 100 minus 'gapbase'. + * Finally, it sets the 'rate' to the 'compression' value. + */ +static void slup_dummy_update(SLUP *slup) +{ + const uint32_t resolution = 5; + + /* Update the deviation value and record it in 'slup->dummy.gapcount' */ + if (slup->uprate < slup->dummy.target) + { + if (slup->dummy.gapcount + resolution > slup->dummy.gapcount) + { + slup->dummy.gapcount += resolution; + } + } + else if (slup->uprate > slup->dummy.target) + { + if (slup->dummy.gapcount >= resolution) + { + slup->dummy.gapcount -= resolution; + } + } + + /* Update the basic sending number and compression rate of the sending point */ + slup->dummy.gapbase = (uint32_t)(slup->dummy.gapcount / 100); + slup->dummy.compression = (double)slup->dummy.gapcount / 100.0 - slup->dummy.gapbase; + slup->dummy.rate = slup->dummy.compression; +} + +/** + * \brief Execute the dummy data sending process based on the calculated parameters. + * \param slup: Pointer to the SLUP structure. + * + * This function calculates the number of messages to send based on the 'gapbase' + * and 'compression' values in the 'dummy' structure of the SLUP object. It adds the + * 'compression' value to the 'rate'. If the 'rate' exceeds 1.0, it subtracts 1.0 from + * the 'rate' and increments the number of messages to send. + * If there are messages to send, it calls the 'slup_send' function to send the dummy data + * in the 'dummy' structure 'send' times. + */ +static void slup_dummy_execute(SLUP *slup) +{ + uint32_t send = 0; + + /* Calculate how many messages the current sending point needs to send */ + send = slup->dummy.gapbase; + slup->dummy.rate += slup->dummy.compression; + if (slup->dummy.rate > 1.0) + { + slup->dummy.rate -= 1.0; + send++; + } + + /* Evenly 'gapCount' the difference to each sending point for sending */ + if (send > 0) + { + for (int i = 0; i < send; i++) + { + slup_send(slup, slup->dummy.data, sizeof(slup->dummy.data)); + } + } +} + +/** + * \brief Sends a heartbeat message in the SLUP protocol. + * \param slup: Pointer to the SLUP structure. + * \return SLUP_E_OK if the message is successfully sent, appropriate error code otherwise. + * + * This function constructs a heartbeat message. It sets the first byte of the data buffer + * to the current link status from the SLUP structure. Then it calls the slup_send_package + * function with a frame type of 0x00 to send the message. + */ +static int slup_send_heart(SLUP *slup) +{ + uint8_t data[SLUP_SN_MAX]; + uint16_t length = 0; + + data[0] = slup->link; + + length = 1; + + return slup_send_package(slup, 0x00, data, length); +} + +/** + * \brief Initializes the SLUP structure. + * \param slup: Pointer to the SLUP structure. + * \return SLUP_E_OK if the initialization is successful, SLUP_E_INVALID if the pointer is NULL. + * + * This function initializes various fields in the SLUP structure. It sets the buffer size (bsize), + * sequence number (sn), and timestamp to 0. It also initializes the queue's head, tail, and size to 0, + * and clears the parser structure using memset. + */ +int slup_init(SLUP *slup) +{ + if (!slup) return SLUP_E_INVALID; + + // memset(slup, 0, sizeof(SLUP)); + + slup->bsize = 0; + slup->sn = 0; + slup->timestamp = 0; + + /* Init queue */ + slup->queue.head = 0; + slup->queue.tail = 0; + slup->queue.size = 0; + + /* Init parser */ + memset(&slup->parser, 0, sizeof(SLUP_PARSER)); + + return SLUP_E_OK; +} + +/** + * \brief Sends data in the SLUP protocol with a frame type of 0x01. + * \param slup: Pointer to the SLUP structure. + * \param data: Pointer to the data buffer to be sent. + * \param length: The length of the data buffer. + * \return SLUP_E_OK if the data is successfully sent, appropriate error code otherwise. + * + * This function simply calls the slup_send_package function with a frame type of 0x01 + * to send the provided data. + */ +int slup_send(SLUP *slup, uint8_t *data, uint16_t length) +{ + return slup_send_package(slup, 0x01, data, length); +} + +/** + * \brief Handles the reception of a single character in the SLUP system. + * \param slup: Pointer to the SLUP structure. + * \param c: The received character. + * + * This function updates the link status to indicate a received character (sets SLUP_LINK_RX), + * clears the silent flag, updates the download statistics, and pushes the received character + * into the queue. + */ +void slup_getc(SLUP *slup, char c) +{ + if (!slup) return; + + slup->link |= SLUP_LINK_RX; + slup->silent = 0; + + slup_statis_download(slup); + + slup_queue_push(&slup->queue, c); +} + +/** + * \brief Retrieves the current link status from the SLUP structure. + * \param slup: Pointer to the SLUP structure. + * \param link: Pointer to a variable where the link status will be stored. + * \return SLUP_E_OK if the retrieval is successful, appropriate error code otherwise. + * + * This function checks if the provided pointers are valid. If so, it copies the current + * link status from the SLUP structure to the provided variable. + */ +int slup_link_status(SLUP *slup, uint8_t *link) +{ + if (!slup) return SLUP_E_INVALID; + if (!link) return SLUP_E_LINK; + + *link = slup->link; + + return SLUP_E_OK; +} + +/** + * \brief Retrieves the upload rate from the SLUP structure. + * \param slup: Pointer to the SLUP structure. + * \param rate: Pointer to a variable where the upload rate will be stored. + * \return SLUP_E_OK if the retrieval is successful, appropriate error code otherwise. + * + * This function checks if the provided pointers are valid. If so, it copies the current + * upload rate from the SLUP structure to the provided variable. + */ +int slup_upload_rate(SLUP *slup, uint32_t *rate) +{ + if (!slup) return SLUP_E_INVALID; + if (!rate) return SLUP_E_RATE; + + *rate = slup->uprate; + + return SLUP_E_OK; +} + +/** + * \brief Retrieves the download rate from the SLUP structure. + * \param slup: Pointer to the SLUP structure. + * \param rate: Pointer to a variable where the download rate will be stored. + * \return SLUP_E_OK if the retrieval is successful, appropriate error code otherwise. + * + * This function checks if the provided pointers are valid. If so, it copies the current + * download rate from the SLUP structure to the provided variable. + */ +int slup_download_rate(SLUP *slup, uint32_t *rate) +{ + if (!slup) return SLUP_E_INVALID; + if (!rate) return SLUP_E_RATE; + + *rate = slup->downrate; + + return SLUP_E_OK; +} + +/** + * \brief Sets the target rate for the dummy data in the SLUP structure. + * \param slup: Pointer to the SLUP structure. + * \param rate: The target rate value to be set. + * \return SLUP_E_OK if the setting is successful, SLUP_E_INVALID if the pointer is NULL. + * + * This function checks if the provided pointer is valid. If so, it sets the target rate + * in the dummy data part of the SLUP structure to the provided value. + */ +int slup_set_dummy(SLUP *slup, uint32_t rate) +{ + if (!slup) return SLUP_E_INVALID; + + slup->dummy.target = rate; + + return SLUP_E_OK; +} + +/** + * \brief The main task function for the SLUP system. + * \param slup: Pointer to the SLUP structure. + * + * This function is the main task handler for the SLUP system. It updates the timestamp, + * and based on the timestamp value, performs various operations such as sending heartbeat + * messages, calculating rates, executing dummy data sending, and updating dummy data parameters. + * It also calls the slup_parse_task function to parse the received data in the queue. + */ +void slup_task(SLUP *slup) +{ + if (!slup) return; + + slup->timestamp += slup->period; + if (slup->timestamp >= 252000000) slup->timestamp = 0; + + if (slup->timestamp % 500 == 0) + { + if (slup->silent < 3) + { + slup->silent++; + } + else + { + slup->link &= (~SLUP_LINK_RX); + } + + slup_send_heart(slup); + } + + if (slup->timestamp % 1000 == 0) + { + slup_calrate_task(slup); + } + + if (slup->timestamp % 10 == 0) + { + slup_dummy_execute(slup); + } + + if (slup->timestamp % 1000 == 0) + { + slup_dummy_update(slup); + } + + slup_parse_task(slup); +} + diff --git a/source/06_performance/slup.h b/source/06_performance/slup.h new file mode 100644 index 0000000..0cb98dc --- /dev/null +++ b/source/06_performance/slup.h @@ -0,0 +1,205 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file slup.h + * \unit slup + * \brief This is a simple serial link universal protocol for C language + * \author Lamdonn + * \version v0.1.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __slup_H +#define __slup_H + +#include +#include +#include +#include + +/* Version infomation */ +#define SLUP_V_MAJOR 0 +#define SLUP_V_MINOR 1 +#define SLUP_V_PATCH 0 + +// Maximum size of the head part in the SLUP structure, set to 4 bytes. +#define SLUP_HEAD_MAX 4 +// Maximum size of the tail part in the SLUP structure, set to 4 bytes. +#define SLUP_TAIL_MAX 4 +// Maximum size of the check code part in the SLUP structure, set to 4 bytes. +#define SLUP_CHECK_MAX 4 +// Maximum size of the sequence number (SN) part in the SLUP structure, set to 4 bytes. +#define SLUP_SN_MAX 4 +// Maximum size of the frame in the SLUP structure, set to 4095 bytes. +#define SLUP_FRAME_MAX 4095 +// Size of the receive queue in the SLUP structure, set to 1024 bytes. +#define SLUP_RXQUE_SIZE 1024 + +// Success code, indicating that an operation has been completed successfully. +#define SLUP_E_OK 0 +// Error code, indicating that the 'slup' pointer is invalid. +#define SLUP_E_INVALID 1 +// Error code, indicating that the 'data' pointer is invalid. +#define SLUP_E_DATA 2 +// Error code, indicating that the length value is invalid. +#define SLUP_E_LEN 3 +// Error code, indicating that the SLUP load testing has not started. +#define SLUP_E_NTEST 4 +// Error code, indicating that the SLUP load exceeds the defined range. +#define SLUP_E_OVER 5 +// Error code, indicating that the 'link' pointer is invalid. +#define SLUP_E_LINK 6 +// Error code, indicating that the 'rate' pointer is invalid. +#define SLUP_E_RATE 7 + +// Link status value representing the link is in the down state. +#define SLUP_LINK_DOWN 0x00 +// Link status value representing the link is in the transmit (TX) state. +#define SLUP_LINK_TX 0x01 +// Link status value representing the link is in the receive (RX) state. +#define SLUP_LINK_RX 0x02 +// Link status value representing the link is in the up state. +#define SLUP_LINK_UP 0x03 + +// Function pointer type for a function that writes a character. +// It takes a character 'c' as an argument and returns an 'int'. +typedef int (*slup_putc_t)(char c); + +// Function pointer type for a function that checks data. +// It takes a pointer to an array of 'uint8_t' data and its length (in 'uint16_t') as arguments +// and returns a 'uint32_t'. +typedef uint32_t (*slup_check_t)(uint8_t *data, uint16_t length); + +// Function pointer type for a function that receives data. +// It takes a pointer to an array of 'uint8_t' data and its length (in 'uint16_t') as arguments +// and returns an 'int'. +typedef int (*slup_receive_t)(uint8_t *data, uint16_t length); + +// Structure definition for SLUP configuration. +typedef struct +{ + // Array to store the head mask, with a maximum size defined by SLUP_HEAD_MAX. + uint8_t head[SLUP_HEAD_MAX]; /**< head mask */ + // Array to store the tail mask, with a maximum size defined by SLUP_TAIL_MAX. + uint8_t tail[SLUP_TAIL_MAX]; /**< tail mask */ + // Size of the head part, stored in 4 bits. + uint8_t hsize : 4; /**< head size */ + // Size of the tail part, stored in 4 bits. + uint8_t tsize : 4; /**< tail size */ + // Size of the check code part, stored in 4 bits. + uint8_t csize : 4; /**< check code size */ + // Size of the sequence number (sn) part, stored in 4 bits. + uint8_t ssize : 4; /**< sn size */ +} SLUP_CFG; + +/* queue type define */ +// Structure definition for an SLUP queue. +typedef struct +{ + // Array that serves as the base address for the data in the queue, + // with a size defined by SLUP_RXQUE_SIZE. + char base[SLUP_RXQUE_SIZE]; /* base address of data */ + // Total size of the queue. + uint32_t size; /* size of queue */ + // Index of the queue head. + uint32_t head; /* index of queue head */ + // Index of the queue tail. + uint32_t tail; /* index of queue tail */ +} SLUP_QUEUE; + +// Structure definition for an SLUP parser. +typedef struct +{ + // Current state of the parser. + uint8_t state; + // Index related to the head part, stored in 4 bits. + uint8_t hindex : 4; + // Index related to the tail part, stored in 4 bits. + uint8_t tindex : 4; + // Index related to the check code part, stored in 4 bits. + uint8_t cindex : 4; + // Index related to the sequence number (sn) part, stored in 4 bits. + uint8_t sindex : 4; + // Index related to the length part, stored in 2 bits. + uint8_t lindex : 2; + // Flag indicating the frame status. + uint8_t frame; + // Length of the data being processed. + uint16_t length; + // Index within the data. + uint16_t dindex; + // Check value for the data. + uint32_t check; + // Sequence number. + uint32_t sn; +} SLUP_PARSER; + +// Structure definition for an SLUP dummy data structure. +typedef struct +{ + // Compression factor, represented as a floating-point number. + float compression; + // Rate value, represented as a floating-point number. + float rate; + // Target value, represented as a 32-bit unsigned integer. + uint32_t target; + // Base value for the gap, represented as a 32-bit unsigned integer. + uint32_t gapbase; + // Count value for the gap, represented as a 32-bit unsigned integer. + uint32_t gapcount; + // Array to store data, with a size of 64 bytes. + uint8_t data[64]; +} SLUP_DUMMY; + +// Structure definition for the main SLUP structure that combines multiple components. +typedef struct +{ + // SLUP configuration structure. + SLUP_CFG cfg; + // SLUP queue structure. + SLUP_QUEUE queue; + // SLUP parser structure. + SLUP_PARSER parser; + // SLUP dummy data structure. + SLUP_DUMMY dummy; + // Buffer to store data, with a maximum size defined by SLUP_FRAME_MAX. + uint8_t buffer[SLUP_FRAME_MAX]; + // Size of the buffer. + uint32_t bsize; + // Sequence number. + uint32_t sn; + // Link status flag. + uint8_t link; + // Silent status flag. + uint8_t silent; + // Period value, represented as a 16-bit unsigned integer. + uint16_t period; + // Timestamp value, represented as a 32-bit unsigned integer. + uint32_t timestamp; + // Number of bits for the up direction, represented as a 32-bit unsigned integer. + uint32_t upbits; + // Number of bits for the down direction, represented as a 32-bit unsigned integer. + uint32_t downbits; + // Rate value for the up direction, represented as a 32-bit unsigned integer. + uint32_t uprate; + // Rate value for the down direction, represented as a 32-bit unsigned integer. + uint32_t downrate; + // Function pointer of type 'slup_putc_t' for writing a character. + slup_putc_t putc; + // Function pointer of type 'slup_check_t' for checking data. + slup_check_t check; + // Function pointer of type 'slup_receive_t' for receiving data. + slup_receive_t receive; +} SLUP; + +int slup_init(SLUP *slup); +int slup_send(SLUP *slup, uint8_t *data, uint16_t length); +int slup_link_status(SLUP *slup, uint8_t *link); +int slup_upload_rate(SLUP *slup, uint32_t *rate); +int slup_download_rate(SLUP *slup, uint32_t *rate); +int slup_set_dummy(SLUP *slup, uint32_t rate); +void slup_getc(SLUP *slup, char c); +void slup_task(SLUP *slup); + +#endif diff --git a/source/07_math/floatl.c b/source/07_math/floatl.c index 3b00657..21b3882 100644 --- a/source/07_math/floatl.c +++ b/source/07_math/floatl.c @@ -3075,7 +3075,7 @@ static char floatlIntLowChar(floatl *num) // Get the character of the lowest - order digit of the integer part c = gChar(*num); // Divide the 'floatl' number by 10 to move to the next digit - *num = floatl_div(*num, floatl_from_f(10.0)); + *num = floatl_div(*num, floatl_from_d(10.0)); } return c; @@ -3101,7 +3101,7 @@ static char floatlDitHighChar(floatl *num) if (ST1(temp)) // The decimal part is not 0 { // Multiply the 'floatl' number by 10 to get the next digit - temp = floatl_mul(temp, floatl_from_f(10.0)); + temp = floatl_mul(temp, floatl_from_d(10.0)); // Get the character of the highest - order digit of the new number c = gChar(temp); // Update the original 'floatl' number diff --git a/test/test.mk b/test/test.mk index 61a6aaa..f64539f 100644 --- a/test/test.mk +++ b/test/test.mk @@ -41,6 +41,8 @@ TEST_LIST += floatl TEST_LIST += flmath TEST_LIST += ramt TEST_LIST += romt +TEST_LIST += cant +TEST_LIST += slup # TEST_LIST += cpul TEST_LIST += date TEST_LIST += unitt diff --git a/test/test_cant.c b/test/test_cant.c new file mode 100644 index 0000000..82ef0d8 --- /dev/null +++ b/test/test_cant.c @@ -0,0 +1,377 @@ +#include +#include +#include + +#if defined(TEST_TARGET_cant) +#include +#include +#include +#include +#else +#include "init.h" +#include "command.h" +#include "unitt.h" +#include "kern.h" +#include "cant.h" +#include "cQueue.h" +#endif + +/************************************************************************************/ +/************************************* Unit Test ************************************/ +/************************************************************************************/ + +// #define EXIT_TEST +extern uint64_t unitt_clock(void); + +static int test_0(void) +{ + for (int i = 0; i < 100; i++) + { + if (0) + { + + #if defined (EXIT_TEST) + exit(0); + #endif + return UNITT_E_FAIL; + } + } + + return UNITT_E_OK; +} + +static void unitt_task(void) +{ + static UNITT_TCASE rand_tests[] = { + UNITT_TCASE(test_0), + // UNITT_TCASE(test_1), + // UNITT_TCASE(test_2), + }; + + static UNITT suites[] = { + { "xxx suite", rand_tests, sizeof(rand_tests) / sizeof(rand_tests[0]) , unitt_clock }, + }; + + UNITT_EXE(suites); +} + +/************************************************************************************/ +/************************************* Base Test ************************************/ +/************************************************************************************/ + +///////////////////////////////// +//// Simulating CAN BUS ///// +///////////////////////////////// + +typedef int (*canbus_receive_t)(uint32_t canid, uint8_t *data, uint16_t length); + +typedef struct +{ + uint8_t devid; + canbus_receive_t receive; +} CanDevType; + +typedef struct +{ + uint8_t devid; + uint16_t length; + uint32_t canid; + uint8_t data[64]; +} CanTpType; + +typedef struct +{ + CanDevType devs[8]; + uint8_t devn; + cQueue(CanTpType, 1024) tp; +} CanBusType; + +static CanBusType canbus; +static uint8_t canbus_maxdev = (uint8_t)(sizeof(canbus.devs) / sizeof(canbus.devs[0])); + +static int canbus_attach_dev(uint8_t devid, canbus_receive_t receive) +{ + if (devid >= canbus_maxdev) return -1; + if (!receive) return -2; + + canbus.devs[devid].devid = devid; + canbus.devs[devid].receive = receive; + + return 0; +} + +static int canbus_transer(uint8_t devid, uint32_t canid, uint8_t *data, uint16_t length) +{ + CanTpType tp; + int ret = 0; + + if (devid >= canbus_maxdev) return -1; + if (canbus.devs[devid].devid == 0xFF) return -3; + if (!data) return -4; + if (length > 64) return -5; + + tp.devid = devid; + tp.length = length; + tp.canid = canid; + for (uint16_t i = 0; i < length; i++) tp.data[i] = data[i]; + + ret = cQueue_push(canbus.tp, tp); + if (!ret) return -6; + + return 0; +} + +static void canbus_init() +{ + memset(&canbus, 0, sizeof(canbus)); + + for (uint8_t devid = 0; devid < canbus_maxdev; devid++) + { + canbus.devs[devid].devid = 0xFF; + } + + cQueue_init(canbus.tp); +} + +static void canbus_task() +{ + CanTpType tp; + + while (cQueue_pop(canbus.tp, tp)) + { + for (uint8_t devid = 0; devid < canbus_maxdev; devid++) + { + if (canbus.devs[devid].devid == 0xFF) continue; + if (canbus.devs[devid].devid == tp.devid) continue; + + if (canbus.devs[devid].receive) + { + canbus.devs[devid].receive(tp.canid, tp.data, tp.length); + } + } + } +} + +///////////////////////////////// +//// Simulating CAN DEV ///// +///////////////////////////////// + +static CANT cant0; +static CANT cant1; + +static int dev0_can_transmit(uint32_t canid, uint8_t *data, uint16_t length) +{ + int ret = canbus_transer(0, canid, data, length); + return ret; +} + +static int dev1_can_transmit(uint32_t canid, uint8_t *data, uint16_t length) +{ + int ret = canbus_transer(1, canid, data, length); + return ret; +} + +static int dev0_can_receive(uint32_t canid, uint8_t *data, uint16_t length) +{ +#if 0 + printf("[DEV0][%08x] ", canid); + for (int i = 0; i < length; i++) + { + printf("%02x ", data[i]); + } + printf("\n"); +#endif + cant_receive(&cant0, canid, data, length); + return 0; +} + +static int dev1_can_receive(uint32_t canid, uint8_t *data, uint16_t length) +{ +#if 0 + printf("[DEV1][%08x] ", canid); + for (int i = 0; i < length; i++) + { + printf("%02x ", data[i]); + } + printf("\n"); +#endif + cant_receive(&cant1, canid, data, length); + return 0; +} + +///////////////////////////////// +///// CANT ////////// +///////////////////////////////// + +static CANT cant0 = { + .config = { + .channel = 0, + .baundrate = CANT_BAUDRATE_500K, + .period = 5, + .transmit = dev0_can_transmit, + .receive = NULL, + }, +}; + +static CANT cant1 = { + .config = { + .channel = 0, + .baundrate = CANT_BAUDRATE_500K, + .period = 5, + .transmit = dev1_can_transmit, + .receive = NULL, + }, +}; + +static void dev0_cant_task(void) +{ + static uint32_t count = 0; + + count += 5; + if (count >= 25200000) count = 0; + + if (count % 5 == 0) + { + cant_task(&cant0); + } + + if (count % 1000 == 0) + { + static uint8_t data[64]; + static uint32_t sc = 0; + *(uint32_t *)data = sc++; + cant_transmit(&cant0, 0x75, data, 64); + + printf("road = %d\r\n", cant0.busload); + } +} + +static void dev1_cant_task(void) +{ + cant_task(&cant1); +} + +static void test_base(void) +{ + printf("cant test!\r\n"); + + /* can bus init */ + canbus_init(); + task_create(5, canbus_task); + + /* can dev init */ + canbus_attach_dev(0, dev0_can_receive); + canbus_attach_dev(1, dev1_can_receive); + + /* cant protcol init */ + cant_init(&cant0); + cant_init(&cant1); + task_create(5, dev0_cant_task); + task_create(5, dev1_cant_task); + + cant_set_dummy_canid(&cant0, 0x777); + cant_set_busload(&cant0, 3000); +} + +/************************************************************************************/ +/************************************* Command ************************************/ +/************************************************************************************/ + +static void usage(void) +{ + printf( +"Usage: cant [opt] [arg] ...\n" +"\n" +"options:\n" +" -e Specifies the function to execute, the default is the test\n" +" Test base function\n" +" Unit test\n" +" -h Print help\n" +" -v Print version\n" +" -u [] Unit test period, unit ms, the default is 1000ms\n" +"\n" + + ); +} + +static int test(int argc, char *argv[]) +{ + char *execute = NULL; + int ut_period = 1000; + int period = 1000; // ms + + /* reset getopt */ + command_opt_init(); + + while (1) + { + int opt = command_getopt(argc, argv, "e:hvu::p:"); + if (opt == -1) break; + + switch (opt) + { + case 'p' : + period = atoi(command_optarg); + break; + case 'u' : + if (command_optarg) ut_period = atoi(command_optarg); + break; + case 'e' : + execute = command_optarg; + break; + case 'v' : + printf("cant version %d.%d.%d\r\n", CANT_V_MAJOR, CANT_V_MINOR, CANT_V_PATCH); + return 0; + case '?': + printf("Unknown option `%c`\r\n", command_optopt); + return -1; + case 'h' : + default: + usage(); + return 0; + } + } + + if (execute) + { + if (!strcmp(execute, "base")) + { + test_base(); + } + else if (!strcmp(execute, "ut")) + { + #if defined(TEST_TARGET_cant) + while (1) + { + unitt_task(); + usleep(1000 * ut_period); + } + #else + printf("create task %d\r\n", task_create(ut_period, unitt_task)); + #endif + } + } + else + { + test_base(); + } + + return 0; +} + +/************************************************************************************/ +/************************************ Test entry ************************************/ +/************************************************************************************/ + +#if defined(TEST_TARGET_cant) +int main(int argc, char *argv[]) +{ + return test(argc, argv); +} +#else +void test_cant(void) +{ + command_export("cant", test); +} +init_export_app(test_cant); +#endif diff --git a/test/test_slup.c b/test/test_slup.c new file mode 100644 index 0000000..3f184cb --- /dev/null +++ b/test/test_slup.c @@ -0,0 +1,294 @@ +#include +#include +#include + +#if defined(TEST_TARGET_slup) +#include +#include +#include +#include +#else +#include "init.h" +#include "command.h" +#include "unitt.h" +#include "kern.h" +#include "slup.h" +#include "cQueue.h" +#endif + +/************************************************************************************/ +/************************************* Unit Test ************************************/ +/************************************************************************************/ + +// #define EXIT_TEST +extern uint64_t unitt_clock(void); + +static int test_0(void) +{ + for (int i = 0; i < 100; i++) + { + if (0) + { + + #if defined (EXIT_TEST) + exit(0); + #endif + return UNITT_E_FAIL; + } + } + + return UNITT_E_OK; +} + +static void unitt_task(void) +{ + static UNITT_TCASE rand_tests[] = { + UNITT_TCASE(test_0), + // UNITT_TCASE(test_1), + // UNITT_TCASE(test_2), + }; + + static UNITT suites[] = { + { "xxx suite", rand_tests, sizeof(rand_tests) / sizeof(rand_tests[0]) , unitt_clock }, + }; + + UNITT_EXE(suites); +} + +/************************************************************************************/ +/************************************* Base Test ************************************/ +/************************************************************************************/ + +//////////// sim uart dev //////////// +typedef void (*uart_rx_t)(char c); +static cQueue(char, 1024) qdev0; +static cQueue(char, 1024) qdev1; +static uart_rx_t dev0_rx = NULL; +static uart_rx_t dev1_rx = NULL; +static int dev0_uart_send(char c) { cQueue_push(qdev0, c); } +static int dev1_uart_send(char c) { cQueue_push(qdev1, c); } +static void dev0_uart_rxhandle(void) { char c = 0; while (cQueue_pop(qdev1, c)) { if (dev0_rx) dev0_rx(c); } } +static void dev1_uart_rxhandle(void) { char c = 0; while (cQueue_pop(qdev0, c)) { if (dev1_rx) dev1_rx(c); } } +static void dev0_uart_setrx(uart_rx_t rx) { dev0_rx = rx; } +static void dev1_uart_setrx(uart_rx_t rx) { dev1_rx = rx; } +////////////////////////////////////// + +static uint32_t crc8(uint8_t* data, uint16_t len); +static int dev0_slup_receive(uint8_t *data, uint16_t length); +static int dev1_slup_receive(uint8_t *data, uint16_t length); + +static SLUP slup0 = { + .cfg = { + .head = {'H', 'E', 'A', 'D'}, + .tail = {'T', 'A', 'I', 'L'}, + .hsize = SLUP_HEAD_MAX, + .tsize = SLUP_TAIL_MAX, + .csize = sizeof(uint8_t), + .ssize = sizeof(uint32_t), + }, + .period = 5, + .putc = dev0_uart_send, + .check = crc8, + .receive = dev0_slup_receive, +}; +static SLUP slup1 = { + .cfg = { + .head = {'H', 'E', 'A', 'D'}, + .tail = {'T', 'A', 'I', 'L'}, + .hsize = SLUP_HEAD_MAX, + .tsize = SLUP_TAIL_MAX, + .csize = sizeof(uint8_t), + .ssize = sizeof(uint32_t), + }, + .period = 5, + .putc = dev1_uart_send, + .check = crc8, + .receive = dev1_slup_receive, +}; + +static void dev0_uart_receive(char c) +{ + slup_getc(&slup0, c); +} + +static void dev1_uart_receive(char c) +{ + slup_getc(&slup1, c); +} + +static uint32_t crc8(uint8_t* data, uint16_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 0x80) crc = (crc << 1) ^ 0x07; + else crc <<= 1; + } + } + return (uint32_t)crc; +} + +static int dev0_slup_receive(uint8_t *data, uint16_t length) +{ + printf("length %d\r\n", length); + return 0; +} + +static int dev1_slup_receive(uint8_t *data, uint16_t length) +{ + printf("length %d\r\n", length); + printf("%s\r\n", data); + return 0; +} + +static void dev0_slup_task(void) +{ + static uint32_t count = 0; + + count += 5; + if (count >= 25200000) count = 0; + + if (count % 5 == 0) + { + slup_task(&slup0); + } + + if (count % 1000 == 0) + { + uint8_t link = 0; + slup_link_status(&slup0, &link); + printf("dev0[%02x] send...\r\n", link); + slup_send(&slup0, "0123456789", 10); + } +} + +static void dev1_slup_task(void) +{ + slup_task(&slup1); +} + +static void test_base(void) +{ + printf("slup test!\r\n"); + + /* uart dev init */ + cQueue_init(qdev0); + cQueue_init(qdev1); + dev0_uart_setrx(dev0_uart_receive); + dev1_uart_setrx(dev1_uart_receive); + task_create(5, dev0_uart_rxhandle); + task_create(5, dev1_uart_rxhandle); + + /* slup protcol init */ + slup_init(&slup0); + slup_init(&slup1); + task_create(5, dev0_slup_task); + task_create(5, dev1_slup_task); +} + +/************************************************************************************/ +/************************************* Command ************************************/ +/************************************************************************************/ + +static void usage(void) +{ + printf( +"Usage: slup [opt] [arg] ...\n" +"\n" +"options:\n" +" -e Specifies the function to execute, the default is the test\n" +" Test base function\n" +" Unit test\n" +" -h Print help\n" +" -v Print version\n" +" -u [] Unit test period, unit ms, the default is 1000ms\n" +"\n" + + ); +} + +static int test(int argc, char *argv[]) +{ + char *execute = NULL; + int ut_period = 1000; + int period = 1000; // ms + + /* reset getopt */ + command_opt_init(); + + while (1) + { + int opt = command_getopt(argc, argv, "e:hvu::p:"); + if (opt == -1) break; + + switch (opt) + { + case 'p' : + period = atoi(command_optarg); + break; + case 'u' : + if (command_optarg) ut_period = atoi(command_optarg); + break; + case 'e' : + execute = command_optarg; + break; + case 'v' : + printf("slup version %d.%d.%d\r\n", SLUP_V_MAJOR, SLUP_V_MINOR, SLUP_V_PATCH); + return 0; + case '?': + printf("Unknown option `%c`\r\n", command_optopt); + return -1; + case 'h' : + default: + usage(); + return 0; + } + } + + if (execute) + { + if (!strcmp(execute, "base")) + { + test_base(); + } + else if (!strcmp(execute, "ut")) + { + #if defined(TEST_TARGET_slup) + while (1) + { + unitt_task(); + usleep(1000 * ut_period); + } + #else + printf("create task %d\r\n", task_create(ut_period, unitt_task)); + #endif + } + } + else + { + test_base(); + } + + return 0; +} + +/************************************************************************************/ +/************************************ Test entry ************************************/ +/************************************************************************************/ + +#if defined(TEST_TARGET_slup) +int main(int argc, char *argv[]) +{ + return test(argc, argv); +} +#else +void test_slup(void) +{ + command_export("slup", test); +} +init_export_app(test_slup); +#endif