This problem has nothing to do with the language you program with. I write my example codes in (pseudo) C because it is readable and it is not tied to any specific chip. Of course you can squeeze some cycles out if you optimize the code with some inline asm.
Clever assembly program on pic does exactly the same as clever assembly program on avr.
Only that avr cpu runs exactly the same speed as the clock.. no dividing by 4. With 20 MHz you get almost 20 MIPS (some instructions take 2 cycles).
6 dollars for 12 pwm channels is a lot in my opinion.).
Yes, I should not have mentioned optimization or inline asm. My point is that I write code examples in C because they are easy to follow and the general idea comes through easily. There would not be any point to say "do something like this" and then post assembly code. I don't think the OP is stupid, he understands that speed and efficiency is important in this application. But, choosing the right strategy is key.. if you have wrong general plan then assembly or any other trick will not solve your problems.Some cycles? In this particular case, you need to run it as fast as you possibly can because speed here directly translates into the accuracy of frequency reproduction. Speed is really the only requirement here. It needs to run several million times per second, which leaves you just few cycles to work with.
Yes, and that is also a lot if all you get is 12 square wave frequency outputs (with crappy quality). Maybe some small controllers that cost around 0.5 dollars and could do at least two channels in hardware would be a cost effective solution. The happy situation is that 12 is divisible by 2, 3 and 4Looked at DigiKey. ATMega640 is over $10.
#include <avr/io.h>
#include <stdint.h>
#define sbi(b,n) (b |= (1<<n)) /* Set bit number n in byte b */
#define cbi(b,n) (b &= (~(1<<n))) /* Clear bit number n in byte b */
volatile uint8_t counter[12];
/* Just some dummy values */
const uint8_t top[12] = {112, 119, 126, 134, 141, 150, 159, 168, 178, 189, 200, 212};
void main(void)
{
while(1)
{
/* The compiler will unroll this when compiled with -O3 */
for(uint8_t i = 0; i<12; i++) {
/* Increment each counter */
counter[i]++;
/* Check if counter is over the top */
if (counter[i] > top[i]) {counter[i] = 0; }
/* Compare for 50% square wave */
(counter[i] > (top[i]>>1)) ? (sbi(PORTB, i)) : (cbi(PORTB, i));
}
}
}
counter[i]++;
000000A3 LDS R24,0x0201 Load direct from data space
000000A5 SUBI R24,0xFF Subtract immediate
000000A6 STS 0x0201,R24 Store direct to data space
if (counter[i] > top[i]) {counter[i] = 0; }
000000A8 LDS R24,0x0201 Load direct from data space
000000AA CPI R24,0x78 Compare with immediate
000000AB BRCS PC+0x03 Branch if carry set
000000AC STS 0x0201,R1 Store direct to data space
(counter[i] > (top[i]>>1)) ? (sbi(PORTB, i)) : (cbi(PORTB, i));
000000AE LDS R24,0x0201 Load direct from data space
000000B0 CPI R24,0x3C Compare with immediate
000000B1 BRCS PC+0x02 Branch if carry set
000000B2 RJMP PC+0x0071 Relative jump
000000B3 CBI 0x05,1 Clear bit in I/O register
This is the code where the relative jump points
00000123 SBI 0x05,1 Set bit in I/O register
00000124 RJMP PC-0x0070 Relative jump
how GCC uses the registers. This section describes how registers are allocated and
used by the compiler.
Register Use
r0: This can be used as a temporary register. If you assigned a value to this
register and are calling code generated by the compiler, you’ll need to save r0,
since the compiler may use it. Interrupt routines generated with the compiler save
and restore this register.
r1: The compiler assumes that this register contains zero. If you use this register
in your assembly code, be sure to clear it before returning to compiler generated
code (use "clr r1"). Interrupt routines generated with the compiler save and
restore this register, too.
r2–r17, r28, r29: These registers are used by the compiler for storage. If your
assembly code is called by compiler generated code, you need to save and restore
any of these registers that you use. (r29:r28 is the Y index register and is used
for pointing to the function’s stack frame, if necessary.)
r18–r27, r30, r31: These registers are up for grabs. If you use any of these
registers, you need to save its contents if you call any compiler generated code.
Function call conventions
Fixed Argument Lists: Function arguments are allocated left to right. They are
assigned from r25 to r8, respectively. All arguments take up an even number of
registers (so that the compiler can take advantage of the movw instruction on
enhanced cores.) If more parameters are passed than will fit in the registers, the
rest are passed on the stack. This should be avoided since the code takes a
performance hit when using variables residing on the stack.
Variable Argument Lists: Parameters passed to functions that have a variable
argument list (printf, scanf, etc.) are all passed on the stack. char parameters
are extended to ints.
Return Values: 8-bit values are returned in r24. 16-bit values are returned in
r25:r24. 32-bit values are returned in r25:r24:r23:r22. 64-bit values are returned
in r25:r24:r23:r22:r21:r20:r19:r18.
#include <avr/io.h>
#include <stdint.h>
#define sbi(b,n) (b |= (1<<n)) /* Set bit number n in byte b */
#define cbi(b,n) (b &= (~(1<<n))) /* Clear bit number n in byte b */
/* Keep the counters in registers */
register uint8_t counter1 asm("r2");
register uint8_t counter2 asm("r3");
register uint8_t counter3 asm("r4");
register uint8_t counter4 asm("r5");
register uint8_t counter5 asm("r6");
register uint8_t counter6 asm("r7");
register uint8_t counter7 asm("r8");
register uint8_t counter8 asm("r9");
register uint8_t counter9 asm("r10");
register uint8_t counter10 asm("r11");
register uint8_t counter11 asm("r12");
register uint8_t counter12 asm("r13");
const uint8_t top[12] = {112, 119, 126, 134, 141, 150, 159, 168, 178, 189, 200, 212};
int main(void)
{
while(1)
{
/* Increment each counter */
counter1++;
counter2++;
counter3++;
counter4++;
counter5++;
counter6++;
counter7++;
counter8++;
counter9++;
counter10++;
counter11++;
counter12++;
/* Check if counter is over the top */
if (counter1 > top[0]) {counter1 = 0; }
if (counter2 > top[1]) {counter2 = 0; }
if (counter3 > top[2]) {counter3 = 0; }
if (counter4 > top[3]) {counter4 = 0; }
if (counter5 > top[4]) {counter5 = 0; }
if (counter6 > top[5]) {counter6 = 0; }
if (counter7 > top[6]) {counter7 = 0; }
if (counter8 > top[7]) {counter8 = 0; }
if (counter9 > top[8]) {counter9 = 0; }
if (counter10 > top[9]) {counter11 = 0; }
if (counter11 > top[10]) {counter11 = 0; }
if (counter12 > top[11]) {counter12 = 0; }
/* Compare for 50% square wave */
(counter1 > (top[0]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter2 > (top[1]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter3 > (top[2]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter4 > (top[3]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter5 > (top[4]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter6 > (top[5]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter7 > (top[6]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter8 > (top[7]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter9 > (top[8]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter10 > (top[9]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter11 > (top[10]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
(counter12 > (top[11]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
}
}
counter1++;
0000008A INC R2 Increment
counter2++;
0000008B INC R3 Increment
counter3++;
0000008C INC R4 Increment
counter4++;
0000008D INC R5 Increment
counter5++;
0000008E INC R6 Increment
counter6++;
0000008F INC R7 Increment
counter7++;
00000090 INC R8 Increment
counter8++;
00000091 INC R9 Increment
counter9++;
00000092 INC R10 Increment
counter10++;
00000093 INC R11 Increment
counter11++;
00000094 INC R12 Increment
counter12++;
00000095 INC R13 Increment
if (counter1 > top[0]) {counter1 = 0; }
00000096 LDI R24,0x70 Load immediate
00000097 CP R24,R2 Compare
00000098 BRCC PC+0x02 Branch if carry cleared
00000099 MOV R2,R1 Copy register
0000009A LDI R24,0x77 Load immediate
0000009B CP R24,R3 Compare
0000009C BRCC PC+0x02 Branch if carry cleared
0000009D MOV R3,R1 Copy register
0000009E LDI R24,0x7E Load immediate
0000009F CP R24,R4 Compare
000000A0 BRCC PC+0x02 Branch if carry cleared
000000A1 MOV R4,R1 Copy register
000000A2 LDI R24,0x86 Load immediate
000000A3 CP R24,R5 Compare
000000A4 BRCC PC+0x02 Branch if carry cleared
000000A5 MOV R5,R1 Copy register
000000A6 LDI R24,0x8D Load immediate
000000A7 CP R24,R6 Compare
000000A8 BRCC PC+0x02 Branch if carry cleared
000000A9 MOV R6,R1 Copy register
000000AA LDI R24,0x96 Load immediate
000000AB CP R24,R7 Compare
000000AC BRCC PC+0x02 Branch if carry cleared
000000AD MOV R7,R1 Copy register
000000AE LDI R24,0x9F Load immediate
000000AF CP R24,R8 Compare
000000B0 BRCC PC+0x02 Branch if carry cleared
000000B1 MOV R8,R1 Copy register
000000B2 LDI R24,0xA8 Load immediate
000000B3 CP R24,R9 Compare
000000B4 BRCC PC+0x02 Branch if carry cleared
000000B5 MOV R9,R1 Copy register
000000B6 LDI R24,0xB2 Load immediate
000000B7 CP R24,R10 Compare
000000B8 BRCC PC+0x02 Branch if carry cleared
000000B9 MOV R10,R1 Copy register
000000BA LDI R24,0xBD Load immediate
000000BB CP R24,R11 Compare
000000BC BRCS PC+0x04 Branch if carry set
000000BD LDI R24,0xC8 Load immediate
000000BE CP R24,R12 Compare
000000BF BRCC PC+0x02 Branch if carry cleared
000000C0 MOV R12,R1 Copy register
000000C1 LDI R24,0xD4 Load immediate
000000C2 CP R24,R13 Compare
000000C3 BRCC PC+0x02 Branch if carry cleared
000000C4 MOV R13,R1 Copy register
000000C5 LDI R24,0x38 Load immediate
000000C6 CP R24,R2 Compare
000000C7 BRCC PC+0x2F Branch if carry cleared
000000C8 SBI 0x05,0 Set bit in I/O register
000000C9 LDI R24,0x3B Load immediate
000000CA CP R24,R3 Compare
000000CB BRCC PC+0x2F Branch if carry cleared
000000CC SBI 0x05,0 Set bit in I/O register
000000CD LDI R24,0x3F Load immediate
000000CE CP R24,R4 Compare
000000CF BRCC PC+0x2F Branch if carry cleared
000000D0 SBI 0x05,0 Set bit in I/O register
000000D1 LDI R24,0x43 Load immediate
000000D2 CP R24,R5 Compare
000000D3 BRCC PC+0x2F Branch if carry cleared
000000D4 SBI 0x05,0 Set bit in I/O register
(counter5 > (top[4]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000D5 LDI R24,0x46 Load immediate
000000D6 CP R24,R6 Compare
000000D7 BRCC PC+0x2F Branch if carry cleared
000000D8 SBI 0x05,0 Set bit in I/O register
(counter6 > (top[5]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000D9 LDI R24,0x4B Load immediate
000000DA CP R24,R7 Compare
000000DB BRCC PC+0x2F Branch if carry cleared
000000DC SBI 0x05,0 Set bit in I/O register
(counter7 > (top[6]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000DD LDI R24,0x4F Load immediate
000000DE CP R24,R8 Compare
000000DF BRCC PC+0x2F Branch if carry cleared
000000E0 SBI 0x05,0 Set bit in I/O register
(counter8 > (top[7]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000E1 LDI R24,0x54 Load immediate
000000E2 CP R24,R9 Compare
000000E3 BRCC PC+0x2F Branch if carry cleared
000000E4 SBI 0x05,0 Set bit in I/O register
(counter9 > (top[8]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000E5 LDI R24,0x59 Load immediate
000000E6 CP R24,R10 Compare
000000E7 BRCC PC+0x2F Branch if carry cleared
000000E8 SBI 0x05,0 Set bit in I/O register
(counter10 > (top[9]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000E9 LDI R24,0x5E Load immediate
000000EA CP R24,R11 Compare
000000EB BRCC PC+0x2F Branch if carry cleared
000000EC SBI 0x05,0 Set bit in I/O register
(counter11 > (top[10]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000ED LDI R24,0x64 Load immediate
000000EE CP R24,R12 Compare
000000EF BRCC PC+0x2F Branch if carry cleared
000000F0 SBI 0x05,0 Set bit in I/O register
(counter12 > (top[11]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000F1 LDI R24,0x6A Load immediate
000000F2 CP R24,R13 Compare
000000F3 BRCC PC+0x2F Branch if carry cleared
000000F4 SBI 0x05,0 Set bit in I/O register
000000F5 RJMP PC-0x006C Relative jump
(counter1 > (top[0]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000F6 CBI 0x05,0 Clear bit in I/O register
(counter2 > (top[1]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000F7 LDI R24,0x3B Load immediate
000000F8 CP R24,R3 Compare
000000F9 BRCS PC-0x2D Branch if carry set
000000FA CBI 0x05,0 Clear bit in I/O register
(counter3 > (top[2]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000FB LDI R24,0x3F Load immediate
000000FC CP R24,R4 Compare
000000FD BRCS PC-0x2D Branch if carry set
000000FE CBI 0x05,0 Clear bit in I/O register
(counter4 > (top[3]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
000000FF LDI R24,0x43 Load immediate
00000100 CP R24,R5 Compare
00000101 BRCS PC-0x2D Branch if carry set
00000102 CBI 0x05,0 Clear bit in I/O register
(counter5 > (top[4]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
00000103 LDI R24,0x46 Load immediate
00000104 CP R24,R6 Compare
00000105 BRCS PC-0x2D Branch if carry set
00000106 CBI 0x05,0 Clear bit in I/O register
(counter6 > (top[5]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
00000107 LDI R24,0x4B Load immediate
00000108 CP R24,R7 Compare
00000109 BRCS PC-0x2D Branch if carry set
0000010A CBI 0x05,0 Clear bit in I/O register
(counter7 > (top[6]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
0000010B LDI R24,0x4F Load immediate
0000010C CP R24,R8 Compare
0000010D BRCS PC-0x2D Branch if carry set
0000010E CBI 0x05,0 Clear bit in I/O register
(counter8 > (top[7]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
0000010F LDI R24,0x54 Load immediate
00000110 CP R24,R9 Compare
00000111 BRCS PC-0x2D Branch if carry set
00000112 CBI 0x05,0 Clear bit in I/O register
(counter9 > (top[8]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
00000113 LDI R24,0x59 Load immediate
00000114 CP R24,R10 Compare
00000115 BRCS PC-0x2D Branch if carry set
00000116 CBI 0x05,0 Clear bit in I/O register
(counter10 > (top[9]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
00000117 LDI R24,0x5E Load immediate
00000118 CP R24,R11 Compare
00000119 BRCS PC-0x2D Branch if carry set
0000011A CBI 0x05,0 Clear bit in I/O register
(counter11 > (top[10]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
0000011B LDI R24,0x64 Load immediate
0000011C CP R24,R12 Compare
0000011D BRCS PC-0x2D Branch if carry set
0000011E CBI 0x05,0 Clear bit in I/O register
(counter12 > (top[11]>>1)) ? (sbi(PORTB, 0)) : (cbi(PORTB, 0));
0000011F LDI R24,0x6A Load immediate
00000120 CP R24,R13 Compare
00000121 BRCS PC-0x2D Branch if carry set
00000122 CBI 0x05,0 Clear bit in I/O register
00000123 RJMP PC-0x009A Relative jump
process_channel_1_first_byte:
LDS R24, channel_1_counter
DEC R24
STS R24, channel_1_counter
BREQ process_channel_1_second_byte
process_channel_1_first_byte:
LDS R24, channel_1_counter
DEC R24
STS channel_1_counter, R24
BREQ process_channel_1_second_byte
process_channel_2_first_byte:
LDS R24, channel_2_counter
DEC R24
STS channel_2_counter, R24
BREQ process_channel_2_second_byte
process_channel_1_second_byte:
LDS R24, channel_1_counter+1
DEC R24
STS channel_1_counter+1, R24
BRCC process_channel_2_first_byte ; I hope I got this opcode right?
; our counter has expired
; toggle the LED here. This is my first exercise with AVR.
; I assume this can be done in 6 instructions
; I skip this part
LDI R24, lsb_byte_of_the_channel_1_tick_count
STS channel_1_counter, R24
LDI R24, msb_byte_of_the_channel_1_tick_count
STS channel_1_counter+1, R24
JMP process_channel_2_first_byte
Why? Tell me why?With 16-bit counters, C will do much worse.
Why? Tell me why?
How can any processor update anything in less than one cycle? I did simulations etc. to show some actual data.. could you even try to match that if you are going to put down the work I did for the OP..Assume this runs in 20 cycles, adding 20/128 = 0.15 cycles per channel/per interrupt.
Because it's an 8-bit processor. 16-bit operations must be cretated through a combination of 8-bit operations.
Compile your example with char. Clock the speed. Them change char to int, compile again and clock the speed. You will see that it got slower.
Do you think I'm stupid? I did not declare the variables in my example code to be uint8_t just for fun.. and I did clock the speed.. 128 cycles to update 12 channels. Your post was very confusing.. how fast your solution updates all 12 channels?Compile your example with char. Clock the speed. Them change char to int, compile again and clock the speed. You will see that it got slower.
You said that "C will do much worse".. I do not understand why you think C language is worse with 16bit variables.. it is exactly the same with assembly. Char is for characters.. if you need 16 bit integers then use the proper size variable. I think the main problem you have is that you do not know the C programming language.
That is just stupid.. do you think that assembly program does not get slower if you use 16bit variables instead of 8bit variables?C will be considerably slower with 16-bit variables compared to 8-bit.
Do you think I'm stupid? I did not declare the variables in my example code to be uint8_t just for fun.. and I did clock the speed.. 128 cycles to update 12 channels.
Your post was very confusing.. how fast your solution updates all 12 channels?
If you think assembly is so superior, then post the code.. I mean full code.. like I did.
8bit counter will fit in 8 bit variable. 8 bit counter is enough if you are clever.The problem with this is that counters that we want to use won't fit into uint8_t.
yes.. my example was supposed to show a naive approach to the problem and how much processing time it takes etc.. kind of "worst case scenario".. that gives you an idea of the problem you are dealing with. That is something assembler programmers are not familiar with... apparently.Your 128 cycles number is not actually feasible. During the main loop execution, an USB program will use registers, so when you enter the interrupt, you will have to (a) store all the register you use, (b) load your variables into registers. When you leave, you need to (a) store your variables back, (b) restore registers to what they were at the entry. With 12 registers and 4 operations this is 48 more cycles, so correct count is 176, or somewhere around 300 if you go from 8-bit variables to 16-bit ones.
Ok.. so how many cycles your code takes to update 12 channels? What is confusing for me is that you say that your code takes ~50 cycles, but then you say that my 128 cycles is not feasible..I did post the code in post #26.
Your 128 cycles number is not actually feasible. During the main loop execution, an USB program will use registers, so when you enter the interrupt, you will have to (a) store all the register you use, (b) load your variables into registers. When you leave, you need to (a) store your variables back, (b) restore registers to what they were at the entry. With 12 registers and 4 operations this is 48 more cycles, so correct count is 176, or somewhere around 300 if you go from 8-bit variables to 16-bit ones.
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?