С недавних пор я начал заниматься встраиваемыми системами и докатился до программирования микроконтроллеров, а именно STM32F373. Одной из задач было развернуть Modbus Slave RTU поверх интерфейса rs485.
Поскольку сроки поджимали было принято решение взять что-нибудь готовое, чем я и занялся. Недолгое гугление навело меня на библиотеку FreeModbus, а вот тут началась боль с которой, я надеюсь, вы не столкнетесь.
0. Готовим проект
Для быстрого старта я использую STM32CubeMX, он позволяет быстро генерить код инициализации контроллера без боли. В моем случае это STM32F373VCTx LQFP100.
Переферия:
- TIM6
- USART1
Делаю это только для генерации функций HAL_TIM_Base_MspInit()
и HAL_UART_MspInit()
, это упростит вам жизнь, если будете подключать другие таймеры и usart’ы.
Тактирование:
Нам необходимо, чтобы таймер TIM6 или тот, который вы выбрали работал на частоте не ниже 20кГц. Пошуршав по коду инициализации я понял, что TIM6 работает на PCLK1 (Peripheral CLocK). По умолчанию он (таймер) тактируется от HSI (High Speed Internal resonator), так же как и все остальное, но мне показалось, что это маловато, поэтому я погнал все это дело через PLL (Phase-Locked Loop) и выставил значение множителя на x8, чтобы поднять до 32МГц, так МК работает плавнее и приятнее.
Дополнительная конфигурация:
Нам потребуется работать с таймером по прерыванию, поэтому включим его на вкладке Configuration раздел System NVIC «TIM6 global interrupt and DAC1 underrun error interrupts». Также нам нужны прерывания от USART1 «USART1 global interrupt / USART1 wake-up interrupt through EXTI line 25».
Я использую много дополнительных прерываний для индикации ошибок, поэтому не обращайте внимание на кучу галочек.
На этом заканчиваем, сохраняем проект, генерим код.
Я генерирую код для SW4STM32 (System Workbench for STM32, потребуется регистрация). Предпочитаю генерировать отдельные .h/.c файлы для переферии, включать full_assert и отключать генерацию вызова функций инициализации переферии.
1. Download & install
Тут все просто: качаем архив, распаковываем, копируем сорцы из архив/modbus/ в проект, не забываем про архив/modbus/include, добавить в include’ы.
Также нам потребуется port layer, с которым мы и будем работать, берем его из архив/demo/BARE, копируем, готово.
2. Портируем
Все делаем опираясь на официальную документацию.
port.h
Начнем с include’ов: я убираю assert.h, потому что перекидываю assert()
на assert_param()
, и добавляю stm32f3xx_hal.h, если вы используете процессор другой серии, то вместо этой используйте соответствующую библиотеку.
Далее нас интересуют макросы ENTER_CRITICAL_SECTION( )
и EXIT_CRITICAL_SECTION( )
. Заменяем их на __disable_irq()
и __enable_irq()
соответственно.
Также я добавляю прототип UART_IRQ_Handler()
, который будет использоваться для немного костыльной реализации реакции на прерывания.
/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.at> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id: port.h,v 1.1 2006/08/22 21:35:13 wolti Exp $ */ #ifndef _PORT_H #define _PORT_H #include "stm32f3xx_hal.h" #include <inttypes.h> #define INLINE inline #define PR_BEGIN_EXTERN_C extern "C" { #define PR_END_EXTERN_C } // управление прерываниями #define ENTER_CRITICAL_SECTION( ) __disable_irq() #define EXIT_CRITICAL_SECTION( ) __enable_irq() // подменим вызовы по всей библиотеки #define assert(val) assert_param(val) typedef uint8_t BOOL; typedef unsigned char UCHAR; typedef char CHAR; typedef uint16_t USHORT; typedef int16_t SHORT; typedef uint32_t ULONG; typedef int32_t LONG; #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif // callback для uart BOOL UART_IRQ_Handler(USART_TypeDef * usart); #endif
porttimer.c
Опять начнем с include’ов: в разделе platform includes добавим stm32f3xx_hal_tim.h
, из него нужна константа TIM_COUNTERMODE_UP
.
В разделе static functions я добавляю handler для таймера и 2 переменных для хранения таймаута, и текущего значения счетчика.
Функцию xMBPortTimersInit()
наполним инициализацией таймера. Рассчитываем на то, что он будет тикать каждые 50 мкс.
htim — наш handler, тип — static TIM_HandleTypeDef
, глобальный
timeout — static uint16_t
, глобальный
downcounter — static uint16_t
, глобальный
htim.Instance = TIM6; // указываем, что будем работать с 6 таймером htim.Init.CounterMode = TIM_COUNTERMODE_UP; // тип работы таймера (от 0 и вверх) /* инициализируем делитель частоты таймера, нам же не нужен таймер на 32МГц HAL_RCC_GetPCLK1Freq() - возвращает частоту PCLK1, в нашем случае будет 32000000 делим на 1000000, чтобы получить делитель для работы таймера с частотой 1МГц а -1 - это самый сок: дело в том, что 0 - это делитель на 1, 1 - делитель на 2 и т.д.*/ htim.Init.Prescaler = (HAL_RCC_GetPCLK1Freq() / 1000000) - 1; // период 50 мкс, с -1, думаю, все понятно htim.Init.Period = 50 - 1; // запишем значение таймаута, оно понадобится для перезапуска таймера timeout = usTim1Timerout50us; // все, вызываем инициализацию таймера, все остальное сделает HAL return HAL_OK == HAL_TIM_Base_Init(&htim) ? TRUE : FALSE;
Функция vMBPortTimersEnable()
. Тут 2 простых действия:
- Сбросить таймер.
- Запустить таймер асинхронно с обратной связью по прерыванию.
downcounter = timeout; HAL_TIM_Base_Start_IT(&htim);
Функция vMBPortTimersDisable()
. Тут просто отключим таймер.
HAL_TIM_Base_Stop_IT(&htim);
Также добавим функцию для реагирования на таймер HAL_TIM_Base_Stop_IT()
:
// проверяем, что прерывание того типа, который нужен и он нашего таймера if(__HAL_TIM_GET_FLAG(&htim, TIM_FLAG_UPDATE) != RESET && __HAL_TIM_GET_IT_SOURCE(&htim, TIM_IT_UPDATE) !=RESET) { __HAL_TIM_CLEAR_IT(&htim, TIM_IT_UPDATE); // сбрасываем флаг прерывания if (!--downcounter) // декрементируем счетчик, пока он не достигнет нуля prvvTIMERExpiredISR(); // вызываем callback библиотеки }
/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.at> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id: porttimer.c,v 1.1 2006/08/22 21:35:13 wolti Exp $ */ /* ----------------------- Platform includes --------------------------------*/ #include "port.h" #include "stm32f3xx_hal_tim.h" /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" /* ----------------------- static functions ---------------------------------*/ static void prvvTIMERExpiredISR( void ); static TIM_HandleTypeDef htim; static uint16_t timeout = 0; static uint16_t downcounter = 0; /* ----------------------- Start implementation -----------------------------*/ BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) { htim.Instance = TIM6; htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.Prescaler = (HAL_RCC_GetPCLK1Freq() / 1000000) - 1; htim.Init.Period = 50 - 1; timeout = usTim1Timerout50us; return HAL_OK == HAL_TIM_Base_Init(&htim) ? TRUE : FALSE; } inline void vMBPortTimersEnable( ) { /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */ downcounter = timeout; HAL_TIM_Base_Start_IT(&htim); } inline void vMBPortTimersDisable( ) { /* Disable any pending timers. */ HAL_TIM_Base_Stop_IT(&htim); } /* Create an ISR which is called whenever the timer has expired. This function * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that * the timer has expired. */ static void prvvTIMERExpiredISR( void ) { ( void )pxMBPortCBTimerExpired( ); } void TIM6_DAC1_IRQHandler(void) { /* TIM Update event */ if(__HAL_TIM_GET_FLAG(&htim, TIM_FLAG_UPDATE) != RESET && __HAL_TIM_GET_IT_SOURCE(&htim, TIM_IT_UPDATE) !=RESET) { __HAL_TIM_CLEAR_IT(&htim, TIM_IT_UPDATE); if (!--downcounter) prvvTIMERExpiredISR(); } }
portserial.c
В общем то вся статья была написана именно из-за этого файла, потому что портировать UART-layer на STM32 под «чистым» HAL мне не удалось и пришлось лезть глубже. В описании библиотеки написано, что нужны прерывания, сигнализирующие о том, что буффер передачи пуст и что буффер приема не пуст. А HAL не поддерживает такие callback’и, поэтому будем выпендриваться.
Тезаурус:
huart — handler для UART, static UART_HandleTypeDef
DE_Port — порт ножки управления направлением канала, static GPIO_TypeDef *
DE_Pin — пин управления направлением канала, static uint16_t
Начнем с функции vMBPortSerialEnable()
. Тут мы обратимся к «скрытым возможностям» HAL.
if (xRxEnable) { // если переключаемся в режим приема // переключаем драйвер в режим приема (см. принципы работы rs485) HAL_GPIO_WritePin(DE_Port, DE_Pin, GPIO_PIN_RESET); __HAL_UART_ENABLE_IT(&huart, UART_IT_RXNE); // разрешаем прерывание, реагирует если буфер приема не пуст } else { __HAL_UART_DISABLE_IT(&huart, UART_IT_RXNE); // запрещаем прерывание } if (xTxEnable) { // если переключаемся в режим передачи // переключаем драйвер в режим передачи (см. принципы работы rs485) HAL_GPIO_WritePin(DE_Port, DE_Pin, GPIO_PIN_SET); __HAL_UART_ENABLE_IT(&huart, UART_IT_TXE); // разрешаем прерывание, реагирует если буфер передачи пуст } else { __HAL_UART_DISABLE_IT(&huart, UART_IT_TXE); // запрещаем прерывание }
Далее функция инициализации порта xMBPortSerialInit()
. Сделал ее более-менее универсальной, она сама инициализирует заданный UART, но не забываем, что часть с MSP-инициализацией должна быть сгенерирована: инициализация пинов, прерывания и пр.
huart.Init.Mode = UART_MODE_TX_RX; // работаем на прием и передачу huart.Init.HwFlowCtl = UART_HWCONTROL_NONE; // без контроля потока (у нас же rs485) // сэмплинг, не могу нормально объяснить, но это нужно для защиты от шумов huart.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED; huart.Init.OverSampling = UART_OVERSAMPLING_16; // один стоп-бит huart.Init.StopBits = UART_STOPBITS_1; // без доп-фич huart.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; // портозависимая инициализация: UART, ножка контроля направления приемопередачи switch (ucPORT) { case 0: huart.Instance = USART1; DE_Port = GPIOA; DE_Pin = GPIO_PIN_12; break; case 1: huart.Instance = USART2; DE_Port = GPIOA; DE_Pin = GPIO_PIN_1; break; case 2: huart.Instance = USART3; DE_Port = GPIOB; DE_Pin = GPIO_PIN_14; break; default: // вызовем ошибку, если выбрали не тот номер порта, хотя это будет ошибкой портирования, но хоть что-то return FALSE; } // скорость huart.Init.BaudRate = ulBaudRate; // размер слова switch (ucDataBits) { case 8: huart.Init.WordLength = UART_WORDLENGTH_8B; break; case 9: huart.Init.WordLength = UART_WORDLENGTH_9B; break; default: // вызовем ошибку, если выбрали не тот номер порта, хотя это будет ошибкой портирования, но хоть что-то return FALSE; } // настраиваем бит четности switch (eParity) { case MB_PAR_NONE: huart.Init.Parity = UART_PARITY_NONE; break; case MB_PAR_EVEN: huart.Init.Parity = UART_PARITY_EVEN; break; case MB_PAR_ODD: huart.Init.Parity = UART_PARITY_ODD; break; default: // вызовем ошибку, если выбрали не тот номер порта, хотя это будет ошибкой портирования, но хоть что-то return FALSE; } // Инициализируем порт, как rs485 return HAL_OK == HAL_RS485Ex_Init(&huart, UART_DE_POLARITY_HIGH, 0, 0) ? TRUE : FALSE;
Теперь функции ввода/вывода xMBPortSerialPutByte()
и xMBPortSerialGetByte()
. Тут мы будем делать хардкорный низкоуровневый (для HAL) IO, используя регистры UART’а.
Для чтения байта из порта: *pucByte = huart.Instance->RDR
Для записи байта в порт: huart.Instance->TDR = ucByte
И, напоследок, добавим функцию UART_IRQ_Handler()
, которую мы описали в port.h. Она будет отвечать за перехват прерываний ввода/вывода. Основная идея: если прерывание «наше», т.е. от нашего порта, и то, что мы ждем, то возвращаем TRUE
— это значит, что мы его перехватили.
if (usart == huart.Instance) { // проверим, что прерываниеот нашего порта if((__HAL_UART_GET_IT(&huart, UART_IT_RXNE) != RESET) && (__HAL_UART_GET_IT_SOURCE(&huart, UART_IT_RXNE) != RESET)) { // проверим, что прерывание которое нам нужно pxMBFrameCBByteReceived(); // сообщим об этом библмотеке __HAL_UART_SEND_REQ(&huart, UART_RXDATA_FLUSH_REQUEST); // надо сбросить прерывание return TRUE; // говорим, что перехватили } if((__HAL_UART_GET_IT(&huart, UART_IT_TXE) != RESET) &&(__HAL_UART_GET_IT_SOURCE(&huart, UART_IT_TXE) != RESET)) { // проверим, что прерывание которое нам нужно pxMBFrameCBTransmitterEmpty(); // сообщим об этом библмотеке return TRUE; // говорим, что перехватили } } return FALSE; // говорим, что это не наше, пусть обрабатывают дальше
/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.at> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id: portserial.c,v 1.1 2006/08/22 21:35:13 wolti Exp $ */ #include "port.h" /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" /* ----------------------- static functions ---------------------------------*/ static UART_HandleTypeDef huart; static GPIO_TypeDef * DE_Port; static uint16_t DE_Pin; /* ----------------------- Start implementation -----------------------------*/ void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { /* If xRXEnable enable serial receive interrupts. If xTxENable enable * transmitter empty interrupts. */ if (xRxEnable) { HAL_GPIO_WritePin(DE_Port, DE_Pin, GPIO_PIN_RESET); __HAL_UART_ENABLE_IT(&huart, UART_IT_RXNE); } else { __HAL_UART_DISABLE_IT(&huart, UART_IT_RXNE); } if (xTxEnable) { HAL_GPIO_WritePin(DE_Port, DE_Pin, GPIO_PIN_SET); __HAL_UART_ENABLE_IT(&huart, UART_IT_TXE); } else { __HAL_UART_DISABLE_IT(&huart, UART_IT_TXE); } } BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) { huart.Init.Mode = UART_MODE_TX_RX; huart.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED; huart.Init.OverSampling = UART_OVERSAMPLING_16; huart.Init.StopBits = UART_STOPBITS_1; huart.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; switch (ucPORT) { case 0: huart.Instance = USART1; DE_Port = GPIOA; DE_Pin = GPIO_PIN_4; break; case 1: huart.Instance = USART2; DE_Port = GPIOA; DE_Pin = GPIO_PIN_1; break; case 2: huart.Instance = USART3; DE_Port = GPIOB; DE_Pin = GPIO_PIN_14; break; default: return FALSE; } huart.Init.BaudRate = ulBaudRate; switch (ucDataBits) { case 8: huart.Init.WordLength = UART_WORDLENGTH_8B; break; case 9: huart.Init.WordLength = UART_WORDLENGTH_9B; break; default: return FALSE; } switch (eParity) { case MB_PAR_NONE: huart.Init.Parity = UART_PARITY_NONE; break; case MB_PAR_EVEN: huart.Init.Parity = UART_PARITY_EVEN; break; case MB_PAR_ODD: huart.Init.Parity = UART_PARITY_ODD; break; default: return FALSE; } return HAL_OK == HAL_RS485Ex_Init(&huart, UART_DE_POLARITY_HIGH, 0, 0); } BOOL xMBPortSerialPutByte( CHAR ucByte ) { /* Put a byte in the UARTs transmit buffer. This function is called * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been * called. */ huart.Instance->TDR = ucByte; return TRUE; } BOOL xMBPortSerialGetByte( CHAR * pucByte ) { /* Return the byte in the UARTs receive buffer. This function is called * by the protocol stack after pxMBFrameCBByteReceived( ) has been called. */ *pucByte = huart.Instance->RDR; return TRUE; } BOOL UART_IRQ_Handler(USART_TypeDef * usart) { if (usart == huart.Instance) { if((__HAL_UART_GET_IT(&huart, UART_IT_RXNE) != RESET) && (__HAL_UART_GET_IT_SOURCE(&huart, UART_IT_RXNE) != RESET)) { pxMBFrameCBByteReceived(); __HAL_UART_SEND_REQ(&huart, UART_RXDATA_FLUSH_REQUEST); return TRUE; } if((__HAL_UART_GET_IT(&huart, UART_IT_TXE) != RESET) &&(__HAL_UART_GET_IT_SOURCE(&huart, UART_IT_TXE) != RESET)) { pxMBFrameCBTransmitterEmpty(); return TRUE; } } return FALSE; }
Казалось бы на этом все, но есть еще пара моментов:
Как вы могли заметить, функцию UART_IRQ_Handler()
никто не вызывает. Чтобы это исправить, надо посетить файл stm32f3xx_it.c. Там добавим include для port.h. Во все USARTx_IRQ_Handler’ы надо добавить следующие строки (в нашем случае USART1_IRQ_Handle()
).
// если прерывание перехвачено, то не будем его обрабатывать дальше if (FALSE != UART_IRQ_Handler(USART1)) return;
/** ****************************************************************************** * @file stm32f3xx_it.c * @brief Interrupt Service Routines. ****************************************************************************** * * COPYRIGHT(c) 2016 STMicroelectronics * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of STMicroelectronics nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "stm32f3xx_hal.h" #include "stm32f3xx.h" #include "stm32f3xx_it.h" /* USER CODE BEGIN 0 */ #include "port.h" /* USER CODE END 0 */ /* External variables --------------------------------------------------------*/ extern UART_HandleTypeDef huart1; /******************************************************************************/ /* Cortex-M4 Processor Interruption and Exception Handlers */ /******************************************************************************/ /** * @brief This function handles System tick timer. */ void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); HAL_SYSTICK_IRQHandler(); /* USER CODE BEGIN SysTick_IRQn 1 */ /* USER CODE END SysTick_IRQn 1 */ } /******************************************************************************/ /* STM32F3xx Peripheral Interrupt Handlers */ /* Add here the Interrupt Handlers for the used peripherals. */ /* For the available peripheral interrupt handler names, */ /* please refer to the startup file (startup_stm32f3xx.s). */ /******************************************************************************/ /** * @brief This function handles USART1 global interrupt / USART1 wake-up interrupt through EXTI line 25. */ void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ if (FALSE != UART_IRQ_Handler(USART1)) return; /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ } /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
И самое странное, что я пока так и не смог решить: библиотека, при ответе, отдавала все байты кроме последнего. Меня это убивало, причем при подсчете все было ОК, вероятно это вопрос UART’а или кривых рук, но спасло следующее решение: просто добавим в счетчик байт на отправку еще единицу (файл modbus/rtu/mbrtu.c функция eMBRTUSend()
).
eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) { eMBErrorCode eStatus = MB_ENOERR; USHORT usCRC16; ENTER_CRITICAL_SECTION( ); /* Check if the receiver is still in idle state. If not we where to * slow with processing the received frame and the master sent another * frame on the network. We have to abort sending the frame. */ if( eRcvState == STATE_RX_IDLE ) { /* First byte before the Modbus-PDU is the slave address. */ pucSndBufferCur = ( UCHAR * ) pucFrame - 1; usSndBufferCount = 1; /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */ pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; usSndBufferCount += usLength; /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */ usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ); ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF ); ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 ); usSndBufferCount++; // вот тут этот костыль обитает /* Activate the transmitter. */ eSndState = STATE_TX_XMIT; vMBPortSerialEnable( FALSE, TRUE ); } else { eStatus = MB_EIO; } EXIT_CRITICAL_SECTION( ); return eStatus; }
3. Usage
Уже почти готово.
Настройка
Посетим mbconfig.h. Ищите его в include’ах. У меня SW4STM32, поэтому меня спасает Ctrl + Click. Сначала сконфигурируем библиотеку для работа только с ModbusRTU:
/*! \brief If Modbus ASCII support is enabled. */ #define MB_ASCII_ENABLED ( 0 ) /*! \brief If Modbus RTU support is enabled. */ #define MB_RTU_ENABLED ( 1 ) /*! \brief If Modbus TCP support is enabled. */ #define MB_TCP_ENABLED ( 0 )
Еще можете поотключать функции которые не используете, это облегчит библиотеку и позволит не реализовывать callback’и.
Использование
- #include «mb.h
- eMBInit()
- eMBEnable()
- eMBPoll()
И не забудьте реализовать callback’и.
На этом считаю, что свою миссию выполнил, за примерами и документацией идите на офф-сайт.
Usage: Modules/Modbus
Callbacks: Modules/Modbus Registers
ссылка на оригинал статьи https://habrahabr.ru/post/279747/
Добавить комментарий