【问题标题】:Inline C assembler macros for an AVR RTOSAVR RTOS 的内联 C 汇编器宏
【发布时间】:2017-08-19 07:49:07
【问题描述】:

我正在尝试为 AVR 编写一个抢占式调度程序,因此我需要一些汇编代码……但我没有汇编程序的经验。但是,我在一些 C 宏中编写了我认为需要的所有汇编代码。在编译时,我得到了一些与汇编器相关的错误(需要常量值和行和行的垃圾),这让我认为我的宏中有些地方不正确......

下面的宏 load_SP(some_unsigned_char, some_unsigned_char) 将堆栈指针设置为一个已知的内存位置...我将该位置保存在全局结构 aux_SP 中;

类似的东西是load_PC(...),它正在堆栈上加载,一个程序计数器:“func_pointer”,顾名思义,它实际上是一个指向函数的指针。我在这里假设程序计数器和函数指针都用 2 个字节表示(因为闪存足够小)

为此,我使用处理器寄存器 R16。为了保持这个寄存器不变,我首先用宏“save_R16(tempR)”保存它的值,然后用宏“load_R16(tempR)”恢复它的值,其中“tempR”可以看出是一个全局C变量.

这只是简单地写在头文件中。这与另外两个宏(由于它们的大小而没有写在这里)“pushRegs()”和“popRegs()”基本上是推然后弹出所有处理器寄存器都是我的汇编代码......

我应该怎么做才能更正我的宏?

// used to store the current StackPointer when creating a new task until it is restored at the
// end of createNewTask function.
struct auxSP
{
    unsigned char auxSPH;
    unsigned char auxSPL;
};

struct auxSP cSP = {0,0};

// used to restore processor register when using load_SP or load_PC macros to perform
// a Stack Pointer or Program Counter load.
unsigned char tempReg = 0;

////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////// assembler macros begin ////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////

// save processor register R16
#define save_R16(tempR)                                 \
    asm volatile(                                       \
            "STS tempR, R16                     \n\t"   \
                );

// load processor register R16
#define load_R16(tempR)                                 \
        asm volatile(                                   \
                    "LDS R16, tempR             \n\t"   \
                    );

// load the Stack Pointer.              Warning: Alters the processor registers
#define load_SP(new_SP_H, new_SP_L)                     \
    asm volatile(                                       \
                "LDI R16, new_SP_H              \n\t"   \
                "OUT SPH, R16                   \n\t"   \
                "LDI R16, new_SP_L              \n\t"   \
                "OUT SPL, R16                   \n\t"   \
                );

// load the Program Counter on stack.   Warning: Alters the processor registers
#define load_PC(func_pointer)                           \
    asm volatile(                                       \
                "LDS r16, LOW(func_pointer)     \n\t"   \
                "PUSH r16                       \n\t"   \
                "LDS r16, HIGH(func_pointer)    \n\t"   \
                "PUSH r16                       \n\t"   \
                );

【问题讨论】:

  • 阅读 gcc 文档如何在内联汇编中使用 C 参数和变量。
  • 要成功进行线程切换,您必须将所有寄存器恢复到中断线程时它们所处的状态。这意味着您的定时器中断例程不仅要保存它看到的所有寄存器,还要检查堆栈以恢复由中断硬件逻辑推送到那里的值(PC 的正确值在那里)。编译器生成的 ISR 序言还将一些值压入堆栈。恢复寄存器的顺序也很重要。
  • @MichaelRoy 如前所述,有宏 pushRegs() 将所有寄存器压入堆栈,popRegs() 从堆栈中弹出所有寄存器。这些宏用于其他功能...
  • 顺便说一句,AVR 的 RAM 非常少。为多个堆栈保留空间会浪费相当多的 RAM。
  • @MichaelRoy 我为每个堆栈保留 64 个字节......这更多是为了学术用途......我对一些概念感兴趣

标签: c assembly embedded


【解决方案1】:

您的主要参考来源应该是http://www.nongnu.org/avr-libc/user-manual/inline_asm.html

避免使用“unsigned char” - 使用“uint8_t”,因为它更短且更明确。避免使用宏——尽可能使用静态内联函数。不要为 auxSP 发明自己的结构,尤其是不要使用与目标通常使用的不同的字节序 - 只需使用 uint16_t。当你可以用 C 写东西时,不要用汇编写东西。不要把需要组合在一起的 asm 语句分开(例如在一个语句中保留 R16,然后在第二个语句中使用它)。

这会给我们带来什么影响?

我已经很久没有做过很多 AVR 编程了,但这可能会让你开始:

static inline uint16_t read_SP(void) {
  uint16_t sp;
  asm volatile(
    "in  %A[sp], __SP_L__ \n\t"
    "in  %B[sp], __SP_H__ \n\t"
  : [sp] "=r" (sp) :: );
  return sp;
}

static inline void write_SP(uint16_t sp) {
  asm volatile(
    "out  __SP_L__, %A[sp] \n\t"
    "out  __SP_H__, %B[sp] \n\t"
  :: [sp] "r" (sp) : );
}

typedef void (*FVoid)(void);
static inline void load_PC(FVoid f) __attribute__((noreturn));
static inline void load_PC(FVoid f) {
  asm volatile(
    "ijmp"
  :: "z" (f) );
  __builtin_unreachable();
}

您可能还需要确保在使用任何中断之前禁用中断。

【讨论】:

    【解决方案2】:

    这是我在 AVR 平台上编写的 C 代码示例。它不是宏而是函数,因为它更适合我。

    void call_xxx(uint32_t address, uint16_t data)
    {
        asm volatile("push r15"); //has to be saved
        asm volatile("ldi r18, 0x01");  //r15 will be copied to SPMCSR
        asm volatile("mov r15, r18"); //copy r18 to r15 (cannot be done directly)
        asm volatile("movw r0, r20"); //r1:r0 <= r21:r20 //should conatain "data" parameter
        asm volatile("movw r30, r22"); //31:r30<=r23:r22 // should contain "address" parameter ...
        asm volatile("sts 0x5b, r24"); //RAMPZ
        asm volatile("rcall .+0"); //push PC on top of stack and never pop it
        asm volatile("jmp 0x3ecb7");  //secret function
        asm volatile("eor r1, r1"); //null r1
        asm volatile("pop r15");  //restore value
    
        return;
    }
    

    也可以尝试不使用\n\t,这可能是“在线垃圾”

    需要常量值的问题可能来自这里:

    #define save_R16(tempR)                                 \
        asm volatile(                                       \
                "STS tempR, R16                     \n\t"   \
                    );
    

    对此我不太确定,但 STS(和其他)需要一个可能需要在编译时修复的地址。因此,根据您使用宏的方式,它可能无法编译。如果 tempR 不固定,您可以使用函数而不是宏。

    【讨论】:

    • 如果你打算这样做,只需在 asm 中编写你的整个函数。如果/当它内联到任何东西时,那将会中断。如果您需要一个临时注册,您应该只向编译器询问一个具有虚拟输出约束的注册。 (有关编写内联汇编的信息,请参阅stackoverflow.com/tags/inline-assembly/info,以使编译器在不浪费指令的情况下尽可能优化。)
    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 2013-07-23
    • 1970-01-01
    • 2019-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-01
    相关资源
    最近更新 更多