When you build robots you always have to control lots of motors or RC servos. If you use a micro controller as main computing device you are fine because these devices have plenty of PWM outputs to control motors. But when you switch to more powerful controllers like the Raspberry Pi or the BeagleBone Black you have a little problem because these only have two or three PWM outputs and you can't stick your motor drivers directly to them.
Then you usually use an additional motor controller board with a micro controller and connect it via serial or SPI to your Raspberry Pi or BeagleBone. Another option is to use a CPLD, connect it to the main CPU via SPI and generate the PWM outputs with this device. I built a board doing this and I will describe it here.
Hardware
Here are two photos of the board:
You can find the schematic attached to this blog post.
The board is plugged on top of the BeagleBone black. The schematic consists of the connector to the BeagleBone. Then the CPLD Altera Max II EPM1270 in a TQFP144 package. Every free pin of the BeagleBone is connected to the CPLD but only the SPI pins are used. There are four motor drivers TI DRV8800. Additionally there are outputs to control twelve RC servos and inputs/outputs for four ultrasonic distance sensors. Finally there are some op amps to connect odometer sensors.
Also there are power regulators on the board to generate the supply voltage for the BeagleBone Black and the RC servos.
CPLD code
The CPLD code was made with the Altera Quartus II software.
The concept is that you always send the full stack of registers with one SPI transmission. So you don't have to decode which register has to be changed. And so the code is quite simple and consists mainly of a big shifting register which parallelizes the data of the SPI port and sends them to the right function block.
There are function blocks which generate the PWM signal for the RC servos. And there are blocks which generate the signals for the DRV8800. There are always multiple identical copys of the same function blocks as the function are needed several times on the board.
The clock for the PWM is generated by a clock output of the BeagleBone and divided internally in the CPLD.
You can find the whole code on github: https://github.com/generationmake/bb-cpld
Software
The software on the BeagleBone is done in pure C and is quite simple.
There exists a struct that represents all the registers which are connected to the SPI block in the CPLD:
struct cpld_data{ char status; char servo0; char servo1; char servo2; char servo3; char servo4; char servo5; char servo6; char servo7; char servo8; char servo9; char servo10; char servo11; char mota_ctr; char mota_pwm; char motb_ctr; char motb_pwm; char motc_ctr; char motc_pwm; char motd_ctr; char motd_pwm; char led01; char led23; };
And then there are some function to initialize the SPI port and send the whole struct to the CPLD:
#include "beaglespi.h" int spi_send(struct cpld_data *x) { FILE *fp; if(!(fp=fopen("/dev/spidev1.0","w+"))) { printf("could not open spi dev\n"); return -1; } fwrite(x,sizeof(struct cpld_data),1,fp); fclose(fp); return 0; } int spi_send_rw(struct cpld_data *x, struct cpld_data *y) { int status; int mode; int fp; if(!(fp=open("/dev/spidev1.0",O_RDWR))) { printf("could not open spi dev!\n"); return -1; } status=ioctl(fp,SPI_IOC_RD_MODE, &mode); mode|=SPI_CPOL|SPI_CPHA; status=ioctl(fp,SPI_IOC_WR_MODE, &mode); xfer[0].tx_buf=(unsigned long)x; xfer[0].len=sizeof(struct cpld_data); xfer[0].rx_buf=(unsigned long)y; status=ioctl(fp,SPI_IOC_MESSAGE(1),xfer); close(fp); return status; } int cpld_data_init(struct cpld_data *a) { a->status=0; a->servo0=0; a->servo1=0; a->servo2=0; a->servo3=0; a->mota_ctr=0; a->mota_pwm=0; a->motb_ctr=0; a->motb_pwm=0; a->motc_ctr=0; a->motc_pwm=0; a->motd_ctr=0; a->motd_pwm=0; a->led01=0; a->led23=0; } int motA(int speed, struct cpld_data *a) { if(speed>=0) { a->mota_ctr = 0x03; a->mota_pwm = (char)speed; } else { a->mota_ctr = 0x02; a->mota_pwm = (char)-speed; } } int motB(int speed, struct cpld_data *a) { if(speed>=0) { a->motb_ctr = 0x03; a->motb_pwm = (char)speed; } else { a->motb_ctr = 0x02; a->motb_pwm = (char)-speed; } } int motC(int speed, struct cpld_data *a) { if(speed>=0) { a->motc_ctr = 0x03; a->motc_pwm = (char)speed; } else { a->motc_ctr = 0x02; a->motc_pwm = (char)-speed; } } int motD(int speed, struct cpld_data *a) { if(speed>=0) { a->motd_ctr = 0x03; a->motd_pwm = (char)speed; } else { a->motd_ctr = 0x02; a->motd_pwm = (char)-speed; } }
Manipulation of the outputs are always done in the same way: The function (for example to set the motor speed) always reads and changes the values in the struct. After that the whole struct is transmitted via SPI. And after a full transmission to the CPLD the internal registers in the CPLD are updated.
Video
Here is a video of the robot: