完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
软件准备:
需要移植的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!rn"); } else { MODBUS_DEBUG("xMBPortEventInit Faild !rn"); return FALSE; } return TRUE; } BOOL xMBPortEventPost(eMBEventType eEvent) { BaseType_t flag; MODBUS_DEBUG("Post eEvent=%d!rn", 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!rn"); #endif } else if (ucPORT == 2) { #if defined(USING_UART2) extern UART_HandleTypeDef huart2; serial = &huart2; MODBUS_DEBUG("Slave using uart2!rn"); #endif } else if (ucPORT == 3) { #if defined(USING_UART3) extern UART_HandleTypeDef huart3; serial = &huart3; MODBUS_DEBUG("Slave using uart3!rn"); #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!rn"); } else { MODBUS_DEBUG("Create Slave event_serial Event Faild!rn"); } 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 successrn"); } 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_MODErn"); SLAVE_RS485_RX_MODE; } else { /* switch 485 to transmit mode */ MODBUS_DEBUG("RS485_TX_MODErn"); 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=%.2frn", __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。舒服! |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1595 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1531 浏览 1 评论
967 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
679 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1578 浏览 2 评论
1860浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
634浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
512浏览 3评论
522浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
498浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-18 16:15 , Processed in 0.793173 second(s), Total 78, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号