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 |
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 |
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 |
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 |
> 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
Free forum by Nabble | Edit this page |