Доброго времени суток, любители и профессионалы программирования на микроконтроллерах. Эта статья посвящена портированию библиотеки freemodbus на STM32F100 (тот, что в discovery vl). Да, на habrahabr уже есть подобная статья, но мне она кажется не самой удачной. Буду использовать Modbus RTU в режиме slave. Для успешного портирования библиотеки freemodbus на платформу без операционной системы, необходимо выполнить три шага:
1. прописать файл port.h
2. настроить таймер
3. настроить usart
Итак, план составлен — пора за работу.
Для удобства, сгенерируем проект при помощи STM Qube для IAR. Нам потребуется включить отладку, настроить таймер и я также задействовал кварцы, которые присутствуют на плате.
Генерируем проект. Скачаем исходники freemodbus-v1.5.0. Нам понадобится папка modbus. Поместим её в папку проекта в Drivers.
Туда же поместим папку port из freemodbus-v1.5.0demoBARE.
Откроем проект и прикрепим к нему только что скопированные исходники.
Далее необходимо прописать пути к папкам в опциях проекта во вкладке Preprocessor.
STM QUBE почему то указал в качестве девайса none. Исправляем.
На этом этапе проект собирается, хоть и с предупреждениями. Перейдём непосредственно к портированию. Откроем port.h. Обьявим функции, обеспецивающие атомарность операций. Сюда же вынесем объявления функций для UART.
void __critical_enter(void);
void __critical_exit(void);
#define ENTER_CRITICAL_SECTION( ) ( __critical_enter( ) )
#define EXIT_CRITICAL_SECTION( ) ( __critical_exit( ) )
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );
Дефинишн я написал в main().
static uint32_t lock_nesting_count = 0;
void __critical_enter(void)
{
__disable_irq();
++lock_nesting_count;
}
void __critical_exit(void)
{
/* Unlock interrupts only when we are exiting the outermost nested call. */
--lock_nesting_count;
if (lock_nesting_count == 0) {
__enable_irq();
}
}
Львиную часть таймера нам настроил Qube. Осталось лишь немного дописать в porttimer.c. Эта часть полностью написана на HAL и в лишних коментариях не нуждается.
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "stm32f1xx_hal.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );
extern TIM_HandleTypeDef htim6;
uint16_t timeout = 0;
volatile uint16_t counter = 0;
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
timeout = usTim1Timerout50us;
return TRUE;
}
void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
counter=0;
HAL_TIM_Base_Start_IT(&htim6);
}
void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
HAL_TIM_Base_Stop_IT(&htim6);
}
/* 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 HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if((++counter)>=timeout)
{
prvvTIMERExpiredISR();
}
}
Проверим, что всё идёт по плану. Проверим что тайминги совпадают ожиданиям. проверять буду дедовским методом, осциллографом. должен получиться импульс 1мс. Работает ли vMBPortTimersDisable — я проверять не буду =)
Временно напишем:
И в main():
Смотрим:
Теперь самое интересное — это настройка UART =) Нужно начать с написания xMBPortSerialInit и vMBPortSerialEnable. Так как GetByte из библиотеки принимает char, то работу с 9битными сообщениями я исключаю впринципе. Для написания vMBPortSerialEnable обратимся к схеме прерываний от USART.
Видно, что для разрешения прерывания по приему нужно включить RXNEIE: RXNE interrupt enable, а по событию передатчик готов — TXEIE: TXE interrupt enable.
Принятый байт лежит в регистре huart_m->Instance->DR. Запись в этот регистр вызывает передачу. Всё просто. Для удобства работы с USART, добавим stm32f1xx_hal_uart.c к проекту и задефайним HAL_UART_MODULE_ENABLED. Не буду писать много слов, а просто покажу, что в итоге внутри portserial.c:
#include "port.h"
#include "stm32f1xx_hal.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- static functions ---------------------------------*/
UART_HandleTypeDef huart_m;
HAL_StatusTypeDef USART_Init(UART_HandleTypeDef *huart);
void USART_MspInit(UART_HandleTypeDef* huart);
static void USART_SetConfig(UART_HandleTypeDef *huart);
/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if(xRxEnable)
{
__HAL_UART_ENABLE_IT(&huart_m, UART_IT_RXNE);
}
else
{
__HAL_UART_DISABLE_IT(&huart_m, UART_IT_RXNE);
}
if(xTxEnable)
{
__HAL_UART_ENABLE_IT(&huart_m, UART_IT_TXE);
}
else
{
__HAL_UART_DISABLE_IT(&huart_m, UART_IT_TXE);
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
switch (ucPORT)
{
case 0:
huart_m.Instance = USART1;
break;
case 1:
huart_m.Instance = USART2;
break;
case 2:
huart_m.Instance = USART3;
break;
default:
return FALSE;
}
huart_m.Init.BaudRate = ulBaudRate;
switch (ucDataBits)
{
case 8:
huart_m.Init.WordLength = UART_WORDLENGTH_8B;
break;
default:
return FALSE;
}
switch (eParity)
{
case MB_PAR_NONE:
huart_m.Init.Parity = UART_PARITY_NONE;
break;
case MB_PAR_EVEN:
huart_m.Init.Parity = UART_PARITY_EVEN;
break;
case MB_PAR_ODD:
huart_m.Init.Parity = UART_PARITY_ODD;
break;
default:
return FALSE;
}
huart_m.Init.StopBits = UART_STOPBITS_1;
huart_m.Init.Mode = UART_MODE_TX_RX;
huart_m.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart_m.Init.OverSampling = UART_OVERSAMPLING_16;
return (HAL_OK == USART_Init(&huart_m));
}
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_m.Instance->DR=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.
*/
if(huart_m.Init.Parity == UART_PARITY_NONE)
{
*pucByte = (uint8_t)(huart_m.Instance->DR & (uint8_t)0x00FF);
}
else
{
*pucByte = (uint8_t)(huart_m.Instance->DR & (uint8_t)0x007F);
}
return TRUE;
}
/* Create an interrupt handler for the transmit buffer empty interrupt
* (or an equivalent) for your target processor. This function should then
* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
* a new character can be sent. The protocol stack will then call
* xMBPortSerialPutByte( ) to send the character.
*/
void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
/* Create an interrupt handler for the receive interrupt for your target
* processor. This function should then call pxMBFrameCBByteReceived( ). The
* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
* character.
*/
void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
HAL_StatusTypeDef USART_Init(UART_HandleTypeDef *huart)
{
/* Check the UART handle allocation */
if(huart == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
if(huart->Init.HwFlowCtl != UART_HWCONTROL_NONE)
{
/* The hardware flow control is available only for USART1, USART2, USART3 */
assert_param(IS_UART_HWFLOW_INSTANCE(huart->Instance));
assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl));
}
else
{
assert_param(IS_UART_INSTANCE(huart->Instance));
}
assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));
assert_param(IS_UART_OVERSAMPLING(huart->Init.OverSampling));
if(huart->State == HAL_UART_STATE_RESET)
{
/* Allocate lock resource and initialize it */
huart->Lock = HAL_UNLOCKED;
/* Init the low level hardware */
USART_MspInit(huart);
}
huart->State = HAL_UART_STATE_BUSY;
/* Disable the peripheral */
__HAL_UART_DISABLE(huart);
/* Set the UART Communication parameters */
USART_SetConfig(huart);
/* In asynchronous mode, the following bits must be kept cleared:
- LINEN and CLKEN bits in the USART_CR2 register,
- SCEN, HDSEL and IREN bits in the USART_CR3 register.*/
CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN));
CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN));
/* Enable the peripheral */
__HAL_UART_ENABLE(huart);
/* Initialize the UART state */
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->State= HAL_UART_STATE_READY;
return HAL_OK;
}
void USART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(huart->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* Peripheral interrupt init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
else if(huart->Instance==USART2)
{
/* USER CODE BEGIN USART2_MspInit 0 */
/* USER CODE END USART2_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_USART2_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* Peripheral interrupt init */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspInit 1 */
/* USER CODE END USART2_MspInit 1 */
}
else if(huart->Instance==USART3)
{
/* USER CODE BEGIN USART3_MspInit 0 */
/* USER CODE END USART3_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_USART3_CLK_ENABLE();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* Peripheral interrupt init */
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART3_IRQn);
/* USER CODE BEGIN USART3_MspInit 1 */
/* USER CODE END USART3_MspInit 1 */
}
}
static void USART_SetConfig(UART_HandleTypeDef *huart)
{
uint32_t tmpreg = 0x00;
/* Check the parameters */
assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate));
assert_param(IS_UART_STOPBITS(huart->Init.StopBits));
assert_param(IS_UART_PARITY(huart->Init.Parity));
assert_param(IS_UART_MODE(huart->Init.Mode));
/*------- UART-associated USART registers setting : CR2 Configuration ------*/
/* Configure the UART Stop Bits: Set STOP[13:12] bits according
* to huart->Init.StopBits value */
MODIFY_REG(huart->Instance->CR2, USART_CR2_STOP, huart->Init.StopBits);
/*------- UART-associated USART registers setting : CR1 Configuration ------*/
/* Configure the UART Word Length, Parity and mode:
Set the M bits according to huart->Init.WordLength value
Set PCE and PS bits according to huart->Init.Parity value
Set TE and RE bits according to huart->Init.Mode value */
tmpreg = (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode ;
MODIFY_REG(huart->Instance->CR1,
(uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | USART_CR1_RE),
tmpreg);
/*------- UART-associated USART registers setting : CR3 Configuration ------*/
/* Configure the UART HFC: Set CTSE and RTSE bits according to huart->Init.HwFlowCtl value */
MODIFY_REG(huart->Instance->CR3, (USART_CR3_RTSE | USART_CR3_CTSE), huart->Init.HwFlowCtl);
/*------- UART-associated USART registers setting : BRR Configuration ------*/
if((huart->Instance == USART1))
{
huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
}
else
{
huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), huart->Init.BaudRate);
}
}
Теперь необходимо настроить прерывания в stm32f1xx_it.c.
void DINAR_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t tmp_flag = 0, tmp_it_source = 0;
tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);
tmp_it_source = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);
/* UART in mode Receiver ---------------------------------------------------*/
if((tmp_flag != RESET) && (tmp_it_source != RESET))
{
prvvUARTRxISR( );
}
tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_TXE);
tmp_it_source = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_TXE);
/* UART in mode Transmitter ------------------------------------------------*/
if((tmp_flag != RESET) && (tmp_it_source != RESET))
{
prvvUARTTxReadyISR( );
}
}
void USART1_IRQHandler(void)
{
DINAR_UART_IRQHandler(&huart_m);
}
void USART2_IRQHandler(void)
{
DINAR_UART_IRQHandler(&huart_m);
}
void USART3_IRQHandler(void)
{
DINAR_UART_IRQHandler(&huart_m);
}
Как и в случае с таймером — надо убедиться что всё идёт по плану. Проверяем. Временно пропишем:
Поставим брекпоинт и убедимся, что при приеме байта получаем прерывание. Я слал через hercules.
Также проверяем передатчик. Временно пропишем:
Смотрим:
Попробуем теперь опросить наше устройство с помощью Modbus Poll. Возьмем пример из Demo.
Запустим опрос.
Работает! Надеюсь эта статья поможет начинающим, таким как я, в реализации этого простого но в то же время полезного протокола.
Автор: dinartal