Path: Home ==> AVR-EN ==> Micro beginner ==> 8. Intensity regulation     Diese Seite in Deutsch (extern):

# Lecture 8: Regulation of the intensity of a LED

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

## 8.1 Introduction to AD conversion

• They store a voltage in a capacitor and then decouple this from the source ("Sample-and-Hold").
• Then this voltage is compared with half the reference voltage. If the stored voltage is smaller, the most significant result bit is low, otherwise high. If high, the comparer voltage remains at half the reference voltage otherwise the compare voltage is zero.
• Then the compare voltage is increased by one quarter of the reference voltage. If the stored voltage is larger than this compare voltage, the next result bit is low, otherwise high. The compare voltage is set depending from this result.
• This goes on with one eights, one sixteenth, one thirtysecond, etc. of the reference voltage, until all bits have been collected.
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.

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.

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.

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.

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.

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.

A typical sequence to start the ADC with ADC3 as voltage source is as follows:
``````
ldi rmp,(1<<MUX1)|(1<<MUX0) ; Channel 3
; switch ADC on, start conversion, interrupt when complete, precaler 128
```
```
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.

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.

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

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

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

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

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
• the timer mode will be Fast PWM,
• the voltage has to be measured continously,
• the result of the ADC measurement is between 0 and 1023, that has to be converted to the PWM comparer range of 0 to 255,
• the PWM comparer has to be written with this converted value.
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
• to wait until the AD conversion is completed (the ADSC bit clears),
• to divide the AD result by four and write that to the comparer.
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 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
; 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)
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
;
; Interrupt service routines
;
in rSreg,SREG ; save SREG
; 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)
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
out DIDR0,rmp ; to disable port
; Start ADC, enable interrupt, prescaler = 128
; 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.

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.

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.

### 8.4.4 Simulating program execution

The following simulates program execution using the simulator avr_sim.

The program init phase has been executed. 18.3 µs have elapsed.

The output port has been initiated to drive the duo LED: the direction bits on the pins PB0 and PB2 have been set, both port bits are cleared and the duo LED is off.

The timer TC0 is initiated in fast PWM mode, with overflow on TOP (255) and sets the I/O pin PB0 on compare match A, which is set to 128 (nearly half intensity).

The AD converter channel ADC2 has been set to work with a reference voltage of 5 V (operating voltage), with 1.25 V on the input. Conversion is clocked by the processor clock divided by 128. The yellow field on the interrupt section signals that the conversion complete interrupt is enabled and execution will result in a jump to the ADC vector.

The AD conversion has started, conversion progress is displayed on a progress bar.

On conversion complete the respective interrupt flag is set and the interrupt is executed if no other higher-levelled int is requested.

On int 1.38 ms have elapsed since the init phase was completed. Conversion is clocked with 1,200,000 / 128 = 9,375 Hz or 0.107 ms per conversion clock. 13 conversion steps are required, so 1.38 ms is correct.

The conversion result,
1023 * 1.25 / 5.0 = 255
is divided by four (=63) and written to the output compare register OCR0A of the timer TC0, controlling the pulse width of the duo LED.

But this change to the OCR0A port register does not come into effect immediately. If it would so, the compare match would not be correct: if OCR0A would be smaller than the current count of TC0 the compare match would be missed in this cycle and the I/O pin would remain low for the whole cycle.

So it is a good idea to change the effective compare match value only after reaching TOP.

TOP was reached and the compare match value now has been updated.

Here the compare match value of 128 has been reached for the first time. I/O pin PB0 is set.

6.89 ms have elapsed since init until the first compare match occurred. This corresponds to
64 * (128 + 1) / 1,200,000 s
and is correct. The whole cycle will last for 64*256/1,200,000 = 13.65 ms or a PWM frequency of 73.24 Hz.

The registers R14:R13 hold the conversion result, divided by 4.

Simulation offers the opportunity to test and verify source code. Bugs can be detected and corrected. Play around with your source code in this way and see if your software works as designed.

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

## 8.5 Intensity regulation with color change

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:
• a flag that signals that further key input signals do not lead to actions, and
• this flag is only cleared if the key is not pressed for a certain period of time.
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:
• the ADC interrupt occurs with 721 cs/s, each 1,39 ms,
• the TC0 produces a PWM signal with 73 cs/s frequency, the compare match would occur each 13,68 ms.
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.

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.

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
;
; ---------- Timing -----------------------
; Prozessor clock   = 1,200,000 cs/s
; 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)
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
;
; 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
; 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
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
;
; Set new PWM compare value
out OCR0A,rimp ; to compare port A, +1 = 2
; Restart ADC conversion (Prescaler 128, interrupt enable)
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
; Disable ADC input driver port
ldi rmp,1<<bAdD ; Disable port driver
out DIDR0,rmp ; in disable 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

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

``````
;
; *******************************************
; * 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 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
;
; ---------- Timing -----------------------
; Controller clock  = 1.200.000 cs/s
; 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)
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
;
; 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
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
;
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)
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 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
ldi rmp,1<<bAdD ; Disable port driver
out DIDR0,rmp ; to Disable port
; Switch on ADC, enable int and start conversion
; 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:
• MOV Register,Register: copies the content of the second register to the first,
• EOR Register,Register: Exclusive OR of the two registers, result to the first one (inverts all bits in the first register that are set in the second one),
• INC Register: increases the register by one,
• BRCC Label: branches to the label if the carry flag in SREG is clear,
• SUBI Register,Constant: subtracts the constant from the register and writes the result to the register (works only in registers R16 to R31).
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

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

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.

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
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
;
; ----- Interrupt Service Routines -----
;
out OCR0A,rimp ; to Compare port A
out OCR0B,rimp ; to Compare port B
; Restart ADC (prescaler 128, Interrupt enable)
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
ldi rmp,1<<ADC2D ; Disable port driver
out DIDR0,rmp ; in disable port
; Switch on and start ADC
; 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