File Coverage

blib/lib/Electronics/SigGen/FY3200.pm
Criterion Covered Total %
statement 27 86 31.4
branch 0 16 0.0
condition 0 3 0.0
subroutine 9 22 40.9
pod 2 3 66.6
total 38 130 29.2


line stmt bran cond sub pod time code
1             package Electronics::SigGen::FY3200;
2              
3 1     1   2075 use strict;
  1         6  
  1         90  
4 1     1   21 use warnings;
  1         5  
  1         89  
5              
6 1     1   16 use Carp;
  1         6  
  1         205  
7 1     1   18 use Fcntl qw( O_NOCTTY O_NDELAY );
  1         5  
  1         138  
8 1     1   1849 use Future;
  1         16239  
  1         52  
9 1     1   755 use IO::Termios;
  1         34422  
  1         68  
10 1     1   11 use Time::HiRes qw( time sleep );
  1         3  
  1         12  
11              
12 1     1   168 use constant MIN_DELAY => 0.05;
  1         3  
  1         698  
13              
14             # See also:
15             # https://www.eevblog.com/forum/testgear/feeltech-fy3224s-24mhz-2-channel-dds-aw-function-signal-generator/msg708434/#msg708434
16              
17             =head1 NAME
18              
19             C - control a F F signal generator
20              
21             =head1 SYNOPSIS
22              
23             use Electronics::SigGen::FY3200;
24              
25             my $fy3200 = Electronics::SigGen::FY3200->new( dev => "/dev/ttyUSB0" );
26              
27             my $ch1 = $fy3200->channel(1);
28              
29             $ch1->set_wave( 'sine' )->get;
30             $ch1->set_frequency( 1E3 )->get; # in Hz
31             $ch->set_amplitude( 2 )->get; # in Volts peak
32              
33             =head1 DESCRIPTION
34              
35             This module allows control of a F F series signal generator,
36             such as the F, when connected over USB.
37              
38             =head2 Interface Design
39              
40             The interface is currently an ad-hoc collection of whatever seems to work
41             here, but my hope is to find a more generic shareable interface that multiple
42             different modules can use, to provide standard interfaces to various kinds of
43             electronics test equipment.
44              
45             The intention is that it should eventually be possible to write a script for
46             performing automated electronics testing or experimentation, and easily swap
47             out modules to suit the equipment available. Similar concepts apply in fields
48             like L, or L, so there should be plenty of ideas to borrow.
49              
50             =cut
51              
52             sub new
53             {
54 0     0 0   my $class = shift;
55 0           my %opts = @_;
56              
57 0 0         my $fh = IO::Termios->open( $opts{dev}, "9600,8,n,1", O_NOCTTY, O_NDELAY ) or
58             croak "Cannot open $opts{dev} - $!";
59              
60 0           $fh->setflag_clocal( 1 );
61 0           $fh->blocking( 1 );
62 0           $fh->autoflush;
63              
64 0           return bless {
65             fh => $fh,
66             lasttime => time(),
67             }, $class;
68             }
69              
70             sub _command
71             {
72 0     0     my $self = shift;
73 0           my ( $cmd ) = @_;
74              
75 0           my $fh = $self->{fh};
76              
77 0           my $delay = time() - $self->{lasttime};
78 0 0         if( $delay < MIN_DELAY ) {
79 0           sleep MIN_DELAY - $delay;
80             }
81              
82 0           $fh->print( "$cmd\x0a" );
83              
84 0           $self->{lasttime} = time();
85              
86 0           return Future->done;
87             }
88              
89             sub _commandresponse
90             {
91 0     0     my $self = shift;
92 0           my ( $cmd ) = @_;
93              
94 0           my $fh = $self->{fh};
95              
96 0           my $delay = time() - $self->{lasttime};
97 0 0         if( $delay < MIN_DELAY ) {
98 0           sleep MIN_DELAY - $delay;
99             }
100              
101 0           $fh->print( "$cmd\x0a" );
102              
103 0           my $ret = <$fh>;
104 0           chomp $ret;
105              
106 0           $self->{lasttime} = time();
107              
108 0           return Future->done( $ret );
109             }
110              
111             =head1 METHODS
112              
113             =cut
114              
115             =head2 identify
116              
117             $str = $fy3200->identify->get
118              
119             =cut
120              
121             sub identify
122             {
123 0     0 1   my $self = shift;
124 0           return $self->_commandresponse( "a" );
125             }
126              
127             =head2 channel
128              
129             $ch = $fy3200->channel( $n )
130              
131             Returns a Channel object representing the main (if I<$n> is 1) or secondary
132             (if I<$n> is 2) channel.
133              
134             =cut
135              
136             sub channel
137             {
138 0     0 1   my $self = shift;
139 0           my ( $idx ) = @_;
140              
141 0 0 0       croak "Bad channel index" unless $idx == 1 or $idx == 2;
142              
143 0 0         return Electronics::SigGen::FY3200::_Channel->new( $self,
    0          
144             $idx == 1 ? "c" : "",
145             $idx == 1 ? "b" : "d",
146             );
147             }
148              
149             package
150             Electronics::SigGen::FY3200::_Channel;
151              
152 1     1   8 use Carp;
  1         4  
  1         755  
153              
154             sub new
155             {
156 0     0     my $class = shift;
157 0           my ( $fy, $getcmd, $setcmd ) = @_;
158 0           return bless [ $fy, $getcmd, $setcmd ], $class;
159             }
160              
161             sub _set
162             {
163 0     0     my $self = shift;
164 0           my ( $cmd ) = @_;
165 0           return $self->[0]->_command( $self->[2] . $cmd );
166             }
167              
168             =head1 CHANNEL METHODS
169              
170             =cut
171              
172             =head2 set_wave
173              
174             $ch->set_wave( $type )->get
175              
176             Sets the basic wave shape - one of C, C, C, etc... or
177             one of the direct numbers from 0 to 16 recognised by the device.
178              
179             =cut
180              
181             my %WAVES = (
182             sine => 0,
183             square => 1,
184             triangle => 2,
185             arb1 => 3,
186             arb2 => 4,
187             arb3 => 5,
188             arb4 => 6,
189             lorentz => 7,
190             multitone => 8,
191             noise => 9,
192             ecg => 10,
193             trapezoidal => 11,
194             sinc => 12,
195             narrow => 13,
196             gaussnoise => 14,
197             am => 15,
198             fm => 16,
199             );
200              
201             sub set_wave
202             {
203 0     0     my $self = shift;
204 0           my ( $wave ) = @_;
205              
206 0 0         $wave = $WAVES{$wave} if exists $WAVES{$wave};
207 0 0         $wave =~ m/^\d+$/ or croak "Unrecognised wave type $wave";
208              
209 0           $self->_set( sprintf "w%d", $wave );
210             }
211              
212             =head2 set_frequency
213              
214             $ch->set_frequency( $hz )->get
215              
216             Sets the frequency in Hz.
217              
218             =cut
219              
220             sub set_frequency
221             {
222 0     0     my $self = shift;
223 0           my ( $hz ) = @_;
224              
225             # Frequency is in cHz
226 0           $self->_set( sprintf "f%d", $hz * 100 );
227             }
228              
229             =head2 set_amplitude
230              
231             $ch->set_amplitude( $vpk )->get
232              
233             Sets the amplitude in Volts peak.
234              
235             =cut
236              
237             sub set_amplitude
238             {
239 0     0     my $self = shift;
240 0           my ( $vpk ) = @_;
241              
242 0           $self->_set( sprintf "a%.2f", $vpk );
243             }
244              
245             =head2 set_offset
246              
247             $ch->set_offset( $v )->get
248              
249             Sets the offset in Volts.
250              
251             =cut
252              
253             sub set_offset
254             {
255 0     0     my $self = shift;
256 0           my ( $v ) = @_;
257              
258 0           $self->_set( sprintf "o%.2f", $v );
259             }
260              
261             =head2 set_duty
262              
263             $ch->set_duty( $duty )->get
264              
265             Sets the duty cycle as a fraction from 0 to 1.
266              
267             =cut
268              
269             sub set_duty
270             {
271 0     0     my $self = shift;
272 0           my ( $duty ) = @_;
273              
274 0           $self->_set( sprintf "d%d", $duty * 1000 );
275             }
276              
277             =head2 set_phase
278              
279             $ch->set_phase( $phase )->get
280              
281             Sets the phase offset as a fraction from 0 to 1.
282              
283             Note that due to hardware limitations this only takes effect on the secondary
284             channel; the primary channel will ignore it.
285              
286             =cut
287              
288             sub set_phase
289             {
290 0     0     my $self = shift;
291 0           my ( $phase ) = @_;
292              
293 0           $self->_set( sprintf "p%d", $phase * 360 );
294             }
295              
296             =head1 AUTHOR
297              
298             Paul Evans
299              
300             =cut
301              
302             0x55AA;