You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ATtiny13上__uint24数据迁移汇编的正确性与优化问询

问题背景

我原本使用的C代码如下:

volatile register uint16_t counter asm("r12");

__uint24 getCounter() {
  __uint24 res = counter;
  res = (res << 8) | TCNT0;
  return res;
}

这是运行在ATtiny13单片机上的内联热点函数,因资源紧张需要优化。原代码编译后的汇编指令为:

getCounter:
        movw r24,r12
        ldi r26,0
        clr r22
        mov r23,r24
        mov r24,r25
        in r25,0x32
        or r22,r25
        ret

我自行编写了内联汇编实现:

inline __uint24 getCounter() {
  //__uint24 res = counter;
  //res = (res << 8) | TCNT0;
  
  uint32_t result;
  asm(
    "in %A[result],0x32" "\n\t"
    "movw %C[result],%[counter]" "\n\t"
    "mov %B[result],%C[result]" "\n\t"
    "mov %C[result],%D[result]" "\n\t"
    : [result] "=r" (result)
    : [counter] "r" (counter)
    :
  );
  return (__uint24) result;
}

这里用uint32_t是为了分配第四个连续寄存器,让编译器知晓该寄存器被修改(无法在破坏列表中使用类似"%D[result]"的写法)。

我的疑问:

  1. 自编汇编是否正确?目前测试看似可行。
  2. 是否有无需复杂汇编、让编译器更好优化getCounter()的方法?
  3. 是否有更优的汇编实现方式?

补充说明:使用movw是为保证对counter的读取为原子操作,因counter会在中断中被递增。


解答

1. 自编汇编的正确性

你的实现逻辑是正确的:

  • 先读取TCNT0到结果的低8位(%A[result])
  • 用movw原子读取counter到%C[result]和%D[result](对应32位变量的高16位)
  • 通过两次mov调整寄存器位置,最终得到的__uint24结果结构为[C:B:A],完全匹配counter<<8 | TCNT0的逻辑。

需要注意的细节:

  • ATtiny13的movw操作偶对寄存器,counter绑定在r12,所以movw %C[result],%[counter]会读取r12和r13,和uint16_t counter的定义匹配,无问题。
  • 用uint32_t分配连续4个寄存器,指令操作所有四个后只取低24位返回,逻辑自洽。

2. 无需复杂汇编的优化方法

可以通过调整C代码写法,引导编译器生成更优汇编,同时保证counter读取的原子性:

volatile register uint16_t counter asm("r12");

static inline __uint24 getCounter() {
  uint16_t cnt = counter; // 强制原子读取counter
  return ((__uint24)cnt << 8) | TCNT0;
}

或者更简洁的写法:

static inline __uint24 getCounter() {
  return ((__uint24)counter << 8) | TCNT0;
}

关键优化点:

  • 声明函数为static inline,强制编译器内联,省去ret指令的开销(ATtiny13资源有限,内联收益明显)。
  • 明确counter的读取为单个uint16_t操作,编译器会自动用movw实现原子读取,避免拆分。
  • 编译器会直接生成in指令读取TCNT0,不会产生原汇编中ldi r26,0、clr r22这类冗余指令。

3. 更优的汇编实现方式

可以直接利用AVR平台__uint24的寄存器布局(三个连续寄存器,顺序为[高:中:低]),无需借助uint32_t,大幅简化汇编:

static inline __uint24 getCounter() {
  __uint24 res;
  asm volatile(
    "in %A[res], 0x32" "\n\t"    // 读取TCNT0到res的低8位
    "movw %B[res], %[cnt]" "\n\t" // 原子读取counter到res的中8位和高8位
    : [res] "=r" (res)
    : [cnt] "r" (counter)
    :
  );
  return res;
}

这个版本的优势:

  • 仅用两条核心指令,比你的原汇编少两条mov指令,效率更高。
  • 直接匹配__uint24的寄存器结构:%A[res]存TCNT0,%B[res]和%C[res]存counter的低8位和高8位,最终结构正好是counter<<8 | TCNT0。
  • 加上asm volatile防止编译器重排或删除汇编指令(因TCNT0是硬件寄存器,读取有副作用)。

内容的提问来源于stack exchange,提问作者Kryštof Vosyka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 18:50:26