ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SPI通信与M25P16 Flash读写控制系统设计

2026/9/6 4:59:40 拓冰建站 浏览量
SPI通信与M25P16 Flash读写控制系统设计 文章目录一SPI简介二,SPI物理连接2.1物理连线2.2主从方式三SPI协议特点3.1 SPI模式四Flash介绍4.1M25P164.2 M25P16工作模式4.3 M25P16指令五项目实例5.1项目介绍5.2实践方案5.3模块介绍六代码6.1rx6.2flash_ctrl6.3spi_m6.4fifo_rd6.5tx七结果验证一SPI简介SPISerial Peripheral Interface串行外围设备接口通讯协议是 Motorola 公司提出的一种同步串行接口技术是一种高速、全双工、同步通信总线在芯片中只占用四根管脚用来控制及数据传输广泛用于 EEPROM、Flash、RTC实时时钟、ADC数模转换 器、DSP数字信号处理器以及数字信号解码器上是常用的、重要的低速通讯协议之一。二,SPI物理连接2.1物理连线SPI通常包含四根线路分别是SCL,CS,MOSI,MISO.1.SCK (Serial Clock)时钟信号线用于同步通讯数据。由通讯主机产生决定了通讯的速率不同的设备支持的最高时钟频率不同.2.MOSI (Master Output Slave Input)主设备输出/从设备输入引脚。主机的数据从这条信号线输出从机由这条信号线读入主机发送的数据数据方向由主机到从机.3.MISO (Master InputSlave Output)主设备输入/从设备输出引脚。主机从这条信号线读入数据从机的数据由这条信号线输出到主机数据方向由从机到主机.4.CS (Chip Select)片选信号线。当有多个 SPI 从 设备与 SPI 主机相连时设备的其它信号线 SCK、MOSI 及 MISO 同时并联到相同的 SPI 总线上即无论有多少个从设备都共同使用这 3 条总线而每个从设备都有独立的片选信号线即有多少个从设备就有多少条片选信号线。相当于由SPI构成的通信系统中通过CS片选信号来决定通信的从机设备是哪一台。通信期间低电平有效表示对应从机被选中.2.2主从方式SPI支持一主一从和一主多从主机与从机工用SCK,MOSI,MISO每个从机以不同的CS线与主机相连。三SPI协议特点3.1 SPI模式SPI一共有四种模式四种模式由这4种模式分别由时钟极性(CPOLClock Polarity)和时钟相位(CPHAClock Phase)来定义。其中CPOL,CPHA又有两种模式以此来组成SPI的四种模式结构。CPOL0时钟在低电平时空闲在高电平时工作。CPOL1时钟在高电平时空闲在低电平时工作。CPHA0时钟在第一个跳变沿采集数据在下一个跳变沿更新数据。CPHA1时钟在第二个跳变沿采集数据在下一个跳变沿更新数据。1.模式0CPOL 0CPHA0。SCK串行时钟线空闲是为低电平数据在SCK时钟的上升沿被采样数据在SCK时钟的下降沿切换2.模式1CPOL 0CPHA1。SCK串行时钟线空闲是为低电平数据在SCK时钟的下降沿被采样数据在SCK时钟的上升沿切换3.模式2CPOL 1CPHA0。SCK串行时钟线空闲是为高电平数据在SCK时钟的下降沿被采样数据在SCK时钟的上升沿切换4.模式3CPOL 1CPHA1。SCK串行时钟线空闲是为高电平数据在SCK时钟的上升沿被采样数据在SCK时钟的下降沿切换四Flash介绍4.1M25P16此处采用的Flash芯片名为M25P16总容量为16MBit2M字节的大小包含32个扇区每个扇区包含256页每页由256个字节组成。存入数据可以保存20年时间并且芯片支持100000次擦除。M25P16电路逻辑结构串行数据输入输出在SPI上进行串行时钟由主机提供片选信号低电平有效片选信号拉低时M25P16工作。4.2 M25P16工作模式该M25P16支持两种工作模式分别是模式0与模式3.模式0在SCK的的上升沿采样数据下降沿更新数据模式3在SCK的的下降沿采样数据上升沿更新数据。4.3 M25P16指令​写使能控制​WREN、WRDI控制 WEL 写锁存是所有改写操作的前置条件。​状态寄存器操作​RDSR 读状态、WRSR 写保护配置通过 WIP 位轮询内部编程 / 擦除进度。​ID 读取​RDID (JEDEC 标准 ID推荐)、RES (旧版电子签名向后兼容)。​数据读取​READ 普通读、FAST_READ 高速读。​写编程​PP 页编程只能把 bit 1→0写前必须擦除对应存储单元。​擦除​SE 扇区擦除、BE 整片擦除将 bit 置 1BE 受块保护位严格限制。​低功耗​DP 进入深度休眠RES 唤醒芯片。五项目实例5.1项目介绍通过SPI传输数据对M25P16进行控制实现数据的写入与读出最后擦除数据。5.2实践方案使用RX模块将多个字节的命令传入写FIFO保存再由读FIFO读出数据到SPI控制模块传输进入M25P16M25P16将数据由SPI控制模块传入读FIFO最后将数据从读FIFO读出到TX模块输出。5.3模块介绍1.rx:串口接收端将外部串行rx电平信号转换为并行8bit数据传入系统。要写入数据之前应该打开写使能fe,01,01,06,ee。发送写入命令由包头fe总长度写入长度命令地址0地址1地址2写入数据包尾ee组成例如fe,07,07,02,00,00,00,aa,bb,cc,ee,该命令在在地址000000开始写入了三个数据aa,bb,cc。读出命令里面则没有写入数据如fe,07,04,03,00,00,00,ee,该指令便是读出之前写入的aa,bb,cc。2.flash_ctrl:控制rx写入的控制命令进入写FIFO然后把收到数据从写FIFO读出到spi_m进入M25P16。3.spi_mSPI的控制代码控制SPI的工作模式以及数据传输的方式。4.fifo_rd:存入M25P16返回的数据然后读出到tx中5:tx:串口发送端把并行8bit信号转化为串行tx电平信号传出系统。六代码6.1rxmodule rx ( input clk , input rst_n , input rx , output [7:0] data_rx , output done_rx ); parameter count5207 ; parameter number9 ; reg [7:0] data_reg ; wire rx_nedge ; reg [1:0] rx_reg ; reg rx_en ; reg [12:0] baud_cnt ; reg [3:0] baud_count ; //两级打拍消除亚稳态,检测rx进入起始位的下降沿 always (posedge clk or negedge rst_n) begin if (!rst_n) begin rx_reg 2b11; //rx位高电平 end else begin rx_reg {rx_reg[0],rx}; end end assign rx_nedge (rx_reg 2b10)?1:0;//判断下降沿 //使能信号有效判定 always (posedge clk or negedge rst_n) begin if(!rst_n) rx_en 1b0; else if(rx_nedge) rx_en 1b1; else if(baud_count number baud_cnt (count)/2)//当baud_count计数到9且baud_cnt计数到一半条件满足使能信号回低电平 rx_en 1b0; end // baud_cnt计数器 always (posedge clk or negedge rst_n) begin if (!rst_n) baud_cnt 13d0; else if(baud_cnt count || rx_en 1b0) baud_cnt 13d0; else baud_cnt baud_cnt 1b1; end //baud_count计数器 always (posedge clk or negedge rst_n) begin if (!rst_n) begin baud_count 4d0; end else if(rx_en 1b0) begin // 先判断使能关闭清零 baud_count 4d0; end else if(baud_cnt count) begin // 计满一个bitbit计数器1 baud_count baud_count 1b1; end else begin baud_count baud_count; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin data_reg 8b0; end else if(baud_cnt count/2 baud_count 4d1 baud_count 4d8) begin data_reg[baud_count - 1b1] rx_reg[0]; end else begin data_reg data_reg; end end assign data_rx (baud_count numberrx_en 1b1)?data_reg:0; assign done_rx (baud_count number baud_cnt (count)/2 rx_en 1b1) ? 1b1 : 1b0; endmodule6.2flash_ctrlinput clk , // 系统时钟 input rst_n , // 异步复位低有效 input [7:0] data_rx , // UART接收数据 input done_rx , // UART接收完成脉冲 input done_w , // SPI写入完成 input done_spi , // SPI传输完成 output start_spi , // 启动SPI传输脉冲 output [7:0] data_in , // SPI输入数据从FIFO读出 output [7:0] data_len , // 总字节长度 output [7:0] wt_len // 写入字节长度 ); // 状态机参数 parameter IDLE 1, // 空闲状态等待帧头0xFE LENGTH 2, // 接收总长度 WLENGTH 3, // 接收写入长度 DATA 4, // 接收数据并写入FIFO STOP 5; // 结束启动SPI reg [2:0] c_state, n_state; // 当前状态、下一状态 // 内部寄存器 reg start_reg ; // SPI启动寄存器 reg [7:0] data_reg ; // 数据缓存 reg [7:0] data_lenr ; // 总长度寄存器 reg [7:0] wt_lenr ; // 写入长度寄存器 // FIFO信号 wire rdreq; // 读请求由done_w驱动 reg wrreq; // 写请求 wire empty; // FIFO空标志 wire full; // FIFO满标志 wire [7:0] q; // FIFO读出数据 wire [7:0] usedw; // FIFO已用字数 // 输出赋值 assign start_spi start_reg; assign data_in q; // SPI数据来自FIFO输出 assign data_len data_lenr; assign wt_len wt_lenr; assign rdreq done_w; // SPI写入完成时读取FIFO // 状态时序 always (posedge clk or negedge rst_n) begin if(!rst_n) c_state IDLE; else c_state n_state; end // 状态转移组合逻辑 always (*) begin if(!rst_n) n_state IDLE; else case (c_state) IDLE : begin if(done_rx data_rx 8hfe) // 检测帧头 n_state LENGTH; else n_state c_state; end LENGTH : begin if(done_rx) n_state WLENGTH; // 接收总长度 else n_state c_state; end WLENGTH: begin if(done_rx) n_state DATA; // 接收写入长度 else n_state c_state; end DATA : begin if(done_rx data_rx 8hee) // 检测帧尾 n_state STOP; else n_state c_state; end STOP : n_state IDLE; // 回到空闲 default: n_state IDLE; endcase end // 数据与控制输出时序 always (posedge clk or negedge rst_n) begin if(!rst_n) begin start_reg 0; data_reg 0; data_lenr 0; wt_lenr 0; wrreq 0; end else case (c_state) IDLE : begin start_reg 0; data_reg data_reg; // 保持 data_lenr data_lenr; // 保持 wt_lenr wt_lenr; // 保持 wrreq 0; end LENGTH : begin start_reg 0; data_reg 0; if(done_rx) data_lenr data_rx; // 锁存总长度 wt_lenr 0; wrreq 0; end WLENGTH: begin start_reg 0; data_reg 0; if(done_rx) wt_lenr data_rx; // 锁存写入长度 wrreq 0; end DATA : begin start_reg 0; if(done_rx data_rx ! 8hee) begin wrreq 1; // 写入FIFO data_reg data_rx; // 锁存数据 end else begin wrreq 0; data_reg 0; end end STOP : start_reg 1; // 产生SPI启动脉冲 default: begin start_reg 0; data_reg 0; data_lenr 0; wt_lenr 0; end endcase end // FIFO实例化 fifo_w fifo_w_inst ( .aclr ( ~rst_n || done_spi ), // 系统复位或SPI完成时复位FIFO .clock ( clk ), .data ( data_reg ), .rdreq ( rdreq ), // 读请求由done_w驱动 .wrreq ( wrreq ), .empty ( empty ), .full ( full ), .q ( q ), .usedw ( usedw ) ); endmodule6.3spi_mmodule spi_m ( input clk, // 系统时钟 input rst_n, // 异步复位低有效 input start_spi, // SPI启动脉冲 input [7:0] data_in, // 待发送数据来自FIFO input [7:0] data_len, // 总字节长度 input [7:0] wt_len, // 写入字节长度 output done_w, // 写入完成脉冲 output done_r, // 读取完成脉冲 output done_spi, // SPI传输完成脉冲 output [7:0] data_out, // 接收到的数据 output mosi, // 主出从入 output cs_n, // 片选低有效 output sck, // SPI时钟 input miso // 主入从出 ); // 参数定义 parameter delay 6; // SPI时钟分频系数SCK clk/(2*delay) parameter mode 0; // SPI模式0/1/2/3 parameter IDLE 1, // 空闲状态 START 2, // 启动状态CS_N拉低 DATA 3, // 数据传输状态 STOP 4; // 停止状态CS_N拉高 // 状态寄存器 reg [2:0] c_state, n_state; // 计数与数据寄存器 reg [2:0] cnt; // 延时计数器0~delay-1 reg [7:0] cnt_bit; // 位计数器0~data_len*8-1 reg [1:0] start_reg; // start_spi边沿检测寄存器 reg [7:0] data_reg; // 发送数据寄存器 reg [7:0] data_tem; // 接收数据缓存 reg [7:0] data_len_r; // 总长度寄存器 reg [7:0] wt_len_r; // 写入长度寄存器 // SPI信号寄存器 reg mosi_reg; // MOSI输出 reg cs_n_reg; // CS_N输出 reg sck_reg; // SCK输出 reg done_wr; // 写完成标志 reg done_rr; // 读完成标志 // 输出赋值 assign done_w done_wr; assign done_r done_rr; assign mosi mosi_reg; assign cs_n cs_n_reg; assign sck sck_reg; // start_spi边沿检测 always (posedge clk or negedge rst_n) begin if(!rst_n) start_reg 2b00; else start_reg {start_reg[0], start_spi}; // 检测上升沿 end // 状态时序 always (posedge clk or negedge rst_n) begin if(!rst_n) c_state IDLE; else c_state n_state; end // 状态转移组合逻辑 always (*) begin if(!rst_n) n_state IDLE; else case (c_state) IDLE : begin if(start_reg 2b01) // 检测到start_spi上升沿 n_state START; else n_state c_state; end START : begin if(cnt delay-1) // 延时完成 n_state DATA; else n_state c_state; end DATA : begin case (mode) 0,2 : begin // 模式0和2上升沿采样 if(cnt_bit data_len_r*8-1 cnt delay-1) n_state STOP; else n_state c_state; end 1,3 : begin // 模式1和3下降沿采样 if(cnt_bit data_len_r*8 cnt delay/2-1) n_state STOP; else n_state c_state; end default: ; endcase end STOP : begin if(cnt delay-1) // 停止延时完成 n_state IDLE; else n_state c_state; end default : n_state IDLE; endcase end // 时序控制 always (posedge clk or negedge rst_n) begin if(!rst_n) begin cnt 0; cnt_bit 0; data_reg 0; data_tem 0; data_len_r 0; wt_len_r 0; done_wr 0; done_rr 0; mosi_reg 0; cs_n_reg 1; sck_reg 0; end else case (c_state) // IDLE状态 IDLE : begin cnt 0; cnt_bit 0; data_reg data_in; // 锁存输入数据 data_tem 0; data_len_r data_len; // 锁存总长度 wt_len_r wt_len; // 锁存写入长度 done_wr 0; done_rr 0; mosi_reg 0; cs_n_reg 1; // CS_N无效高电平 case (mode) 0,3 : sck_reg 0; // 模式0/3SCK空闲低 1,2 : sck_reg 1; // 模式1/2SCK空闲高 default: ; endcase end // START状态 START : begin if(cnt delay-1) cnt 0; else cnt cnt 1; cnt_bit 0; data_tem 0; mosi_reg 0; cs_n_reg 0; // CS_N拉低选中从机 done_wr 0; done_rr 0; end // DATA状态 DATA : begin // 延时计数器 if(cnt delay-1) cnt 0; else cnt cnt 1; // SCK翻转在delay-1和delay/2-1时刻 if(cnt delay-1 || cnt delay/2-1) sck_reg ~sck_reg; case (mode) //---------------- 模式0和2 ----------------- 0,2 : begin // 位计数器 if(cnt delay-1) begin if(cnt_bit data_len_r*8-1) cnt_bit 0; else cnt_bit cnt_bit 1; end // 数据更新每字节结束 if(cnt_bit%8 7 cnt delay-1) data_reg data_in; // 发送数据在cnt0时更新MOSI if(cnt 0) mosi_reg data_reg[7-cnt_bit%8]; // 接收数据在cntdelay/2-1时采样MISO if(cnt delay/2-1) data_tem[7-cnt_bit%8] miso; // 写入完成发送数据字节结束 if(cnt_bit%8 7 cnt delay-3 cnt_bit wt_len_r*8) done_wr 1; else done_wr 0; // 读取完成接收数据字节结束 if(cnt_bit%8 7 cnt delay-3 cnt_bit wt_len_r*8) done_rr 1; else done_rr 0; end //---------------- 模式1和3 ----------------- 1,3 : begin // 位计数器 if(cnt delay/2-1) begin if(cnt_bit data_len_r*8) cnt_bit 0; else cnt_bit cnt_bit 1; end // 数据更新每字节开始 if(cnt_bit%8 0 cnt delay/2-1) data_reg data_in; // 发送数据在cntdelay/2时更新MOSI if(cnt delay/2 cnt_bit 0) mosi_reg data_reg[7-(cnt_bit-1)%8]; // 接收数据在cnt0时采样MISO if(cnt 0 cnt_bit 0) data_tem[7-(cnt_bit-1)%8] miso; // 写入完成 if(cnt_bit%8 0 cnt delay/2-3 cnt_bit wt_len_r*8 cnt_bit 1) done_wr 1; else done_wr 0; // 读取完成 if(cnt_bit%8 0 cnt delay/2-3 cnt_bit wt_len_r*8) done_rr 1; else done_rr 0; end default: ; endcase end // STOP状态 STOP : begin if(cnt delay-1) cnt 0; else cnt cnt 1; cnt_bit 0; data_reg 0; data_tem 0; data_len_r 0; wt_len_r 0; done_wr 0; done_rr 0; mosi_reg 0; cs_n_reg 1; // CS_N拉高释放从机 sck_reg 0; end default: ; endcase end // 输出信号 assign data_out data_tem; // 接收数据输出 assign done_spi (c_state STOP cnt delay-1); // SPI传输完成脉冲 endmodule6.4fifo_rdmodule fifo_rd ( input clk, // 系统时钟 input rst_n, // 异步复位低有效 input [7:0] data_out, // SPI读取的数据写入FIFO input done_r, // SPI读取完成脉冲FIFO写请求 input done_spi, // SPI传输完成脉冲 input done_tx, // UART发送完成脉冲FIFO读请求 output [7:0] data_tx, // 待发送的UART数据从FIFO读出 output start // UART发送启动脉冲 ); // 读请求延迟寄存器 reg rdreq_r; // rdreq延迟一拍用于产生start信号 always (posedge clk or negedge rst_n) if(!rst_n) rdreq_r 0; else rdreq_r rdreq; // FIFO信号定义 wire [7:0] data; // 写入FIFO的数据 reg rdreq; // FIFO读请求由done_tx触发 wire wrreq; // FIFO写请求由done_r触发 wire empty; // FIFO空标志 wire full; // FIFO满标志 wire [7:0] q; // FIFO读出数据 wire [7:0] usedw; // FIFO已用字数 // 信号赋值 assign data data_out; // SPI接收数据写入FIFO assign wrreq done_r; // SPI读完成时写入FIFO assign data_tx q; // FIFO读出数据作为UART发送数据 assign start (done_spi || rdreq_r) !empty; // 启动UART发送 // FIFO实例化 fifo_r fifo_r_inst ( .aclr ( !rst_n ), // 系统复位时清零 .clock ( clk ), .data ( data ), // 写入数据 .rdreq ( rdreq ), // 读请求 .wrreq ( wrreq ), // 写请求 .empty ( empty ), .full ( full ), .q ( q ), // 读出数据 .usedw ( usedw ) ); // 读请求控制 always (posedge clk or negedge rst_n) begin if(!rst_n) rdreq 0; else if(done_tx) // UART发送完成读取下一个数据 rdreq 1; else rdreq 0; // 只产生一个周期脉冲 end endmodule6.5txmodule tx ( input clk, input rst_n, input start, input [7:0] data_tx, output reg tx, output reg done_tx ); parameter count5207 ; parameter number9 ; reg [12:0] tx_cnt; reg [3:0] tx_count; reg [7:0] data_reg; reg busy_tx; //为1传输数据 always (posedge clk or negedge rst_n) begin if(!rst_n)begin data_reg0; busy_tx0 ; end else if( start1!(busy_tx))begin busy_tx1; data_regdata_tx; end else if(tx_countnumbertx_cnt count)begin busy_tx0; data_reg0; end end //波特计数 always (posedge clk or negedge rst_n) begin if(!rst_n) tx_cnt0; else if(busy_tx 1b0) begin tx_cnt 0; end else begin if(tx_cnt count) tx_cnt 0; else tx_cnt tx_cnt 1b1; end end //比特计数 always (posedge clk or negedge rst_n) begin if(!rst_n) tx_count0; else if(busy_tx 1b0) tx_count0; else if(tx_cnt count) begin tx_count tx_count 1b1; end else tx_counttx_count; end //tx发送输出 always (posedge clk or negedge rst_n) begin if(!rst_n) tx1; else if(busy_tx0) tx1; else begin case (tx_count) 0:tx0; //起始位 1:txdata_reg[0]; 2:txdata_reg[1]; 3:txdata_reg[2]; 4:txdata_reg[3]; 5:txdata_reg[4]; 6:txdata_reg[5]; 7:txdata_reg[6]; 8:txdata_reg[7]; 9:tx1; //停止位 default:tx1; endcase end end //发送完成脉冲 always (posedge clk or negedge rst_n) begin if(!rst_n) done_tx0; else if(tx_count number tx_cnt count) done_tx1; else done_tx0; end endmodule七结果验证写入AA,BB,CC读出AA,BB,CC