53191|21

275

帖子

4855

TA的资源

五彩晶圆(初级)

楼主
 

发个老外的100M的示波器有源码及电路图(转帖) [复制链接]

原作者的網站
http://www.fpga4fun.com/digitalscope_hdl1.html

FPGA board 的網站
http://www.knjn.com/


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 的示波器软件驱动,点击此处下载 FLASHDSO_GB_V9_7.rar (1.14 MB, 下载次数: 592)

Digital oscilloscope - part 1
Here's what is built here:




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.

最新回复

大家有兴趣可以聊聊,随时交流  详情 回复 发表于 2010-12-14 15:40
点赞 关注
 

回复
举报

275

帖子

4855

TA的资源

五彩晶圆(初级)

沙发
 
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.

















[ 本帖最后由 老夫子 于 2009-10-29 17:29 编辑 ]
 
 

回复

1249

帖子

0

TA的资源

裸片初长成(高级)

板凳
 
这个不错,来个DIY?谁有兴趣参加?我第一个报名!!!
 
 
 

回复

223

帖子

0

TA的资源

纯净的硅(高级)

4
 
谢过了!学习学习,最近也有这打算,呵呵
 
 
 

回复

2万

帖子

74

TA的资源

管理员

5
 

回复 4楼 crazyk 的帖子

不如跟帖的,大家有兴趣可以聊聊,随时交流。
加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
个人签名

加油!在电子行业默默贡献自己的力量!:)

 
 
 

回复

1290

帖子

0

TA的资源

五彩晶圆(初级)

6
 
这东西很是小巧哦!不错不错!
 
 
 

回复

132

帖子

2

TA的资源

纯净的硅(中级)

7
 
原作者的網站
http://www.fpga4fun.com/digitalscope_hdl1.html

FPGA board 的網站
http://www.knjn.com/

FPGA 是使用 ALTERA 的         EP1K10
 
 
 

回复

275

帖子

0

TA的资源

纯净的硅(中级)

8
 
好贴~~~跟一个~~~
个人签名做自己喜欢的设计就是一种幸福~~~
 
 
 

回复

338

帖子

2

TA的资源

五彩晶圆(初级)

9
 

原理图在哪?貌似是转贴吧

如果是转贴,宜加上转贴2字,做人要厚道.
个人签名以VS1003B和山景方案为基础,倾心研制数字化语音录放产品
排忧邮箱xg_2004_sy@126.com
 
 
 

回复

22

帖子

0

TA的资源

一粒金砂(中级)

10
 
我最近做项目 需要示波器

一个国产的都几千块  实在舍不得啊

:'(  想自己做
 
 
 

回复

2万

帖子

74

TA的资源

管理员

11
 

回复 10楼 wu_jin_liang 的帖子

坛子里正在做一个60M的,不知道满足你需求不?
加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
个人签名

加油!在电子行业默默贡献自己的力量!:)

 
 
 

回复

23

帖子

0

TA的资源

一粒金砂(中级)

12
 
:不知道什么时候60M的示波器可以买?大家辛苦了!!
 
 
 

回复

117

帖子

0

TA的资源

一粒金砂(初级)

13
 
 
 

回复

2955

帖子

0

TA的资源

纯净的硅(初级)

14
 
好东西,谢谢楼主转过来啊
个人签名不断地学习,才会有创新!
淘宝小店:手机、qq点卡、游戏点卡自动充值 http://shop63727265.taobao.com/
 
 
 

回复

11

帖子

0

TA的资源

一粒金砂(中级)

15
 

ddddddddddddd

e 文看不懂,不过还是要顶
 
 
 

回复

135

帖子

1

TA的资源

一粒金砂(高级)

16
 
看起来前端模拟电路似乎没有或不充足,不能说是个完整的示波器,只能说是个采集器
 
 
 

回复

1万

帖子

16

TA的资源

版主

17
 

谢谢老夫子

个人签名http://shop34182318.taobao.com/
https://shop436095304.taobao.com/?spm=a230r.7195193.1997079397.37.69fe60dfT705yr
 
 
 

回复

9

帖子

0

TA的资源

一粒金砂(初级)

18
 
一张20年前设计的10MHz 8cmCRT便携示波器电路图.从中看看今昔的变化.
 
 
 

回复

368

帖子

0

TA的资源

纯净的硅(初级)

19
 
这个实用性可能并不大,模拟输入范围并不宽
 
 
 

回复

268

帖子

0

TA的资源

一粒金砂(初级)

20
 
等到技术牛叉的时候,在弄吧
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表