Close

Another skirmish with the avr-gcc toolchain

ken-yapKen Yap wrote 03/09/2025 at 06:02 • 16 min read • Like

Wow, it's been almost 5 years and one pandemic since I tried to do something with this chip, first by installing, or rather piggybacking on, the gcc-avr toolchain. This MCU is pin compatible with the 8051 except for the polarity of the reset pin. I tried to get it working in the dev board in #Adventures with a STC89C52 development board but no joy.

Today I decided to try to get it working (or determine that it's bust) once and for all. I flashed a clock program onto it and watched the voltages on the port pins. All high. But the oscillator pins seemed to be ok. Time to look at my firmware.

Disassembling the .elf file with avr-objdump showed the start of the executable area:

Disassembly of section .text:

00000000 <__ctors_end>:
   0:   11 e0           ldi     r17, 0x01       ; 1
   2:   a0 e6           ldi     r26, 0x60       ; 96
   4:   b0 e0           ldi     r27, 0x00       ; 0
   6:   ea ef           ldi     r30, 0xFA       ; 250
   8:   f6 e0           ldi     r31, 0x06       ; 6
   a:   03 c0           rjmp    .+6             ; 0x12 <__zero_reg__+0x11>
   c:   c8 95           lpm
   e:   31 96           adiw    r30, 0x01       ; 1
  10:   0d 92           st      X+, r0
  12:   ac 32           cpi     r26, 0x2C       ; 44
  14:   b1 07           cpc     r27, r17
  16:   d1 f7           brne    .-12            ; 0xc <__zero_reg__+0xb>

00000018 <__do_clear_bss>:
  18:   21 e0           ldi     r18, 0x01       ; 1
  1a:   ac e2           ldi     r26, 0x2C       ; 44
  1c:   b1 e0           ldi     r27, 0x01       ; 1
  1e:   01 c0           rjmp    .+2             ; 0x22 <.do_clear_bss_start>

00000020 <.do_clear_bss_loop>:
  20:   1d 92           st      X+, r1

00000022 <.do_clear_bss_start>:
  22:   a9 34           cpi     r26, 0x49       ; 73
  24:   b2 07           cpc     r27, r18
  26:   e1 f7           brne    .-8             ; 0x20 <.do_clear_bss_loop>

00000028 <__vector_4>:
  28:   1f 92           push    r1
  2a:   0f 92           push    r0
  2c:   0f b6           in      r0, 0x3f        ; 63
  2e:   0f 92           push    r0
  30:   11 24           eor     r1, r1
  32:   8f 93           push    r24
  34:   80 91 37 01     lds     r24, 0x0137     ; 0x800137 
  38:   81 50           subi    r24, 0x01       ; 1
  3a:   80 93 37 01     sts     0x0137, r24     ; 0x800137 
  3e:   8f 91           pop     r24
  40:   0f 90           pop     r0
  42:   0f be           out     0x3f, r0        ; 63
  44:   0f 90           pop     r0
  46:   1f 90           pop     r1
  48:   18 95           reti

Ah, that's no good. Although the code starts at 0, the next few locations should be interrupt vectors. In particular there should be an entry for vector_4, the timer vector. The code clears the BSS, but doesn't set up the PSW and the SP; one would expect some out 0x3[def] instructions. It doesn't call main, but falls through to vector_4, the first routine in the main C program.

We need more than this. Arduino users of the AVR chips have a startup environment that calls setup(), then loop() repeatedly. When doing bare metal (bare silicon?) stuff, like I am there should be startup code. Unfortunately a search on bare metal AVR got lots of pages explaining how to do direct I/O to ports, bypassing pinMode() and digitalWrite(), that's their idea of bare metal. The closest I found was this post on StackOverflow.

Ok so I need the equivalent of what has been known since Unix days as crt0.s, the assembly code for the startup code for C programs. A bit of searching in the avr-gcc directories found crtat90s8515.o. No corresponding .s, but that's ok, I can disassemble it with avr-objdump. It's probably in the source distribution.

Adding crtat90s8515.o to the list of linker input files gets this disassembled code:

Disassembly of section .text:

00000000 <__vectors>:
   0:   0c c0           rjmp    .+24            ; 0x1a <__ctors_end>
   2:   27 c0           rjmp    .+78            ; 0x52 <__bad_interrupt>
   4:   26 c0           rjmp    .+76            ; 0x52 <__bad_interrupt>
   6:   25 c0           rjmp    .+74            ; 0x52 <__bad_interrupt>
   8:   25 c0           rjmp    .+74            ; 0x54 <__vector_4>
   a:   23 c0           rjmp    .+70            ; 0x52 <__bad_interrupt>
   c:   22 c0           rjmp    .+68            ; 0x52 <__bad_interrupt>
   e:   21 c0           rjmp    .+66            ; 0x52 <__bad_interrupt>
  10:   20 c0           rjmp    .+64            ; 0x52 <__bad_interrupt>
  12:   1f c0           rjmp    .+62            ; 0x52 <__bad_interrupt>
  14:   1e c0           rjmp    .+60            ; 0x52 <__bad_interrupt>
  16:   1d c0           rjmp    .+58            ; 0x52 <__bad_interrupt>
  18:   1c c0           rjmp    .+56            ; 0x52 <__bad_interrupt>

0000001a <__ctors_end>:
  1a:   11 24           eor     r1, r1
  1c:   1f be           out     0x3f, r1        ; 63
  1e:   cf e5           ldi     r28, 0x5F       ; 95
  20:   d2 e0           ldi     r29, 0x02       ; 2
  22:   de bf           out     0x3e, r29       ; 62
  24:   cd bf           out     0x3d, r28       ; 61

00000026 <__do_copy_data>:
  26:   11 e0           ldi     r17, 0x01       ; 1
  28:   a0 e6           ldi     r26, 0x60       ; 96
  2a:   b0 e0           ldi     r27, 0x00       ; 0
  2c:   ea e2           ldi     r30, 0x2A       ; 42
  2e:   f7 e0           ldi     r31, 0x07       ; 7
  30:   03 c0           rjmp    .+6             ; 0x38 <__do_copy_data+0x12>
  32:   c8 95           lpm
  34:   31 96           adiw    r30, 0x01       ; 1
  36:   0d 92           st      X+, r0
  38:   ac 32           cpi     r26, 0x2C       ; 44
  3a:   b1 07           cpc     r27, r17
  3c:   d1 f7           brne    .-12            ; 0x32 <__do_copy_data+0xc>

0000003e <__do_clear_bss>:
  3e:   21 e0           ldi     r18, 0x01       ; 1
  40:   ac e2           ldi     r26, 0x2C       ; 44
  42:   b1 e0           ldi     r27, 0x01       ; 1
  44:   01 c0           rjmp    .+2             ; 0x48 <.do_clear_bss_start>

00000046 <.do_clear_bss_loop>:
  46:   1d 92           st      X+, r1

00000048 <.do_clear_bss_start>:
  48:   a9 34           cpi     r26, 0x49       ; 73
  4a:   b2 07           cpc     r27, r18
  4c:   e1 f7           brne    .-8             ; 0x46 <.do_clear_bss_loop>
  4e:   48 d2           rcall   .+1168          ; 0x4e0 
  50:   6a c3           rjmp    .+1748          ; 0x726 <_exit>

00000052 <__bad_interrupt>:
  52:   d6 cf           rjmp    .-84            ; 0x0 <__vectors>

00000054 <__vector_4>:
  54:   1f 92           push    r1
  56:   0f 92           push    r0
  58:   0f b6           in      r0, 0x3f        ; 63
  5a:   0f 92           push    r0
  5c:   11 24           eor     r1, r1
  5e:   8f 93           push    r24
  60:   80 91 37 01     lds     r24, 0x0137     ; 0x800137 
  64:   81 50           subi    r24, 0x01       ; 1
  66:   80 93 37 01     sts     0x0137, r24     ; 0x800137 
  6a:   8f 91           pop     r24
  6c:   0f 90           pop     r0
  6e:   0f be           out     0x3f, r0        ; 63
  70:   0f 90           pop     r0
  72:   1f 90           pop     r1
  74:   18 95           reti

Much better. The vector table is there now, the reset vector points to the startup routines, and vector_4 points to the timer ISR. The routine ctors_end initialises the PSW to 0, and the SP to 0x25f, the last location of RAM. There are routines to clear the BSS and copy constant data to initialised globals. Finally it does a rcall to 0x4e0, the address of main.

I flashed this to the chip and measured the pin voltages again, and got more plausible values. Ok, I'll take the win for this skirmish and test it again after I have connected up the display board.

Update 2025-03-22, round 2:

Well, I didn't quite totally win the last skirmish. It turned out that the voltages were not as expected as I had hastily measured them with a multimeter and thought that a voltage in the middle of the 0 to 5 V range indicated pulsing. A probe with a DSO showed that this wasn't the case.

So the fault lay somewhere in the MCU specific initialisation code since the rest of the code is generic and works on the 8051 family MCUs.

I first tried getting the simulator under MPLAB-X working, but it seems there is no debug model for the AT90S8515 any more, it's too old.

So it was back to scrutinising the code carefully. I'll cut to the chase and show you the diff, then explain.

--- at90s8515.c.orig    2025-01-18 15:39:24.285494723 +1100
+++ at90s8515.c 2025-03-22 21:48:05.804314906 +1100
@@ -11,7 +11,9 @@
 void ports_init(void)
 {
        DDRC = 0xFF;            // all output
+       PORTC = 0xFF;           // all segment outputs high
        DDRA = 0xFF;            // all output
+       PORTA = 0xFF;           // all digit outputs high
        DDRD = 0x00;            // all input
        PORTD = 0xFF;           // input pullup activated
        DDRB = 0xFF;            // all output, to start
@@ -21,12 +23,12 @@
 {
        TCCR1B = 0x00;          // stop counter
        TCCR1A = 0x00;          // disconnected from output pin, no PWM
-       OCR1BH = COUNTDIV >> 8;
-       OCR1BL = COUNTDIV & 0xFF;
+       OCR1AH = COUNTDIV >> 8;
+       OCR1AL = COUNTDIV & 0xFF;
        TCNT1H = 0x00;          // start at 0
        TCNT1L = 0x00;
-       TCCR1B = CTC1 | 0x02;   // clear counter on match, CK/8
-       TIMSK |= OCIE1A;        // enable timer interrupt
+       TCCR1B = (1 << CTC1) | 0x02;    // clear counter on match, CK/8
+       TIMSK |= (1 << OCIE1A); // enable timer interrupt
        sei();                  // enable global interrupt
 }
 
@@ -34,8 +36,8 @@
 {
        TCNT0 = (256 - 5);      // 5µs -> 200 kHz -> 5 counts @ 1 MHz
        TCCR0 = 0x02;           // CK/8
-       while ((TIFR & TOV0) == 0)
+       while ((TIFR & (1 << TOV0)) == 0)
                ;
-       TIFR |= TOV0;
+       TIFR |= (1 << TOV0);
        TCCR0 = 0x00;           // stop counter
 }

The first two changes are simply to set the output ports to high level which is not the power on default so that I know ports_init() is working.

The next 2 line change is because I was using the wrong timer compare register, it should be A. B doesn't have reset to 0 on match so isn't suitable.

The remainder of the diffs are because I had wrongly assumed that the symbols CTC1 and so forth from the include files were bitmasks. They are actually bit positions in the byte hence the edits to (1 << CTC1) and so forth.

With the new image flashed onto the MCU, I can now see the correct waveforms on the digit and segment select lines. Now to connect the board to a display.

Like

Discussions