Toyota Auxiliary Audio Input Enabler

Thanks to SigmaObjects for generously hosting this personal project page.

Introduction

In February 2007 my brother asked me if I would be able to substitute the defective CD player of his Toyota truck by an audio input for his iPOD. Inspired by the good old years when car radios were mostly electromechanical devices I immediately answered yes without knowing the magnitude of the work involved to make it happen. It turns out his unit was very compact and I quickly discovered the CD player didn't have the obvious connections I was expecting to find. I also noticed the unit seemed to have too many connections in the back so I started looking on the Internet hopeful to find pinout information.

I found many interesting sites but one stood out more than any other: http://www.softservice.com.pl/corolla/avc/simpleaux.php  Thanks to Marcin Slonicki. My project wouldn't have been possible without him.

In a nutshell Toyota car radio are equipped with an external differential audio input. Several years ago the input was used for an external cassette player. In recent years it's been used for various features such as multi-CD changers, DVD player, etc... Hooking up an audio device to the auxiliary input is very simple despite the fact it is differential. The challenge is to find a way to enable that damn audio input because the pinout information reveals no hardware way to activate it.

In the early '90 Toyota adopted the IEBus (Inter Equipment Bus) technology for their car radio. More about IEBus This is literally a computer network designed to work in the automotive environment. Typically devices such as the engine control module (ECM), speedometer, ABS brakes, radio head unit (HU), power amplifier, etc share the IEBus. So guess what, the auxiliary audio input has to be enabled from a software command through the bus! This is where Marcin's WEB site came in very handy.

It was almost too good to be true. Marcin designed a device acting as an external CD changer from a software stand point. When the HU queries the bus for devices his module would answer and subsequently register as a valid device known to the HU. Pressing de CD button on the HU would alternatively select the internal CD player or the virtual external unit therefore enabling the auxiliary audio input. All the hardware and firmware information is available on the WEB site and the design uses an Atmel ATmega8 microcontroller which I had in my lab. I was quite enthusiastic thinking it would take me a week-end to complete the project.


The journey begins

As I was getting more familiar with Marcin's project three details caught my attention:

  1. The ATmega8 is clocked at 14.7456 MHz with an external crystal
  2. A PCA82C250 CAN bus driver is used to drive the IEBus
  3. A LM239 comparator is required on top of the PCA82C250 to read from the IEBus

I didn't have any of those components in my lab that day and didn't want to wait before moving on so I carefully studied the source code and the hardware design to find an alternative. This project like many others deals with critical timing. The 14.7456 MHz clock frequency would make the firmware work but I was convinced I could make it work with a standard frequency like 16 MHz. So I simply rewrote the whole firmware inspired by Marcin's code but using hardware based timing instead of trimmed delay loops. My firmware operates from the internal RC oscillator @ 8 MHz and it was successfully proven to work between -50°C and +50°C.

Half of my problem was solved but the IEBus interface circuit ought to be fixed. My HU has a NEC uPD6708 IEBus driver and this chip is not easy to find. I opted to solve the problem with a combination of MCU features and a few resistors and diodes. Two antagonist output pins perform the bus driver whereas the ATmega internal analog comparator handles the receive side.

PDF of Schematic

 

Even though most of the space inside the HU was occupied there was a cutout on the main PCB to let air in for cooling. I quickly figured out my board would occupy some of that empty space. The advantage is that everything resides inside the HU. No need for a 5 volt regulator since the power supply comes directly from the HU PCB. Surface mount assembly ensures the low profile required to fit in there. I switched to SMT several years ago mainly because it makes my life easier; most modern chips like MCUs are not available in through hole so even a through hole project requires some SMT. In addition to that I save a lot of time not having to drill and I can create much denser board because the pads and the components are much smaller. By the way my PCBs are home made and assembled.

PDF of PCB Composite View

PDF of PCB Manufacturing View

 

Fully Assembled PCB

All wires come from underneath the PCB through regular holes to maximize the robustness of the assembly. The header pins are also passing through the PCB. The three 90 degree header pins on the right side of the board are for the external audio wire. I chose to use a header to facilitate repair when the external audio wire gets damaged.

Final Assembly in the Head Unit

In the above picture the CD unit and the front panel of the HU have been removed to show the auxiliary audio input enabler module in its final assembly state.


The Firmware Project

This project was born on a breadboard like any other of my project. The hardware and firmware were developed iteratively. The USART is a key feature in my project. I was monitoring all the IEBus activity on my PC. Once I knew my device could listen and talk to the HU I had to face an embarrassing problem: the software protocol described on Marcin's WEB site did not work on my HU. None of the numerous messages would make the HU react. After several nights of head scratching I concluded my head unit was probably too old to speak the language Marcin describes. As a matter of fact my car radio was stamped with a 1997 sticker. I came close of giving up but one night I wrote a program to hammer the HU with all possible five byte messages similar to the register command. In less than one hour I found the sequence to enable the auxiliary audio input as well as many other interesting features.

My source code is developed with WinAVR. The implementation shown below works on older Toyota models. It traps the message issued by the HU when pressing on CD Eject button to enable the auxiliary audio input. A state machine determines whether one wants to eject a real CD or activate the audio input. I also wrote a version of the firmware that should work with recent models but I never had a chance to try it out on a real HU. The source code is available to download here. I would like to hear from anybody attempting to use the full implementation so I could update this site.

Read through the code to find lots of explanations on IEBus and the communication protocol.

 

/*--------------------------------------------------------------------------------------------------

  Name         :  ToyotaAuxEnabler.c

  Description  :  This program enables the AUX audio input on old Toyota radios with CD. Pressing
                  CD Eject button while no CD is loaded will toggle the AUX input.

  MCU          :  ATmega8 @ 8 MHz.
 
  Author       :  2007-01-27 - Louis Frigon

  Copyright    :  (c) 2007 SigmaObjects

  History      :  2007-01-27 - v0.1 Prototyping draft inspired from Marcin Slonicki's code.
                  2007-02-24 - v1.0 Production release.

--------------------------------------------------------------------------------------------------*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <stdio.h>

#include "GlobalDef.h"
#include "USART.h"
#include "AVCLanDriver.h"

#define FIRMWARE_VERSION    "v1.0"
#define FIRMWARE_DATE       __DATE__

/*--------------------------------------------------------------------------------------------------
                                         Prototypes
--------------------------------------------------------------------------------------------------*/
void InitMCU ( void );

/*--------------------------------------------------------------------------------------------------
                                      Global Variables
--------------------------------------------------------------------------------------------------*/
volatile bool   SecondsTick = FALSE;

char            UsartMsgBuffer[ USART_BUFFER_SIZE ];

/*--------------------------------------------------------------------------------------------------

  Name         :  InitMCU

  Description  :  Performs MCU initialization.

  Argument(s)  :  None.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
void InitMCU ( void )
{
    // Init LED port pin
    LED_DDR |= LEDOUT;
    LedOff();

    // Timer 0 prescaler = 8 ( 1 count / us )
    TCCR0 = _BV(CS01);

    InitUSART();

    // Preset AVC bus driver output pins but leave pins tri-stated until we need to use them.
    PORTD |= _BV(PD3);   // PD3 (+) high.
    PORTD &= ~_BV(PD2);  // PD2 (-) low.

    //  Enable watchdog @ ~2 sec.
    wdt_enable( WDTO_2S );
}

/*--------------------------------------------------------------------------------------------------

  Name         :  LedOn

  Description  :  Turn LED on (active low signal).

  Argument(s)  :  None.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
inline void LedOn ( void )
{
    LED_PORT &= ~LEDOUT;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  LedOff

  Description  :  Turn LED off (active low signal).

  Argument(s)  :  None.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
inline void LedOff ( void )
{
    LED_PORT |= LEDOUT;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  main

  Description  :  Program's main function.

  Argument(s)  :  None.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
int main ( void )
{
    InitMCU();

    UsartPutCStr( PSTR("\r\n\t\t     Toyota AVC-Lan AUX Enabler\r\n") );
    UsartPutCStr( PSTR("\t\tCopyright (C) 2007, SigmaObjects Inc.\r\n") );

    sprintf( UsartMsgBuffer, "\t\t     Firmware %s, %s\r\n\r\n", FIRMWARE_VERSION, FIRMWARE_DATE );
    UsartPutStr( UsartMsgBuffer );

    while ( 1 )
    {
        // Reset watchdog.
        wdt_reset();

        AvcActionID actionID = AvcReadMessage();

        if ( actionID != ACT_NONE )
        {
            AvcProcessActionID( actionID );
        }
    }
}

/*--------------------------------------------------------------------------------------------------
                                         End of file.
--------------------------------------------------------------------------------------------------*/

 

/*--------------------------------------------------------------------------------------------------

  Name         :  AVCLanDriver.c

  Description  :  AVC Lan driver for Toyota devices.

  Author       :  Louis Frigon
 
  Copyright    :  (c) 2007 SigmaObjects
 
----------------------------------------------------------------------------------------------------
                                          AVC LAN Theory

     The AVC bus is an implementation of the IEBus which is a differential line, floating on logical
     level '1' and driving on logical '0'. Floating level shall be below 20 mV whereas driving level
     shall be above 120 mV.
    
     The diagram below represents how things work from a logical perspective on the bus.

     A rising edge indicates a new bit. The duration of the high state tells whether it is a start
     bit (~165 us), a bit '0' (~30 us) or a bit '1' (~20 us). A normal bit length is close to 40 us.

                       |<---- Bit '0' ---->|<---- Bit '1' ---->|
     Physical '1'      ,---------------,   ,---------,         ,---------
                       ^               |   ^         |         ^
     Physical '0' -----'               '---'         '---------'--------- Idle low
                       |---- 32 us ----| 7 |- 20 us -|- 19 us -|

     A bit '1' is typically 20 us high followed by 19 us low.
    
     A bit '0' is typically 32 us high followed by 7 us low. A bit '0' is dominant i.e. it takes
     precedence over a '1' by extending the pulse. This is why lower addresses win on arbitration.
        
     A start bit is typically 165 us high followed by 30 us low.

                                  AVC LAN Frame Format
     Bits Description

      1   Start bit
      1   MSG_NORMAL
      12  Master address
      1   Parity
      12  Slave address
      1   Parity
      1   * Acknowledge * (read below)
      4   Control
      1   Parity
      1   * Acknowledge * (read below)
      8   Payload length (n)
      1   Parity
      1   * Acknowledge * (read below)
          8   Data
          1   Parity
          1   * Acknowledge * (read below)
      repeat 'n' times

     In point-to-point communication, sender issues an ack bit with value '1' (20 us). Receiver
     upon acking will extend the bit until it looks like a '0' (32 us) on the bus. In broadcast
     mode, receiver disregards the bit.

     An acknowledge bit of value '0' means OK, '1' means no ack.

--------------------------------------------------------------------------------------------------*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <stdio.h>

#include "GlobalDef.h"
#include "USART.h"
#include "AVCLanDriver.h"

/*--------------------------------------------------------------------------------------------------
                                      Local Functions
--------------------------------------------------------------------------------------------------*/
static void         SendStartBit ( void );
static void         Send12BitWord ( word data );
static void         Send8BitWord ( byte data );
static void         Send4BitWord ( byte data );
static void         Send1BitWord ( bool data );
static bool         SendMessage ( void );

static word         ReadBits ( byte nbBits );
static bool         ReadAcknowledge ( void );

static bool         HandleAcknowledge ( void );
static bool         IsAvcBusFree ( void );

static AvcActionID  GetActionID ( void );
static void         LoadDataInGlogalRegisters ( AvcOutMessage * msg );

/*--------------------------------------------------------------------------------------------------
                                      Global Variables
--------------------------------------------------------------------------------------------------*/
// Message frame global registers
static const char * Description;
static bool         Broadcast;
static word         MasterAddress;
static word         SlaveAddress;
static byte         Control;
static byte         DataSize;
static bool         ParityBit;
static byte         Data[ 256 ];

       bool         AUX_Enabled     = FALSE;
       AvcActionID  DeviceEnabled   = ACT_NONE;

static AvcInMessage MessageTable [] PROGMEM =
{
/*--------------------------------------------------------------------------------------------------
                                  Head Unit (HU) Messages
    0x60 = Tuner ID
    0x61 = Tape ID
    0x62 = CD ID
    0x63 = CD Changer ID (this is what we're emulating)
--------------------------------------------------------------------------------------------------*/
    { ACT_AUX_IN_USE,   4, {0x11, 0x01, 0x45, 0x01}, "AUX in use" },
    { ACT_TUNER_IN_USE, 4, {0x11, 0x01, 0x45, 0x60}, "Tuner in use" },
    { ACT_TAPE_IN_USE,  4, {0x11, 0x01, 0x45, 0x61}, "Tape in use" },
    { ACT_CD_IN_USE,    4, {0x11, 0x01, 0x45, 0x62}, "CD in use" },

    { ACT_NONE,         3, {0x11, 0x01, 0x46}, "No device in use" },
    { ACT_NONE,         3, {0x11, 0x01, 0x20 /* xx */}, "Ping" }, // Get this once every minute in radio off mode. xx increments
    { ACT_EJECT_CD,    10, {0x62, 0x31, 0xF1, 0x00, 0x30, 0x01, 0x01, 0x00, 0x00, 0x00, 0x80}, "Eject CD" },
    { ACT_NO_CD,       10, {0x62, 0x31, 0xF1, 0x00, 0xF8, 0x01, 0x01, 0x00, 0x00, 0x00, 0x80}, "No CD" },
//    { ACT_CD_INFO,      6, {0x62, 0x31, 0xF1, 0x01, 0x10, 0x01 /* Track #, Min, Sec, 0x00, 0x80 */}, "CD Info: " },

    { ACT_STATUS,       3, {0x00, 0x01, 0x0A}, "LAN Status" },
    { ACT_REGISTER,     3, {0x11, 0x01, 0x00}, "LAN Register" },
    { ACT_INIT,         3, {0x11, 0x01, 0x01}, "LAN Restart" },
    { ACT_CHECK,        3, {0x11, 0x01, 0x20}, "LAN Check" },

    { 0 }
};

const byte MessageTableSize = sizeof( MessageTable ) / sizeof( AvcInMessage );

/*--------------------------------------------------------------------------------------------------
                                    Our (CD) Commands
--------------------------------------------------------------------------------------------------*/
AvcOutMessage CmdReset    PROGMEM = { MSG_BCAST,  5, {0x00, 0x00, 0x00, 0x00, 0x00}, "Reset" }; // This causes HU to send ACT_REGISTER

//AvcOutMessage CmdRegister PROGMEM = { MSG_NORMAL, 5, {0x00, 0x01, 0x11, 0x10, 0x63}, "Register" };
//AvcOutMessage CmdRegister PROGMEM = { MSG_NORMAL, 5, {0x00, 0x01, 0x11, 0x54, 0x63}, "Toggle HU On/Off" };
//AvcOutMessage CmdRegister PROGMEM = { MSG_NORMAL, 5, {0x00, 0x01, 0x11, 0x54, 0x63}, "Toggle HU On/Off" };
AvcOutMessage CmdEnableAux  PROGMEM = { MSG_NORMAL, 5, {0x00, 0x01, 0x11, 0x50, 0x61}, "Enable AUX" };
AvcOutMessage CmdDisableAux PROGMEM = { MSG_NORMAL, 5, {0x00, 0x01, 0x11, 0x51, 0x61}, "Disable AUX" };

/*--------------------------------------------------------------------------------------------------

  Name         :  AvcRegisterMe

  Description  :  Sends registration message to master controller.

  Argument(s)  :  None.

  Return value :  (bool) -> TRUE if successful else FALSE.

--------------------------------------------------------------------------------------------------*/
bool AvcRegisterMe ( void )
{
    Broadcast     = MSG_NORMAL;
    MasterAddress = MY_ADDRESS;
    SlaveAddress  = HU_ADDRESS;
    Control       = CONTROL_FLAGS;

    AvcProcessActionID( ACT_REGISTER );

    return TRUE;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  AvcReadMessage

  Description  :  Read incoming messages on the AVC LAN bus.

  Argument(s)  :  None.

  Return value :  (AvcActionID) -> Action ID associated with this message.

--------------------------------------------------------------------------------------------------*/
AvcActionID AvcReadMessage ( void )
{
    ReadBits( 1 ); // Start bit.

    LedOn();

    Broadcast = ReadBits( 1 );

    MasterAddress = ReadBits( 12 );
    bool p = ParityBit;
    if ( p != ReadBits( 1 ) )
    {
        UsartPutCStr( PSTR("AvcReadMessage: Parity error @ MasterAddress!\r\n") );
        return FALSE;
    }

    SlaveAddress = ReadBits( 12 );
    p = ParityBit;
    if ( p != ReadBits( 1 ) )
    {
        UsartPutCStr( PSTR("AvcReadMessage: Parity error @ SlaveAddress!\r\n") );
        return FALSE;
    }

    bool forMe = ( SlaveAddress == MY_ADDRESS );

    // In point-to-point communication, sender issues an ack bit with value '1' (20us). Receiver
    // upon acking will extend the bit until it looks like a '0' (32us) on the bus. In broadcast
    // mode, receiver disregards the bit.

    if ( forMe )
    {
        // Send ACK.
        Send1BitWord( 0 );
    }
    else
    {
        ReadBits( 1 );
    }

    Control = ReadBits( 4 );
    p = ParityBit;
    if ( p != ReadBits( 1 ) )
    {
        UsartPutCStr( PSTR("AvcReadMessage: Parity error @ Control!\r\n") );
        return FALSE;
    }

    if ( forMe )
    {
        // Send ACK.
        Send1BitWord( 0 );
    }
    else
    {
        ReadBits( 1 );
    }

    DataSize = ReadBits( 8 );
    p = ParityBit;
    if ( p != ReadBits( 1 ) )
    {
        UsartPutCStr( PSTR("AvcReadMessage: Parity error @ DataSize!\r\n") );
        return FALSE;
    }

    if ( forMe )
    {
        // Send ACK.
        Send1BitWord( 0 );
    }
    else
    {
        ReadBits( 1 );
    }

    byte i;

    for ( i = 0; i < DataSize; i++ )
    {
        Data[i] = ReadBits( 8 );
        p = ParityBit;
        if ( p != ReadBits( 1 ) )
        {
            sprintf( UsartMsgBuffer, "AvcReadMessage: Parity error @ Data[%d]\r\n", i );
            UsartPutStr( UsartMsgBuffer );
            return FALSE;
        }

        if ( forMe )
        {
            // Send ACK.
            Send1BitWord( 0 );
        }
        else
        {
            ReadBits( 1 );
        }
    }

    // Dump message on terminal.
    if ( forMe ) UsartPutCStr( PSTR("AvcReadMessage: This message is for me!\r\n") );

    AvcActionID actionID = GetActionID();

    DumpRawMessage( TRUE );

    LedOff();

    return actionID;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  AvcProcessActionID

  Description  :  Perform processing for given action ID.
 
  Argument(s)  :  actionID (AvcActionID) -> Action ID to process.
 
  Return value :  (bool) -> TRUE if action performed.

--------------------------------------------------------------------------------------------------*/
bool AvcProcessActionID ( AvcActionID actionID )
{
    // This function relies on the last received message still being loaded in global registers.

    switch ( actionID )
    {
    case ACT_AUX_IN_USE:

        AUX_Enabled = TRUE;
        return FALSE;

    case ACT_TUNER_IN_USE:
    case ACT_TAPE_IN_USE:
    case ACT_CD_IN_USE:

        DeviceEnabled = actionID;
        AUX_Enabled = FALSE;
        return FALSE;

//    case ACT_NO_CD:

    case ACT_EJECT_CD:

        // Normal CD eject command.
        if ( DeviceEnabled == ACT_CD_IN_USE ) return FALSE;

        if ( AUX_Enabled )
        {
            LoadDataInGlogalRegisters ( &CmdDisableAux );
            AUX_Enabled = FALSE;
        }
        else
        {
            LoadDataInGlogalRegisters ( &CmdEnableAux );
            AUX_Enabled = TRUE;
        }

        return SendMessage();
        break;

    default:

        // No success!
        UsartPutCStr( PSTR("AvcProcessActionID: Unknown action ID!\r\n") );
        return FALSE;
    }

    // Nothing to do!
    return FALSE;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  LoadDataInGlogalRegisters

  Description  :  Loads message data in global registers for given mesage ID.

  Argument(s)  :  msg (AvcOutMessage *) -> Message to load.
 
  Return value :  None.

--------------------------------------------------------------------------------------------------*/
void LoadDataInGlogalRegisters ( AvcOutMessage * msg )
{
    Description = msg->Description;

    Broadcast = pgm_read_byte_near( &msg->Mode );

    MasterAddress = MY_ADDRESS;

    if ( Broadcast == MSG_BCAST )
        SlaveAddress = BROADCAST_ADDRESS;
    else
        SlaveAddress = HU_ADDRESS;

    DataSize = pgm_read_byte_near( &msg->DataSize );

    for ( byte i = 0; i < DataSize; i++ )
    {
        Data[i] = pgm_read_byte_near( &msg->Data[i] );
    }
}

/*--------------------------------------------------------------------------------------------------

  Name         :  GetActionID

  Description  :  Use the last received message to determine the corresponding action ID.

  Argument(s)  :  None.
 
  Return value :  (AvcActionID) -> Action ID corresponding to current message.

--------------------------------------------------------------------------------------------------*/
AvcActionID GetActionID ( void )
{
    Description = PSTR("Unknown message!");

    // Iterate through all HU messages in table.
    for ( byte msg = 0; msg < MessageTableSize; msg++ )
    {
        bool found = TRUE;

        // Identify current message from it's payload data.
        for ( byte i = 0; i < pgm_read_byte_near( &MessageTable[msg].DataSize ); i++ )
        {
            if ( Data[i] != pgm_read_byte_near( &MessageTable[msg].Data[i] ) )
            {
                found = FALSE;
                break;
            }
        }

        if ( found )
        {
            Description = MessageTable[msg].Description;

            // Fetch action corresponding to the message.
            AvcActionID actionID = pgm_read_byte_near( &MessageTable[msg].ActionID );

            return actionID;
        }
    }

    return ACT_NONE;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  Send12BitWord

  Description  :  Writes a 12 bit word on the AVC LAN bus.

  Argument(s)  :  data (word) -> Data to write.

  Return value :  None.
  
--------------------------------------------------------------------------------------------------*/
void Send12BitWord ( word data )
{
    ParityBit = 0;

    // Most significant bit out first.  
    for ( char nbBits = 0; nbBits < 12; nbBits++ )
    {
        // Reset timer to measure bit length.
        TCNT0 = 0;

        // Drive output to signal high.
        DDRD |= _BV(PD2) | _BV(PD3);

        if ( data & 0x0800 )
        {
            // Adjust parity.
            ParityBit = ! ParityBit;

            while ( TCNT0 < BIT_1_HOLD_ON_LENGTH );
        }
        else
        {
            while ( TCNT0 < BIT_0_HOLD_ON_LENGTH );
        }

        // Release output.
        DDRD &= ~( _BV(PD2) | _BV(PD3) );

        // Hold output low until end of bit.
        while ( TCNT0 < NORMAL_BIT_LENGTH );

        // Fetch next bit.
        data <<= 1;
    }
}

/*--------------------------------------------------------------------------------------------------

  Name         :  Send8BitWord

  Description  :  Writes an 8 bit word on the AVC LAN bus.

  Argument(s)  :  data (byte) -> Data to write.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
void Send8BitWord ( byte data )
{
    ParityBit = 0;

    // Most significant bit out first.  
    for ( char nbBits = 0; nbBits < 8; nbBits++ )
    {
        // Reset timer to measure bit length.
        TCNT0 = 0;

        // Drive output to signal high.
        DDRD |= _BV(PD2) | _BV(PD3);

        if ( data & 0x80 )
        {
            // Adjust parity.
            ParityBit = ! ParityBit;

            while ( TCNT0 < BIT_1_HOLD_ON_LENGTH );
        }
        else
        {
            while ( TCNT0 < BIT_0_HOLD_ON_LENGTH );
        }

        // Release output.
        DDRD &= ~( _BV(PD2) | _BV(PD3) );

        // Hold output low until end of bit.
        while ( TCNT0 < NORMAL_BIT_LENGTH );

        // Fetch next bit.
        data <<= 1;
    }
}

/*--------------------------------------------------------------------------------------------------

  Name         :  Send4BitWord

  Description  :  Writes a 4 bit word on the AVC LAN bus.

  Argument(s)  :  data (byte) -> Data to write.

  Return value :  None.
  
--------------------------------------------------------------------------------------------------*/
void Send4BitWord ( byte data )
{
    ParityBit = 0;

    // Most significant bit out first.  
    for ( char nbBits = 0; nbBits < 4; nbBits++ )
    {
        // Reset timer to measure bit length.
        TCNT0 = 0;

        // Drive output to signal high.
        DDRD |= _BV(PD2) | _BV(PD3);

        if ( data & 0x8 )
        {
            // Adjust parity.
            ParityBit = ! ParityBit;

            while ( TCNT0 < BIT_1_HOLD_ON_LENGTH );
        }
        else
        {
            while ( TCNT0 < BIT_0_HOLD_ON_LENGTH );
        }

        // Release output.
        DDRD &= ~( _BV(PD2) | _BV(PD3) );

        // Hold output low until end of bit.
        while ( TCNT0 < NORMAL_BIT_LENGTH );

        // Fetch next bit.
        data <<= 1;
    }
}

/*--------------------------------------------------------------------------------------------------

  Name         :  Send1BitWord

  Description  :  Writes a 1 bit word on the AVC LAN bus.

  Argument(s)  :  data (bool) -> Data to write.

  Return value :  None.
  
--------------------------------------------------------------------------------------------------*/
void Send1BitWord ( bool data )
{
    // Reset timer to measure bit length.
    TCNT0 = 0;

    // Drive output to signal high.
    DDRD |= _BV(PD2) | _BV(PD3);

    if ( data )
    {
        while ( TCNT0 < BIT_1_HOLD_ON_LENGTH );
    }
    else
    {
        while ( TCNT0 < BIT_0_HOLD_ON_LENGTH );
    }

    // Release output.
    DDRD &= ~( _BV(PD2) | _BV(PD3) );

    // Pulse level low duration until 40 us.
    while ( TCNT0 <  NORMAL_BIT_LENGTH );
}

/*--------------------------------------------------------------------------------------------------

  Name         :  SendStartBit

  Description  :  Writes a start bit on the AVC LAN bus.

  Argument(s)  :  None.

  Return value :  None.
  
--------------------------------------------------------------------------------------------------*/
void SendStartBit ( void )
{
    // Reset timer to measure bit length.
    TCNT0 = 0;

    // Drive output to signal high.
    DDRD |= _BV(PD2) | _BV(PD3);

    // Pulse level high duration.
    while ( TCNT0 < START_BIT_HOLD_ON_LENGTH );

    // Release output.
    DDRD &= ~( _BV(PD2) | _BV(PD3) );

    // Pulse level low duration until ~185 us.
    while ( TCNT0 < START_BIT_LENGTH );
}

/*--------------------------------------------------------------------------------------------------

  Name         :  ReadBits

  Description  :  Reads specified number of bits from the AVC LAN bus.

  Argument(s)  :  nbBits (byte) -> Number of bits to read.

  Return value :  (word) -> Data value read.

                       |<---- Bit '0' ---->|<---- Bit '1' ---->|
     Physical '1'      ,---------------,   ,---------,         ,---------
                       ^               |   ^         |         ^
     Physical '0' -----'               '---'         '---------'--------- Idle low
                       |---- 32 us ----| 7 |- 20 us -|- 19 us -|

--------------------------------------------------------------------------------------------------*/
word ReadBits ( byte nbBits )
{
    word data = 0;

    ParityBit = 0;

    while ( nbBits-- > 0 )
    {
        // Insert new bit
        data <<= 1;

        // Wait until rising edge of new bit.
        while ( INPUT_IS_CLEAR )
        {
            // Reset watchdog.
            wdt_reset();
        }

        // Reset timer to measure bit length.
        TCNT0 = 0;

        // Wait until falling edge.
        while ( INPUT_IS_SET );

        // Compare half way between a '1' (20 us) and a '0' (32 us ): 32 - (32 - 20) /2 = 26 us
        if ( TCNT0 < BIT_0_HOLD_ON_LENGTH - (BIT_0_HOLD_ON_LENGTH - BIT_1_HOLD_ON_LENGTH) / 2 )
        {
            // Set new bit.
            data |= 0x0001;

            // Adjust parity.
            ParityBit = ! ParityBit;
        }
    }

    return data;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  SendMessage

  Description  :  Sends the message in global registers on the AVC LAN bus.

  Argument(s)  :  None.
 
  Return value :  (bool) -> TRUE if successful else FALSE.

--------------------------------------------------------------------------------------------------*/
bool SendMessage ( void )
{
    while ( ! IsAvcBusFree() );

    // At this point we know the bus is available.

    LedOn();

    // Send start bit.
    SendStartBit();

    // Broadcast bit.
    Send1BitWord( Broadcast );

    // Master address = me.
    Send12BitWord( MasterAddress );
    Send1BitWord( ParityBit );

    // Slave address = head unit (HU).
    Send12BitWord( SlaveAddress );
    Send1BitWord( ParityBit );

    if ( ! HandleAcknowledge() )
    {
        DumpRawMessage( FALSE );
        UsartPutStr( "SendMessage: No Ack @ Slave address\r\n" );
        return FALSE;
    }

    // Control flag + parity.
    Send4BitWord( Control );
    Send1BitWord( ParityBit );

    if ( ! HandleAcknowledge() )
    {
        DumpRawMessage( FALSE );
        UsartPutStr( "SendMessage: No Ack @ Control\r\n" );
        return FALSE;
    }

    // Data length + parity.
    Send8BitWord( DataSize );
    Send1BitWord( ParityBit );

    if ( ! HandleAcknowledge() )
    {
        DumpRawMessage( FALSE );
        UsartPutStr( "SendMessage: No Ack @ DataSize\r\n" );
        return FALSE;
    }

    for ( byte i = 0; i < DataSize; i++ )
    {
        Send8BitWord( Data[i] );
        Send1BitWord( ParityBit );

        if ( ! HandleAcknowledge() )
        {
            DumpRawMessage( FALSE );
            sprintf( UsartMsgBuffer, "SendMessage: No Ack @ Data[%d]\r\n", i );
            UsartPutStr( UsartMsgBuffer );
            return FALSE;
        }
    }

    DumpRawMessage( FALSE );

    LedOff();

    return TRUE;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  ReadAcknowledge

  Description  :  Reads the acknowledge bit the AVC LAN bus.

  Argument(s)  :  None.

  Return value :  (bool) -> TRUE if ack detected else FALSE.

--------------------------------------------------------------------------------------------------*/
inline bool ReadAcknowledge ( void )
{
    // The acknowledge pattern is very tricky: the sender shall drive the bus for the equivalent
    // of a bit '1' (20 us) then release the bus and listen. At this point the target shall have
    // taken over the bus maintaining the pulse until the equivalent of a bit '0' (32 us) is formed.

    // Reset timer to measure bit length.
    TCNT0 = 0;

    // Drive output to signal high.
    DDRD |= _BV(PD2) | _BV(PD3);

    // Generate bit '0'.
    while ( TCNT0 < BIT_1_HOLD_ON_LENGTH );

    // Release output.
    DDRD &= ~( _BV(PD2) | _BV(PD3) );

    // Measure final resulting bit.
    while ( INPUT_IS_SET );

    // Sample half-way through bit '0' (26 us) to detect whether the target is acknowledging.
    if ( TCNT0 > BIT_0_HOLD_ON_LENGTH - (BIT_0_HOLD_ON_LENGTH - BIT_1_HOLD_ON_LENGTH) / 2 )
    {
        // Slave is acknowledging (ack = 0). Wait until end of ack bit.
        while ( INPUT_IS_SET );
        return TRUE;
    }

    // No sign of life on the bus.
    return FALSE;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  HandleAcknowledge

  Description  :  Sends ack bit if I am broadcasting otherwise wait and return received ack bit.

  Argument(s)  :  None.
 
  Return value :  (bool) -> FALSE if ack bit not detected.

--------------------------------------------------------------------------------------------------*/
bool HandleAcknowledge ( void )
{
    if ( Broadcast == MSG_BCAST )
    {
        // Acknowledge.   
        Send1BitWord( 0 );
        return TRUE;
    }

    // Return acknowledge bit.
    return ReadAcknowledge();
}

/*--------------------------------------------------------------------------------------------------

  Name         :  IsAvcBusFree

  Description  :  Determine whether the bus is free (no tx/rx).

  Argument(s)  :  None.

  Return value :  (bool) -> TRUE is bus is free.

--------------------------------------------------------------------------------------------------*/
bool IsAvcBusFree ( void )
{
    // Reset timer.
    TCNT0 = 0;

    while ( INPUT_IS_CLEAR )
    {
        // We assume the bus is free if anything happens for the length of 1 bit.
        if ( TCNT0 > NORMAL_BIT_LENGTH )
        {
            return TRUE;
        }
    }

    return FALSE;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  DumpRawMessage

  Description  :  Dumps raw content of message registers on the terminal.

  Argument(s)  :  incoming (bool) -> TRUE means incoming data, FALSE means outgoing.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
void DumpRawMessage ( bool incoming )
{
    // Dump message on terminal.

    if ( incoming )
        UsartPutCStr( PSTR("\r\nAUX Enabler <<--- HU\r\n") );
    else
        UsartPutCStr( PSTR("\r\nAUX Enabler --->> HU\r\n") );

    UsartPutCStr( PSTR("   Description:    ") );
    UsartPutCStr( Description );
    UsartPutCStr( PSTR("\r\n") );

    sprintf( UsartMsgBuffer, "   Broadcast:      %d \r\n", Broadcast );
    UsartPutStr( UsartMsgBuffer );

    sprintf( UsartMsgBuffer, "   Master address: 0x%X \r\n", MasterAddress );
    UsartPutStr( UsartMsgBuffer );

    sprintf( UsartMsgBuffer, "   Slave address:  0x%X \r\n", SlaveAddress );
    UsartPutStr( UsartMsgBuffer );

    sprintf( UsartMsgBuffer, "   Control:        0x%X \r\n", Control );
    UsartPutStr( UsartMsgBuffer );

    sprintf( UsartMsgBuffer, "   Data size:      %d \r\n", DataSize );
    UsartPutStr( UsartMsgBuffer );

    sprintf( UsartMsgBuffer, "   Data:           " );
    UsartPutStr( UsartMsgBuffer );

    for ( byte i = 0; i < DataSize; i++ )
    {
        sprintf( UsartMsgBuffer, "%X ", Data[i] );
        UsartPutStr( UsartMsgBuffer );
    }

    UsartPutStr( "\r\n-----\r\n" );
}

/*--------------------------------------------------------------------------------------------------
                                         End of file.
--------------------------------------------------------------------------------------------------*/

 

/*--------------------------------------------------------------------------------------------------

  Name         :  AvcLanDriver.h

  Description  :  AVC Lan driver for Toyota devices

--------------------------------------------------------------------------------------------------*/
#ifndef _AVCLANDRV_H_
#define _AVCLANDRV_H_

#define HU_ADDRESS                  0x190

#define MY_ADDRESS                  0x360       // CD Changer #1

#define BROADCAST_ADDRESS           0x01FF      // All audio devices

#define CONTROL_FLAGS               0xF

/*--------------------------------------------------------------------------------------------------

                       |<---- Bit '0' ---->|<---- Bit '1' ---->|
     Physical '1'      ,---------------,   ,---------,         ,---------
                       ^               |   ^         |         ^
     Physical '0' -----'               '---'         '---------'--------- Idle low
                       |---- 33 us ----| 7 |- 20 us -|- 20 us -|

     A bit '0' is typically 33 us high followed by 7 us low.
     A bit '1' is typically 20 us high followed by 20 us low.
     A start bit is typically 165 us high followed by 30 us low.

--------------------------------------------------------------------------------------------------*/

#define NORMAL_BIT_LENGTH           37

#define BIT_1_HOLD_ON_LENGTH        20
#define BIT_0_HOLD_ON_LENGTH        32

#define START_BIT_LENGTH            186
#define START_BIT_HOLD_ON_LENGTH    168

typedef enum
{   // No this is not a mistake, broadcast = 0!
    MSG_NORMAL      = 1,
    MSG_BCAST       = 0

} AvcTransmissionMode;

typedef enum
{
    ACT_NONE,

    ACT_AUX_IN_USE,
    ACT_TUNER_IN_USE,
    ACT_TAPE_IN_USE,
    ACT_CD_IN_USE,

    ACT_EJECT_CD,
    ACT_NO_CD,

    ACT_STATUS,
    ACT_REGISTER,
    ACT_INIT,
    ACT_CHECK

} AvcActionID;

typedef struct
{
    AvcActionID         ActionID;           // Action to perform after receiving this message.
    byte                DataSize;           // Payload data size (bytes).
    byte                Data[ 11 ];         // Payload data.
    char                Description[ 17 ];  // ASCII description of the command for terminal dump.

} AvcIncomingMessageStruct;

typedef const AvcIncomingMessageStruct AvcInMessage;

typedef struct
{
    AvcTransmissionMode Mode;               // Transmission mode: normal (1) or broadcast (0).
    byte                DataSize;           // Payload data size (bytes).
    byte                Data[ 11 ];         // Payload data.
    char                Description[ 17 ];  // ASCII description of the command for terminal dump.

} AvcOutgoingMessageStruct;

typedef const AvcOutgoingMessageStruct AvcOutMessage;

/*--------------------------------------------------------------------------------------------------
                                         Prototypes
--------------------------------------------------------------------------------------------------*/
AvcActionID     AvcReadMessage ( void );

bool            AvcProcessActionID ( AvcActionID actionID );
void            AvcUpdateStatus ( void );

void            DumpRawMessage ( bool incoming );

#endif // _AVCLANDRV_H_

/*--------------------------------------------------------------------------------------------------
                                         End of file.
--------------------------------------------------------------------------------------------------*/

 

/*--------------------------------------------------------------------------------------------------

  Name         :  GlobalDef.h

  Description  :  Global definitions.

  History      :  2004/04/06 - Created by Louis Frigon.

--------------------------------------------------------------------------------------------------*/
#ifndef _GLOBALDEF_H_
#define _GLOBALDEF_H_

#include <avr/io.h>

/*--------------------------------------------------------------------------------------------------
                                          Constants
--------------------------------------------------------------------------------------------------*/
#define FALSE                   0
#define TRUE                    (!FALSE)

// AVC LAN bus directly connected to internal analog comparator (PD6/7)
// PD6 AIN0 +
// PD7 AIN1 -
#define DATAIN_PIN              ACSR
#define DATAIN                  ACO

#define INPUT_IS_SET            ( bit_is_set( DATAIN_PIN, DATAIN ) )
#define INPUT_IS_CLEAR          ( bit_is_clear( DATAIN_PIN, DATAIN ) )

#define LED_DDR                 DDRC
#define LED_PORT                PORTC
#define LEDOUT                  _BV(PORT0)

/*--------------------------------------------------------------------------------------------------
                                       Type definitions
--------------------------------------------------------------------------------------------------*/
typedef char                    bool;
typedef unsigned char           byte;
typedef unsigned int            word;

/*--------------------------------------------------------------------------------------------------
                                         Prototypes
--------------------------------------------------------------------------------------------------*/
inline void LedOff( void );
inline void LedOn( void );

#endif   //  _GLOBALDEF_H_

/*--------------------------------------------------------------------------------------------------
                                         End of file.
--------------------------------------------------------------------------------------------------*/

 

/*--------------------------------------------------------------------------------------------------

  Name         :  USART.c

  Description  :  Utility functions for ATmega8 USART. 
 
  Author       :  2004-10-22 - Louis Frigon

  History      :  2004-10-22 - First release (v0.1). 

--------------------------------------------------------------------------------------------------*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdio.h>

#include "USART.h"

/*--------------------------------------------------------------------------------------------------

  Name         :  InitUSART

  Description  :  Performs USART initialization: 9600,N,8,1.

  Argument(s)  :  None.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
void InitUSART ( void )
{
    //  Disable USART while setting baud rate.
    UCSRB = 0x00;
    UCSRA = 0x00;

    //  8 data bit, 1 stop, no parity.
    UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0);

    //  Set USART baud rate @ 9600. Divider is 52 @ 8 MHz.
    UBRRL = 52;

    //  Enable internal pull-up on Rx pin.
    PORTD |= _BV(PD0);

    //  Enable Tx & Rx.
    UCSRB = _BV(RXEN) | _BV(TXEN);
}

/*--------------------------------------------------------------------------------------------------

  Name         :  UsartIsChr

  Description  :  Return status of USART Rx buffer.

  Argument(s)  :  None.

  Return value :  0 if Rx buffer empty.

--------------------------------------------------------------------------------------------------*/
bool UsartIsChr ( void )
{
    return UCSRA & _BV(RXC);
}

/*--------------------------------------------------------------------------------------------------

  Name         :  UsartGetChr

  Description  :  Return character USART Rx buffer. Blocking until Rx buffer not empty.

  Argument(s)  :  None.

  Return value :  Character in Rx buffer.

--------------------------------------------------------------------------------------------------*/
char UsartGetChr ( void )
{
    while ( !UsartIsChr() );

    return UDR;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  UsartPutChr

  Description  :  Send a character through the USART.
 
  Argument(s)  :  c -> char to send.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
void UsartPutChr ( char c )
{
    //  Wait for transmit register to be empty.
    while ( !(UCSRA & _BV(UDRE)) );

    UDR = c;
}

/*--------------------------------------------------------------------------------------------------

  Name         :  UsartPutStr

  Description  :  Transmit a string on the serial port.

  Argument(s)  :  str -> pointer to string to send.

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
void UsartPutStr ( char *str )
{
    while ( *str )
    {
        UsartPutChr( *str++ );
    }
}

/*--------------------------------------------------------------------------------------------------

  Name         :  UsartPutCStr

  Description  :  Transmit a string on the serial port.

  Argument(s)  :  str -> pointer to constant string to send (strings in Flash).

  Return value :  None.

--------------------------------------------------------------------------------------------------*/
void UsartPutCStr ( const char *str )
{
    char c;

    while ( (c = pgm_read_byte_near( str++ )) )
    {
        UsartPutChr( c );
    }
}

/*--------------------------------------------------------------------------------------------------
                                         End of file.
--------------------------------------------------------------------------------------------------*/

 

/*--------------------------------------------------------------------------------------------------

  Name         :  USART.h

  Description  :  Header file for USART functions.

  History      :  2004-10-22 - Created by Louis Frigon.

--------------------------------------------------------------------------------------------------*/
#ifndef _USART_H_
#define _USART_H_

#include <avr/pgmspace.h>

#include "GlobalDef.h"

#define USART_BUFFER_SIZE       80

extern char          UsartMsgBuffer[ USART_BUFFER_SIZE ];

/*--------------------------------------------------------------------------------------------------
                                    Function prototypes
--------------------------------------------------------------------------------------------------*/
//  Function prototypes are mandatory otherwise the compiler generates unreliable code.

void InitUSART               ( void );
bool UsartIsChr              ( void );
char UsartGetChr             ( void );
void UsartPutChr             ( char c );
void UsartPutStr             ( char *str );
void UsartPutCStr            ( const char *str );


#endif   //  _USART_H_
/*--------------------------------------------------------------------------------------------------
                                         End of file.
--------------------------------------------------------------------------------------------------*/

 


Downloads & Useful Links

This is a downloadable version of the firmware shown above:

Toyota Auxiliary Audio Input Enabler Firmware with Programmers Notepad project file

In order to compile the source code you need WinAVR, an excellent free C/C++ compiler for Atmel microcontrollers:

WinAVR compiler for Atmel microcontrollers

Atmel microcontrollers are great but fuse bits would be a nightmare to program without a decent tool. Download AVR Studio, another excellent free set of tools:

AVR Studio home page

Got questions? Looking for all kind of tricks? This is a resourceful site that you cannot miss:

AVR Freaks

Finally the last but not the least, visit the WEB site of my best friend Sylvain Bissonnette. He is an amazing guys always creating Atmel based projects.

Microsyl.com


Contact me

You can reach me at info@sigmaobjects.com

 

Top

Created by Louis Frigon

Copyright 2007, SigmaObjects