regarding c and assembly coding for avr

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

Hello dear friends,

I would like to ask you a crucial for me question:

I am currently working on my thesis in embedded systems.
I am designing a wireless system that is based on ATMEGA161 and Bluetooth technology for re/programming of FPGAs.

Initially I have started coding in Assemble in the AVR studio.
However, I just saw the advantages of coding in C.

Which language is better for coding embedded applications?
C or Assembly?

And why?What are the advantages of each language on the boundaries of embedded technology?

thank you a lot
regards,
konstantinos kazakos

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

This has been discussed many times. Basically:
C - faster to code, not as efficient as ASM, code may be larger. May be portable to other cpus.
ASM - harder to code, more error prone.

What is better? Depends on what you call 'better'. If you have a small micro and need to squeeze as much functionality into it, them asm may be the solution, otherwise programmer productivity is much better using C, but the compiler may not be as smart as an ASM programmer so the code generated by the c compiler may be larger and/or slower. I dare say you'll find most professionals will use C and only resort to hand coded asm in special circumstances.

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

C is 10 times more efficient to write and 100 times more efficient to maintain. The AVR was co-designed with C compiler experts making it ideal for C. This is a religious topic and the assembler guys are all going to Hell. Also search on 'versus'.

Smiley

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

Quote:
This is a religious topic and the assembler guys are all going to Hell.

I represent that remark :twisted:

---
Formerly Torby. Stitch626 just seemed a more descriptive nicname.

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

Never ask a salmon fisherman ... "which is best - trout or salmon?"

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

Assembly in avr studio is best. C will ravage your stack and flash program space and all the other resources a chip might have. But hey, its a little easier.

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

outer_space,

Quote:
C will ravage your stack and flash program space and all the other resources a chip might have.

How do you figure that? Aren't they the same instructions? I mean, doesn't assembly language and C use the same instruction set and the same hardware?

Show us your stuff! Give us an example of "Raveged" FLASH, and other resources using the C language. I'm sure that for any example you dare to show, someone else profecient in the C language will "Un-Ravage" it. If the C language is "Ravaging" it's only because the programmer doesn't follow proven methods and practices. But that's true of the assembly language programmer too!

Actually, Just show us some of your syphisticated assembly programming and let us be the judge of it's elegance.

And, just maybe it has absolutely nothing to do with "Easy", "Ravaging" or effeciency... Maybe it only has to do with personal preference, convienience, and plain old lazyness! For me it's a little bit of all three.

This whole assembly/C argument is just really pretty stupid - kinda like in amateur radio and the continuous argument about morse code.

It's really, really simple! If it suites you, do it!!!

So, enlighten us!!!

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

And,

Maybe is stead of the assembly people pounding the snot out of the C guys, and the C guys pounding the sont out of the assembly guys, how about a fair, honest and practicle representation of the pro's and con's of both. Now that would be enlightenment!!!

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

hello dear friends,

I absolutely agree with microcarl.
Can't we stress out the pros and cons of assembly and c?
I am a newbie in the area and i am asking from pure curiosity.

regards,
kitsos

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

If I were in Konstantinos's shoes, I would write in C rather than assembler wherever I can. The most important goals of doing a thesis, are 1) to finish it and 2) to learn ideas and methods as you do that you can apply in your later work.

You can crank out working, maintainable code so much faster in C than assembler, you will almost laugh yourself silly. You can also port your code to another target should you ever really get stuck. (Read about and practice writing portable code: you'll be amazed when that will help you!) That will help you finish your thesis sooner. In some departments it may help you finish.

Although it has its blemishes and after 30 years we should probably be using something better, C has many good ideas in it that you will enjoy learning and that will make you a better programmer, whether you end up writing C or assembler. C is very much like a high-level macroassembler for a clean, orthogonal machine, with user defined data types and automatic register allocation that make programming so much faster and easier. Although I learned assembler first and then switched to C -- What? No coroutines? No multiple-entry functions? -- I believe you will learn a lot of the most important bits of writing assembler if you use a C compiler, try to understand what it is doing and study its generated assembler code. A good compiler distills and expresses the thinking of the experienced programmers who wrote it.

Anyway, just my two cents worth. Good luck on your thesis project and thesis!

- John

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

Either way, asking 'c OR assembly' is like looking at an M10 bolt and wondering how to drive it in with your hammer. There IS the one tool to bring them all together, and that's the linker.

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

I believe you neglect a purist who might do everything himself and write object code. I am not sure how to extend your metaphor.

- John

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

To tell you the truth, I just use whatever tool I'm most familiar with that will get the job done.

---
Formerly Torby. Stitch626 just seemed a more descriptive nicname.

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

outer_space wrote:
Assembly in avr studio is best. C will ravage your stack and flash program space and all the other resources a chip might have. But hey, its a little easier.

Your reputation as a troll is secure. Do you actually have evidence for >anything< you say?

Smiley

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

microcarl wrote:
And,

Maybe is stead of the assembly people pounding the snot out of the C guys, and the C guys pounding the sont out of the assembly guys, how about a fair, honest and practicle representation of the pro's and con's of both. Now that would be enlightenment!!!

I'm not sure about fair or enlighened, but just search on 'vs' or 'versus' and you'll find dozens of extensive and detailed threads on this that have gone before.

It is getting frustrating and probably needs a sticky titled "Before asking about C versus Assembly READ THIS"

I would like to just ignore these threads, but I'm afraid newbies will get misinformed so here we go again, just like last week and next week.

Smiley

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

if C is so good then how come something so simple creates such a mess?

SIGNAL(SIG_ADC)
{
return;
}

Here is an excerpt from the lss file for this 1 command irq-

SIGNAL(SIG_ADC){
 2ce:	1f 92       	push	r1
 2d0:	0f 92       	push	r0
 2d2:	0f b6       	in	r0, 0x3f	; 63
 2d4:	0f 92       	push	r0
 2d6:	11 24       	eor	r1, r1
 2d8:	cf 93       	push	r28
 2da:	df 93       	push	r29
 2dc:	cd b7       	in	r28, 0x3d	; 61
 2de:	de b7       	in	r29, 0x3e	; 62
 2e0:	df 91       	pop	r29
 2e2:	cf 91       	pop	r28
 2e4:	0f 90       	pop	r0
 2e6:	0f be       	out	0x3f, r0	; 63
 2e8:	0f 90       	pop	r0
 2ea:	1f 90       	pop	r1
 2ec:	18 95       	reti

000002ee <__vector_11>:
	return;
}

How complicated is this in assembly?

ADC_Irq:
reti

Clearly, one can see that using C is abusive and exploitive of your microcontroller resources. In assembly, this irq would execute in 4 instruction cycles while using 2 stack bytes. In C the same exact thing uses 19 instructions and 7 stack bytes. Both versions take the same amount of effort to code (one could argue assembly takes less effort) I could go on and on with examples like this.

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

outer_space wrote:
Clearly, one can see that using C is abusive and exploitive of your microcontroller resources. In assembly, this irq would execute in 4 instruction cycles while using 2 stack bytes. In C the same exact thing uses 19 instructions and 7 stack bytes. Both versions take the same amount of effort to code (one could argue assembly takes less effort) I could go on and on with examples like this.

You can always pick tiny examples to show a point, but lets look at a full blown function, and just for >you< outer_space, I'll even use a real interrupt doing something real in the real world not something TOTALLY STUPID like doing freaking nothing as in your example:

C versus Assembly

Let’s look at the AVR Butterfly code for handing the joystick, which is done by generating an interrupt on the state change of any pin connected to the joystick. Look at the C version and the Assembly version, then think about how long it might take to write each, and once written, how easy it would be to maintain in 6 months when you’ve forgotten what you did. You may get some insight into why I usually assert that C is 10 times more efficient to program and 100 times more efficient to maintain.

First the C code:

SIGNAL(SIG_PIN_CHANGE1)
{
    PinChangeInterrupt();
}

/*****************************************************************************
*
*   Function name : PinChangeInterrupt
*
*   Returns :       None
*
*   Parameters :    None
*
*   Purpose :       Check status on the joystick
*
*****************************************************************************/
void PinChangeInterrupt(void)
{
    char buttons;

    char key;

/*
    Read the buttons:

    Bit             7   6   5   4   3   2   1   0
    ---------------------------------------------
    PORTB           B   A       O
    PORTE                           D   C
    ---------------------------------------------
    PORTB | PORTE   B   A       O   D   C
    =============================================
*/


    buttons = (~PINB) & PINB_MASK;
    buttons |= (~PINE) & PINE_MASK;

    // Output virtual keys
    if (buttons & (1<<BUTTON_A))
        key = KEY_PLUS;
    else if (buttons & (1<<BUTTON_B))
        key = KEY_MINUS;
    else if (buttons & (1<<BUTTON_C))
        key = KEY_PREV;
    else if (buttons & (1<<BUTTON_D))
        key = KEY_NEXT;
    else if (buttons & (1<<BUTTON_O))
        key = KEY_ENTER;
    else
        key = KEY_NULL;

    
    if(key != KEY_NULL)
    {
        if(gButtonTimeout)  // set in the LCD_SOF_interrupt in LCD_driver.c
        {
            if (!KEY_VALID)
            {
                KEY = key;          // Store key in global key buffer
                KEY_VALID = TRUE;
            }

         gButtonTimeout = FALSE;
    
        }
    }
    
    EIFR = (1<<PCIF1) | (1<<PCIF0);     // Delete pin change interrupt flags

    gPowerSaveTimer = 0;                // Reset the Auto Power Down timer
    
}

Now let’s see the same thing in assembly generated by the GCC compiler, noting that I added comments similar to those in the C function to help understand what is going on.

.Lscope1:
.global	__vector_3
__vector_3:
.LM17:				;Pin change interrupt 
	push __zero_reg__
	push __tmp_reg__
	in __tmp_reg__,__SREG__
	push __tmp_reg__
	clr __zero_reg__
	push r18
	push r19
	push r20
	push r21
	push r22
	push r23
	push r24
	push r25
	push r26
	push r27
	push r30
	push r31
.LM18:
	call PinChangeInterrupt
	pop r31
	pop r30
	pop r27
	pop r26
	pop r25
	pop r24
	pop r23
	pop r22
	pop r21
	pop r20
	pop r19
	pop r18
	pop __tmp_reg__
	out __SREG__,__tmp_reg__
	pop __tmp_reg__
	pop __zero_reg__
	reti

.Lscope2:
.global	PinChangeInterrupt
PinChangeInterrupt:
.LM20:
.LM21:
.LBB2:
	in r18,35-0x20
	com r18
	andi r18,lo8(-48)
.LM22:
	in r24,44-0x20
	com r24
	andi r24,lo8(12)
	or r18,r24
    
.LM23:				;if (buttons & (1<<BUTTON_A)) -- Output virtual keys
	mov r24,r18
	clr r25
	sbrs r24,6
	rjmp .L5     
.LM24:				;key = KEY_PLUS;
	ldi r25,lo8(4)
	rjmp .L6
.L5:				;else if (buttons & (1<<BUTTON_B))  
.LM25:
	sbrs r18,7
	rjmp .L7      
.LM26:				;key = KEY_MINUS;
	ldi r25,lo8(5)
	rjmp .L6
.L7:
.LM27:				;else if (buttons & (1<<BUTTON_C))
	sbrs r24,2
	rjmp .L9     
.LM28:				;key = KEY_PREV;
	ldi r25,lo8(3)
	rjmp .L6
.L9:   
.LM29:				;else if (buttons & (1<<BUTTON_D))
	sbrs r24,3
	rjmp .L11     
.LM30:				;key = KEY_NEXT;
	ldi r25,lo8(2)
	rjmp .L6
.L11:				;else if (buttons & (1<<BUTTON_O)) 
.LM31:
	sbrs r24,4
	rjmp .L13       
.LM32:				;key = KEY_ENTER;
	ldi r25,lo8(1)
	rjmp .L6
.L13:        
.LM33:				;else -- key = KEY_NULL;
	ldi r25,lo8(0)
.L6:     
.LM34:				;if(key != KEY_NULL)
	tst r25
	breq .L15
.LM35:				;gButtonTimeout is set in the LCD_SOF_interrupt
	lds r24,gButtonTimeout
	tst r24
	breq .L15             
.LM36:				;if (!KEY_VALID)
	lds r24,KEY_VALID
	tst r24
	brne .L17                
.LM37:				;KEY = key --Store key in global key buffer
	sts KEY,r25            
.LM38:				;KEY_VALID = TRUE;
	ldi r24,lo8(1)
	sts KEY_VALID,r24
.L17:				;gButtonTimeout = FALSE;
.LM39:
	sts gButtonTimeout,__zero_reg__
.L15:
.LM40:				;Delete pin change interrupt flags
	ldi r24,lo8(-64)
	out 60-0x20,r24
.LM41:				;Reset the Auto Power Down timer
	sts gPowerSaveTimer,__zero_reg__
.LM42:
.LBE2:
	ret

Now picture writing, not just this simle function but the whole massive Butterfly application in assembly.

Smiley

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

I think its common and natural to use adc_irq: reti, I use it in every project that uses the adc because thats how you use 'noiseless' mode. Youre comparing C code to C generated assembly. Hand generated assembly with smart use of macros, labels, and relative jumps is beautiful to look at, easy to understand, and highly re-useable. Since I work in both assembly and C, I can post examples that were genuinely written in assembly.

Here is an example of a full-blown irq. Its purpose is to serially multiplex an 8 digit segment display.

SIGNAL(SIG_OVERFLOW0){
	unsigned char sbyte = (cath<<4)|digit[cath];
	for(int i = 7; i>=0; i--){
		unsigned char serdata = (sbyte>>i)&(0x01);
		PORTC &= ~(1<<SER);	//clear pin with and
		PORTC |= (serdata<<SER);		//set pin with or
		PORTC &= ~(1<<CLOCK);	//clear clock pin
		PORTC |= (1<<CLOCK);	//clock in the data
	}
	PORTC &= ~(1<<CLOCK);	//clear clock pin
	PORTC |= (1<<CLOCK);	//1 extra clock needed to latch data
	cath++;
	if (cath==8){
		cath=0;
	}
	return;
}

Here is the assembly version

.macro M_8digit_led	
lly digits_bcd 			; macro for load literal Y
	add YL, digits_pointer
	adc YH, zero 		; offset Y index
mov outbyte, digits_pointer
swap outbyte			; cathode in high nib
ld r17, Y 				; retreive digit value
or outbyte, r17			; put bcd value in low nib

ldi r17, 0x08			; for i = 8, i > 0, i--
seroutloop:
	cbi portb, dat_out	; pre-clear data pin
	tst outbyte
	brpl datlow 		; branch if bit 7 is 0
	sbi portb, dat_out
	datlow:
	cbi portb, clk_out
	sbi portb, clk_out	; clock in bit
	lsl outbyte			; rotate to next bit
	dec r17
	brne seroutloop		; end of loop

cbi portb, clk_out
sbi portb, clk_out	; this clocks the latch
inc digits_pointer
andi digits_pointer, 0b00000111 ; reset at 8
.endmacro

Assigning this to timer0 is as easy as-

timer0: M_8digit_led
reti

Now lets discuss the C compiler version of this assembly:

SIGNAL(SIG_OVERFLOW0){
 2ee:	1f 92       	push	r1
 2f0:	0f 92       	push	r0
 2f2:	0f b6       	in	r0, 0x3f	; 63
 2f4:	0f 92       	push	r0
 2f6:	11 24       	eor	r1, r1
 2f8:	2f 93       	push	r18
 2fa:	3f 93       	push	r19
 2fc:	8f 93       	push	r24
 2fe:	9f 93       	push	r25
 300:	ef 93       	push	r30
 302:	ff 93       	push	r31
 304:	cf 93       	push	r28
 306:	df 93       	push	r29
 308:	cd b7       	in	r28, 0x3d	; 61
 30a:	de b7       	in	r29, 0x3e	; 62
 30c:	24 97       	sbiw	r28, 0x04	; 4
 30e:	de bf       	out	0x3e, r29	; 62
 310:	cd bf       	out	0x3d, r28	; 61
	unsigned char sbyte = (cath<<4)|digit[cath];
 312:	80 91 7a 00 	lds	r24, 0x007A
 316:	99 27       	eor	r25, r25
 318:	9c 01       	movw	r18, r24
 31a:	22 95       	swap	r18
 31c:	32 95       	swap	r19
 31e:	30 7f       	andi	r19, 0xF0	; 240
 320:	32 27       	eor	r19, r18
 322:	20 7f       	andi	r18, 0xF0	; 240
 324:	32 27       	eor	r19, r18
 326:	80 91 7a 00 	lds	r24, 0x007A
 32a:	99 27       	eor	r25, r25
 32c:	fc 01       	movw	r30, r24
 32e:	e0 5a       	subi	r30, 0xA0	; 160
 330:	ff 4f       	sbci	r31, 0xFF	; 255
 332:	80 81       	ld	r24, Z
 334:	82 2b       	or	r24, r18
 336:	89 83       	std	Y+1, r24	; 0x01
	for(int i = 7; i>=0; i--){
 338:	87 e0       	ldi	r24, 0x07	; 7
 33a:	90 e0       	ldi	r25, 0x00	; 0
 33c:	8a 83       	std	Y+2, r24	; 0x02
 33e:	9b 83       	std	Y+3, r25	; 0x03
 340:	8a 81       	ldd	r24, Y+2	; 0x02
 342:	9b 81       	ldd	r25, Y+3	; 0x03
 344:	99 23       	and	r25, r25
 346:	2c f1       	brlt	.+74     	; 0x392
		unsigned char serdata = (sbyte>>i)&(0x01);
 348:	89 81       	ldd	r24, Y+1	; 0x01
 34a:	99 27       	eor	r25, r25
 34c:	0a 80       	ldd	r0, Y+2	; 0x02
 34e:	02 c0       	rjmp	.+4      	; 0x354
 350:	95 95       	asr	r25
 352:	87 95       	ror	r24
 354:	0a 94       	dec	r0
 356:	e2 f7       	brpl	.-8      	; 0x350
 358:	81 70       	andi	r24, 0x01	; 1
 35a:	8c 83       	std	Y+4, r24	; 0x04
		PORTC &= ~(1<<SER);	//clear pin with and
 35c:	80 91 35 00 	lds	r24, 0x0035
 360:	8e 7f       	andi	r24, 0xFE	; 254
 362:	80 93 35 00 	sts	0x0035, r24
		PORTC |= (serdata<<SER);		//set pin with or
 366:	90 91 35 00 	lds	r25, 0x0035
 36a:	8c 81       	ldd	r24, Y+4	; 0x04
 36c:	89 2b       	or	r24, r25
 36e:	80 93 35 00 	sts	0x0035, r24
		PORTC &= ~(1<<CLOCK);	//clear clock pin
 372:	80 91 35 00 	lds	r24, 0x0035
 376:	8d 7f       	andi	r24, 0xFD	; 253
 378:	80 93 35 00 	sts	0x0035, r24
		PORTC |= (1<<CLOCK);	//clock in the data
 37c:	80 91 35 00 	lds	r24, 0x0035
 380:	82 60       	ori	r24, 0x02	; 2
 382:	80 93 35 00 	sts	0x0035, r24
 386:	8a 81       	ldd	r24, Y+2	; 0x02
 388:	9b 81       	ldd	r25, Y+3	; 0x03
 38a:	01 97       	sbiw	r24, 0x01	; 1
 38c:	8a 83       	std	Y+2, r24	; 0x02
 38e:	9b 83       	std	Y+3, r25	; 0x03
 390:	d7 cf       	rjmp	.-82     	; 0x340
	}
	PORTC &= ~(1<<CLOCK);	//clear clock pin
 392:	80 91 35 00 	lds	r24, 0x0035
 396:	8d 7f       	andi	r24, 0xFD	; 253
 398:	80 93 35 00 	sts	0x0035, r24
	PORTC |= (1<<CLOCK);	//1 extra clock needed to latch data
 39c:	80 91 35 00 	lds	r24, 0x0035
 3a0:	82 60       	ori	r24, 0x02	; 2
 3a2:	80 93 35 00 	sts	0x0035, r24
	cath++;
 3a6:	80 91 7a 00 	lds	r24, 0x007A
 3aa:	8f 5f       	subi	r24, 0xFF	; 255
 3ac:	80 93 7a 00 	sts	0x007A, r24
	if (cath==8){
 3b0:	80 91 7a 00 	lds	r24, 0x007A
 3b4:	88 30       	cpi	r24, 0x08	; 8
 3b6:	11 f4       	brne	.+4      	; 0x3bc
		cath=0;
 3b8:	10 92 7a 00 	sts	0x007A, r1
 3bc:	24 96       	adiw	r28, 0x04	; 4
 3be:	f8 94       	cli
 3c0:	de bf       	out	0x3e, r29	; 62
 3c2:	cd bf       	out	0x3d, r28	; 61
 3c4:	df 91       	pop	r29
 3c6:	cf 91       	pop	r28
 3c8:	ff 91       	pop	r31
 3ca:	ef 91       	pop	r30
 3cc:	9f 91       	pop	r25
 3ce:	8f 91       	pop	r24
 3d0:	3f 91       	pop	r19
 3d2:	2f 91       	pop	r18
 3d4:	0f 90       	pop	r0
 3d6:	0f be       	out	0x3f, r0	; 63
 3d8:	0f 90       	pop	r0
 3da:	1f 90       	pop	r1
 3dc:	18 95       	reti

Its a mess!

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

Quote:
You can also port your code to another target should you ever really get stuck.

About two yeas ago (maybe a little less) I was working on a stepper motor controller for my table-top mill. I was using the MC68HC11 and the ICC11 C compiler. Then I discovered the AVR and fell in love with it. I purchased a Mega8535 and the ICCAVR compiler. While waiting for their delivery, I re-designed the stepper motor controller board using the Mega8535. When everything arrived and the board was assembled, it took about an hour to fully port the code over to the Mega8535.

I have a lot of code written in assembly for the MC68HC11. It would take me a really long time to move that code over to the AVR using assembly.

To me, thats a huge advantage.

Quote:
outer_space wrote:
Assembly in avr studio is best. C will ravage your stack and flash program space and all the other resources a chip might have. But hey, its a little easier.

Your reputation as a troll is secure. Do you actually have evidence for >anything< you say?

Hense, my previous flame!

Quote:
I would like to just ignore these threads, but I'm afraid newbies will get misinformed so here we go again, just like last week and next week.

And I guess that is why the continual debat about which is better must go on.

Knowing the pro's and con's between assembler language and C is something that really only comes from experience. Like learning mathamitics, this is a participation sport! It comes with repetition and practice.

But then too, if the proponent/oponent doesn't know what the difference is between a "Do LOOP" and a "WHILE LOOP" and other basic programming constructs and concepts, how could that individual possibly differentiate the pro's and con's between the two languages - except that someone guide them.

outer_space, with reverence to your little deception below:

Quote:
Clearly, one can see that using C is abusive and exploitive of your microcontroller resources. Here is an excerpt from the lss file for this 1 command irq- Code: SIGNAL(SIG_ADC){ 2ce: 1f 92 push r1 2d0: 0f 92 push r0 2d2: 0f b6 in r0, 0x3f ; 63 2d4: 0f 92 push r0 2d6: 11 24 eor r1, r1 2d8: cf 93 push r28 2da: df 93 push r29 2dc: cd b7 in r28, 0x3d ; 61 2de: de b7 in r29, 0x3e ; 62 2e0: df 91 pop r29 2e2: cf 91 pop r28 2e4: 0f 90 pop r0 2e6: 0f be out 0x3f, r0 ; 63 2e8: 0f 90 pop r0 2ea: 1f 90 pop r1 2ec: 18 95 reti 000002ee <__vector_11>: return; } How complicated is this in assembly? Code: ADC_Irq: reti

And the deception?

ADC_Irq: 
reti 

Surely, this can't be a fair representation as, you've shown no code addressing any registers that need to be presurved. Where do you set the MUX channel? Where do you input the ADC value? Where do you save the ADC value so that it's accessable after you return from the intrrupt? And, where do you recover the preserved registers?

And I understand that you may have enough registers to preserve things, but you've done nothing functional!

All you have shown us is a label and a return from intrrupt! Wheres the code doing the work?

What you'e shown us is nothing but a "Do Nothing" piece of code.

Can you actually even program in assembly language?

This apears to be nothing more then a piece of "Shuck & Jive"!!!

And be advised, I hold no claim to being an AVR assembly language expert! But something is deffenitly not as it apears here!

Maybe you can explaine to us how your "Two Liner" mystery works!!!

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

ADC_Irq: reti isn't just a 'do nothing' code it is how you handle a 'noiseless' adc read. The theory is that you put the chip to sleep and that automatically starts the adc conversion. It will trigger the ADC_Irq interrupt when it wakes up when the conversion is complete. All the extraneous code the C version uses will reduce the sampling frequency for no reason!

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

If I'm the big ugly assembly troll then where havn't I provided examples? I'll talk about assembly vs C whenever you want and come up with examples too.

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

Smiley beat me to it...

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

Its interesting that the C proponents are also the Conservative proponents.

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

outer_space wrote:
If I'm the big ugly assembly troll then where havn't I provided examples? I'll talk about assembly vs C whenever you want and come up with examples too.

Hey, I never said you were either big or ugly. I did show evidence, and surprisingly, so did you (finally) and now a newbie can look at your 'elegant' assembly code and compare it to some C and judge for herself what she wants to learn first. Even though your C example is a bit dense, I'm sure she will be able to get an idea of what it does, and maybe think about how hard it might be to learn C versus learning assembly.

Your comparison of your assembly code to the compiler generated code is a very good idea, and I want to spend some time looking into this as I would like to see what various opitmizations do. I don't think the compiler is doing all those pushes and pops just because it is lazy or stupid. I think that the compiler has either been set in a non-optimal mode or that it 'knows' that those registers need to be protected from being stepped on. Your use of registers without pushing and poping them indicates that you know that it doesn't matter if they get stepped on and is a good example of human optimization. But I want to take more time to look at this, since finally you are showing some evidence and that alone deserves attention.

Smiley.

Last Edited: Sat. Oct 22, 2005 - 05:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

outer_space wrote:
Its interesting that the C proponents are also the Conservative proponents.

:lol: And I would have expected the exact opposite!

Smiley

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

I know my examples show how C code compiles into bloated and wasteful assembly, maybe I'm just a bad C coder. Maybe one of the C proponents could rewrite SIGNAL(SIG_OVERFLOW0) to show that C isnt such a mess after all? Or else we could let the record stand that assembly is better than C after all.

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

outer_space,

Quote:
Here is an example of a full-blown irq. Its purpose is to serially multiplex an 8 digit segment display.

Code: 
SIGNAL(SIG_OVERFLOW0){ 
   unsigned char sbyte = (cath<<4)|digit[cath]; 
   for(int i = 7; i>=0; i--){ 
      unsigned char serdata = (sbyte>>i)&(0x01); 
      PORTC &= ~(1<<SER);   //clear pin with and 
      PORTC |= (serdata<<SER);      //set pin with or 
      PORTC &= ~(1<<CLOCK);   //clear clock pin 
      PORTC |= (1<<CLOCK);   //clock in the data 
   } 
   PORTC &= ~(1<<CLOCK);   //clear clock pin 
   PORTC |= (1<<CLOCK);   //1 extra clock needed to latch data 
   cath++; 
   if (cath==8){ 
      cath=0; 
   } 
   return; 
} 

The C code is truely beautiful and, understandable. It is a stangard language, universal acrocc many platforms. It is understood in short order.

Quote:
Here is the assembly version

Code: 
.macro M_8digit_led    
lly digits_bcd          ; macro for load literal Y 
   add YL, digits_pointer 
   adc YH, zero       ; offset Y index 
mov outbyte, digits_pointer 
swap outbyte         ; cathode in high nib 
ld r17, Y             ; retreive digit value 
or outbyte, r17         ; put bcd value in low nib 

ldi r17, 0x08         ; for i = 8, i > 0, i-- 
seroutloop: 
   cbi portb, dat_out   ; pre-clear data pin 
   tst outbyte 
   brpl datlow       ; branch if bit 7 is 0 
   sbi portb, dat_out 
   datlow: 
   cbi portb, clk_out 
   sbi portb, clk_out   ; clock in bit 
   lsl outbyte         ; rotate to next bit 
   dec r17 
   brne seroutloop      ; end of loop 

cbi portb, clk_out 
sbi portb, clk_out   ; this clocks the latch 
inc digits_pointer 
andi digits_pointer, 0b00000111 ; reset at 8 
.endmacro 

This much more compact then the C translation that you posted, without doubt. An advantage, yes!

But it is specific to one family of controllers. It can not be easily proted without a major re-write.
It assuredly took much longer to write then the C version.
It is harder to decifer and understand.
It is harder to maintain.

Yes, it saves code space! Yes it is faster at execution time! But if I were paying you to code for me, You'd be using C or some other high level language, as the cost of your time for the little code space and cycles gained, just would not be "Value Added" As someone paying your wages, I would need "The biggest gain for the buck!" I could get!

If that means you'd use a Mega64 or Mega128, rather then a Mega32, to accomodat this phylosiphy, that is what you would do.

My whole point is, assembly language just isn't cost effective, when your time cost me money!

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

outer_space wrote:
I know my examples show how C code compiles into bloated and wasteful assembly, maybe I'm just a bad C coder. Maybe one of the C proponents could rewrite SIGNAL(SIG_OVERFLOW0) to show that C isnt such a mess after all? Or else we could let the record stand that assembly is better than C after all.

You haven't shown that assembly is better than C. You've shown one function you wrote in C and assembly. If your assertion is correct, why did the professionals at Atmel write the Butterfly code in C rather than assembly? Do you think it is it because they aren't as good an assembly coder as you are? Or may I be allowed to speculate that it has something to do with the bottom line, as in dollars (or kroners or euros or whatever) per hour that the programmers were being paid and somebody likely smarter than either one of us decided that it would be quicker and thus cheaper to do it in C?

Anyway I gotta go and I'll look at your code in more detail later, I'm still not convinced that you haven't made my point for me.

Smiley

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

My philosophy is to use the cheapest controller that can get the job done. Each penny saved on the processor will save $10,000 per million units. If $0.25 is saved by writing in assembly on a cheaper controller, thats $250,000 saved per million units. Sure you can't port the code as easy, but you have a nice blueprint and porting from assembly to another assembly is a lot faster than writing new assembly from scratch. If we are talking about using the ATMEGA8 I could say hey boss lets switch to assembly and the ATMEGA48 it will take two more days of work, end up running faster, and most importantly to him, save money.

Ive got a beautiful 8digit_led code that can be reused with ease. I'll admit that its easier to do things like multiply and divide in C, but in assembly there are functions for that which can be included from a macro file in a neat list right after the jump table and before main:

multxy: M_mult_x_y
divxy: M_div_x_y
xbcd: M_xbin2bcd
label: M_macroname

with X and Y loaded with stuff, I can call multxy and it puts the result of X*Y in X:Y. Also, there are versions of multiply and divide that are optimised for speed or codesize and I could use either one depending on circumstances.

Its good to be in complete control using assembly, when something goes wrong you have a clearer idea of what it is and you would much rather step thru assembly that you wrote, stepping thru compiled assembly can be harsh and time consuming.

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

sbrs r16,BUTTON_C ; if (buttons & (1<<BUTTON_C))
rjmp PC+2
ldi r17,KEY_PREV ; key = KEY_PREV;

why not make this-

sbrc r16, BUTTON_C
ldi r16, KEY_PREV

It looks like youre skipping a jump thats only skipping 1 instruction which can be skipped in the first place. Maybe I'm missing something.

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

Not the best in this case, it does make a good template for when I want to skip more than 1 instruction.

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

It was getting lonely being the only one here talking about assembly. We know assembly blows away C when it comes to resources used, the main arguing point is that C is faster to code. How long did it take for you to write that code?

If there is a line to be drawn at a complexity level where C should be used, I draw it starting at ARMs.

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

outer_space wrote:
if C is so good then how come something so simple creates such a mess?

SIGNAL(SIG_ADC)
{
return;
}

Here is an excerpt from the lss file for this 1 command irq-

SIGNAL(SIG_ADC){
...
}

How complicated is this in assembly?

ADC_Irq:
reti

Clearly, one can see that using C is abusive and exploitive of your microcontroller resources. In assembly, this irq would execute in 4 instruction cycles while using 2 stack bytes. In C the same exact thing uses 19 instructions and 7 stack bytes. Both versions take the same amount of effort to code (one could argue assembly takes less effort) I could go on and on with examples like this.

Quote:
Clearly, one can see that using C is abusive and exploitive of your microcontroller resources.

Bull. Clearly, one can see that you paid $0 for your compiler, and you got exactly what you paid for.

interrupt [ACCEL_INT] void pin_change_isr2(void)
{
	return;
}
         ;    1497 interrupt [ACCEL_INT] void pin_change_isr2(void)
         ;    1498 {
          _pin_change_isr2:
         ;    1499 	return;
0002f9 9518      	RETI
         ;    1500 }

"I could go on and on with examples like this", to quote a famous sage.

Now (sigh) I suppose I need to look at the next example of the "superiority", and [sigh] entern the next round of the battle. Actually, I'm not totally in one camp or another. I just hate to see ridiculous claims go unchallenged.

Remember, Outer, a craftsman is only as good as the quality of his tools. ;)

Lee

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

Lee,

Quote:
Remember, Outer, a craftsman is only as good as the quality of his tools.

I also contend that, "The tools do not make, in them selves, a quality craftsman"...

This is not ment to be argumentitive but rather; supportive.

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

outer_space wrote:
...
Youre comparing C code to C generated assembly. Hand generated assembly with smart use of macros, labels, and relative jumps is beautiful to look at, easy to understand, and highly re-useable. Since I work in both assembly and C, I can post examples that were genuinely written in assembly.

Here is an example of a full-blown irq. Its purpose is to serially multiplex an 8 digit segment display.

SIGNAL(SIG_OVERFLOW0){
	unsigned char sbyte = (cath<<4)|digit[cath];
	for(int i = 7; i>=0; i--){
		unsigned char serdata = (sbyte>>i)&(0x01);
		PORTC &= ~(1<<SER);	//clear pin with and
		PORTC |= (serdata<<SER);		//set pin with or
		PORTC &= ~(1<<CLOCK);	//clear clock pin
		PORTC |= (1<<CLOCK);	//clock in the data
	}
	PORTC &= ~(1<<CLOCK);	//clear clock pin
	PORTC |= (1<<CLOCK);	//1 extra clock needed to latch data
	cath++;
	if (cath==8){
		cath=0;
	}
	return;
}

Here is the assembly version

.macro M_8digit_led	
lly digits_bcd 			; macro for load literal Y
	add YL, digits_pointer
	adc YH, zero 		; offset Y index
mov outbyte, digits_pointer
...
.endmacro

Assigning this to timer0 is as easy as-

timer0: M_8digit_led
reti
...

Its a mess!

Bull. Again. You are comparing the proverbial apples to oranges, and are surely ending up with fruit salad.

timer0: M_8digit_led
reti

The ONLY way this would work is if it is the "whole app" which it is obviously not. No care taken with SREG? No preservation of registers before use? Or scratch registers? (e.g., outbyte)

So your comeback is that you >>know << which resources you can use. (I'll wait for ANY defense of ignoring SREG in a real app.) That means that I can use my own model of the chip and app in C, right? I don't have to play with politically-incorrect data hiding, right? Then I use a bunch of global register variables, and get the job done faster than your crafted ASM version, with little or no size or speed penalty.

Lee

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

Youre right, I did pay $0 for that compiler. I have very little experience using pay-for C compilers.

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

Youre right, sreg and data space needs to be taken care of. I'd like to see some examples of C code compiling neatly and then we can shift the focus here to 'why avr-gcc is terrible' I'd really consider paying for a compiler if this is true.

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

And another thing on your C-vs-ASM SIGNAL(SIG_OVERFLOW0){ } ISR. Comparing crappy C to hand-crafted assembly isn't the point. In fact, you made the reverse point, about how elegant your final ASM source can be. There have been converse arguments floating about since Ada & Grace on this. The high-level language proponent shows

i = f;

where they are the same data types or different; then decries the multiple-multiple lines of ASM to carry out same.

Back to this particular rant. If you are on a crusade for size/speed efficiency, and you are writing an ISR, why would anyone do

for(int i = 7; i>=0; i--){ ...

forcing the compiler to clear two registers and do 16-bit operations when 8 bits would do? And then claim that the ASM version is shorter & cleaner. Why not make it a long, really proving your point? There are other parts of the ISR I could comment on. Comparing one man's crappy C to another's elegant assembler is the stuff that this whole topic is made of.

Lee

[ironically, the last big chunk of assembler I did was sorting out the display buffer to LCD segments.]

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

I could increment in floating point addition

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

I used to make a living from music and similar arguments raged over Fender verses Gibson guitars. An opinion for one or the other was ignored, if the guy played like crap on both or was in the business of hawking the product.

JimK

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

outer_space wrote:
Youre right, sreg and data space needs to be taken care of. I'd like to see some examples of C code compiling neatly and then we can shift the focus here to 'why avr-gcc is terrible' I'd really consider paying for a compiler if this is true.

In a previous round of this battle,
https://www.avrfreaks.net/index.p...
I posted several responses to challenges similar to this thread.

In particular, search that thread for the "HALLB" example. That was a critical ISR in one of our apps sensing bi-directional encoder pulses. I assumed I'd have to re-craft that critical ISR in ASM. Careful use of register varaibles left no slop in sight, directly compiled by CodeVision. Yes, a carefully crafted straight ASM could probably save a cycle or two--"any program can be made one instruction shorter" according to one of my first profs. But I've got a lot of ISRs in our production AVR apps, and I rarely even drop in for ASM fragments--ADC, U(S)ART, pin-change/external.

If you like, I'll tackle the exammple you posted. I'll feel just as free as you did in your ASM re-write to assume it is a critical ISR and "register" what I need.

Lee

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

I'd like to see how that asm compares to a properly used pay-for compiler.

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

Quote:

I'd like to see how that asm compares to a properly used pay-for compiler.

You are confusing me--what ASM are you referring to?

The stuff in the link that I gave above? That >>is<< directly from the compiler output.

These "tests" will always be loaded, just like benchmarks. I do respond in these threads to particular claims, especially when I feel they are without merit. You may have seen me state on this Forum before: "The Emporer has no clothes". That was my response to you at least twice in this thread: Once with the "null" ISR where you complained about the bloat in the GCC solution. I demonstrated that it isn't true with common production tools. One could also say that your "benchmark" was nothing but specmanship--a null ISR has little if any purpose, and the GCC framework will also be used in most practical ISRs.

Speaking of framework, my next response to you involved your version in ASM of a C ISR. A great benchmarking specmanship case again--your Emporer was also quite chilly, as your touted one-giant-macro rewtie had >>no<< necessary framework and would not be able to carry out the task.

Lee

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

Outer_Space,

The following is your code compiled with ICCAVR 7.xx with the optimizer turned on.

(0001) #include 
(0002) 
(0003) #define SER 0x01
(0004) #define CLOCK 0x02
(0005)  
(0006) char digit[4];
(0007) char cath = 0;
(0008) int i;
(0009) 
(0010) SIGNAL()
(0011) {
(0012) //SIGNAL(SIG_OVERFLOW0){  HAD TO DELETE, AS I DON"T KNOW WHAT "SIG_OVERFLOW0" IS.
(0013)    unsigned char sbyte = (cath<<4)|digit[cath]; 
    004E 90200060  LDS	R2,cath
    0050 E683      LDI	R24,0x63
    0051 E090      LDI	R25,0
    0052 2DE2      MOV	R30,R2
    0053 27FF      CLR	R31
    0054 0FE8      ADD	R30,R24
    0055 1FF9      ADC	R31,R25
    0056 8040      LDD	R4,Z+0
    0057 2D62      MOV	R22,R2
    0058 706F      ANDI	R22,0xF
    0059 9562      SWAP	R22
    005A 2964      OR	R22,R4
(0014)    for (i = 7; i >= 0; i--)
    005B E087      LDI	R24,7
    005C E090      LDI	R25,0
    005D 93900062  STS	i+1,R25
    005F 93800061  STS	i,R24
(0015)    { 
(0016)       unsigned char serdata = (sbyte>>i)&(0x01); 
    0061 91200061  LDS	R18,i
    0063 91300062  LDS	R19,i+1
    0065 2F06      MOV	R16,R22
    0066 2711      CLR	R17
    0067 D02D      RCALL	asr16
    0068 0000      NOP
    0069 2F40      MOV	R20,R16
    006A 7041      ANDI	R20,1
    006B 7050      ANDI	R21,0
(0017)       PORTC &= ~(1<<SER);   //clear pin with and 
    006C 98A9      CBI	0x15,1
(0018)       PORTC |= (serdata<<SER);      //set pin with or 
    006D 2E24      MOV	R2,R20
    006E 0C22      LSL	R2
    006F B235      IN	R3,0x15
    0070 2832      OR	R3,R2
    0071 BA35      OUT	0x15,R3
(0019)       PORTC &= ~(1<<CLOCK);   //clear clock pin 
    0072 98AA      CBI	0x15,2
(0020)       PORTC |= (1<<CLOCK);   //clock in the data 
    0073 9AAA      SBI	0x15,2
    0074 91800061  LDS	R24,i
    0076 91900062  LDS	R25,i+1
    0078 9701      SBIW	R24,1
    0079 93900062  STS	i+1,R25
    007B 93800061  STS	i,R24
    007D 3080      CPI	R24,0
    007E E0E0      LDI	R30,0
    007F 079E      CPC	R25,R30
    0080 F704      BGE	0x0061
(0021)    } 
(0022)    PORTC &= ~(1<<CLOCK);   //clear clock pin 
    0081 98AA      CBI	0x15,2
(0023)    PORTC |= (1<<CLOCK);   //1 extra clock needed to latch data 
    0082 9AAA      SBI	0x15,2
(0024)    
(0025)    cath++; 
    0083 91800060  LDS	R24,cath
    0085 5F8F      SUBI	R24,0xFF
    0086 93800060  STS	cath,R24
(0026)    if (cath==8)
    0088 3088      CPI	R24,0x8
    0089 F419      BNE	0x008D
(0027)    { 
(0028)       cath=0; 
    008A 2422      CLR	R2
    008B 92200060  STS	cath,R2
(0029)    } 
(0030) 
(0031)    return(cath); 
    008D 91000060  LDS	R16,cath
    008F 2711      CLR	R17
    0090 D00B      RCALL	pop_gset2
    0091 0000      NOP
    0092 9508      RET
(0032) }
(0033) 
(0034) void main()
(0035) {
(0036)  SIGNAL();
FILE: 
_main:
    0093 DFB8      RCALL	_SIGNAL
    0094 9508      RET

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

I used the 1 line ISR to show a simple example of unnecessary bloat. I use that line in every application that uses the ADC, the ISR needs to be empty. Sometimes a function like scaling is put into that ISR if every ADC read needs to be scaled but usually this doesn't happen.

My one giant macro was stripped from an application that was simple enough to use only registers so it doesn't need any more framework than it has. It certainly would take 6? stack operations and an extra sram load. My emporer wears the finest threads.

I assure you that if there is any inefficiency in my C code that it is because I am not an expert. If you could write that function except with a reasonable listing, I would be impressed and would never again attempt to argue that assembly is better than C.

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

Quote:

If you could write that function except with a reasonable listing, I would be impressed and would never again attempt to argue that assembly is better than C.

The gauntlet has been dropped!

Actually, with only 8 reps an unrolled loop looks tempting for top prize...

I've found with several bit-banging apps like you showed that I actually have to slow down my loop, especially if there are slow devices inline like optocouplers.

Lee

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

Lee,

I have been playing with the SHTxx temperature/humidity sensor over the past few weeks. They are painfully slow! Doing many float computations, I still had to have several rather long delays waiting on the thing.

It is so slow, I was even wondering if I could have just used toggle switches...

Parallel port LCD's are really slow during initialization and clearing too!

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

Usually/often/almost always one can find something else useful to do between bits. Many times just doing the next driver task such as fetching or storing the next bit/byte will do it.

Other times you must alarm the purists, and mix one driver with another. If you are waiting on sending an LCD character, fetch the A/D result. The next LCD character, store the A/D result. The next LCD character, start the next A/D conversion. etc.

Lee

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

I just spent hours and hours playing with the codevision eval, it lists things real nicely. And the wizard is sweet. I might buy it if I need to do something intense with an AVR.

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

Here's some ASM code written for encoders. I use it polled from a main routine at around 1ms but give or take a bit is fine.

It NEVER uses a delay of any kind in the subroutine.

It is very easy to expand to 2 or 3 or however many encoders.

It uses 4 register variables and one for each encoder state.

The encoder type is a two pin, each pulse A/B low then the other A/B low followed by the first A/B high then the other A/B high.

This sequence must be followed exactly. i.e. debounce contitions are considered.

The code I will post in full in a bit but will explain the first bit to help your understanding.

clr       R17                    ;;encoder1 left/right
      clr       R18
      sbic      PINC,PC_En1a
      ldi       R17,0x01
      sbic      PINC,PC_En1b
      ldi       R18,0x01
      lds       R16,Encoder1
      rcall     EncoderSub
      brcs      StoreEn1              ;;encoder 1 activated
      sts       Encoder1,R16

This and

StoreEn2:
        sbrs    R17,0
        ldi     R16,0x06
        sbrc    R17,0
        ldi     R16,0x05
        clr     R17
        sts     Encoder2,R17
        rjmp    KeyRegister

Is all that is necessary for an encoder. Change the PInC to whatever (2 lines of code and the pins of your choice, less then 2 minutes work)

Encoder1 variable could be put in one of the R registers for faster speed.

Key register in this case will be values 5 and 6 for left and right. I haven't added this in the code as it is (in my case) passes flags to another routine which I scan around every 20ms.

Ok heres the whole code.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;keyscan routine;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;variables  local           R16,R17,R18,R19                               ;
;;   global     encoder1,                                              ;
;;              encoder2,encoder3                                      ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
KeyScan:

      clr       R17                    ;;encoder1 left/right
      clr       R18
      sbic      PINC,PC_En1a
      ldi       R17,0x01
      sbic      PINC,PC_En1b
      ldi       R18,0x01
      lds       R16,Encoder1
      rcall     EncoderSub
      brcs      StoreEn1              ;;encoder 1 activated
      sts       Encoder1,R16

      clr       R17                    ;;encoder2 left/right
      clr       R18
      sbic      PIND,PD_En2a
      ldi       R17,0x01
      sbic      PIND,PD_En2b
      ldi       R18,0x01
      lds       R16,Encoder2
      rcall     EncoderSub
      brcs      StoreEn2              ;;encoder 2 activated
      sts       Encoder2,R16


      clr       R17                    ;;encoder3 left/right
      clr       R18
      sbic      PIND,PD_En3a
      ldi       R17,0x01
      sbic      PIND,PD_En3b
      ldi       R18,0x01
      lds       R16,Encoder3
      rcall     EncoderSub
      brcs      StoreEn3              ;;encoder 3 activated
      sts       Encoder3,R16

      ret

StoreEn1:
        sbrs    R17,0
        ldi     R16,0x0D
        sbrc    R17,0
        ldi     R16,0x0C
        clr     R17
        sts     Encoder1,R17
        rjmp    KeyRegister

StoreEn2:
        sbrs    R17,0
        ldi     R16,0x06
        sbrc    R17,0
        ldi     R16,0x05
        clr     R17
        sts     Encoder2,R17
        rjmp    KeyRegister

StoreEn3:
        sbrs    R17,0
        ldi     R16,0x0F
        sbrc    R17,0
        ldi     R16,0x0E
        clr     R17
        sts     Encoder3,R17
        rjmp    KeyRegister

Encodersub:
      ldi       Zl,low(EncoderTable)
      ldi       Zh,High(EncoderTable)
      add       Zl,R16
      clr       R19
      adc       Zh,R19
      ijmp
EncoderTable:
      rjmp      Encoder_1        ;;from idle
      rjmp      Encoder_2        ;;clock wise point 1
      rjmp      Encoder_3        ;;clockwise  point 2
      rjmp      Encoder_4        ;;anticlockwise point 1
      rjmp      Encoder_5        ;;anticlockwise point 2


Encoder_1:
      sbrs      R17,0
      rjmp      test_cw
      sbrc      R18,0
      rjmp      NoEncoderChange
      ldi       R16,0x01        ;;clockwise point 1
      rjmp      StoreEncoder

test_cw:
      sbrs      R18,0
      rjmp      NoEncoderChange
      ldi       R16,0x03        ;;anti clockwise point 1
      rjmp      StoreEncoder

Encoder_2:
      sbrs      R17,0
      rjmp      Encoder_2a
      sbrs      R18,0
      rjmp      NoEncoderChange        ;;no change
Invalid_Switch:
      ldi       R16,0x00
      rjmp      StoreEncoder


Encoder_2a:
      sbrc      R18,0
      rjmp      Invalid_Switch
      ldi       R16,0x02                ;;move to point 2
      rjmp      StoreEncoder

Encoder_3:
      sbrs      R17,0
      rjmp      NoEncoderChange
      sbrs      R18,0
      rjmp      NoEncoderChange
                             ;;;;valid clockwise turn
      ldi       R17,0x01
      rjmp      EncoderActivate
      ;;;;;;;;;;
Encoder_4:
      sbrs      R18,0
      rjmp      Encoder_4a
      sbrs      R17,0
      rjmp      NoEncoderChange        ;;no change
      rjmp      Invalid_Switch
Encoder_4a:
      sbrc      R17,0
      rjmp      Invalid_Switch
      ldi       R16,0x04                ;;move to point 2
      rjmp      StoreEncoder

Encoder_5:
      sbrs      R17,0
      rjmp      NoEncoderChange
      sbrs      R18,0
      rjmp      NoEncoderChange
                             ;;;;valid anticlockwise turn
      clr       R17
      rjmp      EncoderActivate
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
StoreEncoder:
        clc
        ret
NoEncoderChange:
        clc
        ret
EncoderActivate:
        sec
        ret

KeyRegister:
        
              ;;;store key value and set flag for keypress
 
        ret

*edit This type of program is not linear. The time for execution will vary depending on the state of the encoder or if there is a change of state, I think it has a minimum for each encoder of around 20 clock cycles and maximum of around 50.

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

I think it's quite obvious that there are pros and cons for either method. C is quicker to develop products and assembler uses less memory and runs quicker in most cases. I use assembler (mainly because I have been too lazy to learn C) so the next sentence is definitely not biased. A newbie would be crazy not to learn C because going forward memories and clock speeds are increasing and soon the inneficiencies of C assemblers will become insignificant. Dos is far more code efficient and faster than windows, but who writes serious programs in dos these days ?
I would however like to challange Smiley on the following: "100 times more efficient to maintain". I always start my design process with a flow chart. It helps me plan my logic more clearly and is very powerful for debugging and maintainance and in actual fact (for me) it speeds up the whole programming process. You would also be amazed at how fast you can convert a flow chart to assembler, provided the flow chart was done properly. If I take a project from say 5 years ago which I have totally forgotten, using the flow chart I can very quickly work out my logic and make minor changes - typically 30 minutes or less. So 100 times faster would be 18 seconds !!! In fact I suspect that I could make minor changes at least as quickly or quicker than most C programmers. I said minor changes - major changes would be a different story. I also write in VB6 and PHP and it takes at least 30 minutes on an old program just to figure out my logic, and I am usually pretty good with comments. For some reason I don't use flow charts on the high level languages.
Anyhow, after reading this debate and many others I have decided to learn C. I have downloaded the 1st 2 chapters of Smiley's book and it looks good so as soon as I get a butterfly I will purchase the book and join the world of C.

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

sefton wrote:

I would however like to challange Smiley on the following: "100 times more efficient to maintain".
[edit]
Anyhow, after reading this debate and many others I have decided to learn C. I have downloaded the 1st 2 chapters of Smiley's book and it looks good so as soon as I get a butterfly I will purchase the book and join the world of C.

I was going to argue with you, then I saw your conclusion and decided not to. :P

I got my 10/100 number from some DARPA sponsored research by (I think) Carnegie Mellon, but I can't find it and will need to do some deep searching to get my sources -- which I will do soon.

Anyhow I'm enjoying watching Lee pulverize outer_space who is now so on the ropes that he is trying to change the topic to GCC is a bad compiler. He's a hoot!

Smiley

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

I think we have come to the conclusion that C 'can' be as efficient as assembly and also have other benefits if you use it smartly. But also its much easier to be good at assembly than it is to be good at C.

I think a full change-of-mind is a knock-out, nobody around here is on any ropes.

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

I have nearly identical routines to outer's example, bit-banging a 74HC595 latch for a bank of LEDs. It isn't an ISR. It won't be exactly the same, because I use 16 bits--we are using the 8-bit latch to output 9 bits. (The app has the same trick on a '165 input latch to handle 9 associated pushbuttons.) Anyway, I'll show the source and the generated listing, then a few comments.

In this particular case I wasn't on a quest for speed like I might be in the ISR; neither are there any tricks with using register variables. Pretty much a straight-C self-contained routine. I'll only show a couple of the pin definitions.

// Latches, Chip Selects, Enables
	// Load 74HC165 on falling edge of this signal
#define	SPI_LOAD			PORTD.6
	// Transfer to outputs of 74HC595 on rising edge of this signal
#define	SPI_XFER			PORTB.6
	// Soft SPI output line to 74HC595
#define	SPI_MOSI			PORTB.5
	// Soft SPI input line from 74HC165
#define	SPI_MISO			PIND.7
	// Soft SPI clock line to/from 74HC165 & 74HC595
#define	SPI_SCK				PORTB.7
	// Clock in 8 '165 bits plus the '165 SDI pin value
#define	SPI_INPUTS			9
	// Clock out 8 '595 bits, then leave SPI_MOSI in the state of the 9th bit
#define	SPI_OUTPUTS			8
...
//
// **************************************************************************
// *
// *		C O N F I G _ L E D S
// *
// **************************************************************************
//
//	A bank of nine red/green indicator LEDs are connected to the system inputs.
//
//	A 74HC595 serial-to-parallel chip controls the state of eight of them;
//	the ninth is controlled by the state of the system SDI (AVR MOSI) line
//	when the operation is completed.
//
// 74HC595 sets indicator "direction"  (red/green for NC/NO)
//
//
void						config_leds			(unsigned int indicators)
{
unsigned int 		work;				// working value of indicators
unsigned char 		looper;				// loop counter

	work = indicators;					// try for register operations


	SPI_SCK = 0;						//	drive the clock low.
	SPI_XFER = 0;						//	drive the latch low.


  	for (looper = 0; looper < SPI_OUTPUTS; looper++)
  		{
		SPI_MOSI = (work & 256) ? 1 : 0;// present the bit to the data line
		SPI_SCK = 1;					// raise the clock to present the bit to the line
		work <<= 1;						// prepare next bit & take some time
		SPI_SCK = 0;					// drop the clock for the next go-round
  		}

	SPI_XFER = 1;						// drive the latch RCLK/XFER high, latching data.
	SPI_MOSI = (work & 256) ? 1 : 0;	// present the 9th bit to the data line; take time
	SPI_XFER = 0;						//	drive the latch low.

}

Now the compiler generated code:

         ;    6031 void						config_leds			(unsigned int indicators)
         ;    6032 {
          _config_leds:
         ;    6033 unsigned int 		work;				// working value of indicators
         ;    6034 unsigned char 		looper;				// loop counter
         ;    6035 
         ;    6036 	work = indicators;					// try for register operations
001b0a 940e 1fa0 	CALL __SAVELOCR3
         ;	indicators -> Y+3
         ;	work -> R16,R17
         ;	looper -> R18
001b0c   +  	__GETWRS 16,17,3
         ;    6037 
         ;    6038 
         ;    6039 	SPI_SCK = 0;						//	drive the clock low.
001b0e 98c7      	CBI  0x18,7
         ;    6040 	SPI_XFER = 0;						//	drive the latch low.
001b0f 98c6      	CBI  0x18,6
         ;    6041 
         ;    6042 
         ;    6043   	for (looper = 0; looper < SPI_OUTPUTS; looper++)
001b10 e020      	LDI  R18,LOW(0)
          _0x236:
001b11 3028      	CPI  R18,8
001b12 f480      	BRSH _0x237
         ;    6044   		{
         ;    6045 		SPI_MOSI = (work & 256) ? 1 : 0;// present the bit to the data line
001b13 ff10      	SBRS R17,0
001b14 c002      	RJMP _0x238
001b15 e0e1      	LDI  R30,LOW(1)
001b16 c001      	RJMP _0x239
          _0x238:
001b17 e0e0      	LDI  R30,LOW(0)
          _0x239:
001b18 940e 1f98 	CALL __BSTB1
001b1a b3e8      	IN   R30,0x18
001b1b f9e5      	BLD  R30,5
001b1c bbe8      	OUT  0x18,R30
         ;    6046 		SPI_SCK = 1;					// raise the clock to present the bit to the line
001b1d 9ac7      	SBI  0x18,7
         ;    6047 		work <<= 1;						// prepare next bit & take some time
001b1e 0f00      	LSL  R16
001b1f 1f11      	ROL  R17
         ;    6048 		SPI_SCK = 0;					// drop the clock for the next go-round
001b20 98c7      	CBI  0x18,7
         ;    6049   		}
001b21 5f2f      	SUBI R18,-1
001b22 cfee      	RJMP _0x236
          _0x237:
         ;    6050 
         ;    6051 	SPI_XFER = 1;						// drive the latch RCLK/XFER high, latching data.
001b23 9ac6      	SBI  0x18,6
         ;    6052 	SPI_MOSI = (work & 256) ? 1 : 0;	// present the 9th bit to the data line; take time
001b24 ff10      	SBRS R17,0
001b25 c002      	RJMP _0x23B
001b26 e0e1      	LDI  R30,LOW(1)
001b27 c001      	RJMP _0x23C
          _0x23B:
001b28 e0e0      	LDI  R30,LOW(0)
          _0x23C:
001b29 940e 1f98 	CALL __BSTB1
001b2b b3e8      	IN   R30,0x18
001b2c f9e5      	BLD  R30,5
001b2d bbe8      	OUT  0x18,R30
         ;    6053 	SPI_XFER = 0;						//	drive the latch low.
001b2e 98c6      	CBI  0x18,6
         ;    6054 
         ;    6055 }
001b2f 940e 1fa7 	CALL __LOADLOCR3
001b31 9625      	ADIW R28,5
001b32 9508      	RET

A few comments:

001b0a 940e 1fa0 CALL __SAVELOCR3
...
001b2f 940e 1fa7 CALL __LOADLOCR3

If this were a critical routine and I could afford it in the app, I'd use global register variables and avoid "making room" and restoring.

         ;    6045 		SPI_MOSI = (work & 256) ? 1 : 0;// present the bit to the data line
001b13 ff10      	SBRS R17,0
001b14 c002      	RJMP _0x238
001b15 e0e1      	LDI  R30,LOW(1)
001b16 c001      	RJMP _0x239
          _0x238:
001b17 e0e0      	LDI  R30,LOW(0)
          _0x239:
001b18 940e 1f98 	CALL __BSTB1
001b1a b3e8      	IN   R30,0x18
001b1b f9e5      	BLD  R30,5
001b1c bbe8      	OUT  0x18,R30

As you know, there is no good way to set an AVR I/O bit to the value of a register bit. You chose to do the clear first, then a conditional set of the I/O bit; this cannot be done in all apps due to the possible side effects of the couple-cycle "glitch" on the pin. It would be OK here since we are waiting for the clock. I chose not to do the glitchy thing. More on this below. Note here that I am shifting out from the >>top<< of my 9 bits of data, MSB-first. The compiler figgered that out, and the "(work & 256) " translated into "SBRS R17,0".

OK, let's use the same method that you used in your "ideal" ASM routine with the "glitchy" method. Change to:

		SPI_MOSI = 0;
		if(work & 256) SPI_MOSI = 1;	// present the bit to the data line

and get generated:

         ;    7519 		SPI_MOSI = 0;
0018ce 98c5      	CBI  0x18,5
         ;    7520 		if(work & 256) SPI_MOSI = 1;	// present the bit to the data line
0018cf fd10      	SBRC R17,0
0018d0 9ac5      	SBI  0x18,5

All except the last trial (which I >>chose<< to do in my original manner) are direct copies from shipping, production, industrial-device code. 'Nuff said? If you really want me to use dedicated registers, I can get the straight C code down to as fast as [further flame bait] any hand-coded ASM for this routine. In this case I didn't care about a few extra words or cycles in setup and tear down; this is only a very small part of the total app; and dedicated register variables were more important for other uses.

Lee

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

Educate us how the experts dedicate variables to regs. I am trying to do that in codevision but looking over the assembly it still uses STS and LDS as if the register was in SRAM.

register unsigned long bignum @16; //take up r16, r17, r18, r19
unsigned int smallnum1 @20; //take up r20, r21
unsigned int smallnum2 @22; //take up r22, r23

bignum = 100000000 ;
smallnum1 = 50000;
smallnum2 = 50500;

with giving these register variable values, it uses ldi for r30, and then uses LDS to load r30 into the proper register as if it were sram.

bignum = (bignum*smallnum1)/smallnum2;

This basic scaling operation was meant to demonstrate how C can efficiently work using allocated registers, but in reality it treats allocated registers as if they are SRAM allowing no savings at all.

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

outer_space wrote:
I think we have come to the conclusion that C 'can' be as efficient as assembly and also have other benefits if you use it smartly.

I've also asserted that an average C programmer can beat an average asm programmer in code size and speed. I follow with that a 'world-class' asm programmer may beat even a good C programmer, but I've never met a 'world-class' asm programmer as they are very rare and are usually doing stuff like writing C compilers that generate better asm than average asm programmers generate.

outer_space wrote:
But also its much easier to be good at assembly than it is to be good at C.

Couldn't disagree more. I think it is vastly more difficult to learn to be a 'good' asm programmer. C on the other hand forces many good programming practices that an asm programmer only picks up with lots of experience or a very good mentor. And the C libraries alone are a major benefit. Try doing a printf(...) in assembler. But we may be disagreeing on the definition of 'good'.

Smiley

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

To have "total" control, you need to learn the "model" that your compiler uses. Check the CV Help/manual to start with; then look at a few generated test programs.

-- R2-R15 are used for global register variables (the R15 limit varies somewhat depending on compiler version, chip type, and the phase of the moon. Not really critical; the important thing is "a bunch of low registers can be used for register variables".)
-- "bit" variables are allocated first, depending on your project settings.
-- "long" variables are never registerized.
-- Default is the first declared global variables that fit are registerized. Change project options to gain specific control.
-- When you use the @, in many/most cases STS/LDS is used. The @ is more used for things like memory-mapped peripherals in my experience. I also use it sometimes for non-standard equivalent to C "union".
-- R16-R2n are used for register local variables in main() & other functions. You can't do what you showed in CV; you are going to screw things up messing with R16-R2x directly. (Once you >>know<< something is allocated there you can play tricks. But to get started play by the rules first, get a feel for the model, >>then<< try to play tricks.
-- Remember that if you drop into #asm there are certain registers (R2x to R31 except Y) that are freely available. Again check the doc.

Anyway, approach your first CV programs like this:
-- Decide how many (if any) registers you want for "bit" flags, and set that in the project options.
-- Turn off the "automatic register allocation" project option.
-- Pick your register variables. Simply "register unsigned char frog;" etc.
-- Check the .MAP file to see where you are for using the available registers.
-- Remember the the >>first declared eligible local variables<< will be registerized. Take advantage of that, especially in main()--loop counters, working 16-bit values, etc.

In a full app, selecting which globals are to be registerized has a greate effect on app size and speed--as much as 10% in my experience. On topic, you do the same thing in your ASM full apps--decide the "model" of how registers can be best used. I'm a heretic in my AVR apps, especially smaller Mega48-class: Use a lot of global variables; few local variables; global scratch/working register vars & use the heck out of them. Not politically correct but fast & tight code.

Other compiler implementations are going to have there own model & register "rules".

Lee

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.