commit 2317b904bad1ac28e74614bea6021ad8c8ba88dc Author: Razvalyaev Date: Thu Aug 28 20:59:31 2025 +0300 Пока не рабоатет diff --git a/Arduino_Modbus.ino b/Arduino_Modbus.ino new file mode 100644 index 0000000..4948b75 --- /dev/null +++ b/Arduino_Modbus.ino @@ -0,0 +1,23 @@ +#include "rs_message.h" + +void setup() { + // put your setup code here, to run once: + // Пример: RX=16, TX=17, скорость 115200 + rs_huart.begin(115200, SERIAL_8N1, 8, 9); + rs_huart.println("start1"); + MODBUS_FirstInit(); + + + Serial.begin(115200); // для отладки + Serial.println("start"); + +} + +void loop() { + // put your main code here, to run repeatedly: + RS_UART_Handler(&hmodbus1); // нужно вызывать периодически + RS_TIM_Handler(&hmodbus1); // проверка таймаута + //delay(500); + //Serial.println("start"); + //rs_huart.println("start1"); +} diff --git a/crc_algs.cpp b/crc_algs.cpp new file mode 100644 index 0000000..fcfa779 --- /dev/null +++ b/crc_algs.cpp @@ -0,0 +1,116 @@ +#include "crc_algs.h" + + +uint32_t CRC_calc; +uint32_t CRC_ref; + +//uint16_t CRC_calc; +//uint16_t CRC_ref; + + +// left this global for debug +uint8_t uchCRCHi = 0xFF; +uint8_t uchCRCLo = 0xFF; +unsigned uIndex; + + +uint32_t crc32(uint8_t *data, uint32_t data_size) +{ + static const unsigned int crc32_table[] = +{ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; +unsigned int crc = 0xFFFFFFFF; + while (data_size--) + { + crc = (crc >> 8) ^ crc32_table[(crc ^ *data) & 255]; + data++; + } + return crc^0xFFFFFFFF; +} + + +uint16_t crc16(uint8_t *data, uint32_t data_size) +{ + /*Table of CRC values for high order byte*/ + static unsigned char auchCRCHi[]= + { + 0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40, + 0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41, + 0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41, + 0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40, + 0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41, + 0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40, + 0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40, + 0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41, + 0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41, + 0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40, + 0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40, + 0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41, + 0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40, + 0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41, + 0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41, + 0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40, + }; + /*Table of CRC values for low order byte*/ + static char auchCRCLo[] = + { + 0x00,0xC0,0xC1,0x01,0xC3,0x03,0x02,0xC2,0xC6,0x06,0x07,0xC7,0x05,0xC5,0xC4,0x04, + 0xCC,0x0C,0x0D,0xCD,0x0F,0xCF,0xCE,0x0E,0x0A,0xCA,0xCB,0x0B,0xC9,0x09,0x08,0xC8, + 0xD8,0x18,0x19,0xD9,0x1B,0xDB,0xDA,0x1A,0x1E,0xDE,0xDF,0x1F,0xDD,0x1D,0x1C,0xDC, + 0x14,0xD4,0xD5,0x15,0xD7,0x17,0x16,0xD6,0xD2,0x12,0x13,0xD3,0x11,0xD1,0xD0,0x10, + 0xF0,0x30,0x31,0xF1,0x33,0xF3,0xF2,0x32,0x36,0xF6,0xF7,0x37,0xF5,0x35,0x34,0xF4, + 0x3C,0xFC,0xFD,0x3D,0xFF,0x3F,0x3E,0xFE,0xFA,0x3A,0x3B,0xFB,0x39,0xF9,0xF8,0x38, + 0x28,0xE8,0xE9,0x29,0xEB,0x2B,0x2A,0xEA,0xEE,0x2E,0x2F,0xEF,0x2D,0xED,0xEC,0x2C, + 0xE4,0x24,0x25,0xE5,0x27,0xE7,0xE6,0x26,0x22,0xE2,0xE3,0x23,0xE1,0x21,0x20,0xE0, + 0xA0,0x60,0x61,0xA1,0x63,0xA3,0xA2,0x62,0x66,0xA6,0xA7,0x67,0xA5,0x65,0x64,0xA4, + 0x6C,0xAC,0xAD,0x6D,0xAF,0x6F,0x6E,0xAE,0xAA,0x6A,0x6B,0xAB,0x69,0xA9,0xA8,0x68, + 0x78,0xB8,0xB9,0x79,0xBB,0x7B,0x7A,0xBA,0xBE,0x7E,0x7F,0xBF,0x7D,0xBD,0xBC,0x7C, + 0xB4,0x74,0x75,0xB5,0x77,0xB7,0xB6,0x76,0x72,0xB2,0xB3,0x73,0xB1,0x71,0x70,0xB0, + 0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54, + 0x9C,0x5C,0x5D,0x9D,0x5F,0x9F,0x9E,0x5E,0x5A,0x9A,0x9B,0x5B,0x99,0x59,0x58,0x98, + 0x88,0x48,0x49,0x89,0x4B,0x8B,0x8A,0x4A,0x4E,0x8E,0x8F,0x4F,0x8D,0x4D,0x4C,0x8C, + 0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,0x40, + }; + uchCRCHi = 0xFF; + uchCRCLo = 0xFF; + /* CRC Generation Function */ + while( data_size--) /* pass through message buffer */ + { + uIndex = uchCRCHi ^ *data++; /* calculate the CRC */ + uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex]; + uchCRCLo = auchCRCLo[uIndex]; + } + return uchCRCHi | uchCRCLo<<8; +} diff --git a/crc_algs.h b/crc_algs.h new file mode 100644 index 0000000..a7a68e8 --- /dev/null +++ b/crc_algs.h @@ -0,0 +1,14 @@ + +#ifndef CRC_ALGS_H +#define CRC_ALGS_H + +#include + +extern uint32_t CRC_calc; +extern uint32_t CRC_ref; + + +uint16_t crc16(uint8_t *data, uint32_t data_size); +uint32_t crc32(uint8_t *data, uint32_t data_size); + +#endif \ No newline at end of file diff --git a/modbus.cpp b/modbus.cpp new file mode 100644 index 0000000..0c180b1 --- /dev/null +++ b/modbus.cpp @@ -0,0 +1,954 @@ +/** +************************************************************************** +* @file modbus.c +* @brief Модуль для реализации MODBUS. +************************************************************************** +* @details Файл содержит реализацию функций работы с Modbus, включая: +* - доступ к coils и registers; +* - обработку команд протокола; +* - взаимодействие с RS (UART); +* - инициализацию. +* +* @section Функции и макросы +* +* ### Доступ к coils: +* - MB_Set_Coil_Local() — Установить coil по локальному адресу. +* - MB_Reset_Coil_Local() — Сбросить coil по локальному адресу. +* - MB_Toogle_Coil_Local() — Инвертировать coil по локальному адресу. +* - MB_Read_Coil_Local() — Прочитать coil по локальному адресу. +* - MB_Write_Coil_Global() — Установить/сбросить coil по глобальному адресу. +* - MB_Read_Coil_Global() — Прочитать coil по глобальному адресу. +* +* ### Обработка команд Modbus: +* - MB_DefineRegistersAddress() — Определить начальный адрес регистра. +* - MB_DefineCoilsAddress() — Определить начальный адрес coils. +* - MB_Check_Address_For_Arr() — Проверить, принадлежит ли адрес массиву. +* - Основные команды Modbus: +* - MB_Read_Coils() +* - MB_Read_Hold_Regs() +* - MB_Write_Single_Coil() +* - MB_Write_Miltuple_Coils() +* - MB_Write_Miltuple_Regs() +* +* ### Функции для работы с RS (UART): +* - RS_Parse_Message() / RS_Collect_Message() — Парсинг и сборка сообщения. +* - RS_Response() — Отправка ответа. +* - RS_Define_Size_of_RX_Message() — Определение размера принимаемого сообщения. +* - RS_Init() — Инициализация UART. +* +* ### Инициализация: +* - MODBUS_FirstInit() — Инициализация модуля Modbus. +* +* @section Структура данных Modbus +* +* #### Holding/Input Registers: +* - Регистры — 16-битные слова. Доступ к регистрам осуществляется через указатель. +* Таким образом, сами регистры могут представлять собой как массив так и структуру. +* +* #### Coils: +* - Coils — это биты, упакованные в 16-битные слова. Доступ к коилам осуществляется через указатель. +* Таким образом, сами коилы могут представлять собой как массив так и структуру. +* +* @section Инструкция по подключению +* Для корректной работы надо подключить обработчики RS_UART_Handler(), RS_TIM_Handler(), +* в соответствубщие низкоуровневые прерывания UART_IRQHandler, TIM_IRQHandler. После HAL'овского обработчика +* +* Также необходимо в modbus_config.h настроить дефайны для нужной работы UART +* После для запуска Modbus: +* @verbatim + //----------------Прием модбас----------------// + #include "rs_message.h" + MODBUS_FirstInit(); + RS_Receive_IT(&hmodbus1, &MODBUS_MSG); +* @endverbatim +* +******************************************************************************/ +#include "crc_algs.h" +#include "rs_message.h" +uint32_t dbg_temp, dbg_temp2, dbg_temp3; // for debug +RS_HandleTypeDef hmodbus1; + +/* DEFINE REGISTERS/COILS */ +MB_DeviceIdentificationTypeDef MB_INFO; +MB_DataStructureTypeDef MB_DATA; +RS_MsgTypeDef MODBUS_MSG; + +//------------------------------------------------------------------- +//-----------------------------FOR USER------------------------------ +/** + * @brief First set up of MODBUS. + * @details Первый инит модбас. Заполняет структуры и инициализирует таймер и юарт для общения по модбас. + * @note This called from main + */ +void MODBUS_FirstInit(void) +{ + MB_DevoceInentificationInit(); + //-----------SETUP MODBUS------------- + // set up modbus: MB_RX_Size_NotConst and Timeout enable + hmodbus1.ID = MODBUS_DEVICE_ID; + hmodbus1.sRS_Timeout = MODBUS_TIMEOUT; + hmodbus1.sRS_Mode = SLAVE_ALWAYS_WAIT; + hmodbus1.sRS_RX_Size_Mode = RS_RX_Size_NotConst; + + // INIT + hmodbus1.RS_STATUS = RS_Init(&hmodbus1, &rs_huart, 0); + + RS_EnableReceive(); +} +/** + * @brief Set or Reset Coil at its global address. + * @param Addr - адрес коила. + * @param WriteVal - Что записать в коил: 0 или 1. + * @return ExceptionCode - Код исключения если коила по адресу не существует, и NO_ERRORS если все ок. + * + * @details Позволяет обратиться к любому коилу по его глобальному адрессу. + Вне зависимости от того как коилы размещены в памяти. + */ +MB_ExceptionTypeDef MB_Write_Coil_Global(uint16_t Addr, MB_CoilsOpTypeDef WriteVal) +{ + //---------CHECK FOR ERRORS---------- + MB_ExceptionTypeDef Exception = NO_ERRORS; + uint16_t *coils; + uint16_t start_shift = 0; // shift in coils register + + //------------WRITE COIL------------- + Exception = MB_DefineCoilsAddress(&coils, Addr, 1, &start_shift, 1); + if(Exception == NO_ERRORS) + { + switch(WriteVal) + { + case SET_COIL: + *coils |= (1<= R_ARR_ADDR) + { + // if quantity too big return error + if ((Addr - R_ARR_ADDR) + Qnt > R_ARR_NUMB) + { + return ILLEGAL_DATA_ADDRESS; // return exception code + } + // if all ok - return no errors + return NO_ERRORS; + } + // if address isnt from this array return error + else + return ILLEGAL_DATA_ADDRESS; // return exception code +} +/** + * @brief Define Address Origin for Input/Holding Registers + * @param pRegs - указатель на указатель регистров. + * @param Addr - адрес начального регистра. + * @param Qnt - количество запрашиваемых регистров. + * @param WriteFlag - флаг регистр нужны для чтения или записи. + * @return ExceptionCode - Код исключения если есть, и NO_ERRORS если нет. + * + * @details Определение адреса начального регистра. + * @note WriteFlag пока не используется. + */ +MB_ExceptionTypeDef MB_DefineRegistersAddress(uint16_t **pRegs, uint16_t Addr, uint16_t Qnt, uint8_t RegisterType) +{ + /* check quantity error */ + if (Qnt > 125) + { + return ILLEGAL_DATA_VALUE; // return exception code + } + + if(RegisterType == RegisterType_Holding) + { + // Default holding registers + if(MB_Check_Address_For_Arr(Addr, Qnt, R_HOLDING_ADDR, R_HOLDING_QNT) == NO_ERRORS) + { + *pRegs = MB_Set_Register_Ptr(&MB_DATA.HoldRegs, Addr); // указатель на выбранный по Addr регистр + } + // if address doesnt match any array - return illegal data address response + else + { + return ILLEGAL_DATA_ADDRESS; + } + } + else if(RegisterType == RegisterType_Input) + { + // Default input registers + if(MB_Check_Address_For_Arr(Addr, Qnt, R_INPUT_ADDR, R_INPUT_QNT) == NO_ERRORS) + { + *pRegs = MB_Set_Register_Ptr(&MB_DATA.InRegs, Addr); // указатель на выбранный по Addr регистр + } + // if address doesnt match any array - return illegal data address response + else + { + return ILLEGAL_DATA_ADDRESS; + } + } + else + { + return ILLEGAL_FUNCTION; + } + // if found requeried array return no err + return NO_ERRORS; // return no errors +} +/** + * @brief Define Address Origin for coils + * @param pCoils - указатель на указатель коилов. + * @param Addr - адресс начального коила. + * @param Qnt - количество запрашиваемых коилов. + * @param start_shift - указатель на переменную содержащую сдвиг внутри регистра для начального коила. + * @param WriteFlag - флаг коилы нужны для чтения или записи. + * @return ExceptionCode - Код исключения если есть, и NO_ERRORS если нет. + * + * @details Определение адреса начального регистра запрашиваемых коилов. + * @note WriteFlag используется для определния регистров GPIO: ODR или IDR. + */ +MB_ExceptionTypeDef MB_DefineCoilsAddress(uint16_t **pCoils, uint16_t Addr, uint16_t Qnt, uint16_t *start_shift, uint8_t WriteFlag) +{ + /* check quantity error */ + if (Qnt > 2000) + { + return ILLEGAL_DATA_VALUE; // return exception code + } + + // Default coils + if(MB_Check_Address_For_Arr(Addr, Qnt, C_CONTROL_ADDR, C_CONTROL_QNT) == NO_ERRORS) + { + *pCoils = MB_Set_Coil_Reg_Ptr(&MB_DATA.Coils, Addr); // указатель на выбранный по Addr массив коилов + } + // if address doesnt match any array - return illegal data address response + else + { + return ILLEGAL_DATA_ADDRESS; + } + + *start_shift = Addr % 16; // set shift to requested coil + // if found requeried array return no err + return NO_ERRORS; // return no errors +} + + +/** + * @brief Proccess command Read Coils (01 - 0x01). + * @param modbus_msg - указатель на структуру собщения modbus. + * @return fMessageHandled - статус о результате обработки комманды. + * @details Обработка команды Read Coils. + */ +uint8_t MB_Read_Coils(RS_MsgTypeDef *modbus_msg) +{ + //---------CHECK FOR ERRORS---------- + uint16_t *coils; + uint16_t start_shift = 0; // shift in coils register + + modbus_msg->Except_Code = MB_DefineCoilsAddress(&coils, modbus_msg->Addr, modbus_msg->Qnt, &start_shift, 0); + if(modbus_msg->Except_Code != NO_ERRORS) + return 0; + + //-----------READING COIL------------ + // setup output message data size + modbus_msg->ByteCnt = Divide_Up(modbus_msg->Qnt, 8); + // create mask for coils + uint16_t mask_for_coils = 0; // mask for coils that've been chosen + uint16_t setted_coils = 0; // value of setted coils + uint16_t temp_reg = 0; // temp register for saving coils that hasnt been chosen + uint16_t coil_cnt = 0; // counter for processed coils + + // cycle until all registers with requered coils would be processed + int shift = start_shift; // set shift to first coil in first register + int ind = 0; // index for coils registers and data + for(; ind <= Divide_Up(start_shift + modbus_msg->Qnt, 16); ind++) + { + //----SET MASK FOR COILS REGISTER---- + mask_for_coils = 0; + for(; shift < 0x10; shift++) + { + mask_for_coils |= 1<<(shift); // choose certain coil + if(++coil_cnt >= modbus_msg->Qnt) + break; + } + shift = 0; // set shift to zero for the next step + + //-----------READ COILS-------------- + modbus_msg->DATA[ind] = (*(coils+ind)&mask_for_coils) >> start_shift; + if(ind > 0) + modbus_msg->DATA[ind-1] |= ((*(coils+ind)&mask_for_coils) << 16) >> start_shift; + + } + // т.к. DATA 16-битная, для 8-битной передачи, надо поменять местами верхний и нижний байты + for(; ind >= 0; --ind) + modbus_msg->DATA[ind] = ByteSwap16(modbus_msg->DATA[ind]); + + return 1; +} + +/** + * @brief Proccess command Read Holding Registers (03 - 0x03). + * @param modbus_msg - указатель на структуру собщения modbus. + * @return fMessageHandled - статус о результате обработки комманды. + * @details Обработка команды Read Holding Registers. + */ +uint8_t MB_Read_Hold_Regs(RS_MsgTypeDef *modbus_msg) +{ + //---------CHECK FOR ERRORS---------- + // get origin address for data + uint16_t *pHoldRegs; + modbus_msg->Except_Code = MB_DefineRegistersAddress(&pHoldRegs, modbus_msg->Addr, modbus_msg->Qnt, RegisterType_Holding); // определение адреса регистров + if(modbus_msg->Except_Code != NO_ERRORS) + return 0; + + + //-----------READING REGS------------ + // setup output message data size + modbus_msg->ByteCnt = modbus_msg->Qnt*2; // *2 because we transmit 8 bits, not 16 bits + // read data + int i; + for (i = 0; iQnt; i++) + { + modbus_msg->DATA[i] = *(pHoldRegs++); + } + return 1; +} + +/** + * @brief Proccess command Read Input Registers (04 - 0x04). + * @param modbus_msg - указатель на структуру собщения modbus. + * @return fMessageHandled - статус о результате обработки комманды. + * @details Обработка команды Read Input Registers. + */ +uint8_t MB_Read_Input_Regs(RS_MsgTypeDef *modbus_msg) +{ + //---------CHECK FOR ERRORS---------- + // get origin address for data + uint16_t *pInRegs; + modbus_msg->Except_Code = MB_DefineRegistersAddress(&pInRegs, modbus_msg->Addr, modbus_msg->Qnt, RegisterType_Input); // определение адреса регистров + if(modbus_msg->Except_Code != NO_ERRORS) + return 0; + + + //-----------READING REGS------------ + // setup output message data size + modbus_msg->ByteCnt = modbus_msg->Qnt*2; // *2 because we transmit 8 bits, not 16 bits + // read data + int i; + for (i = 0; iQnt; i++) + { + if(*((int16_t *)pInRegs) > 0) + modbus_msg->DATA[i] = (*pInRegs++); + else + modbus_msg->DATA[i] = (*pInRegs++); + } + return 1; +} +/** + * @brief Proccess command Write Single Coils (05 - 0x05). + * @param modbus_msg - указатель на структуру собщения modbus. + * @return fMessageHandled - статус о результате обработки комманды. + * @details Обработка команды Write Single Coils. + */ +uint8_t MB_Write_Single_Coil(RS_MsgTypeDef *modbus_msg) +{ + //---------CHECK FOR ERRORS---------- + if ((modbus_msg->Qnt != 0x0000) && (modbus_msg->Qnt != 0xFF00)) + { + modbus_msg->Except_Code = ILLEGAL_DATA_VALUE; + return 0; + } + // define position of coil + uint16_t *coils; + uint16_t start_shift = 0; // shift in coils register + modbus_msg->Except_Code = MB_DefineCoilsAddress(&coils, modbus_msg->Addr, 0, &start_shift, 1); + if(modbus_msg->Except_Code != NO_ERRORS) + return 0; + + + //----------WRITTING COIL------------ + if(modbus_msg->Qnt == 0xFF00) + *(coils) |= 1<Except_Code = MB_DefineRegistersAddress(&pHoldRegs, modbus_msg->Addr, 1, RegisterType_Holding); // определение адреса регистров + if(modbus_msg->Except_Code != NO_ERRORS) + return 0; + + //-----------WRITTING REG------------ + *(pHoldRegs) = modbus_msg->Qnt; + return 1; +} + +/** + * @brief Proccess command Write Multiple Coils (15 - 0x0F). + * @param modbus_msg - указатель на структуру собщения modbus. + * @return fMessageHandled - статус о результате обработки комманды. + * @details Обработка команды Write Multiple Coils. + */ +uint8_t MB_Write_Miltuple_Coils(RS_MsgTypeDef *modbus_msg) +{ + //---------CHECK FOR ERRORS---------- + if (modbus_msg->ByteCnt != Divide_Up(modbus_msg->Qnt, 8)) + { // if quantity too large OR if quantity and bytes count arent match + modbus_msg->Except_Code = ILLEGAL_DATA_VALUE; + return 0; + } + // define position of coil + uint16_t *coils; // pointer to coils + uint16_t start_shift = 0; // shift in coils register + modbus_msg->Except_Code = MB_DefineCoilsAddress(&coils, modbus_msg->Addr, modbus_msg->Qnt, &start_shift, 1); + if(modbus_msg->Except_Code != NO_ERRORS) + return 0; + + //----------WRITTING COILS----------- + // create mask for coils + uint16_t mask_for_coils = 0; // mask for coils that've been chosen + uint32_t setted_coils = 0; // value of setted coils + uint16_t temp_reg = 0; // temp register for saving coils that hasnt been chosen + uint16_t coil_cnt = 0; // counter for processed coils + + // cycle until all registers with requered coils would be processed + int shift = start_shift; // set shift to first coil in first register + for(int ind = 0; ind <= Divide_Up(start_shift + modbus_msg->Qnt, 16); ind++) + { + //----SET MASK FOR COILS REGISTER---- + mask_for_coils = 0; + for(; shift < 0x10; shift++) + { + mask_for_coils |= 1<<(shift); // choose certain coil + if(++coil_cnt >= modbus_msg->Qnt) + break; + } + shift = 0; // set shift to zero for the next step + + + + //-----------WRITE COILS------------- + // get current coils + temp_reg = *(coils+ind); + // set coils + setted_coils = ByteSwap16(modbus_msg->DATA[ind]) << start_shift; + if(ind > 0) + { + setted_coils |= ((ByteSwap16(modbus_msg->DATA[ind-1]) << start_shift) >> 16); + } + // write coils + + *(coils+ind) = setted_coils & mask_for_coils; + // restore untouched coils + *(coils+ind) |= temp_reg&(~mask_for_coils); + + + if(coil_cnt >= modbus_msg->Qnt) // if all coils written - break cycle + break; // *kind of unnecessary + } + + return 1; +} + +/** + * @brief Proccess command Write Multiple Registers (16 - 0x10). + * @param modbus_msg - указатель на структуру собщения modbus. + * @return fMessageHandled - статус о результате обработки комманды. + * @details Обработка команды Write Multiple Registers. + */ +uint8_t MB_Write_Miltuple_Regs(RS_MsgTypeDef *modbus_msg) +{ + //---------CHECK FOR ERRORS---------- + if (modbus_msg->Qnt*2 != modbus_msg->ByteCnt) + { // if quantity and bytes count arent match + modbus_msg->Except_Code = ILLEGAL_DATA_VALUE; + return 0; + } + // get origin address for data + uint16_t *pHoldRegs; + modbus_msg->Except_Code = MB_DefineRegistersAddress(&pHoldRegs, modbus_msg->Addr, modbus_msg->Qnt, RegisterType_Holding); // определение адреса регистров + if(modbus_msg->Except_Code != NO_ERRORS) + return 0; + + //-----------WRITTING REGS----------- + for (int i = 0; iQnt; i++) + { + *(pHoldRegs++) = modbus_msg->DATA[i]; + } + return 1; +} + +void MB_WriteObjectToMessage(char *mbdata, unsigned *ind, MB_DeviceObjectTypeDef *obj) +{ + mbdata[(*ind)++] = obj->length; + for (int i = 0; i < obj->length; i++) + { + mbdata[(*ind)++] = obj->name[i]; + } +} +/** + * @brief Proccess command Read Device Identification (43/14 - 0x2B/0E). + * @param modbus_msg - указатель на структуру собщения modbus. + * @return fMessageHandled - статус о результате обработки комманды. + * @details Обработка команды Write Single Register. + */ +uint8_t MB_Read_Device_Identification(RS_MsgTypeDef *modbus_msg) +{ + char *mbdata = (char *)modbus_msg->DATA; + unsigned ind = 0; + switch(modbus_msg->DevId.ReadDevId) + { + case MB_BASIC_IDENTIFICATION: + mbdata[ind++] = 0x00; + MB_WriteObjectToMessage(mbdata, &ind, &MB_INFO.VendorName); + mbdata[ind++] = 0x01; + MB_WriteObjectToMessage(mbdata, &ind, &MB_INFO.ProductCode); + mbdata[ind++] = 0x02; + MB_WriteObjectToMessage(mbdata, &ind, &MB_INFO.Revision); + modbus_msg->DevId.NumbOfObj = 3; + break; + case MB_REGULAR_IDENTIFICATION: + mbdata[ind++] = 0x03; + MB_WriteObjectToMessage(mbdata, &ind, &MB_INFO.VendorUrl); + mbdata[ind++] = 0x04; + MB_WriteObjectToMessage(mbdata, &ind, &MB_INFO.ProductName); + mbdata[ind++] = 0x05; + MB_WriteObjectToMessage(mbdata, &ind, &MB_INFO.ModelName); + mbdata[ind++] = 0x06; + MB_WriteObjectToMessage(mbdata, &ind, &MB_INFO.UserApplicationName); + modbus_msg->DevId.NumbOfObj = 4; + break; + default: + return 0; + } + + modbus_msg->ByteCnt = ind; + return 1; +} + + +/** + * @brief Respond accord to received message. + * @param hRS - указатель на хендлер RS. + * @param RS_msg - указатель на структуру сообщения. + * @return RS_RES - статус о результате ответа на комманду. + * @details Обработка принятой комманды и ответ на неё. + */ +RS_StatusTypeDef RS_Response(RS_HandleTypeDef *hmodbus, RS_MsgTypeDef *modbus_msg) +{ + RS_StatusTypeDef MB_RES = RS_OK; + hmodbus->f.MessageHandled = 0; + hmodbus->f.EchoResponse = 0; + RS_Reset_TX_Flags(hmodbus); // reset flag for correct transmit + + if(modbus_msg->Func_Code < ERR_VALUES_START)// if no errors after parsing + { + switch (modbus_msg->Func_Code) + { + // Read Coils + case MB_R_COILS: + hmodbus->f.MessageHandled = MB_Read_Coils(hmodbus->pMessagePtr); + break; + + // Read Hodling Registers + case MB_R_HOLD_REGS: + hmodbus->f.MessageHandled = MB_Read_Hold_Regs(hmodbus->pMessagePtr); + break; + case MB_R_IN_REGS: + hmodbus->f.MessageHandled = MB_Read_Input_Regs(hmodbus->pMessagePtr); + break; + + + // Write Single Coils + case MB_W_COIL: + hmodbus->f.MessageHandled = MB_Write_Single_Coil(hmodbus->pMessagePtr); + if(hmodbus->f.MessageHandled) + { + hmodbus->f.EchoResponse = 1; + hmodbus->RS_Message_Size -= 2; // echo response if write ok (minus 2 cause of two CRC bytes) + } + break; + + case MB_W_HOLD_REG: + hmodbus->f.MessageHandled = MB_Write_Single_Reg(hmodbus->pMessagePtr); + if(hmodbus->f.MessageHandled) + { + hmodbus->f.EchoResponse = 1; + hmodbus->RS_Message_Size -= 2; // echo response if write ok (minus 2 cause of two CRC bytes) + } + break; + + // Write Multiple Coils + case MB_W_COILS: + hmodbus->f.MessageHandled = MB_Write_Miltuple_Coils(hmodbus->pMessagePtr); + if(hmodbus->f.MessageHandled) + { + hmodbus->f.EchoResponse = 1; + hmodbus->RS_Message_Size = 6; // echo response if write ok (withous data bytes) + } + break; + + // Write Multiple Registers + case MB_W_HOLD_REGS: + hmodbus->f.MessageHandled = MB_Write_Miltuple_Regs(hmodbus->pMessagePtr); + if(hmodbus->f.MessageHandled) + { + hmodbus->f.EchoResponse = 1; + hmodbus->RS_Message_Size = 6; // echo response if write ok (withous data bytes) + } + break; + + case MB_R_DEVICE_INFO: + hmodbus->f.MessageHandled = MB_Read_Device_Identification(hmodbus->pMessagePtr); + break; + + + /* unknown func code */ + default: modbus_msg->Except_Code = ILLEGAL_FUNCTION; /* set exception code: illegal function */ + } + + if(hmodbus->f.MessageHandled == 0) + { + modbus_msg->Func_Code = static_cast( + static_cast(modbus_msg->Func_Code) + ERR_VALUES_START + ); + } + else + { + } + + + } + + // if we need response - check that transmit isnt busy + if( RS_Is_TX_Busy(hmodbus) ) + RS_Abort(hmodbus, ABORT_TX); // if tx busy - set it free + + // Transmit right there, or sets (fDeferredResponse) to transmit response in main code + MB_RES = RS_Handle_Transmit_Start(hmodbus, modbus_msg); + + hmodbus->RS_STATUS = MB_RES; + return MB_RES; +} + +/** + * @brief Collect message in buffer to transmit it. + * @param hRS - указатель на хендлер RS. + * @param RS_msg - указатель на структуру сообщения. + * @param msg_uart_buff - указатель на буффер UART. + * @return RS_RES - статус о результате заполнения буфера. + * @details Заполнение буффера UART из структуры сообщения. + */ +RS_StatusTypeDef RS_Collect_Message(RS_HandleTypeDef *hmodbus, RS_MsgTypeDef *modbus_msg, uint8_t *modbus_uart_buff) +{ + int ind = 0; // ind for modbus-uart buffer + + if(hmodbus->f.EchoResponse && hmodbus->f.MessageHandled) // if echo response need + ind = hmodbus->RS_Message_Size; + else + { + //------INFO ABOUT DATA/MESSAGE------ + //-----------[first bytes]----------- + // set ID of message/user + modbus_uart_buff[ind++] = modbus_msg->MbAddr; + + // set dat or err response + modbus_uart_buff[ind++] = modbus_msg->Func_Code; + + if (modbus_msg->Func_Code < ERR_VALUES_START) // if no error occur + { + // fill modbus header + if(modbus_msg->Func_Code == MB_R_DEVICE_INFO) // devide identification header + { + modbus_uart_buff[ind++] = modbus_msg->DevId.MEI_Type; + modbus_uart_buff[ind++] = modbus_msg->DevId.ReadDevId; + modbus_uart_buff[ind++] = modbus_msg->DevId.Conformity; + modbus_uart_buff[ind++] = modbus_msg->DevId.MoreFollows; + modbus_uart_buff[ind++] = modbus_msg->DevId.NextObjId; + modbus_uart_buff[ind++] = modbus_msg->DevId.NumbOfObj; + + if (modbus_msg->ByteCnt > DATA_SIZE*2) // if ByteCnt less than DATA_SIZE + { + return RS_COLLECT_MSG_ERR; + } + + + //---------------DATA---------------- + //-----------[data bytes]------------ + uint8_t *tmp_data_addr = (uint8_t *)modbus_msg->DATA; + for(int i = 0; i < modbus_msg->ByteCnt; i++) // filling buffer with data + { // set data + modbus_uart_buff[ind++] = *tmp_data_addr; + tmp_data_addr++; + } + + } + else // modbus data header + { + // set size of received data + if (modbus_msg->ByteCnt <= DATA_SIZE*2) // if ByteCnt less than DATA_SIZE + modbus_uart_buff[ind++] = modbus_msg->ByteCnt; + else // otherwise return data_size err + { + return RS_COLLECT_MSG_ERR; + } + + //---------------DATA---------------- + //-----------[data bytes]------------ + uint16_t *tmp_data_addr = (uint16_t *)modbus_msg->DATA; + for(int i = 0; i < modbus_msg->ByteCnt; i++) // filling buffer with data + { // set data + if (i%2 == 0) // HI byte + modbus_uart_buff[ind++] = (*tmp_data_addr)>>8; + else // LO byte + { + modbus_uart_buff[ind++] = *tmp_data_addr; + tmp_data_addr++; + } + } + + } + + } + else // if some error occur + { // send expection code + modbus_uart_buff[ind++] = modbus_msg->Except_Code; + } + } + //---------------CRC---------------- + //---------[last 16 bytes]---------- + // calc crc of received data + uint16_t CRC_VALUE = crc16(modbus_uart_buff, ind); + // write crc to message structure and modbus-uart buffer + modbus_msg->MB_CRC = CRC_VALUE; + modbus_uart_buff[ind++] = CRC_VALUE; + modbus_uart_buff[ind++] = CRC_VALUE >> 8; + + hmodbus->RS_Message_Size = ind; + + return RS_OK; // returns ok +} + +/** + * @brief Parse message from buffer to process it. + * @param hRS - указатель на хендлер RS. + * @param RS_msg - указатель на структуру сообщения. + * @param msg_uart_buff - указатель на буффер UART. + * @return RS_RES - статус о результате заполнения структуры. + * @details Заполнение структуры сообщения из буффера UART. + */ +RS_StatusTypeDef RS_Parse_Message(RS_HandleTypeDef *hmodbus, RS_MsgTypeDef *modbus_msg, uint8_t *modbus_uart_buff) +{ + uint32_t check_empty_buff; + int ind = 0; // ind for modbus-uart buffer + //-----INFO ABOUT DATA/MESSAGE------- + //-----------[first bits]------------ + // get ID of message/user + modbus_msg->MbAddr = modbus_uart_buff[ind++]; + if(modbus_msg->MbAddr != hmodbus->ID) + return RS_SKIP; + + // get func code + modbus_msg->Func_Code = static_cast(modbus_uart_buff[ind++]); + if(modbus_msg->Func_Code == MB_R_DEVICE_INFO) // if it device identification request + { + modbus_msg->DevId.MEI_Type = static_cast(modbus_uart_buff[ind++]); + modbus_msg->DevId.ReadDevId = static_cast(modbus_uart_buff[ind++]); + modbus_msg->DevId.NextObjId = modbus_uart_buff[ind++]; + modbus_msg->ByteCnt = 0; + } + else // if its classic modbus request + { + // get address from CMD + modbus_msg->Addr = modbus_uart_buff[ind++] << 8; + modbus_msg->Addr |= modbus_uart_buff[ind++]; + + // get address from CMD + modbus_msg->Qnt = modbus_uart_buff[ind++] << 8; + modbus_msg->Qnt |= modbus_uart_buff[ind++]; + } + if(hmodbus->f.RX_Half == 0) // if all message received + { + //---------------DATA---------------- + // (optional) + if (modbus_msg->ByteCnt != 0) + { + ind++; // increment ind for data_size byte + //check that data size is correct + if (modbus_msg->ByteCnt > DATA_SIZE*2) + { + modbus_msg->Func_Code = static_cast( + static_cast(modbus_msg->Func_Code) + ERR_VALUES_START + ); + return RS_PARSE_MSG_ERR; + } + uint16_t *tmp_data_addr = (uint16_t *)modbus_msg->DATA; + for(int i = 0; i < modbus_msg->ByteCnt; i++) // /2 because we transmit 8 bits, not 16 bits + { // set data + if (i%2 == 0) + *tmp_data_addr = ((uint16_t)modbus_uart_buff[ind++] << 8); + else + { + *tmp_data_addr |= modbus_uart_buff[ind++]; + tmp_data_addr++; + } + } + } + + //---------------CRC---------------- + //----------[last 16 bits]---------- + // calc crc of received data + uint16_t CRC_VALUE = crc16(modbus_uart_buff, ind); + // get crc of received data + modbus_msg->MB_CRC = modbus_uart_buff[ind++]; + modbus_msg->MB_CRC |= modbus_uart_buff[ind++] << 8; + // compare crc + if (modbus_msg->MB_CRC != CRC_VALUE) + { + modbus_msg->Func_Code = static_cast( + static_cast(modbus_msg->Func_Code) + ERR_VALUES_START + ); + } + // hmodbus->MB_RESPONSE = MB_CRC_ERR; // set func code - error about wrong crc + + // check is buffer empty + check_empty_buff = 0; + for(int i=0; iMB_RESPONSE = MB_EMPTY_MSG; // + } + + return RS_OK; + +} + +/** + * @brief Define size of RX Message that need to be received. + * @param hRS - указатель на хендлер RS. + * @param rx_data_size - указатель на переменную для записи кол-ва байт для принятия. + * @return RS_RES - статус о корректности рассчета кол-ва байт для принятия. + * @details Определение сколько байтов надо принять по протоколу. + */ +RS_StatusTypeDef RS_Define_Size_of_RX_Message(RS_HandleTypeDef *hmodbus, uint32_t *rx_data_size) +{ + RS_StatusTypeDef MB_RES = RS_OK; + + MB_RES = RS_Parse_Message(hmodbus, hmodbus->pMessagePtr, hmodbus->pBufferPtr); + if(MB_RES == RS_SKIP) // if message not for us + return MB_RES; // return + + + if ((hmodbus->pMessagePtr->Func_Code & ~ERR_VALUES_START) < 0x0F) + { + hmodbus->pMessagePtr->ByteCnt = 0; + *rx_data_size = 1; + + } + else + { + hmodbus->pMessagePtr->ByteCnt = hmodbus->pBufferPtr[RX_FIRST_PART_SIZE-1]; // get numb of data in command + // +1 because that defines is size, not ind. + *rx_data_size = hmodbus->pMessagePtr->ByteCnt + 2; + } + + + if(hmodbus->pMessagePtr->Func_Code == MB_R_DEVICE_INFO) + { + *rx_data_size = 0; + } + + hmodbus->RS_Message_Size = RX_FIRST_PART_SIZE + *rx_data_size; // size of whole message + return RS_OK; +} + +//-----------------------------FOR USER------------------------------ +//------------------------------------------------------------------- + + +void MB_DevoceInentificationInit(void) +{ + MB_INFO.VendorName.name = MODBUS_VENDOR_NAME; + MB_INFO.ProductCode.name = MODBUS_PRODUCT_CODE; + MB_INFO.Revision.name = MODBUS_REVISION; + MB_INFO.VendorUrl.name = MODBUS_VENDOR_URL; + MB_INFO.ProductName.name = MODBUS_PRODUCT_NAME; + MB_INFO.ModelName.name = MODBUS_MODEL_NAME; + MB_INFO.UserApplicationName.name = MODBUS_USER_APPLICATION_NAME; + + + MB_INFO.VendorName.length = sizeof(MODBUS_VENDOR_NAME); + MB_INFO.ProductCode.length = sizeof(MODBUS_PRODUCT_CODE); + MB_INFO.Revision.length = sizeof(MODBUS_REVISION); + MB_INFO.VendorUrl.length = sizeof(MODBUS_VENDOR_URL); + MB_INFO.ProductName.length = sizeof(MODBUS_PRODUCT_NAME); + MB_INFO.ModelName.length = sizeof(MODBUS_MODEL_NAME); + MB_INFO.UserApplicationName.length = sizeof(MODBUS_USER_APPLICATION_NAME); +} + + + diff --git a/modbus.h b/modbus.h new file mode 100644 index 0000000..8514eb4 --- /dev/null +++ b/modbus.h @@ -0,0 +1,372 @@ +/** + ************************************************************************** + * @file modbus.h + * @brief Заголовочный файл модуля MODBUS. + * @details Данный файл необходимо подключить в rs_message.h. После подключать + * rs_message.h к основному проекту. + * + * @defgroup MODBUS + * @brief Modbus stuff + * + *************************************************************************/ +#ifndef __MODBUS_H_ +#define __MODBUS_H_ + +#include "modbus_config.h" +#include "modbus_data.h" +//#include "settings.h" // for modbus settings + +/** + * @addtogroup MODBUS_SETTINGS + * @ingroup MODBUS + * @brief Some defines for modbus + @{ + */ +///////////////////////////////////////////////////////////////////// +//////////////////////////---SETTINGS---///////////////////////////// +// USER SETTINGS FOR MODBUS IN interface_config.h +//////////////////////////---SETTINGS---///////////////////////////// + + +///////////////////////////////////////////////////////////////////// +/////////////////////---USER MESSAGE DEFINES---////////////////////// +//-------------DEFINES FOR STRUCTURE---------------- +/* defines for structure of modbus message */ +#define MbAddr_SIZE 1 ///< size of (MbAddr) +#define Func_Code_SIZE 1 ///< size of (Func_Code) +#define Addr_SIZE 2 ///< size of (Addr) +#define Qnt_SIZE 2 ///< size of (Qnt) +#define ByteCnt_SIZE 1 ///< size of (ByteCnt) +#define DATA_SIZE 125 ///< maximum number of data: DWORD (NOT MESSAGE SIZE) +#define CRC_SIZE 2 ///< size of (MB_CRC) in bytes + +/** @brief Size of whole message */ +#define INFO_SIZE_MAX (MbAddr_SIZE+Func_Code_SIZE+Addr_SIZE+Qnt_SIZE+ByteCnt_SIZE) + +/** @brief Size of first part of message that will be received +first receive info part of message, than defines size of rest message*/ +#define RX_FIRST_PART_SIZE INFO_SIZE_MAX + +/** @brief Size of buffer: max size of whole message */ +#define MSG_SIZE_MAX (INFO_SIZE_MAX + DATA_SIZE*2 + CRC_SIZE) // max possible size of message + +/** @brief Structure for modbus exception codes */ +typedef enum //MB_ExceptionTypeDef +{ + // reading + NO_ERRORS = 0x00, ///< no errors + ILLEGAL_FUNCTION = 0x01, ///< Принятый код функции не может быть обработан + ILLEGAL_DATA_ADDRESS = 0x02, ///< Адрес данных, указанный в запросе, недоступен + ILLEGAL_DATA_VALUE = 0x03, ///< Значение, содержащееся в поле данных запроса, является недопустимой величиной + SLAVE_DEVICE_FAILURE = 0x04, ///< Невосстанавливаемая ошибка имела место, пока ведомое устройство пыталось выполнить затребованное действие +// ACKNOWLEDGE = 0x05, ///< idk +// SLAVE_DEVICE_BUSY = 0x06, ///< idk +// MEMORY_PARITY_ERROR = 0x08, ///< idk +}MB_ExceptionTypeDef; + +#define ERR_VALUES_START 0x80U ///< from this value starts error func codes +/** @brief Structure for modbus func codes */ +typedef enum //MB_FunctonTypeDef +{ + /* COMMANDS */ + // reading + MB_R_COILS = 0x01, ///< Чтение битовых ячеек + MB_R_DISC_IN = 0x02, ///< Чтение дискретных входов +#ifndef MODBUS_SWITCH_COMMAND_R_IN_REGS_AND_R_HOLD_REGS + MB_R_HOLD_REGS = 0x03, ///< Чтение входных регистров + MB_R_IN_REGS = 0x04, ///< Чтение регистров хранения +#else + MB_R_HOLD_REGS = 0x04, ///< Чтение входных регистров + MB_R_IN_REGS = 0x03, ///< Чтение регистров хранения +#endif + + // writting + MB_W_COIL = 0x05, ///< Запись битовой ячейки + MB_W_HOLD_REG = 0x06, ///< Запись одиночного регистра + MB_W_COILS = 0x0F, ///< Запись нескольких битовых ячеек + MB_W_HOLD_REGS = 0x10, ///< Запись нескольких регистров + + MB_R_DEVICE_INFO = 0x2B, ///< Чтения информации об устройстве + + /* ERRORS */ + // error reading + MB_ERR_R_COILS = MB_R_COILS + ERR_VALUES_START, ///< Ошибка чтения битовых ячеек + MB_ERR_R_DISC_IN = MB_R_DISC_IN + ERR_VALUES_START, ///< Ошибка чтения дискретных входов + MB_ERR_R_IN_REGS = MB_R_IN_REGS + ERR_VALUES_START, ///< Ошибка чтения регистров хранения + MB_ERR_R_HOLD_REGS = MB_R_HOLD_REGS + ERR_VALUES_START, ///< Ошибка чтения входных регистров + + // error writting + MB_ERR_W_COIL = MB_W_COIL + ERR_VALUES_START, ///< Ошибка записи битовой ячейки + MB_ERR_W_HOLD_REG = MB_W_HOLD_REG + ERR_VALUES_START, ///< Ошибка записи одиночного регистра + MB_ERR_W_COILS = MB_W_COILS + ERR_VALUES_START, ///< Ошибка записи нескольких битовых ячеек + MB_ERR_W_HOLD_REGS = MB_W_HOLD_REGS + ERR_VALUES_START, ///< Ошибка записи нескольких регистров +}MB_FunctonTypeDef; + +/** @brief Structure for MEI func codes */ +typedef enum //MB_FunctonTypeDef +{ + MEI_DEVICE_IDENTIFICATION = 0x0E, +}MB_MEITypeDef; + +/** @brief Structure for MEI func codes */ +typedef enum //MB_FunctonTypeDef +{ + MB_BASIC_IDENTIFICATION = 0x01, + MB_REGULAR_IDENTIFICATION = 0x02, + + + /* ERRORS */ + MB_ERR_BASIC_IDENTIFICATION = MB_BASIC_IDENTIFICATION + ERR_VALUES_START, + MB_ERR_REGULAR_IDENTIFICATION = MB_REGULAR_IDENTIFICATION + ERR_VALUES_START, +}MB_ConformityTypeDef; + +/** @brief Structure for decive identification message type */ +typedef struct +{ + MB_MEITypeDef MEI_Type; ///< MEI Type assigned number for Device Identification Interface + MB_ConformityTypeDef ReadDevId; + MB_ConformityTypeDef Conformity; + uint8_t MoreFollows; ///< in this library always a zero + uint8_t NextObjId; + uint8_t NumbOfObj; +}MB_DevIdMsgTypeDef; + +/** @brief Structure for modbus messsage */ +typedef struct // RS_MsgTypeDef +{ + uint8_t MbAddr; ///< Modbus Slave Address + MB_FunctonTypeDef Func_Code; ///< Modbus Function Code + MB_DevIdMsgTypeDef DevId; ///< Read Device Identification Header struct + uint16_t Addr; ///< Modbus Address of data + uint16_t Qnt; ///< Quantity of modbus data + uint8_t ByteCnt; ///< Quantity of bytes of data in message to transmit/receive + + uint16_t DATA[DATA_SIZE]; ///< Modbus Data + MB_ExceptionTypeDef Except_Code; ///< Exception Code for the command + + uint16_t MB_CRC; ///< Modbus CRC +}RS_MsgTypeDef; +//-------------------------------------------------- +extern RS_MsgTypeDef MODBUS_MSG; +/////////////////////---MODBUS USER SETTINGS---////////////////////// + +/** MODBUS_SETTINGS + * @} + */ + + +///////////////////////////////////////////////////////////////////// +////////////////////---MODBUS MESSAGE DEFINES---///////////////////// +/** + * @addtogroup MODBUS_MESSAGE_DEFINES + * @ingroup MODBUS + * @brief Some defines for modbus + @{ + */ +/** @brief Structure for coils operation */ +typedef enum +{ + SET_COIL, + RESET_COIL, + TOOGLE_COIL, +}MB_CoilsOpTypeDef; + +//-------------------------------------------------- + +/** + * @brief Macros to set pointer to 16-bit array + * @param _arr_ - массив регистров (16-бит). + */ +#define MB_Set_Arr16_Ptr(_arr_) ((uint16_t*)(&(_arr_))) +/** + * @brief Macros to set pointer to register + * @param _parr_ - массив регистров. + * @param _addr_ - Номер регистра (его индекс) от начала массива _arr_. + */ +#define MB_Set_Register_Ptr(_parr_, _addr_) ((uint16_t *)(_parr_)+(_addr_)) + +/** + * @brief Macros to set pointer to a certain register that contains certain coil + * @param _parr_ - массив коилов. + * @param _coil_ - Номер коила от начала массива _arr_. + * @note Используется вместе с @ref MB_Set_Coil_Mask + @verbatim Пояснение выражений + (_coil_/16) - get index (address shift) of register that contain certain coil + Visual explanation: 30th coil in coils registers array + xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxCx + |register[0]----| |register[1]----| + |skip this------| |get this-------| + |shift to 14 bit| + @endverbatim + */ +#define MB_Set_Coil_Reg_Ptr(_parr_, _coil_) ((uint16_t *)(_parr_)+((_coil_)/16)) +/** + * @brief Macros to set mask to a certain bit in coils register + * @param _coil_ - Номер коила от начала массива _arr_. + * @note Используется вместе с @ref MB_Set_Coil_Reg_Ptr + @verbatim Пояснение выражений + (16*(_coil_/16) - how many coils we need to skip. e.g. (16*30/16) - skip 16 coils from first register + _coil_-(16*(_coil_/16)) - shift to certain coil in certain register + e.g. Coil(30) gets in register[1] (30/16 = 1) coil №14 (30 - (16*30/16) = 30 - 16 = 14) + + Visual explanation: 30th coil in coils registers array + xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxCx + |register[0]----| |register[1]----| + |skip this------| |get this-------| + |shift to 14 bit| + @endverbatim + */ +#define MB_Set_Coil_Mask(_coil_) (1 << ( _coil_ - (16*((_coil_)/16)) )) + +/** + * @brief Read Coil at its local address. + * @param _parr_ - массив коилов. + * @param _coil_ - Номер коила от начала массива _arr_. + * @return uint16_t - Возвращает запрошенный коил на 0м бите. + * + * @details Позволяет обратиться к коилу по адресу относительно _arr_. + */ +#define MB_Read_Coil_Local(_parr_, _coil_) (( *MB_Set_Coil_Reg_Ptr(_parr_, _coil_) & MB_Set_Coil_Mask(_coil_) ) >> (_coil_)) +/** + * @brief Set Coil at its local address. + * @param _parr_ - указатель на массив коилов. + * @param _coil_ - Номер коила от начала массива _arr_. + * + * @details Позволяет обратиться к коилу по адресу относительно _arr_. + */ +#define MB_Set_Coil_Local(_parr_, _coil_) *MB_Set_Coil_Reg_Ptr(_parr_, _coil_) |= MB_Set_Coil_Mask(_coil_) +/** + * @brief Reset Coil at its local address. + * @param _parr_ - указатель на массив коилов. + * @param _coil_ - Номер коила от начала массива _arr_. + * + * @details Позволяет обратиться к коилу по адресу относительно _arr_. + */ +#define MB_Reset_Coil_Local(_parr_, _coil_) *MB_Set_Coil_Reg_Ptr(_parr_, _coil_) &= ~(MB_Set_Coil_Mask(_coil_)) +/** + * @brief Set Coil at its local address. + * @param _parr_ - указатель на массив коилов. + * @param _coil_ - Номер коила от начала массива _arr_. + * + * @details Позволяет обратиться к коилу по адресу относительно _arr_. + */ +#define MB_Toogle_Coil_Local(_parr_, _coil_) *MB_Set_Coil_Reg_Ptr(_parr_, _coil_) ^= MB_Set_Coil_Mask(_coil_) +//-------------------------------------------------- + + +//------------------OTHER DEFINES------------------- +#define RegisterType_Holding 0 +#define RegisterType_Input 1 +#define RegisterType_Discrete 2 +// create hadnles and settings for uart, tim, rs with _modbus_ name +//-------------------------------------------------- + +#ifndef Divide_Up +/** + * @brief Calc dividing including remainder + * @param _val_ - делимое. + * @param _div_ - делитель. + * @details Если результат деления без остатка: он возвращается как есть + Если с остатком - округляется вверх + */ +//#define Divide_Up(_val_, _div_) (((_val_)%(_div_))? (_val_)/(_div_)+1 : (_val_)/_div_) /* через тернарный оператор */ +#define Divide_Up(_val_, _div_) ((_val_ - 1) / _div_) + 1 /* через мат выражение */ +#endif + +#ifndef ByteSwap16 +/** + * @brief Swap between Little Endian and Big Endian + * @param v - Переменная для свапа. + * @return v (new) - Свапнутая переменная. + * @details Переключения между двумя типами хранения слова: HI-LO байты и LO-HI байты. + */ +#define ByteSwap16(v) (((v&0xFF00) >> (8)) | ((v&0x00FF) << (8))) +#endif +/** GENERAL_MODBUS_STUFF + * @} + */ +////////////////////---MODBUS MESSAGE DEFINES---///////////////////// + + + +///////////////////////////////////////////////////////////////////// +/////////////////////////---FUNCTIONS---///////////////////////////// +/** + * @addtogroup MODBUS_FUNCTIONS + * @ingroup MODBUS + * @brief Function for controling modbus communication + */ + +//----------------FUNCTIONS FOR USER---------------- +/** + * @addtogroup MODBUS_DATA_ACCESS_FUNCTIONS + * @ingroup MODBUS_FUNCTIONS + * @brief Function for user use + @{ + */ +/* First set up of MODBUS */ +void MODBUS_FirstInit(void); +/* Set or Reset Coil at its global address */ +MB_ExceptionTypeDef MB_Write_Coil_Global(uint16_t Addr, MB_CoilsOpTypeDef WriteVal); +/* Read Coil at its global address */ +uint16_t MB_Read_Coil_Global(uint16_t Addr, MB_ExceptionTypeDef *Exception); + +/** MODBUS_DATA_ACCESS_FUNCTIONS + * @} + */ + +//---------PROCESS MODBUS COMMAND FUNCTIONS--------- +/** + * @addtogroup MODBUS_CMD_PROCESS_FUNCTIONS + * @ingroup MODBUS_FUNCTIONS + * @brief Function process commands + @{ + */ +/* Check is address valid for certain array */ +MB_ExceptionTypeDef MB_Check_Address_For_Arr(uint16_t Addr, uint16_t Qnt, uint16_t R_ARR_ADDR, uint16_t R_ARR_NUMB); +/* Define Address Origin for Input/Holding Registers */ +MB_ExceptionTypeDef MB_DefineRegistersAddress(uint16_t **pRegs, uint16_t Addr, uint16_t Qnt, uint8_t RegisterType); +/* Define Address Origin for coils */ +MB_ExceptionTypeDef MB_DefineCoilsAddress(uint16_t **pCoils, uint16_t Addr, uint16_t Qnt, uint16_t *start_shift, uint8_t WriteFlag); +/* Proccess command Read Coils (01 - 0x01) */ +uint8_t MB_Read_Coils(RS_MsgTypeDef *modbus_msg); +/* Proccess command Read Holding Registers (03 - 0x03) */ +uint8_t MB_Read_Hold_Regs(RS_MsgTypeDef *modbus_msg); +/* Proccess command Read Input Registers (04 - 0x04) */ +uint8_t MB_Read_Input_Regs(RS_MsgTypeDef *modbus_msg); +/* Proccess command Write Single Coils (05 - 0x05) */ +uint8_t MB_Write_Single_Coil(RS_MsgTypeDef *modbus_msg); +/* Proccess command Write Multiple Coils (15 - 0x0F) */ +uint8_t MB_Write_Miltuple_Coils(RS_MsgTypeDef *modbus_msg); +/* Proccess command Write Multiple Register (16 - 0x10) */ +uint8_t MB_Write_Miltuple_Regs(RS_MsgTypeDef *modbus_msg); + +/** MODBUS_DATA_ACCESS_FUNCTIONS + * @} + */ +/////////////////////////---FUNCTIONS---///////////////////////////// + + + +///////////////////////////////////////////////////////////////////// +/////////////////////////---CALC DEFINES---////////////////////////// + + +// TRACES DEFINES +#ifndef Trace_MB_UART_Enter +#define Trace_MB_UART_Enter() +#endif //Trace_MB_UART_Enter + +#ifndef Trace_MB_UART_Exit +#define Trace_MB_UART_Exit() +#endif //Trace_MB_UART_Exit + +#ifndef Trace_MB_TIM_Enter +#define Trace_MB_TIM_Enter() +#endif //Trace_MB_TIM_Enter + +#ifndef Trace_MB_TIM_Exit +#define Trace_MB_TIM_Exit() +#endif //Trace_MB_TIM_Exit + +#endif //__MODBUS_H_ diff --git a/modbus_config.h b/modbus_config.h new file mode 100644 index 0000000..b173fd9 --- /dev/null +++ b/modbus_config.h @@ -0,0 +1,39 @@ +/** +************************************************************************** +* @file interface_config.h +* @brief Конфигурация для модбаса +*************************************************************************/ +#include +#ifndef _MODBUS_CONFIG_H_ +#define _MODBUS_CONFIG_H_ + +// MODBUS PARAMS +#define MODBUS_DEVICE_ID 1 ///< девайс текущего устройства +#define MODBUS_TIMEOUT 5000 ///< максимальнйы тайтаут MB в тиках таймера + +// STRING OBJECTS MODBUS +#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 "" + +// PERIPH FUNCTIONS AND HANDLERS +#define rs_huart Serial1 //используемый uart +#define HUART_TypeDef HardwareSerial + +/** + * @brief Поменять комманды 0x03 и 0x04 местами (для LabView терминалки от двигателей) + * @details Терминалка от двигателей использует для чтения регистров комманду R_HOLD_REGS вместо R_IN_REGS + * Поэтому чтобы считывать Input Regs - надо поменять их местами. + */ +//#define MODBUS_SWITCH_COMMAND_R_IN_REGS_AND_R_HOLD_REGS + +///////////////////////////////////////////////////////////////////// +/////////////////////////---CALC DEFINES---////////////////////////// + + + +#endif //_MODBUS_CONFIG_H_ \ No newline at end of file diff --git a/modbus_data.h b/modbus_data.h new file mode 100644 index 0000000..e2b5158 --- /dev/null +++ b/modbus_data.h @@ -0,0 +1,160 @@ +/** +************************************************************************** +* @file modbus_data.h +* @brief Заголовочный файл с описанием даты MODBUS. +* @details Данный файл необходимо подключается в rs_message.h. После rs_message.h +* подключается к основному проекту. +* +* @defgroup MODBUS_DATA +* @ingroup MODBUS +* @brief Modbus data description +* +*************************************************************************/ + +#ifndef _MODBUS_DATA_H_ +#define _MODBUS_DATA_H_ + +#include "stdint.h" +//--------------DEFINES FOR REGISTERS--------------- +// DEFINES FOR ARRAYS +/** + * @addtogroup MODBUS_DATA_RERISTERS_DEFINES + * @ingroup MODBUS_DATA + * @brief Defines for registers + Структура дефайна адресов + @verbatim + Для массивов регистров: + R__ADDR - модбас адресс первого регистра в массиве + R__QNT - количество регистров в массиве + + При добавлении новых массивов регистров, необходимо их добавить в функцию MB_DefineRegistersAddress + + if(MB_Check_Address_For_Arr(Addr, Qnt, R__ADDR, R__QNT) == NO_ERRORS) + { + *pRegs = MB_Set_Register_Ptr(&, Addr); // начало регистров хранения/входных + } + @endverbatim + * @{ + */ + + +/** + * @brief Регистры хранения + */ +typedef struct //MB_DataInRegsTypeDef +{ + uint16_t data[10]; +}MB_DataInRegsTypeDef; + + +/** + * @brief Входные регистры + */ +typedef struct //MB_DataInRegsTypeDef +{ + uint16_t data[10]; +}MB_DataHoldRegsTypeDef; + + +// DEFINES FOR INPUT REGISTERS ARRAYS +#define R_INPUT_ADDR 0 +#define R_INPUT_QNT 10 + +// DEFINES FOR HOLDING REGISTERS ARRAYS +#define R_HOLDING_ADDR 0 +#define R_HOLDING_QNT 10 + + +/** MODBUS_DATA_RERISTERS_DEFINES + * @} + */ + +//----------------DEFINES FOR COILS----------------- +/** + * @addtogroup MODBUS_DATA_COILS_DEFINES + * @ingroup MODBUS_DATA + * @brief Defines for coils + @verbatim + Структура дефайна + Для массивов коилов: + C__ADDR - модбас адресс первого коила в массиве + C__QNT - количество коилов в массиве (минимум 16) + + При добавлении новых массивов коилов, необходимо их добавить в функцию MB_DefineCoilsAddress + + if(MB_Check_Address_For_Arr(Addr, Qnt, C__ADDR, C__QNT) == NO_ERRORS) + { + *pCoils = MB_Set_Coil_Reg_Ptr(&, Addr); + } + + @endverbatim + * @{ + */ + + + +/** + * @brief Коилы + * @details Желательно с помощью reserved делать стркутуру кратной 16-битам + */ +typedef struct //MB_DataCoilsTypeDef +{ + unsigned reserved:16; +}MB_DataCoilsTypeDef; + +// DEFINES FOR COIL ARRAYS +#define C_CONTROL_ADDR 0 +#define C_CONTROL_QNT 16 + +/** MODBUS_DATA_COILS_DEFINES + * @} + */ + + +//-----------MODBUS DEVICE DATA SETTING------------- +// MODBUS DATA STRUCTTURE +/** + * @brief Структура со всеми регистрами и коилами модбас + * @ingroup MODBUS_DATA + */ +typedef struct // tester modbus data +{ + MB_DataInRegsTypeDef InRegs; ///< Modbus input registers @ref MB_DataInRegsTypeDef + + MB_DataCoilsTypeDef Coils; ///< Modbus coils @ref MB_DataCoilsTypeDef + + MB_DataHoldRegsTypeDef HoldRegs; ///< Modbus holding registers @ref MB_DataHoldRegsTypeDef +}MB_DataStructureTypeDef; +extern MB_DataStructureTypeDef MB_DATA; + + +/** + * @brief Структура для объекта Modbus + * @ingroup MODBUS_DATA + */ +typedef struct +{ + unsigned length; + char *name; +}MB_DeviceObjectTypeDef; +/** + * @brief Структура для объектов Modbus + * @ingroup MODBUS_DATA + */ +typedef struct +{ + MB_DeviceObjectTypeDef VendorName; + MB_DeviceObjectTypeDef ProductCode; + MB_DeviceObjectTypeDef Revision; + MB_DeviceObjectTypeDef VendorUrl; + MB_DeviceObjectTypeDef ProductName; + MB_DeviceObjectTypeDef ModelName; + MB_DeviceObjectTypeDef UserApplicationName; +}MB_DeviceIdentificationTypeDef; +void MB_DevoceInentificationInit(void); + + +#endif //_MODBUS_DATA_H_ + +///////////////////////////////////////////////////////////// +///////////////////////TEMP/OUTDATE/OTHER//////////////////// \ No newline at end of file diff --git a/rs_message.cpp b/rs_message.cpp new file mode 100644 index 0000000..e95aa44 --- /dev/null +++ b/rs_message.cpp @@ -0,0 +1,339 @@ +/** +************************************************************************** +* @file rs_message.c +* @brief Модуль для реализации протоколов по RS/UART. +**************************************************************************\ +* @details +* Данный модуль реализует основные функции для приема и передачи сообщений +* по протоколу RS через UART в режиме прерываний. Реализована обработка +* приема и передачи данных, управление состояниями RS, а также функции для +* инициализации и управления периферией. +* +* Реализованы следующие функции: +* - RS_Receive_IT() — запуск приема данных в прерывании по UART. +* - RS_Transmit_IT() — запуск передачи данных в прерывании по UART. +* - RS_Init() — инициализация структуры RS и привязка периферии. +* - RS_ReInit_UART() — переинициализация UART и перезапуск приема данных. +* - RS_Abort() — остановка работы RS/UART с очисткой флагов и структур. +* - RS_Handle_Receive_Start() — обработка старта приема данных по RS. +* +* В модуле также определен буфер RS_Buffer[] для хранения принимаемых/передаваемых данных. +* +* @note +* Для корректной работы модуля предполагается использование соответствующих +* обработчиков прерываний UART и таймера (RS_UART_Handler(), RS_TIM_Handler()), +* которые надо вызывать с обработчиках используемой периферии + +@verbatim +//-------------------Функции-------------------// +Functions: users + - RS_Parse_Message/RS_Collect_Message Заполнение структуры сообщения и буфера + - RS_Response Ответ на сообщение + - RS_Define_Size_of_RX_Message Определение размера принимаемых данных + +Functions: general + - RS_Receive_IT Ожидание комманды и ответ на неё + - RS_Transmit_IT Отправление комманды и ожидание ответа + - RS_Init Инициализация переферии и структуры для RS + - RS_ReInit_UART Реинициализация UART для RS + - RS_Abort Отмена приема/передачи по ЮАРТ + - RS_Init Инициализация периферии и modbus handler + +Functions: callback/handler + - RS_Handle_Receive_Start Функция для запуска приема или остановки RS + - RS_Handle_Transmit_Start Функция для запуска передачи или остановки RS + + - RS_UART_RxCpltCallback Коллбек при окончании приема или передачи + RS_UART_TxCpltCallback + + - RS_UART_Handler Обработчик прерывания для UART + - RS_TIM_Handler Обработчик прерывания для TIM + +@endverbatim +*************************************************************************/ +#include "rs_message.h" + +uint8_t RS_Buffer[MSG_SIZE_MAX]; // uart buffer + +//------------------------------------------------------------------- +//-------------------------GENERAL FUNCTIONS------------------------- +/** + * @brief Start receive IT. + * @param hRS - указатель на хендлер RS. + * @param RS_msg - указатель на структуру сообщения. + * @return RS_RES - статус о состоянии RS после инициализации приема. + */ +RS_StatusTypeDef RS_Receive_IT(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { + if (hRS->f.RS_Busy || hRS->f.RX_Busy) return RS_BUSY; + + RS_EnableReceive(); + RS_Set_Busy(hRS); + RS_Set_RX_Flags(hRS); + + hRS->pMessagePtr = RS_msg; + hRS->lastByteTime = millis(); + hRS->RS_STATUS = RS_OK; + + return RS_OK; +} + + +/** + * @brief Start transmit IT. + * @param hRS - указатель на хендлер RS. + * @param RS_msg - указатель на структуру сообщения. + * @return RS_RES - статус о состоянии RS после инициализации передачи. + */ +RS_StatusTypeDef RS_Transmit_IT(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { + if (hRS->f.RS_Busy || hRS->f.TX_Busy) return RS_BUSY; + + RS_StatusTypeDef RS_RES = RS_Collect_Message(hRS, RS_msg, hRS->pBufferPtr); + if (RS_RES != RS_OK) { + RS_Abort(hRS, ABORT_RS); + RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); + return RS_RES; + } + + RS_EnableTransmit(); + RS_Set_Busy(hRS); + RS_Set_TX_Flags(hRS); + + hRS->pMessagePtr = RS_msg; + hRS->huart->write(hRS->pBufferPtr, hRS->RS_Message_Size); + hRS->lastByteTime = millis(); + + return RS_OK; +} + + +/** + * @brief Initialize UART and handle RS stucture. + * @param hRS - указатель на хендлер RS. + * @param suart - указатель на структуру с настройками UART. + * @param stim - указатель на структуру с настройками таймера. + * @param pRS_BufferPtr - указатель на буффер для приема-передачи по UART. Если он NULL, то поставиться библиотечный буфер. + * @return RS_RES - статус о состоянии RS после инициализации. + * @note Инициализация перефирии и структуры для приема-передачи по RS. + */ +RS_StatusTypeDef RS_Init(RS_HandleTypeDef *hRS, HUART_TypeDef *SerialPort, uint8_t *pRS_BufferPtr) { + if (!hRS || !SerialPort) return RS_ERR; + + hRS->huart = SerialPort; + hRS->pBufferPtr = pRS_BufferPtr ? pRS_BufferPtr : RS_Buffer; + hRS->RS_STATUS = RS_OK; + RS_Set_Free(hRS); + RS_Reset_RX_Flags(hRS); + RS_Reset_TX_Flags(hRS); + + hRS->f.RX_Half = 0; + hRS->lastByteTime = millis(); + + return RS_OK; +} + + +/** + * @brief Abort RS/UART. + * @param hRS - указатель на хендлер RS. + * @param AbortMode - выбор, что надо отменить. + - ABORT_TX: Отмена передачи по ЮАРТ, с очищением флагов TX, + - ABORT_RX: Отмена приема по ЮАРТ, с очищением флагов RX, + - ABORT_RX_TX: Отмена приема и передачи по ЮАРТ, + - ABORT_RS: Отмена приема-передачи RS, с очищением всей структуры. + * @return RS_RES - статус о состоянии RS после аборта. + * @note Отмена работы UART в целом или отмена приема/передачи RS. + Также очищается хендл hRS. + */ +RS_StatusTypeDef RS_Abort(RS_HandleTypeDef *hRS, RS_AbortTypeDef AbortMode) { + if ((AbortMode & ABORT_RS) == 0) { + if ((AbortMode & ABORT_RX) == ABORT_RX) RS_Reset_RX_Flags(hRS); + if ((AbortMode & ABORT_TX) == ABORT_TX) RS_Reset_TX_Flags(hRS); + } else { + RS_Clear_All(hRS); + } + + RS_Set_Free(hRS); + hRS->RS_STATUS = RS_ABORTED; + return RS_ABORTED; +} + + +//-------------------------GENERAL FUNCTIONS------------------------- +//------------------------------------------------------------------- + + + +//------------------------------------------------------------------- +//--------------------CALLBACK/HANDLER FUNCTIONS--------------------- +/** + * @brief Handle for starting receive. + * @param hRS - указатель на хендлер RS. + * @param RS_msg - указатель на структуру сообщения. + * @return RS_RES - статус о состоянии RS после инициализации приема или окончания общения. + * @note Определяет начинать прием команды/ответа или нет. + */ +RS_StatusTypeDef RS_Handle_Receive_Start(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) +{ + RS_StatusTypeDef RS_RES = RS_OK; + + switch(hRS->sRS_Mode) + { + case SLAVE_ALWAYS_WAIT: // in slave mode with permanent waiting + RS_RES = RS_Receive_IT(hRS, RS_msg); break; // start receiving again + case SLAVE_TIMEOUT_WAIT: // in slave mode with timeout waiting (start receiving cmd by request) + RS_Set_Free(hRS); RS_RES = RS_OK; break; // end RS communication (set RS unbusy) + } + + if(RS_RES != RS_OK) + { + } + + return RS_RES; +} +/** + * @brief Handle for starting transmit. + * @param hRS - указатель на хендлер RS. + * @param RS_msg - указатель на структуру сообщения. + * @return RS_RES - статус о состоянии RS после инициализации передачи. + * @note Определяет отвечать ли на команду или нет. + */ +RS_StatusTypeDef RS_Handle_Transmit_Start(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) +{ + RS_StatusTypeDef RS_RES = RS_OK; + + switch(hRS->sRS_Mode) + { + case SLAVE_ALWAYS_WAIT: // in slave mode always response + case SLAVE_TIMEOUT_WAIT: // transmit response + RS_RES = RS_Transmit_IT(hRS, RS_msg); break; + } + if(RS_RES != RS_OK) + { + } + + return RS_RES; +} +/** + * @brief UART RX Callback: define behaviour after receiving parts of message. + * @param hRS - указатель на хендлер RS. + * @return RS_RES - статус о состоянии RS после обработки приема. + * @note Контролирует прием сообщения: определяет размер принимаемой посылки и обрабатывает его. + */ +RS_StatusTypeDef RS_UART_RxCpltCallback(RS_HandleTypeDef *hRS) { + if (!hRS->f.RX_Half && hRS->sRS_RX_Size_Mode == RS_RX_Size_NotConst) { + hRS->f.RX_Half = 1; + uint32_t restSize = 0xFFFF; + RS_StatusTypeDef res = RS_Define_Size_of_RX_Message(hRS, &restSize); + if (res == RS_SKIP || restSize == 0xFFFF) { + RS_Abort(hRS, ABORT_RX); + return RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); + } + + // if there is no bytes to receive + if(NuRS_of_Rest_Bytes == 0) + { + hRS->f.RX_Half = 0; + + //---------PROCESS DATA & ENDING RECEIVING-------- + RS_Set_RX_End(hRS); + + // parse received data + RS_RES = RS_Parse_Message(hRS, hRS->pMessagePtr, hRS->pBufferPtr); // parse message + hRS->RS_Message_Size = NuRS_of_Rest_Bytes; + + // RESPONSE + RS_RES = RS_Response(hRS, hRS->pMessagePtr); + return RS_RES; + } + } + else // if we had received whole message + { + hRS->f.RX_Half = 0; + + //---------PROCESS DATA & ENDING RECEIVING-------- + RS_Set_RX_End(hRS); + + // parse received data + RS_RES = RS_Parse_Message(hRS, hRS->pMessagePtr, hRS->pBufferPtr); // parse message + + // RESPONSE + RS_RES = RS_Response(hRS, hRS->pMessagePtr); + } + + return RS_RES; +} + +/** + * @brief UART TX Callback: define behaviour after transmiting message. + * @param hRS - указатель на хендлер RS. + * @return RS_RES - статус о состоянии RS после обработки приема. + * @note Определяет поведение RS после передачи сообщения. + */ +RS_StatusTypeDef RS_UART_TxCpltCallback(RS_HandleTypeDef *hRS) +{ + RS_StatusTypeDef RS_RES = RS_OK; + HAL_StatusTypeDef uart_res = 0; + + //--------------ENDING TRANSMITTING------------- + RS_Set_TX_End(hRS); + RS_EnableReceive(); +// for(int i = 0; i < hRS->sRS_Timeout; i++); + + //-----------START RECEIVING or END RS---------- + RS_RES = RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); + + return RS_RES; +} + +/** + * @brief Handler for UART. + * @param hRS - указатель на хендлер RS. + * @note Обрабатывает ошибки если есть и вызывает RS Коллбеки. + * Добавить вызов этой функции в UARTx_IRQHandler() после HAL_UART_IRQHandler(). + */ +void RS_UART_Handler(RS_HandleTypeDef *hRS) { + while (hRS->huart->available()) { + uint8_t b = hRS->huart->read(); + hRS->pBufferPtr[hRS->RS_Message_Size++] = b; + hRS->lastByteTime = millis(); + + if (hRS->f.RX_Busy && (hRS->RS_Message_Size >= RX_FIRST_PART_SIZE) && !hRS->f.RX_Half) + RS_UART_RxCpltCallback(hRS); + } +} + +/** + * @brief Handler for TIM (timeout check). + * @param hRS - указатель на хендлер RS. + * @note Проверяет таймаут между байтами. Если превышен, сбрасывает RX и перезапускает прием. + * Вызывать в TIMx_IRQHandler() после HAL_TIM_IRQHandler(). + */ +void RS_TIM_Handler(RS_HandleTypeDef *hRS) { + if (!hRS) return; + + unsigned long now = millis(); + + // Если идет прием данных и есть таймаут + if (hRS->f.RX_Busy && hRS->sRS_Timeout > 0) { + if ((now - hRS->lastByteTime) >= hRS->sRS_Timeout) { + // таймаут истек → abort RX и restart + RS_Abort(hRS, ABORT_RX); + RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); + } + } + + // Можно также проверять TX, если нужна логика таймаута передачи + if (hRS->f.TX_Busy && hRS->sRS_Timeout > 0) { + if ((now - hRS->lastByteTime) >= hRS->sRS_Timeout) { + RS_Abort(hRS, ABORT_TX); + // TX не рестартим автоматически + } + } +} + + +// weak functions +__attribute__((weak)) RS_StatusTypeDef RS_Response(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { return RS_ERR; } +__attribute__((weak)) RS_StatusTypeDef RS_Collect_Message(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg, uint8_t *msg_uart_buff) { return RS_ERR; } +__attribute__((weak)) RS_StatusTypeDef RS_Parse_Message(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg, uint8_t *msg_uart_buff) { return RS_ERR; } +__attribute__((weak)) RS_StatusTypeDef RS_Define_Size_of_RX_Message(RS_HandleTypeDef *hRS, uint32_t *rx_data_size) { return RS_ERR; } diff --git a/rs_message.h b/rs_message.h new file mode 100644 index 0000000..dbced66 --- /dev/null +++ b/rs_message.h @@ -0,0 +1,256 @@ +/** +************************************************************************** +* @file rs_message.h +* @brief Заголовочный файл для модуля реализации протоколов по RS/UART. +************************************************************************** +* @defgroup RS_TOOLS +* @brief Всякое для работы по UART/RS +************************************************************************** +@details +************************************************************************** +Для настройки RS/UART под нужный протокол, необходимо: + - Определить структуру сообщения RS_MsgTypeDef и + дефайны RX_FIRST_PART_SIZE и MSG_SIZE_MAX. + - Подключить этот файл в раздел rs_message.h. + - Определить функции для обработки сообщения: RS_Parse_Message(), + RS_Collect_Message(), RS_Response(), RS_Define_Size_of_RX_Message() + - Добавить UART/TIM Handler в Хендлер используемых UART/TIM. + +Так же данный модуль использует счетчики +************************************************************************** +@verbatim +Визуальное описание. Форматирование сохраняется как в коде. +@endverbatim +*************************************************************************/ +#ifndef __RS_LIB_H_ +#define __RS_LIB_H_ +#include "modbus.h" + +///////////////////////////////////////////////////////////////////// +////////////////////////////---DEFINES---//////////////////////////// + +/* Check that all defines required by RS are defined */ +#ifndef MSG_SIZE_MAX +#error Define MSG_SIZE_MAX (Maximum size of message). This is necessary to create buffer for UART. +#endif + +#ifndef RX_FIRST_PART_SIZE +#error Define RX_FIRST_PART_SIZE (Size of first part of message). This is necessary to receive the first part of the message, from which determine the size of the remaining part of the message. +#endif + +/* Clear message-uart buffer */ +#define RS_Clear_Buff(_buff_) for(int i=0; if.RS_Busy = 0) +#define RS_Set_Busy(_hRS_) (_hRS_->f.RS_Busy = 1) + +#define RS_Set_RX_Flags(_hRS_) do { \ + _hRS_->f.RX_Busy = 1; \ + _hRS_->f.RX_Done = 0; \ + _hRS_->f.RX_Half = 0; \ + } while(0) + +#define RS_Set_RX_Active_Flags(_hRS_) (_hRS_->f.RX_Ongoing = 1) +#define RS_Set_TX_Flags(_hRS_) do { \ + _hRS_->f.TX_Busy = 1; \ + _hRS_->f.TX_Done = 0; \ + } while(0) + +#define RS_Reset_RX_Active_Flags(_hRS_) (_hRS_->f.RX_Ongoing = 0) + +#define RS_Reset_RX_Flags(_hRS_) do { \ + RS_Reset_RX_Active_Flags(_hRS_); \ + _hRS_->f.RX_Busy = 0; \ + _hRS_->f.RX_Done = 0; \ + _hRS_->f.RX_Half = 0; \ + } while(0) + +#define RS_Reset_TX_Flags(_hRS_) do { \ + _hRS_->f.TX_Busy = 0; \ + _hRS_->f.TX_Done = 0; \ + } while(0) + +#define RS_Set_RX_End_Flag(_hRS_) (_hRS_->f.RX_Done = 1) +#define RS_Set_TX_End_Flag(_hRS_) (_hRS_->f.TX_Done = 1) + +#define RS_Set_RX_End(_hRS_) do { RS_Reset_RX_Flags(_hRS_); RS_Set_RX_End_Flag(_hRS_); } while(0) +#define RS_Set_TX_End(_hRS_) do { RS_Reset_TX_Flags(_hRS_); RS_Set_TX_End_Flag(_hRS_); } while(0) + +/* Clear all RS stuff */ +#define RS_Clear_All(_hRS_) do { \ + RS_Clear_Buff(_hRS_->pBufferPtr); \ + RS_Reset_RX_Flags(_hRS_); \ + RS_Reset_TX_Flags(_hRS_); \ + } while(0) + +#define RS_Is_RX_Busy(_hRS_) (_hRS_->f.RX_Busy == 1) +#define RS_Is_TX_Busy(_hRS_) (_hRS_->f.TX_Busy == 1) + +/* For Arduino we can redefine later if needed */ +#ifndef RS_EnableReceive +#define RS_EnableReceive() +#endif + +#ifndef RS_EnableTransmit +#define RS_EnableTransmit() +#endif + +////////////////////////////---DEFINES---//////////////////////////// + + + + +///////////////////////////////////////////////////////////////////// +///////////////////////---STRUCTURES & ENUMS---////////////////////// +//------------------ENUMERATIONS-------------------- +/** @brief Enums for respond CMD about RS status */ +typedef enum // RS_StatusTypeDef +{ + /* IN-CODE STATUS (start from 0x01, and goes up)*/ + /*0x01*/ RS_OK = 0x01, + /*0x02*/ RS_ERR, + /*0x03*/ RS_ABORTED, + /*0x04*/ RS_BUSY, + /*0x05*/ RS_SKIP, + + /*0x06*/ RS_COLLECT_MSG_ERR, + /*0x07*/ RS_PARSE_MSG_ERR, + + // reserved values +// /*0x00*/ RS_UNKNOWN_ERR = 0x00, ///< reserved for case, if no one error founded (nothing changed response from zero) +}RS_StatusTypeDef; + + +/** @brief Enums for RS Modes */ +typedef enum // RS_ModeTypeDef +{ + SLAVE_ALWAYS_WAIT = 0x01, ///< Slave mode with infinity waiting + SLAVE_TIMEOUT_WAIT = 0x02, ///< Slave mode with waiting with timeout +// MASTER = 0x03, ///< Master mode +}RS_ModeTypeDef; + +/** @brief Enums for Abort modes */ +typedef enum // RS_AbortTypeDef +{ + ABORT_TX = 0x01, ///< Abort transmit + ABORT_RX = 0x02, ///< Abort receive + ABORT_RX_TX = 0x03, ///< Abort receive and transmit + ABORT_RS = 0x04, ///< Abort uart and reset RS structure +}RS_AbortTypeDef; + +/** @brief Enums for RX Size modes */ +typedef enum // RS_RXSizeTypeDef +{ + RS_RX_Size_Const = 0x01, ///< size of receiving message is constant + RS_RX_Size_NotConst = 0x02, ///< size of receiving message isnt constant +} RS_RX_SizeTypeDef; +//-----------STRUCTURE FOR HANDLE RS------------ +/** @brief Struct for flags RS */ +typedef struct +{ + unsigned RX_Half:1; ///< flag: 0 - receiving msg before ByteCnt, 0 - receiving msg after ByteCnt + + unsigned RS_Busy:1; ///< flag: 1 - RS is busy, 0 - RS isnt busy + unsigned RX_Ongoing:1; ///< flag: 1 - receiving data right now, 0 - waiting for receiving data + + unsigned RX_Busy:1; ///< flag: 1 - receiving is active, 0 - receiving isnt active + unsigned TX_Busy:1; ///< flag: 1 - transmiting is active, 0 - transmiting isnt active + + unsigned RX_Done:1; ///< flag: 1 - receiving is done, 0 - receiving isnt done + unsigned TX_Done:1; ///< flag: 1 - transmiting is done, 0 - transmiting isnt done + + // setted by user + unsigned MessageHandled:1; ///< flag: 1 - RS command is handled, 0 - RS command isnt handled yet + unsigned EchoResponse:1; ///< flag: 1 - response with received msg, 0 - response with own msg + unsigned DeferredResponse:1; ///< flag: 1 - response not in interrupt, 0 - response in interrupt + unsigned ReInit_UART:1; ///< flag: 1 - need to reinitialize uart, 0 - nothing +}RS_FlagsTypeDef; + + +/** + * @brief Handle for RS communication. + * @note Prefixes: h - handle, s - settings, f - flag + */ +typedef struct // RS_HandleTypeDef +{ + /* MESSAGE */ + uint8_t ID; ///< ID of RS "channel" + RS_MsgTypeDef *pMessagePtr; ///< pointer to message struct + uint8_t *pBufferPtr; ///< pointer to message buffer + uint32_t RS_Message_Size; ///< size of whole message, not only data + + /* HANDLERS and SETTINGS */ + HUART_TypeDef *huart; ///< handler for used uart + void *htim; ///< handler for used tim + RS_ModeTypeDef sRS_Mode; ///< setting: slave or master @ref RS_ModeTypeDef + uint16_t sRS_Timeout; ///< setting: timeout in ms + RS_RX_SizeTypeDef sRS_RX_Size_Mode; ///< setting: 1 - not const, 0 - const + + /* FLAGS */ + RS_FlagsTypeDef f; ///< These flags for controling receive/transmit + + /* RS STATUS */ + unsigned long lastByteTime; + RS_StatusTypeDef RS_STATUS; ///< RS status +}RS_HandleTypeDef; +extern RS_HandleTypeDef hmodbus1; + +///////////////////////---STRUCTURES & ENUMS---////////////////////// + + +///////////////////////////////////////////////////////////////////// +///////////////////////////---FUNCTIONS---/////////////////////////// +//----------------FUNCTIONS FOR PROCESSING MESSAGE------------------- +/*--------------------Defined by users purposes--------------------*/ +/* Respond accord to received message */ +RS_StatusTypeDef RS_Response(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg); + +/* Collect message in buffer to transmit it */ +RS_StatusTypeDef RS_Collect_Message(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg, uint8_t *msg_uart_buff); + +/* Parse message from buffer to process it */ +RS_StatusTypeDef RS_Parse_Message(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg, uint8_t *msg_uart_buff); + +/* Define size of RX Message that need to be received */ +RS_StatusTypeDef RS_Define_Size_of_RX_Message(RS_HandleTypeDef *hRS, uint32_t *rx_data_size); + +//-------------------------GENERAL FUNCTIONS------------------------- +/*-----------------Should be called from main code-----------------*/ +/* Initialize UART and handle RS stucture */ +RS_StatusTypeDef RS_Init(RS_HandleTypeDef *hRS, HUART_TypeDef *huart, uint8_t *pRS_BufferPtr); +/* Start receive IT */ +RS_StatusTypeDef RS_Receive_IT(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg); +/* Start transmit IT */ +RS_StatusTypeDef RS_Transmit_IT(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg); +/* Abort RS/UART */ +RS_StatusTypeDef RS_Abort(RS_HandleTypeDef *hRS, RS_AbortTypeDef AbortMode); +//-------------------------GENERAL FUNCTIONS------------------------- + + +//------------------------------------------------------------------- +//--------------------CALLBACK/HANDLER FUNCTIONS--------------------- +/* Handle for starting receive */ +RS_StatusTypeDef RS_Handle_Receive_Start(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg); +/* Handle for starting transmit */ +RS_StatusTypeDef RS_Handle_Transmit_Start(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg); +/* UART RX Callback: define behaviour after receiving parts of message */ +RS_StatusTypeDef RS_UART_RxCpltCallback(RS_HandleTypeDef *hRS); +/* UART TX Callback: define behaviour after transmiting message */ +RS_StatusTypeDef RS_UART_TxCpltCallback(RS_HandleTypeDef *hRS); +/* Handler for UART */ +void RS_UART_Handler(RS_HandleTypeDef *hRS); +/* Handler for TIM */ +void RS_TIM_Handler(RS_HandleTypeDef *hRS); +//--------------------CALLBACK/HANDLER FUNCTIONS--------------------- +///////////////////////////---FUNCTIONS---/////////////////////////// + + +#ifndef printf_rs_err +#define printf_rs_err(...) +#endif + +#ifndef printf_rs +#define printf_rs(...) +#endif +#endif // __RS_LIB_H_