debevv / nanoMODBUS

A compact MODBUS RTU/TCP C library for embedded/microcontrollers
MIT License
282 stars 58 forks source link

two small changes to support native hardware features in embedded (for example hardware DMA and CRC in STM32) #55

Open kpvnnov opened 3 months ago

kpvnnov commented 3 months ago

In order not to send data from one buffer to another, but to immediately receive data where it is needed (for example, in DMA mode), the buf_rec index is needed in structure nmbs_t.msg, it is used in the nmbs_platform_conf.read function and nmbs_crc_calc requires the __weak attribute so that this function can be replaced with hardware calculation of the modbus CRC, a polynomial for which is also available in some embedded systems, including STM32 code examples for stm32 using HAL are attached

kpvnnov commented 3 months ago

in HAL STM functions configurations are declared as __Weak to make override possible in case of other implementations in user file and in headers files difined as

if defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) / ARM Compiler V6 /

ifndef __weak

#define __weak  __attribute__((weak))

endif

ifndef __packed

#define __packed  __attribute__((packed))

endif

elif defined ( GNUC ) && !defined (__CC_ARM) / GNU Compiler /

ifndef __weak

#define __weak   __attribute__((weak))

endif / __weak /

ifndef __packed

#define __packed __attribute__((__packed__))

endif / __packed /

endif / GNUC /

kpvnnov commented 3 months ago

or do we need any other way to use our crc16 function to use the hardware capabilities of calculating the СRC as described in this application note an4187 https://www.st.com/resource/en/application_note/an4187-using-the-crc-peripheral-on-stm32-microcontrollers-stmicroelectronics.pdf

debevv commented 2 months ago

Hi, thanks for the PR.

I don't know if the time spent on calculating the CRC in a typical modbus application justifies doing it via DMA, but I think giving the user the possibility to override the CRC calc function won't hurt. The only thing I would change is, instead of redefining the symbol, using the already established system of defining a function pointer inside the nmbs_platform_conf struct. I will take care of that.

Regarding the STM32 example, can you provide a way for the user to build it? I know on micros it's a bit messy to setup a shareable build environment, but even a simple makefile could be enough. Maybe one where the user must define the path to the STM32 SDK (which I guess should be the only thing needed to build this)

kpvnnov commented 2 months ago

this helps to reduce the cost of the product. you can take a cheaper processor that can handle processing, for example, six streams (ports) at once at high speed (modbus gateway). I will make a ready-made example with a makefile a little later.

debevv commented 1 week ago

Implemented in https://github.com/debevv/nanoMODBUS/pull/64

marcocipriani01 commented 1 week ago

Just for other people's reference, here's how I implemented hardware CRC calculation on my STM32H723ZG:

CRC_HandleTypeDef hcrc;
hcrc.Instance = CRC;
hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_DISABLE;
hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_DISABLE;
hcrc.Init.GeneratingPolynomial = 0x8005;
hcrc.Init.CRCLength = CRC_POLYLENGTH_16B;
hcrc.Init.InitValue = 0xFFFF;
hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_BYTE;
hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_ENABLE;
hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES;
if (HAL_CRC_Init(&hcrc) != HAL_OK) {
    Error_Handler();
}
uint16_t modbusHardwareCRC(const uint8_t* data, uint32_t length, void* arg) {
    const uint16_t crc = HAL_CRC_Calculate(&hcrc, (uint32_t*) data, length);
    return ((uint16_t) (crc << 8)) | ((uint16_t) (crc >> 8));
}

In my testing, hardware CRC was ~10 times faster than software CRC. The only drawback of hardware CRC is that it needs to be protected by a mutex if multiple tasks need it, which slows things down significantly. Here's my workaround, which is still, on average, ~5 times faster than software CRC:

uint16_t modbusHardwareCRC(const uint8_t* data, uint32_t length, void* arg) {
    if (osMutexAcquire(crcMutex, 0UL) == osOK) {
        if (HAL_CRC_GetState(&hcrc) == HAL_CRC_STATE_READY) {
            const uint16_t crc = HAL_CRC_Calculate(&hcrc, (uint32_t*) data, length);
            osMutexRelease(crcMutex);
            return ((uint16_t) (crc << 8)) | ((uint16_t) (crc >> 8));
        }
        osMutexRelease(crcMutex);
        return nmbs_crc_calc(data, length, arg);
    }
    return nmbs_crc_calc(data, length, arg);
}

I got Modbus over TCP working with the lwIP network stack of the STM32. I'll be testing UART with DMA soon. I'll see what I can share. So far, the library has been rock solid during testing.

debevv commented 1 week ago

@marcocipriani01 yes, an STM32 example with lwIP and a custom CRC function would be very useful