Timers and PWM on SparkFun Pro Micro

So this will be a short one. Basically the goal here is use the TIMER to make a PWM.  It builds on the post.  The main thing that I changed now is that instead of having the ISR I let the hardware do all the timing work. The website that really helped me was a post by Vicente and as always the documentation for ATMega32U4.

Remember the Pro Micro and the Leonards use the same processor and all I had to do was look at the hardware schematics to see which pin the processor was outputting to (schematic file found here).  Since most of the information is found in Vicente’s post I don’t want re post all the images he found. I will however walk through my code.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>




//setting up TCCR1A control register
unsigned char COM1A1_val = 0b10000000;
unsigned char COM1A0_val = 0b01000000;
unsigned char COM1B1_val = 0b00100000;
unsigned char COM1B0_val = 0b00010000;
unsigned char COM1C1_val = 0b00001000;
unsigned char COM1C0_val = 0b00000100;
unsigned char WGM11_val = 0b00000010;
unsigned char WGM10_val = 0b00000001;

//setting up TCCR1B control register
unsigned char ICNC1_val = 0b10000000;
unsigned char ICES1_val = 0b01000000;
unsigned char WGM13_val = 0b00010000;
unsigned char WGM12_val = 0b00001000;
unsigned char CS12_val = 0b00000100;
unsigned char CS11_val = 0b00000010;
unsigned char CS10_val = 0b00000001;







//Register variables

unsigned char OCR1AL_reg = 0x88; //output compare low 8 bits for Counter 1
unsigned char OCR1AH_reg = 0x89; //output compare high 8 bits for Counter 1

unsigned char TCCR1B_reg = 0x81; //Timer/Counter0 Control Register B 
unsigned char TCCR1A_reg = 0x80; //Timer/Counter0 Control Register A

unsigned char TIMSK1_reg = 0x6F; //Timer/Counter0 Interrupt Mask Register

unsigned char TIFR1_reg = 0x36; //Timer/Counter0 Interrupt Flag Register

unsigned char TCNT1H_reg = 0x85; //Timer/Counter0 Interrupt Flag Register
unsigned char TCNT1L_reg = 0x84; //Timer/Counter0 Interrupt Flag Register

//setting up PB5
unsigned char DDRB_reg = 0x24; //Sets up the DDR for Bank 5
unsigned char PORTB_reg = 0x25; //sets up port B
unsigned char PINB_reg = 0x23; //to be able to read PINB5

unsigned char SREG_reg = 0x5F; //Status register used for disabling and enabling global interrupts

int stopeverything = 0;

void setup()
{

Serial.begin(9600); //This pipes to the serial monitor

//setting up PB5
 (*(volatile unsigned char *)(DDRB_reg)) = 0b00100000;
 (*(volatile unsigned char *)(PORTB_reg)) = 0b00100000;

//resetting the Timer/Counter1
 (*(volatile unsigned char*)(TCNT1H_reg)) = 0;
 (*(volatile unsigned char*)(TCNT1L_reg)) = 0; 
 
 //disabling all global interrupts
 (*(volatile unsigned char *)(SREG_reg)) = 0b00000000;

//defining prescalar
 //(*(volatile unsigned char *)(TCCR1B_reg)) = ICES1_val|WGM13_val|WGM12_val|CS12_val|CS10_val;//0b01001101; //the last 3 bits set to 101 will make the clock scalar 1024;
 //(*(volatile unsigned char *)(TCCR1B_reg)) = ICES1_val|CS12_val;//0b01001101; //the last 3 bits set to 101 will make the clock scalar 1;
 (*(volatile unsigned char *)(TCCR1B_reg)) = 0;
 (*(volatile unsigned char *)(TCCR1B_reg)) = CS11_val;

//setting up PWM mode
 (*(volatile unsigned char *)(TCCR1A_reg)) = 0; //this is for waveform generation and CTC setting up mode;
 (*(volatile unsigned char *)(TCCR1A_reg)) = COM1A1_val|WGM10_val; //this is for waveform generation and CTC setting up mode;

//defining the output compare value which is calculated to be 3906 assuming 8MHz clock speed: OCRNA = (fClk_I/O)/(2*N*fOCRNA) where N is the prescalar fOCRNA is the compare value and fClk_I/O is the clock speed
 //the value calculated for fOCRNA to be 1Hz is ~3096
 (*(volatile unsigned char *)(OCR1AH_reg)) = 0b00001111; //writing to high first which will write to a temp register first
 (*(volatile unsigned char *)(OCR1AL_reg)) = 0b01000010; //writing to low will write both high and low in the same clock cycle

//using output value of 300
 
 //(*(volatile unsigned char *)(OCR1AH_reg)) = 0b00000001; //writing to high first which will write to a temp register first
 //(*(volatile unsigned char *)(OCR1AL_reg)) = 0b00000100; //writing to low will write both high and low in the same clock cycle

//using compare value of 30
 
 //(*(volatile unsigned char *)(OCR1AH_reg)) = 0b00000000; //writing to high first which will write to a temp register first
 //(*(volatile unsigned char *)(OCR1AL_reg)) = 0b00000110; //writing to low will write both high and low in the same clock cycle
 
 //using compare value of 1
 
 //(*(volatile unsigned char *)(OCR1AH_reg)) = 0b00000000; //writing to high first which will write to a temp register first
 //(*(volatile unsigned char *)(OCR1AL_reg)) = 0b00000001; //writing to low will write both high and low in the same clock cycle




(*(volatile unsigned char *)(TIMSK1_reg)) = 0;//0b00100010; //writing so that output compare A is set up




//enable global interrupts
 (*(volatile unsigned char *)(SREG_reg)) = 0b10000000;







}

/*ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
 //(*(volatile unsigned char *)(PORTB_reg)) = (*(volatile unsigned char *)(PORTB_reg)) ^ (1<<5);
 Serial.println("interrupt!");
 stopeverything=1;
}*/




void loop()
{
 //Serial.println(
 // (*(volatile unsigned char*)TCNT1H_reg << 8) | (*(volatile unsigned char*)TCNT1L_reg),BIN);
 //Serial.println("");
 //if(stopeverything==1){
 //Serial.println("Coming from interrupt!!");
 //delay(2000);
 //stopeverything=0;
 //}
}

 

Let’s start from the top:

//Register variables

unsigned char OCR1AL_reg = 0x88; //output compare low 8 bits for Counter 1
unsigned char OCR1AH_reg = 0x89; //output compare high 8 bits for Counter 1

unsigned char TCCR1B_reg = 0x81; //Timer/Counter0 Control Register B 
unsigned char TCCR1A_reg = 0x80; //Timer/Counter0 Control Register A

unsigned char TIMSK1_reg = 0x6F; //Timer/Counter0 Interrupt Mask Register

unsigned char TIFR1_reg = 0x36; //Timer/Counter0 Interrupt Flag Register

unsigned char TCNT1H_reg = 0x85; //Timer/Counter0 Interrupt Flag Register
unsigned char TCNT1L_reg = 0x84; //Timer/Counter0 Interrupt Flag Register

//setting up PB5
unsigned char DDRB_reg = 0x24; //Sets up the DDR for Bank 5
unsigned char PORTB_reg = 0x25; //sets up port B
unsigned char PINB_reg = 0x23; //to be able to read PINB5

unsigned char SREG_reg = 0x5F; //Status register used for disabling and enabling global interrupts

int stopeverything = 0;

 

My first step was declaring assigning values for the different bits but before we do I want to skip ahead and I assigned all the address as my own variables so I could use them later on. The stopeverything variables does nothing, it is a hold over from my last post.  So I would just ignore it.  All I am doing is asigning all the address and placing them into variables so that I can understand what is going on. There are 2 control registers TCCR1B and TCCR1A which are used for setting the clock prescalar and if you want a PWM output or not. All of this information I found on this page 408-411 on ATMega32U4 datasheet.

Then I assigned variables for each bit that was necessary in order for me to make it easier to work with them instead of trying to count out bits like I have been doing previously.

//setting up TCCR1A control register
unsigned char COM1A1_val = 0b10000000;
unsigned char COM1A0_val = 0b01000000;
unsigned char COM1B1_val = 0b00100000;
unsigned char COM1B0_val = 0b00010000;
unsigned char COM1C1_val = 0b00001000;
unsigned char COM1C0_val = 0b00000100;
unsigned char WGM11_val = 0b00000010;
unsigned char WGM10_val = 0b00000001;

//setting up TCCR1B control register
unsigned char ICNC1_val = 0b10000000;
unsigned char ICES1_val = 0b01000000;
unsigned char WGM13_val = 0b00010000;
unsigned char WGM12_val = 0b00001000;
unsigned char CS12_val = 0b00000100;
unsigned char CS11_val = 0b00000010;
unsigned char CS10_val = 0b00000001;

The information can be found on p.129-133 in the data sheet and you can see I am just setting one bit, but the point of it is so that I can “OR” them together using the “|”.  This allows me to do things like WGM13_val|WGM12_val which is does this operation of 0b00010000 | 0b00001000 = 0b00011000 and this allows me to to not use binary but use the variable name instead and that is much easier to handle when setting up the registers and prescalars.

As  you can see that the bits are called from this table and its too hard to keep track of the bits.  The images are all from Vicente’s website.

The next step is very similar to last time but the big difference is that now I have to set up PB5 to output so that the timer can toggle it up and down.

//setting up PB5
 (*(volatile unsigned char *)(DDRB_reg)) = 0b00100000;
 (*(volatile unsigned char *)(PORTB_reg)) = 0b00100000;

//resetting the Timer/Counter1
 (*(volatile unsigned char*)(TCNT1H_reg)) = 0;
 (*(volatile unsigned char*)(TCNT1L_reg)) = 0; 
 
 //disabling all global interrupts
 (*(volatile unsigned char *)(SREG_reg)) = 0b00000000;

//defining prescalar
 //(*(volatile unsigned char *)(TCCR1B_reg)) = ICES1_val|WGM13_val|WGM12_val|CS12_val|CS10_val;//0b01001101; //the last 3 bits set to 101 will make the clock scalar 1024;
 //(*(volatile unsigned char *)(TCCR1B_reg)) = ICES1_val|CS12_val;//0b01001101; //the last 3 bits set to 101 will make the clock scalar 1;
 (*(volatile unsigned char *)(TCCR1B_reg)) = 0;
 (*(volatile unsigned char *)(TCCR1B_reg)) = CS11_val;

//setting up PWM mode
 (*(volatile unsigned char *)(TCCR1A_reg)) = 0; //this is for waveform generation and CTC setting up mode;
 (*(volatile unsigned char *)(TCCR1A_reg)) = COM1A1_val|WGM10_val; 
//this is for waveform generation and CTC setting up mode;
 (*(volatile unsigned char *)(OCR1AH_reg)) = 0b00001111; //writing to high first which will write to a temp register first
 (*(volatile unsigned char *)(OCR1AL_reg)) = 0b01000010; //writing to low will write both high and low in the same clock cycle

(*(volatile unsigned char *)(TIMSK1_reg)) = 0;//0b00100010; //writing so that output compare A is set up

//enable global interrupts
 (*(volatile unsigned char *)(SREG_reg)) = 0b10000000;

And you can see that first I set PB5 to output by first setting the DDR and then the PORTB5 pin.  I reset the counters to 0, then remove all global interrupts so I don’t get interrupts while the code is trying to execute.  Set up a prescalar and you will notice I first set both TCCR1B and TCCR1A to 0 because I ran into issues because of the “|” operation it kept saving all the bits from last time causing nothing to happen.  I then set the prescalar to be 8 (CS11_val = CSn1 from table) and set the timer to compare the A register and used the WGM10_val to set a version of the PWM.  The next step was to all ISRs by setting the TIMSK1_reg to 0.  Then I enabled all the interrupts and then voila I had a PWM when I scoped Pin 9 on the ProMicro.  As I changed the value of OCR1A I change the PWM duty cycle and with prescalar I change the duration.

As always here are my useful links:

http://www.catonmat.net/blog/low-level-bit-hacks-you-absolutely-must-know/

https://stackoverflow.com/questions/14526584/what-does-the-xor-operator-do

http://r6500.blogspot.com/2014/12/fast-pwm-on-arduino-leonardo.html

Please feel free to ask me and stuff can be found on my github!

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s