/* * 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; }
TinyEmu.c
Subscribe to:
Posts (Atom)
No comments:
Post a Comment