FPGA零基础学习精选|SDRSDRAM驱动设计

原创 FPGA技术江湖 2022-07-19 08:02

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。


本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。

系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用,这种快乐试试你就会懂的。话不多说,上货。



高级设计:SDR SDRAM 驱动设计


作者:郝旭帅  校对:陆辉


本篇实现基于叁芯智能科技的SANXIN -B01 FPGA开发板,以下为配套的教程,如有入手开发板,可以登录官方淘宝店购买,还有配套的学习视频。

FPGA入门进阶板卡推荐 | SANXIN B02 FPGA开发板(Xilinx)

FPGA入门进阶板卡推荐 | SANXIN B01 FPGA开发板(Intel)

SANXIN-B01开发板verilog教程V3电子版

FPGA就业班,课程内容丰富,系统性学习FPGA,高薪就业,线上线下同步,7月15号开班!


本系列的技术文档、源工程代码可以登录叁芯智能科技官方技术论坛下载。

论坛网址:www.sxznfpga.com


随机访问存储器(RAM)分为静态RAM(SRAM)和动态RAM(DRAM)。由于动态存储器存储单元的结构非常简单,所以它能达到的集成度远高于静态存储器。但是动态存储器的存取速度不如静态存储器快。


RAM的动态存储单元是利用电容可以存储电荷的原理制成的。由于存储单元的机构能够做得很简单,所以在大容量、高集成度的RAM中得到了普遍的应用。但是由于电容的容量很小,而漏电流又不可能绝对等于零,所以电荷保存的时间有限。为了及时补充漏掉的电荷以避免存储的信号丢失,必须定时地给电容补充电荷,通常将这种操作称为刷新。


行列地址线被选中后,数据线(data_bit)直接和电容相连接。当写入时,数据线给电容充放电;读取时,电容将数据线拉高或者置低。


SDRAM 的全称即同步动态随机存储器(Synchronous Dynamic Random Access Memory);这里的同步是指其时钟频率与对应控制器的系统时钟频率相同,并且内部命令的发送与数据传输都是以该时钟为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失。


SDR SDRAM中的SDR是指单数据速率,即每一根数据线上,每个时钟只传输一个bit的数据。SDR SDRAM的时钟频率可以达到100MHz以上,按照100MHz的速率计算,一片16位数据宽度的SDR SDRAM的读写数据带宽可以达到1.6Gbit/s。


SANXIN – B01的开发板上有一个容量为256Mbit(16M x 16bit)的SDR SDRAM(H57V2562GTR)。其内部存储时,分为了4个独立的区域(BANK),每个bank为4Mx16bit的存储空间;每个bank在存储时,按照二维的方式进行存储,利用行列来进行确定,有8192行(13bit地址线),有512列(9bit地址线),8192 x 512为4M的存储量。


在进行指定某个地址时,共需要2位bank地址,13位行地址,9位列地址,合计共24位地址。但是在SDR SDRAM的指定某个地址时,行地址和列地址不是同时给出,SDR SDRAM采用行列地址线复用,所以地址线合计为2(bank 地址)+13(行、列地址复用)。


SDR SDRAM需要时钟端和时钟使能端。SDR SDRAM所有的操作都依靠于此时钟;当时钟使能端无效时,SDR SDRAM自动忽略时钟上升沿。


SDR SDRAM拥有四个命令控制线,分别为CS、RAS、CAS、WE。组成的命令表如下:


在写入数据时,有时会出现不想对某8bit进行写入,就可以采用DQM进行控制。


SDR SDRAM的内部机构为:


由于SDR SDRAM为DRAM,内部的存储都是靠电容进行保存数据,电容的保持数据的时间为64ms,SDR SDRAM每次只能够刷新一行,为了不丢失任何数据,所以要保证64ms内,将所有的行都要刷新一遍。


SDR SDRAM支持读写的长度为1、2、4、8和一行(整页)。


具体的SDR SDRAM的介绍可以查看手册。下面只介绍几个相对重要的时序图。


在SDR SDRAM正常使用之前,需要进行初始化。初始化的时序图如下:

在PRECHARGE时,A10为高,表示选中所有的bank;A10为低,表示选中BA0、BA1所指定的bank。初始化中,A10置高。


在LOAD MOOE REGISTER中,采用地址线进行配置模式寄存器。说明如下:


在模式配置中,利用CL(CAS Latency)表示列选通潜伏期,利用BL(Burst Length)表示突发长度。


SDR SDRAM中有内部的刷新控制器和刷新的行计数器,外部控制器只需要保证在64ms之内进行8192次刷新即可。


在进行PRECHARGE时,A10要为高电平。


SDR SDRAM中,我们可以在任意位置进行写入。写入的时序图如下:


SDR SDRAM中,我们可以在任意位置进行读出。读出的时序图如下:


在各个时序中的时序参数如下:



  • 设计要求

设计一个突发长度为2,列选通潜伏期为2的SDR SDRAM的控制器。


  • 设计分析

该控制器共有四部分功能,初始化、刷新、写和读。四部分的执行控制采用一个模块来控制。


SDR SDRAM必须要进行初始化,初始化只用执行一次。然后启动一个计时器,等计时器达到后,进行刷新。在刷新的间隔中,根据读写的要求进行读写。


四个模块都会对SDR SDRAM的命令线和地址线进行控制,所以输出时,采用多路选择器对齐进行选择输出。


四个模块按照对应的时序图进行编写代码即可。


  • 架构设计和信号说明

该控制器命名为sdr_drive。


pll_sdr(锁相环模块):产生驱动所需要的100MHz的时钟(0度相位)、SDR SDRAM所需要的100MHz的时钟(270度相位)、以及PLL锁定信号当作系统复位使用。


timer(刷新计时器):当启动计时器后,开始计时,当计时到规定时间后,输出刷新请求,计数器直接清零计数计数。当控制器响应后,输出清除信号后,刷新请求拉低。


refresh(刷新模块)、init(初始化模块)、sdr_write(写模块)、sdr_read(读模块):当启动模块后,按照规定的时序进行输出即可,然后输出完成信号。


sdr_ctrl(控制模块):控制各个模块协调工作。


mux4_1(四选一多路选择器模块):选择对应的bus总线作为输出。


*_bus的组成为:高四位为sdr_cs_n、sdr_ras_n、sdr_cas_n、sdr_we_n。然后是bank的两位,后续为13位的sdr_addr。


  • sdr_drive_head声明

将驱动中用到各种参数定义在该文件中。

`define       SDR_ADDR_WIDTH                    13`define       SDR_COL_ADDR_WIDTH                9`define       SDR_REFRESH_TIME                  64_000_000
`define ADDR_WIDTH 2 + `SDR_ADDR_WIDTH + `SDR_COL_ADDR_WIDTH`define BUS_WIDTH 4 + 2 + `SDR_ADDR_WIDTH
`define CMD_INH 4'b1000`define NOP 4'b0111`define ACT 4'b0011`define RD 4'b0101`define WR 4'b0100`define BT 4'b0110`define PREC 4'b0010`define REFR 4'b0001`define LMR 4'b0000
`define PU_DELAY 20_000`define Trp 3`define Trfc 7`define Tmrd 3`define Trcd 3`define Twr 3`define Tcl 2
`define CODE 13'b000_0_00_010_0_001`define REFRESH_TIME (`SDR_REFRESH_TIME/(2**`SDR_ADDR_WIDTH))/10


  • pll_sdr设计实现

该模块为IP core,输出0相位的100MHz(系统时钟)和270相位的100MHz(SDR的时钟)。系统设计中,信号在上升沿输出;对于外部器件(相位调整为270),能够较好的满足建立和保持时间。


  • init设计实现

该模块负责将SDR SDRAM进行初始化。上电延迟(PU_DELAY)设置为200us;预充电时间(Trp)设置为3个时钟周期(30ns);自刷新时间(Trfc)设置为7个时钟周期(70ns);模式寄存器应用时间(Tmrd)设置为3个时钟周期(30ns);突发长度为2;列选通潜伏期为3。


按照对应的初始化的时序图,做出如下设计。


本模块采用状态机的方式设计实现。


设计代码为:

`include "../rtl/sdr_drive_head.v"
module init (
input wire clk, input wire rst_n,
input wire init_en, output reg init_done,
output wire [`BUS_WIDTH - 1 : 0] init_bus);
localparam IDLE = 7'b000_0001; localparam PUD = 7'b000_0010; localparam PRECHARGE = 7'b000_0100; localparam AUTOREFR1 = 7'b000_1000; localparam AUTOREFR2 = 7'b001_0000; localparam LMR_STATE = 7'b010_0000; localparam INITDONE = 7'b100_0000;
reg [6:0] c_state; reg [6:0] n_state; wire [1:0] sdr_bank; reg [3:0] sdr_cmd; reg [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr; reg [14:0] cnt;
assign sdr_bank = 2'b00; assign init_bus = {sdr_cmd,sdr_bank,sdr_addr};
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) c_state <= IDLE; else c_state <= n_state; end
always @ * begin case (c_state) IDLE : begin if (init_en == 1'b1) n_state = PUD; else n_state = IDLE; end
PUD : begin if (cnt == `PU_DELAY - 1'b1) n_state = PRECHARGE; else n_state = PUD; end
PRECHARGE : begin if (cnt == `Trp - 1'b1) n_state = AUTOREFR1; else n_state = PRECHARGE; end
AUTOREFR1 : begin if (cnt == `Trfc - 1'b1) n_state = AUTOREFR2; else n_state = AUTOREFR1; end
AUTOREFR2 : begin if (cnt == `Trfc - 1'b1) n_state = LMR_STATE; else n_state = AUTOREFR2; end
LMR_STATE : begin if (cnt == `Tmrd - 1'b1) n_state = INITDONE; else n_state = LMR_STATE; end
INITDONE : begin n_state = INITDONE; end
default : n_state = IDLE; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) sdr_cmd <= `NOP; else case (c_state) IDLE : sdr_cmd <= `NOP; PUD : begin if (cnt == `PU_DELAY - 1'b1) sdr_cmd <= `PREC; else sdr_cmd <= `NOP; end PRECHARGE : begin if (cnt == `Trp - 1'b1) sdr_cmd <= `REFR; else sdr_cmd <= `NOP; end AUTOREFR1 : begin if (cnt == `Trfc - 1'b1) sdr_cmd <= `REFR; else sdr_cmd <= `NOP; end AUTOREFR2 : begin if (cnt == `Trfc - 1'b1) sdr_cmd <= `LMR; else sdr_cmd <= `NOP; end LMR_STATE : sdr_cmd <= `NOP; INITDONE : sdr_cmd <= `NOP; default : sdr_cmd <= `NOP; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) cnt <= 15'd0; else case (c_state) IDLE : cnt <= 16'd0; PUD : begin if (cnt < `PU_DELAY - 1'b1) cnt <= cnt + 1'b1; else cnt <= 16'd0; end
PRECHARGE : begin if (cnt < `Trp - 1'b1) cnt <= cnt + 1'b1; else cnt <= 16'd0; end AUTOREFR1 : begin if (cnt < `Trfc - 1'b1) cnt <= cnt + 1'b1; else cnt <= 16'd0; end AUTOREFR2 : begin if (cnt < `Trfc - 1'b1) cnt <= cnt + 1'b1; else cnt <= 16'd0; end LMR_STATE : begin if (cnt < `Tmrd - 1'b1) cnt <= cnt + 1'b1; else cnt <= 16'd0; end INITDONE : cnt <= 16'd0; default : cnt <= 16'd0; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) init_done <= 1'b0; else if (c_state == LMR_STATE && cnt == `Tmrd - 1'b1) init_done <= 1'b1; else init_done <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) sdr_addr <= 0; else if (c_state == PUD && cnt == `PU_DELAY - 1'b1) sdr_addr[10] <= 1'b1; else if (c_state == AUTOREFR2 && cnt == `Trfc - 1'b1) sdr_addr <= `CODE; else sdr_addr <= 0; end
endmodule


  • timer设计实现


SDR SDRAM内部构造为DRAM,需要不间断的刷新,要求64ms刷新一遍。每次刷新为一行,开发板上的SDR SDRAM共有8192行,平均需要7812.5ns刷新一次,我们选择7810刷新一次。


到达规定的刷新时间时,控制器有可能正在进行其他的操作。在设计时,达到时间后,发出刷新请求,当外部执行刷新后,将次请求清除。发出刷新请求的同时,计数器重新归零计数。

`include "../rtl/sdr_drive_head.v"
module timer (
input wire clk, input wire rst_n,
input wire time_en, input wire req_clr,
output reg refresh_req);
reg [9:0] cnt;
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) cnt <= 10'd0; else if (time_en == 1'b1 && cnt < `REFRESH_TIME - 1'b1) cnt <= cnt + 1'b1; else cnt <= 10'd0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) refresh_req <= 1'b0; else if (cnt == `REFRESH_TIME - 1'b1) refresh_req <= 1'b1; else if (req_clr == 1'b1) refresh_req <= 1'b0; else refresh_req <= refresh_req; end
endmodule


  • refresh设计实现

该模块负责刷新,按照对应的时序图进行控制即可。


该模块利用状态机的方式实现。状态转移图如下:


设计代码为:

`include "../rtl/sdr_drive_head.v"
module refresh (
input wire clk, input wire rst_n,
input wire refresh_en, output reg refresh_done,
output wire [`BUS_WIDTH - 1 : 0] refresh_bus);
localparam IDLE = 5'b0_0001; localparam PRECHARGE = 5'b0_0010; localparam AUTOREFR1 = 5'b0_0100; localparam AUTOREFR2 = 5'b0_1000; localparam REFRDONE = 5'b1_0000;
reg [4:0] c_state; reg [4:0] n_state; wire [1:0] sdr_bank; reg [3:0] sdr_cmd; reg [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr; reg [3:0] cnt;
assign sdr_bank = 2'b00; assign refresh_bus = {sdr_cmd,sdr_bank,sdr_addr};
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) c_state <= IDLE; else c_state <= n_state; end
always @ * begin case (c_state) IDLE : begin if (refresh_en == 1'b1) n_state = PRECHARGE; else n_state = IDLE; end
PRECHARGE : begin if (cnt == `Trp - 1'b1) n_state = AUTOREFR1; else n_state = PRECHARGE; end
AUTOREFR1 : begin if (cnt == `Trfc - 1'b1) n_state = AUTOREFR2; else n_state = AUTOREFR1; end
AUTOREFR2 : begin if (cnt == `Trfc - 1'b1) n_state = REFRDONE; else n_state = AUTOREFR2; end
REFRDONE : begin n_state = IDLE; end
default : n_state = IDLE; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) sdr_cmd <= `NOP; else case (c_state) IDLE : begin if (refresh_en == 1'b1) sdr_cmd <= `PREC; else sdr_cmd <= `NOP; end PRECHARGE : begin if (cnt == `Trp - 1'b1) sdr_cmd <= `REFR; else sdr_cmd <= `NOP; end AUTOREFR1 : begin if (cnt == `Trfc - 1'b1) sdr_cmd <= `REFR; else sdr_cmd <= `NOP; end AUTOREFR2 : sdr_cmd <= `NOP;
REFRDONE : sdr_cmd <= `NOP; default : sdr_cmd <= `NOP; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) cnt <= 4'd0; else case (c_state) IDLE : cnt <= 4'd0;
PRECHARGE : begin if (cnt < `Trp - 1'b1) cnt <= cnt + 1'b1; else cnt <= 4'd0; end AUTOREFR1 : begin if (cnt < `Trfc - 1'b1) cnt <= cnt + 1'b1; else cnt <= 4'd0; end AUTOREFR2 : begin if (cnt < `Trfc - 1'b1) cnt <= cnt + 1'b1; else cnt <= 4'd0; end REFRDONE : cnt <= 4'd0; default : cnt <= 4'd0; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) refresh_done <= 1'b0; else if (c_state == AUTOREFR2 && cnt == `Trfc - 1'b1) refresh_done <= 1'b1; else refresh_done <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) sdr_addr <= 0; else if (c_state == IDLE && refresh_en == 1'b1) sdr_addr[10] <= 1'b1; else sdr_addr <= 0; end
endmodule


  • sdr_write设计实现

该模块负责将外部的数据写入到规定的地址中去。在SDR SDRAM中,每操作(读写)一次,都会引起该存储位的漏电,每次结束时,可以进行预充电。SDR SDRAM提供了自动预充电的机制,在读写命令时,sdr_addr[10]=1,即可自动预充电。在设计时,应该要为自动预充电预留出足够的时间。


根据对应的写入时序图,利用状态机完成此设计。


设计代码如下:

`include "../rtl/sdr_drive_head.v"
module sdr_write (
input wire clk, input wire rst_n,
input wire write_en, input wire [`ADDR_WIDTH - 1 : 0] wr_addr, input wire [31:0] wr_data,
output reg [15:0] odq, output wire [`BUS_WIDTH - 1 : 0] wr_bus, output reg wr_done);
localparam IDLE = 4'b0001; localparam ACT_STATE = 4'b0010; localparam WR1 = 4'b0100; localparam WR2 = 4'b1000;
reg [3:0] c_state; reg [3:0] n_state; wire [1:0] sdr_bank; reg [3:0] sdr_cmd; reg [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr; reg [14:0] cnt;
assign sdr_bank = wr_addr[23:22]; assign wr_bus = {sdr_cmd,sdr_bank,sdr_addr};
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) c_state <= IDLE; else c_state <= n_state; end
always @ * begin case (c_state) IDLE : begin if (write_en == 1'b1) n_state = ACT_STATE; else n_state = IDLE; end
ACT_STATE : begin if (cnt == `Trcd - 1'b1) n_state = WR1; else n_state = ACT_STATE; end
WR1 : n_state = WR2;
WR2 : begin if (cnt == `Twr + `Trp - 1'b1) n_state = IDLE; else n_state = WR2; end
default : n_state = IDLE; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) sdr_cmd <= `NOP; else if (c_state == IDLE && write_en == 1'b1) sdr_cmd <= `ACT; else if (c_state == ACT_STATE && cnt == `Trcd - 1'b1) sdr_cmd <= `WR; else sdr_cmd <= `NOP; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) sdr_addr <= 0; else if (c_state == IDLE && write_en == 1'b1) sdr_addr <= wr_addr[21:9]; else if (c_state == ACT_STATE && cnt == `Trcd - 1'b1) begin sdr_addr[10] <= 1'b1; sdr_addr[8:0] <= wr_addr[8:0]; end else sdr_addr <= 0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) cnt <= 4'd0; else if (c_state == ACT_STATE && cnt < `Trcd - 1'b1) cnt <= cnt + 1'b1; else if (c_state == WR2 && cnt < `Twr + `Trp - 1'b1) cnt <= cnt + 1'b1; else cnt <= 4'd0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) odq <= 16'd0; else if (c_state == ACT_STATE && cnt == `Trcd - 1'b1) odq <= wr_data[15:0]; else if (c_state == WR1) odq <= wr_data[31:16]; else odq <= 16'd0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) wr_done <= 1'b0; else if (c_state == WR2 && cnt < `Twr + `Trp - 1'b1) wr_done <= 1'b1; else wr_done <= 1'b0; end
endmodule


  • sdr_read设计实现

该模块负责从指定的地址中,将数据读出。


按照对应的读时序图即可实现功能,本模块采用状态机方式实现,状态转移图如下:


设计代码为:

module sdr_read (
input wire clk, input wire rst_n,
input wire read_en, input wire [`ADDR_WIDTH - 1 : 0] rd_addr,
input wire [15:0] sdr_dq,
output reg [31:0] rd_data, output reg rd_done,
output wire [`BUS_WIDTH - 1 : 0] rd_bus);
localparam IDLE = 5'b00001; localparam ACT_STATE = 5'b00010; localparam READ_STATE = 5'b00100; localparam RD1 = 5'b01000; localparam RD2 = 5'b10000;
reg [4:0] c_state; reg [4:0] n_state; wire [1:0] sdr_bank; reg [3:0] sdr_cmd; reg [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr; reg [3:0] cnt;
assign sdr_bank = rd_addr[23:22]; assign rd_bus = {sdr_cmd,sdr_bank,sdr_addr};
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) c_state <= IDLE; else c_state <= n_state; end
always @ * begin case (c_state) IDLE : begin if (read_en == 1'b1) n_state = ACT_STATE; else n_state = IDLE; end
ACT_STATE : begin if (cnt == `Trcd - 1'b1) n_state = READ_STATE; else n_state = ACT_STATE; end
READ_STATE : begin if (cnt == `Tcl) n_state = RD1; else n_state = READ_STATE; end
RD1 : n_state = RD2;
RD2 : begin if (cnt == `Trp - 1'b1) n_state = IDLE; else n_state = RD2; end
default : n_state = IDLE; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) sdr_cmd <= `NOP; else if (c_state == IDLE && read_en == 1'b1) sdr_cmd <= `ACT; else if (c_state == ACT_STATE && cnt == `Trcd - 1'b1) sdr_cmd <= `RD; else sdr_cmd <= `NOP; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) sdr_addr <= 0; else if (c_state == IDLE && read_en == 1'b1) sdr_addr <= rd_addr[21:9]; else if (c_state == ACT_STATE && cnt == `Trcd - 1'b1) begin sdr_addr[10] <= 1'b1; sdr_addr[8:0] <= rd_addr[8:0]; end else sdr_addr <= 0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) cnt <= 4'd0; else case (c_state) IDLE : cnt <= 4'd0; ACT_STATE : begin if (cnt < `Trcd - 1'b1) cnt <= cnt + 1'b1; else cnt <= 4'd0; end READ_STATE: begin if (cnt < `Tcl) cnt <= cnt + 1'b1; else cnt <= 4'd0; end
RD1 : cnt <= 4'd0; RD2 : begin if (cnt < `Trp - 1'b1) cnt <= cnt + 1'b1; else cnt <= 4'd0; end default : cnt <= 4'd0; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) rd_data <= 32'd0; else if (c_state == READ_STATE && cnt == `Tcl) rd_data[15:0] <= sdr_dq; else if (c_state == RD1) rd_data[31:16] <= sdr_dq; else rd_data <= rd_data; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) rd_done <= 1'b0; else if (c_state == RD2 && cnt < `Trp - 1'b1) rd_done <= 1'b1; else rd_done <= 1'b0; end
endmodule


  • mux4_1设计实现

该模块负责选择出对应的bus,然后将对应位作为输出即可。


设计代码为:

module mux4_1 (
input wire [`BUS_WIDTH - 1 : 0] init_bus, input wire [`BUS_WIDTH - 1 : 0] refresh_bus, input wire [`BUS_WIDTH - 1 : 0] wr_bus, input wire [`BUS_WIDTH - 1 : 0] rd_bus, input wire [1:0] mux_sel,
output wire [1 : 0] sdr_bank, output wire [`ADDR_WIDTH - 1 : 0] sdr_addr, output wire sdr_cs_n, output wire sdr_ras_n, output wire sdr_cas_n, output wire sdr_we_n);
reg [`BUS_WIDTH - 1 : 0] sdr_bus;
assign sdr_cs_n = sdr_bus[18]; assign sdr_ras_n = sdr_bus[17]; assign sdr_cas_n = sdr_bus[16]; assign sdr_we_n = sdr_bus[15];
assign sdr_bank = sdr_bus[14:13];
assign sdr_addr = sdr_bus[12:0];
always @ * begin case (mux_sel) 2'b00 : sdr_bus = init_bus; 2'b01 : sdr_bus = refresh_bus; 2'b10 : sdr_bus = wr_bus; 2'b11 : sdr_bus = rd_bus; default : sdr_bus = init_bus; endcase end
endmodule


  • sdr_ctrl设计实现

该模块负责调度整个控制器,利用状态机实现。


设计代码为:

`include "../rtl/sdr_drive_head.v"
module sdr_ctrl (
input wire clk, input wire rst_n,
input wire wr_en, input wire rd_en, input wire [`ADDR_WIDTH - 1 : 0] addr, input wire [31:0] wdata, output reg [31:0] rdata, output reg rd_valid,
output wire sdr_busy,
output reg [1:0] mux_sel,
output reg init_en, input wire init_done,
output reg time_en, input wire refresh_req, output reg req_clr,
output reg refresh_en, input wire refresh_done,
output reg out_en, output reg write_en, output reg [`ADDR_WIDTH - 1 : 0] wr_addr, output reg [31:0] wr_data, input wire wr_done,
output reg read_en, output reg [`ADDR_WIDTH - 1 : 0] rd_addr, input wire [31:0] rd_data, input wire rd_done);
localparam IDLE = 6'b000_001; localparam INIT_STATE = 6'b000_010; localparam REFRESH_STATE = 6'b000_100; localparam NO_BUSY = 6'b001_000; localparam WR_STATE = 6'b010_000; localparam RD_STATE = 6'b100_000;
reg [5:0] c_state; reg [5:0] n_state; reg wren; reg wren_clr; reg rden; reg rden_clr; reg [`ADDR_WIDTH - 1 : 0] addrr; reg [31:0] wdatar; reg busy;
assign sdr_busy = busy | rd_en | wr_en;
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) wren <= 1'b0; else if (wr_en == 1'b1) wren <= 1'b1; else if (wren_clr == 1'b1) wren <= 1'b0; else wren <= wren; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) rden <= 1'b0; else if (rd_en == 1'b1) rden <= 1'b1; else if (rden_clr == 1'b1) rden <= 1'b0; else rden <= rden; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) wdatar <= 32'd0; else if (wr_en == 1'b1) wdatar <= wdata; else wdatar <= wdatar; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) addrr <= 0; else if (wr_en == 1'b1 || rd_en == 1'b1) addrr <= addr; else addrr <= addrr; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) c_state <= IDLE; else c_state <= n_state; end
always @ * begin case (c_state) IDLE : n_state = INIT_STATE;
INIT_STATE : begin if (init_done == 1'b1) n_state = REFRESH_STATE; else n_state = INIT_STATE; end
REFRESH_STATE : begin if (refresh_done == 1'b1) n_state = NO_BUSY; else n_state = REFRESH_STATE; end
NO_BUSY : begin if (refresh_req == 1'b1) n_state = REFRESH_STATE; else if (wren == 1'b1) n_state = WR_STATE; else if (rden == 1'b1) n_state = RD_STATE; else n_state = NO_BUSY; end
WR_STATE : begin if (wr_done == 1'b1) n_state = NO_BUSY; else n_state = WR_STATE; end
RD_STATE : begin if (rd_done == 1'b1) n_state = NO_BUSY; else n_state = RD_STATE; end
default : n_state = IDLE; endcase end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) busy <= 1'b1; else if (c_state == NO_BUSY && wren == 1'b0 && rden == 1'b0 && refresh_req == 1'b0) busy <= rd_en | wr_en; else busy <= 1'b1; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) init_en <= 1'b0; else if (c_state == IDLE) init_en <= 1'b1; else init_en <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) time_en <= 1'b0; else if (c_state == INIT_STATE && init_done == 1'b1) time_en <= 1'b1; else time_en <= time_en; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) refresh_en <= 1'b0; else if (c_state == INIT_STATE && init_done == 1'b1) refresh_en <= 1'b1; else if (c_state == NO_BUSY && refresh_req == 1'b1) refresh_en <= 1'b1; else refresh_en <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) req_clr <= 1'b0; else if (c_state == NO_BUSY && refresh_req == 1'b1) req_clr <= 1'b1; else req_clr <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) write_en <= 1'b0; else if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1) write_en <= 1'b1; else write_en <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) out_en <= 1'b0; else if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1) out_en <= 1'b1; else if (c_state == WR_STATE && wr_done == 1'b1) out_en <= 1'b0; else out_en <= out_en; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) wr_addr <= 0; else if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1) wr_addr <= addrr; else wr_addr <= wr_addr; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) wr_data <= 0; else if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1) wr_data <= wdatar; else wr_data <= wr_data; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) wren_clr <= 1'b0; else if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1) wren_clr <= 1'b1; else wren_clr <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) rden_clr <= 1'b0; else if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b0 && rden == 1'b1) rden_clr <= 1'b1; else rden_clr <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) read_en <= 1'b0; else if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b0 && rden == 1'b1) read_en <= 1'b1; else read_en <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) rd_addr <= 0; else if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b0 && rden == 1'b1) rd_addr <= addrr; else rd_addr <= rd_addr; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) rdata <= 32'd0; else if (c_state == RD_STATE && rd_done == 1'b1) rdata <= rd_data; else rdata <= rdata; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) rd_valid <= 1'b0; else if (c_state == RD_STATE && rd_done == 1'b1) rd_valid <= 1'b1; else rd_valid <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) mux_sel <= 2'b00; else case (c_state) IDLE : mux_sel <= 2'b00; INIT_STATE : begin if (init_done == 1'b1) mux_sel <= 2'b01; else mux_sel <= mux_sel; end REFRESH_STATE: mux_sel <= mux_sel; NO_BUSY : begin if (refresh_req == 1'b1) mux_sel <= 2'b01; else if (wren == 1'b1) mux_sel <= 2'b10; else if (rden == 1'b1) mux_sel <= 2'b11; else mux_sel <= mux_sel; end RD_STATE : mux_sel <= mux_sel; WR_STATE : mux_sel <= mux_sel;
default : mux_sel <= 2'b00; endcase end
endmodule


为了防止在进行刷新的起始部分丢失读写命令,所以在设计时,加入了缓存结构,只要有读写命令时,都会进行保存。在读写执行时,才会清除此命令。


  • RTL仿真

为了能够仿真此设计,需要用到SDR SDRAM的仿真模型。仿真模型在msim的sdr_sim_module中,将其修改为行线为13bit,列为9bit,每个bank有4194304个存储空间。


在仿真时,在第二个bank,第五行,第10列,写入一个随机值。然后读取出来。


仿真代码为:

`timescale 1ns/1ps
module sdr_drive_tb;
reg clk; reg rst_n;
wire sys_clk; wire sys_rst_n;
wire sdr_busy; reg wr_en; reg rd_en; reg [23:0] addr; reg [31:0] wdata; wire [31:0] rdata; wire rd_valid;
wire sdr_clk; wire sdr_cke; wire sdr_cs_n; wire sdr_ras_n; wire sdr_cas_n; wire sdr_we_n; wire [15:0] sdr_dq; wire [1:0] sdr_bank; wire [1:0] sdr_dqm; wire [12:0] sdr_addr;
sdr_drive sdr_drive_inst(
.clk (clk), .rst_n (rst_n),
.sys_clk (sys_clk), .sys_rst_n (sys_rst_n),
// local .sdr_busy (sdr_busy), .wr_en (wr_en), .rd_en (rd_en), .addr (addr), .wdata (wdata), .rdata (rdata), .rd_valid (rd_valid),
// sdr .sdr_clk (sdr_clk), .sdr_cke (sdr_cke), .sdr_cs_n (sdr_cs_n), .sdr_ras_n (sdr_ras_n), .sdr_cas_n (sdr_cas_n), .sdr_we_n (sdr_we_n), .sdr_bank (sdr_bank), .sdr_addr (sdr_addr), .sdr_dqm (sdr_dqm), .sdr_dq (sdr_dq) );
mt48lc32m16a2 mt48lc32m16a2_inst( .Dq (sdr_dq), .Addr (sdr_addr), .Ba (sdr_bank), .Clk (sdr_clk), .Cke (sdr_cke), .Cs_n (sdr_cs_n), .Ras_n (sdr_ras_n), .Cas_n (sdr_cas_n), .We_n (sdr_we_n), .Dqm (sdr_dqm) );
initial clk = 1'b0; always # 10 clk = ~clk;
initial begin rst_n = 1'b0; wr_en = 1'b0; rd_en = 1'b0; addr = {2'b01, 13'd5,9'd10}; wdata = 32'd0; # 201 rst_n = 1'b1; @ (negedge sdr_busy); @ (posedge sys_clk); # 2; wr_en = 1'b1; wdata = $random; @ (posedge sys_clk); # 2; wr_en = 1'b0; # 2000; @ (negedge sdr_busy); @ (posedge sys_clk); # 2; rd_en = 1'b1; @ (posedge sys_clk); # 2; rd_en = 1'b0; # 2000; $stop; end
endmodule


这设置激励时,将tb文件和仿真模型文件同时加入添加文件中。

 

在modelsim的报告界面会显示出具体的配置信息以及读写信息。


从打印的报告中可以看出,在初始化时,列选通潜伏期为2,突发长度为2。在后续的读写时,在指定的位置,写入了13604,后续的一个位置为4629;在读出时,也正确的读出了数据。

报告打印出写入数据,即认为写入成功;报告打印出读出数据,只能证明控制器将数据读出,并不表示控制器能把数据接收到。


通过控制输出的rdata以及对应的rd_valid信号,确定读出成功。在rdata中显示为16进制,16进制的1215为十进制的4629;16进制的3524的为十进制的13604。证明读数据接收正确。

 

  • 板级测试

编写控制器的上游模块(sdr_drive_test_crtl),控制写入和读出。在固定的地址中addr = {2'b01, 13'd128, 9'd20},写入一个固定的数字wdata = 32'h5a5aa5a5,然后读出,进行验证。


读者在进行验证时,可以采样其他的地址或者数据进行验证,且可以进行多次尝试,保证设计正确。


该模块采用状态机设计实现。


设计代码为:

`include "../rtl/sdr_drive_head.v"
module sdr_drive_test_ctrl (
input wire clk, input wire rst_n,
input wire sdr_busy, output reg wr_en, output reg rd_en, output wire [31:0] wdata, input wire rd_valid, input wire [31:0] rdata, output wire [`ADDR_WIDTH - 1 : 0] addr);
localparam IDLE = 4'b0001; localparam WR_STATE = 4'b0010; localparam RD_STATE = 4'b0100; localparam TEST_DONE = 4'b1000;
reg [3:0] c_state; reg [3:0] n_state;
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) c_state <= IDLE; else c_state <= n_state; end
always @ * begin case (c_state) IDLE : begin if (sdr_busy == 1'b0) n_state = WR_STATE; else n_state = IDLE; end
WR_STATE : begin if (sdr_busy == 1'b0) n_state = RD_STATE; else n_state = WR_STATE; end
RD_STATE : begin if (rd_valid == 1'b1 && rdata == 32'h5a5aa5a5) n_state = TEST_DONE; else n_state = RD_STATE; end
TEST_DONE : n_state = TEST_DONE;

default : n_state = IDLE; endcase end
assign wdata = 32'h5a5aa5a5; assign addr = {2'b01, 13'd128, 9'd20};
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) wr_en <= 1'b0; else if (c_state == IDLE && sdr_busy == 1'b0) wr_en <= 1'b1; else wr_en <= 1'b0; end
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) rd_en <= 1'b0; else if (c_state == WR_STATE && sdr_busy == 1'b0) rd_en <= 1'b1; else rd_en <= 1'b0; end
endmodule


编写测试顶层,模块命名为sdr_drive_test,并且设置为顶层。


此模块负责例化sdr_drive和sdr_drive_test_ctrl,完成连接功能,以此测试。


代码为:

`include "../rtl/sdr_drive_head.v"
module sdr_drive_test (
input wire clk, input wire rst_n,
// sdr output wire sdr_clk, output wire sdr_cke, output wire sdr_cs_n, output wire sdr_ras_n, output wire sdr_cas_n, output wire sdr_we_n, output wire [1:0] sdr_bank, output wire [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr, output wire [1:0] sdr_dqm, inout wire [15:0] sdr_dq);
wire sys_clk; wire sys_rst_n;
// local wire sdr_busy; wire wr_en; wire rd_en; wire [`ADDR_WIDTH - 1 : 0] addr; wire [31:0] wdata; wire [31:0] rdata; wire rd_valid;
sdr_drive_test_ctrl sdr_drive_test_ctrl_inst(
.clk (sys_clk), .rst_n (sys_rst_n),
.sdr_busy (sdr_busy), .wr_en (wr_en), .rd_en (rd_en), .wdata (wdata), .rd_valid (rd_valid), .rdata (rdata), .addr (addr) );
sdr_drive sdr_drive_inst(
.clk (clk), .rst_n (rst_n),
.sys_clk (sys_clk), .sys_rst_n (sys_rst_n),
// local .sdr_busy (sdr_busy), .wr_en (wr_en), .rd_en (rd_en), .addr (addr), .wdata (wdata), .rdata (rdata), .rd_valid (rd_valid),
// sdr .sdr_clk (sdr_clk), .sdr_cke (sdr_cke), .sdr_cs_n (sdr_cs_n), .sdr_ras_n (sdr_ras_n), .sdr_cas_n (sdr_cas_n), .sdr_we_n (sdr_we_n), .sdr_bank (sdr_bank), .sdr_addr (sdr_addr), .sdr_dqm (sdr_dqm), .sdr_dq (sdr_dq) );
endmodule


经过综合分析后,进行分配管脚。在分配管脚后,需要将双功能管脚中的NCEO设置为普通用户IO。如果不设置,将会出现如下错误:

 

右击器件名称,选择DEVICE。

 

选择device and pin option。

 

选择dual – purpose pins。


将nceo设置为 use as regular IO。


点击OK,进行编译即可。

 

连接上开发板,启动逻辑分析仪。


将采样时钟选择为,sys_clk(PLL的c0)。采样深度选择为1K。

 

添加观测信号如下,将wr_en的上升沿设置为触发条件。

经过保存,重新形成配置文件后,进行下板测试。


下板后,按下复位。等待波形触发。

通过逻辑分析仪,就可以看出可以正确的写入和读出数据。


读者也可以进行尝试一次性写入多个数据,然后进行读出,进行验证设计的正确性。


福利】:QQ交流群173560979,进群备注名字+学校/企业。
淘宝店铺:https://shop588964188.taobao.com
论坛网址:www.sxznfpga.com
叁芯智能FPGA课程


往期精选 

 
 

【免费】FPGA工程师人才招聘平台

FPGA人才招聘,企业HR,看过来!

系统设计精选 | 基于FPGA的实时图像边缘检测系统设计(附代码)

基于原语的千兆以太网RGMII接口设计

时序分析理论和timequest使用_中文电子版

求职面试 | FPGA或IC面试题最新汇总篇

FPGA图像处理专题课新增Vivado部分内容,线上线下均可报名

FPGA时序分析及约束专题课新增Vivado部分内容,线上线下均可报名

资料汇总|FPGA软件安装包、书籍、源码、技术文档…(2022.05.15更新)

FPGA就业班,课程内容丰富,系统性学习FPGA,高薪就业,线上线下同步,7月15号开班!

FPGA技术江湖广发江湖帖

无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。


FPGA技术江湖微信交流群

加群主微信,备注职业+方向+名字进群


FPGA技术江湖QQ交流群

备注地区+职业+方向+名字进群


FPGA技术江湖 任何技术的学习就好比一个江湖,对于每一位侠客都需要不断的历练,从初入江湖的小白到归隐山林的隐世高人,需要不断的自我感悟自己修炼,让我们一起仗剑闯FPGA乃至更大的江湖。
评论
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 75浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 80浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 55浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 85浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 116浏览
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 100浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 104浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 125浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 68浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 87浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 170浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 141浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 44浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦