/********************************MODBUS*************************************
Данный файл содержит объявления базовых функции и дефайны для реализации 
MODBUS.
Данный файл необходимо подключить в rs_message.h. После подключать rs_message.h
к основному проекту.
***************************************************************************/
#ifndef __MODBUS_H_
#define __MODBUS_H_

#include "stm32f4xx_hal.h"
#include "modbus_data.h"
/////////////////////////////////////////////////////////////////////
//////////////////////////---SETTINGS---/////////////////////////////
//----------DEFINES FOR MODBUS SETTING--------------
#define MB_UART_NUMB 							3			// number of used uart
#define MB_SPEED									115200
#define MB_GPIOX									GPIOB
#define MB_GPIO_PIN_RX						GPIO_PIN_11
#define MB_GPIO_PIN_TX						GPIO_PIN_10
/* accord to this define sets define USED_MB_UART = USARTx */
#define MB_TIM_NUMB 							7			// number of used uart
#define MB_TIM_AHB_FREQ						72
/* accord to this define sets define USED_MB_TIM = TIMx */

/* defines for modbus behaviour */
#define MB_ID 										1			// number of used uart
#define MB_MAX_TIMEOUT 						5000 	// is ms
// custom define for size of receive message 
//--------------------------------------------------

//---------------MODBUS DEVICE DATA-----------------



/* EXTERN REGISTERS/COILS */ 


extern uint16_t sine_log[R_SINE_LOG_QNT];					// start from 0x0000
extern uint16_t pwm_log[R_PWM_LOG_QNT];						// start from 500 (0x1F4)
extern uint16_t cnt_log[R_CNT_LOG_QNT];						// start from 100 (0x3E8)
extern uint16_t time_log[R_TIME_LOG_QNT];					// start from 1500 (0x5DC)

extern uint16_t pwm_ctrl[R_PWM_CTRL_QNT];					// start from 2000 (0x7D0)
extern uint16_t log_ctrl[R_LOG_CTRL_QNT];					// start from 2008 (0x7D0)


extern uint16_t uart_ctrl[R_UART_CTRL_QNT];

extern uint16_t coils_regs[C_CTRL_COILS_QNT];				// start from 0x0001 (16th bit)

//--------------------------------------------------
//////////////////////////---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

/* size of info */
#define INFO_SIZE_MAX 					(MbAddr_SIZE+Func_Code_SIZE+Addr_SIZE+Qnt_SIZE+ByteCnt_SIZE)

/* 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

/* 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

/* Structure for modbus exception codes */
typedef enum //MB_ExceptionTypeDef
{
	// reading
	NO_ERRORS =							0x00,		// no errors
	ILLEGAL_FUNCTION =			0x01,		// function cannot be processed
	ILLEGAL_DATA_ADDRESS =	0x02,		// data at this address is not available
	ILLEGAL_DATA_VALUE =		0x03,		// uncorrect data value (quantity too big and cannot be returned or value for coil is incorrect)
	SLAVE_DEVICE_FAILURE =	0x04,		// idk
	ACKNOWLEDGE =						0x05,		// idk
	SLAVE_DEVICE_BUSY =			0x06,		// idk
	MEMORY_PARITY_ERROR =		0x08,		// idk
}MB_ExceptionTypeDef;

/* Structure for modbus func codes */
typedef enum //MB_FunctonTypeDef
{
	// reading
	MB_R_COILS = 			0x01,
	MB_R_DISC_IN = 		0x02,
	MB_R_IN_REGS = 		0x03,
	MB_R_HOLD_REGS = 	0x04,
	
	// writting
	MB_W_COIL = 			0x05,
	MB_W_IN_REG = 		0x06,
	MB_W_COILS = 			0x0F,
	MB_W_IN_REGS = 		0x10,
}MB_FunctonTypeDef;
#define ERR_VALUES_START				0x80U				// from this value starts error func codes

/* Structure for modbus messsage */
typedef struct 	// RS_MsgTypeDef
{
	uint8_t 							MbAddr;
	MB_FunctonTypeDef 		Func_Code;
	uint16_t 							Addr;
	uint16_t 							Qnt;
	uint8_t 							ByteCnt;
	
	uint16_t 							DATA[DATA_SIZE];
	MB_ExceptionTypeDef		Except_Code;
	
	uint16_t 							MB_CRC;
}RS_MsgTypeDef;
//--------------------------------------------------
/////////////////////---USER MESSAGE DEFINES---//////////////////////



/////////////////////////////////////////////////////////////////////
/////////////////////---GENERAL MODBUS STUFF---//////////////////////
/* Structure for coils operation */
typedef enum
{
//	READ_COIL,
	SET_COIL,
	RESET_COIL,
	TOOGLE_COIL,
}MB_CoilsOpTypeDef;

//------------DEFINES FOR PROCESS DATA--------------
/**S
	* @brief	Calc dividing including remainder
	*	@param 	_val_		- делимое.
	*	@param 	_div_		- делитель.
	*	@note 	Если результат деления без остатка: он возвращается как есть
						Если с остатком - округляется вверх
	*/
//#define Divide_Up(_val_, _div_)												(((_val_)%(_div_))? (_val_)/(_div_)+1 : (_val_)/_div_)	/* через тернарный оператор */
#define Divide_Up(_val_, _div_)												((_val_ - 1) / _div_) + 1																	/* через мат выражение */	

/** 
	* @brief 	Swap between Little Endian and Big Endian
	* @param 	v 			- Переменная для свапа.
  * @return v (new)	- Свапнутая переменная.
  * @note 	Переключения между двумя типами хранения слова: HI-LO байты и LO-HI байты.
	*/
#define ByteSwap16(v)																	(((v&0xFF00) >> (8)) | ((v&0x00FF) << (8)))
//--------------------------------------------------


//-----------DEFINES FOR ACCESS TO DATA-------------
/** 
	* @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		Пояснение выражений
	* 					(_coil_/16) - get index (address shift) of register that contain certain coil 
	*						(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:
	* xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxCx
	* |register[0]----|	|register[1]----|
	*	|skip this------|	|get this-------|
	*										|shift to 14 bit|
	*/
#define MB_Set_Coil_Reg_Ptr(_parr_, _coil_)						((uint16_t *)(_parr_)+((_coil_)/16))
#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			- Возвращает весь регистр с маской на запрошенном коиле.
	*
  * @note 	Позволяет обратиться к коилу по адресу относительно _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_.
	*	
  * @note 	Позволяет обратиться к коилу по адресу относительно _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_.
	*
  * @note 	Позволяет обратиться к коилу по адресу относительно _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_.
	*	
  * @note 	Позволяет обратиться к коилу по адресу относительно _arr_.
	*/
#define	MB_Toogle_Coil_Local(_parr_, _coil_)					*MB_Set_Coil_Reg_Ptr(_parr_, _coil_) 	^= 	MB_Set_Coil_Mask(_coil_)
//--------------------------------------------------


//------------------OTHER DEFINES-------------------
// create hadnles and settings for uart, tim, rs with _modbus_ name
#define CONCAT(a,b) a##b
#define Create_MODBUS_Handles(_modbus_)												\
				UART_SettingsTypeDef CONCAT(_modbus_, _suart);				\
				UART_HandleTypeDef CONCAT(_modbus_, _huart);					\
				TIM_SettingsTypeDef CONCAT(_modbus_, _stim);					\
				TIM_HandleTypeDef CONCAT(_modbus_, _htim);						\
				RS_HandleTypeDef CONCAT(h, _modbus_)	
//--------------------------------------------------
///////////////////---MODBUS & MESSAGE DEFINES---////////////////////



/////////////////////////////////////////////////////////////////////
////////////////////---FUNCTIONS FOR USER---/////////////////////////
/** 
	* @brief 	First set up of MODBUS.
	* @note 	Первый инит модбас. Заполняет структуры и инициализирует таймер и юарт для общения по модбас.
	* 				Скважность ШИМ меняется по закону синусоиды, каждый канал генерирует свой полупериод синуса (от -1 до 0 И от 0 до 1)
	* 				ШИМ генерируется на одном канале.
	* @note		This called from main
	*/
void MODBUS_FirstInit(void);
/** 
	* @brief 	Set or Reset Coil at its global address.
	* @param 	Addr 					- адрес коила.
	* @param 	WriteVal 			- Что записать в коил: 0 или 1.
	* @return ExceptionCode	- Код исключения если коила по адресу не существует, и NO_ERRORS если все ок.
	*
  * @note 	Позволяет обратиться к любому коилу по его глобальному адрессу.
						Вне зависимости от того как коилы размещены в памяти.
	*/
MB_ExceptionTypeDef MB_Write_Coil_Global(uint16_t Addr, MB_CoilsOpTypeDef WriteVal);
/** 
	* @brief 	Read Coil at its global address.
	* @param 	Addr 					- адрес коила.
	* @param 	Exception 		- Указатель на переменную для кода исключения, в случа неудачи при чтении.
	* @return uint16_t			- Возвращает весь регистр с маской на запрошенном коиле.
	*
  * @note 	Позволяет обратиться к любому коилу по его глобальному адрессу.
						Вне зависимости от того как коилы размещены в памяти.
	*/
uint16_t MB_Read_Coil_Global(uint16_t Addr, MB_ExceptionTypeDef *Exception);
////////////////////---FUNCTIONS FOR USER---/////////////////////////



/////////////////////////////////////////////////////////////////////
/////////////---PROCESS MODBUS COMMAND FUNCTIONS---//////////////////
/** 
	* @brief 	Check is address valid for certain array.
	* @param 	Addr 					- начальный адресс.
	* @param 	Qnt 					- количество запрашиваемых элементов.
	* @param 	R_ARR_ADDR 		- начальный адресс массива R_ARR.
	* @param 	R_ARR_NUMB 		- количество элементов в массиве R_ARR.
	* @return ExceptionCode	- ILLEGAL DATA ADRESS если адресс недействителен, и NO_ERRORS если все ок.
	*
	* @note 	Позволяет определить, брать ли данные по адрессу Addr из массива R_ARR.
	*					Если адресс Addr находится в диапазоне адрессов массива R_ARR, то возвращаем NO_ERROR.
	*					Если адресс Addr находится за пределами адрессов массива R_ARR - ILLEGAL_DATA_ADDRESSю.
	*/
MB_ExceptionTypeDef MB_Check_Address_For_Arr(uint16_t Addr, uint16_t Qnt, uint16_t R_ARR_ADDR, uint16_t R_ARR_NUMB);
/** 
	* @brief 	Define Address Origin for Input/Holding Registers
	* @param 	pRegs 				- указатель на указатель регистров.
	* @param 	Addr 					- адрес начального регистра.
	* @param 	Qnt 					- количество запрашиваемых регистров.
	* @param 	WriteFlag 		- флаг регистр нужны для чтения или записи.
	* @return ExceptionCode	- Код исключения если есть, и NO_ERRORS если нет.
	*
  * @note 	Определение адреса начального регистра.
	* @note 	WriteFlag пока не используется.
	*/
MB_ExceptionTypeDef MB_DefineRegistersAddress(uint16_t **pRegs, uint16_t Addr, uint16_t Qnt, uint8_t WriteFlag);
/** 
	* @brief 	Define Address Origin for coils
	* @param 	pCoils 				- указатель на указатель коилов.
	* @param 	Addr 					- адресс начального коила.
	* @param 	Qnt 					- количество запрашиваемых коилов.
	* @param 	start_shift 	- указатель на переменную содержащую сдвиг внутри регистра для начального коила.
	* @param 	WriteFlag 		- флаг коилы нужны для чтения или записи.
	* @return ExceptionCode	- Код исключения если есть, и NO_ERRORS если нет.
	*
  * @note 	Определение адреса начального регистра запрашиваемых коилов.
	* @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);
/** 
	* @brief 	Proccess command Read Coils (01 - 0x01).
	* @param 	modbus_msg 				- указатель на структуру собщения modbus.
	* @return fMessageHandled		- статус о результате обработки комманды.
	* @note 	Обработка команды Read Coils.
*/
uint8_t MB_Read_Coils(RS_MsgTypeDef *modbus_msg);
/**
	* @brief 	Proccess command Read Holding Registers (03 - 0x03).
	* @param 	modbus_msg 				- указатель на структуру собщения modbus.
	* @return fMessageHandled		- статус о результате обработки комманды.
	* @note 	Обработка команды Read Holding Registers.
*/
uint8_t MB_Read_Hold_Regs(RS_MsgTypeDef *modbus_msg);
/**
	* @brief 	Proccess command Write Single Coils (05 - 0x05).
	* @param 	modbus_msg 				- указатель на структуру собщения modbus.
	* @return fMessageHandled		- статус о результате обработки комманды.
	* @note 	Обработка команды Write Single Coils.
*/
uint8_t MB_Write_Single_Coil(RS_MsgTypeDef *modbus_msg);
/**
	* @brief 	Proccess command Write Multiple Coils (15 - 0x0F).
	* @param 	modbus_msg 			- указатель на структуру собщения modbus.
	* @return fMessageHandled	- статус о результате обработки комманды.
	* @note 	Обработка команды Write Multiple Coils.
*/
uint8_t MB_Write_Miltuple_Coils(RS_MsgTypeDef *modbus_msg);
/**
	* @brief 	Proccess command Write Multiple Register (16 - 0x10).
	* @param 	modbus_msg 				- указатель на структуру собщения modbus.
	* @return fMessageHandled		- статус о результате обработки комманды.
	* @note 	Обработка команды Write Multiple Register.
	*/
uint8_t MB_Write_Miltuple_Regs(RS_MsgTypeDef *modbus_msg);
/////////////---PROCESS MODBUS COMMAND FUNCTIONS---//////////////////

/////////////////////////////////////////////////////////////////////
/////////////////////////---CALC DEFINES---//////////////////////////

/* set USART_TypeDef for choosen numb of usart */
#if (MB_UART_NUMB == 1) 
	#define USED_MODBUS_UART					USART1 
	#define USE_USART1
#elif (MB_UART_NUMB == 2)
	#define USED_MODBUS_UART					USART2 
	#define USE_USART2
#elif (MB_UART_NUMB == 3)
	#define USED_MODBUS_UART					USART3 
	#define USE_USART3
#elif (MB_UART_NUMB == 4)
	#define USED_MODBUS_UART					UART4 
	#define USE_UART4
#elif (MB_UART_NUMB == 5)
	#define USED_MODBUS_UART					UART5
	#define USE_UART6
#elif (MB_UART_NUMB == 6)
	#define USED_MODBUS_UART					USART6 
	#define USE_USART6
#endif

#if (MB_TIM_NUMB == 1) 
	#define USED_MODBUS_TIM					TIM1 
	#define USE_TIM1
#elif (MB_TIM_NUMB == 2)
	#define USED_MODBUS_TIM					TIM2 
	#define USE_TIM2
#elif (MB_TIM_NUMB == 3)
	#define USED_MODBUS_TIM					TIM3 
	#define USE_TIM3
#elif (MB_TIM_NUMB == 4)
	#define USED_MODBUS_TIM					TIM4 
	#define USE_TIM4
#elif (MB_TIM_NUMB == 5)
	#define USED_MODBUS_TIM					TIM5
	#define USE_TIM5
#elif (MB_TIM_NUMB == 6)
	#define USED_MODBUS_TIM					TIM6 
	#define USE_TIM6
#elif (MB_TIM_NUMB == 7)
	#define USED_MODBUS_TIM					TIM7 
	#define USE_TIM7
#elif (MB_TIM_NUMB == 8)
	#define USED_MODBUS_TIM					TIM8
	#define USE_TIM8 
#elif (MB_TIM_NUMB == 9)
	#define USED_MODBUS_TIM					TIM9 
	#define USE_TIM9
#elif (MB_TIM_NUMB == 10)
	#define USED_MODBUS_TIM					TIM10 
	#define USE_TIM10
#elif (MB_TIM_NUMB == 11)
	#define USED_MODBUS_TIM					TIM11
	#define USE_TIM11
#elif (MB_TIM_NUMB == 12)
	#define USED_MODBUS_TIM					TIM12 
	#define USE_TIM12
#elif (MB_TIM_NUMB == 13)
	#define USED_MODBUS_TIM					TIM13 
	#define USE_TIM13
#elif (MB_TIM_NUMB == 14)
	#define USED_MODBUS_TIM					TIM14 
	#define USE_TIM14
#endif


#endif //__MODBUS_H_