Home ==> Micro beginner ==> 8. Intensity regulation
ATtiny13

Lecture 8: Regulation of the intensity of a LED


In this lecture we apply the built-in Analogue to Digital Converter (ADC).

8.0 Overview

  1. Introduction to AD conversion
  2. Introduction to PCINT interrupts
  3. Hardware, components, mounting
  4. Intensity regulation
  5. Intensity regulation with color change
  6. Intensity regulation dynamically
  7. Red/green

8.1 Introduction to AD conversion

AD converters do the following: The AD converter in the ATtiny13 has a resolution of 10 bits, so at a reference voltage of 5.0 V voltage differences of 5 / 210 = 4.8 mV can be measured, approximately one tenth of a percent. The result of a measurement can be converted to a resulting voltage by multiplying the result with 1,024 and dividing the result through the reference voltage. As reference voltage in the ATtiny13 either the operating voltage (bit REFS0 in control port = 0) or an internally generated reference of 1.1 V can be selected (REFS0 = 1). Other AVR offer the opportunity to source the reference voltage from an external source via a port pin.

ADCH_ADCL After conversion has been completed the 10 bit result is in the ports ADCL (the lower 8 bits) and ADCH (the upper two bits, all other bits at zero) and can be read from there to a register with the "IN Register,Port" instruction.

ADLAR With the bit ADLAR (AD left adjust result) = set this behaviour can be changed: in this case the result bits are stored differently. The eight most significant bytes can be read from ADCH. If 8 bit resolution are sufficient, ADCL must not be read and processed.

ADMUX In the port ADMUX the two bits REFS0 and ADLAR are located. Additionally the bits MUX1 and MUX0 select the pin from which the input voltage is copied in the sample-and-hold phase is taken.

AD pins Those are the ADC pins that can be connected to the internal AD converter. Their numbering has not much in common with the port pin numbering. Note that the RESET pin (ADC0) can only be used as linear input if the RESET function of this pin is disabled by a dedicated fuse. If RESET is disabled, no ISP programming of the ATtiny13 can be made any more. So do that only in case you either own a programmer capable to program in high-voltage mode (e.g. an STK500) or if you are absolutely sure you will never have to re-program the chip because your software is absolutely perfect and the chip will never be used in a different system or for a different purpose.

The bits MUX1 and MUX0 determine which of the sources will feed the sample-and-hold. 00 is ADC0, 01 is ADC1, 11 is ADC3.

DIDR0 The pin or the pins that are used as AD input pins will not be used as digital in- and outputs any more. To reduce current needs those stages can be disabled. Disabling is performed if the respective bits in DIDR0 are set. Please note the strange numbering of those bits. One more argument to use the 1<<bit construction instead of absolute bit values.

ADCSRA The complete control over the ADC is via the port ADCSRA. In that port the bits ADPS2, ADPS1 and ADPS0 refer to a prescaler for clocking the ADC operation (000 = 2, 111 = 128). Per conversion 13 prescaled clock signals are required (a few more if the ADC was not active before). The bit ADIE enables the generation of an ADC interrupt, if set. ADATE allows to Auto-Start the conversion on certain conditions. Otherwise conversion has to be started via an OUT to ADCSRA with the ADSC bit set (AD start conversion). If set, this bit remains set until the conversion is completed. The bit ADEN switches the ADC on and off.

A typical sequence to start the ADC with ADC3 as voltage source is as follows:

	ldi rmp,(1<<MUX1)|(1<<MUX0) ; Channel 3
	out ADMUX,rmp ; to AD multiplexer port
	; switch ADC on, start conversion, interrupt when complete, precaler 128
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; to ADC control port

That were the most relevant things to know about the ADC and its activation.

Home Top AD conversion PCINT Intensity Color selection Dynamic Red/Green


8.2 Introduction to the PCINT programming

In the previous lecture a key on the INT0 input pin was used to detect external events. If the input pin INT0 is blocked by other needs or if additional keys must be attached and detected, the external interrupt PCINT can be used. This works a little bit different than INT0.

PCMSK All digital input pins can be monitored by the PCINT. If an input pin's level changes shall lead to a PCINT just set its bit in the mask register PCMSK. If one or more of these bits are set, software has to be used to determine which of the pins has caused the PCINT. If only one of the PCMSK bits is set, this task is simple. In contrast to INT0 no preselection on rising or falling edges can be made, any changes lead to a PCINT.

GIMSK The PCINT has to be enabled in the General Interrupt Mask port GIMSK by setting the bit PCIE.

That is it to use monitoring of any port pin and to react with an interrupt.

Home Top AD conversion PCINT Intensity Color selection Dynamic Red/Green


8.3 Hardware, components and mounting

8.3.1 Hardware scheme

Scheme The duo-LED is installed like in the previous lecture. The key is on pin 2, which can trigger PCINT3. The potentiometer is connected to ADC2 on pin 3 and divides the operating voltage. The ISP connections remain the same as in all previous lectures. Those connections are not shown here.

8.3.2 Components

The potentiometer

Poti Poti This is the potentiometer, the middle pin is the slider. All three wire pins are too large to fit into breadboard holes, so short wires are soldered to these pins.

8.3.3 Mounting

Mounting This is an example for mounting. The placement of components is not critical.


Home Top AD conversion PCINT Intensity Color selection Dynamic Red/Green


8.4 Intensity regulation

8.4.1 Task 1

In the first task the intensity of the red LED shall be regulated with the potentiometer. The intensity should increase with the increasing angle of the potentiometer (at increased voltages).

8.4.2 Solution 1

It is clear from the task description that To convert 0 to 1023 to the PWM range 0 to 255 it has to be divided by four. This division is one new software skill to be learned here.

We do not necessarily need interrupts here, because all we have to do is Thanks to the automatic timer operation we do not have to control more. If we program this in linear mode the controller waits for more than 95% of its time for the AD conversion complete condition. This is not very intelligent, it is not very aesthetic and wastes current. As the following tasks require the AD conversion complete interrupt anyway, we use this interrupt controlled technology already here.

8.4.3 Program 1

The program is available here as source code file.

;
; *********************************************
; * LED intensity regulator with poti and ADC *
; * (C)2017 by www.avr-asm-tutorial.net       *
; *********************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ---------- Registers --------------------
; free: R0 .. R12
.def rAdcL = R13 ; LSB ADC result
.def rAdcH = R14 ; MSB dto.
.def rSreg = R15 ; SREG save/restore register
.def rmp   = R16 ; Multi purpose register
.def rimp  = R17 ; Multi purpose inside ints
; free: R18 ... R31
;
; ---------- Ports ------------------------
.equ pOut = PORTB ; LED output port
.equ pDir = DDRB  ; Direction port
.equ bRAO = PORTB2 ; Output pin anode ret LED
.equ bRAD = DDB2   ; Direction pin anode red LED
.equ bRKO = PORTB0 ; Output pin cathode red LED
.equ bRKD = DDB0   ; Direction pin cathode red LED
;
; ---------- Timing -----------------------
; Controller clock  = 1.200.000 cs/s
; ADC prescaler     =       128
; ADC cycles        =        13
; Measure frequency =       721 cs/s
; TC0 prescaler     =        64
; PWM resolution    =       256
; PWM frequency     =        73 cs/s
;
; ---------- Reset- and interrupt vectors ---
.CSEG ; Assemble to flash storage (Code Segment)
.ORG 0 ; Addresse to zero (Reset- and interrupt vectors start at zero)
	rjmp Start ; Reset Vector, jump to init
	reti ; INT0-Int, inactive
	reti ; PCINT-Int, inactive
	reti ; TIM0_OVF, inactive
	reti ; EE_RDY-Int, inactive
	reti ; ANA_COMP-Int, inactive
	reti ; TIM0_COMPA-Int, inactive
	reti ; TIM0_COMPB-Int, inactive
	reti ; WDT-Int, inactive
	rjmp AdcIsr ; ADC-Int, active
;
; Interrupt service routines
;
AdcIsr: ; Interrupt Service Routine ADC conversion complete
	in rSreg,SREG ; save SREG
	; Read ADC result
	in rAdcL,ADCL ; Read ADC result, LSB
	in rAdcH,ADCH ; dto., MSB
	; Divide result by 4
	lsr rAdcH ; shift MSB right, bit 0 to carry flag
	ror rAdcL ; rotate LSB right, bit 7 from carry flag
	lsr rAdcH ; shift MSB right, bit 0 to carry
	ror rAdcL ; rotate LSB right, bit 7 from carry flag
	; Set new PWM compare value
	out OCR0A,rAdcL ; to timer Compare port A
	; Restart ADC (prescaler 128, interrupt enable)
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rimp ; to ADC control port
	out SREG,rSreg ; Restore SREG
	reti ; return from interrupt, set I flag
;
; ---------- Main program init ------------
Start:
	; Init stack
	ldi rmp,LOW(RAMEND) ; Stack pointer to RAMEND
	out SPL,rmp ; to stack pointer port
	; Configure LED pins and activate
	ldi rmp,(1<<bRAD)|(1<<bRKD) ; Port pins LED to output
	out pDir,rmp ; to direction port
	ldi rmp,(1<<bRAO)|(1<<bRKO) ; Anode and cathode to high
	out pOut,rmp ; to output port
	; Timer as 8 bit PWM
	ldi rmp,0x80 ; half intensity
	out OCR0A,rmp ; to timer compare port A
	; Timer Fast PWM, clear PB0 at top, set on compare match
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)|(1<<WGM00)
	out TCCR0A,rmp ; to control port A
	ldi rmp,(1<<CS01)|(1<<CS00) ; Prescaler 64
	out TCCR0B,rmp ; to control port B
	; Configure ADC
	ldi rmp,1<<MUX1 ; ADC signal input = ADC2
	out ADMUX,rmp ; to ADC multiplexer port
	ldi rmp,1<<ADC2D ; Disable port driver hardware ADC2
	out DIDR0,rmp ; to disable port
	; Start ADC, enable interrupt, prescaler = 128
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; to ADC control port
	; Sleep mode idle
	ldi rmp,1<<SE ; Enable sleep
	out MCUCR,rmp ; to general control port
	; Interrupts
	sei ; enable interrupts
; Program loop
Loop:
	sleep ; put to sleep
	nop ; after wakeup by interrupt
	rjmp Loop ; back to sleep
;
; End of source code
;

New are the instructions LSR and ROR. We have to look at this closer, because it is used very often when manipulating 16 bit numbers. The instructions LSL and ROL act similar, but shift and rotate to the left.

LEFT The task performed here is to shift the two lower bits in the ADCH result to the LSB read from ADCL. This double right shift is a binary division by four. The operation in the picture is performed twice. First, the MSB is sfifted to the right. A zero bit is shifted into bit 7, while bit 0 is shifted to the carry flag in SREG. In the second instruction this carry bit is rotated into bit 7 of the LSB, while bit 0 is rotated to the carry flag.

The ATtiny13 does not have a 16 bit shift instruction, so the two instructions LSL and ROR perform this. Repeating LSR and ROR twice the two lowest bit in the LSB are now bit 7 and 6 of the LSB, while the two lowest bits in the LSB are scrapped.

The result of the experiment is that the intensity of the LED is a sensitive function of the potentiometer's state. And it is absolutely linear. This shall be compared with a traditional current regulator with a potentiometer.

Current regulator Current Left is a simple current regulator, to the right the current through the LED in different stages of the potentiometer. It is obvious that the PWM regulator is a much better solution.


Home Top AD conversion PCINT Intensity Color selection Dynamic Red/Green


8.5 Intensity regulation with color change

8.5.1 Task 2

Now not only the red but also the green LED shall be regulated. The key shall be used to select the red and green LED. In all cases a higher voltage shall lead to higher intensity.

8.5.2 Debouncing

Switching of the color with the key from red to green and back is simple: if PORTB2 (or PINB2) is one, PORTB2 is switched to zero, and vice versa. This changes the polarity of the LED base reference and its color, while the PWM output on PB0 delivers the pulses.

Fatal in this application is the bouncing of the key. Depending from the number of swarm pulses from the key red or green results. A not very reliable switch, so we need a mechanic that suppresses bounces.

For that we need the following: To measure this duration we could again use counting loops, but this would be below our skill level. Two sources for clocking purposes are available: Avoiding the additional task for the AD conversion complete interrupt we use the TC0 compare match A interrupt. Because 13 ms would be slightly too short for debouncing, we use a downcounter to ensure that at least 40 ms long no further pulse from the key comes in.

From that we get the following time relations between the key signal, the inactivity flag bTA, the color inversion and the TC0 interrupt.

Signals

If the key is pressed (key input goes low), while the flag is zero, the LED changes its color and the downcounter is set to four.

Each following low pulse on the key input resets the counter to four. This ensures that the flag is cleared only after four PWM pulses come in (40 ms delay), following the last key event.

On each PWM interrupt the bTA flag is tested. If not set, nothing further has to be done. If set, the state of the key input is deciding. If this is high, the counter is decreased. If the counter reaches zero, the flag is cleared. If the input is low (key still active) the counter is set to four again.

With that, the flow diagrams for the two interrupt service routines are as follows.

PCINT TC0INT That is it. By combining these mechanics we ensure that no chaos occurs when the key is pressed.

8.5.3 Program 2

Now we use the ADLAR bit of the ADC to avoid shifting and rotating of the 10 bit ADC result. Further we use the switching of the OC0A output to display red and green correctly.

This here is the program, its source code is here.

;
; ******************************************************
; * LED intensity regulator red/green with ADC and key *
; * (C)2017 by www.avr-asm-tutorial.net                *
; ******************************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ---------- Registers --------------------
; free: R0 .. R14
.def rSreg = R15 ; Save/Restore SREG
.def rmp   = R16 ; Multi purpose register
.def rimp  = R17 ; Multi purpose inside interrupts
.def rFlag = R18 ; Flag register
	.equ bTA = 0 ; Key active flag
.def rCnt  = R19 ; Counter for debounce suppression
; free: R20 .. R31
;
; ---------- Ports ------------------------
.equ pOut = PORTB ;  LED output port
.equ pDir = DDRB  ;  LED direction port
.equ pIn  = PINB  ;  LED read port
.equ bRAO = PORTB2 ; Output pin anode red LED
.equ bRAD = DDB2   ; Direction pin anode red LED
.equ bRAI = PINB2  ; Read pin anode red LED
.equ bRKO = PORTB0 ; Output pin cathode red LED
.equ bRKD = DDB0   ; Direction pin cathode red LED
.equ bTaO = PORTB3 ; Output pin pullup key
.equ bTaI = PINB3  ; Input pin key
.equ bTaE = PCINT3 ; PCINT mask bit key
.equ bAdD = ADC2D  ; Input driver disable ADC pin
;
; ---------- Timing -----------------------
; Prozessor clock   = 1,200,000 cs/s
; ADC prescaler     =       128
; ADC cycles        =        13
; ADC complete freq =       721 cs/s
; TC0 prescaler     =        64
; PWM resolution    =       256
; PWM frequency     =        73 cs/s
; TC0 int duration  =        13.65 ms
;
; ----------- Constants -------------------
.equ cClock = 1200000 ; Controller clock
.equ cPresc = 64 ; TC0 prescaler
.equ cPwm   = 256 ; PWM counter stages
.equ cPrell = 50 ; ms key debouncing time
.equ cFPwm  = cClock/cPresc/cPwm ; frequency PWM in cs/s
; Calculation with rounding
.equ cTPwm  = (1000+cFPwm/2)/ cFPwm ; clock duration PWM in ms
.equ cCnt   = (cPrell+cTPwm/2) / cTPwm ; count pulses debounce
;
; ---------- Reset- and interrupt vectors ---
.CSEG ; Assembler to flash storage (Code Segment)
.ORG 0 ; Address to zero (Reset- and interrupt vectors start at zero)
	rjmp Start ; Reset vector, jump to init
	reti ; INT0-Int, inactive
	rjmp PcIntIsr ; PCINT-Int, active
	rjmp TC0OvfIsr ; TIM0_OVF, active
	reti ; EE_RDY-Int, inactive
	reti ; ANA_COMP-Int, inactive
	reti ; TIM0_COMPA-Int, inactive
	reti ; TIM0_COMPB-Int, inactive
	reti ; WDT-Int, inactive
	rjmp AdcIsr ; ADC-Int, active
;
; Interrupt service routines, with number of clock cycles
;
PcIntIsr: ; Interrupt service routine PCINT
	in rSreg,SREG ; Save SREG, +1 = 1
	sbrc rFlag,bTA ; Jump over if blocking flag active, +1/2 = 2/3
	rjmp PcIntIsrSet ; Restart counter, +2 = 4
	sbic pIn,bTaI ; Jump over if key=low, +1/2 = 4/5
	rjmp PcIntIsrSet ; Restart counter, +2 = 6
	sbr rFlag,1<<bTA ; Set flag, +1 = 6
	sbic pIn,bRAI ; Jump over if anode red is low, +1/2 = 7/8
	rjmp PcIntIsrGreen ; Jump to switch LED green, +2 = 9
	; Switch LED to red
	sbi pOut,bRAO ; Set LED anode red output pin, +2 = 10
	ldi rimp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)|(1<<WGM00) ; Fast PWM/clear on match, +1 = 11 
	out TCCR0A,rimp ; to timer control port A, +1 = 12
	rjmp PcIntIsrSet ; Restart counter, +2 = 14
PcIntIsrGreen: ; Switch LED to green
	cbi pOut,bRAO ; Clear LED anode pin, + 2 = 11
	ldi rimp,(1<<COM0A1)|(1<<WGM01)|(1<<WGM00) ; Fast PWM/set on match, +1=12
	out TCCR0A,rimp ; to timer control port A, +1 = 13
PcIntIsrSet: ; Restart counter
	ldi rCnt,cCnt ; Load constant to counter, + 1 = 5/7/15/14
	out SREG,rSreg ; Restore SREG, +1 = 6/8/16/15
	reti ; Return from int/set I flag, + 4 = 10/12/20/19
;
TC0OvfIsr: ; Interrupt service routine TC0-Overflow
	in rSreg,SREG ; Save SREG, +1 = 1
	sbrs rFlag,bTa ; Jump over if bTA flag set, +1/2 = 2/3
	rjmp TC0OvfIsrRet ; End ISR, +2 = 4
	sbis pIn,bTaI ; Jump over if key input high, +1/2 = 4/5
	rjmp TC0OvfIsrNew ; Restart counter, +2 = 6
	dec rCnt ; Decrease counter, + 1 = 6
	brne TC0OvfIsrRet ; Not yet zero, return, +1/2 = 7/8
	cbr rFlag,1<<bTa ; Clear bTa flag, +1 = 8
	rjmp TC0OvfIsrRet ; Jump to return, +2 = 10
TC0OvfIsrNew:
	ldi rCnt,cCnt ; Restart downcounter, +1 = 7
TC0OvfIsrRet:
	out SREG,rSreg ; Restore SREG, +1 = 5/9/8
	reti ; Return from int/set I flag, + 4 = 9/13/12
;
AdcIsr: ; Interrupt service routine ADC conversion complete
	; Read ADC result
	in rimp,ADCH ; Read MSB ADC result, +1 = 1
	; Set new PWM compare value
	out OCR0A,rimp ; to compare port A, +1 = 2
	; Restart ADC conversion (Prescaler 128, interrupt enable)
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); +1=3
	out ADCSRA,rimp ; to ADC control port, +1 = 4
	reti ; Return from int/set I flag, +4 = 8
;
; ---------- Main program init ------------
Start:
	; Init stack
	ldi rmp,LOW(RAMEND) ; Stack pointer to RAMEND
	out SPL,rmp ; to stack port
	; Configure LEDs
	ldi rmp,(1<<bRAD)|(1<<bRKD) ; Port pins as output
	out pDir,rmp ; to direction port
	ldi rmp,(1<<bRKO)|(1<<bTaO) ; LED to red, key input pullup
	out pOut,rmp ; to port output
	; Timer as 8 bit PWM
	ldi rmp,0x80 ; Half intensity
	out OCR0A,rmp ; to compare match port A
	ldi rmp,(1<<COM0A1)|(1<<WGM01)|(1<<WGM00) ; Fast PWM, clear on compare match
	out TCCR0A,rmp ; to timer control port A
	ldi rmp,(1<<CS01)|(1<<CS00) ; Timer prescaler 64, start timer
	out TCCR0B,rmp ; to timer control port B
	ldi rmp,1<<TOIE0 ; Enable overflow int for key processing
	out TIMSK0,rmp ; to timer int mask
	; Configure ADC: Left adjust result, signal input = ADC2
	ldi rmp,(1<<ADLAR)|(1<<MUX1) ; Set ADLAR and MUX1
	out ADMUX,rmp ; in ADC multiplexer port
	; Disable ADC input driver port
	ldi rmp,1<<bAdD ; Disable port driver
	out DIDR0,rmp ; in disable port
	; Start first ADC conversion, enable ADC interrupt, prescaler = 128
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; to ADC control port
	; Enable PCINT
	ldi rmp,1<<bTaE ; Key interrupts
	out PCMSK,rmp ; to PCINT mask port
	ldi rmp,1<<PCIE ; Enable PCINT
	out GIMSK,rmp ; in general interrupt mask port
	; Sleep mode idle and interrupts
	ldi rmp,1<<SE ; Enable sleep mode idle
	out MCUCR,rmp ; in universal control port
	sei ; Enable interrupts
; Program loop
Loop:
	sleep ; put to sleep
	nop ; After wakeup by int
	rjmp Loop ; back to sleep
;
; End of source code
;

The counting of the clock cycles in the interrupt service routines yields a maximum of 20 cycles, which yields 20/1.2 = 16.7 µs duration. This is far below of the milliseconds over which key bouncing occurs and far below the PWM cycle of the timer. The routines are short enough so that we do not need to transfer code to the main program loop.

New instructions are not used.

If we imagine that we program this in linear mode with counting loops we can imagine how complicated this all would get: waiting for the ADC, monitoring the key input, counting times, inverting the color, etc. etc. Compared to this real mass interrupt programming and execution are straight forward and simple if one has understood the concept. Interrupts therefore are simple and useful.

A comment on programming this in high-level languages: even the simple PWM timing and down counting to avoid bouncing is a complicated issue in that languages as those are not really comfortable in execution timing. They are unable to really use the components of the controller to their best, as assembler offers. High level languages are too far away from the available hardware.

Home Top AD conversion PCINT Intensity Color selection Dynamic Red/Green


8.6 Intensity regulation dynamically

8.6.1 Task 3

In this task the intensity of the LED is increasing and decreasing automatically. The potentiometer determines how fast or slow this happens. The key again switches between red and green of the LED, which occurs only in case of switching from increasing to decreasing and vice versa.

8.6.2 Solution

Now the TC0 interrupt at the end of the PWM cycle not only has to control the key event but also has to determine whether the compare value has to be increased and/or decreased. To control the speed of increases/decreases the ADC value has to determine after which number of PWM cycles this increase or decrease has to happen. For that the 8 bit AD result (ADLAR) has to be divided by 16. To avoid a zero value (at which no increase or decrease would happen), we add one. This leads to between one and sixteen stages.

From my experience those cycles are relatively long because of the 256 single steps of the PWM. Therefore we increase the timer clock eightfold (prescaler = 8 instead of 64). This leads to a visible dynamic of the LED.

Rising and falling intensity of the LED is again be performed by changing the OCR0A value. To determine whether a change in direction is necessary requires some more complicated decisions. Therefore this part is performed outside the ISR. A flag signals that the main loop has to do that part of the work.

The main loop also controls the color of the LED. The color is one bit in the flag register. This bit is inverted by a key event, the color change actually occurs only after enough PWM cycles have been processed.

All elements of the program are clear now and we can start programming source code.

8.6.3 Program 3

This is the source code, the code for download is here.

;
; *******************************************
; * LED intensity control red/green up/down *
; * (C)2017 by www.avr-asm-tutorial.net     *
; *******************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ---------- Registers --------------------
; free: R0 .. R12
.def rCyc  = R13 ; Cycle counter
.def rAdc  = R14 ; ADC result
.def rSreg = R15 ; Save/Restore SREG
.def rmp   = R16 ; Multi purpose register
.def rimp  = R17 ; Multi purpose inside interrupts
.def rFlag = R18 ; Flag register
	.equ bTA = 0 ; Key aktive flag
	.equ bCy = 1 ; Task flag cycle end
	.equ bAb = 2 ; Cout down flag
	.equ bGn = 3 ; LED green flag
.def rCnt  = R19 ; Counter for key debouncing
; free: R20 .. R31
;
; ---------- Ports ------------------------
.equ pOut = PORTB ;  Output port
.equ pDir = DDRB  ;  Direction port
.equ pIn  = PINB  ;  Input port
.equ bRAO = PORTB2 ; Output pin anode red LED
.equ bRAD = DDB2   ; Direction pin anode red LED
.equ bRAI = PINB2  ; Read pin anode red LED
.equ bRKO = PORTB0 ; Output pin cathode red LED
.equ bRKD = DDB0   ; Direction pin cathode red LED
.equ bTaO = PORTB3 ; Pullup pin key
.equ bTaI = PINB3  ; Input pin key
.equ bTaE = PCINT3 ; PCINT mask bit key
.equ bAdD = ADC2D  ; Input disable ADC pin
;
; ---------- Timing -----------------------
; Controller clock  = 1.200.000 cs/s
; ADC prescaler     =       128
; ADC cycles ecec.  =        13
; ADC frequency     =       721 cs/s
; TC0 prescaler     =         8
; PWM stages        =       256
; PWM frequency     =       585 cs/s
; TC0 int repeat    =         1.7 ms
; Count time min    =         1.7 ms
; Count time max    =        28.9 ms
; Up/Down cycle min =         0.88 s
;               max =        14.8 sec
;
; ----------- Constants ------------------
.equ cClock = 1200000 ; Controller clock
.equ cPresc = 8 ; TC0 prescaler
.equ cPwm   = 256 ; PWM resolution
.equ cBounce= 50 ; ms bouncing duration key
.equ cFPwm  = cClock/cPresc/cPwm ; Frequency PWM in cs/s
; Calculation with rounding
.equ cTPwm  = (1000+cFPwm/2)/ cFPwm ; Clock duration PWM in ms
.equ cCnt   = (cBounce+cTPwm/2) / cTPwm ; Count pulses debouncing
;
; ---------- Reset- and interrupt vectors ---
.CSEG ; Assemble to the flash storage (Code Segment)
.ORG 0 ; Address to zero (Reset- and interrupt vectors start at zero)
	rjmp Start ; Reset vector, jump to init
	reti ; INT0-Int, inactive
	rjmp PcIntIsr ; PCINT-Int, active
	rjmp TC0OvfIsr ; TIM0_OVF, active
	reti ; EE_RDY-Int, inactive
	reti ; ANA_COMP-Int, inactive
	reti ; TIM0_COMPA-Int, inactive
	reti ; TIM0_COMPB-Int, inactive
	reti ; WDT-Int, inactive
	rjmp AdcIsr ; ADC-Int, active
;
; Interrupt service routines, with number of clock cycles
;
PcIntIsr: ; Interrupt service routine PCINT
	in rSreg,SREG ; Save SREG, +1 = 1
	sbrc rFlag,bTA ; Jump over if flag clear, +1/2 = 2/3
	rjmp PcIntIsrSet ; Restart counter, +2 = 4
	sbic pIn,bTaI ; Jump ober if key input low ; +1/2 = 4/5
	rjmp PcIntIsrSet ; Restart counter ; +2 = 6
	sbr rFlag,1<<bTA ; Set bTA flag, +1 = 6
	ldi rimp,1<<bGn ; Invert Green flag, +1 = 7
	eor rFlag,rimp ; from red to green or back, +1 = 8
PcIntIsrSet:
	ldi rCnt,cCnt ; Restart counter, + 1 = 5/7/9
	out SREG,rSreg ; Restore SREG, +1 = 6/8/10
	reti ; Return from int/set I flag, + 4 = 10/12/14
;
TC0OvfIsr: ; Interrupt service routine TC0-Overflow
	in rSreg,SREG ; Save SREG, +1 = 1
	sbrs rFlag,bTa ; Jump over if bTA flag set, +1/2 = 2/3
	rjmp TC0OvfIsrDwn ; Jump to count down, +2 = 4
	sbis pIn,bTaI ; Jump over if key input high, +1/2 = 4/5
	rjmp TC0OvfIsrNew ; Restart counter, +2 = 6
	dec rCnt ; Decrease counter, + 1 = 6
	brne TC0OvfIsrDwn ; Not yet zero, count down, +1/2 = 7/8
	cbr rFlag,1<<bTa ; Clear bTa flag, +1 = 8
	rjmp TC0OvfIsrDwn ; Count down, +2 = 10
TC0OvfIsrNew:
	ldi rCnt,cCnt ; Restart down counter, +1 = 7
TC0OvfIsrDwn:
	dec rCyc ; Decrease count cycles, +1 = 5/9/11/8
	brne TC0OvfIsrRet ; Not yet zero, +1/2 = 6/7/12/13/9/10
	mov rCyc,rAdc ; Restart cycle counter, +1 = 7/13/10
	sbr rFlag,1<<bCy ; Set handling flag, +1 = 8/14/11
TC0OvfIsrRet:
	out SREG,rSreg ; Restore SREG, +1 = 8/13/11/9/15/12
	reti ; Return from int/set I flag, + 4 = 12/17/15/13/19/16
;
AdcIsr: ; Interrupt service routine ADC
	; Read ADC result
	in rAdc,ADCH ; Read MSB ADC result, +1 = 1
	lsr rAdc ; divide by 16, +1 = 2
	lsr rAdc ; +1 = 3
	lsr rAdc ; +1 = 4
	lsr rAdc ; +1 = 5
	inc rAdc ; +1 = 6
	; Restart ADC (Prescaler 128, Enable interrupt)
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); +1=7
	out ADCSRA,rimp ; to ADC control port, +1 = 8
	reti ; Return from int/set I flag, +4 = 12
;
; ---------- Main program init ------------
Start:
	; Stack setup
	ldi rmp,LOW(RAMEND) ; Stack pointer to RAMEND
	out SPL,rmp ; to stack port
	; Configure LEDs
	ldi rmp,(1<<bRAD)|(1<<bRKD) ; Port pins to output
	out pDir,rmp ; to direction port
	ldi rmp,(1<<bRKO)|(1<<bTaO) ; LED red, key pullup on
	out pOut,rmp ; to port output
	; Start conditions
	clr rFlag ; Clear flags
	ldi rmp,0x10 ; Short cycle
	mov rAdc,rmp ; to ADC register
	mov rCyc,rmp ; and to cycle counter
	; Timer as 8 bit PWM
	ldi rmp,0x80 ; Half intensity
	out OCR0A,rmp ; to timer compare port A
	ldi rmp,(1<<COM0A1)|(1<<WGM01)|(1<<WGM00) ; Fast PWM, clear on TOP
	out TCCR0A,rmp ; to timer control port A
	ldi rmp,(1<<CS01) ; Prescaler 8
	out TCCR0B,rmp ; to control port B
	ldi rmp,1<<TOIE0 ; Overflow Interrupt
	out TIMSK0,rmp ; to timer int mask
	; Configure ADC: Left adjust result, signal input = ADC2
	ldi rmp,(1<<ADLAR)|(1<<MUX1) ; ADLAR and ADC-Pin
	out ADMUX,rmp ; to ADC multiplexer port
	; Disable ADC pin driver
	ldi rmp,1<<bAdD ; Disable port driver
	out DIDR0,rmp ; to Disable port
	; Switch on ADC, enable int and start conversion
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; to ADC control port
	; Enable PCINT
	ldi rmp,1<<bTaE ; Key interrupts
	out PCMSK,rmp ; to PCINT mask port
	ldi rmp,1<<PCIE ; Enable PCINT
	out GIMSK,rmp ; to General Interrupt mask
	; Sleep mode and interrupts
	ldi rmp,1<<SE ; Enable sleep mode idle
	out MCUCR,rmp ; to control port
	sei ; Enable interrupts
; Program loop
Loop:
	sleep ; put to sleep
	nop ; After wakeup by interrupts
	sbrc rFlag,bCy ; Handling flag cycle end?
	rcall Cycle ; Handle flag
	rjmp Loop ; Go to sleep again
;
Cycle: ; Handle flag, with clock cycles
	cbr rFlag,1<<bCy ; Clear flag, +1 = 1
	sbrc rFlag,bGn ; Green LED?, +1/2 = 2/3
	rjmp CycleGreen ; Yes, LED to green, +2 = 4
	sbi pOut,bRAO ; switch LED red, +2 = 5
	ldi rmp,(1<<COM0A1)|(1<<WGM01)|(1<<WGM00); +1 = 6
	out TCCR0A,rmp ; to timer control port A, +1 = 7
	rjmp CycleDirection ; Change direction, +2 = 9
CycleGreen:
	cbi pOut,bRAO ; switch LED to green, +2 = 6
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)|(1<<WGM00); +1 = 7
	out TCCR0A,rmp ; to timer control port A, +1 = 8
CycleDirection:
	in rmp,OCR0A ; Read PWM compare match value, +1 = 10/9
	sbrc rFlag,bAb ; Jump over if downward flag clear, +1/2 = 11/12/10/11
	rjmp CycleDown ; to downward ; +2 = 13/12
	; Compare value upward
	inc rmp ; PWM one step up, +1 = 13/12
	brne CycleSet ; not zero, write compare, +1/2 = 14/14
	sbr rFlag,1<<bAb ; Set downward flag, +1 = 15
	ldi rmp,0xFF ; to TOP value, +1 = 16
	rjmp CycleSet ; write new value, +2 = 18
CycleDown:
	subi rmp,1 ; Decrease by one, +1 = 14/13
	brcc CycleSet ; write value if carry clear, +1/2 = 15/16/14/15
	cbr rFlag,1<<bAb ; clear downward flag, +1 = 16/15 
	clr rmp ; start at zero, +1 = 17/16
CycleSet:
	out OCR0A,rmp ; write new compare value, +1 = 15/15/19/17/16/18/17
	ret ; ready, return, +4 = 19/19/23/21/20/22/21
;
; End of source code
;

The following instructions are new: Compared to the previous program the timer now runs eight times faster. Only the line with the timer init has been changed. In the calculation of the constant in the header 64 has been exchanged with 8. The constant to determine the inactivity duration of the key has increased accordingly to increase the monitored time. The constant now is cCnt=25. This is the advantage to calculate such constants on top of the source code, any subsequent changes are then simplified.

To find out which value cCnt now has you need again the symbol listing that gavrasm provides if -s has been set as parameter.

Home Top AD conversion PCINT Intensity Color selection Dynamic Red/Green


8.7 Fast red/green change

8.7.1 Task 3

This is a bonus task. What happens with the color of the LED if we switch fast between red and green? Higher voltages on the potentiometer shall increase the red duration.

8.7.2 Solution

Push pull flow This is the timing diagram. It is essential that both output pins change their polarity when they reach their compare match, and that their polarity is complementary. The signal outputs OC0A and OC0B, when correctly programmed, deliver this push-pull signal that switches between the two colors.

Scheme Because the OC0B pin is needed here, the second pin of the duo LED has to be placed on pin 6. The key is not needed here, but can remain part of the hardware.

8.7.3 Program

This is the program, the source code is here. Because the switching of OC0A and OC0B is performed by the timer, only the interrupt service routine for the AD conversion is needed.

;
; *********************************
; * Duo-LED in push-pull ATtiny13 *
; * (C)2017 by gsc-elektronic.net *
; *********************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; -------- Registers --------------
; frei: R0 .. R15
.def rmp = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside ints
;
; -------- Ports ------------------
.equ pDir = DDRB ; Port outputs
.equ bARD = DDB1 ; Red anode
.equ bCRD = DDB0 ; Red cathode
;
; -------- Timing -----------------
;  Clock         = 1200000 Hz
;  Prescaler     =      64
;  PWM-Stages    =     256
;  PWM-Frequency =      73 Hz
;
; -- Reset- and Interrupt vectors -
.CSEG ; Code Segment
.ORG 0 ; Start at address 0
	rjmp Start ; Reset vector, jump to init
	reti ; INT0-Int, inactive
	reti ; PCINT-Int, inactive
	reti ; TIM0_OVF, inactive
	reti ; EE_RDY-Int, inactive
	reti ; ANA_COMP-Int, inactive
	reti ; TIM0_COMPA-Int, inactive
	reti ; TIM0_COMPB-Int, inactive
	reti ; WDT-Int, inactive
	rjmp AdcIsr ; ADC-Int, active
;
; ----- Interrupt Service Routines -----
;
AdcIsr:
	in rimp,ADCH ; Read ADC result MSB
	out OCR0A,rimp ; to Compare port A
	out OCR0B,rimp ; to Compare port B
	; Restart ADC (prescaler 128, Interrupt enable)
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rimp ; to ADC control port
	reti
;
; ----- Main program init --------------
Start:
	; Stack init
	ldi rmp,LOW(RAMEND) ; to SRAM end
	out SPL,rmp ; to stack pointer
	; Init output pins
	ldi rmp,(1<<bARD)|(1<<bCRD) ; Anode and cathode are output
	out pDir,rmp ; to direction port
	; Init comparer
	ldi rmp,0x80 ; half/half at start
	out OCR0A,rmp ; to compare port A
	out OCR0B,rmp ; to compare port B
	; Timer as PWM with A- and B-output control
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<COM0B1)|(1<<WGM01)|(1<<WGM00)
	out TCCR0A,rmp ; to timer control port A
	; Start timer with a prescaler of 64
	ldi rmp,(1<<CS01)|(1<<CS00) ; Prescaler 64
	out TCCR0B,rmp ; to timer control port B
	; Configure ADC: Left adjust, signal input = ADC2
	ldi rmp,(1<<ADLAR)|(1<<MUX1) ; ADLAR and ADC pin
	out ADMUX,rmp ; to ADC multiplexer port
	; ADC input driver off
	ldi rmp,1<<ADC2D ; Disable port driver
	out DIDR0,rmp ; in disable port
	; Switch on and start ADC
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; to ADC control port
	; Sleep enable
	ldi rmp,1<<SE ; Sleep mode idle
	out MCUCR,rmp ; to general control port
	; Enable interrupts
	sei ; set I flag
Loop:
	sleep ; go to sleep
	nop ; after waking up
	rjmp Loop ; go to sleep again
;
; End of source code
;

No new instructions are used here.

The result is not exactly what is to be expected: the red and green light of the LED does not really mix to new colors. The reason for that is that the red and green light are not generated by the same diode. There are two separated diodes in action, that are located in some distance to each other. So unfortunately we still see the origin of the green and red light separately instead of an ideally mixed color.

Home Top AD conversion PCINT Intensity Color selection Dynamic Red/Green


©2017 by http://www.avr-asm-tutorial.net