搜索
bottom↓
回复: 26
打印 上一主题 下一主题

发个老外的100M的示波器有部分源码(FPGA + ADC08100)

[复制链接]

出0入0汤圆

跳转到指定楼层
1
发表于 2009-10-29 13:02:34 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
Hands-on - A digital oscilloscope
Let's build a simple digital oscilloscope.
Single channel 100MHz = 100MSPS (100 mega-samples-per-second)
RS-232 based (we'll look into USB too)
Inexpensive!
A simple digital oscilloscope recipe
Using parts from KNJN.com, here are the basic items of our recipe.
1 x Pluto FPGA board, with TXDI and cable (item#6121 = $39.95)
1 x Flash acquisition board (item#1205 = $39.95)
BNC connector + Male/female connectors 2x8 + Nylon standoffs/screws (item#1250 + #1275 + #1270 = $10.85)
That's about $90.75 so far.
可以使用 WWW.OST2002.COM 的示波器软件驱动,点击此处下载 ourdev_497255.rar(文件大小:1.14M) (原文件名:FLASHDSO_GB_V9_7.rar)
Digital oscilloscope - part 1
Here's what is built here:


(原文件名:scope_FIFO.gif)



The FPGA receives 2 clocks:
A slow "system" clock, fixed at 25MHz.
An ADC sampling clock (something faster, let's say 100MHz), that is connected to both the ADC and the FPGA.
Having these 2 clocks gives flexibility to the design. But that also means we need a way to transfer information from one clock domain to the other. To validate that the hardware works, let's go the easy route and use a FIFO. The acquired samples from the ADC are stored in the FPGA FIFO at full ADC speed (100MHz).

Then, the FIFO content is read back, serialized and sent on a serial port at a much slower speed (115200 baud). Finally we connect the serial output to a PC that receives each byte and displays a signal trace.

For this first attempt, there is no trace triggering mechanism. The ADC storage starts at random intervals so the trace will jump left and right, but that's fine for now.

Design considerations
At 100MHz, the FIFO fills up in about 10us. That's pretty fast. Once full, we have to stop feeding it. What is stored needs to be completely sent to the PC before we can start feeding the FIFO again.

The serial communication used here works at 115200 bauds, so roughly 10KBytes/s. 1024 samples take about 100ms to transmit. During that time, the oscilloscope is "blind", because we discard the data coming from the ADC. So it is blind 99.99% of the time. That's typical of this type of architecture.

That can be partially compensated when we add a trigger mechanism later, because while the trigger is armed, it works at full ADC speed and can stay armed as long as it takes for the trigger condition to happen. More on that later.

Register the inputs
The ADC output data bus is connected to the FPGA using 8 pins that we call "data_flash[7:0]". These come at speed of up to 100MHz. Since this is fast, it is best to "register" them right when they come in the FPGA.

reg [7:0] data_flash_reg;
always @(posedge clk_flash) data_flash_reg <= data_flash;  

Now "data_flash_reg" is fully internal to the FPGA and can be fed to the FPGA FIFO.

The FIFO
The FIFO is 1024 words deep x 8 bits wide. Since we receive 8 bits per clock from the ADC, we can store 1024 ADC samples. At 100MHz, it takes about 10us to fill up the FIFO.

The FIFO uses synchronous static RAM blocks available inside the FPGA. Each storage block can store typically 512x8bits. So the FIFO uses 2 blocks.

The FIFO logic itself is created by using the FPGA vendor "function builder". Xilinx calls it "coregen" while Altera "Megafunctions wizard". Here let's use Altera's Quartus to create this file.

So now, using the FIFO is just a connectivity issue.

fifo myfifo(.data(data_flash_reg), .wrreq(wrreq), .wrclk(clk_flash), .wrfull(wrfull), .wrempty(wrempty), .q(q_fifo), .rdreq(rdreq), .rdclk(clk), .rdempty(rdempty));  

Using a FIFO is nice because it takes care of the different clocks. We connected the write side of the FIFO to the "clk_flash" (100MHz), and the read side of the FIFO to "clk" (25MHz).

The FIFO provides the full and empty signals for each clock domain. For example, "wrempty" is an empty signal that can be used in the write clock domain ("clk_flash"), and "rdempty" can be used in the read clock domain ("clk").

Using the FIFO is simple: Writing to it is just a matter of asserting the "wrreq" signal (and providing the data to the ".data" port), while reading from it a matter of asserting "rdreq" (and the data comes on the ".q" port).

Writing to the FIFO
To start writing to the FIFO, we wait until it is empty. Of course, at power-up (after the FPGA is configured), that is true.
We stop only when it gets full. And then the process starts again... we wait until it is empty... feed it until it is full... stop.

reg fillfifo;
always @(posedge clk_flash)
if(~fillfifo)
  fillfifo <= wrempty; // start when empty
else
  fillfifo <= ~wrfull; // stop when full

assign wrreq = fillfifo;  

Reading to the FIFO
We read from the FIFO as long as it is not empty. Each byte read is send to a serial output.

wire TxD_start = ~TxD_busy & ~rdempty;
assign rdreq = TxD_start;

async_transmitter async_txd(.clk(clk), .TxD(TxD), .TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(q_fifo));


We use the async_transmitter module to serialize the data and transmit it to a pin called "TxD".
Complete design
Our first working oscilloscope design, isn't that nice?

module oscillo(clk, TxD, clk_flash, data_flash);
input clk;
output TxD;

input clk_flash;
input [7:0] data_flash;

reg [7:0] data_flash_reg; always @(posedge clk_flash) data_flash_reg <= data_flash;

wire [7:0] q_fifo;
fifo myfifo(.data(data_flash_reg), .wrreq(wrreq), .wrclk(clk_flash), .wrfull(wrfull), .wrempty(wrempty), .q(q_fifo), .rdreq(rdreq), .rdclk(clk), .rdempty(rdempty));

// The flash ADC side starts filling the fifo only when it is completely empty,
// and stops when it is full, and then waits until it is completely empty again
reg fillfifo;
always @(posedge clk_flash)
if(~fillfifo)
  fillfifo <= wrempty; // start when empty
else
  fillfifo <= ~wrfull; // stop when full

assign wrreq = fillfifo;

// the manager side sends when the fifo is not empty
wire TxD_busy;
wire TxD_start = ~TxD_busy & ~rdempty;
assign rdreq = TxD_start;

async_transmitter async_txd(.clk(clk), .TxD(TxD), .TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(q_fifo));

endmodule  

Digital oscilloscope - part 2
The FIFO allowed us to get a working design very quickly.
But for our simple oscilloscope, it is overkill.

We need a mechanism to store data from one clock domain (100MHz) and read it in another (25MHz). A simple dual-port RAM does that.

The disadvantage of not using a FIFO is that all the synchonization between the 2 clock domains (that the FIFO was doing for us) has to be done "manually" now.

Trigger
The "FIFO based" oscilloscope design didn't have an explicit trigger mechanism.
Let's change that. Now the oscilloscope will be triggered everytime it receives a character from the serial port. Of course, that's still not a very useful design, but we'll improved on that later.

We receive data from the serial port:
wire [7:0] RxD_data;
async_receiver async_rxd(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));  

Everytime a new character is received, "RxD_data_ready" goes high for one clock. We use that to trigger the oscilloscope.
Synchronization
We need to transfer this "RxD_data_ready went high" information from the "clk" (25MHz) domain to the "clk_flash" (100MHz) domain.

First, a signal "startAcquisition" goes high when a character is received.
reg startAcquisition;
wire AcquisitionStarted;

always @(posedge clk)
if(~startAcquisition)
  startAcquisition <= RxD_data_ready;
else
if(AcquisitionStarted)
  startAcquisition <= 0;  

We use synchronizers in the form of 2 flipflops (to transfer this "startAcquisition" to the other clock domain).
reg startAcquisition1; always @(posedge clk_flash) startAcquisition1 <= startAcquisition;
reg startAcquisition2; always @(posedge clk_flash) startAcquisition2 <= startAcquisition1;  

Finally, once the other clock domain "sees" the signal, it "replies" (using another synchronizer "Acquiring").
reg Acquiring;
always @(posedge clk_flash)
if(~Acquiring)
  Acquiring <= startAcquisition2;  // start acquiring?
else
if(&wraddress)  // done acquiring?
  Acquiring <= 0;

reg Acquiring1; always @(posedge clk) Acquiring1 <= Acquiring;
reg Acquiring2; always @(posedge clk) Acquiring2 <= Acquiring1;
assign AcquisitionStarted = Acquiring2;  

The reply resets the original signal.

Dual-port RAM
Now that the trigger is available, we need a dual-port RAM to store the data.
Notice how each side of the RAM uses a different clock.
ram512 ram_flash(
  .data(data_flash_reg), .wraddress(wraddress), .wren(Acquiring), .wrclock(clk_flash),
  .q(ram_output), .rdaddress(rdaddress), .rden(rden), .rdclock(clk)
);  

The ram address buses are created easily using binary counters.
First the write address:
reg [8:0] wraddress;
always @(posedge clk_flash) if(Acquiring) wraddress <= wraddress + 1;  

and the read address:
reg [8:0] rdaddress;
reg Sending;
wire TxD_busy;

always @(posedge clk)
if(~Sending)
  Sending <= AcquisitionStarted;
else
if(~TxD_busy)
begin
  rdaddress <= rdaddress + 1;
  if(&rdaddress) Sending <= 0;
end  

Notice how each counter uses a different clock.

Finally we send data to the PC:
wire TxD_start = ~TxD_busy & Sending;
wire rden = TxD_start;

wire [7:0] ram_output;
async_transmitter async_txd(.clk(clk), .TxD(TxD), .TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(ram_output));  

The complete design
module oscillo(clk, RxD, TxD, clk_flash, data_flash);
input clk;
input RxD;
output TxD;

input clk_flash;
input [7:0] data_flash;

///////////////////////////////////////////////////////////////////
wire [7:0] RxD_data;
async_receiver async_rxd(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));

reg startAcquisition;
wire AcquisitionStarted;

always @(posedge clk)
if(~startAcquisition)
  startAcquisition <= RxD_data_ready;
else
if(AcquisitionStarted)
  startAcquisition <= 0;

reg startAcquisition1; always @(posedge clk_flash) startAcquisition1 <= startAcquisition ;
reg startAcquisition2; always @(posedge clk_flash) startAcquisition2 <= startAcquisition1;

reg Acquiring;
always @(posedge clk_flash)
if(~Acquiring)
  Acquiring <= startAcquisition2;
else
if(&wraddress)
  Acquiring <= 0;

reg [8:0] wraddress;
always @(posedge clk_flash) if(Acquiring) wraddress <= wraddress + 1;

reg Acquiring1; always @(posedge clk) Acquiring1 <= Acquiring;
reg Acquiring2; always @(posedge clk) Acquiring2 <= Acquiring1;
assign AcquisitionStarted = Acquiring2;

reg [8:0] rdaddress;
reg Sending;
wire TxD_busy;

always @(posedge clk)
if(~Sending)
  Sending <= AcquisitionStarted;
else
if(~TxD_busy)
begin
  rdaddress <= rdaddress + 1;
  if(&rdaddress) Sending <= 0;
end

wire TxD_start = ~TxD_busy & Sending;
wire rden = TxD_start;

wire [7:0] ram_output;
async_transmitter async_txd(.clk(clk), .TxD(TxD), .TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(ram_output));

///////////////////////////////////////////////////////////////////
reg [7:0] data_flash_reg; always @(posedge clk_flash) data_flash_reg <= data_flash;

ram512 ram_flash(
  .data(data_flash_reg), .wraddress(wraddress), .wren(Acquiring), .wrclock(clk_flash),
  .q(ram_output), .rdaddress(rdaddress), .rden(rden), .rdclock(clk)
);

endmodule  


Digital oscilloscope - part 3
Our first trigger is simple - we detect a rising edge crossing a fixed threshold. Since we use an 8-bit ADC, the acquisition range goes from 0x00 to 0xFF.
So let's set the threshold to 0x80 for now.
Detecting a rising edge
If a sample is above the threshold, but the previous sample was below, trigger!

reg Threshold1, Threshold2;
always @(posedge clk_flash) Threshold1 <= (data_flash_reg>=8'h80);
always @(posedge clk_flash) Threshold2 <= Threshold1;

assign Trigger = Threshold1 & ~Threshold2;  // if positive edge, trigger!  

Mid-display trigger
One great feature about a digital scope is the ability to see what's going on before the trigger.

How does that work?
The oscilloscope is continuously acquiring. The oscilloscope memory gets overwritten over and over - when we reach the end, we start over at the beginning. But if a trigger happens, the oscilloscope keeps acquiring for half more of its memory depth, and then stops. So it keeps half of its memory with what happened before the trigger, and half of what happened after.

We are using here a 50% or "mid-display trigger" (other popular settings would have been 25% and 75% settings, but that's easy to add later).

The implementation is easy. First we have to keep track of how many bytes have been stored.
reg [8:0] samplecount;


With a memory depth of 512 bytes, we first make sure to acquire at least 256 bytes, then stop counting but keep acquiring while waiting for a trigger. Once the trigger comes, we start counting again to acquire 256 more bytes, and stop.
reg PreTriggerPointReached;
always @(posedge clk_flash) PreTriggerPointReached <= (samplecount==256);


The decision logic deals with all these steps:
always @(posedge clk_flash)
if(~Acquiring)
begin
  Acquiring <= startAcquisition2;  // start acquiring?
  PreOrPostAcquiring <= startAcquisition2;
end
else
if(&samplecount)  // got 511 bytes? stop acquiring
begin
  Acquiring <= 0;
  AcquiringAndTriggered <= 0;
  PreOrPostAcquiring <= 0;
end
else
if(PreTriggerPointReached)  // 256 bytes acquired already?
begin
  PreOrPostAcquiring <= 0;
end
else
if(~PreOrPostAcquiring)
begin
  AcquiringAndTriggered <= Trigger;  // Trigger? 256 more bytes and we're set
  PreOrPostAcquiring <= Trigger;
  if(Trigger) wraddress_triggerpoint <= wraddress;  // keep track of where the trigger happened
end

always @(posedge clk_flash) if(Acquiring) wraddress <= wraddress + 1;
always @(posedge clk_flash) if(PreOrPostAcquiring) samplecount <= samplecount + 1;

reg Acquiring1; always @(posedge clk) Acquiring1 <= AcquiringAndTriggered;
reg Acquiring2; always @(posedge clk) Acquiring2 <= Acquiring1;
assign AcquisitionStarted = Acquiring2;  

Notice that we took care of remembering where the trigger happened. That's used to determine the beginning of the sample window in the RAM to send to the PC.
reg [8:0] rdaddress, SendCount;
reg Sending;
wire TxD_busy;

always @(posedge clk)
if(~Sending)
begin
  Sending <= AcquisitionStarted;
  if(AcquisitionStarted) rdaddress <= (wraddress_triggerpoint ^ 9'h100);
end
else
if(~TxD_busy)
begin
  rdaddress <= rdaddress + 1;
  SendCount <= SendCount + 1;
  if(&SendCount) Sending <= 0;
end  

With this design, we finally get a useful oscilloscope. We just need to customize it now.
Digital oscilloscope - part 4
Now that the oscilloscope skeleton is working, it is easy to add more functionality.
Edge-slope trigger
Let's add the ability to trigger on a rising-edge or falling-edge. Any oscilloscope can do that.

We need one bit of information to decide with direction we want to trigger on. Let's use bit-0 of the data sent by the PC.
assign Trigger = (RxD_data[0] ^ Threshold1) & (RxD_data[0] ^ ~Threshold2);  

That was easy.
More options
Let's add the ability to control the trigger threshold. That's an 8-bits value. Then we require horizontal acquisition rate control, filtering control... That requires multiple control bytes from the PC to control the oscilloscope.

The simplest approach is to use the "async_receiver" gap detection feature. The PC sends control bytes in burst, and when it stops sending, the FPGA detects it and assert an "RxD_gap" signal. wire RxD_gap;
async_receiver async_rxd(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data), .RxD_gap(RxD_gap));

reg [1:0] RxD_addr_reg;
always @(posedge clk) if(RxD_gap) RxD_addr_reg <= 0; else if(RxD_data_ready) RxD_addr_reg <= RxD_addr_reg + 1;

// register 0: TriggerThreshold
reg [7:0] TriggerThreshold;
always @(posedge clk) if(RxD_data_ready & (RxD_addr_reg==0)) TriggerThreshold <= RxD_data;

// register 1: "0 0 0 0 HDiv[3] HDiv[2] HDiv[1] HDiv[0]"
reg [3:0] HDiv;
always @(posedge clk) if(RxD_data_ready & (RxD_addr_reg==1)) HDiv <= RxD_data[3:0];

// register 2: "StartAcq TriggerPolarity 0 0 0 0 0 0"
reg TriggerPolarity;
always @(posedge clk) if(RxD_data_ready & (RxD_addr_reg==2)) TriggerPolarity <= RxD_data[6];
wire StartAcq = RxD_data_ready & (RxD_addr_reg==2) & RxD_data[7];  

We've also added a 4 bits register (HDiv[3:0]) to control the horizontal acquisition rate. When we want to decrease the acquisition rate, either we discard samples coming from the ADC, or we filter/downsample them at the frequency we are interested in.


(原文件名:Flashy_FlashBottomConnector.jpg)


(原文件名:Flashy_PlutoConnector.jpg)


(原文件名:Flashy_PlutoFlashyCombo.jpg)


(原文件名:Flashy_PlutoKit_labels.jpg)



(原文件名:LCD.jpg)

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入0汤圆

2
发表于 2009-10-29 13:18:46 | 只看该作者
很强悍的样子

出0入0汤圆

3
发表于 2009-10-29 15:07:12 | 只看该作者
you三件吗

出0入0汤圆

4
发表于 2009-10-29 15:20:26 | 只看该作者
哪个高手做个出来看看啊。

出0入0汤圆

5
发表于 2009-10-29 15:24:53 | 只看该作者
老早就看到过了http://www.fpga4fun.com 网站上的,上面讲FPGA,还是很不错的。例子很好。
原帖地址
http://www.fpga4fun.com/Hands-on_Flashy.html

出0入0汤圆

6
发表于 2009-10-29 15:30:05 | 只看该作者
学习

出0入8汤圆

7
发表于 2009-10-29 15:43:32 | 只看该作者
强, 下载中.............

出0入0汤圆

8
发表于 2009-10-29 16:01:34 | 只看该作者

出0入0汤圆

9
发表于 2009-10-29 16:52:49 | 只看该作者
先记号一下,有空研究下。

出0入0汤圆

10
发表于 2009-10-29 16:57:41 | 只看该作者
呵呵,做得蛮精致.

出0入0汤圆

11
发表于 2009-10-29 16:59:26 | 只看该作者
请都楼主,原理图在那?本贴没找到,

出0入0汤圆

12
发表于 2009-10-29 18:08:09 | 只看该作者
带 宽多大?只说采样100M,意义不大,重要的是带宽。

出0入0汤圆

13
发表于 2009-10-29 18:18:18 | 只看该作者
好像前端是用的场管,带宽应该不是问题,但可控增益是问题。

出0入0汤圆

14
发表于 2009-10-29 18:43:18 | 只看该作者
原理图好像没有的!那个上面的模块也是作者店里买的模块!具体看看http://www.fpga4fun.com/Hands-on_Flashy.html

出0入0汤圆

15
发表于 2010-2-23 10:24:02 | 只看该作者
mark

出0入0汤圆

16
发表于 2010-2-23 11:27:43 | 只看该作者
先记下了
以后研究

出0入0汤圆

17
发表于 2010-2-23 14:29:10 | 只看该作者
hen hao

出0入0汤圆

18
发表于 2011-3-9 22:44:50 | 只看该作者
mark 学习一下。多谢楼主分享。

出0入0汤圆

19
发表于 2011-4-15 01:31:23 | 只看该作者
mark

出0入0汤圆

20
发表于 2011-9-5 20:54:24 | 只看该作者
mark

出0入0汤圆

21
发表于 2012-3-29 16:33:10 | 只看该作者
mark.................

出0入0汤圆

22
发表于 2012-4-4 14:36:36 | 只看该作者
标记            

出0入0汤圆

23
发表于 2013-4-17 22:19:26 | 只看该作者
MARK~~~~~~

出0入0汤圆

24
发表于 2013-4-18 13:18:41 | 只看该作者
很久以前的帖子了

出0入0汤圆

25
发表于 2013-4-18 13:24:33 | 只看该作者
MARKAAAA  A

出0入0汤圆

26
发表于 2015-3-31 15:23:41 | 只看该作者
mark......

出0入0汤圆

27
发表于 2015-7-17 08:36:13 | 只看该作者
代码自己早先试过了,学习FPGA很有帮助。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 13:34

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表