Serial Peripheral Interface (SPI)
직렬 주변 장치 인터페이스 (Serial Peripheral Interface, SPI)는 두 장치 간의 양방향 통신에 사용되는 매우 일반적인 통신 프로토콜입니다. 표준 SPI 버스는 MOSI (Master Out Slave In), MISO (Master In Slave Out), SCK (Clock In) 및 SS (Slave Select)의 네 가지 신호로 구성됩니다. 비동기 직렬 인터페이스와 달리 SPI는 대칭이 아닙니다(H/W 가 Master와 Slave 동일하지 않다.). SPI 버스에는 하나의 마스터와 하나 이상의 슬레이브가 있습니다. 마스터는 버스의 모든 슬레이브와 통화 할 수 있지만 각 슬레이브는 마스터와 만 통화 할 수 있습니다. 버스의 각 슬레이브는 고유 한 슬레이브 선택 신호를 가져야합니다. 마스터는 슬레이브 선택 신호를 사용하여 어떤 슬레이브가 통화 할 것인지 선택합니다. SPI는 또한 클록 신호를 포함하고 있기 때문에, 두 디바이스는 모두 데이터 속도에 동의 할 필요가 없다. 유일한 요구 사항은 clock이 관련된 모든 장치의 최대 주파수보다 낮다는 것입니다.
SPI 전송의 예
SPI 버스의 마스터가 전송을 시작하고자 할 때, 먼저 통신하고자하는 슬레이브에 대해 SS 신호를 로우로 당겨야합니다. 일단 SS 신호가 낮 으면, 그 슬레이브는 버스에서 청취 할 것입니다. 그러면 마스터는 자유롭게 데이터 전송을 시작합니다.
SCK 신호와 관련하여 4 가지 SPI 버스 표준이 있습니다. 4 가지 모드는 CPOL과 CPHA의 두 매개 변수로 나뉩니다.
CPOL은 Clock POLarity를 나타내며 버스가 유휴 상태 일 때 SCK 신호의 기본값 (고 / 저)을 지정합니다.
CPHA는 Clock PHAse의 약자로 클록 데이터의 어느 에지가 샘플링되는지 (상승 / 하강)를 결정합니다.
모든 장치의 데이터 시트는 이러한 매개 변수를 지정하므로 적절하게 조정할 수 있습니다. 가장 일반적인 설정은 CPOL = 0 (공회전 낮음) 및 CPHA = 0 (샘플 상승 에지)입니다.
다음은 CPOL = 0 및 CPHA = 0을 사용한 전송 예입니다.
SPI 전송의 비트는 LSB 우선 전송됩니다.
모든 SPI 전송은 마스터에 의해서만 제어됩니다. 마스터는 클럭을 생성하고 슬레이브 선택 신호를 제어합니다. 이것은 슬레이브 자체가 마스터에게 데이터를 보낼 수있는 방법이 없다는 것을 의미합니다!
각 SPI 전송은 풀 듀플렉스입니다. 즉, 데이터가 마스터에서 슬레이브로, 슬레이브에서 마스터로 동시에 전송(양방향)됩니다. 마스터가 전송을 할 때 슬레이브가 데이터 전송을 거부 할 수있는 방법은 없지만 통신이 한 방향 일 때 장치는 더미 바이트 (일반적으로 모두 1 또는 모두 0)를 전송합니다. 마스터가 슬레이브에 대해 데이터를 읽는 중이라면 슬레이브는 마스터가 보내는 데이터를 무시한다는 것을 알게됩니다.
SPI를 사용하는 장치는 일반적으로 SS 신호가 낮아질 때마다 여러 바이트를 보내거나받습니다. 이 방식으로 SS 신호는 전송을 프레임하는 방법으로 작동합니다. 예를 들어, SPI 버스가있는 플래시 메모리가 있고 일부 데이터를 읽으려는 경우 SS 신호가 낮아지고 마스터가 특정 주소의 메모리 읽기 명령을 전송하고 마스터가 보관하는 한 SS가 낮고 SCK를 토글하면 플래시 메모리가 데이터를 계속 전송합니다. SS가 하이로 복귀하면 플래시 메모리는 판독 명령을 종료하는 것을 알고있다.
MISO 신호는 여러 장치에 연결될 수 있기 때문에 각 장치는 SS 신호가 낮을 때만 회선을 구동합니다. 이것은 회색 영역으로 표시됩니다.
SPI 슬레이브
Mojo 기본 프로젝트에서, spi_slave.v 파일은 AVR과 인터페이스하는 데 사용되는 SPI 모듈을 포함합니다. 이 경우 AVR은 마스터이고 FPGA는 슬레이브입니다. AVR이 마스터 인 이유는 SPI 버스가 아날로그 핀에서 데이터를 전송하는 데 사용되기 때문입니다. FPGA가 언제 데이터를 사용할 수 있는지 알 수있는 방법이 없기 때문에 FPGA는 AVR에 데이터가 있는지 계속 묻습니다. AVR을 마스터로 만들면 준비가되었을 때 바로 데이터를 보낼 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| module spi_slave ( input clk , input rst , input ss , input mosi , output miso , input sck , output done , input [ 7 : 0 ] din , output [ 7 : 0 ] dout ); reg mosi_d , mosi_q ; reg ss_d , ss_q ; reg sck_d , sck_q ; reg sck_old_d , sck_old_q ; reg [ 7 : 0 ] data_d , data_q ; reg done_d , done_q ; reg [ 2 : 0 ] bit_ct_d , bit_ct_q ; reg [ 7 : 0 ] dout_d , dout_q ; reg miso_d , miso_q ; assign miso = miso_q ; assign done = done_q ; assign dout = dout_q ; always @(*) begin ss_d = ss ; mosi_d = mosi ; miso_d = miso_q ; sck_d = sck ; sck_old_d = sck_q ; data_d = data_q ; done_d = 1'b0 ; bit_ct_d = bit_ct_q ; dout_d = dout_q ; if ( ss_q ) begin // if slave select is high (deselcted) bit_ct_d = 3'b0 ; // reset bit counter data_d = din ; // read in data miso_d = data_q [ 7 ]; // output MSB end else begin // else slave select is low (selected) if (! sck_old_q && sck_q ) begin // rising edge data_d = { data_q [ 6 : 0 ], mosi_q }; // read data in and shift bit_ct_d = bit_ct_q + 1'b1 ; // increment the bit counter if ( bit_ct_q == 3'b111 ) begin // if we are on the last bit dout_d = { data_q [ 6 : 0 ], mosi_q }; // output the byte done_d = 1'b1 ; // set transfer done flag data_d = din ; // read in new byte end end else if ( sck_old_q && ! sck_q ) begin // falling edge miso_d = data_q [ 7 ]; // output MSB end end end always @( posedge clk ) begin if ( rst ) begin done_q <= 1'b0 ; bit_ct_q <= 3'b0 ; dout_q <= 8'b0 ; miso_q <= 1'b1 ; end else begin done_q <= done_d ; bit_ct_q <= bit_ct_d ; dout_q <= dout_d ; miso_q <= miso_d ; end sck_q <= sck_d ; mosi_q <= mosi_d ; ss_q <= ss_d ; data_q <= data_d ; sck_old_q <= sck_old_d ; end endmodule |
이 모듈은 CPOL = 0 및 CPHA = 0으로 가정합니다.
SS가 낮아질 때까지 기다립니다. SS가 낮아지면 data_d / _q 레지스터로 데이터 이동을 시작합니다. 8 비트가 옮겨지면 dout에 새로운 데이터가 있음을 알립니다. 클럭의 하강 에지에서 전송 시작시 din에 의해 제공된 데이터가 시프트 아웃됩니다.
SPI 마스터
우리의 Clock / Visualizer Shield는 Mojo에 현재 시간을 제공하는 RTC (Real-Time Clock) 장치를 사용합니다. RTC는 SPI 버스를 통해 Mojo에 연결됩니다. 이 경우 Mojo의 FPGA는 마스터이고 RTC는 슬레이브입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
| module spi #( parameter CLK_DIV = 2 )( input clk , input rst , input miso , output mosi , output sck , input start , input [ 7 : 0 ] data_in , output [ 7 : 0 ] data_out , output busy , output new_data ); localparam STATE_SIZE = 2 ; localparam IDLE = 2'd0 , WAIT_HALF = 2'd1 , TRANSFER = 2'd2 ; reg [ STATE_SIZE - 1 : 0 ] state_d , state_q ; reg [ 7 : 0 ] data_d , data_q ; reg [ CLK_DIV - 1 : 0 ] sck_d , sck_q ; reg mosi_d , mosi_q ; reg [ 2 : 0 ] ctr_d , ctr_q ; reg new_data_d , new_data_q ; reg [ 7 : 0 ] data_out_d , data_out_q ; assign mosi = mosi_q ; assign sck = (~ sck_q [ CLK_DIV - 1 ]) & ( state_q == TRANSFER ); assign busy = state_q != IDLE ; assign data_out = data_out_q ; assign new_data = new_data_q ; always @(*) begin sck_d = sck_q ; data_d = data_q ; mosi_d = mosi_q ; ctr_d = ctr_q ; new_data_d = 1'b0 ; data_out_d = data_out_q ; state_d = state_q ; case ( state_q ) IDLE : begin sck_d = 4'b0 ; // reset clock counter ctr_d = 3'b0 ; // reset bit counter if ( start == 1'b1 ) begin // if start command data_d = data_in ; // copy data to send state_d = WAIT_HALF ; // change state end end WAIT_HALF : begin sck_d = sck_q + 1'b1 ; // increment clock counter if ( sck_q == { CLK_DIV - 1 { 1'b1 }}) begin // if clock is half full (about to fall) sck_d = 1'b0 ; // reset to 0 state_d = TRANSFER ; // change state end end TRANSFER : begin sck_d = sck_q + 1'b1 ; // increment clock counter if ( sck_q == 4'b0000 ) begin // if clock counter is 0 mosi_d = data_q [ 7 ]; // output the MSB of data end else if ( sck_q == { CLK_DIV - 1 { 1'b1 }}) begin // else if it's half full (about to fall) data_d = { data_q [ 6 : 0 ], miso }; // read in data (shift in) end else if ( sck_q == { CLK_DIV { 1'b1 }}) begin // else if it's full (about to rise) ctr_d = ctr_q + 1'b1 ; // increment bit counter if ( ctr_q == 3'b111 ) begin // if we are on the last bit state_d = IDLE ; // change state data_out_d = data_q ; // output data new_data_d = 1'b1 ; // signal data is valid end end end endcase end always @( posedge clk ) begin if ( rst ) begin ctr_q <= 3'b0 ; data_q <= 8'b0 ; sck_q <= 4'b0 ; mosi_q <= 1'b0 ; state_q <= IDLE ; data_out_q <= 8'b0 ; new_data_q <= 1'b0 ; end else begin ctr_q <= ctr_d ; data_q <= data_d ; sck_q <= sck_d ; mosi_q <= mosi_d ; state_q <= state_d ; data_out_q <= data_out_d ; new_data_q <= new_data_d ; end end endmodule |
이 경우 CPOL = 0 및 CPHA = 1입니다.
전반적인 아이디어는 동일하지만, 이제는 FPGA가 SCK 신호를 생성해야한다. CLK_DIV 파라미터는 FPGA의 클럭을 얼마나 분할할지 지정하는 데 사용됩니다. 기본값은 2이며 이는 SCK의 주파수가 FPGA의 주파수의 1/4 (2 ^ 2 = 4 클록 사이클)이됨을 의미합니다. CLK_DIV가 3으로 설정되면 SCK는 FPGA 클럭의 1/8 (2 ^ 3 = 8 클럭 사이클)이됩니다.