From 1aca0df2823f3a50e22121d6211f1eda4ca2bdb4 Mon Sep 17 00:00:00 2001 From: qnx425 Date: Sat, 26 Jan 2019 11:40:48 +0500 Subject: [PATCH] Initial --- .gitignore | 2 +- Makefile | 13 ++ README.md | 63 +++++ can.c | 230 ++++++++++++++++++ can.h | 41 ++++ clock.c | 48 ++++ clock.h | 12 + elm327slcan.h | 69 ++++++ frontend.c | 517 +++++++++++++++++++++++++++++++++++++++++ frontend.h | 28 +++ images/ELM327SLCAN.png | Bin 0 -> 17811 bytes images/cmd.png | Bin 0 -> 18336 bytes images/filtering.png | Bin 0 -> 32804 bytes init.c | 89 +++++++ main.c | 73 ++++++ out/ELM327SLCAN.hex | 313 +++++++++++++++++++++++++ rbuf.c | 70 ++++++ rbuf.h | 33 +++ 18 files changed, 1600 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 README.md create mode 100644 can.c create mode 100644 can.h create mode 100644 clock.c create mode 100644 clock.h create mode 100644 elm327slcan.h create mode 100644 frontend.c create mode 100644 frontend.h create mode 100644 images/ELM327SLCAN.png create mode 100644 images/cmd.png create mode 100644 images/filtering.png create mode 100644 init.c create mode 100644 main.c create mode 100644 out/ELM327SLCAN.hex create mode 100644 rbuf.c create mode 100644 rbuf.h diff --git a/.gitignore b/.gitignore index c6127b3..6dcae4b 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,7 @@ *.app *.i*86 *.x86_64 -*.hex +#*.hex # Debug files *.dSYM/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b6534be --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +MAIN = ELM327SLCAN +SRC = frontend.c init.c rbuf.c clock.c main.c can.c +CC = C:\Program Files\Microchip\xc8\v1.42\bin\xc8.exe +CHIP = 18F25K80 + +all: $(MAIN).hex + +$(MAIN).hex: $(SRC) + $(CC) $(SRC) --chip=$(CHIP) --MODE=pro --OPT=+speed --OUTDIR=out -O$(MAIN) --ROM=default,-7cfc-7fff + +clean: + rm -f $(MAIN).hex funclist $(MAIN).cof $(MAIN).hxl $(MAIN).p1 $(MAIN).sdb startup.* $(MAIN).lst $(MAIN).pre $(MAIN).sym + diff --git a/README.md b/README.md new file mode 100644 index 0000000..32f994f --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +#### ELM327SLCAN + +The cheap vehicle's CAN bus sniffer based on ELM327, special [bootloader](https://fischl.de/usbtin/), and [USBtin](https://fischl.de/usbtin/). +With this device you can monitor vehicle's CAN bus. Monitoring software – [CANHacker](https://cdn.hackaday.io/files/12644540960896/CANHackerV2.00.02.zip) or [similar](https://fischl.de/usbtin/#usbtinviewer). + +##### LEDs + +1. Red – power supply. +2. Green – current connection status. If lights steady – connection is open. Flashes once in three seconds – connection is closed. +3. Orange – error status. + - If lights steady – serial port buffer overflow error. This error occurs when the MCU does not have time to read the next command from PC. You can reset this error only by turning off the power supply. + - One flash in three seconds – CAN messages FIFO-buffer overflow in MCU. The depth of a FIFO-buffer is eight messages. Then the ninth message is received, this error is raised. + - Double flash in three seconds – internal text buffer overflow in MCU. This error is raised then the rate of incoming CAN-messages is very high and MCU does not have time to send them to PC. + - Triple flash in three seconds – internal text buffer overflow in MCU. This error is raised then the rate of incoming commands from PC is very high and MCU does not have time to process them all. + + The last three errors could be reset with f2 command. + + +##### Switch to Bootloader mode for firmware update: + +1. Issue the command B10[CR] in RS232 terminal software (for instance, [termite](https://www.compuphase.com/software_termite.htm)). +2. Disconnect the COM port in RS232 terminal software. +3. Press the red button 'Bootloader Mode' in [Serial Bootloader AN1310](http://ww1.microchip.com/downloads/en/AppNotes/Serial%20Bootloader%20AN1310%20v1.05r.zip) software. +4. Switching to Bootloader mode is reflected in the status bar. +5. Update the firmware. +6. To start the updated application it is need to turn off and on the power. + + +![](images/ELM327SLCAN.png) + + +##### Filtering + +Implemented filtering 'Single filter mode' with three modifications as described in [SJA1000](https://www.nxp.com/docs/en/data-sheet/SJA1000.pdf) datasheet and [AN97076](https://www.nxp.com/docs/en/application-note/AN97076.pdf). The differences: + +1. RTR bit does not take part in filtering. +2. Two message's bytes do not take part in filtering. LSB ACR3 must be zero in case of work in CAN bus with 11-bit IDs. +3. LSB ACR3 is copied to EXIDEN bit of RXF0SIDL. LSB AMR3 is copied to EXIDEN bit of RXM0SIDL. See the PIC18FXXK80 datasheet. One mask (RXM0) and one filter (RXF0) are used. + +To activate the CANHacker Filter settings it is necessary that the connection has been already established (Connected to …). So - see the first Termite window: + +1. Establish connection. +2. Click Filter button. +3. Set Mask Filter in the upper group box. +4. Click OK button. As a result, we get ‘close’, ‘set mask’, ‘set filter’, ‘open’ commands. + +Range Filter filtering (lower group box) works in software. Issued command allows all incoming messages (mFFFFFF). + +If you remove both checkboxes and press OK button, issued command allows all incoming messages (mFFFFFF). See the second Termite window. + + +![](images/filtering.png) + +##### Bluetooth + +Bluetooth name: OBDII +Bluetooth PIN: 1234 +Serial port settings: 38400, 8n1 + + +##### To build new firmware, it is need to install Microchip XC8 Compiler and MinGW. + +![](images/cmd.png) diff --git a/can.c b/can.c new file mode 100644 index 0000000..091337c --- /dev/null +++ b/can.c @@ -0,0 +1,230 @@ +#include +#include "elm327slcan.h" +#include "clock.h" +#include "can.h" + +/** + * \brief Write to given register + * + * \param address Register address + * \param data Value to write to given register + */ +void can_write_register(unsigned short address, unsigned char data) { + FSR0 = address; + INDF0 = data; +} + +/** + * \brief Read from given register + * + * \param address Register address + * \return register value + */ +unsigned char can_read_register(unsigned short address) { + FSR0 = address; + + return INDF0; +} + +/** + * \brief Set filter mask of given SJA1000 register values + * + * \param amr0 Acceptence mask register 0 + * \param amr1 Acceptence mask register 1 + * \param amr2 Acceptence mask register 2 + * \param amr3 Acceptence mask register 3 + * + * This function has only affect if can controller is in configuration mode. + */ +void can_set_SJA1000_filter_mask(unsigned char amr0, unsigned char amr1, unsigned char amr2, unsigned char amr3) { + + // SJA1000 mask bit definition: 1 = accept without matching, 0 = do matching with acceptance code + // Microchip mask bit definition: 0 = accept without matching, 1 = do matching with acceptance filter + // -> invert mask + + RXM0SIDH = ~amr0; + RXM0SIDL = ((~amr1) & 0xE0) | (((~amr1) >> 3) & 3); + if (amr3 & 1) RXM0SIDL |= 8; + RXM0EIDH = ((~amr2) >> 3) | ((~amr1) << 5); + RXM0EIDL = ((~amr3) >> 3) | ((~amr2) << 5); +/* + // mask for filter 1 + RXM0SIDH = ~amr0; + RXM0SIDL = ((~amr1) & 0xE3) | 8; + RXM0EIDH = ~amr2; + RXM0EIDL = ~amr3; + + // mask for filter 2 + RXM1SIDH = ~amr2; + RXM1SIDL = (~amr3) & 0xE0; + RXM1EIDH = 0x00; + RXM1EIDL = 0x00; +*/ +} + +/** + * \brief Set filter code of given SJA1000 register values + * + * \param amr0 Acceptence code register 0 + * \param amr1 Acceptence code register 1 + * \param amr2 Acceptence code register 2 + * \param amr3 Acceptence code register 3 + * + * This function has only affect if controller is in configuration mode. + */ +void can_set_SJA1000_filter_code(unsigned char acr0, unsigned char acr1, unsigned char acr2, unsigned char acr3) { + RXF0SIDH = acr0; + RXF0SIDL = (acr1 & 0xE0) | ((acr1 >> 3) & 3); + if (acr3 & 1) RXF0SIDL |= 8; + RXF0EIDH = (acr2 >> 3) | (acr1 << 5); + RXF0EIDL = (acr3 >> 3) | (acr2 << 5); +/* + // acceptance code for filter 1 + RXF0SIDH = acr0; + RXF0SIDL = (acr1) & 0xE0; // standard + RXF1SIDH = acr0; + RXF1SIDL = ((acr1) & 0xE0) | 0x08; // extended + + // acceptance code for filter 2 + RXF2SIDH = acr2; + RXF2SIDL = (acr3) & 0xE0; // standard + RXF3SIDH = acr2; + RXF3SIDL = ((acr3) & 0xE0) | 0x08; // extended + + // fill remaining filters with zero +// RXF4SIDH = 0x00; +// RXF4SIDL = 0x00; +// RXF5SIDH = 0x00; +// RXF5SIDL = 0x00; +*/ +} + +/** + * \brief Set bit timing registers + * + * \param cnf1 Configuration register 1 + * \param cnf2 Configuration register 2 + * \param cnf3 Configuration register 3 + * + * This function has only affect if controller is in configuration mode + */ +void can_set_bittiming(unsigned char cnf1, unsigned char cnf2, unsigned char cnf3) { + + BRGCON1 = cnf1; + BRGCON2 = cnf2; + BRGCON3 = cnf3; +} + +/** + * \brief Send given CAN message + * + * \ p_canmsg Pointer to can message to send + * \return 1 if transmitted successfully to transmit buffer, 0 on error (= no free buffer available) + */ +unsigned char can_send_message(canmsg_t * p_canmsg) { + ECANCON &= 0xE0; + ECANCON |= 0x05; + + if (RXB0CON & 0x08) { + ECANCON--; + if (RXB0CON & 0x08) { + ECANCON--; + if (RXB0CON & 0x08) { + return 0; //All TX buffers are busy + } + } + } + + unsigned char length = p_canmsg->dlc; + if (length > 8) length = 8; + + if (p_canmsg->flags.extended) { + RXB0SIDH = p_canmsg->id >> 21; + RXB0SIDL = ((p_canmsg->id >> 13) & 0xe0) | ((p_canmsg->id >> 16) & 0x03) | 0x08; + RXB0EIDH = p_canmsg->id >> 8; + RXB0EIDL = p_canmsg->id; + } else { + RXB0SIDH = p_canmsg->id >> 3; + RXB0SIDL = p_canmsg->id << 5; + } + + RXB0DLC = length; + + if (p_canmsg->flags.rtr) { + RXB0DLC = length | 0x40; + } else { + if (length) { + unsigned char *pTxBuf, i; + + pTxBuf = &RXB0D0; + for (i = 0; i < length; i++) { + *pTxBuf++ = p_canmsg->data[i]; + } + } + } + + RXB0CON |= 8; + + return 1; +} + +/* + * \brief Read out one can message from controller + * + * \param p_canmsg Pointer to can message structure to fill + * \return 1 on success, 0 if there is no message to read + */ +unsigned char can_receive_message(canmsg_t * p_canmsg) { + + //unsigned char address; + + if (nFIFOEMPTY == 0) return 0; + + // store timestamp + p_canmsg->timestamp = clock_getMS(); + + ECANCON &= 0xE0; + ECANCON |= ((CANCON & 0x07) | 0x10); + + if (RXB0FUL == 0) return 0; + + unsigned char sidh = RXB0SIDH; + unsigned char sidl = RXB0SIDL; + + if (sidl & 0x08) { + // extended + p_canmsg->flags.extended = 1; + p_canmsg->id = (unsigned long) sidh << 21; + p_canmsg->id |= (unsigned long)(sidl & 0xe0) << 13; + p_canmsg->id |= (unsigned long)(sidl & 0x03) << 16; + p_canmsg->id |= (unsigned long)(RXB0EIDH) << 8; + p_canmsg->id |= (unsigned long) RXB0EIDL; + unsigned char dlc = RXB0DLC; + p_canmsg->dlc = dlc & 0x0f; + p_canmsg->flags.rtr = (dlc >> 6) & 0x01; + } else { + // standard + p_canmsg->flags.extended = 0; + p_canmsg->flags.rtr = (sidl >> 4) & 0x01; + p_canmsg->id = (unsigned long) sidh << 3; + p_canmsg->id |= (unsigned long) sidl >> 5; + p_canmsg->dlc = RXB0DLC & 0x0f; + } + + // get data + if (!p_canmsg->flags.rtr) { + unsigned char i, *ptr; + unsigned char length = p_canmsg->dlc; + if (length > 8) length = 8; + ptr = &RXB0D0; + for (i = 0; i < length; i++) { + p_canmsg->data[i] = *ptr++; + } + } + + RXB0FUL = 0; + NOP(); + RXBnIF = 0; + + return 1; +} diff --git a/can.h b/can.h new file mode 100644 index 0000000..864a481 --- /dev/null +++ b/can.h @@ -0,0 +1,41 @@ +#ifndef _CAN_H_INC +#define _CAN_H_INC + +// timing for Fosc = 16MHz // KVN +#define CAN_TIMINGS_10K 0x00 | 49, 0x80 | ((4-1)<<3) | (7-1), 4-1 // Prop=7, PS1=4, PS2=4, SamplePoint=75% +#define CAN_TIMINGS_20K 0x00 | 39, 0x80 | ((2-1)<<3) | (5-1), 2-1 // Prop=5, PS1=2, PS2=2, SamplePoint=80% +#define CAN_TIMINGS_50K 0x00 | 15, 0x80 | ((2-1)<<3) | (5-1), 2-1 // Prop=5, PS1=2, PS2=2, SamplePoint=80% +#define CAN_TIMINGS_100K 0x00 | 07, 0x80 | ((2-1)<<3) | (5-1), 2-1 // Prop=5, PS1=2, PS2=2, SamplePoint=80% +#define CAN_TIMINGS_125K 0x00 | 07, 0x80 | ((2-1)<<3) | (3-1), 2-1 // Prop=3, PS1=2, PS2=2, SamplePoint=75% +#define CAN_TIMINGS_250K 0x00 | 03, 0x80 | ((2-1)<<3) | (3-1), 2-1 // Prop=3, PS1=2, PS2=2, SamplePoint=75% +#define CAN_TIMINGS_500K 0x00 | 01, 0x80 | ((2-1)<<3) | (3-1), 2-1 // Prop=3, PS1=2, PS2=2, SamplePoint=75% +#define CAN_TIMINGS_800K 0x00 | 00, 0x80 | ((2-1)<<3) | (5-1), 2-1 // Prop=5, PS1=2, PS2=2, SamplePoint=80% +#define CAN_TIMINGS_1M 0x00 | 00, 0x80 | ((2-1)<<3) | (3-1), 2-1 // Prop=3, PS1=2, PS2=2, SamplePoint=75% + +// can message data structure +typedef struct +{ + unsigned long id; // identifier (11 or 29 bit) + struct { + unsigned char rtr : 1; // remote transmit request + unsigned char extended : 1; // extended identifier + } flags; + + unsigned char dlc; // data length code + unsigned char data[8]; // payload data + unsigned short timestamp; // timestamp +} canmsg_t; + +// function prototypes +//extern unsigned char can_init(); +extern unsigned char can_read_register(unsigned short); +extern void can_write_register(unsigned short, unsigned char); +//extern void can_bit_modify(unsigned char address, unsigned char mask, unsigned char data); +extern void can_set_SJA1000_filter_mask(unsigned char, unsigned char, unsigned char, unsigned char); +extern void can_set_SJA1000_filter_code(unsigned char, unsigned char, unsigned char, unsigned char); +extern unsigned char can_read_errorflags(void); +extern void can_set_bittiming(unsigned char, unsigned char, unsigned char); +extern unsigned char can_send_message(canmsg_t *); +extern unsigned char can_receive_message(canmsg_t *); + +#endif diff --git a/clock.c b/clock.c new file mode 100644 index 0000000..3a011a5 --- /dev/null +++ b/clock.c @@ -0,0 +1,48 @@ +#include +#include +#include "clock.h" +#include "elm327slcan.h" + +extern uint8_t state; + +static volatile uint16_t clock_msticker = 0, tmp; +static volatile uint8_t led_ticker = 0; + +void __interrupt(low_priority) myLoIsr(void) { + if (CCP1IF && CCP1IE) { + CCP1IF = 0; + // 1 ms + if (++clock_msticker == 60000) clock_msticker = 0; + } + + if (CCP2IF && CCP2IE) { + CCP2IF = 0; + // 100 ms + led_ticker = (led_ticker + 1) & 31; + stateLED((led_ticker == 0) || (state != STATE_CONFIG)); + + if (OERR()) { // steady orange light + errorLED(1); // device must be repowered + } else if (RXBnOVFL()) { // one orange light flash + errorLED((led_ticker & 2) && (led_ticker < 4)); + } else if (RX_OVFL()) { // two orange light flash + errorLED((led_ticker & 2) && (led_ticker < 8)); + } else if (TX_OVFL()) { // three orange light flash + errorLED((led_ticker & 2) && (led_ticker < 12)); + } + } +} + +uint16_t clock_getMS(void) { + CCP1IE = 0; + tmp = clock_msticker; + CCP1IE = 1; + + return tmp; +} + +void clock_reset(void) { + CCP1IE = 0; + clock_msticker = 0; + CCP1IE = 1; +} diff --git a/clock.h b/clock.h new file mode 100644 index 0000000..9153456 --- /dev/null +++ b/clock.h @@ -0,0 +1,12 @@ +#ifndef _CLOCK_ +#define _CLOCK_ + +#include + +extern uint16_t clock_getMS(void); +extern void clock_reset(void); + +//#define CLOCK_TIMERTICKS_1MS 500 +//#define CLOCK_TIMERTICKS_100MS 50000 + +#endif diff --git a/elm327slcan.h b/elm327slcan.h new file mode 100644 index 0000000..0edd0f3 --- /dev/null +++ b/elm327slcan.h @@ -0,0 +1,69 @@ +#ifndef __CONFIG_H_INC +#define __CONFIG_H_INC + +#include + +#define VERSION_HARDWARE_MAJOR 1 +#define VERSION_HARDWARE_MINOR 0 +#define VERSION_FIRMWARE_MAJOR 1 +#define VERSION_FIRMWARE_MINOR 8 + +#define BOOTLOADER_ENTRY_ADDRESS 0x7D00 + +#define STATE_CONFIG 0 +#define STATE_OPEN 1 +#define STATE_LISTEN 2 + +#define _XTAL_FREQ 16000000UL +#define BAUDRATE 38400UL +#define BRGVALUE ((_XTAL_FREQ + 2*BAUDRATE) / (4*BAUDRATE) - 1) + +#if defined(__DEBUG) + #define UxSPBRG SPBRG2 + #define UxSPBRGH SPBRGH2 + #define UxRCSTA RCSTA2 + #define UxTXSTA TXSTA2 + #define UxRCREG RCREG2 + #define UxTXREG TXREG2 + #define UxPIR PIR3 + #define UxRCIF PIR3bits.RC2IF + #define UxTXIF PIR3bits.TX2IF + #define UxRCIE PIE3bits.RC2IE + #define UxTXIE PIE3bits.TX2IE + #define UxBAUDCON BAUDCON2 + #define OERR() OERR2 + + #define stateLED(value) LATA0 = (value) + #define errorLED(error) LATA5 = (error) +#else + #define UxSPBRG SPBRG1 + #define UxSPBRGH SPBRGH1 + #define UxRCSTA RCSTA1 + #define UxTXSTA TXSTA1 + #define UxRCREG RCREG1 + #define UxTXREG TXREG1 + #define UxPIR PIR1 + #define UxRCIF PIR1bits.RC1IF + #define UxTXIF PIR1bits.TX1IF + #define UxRCIE PIE1bits.RC1IE + #define UxTXIE PIE1bits.TX1IE + #define UxBAUDCON BAUDCON1 + #define OERR() OERR1 + + #define stateLED(value) LATB7 = !(value) + #define errorLED(error) LATB6 = !(error) +#endif + +#define RXBnOVFL() RXBNOVFL +#define RX_OVFL() LATA2 +#define TX_OVFL() LATA3 + +#define SET_RX_OVFL() LATA2 = 1 +#define SET_TX_OVFL() LATA3 = 1 +#define RESET_RX_OVFL() LATA2 = 0 +#define RESET_TX_OVFL() LATA3 = 0 + +#define cmdSend() UxTXIE = 1 +#define cmdStop() UxTXIE = 0 + +#endif diff --git a/frontend.c b/frontend.c new file mode 100644 index 0000000..f8449ac --- /dev/null +++ b/frontend.c @@ -0,0 +1,517 @@ +#include +#include +#include "can.h" +#include "clock.h" +#include "elm327slcan.h" +#include "frontend.h" +#include "rbuf.h" + +extern uint8_t state; +extern rbuf_t sRing; + +#define cmdPush(data) do { \ + if (!rbuf_push((rbuf_t *)&sRing, data)) \ + SET_RX_OVFL(); \ + } while (0) + +unsigned char timestamping = 0; + +/** + * Parse hex value of given string + * + * @param line Input string + * @param len Count of characters to interpret + * @param value Pointer to variable for the resulting decoded value + * @return 0 on error, 1 on success + */ +unsigned char parseHex(char * line, unsigned char len, unsigned long * value) { + *value = 0; + while (len--) { + if (*line == 0) return 0; + *value <<= 4; + if ((*line >= '0') && (*line <= '9')) { + *value += *line - '0'; + } else if ((*line >= 'A') && (*line <= 'F')) { + *value += *line - 'A' + 10; + } else if ((*line >= 'a') && (*line <= 'f')) { + *value += *line - 'a' + 10; + } else return 0; + line++; + } + return 1; +} + +/** + * Send given byte value as hexadecimal string + * + * @param value Byte value to send over UART + */ +void sendByteHex(unsigned char value) { + +// sendHex(value, 2); + + unsigned char ch = value >> 4; + if (ch > 9) ch = ch - 10 + 'A'; + else ch = ch + '0'; + cmdPush(ch); + + ch = value & 0xF; + if (ch > 9) ch = ch - 10 + 'A'; + else ch = ch + '0'; + cmdPush(ch); + +} + +/** + * Interprets given line and transmit can message + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_transmit(char *line) { + canmsg_t canmsg; + unsigned long temp; + unsigned char idlen; + + canmsg.flags.rtr = ((line[0] == 'r') || (line[0] == 'R')); + + // upper case -> extended identifier + if (line[0] < 'Z') { + canmsg.flags.extended = 1; + idlen = 8; + } else { + canmsg.flags.extended = 0; + idlen = 3; + } + + if (!parseHex(&line[1], idlen, &temp)) return 0; + canmsg.id = temp; + + if (!parseHex(&line[1 + idlen], 1, &temp)) return 0; + canmsg.dlc = temp; + + if (!canmsg.flags.rtr) { + unsigned char i; + unsigned char length = canmsg.dlc; + if (length > 8) length = 8; + for (i = 0; i < length; i++) { + if (!parseHex(&line[idlen + 2 + i*2], 2, &temp)) return 0; + canmsg.data[i] = temp; + } + } + + return can_send_message(&canmsg); +} + +/** + * Interprets given line and set up bit timing + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_setupUserdefined(char * line) { + + if (state == STATE_CONFIG) { + unsigned long cnf1, cnf2, cnf3; + if (parseHex(&line[1], 2, &cnf1) && parseHex(&line[3], 2, &cnf2) && parseHex(&line[5], 2, &cnf3)) { + can_set_bittiming(cnf1, cnf2, cnf3); + return CR; + } + } + return BELL; +} + +/** + * Interprets given line and reads register + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_readRegister(char * line) { + + unsigned long address; + if (parseHex(&line[1], 3, &address)) { + unsigned char value = can_read_register(address); + sendByteHex(value); + return CR; + } + return BELL; +} + +/** + * Interprets given line and writes register + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_writeRegister(char * line) { + + unsigned long address, data; + if (parseHex(&line[1], 3, &address) && parseHex(&line[4], 2, &data)) { + can_write_register(address, data); + return CR; + } + return BELL; +} + +/** + * Interprets given line and set time stamping + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_setTimestamping(char * line) { + + unsigned long stamping; + if (parseHex(&line[1], 1, &stamping)) { + timestamping = (stamping != 0); + return CR; + } + + return BELL; +} + +/** + * Send out given error flags + * + * @param flags Error flags to send out + */ +void frontend_sendErrorflags(unsigned char flags) { + + cmdPush('F'); + sendByteHex(flags); + cmdPush(CR); +} + +/** + * Interprets given line and handle status flag requests + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_errorFlags(char * line) { + unsigned char flags = COMSTAT & 0x7F; + + cmdPush('F'); + sendByteHex(flags); + return CR; +} + +/** + * Interprets given line and handle error reporting requests + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_errorReporting(char * line) { + + unsigned long subcmd = 0; + + if (parseHex(&line[1], 1, &subcmd)) { + if (subcmd == 2) { + RXBNOVFL = 0; + RESET_RX_OVFL(); + RESET_TX_OVFL(); + errorLED(0); + return CR; + } + } + + return BELL; +} + +/** + * Interprets given line and set filter mask + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_setFilterMask(char * line) { + if (state == STATE_CONFIG) + { + unsigned long am0, am1, am2, am3; + if (parseHex(&line[1], 2, &am0) && parseHex(&line[3], 2, &am1) && parseHex(&line[5], 2, &am2) && parseHex(&line[7], 2, &am3)) { + can_set_SJA1000_filter_mask(am0, am1, am2, am3); + return CR; + } + } + + return BELL; +} + +/** + * Interprets given line and set filter code + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_setFilterCode(char * line) { + if (state == STATE_CONFIG) + { + unsigned long ac0, ac1, ac2, ac3; + if (parseHex(&line[1], 2, &ac0) && parseHex(&line[3], 2, &ac1) && parseHex(&line[5], 2, &ac2) && parseHex(&line[7], 2, &ac3)) { + can_set_SJA1000_filter_code(ac0, ac1, ac2, ac3); + return CR; + } + } + return BELL; +} + +/** + * Interprets given line and jump to bootloader + * + * @param line Line string which contains the transmit command + */ +unsigned char parseCmd_bootloaderJump(char * line) { + + unsigned long magic; + if (parseHex(&line[1], 2, &magic)) { + + // check magic code and if bootloader version is new enough to supports bootloader entry via jump + if (magic == 0x10) { + CANCON = 0x80; + INTCON = 0; + #asm + goto BOOTLOADER_ENTRY_ADDRESS + #endasm + } + } + + return BELL; +} + + +/** + * Parse given command line + * + * @param line Line string to parse + */ +void parseLine(char * line) { + + unsigned char result = BELL; + + switch (line[0]) { + case 'S': // Setup with standard CAN bitrates + if (state == STATE_CONFIG) + { + switch (line[1]) { + case '0': can_set_bittiming(CAN_TIMINGS_10K); result = CR; break; + case '1': can_set_bittiming(CAN_TIMINGS_20K); result = CR; break; + case '2': can_set_bittiming(CAN_TIMINGS_50K); result = CR; break; + case '3': can_set_bittiming(CAN_TIMINGS_100K); result = CR; break; + case '4': can_set_bittiming(CAN_TIMINGS_125K); result = CR; break; + case '5': can_set_bittiming(CAN_TIMINGS_250K); result = CR; break; + case '6': can_set_bittiming(CAN_TIMINGS_500K); result = CR; break; + case '7': can_set_bittiming(CAN_TIMINGS_800K); result = CR; break; + case '8': can_set_bittiming(CAN_TIMINGS_1M); result = CR; break; + } + + } + break; + case 's': // Setup with user defined timing settings for CNF1/CNF2/CNF3 + result = parseCmd_setupUserdefined(line); + break; + + case 'G': // Read given register + result = parseCmd_readRegister(line); + break; + + case 'W': // Write given register + result = parseCmd_writeRegister(line); + break; + + case 'V': // Get hardware version + { + + cmdPush('V'); + sendByteHex(VERSION_HARDWARE_MAJOR); + sendByteHex(VERSION_HARDWARE_MINOR); + result = CR; + } + break; + case 'v': // Get firmware version + { + + cmdPush('v'); + sendByteHex(VERSION_FIRMWARE_MAJOR); + sendByteHex(VERSION_FIRMWARE_MINOR); + result = CR; + } + break; + case 'N': // Get serial number + { + cmdPush('N'); + cmdPush('1'); + cmdPush('9'); + cmdPush('7'); + cmdPush('6'); + result = CR; + } + break; + case 'O': // Open CAN channel + if (state == STATE_CONFIG) { + // normal mode + CANCON = 0x00; + while ((CANSTAT & 0xE0) != 0x00) continue; + + clock_reset(); + + state = STATE_OPEN; + result = CR; + } + break; + + case 'l': // Loop-back mode + if (state == STATE_CONFIG) { + CANCON = 0x40; + while ((CANSTAT & 0xE0) != 0x40) continue; + + state = STATE_OPEN; + result = CR; + } + break; + + case 'L': // Open CAN channel in listen-only mode + if (state == STATE_CONFIG) { + CANCON = 0x60; + while ((CANSTAT & 0xE0) != 0x60) continue; + + state = STATE_LISTEN; + result = CR; + } + break; + + case 'C': // Close CAN channel + if (state != STATE_CONFIG) { + CANCON = 0x80; + while ((CANSTAT & 0xE0) != 0x80) continue; + + state = STATE_CONFIG; + result = CR; + } + break; + + case 'r': // Transmit standard RTR (11 bit) frame + case 'R': // Transmit extended RTR (29 bit) frame + case 't': // Transmit standard (11 bit) frame + case 'T': // Transmit extended (29 bit) frame + if (state == STATE_OPEN) { + if (parseCmd_transmit(line)) { + if (line[0] < 'Z') cmdPush('Z'); + else cmdPush('z'); + result = CR; + } + } + break; + + case 'f': // Handle error reporting requests + result = parseCmd_errorReporting(line); + break; + + case 'F': // Handle status flag requests + result = parseCmd_errorFlags(line); + break; + + case 'Z': // Set time stamping + result = parseCmd_setTimestamping(line); + break; + + case 'm': // Set accpetance filter mask + result = parseCmd_setFilterMask(line); + break; + + case 'M': // Set accpetance filter code + result = parseCmd_setFilterCode(line); + break; + + case 'B': // Jump to bootloader + result = parseCmd_bootloaderJump(line); + break; + } + + cmdPush(result); + + cmdSend(); +} + +uint8_t canmsg2ascii(canmsg_t * canmsg, char * s) { + char ch, step = RX_STEP_TYPE; + uint8_t num = 0; + + do { + if (step == RX_STEP_TYPE) { + // type + if (canmsg->flags.extended) { + step = RX_STEP_ID_EXT; + if (canmsg->flags.rtr) *s++ = 'R'; + else *s++ = 'T'; + num++; + } else { + step = RX_STEP_ID_STD; + if (canmsg->flags.rtr) *s++ = 'r'; + else *s++ = 't'; + num++; + } + } else if (step < RX_STEP_DLC) { + // id + unsigned char i = step - 1; + unsigned char * id_bp = (unsigned char*) &canmsg->id; + + ch = id_bp[3 - (i / 2)]; + if ((i % 2) == 0) ch = ch >> 4; + + ch = ch & 0xF; + if (ch > 9) ch = ch - 10 + 'A'; + else ch = ch + '0'; + + *s++ = ch; + num++; + + step++; + } else if (step < RX_STEP_DATA) { + // length + ch = canmsg->dlc; + + ch = ch & 0xF; + if (ch > 9) ch = ch - 10 + 'A'; + else ch = ch + '0'; + + *s++ = ch; + num++; + + if ((canmsg->dlc == 0) || canmsg->flags.rtr) step = RX_STEP_TIMESTAMP; + else step++; + } else if (step < RX_STEP_TIMESTAMP) { + // data + unsigned char i = step - RX_STEP_DATA; + + ch = canmsg->data[i / 2]; + if ((i % 2) == 0) ch = ch >> 4; + + ch = ch & 0xF; + if (ch > 9) ch = ch - 10 + 'A'; + else ch = ch + '0'; + + *s++ = ch; + num++; + + step++; + if (step - RX_STEP_DATA == canmsg->dlc*2) step = RX_STEP_TIMESTAMP; + } else if (timestamping && (step < RX_STEP_CR)) { + // timestamp + unsigned char i = step - RX_STEP_TIMESTAMP; + + if (i < 2) ch = (canmsg->timestamp >> 8) & 0xff; + else ch = canmsg->timestamp & 0xff; + if ((i % 2) == 0) ch = ch >> 4; + + ch = ch & 0xF; + if (ch > 9) ch = ch - 10 + 'A'; + else ch = ch + '0'; + + *s++ = ch; + num++; + + step++; + } else { + // linebreak + *s = CR; + num++; + + step = RX_STEP_FINISHED; + } + } while (step != RX_STEP_FINISHED); + + return num; +} diff --git a/frontend.h b/frontend.h new file mode 100644 index 0000000..6cd49bc --- /dev/null +++ b/frontend.h @@ -0,0 +1,28 @@ +#ifndef _FRONTEND_ +#define _FRONTEND_ + +#define LINE_MAXLEN 100 +#define BELL 7 +#define CR 13 +#define LR 10 + +#define RX_STEP_TYPE 0 +#define RX_STEP_ID_EXT 1 +#define RX_STEP_ID_STD 6 +#define RX_STEP_DLC 9 +#define RX_STEP_DATA 10 +#define RX_STEP_TIMESTAMP 26 +#define RX_STEP_CR 30 +#define RX_STEP_FINISHED 0xff + +extern unsigned char transmitStd(char *); +extern void parseLine(char *); +extern char canmsg2ascii_getNextChar(canmsg_t *, unsigned char *); +extern void sendbuffer_send(void); +extern unsigned char sendbuffer_isEmpty(void); +extern void sendStatusflags(unsigned char); +extern void frontend_sendErrorflags(unsigned char); + +extern unsigned char canmsg2ascii(canmsg_t *, char *); + +#endif diff --git a/images/ELM327SLCAN.png b/images/ELM327SLCAN.png new file mode 100644 index 0000000000000000000000000000000000000000..597bb0b55c0de05aaeecf227b869161a11305de9 GIT binary patch literal 17811 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�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 a`@YLY;k{`$b^#AkVeoYIb6Mw<&;$VZmy)9Z literal 0 HcmV?d00001 diff --git a/images/cmd.png b/images/cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..92e9542faa4abb93175532e97ac9f2c74751ff4d GIT binary patch literal 18336 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 z zh?)+~k9p>PsdA0tLI29n-FLv#Ll0jG0t^cOK8Y7G;Sh?k0%5L+M`&QQ=4Yg^FYJ^?Cp$OVPy;@BbWY=H?@}HxWZv&7E z&Rryp3mi?u-!cm7`DXBqC=fd?3A$;kZPfJt4&lZ_5a5D(eRkIx&R9U`+VVa&UR~yp zv??gH@Dp_eTD4&(Xhf-`Jt_zX4e_QUY>cE57E%oB4Ty$uS6Cg1P?O#a;x(=zDI8$O+5+ss(XhD{?s z`)mbYc7U2Upd#Jl`e>+hX2LcdI{jJ)R6#+377h0DsfH`})BTkr#Wn1sGnF3BUibY6 zD#I9o{Yg;rE3!EDaTH`A5RVj~fOh+@*wdhnKO#EWwx{n+<|}NwWqu^6m2%qmQ~JDm z_mdL>1}X#Gv=9%M>i8Nrp08`|P1Me87yudn1gOuN42!yrQSC;p3N@)40+WcxbEH9s z%WBC%X;VJJ6KG5Sa*rgfM-j*tii#&^K^mK`k9ua$U~xmMw>|ToXLQ3UlhLRl(S&)~ z1lgV&PG;3lP}|ie=|*|la!(bO_ItmvAS%g3_JMD|2wK*}w$U~M#tOtf|IPVx!Jc6* zfb?&uya-x)^|gbVOm|zBGyZ>lT}C7ZRG&lu3oEY20_ME1r@nInVB<%F%9E7+5xd*%MjG}FN!u>0cb%*gDFUZDVW=m1mTZlsTCp_Ryawc_C0x)`+*O zq5{g!|C=R+QdZK7T^*C8o~Bf8%)C(N+cNBV@TX1!PO2Bb(!St%igDef7#N`}Smqe9 zz!g~t4IQ=3O1cC;*Y&(%%;jJJve#)^#pT|haDKWDENZCrMZnrvCH%3DEZ=mWC1_tG zmMsVxA2o7ZPF-Yb?lBQvTLx{jy2y&##KF&%P7T3RgeTX;%bGyFDyR{3UyRL`s1!rf zPCMfoomAx;IY0e&B9OGGW9ZsS=-gx}U5+hgDdxdSz@y8&_22uti9ewL*AFPU zeW7vkS_OnS{MOY!?UFx!@>tR->48|?=kN}^2n>CCa9-s^!__R zxnM?D;`NfdZ!$8D_w}|)w_Utq`O!=!iGNTp`Y(7PZIu9Qg?8BS*K(#n?D6VhJuzsl wc0<~#fnN$;tH4h40EUeY_RXSrvfm%q|KZ*>;|%_)&N}3A3zL^eFW>q90OnsgjQ{`u literal 0 HcmV?d00001 diff --git a/images/filtering.png b/images/filtering.png new file mode 100644 index 0000000000000000000000000000000000000000..a2b03822f20925e9ee1e401896d6f916d5d046b3 GIT binary patch literal 32804 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 b7uT2{lc<=xO)!A&fhHrNApYZtzW09twNBfP literal 0 HcmV?d00001 diff --git a/init.c b/init.c new file mode 100644 index 0000000..4a60a17 --- /dev/null +++ b/init.c @@ -0,0 +1,89 @@ +#include +#include +#include "elm327slcan.h" + +void init(void) { + LATA = 0; +#if defined(__DEBUG) + TRISA &= 0b11010000; +#endif + ANCON0 = 0; // Digital port + ANCON1 &= 0xF8; // Digital port + + // EUSART init + UxBAUDCON = 0b01001000; + UxSPBRGH = BRGVALUE >> 8; + UxSPBRG = BRGVALUE & 255; + UxTXSTA = 0b00100110; + UxRCSTA = 0b10010000; + + // ECAN init +#if defined(__DEBUG) + LATC = 0x40; // RC6 as CANTX output + TRISC = 0x80; // RC7 as CANRX input + TRISB6 = 0; +#else + LATB = 0b11000100; // RB2 as CANTX output + TRISB = 0b00111011; // RB3 as CANRX input + TRISC6 = 0; +#endif + CANCON = 0x80; // Configuration mode + while ((CANSTAT & 0xE0) != 0x80) continue; + BSEL0 = 0; // Buffer 5 to 0 are configured in Receive mode +// BRGCON1 = 1; // Tq = 1/4 us, SJW = 1 +// BRGCON2 = 0b10001010; // SEG1PH = 2, PRSEG = 3 +// BRGCON3 = 1; // SEG2PH = 2 + CIOCON = 0b00100000; + ECANCON = 0b10110000; // Mode 2 + + RXM0SIDH = 0xFF; + //RXM0SIDL = 0xEB; + RXM0SIDL = 0xE3; + RXM0EIDH = 0xFF; + RXM0EIDL = 0xFF; + RXM1SIDH = 0xFF; + //RXM1SIDL = 0xEB; + RXM1SIDL = 0xE3; + RXM1EIDH = 0xFF; + RXM1EIDL = 0xFF; + + uint8_t i, *ptr; + + i = 24; + ptr = &RXF0SIDH; + do { + *ptr++ = 0; + } while (--i); + + i = 40; + ptr = &RXF6SIDH; + do { + *ptr++ = 0; + } while (--i); + + IPEN = 1; + + // clock + CCP1IP = 0; + CCPR1H = 0x0F; + CCPR1L = 0xA0; + CCP1CON = 0x0B; + CCP1IE = 1; + + CCP2IP = 0; + CCPR2H = 0xC3; + CCPR2L = 0x50; + CCP2CON = 0x0B; + C2TSEL = 1; + CCP2IE = 1; + + T1CON = 1; + T3CON = 0x31; + + // uart + UxRCIE = 1; + + stateLED(0); + + INTCON = 0xC0; +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..eb22cc6 --- /dev/null +++ b/main.c @@ -0,0 +1,73 @@ +#include +#include +#include "clock.h" +#include "can.h" +#include "frontend.h" +#include "elm327slcan.h" +#include "rbuf.h" + +extern void init(void); + +#define RSIZE 128 +#define SSIZE 248 + +char rBuf[RSIZE]; +char sBuf[SSIZE]; + +rbuf_t rRing = { rBuf, 0, 0, RSIZE }; +rbuf_t sRing = { sBuf, 0, 0, SSIZE }; + +uint8_t state = STATE_CONFIG; + +void __interrupt(high_priority) myIsr(void) { + uint8_t data; + + if (UxTXIE && UxTXIF) { + if (rbuf_pop_isr((rbuf_t *)&sRing, &data)) + UxTXREG = data; + else + cmdStop(); + } + + if (UxRCIF && UxRCIE) { + if (!rbuf_push_isr((rbuf_t *)&rRing, UxRCREG)) + SET_TX_OVFL(); + } +} + +int main(void) { + uint8_t ch, num, txtbuf[32]; + char line[LINE_MAXLEN]; + uint8_t linepos = 0; + + canmsg_t canmsg_buffer; + + init(); + + while (1) { + if (state != STATE_CONFIG) { + while (can_receive_message(&canmsg_buffer)) { + num = canmsg2ascii(&canmsg_buffer, &txtbuf); + if (num < rbuf_free_items((rbuf_t *)&sRing)) { + for (ch = 0; ch < num; ch++) { + rbuf_push((rbuf_t *)&sRing, txtbuf[ch]); + } + cmdSend(); + } + else SET_RX_OVFL(); + } + } + + while (rbuf_pop((rbuf_t *)&rRing, &ch)) { + if (ch == CR) { + line[linepos] = 0; + parseLine(line); + linepos = 0; + } else if (ch != LR) { + line[linepos] = ch; + if (linepos < LINE_MAXLEN - 1) linepos++; + } + } + } + return 0; +} diff --git a/out/ELM327SLCAN.hex b/out/ELM327SLCAN.hex new file mode 100644 index 0000000..6393e29 --- /dev/null +++ b/out/ELM327SLCAN.hex @@ -0,0 +1,313 @@ +:040000006FEF00F0AE +:060008004D8260EF08F0DC +:100018004D80D8CF0CF0E8CF0DF0E0CF0EF0A4B2B1 +:10002800A3A20BD0A492484A492A600E481805E1B9 +:10003800EA0E491802E1486A496AA4B4A3A443D065 +:10004800A4944A281F0B4A6E0F6A4A5004E04C5089 +:1000580002E1010E0F6E0FB002D08A9E01D08A8E87 +:10006800ABA202D08A9C2FD071AC0ED0010E106EBC +:100078004AA204D0040E4A6001D0106A10B002D01F +:100088008A9C01D01FD01FD089A40ED0010E116EFA +:100098004AA204D0080E4A6001D0116A11B002D0F9 +:1000A8008A9C01D00FD00FD089A60DD0010E126EF8 +:1000B8004AA204D00C0E4A6001D0126A12B002D0D3 +:1000C8008A9C01D08A8C0EC0E0FF0DC0E8FF0CC0EE +:1000D800D8FF4D901000B8EF08F0070E606F2ED1D2 +:1000E8004C5049E062D19E0E136E030E146E310E11 +:1000F8009DEC09F00D0E20D18C0E136E010E146EBE +:10010800270E9DEC09F0F6D78C0E136E010E146EB7 +:100118000F0E9DEC09F0EED78C0E136E010E146EC7 +:10012800070E9DEC09F0E6D78A0E136E010E146EC9 +:10013800070E9DEC09F0DED78A0E136E010E146EC1 +:10014800030E9DEC09F0D6D78A0E136E010E146EBD +:100158009DEC09F0CFD78C0E136E010E146E000EB5 +:100168009DEC09F0C7D78A0E136E010E146E000EAF +:100178009DEC09F0BFD73A50D96EDA6AD92ADF5018 +:10018800300AB1E0010AB8E0030ABEE0010AC4E09F +:10019800070ACAE0010AD0E0030AD6E0010ADBE058 +:1001A8000F0AE1E002D13AC01AF08DEC08F0C4D091 +:1001B8003AC01BF042EC09F0BFD03AC01AF009EC83 +:1001C80009F0BAD0410E136E560E146E18EC07F0F3 +:1001D800000901E18984010EE1EC08F0000EE1EC70 +:1001E80008F088D7410E136E760E146E18EC07F0DF +:1001F800000901E18984010EE1EC08F0080EE1EC48 +:1002080008F078D7410E136E4E0E146E18EC07F0F6 +:10021800000901E18984410E136E310E146E18EC49 +:1002280007F0000901E18984410E136E390E146E3E +:1002380018EC07F0000901E18984410E136E370EAE +:10024800146E18EC07F0000901E18984410E136E61 +:10025800360E146E18EC07F0000901E04BD78984BC +:1002680049D74C5001E0A1D06F6A6ECF3BF0E00E49 +:100278003B16FBE1B7EC09F0010E4C6E3BD74C5036 +:1002880001E093D0400E6F6E6E50E00B400AFCE127 +:10029800010E0CD0606F89D04C5001E086D0600E02 +:1002A8006F6E6E50E00B600AFCE1020E4C6E0D0E94 +:1002B800F1D74C507AE0800E6F6E6E50E00B800ADA +:1002C800FCE14C6A17D74C0470E13AC020F0C4EC4A +:1002D80004F000096AE03A50D96EDA6A5A0EDF6013 +:1002E80007D0410E136E5A0E146E18EC07F0B4D7EF +:1002F800410E136E7A0E146E18EC07F0ADD73AC0A3 +:100308001AF028EC09F018D03AC01BF082EC09F07A +:1003180013D03AC01AF06EEC09F00ED03AC01DF0B6 +:100328008AEC07F009D03AC01AF0C2EC07F004D002 +:100338003AC01AF058EC09F00001ACD73A50D96E1F +:10034800DA6ADF50420AF4E0010AB3E0050ADCE0A9 +:10035800010A01E12DD70B0A9FE0010AE4E0030A34 +:1003680001E150D7010A01E17CD71D0AACE0010A7E +:1003780001E1B6D6070AA7E0020A01E123D7010A7C +:1003880001E11BD70D0AC5E03C0AB9E00A0A01E100 +:1003980076D7010AC3E01F0A96E0010A01E103D7F4 +:1003A800070A91E0020A01E11DD7410E136E60C0F1 +:1003B80014F018EC07F0000901E189849D88120007 +:1003C80071AE000C92EC09F01550D96EDA6A0E0E77 +:1003D800D92613C0DEFF14C0DDFFE00E72166F5081 +:1003E800070B1009721260AE000C61CF1DF062CFCE +:1003F80021F021A692D01550D96EDA6A040ED926BA +:10040800DF821DC016F0176A186A196A150ED8908F +:100418001636173618361936E82EF9D71550D96E0C +:10042800DA6A16C0DEFF17C0DEFF18C0DEFF19C08B +:10043800DEFF21C016F0E00E161616C017F0186A77 +:10044800196A1A6A0D0ED8901736183619361A36E0 +:10045800E82EF9D71550D96EDA6A1750DE121850FF +:10046800DE121950DE121A50DE1221C016F0030EE9 +:10047800161616C017F0186A196A1A6A18C01AF000 +:1004880017C019F0186A176A1550D96EDA6A17502A +:10049800DE121850DE121950DE121A50DE1263CF27 +:1004A80016F0176A186A196A18C019F017C018F0F8 +:1004B80016C017F0166A1550D96EDA6A1650DE1291 +:1004C8001750DE121850DE121950DE121550D96E70 +:1004D800DA6A6450DE12000EDE12DE12DE1265CF1A +:1004E8001EF01550D96EDA6A050ED9261E500F0B6C +:1004F800DF6E1EC016F0060E176ED8901632172E35 +:10050800FCD7010E16161550D96EDA6A040ED926D4 +:10051800DF501618FE0B161841D01550D96EDA6A3E +:10052800040ED926DF9221C016F0163A0F0E1616C1 +:10053800010E16161550D96EDA6A040ED926DF5048 +:100548001618FE0B1618DF6E1DC016F0176A186A0B +:10055800196A030ED8901636173618361936E82E4B +:10056800F9D71550D96EDA6A16C0DEFF17C0DEFF5C +:1005780018C0DEFF19C0DEFF1550D96EDA6A2138BF +:10058800E842070BDE12000EDE12DE12DE121550F4 +:10059800D96EDA6A050ED92665500F0BDF6E155035 +:1005A800D96EDA6A040ED926DFB023D01550D96E79 +:1005B800DA6A050ED926DF50206E080E206401D0B5 +:1005C800206E660E1B6E0F0E1C6E1F6A0FD01BC0AE +:1005D800D9FF1CC0DAFF15501F24E16EE26A060E2F +:1005E800E126DFCFE7FF1B4A1C2A1F2A20501F5C89 +:1005F800EEE3609E00F07792010C1E6A1D6A1E50A1 +:1006080031E11350D96EDA6A040ED926DFA217D069 +:10061800010E1E6E1350D96EDA6A040ED926DFA0B9 +:1006280007D01450D96EDA6A520EDF6E142A05D03C +:100638001450D96EDA6A540EF8D71D2AD3D0060E94 +:100648001E6E1350D96EDA6A040ED926DFA005D0C3 +:100658001450D96EDA6A720EE8D71450D96EDA6A75 +:10066800740EE3D7090E1E6023D01E041A6E13C041 +:1006780019F01AC015F0D89015321550195CD96EBA +:10068800DA6A030ED926DF501F6E1AB09ED01F3AC1 +:100698000F0E1F169AD0370E01D0300E1F26145099 +:1006A800D96EDA6A1FC0DFFF142A1D2A1E2A9AD0C3 +:1006B8000A0E1E6028D01350D96EDA6A050ED926A4 +:1006C800DF501F6E0F0E1F16090E1F6402D0370E63 +:1006D80001D0300E1F261450D96EDA6A1FC0DFFF12 +:1006E800142A1D2A1350D96EDA6A050ED926DF504E +:1006F80007E01350D96EDA6A040ED926DFA0D6D7E0 +:100708001A0E1E6E6FD01A0E1E603DD01E50F60FC8 +:100718001B6E1BC015F0D890153213501524D96ED6 +:10072800DA6A060ED926DF501F6E1BB003D01F3AB7 +:100738000F0E1F160F0E1F16090E1F6402D0370E5C +:1007480001D0300E1F261450D96EDA6A1FC0DFFFA1 +:10075800142A1D2A1E2A1350D96EDA6A050ED926C4 +:10076800DF50020DF60E156E16681E50176E186AC9 +:100778001550172616501822F350171833E1F45065 +:100788001818BEE02FD04B5026E01E0E1E6023D056 +:100798001E50E60F1C6E020E1C600ED01350D96E50 +:1007A800DA6A0E0ED926DECF15F0DDCF16F016C0A8 +:1007B80015F0166A155006D01350D96EDA6A0E0E67 +:1007C800D926DF501F6E1CA062D70F0E1F16090E08 +:1007D8001F6463D760D71450D96EDA6A0D0EDF6EC6 +:1007E8001D2A1E681E2801E00AD71D501200E00EBF +:1007F8007216050E721260A606D0720660A603D0A5 +:10080800720660B6000C1350D96EDA6A050ED92646 +:10081800DF501F6E080E1F6401D01F6E1350D96E73 +:10082800DA6A040ED926DFA25FD01350D96EDA6ACD +:10083800DECF14F0DECF15F0DECF16F0DECF17F0E6 +:10084800160E05D0D8901732163215321432E82E0B +:10085800F9D71450616E1350D96EDA6ADECF14F0EE +:10086800DECF15F0DECF16F0DECF17F0110E05D073 +:10087800D8901732163215321432E82EF9D7030EF3 +:1008880014161350D96EDA6ADECF18F0DECF19F0DD +:10089800DECF1AF0DECF1BF00E0E05D0D8901B323B +:1008A8001A3219321832E82EF9D71850E00B141002 +:1008B8000809626E1350D96EDA6ADECF14F0DECF03 +:1008C80015F0DECF16F0DECF17F015C014F016C005 +:1008D80015F017C016F0176A1450636E1350D96ECE +:1008E800DA6ADF50646E23D01350D96EDA6ADECF2D +:1008F80014F0DECF15F0DECF16F0DECF17F0040EC1 +:1009080005D0D8901732163215321432E82EF9D79E +:100918001450616E1350D96EDA6ADF50146E050EEA +:10092800156ED8901436152EFCD71450626E1FC061 +:1009380065FF1350D96EDA6A040ED926DFA004D0F9 +:100948001F504009656E1AD01F5018E0660E1C6EC5 +:100958000F0E1D6E1E6A0FD013501E24D96EDA6A50 +:10096800060ED9261CC0E1FF1DC0E2FFDFCFE7FF5E +:100978001C4A1D2A1E2A1F501E5CEEE36086010CCD +:10098800010E226E2050D96EDA6A720EDE1807E068 +:100998002050D96EDA6A520EDE1801E0226A22B0BF +:1009A80002D02E9001D02E802050D96EDA6A5A0ECD +:1009B800DF6003D02E82080E02D02E92030E246E22 +:1009C8002028136E24C014F0260E156E96EC05F040 +:1009D800000901E1000C26C02AF027C02BF028C02E +:1009E8002CF029C02DF020502424216E2128136ECC +:1009F800010E146E260E156E96EC05F0000901E145 +:100A0800000C26C02FF02EB026D02FC023F0080EE1 +:100A1800236401D0236E256A1BD02550020D205077 +:100A2800F3242424020F136E020E146E260E156E84 +:100A380096EC05F0000901E1000C25C021F0060E36 +:100A4800212621502A0FD96EDA6A26C0DFFF252A0F +:100A58002350255CE2E32A0E136EFBEF03F0896A4C +:100A68000F015D6BF80E5C17480EA76E7D6A670E66 +:100A7800AF6E260EAC6E900EAB6EC40E8A6E3B0E39 +:100A8800936E949C800E6F6E6E50E00B800AFCE1B2 +:100A98000E017D6B200E706EB00E726EF869E30E5B +:100AA800F96FFA69FB69FC69E30EFD6FFE69FF697E +:100AB800180E136EE00E146E0E0E156E14C0D9FFCC +:100AC80015C0DAFFDF6A144A152A132EF7D7280E45 +:100AD800136E480E146E0E0E156E14C0D9FF15C095 +:100AE800DAFFDF6A144A152A132EF7D7D08EA5929B +:100AF8000F0EBD6EA00EBC6E0B0EBB6EA382A5942E +:100B0800C30E0F01526F500E516F0B0E506F99822A +:100B1800A384010ECD6E310EB16E9D8A8A8EC00EF1 +:100B2800F26EC00C1550D96EDA6ADE6ADE6ADE6AC9 +:100B3800DD6A53D01350D96EDA6ADF5001E1000C38 +:100B48001550D96EDA6A040ED890DE36DE36DE36F7 +:100B5800DD36D906D906E82EF7D71350D96EDA6AEA +:100B68002F0EDF6408D01350D96EDA6A3A0EDF60B0 +:100B780002D0D00E1BD01350D96EDA6A400EDF6453 +:100B880008D01350D96EDA6A470EDF6002D0C90E5A +:100B98000DD01350D96EDA6A600EDF64000C135062 +:100BA800D96EDA6A670EDF60000CA90E166E176838 +:100BB8001350D96EDA6ADF50186E196A1650182663 +:100BC800175019221550D96EDA6A1850DE261950B6 +:100BD800DE22000EDE22DE22132A14061428AAE1E1 +:100BE800010C0001E66B33EC05F04C5023E146D0D4 +:100BF800E80E136EC50E146E01EC03F00001E56FEC +:100C0800410E136EFAEC07F0E56113D0E76B0CD0D8 +:100C1800410E136EE751C50FD96EDA6ADF50146EB4 +:100C280018EC07F00001E72BE551E75DF1E39D883B +:100C380001D08984E80E156EE4EC01F000091EE08D +:100C4800D7D70D0E0001E7190CE1E651610FD96EF7 +:100C5800DA6ADF6A610E3A6E71EC00F00001E66B49 +:100C68000DD00A0EE7190AE0E651610FD96EDA6A6B +:100C7800E7C0DFFF630EE66101D0E62B3C0E136E82 +:100C8800E70E146E96EC06F00009AFE0DAD71C6E9A +:100C98001C1C0E01F86F131C166ED8901632D890D3 +:100CA8001632D8901632030E1616131CE00B1610C7 +:100CB800F96F15B0F987131C166E050E176ED890CC +:100CC8001636172EFCD71450186E196A181C1A6E8F +:100CD800191C1B6E1B341B321A321B341B321A327E +:100CE8001B341B321A321A501610FA6F141C166E67 +:100CF800050E176ED8901636172EFCD71550186E9D +:100D0800196A181C1A6E191C1B6E1B341B321A32F6 +:100D18001B341B321A321B341B321A321A5016106B +:100D2800FB6F12001350D96EDA6A030ED9261350DE +:100D3800E16EE26A020EE126DE50E61801E1000CDF +:100D48001350D96EDA6A030ED926DF50156E1528AE +:100D5800186E1350D96EDA6A040ED926DE50185C64 +:100D680001E3186A1350D96EDA6A030ED926DF50E8 +:100D7800156E1350D96EDA6ADECF16F0DDCF17F094 +:100D880015501624D96E000E1720DA6E1450E16E35 +:100D9800E26ADFCFE7FF1350D96EDA6A030ED9266D +:100DA80018C0DFFF010C0150D96EDA6A030ED9268C +:100DB8000150E16EE26A020EE126DE50E61801E11A +:100DC800000C0150D96EDA6A030ED926DF50036E83 +:100DD8000328066E0150D96EDA6A040ED926DE5051 +:100DE800065C01E3066A0150D96EDA6A030ED92659 +:100DF800DF50036E0150D96EDA6ADECF04F0DDCF22 +:100E080005F003500424D96E000E0520DA6E025056 +:100E1800E16EE26ADFCFE7FF0150D96EDA6A030EAE +:100E2800D92606C0DFFF010C1350D96EDA6A020E0C +:100E3800D926DF50156E1528186E1350D96EDA6A48 +:100E4800040ED926DE50185C01E3186A1350D96ED7 +:100E5800DA6A030ED9261850DE1801E1000C135087 +:100E6800D96EDA6A020ED926DF50156E1350D96E84 +:100E7800DA6ADECF16F0DDCF17F015501624D96EDA +:100E8800000E1720DA6E14C0DFFF1350D96EDA6A2D +:100E9800020ED92618C0DFFF010C0150D96EDA6A9C +:100EA800020ED926DF50036E0328066E0150D96E54 +:100EB800DA6A040ED926DE50065C01E3066A0150A0 +:100EC800D96EDA6A030ED9260650DE1801E1000C45 +:100ED8000150D96EDA6A020ED926DF50036E01502E +:100EE800D96EDA6ADECF04F0DDCF05F003500424B2 +:100EF800D96E000E0520DA6E02C0DFFF0150D96EF0 +:100F0800DA6A020ED92606C0DFFF010C4C5035E123 +:100F18001D28136E020E146E1E0E156E96EC05F04B +:100F280000092BE01D50030F136E020E146E220EE3 +:100F3800156E96EC05F0000920E01D50050F136EA4 +:100F4800020E146E260E156E96EC05F0000915E0DB +:100F58001D50070F136E020E146E2A0E156E96ECB6 +:100F680005F000090AE022C013F026C014F02AC0D8 +:100F780015F01E504BEC06F00D0C070C4C5035E1EB +:100F88001A28136E020E146E1B0E156E96EC05F0E1 +:100F980000092BE01A50030F136E020E146E1F0E79 +:100FA800156E96EC05F0000920E01A50050F136E37 +:100FB800020E146E230E156E96EC05F0000915E06E +:100FC8001A50070F136E020E146E270E156E96EC4C +:100FD80005F000090AE01FC013F023C014F027C071 +:100FE80015F01B5031EC08F00D0C070C1350D96E9E +:100FF800DA6A030ED9261350E16EE26A020EE12680 +:10100800DE50E65C19E31350D96EDA6A020ED9266F +:10101800DF50146E1350D96EDA6A040ED926DF50E9 +:10102800156E1450155C166E1350D96EDA6A030EDD +:10103800D926DF50162412001350D96EDA6A020E30 +:10104800D926DF50146E1350D96EDA6A030ED926EA +:10105800DF50156E1450155C1200186E18C0E0FEB3 +:1010680013C016F0D8901632D8901632D89016328F +:10107800030E16161350E00B16100E01E16F15B093 +:10108800E18713C016F0050E176ED8901636172E86 +:10109800FCD71438E8461F0B1610E26F14C016F080 +:1010A800050E176ED8901636172EFCD71538E84659 +:1010B8001F0B1610E36F1200E1CF07F0E2CF08F024 +:1010C800D9CF09F0DACF0AF09DB89EA80CD0410E0E +:1010D800016E0B0E026ED7EC06F0000903E00BC0A0 +:1010E800ADFF01D09D989EBA9DAA09D03C0E016E15 +:1010F800AECF02F051EC07F0000901E189860AC081 +:10110800DAFF09C0D9FF08C0E2FF07C0E1FF4D922E +:1011180011004C5028E11A28136E020E146E1B0E93 +:10112800156E96EC05F000091EE01A50030F136EB9 +:10113800020E146E1F0E156E96EC05F0000913E0F2 +:101148001A50050F136E020E146E230E156E96ECD0 +:1011580005F0000908E01FC013F023C014F01B506D +:101168009DEC09F00D0C070C640EF66E130EF76E6D +:10117800000EF86E00EE3CF010EE0AF00900F5CF14 +:10118800EEFFE550E150FAE102EE00F0F80EEE6AEB +:10119800E806FDE101EE00F0800EEE6AE806FDE1EA +:1011A80000EE46F0070EEE6AE806FDE14D904D921E +:1011B800000EF86E0001F5EF05F0196E19380F0BE7 +:1011C8001A6E090E1A6402D0370E01D0300E1A2694 +:1011D800410E136E1AC014F018EC07F0000901E173 +:1011E800898419500F0B1A6E090E1A6402D0370E33 +:1011F80001D0300E1A26410E136E1AC014F018ECE6 +:1012080007F00009D8B4898412001A28136E030E57 +:10121800146E1B0E156E96EC05F0000914E01A50BA +:10122800040F136E020E146E1F0E156E96EC05F069 +:10123800000909E01BC013F01CC014F01FC015F012 +:10124800A5EC09F00D0C070C1B6A1C6A1D6A1E6AC6 +:101258001A28136E010E146E1B0E156E96EC05F00F +:1012680000090BE0020E1B181C101D101E1005E1D2 +:10127800719C899489968A8C0D0C070C1B28136E17 +:10128800030E146E1D0E156E96EC05F000090BE0AA +:101298001DC013F01EC014F0ACEC09F01C6E1C50FD +:1012A800E1EC08F00D0C070C1A28136E020E146EF0 +:1012B8001B0E156E96EC05F000090BE0100E1B18BE +:1012C8001C101D101E1005E1800E6F6EF26A80EF73 +:1012D8003EF0070C1A28136E010E146E1B0E156EC5 +:1012E80096EC05F0000909E01B501C101D101E109B +:1012F800010ED8B4000E4B6E0D0C070C71507F0B0D +:101308001C6E410E136E460E146E18EC07F00009A1 +:1013180001E189841C50E1EC08F00D0CA39248C04F +:1013280046F049C047F0A38246C013F047C014F006 +:101338001200156E15C043FE13C044FE14C045FECE +:10134800120013C0E9FF14C0EAFF15C0EFFF120036 +:1013580013C0E9FF14C0EAFFEF50120000010000BB +:101368008000020000F8A392486A496AA38212002A +:00000001FF diff --git a/rbuf.c b/rbuf.c new file mode 100644 index 0000000..1a2403b --- /dev/null +++ b/rbuf.c @@ -0,0 +1,70 @@ +#include "rbuf.h" + +uint8_t rbuf_push(rbuf_t *c, uint8_t data) { + uint8_t next; + + next = c->head + 1; // next is where head will point to after this write. + if (next >= c->maxlen) + next = 0; + + if (next == c->tail) // if the head + 1 == tail, circular buffer is full + return 0; + + c->buffer[c->head] = data; // Load data and then move + c->head = next; // head to next data offset. + return 1; // return success to indicate successful push. +} + +uint8_t rbuf_push_isr(rbuf_t *c, uint8_t data) { + uint8_t next; + + next = c->head + 1; // next is where head will point to after this write. + if (next >= c->maxlen) + next = 0; + + if (next == c->tail) // if the head + 1 == tail, circular buffer is full + return 0; + + c->buffer[c->head] = data; // Load data and then move + c->head = next; // head to next data offset. + return 1; // return success to indicate successful push. +} + +uint8_t rbuf_pop(rbuf_t *c, uint8_t *data) { + uint8_t next; + + if (c->head == c->tail) // if the head == tail, we don't have any data + return 0; + + next = c->tail + 1; // next is where tail will point to after this read. + if(next >= c->maxlen) + next = 0; + + *data = c->buffer[c->tail]; // Read data and then move + c->tail = next; // tail to next offset. + return 1; // return success to indicate successful push. +} + +uint8_t rbuf_pop_isr(rbuf_t *c, uint8_t *data) { + uint8_t next; + + if (c->head == c->tail) // if the head == tail, we don't have any data + return 0; + + next = c->tail + 1; // next is where tail will point to after this read. + if(next >= c->maxlen) + next = 0; + + *data = c->buffer[c->tail]; // Read data and then move + c->tail = next; // tail to next offset. + return 1; // return success to indicate successful push. +} + +uint8_t rbuf_free_items(rbuf_t *c) { + if (c->head >= c->tail) { + uint8_t tmp = c->maxlen - c->head; + return (tmp + c->tail); + } + + return (c->tail - c->head); +} diff --git a/rbuf.h b/rbuf.h new file mode 100644 index 0000000..baed947 --- /dev/null +++ b/rbuf.h @@ -0,0 +1,33 @@ +#ifndef __RBUF_H_ +#define __RBUF_H_ + +#include + +typedef struct { + char *buffer; + uint8_t head; + uint8_t tail; + uint8_t maxlen; +} rbuf_t; + +extern uint8_t rbuf_free_items(rbuf_t *); + +/* + * Method: rbuf_pop + * Returns: + * 1 - Success + * 0 - Empty + */ +extern uint8_t rbuf_pop (rbuf_t *, uint8_t *); +extern uint8_t rbuf_pop_isr (rbuf_t *, uint8_t *); + +/* + * Method: rbuf_push + * Returns: + * 1 - Success + * 0 - Out of space + */ +extern uint8_t rbuf_push (rbuf_t *, uint8_t); +extern uint8_t rbuf_push_isr(rbuf_t *, uint8_t); + +#endif /* __RBUF_H_ */