don't dist acq-seg.gif
[comedilib.git] / doc / tutorial.sgml
1 <!-- <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V3.1//EN" "docbook/dtd/3.1/docbook.dtd">  -->
2
3 <section id="writingprograms">
4 <title>
5 Writing &comedi; programs
6 </title>
7 <para>
8 This Section describes how a well-installed and configured &comedi;
9 package can be used in an application, to communicate data with a set
10 of &comedi; devices.
11 <xref linkend="acquisitionfunctions"> gives more details about
12 the various acquisition functions with which the application
13 programmer can perform data acquisition in &comedi;.
14 </para>
15 <para>
16 Also don't forget to take a good look at the
17 <filename class=directory>demo</filename>
18 directory of the Comedilib source code. It contains lots of examples
19 for the basic functionalities of &comedi;.
20 </para>
21
22 <section id="firstprogram">
23 <title>
24 Your first &comedi; program
25 </title>
26
27 <para>
28 This example requires a card that has analog or digital input. This
29 progam opens the device, gets the data, and prints it out:
30 <programlisting>
31 #include <![CDATA[<stdio.h>]]>   /* for printf() */
32 #include <![CDATA[<]]><link linkend="comedi-comedilib-h">comedilib.h</link><![CDATA[>]]>
33
34 int subdev = 0;         /* change this to your input subdevice */
35 int chan = 0;           /* change this to your channel */
36 int range = 0;          /* more on this later */
37 int aref = <link linkend="aref-ground">AREF_GROUND</link>; /* more on this later */
38
39 int main(int argc,char *argv[])
40 {
41   <link linkend="ref-type-comedi-t">comedi_t</link> *it;
42   <link linkend="ref-type-lsampl-t">lsampl_t</link> data;
43
44   it=<link linkend="func-ref-comedi-open">comedi_open</link>("/dev/comedi0");
45
46   <link linkend="func-ref-comedi-data-read">comedi_data_read</link>(it,subdev,chan,range,aref, & data);
47
48   printf("%d\n",data);
49
50   return 0;
51 }
52 </programlisting>
53 The
54 <function>
55  <link linkend="func-ref-comedi-open">comedi_open()</link>
56 </function> can only be successful if the
57 <filename>comedi0</filename> device file is configured to point to a
58 valid &comedi; driver. <xref linkend="cardconfiguration"> explains
59 how this driver is linked to the <quote>device file</quote>.
60 <para>
61 The code above is basically the guts of
62 <filename>demo/inp.c</filename>, without error checking or fancy
63 options.  Compile the program using
64 </para>
65
66 <screen>
67 cc tut1.c -lcomedi -o tut1
68 </screen>
69 <para>
70 (Replace <literal>cc</literal> by your favourite C compiler command.)
71 </para>
72
73 <para>
74 The <parameter class=function>range</parameter> variable tells
75 &comedi; which gain to use when measuring an analog voltage.  Since we
76 don't know (yet) which numbers are valid, or what each means, we'll
77 use <literal>0</literal>, because it won't cause errors.  Likewise
78 with <parameter class=function>aref</parameter>, which determines the
79 analog reference used.
80 </para>
81 </section>
82
83
84 <section id="convertingsamples">
85 <title>
86 Converting samples to voltages
87 </title>
88
89 <para>
90 If you selected an analog input subdevice, you probably noticed
91 that the output of <command>tut1</command> is a number between
92 <literal>0</literal> and <literal>4095</literal>, or
93 <literal>0</literal> and <literal>65535</literal>, depending on the
94 number of bits in the A/D converter. &comedi; samples are
95 <emphasis>always</emphasis> unsigned,
96 with <literal>0</literal>  representing the lowest voltage of the ADC,
97 and <literal>4095</literal>
98 the highest.  &comedi; compensates for anything else the manual for
99 your device says.  However, you probably prefer to have this number
100 translated to a voltage.  Naturally, as a good programmer, your first
101 question is: <quote>How do I do this in a device-independent
102 manner?</quote>
103 </para>
104
105 <para>
106 Most devices give you a choice of gain and unipolar/bipolar
107 input, and &comedi; allows you to select which of these to use.  This
108 parameter is called the <quote>range parameter,</quote> since it
109 specifies the <quote>input range</quote> for analog input (or
110 <quote>output range</quote> for analog output.)  The range parameter
111 represents both the gain and the unipolar/bipolar aspects.
112 </para>
113
114 <para>
115 &comedi; keeps the number of available ranges and the largest
116 sample value for each subdevice/channel combination.  (Some
117 devices allow different input/output ranges for different
118 channels in a subdevice.)
119 </para>
120
121 <para>
122 The largest sample value can be found using the function
123 <programlisting>
124  <link linkend="ref-type-lsampl-t">lsampl_t</link> <link linkend="func-ref-comedi-get-maxdata">comedi_get_maxdata</link>(<link linkend="ref-type-comedi-t">comedi_t</link> * device, unsigned int subdevice, unsigned int channel))
125 </programlisting>
126 The number of available ranges can be found using the function:
127 <programlisting>
128  int <link linkend="func-ref-comedi-get-n-ranges">comedi_get_n_ranges</link>(<link linkend="ref-type-comedi-t">comedi_t</link> * device, unsigned int subdevice, unsigned int channel);
129 </programlisting>
130 </para>
131
132 <para>
133 For each value of the range parameter for a particular
134 subdevice/channel, you can get range information using:
135 <programlisting>
136  <link linkend="ref-type-comedi-range">comedi_range</link> * <link linkend="func-ref-comedi-get-range">comedi_get_range</link>(<link linkend="ref-type-comedi-t">comedi_t</link> * device, 
137                                  unsigned int subdevice, unsigned int channel, unsigned int range);
138 </programlisting>
139 which returns a pointer to a
140 <link linkend="ref-type-comedi-range">comedi_range</link>
141 structure, which has the following contents:
142 <programlisting>
143 typedef struct{
144         double min;
145         double max;
146         unsigned int unit;
147 }comedi_range;
148 </programlisting>
149 The structure element <parameter class=function>min</parameter>
150 represents the voltage corresponding to
151 <link linkend="func-ref-comedi-data-read">comedi_data_read()</link>
152 returning <literal>0</literal>,
153 and <parameter class=function>max</parameter> represents
154 <link linkend="func-ref-comedi-data-read">comedi_data_read()</link>
155 returning <parameter class=function>maxdata</parameter>,
156 (i.e., <literal>4095</literal> for <literal>12</literal> bit A/C
157 converters, <literal>65535</literal> for <literal>16</literal> bit,
158 or, <literal>1</literal> for digital input; more on this in a bit.)
159 The <parameter class=function>unit</parameter> entry tells you if
160 <parameter class=function>min</parameter> and
161 <parameter class=function>max</parameter> refer to voltage, current,
162 or are dimensionless (e.g., for digital I/O).
163 </para>
164
165 <para>
166 <quote>Could it get easier?</quote> you say.  Well, yes.  Use
167 the function <function>comedi_to_phys()</function>
168 <link linkend="func-ref-comedi-to-phys">comedi_to_phys()</link>, which
169 converts data values to physical units.  Call it using something like
170 </para>
171
172 <programlisting>
173 volts=<link linkend="func-ref-comedi-to-phys">comedi_to_phys</link>(it,data,range,maxdata);
174 </programlisting>
175
176 <para>
177 and the opposite
178 </para>
179
180 <programlisting>
181 data=<link linkend="func-ref-comedi-from-phys">comedi_from_phy</link>s(it,volts,range,maxdata);
182 </programlisting>
183
184 </section>
185
186 <section id="usingfileinterface">
187 <title>
188 Using the file interface
189 </title>
190
191
192 <para>
193 In addition to providing low level routines for data
194 access, the &comedi; library provides higher-level access,
195 much like the standard <acronym>C</acronym> library provides
196 <function>fopen()</function>, etc.  as a high-level (and portable)
197 alternative to the direct <acronym>UNIX</acronym> system calls
198 <function>open()</function>, etc.  Similarily to
199 <function>fopen()</function>, we have
200 <link linkend="func-ref-comedi-open">comedi_open()</link>:
201 </para>
202
203 <programlisting>
204 file=<link linkend="func-ref-comedi-open">comedi_open</link>("/dev/comedi0");
205 </programlisting>
206
207 <para>
208 where <parameter class=function>file</parameter> is of type 
209 <parameter>(<link linkend="ref-type-comedi-t">comedi_t</link> *)</parameter>.
210 This function calls <function>open()</function>, as done explicitly in
211 a previous section, but also fills the
212 <link linkend="ref-type-comedi-t">comedi_t</link>
213 structure with lots of goodies; this information will be useful soon.  
214 </para>
215
216 <para>
217 Specifically, you need to know
218 <parameter class=function>maxdata</parameter> for a specific
219 subdevice/channel. How about:
220
221 <programlisting>
222 maxdata=<link linkend="func-ref-comedi-get-maxdata">comedi_get_maxdata</link>(file,subdevice,channel);
223 </programlisting>
224
225 Wow! How easy. And the range information?
226
227 <programlisting>
228 <link linkend="ref-type-comedi-range">comedi_range</link> * <link linkend="func-ref-comedi-get-range">comedi_get_range(<link linkend="ref-type-comedi-t">comedi_t</link>comedi_t</link> *it,unsigned int subdevice,unsigned int chan,unsigned int range);
229 </programlisting>
230
231 </para>
232
233 </section>
234
235
236 <section id="secondprogram">
237 <title>
238 Your second &comedi; program: simple acquisition
239 </title>
240
241
242 <para>
243 Actually, this is the first &comedi; program again, just
244 that we've added what we've learned.
245 </para>
246
247
248 <programlisting>
249 #include &lt;stdio.h&gt;      /* for printf() */
250 #include <![CDATA[<]]><link linkend="comedi-comedilib-h">comedilib.h</link><![CDATA[>]]>
251
252 int subdev = 0;         /* change this to your input subdevice */
253 int chan = 0;           /* change this to your channel */
254 int range = 0;          /* more on this later */
255 int aref = 0;           /* more on this later */
256
257 int main(int argc,char *argv[])
258 {
259   <link linkend="ref-type-comedi-t">comedi_t</link> *cf;
260   int chan=0;
261   <link linkend="ref-type-lsampl-t">lsampl_t</link> data;
262   int maxdata,rangetype;
263   double volts;
264
265   cf=<link linkend="func-ref-comedi-open">comedi_open</link>("/dev/comedi0");
266
267   maxdata=<link linkend="func-ref-comedi-get-maxdata">comedi_get_maxdata</link>(cf,subdev,chan);
268
269   rangetype=comedi_get_rangetype(cf,subdev,chan);
270
271   <link linkend="func-ref-comedi-data-read">comedi_data_read</link>(cf->fd,subdev,chan,range,aref,&amp;data);
272
273   volts=<link linkend="func-ref-comedi-to-phys">comedi_to_phys</link>(data,rangetype,range,maxdata);
274
275   printf("%d %g\n",data,volts);
276
277  return 0;
278 }
279 </programlisting>
280
281 </section>
282
283 <section id="thirdprogram">
284 <title>
285 Your third &comedi; program: instructions
286 </title>
287 <para>
288 This program (taken from the set of demonstration examples that come
289 with &comedi;) shows how to use a somewhat more flexible acquisition
290 function, the so-called <link linkend="instructions">instruction</link>.
291 <programlisting>
292 <![CDATA[
293 #include <stdio.h>
294 #include <]]><link linkend="comedi-comedilib-h">comedilib.h</link><![CDATA[>
295 #include <fcntl.h>
296 #include <unistd.h>
297 #include <errno.h>
298 #include <sys/time.h>
299 #include <unistd.h>
300 #include "examples.h"
301 ]]>
302
303 /*
304  * This example does 3 instructions in one system call.  It does
305  * a gettimeofday() call, then reads N_SAMPLES samples from an
306  * analog input, and the another gettimeofday() call.
307  */
308
309 #define MAX_SAMPLES 128
310
311 <link linkend="ref-type-comedi-t">comedi_t</link> *device;
312
313 int main(int argc, char *argv[])
314 {
315   int ret,i;
316   <link linkend="ref-type-comedi-insn">comedi_insn</link> insn[3];
317   <link linkend="ref-type-comedi-insnlist">comedi_insnlist</link> il;
318   struct timeval t1,t2;
319   <link linkend="ref-type-lsampl-t">lsampl_t</link> data[MAX_SAMPLES];
320
321   parse_options(argc,argv);
322
323
324   device=<link linkend="func-ref-comedi-open">comedi_open</link>(filename);
325   if(!device){
326     <link linkend="func-ref-comedi-perror">comedi_perror</link>(filename);
327     exit(0);
328   }
329
330   if(verbose){
331     printf("measuring device=%s subdevice=%d channel=%d range=%d analog reference=%d\n",
332       filename,subdevice,channel,range,aref);
333   }
334
335   /* Set up a the "instruction list", which is just a pointer
336    * to the array of instructions and the number of instructions.
337    */
338   il.n_insns=3;
339   il.insns=insn;
340
341   /* Instruction 0: perform a gettimeofday() */
342   insn[0].insn=<link linkend="insn-gtod">INSN_GTOD</link>;
343   insn[0].n=2;
344   insn[0].data=(void *)&amp;t1;
345
346   /* Instruction 1: do 10 analog input reads */
347   insn[1].insn=<link linkend="insn-read">INSN_READ</link>;
348   insn[1].n=n_scan;
349   insn[1].data=data;
350   insn[1].subdev=subdevice;
351   insn[1].chanspec=<link linkend="ref-macro-CR-PACK">CR_PACK</link>(channel,range,aref);
352
353   /* Instruction 2: perform a gettimeofday() */
354   insn[2].insn=<link linkend="insn-gtod">INSN_GTOD</link>;
355   insn[2].n=2;
356   insn[2].data=(void *)&amp;t2;
357
358   ret=<link linkend="func-ref-comedi-do-insnlist">comedi_do_insnlist</link>(device,&amp;il);
359   if(ret<![CDATA[<]]>0){
360     <link linkend="func-ref-comedi-perror">comedi_perror</link>(filename);
361     exit(0);
362   }
363
364   printf("initial time: %ld.%06ld\n",t1.tv_sec,t1.tv_usec);
365   for(i=0;i<![CDATA[<]]>n_scan;i++){
366     printf("%d\n",data[i]);
367   }
368   printf("final time: %ld.%06ld\n",t2.tv_sec,t2.tv_usec);
369
370   printf("difference (us): %ld\n",(t2.tv_sec-t1.tv_sec)*1000000+
371       (t2.tv_usec-t1.tv_usec));
372
373   return 0;
374 }
375 </programlisting>
376 </para>
377
378 </section>
379
380 <section id="fourthprogram">
381 <title>
382 Your fourth &comedi; program: commands
383 </title>
384
385 <para>
386 This example programs an analog output subdevice with &comedi;'s most
387 powerful acquisition function, the asynchronous
388 <link linkend="commandsstreaming">command</link>, to generate a waveform. 
389 </para>
390
391 <para>
392 The waveform in this example is a sine wave, but this can be easily
393 changed to make a generic function generator.
394 </para>
395
396 <para>
397 The function generation algorithm is the same as what is typically
398 used in digital function generators.  A 32-bit accumulator is
399 incremented by a phase factor, which is the amount (in radians) that
400 the generator advances each time step.  The accumulator is then
401 shifted right by 20 bits, to get a 12 bit offset into a lookup table.
402 The value in the lookup table at that offset is then put into a buffer
403 for output to the DAC.
404 </para>
405
406 <para>
407 Once you have
408 issued the command, &comedi; expects you to keep the buffer full of
409 data to output to the acquisition card.  This is done by 
410 <function>write()</function>.  Since there may be a delay between the 
411 <link linkend="func-ref-comedi-command">comedi_command()</link>
412 and a subsequent <function>write()</function>, you
413 should fill the buffer using <function>write()</function> before you call 
414 <link linkend="func-ref-comedi-command">comedi_command()</link>,
415 as is done here.
416 <programlisting>
417 <![CDATA[
418 #include <stdio.h>
419 #include <]]><link linkend="comedi-comedilib-h">comedilib.h</link><![CDATA[>
420 #include <fcntl.h>
421 #include <stdlib.h>
422 #include <unistd.h>
423 #include <errno.h>
424 #include <getopt.h>
425 #include <ctype.h>
426 #include <math.h>
427 #include "examples.h"
428 ]]>
429
430 double waveform_frequency = 10.0; /* frequency of the sine wave to output */
431 double amplitude          = 4000; /* peak-to-peak amplitude, in DAC units (i.e., 0-4095) */
432 double offset             = 2048; /* offset, in DAC units */
433
434 /* This is the size of chunks we deal with when creating and
435    outputting data.  This *could* be 1, but that would be
436    inefficient */
437 #define BUF_LEN 4096
438
439 int subdevice;
440 int external_trigger_number = 0;
441
442 sampl_t data[BUF_LEN];
443
444 void <link linkend="dds-output">dds_output</link>(sampl_t *buf,int n);
445 void <link linkend="dds-init">dds_init</link>(void);
446
447 /* This define determines which waveform to use. */
448 #define <anchor id="dds-init-function">dds_init_function <link linkend="dds-init-sine">dds_init_sine</link>
449
450 void <link linkend="dds-init-sine">dds_init_sine</link>(void);
451 void <link linkend="dds-init-pseudocycloid">dds_init_pseudocycloid</link>(void);
452 void <link linkend="dds-init-sawtooth">dds_init_sawtooth</link>(void);
453
454 int <anchor id="comedi-internal-trigger">comedi_internal_trigger(<link linkend="ref-type-comedi-t">comedi_t</link> *dev, unsigned int subd, unsigned int trignum)
455 {
456   <link linkend="ref-type-comedi-insn">comedi_insn</link> insn;
457   <link linkend="ref-type-lsampl-t">lsampl_t</link> data[1];
458
459   memset(<![CDATA[&insn]]>, 0, sizeof(<link linkend="ref-type-comedi-insn">comedi_insn</link>));
460   insn.insn = <link linkend="insn-inttrig">INSN_INTTRIG</link>;
461   insn.subdev = subd;
462   insn.data = data;
463   insn.n = 1;
464
465   data[0] = trignum;
466
467   return <link linkend="func-ref-comedi-do-insn">comedi_do_insn</link>(dev, <![CDATA[&insn]]>);
468 }
469
470
471 int main(int argc, char *argv[])
472 {
473   <link linkend="ref-type-comedi-cmd">comedi_cmd</link> cmd;
474   int err;
475   int n,m;
476   int total=0;
477   <link linkend="ref-type-comedi-t">comedi_t</link> *dev;
478   unsigned int chanlist[16];
479   unsigned int maxdata;
480   <link linkend="ref-type-comedi-range">comedi_range</link> *rng;
481   int ret;
482   <link linkend="ref-type-lsampl-t">lsampl_t</link> insn_data = 0; 
483
484   parse_options(argc,argv);
485
486   /* Force n_chan to be 1 */
487   n_chan = 2;
488
489   if(value){ waveform_frequency = value; }
490
491   dev = <link linkend="func-ref-comedi-open">comedi_open</link>(filename);
492   if(dev == NULL){
493     fprintf(stderr, "error opening %s\n", filename);
494     return -1;
495   }
496   subdevice = <link linkend="func-ref-comedi-find-subdevice-by-type">comedi_find_subdevice_by_type</link>(dev,COMEDI_SUBD_AO,0);
497
498   maxdata   = <link linkend="func-ref-comedi-get-maxdata">comedi_get_maxdata</link>(dev,subdevice,0);
499   rng       = <link linkend="func-ref-comedi-get-range">comedi_get_range</link>(dev,subdevice,0,0);
500   offset    = (double)<link linkend="func-ref-comedi-from-phys">comedi_from_phys</link>(0.0,rng,maxdata);
501   amplitude = (double)<link linkend="func-ref-comedi-from-phys">comedi_from_phys</link>(1.0,rng,maxdata) - offset;
502
503   memset(<![CDATA[&cmd]]>,0,sizeof(cmd));
504   /* fill in the <link linkend="ref-type-comedi-cmd">command data structure</link>: */
505   cmd.subdev         = subdevice;
506   cmd.flags          = 0;
507   cmd.start_src      = <link linkend="trig-int-start-src">TRIG_INT</link>;
508   cmd.start_arg      = 0;
509   cmd.scan_begin_src = <link linkend="trig-timer">TRIG_TIMER</link>;
510   cmd.scan_begin_arg = 1e9/freq;
511   cmd.convert_src    = <link linkend="trig-now">TRIG_NOW</link>;
512   cmd.convert_arg    = 0;
513   cmd.scan_end_src   = <link linkend="trig-count">TRIG_COUNT</link>;
514   cmd.scan_end_arg   = n_chan;
515   cmd.stop_src       = <link linkend="trig-none">TRIG_NONE</link>;
516   cmd.stop_arg       = 0;
517
518   cmd.chanlist       = chanlist;
519   cmd.chanlist_len   = n_chan;
520
521   chanlist[0] = <link linkend="ref-macro-CR-PACK">CR_PACK</link>(channel,range,aref);
522   chanlist[1] = <link linkend="ref-macro-CR-PACK">CR_PACK</link>(channel+1,range,aref);
523
524   <link linkend="dds-init">dds_init</link>();
525
526   <link linkend="dds-output">dds_output</link>(data,BUF_LEN);
527   <link linkend="dds-output">dds_output</link>(data,BUF_LEN);
528
529   dump_cmd(stdout,<![CDATA[&cmd]]>);
530
531   if ((err = <link linkend="func-ref-comedi-command">comedi_command</link>(dev, <![CDATA[&cmd]]>)) < 0) {
532     <link linkend="func-ref-comedi-perror">comedi_perror</link>("comedi_command");
533     exit(1);
534   }
535
536   m=write(comedi_fileno(dev),data,BUF_LEN*sizeof(sampl_t));
537   if(<![CDATA[m<0]]>){
538     perror("write");
539     exit(1);
540   }
541   printf("m=%d\n",m);
542   
543   ret = <link linkend="comedi-internal-trigger">comedi_internal_trigger</link>(dev, subdevice, 0);
544 <![CDATA[
545   if(ret<0){
546 ]]>
547     perror("comedi_internal_trigger\n");
548     exit(1);
549   }
550
551   while(1){
552     <link linkend="dds-output">dds_output</link>(data,BUF_LEN);
553     n=BUF_LEN*sizeof(sampl_t);
554     while(n>0){
555       m=write(comedi_fileno(dev),(void *)data+(BUF_LEN*sizeof(sampl_t)-n),n);
556 <![CDATA[
557       if(m<0){
558 ]]>
559         perror("write");
560         exit(0);
561       }
562       printf("m=%d\n",m);
563       n-=m;
564     }
565     total+=BUF_LEN;
566   }
567
568   return 0;
569 }
570
571 #define WAVEFORM_SHIFT 16
572 <![CDATA[
573 #define WAVEFORM_LEN (1<<WAVEFORM_SHIFT)
574 ]]>
575 #define WAVEFORM_MASK (WAVEFORM_LEN-1)
576
577 sampl_t waveform[WAVEFORM_LEN];
578
579 unsigned int acc;
580 unsigned int adder;
581
582 void <anchor id="dds-init">dds_init(void)
583 {
584 <![CDATA[
585   adder=waveform_frequency/freq*(1<<16)*(1<<WAVEFORM_SHIFT);
586 ]]>
587
588   <link linkend="dds-init-function">dds_init_function</link>();
589 }
590
591 void <anchor id="dds-output">dds_output(sampl_t *buf,int n)
592 {
593   int i;
594   sampl_t *p=buf;
595
596   <![CDATA[
597   for(i=0;i<n;i++){
598   *p=waveform[(acc>>16)&WAVEFORM_MASK];
599   ]]>
600   p++;
601   acc+=adder;
602   }
603 }
604
605
606 void <anchor id="dds-init-sine">dds_init_sine(void)
607 {
608   int i;
609
610   <![CDATA[
611   for(i=0;i<WAVEFORM_LEN;i++){
612   waveform[i]=rint(offset+0.5*amplitude*cos(i*2*M_PI/WAVEFORM_LEN));
613   ]]>
614   }
615 }
616
617 /* Yes, I know this is not the proper equation for a cycloid.  Fix it. */
618 void <anchor id="dds-init-pseudocycloid">dds_init_pseudocycloid(void)
619 {
620   int i;
621   double t;
622
623   <![CDATA[
624   for(i=0;i<WAVEFORM_LEN/2;i++){
625   t=2*((double)i)/WAVEFORM_LEN;
626   waveform[i]=rint(offset+amplitude*sqrt(1-4*t*t));
627   }
628   for(i=WAVEFORM_LEN/2;i<WAVEFORM_LEN;i++){
629   t=2*(1-((double)i)/WAVEFORM_LEN);
630   waveform[i]=rint(offset+amplitude*sqrt(1-t*t));
631   }
632   ]]>
633 }
634
635 void <anchor id="dds-init-sawtooth">dds_init_sawtooth(void)
636 {
637   int i;
638
639   <![CDATA[
640   for(i=0;i<WAVEFORM_LEN;i++){
641   waveform[i]=rint(offset+amplitude*((double)i)/WAVEFORM_LEN);
642   ]]>
643   }
644 }
645 </programlisting>
646 </para>
647
648 </section>
649
650 </section>
651