TinyEmu.c


/*
* TinyEmu.c
*
* Created: 17.09.2012 18:04:33
* Author: Mati
*/

/*
* Saab CD-Changer emulator / Aux-In Interface
* Copyright (C) 2012-2013  Mati Ustav <triibutu@hotmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include "TinyEmu.h"

/* Function declarations */
static void spi_select(void);
static byte spi_rw(byte dat);
static inline void spi_deselect(void);
static inline void spi_init(void);
static inline void mcp_init(void);
static inline void timer_init(void);
static inline void mcp_set_rx_filters(void);
static void mcp_set_mode(byte new_mode);
static byte mcp_read_rx_status(void);
static byte mcp_read_reg(byte mcp_reg);
static void mcp_write_reg(byte mcp_reg, byte dat);
static void mcp_write_sid(byte mcp_reg, byte sid_high, byte sid_low);
static void mcp_read_rx_msg(byte buf_addr, byte rx_msg[]);
static void mcp_rts(byte rts_cmd);
static void send_msg_pgm(const byte msg[]);
static void mcp_load_tx_buffer(const byte msg[], const byte buf_addr);

/* CAN messages to send */
/* 1. CDC handshake message */
/* First data byte -- It does appear from the real CDC logs, that
* the first message uses 0x32 and following messages use 0x62.
* This also works with emulation, but is unnecessary, just 0x32 is good */
const byte PROGMEM cdc_msg_1[] = {         //
 SIDH(0x6A2), SIDL(0x6A2), 0x00, 0x00, 0x08, 0x32, 0x00, 0x00, 0x16, 0x01, 0x02, 0x00, 0x00
};

/* 2. CDC status message */
const byte PROGMEM cdc_msg_2[] = {         //   Chg    -    Mag   Disc  Trck  Min   Sec   Secu
 SIDH(0x3C8), SIDL(0x3C8), 0x00, 0x00, 0x08, 0xE0, 0x00, 0x3F, 0x41, 0x01, 0x01, 0x01, 0xD0
};

byte rx_msg[13];
byte rx_status;
volatile byte cdc_tmr = 250;   //Extending timer period

int main(void) {
 /* Setup */
 spi_init();       //SPI port init
 mcp_init();       //MCP2515 controller init
 mcp_set_rx_filters();    //Receive filters and masks
 mcp_set_mode(MCP_MODE_NORMAL);  //Set device in active mode

 /* The CDC control messages are loaded in TX buffers once and then 
  * just sent again when needed. First send messages once at boot. */
 mcp_load_tx_buffer(cdc_msg_1, TXB0);
 mcp_rts(TXB0RTS);
 mcp_load_tx_buffer(cdc_msg_2, TXB1);
 mcp_rts(TXB1RTS);
 
 /* Timer for periodic messages */
 timer_init();

 /* Enable interrupts */
 sei();

 /* Work */
 while(1) {

  /* 1. Check/handle incoming messages */
  rx_status = mcp_read_rx_status();
  
  if (rx_status & 0xC0) {     //Have new matching message
   if (rx_status & (1<<6)) {   //Msg in RXB0
    mcp_read_rx_msg(RXB0, rx_msg);
   } else if (rx_status & (1<<7)) { //Msg in RXB1
    mcp_read_rx_msg(RXB1, rx_msg);
   }
   /* Received message is now in rx_msg, but we don't really even need 
   * the actual message, just checking if a filter had a match is enough */
   byte filt = rx_status & 0x7;  //Which filter matched the message   
   switch(filt) {
    case 0:       //Match filter 0 => got msg with id 6A1 from HU
     mcp_rts(TXB0RTS);   //Respond with 6A2
     break;
    case 2:       //Match filter 2 => msg id xxx
     //Do something else, etc
     break;
    case 3:       //Match filter 3 => msg id xxx
     //Etc
     break;
    default:
     break;
   } //switch(filt)
        
  } //if (rx_status & 0xC0)
 
  /* 2. Send periodic messages - CDC information */
  if (cdc_tmr == 0) {
   mcp_rts(TXB1RTS);
   cdc_tmr = 250;
  }
  
 } //while(1)
 
} //int main(void)

void timer_init(void) {
 TCCR0A = (1<<WGM01); //CTC mode
 TCCR0B = (1<<CS01) | (1<<CS00); //Clock prescaler Clk/64
 OCR0A = TIMER_TOP; //TOP value for compare, that results in 4ms period
 TIMER_MASK = (1<<OCIE0A); //Interrupt on compare match 
}

/* Timer interrupt function */
ISR(TIM0_COMPA_vect) {
 if (cdc_tmr > 0) cdc_tmr--;
}

/* Mark TX buffer as ready-to-send */
void mcp_rts(byte rts_cmd) {
 spi_select();
 spi_rw(rts_cmd);
 spi_deselect();
}

/* Send a message from flash memory, using TX buffer 2 */
void send_msg_pgm(const byte msg[]) {
 while (mcp_read_reg(TXB2CTRL) & (1<<TXREQ)) {
   //Wait TXB2CNTRL.TXREQ bit to clear
 }
 mcp_load_tx_buffer(msg, TXB2); //Load msg into TXB2
 mcp_rts(TXB2RTS); //Queue for send
}

/* Load CAN message from flash to TX buffer */
void mcp_load_tx_buffer(const byte msg[], const byte buf_addr) {
 spi_select();
 spi_rw(buf_addr); //Load msg into TXBn
 for (uint8_t i = 0; i < 13; i++) {
  spi_rw(pgm_read_byte(&msg[i]));
 }
 spi_deselect(); 
}

/* MCP2515 CAN controller setup */
void mcp_init(void) {
 spi_select();
 spi_rw(SPI_WRITE);
 spi_rw(CNF3);   //CNF3..1 - bit timing registers
 spi_rw(MCP_CNF3);  //CNF3
 spi_rw(MCP_CNF2);  //CNF2
 spi_rw(MCP_CNF1);  //CNF1
 spi_rw(MCP_CANINTE); //Receive interrupts enabled
 spi_deselect();
 /* Test read a register to verify successful communication */
 if (mcp_read_reg(CNF2) != MCP_CNF2) 
  while(1) {} //Stop here, if problems
}

/* Write CAN Standard ID bytes (or just any 2 bytes) to mcp registers */
void mcp_write_sid(byte mcp_reg, byte sid_high, byte sid_low) {
 spi_select();
 spi_rw(SPI_WRITE);
 spi_rw(mcp_reg);
 spi_rw(sid_high);
 spi_rw(sid_low);
 spi_deselect();
}

/* Set receive filters to receive only messages 6A1h
 * RXF0, RXF1, RXM0 --> RXB0
 * RXF2, RXF3, RXF4, RXF5, RXM1 --> RXB1 */
void mcp_set_rx_filters(void) {
 /* Set filters/masks for RXB0 to only accept 0x6A1 messages */
 mcp_write_sid(RXF0SIDH, SIDH(0x6A1), SIDL(0x6A1));
 mcp_write_sid(RXF1SIDH, 0, 0);
 mcp_write_sid(RXM0SIDH, 0xFF, 0xE0);
 
 /* Set filters/masks for RXB1 to not accept anything */
 mcp_write_sid(RXF2SIDH, 0, 0);
 mcp_write_sid(RXF3SIDH, 0, 0);
 mcp_write_sid(RXF4SIDH, 0, 0);
 mcp_write_sid(RXF5SIDH, 0, 0);
 mcp_write_sid(RXM1SIDH, 0xFF, 0xE0);
}

/* Set MCP1515 operation mode */
void mcp_set_mode(byte new_mode) {
 mcp_write_reg(CANCTRL, new_mode); //Request new mode
 while ((mcp_read_reg(CANSTAT) & 0xe0) != new_mode) {
 } //Wait until the new mode becomes active
}

/* Write one byte to MCP2515 register */
void mcp_write_reg(byte mcp_reg, byte dat) {
 spi_select();
 spi_rw(SPI_WRITE);
 spi_rw(mcp_reg);
 spi_rw(dat);
 spi_deselect(); 
}

/* Read one byte from MCP2515 register */
byte mcp_read_reg(byte mcp_reg) {
 byte ret_val;
 spi_select();
 spi_rw(SPI_READ);
 spi_rw(mcp_reg);
 ret_val = spi_rw(0xFF);
 spi_deselect(); 
 return ret_val;
}

/* Read MCP2515 RX status register */
byte mcp_read_rx_status(void) {
 byte ret_val;
 spi_select();
 spi_rw(SPI_RX_STATUS);
 ret_val = spi_rw(0xFF);
 spi_deselect();
 return ret_val; 
}

/* Read received CAN message (13 byes) from MCP2515 RX buffer */
void mcp_read_rx_msg(byte buf_addr, byte rx_msg[]) {
 spi_select();
 spi_rw(buf_addr);
 for (uint8_t i = 0; i < 13; i++) {
  rx_msg[i] = spi_rw(0xFF);
 }
 spi_deselect(); 
}

/* SPI slave select */
void spi_select(void) {
 MOSI_LOW;
 SCK_LOW;
 SPI_ENABLE;
}

/* SPI release */
void spi_deselect(void) {
 SPI_DISABLE;
}

/* Pins setup for software SPI */
void spi_init(void) {
 PORTB = (1<<CS) | (1<<INT); //Disable SS (high), enable pullup on INT, others low
 DDRB = (1<<MOSI) | (1<<SCK) | (1<<CS); //Set MOSI, SCK, CS as outputs
}

/* Write and read one byte over SPI. Return byte read.
 * Commands and data are sent to the device via the SI pin, with data being
 * clocked in on the rising edge of SCK. Data is driven out by the MCP2515
 * (on the SO line) on the falling edge of SCK. */
byte spi_rw(byte dat) {
 uint8_t cnt = 8;
 while (cnt--) {
  if (dat & 0x80) MOSI_HIGH;
  else MOSI_LOW;    //Set the MOSI bit
  SCK_HIGH;     //Clock high
  dat <<= 1;     //Shift next bit and make room for incoming bit (set to 0)
  SCK_LOW;     //Clock low
  if (IS_MISO_HIGH) dat++; //Set the LSB bit, if MISO is high
 }
 return dat;
}

No comments:

Post a Comment