beibei1234 发表于 2009-10-29 13:02:34

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

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:

http://cache.amobbs.com/bbs_upload782111/files_21/ourdev_497257.gif
(原文件名: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". 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 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 data_flash;

reg data_flash_reg; always @(posedge clk_flash) data_flash_reg <= data_flash;

wire 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 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 wraddress;
always @(posedge clk_flash) if(Acquiring) wraddress <= wraddress + 1;

and the read address:
reg 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 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 data_flash;

///////////////////////////////////////////////////////////////////
wire 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 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 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 ram_output;
async_transmitter async_txd(.clk(clk), .TxD(TxD), .TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(ram_output));

///////////////////////////////////////////////////////////////////
reg 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 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 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 ^ Threshold1) & (RxD_data ^ ~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 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 TriggerThreshold;
always @(posedge clk) if(RxD_data_ready & (RxD_addr_reg==0)) TriggerThreshold <= RxD_data;

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

// 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;
wire StartAcq = RxD_data_ready & (RxD_addr_reg==2) & RxD_data;

We've also added a 4 bits register (HDiv) 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.

http://cache.amobbs.com/bbs_upload782111/files_21/ourdev_497247.jpg
(原文件名:Flashy_FlashBottomConnector.jpg)

http://cache.amobbs.com/bbs_upload782111/files_21/ourdev_497248.jpg
(原文件名:Flashy_PlutoConnector.jpg)

http://cache.amobbs.com/bbs_upload782111/files_21/ourdev_497249.jpg
(原文件名:Flashy_PlutoFlashyCombo.jpg)

http://cache.amobbs.com/bbs_upload782111/files_21/ourdev_497250.jpg
(原文件名:Flashy_PlutoKit_labels.jpg)


http://cache.amobbs.com/bbs_upload782111/files_21/ourdev_497254.jpg
(原文件名:LCD.jpg)

ksniper 发表于 2009-10-29 13:18:46

很强悍的样子

shunzi6 发表于 2009-10-29 15:07:12

you三件吗

hebj 发表于 2009-10-29 15:20:26

哪个高手做个出来看看啊。

chaplin1999 发表于 2009-10-29 15:24:53

老早就看到过了http://www.fpga4fun.com 网站上的,上面讲FPGA,还是很不错的。例子很好。
原帖地址
http://www.fpga4fun.com/Hands-on_Flashy.html

vv3g 发表于 2009-10-29 15:30:05

学习

WGJ5767351 发表于 2009-10-29 15:43:32

强, 下载中.............

shanyan 发表于 2009-10-29 16:01:34

jamesyu 发表于 2009-10-29 16:52:49

先记号一下,有空研究下。

gdrc 发表于 2009-10-29 16:57:41

呵呵,做得蛮精致.

gdrc 发表于 2009-10-29 16:59:26

请都楼主,原理图在那?本贴没找到,

kanggnak 发表于 2009-10-29 18:08:09

带 宽多大?只说采样100M,意义不大,重要的是带宽。

fsclub 发表于 2009-10-29 18:18:18

好像前端是用的场管,带宽应该不是问题,但可控增益是问题。

chaplin1999 发表于 2009-10-29 18:43:18

原理图好像没有的!那个上面的模块也是作者店里买的模块!具体看看http://www.fpga4fun.com/Hands-on_Flashy.html

sunzhaod 发表于 2010-2-23 10:24:02

mark

Jigsaw 发表于 2010-2-23 11:27:43

先记下了
以后研究

hongyancl 发表于 2010-2-23 14:29:10

hen hao

feiyang_zc 发表于 2011-3-9 22:44:50

mark 学习一下。多谢楼主分享。

nevermoore 发表于 2011-4-15 01:31:23

mark

36wj 发表于 2011-9-5 20:54:24

mark

1伤1 发表于 2012-3-29 16:33:10

mark.................

tianlai8624 发表于 2012-4-4 14:36:36

标记            

hailiyayaya 发表于 2013-4-17 22:19:26

MARK~~~~~~

ele-boy 发表于 2013-4-18 13:18:41

很久以前的帖子了

mage99 发表于 2013-4-18 13:24:33

MARKAAAAA

mrlee866 发表于 2015-3-31 15:23:41

mark......

liucoldstarplus 发表于 2015-7-17 08:36:13

代码自己早先试过了,学习FPGA很有帮助。
页: [1]
查看完整版本: 发个老外的100M的示波器有部分源码(FPGA + ADC08100)