2023-08-22 07:22:00 +00:00
|
|
|
|
/**
|
|
|
|
|
ECAN Generated Driver File
|
|
|
|
|
|
|
|
|
|
@Company
|
|
|
|
|
Microchip Technology Inc.
|
|
|
|
|
|
|
|
|
|
@File Name
|
|
|
|
|
ecan.c
|
|
|
|
|
|
|
|
|
|
@Summary
|
|
|
|
|
This is the generated driver implementation for the CAN driver using PIC10 / PIC12 / PIC16 / PIC18 MCUs
|
|
|
|
|
|
|
|
|
|
@Description
|
|
|
|
|
This source file provides APIs for CAN.
|
|
|
|
|
Generation Information :
|
2023-08-24 15:28:07 +00:00
|
|
|
|
Product Revision : PIC10 / PIC12 / PIC16 / PIC18 MCUs - 1.81.8
|
|
|
|
|
Device : PIC18F25K83
|
2023-08-22 07:22:00 +00:00
|
|
|
|
Driver Version : 3.0.0
|
|
|
|
|
The generated drivers are tested against the following:
|
2023-08-24 15:28:07 +00:00
|
|
|
|
Compiler : XC8 2.36 and above
|
|
|
|
|
MPLAB : MPLAB X 6.00
|
2023-08-22 07:22:00 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
(c) 2018 Microchip Technology Inc. and its subsidiaries.
|
|
|
|
|
|
|
|
|
|
Subject to your compliance with these terms, you may use Microchip software and any
|
|
|
|
|
derivatives exclusively with Microchip products. It is your responsibility to comply with third party
|
|
|
|
|
license terms applicable to your use of third party software (including open source software) that
|
|
|
|
|
may accompany Microchip software.
|
|
|
|
|
|
|
|
|
|
THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
|
|
|
|
|
EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY
|
|
|
|
|
IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS
|
|
|
|
|
FOR A PARTICULAR PURPOSE.
|
|
|
|
|
|
|
|
|
|
IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
|
|
|
|
|
INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
|
|
|
|
|
WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP
|
|
|
|
|
HAS BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO
|
|
|
|
|
THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL
|
|
|
|
|
CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT
|
|
|
|
|
OF FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS
|
|
|
|
|
SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Section: Included Files
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <xc.h>
|
|
|
|
|
#include "ecan.h"
|
|
|
|
|
|
|
|
|
|
static void (*RXBnInterruptHandler)(void);
|
|
|
|
|
static void (*RXBnOverflowHandler)(void);
|
|
|
|
|
static void (*BusOffHandler)(void);
|
|
|
|
|
static void (*TXPassiveHandler)(void);
|
|
|
|
|
static void (*RXPassiveHandler)(void);
|
|
|
|
|
static void (*TXWarningHandler)(void);
|
|
|
|
|
static void (*RXWarningHandler)(void);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Local Functions
|
|
|
|
|
*/
|
|
|
|
|
static uint32_t convertReg2ExtendedCANid(uint8_t tempRXBn_EIDH, uint8_t tempRXBn_EIDL, uint8_t tempRXBn_SIDH, uint8_t tempRXBn_SIDL);
|
|
|
|
|
static uint32_t convertReg2StandardCANid(uint8_t tempRXBn_SIDH, uint8_t tempRXBn_SIDL);
|
|
|
|
|
static void convertCANid2Reg(uint32_t tempPassedInID, uint8_t canIdType, uint8_t *passedInEIDH, uint8_t *passedInEIDL, uint8_t *passedInSIDH, uint8_t *passedInSIDL);
|
|
|
|
|
|
|
|
|
|
static void RXBnDefaultInterruptHandler(void) {}
|
|
|
|
|
static void RXBnOverflowDefaultHandler(void) {}
|
|
|
|
|
static void BusOffDefaultHandler(void) {}
|
|
|
|
|
static void TXPassiveDefaultHandler(void) {}
|
|
|
|
|
static void RXPassiveDefaultHandler(void) {}
|
|
|
|
|
static void TXWarningDefaultHandler(void) {}
|
|
|
|
|
static void RXWarningDefaultHandler(void) {}
|
|
|
|
|
|
|
|
|
|
void ECAN_Initialize(void)
|
|
|
|
|
{
|
|
|
|
|
CANCON = 0x80;
|
|
|
|
|
while (0x80 != (CANSTAT & 0xE0)); // wait until ECAN is in config mode
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Mode 2
|
|
|
|
|
*/
|
|
|
|
|
ECANCON = 0x90;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Initialize CAN I/O
|
|
|
|
|
*/
|
|
|
|
|
CIOCON = 0x00;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Mask and Filter definitions
|
|
|
|
|
........................................................
|
|
|
|
|
CAN ID ID Type Mask Filter Buffer
|
|
|
|
|
........................................................
|
|
|
|
|
0x123 SID Acceptance Mask 0 Filter 0 FIFO
|
|
|
|
|
........................................................
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Configure Generic Buffers to be Transmit or Receive
|
|
|
|
|
*/
|
|
|
|
|
BSEL0 = 0x00;
|
|
|
|
|
/**
|
|
|
|
|
Mask and Filter definitions
|
|
|
|
|
........................................................
|
|
|
|
|
CAN ID ID Type Mask Filter Buffer
|
|
|
|
|
........................................................
|
|
|
|
|
........................................................
|
|
|
|
|
*/
|
2023-08-25 07:06:54 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Mask 0 is 11 bits for filters 0 and 1 only
|
|
|
|
|
* filter set for 3 recipient bits
|
|
|
|
|
* Mask 1 is copy of mask 0 but for other filters
|
|
|
|
|
*/
|
2023-08-22 07:22:00 +00:00
|
|
|
|
convertCANid2Reg(0x070, dSTANDARD_CAN_MSG_ID_2_0B, &RXM0EIDH, &RXM0EIDL, &RXM0SIDH, &RXM0SIDL);
|
2023-08-25 07:06:54 +00:00
|
|
|
|
convertCANid2Reg(0x070, dSTANDARD_CAN_MSG_ID_2_0B, &RXM1EIDH, &RXM1EIDL, &RXM1SIDH, &RXM1SIDL);
|
|
|
|
|
|
|
|
|
|
// filter 0 is broadcast message
|
2023-08-22 07:22:00 +00:00
|
|
|
|
convertCANid2Reg(0x000, dSTANDARD_CAN_MSG_ID_2_0B, &RXF0EIDH, &RXF0EIDL, &RXF0SIDH, &RXF0SIDL);
|
2023-08-25 07:06:54 +00:00
|
|
|
|
|
|
|
|
|
// filter 1 is message for controller
|
2023-08-22 07:22:00 +00:00
|
|
|
|
convertCANid2Reg(0x010, dSTANDARD_CAN_MSG_ID_2_0B, &RXF1EIDH, &RXF1EIDL, &RXF1SIDH, &RXF1SIDL);
|
2023-08-25 07:06:54 +00:00
|
|
|
|
|
|
|
|
|
// filter 2 is message for recipient n<>7, not yet defined
|
2023-08-22 07:22:00 +00:00
|
|
|
|
convertCANid2Reg(0x070, dSTANDARD_CAN_MSG_ID_2_0B, &RXF2EIDH, &RXF2EIDL, &RXF2SIDH, &RXF2SIDL);
|
|
|
|
|
|
2023-08-25 07:06:54 +00:00
|
|
|
|
/*
|
|
|
|
|
* ENABLE FILTERS
|
|
|
|
|
*
|
|
|
|
|
* Filter 0 set on mask 0
|
|
|
|
|
* Filter 1 set on mask 0
|
|
|
|
|
* Filter 2 set on mask 1
|
|
|
|
|
*/
|
|
|
|
|
RXFCON0 = 0x07;
|
|
|
|
|
|
2023-08-22 07:22:00 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Assign Filters to Masks
|
|
|
|
|
*/
|
2023-08-25 07:06:54 +00:00
|
|
|
|
// Filter 0 & 1 assigned to mask 0 and filter 2 assigned to mask 1
|
|
|
|
|
MSEL0 = 0x10;
|
2023-08-22 07:22:00 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Initialize CAN Timings
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Baud rate: 250kbps
|
|
|
|
|
System frequency: 64000000
|
|
|
|
|
ECAN clock frequency: 64000000
|
|
|
|
|
Time quanta: 8
|
|
|
|
|
Sample point: 1-1-4-2
|
|
|
|
|
Sample point: 75%
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
BRGCON1 = 0x0F;
|
|
|
|
|
BRGCON2 = 0x98;
|
|
|
|
|
BRGCON3 = 0x81;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ECAN_SetRXBnInterruptHandler(RXBnDefaultInterruptHandler);
|
|
|
|
|
PIR5bits.RXBnIF = 0;
|
|
|
|
|
PIE5bits.RXBnIE = 1;
|
|
|
|
|
|
|
|
|
|
ECAN_SetRXBnOverflowHandler(RXBnOverflowDefaultHandler);
|
|
|
|
|
ECAN_SetBusOffHandler(BusOffDefaultHandler);
|
|
|
|
|
ECAN_SetTXPassiveHandler(TXPassiveDefaultHandler);
|
|
|
|
|
ECAN_SetRXPassiveHandler(RXPassiveDefaultHandler);
|
|
|
|
|
ECAN_SetTXWarningHandler(TXWarningDefaultHandler);
|
|
|
|
|
ECAN_SetRXWarningHandler(RXWarningDefaultHandler);
|
|
|
|
|
PIR5bits.ERRIF = 0;
|
|
|
|
|
PIE5bits.ERRIE = 1;
|
|
|
|
|
|
|
|
|
|
CANCON = 0x00;
|
|
|
|
|
while (0x00 != (CANSTAT & 0xE0)); // wait until ECAN is in Normal mode
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
Section: ECAN APIs
|
|
|
|
|
*/
|
|
|
|
|
void CAN_sleep(void)
|
|
|
|
|
{
|
|
|
|
|
CANCON = 0x20; // request disable mode
|
|
|
|
|
while ((CANSTAT & 0xE0) != 0x20); // wait until ECAN is in disable mode
|
|
|
|
|
//Wake up from sleep should set the CAN module straight into Normal mode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t CAN_transmit(uCAN_MSG *tempCanMsg)
|
|
|
|
|
{
|
|
|
|
|
uint8_t tempEIDH = 0;
|
|
|
|
|
uint8_t tempEIDL = 0;
|
|
|
|
|
uint8_t tempSIDH = 0;
|
|
|
|
|
uint8_t tempSIDL = 0;
|
|
|
|
|
uint8_t returnValue = 0;
|
|
|
|
|
|
|
|
|
|
if (TXB0CONbits.TXREQ != 1)
|
|
|
|
|
{
|
|
|
|
|
convertCANid2Reg(tempCanMsg->frame.id, tempCanMsg->frame.idType, &tempEIDH, &tempEIDL, &tempSIDH, &tempSIDL);
|
|
|
|
|
|
|
|
|
|
TXB0EIDH = tempEIDH;
|
|
|
|
|
TXB0EIDL = tempEIDL;
|
|
|
|
|
TXB0SIDH = tempSIDH;
|
|
|
|
|
TXB0SIDL = tempSIDL;
|
|
|
|
|
TXB0DLC = tempCanMsg->frame.dlc | ((tempCanMsg->frame.rtr)<<6);
|
|
|
|
|
TXB0D0 = tempCanMsg->frame.data0;
|
|
|
|
|
TXB0D1 = tempCanMsg->frame.data1;
|
|
|
|
|
TXB0D2 = tempCanMsg->frame.data2;
|
|
|
|
|
TXB0D3 = tempCanMsg->frame.data3;
|
|
|
|
|
TXB0D4 = tempCanMsg->frame.data4;
|
|
|
|
|
TXB0D5 = tempCanMsg->frame.data5;
|
|
|
|
|
TXB0D6 = tempCanMsg->frame.data6;
|
|
|
|
|
TXB0D7 = tempCanMsg->frame.data7;
|
|
|
|
|
|
|
|
|
|
TXB0CONbits.TXREQ = 1; //Set the buffer to transmit
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if (TXB1CONbits.TXREQ != 1)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
convertCANid2Reg(tempCanMsg->frame.id, tempCanMsg->frame.idType, &tempEIDH, &tempEIDL, &tempSIDH, &tempSIDL);
|
|
|
|
|
|
|
|
|
|
TXB1EIDH = tempEIDH;
|
|
|
|
|
TXB1EIDL = tempEIDL;
|
|
|
|
|
TXB1SIDH = tempSIDH;
|
|
|
|
|
TXB1SIDL = tempSIDL;
|
|
|
|
|
TXB1DLC = tempCanMsg->frame.dlc | ((tempCanMsg->frame.rtr)<<6);
|
|
|
|
|
TXB1D0 = tempCanMsg->frame.data0;
|
|
|
|
|
TXB1D1 = tempCanMsg->frame.data1;
|
|
|
|
|
TXB1D2 = tempCanMsg->frame.data2;
|
|
|
|
|
TXB1D3 = tempCanMsg->frame.data3;
|
|
|
|
|
TXB1D4 = tempCanMsg->frame.data4;
|
|
|
|
|
TXB1D5 = tempCanMsg->frame.data5;
|
|
|
|
|
TXB1D6 = tempCanMsg->frame.data6;
|
|
|
|
|
TXB1D7 = tempCanMsg->frame.data7;
|
|
|
|
|
|
|
|
|
|
TXB1CONbits.TXREQ = 1; //Set the buffer to transmit
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
else if (TXB2CONbits.TXREQ != 1)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
convertCANid2Reg(tempCanMsg->frame.id, tempCanMsg->frame.idType, &tempEIDH, &tempEIDL, &tempSIDH, &tempSIDL);
|
|
|
|
|
|
|
|
|
|
TXB2EIDH = tempEIDH;
|
|
|
|
|
TXB2EIDL = tempEIDL;
|
|
|
|
|
TXB2SIDH = tempSIDH;
|
|
|
|
|
TXB2SIDL = tempSIDL;
|
|
|
|
|
TXB2DLC = tempCanMsg->frame.dlc | ((tempCanMsg->frame.rtr)<<6);
|
|
|
|
|
TXB2D0 = tempCanMsg->frame.data0;
|
|
|
|
|
TXB2D1 = tempCanMsg->frame.data1;
|
|
|
|
|
TXB2D2 = tempCanMsg->frame.data2;
|
|
|
|
|
TXB2D3 = tempCanMsg->frame.data3;
|
|
|
|
|
TXB2D4 = tempCanMsg->frame.data4;
|
|
|
|
|
TXB2D5 = tempCanMsg->frame.data5;
|
|
|
|
|
TXB2D6 = tempCanMsg->frame.data6;
|
|
|
|
|
TXB2D7 = tempCanMsg->frame.data7;
|
|
|
|
|
|
|
|
|
|
TXB2CONbits.TXREQ = 1; //Set the buffer to transmit
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (returnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Version A2 has a silicon errata
|
|
|
|
|
This code works for all revisions
|
|
|
|
|
*/
|
|
|
|
|
//Fix for Errata
|
|
|
|
|
#define dRXB0CON_FIFO_POINTER_VALUE 0
|
|
|
|
|
#define dRXB1CON_FIFO_POINTER_VALUE 1
|
|
|
|
|
#define dB0CON_FIFO_POINTER_VALUE 2
|
|
|
|
|
#define dB1CON_FIFO_POINTER_VALUE 3
|
|
|
|
|
#define dB2CON_FIFO_POINTER_VALUE 4
|
|
|
|
|
#define dB3CON_FIFO_POINTER_VALUE 5
|
|
|
|
|
#define dB4CON_FIFO_POINTER_VALUE 6
|
|
|
|
|
#define dB5CON_FIFO_POINTER_VALUE 7
|
|
|
|
|
|
|
|
|
|
uint8_t CAN_receive(uCAN_MSG *tempCanMsg) {
|
|
|
|
|
uint8_t returnValue = 0;
|
|
|
|
|
uint8_t tempECANCON;
|
|
|
|
|
uint8_t tempReg;
|
|
|
|
|
|
|
|
|
|
tempReg = (CANCON & 0x0F); //get the next RX buffer to read
|
|
|
|
|
tempECANCON = ECANCON; //Backup
|
|
|
|
|
ECANCON |= (tempReg + 0x10);
|
|
|
|
|
|
|
|
|
|
//Per Errata need to use this method to read out BxCON register
|
|
|
|
|
switch (tempReg)
|
|
|
|
|
{
|
|
|
|
|
case dRXB0CON_FIFO_POINTER_VALUE:
|
|
|
|
|
if (RXB0CONbits.RXFUL != 0) // Check RXB0
|
|
|
|
|
{
|
|
|
|
|
if ((RXB0SIDL & 0x08) == 0x08) //If Extended Message
|
|
|
|
|
{
|
|
|
|
|
//message is extended
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dEXTENDED_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2ExtendedCANid(RXB0EIDH, RXB0EIDL, RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//message is standard
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dSTANDARD_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2StandardCANid(RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tempCanMsg->frame.dlc = RXB0DLC & 0x0F;
|
|
|
|
|
tempCanMsg->frame.rtr = RXB0DLC >> 6;
|
|
|
|
|
tempCanMsg->frame.data0 = RXB0D0;
|
|
|
|
|
tempCanMsg->frame.data1 = RXB0D1;
|
|
|
|
|
tempCanMsg->frame.data2 = RXB0D2;
|
|
|
|
|
tempCanMsg->frame.data3 = RXB0D3;
|
|
|
|
|
tempCanMsg->frame.data4 = RXB0D4;
|
|
|
|
|
tempCanMsg->frame.data5 = RXB0D5;
|
|
|
|
|
tempCanMsg->frame.data6 = RXB0D6;
|
|
|
|
|
tempCanMsg->frame.data7 = RXB0D7;
|
|
|
|
|
RXB0CONbits.RXFUL = 0;
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case dRXB1CON_FIFO_POINTER_VALUE:
|
|
|
|
|
if (RXB1CONbits.RXFUL != 0) // Check RXB1
|
|
|
|
|
{
|
|
|
|
|
if ((RXB0SIDL & 0x08) == 0x08) //If Extended Message
|
|
|
|
|
{
|
|
|
|
|
//message is extended
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dEXTENDED_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2ExtendedCANid(RXB0EIDH, RXB0EIDL, RXB0SIDH, RXB0SIDL);
|
|
|
|
|
} else {
|
|
|
|
|
//message is standard
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dSTANDARD_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2StandardCANid(RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tempCanMsg->frame.dlc = RXB0DLC & 0x0F;
|
|
|
|
|
tempCanMsg->frame.rtr = RXB0DLC >> 6;
|
|
|
|
|
tempCanMsg->frame.data0 = RXB0D0;
|
|
|
|
|
tempCanMsg->frame.data1 = RXB0D1;
|
|
|
|
|
tempCanMsg->frame.data2 = RXB0D2;
|
|
|
|
|
tempCanMsg->frame.data3 = RXB0D3;
|
|
|
|
|
tempCanMsg->frame.data4 = RXB0D4;
|
|
|
|
|
tempCanMsg->frame.data5 = RXB0D5;
|
|
|
|
|
tempCanMsg->frame.data6 = RXB0D6;
|
|
|
|
|
tempCanMsg->frame.data7 = RXB0D7;
|
|
|
|
|
RXB1CONbits.RXFUL = 0;
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case dB0CON_FIFO_POINTER_VALUE:
|
|
|
|
|
if (B0CONbits.RXFUL != 0) //Check B0
|
|
|
|
|
{
|
|
|
|
|
if ((RXB0SIDL & 0x08) == 0x08) //If Extended Message
|
|
|
|
|
{
|
|
|
|
|
//message is extended
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dEXTENDED_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2ExtendedCANid(RXB0EIDH, RXB0EIDL, RXB0SIDH, RXB0SIDL);
|
|
|
|
|
} else {
|
|
|
|
|
//message is standard
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dSTANDARD_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2StandardCANid(RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tempCanMsg->frame.dlc = RXB0DLC & 0x0F;
|
|
|
|
|
tempCanMsg->frame.rtr = RXB0DLC >> 6;
|
|
|
|
|
tempCanMsg->frame.data0 = RXB0D0;
|
|
|
|
|
tempCanMsg->frame.data1 = RXB0D1;
|
|
|
|
|
tempCanMsg->frame.data2 = RXB0D2;
|
|
|
|
|
tempCanMsg->frame.data3 = RXB0D3;
|
|
|
|
|
tempCanMsg->frame.data4 = RXB0D4;
|
|
|
|
|
tempCanMsg->frame.data5 = RXB0D5;
|
|
|
|
|
tempCanMsg->frame.data6 = RXB0D6;
|
|
|
|
|
tempCanMsg->frame.data7 = RXB0D7;
|
|
|
|
|
B0CONbits.RXFUL = 0;
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case dB1CON_FIFO_POINTER_VALUE:
|
|
|
|
|
if (B1CONbits.RXFUL != 0) //CheckB1
|
|
|
|
|
{
|
|
|
|
|
if ((RXB0SIDL & 0x08) == 0x08) //If Extended Message
|
|
|
|
|
{
|
|
|
|
|
//message is extended
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dEXTENDED_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2ExtendedCANid(RXB0EIDH, RXB0EIDL, RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//message is standard
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dSTANDARD_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2StandardCANid(RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tempCanMsg->frame.dlc = RXB0DLC & 0x0F;
|
|
|
|
|
tempCanMsg->frame.rtr = RXB0DLC >> 6;
|
|
|
|
|
tempCanMsg->frame.data0 = RXB0D0;
|
|
|
|
|
tempCanMsg->frame.data1 = RXB0D1;
|
|
|
|
|
tempCanMsg->frame.data2 = RXB0D2;
|
|
|
|
|
tempCanMsg->frame.data3 = RXB0D3;
|
|
|
|
|
tempCanMsg->frame.data4 = RXB0D4;
|
|
|
|
|
tempCanMsg->frame.data5 = RXB0D5;
|
|
|
|
|
tempCanMsg->frame.data6 = RXB0D6;
|
|
|
|
|
tempCanMsg->frame.data7 = RXB0D7;
|
|
|
|
|
B1CONbits.RXFUL = 0;
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case dB2CON_FIFO_POINTER_VALUE:
|
|
|
|
|
if (B2CONbits.RXFUL != 0) //CheckB2
|
|
|
|
|
{
|
|
|
|
|
if ((RXB0SIDL & 0x08) == 0x08) //If Extended Message
|
|
|
|
|
{
|
|
|
|
|
//message is extended
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dEXTENDED_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2ExtendedCANid(RXB0EIDH, RXB0EIDL, RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//message is standard
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dSTANDARD_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2StandardCANid(RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tempCanMsg->frame.dlc = RXB0DLC & 0x0F;
|
|
|
|
|
tempCanMsg->frame.rtr = RXB0DLC >> 6;
|
|
|
|
|
tempCanMsg->frame.data0 = RXB0D0;
|
|
|
|
|
tempCanMsg->frame.data1 = RXB0D1;
|
|
|
|
|
tempCanMsg->frame.data2 = RXB0D2;
|
|
|
|
|
tempCanMsg->frame.data3 = RXB0D3;
|
|
|
|
|
tempCanMsg->frame.data4 = RXB0D4;
|
|
|
|
|
tempCanMsg->frame.data5 = RXB0D5;
|
|
|
|
|
tempCanMsg->frame.data6 = RXB0D6;
|
|
|
|
|
tempCanMsg->frame.data7 = RXB0D7;
|
|
|
|
|
B2CONbits.RXFUL = 0;
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case dB3CON_FIFO_POINTER_VALUE:
|
|
|
|
|
if (B3CONbits.RXFUL != 0) //CheckB3
|
|
|
|
|
{
|
|
|
|
|
if ((RXB0SIDL & 0x08) == 0x08) //If Extended Message
|
|
|
|
|
{
|
|
|
|
|
//message is extended
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dEXTENDED_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2ExtendedCANid(RXB0EIDH, RXB0EIDL, RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//message is standard
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dSTANDARD_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2StandardCANid(RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tempCanMsg->frame.dlc = RXB0DLC & 0x0F;
|
|
|
|
|
tempCanMsg->frame.rtr = RXB0DLC >> 6;
|
|
|
|
|
tempCanMsg->frame.data0 = RXB0D0;
|
|
|
|
|
tempCanMsg->frame.data1 = RXB0D1;
|
|
|
|
|
tempCanMsg->frame.data2 = RXB0D2;
|
|
|
|
|
tempCanMsg->frame.data3 = RXB0D3;
|
|
|
|
|
tempCanMsg->frame.data4 = RXB0D4;
|
|
|
|
|
tempCanMsg->frame.data5 = RXB0D5;
|
|
|
|
|
tempCanMsg->frame.data6 = RXB0D6;
|
|
|
|
|
tempCanMsg->frame.data7 = RXB0D7;
|
|
|
|
|
B3CONbits.RXFUL = 0;
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case dB4CON_FIFO_POINTER_VALUE:
|
|
|
|
|
if (B4CONbits.RXFUL != 0) //CheckB4
|
|
|
|
|
{
|
|
|
|
|
if ((RXB0SIDL & 0x08) == 0x08) //If Extended Message
|
|
|
|
|
{
|
|
|
|
|
//message is extended
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dEXTENDED_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2ExtendedCANid(RXB0EIDH, RXB0EIDL, RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//message is standard
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dSTANDARD_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2StandardCANid(RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tempCanMsg->frame.dlc = RXB0DLC & 0x0F;
|
|
|
|
|
tempCanMsg->frame.rtr = RXB0DLC >> 6;
|
|
|
|
|
tempCanMsg->frame.data0 = RXB0D0;
|
|
|
|
|
tempCanMsg->frame.data1 = RXB0D1;
|
|
|
|
|
tempCanMsg->frame.data2 = RXB0D2;
|
|
|
|
|
tempCanMsg->frame.data3 = RXB0D3;
|
|
|
|
|
tempCanMsg->frame.data4 = RXB0D4;
|
|
|
|
|
tempCanMsg->frame.data5 = RXB0D5;
|
|
|
|
|
tempCanMsg->frame.data6 = RXB0D6;
|
|
|
|
|
tempCanMsg->frame.data7 = RXB0D7;
|
|
|
|
|
B4CONbits.RXFUL = 0;
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case dB5CON_FIFO_POINTER_VALUE:
|
|
|
|
|
if (B5CONbits.RXFUL != 0) //CheckB5
|
|
|
|
|
{
|
|
|
|
|
if ((RXB0SIDL & 0x08) == 0x08) //If Extended Message
|
|
|
|
|
{
|
|
|
|
|
//message is extended
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dEXTENDED_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2ExtendedCANid(RXB0EIDH, RXB0EIDL, RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//message is standard
|
|
|
|
|
tempCanMsg->frame.idType = (uint8_t) dSTANDARD_CAN_MSG_ID_2_0B;
|
|
|
|
|
tempCanMsg->frame.id = convertReg2StandardCANid(RXB0SIDH, RXB0SIDL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tempCanMsg->frame.dlc = RXB0DLC & 0x0F;
|
|
|
|
|
tempCanMsg->frame.rtr = RXB0DLC >> 6;
|
|
|
|
|
tempCanMsg->frame.data0 = RXB0D0;
|
|
|
|
|
tempCanMsg->frame.data1 = RXB0D1;
|
|
|
|
|
tempCanMsg->frame.data2 = RXB0D2;
|
|
|
|
|
tempCanMsg->frame.data3 = RXB0D3;
|
|
|
|
|
tempCanMsg->frame.data4 = RXB0D4;
|
|
|
|
|
tempCanMsg->frame.data5 = RXB0D5;
|
|
|
|
|
tempCanMsg->frame.data6 = RXB0D6;
|
|
|
|
|
tempCanMsg->frame.data7 = RXB0D7;
|
|
|
|
|
B5CONbits.RXFUL = 0;
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ECANCON = tempECANCON;
|
|
|
|
|
return (returnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t CAN_messagesInBuffer(void) {
|
|
|
|
|
uint8_t messageCount = 0;
|
|
|
|
|
if (RXB0CONbits.RXFUL != 0) //CheckRXB0
|
|
|
|
|
{
|
|
|
|
|
messageCount++;
|
|
|
|
|
}
|
|
|
|
|
if (RXB1CONbits.RXFUL != 0) //CheckRXB1
|
|
|
|
|
{
|
|
|
|
|
messageCount++;
|
|
|
|
|
}
|
|
|
|
|
if (B0CONbits.RXFUL_TXBIF != 0) //CheckB0
|
|
|
|
|
{
|
|
|
|
|
messageCount++;
|
|
|
|
|
}
|
|
|
|
|
if (B1CONbits.RXFUL_TXBIF != 0) //CheckB1
|
|
|
|
|
{
|
|
|
|
|
messageCount++;
|
|
|
|
|
}
|
|
|
|
|
if (B2CONbits.RXFUL_TXBIF != 0) //CheckB2
|
|
|
|
|
{
|
|
|
|
|
messageCount++;
|
|
|
|
|
}
|
|
|
|
|
if (B3CONbits.RXFUL_TXBIF != 0) //CheckB3
|
|
|
|
|
{
|
|
|
|
|
messageCount++;
|
|
|
|
|
}
|
|
|
|
|
if (B4CONbits.RXFUL_TXBIF != 0) //CheckB4
|
|
|
|
|
{
|
|
|
|
|
messageCount++;
|
|
|
|
|
}
|
|
|
|
|
if (B5CONbits.RXFUL_TXBIF != 0) //CheckB5
|
|
|
|
|
{
|
|
|
|
|
messageCount++;
|
|
|
|
|
}
|
|
|
|
|
return (messageCount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t CAN_isBusOff(void)
|
|
|
|
|
{
|
|
|
|
|
uint8_t returnValue = 0;
|
|
|
|
|
|
|
|
|
|
//COMSTAT bit 5 TXBO: Transmitter Bus-Off bit
|
|
|
|
|
//1 = Transmit error counter > 255
|
|
|
|
|
//0 = Transmit error counter less then or equal to 255
|
|
|
|
|
|
|
|
|
|
if (COMSTATbits.TXBO == 1) {
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
return (returnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t CAN_isRXErrorPassive(void)
|
|
|
|
|
{
|
|
|
|
|
uint8_t returnValue = 0;
|
|
|
|
|
|
|
|
|
|
//COMSTAT bit 3 RXBP: Receiver Bus Passive bit
|
|
|
|
|
//1 = Receive error counter > 127
|
|
|
|
|
//0 = Receive error counter less then or equal to 127
|
|
|
|
|
|
|
|
|
|
if (COMSTATbits.RXBP == 1) {
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
return (returnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t CAN_isTXErrorPassive(void)
|
|
|
|
|
{
|
|
|
|
|
uint8_t returnValue = 0;
|
|
|
|
|
|
|
|
|
|
//COMSTAT bit 4 TXBP: Transmitter Bus Passive bit
|
|
|
|
|
//1 = Transmit error counter > 127
|
|
|
|
|
//0 = Transmit error counter less then or equal to 127
|
|
|
|
|
|
|
|
|
|
if (COMSTATbits.TXBP == 1)
|
|
|
|
|
{
|
|
|
|
|
returnValue = 1;
|
|
|
|
|
}
|
|
|
|
|
return (returnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Internal functions
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
static uint32_t convertReg2ExtendedCANid(uint8_t tempRXBn_EIDH, uint8_t tempRXBn_EIDL, uint8_t tempRXBn_SIDH, uint8_t tempRXBn_SIDL) {
|
|
|
|
|
uint32_t returnValue = 0;
|
|
|
|
|
uint32_t ConvertedID = 0;
|
|
|
|
|
uint8_t CAN_standardLo_ID_lo2bits;
|
|
|
|
|
uint8_t CAN_standardLo_ID_hi3bits;
|
|
|
|
|
|
|
|
|
|
CAN_standardLo_ID_lo2bits = (uint8_t)(tempRXBn_SIDL & 0x03);
|
|
|
|
|
CAN_standardLo_ID_hi3bits = (uint8_t)(tempRXBn_SIDL >> 5);
|
|
|
|
|
ConvertedID = (uint32_t)(tempRXBn_SIDH << 3);
|
|
|
|
|
ConvertedID = ConvertedID + CAN_standardLo_ID_hi3bits;
|
|
|
|
|
ConvertedID = (ConvertedID << 2);
|
|
|
|
|
ConvertedID = ConvertedID + CAN_standardLo_ID_lo2bits;
|
|
|
|
|
ConvertedID = (ConvertedID << 8);
|
|
|
|
|
ConvertedID = ConvertedID + tempRXBn_EIDH;
|
|
|
|
|
ConvertedID = (ConvertedID << 8);
|
|
|
|
|
ConvertedID = ConvertedID + tempRXBn_EIDL;
|
|
|
|
|
returnValue = ConvertedID;
|
|
|
|
|
return (returnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint32_t convertReg2StandardCANid(uint8_t tempRXBn_SIDH, uint8_t tempRXBn_SIDL) {
|
|
|
|
|
uint32_t returnValue = 0;
|
|
|
|
|
uint32_t ConvertedID;
|
|
|
|
|
//if standard message (11 bits)
|
|
|
|
|
//EIDH = 0 + EIDL = 0 + SIDH + upper three bits SIDL (3rd bit needs to be clear)
|
|
|
|
|
//1111 1111 111
|
|
|
|
|
ConvertedID = (uint32_t)(tempRXBn_SIDH << 3);
|
|
|
|
|
ConvertedID = ConvertedID + (uint32_t)(tempRXBn_SIDL >> 5);
|
|
|
|
|
returnValue = ConvertedID;
|
|
|
|
|
return (returnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void convertCANid2Reg(uint32_t tempPassedInID, uint8_t canIdType, uint8_t *passedInEIDH, uint8_t *passedInEIDL, uint8_t *passedInSIDH, uint8_t *passedInSIDL) {
|
|
|
|
|
uint8_t wipSIDL = 0;
|
|
|
|
|
|
|
|
|
|
if (canIdType == dEXTENDED_CAN_MSG_ID_2_0B)
|
|
|
|
|
{
|
|
|
|
|
//EIDL
|
|
|
|
|
*passedInEIDL = 0xFF & tempPassedInID; //CAN_extendedLo_ID_TX1 = &HFF And CAN_UserEnter_ID_TX1
|
|
|
|
|
tempPassedInID = tempPassedInID >> 8; //CAN_UserEnter_ID_TX1 = CAN_UserEnter_ID_TX1 >> 8
|
|
|
|
|
|
|
|
|
|
//EIDH
|
|
|
|
|
*passedInEIDH = 0xFF & tempPassedInID; //CAN_extendedHi_ID_TX1 = &HFF And CAN_UserEnter_ID_TX1
|
|
|
|
|
tempPassedInID = tempPassedInID >> 8; //CAN_UserEnter_ID_TX1 = CAN_UserEnter_ID_TX1 >> 8
|
|
|
|
|
|
|
|
|
|
//SIDL
|
|
|
|
|
//push back 5 and or it
|
|
|
|
|
wipSIDL = 0x03 & tempPassedInID;
|
|
|
|
|
tempPassedInID = tempPassedInID << 3; //CAN_UserEnter_ID_TX1 = CAN_UserEnter_ID_TX1 << 3
|
|
|
|
|
wipSIDL = (0xE0 & tempPassedInID) + wipSIDL;
|
|
|
|
|
wipSIDL = (uint8_t)(wipSIDL + 0x08); // TEMP_CAN_standardLo_ID_TX1 = TEMP_CAN_standardLo_ID_TX1 + &H8
|
|
|
|
|
*passedInSIDL = (uint8_t)(0xEB & wipSIDL); //CAN_standardLo_ID_TX1 = &HEB And TEMP_CAN_standardLo_ID_TX1
|
|
|
|
|
|
|
|
|
|
//SIDH
|
|
|
|
|
tempPassedInID = tempPassedInID >> 8;
|
|
|
|
|
*passedInSIDH = 0xFF & tempPassedInID;
|
|
|
|
|
}
|
|
|
|
|
else //(canIdType == dSTANDARD_CAN_MSG_ID_2_0B)
|
|
|
|
|
{
|
|
|
|
|
*passedInEIDH = 0;
|
|
|
|
|
*passedInEIDL = 0;
|
|
|
|
|
tempPassedInID = tempPassedInID << 5;
|
|
|
|
|
*passedInSIDL = 0xFF & tempPassedInID;
|
|
|
|
|
tempPassedInID = tempPassedInID >> 8;
|
|
|
|
|
*passedInSIDH = 0xFF & tempPassedInID;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_SetRXBnInterruptHandler(void (*handler)(void))
|
|
|
|
|
{
|
|
|
|
|
RXBnInterruptHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_RXBnI_ISR(void)
|
|
|
|
|
{
|
|
|
|
|
RXBnInterruptHandler();
|
|
|
|
|
PIR5bits.RXBnIF = 0; // The ECAN hardware overrides the setting of this bit (to '1') when any receive buffer is not empty.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_SetRXBnOverflowHandler(void (*handler)(void))
|
|
|
|
|
{
|
|
|
|
|
RXBnOverflowHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_SetBusOffHandler(void (*handler)(void))
|
|
|
|
|
{
|
|
|
|
|
BusOffHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_SetTXPassiveHandler(void (*handler)(void))
|
|
|
|
|
{
|
|
|
|
|
TXPassiveHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_SetRXPassiveHandler(void (*handler)(void))
|
|
|
|
|
{
|
|
|
|
|
RXPassiveHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_SetTXWarningHandler(void (*handler)(void))
|
|
|
|
|
{
|
|
|
|
|
TXWarningHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_SetRXWarningHandler(void (*handler)(void))
|
|
|
|
|
{
|
|
|
|
|
RXWarningHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ECAN_ERRI_ISR(void)
|
|
|
|
|
{
|
|
|
|
|
if (COMSTATbits.RXB1OVFL)
|
|
|
|
|
{
|
|
|
|
|
RXBnOverflowHandler();
|
|
|
|
|
COMSTATbits.RXB1OVFL = 0; // In mode 2, this clears RXBnOVFL
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (COMSTATbits.TXBO)
|
|
|
|
|
{
|
|
|
|
|
BusOffHandler();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (COMSTATbits.TXBP)
|
|
|
|
|
{
|
|
|
|
|
TXPassiveHandler();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (COMSTATbits.RXBP)
|
|
|
|
|
{
|
|
|
|
|
RXPassiveHandler();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (COMSTATbits.TXWARN)
|
|
|
|
|
{
|
|
|
|
|
TXWarningHandler();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (COMSTATbits.RXWARN)
|
|
|
|
|
{
|
|
|
|
|
RXWarningHandler();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PIR5bits.ERRIF = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|