Портирование FreeModbus 1.5 под STM32 HAL rs485 без RTOS

от автора

С недавних пор я начал заниматься встраиваемыми системами и докатился до программирования микроконтроллеров, а именно STM32F373. Одной из задач было развернуть Modbus Slave RTU поверх интерфейса rs485.

Поскольку сроки поджимали было принято решение взять что-нибудь готовое, чем я и занялся. Недолгое гугление навело меня на библиотеку FreeModbus, а вот тут началась боль с которой, я надеюсь, вы не столкнетесь.

0. Готовим проект

Для быстрого старта я использую STM32CubeMX, он позволяет быстро генерить код инициализации контроллера без боли. В моем случае это STM32F373VCTx LQFP100.

Переферия:

  • TIM6
  • USART1

Делаю это только для генерации функций HAL_TIM_Base_MspInit() и HAL_UART_MspInit(), это упростит вам жизнь, если будете подключать другие таймеры и usart’ы.

peripheral

Тактирование:

Нам необходимо, чтобы таймер TIM6 или тот, который вы выбрали работал на частоте не ниже 20кГц. Пошуршав по коду инициализации я понял, что TIM6 работает на PCLK1 (Peripheral CLocK). По умолчанию он (таймер) тактируется от HSI (High Speed Internal resonator), так же как и все остальное, но мне показалось, что это маловато, поэтому я погнал все это дело через PLL (Phase-Locked Loop) и выставил значение множителя на x8, чтобы поднять до 32МГц, так МК работает плавнее и приятнее.

clock config

Дополнительная конфигурация:

Нам потребуется работать с таймером по прерыванию, поэтому включим его на вкладке Configuration раздел System NVIC «TIM6 global interrupt and DAC1 underrun error interrupts». Также нам нужны прерывания от USART1 «USART1 global interrupt / USART1 wake-up interrupt through EXTI line 25».

image

Я использую много дополнительных прерываний для индикации ошибок, поэтому не обращайте внимание на кучу галочек.

На этом заканчиваем, сохраняем проект, генерим код.

Я генерирую код для SW4STM32 (System Workbench for STM32, потребуется регистрация). Предпочитаю генерировать отдельные .h/.c файлы для переферии, включать full_assert и отключать генерацию вызова функций инициализации переферии.

imageimageimage

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(), который будет использоваться для немного костыльной реализации реакции на прерывания.

port.h

/*  * 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 простых действия:

  1. Сбросить таймер.
  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 библиотеки     } 

porttimer.c

/*  * 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-инициализацией должна быть сгенерирована: инициализация пинов, прерывания и пр.

xMBPortSerialInit()

    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; // говорим, что это не наше, пусть обрабатывают дальше 

portserial.c

/*  * 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; 

stm32f3xx_it.h

/**   ******************************************************************************   * @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()).

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’и.

Использование

  1. #include «mb.h
  2. eMBInit()
  3. eMBEnable()
  4. eMBPoll()

И не забудьте реализовать callback’и.

На этом считаю, что свою миссию выполнил, за примерами и документацией идите на офф-сайт.

Usage: Modules/Modbus
Callbacks: Modules/Modbus Registers

ссылка на оригинал статьи https://habrahabr.ru/post/279747/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *