Help! My lights are making noise!

Read time: 53 minutes (13476 words)

So far, we have written simple programs that do basically one thing at a time. What if we want to do multiple things? What we need to do is multi-task. Now some folks are good at this, others not so good. Fortunately, we are not going to try to help people multi-task, we are going to help our microcontroller handle more than one thing at the same (well almost) time.

So, what should we do? Well, to keep things simple, let’s combine our blinky light code and our buzzer code and see if we can do both in one program! I want the buzzer to sound each time the LED lights. Just to make things a bit more interesting, let’s also consider that we have one more thing to do, and we want that thing to happen about 50 times per second. Phew! How do we handle all this?

Timing analysis

Before we get into how we will make more than one thing happen at the same time, we need to do a few simple calculations. First, remember that our processor is running at 16MHz, which means that we can run on the order of 16,000,000 instructions each second - sounds like a bunch. How many instructions will each job we have defined take to complete? My bet is - not that many!

Buzzer timing

As we saw in our work on the buzzer, we need to cause the buzzer to pulse at a rate of 4000 times per second to get a reasonable noise out of our little piezo buzzer. In thinking about this, it seems we need to flip the pin on and off at that rate to cause one full cycle of the buzzer to occur. Than means we need to toggle (compliment) the outputs at twice that rate - or 8000 times per second. Given the speed of our little chip, that means we can run about 2000 instructions before we need to toggle the buzzer pins. That sounds like more than enough!

Blinky LED timing

This one was easy, we want the light to blink on every second or so. Once again, if we want the light to come on at one second intervals, we toggle it twice each second! Blinking the LED is basically the same as toggling the buzzer pins, only here, we toggle a single pin.

Special Task

The last, unnamed task needs to happen 50 times per second. What it will do, we will figure out later. For now, we will simply call a procedure to do something 50 times per second. To check that we have timed this right, we will toggle a pin on the Teensy2 and check it with an oscilloscope.

Summary of basic timing

With all of these simple calculations in mind, it looks like we need to set up a program to call a buzzer toggle routine 8000 times per second. If we can get interrupts happening at this rate, we have a good start on our program.

We need to let 8000/50 = 160 interrupts go by before we call the special task code. Finally, we need to let 4000 interrupts go by before we call the LED blink toggle code.

Sounds like an interesting chunk of code to develop!

Wiring the Arduino

This experiment will involve three separate signals. We will see one, hear another, and for the third we will need an oscilloscope!

Defining the pins

The first thing we need to do is to define how the Teensy2 will be wired. Let’s collect the pin assignments into a simple include file so our code is more generic, and it is easy to rearrange pin assignments if needed. This is my config.inc file:

; config.inc - pin assignments for this project

#include <avr/io.h>

#define     LED_PORT   _(PORTB)
#define     LED_DIR    _(DDRB)
#define     LED_PIN    5

#define     BUZZ_PORT  _(PORTB)
#define     BUZZ_DIR   _(DDRB)
#define     BUZZ_PIN1  4

#define     PULSE_DIR  _(DDRD)
#define     PULSE_PORT _(PORTD)
#define     PULSE_PIN  7

// include this line to avoid SFR_REG issues
#define _(s)    _SFR_IO_ADDR(s)

You can see that the include for the processor is in this file meaning all we need to do is to include this file in all of our assembly language files. Everything we need will be defined through that one include!

Wiring this project is simple. Just add the buzzer you used for the previous project, and we will use the onboard LED. The third task must be checked with an oscilloscope I will bring to class.

Organizing our code

To make it easy to keep track of all the parts of this project, we will try to isolate functions in files associated with those functions.

  • main.S - the main program for the project (configures the chip)
  • timer.S - set up timer and define timer interrupt handler
  • task1.S - buzzer task
  • task2.S - LED task
  • task3.S - special purpose task
  • config.inc - definitions related to the project

Since we have greatly simplified our Makefile, add this list of AVR assembly language files to your Makefile, then go off and write the routines!

Warning

Another thing I discovered after trying to diagnose one students problems with the code in this section. Make sure you place the “main.S” file first in the list of files that make up your project. The linker does not seem to search for the code we need to load at location 0, and simply places the code from the first module into the “hex” file. I figured this out by loading the “hex” file into the AVR Studio simulator.

On to the code

Now that we have the basic idea of what we want to do, let’s see if we can get something working. We start off by noting that we need some kind of signal that will tell us to do something every 1/8000 of a second. Last time, we saw how to set up Timer0 to generate just such a signal. But we have more than one thing to do, so how does this help.

A Multi-tasking kernel

What we will do is set up the timer to go off 8000 times a second. We will use an interrupt handler just like we did in our last lab, but this time all we will ask the handler to do is to set a trigger variable to one and quit! Huh?

Remember what our main routine was doing while we used the interrupt to blink our light? Basically, it was doing nothing. This time, we will set up our main code to do all of our real work, but we will use the trigger variable to decide what we are to do and when! Here is how it works (in “C”):

int trigger = 0;        // will be set to 1 by ISR
...
while(1) {
    if(trigger == 1) {
        // interrupt went off
        // do work
        trigger = 0;
    }
}

Now the main loop is looking to see if the trigger signal went off. This will happen when the interrupt handler is run. We will do some work, then clear the trigger variable. The main loop will then do nothing until the next interrupt. (It seems we are back to polling here, but we will make it more interesting in a bit.)

In order for this to work, whatever we intend to do when the trigger is sensed must happen fast enough that we get done before the next interrupt goes off. For our experiment, this will be no problem.

Let’s expand this logic a bit and take care of the buzzer first. According to our calculations, we need to trigger the buzzer every time the trigger is sensed. Easy, just call a simple routine that includes the code we showed above to swap the signals on the two pins:

while(1) {
    if(Trigger == 1) {
        BuzzerTask();
        Trigger = 0;
    }
}
...
void BuzzerTask() {
    // swap signals on defined pins
}

Now, we actually have a number of tasks to take care of, so we can extend this code easily to deal with those:

while(1) {
    if(trigger == 1){
        BuzzerTask();
        LEDTask();
        AuxTask()
    }
    trigger = 0;
}

But wait! we said that the other tasks do not need to happen every time the trigger is set, but only every so often. How do we handle that? Well, if we set up a counter for each defined task, we can let the task code figure that out:

int LEDcounter = 0;

void LEDTask() {
    LEDcounter++
    if(LEDcounter == 100) {
        // do work
        LEDcounter = 0;
    }
}

This would make the LED task only do real work when its counter reached 100 - or every 100th interrupt. See how it works? Using a scheme such as this will let you handle a bunch of simple tasks easily.

Let’s build the code and try it out!

Building the Kernel code

In our previous lecture on interrupts, we used Timer0 to generate our interrupts. Timer 0 is an 8 bit timer that can only count to 255 before rolling over. We need to be able to count higher than that for this experiment, so we will use a 16 bit timer - Timer1!

Timer routines

Basically, we will configure Timer1 the same way we configured Timer0 last time. Once we turn it loose, the timer will increment a 16 bit counter each time we hit the right number of clock ticks (as defined by the prescaler value we choose). This time, though, we will not wait for the counter to overflow before triggering an interrupt, we will use another mode of the timer that will fire the interrupt when the counter reaches a predefined value. As the interrupt is generated, the timer counter will be reset automatically so the process can continue. This gives us very fine control over the frequency of our interrupts!

Here is the math we need:

  • 16000000/8 = 2000000 (main processor clock speed)
  • 2000000/8000 = 250 (timer max value before triggering interrupt)

(OK, so we could have used Timer0 for this, but we will use Timer1 anyway!)

So, if we use a prescaler value of 8 on the processor clock, the counter in the timer will increment 2000000 times per second! If we let the counter reach 250 before triggering an interrupt, the will happen 8000 times per second. Right what we are after! Remember, we will need to configure the timer to reset the counter when it reaches this count.

Here is the code we need to make the timer work this way (details are described in the processor data sheet):

timer.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
; timer.S - Timer1 code for interrupt blink-buzz

#include "config.inc"

        .global Timer1Setup
        .global TIMER1_COMPA_vect
        .global trigger

.equ    COUNT,      250                 ; counts per trigger

        .section    .data

trigger:
        .byte   0

        .section    .text

;----------------------------------------------------------------------
; Initialize Timer 1 for interrupts
;
Timer1Setup:
        lds     r16, TCCR1B
        ori     r16, (1<<WGM12)|(1<<CS11)           ; divide by 8
        sts     TCCR1B, r16                         ; set timer clock 
        ldi     r16, (COUNT & 0xff)                 ; set in compare count
        sts     OCR1AL, r16                         
        ldi     r16, (COUNT >> 8)
        sts     OCR1AH, r16
        lds     r16, TIMSK1                         ; get interrupt mask reg
        ori     r16, (1<<OCIE1A)                    ; enable interrupt 
        sts     TIMSK1, r16                         ; write it back
        sei                                         ; let the fun begin
        ret

The code to load the timer value will handle numbers greater than 255 in this example.

The Timer ISR routines

With the timer properly initialized, the ISR is pretty simple:

timer.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
;----------------------------------------------------------------------
; Timer0 overflow ISR
;
TIMER1_COMPA_vect:
        ; save callers registers
        push    r16
        push    r0
        in      r0, _(SREG)            ; get flags
        push    r0
        ;
        ldi     r16, 1
        sts     trigger, r16
        ;
        pop     r0
        out     _(SREG), r0            ; recover flags
        pop     r0
        pop     r16
        reti

All we need to do is set the trigger and we are done. This is almost too easy!

Place this code in a timer.S together with the other timer setup code.

The timer interrupt handler jump needs to be set at the OC1Aaddr address which is defined in the defined in the chip definition file.

Note

The values in the definition file are based on addresses into the program memory pointing to 16 bit chinks, not 8 bit chunks like we are used to. All instructions in the AVR are either 16 or 32 bits long, so this form of addressing makes sense in a Harvard architecture. However it means that we need to multiply the instruction addresses by 2 to get the byte address needed to set the interrupt vector in the right spot with avr-gcc.

The main routine

Here is the initialization we need in our main code:

main.S
; main.S - avr-gcc assembly language

#include "config.inc"

        .extern Timer1Setup
        .extern trigger

        .extern Task1
        .extern Task2
        .extern Task3
        .extern Task1_init
        .extern Task2_init
        .extern Task3_init

        .section    .text
        .global main

main: 
        ; set up the system clock
        ldi     r24, 0x80               ; set up prescaler
        sts     CLKPR, r24
        sts     CLKPR, r1               ; set to full speed

At this point in our main code, the interrupt table is set up properly, but we need to initialize the chip, timer and all the tasks. We will set up initialization routines in each task file and simply call those routines here:

main.S
1
2
3
4
5
        ; initialize the system
        call    Timer1Setup
        call    Task1_init
        call    Task2_init
        call    Task3_init

I am using Task3 to make sure the timer is running correctly, so the initialization call is shown.

We have one more chunk of code to set up. Our main code needs to check the trigger variable and call the tasks into life. Place this code right after the call to the timer setup routine:

main.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        ; task management loop 
1:      
        lds     r16, trigger
        cpi     r16, 1                  ; gone off yet?
        brne    1b                      ; wait for it
        call    Task1
        call    Task2
        call    Task3
        sts     trigger, r1             ; turn off the trigger
        rjmp    1b

The task framework

Now that we have an interrupt going off every so often, we can turn to the code needed to make our tasks work as we wish. Here is the framework for a single task. Modifying this code for each designated task is pretty easy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
; task1.S - avr-gcc assembly code

#include "config.inc"

; buzzer task
;
;   hook the buzzer to PB4 and PB5 for this task

            .global     Task1
            .global     Task1_init

.equ        TASK1_MAX,  1               ; service every interrupt

            .section    .data
Task1counter:
            .byte       0

            .section    .text

; Task 1 - piezo buzzer at 4000Hz
;   uses r16, r17
;
Task1_init:
            sbi     BUZZ_DIR, BUZZ_PIN1     ; set to output
            cbi     BUZZ_PORT, BUZZ_PIN1    ; start this one with 0 
            ret

Task1:
            lds     r16, Task1counter
            inc     r16
            sts     Task1counter, r16
            ;
            ldi     r17, TASK1_MAX
            cpse    r16, r17
            rjmp    1f
            ;
            in      r16, BUZZ_PORT
            ldi     r17, (1<<BUZZ_PIN1)
            eor     r16, r17
            out     BUZZ_PORT, r16  
            ;
2:
            sts     Task1counter, r1        ; zero the counter
1:                    
            ret

We do the work needed for this task above the line labeled 2 above

Testing our interrupts

As a test of the basic logic we have presented here, I set up Task3 to fire the logic to toggle the pulse pin each time it is called (max task count value is 1). I hooked an oscilloscope to the pulse pin to see how often they are being triggered. Here is what I saw:

../_images/KernelClock.png

This figure shows an interrupt being triggered about every 125 microseconds, which works out to 8000 times per second, right on the money! Now, we can go off and set up the remaining tasks

Blinking the LED

Now that we have the basic interrupt system running, let’s slow things down and blink the light.

In this task, we need to let about 4000 interrupts go by. That is too many to count with a simple 8 bit register, so we need a 16 bit counter to do the job. This code is a bit more involved, but it is just the multi-precision code we discussed in the Pentium part of the class! The AVR has a few registers it treats as 16 bit registers, used mainly for indirect addressing. Since we do not need any of that at the moment, we will use one register pair (r26, r27) as a 16 bit register. The AVR calls this pair X:

Here is our LED task:

task2.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
; task2.S - avr-gcc assembly code

#include "config.inc"

; LED task
;
;   This task uses the built-in LED on the Teensy

        .global     Task2_init
        .global     Task2
        .global     led_on

.equ    TASK2_MAX,  4000                ; trigger every 1/2 second


        .section    .data
Task2countH:        .byte       0
Task2countL:        .byte       0

        .section    .text

Task2_init:
        sts         Task2countH, r1     ; clear counter
        sts         Task2countL, r1
        ; set up LED port
        sbi         LED_DIR, LED_PIN    ; set up the output port (bit 6)
        cbi         LED_PORT, LED_PIN   ; start off with the LED off
        sts         led_on, r1
        ret

Task2:
        lds         r26, Task2countL        ; load counter
        lds         r27, Task2countH
        adiw        r26, 1                  ; increment counter
        sts         Task2countL, r26        ; save it back
        sts         Task2countH, r27 
        ;
        cpi         r26, TASK2_MAX & 0xff    ; Compare low byte
        ldi         r16, TASK2_MAX >> 8
        cpc         r27, r16                ; Compare high byte
        brne        1f                      ; branch if not equal
        ;
        in          r16, LED_PORT           ; toggle LED pin
        ldi         r17, (1<<LED_PIN)
        eor         r16, r17                ; flip the LED bit
        ;
        in          r16, LED_PORT           ; toggle LED pin
        ldi         r17, (1<<LED_PIN)
        eor         r16, r17                ; flip the LED bit
        ;
        sts         led_on, r1              ; say it is off
        sbrs        r16, LED_PIN            ; skip if it is on
        rjmp        2f
        ldi         r17, 1                  ; get a 1
        sts         led_on, r17             ; say it is on
2:
        out         LED_PORT, r16           ; set the LED pin
        sts         Task2countL, r1         ; and clear the counter
        sts         Task2countH, r1
1:
        ret

Notice the code we need to check this 16 bit counter against the max defined value.

Buzzing the buzzer

The code needed to make the buzzer work is placed in task1 above the line labeled 2:

task1.S
1
2
3
4
5
            in      r16, BUZZ_PORT
            ldi     r17, (1<<BUZZ_PIN1)
            eor     r16, r17
            out     BUZZ_PORT, r16  
            ;

If you test the code at this point, the buzzer should sound and the LED should blink at about the right rate. Cool, but we wanted to cause the buzzer to sound when the light is on. So, we have a bit more work to do.

Basically, we need to let one task talk to another task. We need to have the LED task set a variable when the LED goes on, and tell the buzzer task to shut things down when the LED is off. Hmmm - let’s see if we can get this working!

First, we need to set up the variable. We will define it in Task2 and let Task1 see it.

task2.S
1
2
3
4
        .global     led_on

        .section    .data
led_on:             .byte       0

And in Task1:

task1.S
1
            .extern     led_on

Now, we need to set this variable when the LED is on. Unfortunately, since we are just toggling the bit, we do not know if the LED is on or off, so we will just test the right bit. If it is a 1, the LED is on!

task2.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
        in          r16, LED_PORT           ; toggle LED pin
        ldi         r17, (1<<LED_PIN)
        eor         r16, r17                ; flip the LED bit
        ;
        sts         led_on, r1              ; say it is off
        sbrs        r16, LED_PIN            ; skip if it is on
        rjmp        2f
        ldi         r17, 1                  ; get a 1
        sts         led_on, r17             ; say it is on
2:
        out         LED_PORT, r16           ; set the LED pin
        sts         Task2countL, r1         ; and clear the counter
        sts         Task2countH, r1
1:
        ret

With this change, we have the led_on variable set if the LED is on. Now, we can use this variable to decide if we should toggle the buzzer pins. (Skipping this will cause the buzzer to shut off.)

task1.S
1
2
3
4
5
6
7
8
9
            cpse    r16, r17
            rjmp    1f
            ;
            lds     r16, led_on             ; see if the LED is on
            cpi     r16, 1                  ;is it on
            brne    1f
            cpi     r16, 1                  ; test it
            brne    2f                      ; not on - no buzz
            ;

Try this change! With any luck, the buzzer will only sound when the LED is on!. Cool, multi-tasking working in a neat way!

What about that last task?

We had one last task to handle. This one is an example of a task that could be expanded to fire off a signal to a servo. If you look into the documentation for servos, you discover that they like to have a pulse sent their way about 50 times a second. (Actually, that number is soft, they work well with a range of values.)

Let’s set up a simple task and see if we can have is generate a short pulse we can look at on the oscilloscope. This will not be enough code to run the servo, but it should give you ideas.

Here is the task we will use:

task3.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "config.inc"

; Task 3 - Servo task - service at 50Hz

            .global     Task3
            .global     Task3_init

.equ        TASK3_MAX,  160

            .section    .data
Task3countH:
            .byte       0
Task3countL:
            .byte       0

            .section    .text

Task3_init:
            sts         Task3countH, r1         ; clear counter
            sts         Task3countL, r1
            sbi         PULSE_DIR, PULSE_PIN
            ret

Task3:
            lds         r26, Task3countL        ; load counter into X
            lds         r27, Task3countH
            adiw        r26, 1                  ; increment counter
            sts         Task3countL, r26        ; save it back
            sts         Task3countH, r27 
            ;
            cpi         r26, TASK3_MAX & 0xff   ; Compare low byte
            ldi         r16, TASK3_MAX >> 8
            cpc         r27, r16                ; Compare high byte
            brne        1f                      ; branch if not equal
            ;
            in          r16, PULSE_PORT         ; toggle LED pin
            ldi         r17, (1<<PULSE_PIN)
            eor         r16, r17                ; flip the LED bit
            ;
            out         PULSE_PORT, r16         ; set the LED pin
            sts         Task3countL, r1         ; and clear the counter
            sts         Task3countH, r1
1:
            ret

You should be able to add this task to your code. When I ran the resulting program, this is what I saw on the output pin we set up for Task3:

../_images/Pulse.png

That is one call every 20ms, which works out to our target of 50Hz. Looks like we have a program we can work with!

Testing your code

As your code gets more complex, you will soon wish you had better tools to help debug your code. Fortunately, there are many such tools available, some free, some not free. Here are some references to tools we might use:

Further Reading

In case you are interested in seeing a more sophisticated way to set up a multi-tasking kernel on the Arduino, here is a paper to get you started: