在纯粹逻辑的配置中我写一个外设,进行SPI接口的读写。几乎是跟AD9361定制的SPI接口:发送三个字节,接收到一个字节。这个小控制器为了简单,只实现对一个AD9361寄存器的读写。我打算将这个一直到NO-OS那套软件硬件代码里面这样减少对ZYNQ硬件外设的依赖,使得这套C代码更容易移植。
关于AD9361中SPI的时序,在接口手册上明确写了,并给出了波形和时序。
这里贴出来部分这个控制器的部分VERILOG代码:
module spi_ad9361_if#(parameter cnt_bit=2)(
input clk,rst,
output reg spi_mosi,spi_clk,spi_csn,
input spi_miso,
input [23:0] din,
output reg [7:0] dout=0 ,
input valid ,
output reg ready
);
initial {spi_mosi,spi_clk,spi_csn,ready} = 0 ;
reg [7:0]st = 0 ;
wire is_delay_state =1;
reg [1:0] c = 0 ;always @ (posedge clk) if (is_delay_state) c<=c+1;else c<=0;
wire sync = c == 2'b11 ;
reg [23:0] reg24=0 ;
reg [7:0] reg8=0;
always@(posedge clk)if (rst)st<=0;else
case (st)
0 : begin st <= 1 ; ready<=0; spi_csn<=1; spi_clk<=0; end
1 : if (valid)begin st<=2; ready<=0;end
2 : begin reg24<=din ; st <= 100 ;end
100 : if (sync) begin st <= 200; spi_csn<=0;spi_clk<=0; end //spi_csn=0;
200 : if (sync) begin st <= 201 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 201 : if (sync) begin st <= 202 ; spi_clk<=0; end // bit 23
202 : if (sync) begin st <= 203 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 203 : if (sync) begin st <= 204 ; spi_clk<=0; end // bit 22
204 : if (sync) begin st <= 205 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 205 : if (sync) begin st <= 206 ; spi_clk<=0; end // bit 21
206 : if (sync) begin st <= 207 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 207 : if (sync) begin st <= 208 ; spi_clk<=0; end // bit 20
208 : if (sync) begin st <= 209 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 209 : if (sync) begin st <= 210 ; spi_clk<=0; end // bit 19
210 : if (sync) begin st <= 211 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 211 : if (sync) begin st <= 212 ; spi_clk<=0; end // bit 18
//此处删节
240 : if (sync) begin st <= 241 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 241 : if (sync) begin st <= 242 ; spi_clk<=0; reg8[3] <= spi_miso; end // bit 3
242 : if (sync) begin st <= 243 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 243 : if (sync) begin st <= 244 ; spi_clk<=0; reg8[2] <= spi_miso; end // bit 2
244 : if (sync) begin st <= 245 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 245 : if (sync) begin st <= 246 ; spi_clk<=0; reg8[1] <= spi_miso; end // bit 1
246 : if (sync) begin st <= 247 ; spi_clk<=1; spi_mosi<=reg24[23]; reg24[23:0]<={reg24[23:0],1'b0} ; end 247 : if (sync) begin st <= 248 ; spi_clk<=0; reg8[0] <= spi_miso; end // bit 0
248 : if (sync) begin st <= 249 ; spi_csn<=1; end
249 : if (sync) begin st <= 250 ; end
250 : begin st<=251;ready<=1;dout<=reg8;end
251 : st<=1;
default st <= 0 ;
endcase
endmodule
很显然这是一个聪明人用本办法写的状态机。运行效果非常好。
做成AXI_LITE接口时候顺便加上了其他一些对外AD9361控制的信号,不使用情况下可以悬空就行。
这里的控制也非常简洁。对应操作这部分SPI控制器的代码如下:
#define CFG_SPI_ACC_ADDR 0X43C00000
unsigned char cfg_spi_acc(unsigned int cmd){ //added by liwei
unsigned int r;
*(volatile unsigned int * ) CFG_SPI_ACC_ADDR = cmd ;
while(1){
r = *(volatile unsigned int * )CFG_SPI_ACC_ADDR ;
if (r&(1<<31))break;
}
return r&0xff ;
}
这里读和写都是这个函数。执行写操作时候,忽略返回数值就可以。
接下来我们看SDK项目里面要移植的接口部分。
我们先实现通过此SPI控制器对单个AD9361寄存器的读写实现:
/**
* SPI register read.
* @param spi
* @param reg The register address.
* @return The register value or negative error code in case of failure.
*/
int32_t ad9361_spi_read(struct spi_device *spi, uint32_t reg)
{
unsigned int cmd ;
cmd = AD_READ | AD_CNT(1) | AD_ADDR(reg);
cmd<<=8;
return 0xff & cfg_spi_acc(cmd);
}
/**
* SPI register write.
* @param spi
* @param reg The register address.
* @param val The value of the register.
* @return 0 in case of success, negative error code otherwise.
*/
int32_t ad9361_spi_write(struct spi_device *spi,
uint32_t reg, uint32_t val)
{
int32_t cmd;
cmd = AD_WRITE | AD_CNT(1) | AD_ADDR(reg);
cmd <<=8;
cmd |= val&0xff;
if (reg==6) printf("lw:reg[%d]=0x%02x\n",reg,(unsigned char )val);
if (reg==7) printf("lw:reg[%d]=0x%02x\n",reg,(unsigned char )val);
cfg_spi_acc(cmd);
#ifdef _DEBUG
dev_dbg(&spi->dev, "%s: reg 0x%"PRIX32" val 0x%X", __func__, reg, buf[2]);
#endif
return 0;
}
移植后的代码非常简洁明了。
原来的SDK代码里面实现了连续对多个寄存器进行读写,而我们这里只有对一个寄存进行读写的控制器外设,所以要现在C语言层面把对多个寄存器进行读写的代码转换成调用对一个寄存器进行读写的实现。
读9316的接口文档,可以看到关于连续多寄存器写的描述。在我们采用的是MSB方式先(就是SPI 先传输字节的最高位 Most Signifiaenr Bit )的情况下 ,寄存器地址是递减的,如果要写从8开始的三个寄存器,就是依次写8,7,6。这点一定注意!
/**
* SPI multiple bytes register write.
* @param spi
* @param reg The register address.
* @param tbuf The data buffer.
* @param num The number of bytes to read.
* @return 0 in case of success, negative error code otherwise.
*/
static int32_t ad9361_spi_writem(struct spi_device *spi,
uint32_t reg, uint8_t *tbuf, uint32_t num)
{
uint8_t buf[10];
int32_t ret;
uint16_t cmd;
int32_t i;
if (num > MAX_MBYTE_SPI)
return -EINVAL;
for(i=0;i<num;++i)
ad9361_spi_write( spi, reg - i ,tbuf[i]);
#ifdef _DEBUG
{
for (i = 0; i < num; i++)
dev_dbg(&spi->dev, "Reg 0x%"PRIX32" val 0x%X", reg--, tbuf[i]);
}
#endif
return 0;
}
ad9361_spi_writem这个函数是对多个寄存器进行写。ad9361_spi_write这个函数是对个寄存器进行写。我们将读多个寄存器的连续写改成了调用ad9361_spi_write函数多多个寄存器的分别写。 这里注意的是连续写方式下,是先写高地址的寄存,所以这里的索引变量i作为减数。
/**
* SPI multiple bytes register read.
* @param spi
* @param reg The register address.
* @param rbuf The data buffer.
* @param num The number of bytes to read.
* @return 0 in case of success, negative error code otherwise.
*/
int32_t ad9361_spi_readm(struct spi_device *spi, uint32_t reg,
uint8_t *rbuf, uint32_t num)
{
int32_t i;
for(i=0;i<num;++i) rbuf[i]=ad9361_spi_read(spi,reg-i);
#ifdef _DEBUG
{
for (i = 0; i < num; i++)
dev_dbg(&spi->dev, "%s: reg 0x%"PRIX32" val 0x%X",
__func__, reg--, rbuf[i]);
}
#endif
return 0;
}
这里的ad9361_spi_readm,我们改成了多次调用ad9361_spi_read来实现。注意在原来的SDK代码里面ad9361_spi_read的实现是调用ad9361_spi_readm实现的,可以这样做的道理很简单一个就是多个的特例嘛。我这里已经修改了ad9361_spi_read代码成了驱动自己的控制器。
这些工作都做完了之后,从新编译运行,一切OK!
运行单音测试选取一路组成波形。完全OK.