Damn it, Markus and Karl-Heinz!
You made me succumb to the temptation of ignoring your project and, finally, to build that relentless spare-time killing device*!
Kidding aside, please let me contribute a little something to this fine project, and feel free to use it at will. This will be a surprisingly accurate version of the Sleep_5ms/MilliSleep time delay functions you already make use of.
The following version of the sleep-time delay breaks down the total requested sleep-time to three partial and very accurate TC2 wait times of 16.00 ms, 3.00 ms and 1.00 ms. Additionally, it utilises the Extended Standby Sleep Mode instead of the Idle Sleep Mode (that introduces timing errors for reasons currently unknown to me). Trying to keep the function spartan, the resulting timing error is a couple (or three or four in the worst case!) microseconds only, regardless of the total requested time delay, which is in the range of 1.00 ms to 65535.00 ms!
There are two problems, though:
The obvious one is that this function has to be ported to C, in order to be utilised in the current project --which is a trivial task.
The second one is that, in its current form, the function does only support the crystal oscillator option at 8.0 MHz (that I use in my implementation), since the RC oscillator needs very different start-up times and makes the redefinition of the partial delays an absolute requirement. Of course, the function can be rewritten to support the 16.0 MHz crystal oscillator or the 8 MHz RC oscillator option.
This is the assembly code:
;-------------------------------------------------------------------------------
; Registers definitions
;-------------------------------------------------------------------------------
;.def = r0 ; Temp / Product of the MULx instructions: [r1:r0]
;.def = r1 ;
.def C0 = r2 ; Temp / Math registers: [C7:C0]
.def C1 = r3 ;
.def C2 = r4 ;
.def C3 = r5 ;
.def C4 = r6 ;
.def C5 = r7 ;
.def C6 = r8 ;
.def C7 = r9 ;
.def D0 = r10 ; Temp / Math registers: [D3:D0]
.def D1 = r11 ;
.def D2 = r12 ;
.def D3 = r13 ;
;.def = r14 ; Ct. = 0xFF
.def Zero = r15 ; Ct. = 0x00
.def A0 = r16 ; Temp / Math registers: [A3:A0]
.def A1 = r17 ;
.def A2 = r18 ;
.def A3 = r19 ;
.def B0 = r20 ; Temp / Math registers: [B3:B0]
.def B1 = r21 ;
.def B2 = r22 ;
.def B3 = r23 ;
.def T0 = r24 ; 16-bit temp: [T1:T0]
.def T1 = r25 ;
;.def XL = r26 ; X pointer: [XH:XL]
;.def XH = r27 ;
;.def YL = r28 ; Y pointer: [YH:YL]
;.def YH = r29 ;
;.def ZL = r30 ; Z pointer: [ZH:ZL]
;.def ZH = r31 ;
;-------------------------------------------------------------------------------
Sleep_1ms_Init: ; <Sleep_1ms> function initialisation
;-------------------------------------------------------------------------------
; Initialisation of the <Sleep_1ms> function
;
; Syntax:
; call Sleep_1ms_Init ; Initialise the <Sleep_1ms> function
;
; <Sleep_1ms_Init> Parameters:
; None!
; <Sleep_1ms_Init> Returns:
; None!
;
; Registers used:
; Zero; Non-destructive use of <Zero> that can be any CPU register loaded with the constant value of 0x00
; A0; Non-destructive use of <A0> that can be any of the CPU high-registers (r16..r31)
;
push A0 ; Preserve the <A0> temp
; Stop and disable TC2 and TC2 ISRs
sts TCCR2B,Zero ; [FOC2A,FOC2B,-,-,WGM22,CS22,CS21,CS20]
sts TIMSK2,Zero ; [-,-,-,-,-,OCIE2B,OCIE2A,TOIE2]
sts TCNT2,Zero ; Reset TC2
; Set TC2 mode to CTC (WGM22:20 = 0b010)
ldi A0,1<<WGM21 ; Mode 2 (CTC): WGM22:20 = 0b010
sts TCCR2A,A0 ; [COM2A1,COM2A0,COM2B1,COM2B0,-,-,WGM21,WGM20]
sts ASSR,Zero ; [-,EXCLK,AS2,TCN2UB,OCR2AUB,OCR2BUB,TCR2AUB,TCR2BUB]
; Avoid any TIM2_COMPA ISR firing caused by possible low values loaded to OCR2A
ldi A0,-1 ;
out TIFR2,A0 ; [-,-,-,-,-,OCF2B,OCF2A,TOV2]
sts OCR2A,A0
; Done!
; cbi PORTC,3 ; ######## D E B U G G I N G ########
; cbi PORTC,4 ; ######## D E B U G G I N G ########
; sbi DDRC,3 ; ######## D E B U G G I N G ########
; sbi DDRC,4 ; ######## D E B U G G I N G ########
pop A0 ; Restore the <A0> temp
ret
;-------------------------------------------------------------------------------
Sleep_1ms: ; Sleep for <T1:T0> * 1 ms time
;-------------------------------------------------------------------------------
; Set the CPU to sleep for <T1:T0> times 1ms
;
; Syntax:
; ldi T0,Byte1(Delay) ; <Delay> is the requested sleep-time delay
; ldi T1,Byte2(Delay) ; in 1 ms steps. Range: Delay = 1..65535
; call Sleep_1ms ; Sleep for <T1:T0> * 1 ms time
;
; <Sleep_1ms> Parameters:
; T1:T0 = 1..65535, in 1 ms steps
; <Sleep_1ms> Returns:
; None!
;
; Registers used:
; Zero; Non-destructive use of <Zero> that can be any CPU register loaded with the constant value of 0x00
; A0; Non-destructive use of <A0> temp that can be any of the CPU high-registers (r16..r31)
; T1:T0; Destructive use of <T1:T0> that should be any of the 16-bit addressing capable register pairs (r25:r24, X, Y or Z)
;
.equ Restart_Delay_Tics = 16384 ; CPU restart delay, in Clk cycles
.equ Restart_Delay = Restart_Delay_Tics*1000000/F_CPU ; 2048 ms for 8 MHz clock
.equ T2_Period_004 = 32 * 1000000/F_CPU ; 4 µs for 8 MHz clock
.equ T2_Period_128 = 1024 * 1000000/F_CPU ; 128 µs for 8 MHz clock
; Preamble
; sbi PORTC,3 ; ######## D E B U G G I N G ########
push A0 ; Preserve the <A0> temp
ldi A0,1<<OCIE2A ; Enable the TC2 Output Compare Match A interrupt
out TIFR2,A0 ; [-,-,-,-,-,OCF2B,OCF2A,TOV2]
sts TIMSK2,A0 ; [-,-,-,-,-,OCIE2B,OCIE2A,TOIE2]
sei ; Now, we can safely enable global interrupts
; <T1:T0>: Delay register, in 1.0 ms units step
_Slp1ms_Dly: wdr ; Prevent a WD timeout event
; sbi PORTC,4 ; ######## D E B U G G I N G ########
; Fast Clk (4 µs / 250 kHz)
ldi A0,1<<CS21|1<<CS20 ; T2_Period = Clk/32 = 4 µs
sts TCCR2B,A0 ; [FOC2A,FOC2B,-,-,WGM22,CS22,CS21,CS20]
ldi A0,1<<PSRASY ; TC2 prescaller reset
out GTCCR,A0 ; [TSM,-,-,-,-,-,PSRASY,PSRSYNC]
sts TCNT2,Zero ; Reset TC2
; Delay module (1/3/16 ms)
cpi T0,Byte1(3)
cpc T1,Zero
brlo _Slp1ms_01m ; Delay < 3 ms
cpi T0,Byte1(16)
cpc T1,Zero
brlo _Slp1ms_03m ; Delay < 16 ms
; Slow Clk (128 µs / 7812.5 Hz)
ldi A0,1<<CS22|1<<CS21|1<<CS20 ; T2_Period = Clk/1024 = 128 µs
sts TCCR2B,A0 ; [FOC2A,FOC2B,-,-,WGM22,CS22,CS21,CS20]
ldi A0,1<<PSRASY ; TC2 prescaller reset
out GTCCR,A0 ; [TSM,-,-,-,-,-,PSRASY,PSRSYNC]
sts TCNT2,Zero ; Reset TC2
; 16.00 ms to sleep: Use the Power Save Sleep Mode
; OCR2A = ((16000µs-2048µs)/128µs)-1 = (13952µs/128µµs)-1 = 109-1
_Slp1ms_16m: ldi A0,((16000-Restart_Delay)/T2_Period_128)-1
sts OCR2A,A0
; Set the Sleep Mode to Power Save (SM2:0 = 0b011)
ldi A0,1<<SM1|1<<SM0|1<<SE
out SMCR,A0 ; [-,-,-,-,SM2,SM1,SM0,SE]
sleep
; Update the Delay register
sbiw T0,16 ; Delay register timed-out?
; cbi PORTC,4 ; ######## D E B U G G I N G ########
brne _Slp1ms_Dly ; Not yet. Else:
rjmp __Slp1ms_X
; 3.00 ms to sleep: Use the Power Save Sleep Mode
; OCR2A = ((3000µs-2048µs)/4µs)-1 = (952µs/4µs)-1 = 238-1
_Slp1ms_03m: ldi A0,((3000-Restart_Delay)/T2_Period_004)-1
sts OCR2A,A0
; Set the Sleep Mode to Power Save (SM2:0 = 0b011)
ldi A0,1<<SM1|1<<SM0|1<<SE
out SMCR,A0 ; [-,-,-,-,SM2,SM1,SM0,SE]
sleep
; Update the Delay register
sbiw T0,3 ; Delay register timed-out?
; cbi PORTC,4 ; ######## D E B U G G I N G ########
brne _Slp1ms_Dly ; Not yet. Else:
rjmp __Slp1ms_X
; 1.00 ms to sleep: Use the Extended Standby Sleep Mode (no Restart Delay)
; OCR2A = (1000µs/4µs)-1 = 250-1
_Slp1ms_01m: ldi A0,(1000/T2_Period_004)-1
sts OCR2A,A0
; Set the Sleep Mode to Extended Standby (SM2:0 = 0b111)
ldi A0,1<<SM2|1<<SM1|1<<SM0|1<<SE
out SMCR,A0 ; [-,-,-,-,SM2,SM1,SM0,SE]
sleep
; Update the Delay register
sbiw T0,1 ; Delay register timed-out?
; cbi PORTC,4 ; ######## D E B U G G I N G ########
brne _Slp1ms_Dly ; Not yet. Else:
; Done! Stop TCNT2 and disable OCIE2A and the Sleep Modes
__Slp1ms_X: sts TCCR2B,Zero ; [FOC2A,FOC2B,-,-,WGM22,CS22,CS21,CS20]
sts TIMSK2,Zero ; [-,-,-,-,-,OCIE2B,OCIE2A,TOIE2]
out SMCR,Zero ; [-,-,-,-,SM2,SM1,SM0,SE]
pop A0 ; Restore the <A0> temp
; cbi PORTC,3 ; ######## D E B U G G I N G ########
ret
-George
EDIT:
Corrections of the premature and incomplete assembler code that was mistakenly posted.
(*) Here are a few ideas that might be of some use for the project.
This is my hardware version of a single layer PCB, an easy one to be built at home, with the following modifications:
- Additional PCB space to host a 9V battery, for an autonomous and completely floating testing device.
- The use of the more accurate and flexible LP2950-5.0 LDO/reference, which eliminates the need for an external voltage reference diode.
- The addition of R2 in series to the push-button, in order for the FW to be able to use the LED indicator independently of the push-button state (because without R2, when the push-button is ON the Q3 B-E junction limits the LED anode voltage to ~0.7V only).
- The omission of the LCD backlight current limiting resistor and the LCD Vee bias trimmer, since both those LCD module requirements are handled internally by elements (resistors) added directly to the LCD module PCB.
- Finally, the addition of a BootLoader header port (J3) for faster and less hassle FW updates, for those who really enjoy messing with the firmware!
Here are the schematics and the PCB top and bottom layout views of my Component Tester implementation: