0

我正在玩 PIC 24,目前我在将赫兹转换为秒然后将其用作发送到压电片(蜂鸣器、缩放器、扬声器等)的信号延迟时遇到了一点问题,我想使其播放某些音符。

我想知道我是否正在从赫兹转换为秒(在提供的代码中为毫秒),以及我是否正确地进行了信号处理。

这是我遇到麻烦的代码:

我的赫兹到 int 的转换:

int16 note(int i)
{
   float time = (1.0/i);

   int fin = ((time/2) * 1000);

   return fin;
}

这是我将信号发送到我正在使用的 pic24 的方式:

void main()
{
InitMCU();

 output_high(PIN_D1);
 delay_ms(note(E));
 output_low(PIN_D1);
 delay_ms(200); 
 output_high(PIN_D1);
 delay_ms(note(E));
 output_low(PIN_D1);
 delay_ms(200);
}

这是我定义注释的方式:

#define C 255 //do
#define D 227 //re
#define E 204 //mi
#define F 191
#define G 170
#define A 153
#define B 136
#define C2 127
4

3 回答 3

2

你需要一个循环!

repeat for note length:
   on
   delay
   off
   delay

还要考虑离线计算每个音符所需的周期并将其输入为整数而不是赫兹 - CPU 不会在浮点计算中浪费时间

于 2016-12-21T14:01:19.777 回答
1

首先,您尝试使用方波来制作正弦波。因此,如果您使用计时器专门针对目标频率,它总是听起来有点错误。制作一个 440Hz 的方波,它不会“听起来”像 440Hz 的正弦波。也许物理学会解决它,但我打赌没有你想要的那么多。

如果你有速度,你可以。从 90 年代或任何时候开始做一位 DAC 的事情。如果您可以使方波的传播速度超过扬声器的物理移动速度,您可以说在一段时间内撒上比零更多的 1,比如说以受控的速率将扬声器推出一点,然后在一段时间内让扬声器进入更多的零。扬声器成为物理低通滤波器。您可能可以使用微控制器中的 PWM 来帮助解决这个问题。但是您需要动态更改它,这是 PIC,因此您可能会耗尽资源,然后才能编写大量表格以获得清晰的声音。

要做方波的事情,你需要以一半的频率改变输出引脚。不要在运行时进行计算,然后在您的计算器上进行或让工具链进行计算。假设您正在以 1Mhz 运行您的处理器/计时器,并且您希望引脚上的频率为 440Hz。您需要周期为 1/440。hz 是每秒周期数,因此倒相使其成为每个周期的秒数。每个周期或周期为 0.00227272(重复)秒,因此您需要一半高,一半低(反之亦然,没关系),这意味着输出状态变化之间的 0.00113636 ...... 如果您的计时器是 1Mhz,即每周期 1/1 百万秒。或一微秒。0.00113636 中有多少微秒... 1136。所以每个 1136 计时器滴答你改变状态,您阅读计时器上的文档并让它倒计时或倒计时或任何 1136 计数(通常这些是从零开始的数字,所以 1135 然后计数零,然后计数中断或状态标志或其他)。您也可以轮询一个计数器,该计数器从全零计数到全零并翻转,然后从现在开始减去并用正在计数的位数屏蔽它,差值是时间。使用 16 位计数器(从现在开始)&0xFFFF 是不同的,只要您想要的时间足够小于 0xFFFF。1136肯定是。等到开始减去现在(如果它是向下计数器或现在分钟开始,如果向上计数器)。从现在开始减去 then 并用正在计数的位数对其进行掩码,差值就是时间。使用 16 位计数器(从现在开始)&0xFFFF 是不同的,只要您想要的时间足够小于 0xFFFF。1136肯定是。等到开始减去现在(如果它是向下计数器或现在分钟开始,如果向上计数器)。从现在开始减去 then 并用正在计数的位数对其进行掩码,差值就是时间。使用 16 位计数器(从现在开始)&0xFFFF 是不同的,只要您想要的时间足够小于 0xFFFF。1136肯定是。等到开始减去现在(如果它是向下计数器或现在分钟开始,如果向上计数器)。

预先计算您想要的每个频率的半个周期的计数时间。当你发出一个音调时,你必须以某种方式循环每个周期,半周期打开,半周期关闭,半周期打开半周期。

根据扬声器和您的频率,它应该可以工作。如果你想尝试一点点

https://en.wikipedia.org/wiki/1-bit_DAC

您可以从三角波开始,在 1136 微秒的 1/4 中执行 66% 的占空比,在 1136 微秒的 1/2 中执行 33% 的占空比,然后在最后的 1/4 中执行 66% 的占空比。或者一次做 1/4,然后在一个占空比做 1/2,然后在另一个做 1/2。您应该能够找到或编写一个低通滤波器,并不是说您会知道扬声器的属性是什么,而是您可以了解如何在较高的波中生成较慢的波。在三角形之后,您可以尝试梯形。以一定的速度加速,稍微做 50%,然后减速,在另一半周期重复。

出于实验目的,查找或预先计算一个覆盖整个周期的序列,您可能拥有生成数百或数千行代码的代码,如果不是完全确定性,您可以通过图片获得 X 微秒数准确执行 Y 条指令或 Y 条指令的正确组合,例如

on
on
off
on
off
on
on
off

然后最后一条指令跳到顶部,再次进行实验,但你可能会发现如果你做对了,你会得到一个更干净的声音,也许比方波更干净。

您可以添加一个简单的 R/C 滤波器,实际上是两个组件,将您的比特流转换为模拟波形,然后将其馈入扬声器或放大然后馈入扬声器,这里的功能是您可以在示波器上查看它。

您可以采用便宜且简单的另一条路径是梯形电阻器,而不是一个位,您基本上输出该时间段内该位置的模拟值,并使用梯形电阻器将其转换为模拟信号。

或者只使用真正的 dac。根据您可以为 dac 提供多快,预先计算一个周期内需要发送的值的数量,并制作一个表格并在循环中发送它们,直到您完成该频率。

因此,所有这些都回到您的代码中。您正在尝试使用一些我猜的毫秒库函数?所以让我们说这就是你想要做的。

我们看到的 440Hz 是 0.00113636 秒半周期,所以这将是 1 毫秒,然后 1 毫秒,你的代码应该这样做

for(i=0;i<nperiods;i++)
{
on
delay_ms(1)
off
delay_ms(1)
}

那里的任何其他延迟都会使它出错……对于方波。对于其他人,我怀疑你会有硬编码的延迟。

所以上面有很多问题,首先一毫秒的延迟会减慢你正在尝试的东西你需要一个微秒的延迟,你需要了解循环中的开销需要多长时间,我们的数学显示为 1136 440hz 的微秒,有一些精度截断。但是执行延迟的代码,尤其是像这样的慢速 MCU 上的代码需要很多时钟周期,如果这是 C 代码而不是 asm,那么还有很多,加上打开和关闭 gpio 引脚的代码,你必须减去/把那些调出来。示波器会有所帮助,如果您从 1136 开始并且示波器显示的周期为 3000us 而不是 2272.7,那么您需要将其减去并重试。所以延迟772而不是1136。那种事情。

根据谷歌的说法,中间 C 是 261.6Hz,对与错让我们用它来运行。即 3823 微秒。输入您的注释函数 255,我假设这是定义的用途,我假设为 1.9 毫秒。就毫秒而言,这是正确的。它被截断为 1 毫秒或 2000 微秒,即 500hz,这当然是遥不可及的。该定义应该是 261 或 262 而不是 255 对吗?

嗯,所以你想先发出一个高脉冲,然后是一个 200 毫秒的固定长度低脉冲?所以如果你给它注 E 并假设代码没有时间运行。它会高 2 毫秒,然后低 200 毫秒,假设您重复说这是 1% 占空比,频率为 1/201 毫秒或 4.97...Hz,钢琴频率

https://en.wikipedia.org/wiki/Piano_key_frequencies

不显示 5hz 的音符。我敢肯定它在下面接近一些谐波,但很低。

A 将是 3ms 高 200 低或 1/203ms 或 4.9hz 的频率

除了数学之外,在图片(或没有 fpu 的任何地方)上执行运行时浮点非常昂贵,仅数学可能需要比整个周期更长的时间。绝对没有理由计算该运行时间。您可以轻松地使用数学进行定义,或者使用计算器并使用硬编码的手动计算数字进行定义。与您需要的数字相比,它仍然无法使用一个固定的低周期,尤其是一个非常大的周期。假设代码立即运行,延迟毫秒。开启延迟 1ms 关闭延迟 1ms 为 500hz。开,延迟 2,关延迟 2,250hz,延迟 3 为 166,延迟 4 为 125,依此类推。假设代码没有立即运行并且使用毫秒延迟,那么您不会打出很多真正的笔记。

为了产生压力波,你基本上想让扬声器从静止状态推出半个周期,然后从静止状态吸回半个周期,理想情况下以正弦方式,这样它就会慢慢熄灭然后回来进出。从重置状态变为 out 肯定会起作用,但您仍然需要正确的占空比才能接近方波。因此,了解您拥有的计时器,通过图片您可以轻松地手动编写一些消耗时钟周期的循环,因为它们是我记忆中的确定性。先从你处理器的频率说起,它是什么?你的笔记有多少个处理器周期?与上面的 440hz 到 1mhz 数学相同。0。

然后弄清楚如何打开它等待那个数量的 cpu 时钟然后关闭并等待那个数量的 cpu 时钟。把它输入你的压电或其他,看看它的声音。

如果你有 16 位寄存器,我敢打赌你没有,但假设你的时钟是 1mhz

load reg with some number
top
subtract reg,1
compare with zero
branch to top

当然是在组装中。假设一个时钟周期用于减法并比较每个然后两个用于分支假设每个循环是四个,因此 1136/4 = 284。使用预先计算的值 284 加载寄存器。

手工编码一些程序集

top:
gpio on
load reg,284
one:
sub reg,1
cmp reg,0
bne one
gpio off
load reg,284
two:
sub reg,1
cmp reg,0
bne two
jmp top

粗略,但它会让你开始走上这条路。

如果您没有 16 位寄存器而是 8 位寄存器,那么 1mhz 1136/0x100 = 4 余数 112 如果这个处理器每个循环需要 4 个时钟,就像我在上面所幻想的那样,那将会非常好。每次延迟将是

mov reg,0xFF
A:
sub reg,1
cmp reg,0
bne A
mov reg,28
B:
sub reg,1
cmp reg,0
bne B

毫无疑问,这里和其他地方有无数资源为 PIC 系列成员描述延迟循环。

你可以直接飞起来看看声音的变化

#define DELX 300
volatile unsigned int x;
while(1)
{
    output_high(PIN_D1);
    for(x=0;x<DELX;x++) continue; 
    output_low(PIN_D1);
    for(x=0;x<DELX;x++) continue; 
}

并使用不同的数字进行定义。音调应该会改变,质量可能不会那么好,或者可能比你现在拥有的更好,但如果可以听到的话,它应该会改变。您可能会遇到悬崖我假设它是一个 8 位处理器,因此数到 200 与数到 300 的方式不同,它不会是一个低一倍半的频率。可能它不是线性的,可能取决于编译器,但它可能是线性段,到处都有扭结。100 到 200 可能是线性的,300 到 400 但 200 到 300 可能不是。

这可能是线性的

volatile unsigned int x;
unsigned int y,z;
for(z=0;z<1000;z++)
for(y=0;y<100;y++)
{
    output_high(PIN_D1);
    for(x=0;x<z;x++) continue; 
    output_low(PIN_D1);
    for(x=0;x<z;x++) continue; 
}
于 2016-12-28T04:22:23.390 回答
1

中间的 A 音符通常是 440 Hz 音调。如果您每秒打开和关闭压电扬声器 440 次,则连续切换之间的延迟为 1/880 秒,即 1.13636363636 毫秒。这表明了两件事:

  • 你的计算很差;和
  • 一毫秒的计时器分辨率对于此应用程序来说太粗糙了。

当然,您需要一个循环来播放一段持续时间的音调。

于 2016-12-21T14:45:39.307 回答