STM32 HAL library realizes FreeRTOS+FreeModbus (slave)

Posted by Dvorak on Tue, 08 Feb 2022 00:53:16 +0100

Software preparation:
FreeModbus source code: Source code address of FreeModbus master and slave
Development tools: Keilv5 and CubeMX
Hardware platform: STM32F407VET6 (with RS485 interface)

1. Generate Keil project with CubeMX:

Skip other basic parts. Here are some points that need attention.
Configure the system debugging pin and select the basic timer as the FreeRTOS clock source.

Configure a basic timer for modbus detection response timeout and data frame synchronization. According to the standard, the interval between each frame of modbus must be at least 3.5 bytes, which is about 1.8ms at 115200 baud rate. Therefore, here, the timer frequency is set to 1Mhz and interrupt is enabled.

Here, UART1 is used as the Debug interface and is set to asynchronous communication mode. Others can be set by default.

UART2, as a Modbus communication interface, is set to asynchronous communication mode.
Turn on UART2 interrupt

The interrupt service function of HAL is not used because the interrupt service function of HAL library is too long, which affects the performance of serial port acceptance. (dislike) so cancel this check!

Enable FreeRTOS to use V2 interface

Add Modbus task

Generate cubeli project

Code migration

Open the downloaded source code and copy all Freemodbus files to the root directory of your project.

Copy to project

Found stm32f4xx in project_ hal_ Conf.h file to modify the macro definition of interrupt callback function

#define  USE_HAL_TIM_REGISTER_CALLBACKS         1U /* TIM register callback disabled       */
#define  USE_HAL_UART_REGISTER_CALLBACKS        1U /* UART register callback disabled      */

Open the Freemodbus folder, where there are modbus and port folders. The interface code under the port file needs to be carefully modified, and the rest need not be ignored. (some files end with _m, which are related to the host. This is a tutorial for porting the slave, so such files are ignored.)
Open the port folder and find a RTT folder, because the source code is based on RTT. Let's create a new folder named FreeRTOS. Copy all the files in RTT into it.
Let's first look at the files in the FreeRTOS folder:
port.c: Implement the interface related to context protection
portevent.c: Realize event notification and synchronization of tasks
portserial.c: Realize the receiving and sending of serial port
porttimer.c: Implement a microsecond timer
First implement port Interface in C

void EnterCriticalSection(void)
{
  taskENTER_CRITICAL();
}

void ExitCriticalSection(void)
{
  taskEXIT_CRITICAL();
}

Implement portevent C interface

/* ----------------------- Variables ----------------------------------------*/
static osEventFlagsId_t xSlaveOsEvent;
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortEventInit(void)
{
    //xSlaveOsEvent = xEventGroupCreate();
    xSlaveOsEvent = osEventFlagsNew(NULL);
    if (xSlaveOsEvent == NULL)
    {
        MODBUS_DEBUG("xMBPortEventInit ERR=%d!\r\n", xSlaveOsEvent);
      return FALSE;
    }
    return TRUE;
}

BOOL xMBPortEventPost(eMBEventType eEvent)
{
    MODBUS_DEBUG("set enevt=%d!\r\n", eEvent);
    osEventFlagsSet(xSlaveOsEvent, eEvent);
    return TRUE;
}

BOOL xMBPortEventGet(eMBEventType *eEvent)
{
    uint32_t recvedEvent;
    /* waiting forever OS event */
    MODBUS_DEBUG("wait for enevt...\r\n");
    recvedEvent = osEventFlagsWait(xSlaveOsEvent, EV_READY | EV_FRAME_RECEIVED | EV_EXECUTE | EV_FRAME_SENT,
                                   osFlagsWaitAny, osWaitForever);
    MODBUS_DEBUG("recieved enevt=%d\r\n", recvedEvent);
    switch (recvedEvent)
    {
    case EV_READY:
        *eEvent = EV_READY;
        break;
    case EV_FRAME_RECEIVED:
        *eEvent = EV_FRAME_RECEIVED;
        break;
    case EV_EXECUTE:
        *eEvent = EV_EXECUTE;
        break;
    case EV_FRAME_SENT:
        *eEvent = EV_FRAME_SENT;
        break;
    }
    return TRUE;
}

Implement porttimer H interface, register timer interrupt callback function

/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR(void);
static void timer_timeout_ind(TIM_HandleTypeDef *xTimer);

/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{
    /*
    Freertos can't create timer in isr!
    So,I use hardware timer here! !Freq=1Mhz
    */
    MODBUS_DEBUG("Init timer!\r\n");
    __HAL_TIM_SetAutoreload(&htim7, 50 * (usTim1Timerout50us + 1));
    HAL_TIM_RegisterCallback(&htim7, HAL_TIM_PERIOD_ELAPSED_CB_ID, timer_timeout_ind);
    return TRUE;
}
/*Start timer*/
void vMBPortTimersEnable()
{
    //MODBUS_DEBUG("Start timer!\r\n");
    HAL_TIM_Base_Stop_IT(&htim7);
    __HAL_TIM_SetCounter(&htim7, 0);
    HAL_TIM_Base_Start_IT(&htim7);
}

void vMBPortTimersDisable()
{
    //MODBUS_DEBUG("Stop timer!\r\n");
    HAL_TIM_Base_Stop_IT(&htim7);
}

void prvvTIMERExpiredISR(void)
{
    (void)pxMBPortCBTimerExpired();
}
static void timer_timeout_ind(TIM_HandleTypeDef *xTimer)
{
    //MODBUS_DEBUG("Timer callback!\r\n");
    HAL_TIM_Base_Stop_IT(&htim7);
    prvvTIMERExpiredISR();
}
// static void timer_timeout_ind(TimerHandle_t xTimer)
// {
//     MODBUS_DEBUG("Timer callback!\r\n");
//     prvvTIMERExpiredISR();
// }

Implement portserial H interface, here is the most important and complex part
In this file, the serial port receiving completion interrupt of uart, RS485 switching mode and a ring buffer queue need to be realized to improve the serial port receiving efficiency.
The ring queue code is as follows:

/*put  bytes in buff*/
void Put_in_fifo(Serial_fifo *buff, uint8_t *putdata, int length)
{
  portDISABLE_INTERRUPTS();
  while (length--)
  {
    buff->buffer[buff->put_index] = *putdata;
    buff->put_index += 1;
    if (buff->put_index >= MB_SIZE_MAX)
      buff->put_index = 0;
    /* if the next position is read index, discard this 'read char' */
    if (buff->put_index == buff->get_index)
    {
      buff->get_index += 1;
      if (buff->get_index >= MB_SIZE_MAX)
        buff->get_index = 0;
    }
  }
  portENABLE_INTERRUPTS();
}
/*get  bytes from buff*/
int Get_from_fifo(Serial_fifo *buff, uint8_t *getdata, int length)
{
  int size = length;
  /* read from software FIFO */
  while (length)
  {
    int ch;
    /* disable interrupt */
    portDISABLE_INTERRUPTS();
    if (buff->get_index != buff->put_index)
    {
      ch = buff->buffer[buff->get_index];
      buff->get_index += 1;
      if (buff->get_index >= MB_SIZE_MAX)
        buff->get_index = 0;
    }
    else
    {
      /* no data, enable interrupt and break out */
      portENABLE_INTERRUPTS();
      break;
    }
    *getdata = ch & 0xff;
    getdata++;
    length--;
    /* enable interrupt */
    portENABLE_INTERRUPTS();
  }
  return size - length;
}

portserial.c all contents are as follows:

/* ----------------------- Static variables ---------------------------------*/
/* software simulation serial transmit IRQ handler thread */
static TaskHandle_t thread_serial_soft_trans_irq = NULL;
/* serial event */
static osEventFlagsId_t event_serial;
/* modbus slave serial device */
static UART_HandleTypeDef *serial;
/*
 * Serial FIFO mode 
 */

volatile uint8_t rx_buff[MB_SIZE_MAX];
Serial_fifo Slave_serial_rx_fifo;
/* ----------------------- Defines ------------------------------------------*/
/* serial transmit event */
#define EVENT_SERIAL_TRANS_START (1 << 0)

/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR(void);
static void prvvUARTRxISR(void);
//static rt_err_t serial_rx_ind(rt_device_t dev, rt_size_t size);
static void serial_soft_trans_irq(void *parameter);
static void Slave_RxCpltCallback(struct __UART_HandleTypeDef *huart);
static int stm32_getc(void);
static int stm32_putc(char c);
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,
                       eMBParity eParity)
{
    /**
     * set 485 mode receive and transmit control IO
     * @note MODBUS_SLAVE_RT_CONTROL_PIN_INDEX need be defined by user
     */
    //rt_pin_mode(MODBUS_SLAVE_RT_CONTROL_PIN_INDEX, PIN_MODE_OUTPUT);

    /* set serial name */
    if (ucPORT == 1)
    {
#if defined(USING_UART1)
        extern UART_HandleTypeDef huart1;
        serial = &huart1;
#endif
    }
    else if (ucPORT == 2)
    {
#if defined(USING_UART2)
        extern UART_HandleTypeDef huart2;
        serial = &huart2;
        serial->Instance = USART2;
        MODBUS_DEBUG("Slave using uart2!\r\n");

#endif
    }
    else if (ucPORT == 3)
    {
#if defined(USING_UART3)
        extern UART_HandleTypeDef huart3;
        serial = &huart3;
#endif
    }
    /* set serial configure */
    serial->Init.BaudRate = ulBaudRate;
    serial->Init.StopBits = UART_STOPBITS_1;
    switch (eParity)
    {
    case MB_PAR_NONE:
    {
        serial->Init.WordLength = UART_WORDLENGTH_8B;
        serial->Init.Parity = UART_PARITY_NONE;
        break;
    }
    case MB_PAR_ODD:
    {
        serial->Init.WordLength = UART_WORDLENGTH_9B;
        serial->Init.Parity = UART_PARITY_ODD;
        break;
    }
    case MB_PAR_EVEN:
    {
        serial->Init.WordLength = UART_WORDLENGTH_9B;
        serial->Init.Parity = UART_PARITY_EVEN;
        break;
    }
    }
    if (HAL_UART_Init(serial) != HAL_OK)
    {
        Error_Handler();
    }
    __HAL_UART_DISABLE_IT(serial, UART_IT_RXNE);
    __HAL_UART_DISABLE_IT(serial, UART_IT_TC);
    /*registe recieve callback*/
    HAL_UART_RegisterCallback(serial, HAL_UART_RX_COMPLETE_CB_ID, Slave_RxCpltCallback);

    /* software initialize */
    Slave_serial_rx_fifo.buffer = rx_buff;
    Slave_serial_rx_fifo.get_index = 0;
    Slave_serial_rx_fifo.put_index = 0;
    //event_serial = osEventFlagsNew(NULL); // id cannot be created with CMSISv2 interface here
    /* Create master send task */
    BaseType_t xReturn = xTaskCreate((TaskFunction_t)serial_soft_trans_irq,          /* Task entry function */
                                     (const char *)"slave trans",                    /* Task name */
                                     (uint16_t)512,                                  /* Task stack size */
                                     (void *)NULL,                                   /* Task entry function parameters */
                                     (UBaseType_t)5,                                 /* Priority of task */
                                     (TaskHandle_t *)&thread_serial_soft_trans_irq); /*Task control block pointer */

    if (xReturn == pdPASS)
    {
        MODBUS_DEBUG("xTaskCreate slave trans success\r\n");
    }
    return TRUE;
}

void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{

    if (xRxEnable)
    {
        /* enable RX interrupt */
        __HAL_UART_ENABLE_IT(serial, UART_IT_RXNE);
        /* switch 485 to receive mode */
        MODBUS_DEBUG("RS485_RX_MODE\r\n");
        RS485_RX_MODE;
    }
    else
    {
        /* switch 485 to transmit mode */
        MODBUS_DEBUG("RS485_TX_MODE\r\n");
        RS485_TX_MODE;
        /* disable RX interrupt */
        __HAL_UART_DISABLE_IT(serial, UART_IT_RXNE);
    }
    if (xTxEnable)
    {
        /* start serial transmit */
        osEventFlagsSet(event_serial, EVENT_SERIAL_TRANS_START);
    }
    else
    {
        /* stop serial transmit */
        osEventFlagsClear(event_serial, EVENT_SERIAL_TRANS_START);
    }
}

void vMBPortClose(void)
{
    __HAL_UART_DISABLE(serial);
}

BOOL xMBPortSerialPutByte(CHAR ucByte)
{
    stm32_putc(ucByte);
    return TRUE;
}

BOOL xMBPortSerialGetByte(CHAR *pucByte)
{
    Get_from_fifo(&Slave_serial_rx_fifo, (uint8_t*)pucByte, 1);
    return TRUE;
}

/*
 * Create an interrupt handler for the transmit buffer empty interrupt
 * (or an equivalent) for your target processor. This function should then
 * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
 * a new character can be sent. The protocol stack will then call
 * xMBPortSerialPutByte( ) to send the character.
 */
void prvvUARTTxReadyISR(void)
{
    pxMBFrameCBTransmitterEmpty();
}

/*
 * Create an interrupt handler for the receive interrupt for your target
 * processor. This function should then call pxMBFrameCBByteReceived( ). The
 * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
 * character.
 */
void prvvUARTRxISR(void)
{
    pxMBFrameCBByteReceived();
}

/**
 * Software simulation serial transmit IRQ handler.
 *
 * @param parameter parameter
 */
static void serial_soft_trans_irq(void *parameter)
{
    /*Create UARTTX event handle */
    event_serial=osEventFlagsNew(NULL);
    if (NULL != event_serial)
    {
        MODBUS_DEBUG("Slave event_serial Event creat success id=%d\r\n", event_serial);
    }
    else
    {
        MODBUS_DEBUG("Slave event_serial Event creat faild err=%d\r\n", event_serial);
    }
    while (1)
    {
        /* waiting for serial transmit start */
        osEventFlagsWait(event_serial, EVENT_SERIAL_TRANS_START, osFlagsWaitAny | osFlagsNoClear, osWaitForever);
        /* execute modbus callback */
        prvvUARTTxReadyISR();
    }
}

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void Slave_RxCpltCallback(UART_HandleTypeDef *huart)
{
    int ch = -1;
    while (1)
    {
        ch = stm32_getc();
        if (ch == -1)
            break;
        Put_in_fifo(&Slave_serial_rx_fifo, (uint8_t *)&ch, 1);
    }
    prvvUARTRxISR();
}

static int stm32_putc(char c)
{
    serial->Instance->DR = c;
    while (!(serial->Instance->SR & UART_FLAG_TC))
        ;
    return TRUE;
}

static int stm32_getc(void)
{
    int ch;
    ch = -1;
    if (serial->Instance->SR & UART_FLAG_RXNE)
    {
        ch = serial->Instance->DR & 0xff;
    }
    return ch;
}

Preparation of UART interrupt service function (again, do not use HAL interrupt callback function, otherwise there will be problems in receiving variable length data and low efficiency)

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
  if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))
  {
    huart2.RxCpltCallback(&huart2);
    __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE);
  }
  if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_ORE))
  {
    uint16_t pucByte = (uint16_t)((&huart2)->Instance->DR & (uint16_t)0x01FF);//The data overflow is interrupted, and the data needs to be read and cleared
    __HAL_UART_CLEAR_OREFLAG(&huart2);
  }
  if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC))
  {
    __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TC);
  }

  /* USER CODE END USART2_IRQn 0 */
  /* USER CODE BEGIN USART2_IRQn 1 */
  /* USER CODE END USART2_IRQn 1 */
}

At FreeRTOS Create the following tasks in the C file

/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  eMBInit(MB_RTU, 0x01, 2, 115200, MB_PAR_NONE);
  eMBEnable();
  /* Infinite loop */
  for (;;)
  {
    eMBPoll();
  }
  /* USER CODE END StartDefaultTask */
}

Migration complete!

When the comments are improved, paste the source code address.
See the effect
Ping 10 times per second, more than 300000 times a night, 0 Err. Comfortable!

Topics: Embedded system Single-Chip Microcomputer stm32 modbus