陕西棋牌

IT技术互动交流平台

FPGA实现串口与iic控制器总结(3)

来源:IT165收集  发布日期:2016-05-03 22:37:14

在剖析了《深入浅出玩转FPGA》的串口代码和IIC控制器代码、xilinx官方的xilinx的iic控制器(参见书《FPGACPLD设计工具──Xilinx ISE使用详解》)、《片上系统设计思想与源代码分析》一书中带有wishbone接口的iic控制器后,本文尝试对以上做一些总结,并分析不同的iic控制器的实现区别。

陕西棋牌上一讲,我们分析了特权的iic控制器的实现,这一讲将继续另2种带有总线接口和更充分实现iic协议的例子。下一讲讲谈谈SOC架构中的wishbone总线架构及外设挂载,以及一些软硬件结合的底层的东西,主要参考《自己动手写cpu》一书及相关的网页资料等,敬请期待。

 

2、IIC控制器

2.2 xilinx的iic控制器

该控制器主要是参考《FPGACPLD设计工具──Xilinx ISE使用详解》第10章和官方代码,资料链接见文末给出的下载链接地址。

陕西棋牌iic协议的知识同样不再赘述,只是提几点上一讲中没有或无需关注的地方:

1、I2C总线通信时,起始位后的第一个字节用于寻址,该字节包含7比特的从机地址和1比特的读写指示比特, 一般而言,从机地址由一个固定部分和一个可编程部分构成。

2、复合格式中,此时主机连续对从机进行多次读写操作,因此在产生起始位、收发数据、产生停止位的整个传输过程中,数据的方向发生多次改变,传输改变方向时,主机会重新发出重复起始位和从机地址(上一讲中为什么读的流程要复杂些的原因)。

3、I2C总线在一个时刻只能有一个主机,当I2C总线同时有两个或更多的器件想成为主机时,就需要进行仲裁,时钟同步过程的目的是为仲裁提供一个确定的时钟。SCL 线低电平时间取决于低电平时间最长的主机,高电平时间取决于高电平时间最短的主机。主机只会在 I2C 总线空闲时产生起始位,但是在起始位的保持时间 tHD;STA内可能有两个或以上的主机产生起始位,最终总线上的起始位由它们之间的线与运算决定,仲裁在随后 SDA 线上发送的比特中进行。如果一个主机具有从机功能,那么当它失去仲裁时,必须立即切换到从机状态,因为它可能正在被其他主机寻址。

4、如图

陕西棋牌讲完了这些关于iic协议的点继续下面的部分.

陕西棋牌I2C总线控制器的主要作用是提供UC(Microcontroller,微控制器或单片机)和I2C总线之间的接口,为两者之间的通信提供物理层协议的转换。这些I2C协议的器件,就不能直接和单片机外围总线相连,这些器件可以挂在一套I2C总线上,再通过I2C总线控制器和µC连起来,如图所示。在SOC设计中类似的协议转换模块用得非常多。

I2C 总线控制器包含两个主要部分,一是微控制器接口,简称µC接口,二是 I2C Master/Slave 接口,即I2C接口,通过这两个接口,I2C总线控制器实现了微控制器外围总线和 I2C 总线的连接。那么这里的微控制器接口是哪种控制器?是否是哪种总线协议,根据readme文件可以发现原来是 MC68307单片机,是一款集成的多总线处理器。

陕西棋牌讲清楚了原理,我们来看看具体设计:

This zip file contains the following folders:
-- Verilog Source Files:
i2c_blk_ver.v - top level file
i2c_control_blk_ver.v - control function for the I2C master/slave
shift8_blk_ver.v - shift register
uc_interface_blk_ver.v - uC interface function for an 8-bit 68000-like uC
upcnt4_blk_ver.v - 4-bit up counter

陕西棋牌可以看到5个文件。uc_interface_blk_ver.v 实现了一些寄存器以及对下层模块的控制信号,shift8_blk_ver.v是串行收发,upcnt4_blk_ver.v是4位计数器,i2c_blk_ver.v 是顶层,例化了uc_interface_blk_ver和i2c_control_blk_ver。i2c_control_blk_ver则实现了仲裁,start等收发流程状态机等。整体架构如下图:

陕西棋牌整个思路还是很清楚的。接口处主要是一些寄存器,寄存器的信号到iic中控制底层的运转。

µC 接口主要包含状态寄存器MBSR、控制寄存器MBCR、地址寄存器MADR、数据寄存器MBDR 和地址译码/总线接口模块。状态寄存器指示I2C总线控制器的当前状态,如传送是否完成、总线是否忙等信息,控制寄存器是µC控制I2C总线控制器的主要途径,通过置0/置1可以完成I2C总线控制器使能、中断使能、Master/Slave模式选择、产生起始位等操作。地址寄存器保存着I2C总线控制器作为Slave时的地址。数据寄存器陕西棋牌用于保存接收或是待发送的数据。

下面分析源代码,由于源代码众多,只能摘出部分,梳理主要思路和重要信号线,大家可以下载我注释的代码。

i2c_blk_ver.v 顶层文件:

 

module i2c_blk (sda, scl, addr_bus, data_bus, as, ds, r_w, dtack, irq, mcf, clk,
                reset);
 
  parameter I2C_ADDRESS = 16'b0000000000000000;
  //   I2C bus signals
  inout sda;
  inout scl;
  //   uC interface signals
  input [23:0] addr_bus;
  inout [7:0] data_bus;
  input as;                //   address strobe, active low
  input ds;                //   data strobe, active low
  input r_w;               //   read/write
  output dtack;            //   data transfer acknowledge 给处理器的,表示数据是否准备好了
  output irq;              //   interrupt request
  inout mcf;               //   temporary output for testing  给处理器,表示传输是否结束
  //   clock and reset
  input clk;
  input reset;
这个是顶层的输入输出,不是什么特殊的总线结构,比较好理解,pdf中有详细的中文解释每个信号的含义。

 

接下来是一些wire型,实际上就是那些寄存器中有含义的每一位,即各种控制信号线,便于例化的 i2c_control I2C_CTRL与 uC_interface #(I2C_ADDRESS) uC_CTRL模块间的接口互联。

陕西棋牌接下来从顶层往下,uC_interface模块:

 

 //   Internal I2C Bus Registers
  //   Address Register (Contains slave address)
  inout [7:0] madr;
  //   Control Register		
  inout men;               //   I2C Enable bit
  inout mien;              //   interrupt enable
  inout msta;              //   Master/Slave bit
  inout mtx;               //   Master read/write
  inout txak;              //   acknowledge bit
  inout rsta;              //   repeated start
  output mbcr_wr;          //   indicates that the control reg has been written
  //   Status Register
  input mcf;               //   end of data transfer
  input maas;              //   addressed as slave
  input mbb;               //   bus busy
  input mal;               //   arbitration lost
  input srw;               //   slave read/write
  input mif;               //   interrupt pending
  input rxak;              //   received acknowledge
  output mal_bit_reset;    //   indicates that the MAL bit should be reset
  output mif_bit_reset;    //   indicates that the MIF bit should be reset
  input msta_rst;          //   resets the MSTA bit if arbitration is lost
  //   Data Register
  inout [7:0] mbdr_micro;
  input [7:0] mbdr_i2c;
  output mbdr_read;
可以看到实际上这个模块的很多输出信号就是这些寄存器的位,控制更底层的如何实现。可以参考pdf查询具体的寄存器(8位)的某一位是什么含义。比如状态寄存器都是input型,因为他主要反馈给uc当前状态,不对底层的状态机控制。数据寄存器中mbdr_micro是inout型,表示是uc总线那一端的,mbdr_i2c是input型,表示是从iic接收到的那一端的。

 

 

  //   State Machine Signals
  `define STATE_TYPE_IDLE	 2'd0
  `define STATE_TYPE_ADDR	 2'd1
  `define STATE_TYPE_DATA_TRS	 2'd2
  `define STATE_TYPE_ASSERT_DTACK	 2'd3
  //   Constant Declarations
  parameter RESET_ACTIVE = 1'b0;
  //   Base Address for I2C Module (addr_bus[23:8])
  parameter MBASE = UC_ADDRESS;
  //   Register Addresses (5 Total):
  //   Address Register (MBASE + 8Dh)
  `define MADR_ADDR	 8'b10001101
  //   Control Register (MBASE + 91h)
  `define MBCR_ADDR	 8'b10010001
  //   Status Register (MBASE + 93h)
  `define MBSR_ADDR	 8'b10010011
  //   Data I/O Register (MBASE + 95h)
  `define MBDR_ADDR	 8'b10010101
uc部分的状态机,µC和 I2C总线控制器之间的交互要用到 I2C总线控制器内部的寄存器,寄存器的地址是24位的,其中高16比特为I2C总线控制器的基址,低8位用于区别不同寄存器。接下来定义了10多个wire型的中间变量,这些事状态机中产生的操纵控制状态机的。由于wire型变量不能再always中赋值,所以后面又用这种方式定义一个相应的reg型变量。

 

 

//   Address match
  wire address_match;
 reg visual_0_address_match;
  assign address_match = visual_0_address_match;

加上了visual_0的前缀,这样做的好处?我想一是时钟同步,reg信号由clock打一拍同步,也便于去除毛刺,因为assign信号直接相连,稍有抖动就有毛刺,reg信号打拍子可以避免这种问题。二是可以某些信号做类似案件检测一样的消抖,举例:

 

51    input as; 
221   begin
      visual_0_as_int 						//这里又有一个时钟的延迟一拍
      visual_0_as_int_d1					//经过一个时钟的延迟一拍,为了检测到正确的下降沿而没有抖动的干扰
      visual_0_ds_int <= ds;
      if ((!as && as_int_d1 && addr_bus[23:8] == MBASE))		//低8位是区别那个寄存器
        visual_0_address_match <= 1'b1;
      else
        visual_0_address_match <= 1'b0;
      end
135   reg visual_0_as_int;
      assign as_int = visual_0_as_int;
138   reg visual_0_as_int_d1;
      assign as_int_d1 = visual_0_as_int_d1;

 

 

可以看见从221行开始,获取as输入,经过几个中间变量的周转,在if中通过!as && as_int_d1来达到类似案件去抖的效果。另外有些变量是对外输出的,也需变成reg型。 这部分的实际工作流程如图:
根据上图的流程:第一个always块, // Process:SYNCH_INPUTS 判断as是否有有效的下降沿,然后总线的高16位是否是正确的我们这个iic设备在真个cpu外设总线上的地址。为真,则visual_0_address_match信号为1,起始对应为address_match,这个成为后续的控制判断,状态机的重要依据。接下来是uc模块的主状态机:

 

  always @(prs_state or as or as_int_d1 or ds_int or address_match)
  begin
    visual_0_next_state <= prs_state;			//2位的变量,4个状态
    visual_0_dtack_com <= 1'b1;					
    visual_0_dtack_oe <= 1'b0;
    case (prs_state)
      `STATE_TYPE_IDLE :						//引用定义的变量
        //  ----------- IDLE State (00) -------------
        //   Wait for falling edge of as
        if (as_int_d1 && !as)					//as表示输入地址有效信号,低有效,这里的as_int_d1实际上是一个类似按键去抖的效果,延迟了2拍,追踪信号就可以发现
          //   falling edge of AS
          visual_0_next_state <= `STATE_TYPE_ADDR;
 
      `STATE_TYPE_ADDR :
        //  ---------- ADDR State (01) --------------
        //   Check that this module is being address
        if (address_match)						//225行由输入的bus决定,也是打了一拍,做到时钟同步
          //   Wait for ds to be asserted, active low
          if (!ds_int)							//由输入的ds决定
            visual_0_next_state <= `STATE_TYPE_DATA_TRS;
          else
            visual_0_next_state <= `STATE_TYPE_ADDR;
        else
          //   this module is not being addressed
          visual_0_next_state <= `STATE_TYPE_IDLE; 
      `STATE_TYPE_DATA_TRS :
      begin
        //  -------- DATA_TRS State (10) ------------
        //   Read or write from enabled register
        visual_0_next_state <= `STATE_TYPE_ASSERT_DTACK;	//过渡态,为了给出oe的信号,内部控制线
        visual_0_dtack_oe <= 1'b1;							//前面被置为0了的
      end
      `STATE_TYPE_ASSERT_DTACK :
      begin
        //  ------ ASSERT_DTACK State (11) ----------
        //   Assert dtack to uProcessor
        visual_0_dtack_com <= 1'b0;							//前面被置为1了的
        visual_0_dtack_oe <= 1'b1;
        //   Wait for rising edge of as and ds
        if ((!as_int_d1) && (!ds_int))						//锁存操作,直至DTACK有效,甚至可以分析锁存了几个时钟?
          visual_0_next_state <= `STATE_TYPE_ASSERT_DTACK;
        else if ((as_int_d1) && (ds_int))
          visual_0_next_state <= `STATE_TYPE_IDLE;
      end
    endcase
  end
</pre><pre code_snippet_id="1669606" snippet_file_name="blog_20160502_10_3192142" name="code" class="plain">注意在848行处://   set SDA and SCL
  assign sda = (sda_oe == 1'b1 ? 1'b0 : 1'bz);
  assign scl = (scl_out_reg == 1'b0 ? 1'b0 : 1'bz);
  assign scl_not =  (~ (scl)) ;
  //   sda_oe is set when master and arbitration is not lost and data to be output = 0 or
  //   when slave and data to be output is 0
  assign sda_oe = (((master_slave == 1'b1 && arb_lost == 1'b0 && sda_out_reg ==
                  1'b0) || (master_slave == 1'b0 && slave_sda == 1'b0) ||
                  stop_scl_reg == 1'b1) ? 1'b1 : 1'b0);
所以sda实际上经由sda_out_reg,visual_0_sda_out_reg,sda_out,visual_0_sda_out控制,即下图状态机中的visual_0_sda_out实际代表了sda,同样scl也一样。

 

 

 

该状态机还是比较清晰的。

next_state信号通过几个中间变量的周转,还是赋给了prs_state实现状态跳转,292的always块即实现了这种从next_state到prs_state的驱动。dtack_com是一个状态机变化的中间信号,转化成dtack_int变成dtack信号,dtack_oe决定是否输出dtack信号。注意这3条语句每次进来就更新,要注意visual_0_dtack_com与visual_0_dtack_oe这里每次被重置。首先是idle状态,检测到有效的as信号。进入addr状态,监测地址是否匹配,数据是否来了,来了就进入trs,否则继续等。Trs状态是过渡态,为了给出visual_0_dtack_oe信号,进入dtack状态,visual_0_dtack_com与visual_0_dtack_oe被设置表明数据已经放到uc与iic的总线上。等待ds失效,回到idle状态。那么更多的过程实际上是根据状态机的中间变量,在别的always块中实现的,可以分析下两者间的相对时间关系。

接下来的always实现visual_0_prs_state <= next_state;驱动状态更迭。接下来是判断那个24位的地址是映射到那个寄存器,每个寄存器给出一个信号线,若选中则为1,驱动下一个always块中的数据具体传到哪个寄存器中。下个always块中,实现了对不同状态下的一些信号量的设置,而没有在之前的状态机中实现。每根信号线的含义可以查pdf。mbcr_wr代表了mbcr是被读了还是被写了。其实Status Register是只读的,写的话会产生复位。其他几个寄存器类似,比较好理解,理解关于数据寄存器的数据流向。这里面的8位数据都是一个clk完成,注意与上面的dtack的含义在时序上是否冲突,因为dtack是表征数据已经准备好

最后就是几个assign语句,根据上面的信号处理是否有中断,dtack,决定是传输进来某个信号还是给出某个信号。

陕西棋牌总的来说结构比较清晰,模块化比较清晰。状态机采用了分段式的写法。

i2c_control_blk_ver.v文件

它的接口主要是uc接口文件中的寄存器的信号线。分别定义了数据流程的状态机和scl信号的状态机。接下来是一堆的中间信号的定义和类似的加上visual_0后的reg处理。

陕西棋牌接下来首先例化了upcnt4模块用来统计bit数。这是一个不封顶的4位计数器,但是可以输入一个4位数据来修改计数值。接下来再例化一个用于clk的计数,达到分频的效果。接下来是例化了2个SHIFT8_blk,改模块可以load和输出1个8位数,也可串行接收和发送一个一位数。例化了2个第一个是iic端的,一个是uc端的。接下来是总线仲裁,主要是msta_rst与arb_lost位。当为主机,scl_in为scl的采样。有待重点分析?

接下来是scl信号的一个状态机:

陕西棋牌I2C 总线控制器复位后处于IDLE 状态,不驱动SCL 和SDA,此时I2C 总线上的其他Master 可以控制SCL 和SDA。如果I2C 总线控制器处于Master 模式,而且I2C 总线处于空闲状态,μC 通过置位MBCR 寄存器的MSTA 比特使GEN_START 信号为高,那么状态机进入START 状态。

陕西棋牌在 START 状态,SCL 保持为高电平,同时驱动SDA 信号变低,从而在I2C 总线上产生一个起始位。系统时钟计数器启动计数,直到满足I2C 规范要求的起始条件保持时间(>4ns),状态机进入SCL_LOW_EDGE状态。

在 SCL_LOW_EDGE 状态,状态机使SCL 产生一个下降沿并复位系统时钟计数器,然后在下一个时钟沿到来时进入SCL_LOW 状态。

在SCL_LOW 状态,SCL 保持为低,同时进行计数,直到产生规定的SCL 低电平时间(>4.7ns)。产生规定的 SCL 低电平时间后,如果失去仲裁,那么完成一个字节的传输之后状态机回到IDLE 状态,否则状态机进入SCL_HI_EDGE 状态。

陕西棋牌在 SCL_HI_EDGE 状态中,状态机释放SCL 线,希望产生SCL 上升沿,但是SCL 线可能被其他Master 置低,因此状态机并不直接转移到SCL_HI 状态,而是等待SCL 信号变高之后才进入SCL_HI 状态。

进入 SCL_HI 状态后,系统时钟计数器进行计数,以产生I2C规范要求的SCL高电平时间(>4.0ns),如果检测到重复起始条件或停止条件,状态机将在1/2SCL 高电平时间之后转移到 START状态重新开始,或转移到 IDLE状态,否则产生要求的 SCL高电平时间后状态机进入 SCL_LOW_EDGE状态,继续产生下一个 SCL脉冲。

接下来是状态机的驱动visual_0_scl_state <=next_scl_state;接下来是visual_0_sda_in这类信号,这个处理,visual_0_scl_in并不是由scl扇出,仅仅是对这个信号的一个采样。也是上面我们判断scl是否被别人拉高的依据。

后面是start和stop信号产生和检测的一个信号线的处理,也包括主从机的。在下来就是主状态机了:

 

复位后,状态机在 IDLE 状态,当检测到 START 信号时,转移到HEADER 状态。START信号由 I2C 总线上的起始位触发,触发这个起始位的 Master 可以是 I2C 总线控制器本身或其他的 I2C 总线主机。

陕西棋牌在 HEADER 状态,如果 I2C 总线控制器处于 Master 模式,它会把 MBDR 中的数据作为HEADER 发送到 I2C 总线上,以寻址特定的 Slave。不管 I2C 总线控制器处于 Master 还是Slave模式,在HEADER 状态时,I2C 总线控制器都会接收总线上的数据,保存到 I2C Header Shift Register 中,收到8 个比特后,状态机转移至 ACK_HEADER 状态。

在 ACK_HEADER 状态,如果 I2C 总线控制器处于 Master 模式,它会采样 SDA 线,以判 断 所 寻 址 的Slave 是 否 响 应 。 如 果 没 有 响 应 , 状 态 机 转 移 到STOP 状 态 , 通 知SCL/START/STOPGenerator 产生STOP 信号,中止传输。如果 Slave 产生了响应比特,状态机根据 Header 的最低位判断发起的是发送操作还是接收操作,然后转移到 RCV_DATA 状态或 XMIT_DATA 状态。如果I2C 控制器处于Slave 模式,电路会不断比较 Header Shift Register 的内容和 I2C 总线控制器地址寄存器 MADR 的内容是否相等,如果相等,说明本I2C总线控制器被其他 Master 寻址,于是 I2C 总线控制器立即转换到 Slave 模式,并把状态寄存器 MBSR 的 MAAS 比特置 1,指示 I2C 总线控制器被其他 Master 寻址。同时MBSR 的SRW 比特记录Header 的最低位,以便µC 判断Master 请求的是读还是写操作。

在 RCV_DATA 状态, I2C 总线控制器处于接收状态(即主机接收状态或从机接收状态),状态机读入 I2C 总线上的数据并保存到移位寄存器中,读完 8 比特的数据后进入ACK_DATA 状态,发出响应比特。响应比特的取值根据 I2C 总线控制器是 Master 还是 Slave有所不同,当I2C 总线控制器是Slave 时,响应比特应为 0,表示正常接收;当 I2C 总线控制器是Master 时,如果已经收到了足够的数据,响应比特要设置为 1,通知 Slave 停止发送,否则响应比特应为 0,通知Slave 继续发送。响应比特的值由 MBCR 的 TXAK 位决定, µC 可以在适当的时候写入。检测到 I2C 总线上的停止位时,状态机转移到 STOP 状态。

在 XMIT_DATA 状态, I2C 总线控制器处于发送状态(即主机发送,或从机发送状态),状态机把数据寄存器 MBDR 的数据移位输出到 SDA 线上,发送 8 比特后进入GET_ACK_DATA 状态,收到响应比特后,状态机回到 XMIT_DATA 状态,继续发送下一个字节。如果没有收到响应比特,说明发送结束或出错,状态机转到 STOP 状态。

陕西棋牌在 STOP 状态,如果处于 Master 模式,主状态机通知 SCL/START/STOP Generator 产生停止位。下一个时钟沿到来时,状态机自动转移到 IDLE 状态。

后面就是start stop检测模块,还有一些寄存器信号线的产生。

可以看到这种设计思路是分层次模块的。uc那一端是根据数据交互的流程,读写控制寄存器,然后寄存器的信号输出给底层去实现各种控制。而底层也是分区块,2个状态机,一个实现scl信号的置高和置低,期间有仲裁检测,主从机的切换,结束起始位的判断等等,另一个实现iic与外部设备通信时的交互流程,如先寻址,在等待确认,再续写之类的。中间有很多信号线去同步控制别的always块或者被别的信号线控制。还有起始,结束检测的always块,针对各个寄存器的赋值的专门的always块等等。

2.3 OR1200的iic控制器

陕西棋牌这是一个带wishbone总线结构的iic接口,所以按照顶层框图来画输入输出信号,可以发现输入一段全是wb接口的信号线,输出就是2根scl和sda,但是注意的是这里并不是scl与sda:

 

	// I2C signals
	// i2c clock line
	input  scl_pad_i;       // SCL-line input			//?不是inout结构?
	output scl_pad_o;       // SCL-line output (always 1'b0)
	output scl_padoen_o;    // SCL-line output enable (active low)

	// i2c data line
	input  sda_pad_i;       // SDA-line input
	output sda_pad_o;       // SDA-line output (always 1'b0)
	output sda_padoen_o;    // SDA-line output enable (active low)
实际上我们需要在这个三态门添加更高的设计层次:

 

 

	assign scl = scl_padoen_o ? 1'bz : scl_pad_o;
	assign sda = sda_padoen_o ? 1'bz : sda_pad_o;
	assign scl_pad_i = scl;
	assign sda_pad_i = sda;

 

定义了不同的寄存器,包括时钟分频的寄存器,支持异步复位。wb_adr_i信号决定具体对哪一个寄存器操作。然后是寄存器的值的修改。这个top模块中没有定义状态机,是由于wb接口的原因,不想2.2的实现方式是有这么一个流程的。而这里因为是有统一的wb接口的原因,所以简单的多。另外wb接口的很多信号线可以根据自己的需求去修改其功能含义的。

但是这个仅仅是主机,并没有实现完整的iic接口,没有仲裁等。

top中例化了i2c_master_byte_ctrl,i2c_master_byte_ctrl中例化了更低层次的i2c_master_bit_ctrl。i2c_master_byte_ctrl中也是一个主状态机维持着start、发、收、ack等交互流程,i2c_master_bit_ctrl将start、stop、rd、wr都分成了好几段,维持着一个庞大的状态机 ,里面是对sda,scl信号线的高低的控制。也是模块化的比较清晰,就不仔细讲了

说几点感受吧:

陕西棋牌1、对于这种比较复杂的设计,实际上模块的划分非常重要,理清楚各个模块间的互联,每根信号线的含义。比如这里uc那一端的分段式状态机,与寄存器的设计等。

2、该例程比较复杂,非常繁杂,有很多的信号线。各信号线之间的连接比较繁杂,特别是还有仲裁这一块,不好理解。所有对于这种复杂的设计,没有详细的注释很难理解。另外针对iic的协议,大家的理解和设计思路不一样,可以有很多种实现方式,包括一些个人的写法习惯,如对于状态,对中间信号的定义与处理等,都有差异

3、很多的小技巧,对于信号的命名,打节拍的处理等等

Tag标签:      
  • 专题推荐

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规