Hello community,
Programming my Atmega8 I came across the task of doing the following
operation:
bit A of byte BUFFER = bit B of byte PORT
I have 2 questions, the first is about efficiency:
I'd like to read from a port with this operation (yess it would be
better to do this with a PAL or interface chip), and therefore I only
have 30 or so clock cycles to do my bit operation.
I thought this should be plenty, but it wasn't. Looking at the
disassembly it became clear why. Is there a nicer or more efficient way
to do this?
Secondly, I'd like to have a timer interrupt (Here I wrote the dummy
function timer_start()) which should interrupt the reading after a
certain period (for example if no more data come in - this is not shown
in the code for simplicity).
In Java I have this wonderful throw operation for interrupts. Is there a
possibility to do this here as well, or do I really need to check the
timer every iteration? What's the most efficient implementation?
In Assembler I imagine I could, within the timer interrupt routine,
change the return address, so after the timer interrupt the program
doesn't jump back to my loop but to the code after it. Is this possible
in C?
I'm using AVR Studio 5 with maximum optimization option for my Atmega8
with 16MHz. I'd like to read a data but @500kHz -> 32 clock cycles.
I'd be glad for any hints or book suggestions :)
Thank you very much in advance and best regards
Alex
1
// C-CODE
2
voidmyfunction(){
3
start_timer();// Starts a timer that should interrupt the loop
4
for(uint16_ti=0;i<1000;i++){// This is time critical -> I don't want to check the timer every loop iteration
5
if(PORTD&(1<<DATA))buffer[i/8]|=1<<(i&0xFF);
6
}
7
}
1
# DISASSEMBLY
2
# void myfunction() {
3
# start_timer(); // Starts a timer that should interrupt the loop
4
# for(uint16_t i=0; i<1000; i++) { // This is time critical -> I
5
# don't want to check the timer every loop iteration
> have 30 or so clock cycles to do my bit operation.> I thought this should be plenty, but it wasn't. Looking at the> disassembly it became clear why.
The main problem here is that the AVR can only shift by one bit, so if
you get your shift width from a variable, the compiler has to do this as
a loop. In addition, since i is of type int, the the shift operation is
done in 16 bit.
> Is there a possibility to do this here as well, or do I really need to check the> timer every iteration? What's the most efficient implementation?
I would check it every iteration. Alternatively, you can make two nested
loops and only check once per iteration of the outer loop if it's ok to
have the loop run for a few more microseconds before it stops. That
would also have the advantage that your loop counters could be 8 bit and
you could lose the division.
> In Assembler I imagine I could, within the timer interrupt routine,> change the return address, so after the timer interrupt the program> doesn't jump back to my loop but to the code after it. Is this possible> in C?
No. There is setjmp/longjmp, but that probably won't work from an ISR.
Even in assembler, I would consider it a dirty hack.
I'd suggest something like this:
1
for(uint8_ti=0;i<125;i++)
2
{
3
uint8_tbitval=1;
4
for(uint8_tj=0;j<8;j++)
5
{
6
if(PORTD&(1<<DATA))
7
buffer[i]|=bitval;
8
bitval<<=1;
9
}
10
}
When compiling this with -O3, the code will be quite long due to loop
unrolling, but since that code does 8 iterations, it should in fact be
faster than yours.
After reading your posta second time, there are some questions ;)
Fist of all,
>if(PORTD & (1<<DATA))
will not do, what you want.
But:
What exactly do you want to achive?
Is it necessary to do the bit shift during the measurement, or can it be
done later?
What is DATA? Where does it come from?
What happens, if you have finished all 1000 loop iterations, without
timer interrupt?
Oliver
Awesome! Thanks a lot Rolf and Oliver, that was just what I was looking
for.
> I would check it every iteration.
What a pitty! It would be awesome to have interrupts that can interrupt
loops or even functions without much computational effort.
> if(PORTD & (1<<DATA))
I should have explained, DATA is just the bitnumber where in PORTD the
data comes in. In fact, I had to test the clock as well and everything,
but I left it out for the sample here because that worked fine.
That's why I wanted the timer interrupt, because what if I'm waiting for
the clock and the sender doesn't want to send me any more data?
1
while(!(PORTD&(1<<CLOCK))&&(stillTimeLeft))// I need to add the (stillTimeLeft)-bit to check the timer
2
// because my interrupt can't stop this loop otherwise
I would get stuck in that loop then.
But your suggestion with the switch statement looks much better in the
Disassembler:
1
# case 5: buffer[i>>3] |= (1<<5); break;
2
00000555 MOVW R30,R24 Copy register pair
3
00000556 LSR R31 Logical shift right
4
00000557 ROR R30 Rotate right through carry
5
00000558 LSR R31 Logical shift right
6
00000559 ROR R30 Rotate right through carry
7
0000055A LSR R31 Logical shift right
8
0000055B ROR R30 Rotate right through carry
9
0000055C SUBI R30,0x6B Subtract immediate
10
0000055D SBCI R31,0xFE Subtract immediate with carry
11
0000055E LDD R18,Z+0 Load indirect with displacement
12
0000055F ORI R18,0x20 Logical OR with immediate
13
00000560 STD Z+0,R18 Store indirect with displacement
>> if(PORTD & (1<<DATA))>I should have explained, DATA is just the bitnumber where in PORTD the>data comes in.
Well, in PORTD never anything will come in...
Again my question: Why can't you do the bitshifting stuff after the
measurement loop has finished? This would reduce the required cycles in
the measurement loop significantly.
Oliver
> Well, in PORTD never anything will come in...
Aaah yes I meant PIND of course sorry.
> Why can't you do the bitshifting stuff after the
measurement loop has finished?
That's what I did in the end, but I think the much nicer solution would
have been to improve efficiency and do it directly instead of just
"recording" it in realtime and then getting the data out afterwards.
Alex
Compiling this with an optimizing avr-gcc yields, here with version 4.7:
1
set:
2
sbi 0x18,6 ; 11 *sbi [length = 1]
3
in r25,0x18 ; 13 movqi_insn/4 [length = 1]
4
mov r18,r24 ; 51 movqi_insn/1 [length = 1]
5
/* #APP */
6
ldi r24, 1 << 1
7
sbrs r18, 1
8
clr r24
9
sbrc r24, 0
10
lsl r24
11
sbrc r18, 2
12
swap r24
13
/* #NOAPP */
14
or r25,r24 ; 16 iorqi3/1 [length = 1]
15
out 0x18,r25 ; 18 movqi_insn/3 [length = 1]
16
cbi 0x18,6 ; 23 *cbi [length = 1]
17
in r25,0x18 ; 25 movqi_insn/4 [length = 1]
18
and r25,r24 ; 28 andqi3/1 [length = 1]
19
breq .L1 ; 30 branch [length = 1]
20
/* #APP */
21
ldi r25, 1 << 1
22
sbrs r22, 1
23
clr r25
24
sbrc r25, 0
25
lsl r25
26
sbrc r22, 2
27
swap r25
28
/* #NOAPP */
29
lds r24,c ; 34 movqi_insn/4 [length = 2]
30
or r24,r25 ; 35 iorqi3/1 [length = 1]
31
sts c,r24 ; 36 movqi_insn/3 [length = 2]
32
.L1:
33
ret ; 54 return [length = 1]
In the 1st and 3rd call of bitmask the argument is known at compile
time and the compiler can fold the expressions to SBI resp. CBI.
If the argument to bitmask is not a compile time constant, then the
optimized asm sequence will be used. That sequence takes 7 ticks, and
you need some additional ticks for IN and OUT.
Notice that in the latter case the port change is not atomic.
Moreover, the asm sequence is only expanded once for data and then
reused in the remainder. That's the reason why the asm should not be
volatile: The asm is const (like a function can be const) and thus has
no side effects and can be reused.
For the sake of completeness:
The AVR instruction set provides the BLD and BST operations which allow
easy transfer of single bits from one register to another, both at
arbitrary bit positions. They use the T-bit in the SREG which is
otherwise not used by gcc.
Using inline assembler a single bit can be transferred between two 8-bit
variables with two instructions in two cycles as simple as:
1
asmvolatile(
2
"bst %[src], %[srcbit] \r\n"
3
"bld %[dest], %[destbit] \r\n"
4
:[dest]"+r"(dest)
5
:[src]"r"(src),
6
[srcbit]"n"(2),
7
[destbit]"n"(7)
8
);
Using BLD/BST, no shifts/rotates are needed, and no AND/OR operation
either. Only the designated bit in dest is affected.