基于MCP3313-10 ADC 的SPI接口驱动练习

Posted 好记性不如烂笔头

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于MCP3313-10 ADC 的SPI接口驱动练习相关的知识,希望对你有一定的参考价值。

先了解MCP3313-10的操作步骤:

 

看上图可以了解,上电之后先经过tACQ(输入采集,但这个时候并不能对数据进行采集)一段时间,设定为状态0;再进入tCNV(数据转换期间,不能采集数据),设定为状态1;最后在进入tACQ(可以进行数据采集),设定位状态2。状态1-2-1-2之间进行轮回。

然后查看具体时序:

 

 

 CNVST 初始值为低,经过tACQ在上升沿到来时,adc进入数据转换,转换时间需要tCNV,然后进入数据采集,采集时间tACQ,所以需要知道这几个具体时间

tACQ 有一个最小值290ns,没标最大值,说明这个时间可以更长点

tCNV有标最大值,没标最小时值,说明这个时间可以短点

 

 

OK 确认之后,可以对计数器进行设计了。一共需要两个计数器,一个计数器产生sclk,另外一个计数器需对数据采集个数进行计数。由于转换期间和数据采集期间的时间不一样,不会同时计数,所以可以重复利用一个计数器,引入一个变量x.

上电时的tACQ ,和数据转换后的tACQ 时间是一样,计数器也可以共用一个。代码如下:

 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         cnt0 <= 0;
     end
     else if(add_cnt0)begin
         if(end_cnt0)begin
             cnt0 <= 0;
         end
         else begin
             cnt0 <= cnt0 + 1;
         end
     end
 end
 
 assign add_cnt0 = flag_add;
 assign end_cnt0 = add_cnt0 && cnt0 == 4 - 1; //输入时钟50M,4分屏,sclk = 50/4 = 12.5M
 
 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         cnt1 <= 0;
     end
     else if(add_cnt1)begin
         if(end_cnt1)begin
             cnt1 <= 0;
         end
         else begin
             cnt1 <= cnt1 + 1;
         end
     end
 end
 
 assign add_cnt1 = end_cnt0;
 assign end_cnt1 = add_cnt1 && cnt1 == x - 1;
 
 always @(*)begin
     if(state == 0)begin
         x = 16;     //tACQ:等待16 * 80 = 1280 ns
     end
     else if(state == 1)begin
         x = 8;        //tCNV:等待8 * 80 = 640 ns ,
     end
     else begin
         x = 16;        //tACQ:等待16 * 80 = 1280 ns
     end
 end

引入了一个状态机,上电时进入state =0 状态,数据转换进入state =1 状态,数据采集进入state =2 状态

 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         state <= 0;
         flag_add <= 0;
         adc_cs <= 0;
         flag_sel <= 0;
         dout_vld <= 0;
     end
     else case(state)
 
         0:    begin
             flag_add <= 1;
             adc_cs <= 0;
             flag_sel <= 0;
             dout_vld <= 0;
             if(end_cnt1)begin //等待16 * 80 = 1280 ns
                 state <= 1;
                 adc_cs <= 1;
                 flag_sel <= 0;
                 dout_vld <= 0;
             end
         end
         
         1:    begin    //adc数据在转换中
             adc_cs <= 1;
             flag_sel <= 0;
             dout_vld <= 0;
             if(end_cnt1)begin //等待8*80 = 640 ns
                 state <= 2;
                 adc_cs <= 0;
                 flag_sel <= 1;
                 dout_vld <= 0;
             end
         end
         
         2:    begin    // 数据采集期间
             adc_cs <= 0;
             flag_sel <= 1;
             dout_vld <= 0;
             if(end_cnt1)begin //等待16 * 80 = 1280 ns,数据采集完进入下一轮转换
                 state <= 1;
                 adc_cs <= 1;
                 flag_sel <= 0;
                 dout_vld <= 1;
             end
         end
         
         default: state <= 0;
     endcase
 end

引入了几个信号:

flag_add: 计数器启动条件,这里设置为1,计数器一直计数

 

flag_sel:将数据采集那段时间进行标注,用于区分哪段时间需要进行采数据

dout_vld:数据采集完产生一个有效标志,然后对一个完整数据进行锁存

 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         adc_data_temp <= 0;
     end
     else if(flag_sel == 1 && add_cnt0 && cnt0 == 2-1 && cnt1 >= 0 && cnt1 < 16)begin //在sclk的下降沿采数据,同时在flag_sel有效期间
             adc_data_temp[15-cnt1] <= adc_sdo;
     end
 end
 
 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         adc_data <= 0;
     end
     else if(dout_vld)begin //一个完整的16bit数据采集完
         adc_data <= adc_data_temp;
     end
 end

然后产生sclk时钟:

 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         adc_sclk <= 1;
     end
     else if(add_cnt0 && cnt0 == 2-1)begin
         adc_sclk <= 0;
     end
     else if(end_cnt0)begin
         adc_sclk <= 1;
     end
 end

完整代码:

 module mcp3313_10_test(
                     clk,
                     rst_n,
                     adc_sdo,
                     
                     adc_cs,
                     adc_sclk,
                     adc_data        
 );
 
 input     clk;
 input     rst_n;
 input     adc_sdo;
 
 output                 adc_cs;
 output                 adc_sclk;
 output     [16-1 : 0]    adc_data;
 
 reg     [16-1 : 0]     adc_data_temp;
 reg     [16-1 : 0]    adc_data;
 reg     [3 -1 : 0]     cnt0/* synthesis keep*/;
 reg     [6 -1 : 0]     cnt1/* synthesis keep*/;
 reg     [6 -1 : 0]     x/* synthesis keep*/;
 reg     [2 -1 : 0]     state/* synthesis keep*/;
 reg                    flag_add;
 reg                    flag_sel;
 reg                 dout_vld;
 reg                 adc_cs;
 reg                 adc_sclk;
 
 
 wire                 add_cnt0;
 wire                 end_cnt0;
 wire                 add_cnt1;
 wire                 end_cnt1;
 
 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         cnt0 <= 0;
     end
     else if(add_cnt0)begin
         if(end_cnt0)begin
             cnt0 <= 0;
         end
         else begin
             cnt0 <= cnt0 + 1;
         end
     end
 end
 
 assign add_cnt0 = flag_add;
 assign end_cnt0 = add_cnt0 && cnt0 == 4 - 1; //输入时钟50M,4分屏,sclk = 50/4 = 12.5M
 
 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         cnt1 <= 0;
     end
     else if(add_cnt1)begin
         if(end_cnt1)begin
             cnt1 <= 0;
         end
         else begin
             cnt1 <= cnt1 + 1;
         end
     end
 end
 
 assign add_cnt1 = end_cnt0;
 assign end_cnt1 = add_cnt1 && cnt1 == x - 1;
 
 always @(*)begin
     if(state == 0)begin
         x = 16;     //tACQ:等待16 * 80 = 1280 ns
     end
     else if(state == 1)begin
         x = 8;        //tCNV:等待8 * 80 = 640 ns ,
     end
     else begin
         x = 16;        //tACQ:等待16 * 80 = 1280 ns
     end
 end
 
 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         state <= 0;
         flag_add <= 0;
         adc_cs <= 0;
         flag_sel <= 0;
         dout_vld <= 0;
     end
     else case(state)
 
         0:    begin
             flag_add <= 1;
             adc_cs <= 0;
             flag_sel <= 0;
             dout_vld <= 0;
             if(end_cnt1)begin //等待16 * 80 = 1280 ns
                 state <= 1;
                 adc_cs <= 1;
                 flag_sel <= 0;
                 dout_vld <= 0;
             end
         end
         
         1:    begin    //adc数据在转换中
             adc_cs <= 1;
             flag_sel <= 0;
             dout_vld <= 0;
             if(end_cnt1)begin //等待8*80 = 640 ns
                 state <= 2;
                 adc_cs <= 0;
                 flag_sel <= 1;
                 dout_vld <= 0;
             end
         end
         
         2:    begin    // 数据采集期间
             adc_cs <= 0;
             flag_sel <= 1;
             dout_vld <= 0;
             if(end_cnt1)begin //等待16 * 80 = 1280 ns,数据采集完进入下一轮转换
                 state <= 1;
                 adc_cs <= 1;
                 flag_sel <= 0;
                 dout_vld <= 1;
             end
         end
         
         default: state <= 0;
     endcase
 end
 
 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         adc_data_temp <= 0;
     end
     else if(flag_sel == 1 && add_cnt0 && cnt0 == 2-1 && cnt1 >= 0 && cnt1 < 16)begin //在sclk的下降沿采数据,同时在flag_sel有效期间
             adc_data_temp[15-cnt1] <= adc_sdo;
     end
 end
 
 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         adc_data <= 0;
     end
     else if(dout_vld)begin //一个完整的16bit数据采集完
         adc_data <= adc_data_temp;
     end
 end
 
 always @(posedge clk or negedge rst_n)begin
     if(!rst_n)begin
         adc_sclk <= 1;
     end
     else if(add_cnt0 && cnt0 == 2-1)begin
         adc_sclk <= 0;
     end
     else if(end_cnt0)begin
         adc_sclk <= 1;
     end
 end
 
 endmodule

SignalTap II Logic Analyzer 抓取波形如下:

 

以上是关于基于MCP3313-10 ADC 的SPI接口驱动练习的主要内容,如果未能解决你的问题,请参考以下文章

树梅派硬件编程_ADC

STM32入门开发:编写XPT2046电阻触摸屏驱动(模拟SPI)

全志T507实现SPI转CAN-全程详解

STM32H7教程第93章 STM32H7的SPI总线应用之驱动ADS1256(8通道24bit ADC, 增益可编程)

基于iio的ADC驱动(android)

驱动程序——MCP4922——基于STM32F103