/*
 * LinBus.h
 *
 *  Created on: 17 дек. 2015 г.
 *      Author: esaulenko
 */

#ifndef DRIVERS_LINBUS_H_
#define DRIVERS_LINBUS_H_

#ifndef CAN_FIRMWARE

#include "KLineDrv.h"
#include "ImoDrv.h"
#include "Buffer.h"
#include "SysTimer.h"
#include "scmRTOS/scmRTOS.h"

extern CLin LinBus1;
extern CLin LinBus2;
extern CLin LinBus3;

union TLinInit;

#endif // CAN_FIRMWARE

#include "LinFrame.h"

class CLin
{
public:

	enum TLinMode {
		Mode_RawNoParity,		// без стандарта, принимает-передаёт любые пакеты (без чётности)
		Mode_Master,			// LIN-мастер
		Mode_Slave,				// LIN-слейв
		Mode_RawEvenParity,		// без стандарта, байты с проверкой на чётность
		Mode_RawOddParity,		// без стандарта, байты с проверкой на нечётность
		Mode_Raw9bit,			// без стандарта, в байте 9 бит данных без чётности
		Mode_RawIgnoreError,	// без стандарта, принимаются байты без чётности, стоп-бита и т.д.
		Mode_RawStop2bit,		// без стандарта, 2 стоп-бита
		Mode_ImoImi,			// протокол Toyota (старый), 48.8 бит/сек, 16 бит данных
		Mode_ImoRenault,		// протокол Renault, 81,4 бит/с
		Mode_ImoImi2,			// протокол Toyota (новый), 195.3 бит/сек, 32 бита данных
		Mode_FordDstTx,			// протокол Ford DST80 (ШИМ, 1 бит = 1 мс / 0,5 мс)
		Mode_FordUartRx,		// протокол Ford (UART 15625 бит/с, 384 мкс между байтами)
		Mode_Haval,				// протокол с импульсами подтверждения (фильтрация/генерация)
		Mode_FordDstInversion,	// протокол Ford DST80 (ШИМ, 1 бит = 1 мс / 0,5 мс, ИНВЕРСНЫЙ сигнал)
	};

#ifndef CAN_FIRMWARE

	CLin(const KLineHw & cfg) : _drv(cfg, this), _imoDrv(this, cfg)
	{}

	uint32_t Init(const TLinInit & init);

	// Для режима Haval
	void InitEXTI(void);
	void DeinitEXTI(void);
	void EdgeRcv(const bool newVal);

	void CheckTimeout(void);

	int32_t GetFrame(TLinFrame & aFrame);

	uint32_t SendFrame(const TLinFrame & aFrame);

	void Sleep(const bool sleepEnable);

// вызывается из обработчиков прерываний:
// приём/передача UART
	void IrqUart(void)
	{	_drv.IrqProcess();	}
// передача UART по таймеру
	uint32_t IrqTimerSend(void)
	{	return _drv.IrqTimerSend();	}
// фронт в режиме IMO/IMI
	void IrqImoEdge(const bool val)
	{
		Sleep(false);
		if (_mode == Mode_Haval)
			EdgeRcv(val);
		else
			_imoDrv.rx.EdgeRcv(val);
	}
// таймер передачи в режиме IMO/IMI (только для LIN1)
	void IrqImoSend(void)
	{	_imoDrv.tx.IrqSetNextBit();	}

// управление выходом напрямую, как GPIO
	void SetOutput(const bool setZero);

private:
	CKLineDrv _drv;
	CImoDrv   _imoDrv;

// вызывается из прерывания по приёму
	void DataRcvd(uint16_t data);
	void BreakRcvd(void);
	void ErrorRcvd(void);

// вызывается из прерываний IMO/IMI
	void DataImiRcvd(const uint8_t * data, uint32_t data_len);

// вызывается из прерывания по передаче
	bool IsTxData(void)
	{	return _tx_buf.Avail() > 0;	}
	uint16_t GetTxData(void)
	{	return _tx_buf.Get();		}
	void TxComplete(void);

	friend CKLineDrv;
	friend CImoDrv;

	TLinMode _mode;

	union {
		bool _mode9bit = false;
		bool _InverseSignal;	// инверсия входного сигнала
	};

	// не-UART протоколы на таймере
	bool isCustomMode(void) const
	{
		return	_mode == Mode_ImoImi ||
				_mode == Mode_ImoImi2 ||
				_mode == Mode_ImoRenault ||
				_mode == Mode_FordDstTx ||
				_mode == Mode_FordDstInversion;
	}

	enum {
		State_NoInit,
		State_BusOff,
		State_Idle,
		State_WaitSync,
		State_WaitId,
		State_WaitData,
		State_SlaveTxData,
	} _state = State_NoInit;

	TLinFrame _rx_frame;
	TimerMs	_rx_tmr;
	uint32_t _rx_timeout;

	TimerMs _sleep_tmr;
	uint32_t _sleep_timeout;

	uint8_t _bytecnt;		// счетчик байт для Mode_Haval
	uint8_t	_bytes_noack;	// кол-во байт без подтверждения для Mode_Haval
	uint8_t _bytes_ack;		// кол-во байт c подтверждением для Mode_Haval
//	TimerUs	_ack_tmr;		// таймер для генерации импульов Mode_Haval
	uint8_t _bit_length_us;
	uint16_t _ack_pause_us;

	bool FrameToRxQueue(void);
	CircularBuffer<TLinFrame, 8> _rx_queue;

	CircularBuffer<uint16_t, 16> _tx_buf;	// буфер на передачу

	TLinFrame _tx_fr_buf[16];				// кадры для передачи slave'ом
	TLinFrame*_tx_fr_cur = _tx_fr_buf;		// текущий передающийся кадр
	bool StartSlaveTx(uint8_t id);			// функция отправки этого кадра

	TLinFrame * getTxFrame(void)			// забрать фрейм из буфера (для imo_drv)
	{
		for (auto & it: _tx_fr_buf)
			if (it.data_len > 0)
				return &it;
		return nullptr;
	}

// возвращает номер канала
	inline TLinChannel Instance(void) const
	{
#ifdef MOBICAR_3
		if (this == &LinBus3)
			return LINch3;
#endif
		if (this == &LinBus2)
			return LINch2;
		return LINch1;
	}

#endif // not CAN_FIRMWARE

};

union TLinInit
{
	struct TLin
	{
		uint16_t		baudrate;
		CLin::TLinMode	mode;
		uint32_t 		rxTimeout;
		uint32_t 		sleepTimeout;
	}Lin;

	struct THaval
	{
		uint16_t		baudrate;
		CLin::TLinMode	mode;
		uint16_t 		rxTimeout;
		uint32_t 		sleepTimeout;
		uint8_t			bytes_noack;			// кол-во байт без подтверждения
		uint8_t			bytes_ack;				// кол-во байт c подтверждением
		uint16_t		ack_pause_us;			// пауза в us перед ack в режиме генерации (0-режим фильтрации)
	}Haval;
};

#endif /* DRIVERS_LINBUS_H_ */
