Nico Bollen / LIN

Dependents:   MBED_LIN_RGB_Master_Example

LinMaster.cpp

Committer:
bollenn
Date:
2015-05-26
Revision:
3:3656b0de0e43
Parent:
2:6d4c7f841a5d
Child:
4:41b153e9a39c

File content as of revision 3:3656b0de0e43:

/* 
 * Master device LIN communication library for mbed
 *
 * Copyright (C) 2014 TASS Belgium NV
 * 
 * Released under GPL v2
 *
 * Other licensing models might apply at the sole discretion of the copyright holders.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
 
#include "LinMaster.h"

const uint8_t breakPeriodMessage = 40;                      /* number of timer overflows in the break field during normal LIN messages */
const uint8_t breakPeriodAcfg = 74;                         /* number of timer overflows in the break field during autoconfig messages */
    
LinMaster::LinMaster(PinName InPin, PinName OutPin)
{
    this->DriverState  = INIT;
    this->LastError    = NoError;
    this->MyInPin      = InPin;
    this->MyOutPin     = OutPin;
    (void)this->baudrate(9600);
}

LinMaster::~LinMaster()
{
    this->MyTicker.detach(); 
    this->MyTimer.stop();
}

bool LinMaster::init(void)
{
    DigitalInOut LinOutPin(this->MyOutPin);
    LinOutPin.output();
    LinOutPin.write(1);
    DigitalInOut LinInPin(this->MyInPin);
    LinInPin.input();
    
    this->DriverState = IDLE;
    
    return ( true );
}

bool LinMaster::baudrate(uint16_t uBaud)
{
    bool blReturn = false;
    
    if ((uBaud > 0) && (uBaud <= 20000))
    {
        this->u16HalfBitPeriod = 1000000/(2*uBaud);
        blReturn = true;
    }
    
    return ( blReturn );
}

uint16_t LinMaster::baudrate(void)
{
    return ( 1000000 / (2 * this->u16HalfBitPeriod) );
}

bool LinMaster::tx_frame(Frame_t * ptrFrame)
{
    bool blReturn = false;
    DigitalInOut LinInPin(this->MyInPin);
    LinInPin.input();
    
    if ( (this->DriverState == IDLE) && (LinInPin.read() == 1) )
    {
        /* Clear and initialize all registers */
        
        /* Disable half bit interrupt */
        this->MyTicker.detach();
        
        InterruptIn IntPin(this->MyInPin);
        IntPin.fall(this, &LinMaster::PinEventHndl);
        IntPin.disable_irq();
        
        DigitalInOut LinPin(this->MyOutPin);        
        LinPin.output();
        LinPin.write(1);
        
        this->DriverState = TRANSMIT;                       /* State of the LIN bus is transceiving a frame */
        this->LastError = NoError;
        this->FrameStatus = FStart;
        this->ByteStatus = BStart;
        this->RXbufIndex = 0;                               /* Reset index in the receiver buffer */
        this->TXbufIndex = 0;                               /* Reset index in the transmit buffer */
    
        /* Set the correct brake-length */
        if (ptrFrame->Brake == AutoConfig)
        {
            this->breakLength = breakPeriodAcfg;
        }
        else
        {
            this->breakLength = breakPeriodMessage;
        }
    
        this->FrameLength = 1 + 1 + ptrFrame->DataLen + 1;  /* Frame length = Sync + Frame ID + Data Len + CRC */
        this->linMessageType = ptrFrame->FrameType;
    
        /* Create the correct frame buffer */
        this->TXbuf[0] = 0x55;                              /* Sync field */
        this->TXbuf[1] = this->parity(ptrFrame->FrameID);   /* Frame ID */
    
        if (this->linMessageType == M2S)
        {
            uint16_t u16Crc;
            uint8_t i;
            
            if (ptrFrame->CrcType == Enhanced)
            {
                u16Crc = TXbuf[1];
            }
            else
            {
                u16Crc = 0;
            }
    
            for (i = 0; i < ptrFrame->DataLen; i++)
            {
                this->TXbuf[i + 2] = ptrFrame->Data[i];     /* Data */
                u16Crc += ptrFrame->Data[i];
                if (u16Crc >= 256)
                {
                    u16Crc -= 255;
                }
            }
            this->TXbuf[ptrFrame->DataLen + 2] = (uint8_t)(~u16Crc);
        }
        else
        {
            /* S2M message */
            this->RXtimeout = ((ptrFrame->DataLen + 1) * 14) * 2;   /* Calculate RX timeout in half bit-times */
            this->RXtimeoutSubCTR = 0;
        }
    
        /* Configure and start the half bit timer */
        this->MyTicker.attach_us(this, &LinMaster::TickEventHndl, this->u16HalfBitPeriod);
        
        blReturn = true;
    }
    
    return ( blReturn );
}

bool LinMaster::rx_frame(Frame_t *ptrFrame)
{
    uint16_t u16Crc;
    uint8_t i;

    if (this->DriverState != IDLE)
    {
        return (false);
    }

    /* Copy data and check RX frame CRC */
    if (ptrFrame->CrcType == Enhanced)
    {
        u16Crc = RXbuf[1];
    }
    else
    {
        u16Crc = 0;
    }

    for (i = 0; i < ptrFrame->DataLen; i++)
    {
        ptrFrame->Data[i] = RXbuf[1 + 1 + i];
        u16Crc += RXbuf[1 + 1 + i];
        if (u16Crc >= 256)
        {
            u16Crc -= 255;
        }
    }
    
    if (this->RXbuf[ptrFrame->DataLen + 2] == (uint8_t)(~u16Crc))
    {
        return (true);
    }
    else
    {
        return (false);
    }
}

void LinMaster::TickEventHndl(void)
{
    DigitalInOut LinOutPin(this->MyOutPin);  
    LinOutPin.output();
    
    if (this->FrameStatus < Break_OK)
    {
        /* Do break field transmission */
        if (this->breakLength > 2)
        {
            /* Dominant Level */
            LinOutPin.write(0); 
        }
        else
        {
            /* Recessive Level */
            LinOutPin.write(1); 
        }

        if (this->breakLength > 0)
        {
            this->breakLength--;
        }
        else
        {
            this->FrameStatus = Break_OK;
        }
    }
    else
    {
        /* Break field was transmitted */
        if (this->FrameLength == 0)
        {
            /* No data needs to be transmitted */
            
            /* Disable half bit interrupt */
            this->MyTicker.detach();
            
            /* Disable LIN bus level interrupt */
            InterruptIn IntPin(this->MyInPin);
            IntPin.disable_irq();
        }
        else
        {
            DigitalInOut LinInPin(this->MyInPin);
            LinInPin.input();
            
            /* Data needs to be transmitted or received */
            if ( (this->linMessageType == S2M) &&
                 (this->FrameStatus >= ID_OK))
            {
                if (this->ByteStatus > BStart)
                {
                    /* Not waiting for start bit */
                    this->ByteStatus = static_cast<ByteStatus_t>(static_cast<int>(this->ByteStatus)+1);
                }

                if (this->RXtimeout > 0)
                {
                    this->RXtimeout--;
                }
                else
                {
                    this->DriverState = IDLE;
                    this->LastError = NoSlaveResp;
                    
                    /* Disable half bit interrupt */
                    this->MyTicker.detach();
                    
                    /* Disable LIN bus level interrupt */
                    InterruptIn IntPin(this->MyInPin);
                    IntPin.disable_irq();
                }

                /* S2M message data receiving */
                switch (this->ByteStatus)
                {
                case StartbitSample:
                    if (LinInPin.read() == 0)
                    {
                        /* OK */
                    }
                    else
                    {
                        /* TODO error */
                    }

                    break;

                case Databit0Sample:
                case Databit1Sample:
                case Databit2Sample:
                case Databit3Sample:
                case Databit4Sample:
                case Databit5Sample:
                case Databit6Sample:
                case Databit7Sample:
                    /* Mid of single bit time, do sampling */
                    this->RXbuf[RXbufIndex] >>= 1;
                    this->RXbuf[RXbufIndex] |= (LinInPin.read() << 7);
                    break;

                case StopbitSample:
                    /* End of stop bit, stop Timer IRQ */
                    this->RXbufIndex++;
                    this->FrameStatus = static_cast<FrameStatus_t>(static_cast<int>(this->FrameStatus)+1);
                    this->ByteStatus = BStart;

                    /* Check the current bus level */
                    if (LinInPin.read() == 0)
                    {
                        this->LastError = FramingErr;       /* stop bit not valid => framing error */
                    }

                    if ((this->RXbufIndex >= this->FrameLength) ||
                        (this->LastError != NoError))
                    {
                        /* All requested data bytes are received or an error occurred */
                        this->DriverState = IDLE;
                        
                        /* Disable half bit interrupt */
                        this->MyTicker.detach();
                        
                        /* Disable LIN bus level interrupt */
                        InterruptIn IntPin(this->MyInPin);
                        IntPin.disable_irq();
                    }
                    else
                    {
                        /* Wait for a new data byte */
                        
                        /* Disable LIN bus level interrupt */
                        this->MyTimer.start();
                        InterruptIn IntPin(this->MyInPin);
                        IntPin.enable_irq();
                    }

                    break;

                case Databit0Edge:
                case Databit1Edge:
                case Databit2Edge:
                case Databit3Edge:
                case Databit4Edge:
                case Databit5Edge:
                case Databit6Edge:
                case Databit7Edge:
                case StopbitEdge:
                case BDone:
                case BStart:
                default:
                    /* Do nothing */
                    break;
                }
            }
            else
            {
                /* Transmission of Sync + Frame ID and M2S frame data */
                this->ByteStatus = static_cast<ByteStatus_t>(static_cast<int>(this->ByteStatus)+1);

                switch (this->ByteStatus)
                {
                case StartbitEdge:
                    /* Start bit : start */
                    LinOutPin.write(0); 
                    break;

                case StartbitSample:
                    /* Start bit : mid */
                    break;

                case Databit0Edge:
                case Databit1Edge:
                case Databit2Edge:
                case Databit3Edge:
                case Databit4Edge:
                case Databit5Edge:
                case Databit6Edge:
                case Databit7Edge:
                    /* Start of new bit time */
                    if (this->TXbuf[this->TXbufIndex] & 0x01)
                    {
                        /* Recessive Level */
                        LinOutPin.write(1); 
                    }
                    else
                    {
                        /* Dominant Level */
                        LinOutPin.write(0); 
                    }

                    this->TXbuf[this->TXbufIndex] >>= 1;
                    break;

                case Databit0Sample:
                case Databit1Sample:
                case Databit2Sample:
                case Databit3Sample:
                case Databit4Sample:
                case Databit5Sample:
                case Databit6Sample:
                case Databit7Sample:
                    /* Odd overflow, mid of bit time ==> sample the bus for RX */
                    this->RXbuf[this->RXbufIndex] >>= 1;
                    this->RXbuf[this->RXbufIndex] |= (LinInPin.read() << 7);
                    break;

                case StopbitEdge:
                    /* Stop bit : start */
                    LinOutPin.write(1); 
                    break;

                case StopbitSample:
                    /* Stop bit : mid / level check */
                    if (LinInPin.read() == 0)
                    {
                        /* Stop bit not valid => framing error */
                        this->LastError = FramingErr;
                        this->DriverState = IDLE;
                        
                        /* Disable half bit interrupt */
                        this->MyTicker.detach();
                        
                        /* Disable LIN bus level interrupt */
                        InterruptIn IntPin(this->MyInPin);
                        IntPin.disable_irq();
                    }

                    break;

                case BDone:
                    /* Stop bit : finished */
                    this->ByteStatus = BStart;
                    this->FrameStatus = static_cast<FrameStatus_t>(static_cast<int>(this->FrameStatus)+1);
                    this->TXbufIndex++;
                    this->RXbufIndex++;

                    if (this->linMessageType == S2M)
                    {
                        /* S2M frame */
                        if (this->FrameStatus == ID_OK)
                        {
                            /* Stop bit of header is sent, now start receiving data bytes */
                            this->MyTimer.start();
                            InterruptIn IntPin(this->MyInPin);
                            IntPin.enable_irq();
                        }
                    }
                    else if (this->TXbufIndex >= this->FrameLength)
                    {
                        /* M2S frame, Last byte is sent */
                        this->LastError = NoError;
                        this->DriverState = IDLE;
                        
                        /* Disable half bit interrupt */
                        this->MyTicker.detach();
                        
                        /* Disable LIN bus level interrupt */
                        InterruptIn IntPin(this->MyInPin);
                        IntPin.disable_irq();
                    }

                    break;

                default:
                    break;
                }
            }
        }
    }
}

void LinMaster::PinEventHndl(void)
{
    switch (this->DriverState)
    {
    case TRANSMIT:
    case RECEIVE:
    {
        this->ByteStatus = StartbitEdge;                    /* Set status of the received byte */
        this->RXtimeoutSubCTR += this->MyTimer.read_us();
        this->MyTimer.stop();
        
        /* Reset ticker */
        this->MyTicker.detach();
        this->MyTicker.attach_us(this, &LinMaster::TickEventHndl, this->u16HalfBitPeriod);
        
        /* Disable LIN bus level interrupt */
        InterruptIn IntPin(this->MyInPin);
        IntPin.disable_irq();

        if (this->RXtimeoutSubCTR > this->u16HalfBitPeriod)
        {
            this->RXtimeoutSubCTR -= this->u16HalfBitPeriod;
            if (this->RXtimeout > 0)
            {
                this->RXtimeout--;
            }
        }

        break;
    }
    
    case IDLE:
    case DOMINANT:
    case TXWAKEUP:
        break;

    case RXWAKEUP:
    default:
        this->DriverState = RXWAKEUP;                       /* It's a wake up pulse */
        break;
    }
}

/** Calculate the parity bits
 *
 * @param u8BYTE original byte
 * @return BYTE including parity bits
 */
uint8_t LinMaster::parity(uint8_t u8BYTE)
{
    uint8_t P0 = 0;
    uint8_t P1 = 0;

    /* P0 = ID0 + ID1 + ID2 + ID4
     * P1 = ~(ID1 + ID3 + ID4 + ID5)
     */
    if ((u8BYTE & (1 << 0)) != 0)
    {
        P0 = ~P0;
    }

    if ((u8BYTE & (1 << 1)) != 0)
    {
        P0 = ~P0;
    }

    if ((u8BYTE & (1 << 2)) != 0)
    {
        P0 = ~P0;
    }

    if ((u8BYTE & (1 << 4)) != 0)
    {
        P0 = ~P0;
    }

    if ((u8BYTE & (1 << 1)) != 0)
    {
        P1 = ~P1;
    }

    if ((u8BYTE & (1 << 3)) != 0)
    {
        P1 = ~P1;
    }

    if ((u8BYTE & (1 << 4)) != 0)
    {
        P1 = ~P1;
    }

    if ((u8BYTE & (1 << 5)) != 0)
    {
        P1 = ~P1;
    }

    P1 = ~P1;

    u8BYTE &= 0x3f;                                         /* Delete MSB's */

    if (P0 != 0)
    {
        u8BYTE |= (1 << 6);
    }

    if (P1 != 0)
    {
        u8BYTE |= (1 << 7);
    }

    return (u8BYTE);
}

/* EOF */