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, с возможностью управлять светодиодами и запрашивать информацию с акселерометра;

 

 

© ООО "Контроль-В" 2012 - 2021