4 * This file is part of BeRTOS.
6 * Bertos is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 * As a special exception, you may use this file as part of a free software
21 * library without restriction. Specifically, if other files instantiate
22 * templates or use macros or inline functions from this file, or you compile
23 * this file and link it with other files to produce an executable, this
24 * file does not by itself cause the resulting executable to be covered by
25 * the GNU General Public License. This exception does not however
26 * invalidate any other reasons why the executable file might be covered by
27 * the GNU General Public License.
29 * Copyright 2008 Develer S.r.l. (http://www.develer.com/)
33 * \brief DC motor driver (implementation)
35 * Thi module provide a simple api to controll a DC motor in direction and
36 * speed, to allow this we use a Back-EMF technique.
37 * This technique is based, on the capability of the DC motor to become a generator
38 * of voltage when we turn off its supply. This happen every time we turn off the
39 * DC motor supply, and it continues to rotate for a short time thanks to its mechanical
40 * energy. Using this idea we can turn off the motor for a very short time, and
41 * we read the volage value from DC motor supply pins. This voltage say to us
42 * the actual speed of the motor.
44 * \author Daniele Basile <asterix@develer.com>
48 #include "hw/hw_dc_motor.h"
50 // Define logging setting (for cfg/log.h module).
51 #define LOG_LEVEL DC_MOTOR_LOG_LEVEL
52 #define LOG_FORMAT DC_MOTOR_LOG_FORMAT
55 #include <cfg/debug.h>
57 #include <algo/pid_control.h>
59 #include <drv/timer.h>
61 #include <kern/proc.h>
63 #include <cpu/power.h>
68 * Define status bit for DC motor device.
70 #define DC_MOTOR_ACTIVE BV(0) ///< DC motor enable or disable flag.
71 #define DC_MOTOR_DIR BV(1) ///< Spin direction of DC motor.
74 * Some utility macro for motor directions
78 #define DC_MOTOR_POS_DIR(x) ((x) |= DC_MOTOR_DIR) // Set directions status positive
79 #define DC_MOTOR_NEG_DIR(x) ((x) &= ~DC_MOTOR_DIR) // Set directions status negative
81 // Update the status with current direction
82 #define DC_MOTOR_SET_STATUS_DIR(status, dir) \
83 (dir == POS_DIR ? DC_MOTOR_POS_DIR(status) : DC_MOTOR_NEG_DIR(status))
85 #if (CONFIG_KERN && CONFIG_KERN_PREEMPT)
86 #if CONFIG_DC_MOTOR_USE_SEM
89 Semaphore dc_motor_sem;
90 #define DC_MOTOR_LOCK sem_obtain(&dc_motor_sem)
91 #define DC_MOTOR_UNLOCK sem_release(&dc_motor_sem)
93 #define DC_MOTOR_LOCK proc_forbid()
94 #define DC_MOTOR_UNLOCK proc_permit()
97 #define DC_MOTOR_LOCK /* None */
98 #define DC_MOTOR_UNLOCK /* None */
102 * DC motor definition.
104 static DCMotor dcm_all[CONFIG_NUM_DC_MOTOR];
107 * Process to poll dc motor status
109 struct Process *dc_motor;
112 // Stack process for DC motor poll.
113 static PROC_DEFINE_STACK(dc_motor_poll_stack, 500);
116 LOG_INFOB(static int debug_msg_delay = 0;);
119 INLINE dc_speed_t dc_motor_readSpeed(int index)
121 DCMotor *dcm = &dcm_all[index];
122 return HW_DC_MOTOR_READ_VALUE(dcm->cfg->adc_ch, dcm->cfg->adc_min, dcm->cfg->adc_max);
126 * Read the target speed from select device.
128 dc_speed_t dc_motor_readTargetSpeed(int index)
130 DCMotor *dcm = &dcm_all[index];
131 return HW_DC_MOTOR_READ_VALUE(dcm->cfg->speed_dev_id, CONFIG_DC_MOTOR_MIN_SPEED, CONFIG_DC_MOTOR_MAX_SPEED);
134 static void dc_motor_start(int index)
136 DCMotor *dcm = &dcm_all[index];
140 * Clean all PID stutus variable, becouse
141 * we start with new one.
143 pid_control_reset(&dcm->pid_ctx);
144 dcm->status |= DC_MOTOR_ACTIVE;
149 * There are two \a mode to stop the dc motor:
150 * - DC_MOTOR_DISABLE_MODE
153 * The DC_MOTOR_DISABLE_MODE shut down the DC motor and
154 * leave it floating to rotate.
155 * The DC_MOTOR_IDLE does not shut down DC motor, but put
156 * its supply pin in short circuite, in this way the motor result
157 * braked from intentional rotation.
159 static void dc_motor_stop(int index)
161 DCMotor *dcm = &dcm_all[index];
165 dcm->status &= ~DC_MOTOR_ACTIVE;
166 dcm->expire_time = DC_MOTOR_NO_EXPIRE;
167 pwm_enable(dcm->cfg->pwm_dev, false);
169 if (dcm->cfg->braked)
171 DC_MOTOR_STOP_BRAKED(dcm->index);
175 DC_MOTOR_STOP_FLOAT(dcm->index);
182 * Sampling a signal on DC motor and compute
183 * a new value of speed according with PID control.
185 static void dc_motor_do(int index)
187 DCMotor *dcm = &dcm_all[index];
189 dc_speed_t curr_pos = 0;
190 pwm_duty_t new_pid = 0;
194 //If select DC motor is not active we return
195 if (!(dcm->status & DC_MOTOR_ACTIVE))
202 * To set dc motor direction we must also set the
203 * PWM polarity according with dc motor driver chip
205 pwm_setPolarity(dcm->cfg->pwm_dev, dcm->status & DC_MOTOR_DIR);
206 DC_MOTOR_SET_DIR(dcm->index, dcm->status & DC_MOTOR_DIR);
208 //Compute next value for reaching target speed from current position
209 if (dcm->cfg->pid_enable)
212 * Here we cannot disable the switch context because the
213 * driver, that read the speed could be need to use signal or
214 * other thing that needs the kernel switch context, for this
215 * reason we unlock before to read the speed.
218 curr_pos = dc_motor_readSpeed(index);
220 new_pid = pid_control_update(&dcm->pid_ctx, dcm->tgt_speed, curr_pos);
224 new_pid = dcm->tgt_speed;
228 if (debug_msg_delay == 20)
230 LOG_INFO("DC Motor[%d]: curr_speed[%d],curr_pos[%d],tgt[%d]\n", dcm->index,
231 curr_pos, new_pid, dcm->tgt_speed);
237 //Apply the compute duty value
238 pwm_setDuty(dcm->cfg->pwm_dev, new_pid);
241 pwm_enable(dcm->cfg->pwm_dev, true);
243 DC_MOTOR_ENABLE(dcm->index);
249 * Check if the DC motor run time is expired, if this happend
250 * we turn off motor and reset status.
252 INLINE bool check_timerIsExpired(int index)
256 bool check = ((dcm_all[index].expire_time - timer_clock()) < 0) &&
257 (dcm_all[index].expire_time != DC_MOTOR_NO_EXPIRE);
264 * Process to poll DC motor status.
265 * To use a Back-EMF technique (see brief for more details),
266 * we turn off a motor for CONFIG_DC_MOTOR_SAMPLE_DELAY, that value are stored
267 * in each DC motor config. For this implementation we assume
268 * that have a common CONFIG_DC_MOTOR_SAMPLE_DELAY, choose among a max delay
269 * to all DC motor configuration.
270 * The DC motor off time is choose to allow the out signal to
271 * be stable, so we can read and process this value for feedback controll loop.
272 * The period (CONFIG_DC_MOTOR_SAMPLE_PERIOD - CONFIG_DC_MOTOR_SAMPLE_DELAY)
273 * that every time we turn off a DC motor is choose to have a feedback controll
274 * more responsive or less responsive.
276 static void NORETURN dc_motor_poll(void)
281 * For all DC motor we read and process output singal,
282 * and choose the max value to off time
284 for (int i = 0; i < CONFIG_NUM_DC_MOTOR; i++)
289 if (check_timerIsExpired(i))
295 * If we read speed from trimmer we update the target
296 * speed value when motor is running so we can make
297 * dc motor speed regulation.
299 if (dcm_all[i].cfg->speed_dev_id != DC_MOTOR_NO_DEV_SPEED)
300 dc_motor_setSpeed(i, dc_motor_readTargetSpeed(i));
303 //Wait for next sampling
304 timer_delay(CONFIG_DC_MOTOR_SAMPLE_PERIOD - CONFIG_DC_MOTOR_SAMPLE_DELAY);
306 for (int i = 0; i < CONFIG_NUM_DC_MOTOR; i++)
311 if (check_timerIsExpired(i))
315 if (dcm_all[i].status & DC_MOTOR_ACTIVE)
317 DC_MOTOR_DISABLE(dcm_all[i].index);
318 pwm_enable(dcm_all[i].cfg->pwm_dev, false);
323 //Wait some time to allow signal to stabilize before sampling
324 timer_delay(CONFIG_DC_MOTOR_SAMPLE_DELAY);
329 * Set spin direction of DC motor.
331 * \a index number of DC motor
332 * \a dir direction of DC motor
334 void dc_motor_setDir(int index, bool dir)
336 DCMotor *dcm = &dcm_all[index];
338 DC_MOTOR_SET_STATUS_DIR(dcm->status, dir);
343 * Set DC motor speed.
345 void dc_motor_setSpeed(int index, dc_speed_t speed)
347 DCMotor *dcm = &dcm_all[index];
350 dcm->tgt_speed = speed;
353 LOG_INFO("DC Motor[%d]: tgt_speed[%d]\n", index, dcm->tgt_speed);
357 * Set among of time that dc motor should run.
359 void dc_motor_startTimer(int index, mtime_t on_time)
362 dcm_all[index].expire_time = DC_MOTOR_NO_EXPIRE;
363 if (on_time != DC_MOTOR_NO_EXPIRE)
365 dcm_all[index].expire_time = timer_clock() + ms_to_ticks(on_time);
366 dc_motor_start(index);
371 void dc_motor_waitStop(int index)
373 DCMotor *dcm = &dcm_all[index];
379 loop = dcm->status & DC_MOTOR_ACTIVE;
387 * Enable or disable dc motor.
389 void dc_motor_enable(int index, bool state)
392 dc_motor_start(index);
394 dc_motor_stop(index);
398 * Apply a confinguration to select DC motor.
400 void dc_motor_setup(int index, DCMotorConfig *dcm_conf)
402 DCMotor *dcm = &dcm_all[index];
406 * We are using the same sample period for each
407 * motor, and so we check if this value is the same
408 * for all. The sample period time is defined in pid
411 * TODO: Use a different sample period for each motor
412 * and refactor a module to allow to use a timer interrupt,
413 * in this way we can controll a DC motor also without a
414 * kernel, increasing a portability on other target.
416 pid_control_setPeriod(&dcm_conf->pid_cfg, CONFIG_DC_MOTOR_SAMPLE_PERIOD);
419 pid_control_init(&dcm->pid_ctx, &dcm_conf->pid_cfg);
425 * Apply config value.
430 * By default the motor run forever..
432 dcm->expire_time = DC_MOTOR_NO_EXPIRE;
435 * By default set target speed.
437 dcm->tgt_speed = dcm_conf->speed;
444 pwm_setFrequency(dcm->cfg->pwm_dev, dcm->cfg->freq);
445 pwm_enable(dcm->cfg->pwm_dev, false);
447 //Set default direction for DC motor
448 DC_MOTOR_SET_DIR(dcm->index, dcm->cfg->dir);
449 DC_MOTOR_SET_STATUS_DIR(dcm->status, dcm->cfg->dir);
453 LOG_INFO("DC motor[%d]:\n", dcm->index);
454 LOG_INFO("> PID: kp[%f],ki[%f],kd[%f]\n", dcm->cfg->pid_cfg.kp, dcm->cfg->pid_cfg.ki, dcm->cfg->pid_cfg.kd);
455 LOG_INFO("> PWM: pwm_dev[%d], freq[%ld], sample[%d]\n", dcm->cfg->pwm_dev, dcm->cfg->freq,CONFIG_DC_MOTOR_SAMPLE_DELAY);
456 LOG_INFO("> ADC: adc_ch[%d], adc_max[%d], adc_min[%d]\n", dcm->cfg->adc_ch, dcm->cfg->adc_max, dcm->cfg->adc_min);
457 LOG_INFO("> DC: dir[%d], speed[%d]\n", dcm->cfg->dir, dcm->cfg->speed);
461 * If we had enabled the priority scheduling, we can adjust the
462 * DC motor poll process priority.
464 void dc_motor_setPriority(int priority)
468 proc_setPri(dc_motor, priority);
473 * \a priority: sets the dc motor process priority.
475 void dc_motor_init(void)
481 #if (CONFIG_KERN_PREEMPT && CONFIG_DC_MOTOR_USE_SEM)
482 sem_init(&dc_motor_sem);
485 //Create a dc motor poll process
486 dc_motor = proc_new_with_name("DC_Motor", dc_motor_poll, NULL, sizeof(dc_motor_poll_stack), dc_motor_poll_stack);