/******************************************************************************
文 件 名：MSCAN0.h
功能描述：MSCAN0总线收发控制函数库文件
作    者：张暄
版    本：V1.0
日    期：2016.5.5
******************************************************************************/

#include "MSCAN0.h"
uint8_t    WakeUpFlag;
uint8_t    WakeUpID;
uint16_t                      MSCAN0TxID[3];
MSCAN0BusoffMonitorStruct     MSCAN0Busoff;
extern uint8_t NM_RECEIVE;

extern void CoCAN_L_Data_Indication(uint16_t Identifier, uint8_t DLC, uint8_t *Data);
extern void CoCAN_L_Data_Confirm(uint16_t Identifier, uint8_t TransferStatus);

extern void DoCAN_L_Data_Indication(uint16_t Identifier, uint8_t dlc, uint8_t *pData);
extern void DoCAN_L_Data_Confirm(uint16_t Identifier, uint8_t TransferStatus);

extern FUNC(void, CANNM_CODE) CanNm_RxIndication
(
  PduIdType RxPudId,
  const uint16 RxId,
  P2CONST(PduInfoType, AUTOMATIC, CANNM_APPL_DATA)PduInfoPtr
);

extern FUNC(void, CANNM_CODE) CanNm_TxConfirmation
(
  PduIdType TxPudId
);

/******************************************************************************
函数名：MSCAN0_L_Data_Request
功  能：该函数由上层调用,用于请求向总线上发送一组报文
参  数：Identifier：报文ID
        DLC       ：报文长度
        Data      ：报文数据
返回值：无
******************************************************************************/
void MSCAN0_L_Data_Request(uint16_t Identifier, uint8_t DLC, uint8_t *Data)
{
  uint8_t  i;
  uint8_t  BufDetect;
  uint8_t  BufSel;

  //发送数据长度大于8或MSCAN与CAN BUS不同步或3个发送缓冲区都没有空闲时或ID无效,上报发送失败并退出发送
  if ((DLC > 8) ||	(CAN0CTL0_SYNCH == 0) || (CAN0TFLG & 0x07 == 0) || (Identifier > 0x07FF))
  {
    MSCAN0_L_Data_Confirm(Identifier, NOT_COMPLETE);
    return;
  }

  CAN0TBSEL = CAN0TFLG;   		              //选择指向空闲的发送缓冲区(多个空闲选择最低的)
  BufSel    = CAN0TBSEL;

  BufDetect = 0x01;
  for (i = 0; i < 3; i++)
  {
    if (BufDetect == BufSel)
    {
      if (MSCAN0TxID[i] != 0xFFFF)           //前一次报文发送未得到结果但是Buffer被意外清空
      {
        MSCAN0_L_Data_Confirm(MSCAN0TxID[i], NOT_COMPLETE);
        MSCAN0TxID[i] = 0xFFFF;
      }

      MSCAN0TxID[i] = Identifier;            //保存ID
    }

    BufDetect <<= 1;
  }

  CAN0TXIDR0 = (uint8_t)(Identifier >> 3);  //写入标识符ID
  CAN0TXIDR1 = (uint8_t)(Identifier << 5);
  CAN0TXIDR1_SRR = 0;
  CAN0TXIDR1_IDE = 0;

  for (i = 0; i < DLC; i++)                 //写入数据
    *((&CAN0TXDSR0) + i) = Data[i];

  CAN0TXDLR = DLC;

  //CAN0TXTBPR = 0;                         //设置优先级,各Buffer优先级相同,则BufferID越小,优先级越高
  CAN0TFLG  = BufSel;                       //启动发送
  CAN0TIER |= BufSel;                       //启动相应CAN发送中断
}

/******************************************************************************
函数名：MSCAN0_L_Data_Indication
功  能：该函数用于向上层指示一帧报文的到来,并同时将报文数据传递至上层
        注意：1.使用时应将上层的报文传递函数包含于本函数之中,报文到来时则会调用
                相应的函数传递报文数据
              2.本函数与中断相关,被调用的上层的报文传递函数应尽可能简短
参  数：Identifier：报文ID
        DLC       ：报文长度
        Data      ：报文数据
返回值：无
******************************************************************************/
void MSCAN0_L_Data_Indication(uint16_t Identifier, uint8_t DLC, uint8_t *Data)
{
  PduInfoType NW_MessageBuffer;

  if( ( Identifier >= 0x400 ) && ( Identifier <= 0x47F ) )  //网络管理报文
  {
    NW_MessageBuffer.SduDataPtr = Data;
    NW_MessageBuffer.SduLength = DLC;
    CanNm_RxIndication(0, 0, &NW_MessageBuffer);
    NM_RECEIVE = 1;

  }
  else    if ((Identifier == 0x7E3) || (Identifier == 0x7EB))
  {
    DoCAN_L_Data_Indication(Identifier, DLC, Data);
  }
  else
  {
    //应用报文
    if( sleepFlg == NET_WORK_STATE )
    {
      CoCAN_L_Data_Indication(Identifier, DLC, Data);

    }
    else if( sleepFlg == PREPARE_BUS_SLEEP_STATE )
    {


    }
    else if( sleepFlg == BUS_SLEEP_STATE )
    {

    }
    else if( sleepFlg == START_INDICATION_STATE )
    {
      CoCAN_L_Data_Indication(Identifier, DLC, Data);
    }
  }

  //  if ((Identifier == 0x7E3) || (Identifier == 0x7EB))
  //    DoCAN_L_Data_Indication(Identifier, DLC, Data);
  //  else
  //   CoCAN_L_Data_Indication(Identifier, DLC, Data);
}

/******************************************************************************
函数名：MSCAN0_L_Data_Confirm
功  能：该函数用于向上层指示请求发送的报文的发送结果
        注意：1.使用时应将上层的报文发送结果传递函数包含于本函数之中,报文发送事
                件产生时则会调用相应的函数传递报文发送结果
              2.本函数与中断相关,被调用的上层的报文发送结果传递函数应尽可能简短
参  数：Identifier    ：报文ID
        TransferStatus：报文发送结果：COMPLETE     发送完成
                                      NOT_COMPLETE 报文未能发送完成
返回值：无
******************************************************************************/
void MSCAN0_L_Data_Confirm(uint16_t Identifier, uint8_t TransferStatus)
{
  if( ( Identifier >= 0x400 ) && ( Identifier <= 0x47F ) )  //网络管理报文
  {
    CanNm_TxConfirmation(0);
  }
  else if (Identifier == 0x7E3)
    DoCAN_L_Data_Confirm(Identifier, TransferStatus);
  else
  {
    CoCAN_L_Data_Confirm(Identifier, TransferStatus);
  }

  //if (Identifier == 0x7E3)
  //   DoCAN_L_Data_Confirm(Identifier, TransferStatus);
  // else
  //   CoCAN_L_Data_Confirm(Identifier, TransferStatus);
}

/******************************************************************************
函数名：MSCAN0_Bus_Off_Monitoring_Service
功  能：MSCAN0 Bus-off状态监控服务,监控Bus-off状态并尝试恢复
参  数：无
返回值：无
*******************************************************************************
注  意：该服务函数必须每20ms被调用一次
******************************************************************************/
void MSCAN0_Bus_Off_Monitoring_Service(void)
{
#if !MSCAN0_BUS_OFF_AUTO_RECOVERY

#if MSCAN0_BUS_LIMP_MODE_ENABLE
  if (MSCAN0Busoff.Status == MSCAN0_BUS_LIMP)
    return;	                                  //CAN总线已经在跛行模式(多次恢复失败),则不再恢复
#endif

  if (CAN0MISC_BOHOLD)                          //如果Bus-off发生
  {



    MSCAN0Busoff.Timer++;
    if (MSCAN0Busoff.Timer >= MSCAN0_BUS_OFF_RECOVERY_TIME / 20)
    {
      MSCAN0Busoff.Timer = 0;

      MSCAN0Busoff.Status = MSCAN0_BUS_OFF;     //记录Bus-off状态

      CAN0MISC_BOHOLD = 1;                      //写1清零,请求恢复Bus-off

#if MSCAN0_BUS_LIMP_MODE_ENABLE
      MSCAN0Busoff.Cnt++;
      if (MSCAN0Busoff.Cnt > MSCAN0_BUS_LIMP_MODE_THRESHOLD)
      {
        MSCAN0Busoff.Cnt = 0;
        CAN0MISC_BOHOLD = 1;                      //写1清零,请求恢复Bus-off
        MSCAN0Busoff.Status = MSCAN0_BUS_LIMP;  //进入CAN总线跛行模式
        CAN0_STB = 1;                       //进入省电模式
        MSCAN0_Init();				                  //通过初始化终止所有CAN收发
        CAN0RIER_RXFIE = 0;   		              //禁止CAN接收中断
        CAN0RIER_WUPIE = 0;                     //关闭唤醒中断
        CAN0CTL0_WUPE  = 0;                     //禁止唤醒使能
        if (CAN0RFLG_WUPIF)
          CAN0RFLG_WUPIF = 1;                   //如果有唤醒中断请求标志也一并清除(写1清零)
      }
#endif
    }
  }
  else                                          //总线正常,没有Bus-off发生
  {
    MSCAN0Busoff.Status = MSCAN0_BUS_STABLE;
    MSCAN0Busoff.Timer = 0;

#if MSCAN0_BUS_LIMP_MODE_ENABLE
    MSCAN0Busoff.Cnt   = 0;
#endif
  }
#endif
}

/******************************************************************************
函数名：MSCAN0_Get_Bus_Status
功  能：获取MSCAN0总线状态
参  数：无
返回值：MSCAN0_BUS_STABLE：总线正常,稳定工作
        MSCAN0_BUS_OFF   ：总线处于Bus-off状态
        MSCAN0_BUS_LIMP  ：总线处于跛行状态(Bus-off后多次恢复均失败而关闭总线)
******************************************************************************/
uint8_t MSCAN0_Get_Bus_Status(void)
{
  return MSCAN0Busoff.Status;
}

/******************************************************************************
函数名：MSCAN0_Init
功  能：MSCAN0初始化程序
参  数：无
返回值：无
******************************************************************************/
void MSCAN0_Init(void)
{
  uint8_t i;

  CAN0CTL0 = 0x01;            //请求 MSCAN进入初始化模式
  while(!(CAN0CTL1_INITAK));  //等待进入初始化模式

  CAN0IDAC = 0;   		        //配置屏蔽验收寄存器的工作方式  two 32-bit filter

  CAN0IDAR0 = 0x00;     	    //设置验收寄存器
  CAN0IDAR1 = 0x00;
  CAN0IDAR2 = 0x00;
  CAN0IDAR3 = 0x00;

  CAN0IDMR0 = 0xFF;           //设置屏蔽寄存器 (这里设置为所有报文ID都接收)
  CAN0IDMR1 = 0xFF;
  CAN0IDMR2 = 0xFF;
  CAN0IDMR3 = 0xFF;

  CAN0IDAR4 = 0x00;      	    //设置验收寄存器
  CAN0IDAR5 = 0x00;
  CAN0IDAR6 = 0x00;
  CAN0IDAR7 = 0x00;

  CAN0IDMR4 = 0xFF;           //设置屏蔽寄存器 (这里设置为所有报文ID都接收)
  CAN0IDMR5 = 0xFF;
  CAN0IDMR6 = 0xFF;
  CAN0IDMR7 = 0xFF;

  CAN0CTL1_CANE   = 1;        //使能MSCAN模块
  CAN0CTL1_CLKSRC = 0;        //选择OSCCLK(8MHz)为MSCAN的时钟源
  CAN0CTL1_LISTEN = 0;        //正常运行模式(非监听模式)

#if MSCAN0_BUS_OFF_AUTO_RECOVERY
  CAN0CTL1_BORM = 0;	      //自动总线BUS_OFF恢复
#else
  CAN0CTL1_BORM = 1;	      //总线BUS_OFF由用户控制恢复
#endif

  CAN0CTL1_WUPM = 1;		      //给唤醒加滤波

  CAN0BTR0 = MSCAN0_BTR0_VALUE;   //波特率设置
  CAN0BTR1 = MSCAN0_BTR1_VALUE;

  CAN0CTL0 = 0x00;            //退出初始化模式
  while (CAN0CTL1_INITAK);    //等待退出初始化模式
  while (!CAN0CTL0_SYNCH);    //等待MSCAN与CAN总线同步


  CAN0TIER = 0x00;            //禁止CAN发送中断
  CAN0RIER_RXFIE = 1;   		  //使能CAN接收中断
  CAN0RIER_WUPIE = 1;

  CAN0_STB = 0;   	          //使能CAN收发器

  MSCAN0Busoff.Status = MSCAN0_BUS_STABLE;
  MSCAN0Busoff.Timer = 0;

#if MSCAN0_BUS_LIMP_MODE_ENABLE
  MSCAN0Busoff.Cnt   = 0;
#endif

  for(i = 0; i < 3; i++)
    MSCAN0TxID[i] = 0xFFFF;   //0xFFFF表示无效ID
}

/******************************************************************************
函数名：MSCAN0_Low_Power_Mode
功  能：令MSCAN0进入低功耗模式
参  数：无
返回值：无
******************************************************************************/
void MSCAN0_Low_Power_Mode(void)
{
  //MSCAN进入低功耗模式
  MSCAN0_Init();               //透过初始化终止CAN收发
  //	CAN0_STB = 1;   	          //使能CAN收发器
  CAN0RIER_RXFIE = 0;   			//禁止CAN接收中断
  if(CAN0RFLG_WUPIF)          //唤醒中断请求标志 = 1 则写1清0
    CAN0RFLG_WUPIF = 1;
  CAN0CTL0_WUPE = 1;			    //唤醒使能

  //置1后,当MSCAN所有发送完成(发送队列空),接受也完成(CAN总线空闲)后,
  //MSCAN 进入SLEEP模式,并且置 CAN0CTL1_SLPAK=1表示确认
  //在置此位前 CAN0RFLG_WUPIF 必须清除

  //当CAN0CTL0_SLPRQ=1,且CAN0CTL1_SLPAK=1 ,表示MSCAN已经进入睡眠模式
  CAN0CTL0_SLPRQ = 1;			    //请求置MSCAN进入睡眠模式

  //当MSCAN进入睡眠模式后,如果 CAN0CTL0_WUPE = 1 且收到CAN报文后,MSCAN退出睡眠,且CAN0RFLG_WUPIF置1
  //同时 CAN0CTL0_SLPRQ,CAN0CTL1_SLPAK 也将自动清0.

  //在MSCAN进入睡眠模式后,软件也可以主动置CAN0CTL0_SLPRQ = 0,使得MSCAN退出睡眠,CAN0CTL1_SLPAK 也将自动清0.
  //当 CAN0CTL0_SLPRQ = 1后,如果MSCAN还没有进入睡眠模式,那么不能主动清CAN0CTL0_SLPRQ = 0.

  CAN0TARQ = 0x07;            //请求终止所有报文发送.
}

#pragma CODE_SEG __NEAR_SEG NON_BANKED   	//置所有中断函数放在FLASH的非分页区

/******************************************************************************
函数名：MSCAN0_TX_ISR
功  能：MSCAN0发送中断服务函数。
        当有CAN报文发送完毕或被中止发送时，执行此函数
参  数：无
返回值：无
******************************************************************************/
void interrupt MSCAN0_TX_ISR(void)
{
  uint8_t i;
  uint8_t TxFlag;
  uint8_t TxBuf;
  uint8_t BufDetect;

  do
  {
    TxFlag = CAN0TFLG;

    TxBuf = CAN0TIER & TxFlag;              //获取发送完毕或被中止发送的报文Buffer
    CAN0TIER &= ~TxBuf;                     //关闭发送完毕或被中止发送的报文Buffer对应的中断

    BufDetect = 1;
    for (i = 0; i < 3; i++)
    {
      if (BufDetect & TxBuf)
      {
        if (BufDetect & CAN0TAAK)           //报文被成功中止发送
          MSCAN0_L_Data_Confirm(MSCAN0TxID[i], NOT_COMPLETE);
        else                                //报文发送成功
          MSCAN0_L_Data_Confirm(MSCAN0TxID[i], COMPLETE);

        MSCAN0TxID[i] = 0xFFFF;             //清除报文记录
      }

      BufDetect <<= 1;
    }
  }
  while (TxFlag != CAN0TFLG);               //避免中断处理期间有新的报文发送完毕
}

/******************************************************************************
函数名：MSCAN0_RX_ISR
功  能：MSCAN0接收中断服务函数。
        当有CAN报文送达时，接收CAN报文
参  数：无
返回值：无
******************************************************************************/
void interrupt MSCAN0_RX_ISR(void)
{
  uint16_t CANMsgID;

  CAN0RIER_RXFIE = 0;                      //先禁止MSCAN接收中断

  if (CAN0RXIDR1_IDE || CAN0RXIDR1_SRR)    //如果收到的数据为扩展帧,或为远程帧
  {
    CAN0RFLG_RXF = 1;                      //清接收中断标志
    CAN0RIER_RXFIE = 1;                    //重新启动MSCAN接收中断

    return;
  }

  CANMsgID = ((uint16_t)(CAN0RXIDR0 << 3)) | ((uint16_t)(CAN0RXIDR1 >> 5));

  MSCAN0_L_Data_Indication(CANMsgID, CAN0RXDLR & 0x0F, &CAN0RXDSR0);

  CAN0RFLG_RXF = 1;                        //清接收中断标志
  CAN0RIER_RXFIE = 1;                      //重新启动MSCAN接收中断
}

/******************************************************************************
函数名：MSCAN0_WAKEUP_ISR
功  能：MSCAN0唤醒中断
参  数：无
返回值：无
******************************************************************************/
void interrupt MSCAN0_WAKEUP_ISR(void)
{
  CAN0RFLG_WUPIF = 1;
  WakeUpFlag = 1;
}

uint8_t GetWakeUPFLAG(void)
{
  return    WakeUpFlag;
}

#pragma CODE_SEG DEFAULT

Std_ReturnType CanIf_Transmit(PduIdType CanTxPduId, PduInfoType *PduInfoPtr)
{
  MSCAN0_L_Data_Request( 0x402, (uint8_t)PduInfoPtr->SduLength, (uint8_t *)PduInfoPtr->SduDataPtr );
  return E_OK;
}
