DC Motor Controller Board
|Author:||Sebastien Lelong, The SirBot Project, 2005-2008|
|$Revision: 600 $||Initial creation|
The DC motor controller board for RC tank is a daughter board. It is used to control two DC motors, acting on existing motor H-Brigdes. Such H-Bridges can be found in RC toys (for instance in RC tanks), this board should easily be adapted to control any existing H-Bridges.
This board handles both speed control, using PWM, and speed monitoring, analyzing back-emf produced by motors. Every parameters (speed, delays, ADC, ...) can be configured.
This board includes a 16F88 microcontroller. It can be controlled using a serial link, or using a I2C bus.
|Warning:||This board has been designed to be integrated with existing H-Bridges found on a RC tank. It should handle any other existing H-Bridges, but be sure voltage produced by 16F88 outputs is compatible with the logic input pins of the H-Bridges, or you may damage them (or the board). You've been warned !!!|
|Warning:||This board uses ADC (Analog-to-Digital-Conversion) to evaluate motor's speed. Voltages produced by motors are oftenly far greater than those ADC can handle. There are voltage dividers on this board which keep those in correct range. These voltage dividers are designed so voltage produced by motors can be up to max 12V. If your DC motors produce more than 12V, you'll need to adjust voltage dividers' values or you will damage this board. You've been warned !!!|
The board is both an actuator and sensor: it controls and monitors motors' speed. This section describes how speed control and monitoring is done, and which parameters are used to configure the whole thing. Since the board can handle two dc motors, you can find those parameters in jal libraries, with "_0" and "_1" suffices. The next section describes how to get and set those parameters through a serial or i2c link.
|Note:||Some words about implementation... i2c bus is handled with interrupts, while serial is handled using a "forever loop", polling for potential received char. In following sections, the *main loop* refers to one step of this "forever loop". Depending on Xtal or oscillators, depending on several parameter's value, the time spent to achieve one step in this main loop may vary a lot. This is not a constant.|
Motor's speed is done using PWM. Note PWM is done manually, not using built-in PWM pins, since 16F88 only have one PWM (having at least 2 PWM pins require a "big" chip...).
PWM is implemented using a counter and defining a point under which the PWM is active, and over which it's inactive. There are two main parameters:
- max_pwm specifies the maximum value the counter can have. It should be left to its highest value, that is 255, to have a nice resolution
- active_pwm is the point defining if PWW is active or not.
To control the speed, both parameters can be used. To shut down motors, active_pwm must be set to 0, for maximum speed, it must equals max_pwm...
Monitoring is done measuring the back-emf produced by the DC motor. There are several sources on Internet, and you may want to have a look at my own experiment (which reference those sources).
|Note:||Current version of this board only monitors back-emf, and does not adjust speed to have a constant. In other words, for now, there's no control loop, such as PID controller. (but it may be available soon...)|
Using ADC, 16F88 can measure the voltage produced by motors, thus evaluates their speed. As you may guess, there's a lot of noise coming from motors, back-emf is thus very noisy. That's why an average back-emf is computed. Actually, that's a cumulative back-emf average, where each new measure is incorporated to the current mean, with a given weight, according to the following formula:
cumul_avg = ((cumul_avg * (100 - adc_weight)) + (value * adc_weight)) / 100
ADC measures can't be done at each loop, it would take too much time, and motors won't turn at all (remember, PWM is done manually). There's a counter which defines when ADC measures should be performed. When performed, PWM is deactivated. It may be important to wait a little before actually requiring ADC (to skip some noise). It may also be interesting to acquire ADC several times within a same loop to prevent any "weird" point. All of this can be configured:
- sleep_before_adc specifies the number of 10us (micro-secs) it must wait before proceeding to ADC
- activate_adc proceeds to ADC when a counter reaches this value (counter is incremented on each main loop)
- howmany_adc specifies how many ADC acquisitions must be performed when activated
- adc_weight specifies the weight of the ADC measure (it's a byte but a percentage, so 0 < weight < 100)
- send_raw_data specifices whether raw data or cumulative adc average should be sent when getting speed
The DC motor controller board can be accessed both via serial and i2c. This means all the parameters previously described can be set using serial or i2c. Every instruction is sent and is eval'ed. There are two main actions:
- SET actions: you set (write) some parameter's value
- GET actions: you get (read) some values
If you use several daughter boards, you may prefer using i2c bus since multiple slave board can be controlled from a single master (the mainboard for instance).
Serial and I2C communication specification are quite the same, except every instruction must be prefixed by "M" (as in motor...) using serial.
All SET actions are 3 characters long using i2c (and 4 with prefix "M" using serial). These are:
- motor number: 0 or 1
- action (one-char coded)
Here are available action characters (see dcmc_commands.jal):
|a||active_pwm||Set motor's speed. Related to PWM and max_pwm parameter.||
0 < active_pwm < max_pwm
|p||max_pwm||Set the maximum value PWW counter can reach. Kind-of set the PWM resolution||Should be left to maximum value, 255 Can also be used to control speed, but using active_pwm is preferred|
|s||sleep_before_adc||Delay before actually performing ADC May help reducing back-emf noise while stopping motors||Multiple of 10us (eg. value = 3 means "waits 30 microsecs before adc")|
|A||activate_adc||Specifies when ADC should be performed. A counter starting from 0 is incremented on each main loop (that is, as fast as possible). When this counter reaches activate_adc, ADC is performed||Without this value, or if activate_adc = 0, ADC would be performed on each loop, performances (such as speed control) would dramatically decreased. On the other hand, ADC should be performed as often as possible to get as much values as possible. This is an empirical value, it depends on the the main loop speed, which may not be constant... Anyway, try with 50, that's a nice value ;)|
|C||howmany_adc||Once activate_adc is reached (ie. ADC measure must be acquired), specifies how many ADC measures must be performed||Performing several ADC measure within a same main loop may help having better, noiseless data. However, too much measure would decrease the main loop speed, and worst, may result in wrong data: remember while perfoming ADC to get back-emf, motors must be stopped. If too many ADC is done, motors may slow down, and the actual mean speed may become wrong... Start with 1. If too noisy, try to adjust sleep_before_adc before using this parameter.|
|w||adc_weight||Specifies the weight of ADC measures, in order to compute the cumulative back-emf average. Should be set once.||
adc_weight is percentage so:
Again, several experiments may be required to adjust this empirical parameter. Start with 2% (that is, each new value represents only 2% of the mean...)
|r||send_raw_data||While getting speed, specifies whether raw data (last ADC measure) or cumulative average value should be sent.||
Sending raw data may be interesting for further processing on the PC side
|f||forward||Order the motor to move forward||There's actually no value... but it must be sent, to preserve 3- or 4- chars length actions. Value will be ignore.|
|b||backward||Order the motor to move backward||Still no value, see forward...|
All GET actions are 2 characters long using i2c (and 3 with prefix "M" using serial). These are:
- motor number: 0 or 1
- action (one-char coded)
You can, for now, only get speed value (yes, you're supposed to know what you've set for all other parameters, such as "forward", "backward", ...)
|S||get_speed||Get the actual motor's speed value. What is sent actually depends on what is set for send_raw_data (either cumulative average or raw data)|
|Notes:||Refer to "man ascii", or the like, to convert decimal to char Actions are what actually between quotes...|
- set speed to 120 for motor 1, 50 for motor 0: "1ax", "0a2"
- move motor 1 forward, then backward: "1f ", "1b " (spaces are values and are ignored)
- assuming send_raw_data is 0, get cumulative average speed for motor 0: "0S"
- swith to raw data and get speed for motor 1: SET "1r1" then GET "1S"
- set speed to 120 for motor 1, 50 for motor 0: "M1ax", "M0a2"
- move motor 1 forward, then backward: "M1f ", "M1b " (spaces are values and are ignored)
- assuming send_raw_data is 0, get cumulative average speed for motor 0: "M0S"
- swith to raw data and get speed for motor 1: SET "M1r1" then GET "M1S"
The DC motor controller board for H-Bridge corresponds to the following diagram:
|Note:||The DC motor controller board must be connected to the mainboard on PORTB, using a buscable.|
|Note:||This board doesn't consume 5V coming from the mainboard, it uses a dedicated voltage regulator to prevent any voltage spikes which could cause PIC's reset...|
The following pictures show the components implementation on the board, and the copper side. Light blue areas are here to fill gaps, so etching is faster. (using PCB toner transfer, it also helps the paper (or transparent) to stick to the board)
You may want to download PosctScript versions (1:1 scaled) if you plan to reproduce this board.
|Note:||Whatever the technique you use to build a PCB, remember the text on the board must be readable while looking at the copper side... My two cents...|
All JAL files related to this board are located in jal/labs/dcmotor directory (starting from release 0.2.2).
cd jal ./bin/sbjal labs/dcmotor/dcmc.jal
Then program your 16F88 chip with the generated labs/dcmotor/dcmc.hex file.
There's also a dcmc_master.jal file which implements a very simple i2c master talking to this board (for testing purpose for instance).
|Note:||sbjal is wrapper over jalv2 compiler where library path is correctly set (essentially -s option).|
There are several files used to build the final dcmc.hex file. These dependencies are organized so one may customize its own DC motor controller board (and even write its own...).
dcmc.jal is the main file, where all is assembled.
dcmc_protocol.jal defines the protocol used to communicate with the board. This is not about i2c or serial, this is about how frame are built and evaled by the microcontroller (do you want to use echo ? should all frames start with SOF char and end with EOF ? ...). It should be left as is
dcmc_config.jal is where you define chip, xtal, pins, USART speed, i2c adress.
dcmc_controller.jal is the file where you select which controller to use. There are for now two controllers, one being not fully tested. You can use either:
- dcmc_with_rctank.jal to control existing H-Bridges
- dcmc_with_l293d where you directly talk to a controller chip such as L293D or its pin-compatible SN754410 replacement. This controller is not fully tested
For now, only dcmc_with_rctank.jal should be used. In this file, there are some pins definition specific to the controller and some important procedures. If you plan to write your own controller (you can...), you may have a look at these files as examples, and implement the following prodecures (which are called in the main program, and thus are expected to exist...):
- init_dcmc() : this is where you setup pins, and anything that should be done for the board to be ready
- proceed_forward_char(byte in motor) : is where you implement the way the given motor is going forward
- proceed_backward_char(byte in motor) : same as above but for backward direction
- pwmize_0() : PWM implementation for motor 0
- pwmize_1() : PWM implementation for motor 1 (no factoring...)
- suspend_pwm(byte in motor) : is called when performing ADC. It should store the current PWM level...
- restore_pwm(byte in motor) : ... so it's then restored
dcmc_eval_i2c.jal : is state-machine implementation of i2c hardware slave. This is where i2c data is eval'ed (get/set actions). It uses interrupts.
dcmc_eval_serial.jal : is the same as i2c, but for serial. There's no interrupts here, but a main forever loop, polling for new incoming serial data
dcmc_commands.jal : is the commands definitions (GET/SET actions). If you don't like those, change them here !