goofson
goofson

Reputation: 51

ATtiny167 and Attiny87 NOT backwards compatible due to JMP vs RJMP. How to resolve

The problem I face is that the Attiny167 and Attiny87 despite on the datasheets saying they are drop in replacements are in fact not. The program is written in AVR assembly.

The vector table for the Attiny87 uses RJMP meanwhile the Attiny167 uses JMP. This means I need two separate instances of the vector table in my program somehow which I don't believe is possible. Is there a way to write the code so that the program can check the signature bytes to check which hardware device is on and then use the appropriate "jmp" instruction? Thank you.

I have tried so far writing two separate instances of the program using whichever required jmp instruction, however I need a way for the program to do this automatically instead of having to manually check.

Upvotes: 5

Views: 137

Answers (4)

emacs drives me nuts
emacs drives me nuts

Reputation: 4003

ATtiny167 and ATtiny87 despite on the datasheets saying they are drop in replacements are in fact not.

"Drop-In Replacement" means that it is possible to replace one controller by the other one physically, which includes:

  • Same footprint / package
  • Same current consumption under comparable conditions
  • Same source / sink capabilities of I/O pins
  • ...

It does not imply that the chips are binary compatible, i.e. that they can be programmed using the same binary code / executable.


Edit1:

In the ATtiny87/167 Datasheet "7.1 Interrupt Vectors in ATtiny87/167", pp 57 the interrupt addresses of either device are different.

In "1.1 Comparison between ATtiny87 and ATtiny167" the vector size of 2 words for ATtiny87 is a Bug in the manual.

Conclusion

ATtiny87 and ATtiny167 are not binary compatible. To get a binary that's compatible requires some effort... Compile your code for ATtiny87 but with the following modifications:

  1. Write a startup code (crtattiny87.o) for ATtiny87 that has twice as many IRQ vectors like the actual hardware. Then, if you need an ISR for IRQ N, also implement IRQ 2N. If an IRQ triggers on ATtiny87, you will get IRQ N; if it triggers on ATtiny167, you will get IRQ 2N.

    Then determine at runtime, which IRQ actually triggered and forward to the appropriate ISR code. (No idea how to discriminate between the two µC's at runtime, though.) If you only need few IRQs, the IRQ source might be clear without a runtime check.

  2. RJMP and RCALL instructions behave differently: They wrap around at 8KiB on ATtiny87, but wrap around at 16KiB on ATtiny167. So you must make sure that the binary does not use wrap-around in these instructions. You can determine wrap-around in a disassembly when the absolut byte address is outside [0,0x1ffe], e.g. with avr-objdump -d x.elf for example:
    1860: f4 d3 rcall .+2024 ; 0x204a <positive wrap-around>
    46: 0e cc rjmp .-2020 ; 0xfffff864 <negative wrap-around>
    Make sure the binary has no such wrap-arounds and if, re-organize the code accordingly.

Upvotes: 1

dvd280
dvd280

Reputation: 962

I am surprised no one proposed using assembler macros, suppose your interrupt vector routine is at address 0x1200, and that your interrupt vector address is at at address 0x0006:

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

You can alternatively do something a bit more dirty but a bit cooler using the preprocessor:

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

For this second method, you can basically now replace every instance of JMP and RJMP with MJMP everywhere in your code, and it will solve your problem more globally (not just with interrupts).

Upvotes: 0

Jester
Jester

Reputation: 58802

The simple solution is to have your reset read the signature byte into a variable so you can branch on that later. Initial code could look like:

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
...

If you don't like the repeated branching you can put together a clever dispatcher working with RCALL and the return addresses. This may be overkill but was fun so here it is:

#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

Upvotes: 3

Peter Plesn&#237;k
Peter Plesn&#237;k

Reputation: 819

According to the catalog sheet, both types are hw and sw compatible. Both use two words for the vector table. One word (rjmp) can be used only if the FLASH size is less than 4KiBy.

Atmel® ATtiny87 and ATtiny167 are hardware and software compatible. They differ only in memory sizes as shown in Table 1-1 Table 1.1

Read 1.1 of the DS

So you can use JMP for both MCU

In the case of using several incompatible types, this is solved by conditional translation, for example according to signature or family type.

Edit: I was confused by the information in table 1.1. Where it is written for ATTiny87 that it uses 2 words for the table of interrupt vectors. In fact, it only uses one word as rjmp can jumps to 8KiBy (+-2KWord). In that case, conditional translation can be used. For example in AVRASM2 like this

.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

Upvotes: 0

Related Questions