В прошлый раз мы тестировали отправку байта по UART, при этом данные отправлялись в терминал на ПК. Результат как то не очень вдохновил, поэтому я продолжил изыскания. В результате чего удалось совокупить AVR и ПЛИС.
Задача передать символ нуля. В ASCII кодировке он имеет значение 0x30, в двоичном коде 0b 0011 0000. Интерфейс spi достаточно прост, для того чтобы передать байт нужно выбрать ведомого — прижать ножку SS к земле и передавать импульсы. Синхронно с импульсами передаются 8 бит. Как видно никаких стоп битов, никаких старт битов. Но также становится понятно, что биты могут передаваться либо по восходящему фронту импульса, либо по нисходящему. Кроме того важно в каком состоянии до передачи находится линия SCLK, либо логический ноль, либо единица.
Таким образом у нас получаются 4 режима работы SPI. Режим 0, в момент когда данные не передаются на SCLK лог 0. Биты записываются по восходящему фронту.
Режим 1. Когда до начала тактовых импульсов сигнал на ноге SCLK равен логическому нулю. И первый бит считывается по спадающему фронту.
Режим 2. Когда до начала тактовых импульсов сигнал на ноге SCLK равен логической единице. И первый бит считывается по нарастающему фронту.
Режим 3. Когда до начала тактовых импульсов сигнал на ноге SCLK равен логическому нулю. И первый бит считывается по нарастающему фронту.
Кроме того, еще может быть важен порядок байтов MSB или LSB, т.е. либо передаем 0011 0000 справа налево, либо наоборот слева на право.
Теперь нужно потестить. Передачу данных. В роли «господина» AVR микроконтроллер, нажимаем PD0 передается ноль, нажимаем PD1 передается единица.
#include <mega8.h> #include <spi.h> void main(void) { // Function: Bit7=In Bit6=In Bit5=Out Bit4=In Bit3=Out Bit2=Out Bit1=In Bit0=In DDRB=(0<<DDB7) | (0<<DDB6) | (1<<DDB5) | (0<<DDB4) | (1<<DDB3) | (1<<DDB2) | (0<<DDB1) | (0<<DDB0); // State: Bit7=T Bit6=T Bit5=0 Bit4=T Bit3=0 Bit2=0 Bit1=T Bit0=T PORTB=(0<<PORTB7) | (0<<PORTB6) | (0<<PORTB5) | (0<<PORTB4) | (0<<PORTB3) | (0<<PORTB2) | (0<<PORTB1) | (0<<PORTB0); // Port D initialization // Function: Bit7=In Bit6=In Bit5=In Bit4=In Bit3=In Bit2=In Bit1=In Bit0=In DDRD=(0<<DDD7) | (0<<DDD6) | (0<<DDD5) | (0<<DDD4) | (0<<DDD3) | (0<<DDD2) | (0<<DDD1) | (0<<DDD0); // State: Bit7=T Bit6=T Bit5=T Bit4=T Bit3=T Bit2=T Bit1=P Bit0=P PORTD=(0<<PORTD7) | (0<<PORTD6) | (0<<PORTD5) | (0<<PORTD4) | (0<<PORTD3) | (0<<PORTD2) | (1<<PORTD1) | (1<<PORTD0); // SPI initialization // SPI Type: Master // SPI Clock Rate: 2000,000 kHz // SPI Clock Phase: Cycle Start // SPI Clock Polarity: Low // SPI Data Order: MSB First SPCR=(0<<SPIE) | (1<<SPE) | (0<<DORD) | (1<<MSTR) | (0<<CPOL) | (0<<CPHA) | (0<<SPR1) | (0<<SPR0); SPSR=(0<<SPI2X); while (1) { if(PIND.0==0) { spi('0'); } if(PIND.1==0) { spi('1'); } } } |
В качестве «раба» будет плисина
module test_spi(clk,Mosi,ss,led); input clk; //сюда прихолят тактовые импульсы input ss; //выбор чипа input Mosi; //ножка приема output reg led = 0; //светодиодик reg [7:0]data = 0; reg [3:0]nBit = 0; reg [8:0]clock = 0; always @ (posedge clk) //по приходу тактовых импульсов begin if(ss==0) //если чип селект равен лог нулю begin if(nBit != 8)begin data[7:0] <= {data[6:0],Mosi}; //считываем бит и сдвигаем данные nBit <= nBit + 1; //следующий бит end if(nBit == 8)begin //когда байт пришел полностью смотрим if(data == 8'b00110000)begin led = 1'b1; end //если пришел '0' включаем светодиод if(data == 8'b00110001)begin led = 1'b0; end //если пришла '1' выключаем светодиод nBit <= 0; end end end endmodule |
Особо хочется отметить красивый кусок кода
data[7:0] <= {data[6:0],Mosi}; |
Сначала от этой строчки у меня немного задымился мозг, но потом вкурил. Представим на манер Си, как объявляется массив
char data[3] = {1, 2, 3}; |
т.е. 3 элементам массива мы присвоили 3 числа
Здесь по сути тоже самое, если расписать выражение, то получится вот так:
data[7:0] <= {data[6],data[5],data[4],data[3],data[2],data[1],data[0],Mosi}; |
т.е. по сути элементам data[7:1] будут присвоены значения data[6:0], а значению data[0] будет присвоено значение Mosi, т.е. то что на входной ножке. По сути это сдвиг и присвоение нового значения.
Собственно все работает и занимает достаточно мало ресурсов ПЛИСины, а если прибавить еще то, что работает SPI на 2МГц, т.е. байт уйдет 4мкс. Теоретически можно выжать скорость около 250кБайт/c, и это при кварце на 8МГц.
Автор! ОЧень хороша статья, только начал изучать плис. Разве что вам проверить работу надо было и в длительном режиме, а не по одному импульсу с кнопки. У вас отстутсвует сброс и синхронизация начала пакета. нужно в строке:
if(nBit == 8)begin //когда байт пришел полностью смотрим
добавить условие сброса по высокому уровню SS
if(nBit == 8 | ss == 1)
Википедия:
Как в ведущем устройстве, так и в ведомом устройстве имеется счетчик импульсов синхронизации (битов). Счетчик в ведомом устройстве позволяет последнему определить момент окончания передачи пакета. Счетчик сбрасывается при выключении подсистемы SPI, такая возможность всегда имеется в ведущем устройстве. В ведомом устройстве счетчик обычно сбрасывается деактивацией интерфейсного сигнала SS.
Строчка data[7:0] <= {data[6:0],Mosi}; соответствует строчке data[7:0] <= {data[6],data[5],data[4],data[3],data[2],data[1],data[0],Mosi}; Т.е. Вы пропустили бит data[0].