# STM Modbus: подключение и использование

Библиотека реализует Modbus RTU Slave для STM32 с использованием HAL. Работает в прерываниях с детектированием конца фрейма по IDLE линии и таймаутами через прерывание таймера.

## Быстрый старт
_Note: Здесь описано подключение просто архива c исходниками. Если надо подключить библиотеку как субмодуль: см. ветку **release** в этом репозитории._

### 1. Настройка периферии

- **UART**: Настройте в режиме Asynchronous, нужная скорость (9600, 19200, etc), 8N1
- **TIM**: Настройте таймер для генерации прерываний (например, 1ms tick)
- **Включите прерывания** для UART и TIM

### 2. Подключение обработчиков прерываний

Подключите обработчики прерываний **UART** и **TIM** в свои IRQ обработчики ***вместо*** HAL-обработчиков:

```c
#include "modbus.h"

void USARTx_IRQHandler(void)
{
    RS_UART_Handler(&hmodbus1);
    return;
    HAL_UART_IRQHandler(&huart);
}

void TIMx_IRQHandler(void)
{
    RS_TIM_Handler(&hmodbus1);
    return;
    HAL_TIM_IRQHandler(&htim);
}
```

### 3. Настройка конфигурации

В `modbus_config.h` укажите параметры устройства:

```c
// MODBUS PARAMS
#define MODBUS_DEVICE_ID              1       ///< Адрес устройства в сети Modbus
#define MODBUS_TIMEOUT                5000    ///< Таймаут в тиках таймера

// Строковые идентификаторы устройства
#define MODBUS_VENDOR_NAME            "NIO-12"
#define MODBUS_PRODUCT_CODE           ""
#define MODBUS_REVISION               "Ver. 1.0"
#define MODBUS_VENDOR_URL             ""
#define MODBUS_PRODUCT_NAME           ""
#define MODBUS_MODEL_NAME             "STM32F103"
#define MODBUS_USER_APPLICATION_NAME  ""
#define MODBUS_NUMB_OF_USEROBJECTS    0

// Периферия
#define mb_huart                      huart1  // Удобный дефайн для модбасовского UART
#define mb_htim                       htim3   // Удобный дефайн для модбасовского таймера

// Модули modbus
#define MODBUS_ENABLE_SLAVE                   ///< Включить обработку МАСТЕР режима
//#define MODBUS_ENABLE_MASTER                  ///< Включить обработку СЛЕЙВ режима

#define MODBUS_ENABLE_COILS                   ///< Включить обработку коилов
#define MODBUS_ENABLE_HOLDINGS                ///< Включить обработку регистров хранения
#define MODBUS_ENABLE_INPUTS                  ///< Включить обработку входных регистров
#define MODBUS_ENABLE_DEVICE_IDENTIFICATIONS   ///< Включить обработку идентификаторы устройства
#define MODBUS_ENABLE_DIAGNOSTICS             ///< Включить обработку диагностики модбас

```

### 4. Инициализация в коде

Чтобы настроить Slave-режим `main()` после инициализации HAL:

```c
#include "modbus.h"

int main(void)
{
	// Инициализация HAL
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	MX_USART1_UART_Init();
	MX_TIM3_Init();
	
	// Инициализация Modbus
	MODBUS_FirstInit(&hmodbus1, &mb_huart, &mb_htim);
	MODBUS_Config(&hmodbus1, MODBUS_DEVICE_ID, MODBUS_TIMEOUT, MODBUS_MODE_SLAVE);
	
	// Запуск приема Modbus
	MODBUS_SlaveStart(&hmodbus1, NULL);
	
	while (1)
	{
		// Основной цикл
	}
}
```

Чтобы настроить Master-режим `main()` после инициализации HAL:

```c
#include "modbus.h"
read_hold[10];
// Запрос на 1 ID, считать холдинг регистры с 0 адреса 10 штук
RS_MsgTypeDef read_hold_cmd = MB_MASTER_READ_HOLDING_REGS(1, 0, 10);
// коллбек, вызовется при получении ответа от слейва
void callback_func(RS_HandleTypeDef *hmodbus, RS_MsgTypeDef *modbus_msg) 
{
	// MB_RespGet_... Чтобы достать нужные данные из ответа
	for(int addr = MODBUS_MSG.Addr; addr < MODBUS_MSG.Addr + MODBUS_MSG.Qnt; addr++)
	{
		uint16_t value;
		if(MB_RespGet_RegisterValue(&MODBUS_MSG, addr, &value))
		{
			read_hold[i] = value;
		}
	}
}
int main(void)
{
	// Инициализация HAL
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	MX_USART1_UART_Init();
	MX_TIM3_Init();
	
	// Инициализация Modbus
	MODBUS_FirstInit(&hmodbus1, &mb_huart, &mb_htim);
	MODBUS_Config(&hmodbus1, 0, MODBUS_TIMEOUT, MODBUS_MODE_MASTER);
	
	// Запрос по Modbus
	MODBUS_MasterRequest(&hmodbus1, &read_hold_cmd, &callback_func);

}
```
### 5. Настройка карты данных

В `modbus_data.h` настройте регистры и coils под ваше устройство:

#### Input Registers (только чтение)
```c
typedef struct
{
    uint16_t Temperature;      // Адрес 0
    uint16_t Humidity;         // Адрес 1  
    uint16_t Pressure;         // Адрес 2
    uint16_t Voltage;          // Адрес 3
} MB_DataInRegsTypeDef;

#define R_INPUT_ADDR        0  // Начальный адрес Input регистров
#define R_INPUT_QNT         4  // Количество Input регистров
```

#### Holding Registers (чтение/запись)
```c
typedef struct
{
    uint16_t SetpointTemp;     // Адрес 0
    uint16_t SetpointHumidity; // Адрес 1
    uint16_t ControlMode;      // Адрес 2
} MB_DataHoldRegsTypeDef;

#define R_HOLDING_ADDR      0  // Начальный адрес Holding регистров  
#define R_HOLDING_QNT       3  // Количество Holding регистров
```

#### Coils (1-битные)
```c
typedef struct
{
    unsigned Relay1    : 1;    // Адрес 0
    unsigned Relay2    : 1;    // Адрес 1  
    unsigned Pump      : 1;    // Адрес 2
    unsigned Heater    : 1;    // Адрес 3
    unsigned reserved  : 12;   // Резерв (выравнивание до 16 бит)
} MB_DataCoilsTypeDef;

#define C_COILS_ADDR        0  // Начальный адрес Coils
#define C_COILS_QNT         4  // Количество Coils
```

### 6. Доступ к данным в коде

В режиме слейва есть дефайны для удобного выставления Коилов. На случай если они не упакованы в битовые поля
```c
// Чтение входных регистров
uint16_t temp = MB_DATA.InRegs.Temperature;

// Запись в регистры хранения
MB_DATA.HoldRegs.SetpointTemp = 2500;

// Управление coils
MB_Coil_Set_Local(&MB_DATA.Coils, 0);  // Включить Relay1
MB_Coil_Reset_Local(&MB_DATA.Coils, 1); // Выключить Relay2

// Чтение coil
if (MB_Coil_Read_Local(&MB_DATA.Coils, 2)) {
    // Pump включен
}
```

В режиме мастера есть функции для получения информации из ответа `MB_RespGet_...()`
```c
//  Чтение регистров: Получить запрошенные регистры
	uint16_t value;
	if(MB_RespGet_RegisterValue(&MODBUS_MSG, 105, &reg_value))
	{
		printf("Register 105 value: %d\n", reg_value);
	}
//  Чтение коилов: Получить запрошенные коилы
	int state;
	if(MB_RespGet_CoilState(&MODBUS_MSG, 25, &coil_state))
	{
		printf("Coil 25 state: %s\n", coil_state ? "ON" : "OFF");
	}
//  Чтение диагностики: Получить запрошенныую диагностику
	uint16_t counter_value;
	if(MB_RespGet_DiagnosticResponse(&MODBUS_MSG, &counter_value))
	{
		printf("Counter value: %d\n", counter_value);
	}
//  Чтение идентификаторов: Получить запрошенные идентификаторы
	uint8_t length;
	char vendor_name[64];
	if(MB_RespGet_ObjectById(&MODBUS_MSG, 0x00, vendor_name, &length))
	{
		printf("Vendor Name: %s (length: %d)\n", vendor_name, length);
	}	
	
	uint8_t obj_id, obj_length;
	char obj_data[64];
	if(MB_RespGet_ObjectByIndex(&MODBUS_MSG, 0x00, &obj_id, obj_data, &obj_length))
	{
		printf("First object - ID: 0x%02X, Data: %s\n", obj_id, obj_data);
	}	
```
## Поддерживаемые функции Modbus

| Функция | Код | Описание |
|---------|-----|-----------|
| Read Coils | 0x01 | Чтение дискретных выходов |
| Read Input Registers | 0x04 | Чтение входных регистров |
| Read Holding Registers | 0x03 | Чтение регистров хранения |
| Write Single Coil | 0x05 | Запись одиночного coil |
| Write Single Register | 0x06 | Запись одиночного регистра |
| Diagnostics (Serial Line only) | 0x08 | Чтение диагностически и управление режимом работы |
| Write Multiple Coils | 0x0F | Запись множественных coils |
| Write Multiple Registers | 0x10 | Запись множественных регистров |
| Read Device Identification | 0x2B | Чтение идентификации устройства |
