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