Avr (8 bit): programs that block arduino nano programmer

I was going to post what I’ll be writing here on avr freaks forum,
but, unfortunately, I was not able to register there:

Access Denied You don't have permission to access "http://login.microchip.com/ssologin/connect/authorize?" on this server. Reference #18.21f01502.1701763696.382a727f

Ok, I will post some of my observations here.

Let me start from very simple example.

export fn main () noreturn {
    const ddrb: *u8 = @ptrFromInt(0x24);
    const portb: *u8 = @ptrFromInt(0x25);
    const led_pin: u3 = 5;

    ddrb.* |= (1 << led_pin);   // as output
    portb.* |= (1 << led_pin);  // LED on
    //portb.* &= ~(@as(u8,1) << led_pin);  // LED off
    while (true) {
    }
}

This program just turn the LED on or off and then starts spinning in the loop.
I am using this simple bash script to build:

rm -f prog.*
rm -f main.o
rm -f main.o.o

#ZC=/opt/zig-0.10/zig
#ZC=/opt/zig-0.11/zig
ZC=/opt/zig-0.12/zig

export LANG=C
$ZC build-obj -O ReleaseSmall -target avr-freestanding-none -mcpu atmega328p main.zig &&
avr-ld -o prog.elf main.o &&
avr-objcopy -j .text -j .data -O ihex prog.elf prog.hex &&
avr-objdump -D -h prog.elf > prog.dmp

Let’s look at disassembly (prog.dmp):

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000006  00000000  00000000  00000074  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00800060  00800060  0000007a  2**0
                  CONTENTS, ALLOC, LOAD, DATA

Disassembly of section .text:

00000000 <main>:
   0:   25 9a           sbi     0x04, 5 ; 4
   2:   2d 9a           sbi     0x05, 5 ; 5
   4:   ff cf           rjmp    .-2             ; 0x4 <__zero_reg__+0x3>

Looks ok. Note sbi, btw.

Now, let’s add some more stuff:

var x: u16 = 0;

export fn main () noreturn {
    const ddrb: *u8 = @ptrFromInt(0x24);
    const portb: *u8 = @ptrFromInt(0x25);
    const led_pin: u3 = 5;

    ddrb.* |= (1 << led_pin);   // as output
    //portb.* |= (1 << led_pin);  // LED on
    portb.* &= ~(@as(u8,1) << led_pin);  // LED off
    x = 7;
    while (true) {
        x += 1;
        if (0x01 == (x & 0x01)) {
            portb.* |= (1 << led_pin);
        } else {
            portb.* &= ~(@as(u8,1) << led_pin);
        }
    }
}

And the the disassembly of this is:

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000006  00000000  00000000  00000074  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00800060  00800060  0000007a  2**0
                  CONTENTS, ALLOC, LOAD, DATA

Disassembly of section .text:

00000000 <main>:
   0:   25 9a           sbi     0x04, 5 ; 4
   2:   2d 98           cbi     0x05, 5 ; 5
   4:   ff cf           rjmp    .-2             ; 0x4 <__zero_reg__+0x3>

Surprise! Same code as in the first example, compiler has thrown away my nifty loop!
Ok, let’s add one little word:

export var x: u16 = 0; // added export keyword

export fn main () noreturn {
    const ddrb: *u8 = @ptrFromInt(0x24);
    const portb: *u8 = @ptrFromInt(0x25);
    const led_pin: u3 = 5;

    ddrb.* |= (1 << led_pin);               // output
    //portb.* |= (1 << led_pin);            // LED on
    portb.* &= ~(@as(u8,1) << led_pin);     // LED off
    x = 7;
    while (true) {
        x += 1;
        if (0x01 == (x & 0x01)) {
            portb.* |= (1 << led_pin);
        } else {
            portb.* &= ~(@as(u8,1) << led_pin);
        }
    }
}

Now the disassembly is more or less is what was intented:

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000002c  00000000  00000000  00000094  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00800060  00800060  000000c0  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000002  00800060  00800060  000000c0  2**0
                  ALLOC

Disassembly of section .text:

00000000 <main>:
   0:   25 9a           sbi     0x04, 5 ; 4
   2:   2d 98           cbi     0x05, 5 ; 5
   4:   87 e0           ldi     r24, 0x07       ; 7
   6:   90 e0           ldi     r25, 0x00       ; 0
   8:   01 96           adiw    r24, 0x01       ; 1
   a:   90 93 61 00     sts     0x0061, r25     ; 0x800061 <__DATA_REGION_ORIGIN__+0x1>
   e:   80 93 60 00     sts     0x0060, r24     ; 0x800060 <__DATA_REGION_ORIGIN__>
  12:   95 b1           in      r25, 0x05       ; 5
  14:   9f 7d           andi    r25, 0xDF       ; 223
  16:   82 95           swap    r24
  18:   80 7f           andi    r24, 0xF0       ; 240
  1a:   88 0f           add     r24, r24
  1c:   80 72           andi    r24, 0x20       ; 32
  1e:   89 2b           or      r24, r25
  20:   85 b9           out     0x05, r24       ; 5
  22:   80 91 60 00     lds     r24, 0x0060     ; 0x800060 <__DATA_REGION_ORIGIN__>
  26:   90 91 61 00     lds     r25, 0x0061     ; 0x800061 <__DATA_REGION_ORIGIN__+0x1>
  2a:   ee cf           rjmp    .-36            ; 0x8 <__zero_reg__+0x7>

Disassembly of section .bss:

00800060 <x>:
        ...

Now we have bss. Note sts/lds instructions.

Closer to the topic; after I uploaded this to the device, I was not able to upload any other code anymore!!!. avrdude was saying “programmer is not responding” permanently. But after some thinking I discovered a procedure to deal with it, here it is:

  • disconnect USB cable
  • press and hold down reset button
  • connect USB cable
  • now tricky part - release reset button and run a programmer (avrdude) immediately (it must start communicating with the board before a program has started execution).

At this point I started to think, that Zig/LLVM is doing something wrong (sts 0x0060…?).
If I understand it right, STS/LDS instructions require “absolute” address; for example, to work with port B we must use 0x23 (PINB), 0x24 (DDRB) and 0x25 (PORTB) (unlike IN/OUT and SBI/CBI where we have to substruct 0x20)

If so STS 0x0060 / 0x0060 is not what I want, because it’s WDTCSR and CLKPR, respectively and I was not going to write to these I/O registers.

Ok let’s do the same things in C:

#include <avr/io.h>

unsigned short x = 0;

void main(void) {

    DDRB |=  _BV(DDB5); /* output   */
    PORTB &= ~_BV(PORTB5);      /* turn LED off */

    x = 7;
    while (1) {
        x++;
        if (x& 0x0001) {
            PORTB |= _BV(PORTB5);       /* turn LED on */
        } else {
            PORTB &= ~_BV(PORTB5);      /* turn LED off */
        }
    }
}

Hmm… there was no such an amazing effect (programmer lock up) when I uploaded this.
What is in disassembly?

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000003e  00000000  00000000  00000094  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00800060  0000003e  000000d2  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000002  00800060  00800060  000000d2  2**0
                  ALLOC
  3 .comment      00000011  00000000  00000000  000000d2  2**0
                  CONTENTS, READONLY

Disassembly of section .text:

00000000 <__ctors_end>:
   0:   20 e0           ldi     r18, 0x00       ; 0
   2:   a0 e6           ldi     r26, 0x60       ; 96
   4:   b0 e0           ldi     r27, 0x00       ; 0
   6:   01 c0           rjmp    .+2             ; 0xa <.do_clear_bss_start>

00000008 <.do_clear_bss_loop>:
   8:   1d 92           st      X+, r1

0000000a <.do_clear_bss_start>:
   a:   a2 36           cpi     r26, 0x62       ; 98
   c:   b2 07           cpc     r27, r18
   e:   e1 f7           brne    .-8             ; 0x8 <.do_clear_bss_loop>

00000010 <main>:
  10:   25 9a           sbi     0x04, 5 ; 4
  12:   2d 98           cbi     0x05, 5 ; 5
  14:   87 e0           ldi     r24, 0x07       ; 7
  16:   90 e0           ldi     r25, 0x00       ; 0
  18:   90 93 61 00     sts     0x0061, r25     ; 0x800061 <__DATA_REGION_ORIGIN__+0x1>
  1c:   80 93 60 00     sts     0x0060, r24     ; 0x800060 <__DATA_REGION_ORIGIN__>
  20:   80 91 60 00     lds     r24, 0x0060     ; 0x800060 <__DATA_REGION_ORIGIN__>
  24:   90 91 61 00     lds     r25, 0x0061     ; 0x800061 <__DATA_REGION_ORIGIN__+0x1>
  28:   01 96           adiw    r24, 0x01       ; 1
  2a:   90 93 61 00     sts     0x0061, r25     ; 0x800061 <__DATA_REGION_ORIGIN__+0x1>
  2e:   80 93 60 00     sts     0x0060, r24     ; 0x800060 <__DATA_REGION_ORIGIN__>
  32:   80 ff           sbrs    r24, 0
  34:   02 c0           rjmp    .+4             ; 0x3a <main+0x2a>
  36:   2d 9a           sbi     0x05, 5 ; 5
  38:   f3 cf           rjmp    .-26            ; 0x20 <main+0x10>
  3a:   2d 98           cbi     0x05, 5 ; 5
  3c:   f1 cf           rjmp    .-30            ; 0x20 <main+0x10>

STS 0x0060 is there.

And then when I added second global variable:

#include <avr/io.h>

unsigned short x = 0;
unsigned short y = 0;

void main(void) {

    DDRB |=  _BV(DDB5); /* output   */
    PORTB &= ~_BV(PORTB5);      /* turn LED off */

    x = 7;
    y = 9;
    while (1) {
        x++;
        y++;
        if ((x + y) & 0x0001) {
            PORTB |= _BV(PORTB5);       /* turn LED on */
        } else {
            PORTB &= ~_BV(PORTB5);      /* turn LED off */
        }
    }
}

I got exactly the same behavior - programmer stopped responding to requests from avrdude.

Now my brain is broken. Nevertheless, I think these miracles are somehow related with allocating global vars at addresses which belong to “extended I/O registers”.

1 Like

Just some side notes, do not pay much attention.

#include <avr/io.h>
unsigned short x;
void main(void)
{
    DDRB |=  _BV(DDB5); /* output   */
    PORTB &= ~_BV(PORTB5);      /* turn LED off */
    x = 7;
    while (1) {
        x++;
        if (x& 0x0001) {
            PORTB |= _BV(PORTB5);       /* turn LED on */
        } else {
            PORTB &= ~_BV(PORTB5);      /* turn LED off */
        }
    }
}
  • single step build
avr-gcc test__.c -o test__.elf -Os -mmcu=atmega328p
avr-objdump -d test__.elf > test__.dmp
$ grep sts test.dmp
  98:	90 93 01 01 	sts	0x0101, r25	; 0x800101 <_edata+0x1>
  9c:	80 93 00 01 	sts	0x0100, r24	; 0x800100 <_edata>
  aa:	90 93 01 01 	sts	0x0101, r25	; 0x800101 <_edata+0x1>
  ae:	80 93 00 01 	sts	0x0100, r24	; 0x800100 <_edata>

SRAM addresses are correct.

  • two step build:
avr-gcc -c test__.c -o test__.o -Os -mmcu=atmega328p
avr-ld test__.o -o test__.elf
avr-objdump -d test__.elf > test__.dmp
$ grep lds test__.dmp
  10:	80 91 60 00 	lds	r24, 0x0060	; 0x800060 <__DATA_REGION_ORIGIN__>
  14:	90 91 61 00 	lds	r25, 0x0061	; 0x800061 <__DATA_REGION_ORIGIN__+0x1>

SRAM addresses are incorrect.

The point is that avr-ld when invoked directly (not via avr-gcc), should be supplied with data start address, like this:

avr-gcc -c test__.c -o test__.o -Os -mmcu=atmega328p
avr-ld test__.o -o test__.elf -Tdata 0x800100
avr-objdump -d test__.elf > test__.dmp

Now the address of x is correct, 0x0100:

$ grep lds test__.dmp
  10:	80 91 00 01 	lds	r24, 0x0100	; 0x800100 <_edata>
  14:	90 91 01 01 	lds	r25, 0x0101	; 0x800101 <_edata+0x1>

Now zig + avr-ld:

export var x: u16 = 0;

export fn main () noreturn {
    const ddrb: *u8 = @ptrFromInt(0x24);
    const portb: *u8 = @ptrFromInt(0x25);
    const led_pin: u3 = 5;

    ddrb.* |= (1 << led_pin);               // output
    //portb.* |= (1 << led_pin);            // LED on
    portb.* &= ~(@as(u8,1) << led_pin);     // LED off
    x = 7;
    while (true) {
        x += 1;
        if (0x01 == (x & 0x01)) {
            portb.* |= (1 << led_pin);
        } else {
            portb.* &= ~(@as(u8,1) << led_pin);
        }
    }
}
zig build-obj -O ReleaseSmall -target avr-freestanding-none -mcpu atmega328p test__.zig
avr-ld test__.o -o test__.elf -Tdata 0x800100
avr-objdump -d test__.elf > test__.dmp
$ grep sts test__.dmp
   a:	90 93 01 01 	sts	0x0101, r25	; 0x800101 <_edata+0x1>
   e:	80 93 00 01 	sts	0x0100, r24	; 0x800100 <_edata>

Everything in the garden is lovely.

As someone who has a little experience with Arduino, ESP8266 / ESP32, I’m curious about current state of Zig on those platforms.

Is it usable in practice?
Are there articles on setting up a Zig project for those?

Actually I also do not know much.
I’m just trying to re-implement this thing in Zig, but currently with no any success.
And at the moment I tend to believe, zig/(or llvm?..) is not completely ready for 8-bit AVR.
Yes, you can write something working, but I’m not sure about complex programs.
See also this topic

There is Microzig project, but currently I do not want to use no HALs/frameworks,
so can’t say much about it.

Also, there is an (open) issue
No answer to the question in the most recent comment.

1 Like

Let’s see :slight_smile:
Single step build, i.e. without avr-ld:

LANG=C /opt/zig-0.11/zig build-exe -O ReleaseSmall -target avr-freestanding-none -mcpu atmega328p test__.zig
LLVM Emit Object... LLVM ERROR: Expected a constant shift amount!
Аварийный останов (стек памяти сброшен на диск) // LANG=C does not help here for some reason

Ok, try 0.12:

$ LANG=C /opt/zig-0.12/zig build-exe -O ReleaseSmall -target avr-freestanding-none -mcpu atmega328p test__.zig
LLD Link... warning(link): unexpected LLD stderr:
ld.lld: warning: cannot find entry symbol _start; not setting start address

Ok, let’s rename main to _start:

export var x: u16 = 0;

//export fn main () noreturn {
export fn _start () noreturn {
    const ddrb: *u8 = @ptrFromInt(0x24);
    const portb: *u8 = @ptrFromInt(0x25);
    const led_pin: u3 = 5;

    ddrb.* |= (1 << led_pin);               // output
    //portb.* |= (1 << led_pin);            // LED on
    portb.* &= ~(@as(u8,1) << led_pin);     // LED off
    x = 7;
    while (true) {
        x += 1;
        if (0x01 == (x & 0x01)) {
            portb.* |= (1 << led_pin);
        } else {
            portb.* &= ~(@as(u8,1) << led_pin);
        }
    }
}

Compiles successfully, but:

$ LANG=C avr-objdump -d test__ 

test__:     file format elf32-avr

Disassembly of section .text:

000110d4 <.text>:
   110d4:	25 9a       	sbi	0x04, 5	; 4
   110d6:	2d 98       	cbi	0x05, 5	; 5
   110d8:	87 e0       	ldi	r24, 0x07	; 7
   110da:	90 e0       	ldi	r25, 0x00	; 0
   110dc:	01 96       	adiw	r24, 0x01	; 1
   110de:	90 93 01 21 	sts	0x2101, r25	;  0x802101
   110e2:	80 93 00 21 	sts	0x2100, r24	;  0x802100
   110e6:	95 b1       	in	r25, 0x05	; 5
   110e8:	9f 7d       	andi	r25, 0xDF	; 223
   110ea:	82 95       	swap	r24
   110ec:	80 7f       	andi	r24, 0xF0	; 240
   110ee:	88 0f       	add	r24, r24
   110f0:	80 72       	andi	r24, 0x20	; 32
   110f2:	89 2b       	or	r24, r25
   110f4:	85 b9       	out	0x05, r24	; 5
   110f6:	80 91 00 21 	lds	r24, 0x2100	;  0x802100
   110fa:	90 91 01 21 	lds	r25, 0x2101	;  0x802101
   110fe:	ee cf       	rjmp	.-36     	;  0x110dc

Code in general is ok, but flash addresses are mad, they must start from 0 (RAM addresses are also crazy). Maybe, I am doing something wrong, I do not know…

   110de:	90 93 01 21 	sts	0x2101, r25	;  0x802101
   110e2:	80 93 00 21 	sts	0x2100, r24	;  0x802100

It seems that zig/llvm:

  • implies that a device has 8K of internal SRAM (0x2000 + 0x100 for register file and i/o registers (which is not true in general case)
  • implies that a device has also an external RAM, hence 0x2100

And I have no idea why program memory (flash) cells are enumerated from 0x110d4, and not from zero.

…looks like I won’t be diving into Zig on microcontrollers anytime soon. I simply don’t have time for this now.
Maybe someday…

And rightly so! :slight_smile:
I encountered too many weird things which I do not understand.
… meanwhile I made some progress in porting my demo -
yesterday I had one working interrupt driven state machine (blinker).
But… then I continued with adding other machines (for USART),
uploaded some bad code and… oops, my arduino is a brick again
and this time that trick does not help :frowning: . There are ways to recover
(using external programmer), but I’m lazy to bother with it.

1 Like