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.

8 comments:

  1. Hi. Please check out my project servodrive at www.github.com/tallakt/servodrive

    i am looking at the same task as you. My servo driver runa as a kernel module so that timing is better, you don't need to Think about multithreading, and it has a simple file based interface

    If you like what you see we could join forces wrt further development.

    ReplyDelete
  2. One other thing: my servos run fine With 5V supply and 1.8V pwm signal, no interface electronics necessary

    ReplyDelete
  3. Tallak, thank you very much for pointing out your approach.I was not aware of these things, I will try to use this methodology for my future work.

    ReplyDelete
  4. The post is very useful for beagleboard beginnners but we are using ubuntu 11.04 in our beagleboard . we want generate pwm , can we use the same procedure to acess the ports in ubuntu .

    ReplyDelete
  5. I have the same question of bhargav, but I use Ubuntu 11.10

    ReplyDelete
  6. @bhargav & Anonymous,

    The code above looks like clean POSIX to me. It should work on similarly on all Linux-es running the same kernel. If you have a different kernel, your response time might be different, but the effects of the kernel differences will likely be minimal compared to the other processes that you may have running in userspace.

    ReplyDelete
  7. This post was great helpful for me but I have some question.

    1. in the main.c program what is the meaning of these lines : pwm_times.time_h=(300000+1*1000);
    pwm_times.time_l=(300000-l*1000);

    2. could you give me a simple example for placing source in the 3 nested for loops. Because I couldn't figure out how can I modify my code for toggling the gpio on there.

    ReplyDelete
  8. time_h & time_l are the numerical values (of high time & low time )that you have to give to the pins . pwm_times is the data structure for these time period.

    You can make what ever nested loop you want, just that wherever you want to change the high time and low time , pass the change in value of time_h & time_l at that point.

    ReplyDelete