Setting pin to high in function instead of main, not full voltage output?
Wow, that's pretty crazy. Those programs are almost identical. Just for easier comparison, the first program, with the assignment in main
, has the assembly (with comments):
0: 20 9a sbi 0x04, 0 ; Set Bit of IO for DDRB
2: 28 9a sbi 0x05, 0 ; Set Bit of IO for PORTB
4: 00 c0 rjmp .+0 ; Relative JuMP of 0 bytes functions as `while(1);`
The second program, with the assignment in a function, has the assembly:
0: 28 9a sbi 0x05, 0 ; Set Bit of IO for PORTB
2: 08 95 ret ; Return to the address on the call stack
4: 20 9a sbi 0x04, 0 ; Set Bit of IO for DDRB
6: 0e 94 00 00 call 0 ; Call the function at address 0 (at top)
a: 00 c0 rjmp .+0 ; Relative JuMP of 0 bytes functions as `while(1);`
The third program does an interesting optimization - it removes the function call completely, inlining it and making this program identical to the first. You could probably get an identical effect with inline void turn_on_led()
. For completeness' sake, the assembly is:
0: 20 9a sbi 0x04, 0 ; Set Bit of IO for DDRB
2: 28 9a sbi 0x05, 0 ; Set Bit of IO for PORTB
4: 00 c0 rjmp .+0 ; Relative JuMP of 0 bytes functions as `while(1);`
The Interrupt Vector Table
The addresses 0
to a
are addresses within the .text
section, not in the program memory. The .text section starts at offset 0x34, per the File off[set]
directive:
Idx Name Size VMA LMA File off Algn
0 .text 00000006 00000000 00000000 00000034 2**0
What's really at addresses 0-34 is (usually) the Interrupt Vector Table. If you had selected the BOOTRST
flag, it would be at the start of the bootloader, but you didn't so it's not. The first element in the Interrupt Vector Table tells the processor where to go on reset or boot-up.
This location should be the first instruction of main
for these programs (0
for the first case, 4
for the second case), but it's possible that it's defaulting to address 0
in the second program, which would set the output high while the data direction register was still set to input per the defaults. This would turn on the weak pullup, and the change of the data direction register later on would have no effect.
I'd guess that this is what's happening. To test whether your issue is that your output is only using the weak pullup, you could remove the DDRB assignment entirely from either program. The result should be a dimly-lit LED. If it's not the same brightness, then this isn't your problem. If it is the same brightness, I'd guess that this is, in fact, your problem.
Alternative explanations, not likely to be the problem (but could be in other situations)
Do you need a delay?
Another hiccup could be the mention in section 11.2 of the datasheet, "Ports as General Digital I/O" that:
As shown in Figure 11-2, the PINxn Register bit and the preceding latch constitute a synchronizer. This is needed to avoid metastability if the physical pin changes value near the edge of the internal clock, but it also introduces a delay. Figure 11-3 shows a timing diagram of the synchronization when reading an externally applied pin value.
Therefore, in every example of reading a pin, there is a null operation during which this synchronizer delay is accounted for. Use __no_operation();
in C for this effect. This need for a delay is very common in embedded systems programming; it's cheaper to make the programmer stick a delay in his code than it is to make some things happen in a single clock cycle.
In your first program, you have no such delay. In your second program, you have a delay. This should cause the first program not to work, but the second program should work. This isn't what's happening, and there's no such delay on the outputs, so I doubt that this is the problem.
Accidental PWM
Your assembly demonstrates that this is not the case, but a common mistake is to forget the while(1)
. This can cause the processor to go into reset immediately after turning the LED on. While the processor is resetting itself and the DDRB register is being set, the LED is off. Then, it's briefly on, and the reset starts all over again. This forms a rudimentary PWM system on accident, causing the LED to appear dim.
However, you do have a while(1)
(note that for(;;)
is a popular equivalent), and it does appear in the assembly as rjmp .+0
, so this doesn't seem to be your problem. I am a little confused by the 0
, rjmp
changes the program counter to PC + k + 1. Usually, we use labels for this when writing in assembly, and this should therefore output a k
of -1, but it seems reasonable to trust that the compiler is doing the right thing here.
However, let's take a better look at the encoding. The hex code for the instruction is 00 c0
. According to the AVR Instruction Set manual, the opcode for rjmp is 1100 kkkk kkkk kkkk
, or, in hex, 0xCK KK, where the concatenation of K
is k, our relative jump. The AVR we're using is little-endian, so 00 C0
as seen in the program is a relative jump (C) to a position 0 bytes away.
According to the operation description, this will perform the operation PC <- PC + 0 + 1, or advance the program counter beyond this address. However, it may be that this isn't the correct interpretation of this operation, I've always used labels when working with this instruction, so the actual number used has been abstracted away by the assembler.
Accusing the compiler of misinterpreting the lines
while(1) {
}
is rather extreme, though. I don't think this is the problem.
Hope this helps!
After reading up on the information Kevin provided in his answer I read a document called "AVR32006 : Getting started with GCC for AVR32". In this document there was a particular section which got me thinking about what Kevin said about the vector table.
The linking process needs information about code and data memory location. Using a linker script provides this. If specifying which device you are compiling code for by using the ?-mpart=? option in avr32-gcc, a default linker script for that device will be used.
So I tried adding the mmcu
flag to the linking command, so that it was changed from:
elf:
avr-gcc example.o -o example.elf
To:
elf:
avr-gcc -mmcu=atmega328 example.o -o example.elf
This proved to be the problem, the vector table was not setup properly and a lot of other parts were missing.
Here is the disassembly after adding the mmcu
flag:
example.elf: file format elf32-avr
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000090 00000000 00000000 00000054 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .stab 000006cc 00000000 00000000 000000e4 2**2
CONTENTS, READONLY, DEBUGGING
2 .stabstr 00000081 00000000 00000000 000007b0 2**0
CONTENTS, READONLY, DEBUGGING
Disassembly of section .text:
00000000 <__vectors>:
0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
10: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
14: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
18: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
1c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
20: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
24: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
28: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
2c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
30: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
34: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
38: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
3c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
40: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
44: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
48: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
4c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
50: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
54: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
58: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
5c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
60: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
00000068 <__ctors_end>:
68: 11 24 eor r1, r1
6a: 1f be out 0x3f, r1 ; 63
6c: cf ef ldi r28, 0xFF ; 255
6e: d8 e0 ldi r29, 0x08 ; 8
70: de bf out 0x3e, r29 ; 62
72: cd bf out 0x3d, r28 ; 61
74: 0e 94 42 00 call 0x84 ; 0x84 <main>
78: 0c 94 46 00 jmp 0x8c ; 0x8c <_exit>
0000007c <__bad_interrupt>:
7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
00000080 <turn_on_pb>:
return 0;
}
void turn_on_pb(void)
{
PORTB |= (1 << PB0);
80: 28 9a sbi 0x05, 0 ; 5
}
82: 08 95 ret
00000084 <main>:
void turn_on_pb(void);
int main(void)
{
DDRB |= (1 << PB0);
84: 20 9a sbi 0x04, 0 ; 4
turn_on_pb();
86: 0e 94 40 00 call 0x80 ; 0x80 <turn_on_pb>
8a: ff cf rjmp .-2 ; 0x8a <main+0x6>
0000008c <_exit>:
8c: f8 94 cli
0000008e <__stop_program>:
8e: ff cf rjmp .-2 ; 0x8e <__stop_program>