Arduino I2S 正弦波
Arduino I2S sine wave
我正在做一个项目,我想通过组合不同的正弦波来生成(简单的)声音。我使用的是 arduino mkrZero,因为它内置了 I2S 接口,而且它似乎有足够的处理能力来满足我的需求。
我已经按照 arduino I2S simpleTone 教程中的方式连接了我的系统:
tutorial 代码工作得很好,我从扬声器中得到一个简单的方波音调。
现在我已经修改了生成正弦波的代码,有一个查找 table sin 函数以使其足够快:
#include <I2S.h>
uint16_t isin16[] = {
0, 1144, 2287, 3430, 4571, 5712, 6850, 7987, 9121, 10252, 11380,
12505, 13625, 14742, 15854, 16962, 18064, 19161, 20251, 21336, 22414,
23486, 24550, 25607, 26655, 27696, 28729, 29752, 30767, 31772, 32768,
33753, 34728, 35693, 36647, 37589, 38521, 39440, 40347, 41243, 42125,
42995, 43851, 44695, 45524, 46340, 47142, 47929, 48702, 49460, 50203,
50930, 51642, 52339, 53019, 53683, 54331, 54962, 55577, 56174, 56755,
57318, 57864, 58392, 58902, 59395, 59869, 60325, 60763, 61182, 61583,
61965, 62327, 62671, 62996, 63302, 63588, 63855, 64103, 64331, 64539,
64728, 64897, 65047, 65176, 65286, 65375, 65445, 65495, 65525, 65535,
}; //0-90
const int sincount = 2;
int freqs[] = {50*360,51*360};
float amps[] ={0.1,0.1};
const int sampleRate = 8000; // sample rate in Hz
short sample = 0;
double t = 0;
double dt = 1.0/(1.0*sampleRate);
short LR[] = {0,0};
void setup() {
Serial.begin(115200);
// start I2S at the sample rate with 16-bits per sample
if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, 16)) {
while (1); // do nothing
}
}
void loop() {
sample = 0;
for(int n= 0;n<sincount;n++)
{
sample += fSin(freqs[n]*t)*amps[n];
}
t += dt;
LR[0] = sample;
LR[1] = sample;
I2S.write(LR[0]);//left channel
I2S.write(LR[1]);//right channel
}
float fSin(long x)
{
boolean pos = true; // positive - keeps an eye on the sign.
if (x < 0)
{
x = -x;
pos = false;
}
if (x >= 360) x %= 360;
if (x > 180)
{
x -= 180;
pos = !pos;
}
if (x > 90) x = 180 - x;
if (pos) return isin16[x]; // = /65535.0
return isin16[x];
}
这也很好用。
但是!
如果我稍微更改一下代码然后写
I2S.write(LR,2);
而不是
I2S.write(LR[0]);//left channel
I2S.write(LR[1]);//right channel
一切都坏了,扬声器的声音听起来像 horrible scream
来自 I2S 库参考:
Description
Writes binary data to the I2S interface. This data is sent as a sample
or series of samples.
Syntax
I2S.write(val)
// blocking
I2S.write(buf, len)
// not blocking
Parameters
val: a value to send as a single sample
buf: an array to send as a series of samples
len: the length of the buffer
Returns
byte write() will return the number of bytes written, though reading
that number is optional.
我想使用后一个版本的写入函数,因为它不会阻塞,而且我可以在播放之前的样本时生成新样本。
关于如何使缓冲版本也能正常工作的任何想法?
在您的代码中,您将 LR
声明为 array of 2 short
,Arduino 中的 short
大小为 2 bytes
。
当你这样写的时候:
I2S.write(LR[0]); //left channel
I2S.write(LR[1]); //right channel
给定
size_t I2SClass::write(uint8_t data)
{
return write((int32_t)data);
}
size_t I2SClass::write(int sample)
{
return write((int32_t)sample);
}
您的 short
值应该自动 提升 以适应 int
类型。现在,我有点 不确定 你正在处理的 int
的大小,因为唯一的 I2S.h 库我发现是用于 SAMD 板 on which an int
has 4 bytes
size,但通常在 Arduino 上 int
的大小为 2 bytes
.在任何一种情况下,该值都应按原样使用,没有问题。
当你这样写的时候:
I2S.write(LR,2);
给出
size_t I2SClass::write(const uint8_t *buffer, size_t size)
{
return write((const void*)buffer, size);
}
size_t I2SClass::write(const void *buffer, size_t size)
{
...
written = _doubleBuffer.write(buffer, size);
...
}
你的 array of 2 short
应该转换为 const void *
.
来自 I2SDoubleBuffer 中的代码:
size_t I2SDoubleBuffer::write(const void *buffer, size_t size) {
size_t space = availableForWrite();
if (size > space) {
size = space;
}
if (size == 0) {
return 0;
}
memcpy(&_buffer[_index][_length[_index]], buffer, size);
_length[_index] += size;
return size;
}
看起来 _doubleBuffer
不知道声明的 样本 的大小(您声明它是16 bits
),它只是将 size
个字节从输入复制到内部缓冲区。
因此,我猜测真正发生的是,当你要求2 shorts
被缓冲时,实际上只有2 bytes
被复制。
示例:
假设您的 LR
包含以下值
short LR[2] = { 0x89AB, 0xCDEF };
那么这就是我想象的它发生了
I2S.write(LR[0]); // actual left sample is 0x89AB
I2S.write(LR[1]); // actual right sample is 0xCDEF
I2S.write(LR, 2); // actual left sample is 0x89AB
// 0xCDEF is not copied
/* next loop iteration */
I2S.write(LR, 2); // actual right sample is 0x89AB
// 0xCDEF is not copied
建议的解决方案:
尝试要求4 bytes
被复制:
I2S.write(LR, 4); // actual left sample is 0x89AB
// actual right sample is 0xCDEF
我正在做一个项目,我想通过组合不同的正弦波来生成(简单的)声音。我使用的是 arduino mkrZero,因为它内置了 I2S 接口,而且它似乎有足够的处理能力来满足我的需求。
我已经按照 arduino I2S simpleTone 教程中的方式连接了我的系统:
tutorial 代码工作得很好,我从扬声器中得到一个简单的方波音调。
现在我已经修改了生成正弦波的代码,有一个查找 table sin 函数以使其足够快:
#include <I2S.h>
uint16_t isin16[] = {
0, 1144, 2287, 3430, 4571, 5712, 6850, 7987, 9121, 10252, 11380,
12505, 13625, 14742, 15854, 16962, 18064, 19161, 20251, 21336, 22414,
23486, 24550, 25607, 26655, 27696, 28729, 29752, 30767, 31772, 32768,
33753, 34728, 35693, 36647, 37589, 38521, 39440, 40347, 41243, 42125,
42995, 43851, 44695, 45524, 46340, 47142, 47929, 48702, 49460, 50203,
50930, 51642, 52339, 53019, 53683, 54331, 54962, 55577, 56174, 56755,
57318, 57864, 58392, 58902, 59395, 59869, 60325, 60763, 61182, 61583,
61965, 62327, 62671, 62996, 63302, 63588, 63855, 64103, 64331, 64539,
64728, 64897, 65047, 65176, 65286, 65375, 65445, 65495, 65525, 65535,
}; //0-90
const int sincount = 2;
int freqs[] = {50*360,51*360};
float amps[] ={0.1,0.1};
const int sampleRate = 8000; // sample rate in Hz
short sample = 0;
double t = 0;
double dt = 1.0/(1.0*sampleRate);
short LR[] = {0,0};
void setup() {
Serial.begin(115200);
// start I2S at the sample rate with 16-bits per sample
if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, 16)) {
while (1); // do nothing
}
}
void loop() {
sample = 0;
for(int n= 0;n<sincount;n++)
{
sample += fSin(freqs[n]*t)*amps[n];
}
t += dt;
LR[0] = sample;
LR[1] = sample;
I2S.write(LR[0]);//left channel
I2S.write(LR[1]);//right channel
}
float fSin(long x)
{
boolean pos = true; // positive - keeps an eye on the sign.
if (x < 0)
{
x = -x;
pos = false;
}
if (x >= 360) x %= 360;
if (x > 180)
{
x -= 180;
pos = !pos;
}
if (x > 90) x = 180 - x;
if (pos) return isin16[x]; // = /65535.0
return isin16[x];
}
这也很好用。
但是!
如果我稍微更改一下代码然后写
I2S.write(LR,2);
而不是
I2S.write(LR[0]);//left channel
I2S.write(LR[1]);//right channel
一切都坏了,扬声器的声音听起来像 horrible scream
来自 I2S 库参考:
Description
Writes binary data to the I2S interface. This data is sent as a sample or series of samples.
Syntax
I2S.write(val)
// blocking
I2S.write(buf, len)
// not blockingParameters
val: a value to send as a single sample
buf: an array to send as a series of samples
len: the length of the buffer
Returns
byte write() will return the number of bytes written, though reading that number is optional.
我想使用后一个版本的写入函数,因为它不会阻塞,而且我可以在播放之前的样本时生成新样本。
关于如何使缓冲版本也能正常工作的任何想法?
在您的代码中,您将 LR
声明为 array of 2 short
,Arduino 中的 short
大小为 2 bytes
。
当你这样写的时候:
I2S.write(LR[0]); //left channel
I2S.write(LR[1]); //right channel
给定
size_t I2SClass::write(uint8_t data)
{
return write((int32_t)data);
}
size_t I2SClass::write(int sample)
{
return write((int32_t)sample);
}
您的 short
值应该自动 提升 以适应 int
类型。现在,我有点 不确定 你正在处理的 int
的大小,因为唯一的 I2S.h 库我发现是用于 SAMD 板 on which an int
has 4 bytes
size,但通常在 Arduino 上 int
的大小为 2 bytes
.在任何一种情况下,该值都应按原样使用,没有问题。
当你这样写的时候:
I2S.write(LR,2);
给出
size_t I2SClass::write(const uint8_t *buffer, size_t size)
{
return write((const void*)buffer, size);
}
size_t I2SClass::write(const void *buffer, size_t size)
{
...
written = _doubleBuffer.write(buffer, size);
...
}
你的 array of 2 short
应该转换为 const void *
.
来自 I2SDoubleBuffer 中的代码:
size_t I2SDoubleBuffer::write(const void *buffer, size_t size) {
size_t space = availableForWrite();
if (size > space) {
size = space;
}
if (size == 0) {
return 0;
}
memcpy(&_buffer[_index][_length[_index]], buffer, size);
_length[_index] += size;
return size;
}
看起来 _doubleBuffer
不知道声明的 样本 的大小(您声明它是16 bits
),它只是将 size
个字节从输入复制到内部缓冲区。
因此,我猜测真正发生的是,当你要求2 shorts
被缓冲时,实际上只有2 bytes
被复制。
示例:
假设您的 LR
包含以下值
short LR[2] = { 0x89AB, 0xCDEF };
那么这就是我想象的它发生了
I2S.write(LR[0]); // actual left sample is 0x89AB
I2S.write(LR[1]); // actual right sample is 0xCDEF
I2S.write(LR, 2); // actual left sample is 0x89AB
// 0xCDEF is not copied
/* next loop iteration */
I2S.write(LR, 2); // actual right sample is 0x89AB
// 0xCDEF is not copied
建议的解决方案:
尝试要求4 bytes
被复制:
I2S.write(LR, 4); // actual left sample is 0x89AB
// actual right sample is 0xCDEF