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


uint8_t          WakeUpFlag;
uint8_t          WakeUpID;
extern uint8_t   NM_RECEIVE;
extern uint8_t   sleepFl1g;
extern uint8_t   CanFrameExist;
extern INT8U     BUS_OFF_FLAG;       //----hyq--20180728
extern uint8_t DiagnosticReceived;
uint32_t                      MSCAN0TxID[3];
MSCAN0BusoffMonitorStruct     MSCAN0Busoff;

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

//
extern void DoCAN_L_Data_Indication(uint32_t Identifier, uint8_t dlc, uint8_t *pData);
extern void DoCAN_L_Data_Confirm(uint32_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      ：报文数据
        Priority  ：发送优先级 0 - 2 数字越小优先级越高
返回值：无
******************************************************************************/

//外发报文的ID列表
static uint16_t CAN_TXIDS[] = {0x26D, 0x315, 0x530, 0x402, 0x7EB};
#define CAN_TXIDS_LEN 5

//CAN数据发送请求
void MSCAN0_L_Data_Request(uint32_t Identifier, uint8_t DLC, uint8_t *Data, uint8_t Priority)
{
  uint8_t  i;
  uint8_t  BufSel;

  //MSCAN与CAN BUS不同步,或ID无效,或发送数据长度大于8,或优先级设置错误时,上报发送失败并退出发送
  if ((CAN0CTL0_SYNCH == 0) || (DLC > 8) || (Priority > 2))
  {
    MSCAN0_L_Data_Confirm(Identifier, NOT_COMPLETE);
    return;
  }

  //ID不在发送的报文ID里
  /*
  for(i=0; i<CAN_TXIDS_LEN; i++)
  {
    if(Identifier==CAN_TXIDS[i])
	  break;
  }
  if(i>=CAN_TXIDS_LEN)
  	return;
  */
  
  //发送缓存指定
  BufSel = (0x01 << Priority);
  
  //当前优先级的发送缓冲区为空时,上报发送失败并退出发送
  if (CAN0TFLG & BufSel == 0)
  {
    MSCAN0_L_Data_Confirm(Identifier, NOT_COMPLETE);
    return;
  }

  //前一次报文发送未得到结果但是Buffer被意外清空
  if (MSCAN0TxID[Priority] != 0xFFFFFFFF)
    MSCAN0_L_Data_Confirm(MSCAN0TxID[Priority], NOT_COMPLETE);
  
  MSCAN0TxID[Priority] = Identifier;        //保存ID

  //!!寄存器操作
  //选择指向指定的发送缓冲区
  CAN0TBSEL  = BufSel;   		            
  //写入标识符ID
  CAN0TXIDR0 =  (uint8_t)(Identifier >> 21);
  CAN0TXIDR1 =  ((uint8_t)(Identifier >> 13) & 0xE0) | ((uint8_t)(Identifier >> 15) & 0x07);
  CAN0TXIDR2 =  (uint8_t)(Identifier >> 7);
  CAN0TXIDR3 =  (uint8_t)(Identifier << 1) & 0xFE;

  //发送（扩展帧）
  CAN0TXIDR1_SRR = 1;
  CAN0TXIDR1_IDE = 1;

  //写入数据
  for (i = 0; i < DLC; i++)
    *((&CAN0TXDSR0)+i) = Data[i];
  
  CAN0TXDLR = DLC;//DLC
  
  //CAN0TXTBPR = 0;                         //设置优先级,各Buffer优先级相同,则BufferID越小,优先级越高
  CAN0TFLG  = BufSel;                       //启动发送
  CAN0TIER |= BufSel;                       //启动相应CAN发送中断
  //end
}

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

  MSCANIDStruct * pID = (MSCANIDStruct*)(&Identifier);
  //网络管理报文处理
  //发给本机的报文
  //if(pID->PS==0x28 || pID->PS==0xBF || Identifier==0x1810A6A0)  //----hyq--20180728  ID过滤
  recv_id =               Identifier;
  if ((Identifier == 0x18DB33F1UL) || (Identifier == 0x18DAFCF1UL))
  {
    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(uint32_t Identifier, uint8_t TransferStatus)
{
  if (Identifier == 0x7EB)//0x7EB是仪表的响应ID
  {
    DoCAN_L_Data_Confirm(Identifier, TransferStatus);
  }
  //网络管理报文
  else if( ( Identifier >= 0x400 ) && ( Identifier <= 0x47F ) )
  {
    ;//CanNm_TxConfirmation(0);
  }
}

/******************************************************************************
函数名：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
	  //CAN总线已经在跛行模式(多次恢复失败),则不再恢复
      if (MSCAN0Busoff.Status == MSCAN0_BUS_LIMP)
        return;
    #endif
    //如果Bus-off发生
    if (CAN0MISC_BOHOLD)
    {
      MSCAN0Busoff.Timer++;//bus off 计时
      //进入一级bus off状态
      if(MSCAN0Busoff.Status == MSCAN0_BUS_STABLE)
        MSCAN0Busoff.Status = MSCAN0_BUS_OFF_LV1;

	  //在一级bus off状态下
      if (MSCAN0Busoff.Status == MSCAN0_BUS_OFF_LV1)
      {
        if (MSCAN0Busoff.Timer >= MSCAN0_BUS_OFF_LV1_RECOVERY_TIME / 20)
        {
          MSCAN0Busoff.Timer = 0;
          
          CAN0MISC_BOHOLD = 1;                      //写1清零,请求恢复Bus-off

          MSCAN0Busoff.Cnt++;
		  //进入bus off 二级
          if (MSCAN0Busoff.Cnt >= 10)
          {
            BUS_OFF_FLAG = 1;
            MSCAN0Busoff.Cnt = 0;
            MSCAN0Busoff.Status = MSCAN0_BUS_OFF_LV2;
          }
        }
      }
	  //二级模式
      else if (MSCAN0Busoff.Status == MSCAN0_BUS_OFF_LV2)
      {
        if (MSCAN0Busoff.Timer >= MSCAN0_BUS_OFF_LV2_RECOVERY_TIME / 20)
        {
          MSCAN0Busoff.Timer = 0;
          
          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;
            
            MSCAN0Busoff.Status = MSCAN0_BUS_LIMP;  //进入CAN总线跛行模式
            CAN0_PHY_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发生
    { 
      BUS_OFF_FLAG = 0;
      MSCAN0Busoff.Status = MSCAN0_BUS_STABLE;
      MSCAN0Busoff.Timer = 0;
      MSCAN0Busoff.Cnt   = 0;
    }
  #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 = 0X43;            //波特率设置 0x4314 250k  0x802B 500K
  CAN0BTR1 = 0X14;

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

  //CAN接收到完整数据后发送中断
  CAN0RIER_RXFIE = 1;   	  //使能CAN接收中断
  CAN0RIER_WUPIE = 1;         //使能WakeUP中断
  
  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_Enter_Low_Power_Mode
功  能：令MSCAN0进入低功耗模式
参  数：无
返回值：无
******************************************************************************/
void MSCAN0_Enter_Low_Power_Mode(void)
{
  MSCAN0_Init();              //透过初始化终止CAN收发
  
  CAN0_STB = 1;   	          //使能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;            //请求终止所有报文发送.
  
  //CAN0RIER_RXFIE = 0;   			//禁止CAN接收中断
}

/******************************************************************************
函数名：MSCAN0_Exit_Low_Power_Mode
功  能：令MSCAN0退出低功耗模式
参  数：无
返回值：无
******************************************************************************/
void MSCAN0_Exit_Low_Power_Mode(void)
{
  CAN0CTL0_SLPRQ = 0;         //使得MSCAN退出睡眠
  
  if(CAN0RFLG_WUPIF)          //唤醒中断请求标志 = 1 则写1清0
    CAN0RFLG_WUPIF = 1;
  CAN0CTL0_WUPE = 0;			    //禁用唤醒功能
  
  CAN0_STB = 0;               //使能CAN收发器
  
  CAN0RIER_RXFIE = 1;   			//使能CAN接收中断
}

/******************************************************************************
函数名：MSCAN0_Get_Wake_Up_Flag
功  能：获取MSCAN0的唤醒状态
参  数：无
返回值：0 - 未被唤醒 1 - 已被唤醒
******************************************************************************/
uint8_t MSCAN0_Get_Wake_Up_Flag(void)
{
  if (CAN0CTL0_SLPRQ == 0)
    return 1;
  
  //if (WakeUpFlag)
  //{
  //  WakeUpFlag = 0;
  //  return 1;
  //}
  
  return 0;
}

#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);               //避免中断处理期间有新的报文发送完毕

  //end
}

/******************************************************************************
函数名：MSCAN0_RX_ISR
功   能：MSCAN0接收中断服务函数。
        当有CAN报文送达时，接收CAN报文
参   数：无
返回值：无
******************************************************************************/
//J1939协议的RX中断接收
void interrupt MSCAN0_RX_ISR(void)
{
  uint32_t tmp;
  uint32_t CANMsgID;
  
  //CAN0RIER_RXFIE = 0;                      //先禁止MSCAN接收中断

  //普通帧
  if(!CAN0RXIDR1_IDE)                        //标准帧识别符 0 标准帧 11 位 1 扩展帧 29 位
  {
    CAN0RFLG_RXF   = 1;                      //清接收中断标志
    CAN0RIER_RXFIE = 1;                      //重新启动MSCAN接收中断
    return;
  }

  //数据接收
  tmp = (uint32_t)(CAN0RXIDR0) << 21;
  tmp &= 0x1FE00000; 
  CANMsgID =  tmp | ((uint32_t)(CAN0RXIDR1 & 0xE0) <<13) |  ((uint32_t)(CAN0RXIDR1 & 0x07) <<15)
                  | ((uint32_t)(CAN0RXIDR2) << 7)
                  | ((uint32_t)(CAN0RXIDR3) >> 1);//ID
                  
  MSCAN0_L_Data_Indication(CANMsgID, CAN0RXDLR & 0x0F, &CAN0RXDSR0);
  
  CanFrameExist=1;
  CAN0RFLG_RXF   = 1;                      //清接收中断标志
  //CAN0RIER_RXFIE = 1;                      //重新启动MSCAN接收中断
  //end
}

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

  if((CANMsgID >= 0x400) && (CANMsgID <= 0x47F))
    MSCAN0_L_Data_Indication(CANMsgID, CAN0RXDLR & 0x0F, &CAN0RXDSR0);
  else
  {
  	switch(CANMsgID)
  	{
    	case  0x068 :
    	case  0x268 :
    	case  0x2D7 :
      case  0x4D4 :
      case  0x1C8 :
      case  0x370 :
      case  0x1C9 :
      case  0x0F2 :
      case  0x0E2 :
      case  0x082 :
      case  0x094 :
      case  0x288 :
      case  0x088 :
      case  0x120 :
      case  0x293 :
      case  0x115 :
      case  0x160 :
      case  0x07A :
      case  0x30F :
      case  0x2F1 :
      case  0x10D :
      case  0x7E3 :
      case  0x7DF :
      case  0x279 : MSCAN0_L_Data_Indication(CANMsgID, CAN0RXDLR & 0x0F, &CAN0RXDSR0);
                    break;
      default     : break;
  	}
	}
	
  CAN0RFLG_RXF = 1;                        //清接收中断标志
	CAN0RIER_RXFIE = 1;                      //重新启动MSCAN接收中断
}*/

/******************************************************************************
函数名：MSCAN0_WAKEUP_ISR
功  能：MSCAN0唤醒中断
参  数：无
返回值：无
******************************************************************************/
void interrupt MSCAN0_WAKEUP_ISR(void)
{
  CAN0RFLG_WUPIF = 1;
  CAN0_STB = 0;             //----hyq--20180806
  WakeUpFlag     = 1;
}
#pragma CODE_SEG DEFAULT

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