STM32 HAL库实现FreeRTOS+FreeModbus(从机和主机)

记录一次Modbus的移植过程

软件准备:
需要移植的FreeModbus源码:FreeModbus 主机和从机源码地址
移植好的FreeModbus(主从机)源码:HAL库版本的主机和从机源码地址
开发工具:Keilv5和CubeMX
硬件平台:STM32F407VET6(带RS485接口)
从机和主机的移植过程一样,这里就拿从机移植来举例
代码段大部分地方我都写了注释,需要注意的地方也单独做了说明和解释

1.用CubeMX生成Keil工程:

其他基础部分就跳过,这里讲讲需要注意的地方。
配置系统调试引脚,选择基础定时器作为FreeRTOS时钟源。
在这里插入图片描述

这里UART1作为Debug接口,设置为异步通信模式,其他默认即可。
在这里插入图片描述
UART2作为Modbus通讯接口,设置为异步通讯模式。
在这里插入图片描述开启UART2中断
在这里插入图片描述
不使用HAL的中断服务函数,因为HAL库的中断服务函数太长了,影响串口接受的性能。(嫌弃)故把这个勾勾取消掉!
在这里插入图片描述
使能FreeRTOS使用V2接口
在这里插入图片描述
添加Modbus任务
在这里插入图片描述
CubeMX生成Keli工程

2.代码移植

打开下载的源码,把Freemodbus文件全部拷贝到自己工程的根目录。
在这里插入图片描述

拷贝到工程
在这里插入图片描述
在工程中找到stm32f4xx_hal_conf.h文件,修改中断回调函数的宏定义

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

打开Freemodbus文件夹里面有modbus和port文件夹,其中port文件下的接口代码需要我们仔细修改,其余不需要理会。(有些文件结尾带_m,这些是与主机有关文件,本次是移植从机得教程,故忽略掉此类文件。)
打开port文件夹发现里面有个rtt文件夹,因为源码是基于RTT实现的。我们这里新建一个文件夹命名为FreeRTOS。把rtt里面所有文件都拷贝到里面。
先来看看FreeRTOS文件夹下的文件:

port.c:实现与上下文保护有关的接口
portevent.c:实现任务的事件通知和同步
portserial.c:实现串口的接收和发送
porttimer.c:实现一个微秒级的定时器
先实现port.c中的接口

/*进入临界段保护*/
void EnterCriticalSection(void) { taskENTER_CRITICAL(); }

void ExitCriticalSection(void) { taskEXIT_CRITICAL(); }
/*判断是否进入在中断中*/
#ifndef IS_IRQ()
extern __asm uint32_t vPortGetIPSR(void); //调用FreeRTOS API
__inline bool IS_IRQ(void) //使用内联函数提高速度
{
  if (vPortGetIPSR()) {
    return TRUE;
  }
  return FALSE;
}

实现portevent.c的接口

/* ----------------------- Variables ----------------------------------------*/
static EventGroupHandle_t xSlaveOsEvent;
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortEventInit(void) {
  xSlaveOsEvent = xEventGroupCreate();
  if (xSlaveOsEvent != NULL) {
    MODBUS_DEBUG("xMBPortEventInit Success!\r\n");
  } else {
    MODBUS_DEBUG("xMBPortEventInit Faild !\r\n");
    return FALSE;
  }
  return TRUE;
}

BOOL xMBPortEventPost(eMBEventType eEvent) {
  BaseType_t flag;
  MODBUS_DEBUG("Post eEvent=%d!\r\n", eEvent);
  if (xSlaveOsEvent != NULL) {
    if (IS_IRQ()) {
      xEventGroupSetBitsFromISR(xSlaveOsEvent, eEvent, &flag);
    } else {
      xEventGroupSetBits(xSlaveOsEvent, eEvent);
    }
  }
  return TRUE;
}

BOOL xMBPortEventGet(eMBEventType *eEvent) {
  uint32_t recvedEvent;
  /* waiting forever OS event */
  recvedEvent = xEventGroupWaitBits(xSlaveOsEvent,
                                    EV_READY | EV_FRAME_RECEIVED | EV_EXECUTE |
                                        EV_FRAME_SENT, /* 接收任务感兴趣的事件
                                                        */
                                    pdTRUE,  /* 退出时清除事件?? */
                                    pdFALSE, /* 满足感兴趣的所有事?? */
                                    portMAX_DELAY); /* 指定超时事件,无限等待 */
  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;
}

实现porttimer.c的接口,注册定时器中断回调函数
注意:使用软件定时器,软件定时器的优先级至少大于协议栈任务的优先级
在FreRTOS的FreeRTOSConfig.h中修改软件定时器的优先级(我这里图省事直接设置为48):

/* Software timer definitions. */
#define configUSE_TIMERS                         1
#define configTIMER_TASK_PRIORITY                ( 48 )
#define configTIMER_QUEUE_LENGTH                 10
#define configTIMER_TASK_STACK_DEPTH             256

porttimer.c的代码如下

/* ----------------------- static functions ---------------------------------*/
static TimerHandle_t timer;
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! 锛丗req=1Mhz
  */
  timer = xTimerCreate(
      "Slave timer",
      (50 * usTim1Timerout50us) / (1000 * 1000 / configTICK_RATE_HZ) + 1,
      pdFALSE, (void *)2, timer_timeout_ind);
  if (timer != NULL)
    return TRUE;
}

void vMBPortTimersEnable() {
  if (IS_IRQ()) {
    xTimerStartFromISR((TimerHandle_t)timer, 0);
  } else {
    xTimerStart((TimerHandle_t)timer, 0);
  }
}

void vMBPortTimersDisable() {
  if (IS_IRQ()) {
    xTimerStopFromISR((TimerHandle_t)timer, 0);
  } else {
    xTimerStop((TimerHandle_t)timer, 0);
  }
}

void prvvTIMERExpiredISR(void) { (void)pxMBPortCBTimerExpired(); }
static void timer_timeout_ind(TIM_HandleTypeDef *xTimer) {
  prvvTIMERExpiredISR();
}

实现portserial.c接口,这里是最重要的也是最复杂的一部分
在该文件中需要实现uart的的串口接收完成中断,RS485切换模式,以及一个环形缓冲队列提升串口接收效率。因为环形队列也可以在modbus主站程序中使用,所以我把环形队列放到port.c文件里。
环形队列代码如下:

/*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全部内容如下

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

static volatile uint8_t rx_buff[FIFO_SIZE_MAX];
static 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 void serial_soft_trans_irq(void *parameter);
static void Slave_TxCpltCallback(struct __UART_HandleTypeDef *huart);
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;
    MODBUS_DEBUG("Slave using uart1!\r\n");

#endif
  } else if (ucPORT == 2) {
#if defined(USING_UART2)
    extern UART_HandleTypeDef huart2;
    serial = &huart2;
    MODBUS_DEBUG("Slave using uart2!\r\n");

#endif
  } else if (ucPORT == 3) {
#if defined(USING_UART3)
    extern UART_HandleTypeDef huart3;
    serial = &huart3;
    MODBUS_DEBUG("Slave using uart3!\r\n");
#endif
  }
  /* set serial configure */

  serial->Init.StopBits = UART_STOPBITS_1;
  serial->Init.BaudRate = ulBaudRate;
  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 = xEventGroupCreate(); //创建事件
  if (NULL != event_serial) {
    MODBUS_DEBUG("Create Slave event_serial Event success!\r\n");
  } else {
    MODBUS_DEBUG("Create Slave event_serial Event  Faild!\r\n");
  }
  BaseType_t xReturn =
      xTaskCreate((TaskFunction_t)serial_soft_trans_irq, /* 任务函数*/
                  (const char *)"slave trans",           /* 任务名称*/
                  (uint16_t)128,                         /* 栈*/
                  (void *)NULL,                          /* 入口参数 */
                  (UBaseType_t)12,                       /* 优先级*/
                  (TaskHandle_t *)&thread_serial_soft_trans_irq); /*任务句柄*/

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

void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) {
	/*清除中断标志,这一步不要省略*/
   __HAL_UART_CLEAR_FLAG(serial,UART_FLAG_RXNE);
   __HAL_UART_CLEAR_FLAG(serial,UART_FLAG_TC);
  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");
    SLAVE_RS485_RX_MODE;
  } else {
    /* switch 485 to transmit mode */
    MODBUS_DEBUG("RS485_TX_MODE\r\n");
    SLAVE_RS485_TX_MODE;
    /* disable RX interrupt */
    __HAL_UART_DISABLE_IT(serial, UART_IT_RXNE);
  }
  if (xTxEnable) {
    /* start serial transmit */
    xEventGroupSetBits(event_serial, EVENT_SERIAL_TRANS_START);
  } else {
    /* stop serial transmit */
    xEventGroupClearBits(event_serial, EVENT_SERIAL_TRANS_START);
    /*测试帧数*/
    // printf("ms=%.2f,fps=%.2f\r\n", __HAL_TIM_GetCounter(&htim7) / 100.f,
    // 1000.f / (__HAL_TIM_GetCounter(&htim7) / 100.f));
  }
}

void vMBPortClose(void) { __HAL_UART_DISABLE(serial); }
/*Send a byte*/
BOOL xMBPortSerialPutByte(CHAR ucByte) {
  stm32_putc(ucByte);
  return TRUE;
}
/*Get a byte from fifo*/
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) {
  uint32_t recved_event;
  while (1) {
    /* waiting for serial transmit start */
    xEventGroupWaitBits(event_serial,             /* 事件对象句柄 */
                        EVENT_SERIAL_TRANS_START, /* 接收任务感兴趣的事件 */
                        pdFALSE,                  /* 退出时清除事件?? */
                        pdFALSE,        /* 满足感兴趣的所有事?? */
                        portMAX_DELAY); /* 指定超时事件,无限等待 */
    /* 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();
}
/*UART发送一个字节*/
static int stm32_putc(CHAR c) {
  serial->Instance->DR = c;
  while (!(serial->Instance->SR & UART_FLAG_TC))
    ;
  return TRUE;
}
/*UART接收一个字节*/
static int stm32_getc(void) {
  int ch;
  ch = -1;
  if (serial->Instance->SR & UART_FLAG_RXNE) {
    ch = serial->Instance->DR & 0xff;
  }
  return ch;
}

串口中断服务函数示例
主站和从站的串口中断服务函数是一样的

/**
  * @brief This function handles USART2 global interrupt.
  */
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);
    __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 */
}

在freertos.c文件创建任务示例
需要注意的地方:
这里最好在CubeMX生成的void MX_FREERTOS_Init(void)函数中去初始化协议栈,因为此时任务调度器是锁了的,以避免高优先级的任务在协议栈还没准备好前就先调用modbus的api。这种方式很不安全,因为协议栈里有很多同步的信号量和事件通知,若是这些信号量还没被创建就被调用,会造成程序卡死或其他未知错误。这一点在主机modbus中尤为明显,须特别注意!

osThreadId_t MasterTaskHandle;
const osThreadAttr_t MasterTask_attributes = {
    .name = "MasterTask",
    .priority = (osPriority_t)osPriorityNormal,
    .stack_size = 128 * 4};
osThreadId_t SlaveTaskHandle;
const osThreadAttr_t SlaveTask_attributes = {
    .name = "SlaveTask",
    .priority = (osPriority_t)osPriorityNormal,
    .stack_size = 128 * 4};
void MX_FREERTOS_Init(void)
{
  /* USER CODE BEGIN Init */
  /*主机部分的协议栈初始化*/
  eMBMasterInit(MB_RTU, 3, 115200, MB_PAR_NONE);
  eMBMasterEnable();
 /*从机部分的协议栈初始化*/
  eMBInit(MB_RTU, 0x01, 2, 115200, MB_PAR_NONE);
  eMBEnable();
  MasterTaskHandle = osThreadNew(MasterTask, NULL, &MasterTask_attributes); //主站的协议栈任务
  SlaveTaskHandle = osThreadNew(SlaveTask, NULL, &SlaveTask_attributes); //从站的协议栈任务
}
void SlaveTask(void *argument)
{
  for (;;)
  {
    eMBPoll();
  ]
}
void MasterTask(void *argument)
{
   for (;;)
  {
    eMBMasterPoll();
  }
}

3.移植完成!

完善了的代码已经放在文章开头了。
看下效果
每秒ping10次,一晚上ping30多万次,0个Err。舒服!
在这里插入图片描述

  • 12
    点赞
  • 111
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
### 回答1: STM32 HAL库可以与FreeRTOSFreeModbus相结合使用,以实现IAR IAR作为开发环境。 FreeRTOS是一个开源的实时操作系统,可以优化处理器资源使用,提供任务调度、时间管理、IPC等功能。STM32 HAL库提供与FreeRTOS的适配层,使得在STM32芯片上可以轻松地使用FreeRTOSFreeModbus是一个开源的Modbus通信协议栈,用于在主机从机之间进行通信。通过使用HAL库的串口驱动功能,可以实现STM32作为Modbus从机主机。 将HAL库FreeRTOS集成到IAR开发环境中,可以按照以下步骤进行: 1. 创建一个新的项目,并包括HAL库FreeRTOS的源码文件。可以从ST官网下载最新版本的HAL库FreeRTOS。 2. 配置IAR工程,包括编译器选项、链接器脚本等。确保HAL库FreeRTOS的路径正确,并添加需要的头文件和库文件。 3. 在main函数中初始化HAL库FreeRTOSHAL库提供了相应的初始化函数和配置选项,可以根据具体的需求进行设置。在FreeRTOS中,可以创建任务、定时器、队列等。 4. 在任务中添加FreeModbus库的代码。根据需要,可以实现STM32作为Modbus从机主机的功能。在从机模式下,可以使用HAL库中的串口驱动来接收和发送Modbus数据。 5. 编译和下载代码到STM32芯片。使用IAR进行编译和链接,并通过JTAG/SWD调试器将代码下载到芯片上。 通过以上步骤,即可实现STM32 HAL库FreeRTOSFreeModbus的结合使用,从而在IAR开发环境中实现相应的功能。 ### 回答2: STM32 HAL库可以与FreeRTOSFreeModbus从机主机)一起使用来开发IAR嵌入式系统。 首先,STM32 HAL库为开发者提供了许多硬件抽象层函数,可以轻松控制STM32系列微控制器的各种外设和功能。它简化了对硬件的操作,提高了开发效率。 FreeRTOS是一个流行的实时操作系统,可用于嵌入式系统的并发和调度管理。通过与STM32 HAL库的配合使用,开发者可以在STM32微控制器上运行多个任务,并使用FreeRTOS提供的任务管理功能来调度和控制任务的执行。 FreeModbus是一种用于Modbus通信协议的开源实现。它提供了主机从机两种模式,可以在STM32微控制器上实现Modbus通信。通过STM32 HAL库FreeModbus的集成,开发者可以使用STM32的串行通信外设来实现Modbus通信,并利用FreeModbus的函数来处理Modbus消息的接收和发送。 IAR是一种广泛使用的集成开发环境(IDE),用于开发嵌入式系统的软件。通过在IAR中配置STM32 HAL库FreeRTOSFreeModbus,开发者可以将它们整合在一起,并通过IAR的编译器和调试器来构建和调试嵌入式应用程序。 综上所述,开发者可以使用STM32 HAL库实现FreeRTOSFreeModbus从机主机)功能,通过IAR进行开发。这样的设计方案可以提高开发效率和可靠性,使得在STM32微控制器上开发嵌入式系统变得更加容易和高效。 ### 回答3: STM32 HAL库可以与FreeRTOSFreeModbus库一起使用来在IAR集成开发环境中实现主机从机的通信。 首先,需要在IAR环境中配置STM32 HAL库,并根据需要选择所需的外设和功能。然后,导入FreeRTOSFreeModbus库,并将其配置为HAL库的一部分。这可以通过在IAR中设置库包含路径和链接库来实现。 对于FreeRTOS实现,首先需要配置任务和中断管理器。可以使用HAL库提供的任务和中断API来创建、挂起和恢复任务,并设置任务优先级。使用HAL库提供的定时器或计数器来实现任务调度。 对于FreeModbus实现,需要配置串口或其他通信接口以与主机进行通信。可以使用HAL库提供的串口或SPI接口功能来配置通信接口。然后,可以使用FreeModbus库的函数来实现Modbus协议的从机主机功能。这些函数包括读写寄存器、处理请求和响应等。 在主机端,可以使用HAL库提供的定时器或计数器来实现Modbus主机的发送和定时功能。在从机端,可以使用HAL库提供的中断或轮询功能来处理Modbus从机的请求和响应。同时,还需要实现处理从机地址和功能码的逻辑。 最后,可以在IAR中编译、调试和烧录代码。使用HAL库FreeRTOS库和FreeModbus库的API来编写主机从机的应用程序代码。在应用程序中,可以实现与其他设备的通信,并处理数据传输和处理的逻辑。 通过使用STM32 HAL库FreeRTOSFreeModbus库,可以方便地在IAR环境中实现主机从机的通信,并实现Modbus协议的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值