Friday, April 30, 2010

PWM generation in BeagleBoard

My primary interest to make a robot centred around BeagleBoard is incomplete without generating PWMs to control DC motors and Servos. Since Beagleboard has all what it takes to make a robot, I am not going to be bothered to have a microcontroller to aid Beagleboard.

My requirement is like this - say an image processing algorithm is running on Beagleboard, and at end of every iteration I want to update the speed of motors by changing the PWM values. In the meantime when the Vision algorithm is running, the board should keep on generating the PWM until it's value is changed.

It started with searching the community for info and this post is quite helpful. this one also is quite helpful. When I first tried all sort of permutations and combinations according to these information, it just didn't work. The file in which we are supposed to write the values like direction, value would give no write excess error. This problem was due to drivers not loaded i.e. this problem got solved when we compiled a new kernel with drivers of TPS320* which handles these IO functionalities.

Now the basic checks like making a pin high or low starts to work. To make a code to suits the above requirement, we went ahead with multi-threading. one thing to just remind here is that, in Linux these port operations are done through writing and reading appropriate files.

So here are the two files new_main.c and waveform.c

new_main.c


#include < stdio.h >
#include < string.h >
#include < stdlib.h >
#include < pthread.h >
#include < time.h >
#include < fcntl.h >
#include < sys types.h="" >
#include < sys stat.h="" >
#include"waveform.c"

time_info pwm_times;
time_info pwm_times2;

int main()
{
    int i=0,j,k,l;

    pthread_t threadid;
    pthread_t threadid2;   

    pwm_times.time_h=0;
    pwm_times.time_l=0;

    pwm_times2.time_h=0;
    pwm_times2.time_l=0;

    pthread_create(&threadid,NULL,pwm,(void*)&pwm_times);
    pthread_create(&threadid2,NULL,pwm2,(void*)&pwm_times2);
    while(1)   
    {
        for(l=0;l<1000;l++)
        {

          // Your main code runs here

            for(j=0;j<1000;j++)
            {
                for(i=0;i<1000;i++)
                {}
            }
           // Your code ends here

           // Update values of PWM
            pwm_times.time_h=(300000+l*1000);
            pwm_times.time_l=(300000-l*1000);

            pwm_times2.time_h=(600000-l*1000);
            pwm_times2.time_l=(600000+l*1000);
        }
    }
}


In the above code, in new_code.c the main algorithm will be running in place of the three nested for loops.
We have created two threads one for each pwm, and created a structure having parameters time_h and time_l denoting high time and low time. Whenever we want to change the value of PWM we change the value of these two parameters.


Once each iteration of the algorithm completes, we change the value of time_h and time_l for both the PWMs.
   

waveform.c

#include < stdio.h >
#include < pthread.h >
#include < time.h >
#include < fcntl.h >
#include < sys/types.h >
#include < sys/stat.h >

typedef struct
{
    long int time_h;
    long int time_l;
}time_info;


void *pwm(void* pwm_times)
{
    FILE *fs;
    int i=0;
    char ch;
    struct timespec pwm_delay;   
    time_info *pwm_ptr=(time_info*)pwm_times;

    fs=fopen("/sys/class/gpio/gpio168/value","w");
    while(1)   
    {   
        fputc('0',fs);
        fgetc(fs);
        pwm_delay.tv_sec=0;
        pwm_delay.tv_nsec=pwm_ptr->time_l;
        nanosleep(&pwm_delay,NULL);   
           
        fputc('1',fs);
        fgetc(fs);     
        pwm_delay.tv_sec=0;
        pwm_delay.tv_nsec=pwm_ptr->time_h;
        nanosleep(&pwm_delay,NULL);     
    }
    fclose(fs);
}

void *pwm2(void* pwm_times2)
{
    FILE *fs;
    int i=0;
    char ch;
    struct timespec pwm_delay;   
    time_info *pwm_ptr=(time_info*)pwm_times2;

    fs=fopen("/sys/class/gpio/gpio183/value","w");
    while(1)   
    {   
        fputc('0',fs);
        fgetc(fs);
        pwm_delay.tv_sec=0;
        pwm_delay.tv_nsec=pwm_ptr->time_l;
        nanosleep(&pwm_delay,NULL);   
           
        fputc('1',fs);
        fgetc(fs);     
        pwm_delay.tv_sec=0;
        pwm_delay.tv_nsec=pwm_ptr->time_h;
        nanosleep(&pwm_delay,NULL);     
    }
    fclose(fs);
}


in waveform.c we perform the toggling of pin ON-OFF manually in the code by writing to the file named value. [Beware that before even executing the program you have to do some initialisation manually which have not been done in code, these are mentioned at the end]

Since these are multithreaded files the compilation is doen by
gcc new_main.c -lpthread

Initialisation needed Before Executing
You need to do the following things:-
1. Create directory for your IO pin/s
2. Set its direction (as output in our case)

For 1st go to the directory /sys/class/gpio . In here issue the command
echo "168" > export

this will create the directory gpio168, giving you access to GPIO168 which is pin number 24, according to RevC3. Similarly do the same for GPIO183 which is pin number 23.

Now for step 2, go to the directory gpio168 and issue command
echo "out" > direction

this declares the GPIO168 as output pin.
Do the same with GPIO183.

Now your pin no 23 and 24 are ready to generate the PWM according to the code. The waveform that you should get is dutycycle changing from 50% to 100% in one pin and from 50% to 0% in other pin.

Now once the PWM are up, we need an interfacing circuit to connect there 1.8V waveform to motor drivers like L293D. So here we can use Optoisolator like MCT2 etc.

Obviously there might be other better methods to use the in-built PWM functionality, but I am still not conversant with them.

This Post possibly will be the last post on Beagleboard and related Tech. topics, since a different line of work awaits me.