用耳朵定义数字:在Arduino上的实现

在本文中,我将继续在已经在腺体中进行的3号实验室工作中体现我的灵感。 我们正在谈论使用Goertzel算法在Arduino的音频拨号模式下通过声音检测数字。

为了实现这一点,我使用了Arduino UNO,一个驻极体麦克风( adafruit )和一个带有MAX7219驱动器的8x8显示器。

行动计划


  • 离散化足够多的样本(使用上一篇文章中的程序,我确信256个样本就足够了)。
  • 找到与编码字符所需的频率相对应的频率响应幅度。
  • 振幅的两个最大值将给出所需字符的行索引和列索引,例如,图3看起来像。
    图片

实作


在开始实施之前,我们先回答一个问题-Arduino UNO是否对我们有足够的性能?

时钟频率:16MHz
一个采样周期需要13个时钟周期
提供最高精度的预分频器值:128

结果表明16 MHz / 13/128〜9615 Hz-所需的采样频率,这意味着您可以使用最高4.8 kHz的频率。

ADC调整


ADC工作有多种模式,最有趣的模式如下( 数据表中ADCSRB关键字的完整列表)

  • 单次读取-使用AnalogRead()方法,但这是一个阻塞调用,需要100µs,并且使用它不可能提供恒定的采样率
  • 自由运行模式-在此模式下,下一个采样周期在上一个采样周期结束后立即开始,并提供最大采样频率
  • 定时器溢出-采样从定时器溢出开始,这使您可以微调采样频率

ADC设置代码,为简单起见,我使用了自由运行模式。

ADMUX = 0; // Channel sel, right-adj, use AREF pin ADCSRA = _BV(ADEN) | // ADC enable _BV(ADSC) | // ADC start _BV(ADATE) | // Auto trigger _BV(ADIE) | // Interrupt enable _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz ADCSRB = 0; // Free-run mode DIDR0 = _BV(0); // Turn off digital input for ADC pin TIMSK0 = 0; // Timer0 off 

信号处理


输入完整的样本数组后,我们将关闭ADC的中断,并使用Goertzel算法计算频谱的幅度。 我不会使用这种详尽的资源来竞争算法的描述,但是我将给出实现:

 void goertzel(uint8_t *samples, float *spectrum) { float v_0, v_1, v_2; float re, im, amp; for (uint8_t k = 0; k < IX_LEN; k++) { float cos = pgm_read_float(&(cos_t[k])); float sin = pgm_read_float(&(sin_t[k])); float a = 2. * cos; v_0 = v_1 = v_2 = 0; for (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (float)(samples[i]) + a * v_1 - v_0; } re = cos * v_2 - v_1; im = sin * v_2; amp = sqrt(re * re + im * im); spectrum[k] = amp; } } 

预先为对应于所需频率的样本计算正弦和余弦。 代码的完整版本在这里

结论


最重要的是,事实证明,Arduino UNO资源足以进行简单的声音处理。


我学到的主要课程是ADC是一件敏感的事情,在成功识别并将角色发送到控制台后,我花了一周的时间对其进行调试以使其与显示器配合使用,因为我将麦克风接地和max7219连接在一起,并且所有采样立即变成了噪声。

会更好吗? 是的,选择采样频率和采样数量以使所需频率与采样晶格重合会更正确,这样可以防止频谱扩展。 这样的参数已经是f = 8 kHz,N = 205,在这种情况下,您需要在自由运行模式下而不是自由模式下运行ADC,但需要定时器溢出,两者之间的差异将非常明显。



课程已接近尾声,但仍有很多想法。
谢谢您的关注。

Source: https://habr.com/ru/post/zh-CN432498/


All Articles