1. Предварительная подготовка
В этой заметке предполагается, что читатель в качестве среды разработки использует Eclipse. Подробнее можно узнать по ссылке.
После первичной подготовки проекта и, в частности, развёртывания библиотеки Standard Peripherals Library, необходимо скачать и установить исходные тексты библиотеки FreeMODBUS. Несмотря на то, что в списках для загрузки фигурируют версии библиотеки вплоть до 1.5, на главной странице проекта в правом верхнем углу последнее упоминание касается библиотеки версии 1.3, поэтому именно эту версию и возьмём за основу для портирования.
После загрузки необходимо распаковать библиотеку в подкаталог проекта lib. Портирование протокола Modbus TCP выходит за рамки данной заметки, поэтому можно сразу же удалить подкаталог lib/freemodbus/modbus/tcp. За основу-шаблон для портирования возьмём AVR-порт из каталога demo/AVR/port, который скопируем в lib/freemodbus. Итоговая структура проекта в Eclipse принимает вид:

Добавим пути до всех имеющих отношение к проекту заголовочных файлов Project -> Properties -> C/C++ General -> Paths and Symbols -> вкладка Includes:

Аналогичным образом на вкладке Source Location пропишем в проекте файлы с исходными текстами:

2. Портирование
2.1. Настройка mbconfig.h
В файле lib/freemodbus/modbus/include/mbconfig.h находим строки:
/*! \brief If Modbus ASCII support is enabled. */
#define MB_ASCII_ENABLED ( 1 )
/*! \brief If Modbus RTU support is enabled. */
#define MB_RTU_ENABLED ( 0 )
/*! \brief If Modbus TCP support is enabled. */
#define MB_TCP_ENABLED ( 0 )
и изменяем их на:
/*! \brief If Modbus ASCII support is enabled. */
#define MB_ASCII_ENABLED ( 1 )
/*! \brief If Modbus RTU support is enabled. */
#define MB_RTU_ENABLED ( 1 )
/*! \brief If Modbus TCP support is enabled. */
#define MB_TCP_ENABLED ( 0 )
2.2. Изменения в port.h
Открываем lib/freemodbus/port/port.h и в разделе "Platform includes" добавляем:
#include "stm32f4xx_conf.h"
Далее находим строчки с макроопределениями ENTER_CRITICAL_SECTION и EXIT_CRITICAL_SECTION и определяем их следующим образом:
#define ENTER_CRITICAL_SECTION( ) ( __disable_irq() )
#define EXIT_CRITICAL_SECTION( ) ( __enable_irq() )
2.3. Изменения в porttimer.c
Для измерения различных временных промежутков в рамках работы Modbus протокола, не нарушая общности, условимся использовать Таймер 2. Его необходимо настроить таким образом, чтобы он формировал прерывания с периодом, равным 50 мкс * N, где N - целое положительное число, указанное пользователем.
Шаг 1. Открываем файл lib/freemodbus/port/porttimer.c
Шаг 2. В разделе "Platform includes" открытого файла добавляем:
#include "stm32f4xx_conf.h"
Шаг 3. Переопределяем функцию xMBPortTimersInit:
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef base_timer;
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE );
TIM_TimeBaseStructInit( &base_timer );
/* 84000 кГц // 4200 = 20 кГц ( 50 мкс ) */
base_timer.TIM_Prescaler = 4200 - 1;
base_timer.TIM_Period = ( (uint32_t) usTim1Timerout50us ) - 1;
base_timer.TIM_ClockDivision = 0;
base_timer.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit( TIM2, &base_timer );
TIM_ClearITPendingBit( TIM2, TIM_IT_Update );
/* Разрешаем прерывание по обновлению (в данном случае - по переполнению) счётчика-таймера TIM2. */
TIM_ITConfig( TIM2, TIM_IT_Update, ENABLE );
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init( &NVIC_InitStructure );
TIM_Cmd( TIM2, ENABLE );
return TRUE;
}
Шаг 4. Переопределяем функцию vMBPortTimersEnable:
void vMBPortTimersEnable()
{
TIM_SetCounter( TIM2, 0 );
TIM_Cmd( TIM2, ENABLE );
}
Шаг 5. Переопределяем функцию vMBPortTimersDisable:
void vMBPortTimersDisable()
{
TIM_Cmd( TIM2, DISABLE );
}
Шаг 6. Добавляем обработчик прерываний Таймера 2:
void TIM2_IRQHandler( void )
{
if ( TIM_GetITStatus( TIM2, TIM_IT_Update ) != RESET )
{
TIM_ClearITPendingBit( TIM2, TIM_IT_Update );
(void) pxMBPortCBTimerExpired();
}
}
2.4. Изменения в portserial.c
В качестве приёмо-передающего устройства, опять же не нарушая общности, выберем USART2.
Шаг 1. Открываем файл lib/freemodbus/port/portserial.c
Шаг 2. Переопределяем функцию xMBPortSerialInit, отвечающей за инициализацию USART2:
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
USART_ClockInitTypeDef USART_ClockInitStructure;
/* подавляем предупреждение компилятора о неиспользуемой переменной */
(void) ucPORT;
RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA, ENABLE );
GPIO_PinAFConfig( GPIOA, GPIO_PinSource2, GPIO_AF_USART2 );
GPIO_PinAFConfig( GPIOA, GPIO_PinSource3, GPIO_AF_USART2 );
GPIO_StructInit( &GPIO_InitStructure );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init( GPIOA, &GPIO_InitStructure );
USART_DeInit( USART2 );
USART_ClockStructInit( &USART_ClockInitStructure );
USART_ClockInit( USART2, &USART_ClockInitStructure );
/* настройка скорости обмена */
USART_InitStructure.USART_BaudRate = (uint32_t)ulBaudRate;
/* настройка кол-ва битов данных */
if( ucDataBits == 9 )
USART_InitStructure.USART_WordLength = USART_WordLength_9b;
else
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
/* кол-во стоп-битов устанавливаем равным 1 */
USART_InitStructure.USART_StopBits = USART_StopBits_1;
/* настройка паритета (по умолчанию - его нет) */
switch( eParity )
{
case MB_PAR_NONE:
USART_InitStructure.USART_Parity = USART_Parity_No;
break;
case MB_PAR_ODD:
USART_InitStructure.USART_Parity = USART_Parity_Odd;
break;
case MB_PAR_EVEN:
USART_InitStructure.USART_Parity = USART_Parity_Even;
break;
default:
USART_InitStructure.USART_Parity = USART_Parity_No;
break;
};
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init( USART2, &USART_InitStructure );
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; /* канал */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; /* приоритет */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; /* приоритет субгруппы */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; /* включаем канал */
NVIC_Init(&NVIC_InitStructure); /* инициализируем */
USART_Cmd( USART2, ENABLE );
vMBPortSerialEnable( TRUE, TRUE );
#ifdef RTS_ENABLE
RTS_INIT;
#endif
return TRUE;
}
Шаг 3. Переопределение функции vMBPortSerialEnable, отвечающей за разрешение/запрет прерываний от модуля USART2:
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if( xRxEnable )
{
USART_ITConfig( USART2, USART_IT_RXNE, ENABLE );
}
else
{
USART_ITConfig( USART2, USART_IT_RXNE, DISABLE );
}
if ( xTxEnable )
{
USART_ITConfig( USART2, USART_IT_TXE, ENABLE );
#ifdef RTS_ENABLE
RTS_HIGH;
#endif
}
else
{
USART_ITConfig( USART2, USART_IT_TXE, DISABLE );
}
}
Шаг 4. Переопределение обработчика прерываний USART2:
void USART2_IRQHandler( void )
{
if ( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET )
{
USART_ClearITPendingBit( USART2, USART_IT_RXNE );
pxMBFrameCBByteReceived();
}
if ( USART_GetITStatus( USART2, USART_IT_TXE ) != RESET )
{
USART_ClearITPendingBit( USART2, USART_IT_TXE );
pxMBFrameCBTransmitterEmpty();
}
}
Шаг 5. Переопределение функции xMBPortSerialPutByte, предназначенной для отправки 1 байта данных в USART2:
BOOL xMBPortSerialPutByte( CHAR ucByte )
{
USART_SendData( USART2, (uint16_t) ucByte );
return TRUE;
}
Шаг 6. Переопределение функции xMBPortSerialGetByte, предназначенной для приёма 1 байта данных из USART2:
BOOL xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = (CHAR) USART_ReceiveData( USART2 );
return TRUE;
}
3. Тестирование
3.1. Подготовка
Для запуска успешного обмена информацией через протокол Modbus RTU/ASCII нам необходимо переопределить следующие функции:
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
имеющие соответственно следующие назначения:
- чтение значений из нескольких регистров ввода (0x04 Read Input Registers)
- чтение значений из нескольких регистров хранения (0x03 Read Holding Registers)
- чтение значений из нескольких регистров флагов (0x01 Read Coil Status)
- чтение значений из нескольких дискретных входов (0x02 Read Discrete Inputs)
Переопределим функцию eMBRegHoldingCB таким образом, чтобы в зависимости от содержимого регистров хранения включался или выключался один из установленных на плате STM32F4Discovery светодиодов.
Шаг 1. Сделаем необходимые подключения в main.c:
#include "stm32f4xx.h"
#include "stm32f4xx_conf.h"
#include "mb.h"
#include "mbport.h"
Шаг 2. Библиотека FreeModbus не резервирует память под регистры ввода/хранения/флагов/дискретных входов, т.е. подразумевается ручное/самостоятельное управление ОЗУ. Поэтому выделим память для 4 регистров хранения, начиная с адреса 40001, в файле main.c следующим образом:
#define REG_HOLDING_START 40001
#define REG_HOLDING_NREGS 4
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
Шаг 3. Собственно реализация функции eMBRegHoldingCB:
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress,
USHORT usNRegs, eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if( ( usAddress >= REG_HOLDING_START ) &&
( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegHoldingStart );
switch ( eMode )
{
case MB_REG_READ:
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE:
while( usNRegs > 0 )
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
break;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
Шаг 4. Для оставшихся функций добавим соответствующие заглушки:
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress,
USHORT usNCoils, eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
return MB_ENOREG;
}
Шаг 5. Установленные на плате STM32F4Discovery светодиоды подключены к ногам PD12, PD13, PD14 и PD15 микроконтроллера таким образом, что включаются при помощи лог."1", а выключаются при помощи лог."0". Функция инициализации ног I/O выглядит следующим образом:
void led_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOD, ENABLE );
GPIO_StructInit( &GPIO_InitStructure );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init( GPIOD, &GPIO_InitStructure );
GPIO_ResetBits( GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15 );
}
Шаг 6. Добавим функцию, выполняющую привязку каждого из четырёх регистров хранения к каждому из четырёх установленных светодиодов:
void led_update()
{
GPIO_WriteBit( GPIOD, GPIO_Pin_12, usRegHoldingBuf[0] ? Bit_SET : Bit_RESET );
GPIO_WriteBit( GPIOD, GPIO_Pin_13, usRegHoldingBuf[1] ? Bit_SET : Bit_RESET );
GPIO_WriteBit( GPIOD, GPIO_Pin_14, usRegHoldingBuf[2] ? Bit_SET : Bit_RESET );
GPIO_WriteBit( GPIOD, GPIO_Pin_15, usRegHoldingBuf[3] ? Bit_SET : Bit_RESET );
}
Из кода видно, что каждый из светодиодов будет гореть только в том случае, если значение соответствующего регистра хранения будет отличным от нуля.
Шаг 7. Файл main.c приобретает следующий вид:
#include "stm32f4xx.h"
#include "stm32f4xx_conf.h"
#include "mb.h"
#include "mbport.h"
#define REG_HOLDING_START 40001
#define REG_HOLDING_NREGS 4
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
void led_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOD, ENABLE );
GPIO_StructInit( &GPIO_InitStructure );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init( GPIOD, &GPIO_InitStructure );
GPIO_ResetBits( GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15 );
}
void led_update()
{
GPIO_WriteBit( GPIOD, GPIO_Pin_12, usRegHoldingBuf[0] ? Bit_SET : Bit_RESET );
GPIO_WriteBit( GPIOD, GPIO_Pin_13, usRegHoldingBuf[1] ? Bit_SET : Bit_RESET );
GPIO_WriteBit( GPIOD, GPIO_Pin_14, usRegHoldingBuf[2] ? Bit_SET : Bit_RESET );
GPIO_WriteBit( GPIOD, GPIO_Pin_15, usRegHoldingBuf[3] ? Bit_SET : Bit_RESET );
}
int main( void )
{
eMBErrorCode eStatus;
led_init();
eStatus = eMBInit( MB_RTU,
0x0A, /* адрес slave-устройства */
0,
9600, /* скорость обмена ( baudrate ) */
MB_PAR_NONE /* без паритета */
);
/*! @todo добавить проверку переменной eStatus после инициализации стэка Modbus */
eStatus = eMBEnable();
/*! @todo добавить проверку переменной eStatus после запуска стэка Modbus */
for ( ;; )
{
(void) eMBPoll();
led_update();
}
return 0;
}
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
return MB_ENOREG;
}
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if( ( usAddress >= REG_HOLDING_START ) &&
( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegHoldingStart );
switch ( eMode )
{
case MB_REG_READ:
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE:
while( usNRegs > 0 )
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
break;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
3.2. Запуск
В качестве ПО Modbus master выберем Modbus Poll, имеющий 30-дневный бесплатный тестовый период. Ключевым аргументом в пользу выбора данного ПО стала его одновременная поддержка Modbus/RTU и Modbus/ASCII. Чего, к сожалению, не скажешь об open-source решениях типа QModbus или QModMaster.
Настройки Modbus Poll включают:
Setup -> Read/Write Definition

Connection -> Connect

Connection -> Connect -> кнопка Advanced

В итоге после начала сбора данных Вы должны увидеть окно, похожее на:

4. Пойдём ещё дальше
На отладочной плате STM32F4Discovery, как известно, установлен 3-осевой акселерометр LIS302DL. В составе STM32F4DISCOVERY board firmware package поставляется всё необходимое ПО для инициализации и обмена данными с акселерометром. В частности, нас интересуют файлы:
- stsw-stm32068/STM32F4-Discovery_FW_V1.1.0/Utilities/STM32F4-Discovery/stm32f4_discovery_lis302dl.h
- stsw-stm32068/STM32F4-Discovery_FW_V1.1.0/Utilities/STM32F4-Discovery/stm32f4_discovery_lis302dl.c
- stsw-stm32068/STM32F4-Discovery_FW_V1.1.0/Utilities/STM32F4-Discovery/stm32f4_discovery.h
Скопируем их во вновь созданный подкаталог проекта src/mems. Структура проекта после этого преобразуется к виду:

4.1. Добавление кода для MEMS
Шаг 1. Добавляем описание для четырёх регистров ввода протокола Modbus:
#define REG_INPUT_START 30001
#define REG_INPUT_NREGS 4
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS];
Шаг 2. Добавляем подключение в main.c:
#include "mems/stm32f4_discovery_lis302dl.h"
Шаг 3. Добавляем объявление необходимых глобальных переменных в main.c:
__IO uint32_t TimingDelay = 0;
__IO int8_t XOffset, YOffset, ZOffset, Buffer[6];
Шаг 4. Добавляем функции, реализующие задержку и циклический опрос MEMS-акселерометра:
void Delay( __IO uint32_t nTime )
{
TimingDelay = nTime;
while ( TimingDelay != 0 )
;
}
void TimingDelay_Decrement( void )
{
if ( TimingDelay != 0x00 )
{
TimingDelay--;
}
}
void SysTick_Handler( void )
{
uint8_t temp1, temp2 = 0;
if ( TimingDelay != 0x00 )
{
TimingDelay_Decrement();
}
else
{
LIS302DL_Read(Buffer, LIS302DL_OUT_X_ADDR, 6);
/* Сохраняем показания по каждой из осей акселерометра */
/* с учётом первоначального смещения */
usRegInputBuf[ 0 ] = Buffer[0] -= XOffset;
usRegInputBuf[ 1 ] = Buffer[2] -= YOffset;
usRegInputBuf[ 2 ] = Buffer[4] -= ZOffset;
}
}
uint32_t LIS302DL_TIMEOUT_UserCallback( void )
{
/* Ошибка: таймаут при обмене данными с акселерометром */
while ( 1 )
{
}
return 0;
}
Шаг 5. Добавляем код инициализации акселерометра LIS302DL:
void lis302dl_init()
{
LIS302DL_InitTypeDef LIS302DL_InitStruct;
LIS302DL_InterruptConfigTypeDef LIS302DL_InterruptStruct;
/* Прерывание каждые 10 мс */
SysTick_Config(SystemCoreClock/ 100);
LIS302DL_InitStruct.Power_Mode = LIS302DL_LOWPOWERMODE_ACTIVE;
LIS302DL_InitStruct.Output_DataRate = LIS302DL_DATARATE_100;
LIS302DL_InitStruct.Axes_Enable = LIS302DL_X_ENABLE | LIS302DL_Y_ENABLE | LIS302DL_Z_ENABLE;
LIS302DL_InitStruct.Full_Scale = LIS302DL_FULLSCALE_2_3;
LIS302DL_InitStruct.Self_Test = LIS302DL_SELFTEST_NORMAL;
LIS302DL_Init( &LIS302DL_InitStruct );
LIS302DL_InterruptStruct.Latch_Request = LIS302DL_INTERRUPTREQUEST_NOTLATCHED;
LIS302DL_InterruptStruct.SingleClick_Axes = LIS302DL_CLICKINTERRUPT_XYZ_DISABLE;
LIS302DL_InterruptStruct.DoubleClick_Axes = LIS302DL_DOUBLECLICKINTERRUPT_XYZ_DISABLE;
LIS302DL_InterruptConfig( &LIS302DL_InterruptStruct );
Delay( 30 );
LIS302DL_Read(Buffer, LIS302DL_OUT_X_ADDR, 6);
/* Запоминаем первоначальные смещения по каждой из осей */
XOffset = Buffer[0];
YOffset = Buffer[2];
ZOffset = Buffer[4];
}
Шаг 6. Переопределяем метод eMBRegInputCB:
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if ( ( usAddress >= REG_INPUT_START ) &&
( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = (int) ( usAddress - usRegInputStart );
while ( usNRegs > 0 )
{
*pucRegBuffer++ = ( UCHAR ) ( usRegInputBuf[ iRegIndex ] >> 8 );
*pucRegBuffer++ = ( UCHAR) ( usRegInputBuf[ iRegIndex ] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
Шаг 7. Добавляем вызов lis302dl_init() в начале функции main, собираем проект и перепрошиваем плату.
4.2. Тестирование
После настроек Modbus Poll, аналогичных предыдущим, и запуска обмена данными окно программы должно стать похожим на:

4.3. Подводя окончательные итоги
Для скачивания доступны следующие файлы:
- stm32f4-serial-modbus-mems.zip - Eclipse-проект с исходниками; ВНИМАНИЕ! Standard Peripheral Library необходимо скачать отдельно;
- stm32f4-serial-modbus-rtu-mems.hex.zip - Готовая прошивка для STM32F4Discovery, работающая по протоколу Modbus RTU при параметрах последовательного порта 9600 8N1, с возможностью управлять светодиодами и запрашивать информацию с акселерометра;