ATmega168 asm help needed

I need some help with this one! I am a NewBee in the AVR world, but have been writing machine code since the days of DIGITAL COMPUTER and the PDP8. Remember, — every time you turned the machine on, you needed to toggle in a bootloader. Anyway, the problem at hand. My need is for a 50% duty cycle square wave. Only six (6) cycles, then fall through to a 10 ms delay, and return, forever! How simple could it be! If I use a “for” loop with PORTB |= xxx and PORTB &= ~xxx, It works just fine. No where near the desired 50% duty cycle, but it works. The following snippet of “in line” asm code is intended to mimic the “for” loop, and I should be able to tweak the duty cycle. If I look at the listing, it shows that compiler did exactly what I expected it to do. Then why does it not work at all ???

The processor is a POLOLU Baby “O”, with a Atmega168. I am using a IBM ThinkPad, running WindowsXP, Pro, if that matters!

//ASM test routine.

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

int main(void) {
   DDRB = 0x20;				// Set data direction register,.

  while(1) {

  asm(" ldi r16, 0x20");

  asm("L1:  sbi 3,5");

  asm(" nop");				// Note A
  asm(" nop");
  asm(" nop");
  asm(" nop");

//  asm(" cbi 3,5");			// Note B
//  asm(" sbi 3,5");

  asm(" cbi 3,5");
  asm(" lsr r16");
  asm(" lsr r16");
  asm(" brne L1");

  }								// End of "while".
}								// End of "main"

The intent: To load a single bit in r16, (ldi r16, 0x20), to set PORTB bit 5 high, (sbi 3,5), to set bit 5 low, (cbi 3,5), to shift r16 by one bit, (lsr r16), to branch to L1, until r16 is zero, then exit. HOW SIMPLE. It just WON’T WORK!!! My scope shows only three (3) cycles! Not six, as desired, and, they are not 50% duty cycle, but for now, that is OK, the lsr and btne instructions will take a little extra time. To verify that the code is really manipulating PORTB pin 5, I enable the code shown as note B. Eureka, I now have six cycles. (I should have 12 cycles). Next, I enable the code shown as note A. I would expect to have the high side of the first cycle widened, the high side of the second cycle unchanged, the high side of the third cycle widened, and so on. But NO, ALL the cycles have a widened high side, and by exactly the same amount! HOW CAN THAT BE?


The reason you’re seeing only three cycles instead of six is that you have two lsr calls in a row, so you’re shifting right by two bits every cycle.

This seems really strange to me. There are two things I would suggest:

  1. Run your code using the AVR debugger/simulator and select the “disassembler” option from the View menu. This will essentially let you step through your list file one assembly instruction at a time, and hopefully this will make it clear why you’re seeing both high cycles widened.

  2. Post the relevant portion of your lss file so I can look it over.

- Ben

Edit: Looking over your code more carefully, I may have found part of your problem. You are setting and clearing bits in I/O register 3, which corresponds to PINB. When influencing AVR pin output behavior, you want to use PORTB (I/O register 5). Also, you should insert the line:

#define F_CPU 20000000

at the top of your program before you include <util/delay.h> since delay.h uses F_CPU to produce delay routines that are independent of your system clock.

Dear Ben:

Thank you, Thank you, my problem is completely solved. Some how I had the perception that the PINB register was the best way to manipulate individual pins. Another review of the ATmega168 manual, found a short paragraph addressing the PINB register, and I quote: “Writing a logic one to PINxn Toggles the value of PORTxn”. My code MUST set and clear the level of a pin, never “toggle” it. The final code will be testing an input pin after the bit set instruction.

Thanks again, lonarl

PS: The double lsr instruction you pointed out was a typo! I intended to delete it. Also I have added the #define F_CPU for the benifit of <util/delay.h>.