You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
3.4 KiB

#include <avr/io.h>
#include <stdint.h>
#include <math.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "main.h"
#include "spi.h"
/* SPI framework.
*
* currently alpha, uses interrupts, single master/single slave operation
*/
uint8_t readbuf[5];
uint8_t writebuf[5];
uint8_t pos;
void spi_init(){
uint8_t spcr, spsr, d;
/* calculate clock divisor,
* gcc with optimize will do that calculation compile-time. */
uint8_t spi_clock_divisor(){
double d;
d = F_CPU / SPI_BAUDRATE;
d = ceil((log(d)/log(2))*0.95); // clock needs dividing by 2^d
// the 0.95 to avoid ceil issues
return d-1; // the -1 because minimum divisor is /2
}
/* do pseudo-calculation in preprocessor, to get warnings
* we right-shift 8 times; if result >0, d will be >7
* so output warning in that case
*/
#define D_VAL (F_CPU / SPI_BAUDRATE)
#if ((D_VAL >> 8) > 0 )
#warning "spi baudrate too slow, cannot be set (clamped to minimum)"
#endif
d = spi_clock_divisor();
if(d>7){
// warning was (hopefully) output previously by cpp
d=7;
}
//DDRs setzen
#ifdef SPI_MASTER
DDRB |= _BV(5) | _BV(3) | _BV(2); //MOSI, SCK, unused SS
DDRB &= ~( _BV(4)); //MISO
SPI_SSDDR |= _BV(SPI_SS_PIN); // actually used SS
#else //SPI_MASTER
DDRB |= _BV(4); //MISO
DDRB &= ~( _BV(5) | _BV(3) | _BV(2)); //MOSI, SCK, SS
#endif //SPI_MASTER
spsr = 0;
spsr |= (d & 1) ? 0 : _BV(SPI2X);
spcr = 0 | _BV(SPE);
spcr |= (d & 2) ? _BV(SPR0) : 0;
spcr |= (d & 4) ? _BV(SPR1) : 0;
#ifdef SPI_MASTER
spcr |= _BV(MSTR);
#endif
SPSR = spsr;
SPCR = spcr;
#ifndef SPI_MASTER
SPCR |= _BV(SPIE); //enable interrupts
PCMSK0 |= _BV(2);
PCICR |= _BV(0);
#endif
pos = 0;
}
void spi_mst_start_packet(){
#ifdef SPI_MASTER
_delay_us(2*1e6/SPI_BAUDRATE);
SPI_SSOUT &= ~(_BV(SPI_SS_PIN));
_delay_us(2*1e6/SPI_BAUDRATE);
#endif
}
void spi_mst_end_packet(){
#ifdef SPI_MASTER
_delay_us(2*1e6/SPI_BAUDRATE);
SPI_SSOUT |= _BV(SPI_SS_PIN);
_delay_us(2*1e6/SPI_BAUDRATE);
#endif
}
#define SPI_WAIT while(!(SPSR & _BV(SPIF))){;}
void spi_mst_write(uint8_t len, uint8_t *data){
while(len>0){
SPDR = *data++;
len--;
SPI_WAIT;
}
}
void spi_mst_read(uint8_t len, uint8_t *data){
while(len>0){
SPDR = 0;
len--;
SPI_WAIT;
*data++ = SPDR;
}
}
void spi_mst_write_read(uint8_t len, uint8_t *data){
while(len>0){
SPDR = *data;
len--;
SPI_WAIT;
*data++ = SPDR;
}
}
void spi_mst_packet_delay(){
// wait for 2 byte
_delay_us(16*1e6/SPI_BAUDRATE);
}
#ifndef SPI_MASTER
void spi_sla_handle_packet(){
// TODO: make slave not hangup in case of partial read
uint8_t opcode, addr;
uint16_t data, newdata;
writebuf[0] = 0; // generate invalid replies, so no inconsistent packets
// are sent.
opcode = readbuf[0];
addr = readbuf[1];
data = (readbuf[2]<<8) | readbuf[3];
newdata = spi_proto_slaveaction(opcode, addr, data);
writebuf[1]=addr;
writebuf[2]=(newdata>>8)&0xff;
writebuf[3]=(newdata)&0xff;
writebuf[0]=opcode; // set opcode last to validate packet (it's a kind of locking)
}
ISR(SPI_STC_vect){
SPDR = writebuf[pos];
readbuf[pos] = SPDR;
pos++;
if(pos>=5){
pos=4;
}
}
ISR(PCINT0_vect){ // SS-line changed
if(SPI_SSIN & _BV(SPI_SS_PIN)){ // SS-line was actually released
pos=0;
SPCR &= ~(_BV(SPIE));
sei();
spi_sla_handle_packet();
SPCR |= _BV(SPIE);
}
}
#endif //not SPI_MASTER