代码之家  ›  专栏  ›  技术社区  ›  goofson

ATtiny167和Attiny87由于JMP与RJMP而不向后兼容。如何解决

  •  1
  • goofson  · 技术社区  · 2 年前

    我面临的问题是,尽管数据表上说Attiny167和Attiny87是替代品,但事实并非如此。该程序是用AVR汇编编写的。

    Attiny87的矢量表使用RJMP,而Attiny167使用JMP。这意味着我的程序中需要向量表的两个独立实例,我认为这是不可能的。有没有一种方法可以编写代码,使程序可以检查签名字节,以检查哪个硬件设备在上,然后使用适当的“jmp”指令?非常感谢。

    到目前为止,我已经尝试使用所需的jmp指令编写程序的两个独立实例,但我需要一种方法让程序自动完成这项工作,而不必手动检查。

    0 回复  |  直到 2 年前
        1
  •  3
  •   Jester    2 年前

    简单的解决方案是 reset 将签名字节读取到一个变量中,以便稍后进行分支。 初始代码可能如下所示:

    rjmp reset
    rjmp vec_1
    rjmp vec_2
    rjmp vec_3
    ...
    
    vec_1:
    ; odd vectors are always attiny87
    ; this is INT0
    ; attiny167 never gets here so no branching
    ...
    
    vec_2:
    ; if we are attiny87 this is INT1
    ; if we are attiny167 this is INT0
    ; so do a conditional branch to the proper place
    ...
    
    vec_3:
    ; again odd vector so this is PCINT0 on an attiny87
    ...
    

    如果你不喜欢重复的分支,你可以组建一个聪明的调度器来处理 RCALL 以及返回地址。这可能有些过头了,但很有趣,所以这里是:

    #include <avr/io.h>
    
    ; set up a variable to store the signature byte
    ; arbitrary address
    .equ signature_byte_1, 0x100
    
        rjmp reset
    .rept 39
        rcall dispatch
    .endr
    
    dispatch:
    ; save Y
        push r28
        push r29
    ; get SP into Y
        in r28, _SFR_IO_ADDR(SPL)
        in r29, _SFR_IO_ADDR(SPH)
    ; put ZH on stack
        std Y + 3, r31
    ; get return address into ZH
        ldd r31, Y + 4
    ; put ZL on stack
        std Y + 4, r30
    ; save SREG
        in r30, _SFR_IO_ADDR(SREG)
        push r30
    ; assume this has been set up during reset
    ; 0x93 for attiny87
    ; 0x94 for attiny167
        lds r30, signature_byte_1
    ; return address is
    ; attiny87:   2, 3, 4, ...
    ; attiny167:  3, 5, 7, ...
    ; we want for indexing: 0, 2, 4 ...
        dec r31
    ; multiply attiny87 by 2
        sbrc r30, 0
        lsl r31
    ; now both are 2, 4, 6, ...
    ; fix pointer, assume it's in first 256 bytes
        mov r30, r31
        ldi r31, 0
        ldi r28, lo8(vectors-2)
        add r30, r28
    ; both LPM and ICALL use Z :(
        lpm r28, Z+
        lpm r29, Z
        mov r30, r28
        mov r31, r29
        icall
    
    ; restore SREG
        pop r30
        out _SFR_IO_ADDR(SREG), r30
    ; restore Y
        pop r29
        pop r28
    ; restore Z
        pop r31
        pop r30
    ; done
        reti
    
    ; here is the real vector table
    vectors:
        .word pm(int0_handler)
        .word pm(int1_handler)
        .word pm(pcint0_handler)
        .word pm(pcint1_handler)
    ; ...
    
    reset:
        ldi r16, lo8(RAMEND)
        ldi r17, hi8(RAMEND)
        out _SFR_IO_ADDR(SPL), r16
        out _SFR_IO_ADDR(SPH), r17
    ; read signature byte into variable
    ; left as exercise for reader :)
    
    ; TEST CODE
    ; verify these are not changed
        ldi r28, 28
        ldi r29, 29
        ldi r30, 30
        ldi r31, 31
    ; simulate attiny87
        ldi r16, 0x93
        sts signature_byte_1, r16
    ; invoke PCINT0
        rcall 3*2
    ; invoke INT1
        rcall 2*2
    ; simulate attiny167
        ldi r16, 0x94
        sts signature_byte_1, r16
    ; invoke PCINT0
        rcall 6*2
    ; invoke INT1
        rcall 4*2
    end:
        rjmp end
    
    int0_handler:
        ret
    
    int1_handler:
        ret
    
    pcint0_handler:
        ret
    
    pcint1_handler:
        ret
    
        2
  •  1
  •   emacs drives me nuts    2 年前

    ATtiny167和ATtiny87,尽管数据表上说它们是替代品,但事实并非如此。

    “插入式更换” 意味着可以用另一个控制器替换一个控制器 身体上 ,包括:

    • 相同的占地面积/封装
    • 可比条件下的相同电流消耗
    • I/O引脚的相同源/汇功能
    • ...

    并不意味着芯片是二进制兼容的 即,它们可以使用相同的二进制代码/可执行文件进行编程。


    第1版:

    在ATtiny87/167数据表中 "7.1 Interrupt Vectors in ATtiny87/167" , pp 57 任何一个设备的中断地址都不同。

    在里面 “1.1 ATtiny87与ATtiny167的比较” ATtiny87的2个单词的矢量大小是一个Bug 在手册中。

    结论

    ATtiny87和ATtiny167不是二进制兼容的。要获得兼容的二进制文件,需要付出一些努力。。。为ATtiny87编译代码,但需要进行以下修改:

    1. 编写启动代码( crtattiny87.o )对于像实际硬件一样具有两倍多的IRQ矢量的ATtiny87。然后,如果您需要IRQ N的ISR,也可以实现IRQ 2N。如果在ATtiny87上触发了IRQ,您将获得IRQ N;如果它在ATtiny167上触发,您将获得IRQ 2N。

      然后在运行时确定哪个IRQ 事实上 触发并转发到适当的ISR代码。(不过,不知道如何在运行时区分这两个C。)如果你只需要几个IRQ,那么在没有运行时检查的情况下,IRQ源可能是清晰的。

    2. RJMP RCALL 指令的行为不同:它们在ATtiny87上以8KiB环绕,但在ATtiny167上以16KiB环绕。因此,您必须确保二进制文件在这些指令中不使用环绕。当绝对字节地址在[0,0x1ffe]之外时,您可以在反汇编中确定环绕,例如 avr-objdump -d x.elf 例如
      1860: f4 d3 rcall .+2024 ; 0x204a <positive wrap-around>
      46: 0e cc rjmp .-2020 ; 0xfffff864 <negative wrap-around>
      确保二进制文件具有 没有这样的包装 如果,则相应地重新组织代码。

        3
  •  0
  •   Peter Plesník    2 年前

    根据目录表,这两种类型都兼容硬件和软件。两者都使用两个单词作为矢量表。只有当FLASH大小小于4KiBy时,才能使用一个字(rjmp)。

    Atmel ATtiny87和ATtiny167是硬件和软件兼容的。如表1-1所示,它们仅在内存大小上有所不同 Table 1.1

    Read 1.1 of the DS

    因此,您可以将JMP用于两个MCU

    在使用几种不兼容类型的情况下,这可以通过条件转换来解决,例如根据签名或族类型。

    编辑 我被表1.1中的信息弄糊涂了。在为ATTiny87编写的地方,它使用2个字作为中断矢量表。事实上,它只使用一个单词,因为rjmp可以跳转到8KiBy(+-2KWord)。在这种情况下,可以使用条件翻译。例如在 AVRASM2 这样地

    .if SIGNATURE_000==0x1e && SIGNATURE_001==0x93 && SIGNATURE_002==0x87   ;ATTiny87
      rjmp RESET ; Reset Handler
      rjmp INT0addr ; IRQ0 Handler
      rjmp INT1addr ; IRQ1 Handler
      rjmp PCINT0addr ; PCINT0 Handler
      rjmp PCINT1addr ; PCINT1 Handler
      rjmp WDTaddr ; Watchdog Timer Handler
      rjmp ICP1addr ; Timer1 Capture Handler
      rjmp OC1Aaddr ; Timer1 Compare A Handler
      rjmp OC1Baddr ; Timer1 Compare B Handler
      rjmp OVF1addr ; Timer1 Overflow Handler
      rjmp OC0Aaddr ; Timer0 Compare A Handler
      rjmp OVF0addr ; Timer0 Overflow Handler
      rjmp LINTCaddr ; LIN Transfer Complete Handler
      rjmp LINERRaddr ; LIN Error Handler
      rjmp SPIaddr ; SPI Transfer Complete Handler
      rjmp ADCCaddr ; ADC Conversion Complete Handler
      rjmp ERDYaddr ; EEPROM Ready Handler
      rjmp ACIaddr ; Analog Comparator Handler
      rjmp USISTARTaddr ; USI Start Condition Handler
      rjmp USIOVFaddr ; USI Overflow Handler
    .endif
    
    .if SIGNATURE_000==0x1e && SIGNATURE_001==0x94 && SIGNATURE_002==0x87   ;ATTiny167
       jmp RESET ; Reset Handler
       jmp INT0addr ; IRQ0 Handler
       jmp INT1addr ; IRQ1 Handler
       jmp PCINT0addr ; PCINT0 Handler
       jmp PCINT1addr ; PCINT1 Handler
       jmp WDTaddr ; Watchdog Timer Handler
       jmp ICP1addr ; Timer1 Capture Handler
       jmp OC1Aaddr ; Timer1 Compare A Handler
       jmp OC1Baddr ; Timer1 Compare B Handler
       jmp OVF1addr ; Timer1 Overflow Handler
       jmp OC0Aaddr ; Timer0 Compare A Handler
       jmp OVF0addr ; Timer0 Overflow Handler
       jmp LINTCaddr ; LIN Transfer Complete Handler
       jmp LINERRaddr ; LIN Error Handler
       jmp SPIaddr ; SPI Transfer Complete Handler
       jmp ADCCaddr ; ADC Conversion Complete Handler
       jmp ERDYaddr ; EEPROM Ready Handler
       jmp ACIaddr ; Analog Comparator Handler
       jmp USISTARTaddr ; USI Start Condition Handler
       jmp USIOVFaddr ; USI Overflow Handler
    
    .endif
    
    
        4
  •  0
  •   dvd280    2 年前

    我很惊讶没有人提议使用汇编宏,假设您的中断向量例程位于 0x1200 ,并且您的中断矢量地址位于at地址 0x0006 :

    .ORG 0x0006
        .IF DEFINED(__ATtiny87__)
            RJMP 0x1200
        .ELIF DEFINED(__ATtiny167__)
            JMP 0x1200
        .ENDIF
    

    您也可以使用预处理器做一些更脏但更酷的事情:

    #define MJUMP defined(__ATtiny87__) ? RJMP : JMP    ; PASTE THIS ANYWHERE IN YOUR CODE
    

    对于第二种方法,现在基本上可以替换的每个实例 JMP RJMP 具有 MJMP 在您的代码中无处不在,它将更全局地解决您的问题(而不仅仅是中断)。