Портування 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 приховує резонатора), так само як і все інше, але мені здалося, що це замало, тому я погнав все це справа через 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 © 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 "С" {
#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 © 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->ТДР = 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 © 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->ТДР = 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->ОР;
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© STMicroelectronics 2016
*
* 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 */
/************************ © 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 where we to
* slow processing with the frame and received 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

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.