В прошлый раз мы тестировали отправку байта по UART, при этом данные отправлялись в терминал на ПК. Результат как то не очень вдохновил, поэтому я продолжил изыскания. В результате чего удалось совокупить AVR и ПЛИС.

Задача передать символ нуля. В ASCII кодировке он имеет значение 0x30, в двоичном коде 0b 0011 0000. Интерфейс spi достаточно прост, для того чтобы передать байт нужно выбрать ведомого — прижать ножку SS к земле и передавать импульсы. Синхронно с импульсами передаются 8 бит. Как видно никаких стоп битов, никаких старт битов. Но также становится понятно, что биты могут передаваться либо по восходящему фронту импульса, либо по нисходящему. Кроме того важно в каком состоянии до передачи находится линия SCLK, либо логический ноль, либо единица.

Таким образом у нас получаются 4 режима работы SPI. Режим 0, в момент когда данные не передаются на SCLK лог 0. Биты записываются по восходящему фронту.
mode0_1

Режим 1. Когда до начала тактовых импульсов сигнал на ноге SCLK равен логическому нулю. И первый бит считывается по спадающему фронту.
mode1_1

Режим 2. Когда до начала тактовых импульсов сигнал на ноге SCLK равен логической единице. И первый бит считывается по нарастающему фронту.
mode2_1

Режим 3. Когда до начала тактовых импульсов сигнал на ноге SCLK равен логическому нулю. И первый бит считывается по нарастающему фронту.
mode3_1

Кроме того, еще может быть важен порядок байтов MSB или LSB, т.е. либо передаем 0011 0000 справа налево, либо наоборот слева на право.

Все эти настройки есть В CAVR
cavr_spi

Теперь нужно потестить. Передачу данных. В роли «господина» 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МГц.

2 комментария: ПЛИС. SPI

  • Автор! ОЧень хороша статья, только начал изучать плис. Разве что вам проверить работу надо было и в длительном режиме, а не по одному импульсу с кнопки. У вас отстутсвует сброс и синхронизация начала пакета. нужно в строке:

    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].

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика