当前位置: 首页 > news >正文

spi_master

`timescale1ns/1ps// **********************************************************************// 模块 : spi_master// 描述 : 通用SPI主机,// spi模式可配置;// 支持 MSB/LSB 收/发;// spi频率可配置;// 地址/数据位宽可配置;// MSB模式下地址最高位为读写控制位(0写1读);// 支持连续/单次读写;// **********************************************************************module spi_master #(parameter integer CLK_FREQ=50_000_000,// 系统时钟频率 (Hz)parameter integer SPI_FREQ=1_000_000,// SPI时钟(spi_sclk)频率 (Hz)parameter integer DATA_WIDTH=8,// 数据位宽parameter integer ADDR_WIDTH=8,// 地址位宽parameter integer MODE=0,// 发送/接收数据模式, MODE=0:MSB; MODE=1:LSB;parameter integer CPOL=0,// 时钟极性 CPOL=0: sclk空闲低电平;CPOL=1: 空闲高电平。parameter integer CPHA=0// 时钟相位 CPHA=0: 第一个边沿采样;CPHA=1: 第二个边沿采样;)(input wire sys_clk,input wire sys_rst_n,input wire start_en,// 启动input wire continue_mode,// 读写连续模式控制,0:单次读写;1:连续读写;// 当为0时,读写数量默认为1;当为1时,读写数量由 rw_num 确定;// 连续模式下只写一次起始地址(address)即可,后续只发送数据(或只接收数据),无需重复发送地址。input wire[31:0]rw_num,// 连续模式下读写数据的数量,非连续模式下该信号不起作用input wire[ADDR_WIDTH-1:0]address,// 待发送地址;地址的最高位为读写控制位,0:写操作; 1:读操作;input wire[DATA_WIDTH-1:0]tx_data,// 待发送数据output reg[DATA_WIDTH-1:0]rx_data,// 接收数据output reg tx_done,// 发送完成脉冲output reg rx_done,// 接收完成脉冲output reg address_done,// 写地址完成脉冲output reg all_done,// 所有操作完成脉冲// SPI接口output reg spi_sclk,output reg spi_cs,output reg spi_mosi,input wire spi_miso);// ========== 参数计算 ==========// 强制偶数分频,确保50%占空比localparam integer SCLK_DIV_RAW=CLK_FREQ/SPI_FREQ;localparam integer SCLK_DIV=(SCLK_DIV_RAW<2)?2:((SCLK_DIV_RAW&1)?SCLK_DIV_RAW+1:SCLK_DIV_RAW);localparam integer SCLK_HALF=(SCLK_DIV/2)-1;// 半周期计数最大值localparam integer SCLK_FULL=SCLK_DIV-1;// 全周期计数最大值localparam integer HALF_CYCLES=SCLK_DIV/2;// CS建立/结束延迟周期数// 边沿定义:根据CPOL/CPHA计算移位边沿和采样边沿localparam SHIFT_ON_RISE=(CPOL^CPHA)!=0;localparam SAMPLE_ON_RISE=(CPOL^CPHA)==0;// ========== 内部信号 ==========reg[31:0]sclk_cnt;// SCLK分频计数器reg sclk_en;// SCLK使能reg sclk_reg;// SCLK寄存器输出reg sclk_prev;// SCLK前一拍值(边沿检测用)wire sclk_rise=sclk_reg&&!sclk_prev;wire sclk_fall=!sclk_reg&&sclk_prev;wire shift_edge=SHIFT_ON_RISE?sclk_rise:sclk_fall;wire sample_edge=SAMPLE_ON_RISE?sclk_rise:sclk_fall;reg[31:0]setup_cnt;// CS建立计数器wire setup_done=(setup_cnt==0);reg[31:0]delay_cnt;// CS结束延迟计数器wire delay_done=(delay_cnt==0);// 状态机定义localparam S_IDLE=4'd0,S_CS_SETUP=4'd1,S_ADDR=4'd2,S_DATA_PREP=4'd3,// 等待第一个采样边沿S_WR_DATA=4'd4,S_RD_DATA=4'd5,S_WAIT_SCLK_IDLE=4'd6,S_END_DELAY=4'd7,S_WAIT_TX=4'd8,S_WAIT_RX=4'd9,S_DONE=4'd10;reg[3:0]state;reg[31:0]bit_cnt;// 当前传输剩余位数reg[31:0]word_cnt;// 剩余读写次数(连续模式)reg wr_mode;// 1:写操作,0:读操作reg[ADDR_WIDTH-1:0]addr_shift;// 地址移位寄存器reg[DATA_WIDTH-1:0]tx_shift;// 发送数据移位寄存器reg[DATA_WIDTH-1:0]rx_shift;// 接收数据移位寄存器// ========== SCLK生成 (偶数分频,50%占空比) ==========always @(posedge sys_clk)beginif(!sys_rst_n)begin sclk_reg<=CPOL;sclk_prev<=CPOL;sclk_cnt<=0;sclk_en<=1'b0;endelsebegin sclk_prev<=sclk_reg;if(sclk_en)beginif(sclk_cnt==SCLK_FULL)begin sclk_cnt<=0;sclk_reg<=~sclk_reg;endelseif(sclk_cnt==SCLK_HALF)begin sclk_cnt<=sclk_cnt+1;sclk_reg<=~sclk_reg;endelsebegin sclk_cnt<=sclk_cnt+1;end endelsebegin sclk_cnt<=0;sclk_reg<=CPOL;end end end// ========== CS建立计数器 ==========always @(posedge sys_clk)beginif(!sys_rst_n)setup_cnt<=HALF_CYCLES-1;elseif(state==S_CS_SETUP)beginif(!setup_done)setup_cnt<=setup_cnt-1;elsesetup_cnt<=HALF_CYCLES-1;endelsesetup_cnt<=HALF_CYCLES-1;end// ========== CS结束延迟计数器 ==========always @(posedge sys_clk)beginif(!sys_rst_n)delay_cnt<=HALF_CYCLES-1;elseif(state==S_END_DELAY)beginif(!delay_done)delay_cnt<=delay_cnt-1;elsedelay_cnt<=HALF_CYCLES-1;endelsedelay_cnt<=HALF_CYCLES-1;end// ========== 主状态机 + MOSI寄存器输出 ==========always @(posedge sys_clk)beginif(!sys_rst_n)begin state<=S_IDLE;spi_cs<=1'b1;spi_mosi<=1'b0;tx_done<=1'b0;rx_done<=1'b0;address_done<=1'b0;all_done<=1'b0;rx_data<=0;bit_cnt<=0;word_cnt<=0;wr_mode<=1'b0;addr_shift<=0;tx_shift<=0;rx_shift<=0;endelsebegin// 默认清除脉冲信号tx_done<=1'b0;rx_done<=1'b0;address_done<=1'b0;all_done<=1'b0;case(state)// 空闲状态S_IDLE:begin spi_cs<=1'b1;spi_mosi<=1'b0;sclk_en<=1'b0;if(start_en)begin wr_mode<=~address[ADDR_WIDTH-1];// 0:读,1:写addr_shift<=address;bit_cnt<=ADDR_WIDTH;word_cnt<=continue_mode?rw_num:1;spi_cs<=1'b0;state<=S_CS_SETUP;end end// CS建立延迟S_CS_SETUP:beginif(setup_done)begin sclk_en<=1'b1;// 根据MODE预置地址首位到MOSIif(MODE==0)spi_mosi<=addr_shift[ADDR_WIDTH-1];// MSB firstelsespi_mosi<=addr_shift[0];// LSB firststate<=S_ADDR;end end// 发送地址S_ADDR:beginif(shift_edge)beginif(bit_cnt>1)beginif(MODE==0)begin// MSB:移出当前最高位,准备次高位spi_mosi<=addr_shift[ADDR_WIDTH-2];addr_shift<={addr_shift[ADDR_WIDTH-2:0],1'b0};endelsebegin// LSB:移出当前最低位,准备次低位spi_mosi<=addr_shift[1];addr_shift<={1'b0,addr_shift[ADDR_WIDTH-1:1]};end bit_cnt<=bit_cnt-1;endelsebegin// bit_cnt == 1address_done<=1'b1;if(wr_mode)begin// 写模式:加载发送数据,并根据MODE设置MOSI首bittx_shift<=tx_data;if(MODE==0)spi_mosi<=tx_data[DATA_WIDTH-1];// MSBelsespi_mosi<=tx_data[0];// LSBbit_cnt<=DATA_WIDTH;state<=S_DATA_PREP;endelsebegin// 读模式:清空接收移位寄存器,MOSI保持低电平rx_shift<={DATA_WIDTH{1'b0}};spi_mosi<=1'b0;bit_cnt<=DATA_WIDTH;state<=S_RD_DATA;end end end end// 数据准备阶段(等待第一个采样边沿,仅用于写操作)S_DATA_PREP:begin// 等待采样边沿,确保从机采样到MOSI数据if(sample_edge)begin state<=S_WR_DATA;end end// 写数据阶段S_WR_DATA:beginif(shift_edge)beginif(bit_cnt>1)beginif(MODE==0)begin// MSB:移出当前最高位,准备次高位spi_mosi<=tx_shift[DATA_WIDTH-2];tx_shift<={tx_shift[DATA_WIDTH-2:0],1'b0};endelsebegin// LSB:移出当前最低位,准备次低位spi_mosi<=tx_shift[1];tx_shift<={1'b0,tx_shift[DATA_WIDTH-1:1]};end bit_cnt<=bit_cnt-1;endelsebegin// bit_cnt == 1tx_done<=1'b1;state<=S_WAIT_SCLK_IDLE;end end end// 读数据阶段S_RD_DATA:beginif(shift_edge)begin// 读操作时MOSI保持低电平spi_mosi<=1'b0;endif(sample_edge)beginif(bit_cnt==1)begin rx_data<={spi_miso,rx_shift[DATA_WIDTH-1:1]};rx_done<=1'b1;state<=S_WAIT_SCLK_IDLE;endelsebegin rx_shift<={spi_miso,rx_shift[DATA_WIDTH-1:1]};bit_cnt<=bit_cnt-1;end end end// 等待SCLK回到空闲电平S_WAIT_SCLK_IDLE:beginif(sclk_reg==CPOL)begin sclk_en<=1'b0;if(word_cnt==1)begin state<=S_END_DELAY;endelsebegin word_cnt<=word_cnt-1;if(wr_mode)state<=S_WAIT_TX;elsestate<=S_WAIT_RX;end end end// 结束延迟,保证CS释放后时序S_END_DELAY:beginif(delay_done)begin spi_cs<=1'b1;all_done<=1'b1;state<=S_DONE;end end// 连续写:准备下一笔数据S_WAIT_TX:begin tx_shift<=tx_data;if(MODE==0)spi_mosi<=tx_data[DATA_WIDTH-1];// MSBelsespi_mosi<=tx_data[0];// LSBbit_cnt<=DATA_WIDTH;sclk_en<=1'b1;state<=S_DATA_PREP;end// 连续读:准备下一笔数据S_WAIT_RX:begin rx_shift<={DATA_WIDTH{1'b0}};spi_mosi<=1'b0;bit_cnt<=DATA_WIDTH;sclk_en<=1'b1;state<=S_RD_DATA;end// 完成状态,一个周期后返回IDLES_DONE:begin state<=S_IDLE;enddefault:state<=S_IDLE;endcase end end// ========== SCLK输出 ==========always @(posedge sys_clk)beginif(!sys_rst_n)spi_sclk<=1'b0;elsespi_sclk<=sclk_reg;end endmodule
http://www.gsyq.cn/news/1429846.html

相关文章:

  • 第八届高分子化学国际研讨会 (ICPC 2026)
  • Python类型推导协议
  • 城通网盘解析器:3分钟掌握免费高速下载的终极方案
  • OpencvSharp 算子学习教案之 - Cv2.CvtColor
  • MATLAB图论实战:除了shortestpath,自己写的Dijkstra函数如何优化与可视化?
  • 3PEAK思瑞浦 TP5551-TR SOT23-5 精密运放
  • OmenSuperHub:彻底释放惠普暗影精灵游戏本性能的终极解决方案
  • OpencvSharp 算子学习教案之 - Cv2.CvtColorTwoPlane
  • 双系统Ubuntu18.04升级22.04,安装docker进行openclaw安装
  • 【电赛保姆级教程】别在比赛时从零写代码了!电赛“祖传代码库”搭建与OLED多级菜单硬核指南
  • 2026年5月AI模型性能排行:代码能力Claude霸榜,智谱GLM杀入前十
  • 调试记录 - 2024年1月15日
  • 告别排版焦虑:西安交大LaTeX论文模板让你专注学术创新
  • 【电赛保姆级教程】别再用L298N了!电赛电机驱动与高阶控制(带FOC扫盲)硬核避坑指南
  • LabVIEW与外部设备通信秘籍:用DLL传递复杂结构体(含数组/嵌套结构)的完整配置流程
  • 那些年,我追Google Trends追到精疲力尽的故事
  • 深入FIO引擎:除了libaio,这些ioengine(如sync, psync, mmap)在Linux下到底怎么选?性能差多少?
  • 口袋神器!Arduino 创客必备,可接入 DeepSeek、Qwen 等 AI 大模型,通过 GPIO 串口控制 IoT 智能设备
  • C# 泛型
  • C++之父开撕AI Coding:资深开发者宁愿退休也不愿伺候AI生成的代码
  • 为什么你的论文参考文献格式总是不对?3个GB/T 7714 BibTeX样式终极解决方案
  • 187、运动控制中的行业应用:机械臂力控打磨
  • 前端内存泄漏常见场景与排查
  • GTA5线上小助手:免费开源工具帮你轻松称霸洛圣都终极指南
  • Kettle官网大变样?别慌!手把手教你找到最新9.3版本的下载入口(附Hadoop Shims获取指南)
  • 【AI+房地产实战指南】:2024年最值得落地的7大智能整合场景与避坑清单
  • ARP 协议:网络世界里的“地址翻译官“
  • SBM-20-1盖革管3D打印端盖制作:从零打造专业级辐射探测器接口
  • 2026AI漫剧创作深度测评:如何为你的创作需求匹配最佳方案? - 速递信息
  • 189、运动控制中的行业应用:医疗设备(手术机器人)