4
\$\begingroup\$

I have a ATmega328-PU chip which is setup to use the internal oscillator without dividing clock speed by 8. The first thing I did was to use a really simple program I uploaded; a program which turned on and off all pins on PORTB. Like so:

#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/delay.h>

int main(void) {
    DDRB = 0xFF;
        PORTB = 0xFF;

    while(1) {
            PORTB = 0x00;
            _delay_ms(50);
            PORTB = 0xFF;
            _delay_ms(50);
    }

    return 0;
}
  • Update: I have tried what Jon L suggested. Still the same result.
  • Update 2: I have tried what Kevin Vermeer suggested. The chip seems to freeze when it enter the first loop. Using the timer with a value < 65000 makes the LED turn on directly, using a value > 65000 results in the LED never turn on.
  • Update 3: I have tried following with a new identical chip with the same results
  • Update 4: I plugged in simulavr and avr-gdb to see whether I found something, this was the output:

    memory.c:267: WARNING: * * Attempt to write invalid io reg: ADCL at 0x0024 memory.c:267: WARNING: * * Attempt to write invalid io reg: ADCH at 0x0025 memory.c:267: WARNING: * * Attempt to write invalid io reg: ADCH at 0x0025 decoder.h:59: WARNING: Unknown opcode: 0xffff

Then unknown opcode loops forever.

However, when I have uploaded the program it reaches the second instruction in main and then freezes. Leaving all PORTB pins in HIGH. So far I have tried:

  • Different ms between 10 - 1000 to see whether there are some values that can't be used
  • Changed F_CPU to 1000000UL in case it would use CKDIV8.
  • Looping _delay_ms(1) in a separate function until it iterates to the given amount
  • Re-compiled and re-uploaded multiple times
  • Tried resetting multiple times
  • Used different PORTB pins
  • Defined F_CPU from the compiler arguments -DF_CPU=8000000

Why I'm asking here and not stackoverflow.com is because I think I should start to eliminate errors on the lowest level of abstraction, that is; hardware.

So what could be the problem?

Here are some information about my setup:

  • Operating system: OS X 10.7.3
  • Programmer: AVRisp MKII
  • Uploader: avrdude
  • Compiler: avr-gcc
  • Bin 2 hex: avr-objcopy

Fuse settings:

avrdude: safemode: lfuse reads as E2
avrdude: safemode: hfuse reads as D9
avrdude: safemode: efuse reads as 7

Input into AVR fuse calculator:

Fuse settings in AVR fuse calculator

makefile:

main:
        avr-gcc -g -Os -Wall -mmcu=atmega328 -c ../src/example.c

hex:
        avr-objcopy -j .text -j .data -O ihex example.o example.hex

dump:
        avr-objdump -h -S example.o > example.lst

upload:
        avrdude -p m328 -c avrispmkII -P usb -U flash:w:example.hex

clean:
        rm -f *.o
        rm -f *.hex
        rm -f *.lst

Pins used on chip:

  • Pin 7 (VCC): 5 volts supply
  • Pin 8 (GND): ground
  • Pin 14 (PB0): resistor and LED
\$\endgroup\$
2
  • 1
    \$\begingroup\$ ...you also have to make sure you have size optimizations enabled for util/delay.h functions to work correctly. \$\endgroup\$
    – vicatcu
    Commented Feb 23, 2012 at 18:05
  • 1
    \$\begingroup\$ Yes, that's enabled. Look at the makefile under main:, where you can see that the -Os is used. \$\endgroup\$ Commented Feb 23, 2012 at 18:52

3 Answers 3

3
\$\begingroup\$

I suspect the problem is not with your C code but with your Makefile.

The following lines in your Makefile produce an example.o object file.

main:
    avr-gcc -g -Os -Wall -mmcu=atmega328 -c ../src/example.c

The created .o file only contains the symbols and code from example.c, not the additional source required to actually make it run on a target system such as interrupt vector jump tables and code to initialise the BSS RAM segment to zeros, and load your initialised data sections.

You'll need to add an additional line something like this to run the linker and produce an output object suitable for download to the AVR part. Alternatively, use avr-ld, but you'll have to work out all the required linker options.

main.elf: example.o
    avr-gcc example.o -o main.elf

You can use avr-objdump --disassemble-all <filename> on both example.o and main.elf yourself to verify the different content of each file.

It's always a good idea to try to reduce your problem in steps to the most simple example possible. In this case, it would probably mean dropping into the AVR Studio software and creating a project running on the simulator using their managed build process. From there, you could them export the Makefile in use by their build process by using the 'Export Makefile' menu option. The generated makefile could then be compared with your version.

Actually, it's probably a good idea to use a Makefile similar to the one generated by AVR Studio because it has the correct rules already defined, you just have to set up some variables with regard to which objects need to be generated and the final target file name.

\$\endgroup\$
2
  • \$\begingroup\$ This sounds really reasonable! I haven't even begin to think of this. It's the one part I have totally over-looked. I will read up on using the linker and try this out. Thank you for the different approach! :) \$\endgroup\$ Commented Feb 25, 2012 at 12:05
  • 1
    \$\begingroup\$ You, sir, has made my day. It works like expected! I added the elf creation to my make file and ran compilation, elf creation, hex creation and finally upload. \$\endgroup\$ Commented Feb 27, 2012 at 7:52
3
\$\begingroup\$

For your CPU speed:

#define F_CPU 80000000UL

Do you have an extra 0 on there? That reads 80 MHz. I think you want 8 MHz.

EDIT: after checking the datasheet, your lfuse value looks to be OK if you're wanting to use the internal oscillator with CKDIV8 disabled.

\$\endgroup\$
8
  • \$\begingroup\$ Well spotted! Although, if you're right, wouldn't the delay loop take 5s, so you'd see some activity... \$\endgroup\$ Commented Feb 23, 2012 at 17:26
  • \$\begingroup\$ @JobyTaffey, yeah... I'm looking elsewhere for any issues as well. \$\endgroup\$
    – Jon L
    Commented Feb 23, 2012 at 17:28
  • \$\begingroup\$ Thanks for spotting that! Other than that, is there anything I should think of and improve? :) I wanted to try out the chip, but I don't have an external oscillator and caps I can connect to it, that's why I used the internal oscillator. \$\endgroup\$ Commented Feb 23, 2012 at 17:44
  • \$\begingroup\$ @rzetterberg, this is probably redundant, but I noticed in one of my old Makefiles that I had this flag after avr-gcc: "-DF_CPU=8000000" - again, since you have this defined in code I think this is redundant, but I'm trying to remember why I had this in here in the first place... might be worth tacking on. \$\endgroup\$
    – Jon L
    Commented Feb 23, 2012 at 18:00
  • \$\begingroup\$ @Jon L: Alright, I will correct the value and try to supply the frequency both ways and see whether that makes a difference. \$\endgroup\$ Commented Feb 23, 2012 at 18:03
3
\$\begingroup\$

Your program should be able to use _delay_ms(), but there are other ways to do a delay.

_delay_ms() just calls _delay_us(), which calls _delay_loop_1() in delay_basic.h. _delay_loop_1(), as the name implies, simply loops. It uses 3 instructions per loop (for comparisons, increments, and jumps), and the time taken for each instruction is constant and known, so it can delay for a given time simply by running in a loop.

To remove any confusion about your program structure and F_CPU search path, you could re-create the function (in a very rough way) in the main function like this:

#include <stdint.h>  // Typedefs `unsigned long` or something to `uint16_t`,
                     // a 16-bit unsigned number
#include <avr/io.h>
int main(void) {
    uint16_t counter1, counter2;

    DDRB = 0xFF;
    PORTB = 0xFF;

    while(1) {
            PORTB ^= 0; // Use XOR to toggle
            for (counter1 = 0; counter1 < 10; counter1++) { 
                for (counter2 = 0; counter2 < 50000; counter2++) { /* Do nothing */ }
            }
    }

    return 0;
}

The other method is to use the onboard timer peripherals, instead of implementing a timer with a loop. You can use these peripherals in many ways, but I'll describe the two simplest ways below. First, you can poll the timer from a loop, and do something when the value reaches a certain point. Second, you can have the timer issue an interrupt when it reaches a certain point, and have your code do nothing.

To poll from a loop, you'd do something like this:

#include <avr/io.h> 

int main (void) 
{ 
   DDRB = 0xFF; // Set port as output 
   TCCR1B |= _BV(CS10); // Set up timer 

   while(1)    { 
      // Check timer value in if statement, true when count matches 1/20th of a second 
      if (TCNT1 >= 49999) 
      { 
         PORTB ^= 0xFF; // Toggle the port 

         TCNT1 = 0; // Reset timer value 
      } 
   } 
}

To issue an interrupt:

#include <avr/io.h> 
#include <avr/interrupt.h> 

int main (void) 
{ 
   DDRB = 0xFF; // Set LED as output 
   TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode 
   TIMSK |= (1 << OCIE1A); // Enable CTC interrupt 
   sei(); //  Enable global interrupts 
   OCR1A   = 10000; // Set CTC compare value, about 50ms.
   TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 

   while(1) { 
       // Do nothing, the interrupt is taking care of it.
   } 
} 

ISR(TIMER1_COMPA_vect) 
{ 
   PORTB ^= 0xFF; // Toggle the LED 
}

The above code samples were adapted from the excellent timer tutorial on AVR Freaks, also available from Dean's website here in PDF format.

Furthermore, your code (and each of the above samples) toggles the entire port. Your LED is on PB0; you can toggle just this pin by replacing every assignment to PORTB with

PORTB ^= _BV(PB0);

which compares PORTB with only 0x0000 0001.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks for the write up and the alternatives. I will try the different methods tomorrow :) \$\endgroup\$ Commented Feb 23, 2012 at 19:25
  • \$\begingroup\$ +1 for trying to rule out the include path \$\endgroup\$
    – Jon L
    Commented Feb 23, 2012 at 19:25
  • \$\begingroup\$ @Jon - Admittedly, I still included stuff, so all I did was eliminate the CPU speed definition. \$\endgroup\$ Commented Feb 23, 2012 at 19:53
  • \$\begingroup\$ @Kevin Vermeer: I have tried what you suggested, see my edit of the question. About why I turned on all ports at the same time is because I wanted to eliminate the possibility that I had put the LED on the wrong pin. \$\endgroup\$ Commented Feb 24, 2012 at 10:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.