CVS: examples/i2c_spy README.txt,NONE,1.1 hardware.h,NONE,1.1 i2c.h,NONE,1.1 i2c_slave.S,NONE,1.1 i2c_spy.c,NONE,1.1 main.c,NONE,1.1 makefile,NONE,1.1

Chris Liechti <[email protected]>
Newsgroups gmane.comp.hardware.texas-instruments.msp430.gcc.cvs
Message-ID <[email protected]>
Update of /cvsroot/mspgcc/examples/i2c_spy
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv14904/examples/i2c_spy

Added Files:
	README.txt hardware.h i2c.h i2c_slave.S i2c_spy.c main.c 
	makefile 
Log Message:
add another example. this one is a complete application that can be useful: a I2C spy

--- NEW FILE: README.txt ---
I2C-Spy
=======

Description
-----------
This is a simple I2C spy soft- and hardware. The software decodes
I2C data and sends a hexdump over the serial port.

The slave sends the data with 115200Baud over the serial port and the
the dump is in hex format, which means it is possible that the I2C bus would
deliver more data than that could be logged. The spy thus uses the clock
stretching feature of the I2C bus (holding SCL low).

It supports 3V .. 3.6V I2C bus voltages, depending on the used voltage
regulator. 5V busses are not supported directly. Up to 100kHz clock is
supported, higher bus frequencies do not work reliably.

Format
~~~~~~
The first two digits on a line are always the the address (7 bit, LSB
aligned). They are followed by the read/write bit: 'r' or 'w'. Then follow
the data bytes as hex (two digits per byte).

- '\\n' (newline) -> start condition (next two hex digits are address)
- 'r', 'w'        -> read or write bit in address
- hex digits      -> data
- '!'             -> last byte was not acknowledged

Examples
~~~~~~~~
Write to address 0x57: "hello world"::

    57w68656C6C6F20776F726C64

Read from address 0x57, 11 bytes. Note that it is normal that the
last byte is not acknowledged. That is the way the master signals
to the slave that it does not need any more data.::

    57r68656C6C6F20776F726C64!

A write to a nonexisting address, no ACK after address::
    
    56w!


Circuit
-------
::

                      Q1   _    8MHz
                        | | | |
                    +---+ | | +---+
                    |   | |_| |   |
                    |             |
              +-----+-------------+-----+
              |    XIN           XOUT   |
              |                         |
              |                         |
  SDA/I2C o---+ P2.0               P3.3 +---------------+ e
              |                         |               v
              |        MSP430F123       |     R1    b |/  V1
  SCL/I2C o---+ P2.1               P3.4 +----XXXXX----|   PNP
              |                         |     1k      |\
              |           IC1           |               \
              |                         |             c +-----------o TXD/RS232
              |    VCC             GND  |               |  
              +-----+---------------+---+               |   10k
                    |               |                   +---XXXXX---o RXD/RS232
                    |               |                       10k
                    |               |                      
                    |               |
  GND/I2C o--------------+----------+-----------+-------------------o GND/RS232
                    |    |   C1     |           |   C2
                    |  --+-- 1u     |         --+-- 10u
                    |  --+-- 5V +---+---+     --+-- 16V
                    |    |      |  GND  |       |              D1
                    +----+------+out  in+-------+-----------+--|<|--o RTS/RS232
                                |   3V  |                   | 
                                +-------+                   |  D2
                                 IC2                        +--|<|--o DTR/RS232
                                                         

The emitter of ``V1`` is connected to ``VCC``. To save a wire, ``P3.3`` is set
to output a logic high and can be used instead of ``VCC``.

All the values are not so critical, except the clock frequency as it is used
for the baudrate generator.

P2.5 is used as debug output. One could connect a LED+470R to GND.
The LED lights up during clock init and whenever an address is read on the
I2C bus. All other pins are set to output/GND. Do not connect them.

Material
~~~~~~~~

- ``IC1``: MSP430 with hardware USART, e.g. F123
- ``IC2``: 3V, 3.3V or 3.6V regulator, e.g. 78LC30. This assumes that the
  serial port does not deliver more that 12 volts.
- ``D1``, ``D2``: Diodes. Used to get the power from the RS232 control
  lines. As the total power consumtion of the circuit is well below 10mA
  it is also possible to use LEDs (The 78LC30 voltage regulator has a maximum
  input voltage of 10V. The LEDs 'burn' the overvoltage). They will light up
  brighter when more current flows. 
- ``C1``: 100n ... 1u (or larger) ceramic
- ``C2``: 10u or bigger elko, >16 Volts
- ``V1``: A PNP transistor, e.g. BC856
- ``R1``: 1k ... 10k
- ``R2``: 5k ... 20k
- ``Q1``: 8MHz crystal or resonator. Some types may require some small caps
  to GND (e.g. 22pf from ``XIN``->``GND`` and ``XOUT``->``GND``)

---------


. (C) 2005-09-27 [email protected]
--- NEW FILE: hardware.h ---
#ifndef HARDWARE_H
#define HARDWARE_H

//Hardware description
//http://mspgcc.sf.net
//chris <[email protected]>

#include <io.h>
#include <signal.h>

//PINS
//--- PORT1 ---
#define P1OUT_INIT      0
#define P1SEL_INIT      0
#define P1DIR_INIT      0xff

#define P1IE_INIT       0
#define P1IES_INIT      0

//--- PORT2 ---
#define SDA             BIT0
#define SCL             BIT1
#define LED             BIT5

#define P2OUT_INIT      0
#define P2SEL_INIT      0
#define P2DIR_INIT      BIT2|BIT3|BIT4|LED

#define CAPD_INIT       0

#define P2IE_INIT       0
#define P2IES_INIT      0

#define I2CIN  P2IN
#define I2CDIR P2DIR

//--- PORT3 ---
#define TX              BIT4
#define RX              BIT5

#define P3OUT_INIT      TX|BIT3
#define P3SEL_INIT      TX//|RX
#define P3DIR_INIT      TX|RX|BIT1|BIT2|BIT3|BIT6|BIT7


////
#define IE1_INIT        0
#define IE2_INIT        0
#define ME1_INIT        0
#define ME2_INIT        0


#define WDTCTL_INIT     WDTPW|WDTHOLD


#endif //HARDWARE_H

--- NEW FILE: i2c.h ---
#ifndef I2C_H
#define I2C_H

void i2c_slave(void *(*initial_state_function)(void));
void i2c_spy(void);

#endif //I2C_H

--- NEW FILE: i2c_slave.S ---
#include "hardware.h"

/*
Highly optimized I2C slave/spy code for the MSP430. It can cope with up to
100kHz bus clock (SCL) with 5MHz MCLK.

                        sda_edge
         start                              stop
           |                                 |
           v    v          v                 v
        ___      __________                   ___
    SDA    |    |          |                 |
           |____|          |_____..._________|
        ______      _____       _..._       _____
    SCL       |    |     |     |     |     |
              |____|     |_____|     |_____|
              ^    ^     ^     ^     ^     ^
              |    |     |     |     |     |  
              |  sh_in   |   sh_in   |   sh_in
            sh_out     sh_out      sh_out   
                                            
                        scl_edge

A state processing function is called on sh_out. This function can decode
the received bits in i2c_shift_read and modify SDA in case its a I2C slave.
The function must return a pointer to the next state function (called on
next sh_out).
For efficiency reasons, the state function pointer is kept in R15, which is
also the register where the return value is passed, so no copying of the
state function pointer is needed.
The SCL line is held low while the state function is executed, so it's
allowed to take some time for the processing, but the I2C bus is locked
during this time which means it should not take too much time.

To save some CPU cycles are the port (PxIN) the value that is read, SDA and
SCL bitmasks are cached in registers.

Optional, currently commented out code:

    Additionaly is the USART RX register and the value for CTRL+C cached in
    registers, so that it can be quickly polled. The slave loop is exited
    if CTRL+C is received. Interrupts are disabled, which means that the
    USART register has to be polled.
*/

// void i2c_slave(void *(*initial_state_function)(void));
// initial state function is passed in R15
// state functions returns the new state in R15

#define ctrl_c  R4              // c code must not use frame pointer!
#define sdamask R8
#define sclmask R9
#define sdascl  R10
#define last    R11

.global i2c_slave
i2c_slave:

        push    r2                      ; save GIE state
        dint                            ; disable all interrupts as the slave/spy is pretty time sensitive
        push    R4
        push    R5
        push    R6
        push    R7
        push    R8
        push    R9
        push    R10
        push    R11
        mov     #SDA, sdamask           ; prepare mask for quick access
        mov     #SCL, sclmask           ; prepare mask for quick access
        mov     #SDA|SCL, R7            ; prepare mask for quick access
        mov     #I2CIN, R6              ; prepare address for quick access
        //~ mov     #U1RXBUF, R5            ; prepare address for quick access
        //~ mov     #0x03, ctrl_c
        clr     sdascl
        jmp     loop

scl_edge:
        bit     sclmask, sdascl         ; pos or neg edge
        jnz     sh_in                   ; pos edge -> jump
sh_out: bis.b   sclmask, &I2CDIR        ; hold SCL down (clock stretching)
        call    R15                     ; process data in state function
        bic.b   sclmask, &I2CDIR        ; release SCL
        jmp     loop
sh_in:  bit.b   sdamask, sdascl         ; check the current level of SDA -> carry bit
        rlc.b   i2c_shift_read          ; shift in data

loop:
        //~ cmp.b   @R5, ctrl_c             ; check if a ctrl+C is in the receive buffer
        //~ jeq     exit                    ; if so -> exit
        mov     sdascl, last            ; save last state of SDA|SCL
        mov.b   @R6, sdascl             ; read in new state of SDA|SCL
        and     R7, sdascl              ; required if other bits are not zero
        xor     sdascl, last            ; calculate edges
        jz      loop                    ; no edge -> loop
        bit     sclmask, last           ; edge on SCL?
        jnz     scl_edge                ; yes -> jump
sda_edge:
        bit     sclmask, sdascl         ; check level of SCL
        jz      loop                    ; SDA is allowed to change if SCL is low
        bit     sdamask, sdascl         ; pos or neg edge
        jnz     stop                    ; pos edge -> jump
start:  mov     i2c_start_state, R15
        jmp     loop
stop:   mov     i2c_stop_state, R15
        jmp     loop

exit:
        pop     R11
        pop     R10
        pop     R9
        pop     R8
        pop     R7
        pop     R6
        pop     R5
        pop     R4
        reti                            ; restore r2, return

--- NEW FILE: i2c_spy.c ---
#include <stdio.h>
#include <mspgcc/util.h>
#include "hardware.h"
#include "i2c.h"

/**
 * I2C software spy.
 * 
 * The spy uses clock stretching to cope with the problem that on a 100kHz
 * I2C bus, data arrives faster than it is possible to dump over a 115200
 * baud serial console. This means:
 * 
 *             attaching the spy slows down the bus!
 * 
 * 
 * The log format is  "aaMxxxxxxxxxx" where:
 * 
 *   aa: i2c slave address in hex (0 ... 0x7F)
 *   M:  'r'/'w'  read or write
 *   xx: hex dump of data
 * 
 *   '!' -> the previous byte has not been acknowledged
 *          note that is normal that the last byte in a read is not
 *          acknowledged. It would be an error if it was not.
 * 
 * Each start condition generates a newline, stop conditions are not logged.
 * 
 * @file i2c_spy.c
 */

void *(*i2c_start_state)(void);
void *(*i2c_stop_state)(void);
unsigned char i2c_shift_read;

/*
 Write 0x55 at 0x57 -> 10101110, 01010101
     S   1   0   1   0   1   1   1   W  ACK   0   1   0   1   0   1   0   1  ACK   P
    _   ___     ___     ___________              ___     ___     ___     _______    _
SDA  | |   |   |   |   |           |       |    |   |   |   |   |   |   |       |  |
     |_|   |___|   |___|           |_______|____|   |___|   |___|   |___|       |__|
    __   _   _   _   _   _   _   _   _   _    _   _   _   _   _   _   _   _   _    __
SCL   | | | | | | | | | | | | | | | | | | |  | | | | | | | | | | | | | | | | | |  |
      |_| |_| |_| |_| |_| |_| |_| |_| |_| |__| |_| |_| |_| |_| |_| |_| |_| |_| |__|
     ^^                           ^   ^   ^                ^               ^   ^
     ||                           |   |   |                |               |   spy_data_0
     |spy_address................ |   |   spy_data_0...... spy_data_1..... spy_data_ack
     spy_start                    |   spy_data_ack
                                  spy_readwrite
*/

// the following functions implement a statemachine. that evaluating the state
// is as fast as possible, a function pointer is used as state variable.
// the function is then called and the function has to return the function
// pointer for the next state.

void *spy_idle(void) {
    return spy_idle;
}

static void *spy_data_D7(void);         // fwd decl

/** handle acknowledge bit, next expect data bit*/
static void *spy_data_ack(void) {
    // only log "not acknowledges"
    if (i2c_shift_read & 1) {
        putchar('!');
    }
    return spy_data_D7;
}
/** last data bit, next expect acknowledge*/
static void *spy_data_D0(void) {
    putchar(HEX_DIGITS[i2c_shift_read & 0xf]);
    return spy_data_ack;
}
static void *spy_data_D1(void) { return spy_data_D0; }
static void *spy_data_D2(void) { return spy_data_D1; }
static void *spy_data_D3(void) { return spy_data_D2; }
static void *spy_data_D4(void) {
    putchar(HEX_DIGITS[i2c_shift_read & 0xf]);
    return spy_data_D3;
}
static void *spy_data_D5(void) { return spy_data_D4; }
static void *spy_data_D6(void) { return spy_data_D5; }
static void *spy_data_D7(void) { return spy_data_D6; }


/** read/write bit, next expect acknowledge*/
static void *spy_address_RW(void) {
    if (i2c_shift_read & 1) {
        putchar('r');
        return spy_data_ack;
    } else {
        putchar('w');
        return spy_data_ack;
    }
}

/** last address bit, next expect read/write bit*/
static void *spy_address_A0(void) {
    P2OUT &= ~LED;
    putchar(HEX_DIGITS[i2c_shift_read & 0xf]);
    return spy_address_RW;
}
static void *spy_address_A1(void) { return spy_address_A0; }
static void *spy_address_A2(void) { return spy_address_A1; }
static void *spy_address_A3(void) { return spy_address_A2; }
static void *spy_address_A4(void) {
    putchar(HEX_DIGITS[i2c_shift_read & 0x7]);
    return spy_address_A3;
}
static void *spy_address_A5(void) { return spy_address_A4; }
static void *spy_address_A6(void) { return spy_address_A5; }

/** start condition is detected, next expect slave address */
void *spy_start(void) {
    putchar('\r');                  // newline for each start condition
    putchar('\n');                  // newline for each start condition
    P2OUT |= LED;
    return spy_address_A6;
}




/** Initialize the i2c spy statemachine */
void i2c_spy(void) {
    i2c_start_state = spy_start;
    i2c_stop_state = spy_idle;
    i2c_slave(spy_idle);
}

--- NEW FILE: main.c ---
#include "hardware.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#include <mspgcc/util.h>        //delay()
#include "i2c.h"

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// configure target pins for i2c 
static void i2c_setup(void) {
    P2SEL &= ~(BIT0|BIT1);
    P2DIR &= ~(BIT0|BIT1);
    P2OUT &= ~(BIT0|BIT1);
}

// disable target pins
static void i2c_teardown(void) {
    P2OUT = P2OUT_INIT;
    P2SEL = P2SEL_INIT;
    P2DIR = P2DIR_INIT;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int putchar(int character) {
    while (!(UTCTL0 & TXEPT)) {}        // wait util tx buffer is free
    TXBUF0 = character;                 // send character
    return 1;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


int main(void) {
    WDTCTL = WDTCTL_INIT;               // Init watchdog timer
    
    P1OUT = P1OUT_INIT;                 // Init output data of port1
    P1SEL = P1SEL_INIT;                 // Select port or module -function on port1
    P1DIR = P1DIR_INIT;                 // Init port direction register of port1
    P1IES = P1IES_INIT;                 // init port interrupts
    P1IE  = P1IE_INIT;
    
    P2OUT = P2OUT_INIT;                 // Init output data of port2
    P2SEL = P2SEL_INIT;                 // Select port or module -function on port2
    P2DIR = P2DIR_INIT;                 // Init port direction register of port2
    P2IES = P2IES_INIT;                 // init port interrupts
    P2IE  = P2IE_INIT;
    CAPD  = CAPD_INIT;                  // Init input buffers on port2 pins
    
    P3OUT = P3OUT_INIT;                 // Init output data of port3
    P3SEL = P3SEL_INIT;                 // Select port or module -function on port3
    P3DIR = P3DIR_INIT;                 // Init port direction register of port3

    IE1 = IE1_INIT;
    IE2 = IE2_INIT;
    //~ ME1 = ME1_INIT;
    ME2 = ME2_INIT;
    
    P2OUT |= LED;
    BCSCTL1 |= XTS;                     // select LF oscillator, enable XT2
    // Wait for crystal
    do {
        IFG1 &= ~OFIFG;
        delay(255);                     // Time for flag to set
    } while (IFG1 & OFIFG);
    BCSCTL2 = SELM_3 ;                  // Select ACLK for MCLK
    BCSCTL1 |= DIVA_DIV4;               // ACLK/4
    P2OUT &= ~LED;
    
    TACTL = 0;                          // stop

    // Init of USART1 Module, 115200,8,N,1
    U0CTL = CHAR|SWRST;                 // init&reset
    U0CTL &= ~SWRST;                    // release reset
    U0TCTL = SSEL_ACLK|TXEPT;
    U0RCTL = 0;
    UBR00=0x11; UBR10=0x00; UMCTL0==0x52; // UART0 2000000Hz 114942bps
    U0ME |= UTXE0;                        // Enable USART0 transmiter and receiver (UART mode)

    delay(10000);
    printf("\r\nI2C-spy (C) 2005 [email protected]\r\n");       // show message
    // I2C spy
    i2c_setup();                        // configure pins
    i2c_spy();                          // run spy
    i2c_teardown();                     // configure pins
    printf("\n");                       // ensure that the prompt is shown on a new line
}

--- NEW FILE: makefile ---
# makfile configuration
NAME            = i2c_spy
CSOURCES        = main.c i2c_spy.c
ASOURCES        = i2c_slave.S
CPU             = msp430x123

#~ BSLOPT = -c COM4

ASFLAGS         = -mmcu=${CPU} -D_GNU_ASSEMBLER_ -I .
CFLAGS          = -mmcu=${CPU} -O2 -Wall -g --std=gnu99 -I .
LDFLAGS         = -lmspgcc

#switch the compiler (for the internal make rules)
CC              = msp430-gcc
AS              = msp430-gcc

OBJECTS         = ${CSOURCES:.c=.o} ${ASOURCES:.S=.o}

.PHONY: all FORCE clean download download-jtag download-bsl dist

#all should be the first target. it's built when make is runwithout args
all: ${NAME}.elf ${NAME}.a43 ${NAME}.lst dependencies.d

#confgigure the next line if you want to use the serial download
download: download-jtag
#~ download: download-bsl

#additional rules for files
${NAME}.elf: ${OBJECTS}
	${CC} -mmcu=${CPU} -o $@ ${OBJECTS} $(LDFLAGS)

${NAME}.a43: ${NAME}.elf
	msp430-objcopy -O ihex $^ $@

${NAME}.lst: ${NAME}.elf
	msp430-objdump -dSt $^ >$@
	@echo "----- RAM/Flash Usage -----"
	msp430-size $^

download-jtag: all
	msp430-jtag -e ${NAME}.elf

download-bsl: all
	msp430-bsl $(BSLOPT) --speed=38400 -e ${NAME}.elf

clean:
	rm -f ${NAME}.elf ${NAME}.a43 ${NAME}.lst ${OBJECTS} dependencies.d

#dummy target as dependecy if something has to be build everytime
FORCE:

#project dependencies
dependencies.d:
	$(CC) -MM ${CFLAGS} ${CSOURCES} > dependencies.d
ifdef ASOURCES
	$(CC) -MM ${ASFLAGS} ${ASOURCES} >> dependencies.d
endif

-include dependencies.d


-------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc. Do you grep through log files
for problems?  Stop!  Download the new AJAX search engine that makes
searching your log files as easy as surfing the  web.  DOWNLOAD SPLUNK!
http://ads.osdn.com/?ad_id=7637&alloc_id=16865&op=click
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.