5

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
goofson
  • 51
  • 1
  • I can think of two hackish solutions. Both require you to fill the table with RJMPs then dynamically route to the proper handler. 1) you can trigger a timer interrupt and detect which slot is activated or 2) self program a signature into the extra flash and see if it succeeds (subsequent power on can simply check if the signature is already there) Alternatively, create a 16k image with the signature already present assuming you can flash it to a 8k device with truncation. – Jester Jul 24 '23 at 22:56
  • Ah yeah, the signature bytes are different so then it's a simple matter of setting up some indirection. – Jester Jul 25 '23 at 00:45
  • @Jester: It is not possible to simply write RJMP everywhere and then figure out where to jump. Each address corresponds to a specific hw interrupt. In that case, it will not be possible to tell which interrupt was activated. – Peter Plesník Jul 25 '23 at 12:14
  • 2
    You would normally generate separate builds for each platform in any event. Is it really necessary you have one binary for both targets? – Clifford Jul 25 '23 at 12:46
  • 1
    @PeterPlesník it is possible. You fill the table with `RJMP`. On the larger model every second will be used. On the smaller model the first 20 will be used. You can then dispatch depending on the model to the proper handler. – Jester Jul 25 '23 at 12:54
  • @Jester: I can't agree with that. Can you please show how you imagine it concretely? It is enough if you show it on the first two vectors. How do you know if you should do a RESET or service IRQ0. So that it is universal for both MCUs – Peter Plesník Jul 25 '23 at 13:01
  • Oh sorry. Reset and IRQ0 can be distinguished. But how to distinguish IRQ1 and IRQ0. In general, vectors at odd addresses are differentiable. But vectors at even addresses 2, 4, ..20 cannot be easily distinguished. Only by checking the interrupt flags in the relevant status registers. So it could be done in this way, but it doesn't seem like a robust solution to me. – Peter Plesník Jul 25 '23 at 13:50
  • You can easily differentiate based on the signature byte. – Jester Jul 25 '23 at 14:04
  • I understood. Thank you for the exercise. – Peter Plesník Jul 25 '23 at 14:24

4 Answers4

3

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
Jester
  • 56,577
  • 4
  • 81
  • 125
1

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.

emacs drives me nuts
  • 2,785
  • 13
  • 23
  • 1
    Except that the [datasheet itself](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7728-Automotive-Microcontrollers-ATtiny87-ATtiny167_Datasheet.pdf) states right at the start in section 1.1 that: _"Atmel® ATtiny87 and ATtiny167 are hardware **and software** compatible."_ (emphasis mine) – Jester Aug 21 '23 at 21:54
  • 1
    @Jester: That's a bug in the data sheet. I edited my answer to explain how to get a binary that works both on ATtiny87 and ATtiny167. – emacs drives me nuts Aug 22 '23 at 18:19
0

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

Peter Plesník
  • 524
  • 1
  • 2
  • 7
0

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

dvd280
  • 876
  • 6
  • 11
  • 1
    The way I understand the question it is about flashing the same binary to different devices so compile time choice is not going to work. – Jester Jul 30 '23 at 13:44
  • @Jester That would be improbable in my view, the two devices do not share the exact same instruction set, and so at the binary level there will practically always be a difference between the two files, unless all of the code (not just the interrupt vectors) is refactored to somehow avoid jump instructions altogether - which is possible using branches , but very unlikely to be what OP meant. There is no reason to insist on doing dynamically what is completely solvable statically. But I agree both interpretations of the question can be valid here. – dvd280 Jul 30 '23 at 20:51
  • What other difference did you find? They are supposed to be compatible. Note that both chips support `JMP` and `RJMP`, it's just that the longer range `JMP` is used for the vectors in the larger model. – Jester Jul 30 '23 at 21:59
  • 1
    @Jester I was not aware of that so thanks for the input, and still - by the way OP mentioned attempting to write two different programs ( one per device) implies that he requires identical functionality (not binaries) - I cant really think of any reason to insist on identical binaries as it seems to somewhat increase complexity with little upside. – dvd280 Jul 31 '23 at 05:12
  • You still need two binaries, and the one for ATtiny87 won't work on ATtiny167 (and vice versa). – emacs drives me nuts Aug 22 '23 at 18:22