优化此 C (AVR) 代码

2024-02-13

我有一个中断处理程序,但运行速度不够快,无法完成我想做的事情。基本上,我使用它通过将查找表中的值输出到 AVR 微控制器上的端口来生成正弦波,但不幸的是,这发生的速度不够快,无法让我获得所需的波频率。有人告诉我,我应该考虑在汇编中实现它,因为编译器生成的汇编可能效率稍低,并且可能能够进行优化,但在查看汇编代码后,我真的看不出我可以做得更好。

这是 C 代码:

const uint8_t amplitudes60[60] = {127, 140, 153, 166, 176, 191, 202, 212, 221, 230, 237, 243, 248, 251, 253, 254, 253, 251, 248, 243, 237, 230, 221, 212, 202, 191, 179, 166, 153, 140, 127, 114, 101, 88, 75, 63, 52, 42, 33, 24, 17, 11, 6, 3, 1, 0, 1, 3, 6, 11, 17, 24, 33, 42, 52, 63, 75, 88, 101, 114};
const uint8_t amplitudes13[13] = {127, 176,  221, 248,  202, 153, 101, 52, 17,  1, 6,  33,  75};
const uint8_t amplitudes10[10] = {127, 176,   248,  202, 101, 52, 17,  1,  33,  75};

volatile uint8_t numOfAmps = 60;
volatile uint8_t *amplitudes = amplitudes60;
volatile uint8_t amplitudePlace = 0; 

ISR(TIMER1_COMPA_vect) 
{
    PORTD = amplitudes[amplitudePlace];

    amplitudePlace++; 

    if(amplitudePlace == numOfAmps)
    {
        amplitudePlace = 0;
    }

}

amplitudes 和 numOfAmps 都被另一个中断例程改变,该例程的运行速度比这个例程慢得多(它基本上是为了改变正在播放的频率而运行的)。最终我不会使用那些完全相同的数组,但它将是一个非常相似的设置。我很可能有一个包含 60 个值的数组,另一个只有 30 个值。这是因为我正在构建一个扫频器,在较低的频率下,我可以给它更多的样本,因为我有更多的时钟周期可以使用,但是在较高的频率下,我的时间非常紧张。

我确实意识到我可以让它以较低的采样率工作,但我不想每个周期的样本数低于 30 个。我不认为拥有指向数组的指针会使速度变慢,因为从数组中获取值的程序集和从指向数组的指针获取值的程序集似乎是相同的(这是有道理的)。

有人告诉我,在我必须产生的最高频率下,我应该能够使其在每个正弦波周期大约 30 个样本的情况下工作。目前,30 个样本的运行速度最快约为所需最大频率的一半,我认为这意味着我的中断需要以两倍的速度运行。

因此,模拟时的代码需要 65 个周期才能完成。我再次被告知我最多应该能够将其减少到大约 30 个周期。

这是生成的 ASM 代码,我思考了它旁边的每一行的作用:

ISR(TIMER1_COMPA_vect) 
{
push    r1
push    r0
in      r0, 0x3f        ; save status reg
push    r0
eor     r1, r1      ; generates a 0 in r1, used much later
push    r24
push    r25
push    r30
push    r31         ; all regs saved


PORTD = amplitudes[amplitudePlace];
lds     r24, 0x00C8     ; r24 <- amplitudePlace I’m pretty sure
lds     r30, 0x00B4 ; these two lines load in the address of the 
lds     r31, 0x00B5 ; array which would explain why it’d a 16 bit number
                    ; if the atmega8 uses 16 bit addresses


add     r30, r24            ; aha, this must be getting the ADDRESS OF THE element 
adc     r31, r1             ; at amplitudePlace in the array.  

ld      r24, Z              ; Z low is r30, makes sense. I think this is loading
                            ; the memory located at the address in r30/r31 and
                            ; putting it into r24

out     0x12, r24           ; fairly sure this is putting the amplitude into PORTD

amplitudePlace++; 
lds     r24, 0x011C     ; r24 <- amplitudePlace
subi    r24, 0xFF       ; subi is subtract imediate.. 0xFF = 255 so I’m
                        ; thinking with an 8 bit value x, x+1 = x - 255;
                        ; I might just trust that the compiler knows what it’s 
                        ; doing here rather than try to change it to an ADDI 

sts     0x011C, r24     ; puts the new value back to the address of the
                        ; variable

if(amplitudePlace == numOfAmps)
lds     r25, 0x00C8 ; r24 <- amplitudePlace
lds     r24, 0x00B3 ; r25 <- numOfAmps 

cp      r24, r24        ; compares them 
brne    .+4             ; 0xdc <__vector_6+0x54>
        {
                amplitudePlace = 0;
                    sts     0x011C, r1 ; oh, this is why r1 was set to 0 earlier
        }


}

pop     r31             ; restores the registers
pop     r30
pop     r25
pop     r24
pop     r19
pop     r18
pop     r0
out     0x3f, r0        ; 63
pop     r0
pop     r1
reti

除了在中断中使用更少的寄存器以便我有更少的推送/弹出之外,我真的看不出这个汇编代码在哪里效率低下。

我唯一的另一个想法是,如果我能弄清楚如何在 C 中获取 n 位 int 数据类型,以便数字在到达末尾时会环绕,那么 if 语句可能会被删除?我的意思是,我将有 2^n - 1 个样本,然后让amplitudePlace 变量继续计数,这样当它达到 2^n 时,它就会溢出并重置为零。

我确实尝试完全不使用 if 位来模拟代码,虽然它确实提高了速度,但只需要大约 10 个周期,因此一次执行大约需要 55 个周期,不幸的是,这仍然不够快,所以我确实需要进一步优化代码,如果没有它只有 2 行,这是很难考虑的!

我唯一真正的想法是看看我是否可以将静态查找表存储在需要更少时钟周期访问的地方?我认为它用来访问数组的 LDS 指令都需要 2 个周期,所以我可能不会真正节省太多时间,但在这个阶段我愿意尝试任何事情。

我完全不知道从这里该去哪里。我不知道如何使我的 C 代码更加高效,但我对这类事情还很陌生,所以我可能会错过一些东西。我希望得到任何形式的帮助..我意识到这是一个非常特殊且复杂的问题,通常我会尽量避免在这里问这类问题,但我已经研究这个问题很多年了,我完全不知所措,所以我真的会接受任何我能得到的帮助。


我可以看到一些需要开始工作的领域,未按特定顺序列出:

1.减少要压入的寄存器数量,因为每个压入/弹出对需要四个周期。例如,avr-gcc允许您从其寄存器分配器中删除一些寄存器,因此您可以将它们用作单个 ISR 中的寄存器变量,并确保它们仍然包含上次的值。你也可以摆脱推动r1 and eor r1,r1如果你的程序永远不会设置r1除了0.

2.使用局部临时变量作为数组索引的新值,以将不必要的加载和存储指令保存到该易失性变量中。像这样的事情:

volatile uint8_t amplitudePlace;

ISR() {
    uint8_t place = amplitudePlace;
    [ do all your stuff with place to avoid memory access to amplitudePlace ]
    amplitudePlace = place;
}

3.从 59 向后计数到 0,而不是从 0 到 59,以避免单独的比较指令(在减法中无论如何都会发生与 0 的比较)。伪代码:

     sub  rXX,1
     goto Foo if non-zero
     movi rXX, 59
Foo:

代替

     add  rXX,1
     compare rXX with 60
     goto Foo if >=
     movi rXX, 0
Foo:

4.也许使用指针和指针比较(与预先计算的值!)而不是数组索引。需要检查与倒数哪个更有效。也许将数组与 256 字节边界对齐,并仅使用 8 位寄存器作为指针,以节省加载和保存地址的高 8 位的时间。 (如果 SRAM 即将耗尽,您仍然可以将 60 字节数组中的 4 个内容放入一个 256 字节数组中,并且仍然可以利用由 8 个常量高位和 8 个变量低位组成的所有地址。)

uint8_t array[60];
uint8_t *idx = array; /* shortcut for &array[0] */
const uint8_t *max_idx = &array[59];

ISR() {
    PORTFOO = *idx;
    ++idx;
    if (idx > max_idx) {
        idx = array;
    }
}

问题是指针是 16 位,而以前的简单数组索引大小是 8 位。如果您设计数组地址,使地址的高 8 位是常量(在汇编代码中,hi8(array)),并且您只处理 ISR 中实际发生变化的低 8 位。但这确实意味着编写汇编代码。上面生成的汇编代码可能是在汇编中编写该版本 ISR 的良好起点。

5.如果从时序角度可行,请将样本缓冲区大小调整为 2 的幂,以用简单的代码替换 if-reset-to-zero 部分i = (i+1) & ((1 << POWER)-1);。如果您想采用 8 位/8 位地址分割中建议的4.,甚至可能达到 256 的 2 次方(并根据需要复制样本数据以填充 256 字节缓冲区)甚至可以在 ADD 之后节省 AND 指令。

6.如果 ISR 仅使用不影响状态寄存器的指令,请停止入栈和出栈SREG.

General

以下内容可能会派上用场,尤其是在手动检查所有其他汇编代码的假设时:

firmware-%.lss: firmware-%.elf
        $(OBJDUMP) -h -S $< > $@

这会生成整个固件映像的带注释的完整汇编语言列表。您可以使用它来验证注册(非)使用情况。请注意,启动代码仅在首次启用中断之前运行一次,不会干扰 ISR 稍后对寄存器的独占使用。

如果您决定不直接在汇编代码中编写 ISR,我建议您编写 C 代码并在每次编译后检查生成的汇编代码,以便立即观察您的更改最终生成的内容。

您最终可能会用 C 和汇编编写 ISR 的十几个变体,将每个变体的周期相加,然后选择最好的一个。

Note在不进行任何寄存器保留的情况下,我最终得到了大约 31 个 ISR 周期(不包括进入和离开,这又增加了 8 或 10 个周期)。完全摆脱寄存器推送将使 ISR 减少到 15 个周期。更改为大小恒定为 256 字节的样本缓冲区,并让 ISR 独占使用四个寄存器,可以将 ISR 所花费的周期减少到 6 个周期(加上 8 或 10 个进入/离开周期)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

优化此 C (AVR) 代码 的相关文章

  • ASP.NET Core Serilog 未将属性推送到其自定义列

    我有这个设置appsettings json对于我的 Serilog 安装 Serilog MinimumLevel Information Enrich LogUserName Override Microsoft Critical Wr
  • 获取按下的按钮的返回值

    我有一个在特定事件中弹出的表单 它从数组中提取按钮并将标签值设置为特定值 因此 如果您要按下或单击此按钮 该函数应返回标签值 我怎样才能做到这一点 我如何知道点击了哪个按钮 此时代码返回 DialogResult 但我想从函数返回 Tag
  • 使闭包捕获的变量变得易失性

    闭包捕获的变量如何与不同线程交互 在下面的示例代码中 我想将totalEvents 声明为易失性的 但C 不允许这样做 是的 我知道这是错误的代码 这只是一个例子 private void WaitFor10Events volatile
  • 在 Visual Studio 2008 上设置预调试事件

    我想在 Visual Studio 中开始调试程序之前运行一个任务 我每次调试程序时都需要运行此任务 因此构建后事件还不够好 我查看了设置的 调试 选项卡 但没有这样的选项 有什么办法可以做到这一点吗 你唯一可以尝试的 IMO 就是尝试Co
  • C#:如何防止主窗体过早显示

    在我的 main 方法中 我像往常一样启动主窗体 Application EnableVisualStyles Application SetCompatibleTextRenderingDefault false Application
  • Json.NET - 反序列化接口属性引发错误“类型是接口或抽象类,无法实例化”

    我有一个类 其属性是接口 public class Foo public int Number get set public ISomething Thing get set 尝试反序列化Foo使用 Json NET 的类给我一条错误消息
  • Cython 和类的构造函数

    我对 Cython 使用默认构造函数有疑问 我的 C 类 Node 如下 Node h class Node public Node std cerr lt lt calling no arg constructor lt lt std e
  • 如何将图像路径保存到Live Tile的WP8本地文件夹

    我正在更新我的 Windows Phone 应用程序以使用新的 WP8 文件存储 API 本地文件夹 而不是 WP7 API 隔离存储文件 旧的工作方法 这是我如何成功地将图像保存到 共享 ShellContent文件夹使用隔离存储文件方法
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • for循环中计数器变量的范围是多少?

    我在 Visual Studio 2008 中收到以下错误 Error 1 A local variable named i cannot be declared in this scope because it would give a
  • 从库中捕获主线程 SynchronizationContext 或 Dispatcher

    我有一个 C 库 希望能够将工作发送 发布到 主 ui 线程 如果存在 该库可供以下人员使用 一个winforms应用程序 本机应用程序 带 UI 控制台应用程序 没有 UI 在库中 我想在初始化期间捕获一些东西 Synchronizati
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 将 unsigned char * (uint8_t *) 转换为 const char *

    我有一个带有 uint8 t 参数的函数 uint8 t ihex decode uint8 t in size t len uint8 t out uint8 t i hn ln for i 0 i lt len i 2 hn in i
  • C++ 复制初始化和直接初始化,奇怪的情况

    在继续阅读本文之前 请阅读在 C 中 复制初始化和直接初始化之间有区别吗 https stackoverflow com questions 1051379 is there a difference in c between copy i
  • 需要哪个版本的 Visual C++ 运行时库?

    microsoft 的最新 vcredist 2010 版 是否包含以前的版本 2008 SP1 和 2005 SP1 还是我需要安装全部 3 个版本 谢谢 你需要所有这些
  • 将文本叠加在图像背景上并转换为 PDF

    使用 NET 我想以编程方式创建一个 PDF 它仅包含一个背景图像 其上有两个具有不同字体和位置的标签 我已阅读过有关现有 PDF 库的信息 但不知道 如果适用 哪一个对于如此简单的任务来说最简单 有人愿意指导我吗 P D 我不想使用生成的
  • WCF:将随机数添加到 UsernameToken

    我正在尝试连接到用 Java 编写的 Web 服务 但有些东西我无法弄清楚 使用 WCF 和 customBinding 几乎一切似乎都很好 除了 SOAP 消息的一部分 因为它缺少 Nonce 和 Created 部分节点 显然我错过了一
  • 32 位到 64 位内联汇编移植

    我有一段 C 代码 在 GNU Linux 环境下用 g 编译 它加载一个函数指针 它如何执行并不重要 使用一些内联汇编将一些参数推送到堆栈上 然后调用该函数 代码如下 unsigned long stack 1 23 33 43 save
  • 使用 z = f(x, y) 形式的 B 样条方法来拟合 z = f(x)

    作为一个潜在的解决方案这个问题 https stackoverflow com questions 76476327 how to avoid creating many binary switching variables in gekk
  • 恢复上传文件控制

    我确实阅读了以下帖子 C 暂停 恢复上传 https stackoverflow com questions 1048330 pause resume upload in c 使用 HTTP 恢复上传 https stackoverflow

随机推荐