optimal ASM code?

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Iam writing some code in C (WinAVR) for an M328 at 8MHz and I want to test the Analog Comparator output twice in a row and increment a counter if ACO is high each time.

Here is the code that WinAVR created.

260:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+0000059C:   B780        IN        R24,0x30       In from I/O location
+0000059D:   E090        LDI       R25,0x00       Load immediate
+0000059E:   9596        LSR       R25            Logical shift right
+0000059F:   9587        ROR       R24            Rotate right through carry
+000005A0:   9592        SWAP      R25            Swap nibbles
+000005A1:   9582        SWAP      R24            Swap nibbles
+000005A2:   708F        ANDI      R24,0x0F       Logical AND with immediate
+000005A3:   2789        EOR       R24,R25        Exclusive OR
+000005A4:   709F        ANDI      R25,0x0F       Logical AND with immediate
+000005A5:   2789        EOR       R24,R25        Exclusive OR
+000005A6:   2F28        MOV       R18,R24        Copy register
+000005A7:   7021        ANDI      R18,0x01       Logical AND with immediate
261:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005A8:   B600        IN        R0,0x30        In from I/O location
+000005A9:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005AA:   5F2F        SUBI      R18,0xFF       Subtract immediate

This seems to me to be rather complicated, and it takes something like 20 clock cycles in the simulator. Is this as simple as it can be? It strikes me as excessively complicated code! I don't know ASM in the AVR, and have not written ASM on anything in decades. I'd like to get as many tests of ACO done in a 5uS window worst case. Code is compiled "-o3" optimization level.

-Tony

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This ASM code takes 6 clocks (C code equivalent shown for better understanding only):

.def    acCounter = r18                                                             // uint8_t acCounter;
.def    foo       = r24                                                             // uint8_t foo;
.def    bar       = r25                                                             // uint8_t bar;

        in      foo,ACSR     ; Read ACSR to first register                          // foo = ACSR;
        in      bar,ACSR     ; Read ACSR again to second register                   // bar = ACSR;
        and     foo,bar      ; Perform ANDing of two registers                      // foo &= bar;
        andi    foo,_BV(ACO) ; Mask out all bits but ACO                            // foo &= _BV(ACO);
        breq    skip         ; Skip acCounter++ if any of the ACO states read as 0, // if foo {
        subi    acCounter,-1 ; otherwise do acCounter++                             // acCounter++;};
skip:
        ....                 ; Go ahead

But IMO a single clock delay is too little for a comparator debouncing.

Warning: Grumpy Old Chuff. Reading this post may severely damage your mental health.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MBedder wrote:

But IMO a single clock delay is too little for a comparator debouncing.

I agree, that is why I wanted to get in as many samples in the 5us window as possible. There is an 8uS pulse coming into the comparator. I know exactly when it is coming and I need to skip over the first bit of the pulse to avoid transients. So, I delay 3us.

Then I have a 5us or so window to sample. I want as many samples in the window as possible. In the simulator, it looks like the disassembler shown below will have a 12 clock (worst case) first test, followed by 3 clocks each for the subsequent tests. Is this timing that I deduced correct?

If so, then I should be able to get 10 tests done in the 5uS window 8MHz clock = 40 clocks in 5uS. And 12 clocks for the first test and 9*3clocks to follow = 39 clocks.

After the tests, I then will consider the pulse "high" if 70% of the samples are high (i.e. acCounter >= 7).

260:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+0000059C:   B780        IN        R24,0x30       In from I/O location
+0000059D:   E090        LDI       R25,0x00       Load immediate
+0000059E:   9596        LSR       R25            Logical shift right
+0000059F:   9587        ROR       R24            Rotate right through carry
+000005A0:   9592        SWAP      R25            Swap nibbles
+000005A1:   9582        SWAP      R24            Swap nibbles
+000005A2:   708F        ANDI      R24,0x0F       Logical AND with immediate
+000005A3:   2789        EOR       R24,R25        Exclusive OR
+000005A4:   709F        ANDI      R25,0x0F       Logical AND with immediate
+000005A5:   2789        EOR       R24,R25        Exclusive OR
+000005A6:   2F28        MOV       R18,R24        Copy register
+000005A7:   7021        ANDI      R18,0x01       Logical AND with immediate
261:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005A8:   B600        IN        R0,0x30        In from I/O location
+000005A9:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005AA:   5F2F        SUBI      R18,0xFF       Subtract immediate
262:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005AB:   B600        IN        R0,0x30        In from I/O location
+000005AC:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005AD:   5F2F        SUBI      R18,0xFF       Subtract immediate
263:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005AE:   B600        IN        R0,0x30        In from I/O location
+000005AF:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005B0:   5F2F        SUBI      R18,0xFF       Subtract immediate
264:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005B1:   B600        IN        R0,0x30        In from I/O location
+000005B2:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005B3:   5F2F        SUBI      R18,0xFF       Subtract immediate
265:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005B4:   B600        IN        R0,0x30        In from I/O location
+000005B5:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005B6:   5F2F        SUBI      R18,0xFF       Subtract immediate
266:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005B7:   B600        IN        R0,0x30        In from I/O location
+000005B8:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005B9:   5F2F        SUBI      R18,0xFF       Subtract immediate
267:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005BA:   B600        IN        R0,0x30        In from I/O location
+000005BB:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005BC:   5F2F        SUBI      R18,0xFF       Subtract immediate
268:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005BD:   B600        IN        R0,0x30        In from I/O location
+000005BE:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005BF:   5F2F        SUBI      R18,0xFF       Subtract immediate
269:      	if (ACSR & _BV(ACO)) acCounter++;	//if ACO is high
+000005C0:   B600        IN        R0,0x30        In from I/O location
+000005C1:   FC05        SBRC      R0,5           Skip if bit in register cleared
+000005C2:   5F2F        SUBI      R18,0xFF       Subtract immediate
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Here is the code that WinAVR created.

Turn on optimization!
Here's what I get with -Os

int main(void) {

    if (ACSR & _BV(ACO)) acCounter++;
  e2:   00 b6           in      r0, 0x30        ; 48
  e4:   05 fe           sbrs    r0, 5
  e6:   05 c0           rjmp    .+10            ; 0xf2 
  e8:   80 91 00 01     lds     r24, 0x0100
  ec:   8f 5f           subi    r24, 0xFF       ; 255
  ee:   80 93 00 01     sts     0x0100, r24
    if (ACSR & _BV(ACO)) acCounter++;
  f2:   00 b6           in      r0, 0x30        ; 48
  f4:   05 fe           sbrs    r0, 5
  f6:   05 c0           rjmp    .+10            ; 0x102 
  f8:   80 91 00 01     lds     r24, 0x0100
  fc:   8f 5f           subi    r24, 0xFF       ; 255
  fe:   80 93 00 01     sts     0x0100, r24
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If ACO bit of ACSR is set then increase acCounter.Since analog comparator can directly accessed by sbis/sbic commands then

sbic ACSR,ACO;//if ACO bit is low skip the next instruction
inc acCounter
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Since analog comparator can directly accessed by sbis/sbic commands

No, it cannot--it is at I/O address 0x30. Only 0x00-0x1f can be reached.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It can not on m328.I just tested on an already made program of attiny13.

in r16,ACSR
sbrc r16,ACO;//skip next instruction if ACO bit is cleared
inc acCounter
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:
Turn on optimization!
Here's what I get with -Os

Well, I did turn it on, just not to "-Os". I used "-O3". I did not want to optimize for small size, instead I was trying to optimise for speed.

-Tony

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Spamiam wrote:
Is this as simple as it can be?

Sometimes, knowing what the actual hardware application is, can generate alternate solutions or even standard practice solutions that one was unaware of.
In short .... what are you tryin' to do ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you want optimal asm code, then write asm code.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

if your counter is 8bit GCC make this code:

24:         if (ACSR & _BV(ACO)) byte++; 
+00000062:   B600        IN      R0,0x30          In from I/O location
+00000063:   FC05        SBRC    R0,5             Skip if bit in register cleared
+00000064:   5F4F        SUBI    R20,0xFF         Subtract immediate
25:         if (ACSR & _BV(ACO)) byte++; 
+00000065:   B600        IN      R0,0x30          In from I/O location
+00000066:   FC05        SBRC    R0,5             Skip if bit in register cleared
+00000067:   5F4F        SUBI    R20,0xFF         Subtract immediate
26:         if (ACSR & _BV(ACO)) byte++; 
+00000068:   B600        IN      R0,0x30          In from I/O location
+00000069:   FC05        SBRC    R0,5             Skip if bit in register cleared
+0000006A:   5F4F        SUBI    R20,0xFF         Subtract immediate
2

That is hard to make faster! (3clk)

But if the other 7 bits in ACSR don't change you can just add ACSR in a byte and from that find out how many times the bit was 1.(2 clk )

and the fastest will be the IN function on a lot of registers, and then find out how many that have the bit set.(1 clk).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JimK wrote:
Spamiam wrote:
Is this as simple as it can be?

Sometimes, knowing what the actual hardware application is, can generate alternate solutions or even standard practice solutions that one was unaware of.
In short .... what are you tryin' to do ?

I am trying to measure a voltage as accurately as reasonably possible. The voltage occurs as a pulse about 8uS wide. The first little bit of the pulse is not representative of the actual voltage due to switching transients. So, I want to skip over the first 3uS or so of the pulse. Also, there is likely to be noise in the signal

My strategy it to use an linearly increasing reference voltage and the analog comparator. If I know when the reference voltage exceeds the test voltage then I can know the value of the test voltage.

I want to sample the comparator several times during the pulse to try to minimize the effects of noise to prevent a false reading.

-Tony

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

just as a test make a sample buffer of ? 200 bytes with the value of ACSR just to see how the AVR sees the world