Nano clone with ATMega328p wacky shenanigans when programmed with functions

I have a clone nano with a genuine ATmega328p chip. Programming it using the Arduino IDE, all seems to function as it should, yet upon attempting to write to it, using avrdude, a C program which at any point defines a function, the PB5 LED begins to flash in 2 short blinks per second. No such malady is observed should the program merely declare a function, or inline it.

For example, the Arduino IDE can successfully write the following code:

  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

void someFunction() {
  ;;
};

And the same can be accomplished with C:

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    DDRB |= 1 << PB5;
    while (1) {
        PORTB |= 1 << PB5;
        _delay_ms(1000);
        PORTB &= ~(1 << PB5);
        _delay_ms(1000);
    }
}

Yet emulating the former's empty function declaration as shown below, causes the silly blinking again.

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    DDRB |= 1 << PB5;
    while (1) {
        PORTB |= 1 << PB5;
        _delay_ms(1000);
        PORTB &= ~(1 << PB5);
        _delay_ms(1000);
    }
}

void some_function() {
    ;;
}

My first thought is that the bootloader is trying to look for the main and loop functions as in an Arduino program, but is confused by the mangled names or something.

Just for reference, here is how I'm programming the board:

	avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o build/main.o main.c
	avr-gcc -o build/main.bin build/main.o
	avr-objcopy -O ihex -R .eeprom build/main.bin build/main.hex
	sudo avrdude -F -V -c arduino -p ATMEGA328p -P /dev/ttyUSB0 -b 115200 -U flash:w:build/main.hex

Any help would be greatly appreciated, thanks in advance.

Welcome to the forum

Have you looked at what the Arduino hidden main() function does before calling setup() and loop() ?

/*
  main.cpp - Main loop for Arduino sketches
  Copyright (c) 2005-2013 Arduino Team.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <Arduino.h>

// Declared weak in Arduino.h to allow user redefinitions.
int atexit(void (* /*func*/ )()) { return 0; }

// Weak empty variant initialization function.
// May be redefined by variant files.
void initVariant() __attribute__((weak));
void initVariant() { }

void setupUSB() __attribute__((weak));
void setupUSB() { }

int main(void)
{
 init();

 initVariant();

#if defined(USBCON)
 USBDevice.attach();
#endif
 
 setup();
    
 for (;;) {
 loop();
 if (serialEventRun) serialEventRun();
 }
        
 return 0;
}

1 Like

Interesting, I have tried to replicate this using my C build system, with the following code:

#include "include/Arduino.h"
#include "include/HardwareSerial.h"

void loop() {};
void init() {};
void setup() {};

// Declared weak in Arduino.h to allow user redefinitions.
int atexit(void (* /*func*/ )()) { return 0; }

// Weak empty variant initialization function.
// May be redefined by variant files.
void initVariant() __attribute__((weak));
void initVariant() { }

void setupUSB() __attribute__((weak));
void setupUSB() { }

int main(void)
{
    init();

    initVariant();

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }

    return 0;
}

It compiles and uploads fine, although some of the dependencies are C++ so I changed avr-gcc to avr-g++. The same blinking happens again, so it seems that the IDE is doing something that my build system is not. Is there any way to get the C code that Arduino IDE produces, if such a thing even exists? The compiled binary would also be nice to have, if that even exists rather that some crazy hex file which I don't understand. I'd just like to try and compare the exact data which are being sent to the board during programming, to see why it is erroring.

If you turn on verbose output for compilation in the IDE you will see more details of the files produced by the compiler and what is uploaded

By the time you have the bin/hex file there is no such thing as main() or loop(). The bootloader hands control to the sketch by performing a jump to the beginning of the code.

I'm using a Nano with the old bootloader, followed your steps and loaded your first C sketch; that works as expected. Next I repeated the exercise with your second C sketch and have similar (but not the same) behaviour; in my case the LED was off. The reason is more than likely the difference in bootloader, you're using the 'new' bootloader (based on your upload speed).

Next I compared the hex files and they are different; I can not explain why they are different. So I created assembly from each using the command avr-objdump -j .sec1 -d -m avr5 foo.hex that I found in assembly - How do I decompile a .hex file into C++ for Arduino? - Stack Overflow; don't ask to explain

Working C

build_OK/main.hex:     file format ihex


Disassembly of section .sec1:

00000000 <.sec1>:
   0:	25 9a       	sbi	0x04, 5	; 4
   2:	2d 9a       	sbi	0x05, 5	; 5
   4:	2f ef       	ldi	r18, 0xFF	; 255
   6:	83 ed       	ldi	r24, 0xD3	; 211
   8:	90 e3       	ldi	r25, 0x30	; 48
   a:	21 50       	subi	r18, 0x01	; 1
   c:	80 40       	sbci	r24, 0x00	; 0
   e:	90 40       	sbci	r25, 0x00	; 0
  10:	e1 f7       	brne	.-8      	;  0xa
  12:	00 c0       	rjmp	.+0      	;  0x14
  14:	00 00       	nop
  16:	2d 98       	cbi	0x05, 5	; 5
  18:	2f ef       	ldi	r18, 0xFF	; 255
  1a:	83 ed       	ldi	r24, 0xD3	; 211
  1c:	90 e3       	ldi	r25, 0x30	; 48
  1e:	21 50       	subi	r18, 0x01	; 1
  20:	80 40       	sbci	r24, 0x00	; 0
  22:	90 40       	sbci	r25, 0x00	; 0
  24:	e1 f7       	brne	.-8      	;  0x1e
  26:	00 c0       	rjmp	.+0      	;  0x28
  28:	00 00       	nop
  2a:	eb cf       	rjmp	.-42     	;  0x2

Non-working C

build/main.hex:     file format ihex


Disassembly of section .sec1:

00000000 <.sec1>:
   0:	08 95       	ret
   2:	25 9a       	sbi	0x04, 5	; 4
   4:	2d 9a       	sbi	0x05, 5	; 5
   6:	2f ef       	ldi	r18, 0xFF	; 255
   8:	83 ed       	ldi	r24, 0xD3	; 211
   a:	90 e3       	ldi	r25, 0x30	; 48
   c:	21 50       	subi	r18, 0x01	; 1
   e:	80 40       	sbci	r24, 0x00	; 0
  10:	90 40       	sbci	r25, 0x00	; 0
  12:	e1 f7       	brne	.-8      	;  0xc
  14:	00 c0       	rjmp	.+0      	;  0x16
  16:	00 00       	nop
  18:	2d 98       	cbi	0x05, 5	; 5
  1a:	2f ef       	ldi	r18, 0xFF	; 255
  1c:	83 ed       	ldi	r24, 0xD3	; 211
  1e:	90 e3       	ldi	r25, 0x30	; 48
  20:	21 50       	subi	r18, 0x01	; 1
  22:	80 40       	sbci	r24, 0x00	; 0
  24:	90 40       	sbci	r25, 0x00	; 0
  26:	e1 f7       	brne	.-8      	;  0x20
  28:	00 c0       	rjmp	.+0      	;  0x2a
  2a:	00 00       	nop
  2c:	eb cf       	rjmp	.-42     	;  0x4

Please note the difference; there is a ret instruction in the non-working version of the code; so your sketch immediately returns to somewhere (and I have no idea where).

So the big question is why those the compiler generates the different files and I do not know.

Hi @mpp-btw.

You should not just stub out the important init function like this. This is the init function code that is used when you compile your sketch with the Arduino development tools:

https://github.com/arduino/ArduinoCore-avr/blob/1.8.6/cores/arduino/wiring.c#L241-L392

void init()
{
	// this needs to be called before setup() or some functions won't
	// work there
	sei();
	
	// on the ATmega168, timer 0 is also used for fast hardware pwm
	// (using phase-correct PWM would mean that timer 0 overflowed half as often
	// resulting in different millis() behavior on the ATmega8 and ATmega168)
#if defined(TCCR0A) && defined(WGM01)
	sbi(TCCR0A, WGM01);
	sbi(TCCR0A, WGM00);
#endif

	// set timer 0 prescale factor to 64
#if defined(__AVR_ATmega128__)
	// CPU specific: different values for the ATmega128
	sbi(TCCR0, CS02);
#elif defined(TCCR0) && defined(CS01) && defined(CS00)
	// this combination is for the standard atmega8
	sbi(TCCR0, CS01);
	sbi(TCCR0, CS00);
#elif defined(TCCR0B) && defined(CS01) && defined(CS00)
	// this combination is for the standard 168/328/1280/2560
	sbi(TCCR0B, CS01);
	sbi(TCCR0B, CS00);
#elif defined(TCCR0A) && defined(CS01) && defined(CS00)
	// this combination is for the __AVR_ATmega645__ series
	sbi(TCCR0A, CS01);
	sbi(TCCR0A, CS00);
#else
	#error Timer 0 prescale factor 64 not set correctly
#endif

	// enable timer 0 overflow interrupt
#if defined(TIMSK) && defined(TOIE0)
	sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
	sbi(TIMSK0, TOIE0);
#else
	#error	Timer 0 overflow interrupt not set correctly
#endif

	// timers 1 and 2 are used for phase-correct hardware pwm
	// this is better for motors as it ensures an even waveform
	// note, however, that fast pwm mode can achieve a frequency of up
	// 8 MHz (with a 16 MHz clock) at 50% duty cycle

#if defined(TCCR1B) && defined(CS11) && defined(CS10)
	TCCR1B = 0;

	// set timer 1 prescale factor to 64
	sbi(TCCR1B, CS11);
#if F_CPU >= 8000000L
	sbi(TCCR1B, CS10);
#endif
#elif defined(TCCR1) && defined(CS11) && defined(CS10)
	sbi(TCCR1, CS11);
#if F_CPU >= 8000000L
	sbi(TCCR1, CS10);
#endif
#endif
	// put timer 1 in 8-bit phase correct pwm mode
#if defined(TCCR1A) && defined(WGM10)
	sbi(TCCR1A, WGM10);
#endif

	// set timer 2 prescale factor to 64
#if defined(TCCR2) && defined(CS22)
	sbi(TCCR2, CS22);
#elif defined(TCCR2B) && defined(CS22)
	sbi(TCCR2B, CS22);
//#else
	// Timer 2 not finished (may not be present on this CPU)
#endif

	// configure timer 2 for phase correct pwm (8-bit)
#if defined(TCCR2) && defined(WGM20)
	sbi(TCCR2, WGM20);
#elif defined(TCCR2A) && defined(WGM20)
	sbi(TCCR2A, WGM20);
//#else
	// Timer 2 not finished (may not be present on this CPU)
#endif

#if defined(TCCR3B) && defined(CS31) && defined(WGM30)
	sbi(TCCR3B, CS31);		// set timer 3 prescale factor to 64
	sbi(TCCR3B, CS30);
	sbi(TCCR3A, WGM30);		// put timer 3 in 8-bit phase correct pwm mode
#endif

#if defined(TCCR4A) && defined(TCCR4B) && defined(TCCR4D) /* beginning of timer4 block for 32U4 and similar */
	sbi(TCCR4B, CS42);		// set timer4 prescale factor to 64
	sbi(TCCR4B, CS41);
	sbi(TCCR4B, CS40);
	sbi(TCCR4D, WGM40);		// put timer 4 in phase- and frequency-correct PWM mode	
	sbi(TCCR4A, PWM4A);		// enable PWM mode for comparator OCR4A
	sbi(TCCR4C, PWM4D);		// enable PWM mode for comparator OCR4D
#else /* beginning of timer4 block for ATMEGA1280 and ATMEGA2560 */
#if defined(TCCR4B) && defined(CS41) && defined(WGM40)
	sbi(TCCR4B, CS41);		// set timer 4 prescale factor to 64
	sbi(TCCR4B, CS40);
	sbi(TCCR4A, WGM40);		// put timer 4 in 8-bit phase correct pwm mode
#endif
#endif /* end timer4 block for ATMEGA1280/2560 and similar */	

#if defined(TCCR5B) && defined(CS51) && defined(WGM50)
	sbi(TCCR5B, CS51);		// set timer 5 prescale factor to 64
	sbi(TCCR5B, CS50);
	sbi(TCCR5A, WGM50);		// put timer 5 in 8-bit phase correct pwm mode
#endif

#if defined(ADCSRA)
	// set a2d prescaler so we are inside the desired 50-200 KHz range.
	#if F_CPU >= 16000000 // 16 MHz / 128 = 125 KHz
		sbi(ADCSRA, ADPS2);
		sbi(ADCSRA, ADPS1);
		sbi(ADCSRA, ADPS0);
	#elif F_CPU >= 8000000 // 8 MHz / 64 = 125 KHz
		sbi(ADCSRA, ADPS2);
		sbi(ADCSRA, ADPS1);
		cbi(ADCSRA, ADPS0);
	#elif F_CPU >= 4000000 // 4 MHz / 32 = 125 KHz
		sbi(ADCSRA, ADPS2);
		cbi(ADCSRA, ADPS1);
		sbi(ADCSRA, ADPS0);
	#elif F_CPU >= 2000000 // 2 MHz / 16 = 125 KHz
		sbi(ADCSRA, ADPS2);
		cbi(ADCSRA, ADPS1);
		cbi(ADCSRA, ADPS0);
	#elif F_CPU >= 1000000 // 1 MHz / 8 = 125 KHz
		cbi(ADCSRA, ADPS2);
		sbi(ADCSRA, ADPS1);
		sbi(ADCSRA, ADPS0);
	#else // 128 kHz / 2 = 64 KHz -> This is the closest you can get, the prescaler is 2
		cbi(ADCSRA, ADPS2);
		cbi(ADCSRA, ADPS1);
		sbi(ADCSRA, ADPS0);
	#endif
	// enable a2d conversions
	sbi(ADCSRA, ADEN);
#endif

	// the bootloader connects pins 0 and 1 to the USART; disconnect them
	// here so they can be used as normal digital i/o; they will be
	// reconnected in Serial.begin()
#if defined(UCSRB)
	UCSRB = 0;
#elif defined(UCSR0B)
	UCSR0B = 0;
#endif
}

I have managed to get functions working by using a slightly different compile command. It seems to me that avr-gcc was, in my original method unable to correctly link/hexify the objects as the mcu was not known to it. The solution, for the time being, seems to be ensuring the use of -mmcu=atmega328p (or whatever other microcontroller) in the initial avr-gcc compile command. This is likely the difference between what I had been doing in my build process and what the Arduino IDE does, as it specifically requires foreknowledge of the board and, from a brief overlook of it's build process, and a look over the source code, this seems to impact the creation of the hex files in some way.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.