在2013年的工作中,涉及到了AM3359与XC7K325T之间的相互通信,其目的是为了获取FPGA设计版本号,该FPGA版本号保存在FPGA的寄存器0xFFFF中,FPGA的版本值随着加载程序发生变化,当时的版本信息为0x1003.
需要说明的是,在本文中的代码风格是刚工作两年的时候的代码风格,现在回看,这些代码风格实在难以阅读。尤其是SPI的verilog程序等。并不代表现在的编程水平与代码风格。
设计框图如下:
本文主要从三个方面介绍AM3359与FPGA的通讯
第一部分:SPI通信的基础定义
第二部分:AM335x的SPI通信编程
第三部分:FPGA从机SPI设计
通信是怎么发生
SPI接口是一种典型的全双工接口。通过同步时钟SCLK的脉冲将数据一位一位地在主机和从机之间交换。所以在开始通信之前,Master首先需要配置接口时钟,在Master配置时钟的时候,不需要通知从机它所配置的时钟频率具体是多少,设计人员只需要确保通讯频率是从机所支持的即可。
当Master通过片选信号(SS,低电平有效)选定一个Slave的时候,每向Slave发送一个周期的SCLK信号,都会有1bit的数据从MOSI引脚发送给Slave,Slave只需要在对应的引脚接收数据即可;同时,slave每收到一个周期的SCLK信号,都会从MISO想Master发送1bit的数据。
从上段描述中可以分析出一下两点:
1.无论Master还是Slave,都是按照bit来传输的,那么对于需要发送或者接收的数据,必须在Master或Slave中有一个移位寄存器,这些是由硬件来保证的,普通的SPI接口设计者不需要考虑移位寄存器的因素。
2.在通信中,Master发送数据后,一般需要保证Slave收到数据,这样才能确定数据在收发的过程中不发生因为硬件而导致的bit丢失。在SPI中,数据传输以“位交换”的方式传输,这样能根据从机返回的数据来确定从机已经收到数据了。SPI同样与其他基础通信方式(USB,I2C,UART等)一样无法确保传输数据的正确性。
SPI是一个相对比较开放的接口,具体表现在时钟极性/相位、帧大小、传输速度、LSB/MSB等规则没有一个确定的定义,需要根据不同的通信环境由设计开发者进行定义。
SPI的接口时序
在实际开发使用SPI的时候,需要注意使Master和Slave处于相同的Mode下工作。不同mode的定义主要是针对时钟的相关特性。
SCLK极性(CPOL):clock Polarity
SCLK相位(CPHA):clock Phase
CPOL
在解释CPOL之前先要介绍什么是SCLK的空闲时刻。在SPI通讯传输的时候,SCLK并不是时刻都有。在SCLK发送数据之前和发送数据之后,都会回到空闲状态,这个状态下,SCLK要么保持在高电平,要么保持在低电平。这个是需要设计者来指定的,CPOL的作用就是来指定SPI在IDLE状态下的点评状态。
CPHA
CPHA表示数据采样,数据有效的时刻。对应的数据采样是在第几个边沿进行采样。
需要注意的是:采样一定是需要先准备好数据,才用时钟的有效沿将数据打到对应的引脚上。
Mode选择参考
SPI没有一个通用的推荐模式,但是基于工程设计的时候是否有一个推荐的Mode选择呢?在CSDN博友的一篇《SPI接口扫盲 SPI定义/SPI时序(CPHA CPOL)》中,作者从功耗的角度分析,建议应该多选择SCLK在空闲状态下处于低电平,即CPOL保持在IDLE状态下为0。这是一个很好的分析方法。对于CPHA的选择分析,我更赞成根据实际的应用来做设计,而不是根据习惯来设计。
在本工程中所使用的SPI为AM335x的SPI外设,即在Linux下,只需要对spidev进行文件操作。所用到的文件操作函数有以下四个:
open()
write()
read()
close()
相关的函数说明,请参考网络,在此不赘述。
在编写SPI驱动的时候,还需用到ioctl()函数,下面对ioctl做一些简要介绍。
什么是ioctl
ioctl是设备驱动程序中对设备的IO通道进行管理的函数。即是对设备的一些特性进行控制,例如对串口设备设置波特率,对SPI设备设置字长,通讯速率等。函数的调用方式如下:
int ioctl(int fd,unsigned long cmd,...);
/*
fd:文件描述符
cmd:控制命令
...:可选参数:插入*argp,具体内容依赖于cmd
*/
其中fd是用户程序打开设备是使用open()函数返回的文件标识符(句柄),cmd是用户程序对设备的控制命令,后面的省略号,表示该函数可能还有其他参数,该参数与cmd相关。
对于ioctl的详细描述,可以参考文末链接1来详细阅读。
AM335x驱动程序源码设计
static uint8_t mode = 0;
static uint8_t bits = 8;
static uint32_t speed = 16000000;
static uint8_t cs = 1;
int fpga_fd = 0;
uint8_t file_name_buf[FILE_NAME_MAX] = "/dev/spidev2.1";
//==
int fpga_config(uint8_t *file_name)
{
if (strlen(file_name) > FILE_NAME_MAX)
{
printf("File name length error\r\n");
return 0;
}
strcpy(file_name_buf, file_name);
return 1;
}
int fpga_spi_open(uint8_t *file_name)
{
int ret = 0;
fpga_fd = open(file_name, O_RDWR);
if (fpga_fd < 0)
{
printf("in fpga_spi_open, can't open device\r\n");
return nQAM_ERROR_CAS_SPI_OPEN;
}
else
{
printf("in fpga_spi_open, open device success\r\n");
}
/*
* spi mode
*/
ret = ioctl(fpga_fd, SPI_IOC_WR_MODE, &mode);
if (ret == -1)
{
printf("in fpga_spi_open, can't set spi mode\r\n");
return nQAM_ERROR_CAS_SPI_CONFIG;
}
else
{
printf("in fpga_spi_open, set spi mode success\r\n");
}
/*
* bits per word
*/
ret = ioctl(fpga_fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
{
printf("in fpga_spi_open, can't set bits per word\r\n");
return nQAM_ERROR_CAS_SPI_CONFIG;
}
else
{
printf("in fpga_spi_open, set bits per word success\r\n");
}
/*
* max speed hz
*/
ret = ioctl(fpga_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
{
printf("in fpga_spi_open, can't set max speed hz\r\n");
return nQAM_ERROR_CAS_SPI_CONFIG;
}
else
{
printf("in fpga_spi_open, set max speed success\r\n");
}
/*
* chip select
*/
ret = ioctl(fpga_fd,SPI_IOC_WR_CHIP_SELECT,&cs);
if(ret == -1)
{
printf("in fpga_spi_configure,can't set chip select\r\n");
return nQAM_ERROR_CAS_SPI_CONFIG;
}
return nQAM_ERROR_NOERROR;
}
int fpga_spi_close()
{
return close(fpga_fd); // close device
}
//数据交换
uint8_t transfer(uint8_t data)
{
uint8_t sbuf = data;
if(write(fpga_fd,&sbuf,1) != -1)
{
if(read(fpga_fd, &sbuf, 1) != -1)
{
printf("In transfer, transfer data over\r\n");
return sbuf;
}
else
{
printf("In transfer, read data from spi device failed!\r\n");
}
}
else
{
printf("In transfer, write data to spi device failed!\r\n");
}
return 0;
}
//通过SPI获取版本号
uint8_t get_fpga_version()
{
uint8_t buf;
if(fpga_spi_open(file_name_buf) != nQAM_ERROR_NOERROR)
{
printf("in get_fpga_vesion, fpga spi open failed\r\n");
return 0;
}
buf = transfer(READ_VESION);
fpga_spi_close();
printf("Read version done.\r\n");
return buf;
}
//测试物理连接状态
uint8_t test_fpga_connect()
{
uint8_t buf;
if(fpga_spi_open(file_name_buf) != nQAM_ERROR_NOERROR)
{
printf("in test_spi_open, fpga spi open failed\r\n");
return 0;
}
buf = transfer(TEST_CONNECT);
fpga_spi_close();
printf("FPGA spi test connect done.\r\n");
return buf;
}
附:spidev的ioctl命令。
SPI_IOC_RD_MODE: 读取spi_device的mode。
SPI_IOC_RD_LSB_FIRST: 如果是SPI_LSB_FIRST的方式则返回1。
SPI_IOC_RD_BITS_PER_WORD: 读取spi_device的bits_per_word.
SPI_IOC_RD_MAX_SPEED_HZ: 读取spi_device的max_speed_hz.
SPI_IOC_WR_MODE: 设置spi_device的mode,并调用spi_setup立即使设置生效。
SPI_IOC_WR_LSB_FIRST: 设置spi使用SPI_LSB_FIRST的传输模式。立即生效。
SPI_IOC_WR_BITS_PER_WORD: 读取字长。
SPI_IOC_WR_MAX_SPEED_HZ: 设置时钟速率。
AM335x应用程序设计
int main(int argc, char *argv )
{
uint8_t connect;
uint8_t version;
connect = test_fpga_connect();
printf("Test Value is %02X\r\n", connect); //测试连接状态
version = get_fpga_version();
printf("the fpga version is %02X\r\n", version);
printf("****fpga app test run over!****\r\n");
return 1;
}
SPI从机的Verilog实现
module spi_slave(clk,rst,data_i,wr_en_i,data_o,tx_valid,valid_o,start_o,end_o,we_ack_o,ss_i,sclk_i,mosi_i,miso_o
);
input clk; // master clock input
input rst; // synchronous active high reset
input [7:0] data_i; // data bus input
input wr_en_i; // write enable input
output [7:0] data_o; // data bus output
output valid_o; // request signal output
output tx_valid;
output start_o;
output end_o;
output reg we_ack_o;
//spi signals
input ss_i; // slave select
input sclk_i; // serial clock
input mosi_i;
output miso_o;
reg [7:0] tx_data;
reg [7:0] rx_data;
reg tx_tip; //tx in progress
reg rx_tip; //rx in progress
wire rx_negedge; //miso is sampled on negative edge
wire tx_negedge; //mosi is driven on nesedge edge
wire [2:0] len; //char length
wire lsb; //lsb first on line
wire pos_edge; //recognize posedge of sclk
wire neg_edge; //recognize negdege of sclk
reg s_out;
reg s_in;
reg [2:0] s_sel;
reg [2:0] s_clk;
assign rx_negedge = 0; // decided by CPOL and CPHA
assign tx_negedge = 1; // means mode == 00
assign len = 7;
assign lsb = 0;
assign miso_o = s_out;
assign valid_o = rst? 1'b0 : (!rx_tip);
assign data_o = rst? 8'h00 : rx_data;
//sync SCK to the FPGA clock using a 3-bits shift register
always @ (posedge clk)
begin
s_clk <= {s_clk[1:0], sclk_i}; //sample the sclk_i using clk,when finding the first posedge the value is 001,when finding the first negedge the value is 110 or 010
end
assign pos_edge = (s_clk[1:0] == 2'b01); // posedge when s_clk[1:0] == 2'b01 or s_clk[2:0] = 3'b001
assign neg_edge = (s_clk[1:0] == 2'b10); // negedge when s_clk[1:0] == 2'b10 or s_clk[2:0] = 3'b110
//SSEL
always @ (posedge clk)
begin
s_sel <= {s_sel[1:0], ss_i}; //sample the ss signal
end
wire sel_active; // from start_o is high to end_o is high sel_active is active
assign sel_active = ~s_sel[1]; // sel[2:0] = 000 001 011 111 110 100 000 when 100 000 001 .0 is high
assign start_o = (s_sel[1:0] == 2'b10); // start_o when s_sel[1:0] = 2'b10 or s_sel[2:0]= 3'b110
assign end_o = (s_sel[2:1] == 2'b01); // end_o when s_sel[2:1] = 2'b01 or s_sel[2:0] = 3'b011
//---------------------receiving bits from line---------------------
wire rx_clk;
wire rx_lst_bit;
reg [2:0] rx_cnt; // rx data bit count
assign rx_clk = rx_negedge? neg_edge : pos_edge; // question is the beginning value is "X"
assign rx_lst_bit = !(|rx_cnt);
always @(posedge clk)
begin
s_in <= mosi_i;
end
always @(posedge clk or posedge rst)
begin
if (rst)
rx_cnt <= len;
else begin
if(!rx_tip || end_o || start_o)
rx_cnt <= len;
else
rx_cnt <= rx_clk ? (rx_cnt - 1'b1) : rx_cnt; //question is the rx_cnt always is 7?
end
end
//Transfer in process
always @(posedge clk or posedge rst)
begin
if(rst)
rx_tip <= 1'b0;
else if(!rx_tip)
rx_tip <= 1'b1;
else if(rx_tip && rx_lst_bit && rx_clk)
rx_tip <= 1'b0;
end
always @(posedge clk or posedge rst)
begin
if(rst)
rx_data <= {8{1'b0}};
else begin
if(sel_active && rx_clk)
rx_data <= lsb ? {s_in,rx_data[7:1]} : {rx_data[6:0],s_in}; // if lsb = 0 rx_data = {rx_data[6:0],s_in}
// if lsb = 1 rx_data = {s_in,rx_data[7:1]}
end // {s_in,rx_data[7:1]} shift right
end // {rx_data[6:0],s_in} shift left
//---------------------sending bits to line---------------------
wire tx_clk;
wire tx_lsb_bit;
reg [2:0] tx_cnt;
assign tx_clk = tx_negedge? neg_edge : pos_edge; // tx_negedge = 1 negedge transfer data
assign tx_lsb_bit = !(|tx_cnt);
assign tx_valid = ((s_sel[2:0] == 3'b111) && (tx_tip == 1'b0)) ? 1'b1 : 1'b0;
// always @(posedge clk or posedge rst)
// begin
// if(rst)
// tx_valid <= 1'b0;
// else if((s_sel[2:0] == 3'b111) && (tx_tip == 1'b0))
// tx_valid <= 1'b1;
// else
// tx_valid <= 1'b0;
// end
// character bit counter
always @(posedge clk or posedge rst)
begin
if(rst)
tx_cnt <= len;
else begin
if(!tx_tip || end_o || start_o)
tx_cnt <= len;
else
tx_cnt <= tx_clk? (tx_cnt-1'b1):tx_cnt;
end
end
//transfer in process
always @(posedge clk or posedge rst)
begin
if(rst) begin
tx_tip <= 1'b0;
end
else if(wr_en_i && (!tx_tip)) begin //wr_en_i is high when transfer the data
tx_tip <= 1'b1;
end
else if(tx_tip && tx_lsb_bit && tx_clk) begin
tx_tip <= 1'b0;
end
end
always @(posedge clk or posedge rst)
begin
if(rst) begin
tx_data <= 8'hff;
we_ack_o <= 1'b0;
end
else begin
we_ack_o <= 1'b0;
if(wr_en_i && (!tx_tip)) begin
tx_data[7:0] <= data_i[7:0];
we_ack_o <= 1'b1;
end
else begin
if(sel_active && rx_clk) begin
s_out <= lsb? tx_data[0] : tx_data[7];
tx_data <= lsb? {1'b1,tx_data[7:1]} : {tx_data[6:0],1'b1};
end
end
end
end
endmodule
`timescale 1 ns / 1 ps
module spi_cmd #(
parameter BUS_DATA_WIDTH = 8,
parameter BUS_ADDR_WIDTH = 16
)(
input clk,
input rst,
input [BUS_DATA_WIDTH-1:0] rx_data,
input rx_valid,
input rx_start,
input rx_end,
input tx_valid,
input tx_ack,
output reg [BUS_DATA_WIDTH-1:0] tx_data,
output reg tx_req
);
localparam CON_WR_REG = 8'h51;
localparam CON_RD_REG = 8'h52;
localparam RD_DATA_REG = 8'h96;
localparam RST_CMD = 8'h55;
localparam ADDR_FPGA_VERSION = 16'hFFFF;
localparam ADDR_SPI_TEST = 16'hF000;
localparam FPGA_VERSION = 16'h1003;
localparam REC_NO_ERR = 8'h90;
localparam REC_INS_ERR = 8'hF1;
localparam REC_LEN_ERR = 8'hF2;
localparam REC_BUSY_ERR = 8'hF3;
localparam REC_WR_ERR = 8'hF4;
localparam REC_START = 1;
localparam REC_TYPE_REG = 2;
localparam REC_ADDR_REGH = 3;
localparam REC_ADDR_REGL = 4;
localparam REC_ADDR_LEN = 5;
localparam REC_CON_WRH = 6;
localparam REC_CON_WRL = 7;
localparam RSP_STATUS = 8;
localparam RSP_LEN = 9;
localparam RSP_DIN = 10;
localparam RSP_DOUT = 11;
localparam READ_DATAH = 12;
localparam READ_DATAL = 13;
localparam READ_DONE = 14;
reg [7:0] rec_type;
reg [15:0] rec_addr;
reg [7:0] addr_len;
reg [7:0] len_count;
reg [3:0] cmd_state;
reg [7:0] err_code;
reg [15:0] rsp_dout;
reg rsp_rd;
reg [15:0] spi_test;
reg [3:0] test_flag;
always@(posedge clk or posedge rst)
begin
if(rst) begin
rec_type <= 0;
rec_addr <= 0;
addr_len <= 0;
len_count <= 0;
err_code <= REC_NO_ERR;
tx_data <= 0;
tx_req <= 0;
rsp_rd <= 1'b0;
cmd_state <= REC_START;
test_flag <= 4'h0;
end
else begin
if(tx_ack) begin
tx_req <= 1'b0;
end
case(cmd_state)
REC_START : begin
len_count <= 0;
if(rx_start) begin
cmd_state <= REC_TYPE_REG;
test_flag <= 4'h1;
end
end
REC_TYPE_REG : begin
if(rx_valid) begin
case(rx_data)
CON_WR_REG, CON_RD_REG : begin
err_code <= REC_NO_ERR;
rec_type <= rx_data;
cmd_state <= REC_ADDR_REGH;
test_flag <= 4'h2;
end
RD_DATA_REG : begin
cmd_state <= RSP_STATUS;
test_flag <= 4'h6;
end
default : cmd_state <= REC_START;
endcase
end
end
REC_ADDR_REGH : begin
if(rx_valid) begin
rec_addr[15:8] <= rx_data;
cmd_state <= REC_ADDR_REGL;
test_flag <= 4'h3;
end
end
REC_ADDR_REGL : begin
if(rx_valid) begin
rec_addr[7:0] <= rx_data;
cmd_state <= REC_ADDR_LEN;
test_flag <= 4'h4;
end
end
REC_ADDR_LEN : begin
if(rx_valid) begin
if(rx_data != 0) begin
addr_len <= rx_data;
test_flag <= 4'h5;
case(rec_type)
CON_WR_REG : cmd_state <= REC_CON_WRH;
CON_RD_REG : cmd_state <= REC_START;
default : cmd_state <= REC_START;
endcase
end
else begin
err_code <= REC_LEN_ERR;
cmd_state <= REC_START;
end
end
end
REC_CON_WRH : begin
if(len_count == addr_len) begin
cmd_state <= REC_START;
end
else begin
if(rx_valid) begin
if(rec_addr == ADDR_FPGA_VERSION) begin
err_code <= REC_WR_ERR;
cmd_state <= REC_START;
end
else if(rec_addr == ADDR_SPI_TEST) begin
spi_test[15:8] <= rx_data;
end
// else begin
// rec_buf_data[15:8] <= rx_data;
// end
cmd_state <= REC_CON_WRL;
end
end
end
REC_CON_WRL : begin
if(rx_valid) begin
if(rec_addr == ADDR_SPI_TEST) begin
spi_test[7:0] <= rx_data;
end
// else begin
// rec_buf_data[7:0] <= rx_data;
// rec_addr <= rec_addr + 1'b1;
// end
len_count <= len_count + 1'b1;
end
end
RSP_STATUS : begin
if(tx_valid) begin
if(tx_ack) begin
tx_req <= 1'b0;
end
else begin
tx_req <= 1'b1;
end
rsp_rd <= 1'b1;
tx_data <= err_code;
cmd_state <= RSP_LEN;
test_flag <= 4'h7;
end
end
RSP_LEN : begin
if(tx_valid) begin
if(tx_ack) begin
tx_req <= 1'b0;
end
else begin
tx_req <= 1'b1;
end
case(rec_type)
CON_WR_REG : begin
tx_data <= 0;
cmd_state <= REC_START;
end
CON_RD_REG : begin
tx_data <= addr_len;
cmd_state <= RSP_DIN;
end
default : begin
tx_data <= 0;
cmd_state <= REC_START;
end
endcase
test_flag <= 4'h8;
end
end
RSP_DIN : begin
if(tx_valid) begin
if(tx_ack) begin
tx_req <= 1'b0;
end
else begin
tx_req <= 1'b1;
end
if(len_count == addr_len) begin
cmd_state <= READ_DONE;
end
else begin
cmd_state <= RSP_DOUT;
rsp_rd <= 1'b0;
end
test_flag <= 4'h9;
end
end
RSP_DOUT : begin
if(tx_valid) begin
if(tx_ack) begin
tx_req <= 1'b0;
end
else begin
tx_req <= 1'b1;
end
cmd_state <= READ_DATAH;
end
end
READ_DATAH : begin
if(tx_valid) begin
if(tx_ack) begin
tx_req <= 1'b0;
end
else begin
tx_req <= 1'b1;
end
tx_data <= rsp_dout[15:8];
cmd_state <= READ_DATAL;
test_flag <= 4'hA;
end
end
READ_DATAL : begin
if(tx_valid) begin
if(tx_ack) begin
tx_req <= 1'b0;
end
else begin
tx_req <= 1'b1;
end
tx_data <= rsp_dout[7:0];
len_count <= len_count + 1'b1;
cmd_state <= RSP_DIN;
test_flag <= 4'hB;
end
end
READ_DONE : begin
if(tx_valid) begin
if(tx_ack) begin
tx_req <= 1'b0;
end
else begin
tx_req <= 1'b1;
end
tx_req <= 1'b0;
len_count <= 0;
tx_data <= 0;
rec_type <= 0;
cmd_state <= REC_START;
end
end
endcase
end
end
always @(posedge clk or posedge rst)
begin
if(rst == 1'b1)
begin
rsp_dout <= {16{1'b0}};
end
else if((rsp_rd == 1'b1) && (test_flag == 4'h7)) begin
case(rec_addr)
ADDR_FPGA_VERSION : rsp_dout <= FPGA_VERSION;
ADDR_SPI_TEST : rsp_dout <= ~spi_test;
default : ;
endcase
end
end
endmodule
链接1 :点击打开链接