Fast unsigned integer multiply by x100 on 8bit AVR? - Page 1
Forum section: electronics / general

author: beduino
author_rank: Regular Contributor
timestamp: January 14, 2019, 12:26:09 am
content: Hello,Just trying to tell somehow AVR C to reuse x2 in code below for fastermultiply by 100, while x2 can be reused and instead of 13 shifts left,we can have only 6 as C code suggest,but I still get in assembled listing ugly looking compilator assembler codewith 3 loops for 2,5,6 left shiftsIt is code for ATTiny85 with optimisation for size enabled,however can not figure out howto get code with x2 reused and x5,while x6 is only 1 left shift moreProbably I will try rewrite this part in assembler, but it puzles me fora while why such ugly assembler code we get,while I've suggested in C language howto make this code more eficcient or maybe not?

author: Yansi
author_rank: Super Contributor
timestamp: January 14, 2019, 12:47:47 am
content: Why in hell would you want that, if AVR already has a two cycle 8x8 multiplier//EDIT: Sorry, I forgot that the "tinyAVR" garbage can't multiply. :-/ It's been a while I toyed with them.Better to ask question then, why do you need to multiply by a 100? (maybe the task can be optimized in other ways)

author: NorthGuy
author_rank: Super Contributor
timestamp: January 14, 2019, 12:49:48 am
content: If you just use multiplication, good chance the C compiler can figure out everything by itself.

author: beduino
author_rank: Regular Contributor
timestamp: January 14, 2019, 01:06:58 am
content: Quote from: NorthGuy on January 14, 2019, 12:49:48 amNope, __mulsi3 is used when we let C compiler for too muchCode:return (x*100 +x0);Any other ideas?BTW: cut down to 16bit probably will also be fine instead of 32bit, while microsecond time is needed at time periods for frequencies higher than 100Hz, but the same - bloody C compiler generate the same ugly looking loops for x5,x6 - only x2looks good while it is replaced by 2 inline shifts

author: Yansi
author_rank: Super Contributor
timestamp: January 14, 2019, 01:10:05 am
content: So what are you trying to achieve with this. Stop being secret. Otherwise we can't help much.More code is needed, than justreturn (x*100 + x0)What is the range of X and X0? Why does it need to get multiplied? What does it calculate. There may be better ways to implement that.

author: Rerouter
author_rank: Super Contributor
timestamp: January 14, 2019, 01:31:21 am
content: Not that good at reading avr assembler. But it would be number in. Shift twice. Copy result to another register. Shift 3 times. Copy to third register. Shift once. Add all 3 registers.Could this method not condense your assembler. If you need more registers. You always have the 3 gpio registers that dont get touched by the c compiler.

author: blacksheeplogic
author_rank: Frequent Contributor
timestamp: January 14, 2019, 01:31:44 am
content: My guess glancing at the output although I don't know the compiler being used is that the optimizer is reducing register dependencies.

author: IanB
author_rank: Super Contributor
timestamp: January 14, 2019, 01:35:57 am
content: Quote from: beduino on January 14, 2019, 12:26:09 amFirstly, this seems to be an 8-bit micro, and you are trying to work with 32 bit values. So that is bound to lead to complex code since the 32 bit quantities have to be split into 8 bit chunks.Secondly, have you tried writing code that looks more like this: ?Code:uint32_t x0 = TCNT0;uint32_t x = avr_time_counter;x <<= 1;x <<= 1;x0 += x;x <<= 1;x <<= 1;x <<= 1;x0 += x;x <<= 1;x0 += x;return x0;

author: sleemanj
author_rank: Super Contributor
timestamp: January 14, 2019, 02:02:47 am
content: You could use a volatile variable to "decouple" the stages, forcing the compiler to save and read the result between each step...Code:volatile uint32_t t_x;x2 = x<<2; // First Stept_x = x2; // Save itx5 = t_x; // Read itx5 = x5 << 3; // Next Stept_x = x5; // Save itx6 = t_x; // Read itx6 = x6 << 1; // Last stepShould work I expect, of course, shuffling that stuff back and forth between ram and registers won't be likely to save you a lot.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: January 14, 2019, 02:42:49 am
content: You told it to optimize for size; I don't see that chaining the shifts saves much space compared to what it's produced. (Changing the last one from six to one shifts would save the last loop, at least.) Reducing register pressure might help, but who knows. That isn't obvious from the short section, at least.Compilers are smart enough to implement constants in non-obvious ways, like this. They're also smart enough to know if a drop-in library routine is better.You can try link-time optimization and expensive optimizations (-flto, -fexpensive-optimizations) too, see if that helps any.If you explicitly want it written as a loop, you should probably start with it that way, and let the compiler unroll if it wants to. Example:Code:for (i = 0x1e; i; i >>= 2) {x0 >>= (i & 0x03);x1 += x0;}Note the magic number 0x1e coding for the number of shifts to perform, as packed 2-bit integers. Just the first thing that came to mind, maybe a bit too perverse for readability/maintainability's sake, but it should compile to almost exactly the function you were expecting.Tim

author: Yansi
author_rank: Super Contributor
timestamp: January 14, 2019, 02:58:30 am
content: Impressive trick with the for cycle there.

author: cv007
author_rank: Frequent Contributor
timestamp: January 14, 2019, 05:41:24 am
content: I could be way off, and don't really know what you are doing, but maybe you can rethink the problem-assuming TCNT0 will be 0-99 (some timer compare mode), and you are incrementing 'avr_time_counter' in an interrupt,and it also seems you want to be able to get the 'current time'this is what it seems you are doing-tcnt0_compare_match_isr(){ avr_time_counter++; } //inc counter every overflowuint32_t get_time(){ return avr_time_counter*100 + TCNT0; } //but don't want *100if so, maybe this would make more sense-tcnt0_compare_match_isr(){ avr_time_counter += 100; } //inc by 100, not too painfuluint32_t get_time(){uint32_t t0; uint8_t t1;do{t1 = TCNT0;t0 = avr_time_counter;}while(t1 < TCNT0); //do again if overflowedreturn t0+t1;}maybe not any better, but maybe it is (the get_time() could probably be better, just showing that TCNT0 can 'overflow' while dealing with these multi-byte numbers and could end up having a mismatch of tcnt0 and its bigger counter, so simply check if tcnt0 overflowed in the process and do again if so)I'm sure there are also ways to stay with base 2 numbers (and its advantages) when you think you have to use base 10. That's probably not thought about much anymore. but when dealing with an attiny or a smaller pic, it sometimes takes a little effort and creative thinking to keep the multiply/divide code from showing up.

author: westfw
author_rank: Super Contributor
timestamp: January 14, 2019, 08:22:05 am
content: If people are going to play this game, they need to attach the code produced, preferably with instruction and cycle counts...This bit has the advantage of being extremely obvious. Both multiplies are optimized to inline code:Code:int main() { // multiply an 8bit number by 100, by first // multiplying by 25 (to yield a 16bit number) // and then by 4 (to yield 24 bits.)uint16_t x = PORTB;__uint24 x1 = x*25; // always fits in 16bits.x1 *= 4; // fits in 24 bits...PORTB = x1;PORTB = x1>>8; // output all the bits...PORTB = x1>>16;}Produces:Code:0: 88 b3 in r24, 0x18 ; 24__uint24 x1 = x*25;2: 90 e0 ldi r25, 0x00 ; extend to 16 bits.4: 9c 01 movw r18, r246: 22 0f add r18, r188: 33 1f adc r19, r19a: 22 0f add r18, r18c: 33 1f adc r19, r19e: 82 0f add r24, r1810: 93 1f adc r25, r1912: 9c 01 movw r18, r2414: 22 0f add r18, r1816: 33 1f adc r19, r1918: 22 0f add r18, r181a: 33 1f adc r19, r191c: 82 0f add r24, r181e: 93 1f adc r25, r1920: a0 e0 ldi r26, 0x00 ; extend to 24 bits.x1 *= 4;22: 88 0f add r24, r2424: 99 1f adc r25, r2526: aa 1f adc r26, r2628: 88 0f add r24, r242a: 99 1f adc r25, r252c: aa 1f adc r26, r26PORTB = x1;2e: 88 bb out 0x18, r24 ; 24That seems pretty good.Note that __uint24 is pretty new.Annoyingly, there is no uint24_t. Also, gcc will will not inline a __uint24 * 10.

author: westfw
author_rank: Super Contributor
timestamp: January 14, 2019, 11:24:56 am
content: Wait a minute. Why are we calculating this with a 32bit integer? 16 is plenty for 255*100

author: cv007
author_rank: Frequent Contributor
timestamp: January 14, 2019, 04:13:30 pm
content: QuoteHe is adding avr_time_counter to tcnt0 to get some kind of system time. So its not an 8bit number*255, its a larger number*100 + 0 to 255 (or to 99 I presume, otherwise it would be a little odd). How large a number is needed, we don't know.Ultimately, I think his bigger problem is yet unseen- reading TCNT0 while its running and adding to presumably an interrupt incremented 32/24/16bit number. As I have shown, that's easily dealt with, but if not dealt with incorrect times will show up occasionally (and in many cases, very incorrect).

author: Yansi
author_rank: Super Contributor
timestamp: January 14, 2019, 04:15:54 pm
content: Well, I think I might now understand what he is doing.Why don't you just increment the 32bit system time variable by 100 by default, instead of 1? (You would not then need to multiply by a 100).

author: beduino
author_rank: Regular Contributor
timestamp: January 14, 2019, 05:27:50 pm
content: Thanks everyone for brain storm which during lunch get me idea of trying to tell AVR C compiler to do something simpler likeinstead of multiplay by 100, just write optimised multiply by 10 and... input result of one to another to get x100= 10x10x10= x2 * 5, x2= (x<<1)Code:// Multiply by 100inline uint32_t avr_x100_uint32(uint32_t x ) {x= avr_x10_uint32(x ); // x10x= avr_x10_uint32(x ); // x10return x;}Hopefuly, when wrote this crude simply x10=x2*5 this wayCode:// Multiply by 10inline uint32_t avr_x10_uint32(uint32_t x ) {uint32_t x2= (x<<1); // x2x= x2;x+= x2;x+= x2;x+= x2;x+= x2;return x;}Now, no complains for AVR C optimizer which done pretty smart code from this crude brute force aproach shown aboveOnly one loop with 2 iterations for shift by 2 - x4 , but didn't even tried understand whole code generated, but it looks much better than those bloody a few loops 2,5,6Code:85.globalavr_time_us_get87avr_time_us_get:88 007c 0F93push r1689 007e 1F93push r1790/* prologue: function */91/* frame size = 0 */92/* stack size = 2 */93.L__stack_usage = 294 0080 42B7in r20,0x3295 0082 0091 0000lds r16,avr_time_counter96 0086 1091 0000lds r17,avr_time_counter+197 008a 2091 0000lds r18,avr_time_counter+298 008e 3091 0000lds r19,avr_time_counter+399 0092 000Flsl r16100 0094 111Frol r17101 0096 221Frol r18102 0098 331Frol r19103 009a D901movw r26,r18104 009c C801movw r24,r16105 009e 52E0ldi r21,21061:107 00a0 880Flsl r24108 00a2 991Frol r25109 00a4 AA1Frol r26110 00a6 BB1Frol r27111 00a8 5A95dec r21112 00aa 01F4brne 1b113 00ac 800Fadd r24,r16114 00ae 911Fadc r25,r17115 00b0 A21Fadc r26,r18116 00b2 B31Fadc r27,r19117 00b4 880Flsl r24118 00b6 991Frol r25119 00b8 AA1Frol r26120 00ba BB1Frol r27121 00bc 8C01movw r16,r24122 00be 9D01movw r18,r26123 00c0 000Flsl r16124 00c2 111Frol r17125 00c4 221Frol r18126 00c6 331Frol r19127 00c8 040Fadd r16,r20128 00ca 111Dadc r17,__zero_reg__129 00cc 211Dadc r18,__zero_reg__130 00ce 311Dadc r19,__zero_reg__131 00d0 080Fadd r16,r24132 00d2 191Fadc r17,r25133 00d4 2A1Fadc r18,r26134 00d6 3B1Fadc r19,r27135 00d8 080Fadd r16,r24136 00da 191Fadc r17,r25137 00dc 2A1Fadc r18,r26138 00de 3B1Fadc r19,r27139 00e0 BC01movw r22,r24140 00e2 CD01movw r24,r26141 00e4 600Fadd r22,r16142 00e6 711Fadc r23,r17143 00e8 821Fadc r24,r18144 00ea 931Fadc r25,r19145/* epilogue start */146 00ec 1F91pop r17147 00ee 0F91pop r16Quote from: cv007 on January 14, 2019, 04:13:30 pmYep, It is another concern - howto synchronize to get TCNT0 and avr_time_counter not corrupted during ISR compare match - exactly how someone noticed, it iscompare match at 99, since at 8Mhz system clock with 8 timer prescaler we have 1MHz, so time tick is 10kHz, so we need to multiply by 100 avr_time_clock to have estimated time in microseconds used rather for time difference than exact timing, while of course here will be some latency in ISR (avr_time_count++ only btw).Looking for a way to check somehow while readint TCNT0 and avr_time_counter to maybe wait until timer compare match ISR (avr_time_counter++) completes - there should be somewhere in AVR register flag than timer 0 compare match is executed?If someone already did such tricky thing on ATTiny85 let us know...

author: cv007
author_rank: Frequent Contributor
timestamp: January 14, 2019, 05:41:38 pm
content: QuoteAs I already showed you, avr_time_count += 100 would work just as well and eliminate any need for *100. You do get one extra instruction in the isr because of that. One.QuoteI already showed that.

author: Kleinstein
author_rank: Super Contributor
timestamp: January 14, 2019, 05:47:52 pm
content: Reading the counter, that is extended in resolution while there is a possible update from an ISR in between is a known problem.A possible solution is to first stop interrupts, than read the timer register (= low byte) and software counter, than check the interrupt flag. One needs to correct the software counter (+1) fIf there is an ISR pending and the reading from the timer is low (just past causing the interrupt). If there is an interrupt pending but the counter value is high, the interrupt comes after the current reading and thus no correction needed.After that interrups can be enabled again.If x100 takes too much time - why do a divide by 100 with the timer at all. x64 or x128 are easier.The 8 bit timers also usually allow for quite some prescaler values.

author: kosine
author_rank: Regular Contributor
timestamp: January 14, 2019, 05:49:59 pm
content: Just a thought, but how accurate do you need this to be?If you're running the chip using the internal clock it may be off by 5%, and will likely vary between chips. If that's tollerable, then maybe just x96 instead of x100.If it needs to be more accurate, then an external crystal may be required, in which case maybe use one that divides by 2.____I just checked the ATtiny85 datasheet. Section 6.1.6 mentions that it has a compatibility mode for ATtiny15 that recalibrates the internal RC oscillator to 6.4MHz. This is divided by 4 to run the system clock at 1.6MHz. Not sure if that will help, but might be an easier frequency to work with.

author: Kilrah
author_rank: Unknown
timestamp: January 14, 2019, 05:58:07 pm
content: TypicalXY problem. OP was asked several times what he actually wants to do, but instead of answering that insists on his x100 that may not even be a good solution to the actual problem in the first place.So post the full picture if you want good answers, until then everyone's jsut wasting their time.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 14, 2019, 06:09:56 pm
content: Quote from: Kilrah on January 14, 2019, 05:58:07 pmI don't agree with that. Besides OP, there are hundreds of other people who read the thread, and there are even more people who will read this thread in the future. They're not interested in the OP problem (whatever it is). They're presumably interested in multiplication by 100. If, instead, they find a solution to the OP problem, it will be mostly useless to them - they will waste their time, and more and more people will continue reading this thread and wasting their time.

author: Kilrah
author_rank: Unknown
timestamp: January 14, 2019, 06:27:07 pm
content: That's a weird way to think about it...OK going about the x100 thing might help future viewers, but AFAIK the main purpose of this thread isn't to give something to a potential future viewer, it's to solve OP's problem they have now. And a x100 may not be a good solution to solve that problem... and we aren't going to even know whether it is until they properly describe their actual goal.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: January 14, 2019, 06:45:39 pm
content: I say answer the OP question as he posted it. Maybe I want to marry a chicken with a porcupine , don't ask why, if you don't have any approaches then don't reply.

author: beduino
author_rank: Regular Contributor
timestamp: January 14, 2019, 07:57:56 pm
content: Quote from: cv007 on January 14, 2019, 05:41:24 amSorry, didn't notice in a hurry earlier that we have +=100 while was so shocked that AVR C generator was not able generate code with less amount of paintfull loops but 2,5,6 for shifting as mentioned beforeTrying to figure out how this loop showed in your code might help ensure we have consistent timer counter and timer counter.Maybe by using this:OCF0A: Output Compare Flag 0 AfromTIFR – Timer/Counter Interrupt Flag Registerwhile according to Attiny85 datasheet:The OCF0A bit is set when a Compare Match occurs between the Timer/Counter0 and the data in OCR0A – Out-put Compare Register0. OCF0A is cleared by hardware when executing the corresponding interrupt handlingvector.so maybe this OCF0A flag could be usefull, but unsure whether is it cleared by hardware at the end of ISR when "reti" is called from ISR, or earlier at the begining of ISR handlingAnyway, at least 8MHz @ 3.3Vcc or 16MHz @ 5Vcc F_CPU usually will be used, so different system clock is not an option.

author: IanB
author_rank: Super Contributor
timestamp: January 14, 2019, 08:03:08 pm
content: Quote from: beduino on January 14, 2019, 07:57:56 pmYou asked it to optimize for size, which will request it to produce the most compact code. In general loops are more compact than other forms of repetition so optimizing for size may favor loops.

author: cv007
author_rank: Frequent Contributor
timestamp: January 14, 2019, 08:20:06 pm
content: QuoteWell, I did have < wrong (should have been t1 > TCNT0)Code:uint32_t get_time(){uint32_t t0; uint8_t t1;do{t1 = TCNT0;t0 = avr_time_counter;}while(t1 > TCNT0); //do again if overflowedreturn t0+t1;}/*00000006 <get_time>:6: 22 b7 in r18, 0x32 ; 508: 80 91 60 00 lds r24, 0x0060 ; 0x800060 <_edata>c: 90 91 61 00 lds r25, 0x0061 ; 0x800061 <_edata+0x1>10: a0 91 62 00 lds r26, 0x0062 ; 0x800062 <_edata+0x2>14: b0 91 63 00 lds r27, 0x0063 ; 0x800063 <_edata+0x3>18: 32 b7 in r19, 0x32 ; 501a: 32 17 cp r19, r181c: a0 f3 brcs .-24 ; 0x6 <get_time>1e: 68 2f mov r22, r2420: 79 2f mov r23, r2522: 8a 2f mov r24, r2624: 9b 2f mov r25, r2726: 62 0f add r22, r1828: 71 1d adc r23, r12a: 81 1d adc r24, r12c: 91 1d adc r25, r12e: 08 95 ret*/It simply gets 'avr_time_counter' (which has to be a volatile, I assume it is), and also gets TCNT0, then it gets TCNT0 again and if the latest version (TCNT0) is less than t1, TCNT0 must have overflowed so do again. When TCNT0 >= t1, no overflow could have happened (not really true, but assuming you have no other interrupts that can exceed 10ms).t1 = 99t0 = 12300//TCNT0 just rolled over, fired the isr, and is now back heret1 > TCNT0 ? yes, do again - t1 = 99, TCNT now is 0 (less than 99 anyway)t1 = 1t0 = 12400t1 > TCNT0 ? no, all done, no rollover, t1 = 1, TCNT0 = 3 (lets say)QuoteI would dare say that flag is already clear upon entering the isr as hardware clears it 'when executing' the vector- which also means if global interrupts not enabled, no vector execute, no flag clear (whcih means you can poll for it and clear it yourself if interrupts not used).I don't have avr hardware (that I want to dig out), but I think the isr simply becomes-ISR(TIM0_COMPA_vect){avr_time_counter += 100;}for a pic16, I use the nco as a system clock, and essentially do the same thing-https://github.com/cv007/SNaPmate/blob/c334890d41945d3a2af0ab1c50772f087cf514a8/nco.c#L76the nco has a 20bit counter with 2us resoltion (internal 500Khz clock), I keep track of overflows and inc a counter +16, then when time wanted I do the function linked above, and shift my counter up into the upper 12 bits, to get 32bits total

author: bingo600
author_rank: Super Contributor
timestamp: January 14, 2019, 09:13:31 pm
content: The Germans dis some neat 64bit routines here (use google xlate)https://www.mikrocontroller.net/topic/237643?reply_to=2411363#2411398Watch out for "linker trickery" , if replacing the built in routines w. the asm code,/Bingo

author: beduino
author_rank: Regular Contributor
timestamp: January 14, 2019, 09:54:08 pm
content: Quote from: cv007 on January 14, 2019, 08:20:06 pmYep, it looked strange because of at 10kHz timer CTC with 8MHz F_CPU system clock, ISR should complete in fraction of time tick period - by time tick I mean ISR call btw..I've changed ISR code and +=100 looks like this:Code:30__vector_10:31 0014 1F92push r132 0016 0F92push r033 0018 0FB6in r0,__SREG__34 001a 0F92push r035 001c 1124clr __zero_reg__36 001e 8F93push r2437 0020 9F93push r2538 0022 AF93push r2639 0024 BF93push r2740/* prologue: Signal */41/* frame size = 0 */42/* stack size = 7 */43.L__stack_usage = 744 0026 8091 0000lds r24,avr_time_counter45 002a 9091 0000lds r25,avr_time_counter+146 002e A091 0000lds r26,avr_time_counter+247 0032 B091 0000lds r27,avr_time_counter+348 0036 8C59subi r24,-10049 0038 9F4Fsbci r25,-150 003a AF4Fsbci r26,-151 003c BF4Fsbci r27,-152 003e 8093 0000sts avr_time_counter,r2453 0042 9093 0000sts avr_time_counter+1,r2554 0046 A093 0000sts avr_time_counter+2,r2655 004a B093 0000sts avr_time_counter+3,r2756/* epilogue start */57 004e BF91pop r2758 0050 AF91pop r2659 0052 9F91pop r2560 0054 8F91pop r2461 0056 0F90pop r062 0058 0FBEout __SREG__,r063 005a 0F90pop r064 005c 1F90pop r165 005e 1895retiWhen added at the begining of avr_time_us_get() wait for OCF0A cleared in TIFR by ISR during its execution, since OCF0A is set when TCNT0 is reset to 0 in CTC as shown in attached image from Atmega328 intro to interruptsCode://nop();uint8_t isr_is;do {isr_is= ((TIFR & (1<<OCF0A) )==0 ? 0 : 1 );} while (isr_is );//nop();without any do-while loop it looks like this, but have no idea for the momenthowto OCF0A set in TIFR when TCNT0 becomes 0 could be usefull if at all in this function:Code:87avr_time_us_get:88/* prologue: function */89/* frame size = 0 */90/* stack size = 0 */91.L__stack_usage = 092/* #APP */93; 12 "avr_utils.c" 194 007c 0000nop95; 0 "" 296/* #NOAPP */97.L7:98 007e 08B6in __tmp_reg__,0x3899 0080 04FCsbrc __tmp_reg__,4100 0082 00C0rjmp .L7101/* #APP */102; 12 "avr_utils.c" 1103 0084 0000nop104; 0 "" 2105/* #NOAPP */106 0086 22B7in r18,0x32107 0088 6091 0000lds r22,avr_time_counter108 008c 7091 0000lds r23,avr_time_counter+1109 0090 8091 0000lds r24,avr_time_counter+2110 0094 9091 0000lds r25,avr_time_counter+3111 0098 620Fadd r22,r18112 009a 711Dadc r23,__zero_reg__113 009c 811Dadc r24,__zero_reg__114 009e 911Dadc r25,__zero_reg__115 00a0 0895retWe can see that this check for this flag set can be very fast, but probably useless since ISR clear this flag during its execution.Code:97.L7:98 007e 08B6in __tmp_reg__,0x3899 0080 04FCsbrc __tmp_reg__,4100 0082 00C0rjmp .L7I wouldn't like to disable global interrupts and make things simple,so I will look closer to your approach for this synchronization if it fits my needs, but I do not like this do-while loop,since I'd like to catch time difference as fast as possible, so there should be enougth time to do some additional computations, so even x100 multiply shouldn't be such horrible, but by using +=100 trick in ISR now the onlly thing is to get consistent not corrupted "avr_time_counter" 32bit value

author: beduino
author_rank: Regular Contributor
timestamp: January 14, 2019, 09:59:17 pm
content: Quote from: bingo600 on January 14, 2019, 09:13:31 pmThanks for this hint, anyway I do not speak or read German

author: NorthGuy
author_rank: Super Contributor
timestamp: January 14, 2019, 10:05:33 pm
content: Can you make your timer roll over at 256 ticks rather than at 100? This way you eliminate the need for multiplication completely.

author: cv007
author_rank: Frequent Contributor
timestamp: January 14, 2019, 10:29:52 pm
content: QuoteYou cannot make use of that flag if using the isr.Quoteit only repeats if there is a problem. Most of the time it will not repeat, but if you happen to hit the wrong time, you will get 1 repeat. Hardly a deal killer to get the correct time, and you have to do 'something' about the problem.QuoteThat's probably a better idea- just let the counter run and use the overflow irq, increment the counter by 256 in the isr. Probably not a big difference, though (but still better). (the multiplication is eliminated in the +=100 version also, by the way)Code://obviously a lot is missing- like timer setup, and so on, this is minimal to show the idea#include <avr/io.h>#include <avr/interrupt.h>volatile uint32_t avr_time_counter;uint32_t get_time(){uint32_t t;do{t = TCNT0 | avr_time_counter;}while((uint8_t)t > TCNT0); //do again if overflowedreturn t;}int main(void) {}ISR(TIM0_OVF_vect){avr_time_counter += 256;}/*00000006 <get_time>:6: 22 b7 in r18, 0x32 ; 508: 80 91 60 00 lds r24, 0x0060 ; 0x800060 <_edata>c: 90 91 61 00 lds r25, 0x0061 ; 0x800061 <_edata+0x1>10: a0 91 62 00 lds r26, 0x0062 ; 0x800062 <_edata+0x2>14: b0 91 63 00 lds r27, 0x0063 ; 0x800063 <_edata+0x3>18: 68 2f mov r22, r241a: 79 2f mov r23, r251c: 8a 2f mov r24, r261e: 9b 2f mov r25, r2720: 62 2b or r22, r1822: 22 b7 in r18, 0x32 ; 5024: 26 17 cp r18, r2226: 78 f3 brcs .-34 ; 0x6 <get_time>28: 08 95 ret*/

author: T3sl4co1l
author_rank: Super Contributor
timestamp: January 14, 2019, 10:47:13 pm
content: Quote from: snarkysparky on January 14, 2019, 06:45:39 pmThe better lesson -- for OPs and readers alike -- is to recognize that there are better root solutions out there, so don't ask XY Problems.(I was wondering how long it would be until someone noticed this was an XY problem)Tim

author: westfw
author_rank: Super Contributor
timestamp: January 14, 2019, 11:42:33 pm
content: Rats. And I thought I was being so clever with the multiple word sizes...QuoteNope, __mulsi3 is used when we let C compiler for too muchChange the optimization to -O3, and it will produce inline code for the 32bit multiply by 100...I believe that there are gcc-specific pragmas that will allow to to change optimization for a specific function.Code:volatile uint32_t counter;uint32_t gettime() {uint32_t y = counter;0: 40 91 00 00 lds r20, 0x0000 ; 0x800000 <__SREG__+0x7fffc1>4: 50 91 00 00 lds r21, 0x0000 ; 0x800000 <__SREG__+0x7fffc1>8: 60 91 00 00 lds r22, 0x0000 ; 0x800000 <__SREG__+0x7fffc1>c: 70 91 00 00 lds r23, 0x0000 ; 0x800000 <__SREG__+0x7fffc1>y *= 100;10: 44 0f add r20, r2012: 55 1f adc r21, r2114: 66 1f adc r22, r2216: 77 1f adc r23, r2318: 44 0f add r20, r201a: 55 1f adc r21, r211c: 66 1f adc r22, r221e: 77 1f adc r23, r2320: db 01 movw r26, r2222: ca 01 movw r24, r2024: 88 0f add r24, r2426: 99 1f adc r25, r2528: aa 1f adc r26, r262a: bb 1f adc r27, r272c: 88 0f add r24, r242e: 99 1f adc r25, r2530: aa 1f adc r26, r2632: bb 1f adc r27, r2734: 48 0f add r20, r2436: 59 1f adc r21, r2538: 6a 1f adc r22, r263a: 7b 1f adc r23, r273c: db 01 movw r26, r223e: ca 01 movw r24, r2040: 88 0f add r24, r2442: 99 1f adc r25, r2544: aa 1f adc r26, r2646: bb 1f adc r27, r2748: 88 0f add r24, r244a: 99 1f adc r25, r254c: aa 1f adc r26, r264e: bb 1f adc r27, r2750: 84 0f add r24, r2052: 95 1f adc r25, r2154: a6 1f adc r26, r2256: b7 1f adc r27, r23y += TCNT0;58: 22 b7 in r18, 0x32 ; 50return y;5a: bc 01 movw r22, r245c: cd 01 movw r24, r265e: 62 0f add r22, r1860: 71 1d adc r23, r162: 81 1d adc r24, r164: 91 1d adc r25, r1}66: 08 95 ret

author: beduino
author_rank: Regular Contributor
timestamp: January 15, 2019, 12:20:07 am
content: Quote from: cv007 on January 14, 2019, 10:29:52 pmAnyway, I've decided to... do not use do-while loop, but instead added correction for time - something like back in timeQuote from: cv007 on January 14, 2019, 10:29:52 pmBut, I've used this flag as you can see in assembler code aboveWarning: Do not try this code at home - it is not tested yet, but "Patent pending"Update: Bug fixed in assembler listing belowCode:85.globalavr_time_us_get87avr_time_us_get:88/* prologue: function */89/* frame size = 0 */90/* stack size = 0 */91.L__stack_usage = 092 007c 22B7in r18,0x3293.L7:94 007e 08B6in __tmp_reg__,0x3895 0080 04FCsbrc __tmp_reg__,496 0082 00C0rjmp .L797 0084 6091 0000lds r22,avr_time_counter98 0088 7091 0000lds r23,avr_time_counter+199 008c 8091 0000lds r24,avr_time_counter+2100 0090 9091 0000lds r25,avr_time_counter+3101 0094 32B7in r19,0x32102 0096 3217cp r19,r18103 0098 00F4brsh .L8104 009a 6091 0000lds r22,avr_time_counter105 009e 7091 0000lds r23,avr_time_counter+1106 00a2 8091 0000lds r24,avr_time_counter+2107 00a6 9091 0000lds r25,avr_time_counter+3108 00aa 6456subi r22,100109 00ac 7109sbc r23,__zero_reg__110 00ae 8109sbc r24,__zero_reg__111 00b0 9109sbc r25,__zero_reg__112.L8:113 00b2 620Fadd r22,r18114 00b4 711Dadc r23,__zero_reg__115 00b6 811Dadc r24,__zero_reg__116 00b8 911Dadc r25,__zero_reg__117 00ba 0895retThanks for many hints

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 15, 2019, 01:31:35 am
content: Quote from: T3sl4co1l on January 14, 2019, 10:47:13 pmReplace"don't ask XY problems"with"describe what you are trying to solve, rather than the problems you are having with your chosen solution to the original problem", and I'll agree. Otherwise it sounds like you don't want OP and readers alike to ask about their problems.For what it's worth, I'd start withCode:#include <stdint.h>static volatile unsigned char avr_timer_pre = 0;static volatile unsigned char avr_timer_post = 0;static volatile uint32_t avr_timer_counter = 0;#define AVR_TIMER_STEP 128void tcnt0_compare_match_isr(void){avr_timer_pre++;avr_timer_counter += AVR_TIMER_STEP;avr_timer_post++;}uint32_t get_time(void){uint32_t counter;unsigned char generation;do {generation = avr_timer_post;counter = avr_timer_counter;} while (generation != avr_timer_pre);return counter;}uint32_t get_time_coarse(void){uint32_t counter;unsigned char generation;do {generation = avr_timer_post;counter = avr_timer_counter;} while (generation != avr_timer_pre);return counter >> 7;}where the multiplier is 128 instead of 100. Theget_timer()returns the original clock, andget_timer_coarse()the slower clock.In a real implementation, mark all these functionsstatic inline, so the compiler can inline them in their callsites; while that increases code size, it can cut down on unnecessary register moves.Theavr_timer_preandavr_timer_postform a generation counter pair. It assumes the hardware does not reorder normal reads and writes to ram. Any timer modification begins with modifying the pre counter, and completes when modifying the post counter. When reading the timer counter, you start by remembering the post counter, then copy the timer counter. If the pre counter does not match the remembered post counter, reading the timer counter was interrupted by a modification, and you redo the entire operation. This is essentially a spin lock, where writers are never interrupted, but readers may have to spin. (Readers will only spin when each iteration is interrupted by a timer update; thus at most twice in normal operation.)With old avr-gcc-4.9.2, using-Wall -Os -mmcu=attiny85, that gets you (omitting directives for simplicity)Code:tcnt0_compare_match_isr:lds r24,avr_timer_presubi r24,lo8(-(1))sts avr_timer_pre,r24lds r24,avr_timer_counterlds r25,avr_timer_counter+1lds r26,avr_timer_counter+2lds r27,avr_timer_counter+3subi r24,-128sbci r25,-1sbci r26,-1sbci r27,-1sts avr_timer_counter,r24sts avr_timer_counter+1,r25sts avr_timer_counter+2,r26sts avr_timer_counter+3,r27lds r24,avr_timer_postsubi r24,lo8(-(1))sts avr_timer_post,r24retget_time:.L3:lds r25,avr_timer_postlds r20,avr_timer_counterlds r21,avr_timer_counter+1lds r22,avr_timer_counter+2lds r23,avr_timer_counter+3lds r24,avr_timer_precpse r25,r24rjmp .L3movw r24,r22movw r22,r20retget_time_coarse:.L7:lds r25,avr_timer_postlds r20,avr_timer_counterlds r21,avr_timer_counter+1lds r22,avr_timer_counter+2lds r23,avr_timer_counter+3lds r24,avr_timer_precpse r25,r24rjmp .L7movw r24,r22movw r22,r20ldi r18,71:lsr r25ror r24ror r23ror r22dec r18brne 1bretNow, let's say you wanted both a fine timer (every tick) and a coarse timer (every 1000th tick), and the division by one thousand is problematic. If you can accept an additional cost to the interrupt service routine, you can provide both, with zero added cost to readers. (The downside is jitter in the ISR duration; every thousandth one takes twice as long as a normal call.)Code:#include <stdint.h>static volatile unsigned char avr_timer_pre;static volatile uint32_t avr_timer_fine; /* = AVR_COARSE_STEPS * avr_timer_coarse + avr_timer_step */static volatile uint32_t avr_timer_coarse;static volatile uint16_t avr_timer_step;static volatile unsigned char avr_timer_post;#define AVR_COARSE_STEPS 1000void tcnt0_compare_match_isr(void){uint16_t step;avr_timer_pre++;avr_timer_fine++;step = avr_timer_step;if (step >= AVR_COARSE_STEPS - 1) {avr_timer_coarse++;avr_timer_step = 0;} else {avr_timer_step = step + 1;}avr_timer_post++;}uint32_t get_time_coarse(void){uint32_t coarse;unsigned char generation;do {generation = avr_timer_post;coarse = avr_timer_coarse;} while (generation != avr_timer_pre);return coarse;}uint32_t get_time_fine(void){uint32_t fine;unsigned char generation;do {generation = avr_timer_post;fine = avr_timer_fine;} while (generation != avr_timer_pre);return fine;}The ISR becomesCode:tcnt0_compare_match_isr:lds r24,avr_timer_presubi r24,lo8(-(1))sts avr_timer_pre,r24lds r24,avr_timer_finelds r25,avr_timer_fine+1lds r26,avr_timer_fine+2lds r27,avr_timer_fine+3adiw r24,1adc r26,__zero_reg__adc r27,__zero_reg__sts avr_timer_fine,r24sts avr_timer_fine+1,r25sts avr_timer_fine+2,r26sts avr_timer_fine+3,r27lds r24,avr_timer_steplds r25,avr_timer_step+1cpi r24,-25ldi r18,3cpc r25,r18brlo .L2lds r24,avr_timer_coarselds r25,avr_timer_coarse+1lds r26,avr_timer_coarse+2lds r27,avr_timer_coarse+3adiw r24,1adc r26,__zero_reg__adc r27,__zero_reg__sts avr_timer_coarse,r24sts avr_timer_coarse+1,r25sts avr_timer_coarse+2,r26sts avr_timer_coarse+3,r27sts avr_timer_step+1,__zero_reg__sts avr_timer_step,__zero_reg__rjmp .L3.L2:adiw r24,1sts avr_timer_step+1,r25sts avr_timer_step,r24.L3:lds r24,avr_timer_postsubi r24,lo8(-(1))sts avr_timer_post,r24retand if you useAVR_COARSE_STEPSless than 256, you can changeavr_timer_stepandstepvariables tounsigned chartype, and simplify it even further.A much more interesting is the case where you want a normal timer tick, but also a slower adjustable/runtime calibrated tick. We can use a 16-bit tick rate, so that the slower tick counter is (ignoring overflows) fast*rate/65536. If you want a /10 slower clock, you can choose between 6553 and 6554, corresponding to 1:10.000916 and 1:9.99939 (or 0.099991 and 0.100006), respectively:Code:#include <stdint.h>static volatile unsigned char avr_timer_pre;static volatile uint32_t avr_timer_fast;static volatile uint32_t avr_timer_slow;static volatile uint16_t avr_timer_phase;static uint16_t avr_timer_rate;static volatile unsigned char avr_timer_post;void tcnt0_compare_match_isr(void){uint16_t phase;avr_timer_pre++;avr_timer_fast++;phase = avr_timer_phase;phase += avr_timer_rate;avr_timer_phase = phase;avr_timer_slow += (phase < avr_timer_rate);avr_timer_post++;}uint32_t get_time_fast(void){uint32_t fast;unsigned char generation;do {generation = avr_timer_post;fast = avr_timer_fast;} while (generation != avr_timer_pre);return fast;}uint32_t get_time_slow(void){uint32_t slow;unsigned char generation;do {generation = avr_timer_post;slow = avr_timer_slow;} while (generation != avr_timer_pre);return slow;}This yieldsCode:tcnt0_compare_match_isr:lds r24,avr_timer_presubi r24,lo8(-(1))sts avr_timer_pre,r24lds r24,avr_timer_fastlds r25,avr_timer_fast+1lds r26,avr_timer_fast+2lds r27,avr_timer_fast+3adiw r24,1adc r26,__zero_reg__adc r27,__zero_reg__sts avr_timer_fast,r24sts avr_timer_fast+1,r25sts avr_timer_fast+2,r26sts avr_timer_fast+3,r27lds r24,avr_timer_phaselds r25,avr_timer_phase+1sts avr_timer_phase+1,r25sts avr_timer_phase,r24lds r24,avr_timer_slowlds r25,avr_timer_slow+1lds r26,avr_timer_slow+2lds r27,avr_timer_slow+3sts avr_timer_slow,r24sts avr_timer_slow+1,r25sts avr_timer_slow+2,r26sts avr_timer_slow+3,r27lds r24,avr_timer_postsubi r24,lo8(-(1))sts avr_timer_post,r24retget_time_fast:.L3:lds r19,avr_timer_postlds r22,avr_timer_fastlds r23,avr_timer_fast+1lds r24,avr_timer_fast+2lds r25,avr_timer_fast+3lds r18,avr_timer_precpse r19,r18rjmp .L3retget_time_slow:.L7:lds r19,avr_timer_postlds r22,avr_timer_slowlds r23,avr_timer_slow+1lds r24,avr_timer_slow+2lds r25,avr_timer_slow+3lds r18,avr_timer_precpse r19,r18rjmp .L7retAgain, using just a 8-bit rate would still give you plenty of precision, to within 0.2% of the fast rate. For /10, 25 would correspond to 1:10.24 (0.09765625), and 26 to 1:9.846154 (0.1015625), but would simplify the timer interrupt service a bit.While the ISR does take roughly twice as long as the simple version, it executes the exact same instructions on every call, so it should take the exact same number of cycles each time. This makes it much easier to check the effects of the ISR latency; no oddball cases where the latency is much higher.Do note that this is not what I'd necessarily go with, because I haven't used ATtiny85's for anything time critical, and I just whipped up the above code without testing it on actual hardware.

author: cv007
author_rank: Frequent Contributor
timestamp: January 15, 2019, 02:17:56 am
content: QuoteSo you want to replace 36 bytes of code/14 instructions, with 52 bytes/22 instructions? You certainly can do whatever you want to. I'm not sure what you are doing with that flag, as you cannot catch it if you are using the compare irq.I've probably said enough, and you have enough info already.

author: beduino
author_rank: Regular Contributor
timestamp: January 15, 2019, 12:56:44 pm
content: Quote from: cv007 on January 15, 2019, 02:17:56 amOne can imagine that similar to this example shown below described here:http://maxembedded.com/2011/07/avr-timers-ctc-mode/Code:// loop foreverwhile(1){// check whether the flag bit is set// if set, it means that there has been a compare match// and the timer has been cleared// use this opportunity to toggle the ledif (TIFR & (1 << OCF1A)) // NOTE: '>=' used instead of '=='{PORTC ^= (1 << 0); // toggles the led}// wait! we are not done yet!// clear the flag bit manually since there is no ISR to execute// clear it by writing '1' to it (as per the datasheet)TIFR |= (1 << OCF1A);// yeah, now we are done!}We would like to disable ISR interrupt so simply by adding a few lines of code now I can use the same time function to catch in a loop time differences without waste time in interrupt ISR, when time between time retrieval is less than 100us, so this TIFR OCF0A flag can be very usefull :Code:inline uint32_t avr_time_us_get_inline() {uint8_t x0 = TCNT0;uint8_t time_flag_is;if( time_flag_is= ((TIFR & (1<<OCF0A) )==0 ? 0 : 1 ) ) {// Manually correct time counter while probably disabled ISRavr_time_counter+= 100;// Clear OCF0A flag by writing logic 1TIFR |= (1<<OCF0A);} // if....

author: cv007
author_rank: Frequent Contributor
timestamp: January 15, 2019, 04:45:13 pm
content: QuoteAnd yet I continueMaybe you already know this, or maybe you already have a truckload of tiny85's you need to use, but I'm sure the attiny family of parts will also have something available (even in 8pins) that has an input capture feature which would make it easier to get a more accurate time at the point of the event (whatever it may be), and with higher resolution. You are finding out why they came up with the input capture feature.

author: beduino
author_rank: Regular Contributor
timestamp: January 15, 2019, 06:43:01 pm
content: Quote from: cv007 on January 15, 2019, 04:45:13 pmMaybe, but for the moment, I've managed to quite easy extend time counter to....64bit which allows collect microseconds for...more than 200000 years32bit time counter could overflow after hour, but thanks to removeing "volatile" statement,when time microseconds needed from last seond or so, assembler code looks much better while loading this 'avr_time_count_max'variable to uint32_t variable - not needed LDS for hi32(avr_time_count) are not generated, so we are able to make "time traveling" hundreds/thousands years and still get event timimgs in the magnitude of microseconds when neededCode:#ifdef AVR_TIME_COUNTER_UINT64T// (%i1) avr_time_count_max: 2^64;// (%o1) 18446744073709551616// (%i2) avr_time_count_max/1000000.0/83376000.0;// (%o2) 221247.6500876697 static uint64_t avr_time_counter;#else// (%i3) avr_time_count_max: 2^32;// (%o3) 4294967296// (%i6) avr_time_count_max/1000000.0/3600;// (%o6) 1.193046471111111 static uint32_t avr_time_counter;#endif // AVR_TIME_COUNTER_UINT64TI hope, that harddware during ISR execution clears timer interrupt flag at the begining, but it is easy to debug, so time to run some code and see logic analyser outputsBTW: It was supprised that there is no left shift with bits number as second parameter on those tiny "RISCy" AVR's

author: Yansi
author_rank: Super Contributor
timestamp: January 15, 2019, 11:22:26 pm
content: I can't get the idea, why would one need such large system tick counter.Typically, it is just used for timing of tasks, probably some coarse interval measurements and such. There is no need to make it for 2000000 years long, unless you want to measure time intervals of such size.uin16_t millisecond timer is sufficient for 65 seconds. If no time interval used in the application will be larger than that, there is little to no use of making it larger.It overflows after the minute or so I hear you saying? So what? There's no problem, if you know how to write code correctly, to accommodate for the overflow.Tip: if writing delays or checking for intervals, use condition written as this: (actual_time - time_stamp > delay_interval). This way the wrap-around will work correctly.

author: tggzzz
author_rank: Super Contributor
timestamp: January 15, 2019, 11:47:14 pm
content: Quote from: Yansi on January 15, 2019, 11:22:26 pmThat rather depends on the processor, of course.The XMOS xCORE processors have 32 bit timers with a 40s interval - because they are counting instructions in their 100MHz/4000MIPS processors. They also have 16 bit timers on each I/O port counting I/O clock cycles at up to 250MHz. The combination of those timers and the architecuture means they can guarantee exactly when output will occur or when input did occur - and the program responds with a 10ns latency

author: Yansi
author_rank: Super Contributor
timestamp: January 16, 2019, 12:35:40 am
content: I think that does not disprove anything I've stated above.Any modern 32bit architecture provides means of clock counting.And BTW, I wouldn't touch XMOS chips with a stick. Blargh.

author: beduino
author_rank: Regular Contributor
timestamp: January 16, 2019, 12:49:39 am
content: Quote from: tggzzz on January 15, 2019, 11:47:14 pmNot too much room for fancy playing with timers on 8 pin Attiny85 since they are 8bit, so in combination with clocks 8MHz/16MHz timer in CTC mode has compare much at 100-1/200-1 when 8 prescaler used, which means that this way just by cating timer counter TCNT0 time differences we have microsecond time differences in 100us window - 64bit time counter can be used to try catch events longer than 1 hour, but for shorter ime differences even 16bit time counter can be used by using only 2 bytes of longer time counter.Regardless of how many bytes we will use to implement longer timer counter than hardware 8bit timers on this device,still a key is to try synchronize reading extender counter with real hardware TCNT0 timer register if, we need something bigger than 100us time difference, so to be below milisecond we need another software time couner byte, so 64bit/32bit/16bit or even 48bit timer counter can be implemented in software depending on application.Since, I've managed howto include in AVR C assembler code to quite easy increment in ISR timer counters of any byte size - eg. 64bit in this example code below, I can define if wanted eg. AVR_TIME_COUNTER_UINT48 for example and do not loose time for 64bitsCode://ISR...asm volatile ("add %, % \n\t""adc %, __zero_reg__ \n\t""adc %, __zero_reg__ \n\t""adc %, __zero_reg__ \n\t"#ifdef AVR_TIME_COUNTER_UINT64T"adc %, __zero_reg__ \n\t""adc %, __zero_reg__ \n\t""adc %, __zero_reg__ \n\t""adc %, __zero_reg__ \n\t"#endif // AVR_TIME_COUNTER_UINT64T"sts avr_time_counter, % \n\t""sts avr_time_counter+1, % \n\t""sts avr_time_counter+2, % \n\t""sts avr_time_counter+3, % \n\t"#ifdef AVR_TIME_COUNTER_UINT64T"sts avr_time_counter+4, % \n\t""sts avr_time_counter+5, % \n\t""sts avr_time_counter+6, % \n\t""sts avr_time_counter+7, % \n\t"#endif // AVR_TIME_COUNTER_UINT64T::"r"(rinc),"a"(*(patc+0) ), "a"(*(patc+1)),"a"(*(patc+2)),"a"(*(patc+3))#ifdef AVR_TIME_COUNTER_UINT64T,"a"(*(patc+4)), "a"(*(patc+5)),"a"(*(patc+6)),"a"(*(patc+7))#endif // AVR_TIME_COUNTER_UINT64T);...reti

author: NorthGuy
author_rank: Super Contributor
timestamp: January 16, 2019, 01:48:47 am
content: Quote from: beduino on January 16, 2019, 12:49:39 amModern PIC16s let you concatenate hardware timers, so you can create a big timer in hardware. Not sure about 64-bit though.Everything is related to time. You may have tasks which need very fine time resolution. Your code also executes in time. Therefore, if you want fine time resolution, you either need to count every cycle of your code, or you will need to use something which is not affected by code execution timing, such as CPP modules.When you need to measure big periods of time, such as days, high resolution cannot be achieved, and even if it could, you usually don't need it for the tasks which measure time in days. Say, if you want to make backup every day, it doesn't matter if it is few minutes earlier or late.Therefore, it is silly to use one single timer for everything. Chips will usually have multiple timers which you can use for different purposes.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 16, 2019, 05:38:34 am
content: Here is a proper suggestion for ATtiny85, for a 32-bit TIMER0 counter:Code:#include <stdint.h>static volatile uint8_t avr_timer_updates;static volatile uint32_t avr_timer_counter;extern uint32_t get_timer(void);extern uint32_t get_timer_coarse(void);with the TIMER0 overflow interrupt,get_timer(), andget_timer_coarse()functions implemented in assembly inasm-timer0.s:Code:.file "asm-timer0.s"; SPDX-License-Identifier: CC0-1.0__SP_H__ = 0x3e__SP_L__ = 0x3d__SREG__ = 0x3f__tmp_reg__ = 0__zero_reg__ = 1.text;; timer0 overflow interrupt vector;.global __vector_5.type __vector_5, @function__vector_5:; ISR prologpush r1push r0in r0, __SREG__push r0clr __zero_reg__push r20push r19push r18lds r20, avr_timer_updates+1inc r20sts avr_timer_updates+1, r20in r19, 0x29 ; OCR0Alds r18, avr_timer_counter+0add r18, r19sts avr_timer_counter+0, r18brcc .donelds r18, avr_timer_counter+1inc r18sts avr_timer_counter+1, r18brne .donelds r18, avr_timer_counter+2inc r18sts avr_timer_counter+2, r18brne .donelds r18, avr_timer_counter+3inc r18sts avr_timer_counter+3, r18.done:sts avr_timer_updates+0, r20pop r18pop r19pop r20; ISR epilogpop r0out __SREG__, r0pop r0pop r1reti.size __vector_5, .-__vector_5.global get_timer.type get_timer, @functionget_timer:lds r21, avr_timer_updates+0lds r22, avr_timer_counter+0lds r23, avr_timer_counter+1lds r24, avr_timer_counter+2lds r25, avr_timer_counter+3in r18, 0x32 ; r18 = TCNT0in r19, 0x38 ; r19 = TIFRsbrc r19, 1 ; TOV0rjmp get_timerlds r20, avr_timer_updates+1cpse r20, r21rjmp get_timeradd r22, r18adc r23, __zero_reg__adc r24, __zero_reg__adc r25, __zero_reg__ret.size get_timer, .-get_timer.global get_timer_coarse.type get_timer_coarse, @functionget_timer_coarse:lds r21, avr_timer_updates+0lds r22, avr_timer_counter+0lds r23, avr_timer_counter+1lds r24, avr_timer_counter+2lds r25, avr_timer_counter+3lds r20, avr_timer_updates+1cpse r20, r21rjmp get_timerret.size get_timer_coarse, .-get_timer_coarse.comm avr_timer_counter,4,1.comm avr_timer_updates,2,1Just feed thatasm-timer0.sfile to avr-gcc as if it was a C file. Not tested on actual ATtiny85 hardware, but it does compile using old avr-gcc-4.9.2 (avr-gcc-4.9.2 -Wall -mmcu=attiny85 -c asm-timer0.s), and the logic is sound, but do beware of bugs.The idea is that whenever an overflow interrupt occurs, theavr_timer_countervalue is incremented byOCR0A. The second of theavr_timer_updatesbytes is incremented before the counter is incremented, and the first after the counter is incremented, so that readers can spin if an interrupt occurs.Theget_timer()function addsTCNT0to the counter value, so the result is essentially the 32-bit TIMER0 virtual counter. It uses both theavr_timer_updatesguard bytes, andTOV0bit inTIFRto detect if the combined 32-bit counter is valid. If interrupts occur too often, it might spin forever; so test before use.Theget_timer_coarse()function omits theTCNT0andTOV0bit checks, and so is more lightweight, although the value is coarser. Although the timers are derived from the same source, you should not mix the values, unless you are prepared forget_timer_coarse() < get_timer()even if obtained at the very same moment somehow.The timer ISR itself is a bit tricky, as (256/OCR0A) of ticks only take 25 instructions (I didn't bother to calculate cycle counts), and uses only six bytes of stack. Of the other cases, it takes 29, 33, or 36 instructions. If the jitter a variable-duration TIMER0 ISR is problematic, the code can be changed to fixed 33 instructions instead (no jumps nor conditional jumps), usingCode:;; timer0 overflow interrupt vector;.global __vector_5.type __vector_5, @function__vector_5:; ISR prologpush r1push r0in r0, __SREG__push r0clr __zero_reg__push r20push r19push r18lds r20, avr_timer_updates+1inc r20sts avr_timer_updates+1, r20in r19, 0x29 ; OCR0Alds r18, avr_timer_counter+0add r18, r19sts avr_timer_counter+0, r18lds r18, avr_timer_counter+1adc r18, __zero_reg__sts avr_timer_counter+1, r18lds r18, avr_timer_counter+2adc r18, __zero_reg__sts avr_timer_counter+2, r18lds r18, avr_timer_counter+3adc r18, __zero_reg__sts avr_timer_counter+3, r18sts avr_timer_updates+0, r20pop r18pop r19pop r20; ISR epilogpop r0out __SREG__, r0pop r0pop r1reti.size __vector_5, .-__vector_5I have no idea which one performs better in practice.Note that lds+adc+sts pattern that uses only one register to update all bytes in a multibyte integer works, because neither lds nor sts modify the carry flag; only adc does. I think avr-gcc only uses N registers for N-byte integers, because that way the externally visible change occurs in one short window, shortening race windows. In my case, using the two generation/update counters and spinning until the match avoid any need for that. In an ISR, it is useful because it lessens the amount of stack used. Probably could reduce the stack use even more, but I just grabbed the ISR prolog and epilog from what avr-gcc generates forISR(TIMER0_OVF_vect) { ... }when using#include <avr/interrupt.h>.

author: beduino
author_rank: Regular Contributor
timestamp: January 16, 2019, 09:25:18 am
content: Quote from: Nominal Animal on January 16, 2019, 05:38:34 amI was thinking about lds+adc+sts pattern, but still learning howto include assembler code inside AVR C code and strugled to manage pass "avr_time_counter" to get given byte from multibyte uint32_t ot uint64_t to register,so for the moment assembler listing of ISR looks like this and of course the less "push/pop" stack operations the better since it costs 2 cycles for each.Code:// ISR...51 0034 E0E0ldi r30,lo8(avr_time_counter)52 0036 F0E0ldi r31,hi8(avr_time_counter)53 0038 2081ld r18,Z54 003a 3181ldd r19,Z+155 003c 4281ldd r20,Z+256 003e 5381ldd r21,Z+357 0040 6481ldd r22,Z+458 0042 7581ldd r23,Z+559 0044 1681ldd r17,Z+660 0046 0781ldd r16,Z+761 0048 84E6ldi r24,lo8(100)62/* #APP */63; 277 "avr_utils.c" 164 004a 280Fadd r18, r2465 004c 311Dadc r19, __zero_reg__66 004e 411Dadc r20, __zero_reg__67 0050 511Dadc r21, __zero_reg__68 0052 611Dadc r22, __zero_reg__69 0054 711Dadc r23, __zero_reg__70 0056 111Dadc r17, __zero_reg__71 0058 011Dadc r16, __zero_reg__...Note, that those ldi/ld/ldd inline assembler code was automatically added by AVR C compiler,when I've those registers like shown a few posts above:Code:...asm volatile(....::"a"(*(patc+0) ), "a"(*(patc+1)),"a"(*(patc+2)),"a"(*(patc+3)),... );...Not sure howto implement in inline assembler something like this:Code:asm volatile( "lds %, avr_time_counter+X \n\t sts avr_time_counter, % " :???:??? );where "ratcx" in Nx byte from N byte time counterUpdate: Never mind - I've found answer howto use LDS in inline AVR C assebler here:https://www.avrfreaks.net/forum/inline-asm-3Code:static inline void test ( uint8_t ok );uint8_t input = 100;static inline void test ( uint8_t ok ){asm volatile("\n\t""lds %, input" "\n\t":  "=d" ( ok ):);}Futher optimisations to timer ISR can be made if needed easy now to support any multibyte time counterAnyway, it is yet another optimisation possible, but I believe in that TCNT0 should be enougth to make decent guard and more - I'm more interest in timing differences when time retrieval function was called, so the only way to do so is I think is to use TCNT0 when this time get function is called, so any other updates to TCNT0 in hardware while processing additional code in get time function, especially reading TCNT0 and adding changed TCNT0 values to software time counter, while time is still running is not too good, so in my implementation in the case when get time function might be hit while processing code after TCNT0 was read at the begining by ISR handling software time counter (whatever size in bytes it has) I simply try to correct this - when ISR finishes we have updated timer counter and TCNT0 starting count from 0 again in timer CTC mode, so we might have quite decent time, but it may be different (longer) than when we called get time function, since you are in do-while loop while waiting for ISR to completeThat is why I've marked in one of those assembler listings of ISR "Patent pending", just for fun, to send a message that something else is going there in my get time implementation since this is what I'm interested in to achieve with extended software time counter and real time TCNT0 update by timer hardware in microsecond intervals on 8MHz Attiny85 @ 10kHz timer ISR

author: beduino
author_rank: Regular Contributor
timestamp: January 16, 2019, 01:20:05 pm
content: 48bit time counter ISR with 100 increment for 8MHz @ 10kHz timer now looks good, while written in AVR C inline assembler - only one register r24 needed to push/pop and temporary r0 as well as zero register r1Time counter stored as "uint64_t", but in ISR only 6 bytes incremented which is enougth even while storing microseconds there for more than a year:Code:// (%i2) avr_time_count_max: 2^48;// (%o2) 281474976710656// (%i3) avr_time_count_max/1000000.0/83376000.0;// (%o3) 3.375971223261562

author: mikerj
author_rank: Super Contributor
timestamp: January 16, 2019, 01:36:22 pm
content: Quote from: Yansi on January 15, 2019, 11:22:26 pmNot actually true for a free running timer, which is what you are describing in the last sentence. The longest time you can cover from a 16 bit free running timer incrementing a 1ms interval is 32.7 seconds.

author: Yansi
author_rank: Super Contributor
timestamp: January 16, 2019, 02:45:10 pm
content: Why so?If I make interval > 1/2 of the range, then it still wraps correctly.For example having a 3bit counter, I can still use that to produce interval over 3, say for example 5.So for example if the timestamp is 2, then at time of 7 i get the difference of 5, at time of 0 I get 6, works correctly.If I pick another timestamp, for example 5, then at time of 2 I get the difference of 5, at time of 3, I get 6.So where do you see the problem? cause I don]t.

author: Leiothrix
author_rank: Regular Contributor
timestamp: January 16, 2019, 10:23:40 pm
content: Quote from: mikerj on January 16, 2019, 01:36:22 pmNo, 65.5 seconds. You'd use an unsigned int to hold the counter, not signed.

author: beduino
author_rank: Regular Contributor
timestamp: January 18, 2019, 11:01:16 pm
content: For short time differences lower than 65ms with ISR disabled, but timer incrementing in hardware TCNT0 each 1us, this codeCode:inline uint16_t avr_time_us_fast_inline(uint16_t *time_us_prev, uint8_t *t0_prev ) {uint8_t t0 = TCNT0;//static uint8_t t0_prev = 0;//static uint16_t time_us_prev= 0;if(t0<(*t0_prev) ) {(*time_us_prev)+= 100;}uint16_t time_us= (*time_us_prev) + t0;(*time_us_prev)= time_us;(*t0_prev)= t0;return time_us;}for fast timing retrieval optimized by AVR C looks very good, since there is no memory acceses despite we have pointers in function definedBelow example where this function was used and in AVR C assemler listing only memory accesses to volatile variables are generated using LDS/STS - no memory acces for function pointers since only registers used in optimized codeCode:// forever initvolatile uint8_t time_fast_is= 0;volatile uint8_t time_fast_is_copy= 1;volatile uint8_t time_fast_is_copy2= 2;uint8_t t0_prev = 0;uint16_t time_us_prev= 0;uint16_t time_us_fast_prev= avr_time_us_fast_inline(&time_us_prev,&t0_prev );// foreverfor(;;) {uint16_t time_us_fast= avr_time_us_fast_inline(&time_us_prev,&t0_prev );uint16_t dtime_us_fast= time_us_fast - time_us_fast_prev;if(dtime_us_fast>0 ) {time_fast_is= 1;} else {time_fast_is= 0;}time_us_fast_prev= time_us_fast;time_fast_is_copy= time_fast_is;time_fast_is_copy2= time_fast_is_copy;} // foreverAssembler listing of section above with inline avr_time_us_fast_inline function optimized:Code:214 0116 1B82std Y+3,__zero_reg__215 0118 81E0ldi r24,lo8(1)216 011a 8A83std Y+2,r24217 011c 82E0ldi r24,lo8(2)218 011e 8983std Y+1,r24219 0120 42B7in r20,0x32220 0122 242Fmov r18,r20221 0124 30E0ldi r19,0222 0126 61E0ldi r22,lo8(1)223.L14:224 0128 52B7in r21,0x32225 012a C901movw r24,r18226 012c 5417cp r21,r20227 012e 00F4brsh .L11228 0130 8C59subi r24,-100229 0132 9F4Fsbci r25,-1230.L11:231 0134 850Fadd r24,r21232 0136 911Dadc r25,__zero_reg__233 0138 2817cp r18,r24234 013a 3907cpc r19,r25235 013c 01F0breq .L12236 013e 6B83std Y+3,r22237.L13:238 0140 2B81ldd r18,Y+3239 0142 2A83std Y+2,r18240 0144 2A81ldd r18,Y+2241 0146 2983std Y+1,r18242 0148 452Fmov r20,r21243 014a 9C01movw r18,r24244 014c 00C0rjmp .L14I haven't got time to test this on real MPU, but assembler code looks very fast, so in "brute force" loop with ISR disabled not sure if we can get 1us in test "forever" loop, but it should be very fast - for example waiting for pin change time difference ...Of course uint16_t time clock used here will overflow after 65ms, but it should be sometimes enougth time for eg. reading packet, etc...

author: NorthGuy
author_rank: Super Contributor
timestamp: January 18, 2019, 11:42:48 pm
content: Quote from: beduino on January 18, 2019, 11:01:16 pmappears to be too complex. Say, with timer overflowing at 256 you can do:Code:typedef union {struct {uint8_t time_low;uint8_t time_high;};uint16_t time;} time_t;uint16_t get_time() {static time_t cur_time;uint8_t t;if (cur_time.time_low > (t = TCNT0)) {cur_time.time_high ++;}cur_time.time_low = t;return cur_time.time;}which does exactly the same as yours, although I don't think I would do this in real life. The necessity of calling it very often is too restrictive.

author: beduino
author_rank: Regular Contributor
timestamp: January 20, 2019, 06:23:24 pm
content: Quote from: NorthGuy on January 18, 2019, 11:42:48 pmMy timer in CTC mode start from 0 after reaching 99, so it is not the same, however when you look into assembler code increment by one (1) looks similar to when 100 is added instead, but I do not have to call too often this function, since when I know that eg. pin change interrupt while decoding some input bits stream is below 100us than it is sufficeint to get time during pin change interrupt and store to calculate differences, so it can be usefull sometimes.It is always worth to see how assembler code looks like to ensure we are not loosing too many time, because of those experiments showed that by playing with different C code static/inline hints sometimes leads to interesting low level code generated, eg. in your case for this variableCode:static time_t cur_time;will not be optimized by using only registers and probably you will have LDS/STS instructions to read/store this variable in generated code, as well as difficult to reinitialize inside "get_time" function- that is why I pass those variables as pointers,which seams in "inline" version that are optimized and registers are used without LDS/STS instructions in your case probably

author: NorthGuy
author_rank: Super Contributor
timestamp: January 20, 2019, 08:47:22 pm
content: Quote from: beduino on January 20, 2019, 06:23:24 pmMake it roll after 255. Are you trying to make your life more difficult on purpose?Quote from: beduino on January 20, 2019, 06:23:24 pmOf course not. It is a long-term variable holding the time.There's no reason to speculate about the assembler. Just compile and post.

author: rhb
author_rank: Super Contributor
timestamp: January 20, 2019, 10:35:05 pm
content: I've not followed or read this thread, but skimming over it I thought this might prove useful.From "Hacker's Delight" by Henry S. Warren, Jr.Edit:100x = (32x - 8x + x) * 43 shifts and 2 adds

author: westfw
author_rank: Super Contributor
timestamp: January 21, 2019, 06:07:14 am
content: QuoteThe original post had 3 shifts and 4 adds - theoretically only slightly worse.The problem is that a 32bit shift is not particularly "inexpensive" on an AVR (which takes at least three instructions to shift 32bits by one position), and the original effort wouldn't factor the shifts to notice that 32x = 4*8x, or equiv.)Since then, much of the discussion has been about how to re-think the overall program so that you never need to multiply by 100 in the first place.

author: rhb
author_rank: Super Contributor
timestamp: January 21, 2019, 11:35:37 am
content: All I'd meant to do was post the section from "Hacker's Delight" which is full of obscure tricks.Unfortunately I read enough while scanning it that the edit popped into my head after I got in bed. I knew it was not going to leave me alone unless I added the edit.

author: splin
author_rank: Frequent Contributor
timestamp: January 22, 2019, 06:11:18 am
content: Quote from: beduino on January 15, 2019, 12:20:07 amI wouldn't bother with that patent as it doesn't workIt seems that what you are trying to do is:QuoteOk, so what you need is to wait (for up to 100us) until you detect that OCFOA has changed from set to cleared at which point you can be sure that the ISR has just executed and you can safely read avr_time_counter knowing that the ISR won't run whilst you're doing so. But as you have already surmised OCFOA will only be activefor a very short time(unless you have interrupts disabled - which I don't believe you do) - specifically from the moment the timer rolls over, until the ISR starts.The exact timings might be published in some application note but it doesn't matter - your non-interrupt code probably won't ever see it and then only if it happens to read the TIFR register within that very short time window. Depending on how the MPU is designed there is a possibility that your code could *never* see the OFCOA flag set because it is only set, within each 1us processor clock cycle, at a point *after* the 'in Rx, 0x38' instruction actually reads the flag; at the end of that instruction the ISR will execute, resetting the flag.If you really want to wait until just after the ISR has executed then have a do while() loop waiting for TCNT0 to change. But I'm pretty sure that isn't what you want as it would waste far too much time - an average of 50us for each call to avr_time_counter(). In your code you check the OFCOA flag and if it's clear you go on to read the 4 bytes of avr_time_counter - but the ISR can occur at any point after your check of OFCOA including part way theough reading avr_time_counter.cv007 had the solution - re-read avr_time_counter if TCNT0 has rolled over (but it doesn't need to be in a loop unless you have an ISR that can take 100us or more).

author: Doctorandus_P
author_rank: Super Contributor
timestamp: January 26, 2019, 10:07:07 am
content: If you need some performance, then use a microcontroller that has MUL instructions, or at least a barrel shifter.If you don't need the performance then why bother?It all looks like some silly academic excersize to me.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 26, 2019, 01:28:04 pm
content: Quote from: Doctorandus_P on January 26, 2019, 10:07:07 amFor the same reason we don't live on the Savannah anymore, clubbing animals on the head, and making single-use tools by knapping flint."Just throw money at it!"is not the best option, it is just the easiest one, and one that any monkey with fistfuls of cash and no real skills can do.In cases like this, where the microcontroller has the necessary performance, but the designer is having difficulty utilizing that, this "academic exercise" has two purposes: One is to save money and resources by using the cheaper hardware, the second is to become a better designer/developer for personal and business reasons by learning how to utilize the microcontroller to its full potential. To me, that makes perfect business sense, and worth the bother.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 27, 2019, 08:03:06 pm
content: Knowing how to use fixed-point or fully integer calculations instead of resorting to floating-point is not just useful for performance reasons on low-end parts.It's an essential skill every time you need to control the precision of calculations at every stage, something that is much harder (sometimes impossible) to guarantee with floating-point.It's also an essential skill to actually understand how to properly use floating-point!Oh, and it's also an essential skill if you do digital design. It's so useful that's it's very far from being an academic exercise only.Lastly, only if you have that skill can you actually judge whether/or when it's appropriate to use it or not.I know they say ignorance is bliss, but it's certainly not an engineer's best friend.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: January 28, 2019, 05:05:06 pm
content: It seems that you are also simply assuming that the undisclosed "8bit AVR" that OP is using is cheaper than some other 32bit ARM (or other) processor.There is hardly any correlation between processing speed and price in small microcontrollers nowaday's.I haven't even seen any evidence that price is a concern in this thread.These "academic excersizes" can be (are) usefull to jog the brain and improve programming skills. That is exactly what academic excersizes are for.But I see my fault now.I should not have used the word "silly", that was a fart of the moment and I apologise for that.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 28, 2019, 08:19:07 pm
content: Quote from: Doctorandus_P on January 28, 2019, 05:05:06 pmTrue, but I'd still claim it is a good idea to make sure you can utilize each microcontrollers' subsystems to their fullest extent.I *am* assuming ATtiny85 here. They happen to be quite cheap, but interestingly powerful microcontrollers. I personally like the original DigiSpark approach, where the less-than-square-inch PCB itself acts as a full-sized USB connector. There are only a couple of I/O pins, but it opens up a large number of options. I do normally use ATmega32u4 for USB 1.1 stuff, and various ARM Cortex microcontrollers (esp. Cortex-M4F) for anything that needs any computational oomph.If it isn't the exact one OP is using, it seems to be darned close; close enough for the discussion to make sense.In this particular case, we can throw away the stated question (fast integer multiply by 100), and look at the underlying problem OP is working on.The microcontroller has an 8-bit timer/counter, that is used for a regular timer tick. (There are two other timers, one of them 16-bit if I recall correctly offhand, but in many cases you want to use them for something more important.)The idea OP has, is to use the actual timer (at or around instruction clock frequency) value as a timer, with the overflow counter just updating the extra bits. The problem is that to use a decimal rate (some power of ten cycles per second), the 8-bit timer must wrap around at 100 or 200. Not all use cases need the exact timer, and for best bang for buck and largest number of options, one would prefer to have both a fine (down to timer/counter step) and coarse (just number of overflows) timer values, especially if coarse timer is cheaper to read.This is not a complicated problem, and definitely does not warrant using a 16- or 32-bit microcontroller.My suggestion above is the absolute overengineered one, that shows how to do it with zero issues as to cases when the reader is interrupted by the timer overflow itself. It involves using generation counters, which are the most basic form of spinlocks (although mine is for a single writer and multiple readers only), and is easily formally proven to result in at most two iterations if the timer overflows occur at intervals greater than a few dozen cycles, and allow "atomic" snapshots ofboth the timer counter and the overflow counter as a single unsigned integer value. Essentially, you can make it a cycle counter on ATtiny85 if you want, if you write the ISR in assembly. The fine counter is incremented by the counter limit, so that the full cycle counter value is just a sum of the timer/counter and the overflow count. The coarse counter value is incremented by one. You can implement either a minimum average cycle version (by only incrementing the least significan bytes when they do not overflow), or a fixed-duration version (whose latency effects are trivial to note an measure at run time, and being absolutely regular, are easy to take into account).If this was a single one-off product being implemented for a paying customer, I'd agree with Doctorandus_P: then, switching to a more powerful microcontroller gives you much more leeway, and simply Makes Sense.However, I don't think people like to discuss any single one-off products they're working on here. So, I am working on the assumption that this is a prototype, or for-learning experimentation. For that, just discovering the generation counters and using a single ISR to provide multiple clocks running at different rates, makes this thread worthwhile.Not seeing the value in doing this, and instead recommending using a more powerful microcontroller, is alarming to me.(This also means my argument may look too aggressive. Just remember I am arguing against your argument, and not you as a person.)I also do not think "academic exercise" is anything anyone should use in any derisive manner,ever.Our technology is advancing at a tremendous pace, and software engineering and programming languages arenotkeeping up. This leads to a prevalent belief that we already know everything there is to know about software engineering, and rather than waste time with "academic exercises", the proper cost-effective solution is to throw more hardware at it.This is simply not true. I have seen this personally in the HPC world. Simply put, aside from support of new, much more performant hardware,no real advanceshave been made in the HPC software engineering side. Almost all simulations still use process distribution, distribute data only when not doing computation, and avoid threading models, simply because they're too hard -- or "not cost-effective to teach the developers to do", as I've been told. Data mining, expert systems, and "AI" are nothing new; even self-organizing maps were pretty well known by 1980s. It's just that now we have the hardware to collect and process the vast amounts of data at timescales that allow even relatively crappy implementations (compared to biological ones!) produce "miraculous" results.The exact same happened in the automotive world in the United States in the last fifty years or so, when the fuel consumption of a typical cargrew, not shrunk, because it was not thought of as important. It is even funnier to think that the typical "grocery bag" car in the early 1900s in New York was an electric car. If you've ever read Donald Duck comics, the car Grandma Duck uses is a Detroit Electric from 1916. Yet, somehow, electric cars are somehow thought of as a new innovation. (The battery technologies and some of the materials tech is, but not the electric car concept, not in the least.) We would have had cheap home 3D printers in the late 1980s, early 1990s at the latest, if it were not for certain patents that were mainly used to protect existing plastics manufacturing methods from competition.I cannot stress enough how important it is to not let engineers and designers to rely on the hardware improvements to keep their work relevant, and stop learning. It just isn't good for anyone in the long term. It is a seductive option, because it makes your work easier in the short term; but in the long term, the side effects make it a poor choice.To everyone with degrees, I'd remind them that their studies were to prepare them for the real work, the real learning. The only way you can belittle "academic exercises" is that if you do your work right, develop your skills to the fullest, you'll do much harder work and learn more every day than you ever did in academia.

author: tggzzz
author_rank: Super Contributor
timestamp: January 28, 2019, 10:13:31 pm
content: Those areverysensible points about "academic exercises", and HPC, and the necessity of academic studies.The point about academic exercises is that they should enable the key point to be considered, without it getting lost in a morass of boring irrelevant stuff. The lessons learned should then be applicable to far more than merely a single example problem.Much tech knowledge has a half-life of a few years, e.g. which button to press to get the frobnitz to kazump when the moon is in the third quarter. The understanding gained from academic exercises lasts a lifetime.I recently returned to embedded software and electronics after a couple of decades doing other things. I was both delighted and horrified at how little had changed since the 1980s - the experience I gained 30 years ago was still directly relevant, so I slotted back in within a couple of weeks!The major changes at the speed/resolution of ADCs and DACs, nanopower electronics, ease of making PCB, and that things were smaller faster and cheaper. But all the fundamentals and pinch points were horrifyingly unchanged.(Well, there are a few glimmers of hope, e.g. the capabilities of XMOS xCORE processors with xC, but even they are familiar from the 80s and 70s, respectively)

author: westfw
author_rank: Super Contributor
timestamp: January 29, 2019, 07:48:34 am
content: QuoteI'm not quite sure what "HPC" is supposed to mean in this context. But I think it's a major mistake to omit"massive performance increases" from the "significant advances" column.QuoteAn interesting example. I wonder at your definition of "cheap", given that "low cost" dot matrix printers from that era were $500 to $1000, and the Mac IIci I bought in that timeframe was about $7k (~1MB RAM, 100MB disk, built-in 640*480 graphics. color monitor.) While the technology of the day would have supported the "several stepper motors and a heater" sort of 3D printer, I think I'll claim that the CAD software needed to effectively drive such a printer would have been essentially impossible in that timeframe (on any "reasonable" (but not "low cost"!) home computer. I mean: no "windows" yet; VGA graphics was "new"; a typical PC had a 16 to 20MHz CPU with 1 to 4MB of RAM.(Although I did find an ad in a 1988 Byte Magazine for "DesignCad 3D that claimed to work "even on EGA graphics."https://www.americanradiohistory.com/Archive-Byte/80s/Byte-1988-04.pdf)

author: westfw
author_rank: Super Contributor
timestamp: January 29, 2019, 08:09:56 am
content: Heh. A whole article on 3D CAD from a 1988 PC Magazine! "If you're going to be spending $3k for software, you should certainly have at least 640k of RAM, and you might want to spring the extra $3k for one of those new 1024*768 color monitors!"https://books.google.com/books?id=ObYblXvjuhUC&lpg=PA121&ots=atgG2szPEd&dq=designcad%203d%201988&pg=PA115#v=onepage&q&f=false

author: NorthGuy
author_rank: Super Contributor
timestamp: January 29, 2019, 02:08:16 pm
content: Quote from: westfw on January 29, 2019, 08:09:56 amInteresting. The resolution of 1080p monitor (which I'm looked at right now) is not that different - only 2.6 times more, but the CPU frequency is 500 times, and the memory is 25000 times bigger. If they could make 3D CAD back then, it should simply fly now.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: January 29, 2019, 03:57:09 pm
content: And just to bring things a little back to topic here -- an AVR isn't much different in terms of raw computing power, versus a PC-compatible of the day (if certainly not one of the better workstations that you'd want to be running CAD on!).The main difference is, programming it that way is a pain, and you have to add a ton of peripherals to support that kind of functionality.Namely: with little SRAM, you need to treat it as a cache against external SRAM and Flash. Probably the same goes for program memory as well. Flash can be rewritten live, but it is a wear item, so that wouldn't be such a great idea; more likely, you'd implement a rich operating system, and run programs from external memory as an interpreted virtual machine.And now that I've speculated about a thoroughly unpleasant system to develop for and use, let's just grab an STM32F4, stick an LCD on it, USB hub, external DRAM and Flash, and run Linux instead. ;-DDTim

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 29, 2019, 04:52:20 pm
content: Quote from: westfw on January 29, 2019, 07:48:34 amThe massive performance increases stem from new hardware: the software side has not changed. The software engineering side utilizing that hardware has not kept up, we are simply relying on very old (>25 year old) techniques with very little change since then. Except for new hardware. There is basically nothing new in coding/software engineering; everyone is just coasting on the hardware.Based on my experience in MD simulations, the software side has really stagnated. The hardware is not utilized to its full potential; the software folks have not kept up. Even multithreading is still avoided, using multiprocessing instead. If you look at GPGPU computation, it treats the GPUs as isolated compute units, very much like separate computers in a cluster environment. Nothing new, and definitely not using the hardware to its full potential.Quote from: westfw on January 29, 2019, 07:48:34 amI was obviously looking only at the hardware cost. Something as simple as a 6502 is definitely beefy enough to run HPGL or basic G-code.Quote from: westfw on January 29, 2019, 07:48:34 amWe got a Star LC-10 in 1988, I think, but I do believe it was much cheaper than that. In 1988, it cost under £200 in the UK (according to adverts).Quote from: westfw on January 01, 1970, 12:00:15 amGUI CAD? Absolutely agreed.But direct path generation via a simple programming language, something between HPGL, Turtle Graphics, and Gcode? I claimthatpossible. Didn't you ever write PostScript by hand to run on the printer itself? (I definitely did, for the first HP LaserJet I got access to, in the early nineties.)PostScript is a simple, but hugely powerful language. Because of rasterization, it did need surprisingly large amounts of memory (as in often more than on the associated computer, in the early times). While we use proper CAD and slicing for 3D printing now, does not mean it cannot be done much, much simpler.Anyway, you have good enough points for me to want to amend my claim, to something like (without the patents,)"we might have had", with the point being that the stopping factor wasnot so muchlack of existing technology, or the high cost of most of that technology (GUI CAD design notwithstanding, definitely), but patents obtained for anticompetitive purposes: for plastics manufacturers to use them to reduce competition in their field, reducing the need for further product development.(While there has been a lot of research and development on the plastic materials themselves, even PLA is a hundred years old invention. I would not be too surprised to find out that Lego's product development efforts have been a very big driving factor in the precision plastics industry. Those little toys are surprisingly high-precision bulk-manufactured things. The tolerances are, and were already in the eighties, absolutely ridiculous for the blocks to attach and detach hundreds of times with very consistent friction fit. That should tell a lot about what kind of engineering/development actually pushes the world forwards.)To clarify, my point was to show that in software engineerin, for decades we have not done what is possible, only what is easy or makes short-term business sense. The development in hardware has masked the software stagnation, but the stagnation is nevertheless obvious in my opinion. Using other engineering areas like electric cars in the automotive world for comparison, this stagnation seems very costly, although calculating its exact price is very difficult: it is hard to say how much you lose by only using a fraction of the available tools.While I do complain about the difficulty in getting funding for overcoming that stagnation by example, I understand the reluctance. I do not agree, but I understand. Funds are limited, and the risk/benefit ratio hard to estimate. Hardware is easy and safe.What I fail to understand, is the unfounded assertion that there is no need to overcome that; that it is somehow unprofessional or wasteful for an engineer to try to do that; that the core of what an engineer or scientist does is something other than learning and sharing that knowledge, even in product form; that that the proper engineering approach is to throw more hardware at it and keep going like we always have in the software side. I see no evidence supporting that approach. It makes no sense in the medium to long term; it only makes sense in the short term, for one-off commercial products and services.I suspect that many have accepted that approach axiomatically, without examining it, because it does feel good to think that what you know and can do now will tide you for the rest of your life, and is therefore emotionally very attractive as an axiomatic approach to your profession: it says you're complete now, no need to struggle to keep up anymore.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 29, 2019, 05:01:39 pm
content: Quote from: NorthGuy on January 29, 2019, 02:08:16 pmIf we look at the differences in the approach, our current tools seem to heavily lean towards a what-you-see-is-what-you-get visual representation. That was not always the case, not even for word processors.This is why I think the CAD software used would have been different; more abstract.Mathematical solid geometry modelling like OpenSCAD could have been possible, maybe. I did not suggest it above, because I think the amount of memory generated would have been costly to store (and I'm too lazy to work out if cassette tape drive data rates would suffice, and the entire tape approach work); but slicing the models would have definitely been too slow to do in real time. Dedicated helper processors, maybe? Simon's Basic equivalent cartridge on the C64, but for 3D printing? Not likely, but I don't think it impossible, either; hits me straight in the Uncanny Valley, whenever I think about it.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 29, 2019, 05:29:36 pm
content: Quote from: Nominal Animal on January 29, 2019, 04:52:20 pmThe rapid growth in hardware was the cause of the "software stagnation". The hardware growth now slowed down, but the other, much worse factor starts to influence the software industry. Lots of software went free and open source. There's no money in it. Hence no progress. I expect it will only get worse with time.

author: janoc
author_rank: Super Contributor
timestamp: January 29, 2019, 06:08:34 pm
content: Quote from: NorthGuy on January 29, 2019, 05:29:36 pmThat's complete nonsense, both on the "no money" and the "open source" parts.If there was no money in it, then why do software guys command so high salaries and are in such high demand? One would think that nobody would want to do such work and companies would be going out of business or pivoting away from software left and right. Kinda don't see it - just look at any job website or ask any recruiter.And re open source - open source certainly didn't cause any quality "stagnation", more like opposite, because more people can (and do) participate and any crap code tends to be quickly pointed out and fixed, at least in the popular and actually used projects. And it also pushes vendors of competing commercial projects to fix their messes or their clients will jump ship - which they didn't have to do before.Look at projects like LLVM which actually enabled building a ton of tooling for programming languages that simply wasn't feasible before because the barrier of entry in terms of complexity was so high. Or Linux. Or FreeBSD (Apple owes the BSD folks quite a bit there). Or OpenCascade. Or GCC ...Also I don't see companies like Autodesk or even Microsoft fearing of going out of business any time soon, despite there being open source alternatives for their products.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 29, 2019, 07:38:13 pm
content: Quote from: janoc on January 29, 2019, 06:08:34 pmThe process is just starting, and you're speaking as it is already complete.LLVM is a huge ecosystem and huge effort, and people use it, but did it really make any difference in software development? Is today's LLVMed software any less buggy or less bloated than the software before LLVM? I don't think so.Linux is developing sideways. It is certainly getting better in some places, but it didn't win lots of new users in the past 10 years. This certainly helps Microsoft, but mostly Microsift twist hands of computer manufacturers to pre-install Windows on every computer. This probably cannot last forever. However, open source Android has already pushed Microsoft away in mobile space.There are places where market reach of free software is huge. GCC for example. Microsoft no longer sell their VC++ compiler, it's forced to be free. Is VC++ any worse than GCC? I don't think so.Or FreeRTOS. 10 years ago there were lots of vendors, such as uOS. FreeRTOS pushed them all out, but not because FreeRTOS is any better, but because it's free.So, there's no doubt that, little by little, the free software will take over everywhere, just give it enough time, I guess 20-30 years.

author: janoc
author_rank: Super Contributor
timestamp: January 29, 2019, 09:59:46 pm
content: Quote from: NorthGuy on January 29, 2019, 07:38:13 pmThat process is "starting" for 30 something years that free/open source software exists. That's eternity in IT.Quote from: NorthGuy on January 29, 2019, 07:38:13 pmCompared to what?If I compare LLVM (or even GCC) to any of the proprietary (and expensive) compilers I had to deal with in the past, jeeze, give me LLVM any day! Most of that proprietary stuff was utter crap compared to LLVM or GCC. In fact, recommendation to install/compile GCC and GNU tools was usually the first thing anyone who had to deal with commercial Unix saw, because the vendor-supplied compilers were buggy and supported only obsolete C/C++ versions.Quote from: NorthGuy on January 29, 2019, 07:38:13 pmAnd what do you think Android is based on? Linux, surprise. Desktop Linux is irrelevant but pretty much everything mobile runs either iOS or a Linux kernel today. And that iOS seems to be doing quite well there. Microsoft had a stake in mobile but they have only themselves to blame because of their clueless and hamfisted OEM and developer support. The cost of the system had little to do with it. The same with Nokia's S60 Symbian - it didn't disappear because Linux or iOS were free (the latter certainly isn't) but because S60 was hopelessly outdated and when the first iPhone appeared it was literally like comparing a bullet train with a steam engine ...Quote from: NorthGuy on January 29, 2019, 07:38:13 pmFree software made some things into commodities. But that doesn't mean the paid-for tools ceased to exist. E.g. Microsoftstillsells their compiler and tools. Only the Community edition of Visual Studio is free, which has severe licensing restrictions. If you have more than 5 users or make more than $100k annually you have to buy the commercial version.The same holds for e.g. Unity 3D or Unreal engine - they are "free" in the sense that the development tools are free for personal use. The moment you start developing commercially or selling something, you owe them money. Etc.Quote from: NorthGuy on January 29, 2019, 07:38:13 pmI do wonder where is VxWorks, EUROS, Neutrino, Nucleus, QNX ... Also that FreeRTOS has a commercial license available as well, same as ChibiOS.Quote from: NorthGuy on January 29, 2019, 07:38:13 pmRiiight ... GCC alone is more than 30 years old. And we still have proprietary compilers (e.g. IAR) and some vendors even repackage andsellGCC-based toolchains (Microchip). Heck, some people prefer to use the expensive IAR compilers even where free GCC-based tools exist. Could it be that GCC simply doesn't (and cannot) cover all the market needs?Free works for some things but we are not going to see a competitive high end CAD system (too complex, requires specialized knowledge and customers don't care about free, they need support, they need import and export of various proprietary data formats, etc.), free office software exists but it is pretty much irrelevant because Microsoft's formats are the standard, tools like the Adobe Creative Suite pretty much have no free or paid competition (and certainly aren't going to have any time soon, given how much work it would require - GIMP really isn't in the same league). Etc.And that's generic, commodity software - most software is made-to-measure, custom development. Even if you use free components you will still need engineers to write all the application glue together. And they don't work for a smile and a beer.I am certainly not worried about lack of work, if anything, there will be more of it in the future because everything is moving from hardware to software due to lower costs of changes and faster time to market.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 29, 2019, 11:47:06 pm
content: Quote from: janoc on January 29, 2019, 09:59:46 pmIf you remember, 30 years ago there was time when people were willing to pay for the software. There were lots of vendors, some of them very useful. Today, most people expect the software to be free. Some even get upset when someone ask them to pay for software. The trend is likely to continue.Quote from: NorthGuy on January 29, 2019, 07:38:13 pmIMHO, GCC which existed 20 years ago was perfectly fine. Moreover, Microsoft VC++ for Windows was fine too. I also remember Borland C. They all worked about the same.Today, I use what comes with the platform. It's GCC on Linux (and GCC-based on most embedded things), VC++ on Windows, LLVM on Mac. I cannot tell the difference.People who develop LLVM do so because they like it. They like how any language can be compiled in, optimized within the same framework, then the assembler produced for any CPU. May be it is fascinating to the LLVM developers, but it doesn't looks particularly fascinating to me, and I do not see any benefits for myself.Quote from: NorthGuy on January 29, 2019, 07:38:13 pmThey cell their C# tools. I don't know if there are any open source substitutes.Their C/C++ compiler is free download which comes with Platform SDK and doesn't even require opt-in for spying. 20 years ago I had to pay for it.Quote from: NorthGuy on January 29, 2019, 07:38:13 pmFor a while. But not forever. We're now in money printing era. When it turns towards austerity there will be huge pull towards money savings and everything free.Right now, Altium is considered better choice than KiCAD, but I wouldn't bet that it stays this way forever.Quote from: NorthGuy on January 29, 2019, 07:38:13 pmOf course there always be custom work for programmers ... or for programming AI robots

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 30, 2019, 04:36:17 am
content: Quote from: NorthGuy on January 29, 2019, 11:47:06 pmTheunwillingnessto pay for something they believe is free, is real.However, I see much more frustration about the black box nature of proprietary software. (Which is why in the past ten years or so, I've looked at how to provide the best of both worlds, especially for "cottage industry" software: having the UI part open source, because that's where the problems that irritate devusers tend to be in; and the proprietary secret-sauce work horse part as a closed library.)It is all a cultural issue, and cultural trends do shift. I think we (as in software engineers and product designers) can push a shift to the better, by doing it ourselves. Leading via example.I just don't want engineers and scientists to recommend against doing that; to claim that it is somehow better to just throw more money and hardware at every problem.As to future austerity or long-term post-scarcity plans, I'd say that it is a question if/when we realize that in a closed economy, neither the amount of debt or the amount of money/capital can act as the primus motor for growth, only better circulation and flow of resources can. If we do not work that out, we're fuckered. At the global scale, we're definitely a closed economy, unless we meet some Ferengi right quick. Thus far, we've coasted on the hardware ignoring the software: made the markets and economies larger, not better.

author: LapTop006
author_rank: Unknown
timestamp: January 30, 2019, 04:58:06 am
content: Quote from: NorthGuy on January 29, 2019, 07:38:13 pmI'd say it has hugely helped, with much better warning output to make fixing them easier, the various sanitizers (ASAN, TSAN, UBSAN, etc.) only some of which were available in GCC.All of that has helped to make software less buggy.

author: beduino
author_rank: Regular Contributor
timestamp: March 30, 2019, 11:08:03 pm
content: Quote from: splin on January 22, 2019, 06:11:18 amSorry, for long delay but a few projects at the same timeHow did you know that it doesn't work?I will take closer look at this code, but I've sucessfully used ideas above to keep timing home roof LED lights so this code works now 24h/day and no problem what so ever.Patent pending markings was made (for fun), while I'm not a fan of patenting anything - I believe in patents are bad for innovation - Instead I simply didn't showed C source for this critical part, but it was smart enougth I think, since now when I'm back to this project I do not remember how I've did itAnyway, optimized ISR using inline AVR assembler code incrementing only given number of bytes in unsigned number of microseconds is perfectly fine.Tricky part is in functions which can be hit by this ISR timer code running at 100kHz frequency, and this part of code will be now very carefully tested - I've showed only assembler code, because of I really think that it is very smart way to do it and it should work - proof is proper timing of home light trigered by PIR sensor module and additional modules like magnetic transducers to detect noisy dog or radio for remote controll.I'll use logic analyser to debug this tricky part of code in realtime, to ensure it runs as expected.

author: beduino
author_rank: Regular Contributor
timestamp: March 30, 2019, 11:30:25 pm
content: Quote from: Nominal Animal on January 28, 2019, 08:19:07 pmYep, I've used sucessfully ATTiny85 in many projects since it fits my performance requirements and has very small footprint on PCB.Now, I'm working on project where this tiny AVR with additional opamp (the same footprint SO8) will make complete speed/cadence sensor for road cycling power meter.At 5VCC I can run ATTiny85 at 16MHz internall clock according to it's specs, while at 3.3Vcc easily at 8MHz.The same 100kHz timer ISR will be used in those sensors and additionally at the same time output data on 1 wire custom protocol with a few bytes messages with priority based on sensor IDs.So, now imagine tens of such small but quite powerfull AVRs running in parallel, catching I/O interrupts, processing, easy to scale just by adding another sensor - something like distributed multiprocessor system with decent processing power and what is most important all sensors running in parallel at 8Mhz/16Mhz at the size of SO8 footprint!It is amazing that such small thing can run so many RISC operations per second and with a little help of manually guided optimized inline AVR assembler code can be very powerfull and small at the same time- we are talking about a few miliamps of current needed to make quite complicated computations but in parallel