切换到宽版
  • 9388阅读
  • 4回复

[AVR]普通arduino打造1.5mHz采样率 “高速”业余示波器Girino(转)(下) [复制链接]

上一主题 下一主题
离线abigbell
 

发帖
11330
M币
1683
专家
457
粉丝
532
只看楼主 倒序阅读 我要置顶 楼主  发表于: 2014-06-14
继续上一篇 普通arduino打造1.5mHz采样率 “高速”业余示波器Girino(转)(上)

Step 14: How the Pulse Width Modulation works
第十四步 脉宽调制




As stated before, a Pulse Width Modulation (PWM) signal is a square signal with fixed frequency but variable
width. On the image we see an example. On each row there is one of such signals with a different duty cycle
(id est the period portion in which the signal is High). Taking the mean signal over a period, we get the red line
that correspond to the duty cycle with respect to the signal maximum.
如前所述,脉宽调制信号是一个定频方波信号,但宽度可调。如图,每条高位蓝线代表做工脉冲,红线则是
等值功率。

Electronically "taking the mean of a signal" can be translated to "passing it to a low-pass filter", as seen on the
preceding step.

How does the Arduino generate a PWM signal? There is a really good tutorial about PWM here:
关于如何利用ardunino产生脉冲信号,参考下链
http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM
We will see just the points that are needed for this project.

In the ATMega328P there are three timers that can be used to generate PWM signals, each one of those
has different characteristics that you can use. For each timer correspond two registers called Output Compare
Registers A/B (OCRnx) that are used to set the signal duty cycle.
atm328p有3个计时器可以用来发生pwm信号,每个计时器特性不同,每个计时器对应两个寄存器,别叫做
输出比较寄存器A/B / OCRnx,用于控制做功么脉冲值。

As for the ADC there is a prescaler (see image), that slows down the main clock to have a precise control of
the PWM frequency. The slowed down clock is fed to a counter that increments a Timer/Counter Register (TCNTn).
This register is continuously compared to the OCRnx, when they are equal a signal is sent to a Waveform Generator
that generate a pulse on the output pin. So the trick is setting the OCRnx register to some value to change the mean
value of the signal.
至于ADC,通过一个分频器来精确控制脉宽调制模块的工作时钟。这个时钟与TCNTn寄存器相连。这个寄存器
中的值会不断地狱OCRnx比较,当两值相等,波形生成器工作发出脉冲至输出针脚。所以这里要做的就是给
OCRnx寄存器赋值。

If we want a 5 V signal (maximum) we must set a 100% duty cycle or a 255 in the OCRnx (maximum for a
8-bit number), while if we want a 0.5 V signal we must set a 10% duty cycle or a 25 in the OCRnx.
如果要100%的功率输出,5V信号,给OCRnx赋值255。想要0.5V信号输出,则OCRnx为25。

Since the clock has to fill the TCNTn register before starting from the beginning for a new pulse the output
frequency of the PWM is:
因为得到相应时钟频率的相应地提前在脉冲发生最开始设定TCNT寄存器值,

    f = (Main clock) / prescaler / (TCNTn maximum)

exempli gratia for the Timer 0 and 2 (8-bit) with no prescaler it will be: 16 MHz / 256 = 62.5 KHz while for
Timer 1 (16-bit) it will be 16 MHz / 65536 = 244 Hz.
例如,8位精度,计时器0、2,不分频,则脉冲频率则为:控制器主频率16Mhz/256 =62.5khz,
当16位精度时,计时器1,脉冲频率则为:16mhz/65536 = 244 hz。

I decided to use the Timer number 2 because
具体地,这里使用计时器2,因为

    Timer 0 is used internally by the Arduino IDE for functions such as millis();
计时器0 ArduinoIDE要用到他用

    Timer 1 has an output frequency too slow because it is a 16-bit timer.
计时器1 的输出频率太低,一位它是16位精度的

In the ATMega328P there are different kinds of operation mode of the timers, but what I wanted was
the Fast PWM one with no prescaling to get the maximum possible output frequency.
atm328p有多种计时器运行模式,这里使用最快的,无分频的,以达到最快的输出频率。


Step 15: Setting up the PWM
第十五步 设置脉宽调制


In the sketch, I wrote another initialization function that sets-up all the parameters of the Timer functioning
and initializes a couple of pins.
以下代码设定相关参数,启动计时器和相关针脚

    void initPins(void)
    {
            //---------------------------------------------------------------------
            // TCCR2A settings
            //---------------------------------------------------------------------
            // These bits control the Output Compare pin (OC2A) behavior. If one or
            // both of the COM2A1:0 bits are set, the OC2A output overrides the
            // normal port functionality of the I/O pin it is connected to.
            // However, note that the Data Direction Register (DDR) bit
            // corresponding to the OC2A pin must be set in order to enable the output driver.
            // When OC2A is connected to the pin, the function of the COM2A1:0 bits
            // depends on the WGM22:0 bit setting.
            //
            // Fast PWM Mode
            // COM2A1 COM2A0
            // 0 0 Normal port operation, OC2A disconnected.
            // 0 1 WGM22 = 0: Normal Port Operation, OC0A Disconnected.
            //   WGM22 = 1: Toggle OC2A on Compare Match.
            // 1 0 Clear OC2A on Compare Match, set OC2A at BOTTOM
            // 1 1 Clear OC2A on Compare Match, clear OC2A at BOTTOM
            cbi(TCCR2A,COM2A1);
            cbi(TCCR2A,COM2A0);
            sbi(TCCR2A,COM2B1);
            cbi(TCCR2A,COM2B0);

            // Combined with the WGM22 bit found in the TCCR2B Register, these bits
            // control the counting sequence of the counter, the source for maximum
            // (TOP) counter value, and what type of waveform generation to be used
            // Modes of operation supported by the Timer/Counter unit are:
            // - Normal mode (counter),
            // - Clear Timer on Compare Match (CTC) mode,
            // - two types of Pulse Width Modulation (PWM) modes.
            //
            // Mode WGM22 WGM21 WGM20 Operation TOP
            // 0 0 0 0 Normal  0xFF
            // 1 0 0 1 PWM  0xFF
            // 2 0 1 0 CTC  OCRA
            // 3 0 1 1 Fast PWM 0xFF
            // 4 1 0 0 Reserved -
            // 5 1 0 1 PWM  OCRA
            // 6 1 1 0 Reserved -
            // 7 1 1 1 Fast PWM OCRA
            cbi(TCCR2B,WGM22);
            sbi(TCCR2A,WGM21);
            sbi(TCCR2A,WGM20);

            //---------------------------------------------------------------------
            // TCCR2B settings
            //---------------------------------------------------------------------
            // The FOC2A bit is only active when the WGM bits specify a non-PWM mode.
            // However, for ensuring compatibility with future devices, this bit
            // must be set to zero when TCCR2B is written when operating in PWM
            // mode. When writing a logical one to the FOC2A bit, an immediate
            // Compare Match is forced on the Waveform Generation unit. The OC2A
            // output is changed according to its COM2A1:0 bits setting. Note that
            // the FOC2A bit is implemented as a strobe. Therefore it is the value
            // present in the COM2A1:0 bits that determines the effect of the forced compare.
            // A FOC2A strobe will not generate any interrupt, nor will it clear
            // the timer in CTC mode using OCR2A as TOP.
            // The FOC2A bit is always read as zero.
            cbi(TCCR2B,FOC2A);
            cbi(TCCR2B,FOC2B);

            // The three Clock Select bits select the clock source to be used by the Timer/Counter.
            // CS22 CS21 CS20 Prescaler
            // 0 0 0 No clock source (Timer/Counter stopped).
            // 0 0 1 No prescaling
            // 0 1 0 8
            // 0 1 1 32
            // 1 0 0 64
            // 1 0 1 128
            // 1 1 0 256
            // 1 1 1 1024
            cbi(TCCR2B,CS22);
            cbi(TCCR2B,CS21);
            sbi(TCCR2B,CS20);

            pinMode( errorPin, OUTPUT );
            pinMode( thresholdPin, OUTPUT );

            analogWrite( thresholdPin, 127 );
    }


Step 16: Volatile variables
第十六步  Volatile 变量


I can not remember where, but I read that variables that are modified inside an ISR should be declared
as volatile.
似乎在ISR内部修改的变量应该声明为Volatile 变量,防止编译器过度优化。

Volatile variables are variables that can change during time, even if the program that is running does
not modify them. Just like Arduino registers that can change value for some external interventions.
Volatile 变量随时可变,即使是在程序运行时,没有对其作出更改的状况下。就像arduino寄存器可能会
因为外界干扰而改变。

Why does the compiler want to know about such variables? That is because the compiler always tries to
optimize the code that we write, to make it faster, and it modifies it a little bit, trying not to change its meaning.
If a variable changes by its own it could seem to the compiler that it is never modified during execution of, say,
a loop and it could ignore it; while it could be crucial that the variable changes its value. So declaring volatile
variables it prevents the compiler to modify the code concerning those.

For some more information I suggest to read the Wikipedia page: http://en.wikipedia.org/wiki/Volatile_variable
更多信息,参看上链wiki

Step 17: Writing the kernel of the sketch
第十七步 编写核心代码




Finally we have gotten to the kernel of the program!

As we saw before, I wanted a continuous acquisition and I wrote the ADC Interrupt Service Routine to store
in the circular buffer the data continuously. It stops whenever it reaches the index that is equal to stopIndex.
The buffer is implemented as circular employing the modulo operator.
为了能持续读取输入信号,ADC中断服务程序将回环缓存中的数据持续地存储为文件。当缓存达到停止位置时,
缓存由整数操作器操作。

    //-----------------------------------------------------------------------------
    // ADC Conversion Complete Interrupt
    //-----------------------------------------------------------------------------
    ISR(ADC_vect)
    {
            // When ADCL is read, the ADC Data Register is not updated until ADCH
            // is read. Consequently, if the result is left adjusted and no more
            // than 8-bit precision is required, it is sufficient to read ADCH.
            // Otherwise, ADCL must be read first, then ADCH.
            ADCBuffer[ADCCounter] = ADCH;

            ADCCounter = ( ADCCounter + 1 ) % ADCBUFFERSIZE;

            if ( wait )
            {
                    if ( stopIndex == ADCCounter )
                    {
                            // Freeze situation
                            // Disable ADC and stop Free Running Conversion Mode
                            cbi( ADCSRA, ADEN );

                            freeze = true;
                    }
            }
    }


The Analog Comparator Interrupt Service Routine (that is called when a signal passes the threshold)
disables itself and tells the ADC ISR to start the waiting phase and sets the stopIndex.
(当信号超过阀值时,执行这段代码)模拟比较器中断服务程序终止执行,ADCISR开始工作,设定停止值。


    //-----------------------------------------------------------------------------
    // Analog Comparator interrupt
    //-----------------------------------------------------------------------------
    ISR(ANALOG_COMP_vect)
    {
            // Disable Analog Comparator interrupt
            cbi( ACSR,ACIE );

            // Turn on errorPin
            //digitalWrite( errorPin, HIGH );
            sbi( PORTB, PORTB5 );

            wait = true;
            stopIndex = ( ADCCounter + waitDuration ) % ADCBUFFERSIZE;
    }


This was really easy after all that grounding!


Step 18: Forming incoming signal
第十八步 输入信号


Let us get to the hardware now. The circuit may look complicated but it is really simple.
硬件不难

    There is a 1 MΩ resistor at the input, to give a mass reference to the signal and have a high impedance
input. A high impedance "simulates" an open circuit if you connect it to a lower impedance one, so the
presence of the Girino does not mess too much with the circuit you want to measure.
输入端1欧电阻,提供输入信号电压一个总体的参考,高阻抗输入。高阻抗模拟开路待测低阻抗信号,
免得示波器对待测电路带来过大影响。

    After the resistor there is an emitter follower to decouple the signal and protect the following electronics.
电阻后边是一个发射极放大器,用于分离信号,保护随后的电路

    There is a simple offset that generates a 2.5 V level with a voltage divider. It is attached to a capacitor to
stabilize it.
这有一个简单的补偿电路,可以产生2.5V的电平,搭配稳定电容使用

    There is a non-inverting sum-amplifier that sums the incoming signal and the offset. I used this technique
because I wanted to be able to see also negative signals, as the Arduino ADC could see signals only between
0 V to 5 V.
这里有一个不可逆的总计放大器,用于将输入信号和补偿电平相加。这就使得这个示波器可以测量负值信号。

    After the sum-amp there is another emitter follower.
经过总计放大后,是另一个发射极放大器。

    A jumper lets us decide if we want to feed the signal with a offset or not.
跳线,用于选择开关补偿电平信号

The Operational Amplifier that I intended to use was a LM324 that is able to work between 0 V to 5 V but
also between, say, -12 V to 12 V. This gives us more possibilities with the power supplies. I also tried a
TL084 that is way faster than the LM324 but requires a dual power supply. They both have the same pinout
so can be changed without any modification of the circuit.
运放使用lm324,工作电压为0V 至 5V,但可能还会更广一点,估算 -12V 至 12V。使用tl084工作速度要更快些。

Step 19: Bypass capacitors
第十九步 旁路电容


Bypass Capacitors are capacitors that are used to filter the power supplies of Integrated Circuits (IC) and they
should be put as close as possible to the alimentation pins of the IC. They are used usually in couple, one ceramic
and one electrolytic because they can filter out different frequencies.
旁路电容,过滤电源,尽量挨紧IC使用,通常成对使用,陶瓷配电解,以过滤不同频率的杂波。

Step 20: Power sources
第二十步 电源




I used a dual power supply for the TL084 that can be converted to a single power supply for the LM324.
双路tl084,搭配lm324

On the image we can see that I used a couple of voltage regulators a 7812, for +12 V, and a 7912, for -12 V.
The capacitors are, as usual, used to stabilize the levels and their values are the ones suggested in the
datasheets.
7812 正12V,7912 负12V,电容稳压

Obviously to have a ±12 V we have to have at least about 30 V on the input because the voltage regulators
require a higher input to provide a stabilized output. Since I did not have such power supply I used the trick of
using two 15 V power supplies in series. One of the two is connected to the Arduino power connector (so it feeds
both the Arduino and my circuit) and the other directly to the circuit.
两个15V开关电源,串联30V。一个接arduino电源连接器(arduino和自制的电路板),另一个直接连接电路板

It is not an error to connect the +15 V of the second power supply to the GND of the first! This is how we get
a -15 V with isolated power supplies.
第一个15V电源正极接第二个电源负极,这点作为接地点,第一个电源15V为正,第二个电源15V为负。

If I do not want to carry around an Arduino and two power supplies I can still use the +5 V provided by the Arduino

changing those jumpers (and using the LM324).
即使不使用这两个电源,也可以使用arduino的5V供电。

Step 21: Preparing a Shield Connector
步骤21,准备硬件物料(看图)











I have always been annoyed by the connectors that I could find to make an Arduino shield, because they always
have pins that are too short and the boards that I use can be soldered only on one side. So I made up a little trick
to make the pins longer so they can be soldered and inserted in the Arduino.
Inserting the pin strip into the board, as on the picture, we can push the pins, to have them only on one side of
the black plastic. Then we can solder them on the same side where they will be inserted in the Arduino.


Step 22: Soldering and testing
步骤22,焊、测

I am unable to show you all the soldering procedure of the circuit because it underwent a lot of trial and error work.
In the end it got a little messy but not too bad, though I will not show the underside because that is really messy.
几经周折,最后焊接成型

At this stage there is not much to say because I already explained in detail all the parts of the circuit. I tested it with
an oscilloscope, that a friend borrowed me, to see the signals at each point of the circuit. It seems that everything is
working alright and I am pretty satisfied.
借用一台示波器,检测运行结果,工作状况良好,使用效果不错

The connector for the incoming signal could seem a little strange for someone that does not come from the High
Energy Physics, it is a LEMO connector. It is the standard connector for nuclear signals, at least in Europe as in
the USA I have seen mostly BNC connectors.
输入信号连接器,是典型的高能物理的LEMO连接器。主要在欧洲地区用于原子信号,但美国多见的是BNC连接器


Step 23: Test signals
步骤23 测试信号




To test the circuit and the Data AcQuisition (DAQ) I used a second Arduino with a simple sketch that generates
square pulses with different lenghts. I also wrote a python script that talks to Girino and tells it to acquire some
data series and saves one of those to a file.
测试电路和数据采集,这里用到了另一块arduino作为信号发生器,利用python脚本联络Girino,将采集到的数据
保存为文件

They are both attached to this step. 软体附上:

Step 24: Time calibration
步骤24 校时


Using the test signals I calibrated the horizontal scale of the plots. By measuring the widths of the pulses
(that are known because were generated) and plotting the measured pulses widths against the known
values, we get a hopefully linear plot. Doing this for each prescaler setting we have the time calibration
for all acquisition rates.
用已知频率的脉冲校准水平刻度。得到一个基本线性的图形。每个等级都进行了校准。

On the images we can see all the data that I took an analyzed. The "Fitted slopes" plot is the most
interesting because it tells us the actual acquisition rate of my system at each prescaler setting.
The slopes were measured as a [ch/ms] number but this is equivalent to a [kHz], so the slopes values
are actually kHz or also kS/s (kilo Samples per second). That means that with the prescaler set to 8
we get an acquisition rate of:
如图,最各个频率都进行了校准,(图太多,让转载者给删了几张,只保留最小、最大)。Fitted Slopes
这张图实际上就是每个频段的采用率,单位是ch/ms,数值等同于khz,所以可以读作khz,kS/s采样率。


    (154±2) kS/s 154 k 采样率,8位精度

Not bad, uh? 不错的结果

While from the "Fitted y-intercepts" plot we get an insight of the system linearity. All the y-intercepts
should be zero because at a signal with zero length should correspond a pulse with a zero length.
As we can see on the graph they all are compatible with zero, but not the 18-prescaler dataset.
This dataset, though, is the worst one because is has just two data and its calibration can not be trusted.
如图Fitted y-intercepts,显示的是系统的线性状态。所有的Y轴值都应该为0,因为0宽脉冲,长度为0。
但18频段的校时不可靠,所以有如图上显示。


Following there is a table with the acquisition rates for each prescaler setting.
采样精度、频段和采样率的对应关系

Prescaler 精度        Acquisition rate [kS/s]采样率
128             9.74 ± 0.04
64             19.39 ± 0.06
32             37.3 ± 0.6
16             75.5 ± 0.3
8             153 ± 2

The cited errors are coming from the Gnuplot fit engine and I am unsure about them.
上述的问题来自Gnuplot 图像引擎,但更具体的不清楚


I also tried an unweighted fit of the rates because you can see that they roughly double when the
prescaling halves, this looks like an inverse proportionality law. So I fitted the rates vs the prescaler
settings with a simple law of
笔者尝试了随意采样精度,一般情况下,采样精度减半,采样频率会增倍。看似反比关系

    y=a/x

I got a value for a of

    a=1223

with a χ2=3.14 and 4 degrees of freedom, this means that the law is accepted with a 95% confidence level!
不懂原文(with a χ2=3.14 and 4 degrees of freedom,)结论,这一反比关系有95%的正确性。

Step 25: Done! (Almost)
基本完成


At the end of this long experience, I feel very satisfied because
满足感

    I learned a lot about microcontrollers in general; 增进知识
    I learned a lot more about the Arduino ATMega328P; 加深了解
    I had some hands-on experience of Data Acquisition, not by using something already done but
by making something;
自制软体用于数据采集
    I realized an amateur oscilloscope that is not that bad. 业余的示波器也可以整得不错

I hope that this guide will be useful to anybody that reads it. I wanted to write it so detailed because
I learned all that in the hard way (surfing the internet, reading the datasheet and by a lot of trial and
error) and I would like to spare anybody from that experience.
希望这帖能帮到读完它的人


Step 26: To be continued...
步骤26 未完待续


The project if far from completed, though. What it is missing is:
远未完成,还差

   1. A test with different analog signals (I am missing an analog signal generator);
各种模拟信号测试
   2. A Graphical User Interface for the computer side.
图像界面

While for point 1. I am not sure when it will be completed, because I am not planning to buy/build one in
the near future.
对第一点,会尽快完成

For point 2. the situation could be better. Is anyone willing to help me with that? I found a nice Python
Oscilloscope here:
第二点,需求帮助,发现一个基于python的示波器软体(下链),应该可以移植,欢迎建议
http://www.phy.uct.ac.za/courses/python/examples/moreexamples.html#oscilloscope-and-spectrum-analyser
I would like to modify it to fit it for Girino, but I am accepting suggestions.
本文内容包含图片或附件,获取更多资讯,请 登录 后查看;或者 注册 成为会员获得更多权限
本帖最近打赏记录:共2条打赏 ,M币+6
离线zhhufe

发帖
721
M币
9
专家
1
粉丝
7
只看该作者 1楼 发表于: 2014-06-15
请登录后查看
离线laulaya

发帖
58
M币
1379
专家
0
粉丝
3
只看该作者 2楼 发表于: 2014-06-15
请登录后查看
离线cjshb888

发帖
395
M币
785
专家
14
粉丝
7
只看该作者 3楼 发表于: 2014-10-30
请登录后查看
离线dlanny

发帖
88
M币
17
专家
1
粉丝
10
只看该作者 4楼 发表于: 2017-09-02
请登录后查看
快速回复
限80 字节
温馨提示:所有技术区严禁灌水,“沙发”“顶”字样;禁止广告贴;以免被删除
 
上一个 下一个