SPI куда элегантнее программировать на ассемблере, если аппаратно этого сделать по каким-то причинам нельзя. Элегантность заключена в том, что так можно эффективно использовать флаг переноса при сдвигах, строго выдержать меандр по времени (хотя последнее, обычно, не требуется) и сделать правильные задержки. На чистом С такое написать нельзя.
Здесь приведены сочиненные мной процедуры:
void Wr_Reg( char byte); // запись по SPI в регистр АЦП
char Rd_Reg( void); // чтение по SPI регистра АЦП
Вся остальная программа у меня на С, откуда я и вызываю функции Wr_Reg(byte) и Rd_Reg(). Их описание в хидере я только что привела, их тела на ассемблере выглядят так:
Код
RSEG CODE:CODE:NOROOT(1)
PUBLIC Wr_Reg
Wr_Reg:
ldi R17, 8
LoopWrReg:
sbi PORTD, SCLK; установим SCLK в 1
rol R16; сдвигаем регистр
brcs SetSDI
cbi PORTD, SDI; SDI = 0
rjmp CliSCLK; 2 clocks
SetSDI:
sbi PORTD, SDI; SDI = 1
nop; 1+1 clocks
nop
CliSCLK:
cbi PORTD, SCLK; установим SCLK в 0
dec R17
brne LoopWrReg; если не 0
ret
PUBLIC Rd_Reg
Rd_Reg:
ldi R17, 8
clr R16; обнуляем регистр
LoopRdReg:
sbi PORTD, SCLK; установим SCLK в 1
nop
cbi PORTD, SCLK; установим SCLK в 0
lsl R16; сдвигаем регистр
sbic PIND, SDO; SDO ?
ori R16, 1; устанавливаем мл.бит
dec R17
brne LoopRdReg; если не 0
ret
Связь осуществляется через порт D, который можно заменить на любой другой. SDI, SDO и SCLK по назначению соответствуют MOSI, MISO и СLK.
Суть не меняется, если вместо АЦП будет какое-то другое устройство.
P.S. Проект на IAR EWAVR, в котором имеется один ассемблерный файл (этот), а остальные на C. Микроконтроллер ATtiny2313.