Search Microcontrollers

Friday, March 14, 2014

2.4W 808nm Infrared Laser

I bought on ebay a laser diode because I need to run some experiments on plastic materials.
Being quite new to the game, I did some research and tests, so I am documenting here what I understood as it might be useful for others.

Laser diodes are normally quite easy to use and, for low power and relatively high frequencies, they offer a good alternative to CO2  laser tubes.

For the application I have in mind I will need to use a 60W CO2 tube, but since I am not sure the concept is sound, before investing in that kind of material I wanted to proof the concept with cheaper and easier to use lasers.




There are two things that actually affect my test :
Obviously the power, for a cheap laser (about 300$ shipped, with driver, power supply and good protection glasses) there is no way you can compete witha 60W tube.
And then the frequency.

Non pigmented plastic is quite transparent to visible light, while it absorbs pretty well in the deep infrared range.
The diode I got is at 808nm which is a wavelength sitting in the "near infrared", it is absorbed by plastic materials only if they are colored, possibly black.

The deep infrared CO2 lasers work at about 10.000nm,  which means they have a wavelength which is more than 10x the 808nm ones.
That makes a lot of difference.

Still, if I use black targets -which is kind of ok for my test- I should be able to achieve something.



Operating the laser itself is quite easy, just plug in the power supply connected to the driver (this one works at 12V) and it basically fires out right away.


WARNING : even few milliWatts can easily damage your eyes permanently, wearing proper protection goggles is mandatory.
No, it is not enough to make sure you are not firing the beam directly to your eyes, as the beam can bounce against any target and being scattered to your eyes, it just takes a small portion of the beam sometimes to be a big issue.
Also, make sure the goggles you use are specific for the frequency you are using :  404nm protection goggles would not help much if your laser is at 808nm.

Particularly, if you are using near infrared diodes, like mine, an additional danger comes from the fact that the beam is not easy to spot, since the frequency is almost invisible for the human eye (cameras can detect it way better).

That said, laser diodes can come with some different features :

Optics : depending on the power and frequency the lenses can be plastic or glass. For high power (more than 0.5 -1W) you normally have glass lenses, like mine.
Normally you should have some kind of regulation on the lenses, to adjust the focus, which is really important to properly use your laser.

Driver : Technically you apply the correct voltage to the diode and it emits the beam, pretty simple.
However a " driver"  circuit is normally used to deliver a constant current to the diode (you can build yourself one of these circuits, there are plenty of schematics on the web normally based on the LM317 - just google the topic-) else your diode might have a short life span.

Power Control : The laser diode is an LED, like any LED it is possible to dim it using a PWM signal. For this reason many lasers come with two additional wires connected to the driver, they are labeled "TTL" and are supposed to be the PWM signal input.
I believe there is a pull-up resistor at the end of those wires (at least in MY driver) because if you do not provide any signal, then the laser is on 100%, if you shortcut them (grounding the signal, I take) the laser goes at 0% (off).
The ttl signal in my case is 5V, 3.3V compatible, so it can be driven by any MCU directly, the suggested frequency says >20KHz.
I completed my PWM setup (my stellaris pwm library here), but did not test it on the laser yet.

Cooling : Laser diodes tend to get hot and heat generates a lot of issues.
For starters it might actually damage your diode, but another  effect is that the emitted frequency starts drifting off, presumably reducing the emitted power as well.
Typically you really want to run your laser at about ambient temperature, not exceeding too much 30C and if so not for long time.
Cooling becomes a real issue when you are planning to use the laser for long periods.
My laser is supposed to be able to operate at 100% for 30 minutes and then rest to cool down for 5 minutes, it just has a small fan.
I am staying on the safe side and operate it continuously for  not more than 5 mins at the moment, but I ordered a few TECs (peltiers) which I plan to use to cool down the assembly.

Now, that said, the first thing I had to do, which proved to be a bit more complex than I expected, is to set the proper focus.

As you cannot see the beam when you are wearing protection goggles (and you DO NOT want to remove them), focusing the beam by looking at the target is pretty much impossible, maybe you can do something using a camera.

The other thing is that if you can chose the distance it is easier to set the lens focus and then move the target  towards  or away the laser until you get the desired focus.

But how do you detect the "desired focus" ?

I placed my laser on a table, taped down a reference with a mark every 5 cm, then moved some target objects on the reference.
The closer you are to the focus, the fastest the target object starts burning, I used a piece of wood for this rough measurement.
Once you get roughly the range in which you believe your focal distance is (between 40 and 55 cm for me), you can assume the correct value is somewhat in the middle.
I then took a black object and moving it I found that the best results were at 47cm, to prove that I let the beam punch a hole in my target, the smallest hole will be the one where the focus distance is optimal.



You can see the difference between the holes on the left and on the right, clearly the left one was way closer to the optimal distance.

At this point I knew roughly the range I would need, so I started building a stand that would allow me to run my tests in a more comfortable and repeatable way.
I came out with this :


It still allows me to move the diode up&down, is uses a glass work area where I can easily place my target objects and holds in place driver, power supply (and in the future the stellaris launchpad for the PWM)... granted, nothing fancy, but quite functional in the end.

One issue you may want to consider is how to attach the diode part as you may want to avoid something that prevents a good thermal dispersion (i.e. avoid to attach it to a wooden support that blocks the heat dispersion on one side).

Now I need to finish the interface program for the stellaris, it is written in Java and communicates via UART with the stellaris, the host computer will be an Olimex A20 running Debian Linux, that will give me a graphical interface to control the power, temperature drive eventual additional fans and peltier cells.

I am planning to record a video to demonstrate the rig, hopefully in few days.

Update : here's  the video


Sunday, March 2, 2014

Stellaris Launchpad - PWM Library

I played a bit with PWM, which I actually need to drive a laser diode (2.4W 808nm) at the moment (will dig into this probably more in future posts).
Once you get the basics PWM is not difficult with the LM4F, however I found way easier for my application to create a minimalist library to deal with PWM signals, each time I look at it I keep repeating to myself that it could be better... but the way it is now already fits my needs (and maybe yours, but feel free to improve it and let me know :) ).

One thing I wanted was to have a simple interface that would allow me to play with multiple PWM generators at the same time, 8 seemed a fair amount, so I pre-allocated an array of 8, but it is fairly easy to expand the array.
Meh, some range checking would help maybe, but the Stellaris does not run on MS Windows, so it does pretty much what you tell it to do, just be careful not to add more than 8 pwm or at least remember to expand the array and you should be fine.

That said, each pwm generator is actually coming from a timer, we can use 16 bit or 32 bit timers and I figured that a 16 bit timer would do (did not try yet, but I think I found how to use it in 32 bit mode).
Still I did not implement pre-scaling which could be beneficial if you have to run with very low frequencies (<2Hz).

Here's  the library (don' t expect anything fancy, it's  just easy to use and actually works)


#include <inc/hw_memmap.h>
#include <inc/hw_types.h>
#include <driverlib/gpio.h>
#include <driverlib/sysctl.h>
#include <driverlib/uart.h>
#include <inc/hw_timer.h>
#include <driverlib/timer.h>
#include <driverlib/pin_map.h>

int pwm_count = 0;
int pwmtimers[8][4];

/**
 * each timer can send the signal out to 2 different gpios
 * 0 means to set it to the lowest of the two 1 to the highest
 *
 * TIMER    PIN(gpio=0) PIN(1)
 * T0A         PB6 PF0
 * T0B PB7 PF1
 * T1A PB4     PF2
 * T1B          PB5     PF3
 * T2A          PB0     PF4
 * T2B          PB1     PB1
 * T3A          PB2     PB2
 * T3B          PB3     PB3
 *
 *
 *
 */
int addPWM(int pwm_timer,int subtimer, int gpio) {
pwmtimers[pwm_count][0] = pwm_timer;
pwmtimers[pwm_count][1] = subtimer;
if (gpio == 0) {
if ((int)subtimer == (int)TIMER_A) {
switch (pwm_timer) {
case TIMER0_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_6;
pwmtimers[pwm_count][3] = GPIO_PB6_T0CCP0;
break;
case TIMER1_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_4;
pwmtimers[pwm_count][3] = GPIO_PB4_T1CCP0;
break;
case TIMER2_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_0;
pwmtimers[pwm_count][3] = GPIO_PB0_T2CCP0;
break;
case TIMER3_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_2;
pwmtimers[pwm_count][3] = GPIO_PB2_T3CCP0;
break;
}
} else {
switch (pwm_timer) {
case TIMER0_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_7;
pwmtimers[pwm_count][3] = GPIO_PB7_T0CCP1;
break;
case TIMER1_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_5;
pwmtimers[pwm_count][3] = GPIO_PB5_T1CCP1;
break;
case TIMER2_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_1;
pwmtimers[pwm_count][3] = GPIO_PB1_T2CCP1;
break;
case TIMER3_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_3;
pwmtimers[pwm_count][3] = GPIO_PB3_T3CCP1;
break;
}
}
} else if (subtimer == TIMER_A) {
switch (pwm_timer) {
case TIMER0_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_0 | 0x100; // port F
pwmtimers[pwm_count][3] = GPIO_PF0_T0CCP0;
break;
case TIMER1_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_2 | 0x100;
pwmtimers[pwm_count][3] = GPIO_PF2_T1CCP0;
break;
case TIMER2_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_4 | 0x100;
pwmtimers[pwm_count][3] = GPIO_PF4_T2CCP0;
break;
case TIMER3_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_2;
pwmtimers[pwm_count][3] = GPIO_PB2_T3CCP0;
break;
}
} else { // timer B
switch (pwm_timer) {
case TIMER0_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_1 | 0x100;
pwmtimers[pwm_count][3] = GPIO_PF1_T0CCP1;
break;
case TIMER1_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_3 | 0x100;
pwmtimers[pwm_count][3] = GPIO_PF3_T1CCP1;
break;
case TIMER2_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_1;
pwmtimers[pwm_count][3] = GPIO_PB1_T2CCP1;
break;
case TIMER3_BASE:
pwmtimers[pwm_count][2] = GPIO_PIN_3;
pwmtimers[pwm_count][3] = GPIO_PB3_T3CCP1;
break;
}
}
return pwm_count++;
}

/**
 *
 */
void pwmStop(int pidx) {
TimerDisable(pwmtimers[pidx][0], pwmtimers[pidx][1]);       
 //enables timerx_y
}

/**
 *
 */
void pwmStart(int pidx) {
TimerEnable(pwmtimers[pidx][0], pwmtimers[pidx][1]);        
//enables timerx_y
}

/**
 * Frequency in HZ and Duty cycle in %
 * example : pwm_setFreqDC(0,25000,0.3)  sets 25KHz with 30% duty cycle
 */
void pwmSetFreqDC(int idx, int freq,double dutyCycle) {
int period = (SysCtlClockGet() / freq);
int pidx = idx;
TimerLoadSet(pwmtimers[pidx][0], 
                     pwmtimers[pidx][1], period - 1); // sets pwm period
TimerMatchSet(pwmtimers[pidx][0], 
                     pwmtimers[pidx][1], period *dutyCycle); // sets duty cycle
}



/**
 *
 */
void pwmConfigure(int idx) {
int timerp = 0;
// ENABLE gpio port
int gpioPort;
volatile int pidx = idx;

if ((pwmtimers[pidx][2] & 0x100) > 0) { // pidx changes value here ????

SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
gpioPort = GPIO_PORTF_BASE;
} else {

SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
gpioPort = GPIO_PORTB_BASE;
}

// configure gpio pin
GPIOPinTypeGPIOOutput(gpioPort, pwmtimers[pidx][2] & 0xff);
GPIOPadConfigSet(gpioPort, // identifies the GPIO PORT
pwmtimers[pidx][2] & 0xff,  // identifies the pin within the GPIO Port
GPIO_STRENGTH_8MA_SC, // sets the strength to 8mA with Slew Rate control
GPIO_PIN_TYPE_STD);   // sets the pad control type to push-pull

// clear output pin
GPIOPinWrite(gpioPort, pwmtimers[pidx][2] & 0xff, 0);

// setup timer
switch (pwmtimers[pidx][0]) {
case TIMER0_BASE:
timerp = SYSCTL_PERIPH_TIMER0;
break;
case TIMER1_BASE:
timerp = SYSCTL_PERIPH_TIMER1;
break;
case TIMER2_BASE:
timerp = SYSCTL_PERIPH_TIMER2;
break;
case TIMER3_BASE:
timerp = SYSCTL_PERIPH_TIMER3;
break;
}
SysCtlPeripheralEnable(timerp);  // enables timer x

GPIOPinConfigure(pwmtimers[pidx][3]);  // port x pin y set to TIMERz_k output
    GPIOPinTypeTimer(gpioPort, pwmtimers[pidx][2] & 0xff); // port x pin y set to timer type output, does not bind

// configure Timer
if (pwmtimers[pidx][1] == TIMER_A)
TimerConfigure(pwmtimers[pidx][0],
(TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_PWM));
else
TimerConfigure(pwmtimers[pidx][0],
(TIMER_CFG_SPLIT_PAIR | TIMER_CFG_B_PWM));
// 16 bit timer TIMERx_y set to PWM mode
TimerControlLevel(pwmtimers[pidx][0], pwmtimers[pidx][1], 1); 
        // invert the signal for TIMERx_y
}

---------------------

The addPWM(timer, subtimer, gpio)  function allows you to select which timer you want to use.
To see which timers are available, you should use the lm4f datahseet or the summary table I placed at the beginning of the code, it should be quite easy to read.
For each timer/subtimer there are up to two output options in regard to the gpio pin to be used.
I sorted them alphabetically, so if for a timerxy you have options being PB2 and PF4 then PB2 would be 0 and PF4 1 in the gpio parameter of the function.
If the timer has one single configurable output, then both 0 and 1 would produce the same result.

This function just fills in the pwmtimers array, does not do anything with the hardware.
Say you call addPWM 3 times, the first one would be index 0 in the array, the second one index 1 and so on.
There is no functionality to remove an element from that array (did not see the point).

Once all the basic parameters are set (by addPWM) it is time to configure the needed hardware.
Relevant timer peripheral must be enabled, associated gpio port should be enabled too, the selected pin should be configured as output, set in timer mode and eventually pad-configured with high current push-pull with slew rate control.
The timer should be configured in split mode / pwm, also the pwm signal should be inverted (see previous article for a detailed explanation of these steps).
It is not difficult, but honestly i' d rather not code this process each time I need to use a pwm, so the pwmConfigure(idx) function takes care of ALL of it by fetching the needed values from the pwmtimers array.
Not really elegant maybe, but quite effective. 

The idea here is that once you specified what a pwm signal should be, the next operation should just refer to the array index, like configure idx 0, set frequency and duty cycle for idx 0 , start idx 0 etc.

So, next in line comes the pwmSetFreqDC( idx,  freq, dutyCycle) which allows us to set the frequency (you need to pass the number of hertz, like 25000  if you want 25KHz, no range checking is done, it' s  up to you to pass a possible value) and the duty cycle as a fraction (i.e. 0.3 if you want 30%).

The you can start your signal with pwmStart(idx)

Let's see an example that drives the R,G and B components of the onboard LED.

---------------------

 /*
 * Stellaris LAUNCHPAD LM4F120H5QR
 * --->  PART_LM4F120H5QR  <---
 */

#include <inc/hw_memmap.h>
#include <inc/hw_types.h>
#include <driverlib/gpio.h>
#include <driverlib/sysctl.h>
#include <driverlib/uart.h>
#include <inc/hw_timer.h>
#include <driverlib/timer.h>
#include <driverlib/pin_map.h>
#include <utils/uartstdio.c>


void setClock()
{
 SysCtlClockSet(  SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL 
             | SYSCTL_XTAL_16MHZ  | SYSCTL_OSC_MAIN);
}


int main(void) {
 setClock();

 // Add PWM timers to the library array

 addPWM(TIMER0_BASE,TIMER_B,1); //0
 addPWM(TIMER1_BASE,TIMER_A,1); //1
 addPWM(TIMER1_BASE,TIMER_B,1); //2

 // Configure Them
 pwmConfigure(0);
 pwmConfigure(1);
 pwmConfigure(2);

 // Set Frequency and duty cycle
 pwmSetFreqDC(0,25000,0.9);
 pwmSetFreqDC(1,25000,0.7);
 pwmSetFreqDC(2,25000,0.3);

 // Start the signal
 pwmStart(0);
 pwmStart(1);
 pwmStart(2);

 while (1) {
    // do nothing, forever... looks like my dream life :)
  }
}

--------------

When I was debugging it, I really stumbled against a stupid detail which I am going to tell you now, so you may avoid to think you are stupid and you should not deal with these things (because I did, at some point).

As you can see I am using all 3 components of the led, to debug my library I tried to use one , then two.
I tried to configure but not start them... but each time all 3 components were active.
I thought that the timers were starting as soon as they were configured, possibly with the frequency and DC I used before.
Why, if I did not start the green signal, the green component is there?
Turns out the answer is way easier than you' d think.
No, there is no green pwm signal, but the green light is on and the reason (my DSO confirmed it, I would have discovered this before if I was not too lazy to pull it out and probe the pins) is that the output is a steady HIGH.
Why?
I configured the pwm signals to be inverted, so when the timers are off, you should expect a steady high output, if this bothers you, then change it to not inverted in the TimerControlLevel call ( set to 0 the last parameter) and calculate the complementary value for the dutycycle in  pwmSetFreqDC simply using (1-dutyCycle).
I tried and it worked (my DSO confirmed it, I love that instrument!).

I hope this simple library can be useful for you, if you are using  another model of the lm4f, you might need to add some code to the addPWM function in order to consider the timers available in your device, the rest should probably be ok.

If you have improvements, I'd be glad to see them.
My next step will be to add UART or I2C control to drive the signals, again nothing fancy, but this is what I need :)

Happy coding!