РадиоКот :: Modbus и stm32
Например TDA7294

РадиоКот >Статьи >

Теги статьи: ModbusSTM32ТеорияДобавить тег

Modbus и stm32

Автор: Артём Тянутов, artem.lab@gmail.com
Опубликовано 12.12.2016
Создано при помощи КотоРед.

Введение

  Здравствуйте, коты!

  Нередко возникает необходимость в общении между каким-либо нашим устройством и компьютером.

  Самый простой и доступный для этих целей интерфейс - UART, имеется по несколько штук в микроконтроллерах, завсегдатай в компьютерах (на крайний случай как USB адаптер), а если нужна связь на некотором расстоянии - можно использовать RS-485.

  В простейшем случае можно ограничиться простым stdio (printf, scanf), периодически выводящим, например, текущую температуру, обороты, состояние и т.п. и принимающим текстовые команды (на манер AT-команд модема). По мере роста числа команд обрабатывать всё это становится тем ещё удовольствием, как по удобству, так и скорости, ну и добивает такой метод общения отсутствие проверки целостности посылки (кроме чётности UART) и адресации.

  В итоге мы так или иначе приходим к необходимости применения какого-либо цифрового протокола, устраняющего все эти недостатки и становимся у развилки - что-то своё или же стандартное.

Нелёгкий выбор

  Можно, конечно, изобрести свой велосипед, но ведь наверняка должно быть что-то готовое и широко применяющееся? Разумеется, есть целый зоопарк протоколов, разобраться в котором довольно не тривиальная задача, поэтому попробуем подобрать что-нибудь, что будет открыто, универсально и с кучей документации.

  Таким протоколом оказался Modbus - открытый коммуникационный протокол, основанный на архитектуре ведущий-ведомый. Его поддерживает огромное количество всевозможного оборудования, в сети есть большое количество информации, в том числе доступен официальный PDF с исчерпывающей спецификацией.

  Рассмотрим этот протокол подробнее.

Протокол Modbus

  Modbus был разработан компанией Modicon для использования в её контроллерах с программируемой логикой. Впервые спецификация протокола была опубликована в 1979 году. Первоначально контроллеры MODICON использовали последовательный интерфейс RS-232. Позднее стал применяться интерфейс RS-485.

  Modbus - это, как уже было сказано выше, протокол вида "ведущий - ведомый" (master - slave). Только один ведущий может быть подключен к сети и только он может инициировать обмен данными с максимум 247 ведомыми устройствами (адреса 0 и 248-255 зарезервированы).

  Обмен данными может осуществляться в двух режимах:

  • Одноадресный (unicast) - ведущий обращается к ведомому по заданному адресу (от 1 до 247) и получает ответ.
  • Широковещательный (broadcast) - ведущий обращается ко всем ведомым одновременно по зарезервированному широковещательному адресу 0. Ответ от ведомых не предусмотрен.

  Общий для всех физических уровней пакет Modbus - Protocol Data Unit (PDU) состоит из кода функции (1 байт) и данных (до 253 байт):

  Для передачи данных по физическим линиям связи PDU оборачивается в другой пакет - Application Data Unit (ADU) - который отличается в зависимости от линии. Всего их три вариант - Modbus ASCII (текстовый вариант), Modbus TCP (для передачи через TCP/IP) и интересующий нас Modbus RTU. ADU для Modbus RTU включает в себя 1 байт адреса ведомого устройства и 2 байта контрольной суммы CRC:

  Пакеты разделяются паузой в линии: не менее 3.5 символов при данной скорости передачи. Во время передачи пакета не должно быть пауз длительностью более 1.5 символов. Для скоростей более 19200 бод допускается использовать интервалы 1.75 и 0.75 мс, соответственно.

  Протокол Modbus оперирует четырьмя стандартными типами данных:

Тип данных Размер Доступ
Регистры флагов (Coils) 1 бит Чтение и запись
Дискретные входы (Discrete Inputs) 1 бит Только чтение
Регистры хранения (Holding Registers) 2 байта Чтение и запись
Регистры ввода (Input Registers) 2 байта Только чтение

  Доступ к каждому элементу производится по 16и-битному адресу, указываемому внутри данных функции, начиная с 0, таким образом может существовать до 65536 элементов. Причём что из себя представляет каждый элемент спецификация умалчивает, т.е. это может быть какая-либо переменная, а может быть и бит состояния ножки микроконтроллера.  Все многобайтовые поля и данные передаются старшим байтом вперёд.

  Modbus включает в себя множество стандартных функций. Причём старший бит в номере функции в ответе ведомого указывает на наличие ошибки, таким образом нам доступны номера функций от 1 до 127 (0 не является допустимым номером функции). Остановимся для примера на нескольких базовых.

Функции Modbus

Чтение регистров флагов (0x1)

  Читает битовое(ВКЛ/ВЫКЛ) состояние дискретных выходов (они же регистры флагов, coils).

  Запрос к ведомому определяет начальный адрес флаговых регистров (номер цифрового выхода) и количество для чтения.

PDU чтения регистров флагов (запрос)
Номер функции 0x01
Начальный адрес (Hi) 0x00
Начальный адрес (Lo) 0x10
Количество (Hi) 0x00
Количество (Lo) 0x02

  Ответ ведомого содержит состояния регистров флагов, запакованные побитово в байты данных (1 - ВКЛ, 0 - ВЫКЛ), в которых младший бит самого первого байта содержит состояние регистра по запрашиваемому адресу. Не используемые биты в последнем байте заполняются нулями. Перед данными передаётся байт, содержащий число передаваемых байт данных с состояниями регистров флагов.

PDU чтения регистров флагов (ответ)
Номер функции 0x01
Число байт данных 0x01
Данные 0x03

  В данном пример байт данных 0x03 (0b00000011) означает, что все два запрашиваемых регистра флагов включены.

Чтение цифровых входов (0x2)

  Формат PDU полностью аналогичен чтению регистров флагов.

Чтение регистров хранения (0x3)

  Читает двухбайтовые значения регистров хранения.

  Запрос к ведомому определяет начальный адрес регистров хранения и количество регистров для чтения.

PDU чтения регистров хранения (запрос)
Номер функции 0x03
Начальный адрес (Hi) 0x00
Начальный адрес (Lo) 0x30
Количество (Hi) 0x00
Количество (Lo) 0x02

  Ответ ведомого содержит двухбайтовые значения регистров хранения (старшим байтом вперёд) по начальному адресу в запросе и байт с количеством передаваемых байт данных со значениями регистров хранения.

PDU чтения регистров хранения (ответ)
Номер функции 0x03
Число байт данных 0x04
Данные (Hi) 0x12
Данные (Lo) 0x34
Данные (Hi) 0x56
Данные (Lo) 0x78

  В данном примере передаваемые данные означают, что по начальному адресу в запросе, два последовательно идущих регистра хранения имеют значения 0x1234 и 0x5678.

Чтение регистров ввода (0x4)

  Формат PDU полностью аналогичен чтению регистров хранения.

Установка одного регистра флагов (0x5)

  Устанавливает битовое (ВКЛ/ВЫКЛ) значение регистра флагов.

  Запрос к ведомому определяет адрес регистра флагов и его значение. Последнее передаётся в двух байтах, причём ВКЛ соответствует значение 0xff00, а ВЫКЛ - 0x0000.

PDU записи регистра флагов (запрос)
Номер функции 0x05
Адрес (Hi) 0x00
Адрес (Lo) 0x50
Значение(Hi) 0xff
Значение (Lo) 0x00

  Ответ ведомого повторяет запрос.

PDU записи регистра флагов (ответ)
Номер функции 0x05
Адрес (Hi) 0x00
Адрес (Lo) 0x50
Значение(Hi) 0xff
Значение (Lo) 0x00

  В данном примере осуществляется запись в регистр флагов по адресу 0x50 значения 0x1 (ВКЛ).

Установка одного регистра хранения (0x6)

  Устанавливает двухбайтовое значение регистра хранения.

  Запрос к ведомому определяет адрес регистра флагов и его значение.

PDU записи регистра хранения (запрос)
Номер функции 0x06
Адрес (Hi) 0x00
Адрес (Lo) 0x60
Значение(Hi) 0x12
Значение (Lo) 0x34

  Ответ ведомого повторяет запрос.

PDU записи регистра хранения (ответ)
Номер функции  0x06
Адрес (Hi) 0x00
Адрес (Lo) 0x60
Значение(Hi) 0x12
Значение (Lo) 0x34

  В данном примере осуществляется запись в регистр хранения по адресу 0x60 значения 0x1234.

Установка нескольких регистров флагов (0xf)

  Устанавливает битовые (ВКЛ/ВЫКЛ) значения регистров флагов.

  Запрос к ведомому содержит начальный адрес регистров флагов и количество для записи.

  Значения регистров флагов упаковываются побитово (1 - ВКЛ, 0 - ВЫКЛ) в байты данных, в которых младший бит самого первого байта содержит значение регистра по заданному адресу. Не используемые биты в последнем байте заполняются нулями. Перед данными передаётся байт, содержащий число передаваемых байт данных со значениями регистров флагов.

PDU записи регистров флагов (запрос)
Номер функции 0x0f
Адрес (Hi) 0x00
Адрес (Lo) 0xf0
Количество (Hi) 0x00
Количество (Lo) 0x02
Число байт данных 0x01
Данные 0x03

  Ответ от ведомого содержит начальный адрес регистров флагов и количество установленных значений.

PDU записи регистров флагов (ответ)
Номер функции 0x0f
Адрес (Hi) 0x00
Адрес (Lo) 0xf0
Количество (Hi) 0x00
Количество (Lo) 0x02

  В данном пример устанавливаются значения двух регистров флагов по начальному адресу 0x00f0 как ВКЛ и ВКЛ.

Установка нескольких регистров хранения (0x10)

  Устанавливает значения нескольких регистров хранения.

  Запрос к ведомому содержит начальный адрес регистров хранения, количество регистров для записи, число байт передаваемых данных значений и двухбайтовые значения регистров хранения.

PDU записи регистров хранения (запрос)
Номер функции 0x10
Адрес (Hi) 0x10
Адрес (Lo) 0x00
Количество (Hi) 0x00
Количество (Lo) 0x02
Число байт данных 0x04
Данные (Hi) 0x12
Данные (Lo) 0x34
Данные (Hi) 0x56
Данные (Lo) 0x78

   Ответ от ведомого содержит начальный адрес регистров хранения и количество установленных значений.

PDU записи регистров хранения (ответ)
Номер функции 0x10
Адрес (Hi) 0x10
Адрес (Lo) 0x00
Количество (Hi) 0x00
Количество (Lo) 0x02

  В данном пример устанавливаются значения двух регистров хранения по начальному адресу 0x1000 как 0x1234 и 0x5678.

Получение идентификатора ведомого устройства (0x11)

  Получает информацию о ведомом устройстве: идентификатор, состояние и специфичные для конкретного типа устройства данные.

  Запрос к ведомому содержит лишь номер функции.

PDU получения идентификатора ведомого (запрос)
Номер функции 0x11

  Ответ от ведомого содержит количество передаваемых следом байт данных, идентификатор ведомого устройства, состояние ведомого устройства (0x00 - ВЫКЛ, 0xFF - ВКЛ) и данные, зависящие от конкретного типа устройства.

PDU получения идентификатора ведомого (ответ)
Номер функции 0x11
Число байт данных 0x05
Идентификатор ведомого 0x55
Состояние ведомого 0xff
Дополнительные данные 0x01
Дополнительные данные 0x02
Дополнительные данные 0x03

  В данном примере получен идентификатор ведомого (0x55), состояние (ВКЛ - 0xff) и дополнительные данные (0x01, 0x02, 0x03).

Реализация для микроконтроллера

USART

  Первое что нужно - это сам USART - именно он будет получать и передавать пакеты в линию. Обязательно должен использовать DMA - зачем нам самим передавать байты, если это может сделать кто-то другой?

  Пакеты Modbus RTU разделяются паузами - можно, конечно, помудрить с таймером, но периферия USART умеет детектировать свободную линию, так что просто предоставим и это периферии, благо это прекрасно работает, приятно когда аппаратура делает работу за нас.

  Хотя Modbus RTU обеспечивает проверку целостности пакета посредством CRC, неплохо бы обнаруживать ошибки приёма USART, не нужно делать лишнюю работу по приёму и обработке заведомо неправильных данных.

  Ещё одной приятной особенностью USART в stm32 является возможность ничего не принимать (спать) до детектирования свободной линии, что можно использовать для пропуска сообщений, адресованных не нашему устройству.

Modbus RTU

  К реализации Modbus RTU есть одна, но важная хотелка - удобство - как можно меньше телодвижений для обработки обращений к нашему устройству и минимум затрат для добавления очередного элемента данных.

Схема

  Схема тривиальна, по сути, имея отладочную плату, достаточно соединить крест-накрест Tx и Rx с Rx и Tx преобразователя USB или RS-323 компьютера в UART с пинами микроконтроллера. Светодиод подключен для управления им по Modbus, на моей отладочной плате он висит на ножке 1 порта A.

Прошивка

  Программа "Привет, Мир!" для микроконтроллера - это моргание светодиодом. Поэтому сделаем так, чтобы этим самым светодиодом можно было управлять с ПК по Modbus, задавая значение регистра флагов, заодно сделаем, скажем, счётчик, значение которого будем получать и задавать как регистр хранения.

Объявим необходимые переменные.

/**
* Регистр хранения Modbus,
* инкрементируется каждую секунду.
*/
static uint16_t cnt_reg = 0;

//! Шина USART.
static usart_bus_t usart_bus;
//! Modbus.
static modbus_rtu_t modbus;
/**
* Сообщения для приёма и передачи Modbus.
* Можно использовать и одно сообщение, если не
* планируется обращаться к полученному сообщению
* при формировании ответа.
*/
static modbus_rtu_message_t modbus_rx_msg, modbus_tx_msg;

 

  Опишем функции для работы с USART.

 /**
 * Каллбэк получения байта.
 * @param byte Байт.
 * @return Флаг обработки.
 */
static bool usart_rx_byte_callback(uint8_t byte)
{
    return modbus_rtu_usart_rx_byte_callback(&modbus, byte);
}

/**
 * Каллбэк получения сообщения.
 * @return Флаг обработки.
 */
static bool usart_rx_callback(void)
{
    return modbus_rtu_usart_rx_callback(&modbus);
}

/**
 * Каллбэк отправки сообщения.
 * @return Флаг обработки.
 */
static bool usart_tx_callback(void)
{
    return modbus_rtu_usart_tx_callback(&modbus);
}

/**
 * Обработчик прерывания USART.
 */
void USART1_IRQHandler(void)
{
    usart_bus_irq_handler(&usart_bus);
}

/**
 * Обработчик прерывания канала DMA передачи данных USART.
 */
void DMA1_Channel4_IRQHandler(void)
{
    usart_bus_dma_tx_channel_irq_handler(&usart_bus);
}

/**
 * Обработчик прерывания канала DMA приёма данных USART.
 */
void DMA1_Channel5_IRQHandler(void)
{
    usart_bus_dma_rx_channel_irq_handler(&usart_bus);
}

/**
 * Инициализирует USART.
 */
static void init_usart(void)
{
    // Ножки GPIO.
    GPIO_InitTypeDef gpio_tx =
        {.GPIO_Pin = GPIO_Pin_9, .GPIO_Speed = GPIO_Speed_10MHz, .GPIO_Mode = GPIO_Mode_AF_PP};
    GPIO_InitTypeDef gpio_rx =
        {.GPIO_Pin = GPIO_Pin_10, .GPIO_Speed = GPIO_Speed_10MHz, .GPIO_Mode = GPIO_Mode_IN_FLOATING};
    GPIO_Init(GPIOA, &gpio_tx);
    GPIO_Init(GPIOA, &gpio_rx);
    
    // USART1: 115200 b/s, 8 бит данных, контроль чётности - "чётный", 1 стоп бит.
    USART_InitTypeDef usart_is =
        {.USART_BaudRate = 115200, .USART_WordLength = USART_WordLength_9b, .USART_StopBits = USART_StopBits_1,
         .USART_Parity = USART_Parity_Even, .USART_Mode = USART_Mode_Rx | USART_Mode_Tx,
         .USART_HardwareFlowControl = USART_HardwareFlowControl_None};
    USART_Init(USART1, &usart_is);
    USART_Cmd(USART1, ENABLE);
    
    // Шина USART.
    usart_bus_init_t usartb_is = {
        .usart_device = USART1, // Периферия.
        .dma_tx_channel = DMA1_Channel4, // Канал DMA на передачу.
        .dma_rx_channel = DMA1_Channel5 // Канал DMA на приём.
    };
    usart_bus_init(&usart_bus, &usartb_is);
    
    // Установка каллбэков.
    usart_bus_set_rx_callback(&usart_bus, usart_rx_callback);
    usart_bus_set_tx_callback(&usart_bus, usart_tx_callback);
    usart_bus_set_rx_byte_callback(&usart_bus, usart_rx_byte_callback);
    
    // При обнаружении свободной линии - прекратить принимать данные.
    usart_bus_set_idle_mode(&usart_bus, USART_IDLE_MODE_END_RX);
    
    // Разрешаем прерывания USART.
    NVIC_SetPriority(USART1_IRQn, 2);
    NVIC_EnableIRQ(USART1_IRQn);
    
    // Разрешаем прерывания DMA USART.
    NVIC_SetPriority(DMA1_Channel4_IRQn, 2);
    NVIC_EnableIRQ(DMA1_Channel4_IRQn);
    NVIC_SetPriority(DMA1_Channel5_IRQn, 2);
    NVIC_EnableIRQ(DMA1_Channel5_IRQn);
}

 

  Теперь очередь функционала, отвечающего за работу Modbus.

/**
 * Обработчик получения сообщения Modbus.
 */
static void modbus_on_msg_recv(void)
{
    // Обработаем сообщение и перенаправим его в зависимости от функции.
    modbus_rtu_dispatch(&modbus);
}

/**
 * Обработчик чтения регистра флагов.
 * @param address Адрес.
 * @param value Значение.
 * @return Код ошибки Modbus.
 */
static modbus_rtu_error_t modbus_on_read_coil(uint16_t address, modbus_rtu_coil_value_t* value)
{
    // Если не адрес светодиода - возврат ошибки.
    if(address != LED_ADDRESS) return MODBUS_RTU_ERROR_INVALID_ADDRESS;

    // Передадим состояние светодиода.
    *value = led_status(LED0);
    
    return MODBUS_RTU_ERROR_NONE;
}

/**
 * Обработчик Записи регистра флагов.
 * @param address Адрес.
 * @param value Значение.
 * @return Код ошибки Modbus.
 */
static modbus_rtu_error_t modbus_on_write_coil(uint16_t address, modbus_rtu_coil_value_t value)
{
    // Если не адрес светодиода - возврат ошибки.
    if(address != LED_ADDRESS) return MODBUS_RTU_ERROR_INVALID_ADDRESS;
    
    // Зажжём или погасим светодиод
    // в зависимости от значения.
    if(value) led_on(LED0);
    else led_off(LED0);
    
    return MODBUS_RTU_ERROR_NONE;
}

/**
 * Обработчик чтения регистра хранения.
 * @param address Адрес.
 * @param value Значение.
 * @return Код ошибки Modbus.
 */
static modbus_rtu_error_t modbus_on_read_hold_reg(uint16_t address, uint16_t* value)
{
    // Если не адрес регистра счётчика - возврат ошибки.
    if(address != CNT_ADDRESS) return MODBUS_RTU_ERROR_INVALID_ADDRESS;
    
    // Передадим значение регистра счётчика.
    *value = cnt_reg;
    
    return MODBUS_RTU_ERROR_NONE;
}

/**
 * Обработчик записи регистра хранения.
 * @param address Адрес.
 * @param value Значение.
 * @return Код ошибки Modbus.
 */
static modbus_rtu_error_t modbus_on_write_reg(uint16_t address, uint16_t value)
{
    // Если не адрес регистра счётчика - возврат ошибки.
    if(address != CNT_ADDRESS) return MODBUS_RTU_ERROR_INVALID_ADDRESS;
    
    // Установим значение регистра счётчика.
    cnt_reg = value;
    
    return MODBUS_RTU_ERROR_NONE;
}

/**
 * Обработчик передачи идентификатора ведомого устройства.
 * @param slave_id Идентификатор ведомого устройства.
 * @return Код ошибки Modbus.
 */
modbus_rtu_error_t modbus_on_report_slave_id(modbus_rtu_slave_id_t* slave_id)
{
    // Состояние - работаем.
    slave_id->status = MODBUS_RTU_RUN_STATUS_ON;
    // Идентификатор - для пример возьмём 0xaa.
    slave_id->id = 0xaa;
    // В дополнительных данных передадим наше имя.
    slave_id->data = "STM32 MCU Modbus v1.0";
    // Длина имени.
    slave_id->data_size = 21;
    
    return MODBUS_RTU_ERROR_NONE;
}

/**
 * Инициализация Modbus.
 */
static void init_modbus(void)
{
    // Структура инициализации Modbus.
    modbus_rtu_init_t modbus_is;
    
    modbus_is.usart = &usart_bus; // Шина USART.
    modbus_is.mode = MODBUS_RTU_MODE_SLAVE; // Режим - ведомый.
    modbus_is.address = 0xaa; // Адрес.
    modbus_is.rx_message = &modbus_rx_msg; // Сообщение для приёма.
    modbus_is.tx_message = &modbus_tx_msg; // Сообщение для передачи.
    
    // Инициализируем Modbus.
    modbus_rtu_init(&modbus, &modbus_is);
    // Установка каллбэка получения сообщения.
    modbus_rtu_set_msg_recv_callback(&modbus, modbus_on_msg_recv);
    // Установка каллбэков доступа к данным.
    modbus_rtu_set_read_coil_callback(&modbus, modbus_on_read_coil);
    modbus_rtu_set_write_coil_callback(&modbus, modbus_on_write_coil);
    modbus_rtu_set_report_slave_id_callback(&modbus, modbus_on_report_slave_id);
    modbus_rtu_set_read_holding_reg_callback(&modbus, modbus_on_read_hold_reg);
    modbus_rtu_set_write_holding_reg_callback(&modbus, modbus_on_write_reg);
}

 

  Вот и вся прошивка (опущена реализация библиотек USART и Modbus RTU - ссылки на них в конце статьи, к тому же последняя довольно громоздка), теперь наше устройство будет реагировать на запросы Modbus на интерфейсе USART1.

  Самое замечательное и удобное, на мой взгляд, это те самые функции обратного вызова (каллбэки) доступа к данным - не нужно заботиться об обработке запроса и правильном заполнении ответа, всё это за нас сделает библиотека, всё что нам нужно - указать какую функцию вызывать для какого типа данных!

  Размер прошивки составляет всего 6.5 кб (6634 байта).

Реализация для компьютера

  Для компьютера воспользуемся довольно удобной библиотекой libmodbus.

Программа

  Тут всё очень просто, библиотека имеет довольно простой и удобный интерфейс.

  Переменные.

// Контекст Modbus.
static modbus_t* modbus = NULL;

И обращение к устройству (опустим обработку ошибок).

// Создадим контекст Modbus и откроем порт с заданными параметрами.
modbus = modbus_new_rtu("/dev/ttyUSB0", 115200, 'E', 9, 1);

// Режим - RS-232.
modbus_rtu_set_serial_mode(modbus, MODBUS_RTU_RS232);
// Установим адрес ведомого.
modbus_set_slave(modbus, 0xaa);

// Соединимся.
modbus_connect(modbus);

// Состояние светодиода.
uint8_t led0 = 0;
// Получим значение регистра флагов.
modbus_read_bits(modbus, LED_ADDR, 1, &led0);

// Выведем на экран.
printf("Led status: %un", (unsigned int)led0);

// Инвертируем.
led0 = !led0;

// Запишем значение регистра флагов.
modbus_write_bit(modbus, LED_ADDR, led0);

 

// Значение счётчика.
uint16_t cnt_reg = 0;

// Получим значение регистра хранения.
modbus_read_registers(modbus, REG_ADDR, 1, &cnt_reg);

// Выведем на экран.
printf("Counter reg value: %un", (unsigned int)cnt_reg);

// Обнулим значение регистра хранения.
modbus_write_register(modbus, REG_ADDR, 0);

 

// Буфер для идентификатора устройства.
uint8_t report[252];
// Получим идентификатор ведомого.
int id_size = modbus_report_slave_id(modbus, report);

// Нулевой байт в ответе - идентификатор.
uint8_t id = report[0];
// Первый байт - состояние.
bool is_run = report[1] == 0xff;
// Затем идут дополнительные данные
// В нашем случае это имя ведомого.
const char* data = (const char*)&report[2];
// Символ конца строки.
report[id_size] = 0x0;

// Выведем полученные сведения на экран.
printf("Slave id: 0x%x running: %d data: %sn", id, (int)is_run, data);

  Всё, теперь при запуске программы, на устройстве светодиод будет менять своё состояние, счётчик сбрасываться, а в консоль будет выводиться состояние светодиода, значение счётчика и идентификатор ведомого устройства.

  Пример вывода на консоль:

Led status: 1
Counter reg value: 12
Slave id: 0xaa running: 1 data: STM32 MCU Modbus v1.0

Заключение

  Теперь можно довольно просто управлять нашим устройством с компьютера, или даже несколькими устройствами, соединёнными в сеть (например в случае того же умного дома). В будущем также планируется реализовать полноценный режим ведущего устройства.

  Спасибо за внимание!

  Исходный код проектов для МК и ПК находится в прикреплённых файлах.

  Библиотеки USART и Modbus RTU лежат на GitHub.


Файлы:
Проект для микроконтроллера
Проект для ПК


Все вопросы в Форум.




Как вам эта статья?

Заработало ли это устройство у вас?

25 6 10
2 0 1

Эти статьи вам тоже могут пригодиться: