Compare commits

..

No commits in common. "c1f87a11da7b86dc4363fe04375dc8ec4a804a0d" and "71a82cfe2c6144995e50f3e5c8914f528e418194" have entirely different histories.

5 changed files with 37 additions and 736 deletions

1
.gitignore vendored
View file

@ -1,5 +1,4 @@
*.d
*.s
*.eeprom
*.hex
*.map

View file

@ -4,7 +4,6 @@ CPU_ECRIS_REMOTE=atxmega128a1
CPU_lcd=atmega16
CPU_sologse=atmega644
CPU_chaos=atmega32m1
CPU_leia=atmega32m1
pMCU-atmega16m1 = m16
pMCU-atmega32m1 = m32m1
@ -13,19 +12,20 @@ pMCU-atmega644 = m644
MCU = $(CPU_$*)
CC=avr-gcc -Wall -Wno-parentheses -MMD -std=c99 -O2 -mmcu=$(MCU) \
CC=avr-gcc -Wall -MMD -std=c99 -O3 -mmcu=$(MCU) \
-funsigned-char \
-funsigned-bitfields \
-fpack-struct \
-fshort-enums
-fshort-enums \
-fverbose-asm
CFLAGS = $($*_CFLAGS) $(DEBUG)
%.s: %.c revision.h
$(CC) -S $(CFLAGS) -fverbose-asm $<
$(CC) -S $(CFLAGS) $<
%.o: %.c revision.h
$(CC) -g -c $(CFLAGS) $<
$(CC) -gstabs -c $(CFLAGS) $<
revision.h: .git
git log --pretty='#define Revision 0x%h' --abbrev=8 HEAD^! > revision.h+
@ -72,12 +72,6 @@ chaos.o: ads8688.h ltc1655.h spi_slave.h tick.h hvosc.h hvled.h
chaos.s: ads8688.h ltc1655.h spi_slave.h tick.h hvosc.h hvled.h
chaos.hex: chaos.eeprom
lfuse_leia=0xef
leia_CFLAGS = -I.
leia.o: spi_slave.h
leia.s: spi_slave.h
leia.hex: leia.eeprom
%.lfuse:
$(AD) -B 5 -U lfuse:w:$(lfuse_$*):m
@ -87,7 +81,7 @@ leia.hex: leia.eeprom
clean:
rm -f *.hex *.o *.s *.map *.elf *.d
VPATH = .:LCD/lcd_slave_sync:sologse:chaos:leia
VPATH = .:LCD/lcd_slave_sync:sologse:chaos
lcd_CFLAGS = -I. -DDEBUG
lcd.o: lcd.c spi_slave.h lcd_routines.h lcd_routines.c uart_atmega16.c

View file

@ -303,6 +303,33 @@ void hv_led_on()
char spi_was_busy;
static inline
void spi_busy_init()
{
PCMSK2 |= 8; // PIND3 PCINT19
while (!(PIND & 8));
PCIFR = 4;
}
static inline
unsigned char spi_busy()
{
// Return true if the SSEL pin became low
unsigned char f = PCIFR & 4;
PCIFR = 4;
spi_was_busy = f && !(PIND & 8);
return spi_was_busy;
}
static inline
void clear_spi_busy()
{
const unsigned char *busy_msg = (const unsigned char *) "\xff\xff\xff" "EEY";
if (spi_was_busy && !(PIND & 8))
spi_slave_Tx(busy_msg, 6);
spi_was_busy = 0;
}
__attribute__((noinline))
void hv_safe()
{
@ -362,7 +389,7 @@ int main()
}
while (1) {
sei();
clear_spi_busy("\xff\xff\xff" "EEY");
clear_spi_busy();
unsigned char cmd[3];
unsigned char resp[3];
unsigned char c;

View file

@ -1,602 +0,0 @@
// irena spi slave to drive two A5985 stepper motor controllers
// ATmega32M1
//
// SPI: slave, three bytes cmd, three bytes resp. main thread.
// PCINT2: disable interrupts when SSEL toggles. Allows the SPI to work undisturbed.
// TIMER1: Motor step period, 23µs resolution, 1.5s range
// OCR1A: interrupt: assert STEP
// OCR2B: interrupt: deassert STEP, RESET
// ADC: Three NTCs ADC8,9,10, Iprim ADC3, internals.
// Interrupt stores conversionresults and advances channels.
// DAC: Motor current reference.
// TIMER0: ADC conversion cadence, 92µs resolution, range 23ms range.
// Interrupt: DAC ramp ticks
// PB[3:6] ENABLE outputs.
// PC[0:3] DIR, MSn outputs, step direction and microstepping
// PD[2,4] STEP outputs
// PD[0,1] inputs: end switch status
// PD[5,7] inputs: motor controller fault
#include <stdint.h>
#include <string.h>
#include "revision.h"
const char revision[] = Id;
#include <avr/interrupt.h>
#include <avr/sleep.h>
#define SPI_Rx_SEI
#include "spi_slave.h"
#define upcase(c) (!((c)&0x20))
#define LIMIT_PORT PIND
#define STEP_PORT PORTD
#define DIR_PORT PORTC
#define RESET_PORT PORTB
// PORT D
#define FAULT1 (1<<7)
#define FAULT2 (1<<5)
#define LIMIT1 (1<<0)
#define LIMIT2 (1<<1)
#define STEP1 (1<<4)
#define STEP2 (1<<2)
// PORT B
#define MISO (1<<0)
#define RESET (1<<3)
#define ENABLE (1<<4)
#define LEDON (1<<5)
#define SLEEP (1<<6)
// PORTC
#define DIR (1<<0)
#define MS1 (1<<3)
#define MS2 (1<<2)
#define MS3 (1<<1)
#define MAGIC 0x1e1a
#define VERSION 1
struct conf {
unsigned int magic; // 0
unsigned char version; // 2
uint8_t flags; // 3
uint16_t period; // 4
uint16_t slen; // 6
uint8_t lmask; // 8
uint8_t lval; // 9
uint8_t step; // 10
uint8_t enable; // 11
uint8_t reset; // 12
uint8_t dir; // 13
uint16_t n_steps; // 14
uint16_t dac; // 16
uint16_t dac_ramp; // 18
uint16_t dac_step; // 20
uint8_t adc_idx; // 22
uint8_t adc_incr; // 23
uint8_t adc_period; // 24
uint8_t pad[7]; // 25
uint8_t adc_ch[16]; // 32
uint8_t tail[16]; // 48
} conf; // 64
enum {
FLAG_WDT = 1,
};
#define STEP_RESOLUTION 23148L // ns
#define STEP_NS(ns) (((ns)+STEP_RESOLUTION/2)/STEP_RESOLUTION)
#define TICK_RESOLUTION 92593L // ns
#define TICK_NS(ns) (((ns)+TICK_RESOLUTION/2)/TICK_RESOLUTION)
__attribute__((section(".eeprom")))
const struct conf runcon = {
.magic = MAGIC,
.version = VERSION,
.period = STEP_NS(100000000),
.slen = STEP_NS(25000),
.lmask = LIMIT1 | LIMIT2 | FAULT1 | FAULT2,
.lval = LIMIT1 | LIMIT2 | FAULT1 | FAULT2,
.enable = SLEEP | RESET,
.reset = RESET,
.adc_ch = {3, 8, 9, 10, 11+0x80, 12+0x80, 17, 18,},
.adc_incr = 8, // two conversions per channel
.adc_period = TICK_NS(1000),
.dac_step = 0x20,
};
void stepper_init()
{
TIMSK1 = 0;
// 16-bit Timer 1 PWM by OCR1A, prescaled by 256
TCCR1A = 1<<WGM10 | 1<<WGM11 ;
TCCR1B = 1<<WGM12 | 1<<WGM13 | 4<<CS10;
OCR1AH = conf.period >> 8;
OCR1AL = conf.period;
OCR1BH = conf.slen >> 8;
OCR1BL = conf.slen;
RESET_PORT = conf.enable & ~conf.reset;
DIR_PORT = conf.dir;
STEP_PORT = 0;
DDRB = MISO | RESET | ENABLE | SLEEP | LEDON;
DDRD = STEP1 | STEP2;
DDRC = DIR | MS1 | MS2 | MS3;
}
static inline
void stepper_start(uint8_t reset)
{
RESET_PORT = conf.enable & ~reset;
DIR_PORT = conf.dir;
TCNT1H = 0;
TCNT1L = 0;
TIMSK1 = (1<<OCIE1B);
}
static inline
void stepper_stop()
{
TIMSK1 = 0;
}
static inline
char stepper_status()
{
return TIMSK1;
}
ISR(TIMER1_COMPA_vect)
{
if (!conf.n_steps || (LIMIT_PORT & conf.lmask) != conf.lval) {
STEP_PORT = 0;
TIMSK1 = 0;
return;
}
conf.n_steps--;
STEP_PORT = conf.step;
}
ISR(TIMER1_COMPB_vect)
{
STEP_PORT = 0;
RESET_PORT = conf.enable;
TIMSK1 = TIFR1 = (1<<OCIE1B) | (1<<OCIE1A);
}
ISR(TIMER0_COMPB_vect)
{
uint16_t d = conf.dac;
if (d == conf.dac_ramp) {
TIMSK0 = 0;
return;
}
uint16_t s = conf.dac_step;
if (d > conf.dac_ramp)
s = -s;
d += s;
DACL = d;
DACH = d>>8;
conf.dac = d;
}
static
void dac_set(uint16_t d)
{
TIMSK0 = 0;
DACL = d;
DACH = d>>8;
DACON = 1<<DALA | 1<<DAEN | 1<<DAOE;
conf.dac = conf.dac_ramp = d;
}
static
void dac_ramp(uint16_t d)
{
uint16_t s = conf.dac_step;
if (!s) {
dac_set(d);
return;
}
s = ~(s & (s-1));
d &= s;
TIMSK0 = 0;
DACON = 1<<DALA | 1<<DAEN | 1<<DAOE;
conf.dac = DAC & s;
conf.dac_ramp = d;
if (d != conf.dac)
TIMSK0 = 1<<OCIE0B;
}
static
void adc_init()
{
TCCR0A = 2 << WGM00;
TCCR0B = 5 << CS00;
DIDR0 = 1<<ADC3D;
DIDR1 = 1<<ADC8D | 1<<ADC9D | 1<<ADC10D;
ADCSRB = 1<<AREFEN | 2<<ADTS0;
ADCSRA = 1<<ADEN | 1<<ADIF | 6<<ADPS0;
}
static
void adc_start(uint8_t i)
{
i &= 15;
conf.adc_idx = i<<4;
OCR0A = OCR0B = conf.adc_period;
if (!conf.adc_ch[i])
return;
ADMUX = 1<<REFS0 | 1<<ADLAR | conf.adc_ch[i];
ADCSRA = 1<<ADSC | 1<<ADATE | 1<<ADIF | 1<<ADIE | 6<<ADPS0;
}
static inline
void adc_stop()
{
ADCSRA = 1<<ADIF | 6<<ADPS0;
}
uint16_t adc[16];
ISR(ADC_vect)
{
uint8_t idx = conf.adc_idx;
uint8_t i = idx >> 4;
uint16_t a = ADCL << 8;
a |= ADCH & 0xc0 | ADMUX & 0x1f | (ADMUX>>(REFS1-5)) & 0x20;
adc[i] = a;
idx += conf.adc_incr;
i = idx >> 4;
if (!conf.adc_ch[i])
i = idx = 0;
if (!conf.adc_ch[i])
adc_stop();
else
ADMUX = 1<<REFS0 | 1<<ADLAR | conf.adc_ch[i];
conf.adc_idx = idx;
}
static void conf_init()
{
cli();
adc_init();
adc_start(conf.adc_idx>>4);
dac_set(conf.dac_ramp);
stepper_init();
}
// Configuration in EEPROM
// Write EEPROM in interrupt.
static uint8_t eewr_n; // number of bytes to write
static uint16_t eewr_a; // EEPROM conf base address
ISR(EE_READY_vect)
{
uint8_t n = eewr_n;
while (n) {
n--;
EEAR = eewr_a + n;
EECR |= 1<<EERE;
uint8_t d = ((uint8_t*)&conf)[n];
if (EEDR == d)
continue;
EEDR = d;
EECR |= 1<<EEMWE;
EECR |= 1<<EEWE;
return;
}
EECR = 0; // clear EERIE
return;
}
static inline
uint8_t eeprom_save(uint16_t a)
{
// eeprom_update_block(&conf, (void*)a, sizeof(conf));
if (EECR & 1<<EERIE)
return 1;
eewr_a = a;
eewr_n = sizeof(conf);
EECR = 1<<EERIE;
return 0;
}
static inline
uint8_t eeprom_load(uint16_t a)
{
// eeprom_read_block(&conf, (void*)a, sizeof(conf));
cli();
if (EECR & 1<<EEWE) {
sei();
return 1;
}
uint8_t n = sizeof(conf);
uint8_t *c = (uint8_t*)&conf;
while (n--) {
EEAR = a++;
EECR |= 1<<EERE;
*c++ = EEDR;
}
sei();
return 0;
}
static inline
void reg16(uint16_t *v, unsigned char *r, const unsigned char *c)
{
int2frame(*v, r+1);
if (upcase(c[0]))
*v = frame2int(c+1);
}
static inline
void reg8(uint8_t *v, unsigned char *r, const unsigned char *c)
{
r[1] = *v;
if (upcase(c[0]))
*v = c[1];
}
static inline
void reg8f(uint8_t *v, unsigned char *r, const unsigned char *c)
{
r[1] = *v;
*v = *v & ~c[2] | c[1];
}
static inline
void reg88(uint8_t *v1, uint8_t *v2, unsigned char *r, const unsigned char *c)
{
r[1] = *v1;
r[2] = *v2;
if (upcase(c[0])) {
*v1 = c[1];
*v2 = c[2];
}
}
// '0': stop dir nsteps → n_steps
// '1': step1 dir nsteps → n_steps
// '2': step2 dir nsteps → n_steps
// 'a': Adc val, what
// 'l': sLen val (STEP pulse duration)
// 'm': limit Mask, val (LIMIT_PORT match)
// 'o': enable set clear (RESET_PORT value)
// 'p': ramP stepsize
// 'q': period val (23.148µs)
// 'r': Reset mask what (RESET_PORT mask)
// 's': Steps n_steps → n_steps
// 'x': conf idx, val
// 'y': poke a, val
// 'z': eeprom a
//
// read/write:
// Return the old value.
// Update the value when the command is upper case.
//
// STEPPING:
//
// 'q': period val
// read/write stepping period, in units of 23.148µs
// reinitialize stepper when written
// 'l': slen val
// STEP pulse duration, in units of 23.148µs
// reinitialize stepper when written
// 'm': limit Mask, val
// read/write the mask and val bytes
// stepping stops when PIND & mask != val
// FAULT: goes low when the motor controller is unhappy
// LIMIT: goes low when the endswitch light bridge is blocked.
// 'o': enable set, clear
// set and clear bits in `enable`
// `enable` is written to PORTD when stepping
// `O`: write to PORTD immediately.
// 'r': Reset mask, what
// read/write the RESET pin mask.
// what != 0: stop stepping
// what[7]: reinitialize the stepper.
// what[0]: issure a reset.
// '0': stop dir, exp, nsteps → n_steps
// '1': step1 dir, exp, nsteps → n_steps
// '2': step2 dir, exp, nsteps → n_steps
// Stop, start stepping
// n_steps = nsteps << exp, when nonzero
// dir,ms = dir
// return `n_steps`
// 's': nSteps nsteps
// read/write n_steps.
//
// ADC
//
// 'a': Adc val, idx, what, start
// idx: adc config index
// start: 'a': stop ADC, 'A': start adc @ idx
// what:
// 1: read/write channel[idx] mux
// 2: read/write ch_idx
// 3: read/write idx increment
// 4: read/write tick TIMER0 period
// The adc_idx is 8 bits, with the 4 MSB being the index into the adc_config.
// adc_incr advances adc_idx, but the channel mux changes only when the MSB change,
// so that each mux may be get sampled multiple times.
// When a channel mux config is empty, restart at idx 0.
// When idx 0 is empty, stop.
//
// DAC
//
// 'd': Dac val
// read/write the DAC value
// When dac_step is not zero, ramp at TIMER0 cadence.
// 'p': ramP dac_step
// read/write dac_step
//
// MEMORY
//
// 'x': conf a, val
// read/write config byte
// 'z': eeprom a
// write conf to eeprom at address a*4
// 'Z': eeprom what, a
// what=='e' or 'E": read eeprom from address a*4 to conf
// what=='F': ignore magic/version mismatch
// what=='E' or 'F': reinitialize with conf.
// 'y': peek a
// 'Y': poke a, v
// The mainloop runs with interrupts disabled. Interrupts are enabled
// while waiting for a command on the SPI. As soon as SSEL toggles,
// interrupts are disabled, so that the SPI reciever can respond to
// all bytes in time. All commands execute fast, the master µC shall
// not time out while waiting for the reponse. The stepping timer is
// restarted after command execution, when any steps are commanded.
//
// Interrupts
// PCINT2: disable global interrupts
// TIMER1_COMPA: assert STEP
// TIMER1_COMPB: deassert STEP, RESET
// TIMER0_COMPB: ramp DAC
// ADC: capture conversion results, advance MUX config
// EE_READY: write conf bytes to EEPROM
int main()
{
MCUSR = 0;
// turn of unused IO modules
PRR = (1<<PRPSC)|(1<<PRCAN)|(1<<PRLIN);
spi_slave_init_sei();
DDRB = MISO;
eeprom_load(0);
adc_init();
if (conf.magic == MAGIC && conf.version == VERSION) {
conf_init();
}
while (1) {
unsigned char cmd[3];
unsigned char resp[3];
if (!stepper_status() && conf.step && conf.n_steps)
stepper_start(0);
if (spi_slave_Rx(cmd, 3)) {
spi_slave_Tx((const unsigned char*)"\xff\xff\xff" "EEY", 6);
continue;
}
stepper_stop();
char up = upcase(cmd[0]);
if (up)
conf.step = 0;
resp[0] = cmd[0];
uint8_t i;
switch (cmd[0] | 0x20) {
default:
resp[0] = 'E';
resp[1] = 'E';
resp[2] = cmd[0];
break;
case 'm': reg88(&conf.lmask, &conf.lval, resp, cmd); break;
case 'q': reg16(&conf.period, resp, cmd);
if (0) case 'l': reg16(&conf.slen, resp, cmd);
if (up)
stepper_init();
break;
case 'o': reg8f(&conf.enable, resp, cmd);
if (up)
RESET_PORT = conf.enable;
break;
case '0': conf.step = 0;
if (0) case '1': conf.step = STEP1;
if (0) case '2': conf.step = STEP2;
conf.dir = cmd[1] & 0xf;
if (cmd[2])
conf.n_steps = cmd[2] << ((cmd[1]>>4) & 7);
// fall through, return n_steps
case 's': reg16(&conf.n_steps, resp, cmd); break;
case 'r': reg8(&conf.reset, resp, cmd);
resp[2] = cmd[2];
if (cmd[2]) {
conf.step = 0;
if (cmd[2] & 0x80)
stepper_init();
if (cmd[2] & 1)
stepper_start(conf.reset);
}
break;
case 'd': reg16(&conf.dac, resp, cmd);
dac_ramp(conf.dac);
break;
case 'p': reg16(&conf.dac_step, resp, cmd); break;
case 'a':
resp[2] = cmd[2];
i = cmd[2] & 15;
switch ((cmd[2]>>4) & 7) {
default: int2frame(adc[i], resp+1);
if (up) adc[i] = 0;
break;
case 1: reg8(conf.adc_ch+i, resp, cmd); break;
case 2: reg8(&conf.adc_idx, resp, cmd); break;
case 3: reg8(&conf.adc_incr, resp, cmd); break;
case 4: reg8(&conf.adc_period, resp, cmd); break;
}
if (cmd[2] & 0x80)
if (up)
adc_start(i);
else
adc_stop();
break;
case 'z': // Load/Save conf
resp[1] = cmd[1];
resp[2] = sizeof(conf);
if (!up) {
if (eeprom_save(cmd[2]*4)) {
resp[0] = 'E';
resp[1] = 'W';
resp[2] = 'Y';
}
break;
}
if ((cmd[1] | 0x20) == 'e') {
if (eeprom_load(cmd[2]*4)) {
resp[0] = 'E';
resp[1] = 'R';
resp[2] = 'Y';
break;
}
}
if (conf.magic != MAGIC || conf.version != VERSION) {
resp[0] = 'E';
resp[2] = conf.version;
if (cmd[1] != 'F')
break;
}
if (cmd[1] && upcase(cmd[1]))
conf_init();
break;
case 'x': // conf Byte
resp[2] = cmd[1];
if (cmd[1] >= sizeof(conf)) {
resp[0] = 'E';
resp[1] = sizeof(conf);
break;
}
resp[1] = ((unsigned char *)(&conf))[cmd[1]];
if (up)
((unsigned char *)(&conf))[cmd[1]] = cmd[2];
break;
case 'y': // peek, poke
resp[2] = cmd[1];
resp[1] = *(unsigned char *)(cmd[1]+0);
if (up)
*(unsigned char *)(cmd[1]+0) = cmd[2];
break;
}
spi_slave_Tx(resp, 3);
}
}

View file

@ -1,6 +1,5 @@
#include <avr/io.h>
#include <avr/interrupt.h>
#define SPSR_IF (1<<SPIF)
static inline void spi_slave_init()
@ -15,8 +14,7 @@ static inline unsigned char spi_slave_Rx_status()
return SPSR & SPSR_IF && !(SPDR & 0x80);
}
#define spi_slave_Rx spi_slave_Rx_n
static inline char spi_slave_Rx_n(unsigned char d[], unsigned char n)
static inline void spi_slave_Rx(unsigned char d[], unsigned char n)
{
SPSR;
SPDR;
@ -32,29 +30,6 @@ static inline char spi_slave_Rx_n(unsigned char d[], unsigned char n)
SPDR = 0xff;
*d++ = SPDR;
}
return 0;
}
static inline char spi_slave_Rx_cli(unsigned char d[], unsigned char n)
{
SPSR;
SPDR;
register unsigned char b;
do {
while (!(SPSR & SPSR_IF))
if (!(PIND & 8))
cli();
SPDR = 0xff;
b = SPDR;
} while (b&0x80);
cli();
*d++ = b;
while (--n) {
while (!(SPSR & SPSR_IF));
SPDR = 0xff;
*d++ = SPDR;
}
return 0;
}
extern volatile unsigned char wdt_tick;
@ -78,8 +53,7 @@ static inline char spi_slave_Rx_wdt(unsigned char d[], unsigned char n)
return wdt_tick;
}
#define spi_slave_Tx spi_slave_Tx_n
static inline void spi_slave_Tx_n(const unsigned char d[], unsigned char n)
static inline void spi_slave_Tx(const unsigned char d[], unsigned char n)
{
SPSR;
SPDR;
@ -96,100 +70,9 @@ static inline void int2frame(unsigned int i, unsigned char *f)
f[0] = i;
f[1] = i>>8;
}
static inline unsigned int frame2int(const unsigned char *f)
static inline unsigned int frame2int(unsigned char *f)
{
return f[1]<<8 | f[0];
}
#define INT2FRAME(i) ((unsigned char []){i, i>>8})
extern char spi_was_busy;
static inline
void spi_busy_init()
{
PCMSK2 |= 8; // PIND3 PCINT19
while (!(PIND & 8));
PCIFR = 4;
}
static inline
unsigned char spi_busy()
{
// Return true if the SSEL pin became low
unsigned char f = PCIFR & 4;
PCIFR = 4;
spi_was_busy = f && !(PIND & 8);
return spi_was_busy;
}
static inline
void clear_spi_busy(const char *busy_msg)
{
if (spi_was_busy && !(PIND & 8))
spi_slave_Tx((const unsigned char *)busy_msg, 6);
spi_was_busy = 0;
}
#ifdef SPI_Rx_SEI
#ifndef PIN_SSEL
// ATmega32M1
# define PIN_SSEL (PIND & 8)
# define PCIF_SSEL 4
// TODO: avoid output in a header file?
ISR(PCINT2_vect, ISR_NAKED)
{
// return without sei()
__asm__ ("ret");
}
static inline
void spi_slave_init_sei()
{
spi_slave_init();
PCMSK2 |= 8;
PCICR |= PCIF_SSEL;
}
#endif
#undef spi_slave_Rx
#define spi_slave_Rx spi_slave_Rx_sei
static inline
char spi_slave_Rx_sei(unsigned char d[], unsigned char n)
{
if (!PIN_SSEL)
return 1;
if (PCIFR & PCIF_SSEL)
return 1;
PCIFR = PCIF_SSEL;
sei();
spi_slave_Rx_n(d, n);
cli();
return 0;
}
#undef spi_slave_Tx
#define spi_slave_Tx spi_slave_Tx_sei
static inline
char spi_slave_Tx_sei(const unsigned char d[], unsigned char n)
{
SPSR;
SPDR;
while (n) {
register unsigned char b = *d++;
while (!(SPSR & SPSR_IF))
if (!PIN_SSEL)
return 1;
SPDR = b;
n--;
}
while (!PIN_SSEL);
PCIFR = PCIF_SSEL;
sei();
return 0;
}
#endif