DAC Module

classic Classic list List threaded Threaded
15 messages Options
Mark Burton Mark Burton
Reply | Threaded
Open this post in threaded view
|

DAC Module


Hello,

I am about to start a project based on eLua running on an STM32F4
board and wish to implement simple playback of sound files via the DAC
peripheral (with DMA+Timer).

Has any work been done already on the DAC module or should I just start
from scratch?

Cheers,

Mark
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
scdr scdr
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module


I have done a little work on a DAC module for a different processor (the Atmel SAM3 - e.g. Arduino Due).
That module is not finished (just some sketches of functions), but what there is is in the SAM
branch in my github repository.  May as well make the module generic so works with as many processors as possible.

  https://github.com/ecdr/elua

The file platform/sam34/module_dac.c


> ----- Original Message -----
> From: Mark Burton
> Sent: 12/29/13 02:06 AM
> To: [hidden email]
> Subject: [eLua-dev] DAC Module
>
> Hello,
>
> I am about to start a project based on eLua running on an STM32F4
> board and wish to implement simple playback of sound files via the DAC
> peripheral (with DMA+Timer).
>
> Has any work been done already on the DAC module or should I just start
> from scratch?
>
> Cheers,
>
> Mark
> _______________________________________________
> eLua-dev mailing list
> [hidden email]
> https://lists.berlios.de/mailman/listinfo/elua-dev

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Mark Burton Mark Burton
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module


Hi,

Thanks for that, I shall take a look.

Cheers,

Mark

--------

On Tue, 31 Dec 2013 01:27:34 -0500
"FP AB" <[hidden email]> wrote:

>
> I have done a little work on a DAC module for a different processor
> (the Atmel SAM3 - e.g. Arduino Due). That module is not finished
> (just some sketches of functions), but what there is is in the SAM
> branch in my github repository.  May as well make the module generic
> so works with as many processors as possible.
>
>   https://github.com/ecdr/elua
>
> The file platform/sam34/module_dac.c
>
>
> > ----- Original Message -----
> > From: Mark Burton
> > Sent: 12/29/13 02:06 AM
> > To: [hidden email]
> > Subject: [eLua-dev] DAC Module
> >
> > Hello,
> >
> > I am about to start a project based on eLua running on an STM32F4
> > board and wish to implement simple playback of sound files via the
> > DAC peripheral (with DMA+Timer).
> >
> > Has any work been done already on the DAC module or should I just
> > start from scratch?
> >
> > Cheers,
> >
> > Mark
> > _______________________________________________
> > eLua-dev mailing list
> > [hidden email]
> > https://lists.berlios.de/mailman/listinfo/elua-dev
>
> _______________________________________________
> eLua-dev mailing list
> [hidden email]
> https://lists.berlios.de/mailman/listinfo/elua-dev
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Martin Guy Martin Guy
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module

In reply to this post by scdr
On 31/12/2013, FP AB <[hidden email]> wrote:

>> From: Mark Burton
>> Subject: [eLua-dev] DAC Module
>>
>> I am about to start a project based on eLua running on an STM32F4
>> board and wish to implement simple playback of sound files via the DAC
>> peripheral (with DMA+Timer).
>>
>> Has any work been done already on the DAC module or should I just start
>> from scratch?
>
> I have done a little work on a DAC module for a different processor (the
> Atmel SAM3 - e.g. Arduino Due).
> That module is not finished (just some sketches of functions), but what
> there is is in the SAM
> branch in my github repository.  May as well make the module generic so
> works with as many processors as possible.
>
>   https://github.com/ecdr/elua
>
> The file platform/sam34/module_dac.c

Hi
   A critical aspect, from eLua's point of view, is the design of the
Lua interface to the module.
   Your aim of doing audio playback will probably lead you to design a
good interface for that, but remember that other people will want to
control devices by providing a certain voltage on a certain pin, so do
make sure it provides for that usage case also.
   You could also consider the current ADC interface as some kind of
model, just to reduce users' confusion, with its use of eLua buffers
in the model it has for dealing with more than one sample in a single
call. And how to set sampling rates. And for DACs with different
numbers of bits, which may be settable in software or maybe not... and
so on. Interfaces designed aimed at a single hardware and a single
usage case risk being ideal for that case, but awkward for anything
else (as seems to have happened with some of the other modules).
   If you care to post the proposed eLua-level interface here, maybe
we can make suggestions based on other hardware differences.
   On the topic of eLua buffers, their size currently has to be power
of two, which is less than useful for ADC and DAC as you can't say
"process 1/100th of a second at 44100Hz". That limitation needs to be
lifted to be of arbitrary length... It's not ADC's fault, but needs
fixing in the buffer-allocation part of eLua deep down, then
propagating to everything that uses them.


    M
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Martin Guy Martin Guy
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module

> On 31/12/2013, FP AB <[hidden email]> wrote:
>>   https://github.com/ecdr/elua
>> The file platform/sam34/module_dac.c

sorry, where?

> go src/platform
OK
> take sam34
I see no sam34 here.

   M
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Mark Burton Mark Burton
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module


Hi Martin,

Thanks for the advice, I intend to follow it. At this stage I am still
deciding what I need and doing some experiments.

> sorry, where?

It's in a branch called SAM.

Cheers,

Mark

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Mark Burton Mark Burton
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module

In reply to this post by Martin Guy

Hi,

I have been working on the DAC module. It's a generic module with a
platform implementation for the stm32f4. The functions that output a
sequence of samples are clocked using timer match interrupts (no DMA
required).

------

The Lua interface currently looks like this:

dac.init( id, [bits_per_sample, [options]] )

initialises a single DAC channel

  id = DAC channel id from 0 upwards (stm32f4 allows 0 or 1)
  bits_per_sample = 8, ... (stm32f4 allows 8, 12 and 16, only 12 bits
  actually used)
  options = platform specific option flags (currently unused)

dac.putsample( id, [val] )

outputs a sample to a DAC channel

  id = DAC channel id
  val = integer value to load into DAC (default 0)

dac.putsamples( id, samples, rate, [timer_id, [bytes_per_sample, [bias,
[offset, [num_samples]]]]] )

outputs a sequence of samples to a DAC channel

  id = DAC channel id
  samples = string containing sample data
  rate = DAC update rate (samples per second)
  timer_id = id of timer to use (defaults to first timer that can be
  used)
  bytes_per_sample = number of bytes per sample (default 1)
  bias = value added to each sample before writing to DAC (default 0)
  offset = skip this many bytes at start of string (default 0)
  num_samples = number of samples to output (defaults to all samples in
  string)

dac.playwavfile( id, wavfilename, [timer_id, [sample_buf_size]] )

plays a wav file (8 or 16 bit uncompressed PCM only) - currently only
one channel is output (mono) but it would not be difficult to output 2
channels for stereo.

  id = DAC channel id
  wavfilename = name of file containing wav data
  timer_id = id of timer to use (defaults to first timer that can be
  used)
  sample_buf_size = size of circular buffer used to hold samples
  (default 32)

-------

The platform API is simple: it basically consists of a function to init
a DAC channel, a function to output a single sample to one or more DAC
channels and a function to check whether a given timer OK to use as a
source of interrupts.

int platform_dac_init(unsigned id, unsigned bits_per_sample, unsigned
options);
void platform_dac_put_sample(unsigned channel_mask, u16 *data);
int  platform_dac_check_timer_id(unsigned id, unsigned timer_id);

-------


If anyone wants to try the code, it's in the mb-stm32f4-dac branch
of https://github.com/smartavionics/elua

All feedback is welcome. It's all very new so there's bound to be some
bugs and design problems.

Cheers,

Mark



_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Martin Guy Martin Guy
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module

On 02/01/2014, Mark Burton <[hidden email]> wrote:
> I have been working on the DAC module. It's a generic module with a
> platform implementation for the stm32f4. The functions that output a
> sequence of samples are clocked using timer match interrupts (no DMA
> required).

THanks! :) Here's my 2c-worth (all negative, of course! :)

> The Lua interface currently looks like this:
>
> dac.init( id, [bits_per_sample, [options]] )
> initialises a single DAC channel
>   id = DAC channel id from 0 upwards (stm32f4 allows 0 or 1)
>   bits_per_sample = 8, ... (stm32f4 allows 8, 12 and 16, only 12 bits
>   actually used)
>   options = platform specific option flags (currently unused)

All the other modules have functions called *.setup(), not *.init(),
so I guess this should be renamed.

I'm not sure that a place-holder for non-existent options is useful.
If, one day, extra options are needed, the implementer can add an
extra parameter with a default value of nil and that will be
backward-compatible with existing code.

Most other eLua functions have a way to request what the current
bits-per-sample are, and the usual eLua way is for the user to say
what they would like, then eLua sets the nearest possible value to the
number of bits you asked for, and return that as the value of the
setup function, the dac.setbitspersample() function or whatever.

That suggests:

actual_bits = dac.setup( id, [bits_per_sample] )
initialises a single DAC channel
    id = DAC channel id from 0 upwards (stm32f4 allows 0 or 1)
   bits_per_sample = 8, 12 and 16, only 12 bits
Returns:
   actual_bits = the actual number of bits per sample set to the DAC

I was wondering whether any hardware can have a different number of
bits per channel.
With other eLua interfaces, like pwm.setclock(id, freq) it lets you
setup the channels with different values if you like, then there is a
note in the platform-specific documentation to tell you that, for
example, on AVR32 all channels run from the same clock, so changing
one changes them all.

> dac.putsample( id, [val] )
>
> outputs a sample to a DAC channel

>   id = DAC channel id
>   val = integer value to load into DAC (default 0)

I'm not sure that having a default value is useful. I'd just make it a
compulsory 2nd argument. That would help people who forget to specify
the channel in their code, saying dac.putsample(sample) and this way
the error message would be that they have forgotten a parameter
instead of either outputting 0 on a random channel or saying "invalid
channel".

> dac.putsamples( id, samples, rate, [timer_id, [bytes_per_sample, [bias,
> [offset, [num_samples]]]]] )
>
> outputs a sequence of samples to a DAC channel
>
>   id = DAC channel id
>   rate = DAC update rate (samples per second)

Is this likely to change from call to call? If not, maybe it would be
more apt to put it in dac.setup()

>   samples = string containing sample data
>   bytes_per_sample = number of bytes per sample (default 1)

I'm not sure about the string data type. There seem to be two idioms
in use in eLua already for variable sizes and number of parameters.
One is switching on the data type, which can be an integer, a string
or an array, the other is to put the samples as the last parameter and
allow any number of them. See i2c.write, which does both (!)

Going back to the string type, this means that if I have an array of
16-bit samples as numbers, I would need a bizarre code to chop each
sample into two bytes and compose a weird string.  Again, I think
using an array of integers seems less awkward.

Also, if we have bits-per-sample in the setup function, doesn't this
determine bytes-per-sample?

>   bias = value added to each sample before writing to DAC (default 0)
>   offset = skip this many bytes at start of string (default 0)
>   num_samples = number of samples to output (defaults to all samples in
>   string)

Are these necessary? There is already a Lua idiom to select a
substring (or sub-array) from an existing sample buffer, without
replicating that functionality here.
That said, if we look at the existing adc module, that has
samples = adc.getsamples( id, count )
whish returns a table of integers, and
adc.insertsamples( id, table, idx, count )
to write samples into a sub-array of an existing table.  Maybe you
imitate this metaphor more closely in dac.*()?

> dac.playwavfile( id, wavfilename, [timer_id, [sample_buf_size]] )
>
> plays a wav file (8 or 16 bit uncompressed PCM only) - currently only
> one channel is output (mono) but it would not be difficult to output 2
> channels for stereo.
>
>   id = DAC channel id
>   wavfilename = name of file containing wav data
>   timer_id = id of timer to use (defaults to first timer that can be
>   used)
>   sample_buf_size = size of circular buffer used to hold samples
>   (default 32)

Most eLua functions are more low-level than this. See i2c, for which I
suggested a higher-level interface to send an entire packet, but that
was rejected in favour of the existing bit-by-bit interface.
Also, opening files, and decoding wav-file-format has nothing to do
with a DAC module, but is a specific need for the audio or sound
effects that you need. For that, you could have a separete
wav-file-reading (and wav-file-writing!) module, returning (and
taking) arrays of integers - that would interface nicely with the ADC
module which would be able to save its results in files, and to the
DAC module which could take those arrays and play them, How does that
sound?

I assume that the critical issue for sound output is being able to
play audio in real time, for which having any Lua code running between
samples will make it too slow. Once you've read your WAV file into a
Lua table, you should be able to call dac.putsamples(id, table [,idx
[,count]]) to achieve the effects you need, with
samples=wav.read(file) as a support function.

Would that work for what you need to do?

> The platform API is simple: it basically consists of a function to init
> a DAC channel, a function to output a single sample to one or more DAC
> channels and a function to check whether a given timer OK to use as a
> source of interrupts.
>
> int platform_dac_init(unsigned id, unsigned bits_per_sample, unsigned
> options);
> void platform_dac_put_sample(unsigned channel_mask, u16 *data);
> int  platform_dac_check_timer_id(unsigned id, unsigned timer_id);

OK. This is nice and clean.

Thanks, and sorry for turning the world upside-down for you!

    M
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Mark Burton Mark Burton
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module


Hi Martin,

Thanks for the excellent feedback, I will digest what you say and make
changes.

Cheers,

Mark
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Martin Guy Martin Guy
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module

In reply to this post by Martin Guy
Oops, there's more, on stereo (or N-channel) output:

On 02/01/2014, Mark Burton <[hidden email]> wrote:
> dac.playwavfile( id, wavfilename, [timer_id, [sample_buf_size]] )
>
> plays a wav file (8 or 16 bit uncompressed PCM only) - currently only
> one channel is output (mono) but it would not be difficult to output 2
> channels for stereo.
:
> void platform_dac_put_sample(unsigned channel_mask, u16 *data);
> int  platform_dac_check_timer_id(unsigned id, unsigned timer_id);

Am I right in thinking that channel_mask is a bit mask for channels 0
and 1 (at present) so you can output stereo by setting both bits to 1,
in which case it will take pairs of samples for channels 0 and 1 from
the array, to beoutput simultaneously? That's a cool solution and
similar to the way eLua handles multiple PIO pins on a single port.

Can you suggest a way to extend this idea to the Lua level?
I see that the adc module already has an idiom for this:

adc.sample( id, count )
Initiate conversion and buffering of samples on an ADC channel.
Arguments:
    id - ADC channel ID. Optionally, this may be a table containing a
list of channel IDs (i.e.: {0, 2, 3}), allowing synchronization of
acquisition.

then when it has finished receiveing the samples, you call
adc.getsamples() for each channel individually, receiving a table of
values for each channel separately.

I guess the simplest way for dac.*() is just to pass it N arrays as
the last parameters, with an initial mask-table saying which DAC
channels the arrays are to be output on.
If that's too horrible for single-channel output, you could provide
dac.output(id, table, idx, count)  which calls
dac.outputs({id-list}, idx, count, table, table, ...)
or
dac.outputs({id-list}, {tables}, idx, count)

If someone then wanted to implement a DMA version on different
hardware, they would just need to add a dac.isdone() function, the
same as adc.isdone().

If you like the wav.read() idea, I guess that should return the number
of bits per sample (or the maxval as 255 or 65536 - I see that ADC
uses maxval, not nbits, and adc.setclock(), not adc.setup()), the
number of channels and the intended sampling rate, then a table of two
arrays for a stereo file containing the samples themselves.  That only
makes it slightly nasty if you are dealing with a single channel, as
you would get an array containing an array of samples instead of just
a mono array of samples.

Ah, choices, choices...

   M
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Mark Burton Mark Burton
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module


Hi,

I have reworked the DAC module and the Lua functions. The biggest change is that dac.putsamples() can now
be passed the sample data as either an array of integers, a string of bytes or a function that should return
a string of bytes every time it is called until all the data has been read when it should return an empty
string or nil.

-------------

setup( dac_id, [bits_per_sample] )

Initialise a single DAC (only required if you are going to call dac.putsample())

 dac_id = id of DAC to initialise
 bits_per_sample = 8, 16, etc. (default is 8)

putsample( dac_id, val )

Output a single sample to a DAC

 dac_id = id of DAC to send output to
 val = value to output

num_samples_output, num_underflows =
  putsamples( dac_id, data_source, rate, [bits_per_sample, [channels, [bias, [timer_id]]]] )

Output a stream of samples to one or more DACs at a specific rate

  dac_id = id of DAC to send output to
  data_source = the source of the data to send to the DAC - can be either an array of integers,
    a string of bytes or a function that returns a string of bytes - the function will be called
    repeatedly until it returns nil or an empty string
  rate = the sample rate in Hz
  bits_per_sample 8, 16, etc. (default is 8)
  channels = the number of channels (consecutive DACs) to output to (default = 1)
  bias = a value that is added to each output value before it is written to the DAC (default = 0)
  timer_id = the id of the timer used to generate the clock (defaults to 1st acceptable timer)

  returns 2 values, the number of samples output and the number of times buffer underflow occurred.

Note that dac.putsamples() calls dac.setup() so you don't need to explicitly call dac.setup() first.

---------------

I have removed the wav playing function and written it in Lua, here's the bit of it that calls dac.putsamples():

  local num_samp, num_uf = dac.putsamples(dac_id, function () return f:read(256) end, sample_rate, bytes_per_sample * 8, num_channels, 0x8000)
  f:close()
  print(string.format("%d samples output, %d underflows", num_samp, num_uf));

---------------

As before, the code can be found at https://github.com/smartavionics/elua

Cheers,

Mark


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
scdr scdr
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module

In reply to this post by Mark Burton

Some initial thoughts on the DAC interfaces.  (Haven't fully digested, so will be more later).

Suggestions:

General thoughts:

Might be worth looking at i2s APIs to see what similar functions might be desirable.  (Not saying need to do i2s now, just
makes sense to consider what an i2s eLua module might look like, since it probably has somewhat similar function to DAC.)

Might also look at some other libraries (e.g. Arduino audio library) for ideas on approach/functions that my be desirable.
(if haven't already done).

Interface described seems to only cover synchronous case, should extend to handle asynchronous (i.e. specify the DAC data
and then let the eLua program keep running, issuing control instructions as needed).
  So add procedures to start, stop, loop, clear buffer, set buffer length, etc.

Interrupt handling, buffering, etc. should probably be moved to the platform code, rather than having in the
eLua module.  The platform interface is at such a low level that could be hard to take advantage of special hardware on various platforms.
(Not clear how could use DMA, DAC interrupts, built in buffers, etc.)

Consider separating the scaling (offset, range adjustment, etc.) from the DAC code?
General purpose scaling lib./function, which can apply to data before presenting to the DAC.  (I think Arduino library uses something similar.)
Could be useful elsewhere, e.g. in graphics, ADC, etc.
Do some DACs provide offset, etc. in hardware?


--
More specific:

Simplify this
> num_samples_output, num_underflows = putsamples( dac_id, data_source, rate, [bits_per_sample, [channels, [bias, [timer_id]]]] )

to something like
num_samples_output = putsamples( dac_id, data_source )

Where data source can be any of the items you gave, or can be several numbers, or mix of the above.

so could say dac.putsamples( 0, 5, 9, 15, 25, 15, 9, 5)
To send the sequence 5, 9, 15, etc. to DAC 0.

Provide separate interface(s) to specify the other parameters (bias, timer_id, bits_per_sample, and rate are all likely to stay constant in most applications).  (Also interface to get/reset number of buffer underflows.)
(If need to have interface that specifies everything, can add a function that combines all the setup plus the putsamples).

Rather than specifying frequency (rate), might make more sense to give period (1,000,000 / rate).
(Depends what various hardware needs, but no sense in requiring an expensive divide in the code if we can easily avoid it.)

Timer ID - look at how ADC handles timers.  Make clear how to handle systems that do not need a timer for the DAC
(i.e. what should relevant functions return, etc.)  Unlikely to need to change this for each call.

Provide interface to set size of DAC buffer (or at least make it a compile time configuration option).
(e.g. the 16 element buffer).

>  bits_per_sample 8, 16, etc. (default is 8)
Instead of defining this as 8, leave the default to be defined by the platform.  If the DAC on a particular platform only does
12 bits per sample (e.g.), then it doesn't make sense to give it a default which can not work and always has to be changed.
(The module could provide a built in constant, something like DEFAULT_BITS_PER_SAMPLE, which informs the code what the default is.)

Caveat on this - if the intention is that the interface can provide data with a different number of bits than the platform's DAC
and that such data will be scaled to fit (e.g. if you give 8 bits but the DAC is 12 bits, then your data will be left shifted by 4 before
it is output, conversely if you provide 12 bit data but the DAC is only 8 bits, then it will shift right by 4 to throw away the excess
detail), then it should be clarified that such Procrustean scaling takes place automatically.
(In that case there probably should not be a default value, and the user should always specify the range of their data.)

Bitarray seems like another natural possability for storing/presenting the data to the DAC module.  Might consider how to incorporate that cleanly.

Taking a function to generate the data to be written is nice.  Could be nice to extend it a bit, accept a function that returns numbers (or a sequence of numbers).  (e.g. if I want to generate a sine wave, how about if I feed it a sin function (with appropriate scaling, offset, and some kind of input generator, etc.)).


Using a bitmask in platform_dac_put_sample looks like it might be a bottleneck?  May be hard to quickly decode a bitmask,
and at the moment that procedure is called for every sample going out to the DAC (so the decode is done a lot).
May not be much of an issue if only have 2 channels, but some audio applications use 6 channels, etc.

Is the added complexity really worth it (maybe offer simple version where specify 1 channel id, and complex version to use
when needed.)
One option is to have samples be tagged with which channel (e.g. in padding bits) - SAM3 does this in some cases.
Does STM do anything similar?
Have you considered varargs?


Once I look at adapting this to work with the SAM3 I may have other thoughts (what works well/doesn't work well with that
platform).



> ----- Original Message -----
> From: Mark Burton
> Sent: 01/03/14 06:43 AM
> To: [hidden email]
> Subject: Re: [eLua-dev] DAC Module
>
> Hi,
>
> I have reworked the DAC module and the Lua functions. The biggest change is that dac.putsamples() can now
> be passed the sample data as either an array of integers, a string of bytes or a function that should return
> a string of bytes every time it is called until all the data has been read when it should return an empty
> string or nil.
>
> -------------
>
> setup( dac_id, [bits_per_sample] )
>
> Initialise a single DAC (only required if you are going to call dac.putsample())
>
>  dac_id = id of DAC to initialise
>  bits_per_sample = 8, 16, etc. (default is 8)
>
> putsample( dac_id, val )
>
> Output a single sample to a DAC
>
>  dac_id = id of DAC to send output to
>  val = value to output
>
> num_samples_output, num_underflows =
>  putsamples( dac_id, data_source, rate, [bits_per_sample, [channels, [bias, [timer_id]]]] )
>
> Output a stream of samples to one or more DACs at a specific rate
>
>  dac_id = id of DAC to send output to
>  data_source = the source of the data to send to the DAC - can be either an array of integers,
>  a string of bytes or a function that returns a string of bytes - the function will be called
>  repeatedly until it returns nil or an empty string
>  rate = the sample rate in Hz
>  bits_per_sample 8, 16, etc. (default is 8)
>  channels = the number of channels (consecutive DACs) to output to (default = 1)
>  bias = a value that is added to each output value before it is written to the DAC (default = 0)
>  timer_id = the id of the timer used to generate the clock (defaults to 1st acceptable timer)
>
>  returns 2 values, the number of samples output and the number of times buffer underflow occurred.
>
> Note that dac.putsamples() calls dac.setup() so you don't need to explicitly call dac.setup() first.
>
> ---------------
>
> I have removed the wav playing function and written it in Lua, here's the bit of it that calls dac.putsamples():
>
>  local num_samp, num_uf = dac.putsamples(dac_id, function () return f:read(256) end, sample_rate, bytes_per_sample * 8, num_channels, 0x8000)
>  f:close()
>  print(string.format("%d samples output, %d underflows", num_samp, num_uf));
>
> ---------------
>
> As before, the code can be found at https://github.com/smartavionics/elua
>
> Cheers,
>
> Mark
>
>
> _______________________________________________
> eLua-dev mailing list
> [hidden email]
> https://lists.berlios.de/mailman/listinfo/elua-dev

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
scdr scdr
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module

In reply to this post by Mark Burton

Few more bits of feedback -
Currently there is no way to find out what actual bits per sample is set.
Making platform_dac_setup and eLua dac.setup return bits per sample would fix that.

Should rename platform_dac_init to platform_dac_setup - just to be consistent with other modules.
(Init is usually called by platform init, setup is generally called by setup routine.)

I made some revisions to your code, fixing some of this and a few of the issues I mentioned below
(e.g. making default bits per sample configurable).  Also fixed one small bug.
It is in the atm32dac branch on my github repository. https://github.com/ecdr/elua  

To be parallel with ADC should probably add these functions to the platform interface.

u32  platform_dac_set_clock( unsigned id, unsigned frequency);  //I still think that period might make more sense, but at least to be consistent.
int  platform_dac_set_timer( unsigned id, u32 timer_id);



> ----- Original Message -----
> From: [hidden email]
> Sent: 01/06/14 10:41 PM
> To: eLua Users and Development List (www.eluaproject.net)
> Subject: Re: [eLua-dev] DAC Module
>
> Some initial thoughts on the DAC interfaces.  (Haven't fully digested, so will be more later).
>
> Suggestions:
>
> General thoughts:
>
> Might be worth looking at i2s APIs to see what similar functions might be desirable.  (Not saying need to do i2s now, just
> makes sense to consider what an i2s eLua module might look like, since it probably has somewhat similar function to DAC.)
>
> Might also look at some other libraries (e.g. Arduino audio library) for ideas on approach/functions that my be desirable.
> (if haven't already done).
>
> Interface described seems to only cover synchronous case, should extend to handle asynchronous (i.e. specify the DAC data
> and then let the eLua program keep running, issuing control instructions as needed).
>   So add procedures to start, stop, loop, clear buffer, set buffer length, etc.
>
> Interrupt handling, buffering, etc. should probably be moved to the platform code, rather than having in the
> eLua module.  The platform interface is at such a low level that could be hard to take advantage of special hardware on various platforms.
> (Not clear how could use DMA, DAC interrupts, built in buffers, etc.)
>
> Consider separating the scaling (offset, range adjustment, etc.) from the DAC code?
> General purpose scaling lib./function, which can apply to data before presenting to the DAC.  (I think Arduino library uses something similar.)
> Could be useful elsewhere, e.g. in graphics, ADC, etc.
> Do some DACs provide offset, etc. in hardware?
>
>
> --
> More specific:
>
> Simplify this
> > num_samples_output, num_underflows = putsamples( dac_id, data_source, rate, [bits_per_sample, [channels, [bias, [timer_id]]]] )
>
> to something like
> num_samples_output = putsamples( dac_id, data_source )
>
> Where data source can be any of the items you gave, or can be several numbers, or mix of the above.
>
> so could say dac.putsamples( 0, 5, 9, 15, 25, 15, 9, 5)
> To send the sequence 5, 9, 15, etc. to DAC 0.
>
> Provide separate interface(s) to specify the other parameters (bias, timer_id, bits_per_sample, and rate are all likely to stay constant in most applications).  (Also interface to get/reset number of buffer underflows.)
> (If need to have interface that specifies everything, can add a function that combines all the setup plus the putsamples).
>
> Rather than specifying frequency (rate), might make more sense to give period (1,000,000 / rate).
> (Depends what various hardware needs, but no sense in requiring an expensive divide in the code if we can easily avoid it.)
>
> Timer ID - look at how ADC handles timers.  Make clear how to handle systems that do not need a timer for the DAC
> (i.e. what should relevant functions return, etc.)  Unlikely to need to change this for each call.
>
> Provide interface to set size of DAC buffer (or at least make it a compile time configuration option).
> (e.g. the 16 element buffer).
>
> >  bits_per_sample 8, 16, etc. (default is 8)
> Instead of defining this as 8, leave the default to be defined by the platform.  If the DAC on a particular platform only does
> 12 bits per sample (e.g.), then it doesn't make sense to give it a default which can not work and always has to be changed.
> (The module could provide a built in constant, something like DEFAULT_BITS_PER_SAMPLE, which informs the code what the default is.)
>
> Caveat on this - if the intention is that the interface can provide data with a different number of bits than the platform's DAC
> and that such data will be scaled to fit (e.g. if you give 8 bits but the DAC is 12 bits, then your data will be left shifted by 4 before
> it is output, conversely if you provide 12 bit data but the DAC is only 8 bits, then it will shift right by 4 to throw away the excess
> detail), then it should be clarified that such Procrustean scaling takes place automatically.
> (In that case there probably should not be a default value, and the user should always specify the range of their data.)
>
> Bitarray seems like another natural possability for storing/presenting the data to the DAC module.  Might consider how to incorporate that cleanly.
>
> Taking a function to generate the data to be written is nice.  Could be nice to extend it a bit, accept a function that returns numbers (or a sequence of numbers).  (e.g. if I want to generate a sine wave, how about if I feed it a sin function (with appropriate scaling, offset, and some kind of input generator, etc.)).
>
>
> Using a bitmask in platform_dac_put_sample looks like it might be a bottleneck?  May be hard to quickly decode a bitmask,
> and at the moment that procedure is called for every sample going out to the DAC (so the decode is done a lot).
> May not be much of an issue if only have 2 channels, but some audio applications use 6 channels, etc.
>
> Is the added complexity really worth it (maybe offer simple version where specify 1 channel id, and complex version to use
> when needed.)
> One option is to have samples be tagged with which channel (e.g. in padding bits) - SAM3 does this in some cases.
> Does STM do anything similar?
> Have you considered varargs?
>
>
> Once I look at adapting this to work with the SAM3 I may have other thoughts (what works well/doesn't work well with that
> platform).
>
>
>
> > ----- Original Message -----
> > From: Mark Burton
> > Sent: 01/03/14 06:43 AM
> > To: [hidden email]
> > Subject: Re: [eLua-dev] DAC Module
> >
> > Hi,
> >
> > I have reworked the DAC module and the Lua functions. The biggest change is that dac.putsamples() can now
> > be passed the sample data as either an array of integers, a string of bytes or a function that should return
> > a string of bytes every time it is called until all the data has been read when it should return an empty
> > string or nil.
> >
> > -------------
> >
> > setup( dac_id, [bits_per_sample] )
> >
> > Initialise a single DAC (only required if you are going to call dac.putsample())
> >
> >  dac_id = id of DAC to initialise
> >  bits_per_sample = 8, 16, etc. (default is 8)
> >
> > putsample( dac_id, val )
> >
> > Output a single sample to a DAC
> >
> >  dac_id = id of DAC to send output to
> >  val = value to output
> >
> > num_samples_output, num_underflows =
> >  putsamples( dac_id, data_source, rate, [bits_per_sample, [channels, [bias, [timer_id]]]] )
> >
> > Output a stream of samples to one or more DACs at a specific rate
> >
> >  dac_id = id of DAC to send output to
> >  data_source = the source of the data to send to the DAC - can be either an array of integers,
> >  a string of bytes or a function that returns a string of bytes - the function will be called
> >  repeatedly until it returns nil or an empty string
> >  rate = the sample rate in Hz
> >  bits_per_sample 8, 16, etc. (default is 8)
> >  channels = the number of channels (consecutive DACs) to output to (default = 1)
> >  bias = a value that is added to each output value before it is written to the DAC (default = 0)
> >  timer_id = the id of the timer used to generate the clock (defaults to 1st acceptable timer)
> >
> >  returns 2 values, the number of samples output and the number of times buffer underflow occurred.
> >
> > Note that dac.putsamples() calls dac.setup() so you don't need to explicitly call dac.setup() first.
> >
> > ---------------
> >
> > I have removed the wav playing function and written it in Lua, here's the bit of it that calls dac.putsamples():
> >
> >  local num_samp, num_uf = dac.putsamples(dac_id, function () return f:read(256) end, sample_rate, bytes_per_sample * 8, num_channels, 0x8000)
> >  f:close()
> >  print(string.format("%d samples output, %d underflows", num_samp, num_uf));
> >
> > ---------------
> >
> > As before, the code can be found at https://github.com/smartavionics/elua
> >
> > Cheers,
> >
> > Mark
> >
> >
> > _______________________________________________
> > eLua-dev mailing list
> > [hidden email]
> > https://lists.berlios.de/mailman/listinfo/elua-dev
>
> _______________________________________________
> eLua-dev mailing list
> [hidden email]
> https://lists.berlios.de/mailman/listinfo/elua-dev

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Mark Burton Mark Burton
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module


Hello scdr,

Thanks for taking the time to look at the DAC code. You have obviously
thought about this far more than I have. My focus is very narrow, I
just need the ability to play WAV files by reading data from the SD
card and clocking it into the DAC.

Cheers,

Mark

----------

On Thu, 09 Jan 2014 00:02:48 -0500
[hidden email] wrote:

>
> Few more bits of feedback -
> Currently there is no way to find out what actual bits per sample is
> set. Making platform_dac_setup and eLua dac.setup return bits per
> sample would fix that.
>
> Should rename platform_dac_init to platform_dac_setup - just to be
> consistent with other modules. (Init is usually called by platform
> init, setup is generally called by setup routine.)
>
> I made some revisions to your code, fixing some of this and a few of
> the issues I mentioned below (e.g. making default bits per sample
> configurable).  Also fixed one small bug. It is in the atm32dac
> branch on my github repository. https://github.com/ecdr/elua  
>
> To be parallel with ADC should probably add these functions to the
> platform interface.
>
> u32  platform_dac_set_clock( unsigned id, unsigned frequency);  //I
> still think that period might make more sense, but at least to be
> consistent. int  platform_dac_set_timer( unsigned id, u32 timer_id);
>
>
>
> > ----- Original Message -----
> > From: [hidden email]
> > Sent: 01/06/14 10:41 PM
> > To: eLua Users and Development List (www.eluaproject.net)
> > Subject: Re: [eLua-dev] DAC Module
> >
> > Some initial thoughts on the DAC interfaces.  (Haven't fully
> > digested, so will be more later).
> >
> > Suggestions:
> >
> > General thoughts:
> >
> > Might be worth looking at i2s APIs to see what similar functions
> > might be desirable.  (Not saying need to do i2s now, just makes
> > sense to consider what an i2s eLua module might look like, since it
> > probably has somewhat similar function to DAC.)
> >
> > Might also look at some other libraries (e.g. Arduino audio
> > library) for ideas on approach/functions that my be desirable. (if
> > haven't already done).
> >
> > Interface described seems to only cover synchronous case, should
> > extend to handle asynchronous (i.e. specify the DAC data and then
> > let the eLua program keep running, issuing control instructions as
> > needed). So add procedures to start, stop, loop, clear buffer, set
> > buffer length, etc.
> >
> > Interrupt handling, buffering, etc. should probably be moved to the
> > platform code, rather than having in the eLua module.  The platform
> > interface is at such a low level that could be hard to take
> > advantage of special hardware on various platforms. (Not clear how
> > could use DMA, DAC interrupts, built in buffers, etc.)
> >
> > Consider separating the scaling (offset, range adjustment, etc.)
> > from the DAC code? General purpose scaling lib./function, which can
> > apply to data before presenting to the DAC.  (I think Arduino
> > library uses something similar.) Could be useful elsewhere, e.g. in
> > graphics, ADC, etc. Do some DACs provide offset, etc. in hardware?
> >
> >
> > --
> > More specific:
> >
> > Simplify this
> > > num_samples_output, num_underflows = putsamples( dac_id,
> > > data_source, rate, [bits_per_sample, [channels, [bias,
> > > [timer_id]]]] )
> >
> > to something like
> > num_samples_output = putsamples( dac_id, data_source )
> >
> > Where data source can be any of the items you gave, or can be
> > several numbers, or mix of the above.
> >
> > so could say dac.putsamples( 0, 5, 9, 15, 25, 15, 9, 5)
> > To send the sequence 5, 9, 15, etc. to DAC 0.
> >
> > Provide separate interface(s) to specify the other parameters
> > (bias, timer_id, bits_per_sample, and rate are all likely to stay
> > constant in most applications).  (Also interface to get/reset
> > number of buffer underflows.) (If need to have interface that
> > specifies everything, can add a function that combines all the
> > setup plus the putsamples).
> >
> > Rather than specifying frequency (rate), might make more sense to
> > give period (1,000,000 / rate). (Depends what various hardware
> > needs, but no sense in requiring an expensive divide in the code if
> > we can easily avoid it.)
> >
> > Timer ID - look at how ADC handles timers.  Make clear how to
> > handle systems that do not need a timer for the DAC (i.e. what
> > should relevant functions return, etc.)  Unlikely to need to change
> > this for each call.
> >
> > Provide interface to set size of DAC buffer (or at least make it a
> > compile time configuration option). (e.g. the 16 element buffer).
> >
> > >  bits_per_sample 8, 16, etc. (default is 8)
> > Instead of defining this as 8, leave the default to be defined by
> > the platform.  If the DAC on a particular platform only does 12
> > bits per sample (e.g.), then it doesn't make sense to give it a
> > default which can not work and always has to be changed. (The
> > module could provide a built in constant, something like
> > DEFAULT_BITS_PER_SAMPLE, which informs the code what the default
> > is.)
> >
> > Caveat on this - if the intention is that the interface can provide
> > data with a different number of bits than the platform's DAC and
> > that such data will be scaled to fit (e.g. if you give 8 bits but
> > the DAC is 12 bits, then your data will be left shifted by 4 before
> > it is output, conversely if you provide 12 bit data but the DAC is
> > only 8 bits, then it will shift right by 4 to throw away the excess
> > detail), then it should be clarified that such Procrustean scaling
> > takes place automatically. (In that case there probably should not
> > be a default value, and the user should always specify the range of
> > their data.)
> >
> > Bitarray seems like another natural possability for
> > storing/presenting the data to the DAC module.  Might consider how
> > to incorporate that cleanly.
> >
> > Taking a function to generate the data to be written is nice.
> >  Could be nice to extend it a bit, accept a function that returns
> > numbers (or a sequence of numbers).  (e.g. if I want to generate a
> > sine wave, how about if I feed it a sin function (with appropriate
> > scaling, offset, and some kind of input generator, etc.)).
> >
> >
> > Using a bitmask in platform_dac_put_sample looks like it might be a
> > bottleneck?  May be hard to quickly decode a bitmask, and at the
> > moment that procedure is called for every sample going out to the
> > DAC (so the decode is done a lot). May not be much of an issue if
> > only have 2 channels, but some audio applications use 6 channels,
> > etc.
> >
> > Is the added complexity really worth it (maybe offer simple version
> > where specify 1 channel id, and complex version to use when needed.)
> > One option is to have samples be tagged with which channel (e.g. in
> > padding bits) - SAM3 does this in some cases. Does STM do anything
> > similar? Have you considered varargs?
> >
> >
> > Once I look at adapting this to work with the SAM3 I may have other
> > thoughts (what works well/doesn't work well with that platform).
> >
> >
> >
> > > ----- Original Message -----
> > > From: Mark Burton
> > > Sent: 01/03/14 06:43 AM
> > > To: [hidden email]
> > > Subject: Re: [eLua-dev] DAC Module
> > >
> > > Hi,
> > >
> > > I have reworked the DAC module and the Lua functions. The biggest
> > > change is that dac.putsamples() can now be passed the sample data
> > > as either an array of integers, a string of bytes or a function
> > > that should return a string of bytes every time it is called
> > > until all the data has been read when it should return an empty
> > > string or nil.
> > >
> > > -------------
> > >
> > > setup( dac_id, [bits_per_sample] )
> > >
> > > Initialise a single DAC (only required if you are going to call
> > > dac.putsample())
> > >
> > >  dac_id = id of DAC to initialise
> > >  bits_per_sample = 8, 16, etc. (default is 8)
> > >
> > > putsample( dac_id, val )
> > >
> > > Output a single sample to a DAC
> > >
> > >  dac_id = id of DAC to send output to
> > >  val = value to output
> > >
> > > num_samples_output, num_underflows =
> > >  putsamples( dac_id, data_source, rate, [bits_per_sample,
> > > [channels, [bias, [timer_id]]]] )
> > >
> > > Output a stream of samples to one or more DACs at a specific rate
> > >
> > >  dac_id = id of DAC to send output to
> > >  data_source = the source of the data to send to the DAC - can be
> > > either an array of integers, a string of bytes or a function that
> > > returns a string of bytes - the function will be called
> > > repeatedly until it returns nil or an empty string rate = the
> > > sample rate in Hz bits_per_sample 8, 16, etc. (default is 8)
> > >  channels = the number of channels (consecutive DACs) to output
> > > to (default = 1) bias = a value that is added to each output
> > > value before it is written to the DAC (default = 0) timer_id =
> > > the id of the timer used to generate the clock (defaults to 1st
> > > acceptable timer)
> > >
> > >  returns 2 values, the number of samples output and the number of
> > > times buffer underflow occurred.
> > >
> > > Note that dac.putsamples() calls dac.setup() so you don't need to
> > > explicitly call dac.setup() first.
> > >
> > > ---------------
> > >
> > > I have removed the wav playing function and written it in Lua,
> > > here's the bit of it that calls dac.putsamples():
> > >
> > >  local num_samp, num_uf = dac.putsamples(dac_id, function ()
> > > return f:read(256) end, sample_rate, bytes_per_sample * 8,
> > > num_channels, 0x8000) f:close() print(string.format("%d samples
> > > output, %d underflows", num_samp, num_uf));
> > >
> > > ---------------
> > >
> > > As before, the code can be found at
> > > https://github.com/smartavionics/elua
> > >
> > > Cheers,
> > >
> > > Mark
> > >
> > >
> > > _______________________________________________
> > > eLua-dev mailing list
> > > [hidden email]
> > > https://lists.berlios.de/mailman/listinfo/elua-dev
> >
> > _______________________________________________
> > eLua-dev mailing list
> > [hidden email]
> > https://lists.berlios.de/mailman/listinfo/elua-dev
>
> _______________________________________________
> eLua-dev mailing list
> [hidden email]
> https://lists.berlios.de/mailman/listinfo/elua-dev
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Martin Guy Martin Guy
Reply | Threaded
Open this post in threaded view
|

Re: DAC Module

On 10/01/2014, Mark Burton <[hidden email]> wrote:
> My focus is very narrow, I
> just need the ability to play WAV files by reading data from the SD
> card and clocking it into the DAC.

It sounds like you might make better progress writing your own
play-wav-file-to-dac module, as was your first idea, without expecting
it to be included in mainline eLua. You're sure to achieve your own
personal goal more quickly that way, and when someone decides to face
making a generic DAC interface, your work is sure to be a precious
example of working code tha coves one usage case.

Good luck!

    M
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev