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!
Best Answer
Assumption: As the specific Arduino model has not been specified, using the Arduino Uno to illustrate this answer. The rationale applies identically to the other Arduinos, for their respective pin-outs and microcontroller operating voltages.
Please refer to this pin-out diagram for the Arduino Uno: (source)
Now let us examine the alternative possibilities:
Having said all that, if for whatever reason Pin 11 no longer performs input or output, the MCU's corresponding internal protection circuitry is irrevocably damaged. There is no way to repair this. This has been covered well in the answer by Manishearth.
Consider yourself fortunate that the entire microcontroller did not get destroyed, and re-code your applications to not use Pin 11 any more.
Personal tip: I've blocked the VIN sockets on my Arduino boards a long time ago by sticking some stripped insulation into them, to avoid ever accidentally exposing any jumper wire to that voltage. If I ever actually need to use VIN some day, I'll spend a delightful hour struggling to extract that bit of insulation stuck in there.