Поскольку сроки поджимали было принято решение взять что-нибудь готовое, чем я и занялся. Недолгое гугление навело меня на библиотеку 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()
соответственно.Сразу стоит отметить, что это не лучшая реализация критических секций: если попадется ситуация в которой функция с такой критической секцией вызовет другую функцию с такой критической секцией из своей критической секции, то мы получим преждевременное включение прерываний (пример в спойлере), но если портировать описанным в статье способом, то такой ситуации не произойдет. А решение проблемы можно найти тут.
void foo() {
ENTER_CRITICAL_SECTION(); // второе попадание в критическую секцию
// делаем полезные штуки не боясь прерываний
EXIT_CRITICAL_SECTION(); // тут плохо, прерывания не должны включаться, мы все еще в критической секции функции bar()
}
void bar() {
ENTER_CRITICAL_SECTION(); // первое попадание в критическую секцию
foo(); // после выполнения функции прерывания уже работают, а это неправильно
EXIT_CRITICAL_SECTION(); // бесполезно =(
}
Также я добавляю прототип
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
Комментарии (19)
skjame
22.03.2016 08:56+1как по мне хуарт так называется неспроста. его использование во многих случаях чрезмерно. единственное что есть переносимость с f0 на другие кристаллы вплоть до л4. хотя я не вижу ничего страшного в правке имен пары регистров для настройки, а вот висеть столько в прерывание — не камильфо. имхо далеко не лушая реализация поставленной перед ТС задачи.
d_ilyich
22.03.2016 13:10Благодарю за статью. Мне как раз предстоит в ближайшем будущем реализовывать поддержку Modbus/RTU.
Только я не вижу обработки интервалов. Их нет вообще или просто Вы не включили их в статью? Я имею в виду межсимвольный интервал t1.5 и межпакетный интервал t3.5 (по оф. терминологии).
Я хочу сделать следующим образом. Согласно документации, интервалы таковы:
19200 бод и ниже: t1.5 = 1.5 char = 3 x 0.5 char; t.3.5 = 3.5 char = 7 x 0.5 char;
19200 бод и выше: t1.5 = 750 мкс = 3 x 250 мкс; t3.5 = 1750 мкс = 7 x 250 мкс.
Т.о. таймер программируется на t0.5 (по моей терминологии). Прикинул, что значение Prescaler = 125(-1) будет оптимальным. Оно позволяет получить целочисленное значение Period, а также единую формулу вычисления для различных камней/частот и скоростей обмена.
Понадобятся 2 счётчика:
счётчик повторений, c_rep — количество срабатываний таймера;
счётчик «ошибок», c_err — количество «косых» фреймов.
При получении байта счётчик c_rep сбрасывается. Когда c_rep == 3, счётчик c_err увеличивается. Т.о. если c_rep == 7, то пакет получен. При этом, если c_err == 1, то пакет целый, в противном случае — битый.
Поскольку я далеко не профи в данной области, прошу прокомментировать мой [пока] чисто умозрительный подход.
build_your_web
Отличная статья! Очень наглядно и доступно.
А можно ли применять STM32CubeMX для генерации кода для камней STM32 F0 и F1?
noonv
Можно.
Lynnfield
Да, смотри, на офф-сайте STM заявляют, что CubeMX работает с линейками от F0 до L4.