-
-
Notifications
You must be signed in to change notification settings - Fork 477
Update i2s.md #303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update i2s.md #303
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,7 +7,7 @@ tags: [I2S, Audio, SAMD] | |||||||||
|
||||||||||
## Overview | ||||||||||
|
||||||||||
This library allows you to use the I2S protocol on SAMD21 based boards (i.e Arduino or Genuino Zero, MKRZero or MKR1000 Board). | ||||||||||
This library allows you to use the I2S protocol on SAMD21 based boards (i.e Arduino or Genuino Zero, MKRZero, MKR1000 or Nano 33 IoT). | ||||||||||
|
||||||||||
To use this library | ||||||||||
|
||||||||||
|
@@ -29,9 +29,11 @@ The SCK line has a frequency that depends on the sample rate, the number of bits | |||||||||
|
||||||||||
- **Frequency = SampleRate x BitsPerChannel x numberOfChannels** | ||||||||||
|
||||||||||
In a typical setup, the sender of audio data is called a Transmitter and it transfers data to a Receiver at the other end. The device that controls the bus clock, SCK, together with the Word Select - WS - signal is the Controller in the network and in any network just one device can be Controller at any given time; all the other devices connected are in Peripheral mode. The Controller can be the Transmitter, or the Receiver, or a standalone controller. The digitized audio data sample can have a size ranging from 4 bits up to 32. | ||||||||||
The left and right channels are interleaved in the data stream, and "one sample" is one value for the left channel and one for the right. This means that the FS line oscillates at the sample rate. | ||||||||||
|
||||||||||
As a general rule of thumb, the higher the sample rate (kHz) and bits per sample, the better audio quality (when the digital data is converted back to analog audio sound). | ||||||||||
In a typical setup, the sender of audio data is called a Transmitter and it transfers data to a Receiver at the other end. The device that controls the bus clock, SCK, together with the Word Select - WS - signal is the Controller in the network and in any network just one device can be Controller at any given time; all the other devices connected are in Peripheral mode. The Controller can be either the Transmitter, or the Receiver, or a standalone controller managing the transfer of data between two other devices. The digitized audio data samples can have a size ranging from 4 bits up to 32 bits for each channel. The data is in **signed** format, meaning that 8-bit values are interpreted as -128 to 127, not 0 to 255, and 16-bit values are interpreted as -32768 to 32767. | ||||||||||
|
||||||||||
As a general rule of thumb, the higher the sample rate (kHz) and bits per sample, the better audio quality when the digital data is converted back to analog audio sound. A digitized signal with a sample rate of X Hz can capture sound frequencies of up to X/2 Hz. Higher frequencies in the sound will cause problems with **aliasing**. For more information on this, please refer to literature on digital sound processing. | ||||||||||
|
||||||||||
## I2S Class | ||||||||||
|
||||||||||
|
@@ -49,9 +51,9 @@ I2S.begin(mode, bitsPerSample); // peripheral device | |||||||||
``` | ||||||||||
|
||||||||||
#### Parameters | ||||||||||
mode: one between I2S_PHILIPS_MODE, I2S_RIGHT_JUSTIFIED_MODE or I2S_LEFT_JUSTIFIED_MODE | ||||||||||
mode: one of I2S_PHILIPS_MODE, I2S_RIGHT_JUSTIFIED_MODE or I2S_LEFT_JUSTIFIED_MODE | ||||||||||
sampleRate: the desired sample rate in Hz - long | ||||||||||
bitsPerSample: the desired bits per sample (i.e 8, 16, 32) | ||||||||||
bitsPerSample: the desired number of bits per sample (i.e 8, 16, 32) | ||||||||||
|
||||||||||
#### Returns | ||||||||||
1 if initialization is ok, 0 otherwise | ||||||||||
|
@@ -77,7 +79,7 @@ nothing | |||||||||
### `available()` | ||||||||||
|
||||||||||
#### Description | ||||||||||
Get the number of bytes available for reading from the I2S interface. This is data that has already arrived and was stored in the I2S receive buffer. available() inherits from the Stream utility class. | ||||||||||
Get the number of **bytes** (not the number of samples) available for reading from the I2S interface. This is data that has already arrived and was stored in the I2S receive buffer. available() inherits from the Stream utility class. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the contrary, my experience is that the most common trap people fall into is to confuse the number of bytes with the number of samples for these functions. This is based on a study of only two test subjects, me and my colleague, but I think it is motivated to point this out. Do as you like. |
||||||||||
|
||||||||||
#### Syntax | ||||||||||
|
||||||||||
|
@@ -113,31 +115,44 @@ The next sample of incoming I2S data available (or 0 if no data is available) | |||||||||
### `write()` | ||||||||||
|
||||||||||
#### Description | ||||||||||
Writes binary data to the I2S interface. This data is sent as a sample or series of samples. | ||||||||||
Writes binary data to the I2S interface. Data is sent either as one sample or a sequence of samples. | ||||||||||
|
||||||||||
The single argument function `write(val)` writes one sample for one channel. A full write requires | ||||||||||
calling this function twice, once for the left channel and once for the right channel. | ||||||||||
If the device buffer is full, the function blocks until the data can be written. | ||||||||||
Before writing, the value `val` will be converted to the appropriate number of bits per sample. | ||||||||||
Comment on lines
+120
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad. I have never worked with mono or multi-channel devices. |
||||||||||
|
||||||||||
The two-argument function `write(buf, len)` writes a sequence of samples from the array `buf`. | ||||||||||
Data must be stored in the proper format for the sample size, with the left and right channels | ||||||||||
interleaved, and the argument `len` specifies the number of **bytes** in the buffer, not the | ||||||||||
number of samples. If the device buffer is too full to hold all the data, the function writes | ||||||||||
as much as will fit (possibly nothing), and returns without blocking. The return value is the | ||||||||||
number of bytes that was actually written. It is the responsibility of the application to | ||||||||||
handle any such partial writes and act accordingly. | ||||||||||
Comment on lines
+125
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be more generic, always referring to the 2 channel scenario There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. my bad. See above. |
||||||||||
|
||||||||||
#### Syntax | ||||||||||
|
||||||||||
``` | ||||||||||
I2S.write(val) // blocking | ||||||||||
I2S.write(buf, len) // not blocking | ||||||||||
I2S.write(val) // Might block for a moment until data can be written | ||||||||||
I2S.write(buf, len) // Will not block, but might not write all the data | ||||||||||
|
||||||||||
``` | ||||||||||
|
||||||||||
#### Parameters | ||||||||||
val: a value to send as a single sample | ||||||||||
|
||||||||||
buf: an array to send as a series of samples | ||||||||||
buf: an array to send as a sequence of samples | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In digital signal processing, the correct term is "sequence" of samples. In mathematics, a "series" is something different. There is no real risk of confusion here, but "sequence" is the term I would strongly recommend. |
||||||||||
|
||||||||||
len: the length of the buffer | ||||||||||
len: the length of the buffer, in bytes (not in number of samples) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reflects my own stumbles, but I agree. |
||||||||||
|
||||||||||
#### Returns | ||||||||||
byte | ||||||||||
- write() will return the number of bytes written, though reading that number is optional. | ||||||||||
size_t | ||||||||||
- write(buf, len) will return the number of bytes written. Reading that number is optional, but partial writes that are not handled properly will cause popping, crackling or stutter in the sound. | ||||||||||
|
||||||||||
### `availableForWrite()` | ||||||||||
|
||||||||||
#### Description | ||||||||||
Get the number of bytes available for writing in the buffer without blocking the write operation. | ||||||||||
Get the number of bytes available for writing in the buffer without blocking the write(val) operation, and without causing the write(buf, len) operation to return without writing all the data. | ||||||||||
|
||||||||||
#### Syntax | ||||||||||
|
||||||||||
|
@@ -150,26 +165,67 @@ I2S.availableForWrite() | |||||||||
none | ||||||||||
|
||||||||||
#### Returns | ||||||||||
the number of bytes available to write. | ||||||||||
size_t | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I get it. Why wouldn't you want to specify the return type for a function in the documentation? Some other pages in the Arduino docs lack this information as well, and I have sometimes been forced to read the source. |
||||||||||
- the number of bytes available to write | ||||||||||
|
||||||||||
### `onTransmit(handler)` | ||||||||||
|
||||||||||
#### Description | ||||||||||
Registers a function to be called when a block of data has been transmitted. | ||||||||||
Registers a function to be called when a block of data has been transmitted. This is used in conjunction with the two-argument function write(buf, len), and the callback function is called when the DMA write operation that was triggered by write(buf, len) finishes. The callback takes no arguments, which mean it cannot determine by itself if the write was completely successful or wrote only some part of the data. Partial writes need to be handled separately by the application. | ||||||||||
|
||||||||||
The callback is not triggered by the single argument version `write(val)`, only by the two argument version `write(buf, len)`. The callback can be registered once at setup of the I2S interface, and will be called for every subsequent write() -- you do not need to register it anew for every write operation. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please send the source of this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verified by my own experiments. I had to use a lot of trial and error to get this library to work for me. I didn't find any documentation on this anywhere else, but with the current library code, the callback was most definitely not executed after a single value write when I checked just now. |
||||||||||
|
||||||||||
#### Parameters | ||||||||||
handler: the function to be called when data is sent and return nothing, e.g.: void myHandler() | ||||||||||
handler: the function to be called when data is sent. It must be a function with no arguments returning nothing, e.g.: void myHandler() | ||||||||||
|
||||||||||
#### Returns | ||||||||||
None | ||||||||||
|
||||||||||
### `read()` | ||||||||||
|
||||||||||
#### Description | ||||||||||
Reads binary data from the I2S interface. Data is read either as one sample or a sequence of samples. | ||||||||||
|
||||||||||
The single argument function `read(val)` reads one sample for one channel. A full read requires | ||||||||||
calling this function twice, once for the left channel and once for the right channel. | ||||||||||
Comment on lines
+189
to
+190
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be generic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course, my bad. |
||||||||||
If the device buffer is empty, the function blocks until there is data available. | ||||||||||
The return value is always an `int`. You will need to know the number of bits per sample | ||||||||||
to handle the data correctly. | ||||||||||
|
||||||||||
The two-argument function `read(buf, len)` reads a sequence of samples into the array `buf`. | ||||||||||
Data will be stored in the binary format for the current sample size, with the left and right | ||||||||||
channels interleaved. The argument `len` specifies the number of **bytes** in the buffer, not the | ||||||||||
Comment on lines
+196
to
+197
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be generic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. |
||||||||||
number of samples. If the device has more data available than what fits in `buf`, | ||||||||||
the function retrieves as much as will fit. If the device has less data available, possibly | ||||||||||
nothing, the function will read what is available and return without blocking. The return | ||||||||||
value is the number of bytes that was actually read. It is the responsibility of the | ||||||||||
application to read the return value and act accordingly. | ||||||||||
|
||||||||||
#### Syntax | ||||||||||
|
||||||||||
``` | ||||||||||
val = I2S.read() // Might block until data is available for reading | ||||||||||
I2S.read(buf, len) // Will not block, but might not read all the data | ||||||||||
Comment on lines
+207
to
+208
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, of course. Sorry. I'm unfamiliar with the format for this documentation. |
||||||||||
|
||||||||||
``` | ||||||||||
|
||||||||||
#### Parameters | ||||||||||
|
||||||||||
buf: an array to store a sequence of samples | ||||||||||
|
||||||||||
len: the length of the `buf` array, in bytes (not in number of samples) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the content of the parenthesis There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If not mentioned here, definitely deserves pointing out elsewhere. Common pitfall. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above. |
||||||||||
|
||||||||||
#### Returns | ||||||||||
size_t | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it does return an int, isn't it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the source, it actually does. It should return a size_t, reasonably, but it doesn't. |
||||||||||
- read(buf, len) will return the number of bytes read. Reading that number is essential, as the transmitting device will typically not fill the buffer completely with every read operation. | ||||||||||
|
||||||||||
### `onReceive(handler)` | ||||||||||
|
||||||||||
#### Description | ||||||||||
Registers a function to be called when a block of data has been received. | ||||||||||
Registers a function to be called when a block of data has been received. This works just like the `onTransmit()` handler, but for the `read(buf, len)` function that receives data from a device. The callback is not triggered for the no-argument version of `read()` that reads a single sample value. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned before send us the source of this please! 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was verified by my own experiments. I don't see it mentioned anywhere in the docs, and this was a major headache for me. The fact that the loop() function executes at the sample rate would also be worth mentioning. It's not explicitly stated anywhere in the current docs. |
||||||||||
|
||||||||||
#### Parameters | ||||||||||
handler: the function to be called when the device receives data and return nothing, e.g.: void myHandler() | ||||||||||
handler: the function to be called when the device receives data. It must be a function with no arguments returning nothing, e.g.: void myHandler() | ||||||||||
|
||||||||||
#### Returns | ||||||||||
None | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not 100% needed, could you elaborate this paragraph more maybe?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment about the FS line is valid only for 2 channels, so scratch that. The rest is exposition about the I2S protocol, and that can also be scrapped, although documentation on the I2S protocol is generally hard to find and difficult to read. This is generic documentation, not a more narrowly focused "howto", and I like to educate people. (Work related habit, do as you like.)