ARM与FPGA通过spi通信设计2.spi master的实现
这里主要放两个代码第一个是正常的不使用状态机的SPI主机代码;第二个是状态机SPI代码
1.不使用状态机:特权同学《深入浅出玩转FPGA》中DIY数码相框部分代码:
//////////////////////////////////////////////////////////////////////////////// module spi_ctrl( clk,rst_n, spi_miso,spi_mosi,spi_clk, spi_tx_en,spi_tx_rdy,spi_rx_en,spi_rx_rdy,spi_tx_db,spi_rx_db ); input clk; //FPAG输入时钟信号25MHz input rst_n; //FPGA输入复位信号 input spi_miso; //SPI主机输入从机输出数据信号 output spi_mosi; //SPI主机输出从机输入数据信号 output spi_clk; //SPI时钟信号,由主机产生 input spi_tx_en; //SPI数据发送使能信号,高有效 output spi_tx_rdy; //SPI数据发送完成标志位,高有效 input spi_rx_en; //SPI数据接收使能信号,高有效 output spi_rx_rdy; //SPI数据接收完成标志位,高有效 input[7:0] spi_tx_db; //SPI数据发送寄存器 output[7:0] spi_rx_db; //SPI数据接收寄存器 //模拟SPI的时序模式为CPOL=1, CPHA=1,模拟速率为25Mbit //------------------------------------------------- //SPI时序控制计数器,所有SPI时序由该计数器值控制 reg[4:0] cnt8; //SPI时序控制计数器,计数范围在0-18 always @(posedge clk or negedge rst_n) if(!rst_n) cnt8 <= 5\'d0; else if(spi_tx_en || spi_rx_en) begin if(cnt8 < 5\'d18)cnt8 <= cnt8+1\'b1; //SPI工作使能 else ; //计数到18停止,等待撤销spi使能 end else cnt8 <= 5\'d0; //SPI关闭,计数停止 //------------------------------------------------- //SPI时钟信号产生 reg spi_clkr; //SPI时钟信号,由主机产生 always @(posedge clk or negedge rst_n) if(!rst_n) spi_clkr <= 1\'b1; else if(cnt8 > 5\'d1 && cnt8 < 5\'d18) spi_clkr <= ~spi_clkr; //在cnt8处于2-17时SPI时钟有效翻转 assign spi_clk = spi_clkr; //------------------------------------------------- //SPI主机输出数据控制 reg spi_mosir; //SPI主机输出从机输入数据信号 always @(posedge clk or negedge rst_n) if(!rst_n) spi_mosir <= 1\'b1; else if(spi_tx_en) begin case(cnt8[4:1]) //主机发送8bit数据 4\'d1: spi_mosir <= spi_tx_db[7]; //发送bit7 4\'d2: spi_mosir <= spi_tx_db[6]; //发送bit6 4\'d3: spi_mosir <= spi_tx_db[5]; //发送bit5 4\'d4: spi_mosir <= spi_tx_db[4]; //发送bit4 4\'d5: spi_mosir <= spi_tx_db[3]; //发送bit3 4\'d6: spi_mosir <= spi_tx_db[2]; //发送bit2 4\'d7: spi_mosir <= spi_tx_db[1]; //发送bit1 4\'d8: spi_mosir <= spi_tx_db[0]; //发送bit0 default: spi_mosir <= 1\'b1; //spi_mosi没有输出时应保持高电平 endcase end else spi_mosir <= 1\'b1; //spi_mosi没有输出时应保持高电平 assign spi_mosi = spi_mosir; //------------------------------------------------- //SPI主机输入数据控制 reg[7:0] spi_rx_dbr; //SPI主机输入从机输出数据总线寄存器 always @(posedge clk or negedge rst_n) if(!rst_n) spi_rx_dbr <= 8\'hff; else if(spi_rx_en) begin case(cnt8) //主机接收并锁存8bit数据 5\'d3: spi_rx_dbr[7] <= spi_miso; //接收bit7 5\'d5: spi_rx_dbr[6] <= spi_miso; //接收bit6 5\'d7: spi_rx_dbr[5] <= spi_miso; //接收bit5 5\'d9: spi_rx_dbr[4] <= spi_miso; //接收bit4 5\'d11: spi_rx_dbr[3] <= spi_miso; //接收bit3 5\'d13: spi_rx_dbr[2] <= spi_miso; //接收bit2 5\'d15: spi_rx_dbr[1] <= spi_miso; //接收bit1 5\'d17: spi_rx_dbr[0] <= spi_miso; //接收bit0 default: ; endcase end assign spi_rx_db = spi_rx_dbr; //------------------------------------------------- //SPI数据发送完成标志位,高有效 assign spi_tx_rdy = (cnt8 == 5\'d18)/* & spi_tx_en)*/; //------------------------------------------------- //SPI数据接收完成标志位,高有效 assign spi_rx_rdy = (cnt8 == 5\'d18)/* & spi_rx_en)*/; endmodule
2.使用状态机的SPI master(来源网络)
module spi_master ( input sys_clk, input rst, output nCS, //chip select (SPI mode) output DCLK, //spi clock output MOSI, //spi master data output input MISO, //spi master input input CPOL, input CPHA, input nCS_ctrl, input[15:0] clk_div, input wr_req, output wr_ack, input[7:0] data_in, output[7:0] data_out ); //状态机状态 localparam IDLE = 0; localparam DCLK_EDGE = 1; localparam DCLK_IDLE = 2; localparam ACK = 3; localparam LAST_HALF_CYCLE = 4; localparam ACK_WAIT = 5; reg DCLK_reg; reg[7:0] MOSI_shift;//移位寄存器 reg[7:0] MISO_shift; reg[2:0] state; reg[2:0] next_state; reg [15:0] clk_cnt; reg[4:0] clk_edge_cnt; assign MOSI = MOSI_shift[7]; assign DCLK = DCLK_reg; assign data_out = MISO_shift; assign wr_ack = (state == ACK); assign nCS = nCS_ctrl; /*************这个就是状态机的定义**************/ always@(posedge sys_clk or posedge rst) begin if(rst) state <= IDLE; else state <= next_state; end /****************end*************************/ /****************状态机的具体过程*************/ always@(*) begin case(state) IDLE: if(wr_req == 1\'b1) next_state <= DCLK_IDLE; else next_state <= IDLE; DCLK_IDLE: //half a SPI clock cycle produces a clock edge//半个SPI时钟周期产生时钟边沿 if(clk_cnt == clk_div) next_state <= DCLK_EDGE; else next_state <= DCLK_IDLE; DCLK_EDGE: //a SPI byte with a total of 16 clock edges//一个SPI字节,总共有16个时钟边沿 if(clk_edge_cnt == 5\'d15) next_state <= LAST_HALF_CYCLE; else next_state <= DCLK_IDLE; //this is the last data edge //这是最后一个数据边缘 LAST_HALF_CYCLE: if(clk_cnt == clk_div) next_state <= ACK; else next_state <= LAST_HALF_CYCLE; //send one byte complete//发送一个字节完成 ACK: next_state <= ACK_WAIT; //wait for one clock cycle, to ensure that the cancel request signal//等待一个时钟周期,以确保取消请求信号 ACK_WAIT: next_state <= IDLE; default: next_state <= IDLE; endcase end /****************时钟翻转************************/ always@(posedge sys_clk or posedge rst) begin if(rst) /*在空闲状态之前,SCK一直保持CPOL的极性*/ DCLK_reg <= 1\'b0; else if(state == IDLE) DCLK_reg <= CPOL; else if(state == DCLK_EDGE) /*边缘检测时,反转SCK*/ DCLK_reg <= ~DCLK_reg;//SPI clock edge end /****************end*****************************/ //SPI clock wait counter /*一个计数器*/ always@(posedge sys_clk or posedge rst) begin if(rst) clk_cnt <= 16\'d0; else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE) clk_cnt <= clk_cnt + 16\'d1; else clk_cnt <= 16\'d0; end //SPI clock edge counter always@(posedge sys_clk or posedge rst) begin if(rst) clk_edge_cnt <= 5\'d0; else if(state == DCLK_EDGE) clk_edge_cnt <= clk_edge_cnt + 5\'d1; else if(state == IDLE) clk_edge_cnt <= 5\'d0; end //SPI data output /*这里就是SPI输出的移位方式*/ always@(posedge sys_clk or posedge rst) begin if(rst) MOSI_shift <= 8\'d0; else if(state == IDLE && wr_req) MOSI_shift <= data_in; else if(state == DCLK_EDGE) if(CPHA == 1\'b0 && clk_edge_cnt[0] == 1\'b1) /*两种方式,取决于CPHA*/ MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]}; /*常见的移位语句,大家要敏感*/ else if(CPHA == 1\'b1 && (clk_edge_cnt != 5\'d0 && clk_edge_cnt[0] == 1\'b0)) MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]}; end //SPI data input always@(posedge sys_clk or posedge rst) begin if(rst) MISO_shift <= 8\'d0; else if(state == IDLE && wr_req) MISO_shift <= 8\'h00; else if(state == DCLK_EDGE) if(CPHA == 1\'b0 && clk_edge_cnt[0] == 1\'b0) MISO_shift <= {MISO_shift[6:0],MISO}; /*MISO输入,然后进行移位*/ else if(CPHA == 1\'b1 && (clk_edge_cnt[0] == 1\'b1)) MISO_shift <= {MISO_shift[6:0],MISO}; end endmodule
第二个例子实现了较为全面的spi主机功能,可以设置SPI相位和极性,有较高的参考价值
以上两个源代码可供大家参考
版权声明:本文为yongleili717原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。