blob: e44e129ef4bb5357aa447de5ba5a671c25303227 [file] [log] [blame]
Damien George04b91472014-05-03 23:27:38 +01001/*
2 * This file is part of the Micro Python project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2013, 2014 Damien P. George
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
Damien George626f6b82014-03-22 15:52:33 +000027#include <stdio.h>
28
29#include "stm32f4xx_hal.h"
30
Paul Sokolovsky9b71b162014-05-02 18:03:04 +030031#include "mpconfig.h"
Damien George0119fc72014-03-22 18:34:16 +000032#include "nlr.h"
Damien George626f6b82014-03-22 15:52:33 +000033#include "misc.h"
Damien George626f6b82014-03-22 15:52:33 +000034#include "qstr.h"
35#include "obj.h"
Damien Georgeeed6f262014-03-26 22:46:03 +000036#include "runtime.h"
Damien Georgea12be912014-04-02 15:09:36 +010037#include "timer.h"
Damien George626f6b82014-03-22 15:52:33 +000038#include "servo.h"
39
Damien George8d096402014-04-29 22:55:34 +010040/// \moduleref pyb
41/// \class Servo - 3-wire hobby servo driver
42///
43/// Servo controls standard hobby servos with 3-wires (ground, power, signal).
44
Damien George626f6b82014-03-22 15:52:33 +000045// this servo driver uses hardware PWM to drive servos on PA0, PA1, PA2, PA3 = X1, X2, X3, X4
46// TIM2 and TIM5 have CH1, CH2, CH3, CH4 on PA0-PA3 respectively
47// they are both 32-bit counters with 16-bit prescaler
Damien Georgea12be912014-04-02 15:09:36 +010048// we use TIM5
Damien George626f6b82014-03-22 15:52:33 +000049
Damien George0119fc72014-03-22 18:34:16 +000050#define PYB_SERVO_NUM (4)
51
52typedef struct _pyb_servo_obj_t {
53 mp_obj_base_t base;
Damien George22934712014-04-09 00:45:45 +010054 uint8_t servo_id;
55 uint8_t pulse_min; // units of 10us
56 uint8_t pulse_max; // units of 10us
57 uint8_t pulse_centre; // units of 10us
58 uint8_t pulse_angle_90; // units of 10us; pulse at 90 degrees, minus pulse_centre
59 uint8_t pulse_speed_100; // units of 10us; pulse at 100% forward speed, minus pulse_centre
60 uint16_t pulse_cur; // units of 10us
61 uint16_t pulse_dest; // units of 10us
Damien George0119fc72014-03-22 18:34:16 +000062 int16_t pulse_accum;
Damien George22934712014-04-09 00:45:45 +010063 uint16_t time_left;
Damien George0119fc72014-03-22 18:34:16 +000064} pyb_servo_obj_t;
65
Damien George0119fc72014-03-22 18:34:16 +000066STATIC pyb_servo_obj_t pyb_servo_obj[PYB_SERVO_NUM];
67
Damien George626f6b82014-03-22 15:52:33 +000068void servo_init(void) {
Damien Georgea12be912014-04-02 15:09:36 +010069 timer_tim5_init();
Damien George0119fc72014-03-22 18:34:16 +000070
71 // reset servo objects
72 for (int i = 0; i < PYB_SERVO_NUM; i++) {
Damien Georgee90eefc2014-04-02 19:55:08 +010073 pyb_servo_obj[i].base.type = &pyb_servo_type;
Damien George0119fc72014-03-22 18:34:16 +000074 pyb_servo_obj[i].servo_id = i + 1;
Damien George22934712014-04-09 00:45:45 +010075 pyb_servo_obj[i].pulse_min = 64;
76 pyb_servo_obj[i].pulse_max = 242;
77 pyb_servo_obj[i].pulse_centre = 150;
78 pyb_servo_obj[i].pulse_angle_90 = 97;
79 pyb_servo_obj[i].pulse_speed_100 = 70;
80 pyb_servo_obj[i].pulse_cur = 150;
Damien George0119fc72014-03-22 18:34:16 +000081 pyb_servo_obj[i].pulse_dest = 0;
Damien George22934712014-04-09 00:45:45 +010082 pyb_servo_obj[i].time_left = 0;
Damien George0119fc72014-03-22 18:34:16 +000083 }
Damien George626f6b82014-03-22 15:52:33 +000084}
85
Damien George0119fc72014-03-22 18:34:16 +000086void servo_timer_irq_callback(void) {
Damien George0119fc72014-03-22 18:34:16 +000087 bool need_it = false;
88 for (int i = 0; i < PYB_SERVO_NUM; i++) {
89 pyb_servo_obj_t *s = &pyb_servo_obj[i];
90 if (s->pulse_cur != s->pulse_dest) {
Damien George22934712014-04-09 00:45:45 +010091 // clamp pulse to within min/max
92 if (s->pulse_dest < s->pulse_min) {
93 s->pulse_dest = s->pulse_min;
94 } else if (s->pulse_dest > s->pulse_max) {
95 s->pulse_dest = s->pulse_max;
96 }
97 // adjust cur to get closer to dest
Damien George0119fc72014-03-22 18:34:16 +000098 if (s->time_left <= 1) {
99 s->pulse_cur = s->pulse_dest;
100 s->time_left = 0;
101 } else {
102 s->pulse_accum += s->pulse_dest - s->pulse_cur;
103 s->pulse_cur += s->pulse_accum / s->time_left;
104 s->pulse_accum %= s->time_left;
105 s->time_left--;
106 need_it = true;
107 }
Damien George22934712014-04-09 00:45:45 +0100108 // set the pulse width
Damien George0119fc72014-03-22 18:34:16 +0000109 switch (s->servo_id) {
Damien Georgea12be912014-04-02 15:09:36 +0100110 case 1: TIM5->CCR1 = s->pulse_cur; break;
111 case 2: TIM5->CCR2 = s->pulse_cur; break;
112 case 3: TIM5->CCR3 = s->pulse_cur; break;
113 case 4: TIM5->CCR4 = s->pulse_cur; break;
Damien George0119fc72014-03-22 18:34:16 +0000114 }
115 }
116 }
117 if (need_it) {
Damien Georgea12be912014-04-02 15:09:36 +0100118 __HAL_TIM_ENABLE_IT(&TIM5_Handle, TIM_IT_UPDATE);
Damien George0119fc72014-03-22 18:34:16 +0000119 } else {
Damien Georgea12be912014-04-02 15:09:36 +0100120 __HAL_TIM_DISABLE_IT(&TIM5_Handle, TIM_IT_UPDATE);
Damien George0119fc72014-03-22 18:34:16 +0000121 }
122}
123
124STATIC void servo_init_channel(pyb_servo_obj_t *s) {
Damien George626f6b82014-03-22 15:52:33 +0000125 uint32_t pin;
126 uint32_t channel;
Damien George0119fc72014-03-22 18:34:16 +0000127 switch (s->servo_id) {
Damien George626f6b82014-03-22 15:52:33 +0000128 case 1: pin = GPIO_PIN_0; channel = TIM_CHANNEL_1; break;
129 case 2: pin = GPIO_PIN_1; channel = TIM_CHANNEL_2; break;
130 case 3: pin = GPIO_PIN_2; channel = TIM_CHANNEL_3; break;
131 case 4: pin = GPIO_PIN_3; channel = TIM_CHANNEL_4; break;
132 default: return;
133 }
134
135 // GPIO configuration
136 GPIO_InitTypeDef GPIO_InitStructure;
137 GPIO_InitStructure.Pin = pin;
138 GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
139 GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
140 GPIO_InitStructure.Pull = GPIO_NOPULL;
Damien Georgea12be912014-04-02 15:09:36 +0100141 GPIO_InitStructure.Alternate = GPIO_AF2_TIM5;
Damien George626f6b82014-03-22 15:52:33 +0000142 HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
143
144 // PWM mode configuration
145 TIM_OC_InitTypeDef oc_init;
146 oc_init.OCMode = TIM_OCMODE_PWM1;
Damien George0119fc72014-03-22 18:34:16 +0000147 oc_init.Pulse = s->pulse_cur; // units of 10us
Damien George626f6b82014-03-22 15:52:33 +0000148 oc_init.OCPolarity = TIM_OCPOLARITY_HIGH;
149 oc_init.OCFastMode = TIM_OCFAST_DISABLE;
Damien Georgea12be912014-04-02 15:09:36 +0100150 HAL_TIM_PWM_ConfigChannel(&TIM5_Handle, &oc_init, channel);
Damien George626f6b82014-03-22 15:52:33 +0000151
152 // start PWM
Damien Georgea12be912014-04-02 15:09:36 +0100153 HAL_TIM_PWM_Start(&TIM5_Handle, channel);
Damien George626f6b82014-03-22 15:52:33 +0000154}
155
156/******************************************************************************/
157// Micro Python bindings
158
159STATIC mp_obj_t pyb_servo_set(mp_obj_t port, mp_obj_t value) {
160 int p = mp_obj_get_int(port);
161 int v = mp_obj_get_int(value);
162 if (v < 50) { v = 50; }
163 if (v > 250) { v = 250; }
164 switch (p) {
Damien Georgea12be912014-04-02 15:09:36 +0100165 case 1: TIM5->CCR1 = v; break;
166 case 2: TIM5->CCR2 = v; break;
167 case 3: TIM5->CCR3 = v; break;
168 case 4: TIM5->CCR4 = v; break;
Damien George626f6b82014-03-22 15:52:33 +0000169 }
170 return mp_const_none;
171}
172
173MP_DEFINE_CONST_FUN_OBJ_2(pyb_servo_set_obj, pyb_servo_set);
174
175STATIC mp_obj_t pyb_pwm_set(mp_obj_t period, mp_obj_t pulse) {
176 int pe = mp_obj_get_int(period);
177 int pu = mp_obj_get_int(pulse);
Damien Georgea12be912014-04-02 15:09:36 +0100178 TIM5->ARR = pe;
179 TIM5->CCR3 = pu;
Damien George626f6b82014-03-22 15:52:33 +0000180 return mp_const_none;
181}
182
183MP_DEFINE_CONST_FUN_OBJ_2(pyb_pwm_set_obj, pyb_pwm_set);
184
Damien George0e9d96f2014-03-24 11:48:39 +0000185STATIC void pyb_servo_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
Damien George626f6b82014-03-22 15:52:33 +0000186 pyb_servo_obj_t *self = self_in;
Damien George22934712014-04-09 00:45:45 +0100187 print(env, "<Servo %lu at %luus>", self->servo_id, 10 * self->pulse_cur);
Damien George626f6b82014-03-22 15:52:33 +0000188}
189
Damien George8d096402014-04-29 22:55:34 +0100190/// \classmethod \constructor(id)
191/// Create a servo object. `id` is 1-4.
Damien George0e9d96f2014-03-24 11:48:39 +0000192STATIC mp_obj_t pyb_servo_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
193 // check arguments
Damien Georged6894302014-04-20 00:16:30 +0100194 mp_arg_check_num(n_args, n_kw, 1, 1, false);
Damien George0e9d96f2014-03-24 11:48:39 +0000195
196 // get servo number
197 machine_int_t servo_id = mp_obj_get_int(args[0]) - 1;
198
199 // check servo number
200 if (!(0 <= servo_id && servo_id < PYB_SERVO_NUM)) {
Damien Georgeea13f402014-04-05 18:32:08 +0100201 nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "Servo %d does not exist", servo_id + 1));
Damien George0e9d96f2014-03-24 11:48:39 +0000202 }
203
204 // get and init servo object
205 pyb_servo_obj_t *s = &pyb_servo_obj[servo_id];
206 s->pulse_dest = s->pulse_cur;
207 s->time_left = 0;
208 servo_init_channel(s);
209
210 return s;
211}
212
Damien George8d096402014-04-29 22:55:34 +0100213/// \method pulse_width([value])
214/// Get or set the pulse width in milliseconds.
Damien George22934712014-04-09 00:45:45 +0100215STATIC mp_obj_t pyb_servo_pulse_width(uint n_args, const mp_obj_t *args) {
216 pyb_servo_obj_t *self = args[0];
217 if (n_args == 1) {
218 // get pulse width, in us
219 return mp_obj_new_int(10 * self->pulse_cur);
220 } else {
221 // set pulse width, in us
222 self->pulse_dest = mp_obj_get_int(args[1]) / 10;
223 self->time_left = 0;
224 servo_timer_irq_callback();
225 return mp_const_none;
226 }
227}
Damien George22934712014-04-09 00:45:45 +0100228STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_servo_pulse_width_obj, 1, 2, pyb_servo_pulse_width);
229
Damien George8d096402014-04-29 22:55:34 +0100230/// \method calibration([pulse_min, pulse_max, pulse_centre, [pulse_angle_90, pulse_speed_100]])
231/// Get or set the calibration of the servo timing.
Damien Georgebaa2afb2014-05-03 16:42:27 +0100232// TODO should accept 1 arg, a 5-tuple of values to set
Damien Georgeaf797f42014-04-21 18:21:07 +0100233STATIC mp_obj_t pyb_servo_calibration(uint n_args, const mp_obj_t *args) {
Damien George22934712014-04-09 00:45:45 +0100234 pyb_servo_obj_t *self = args[0];
235 if (n_args == 1) {
236 // get calibration values
237 mp_obj_t tuple[5];
238 tuple[0] = mp_obj_new_int(10 * self->pulse_min);
239 tuple[1] = mp_obj_new_int(10 * self->pulse_max);
240 tuple[2] = mp_obj_new_int(10 * self->pulse_centre);
241 tuple[3] = mp_obj_new_int(10 * (self->pulse_angle_90 + self->pulse_centre));
242 tuple[4] = mp_obj_new_int(10 * (self->pulse_speed_100 + self->pulse_centre));
243 return mp_obj_new_tuple(5, tuple);
244 } else if (n_args >= 4) {
245 // set min, max, centre
246 self->pulse_min = mp_obj_get_int(args[1]) / 10;
247 self->pulse_max = mp_obj_get_int(args[2]) / 10;
248 self->pulse_centre = mp_obj_get_int(args[3]) / 10;
249 if (n_args == 4) {
250 return mp_const_none;
251 } else if (n_args == 6) {
252 self->pulse_angle_90 = mp_obj_get_int(args[4]) / 10 - self->pulse_centre;
253 self->pulse_speed_100 = mp_obj_get_int(args[5]) / 10 - self->pulse_centre;
254 return mp_const_none;
255 }
256 }
257
258 // bad number of arguments
Damien Georgeaf797f42014-04-21 18:21:07 +0100259 nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "calibration expecting 1, 4 or 6 arguments, got %d", n_args));
Damien George22934712014-04-09 00:45:45 +0100260}
Damien Georgeaf797f42014-04-21 18:21:07 +0100261STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_servo_calibration_obj, 1, 6, pyb_servo_calibration);
Damien George22934712014-04-09 00:45:45 +0100262
Damien George8d096402014-04-29 22:55:34 +0100263/// \method angle([angle, time=0])
264/// Get or set the angle of the servo.
265///
266/// - `angle` is the angle to move to in degrees.
267/// - `time` is the number of milliseconds to take to get to the specified angle.
Damien George0e9d96f2014-03-24 11:48:39 +0000268STATIC mp_obj_t pyb_servo_angle(uint n_args, const mp_obj_t *args) {
Damien George0119fc72014-03-22 18:34:16 +0000269 pyb_servo_obj_t *self = args[0];
270 if (n_args == 1) {
271 // get angle
Damien George22934712014-04-09 00:45:45 +0100272 return mp_obj_new_int((self->pulse_cur - self->pulse_centre) * 90 / self->pulse_angle_90);
Damien George0119fc72014-03-22 18:34:16 +0000273 } else {
Damien George626f6b82014-03-22 15:52:33 +0000274#if MICROPY_ENABLE_FLOAT
Damien George22934712014-04-09 00:45:45 +0100275 self->pulse_dest = self->pulse_centre + self->pulse_angle_90 * mp_obj_get_float(args[1]) / 90.0;
Damien George626f6b82014-03-22 15:52:33 +0000276#else
Damien George22934712014-04-09 00:45:45 +0100277 self->pulse_dest = self->pulse_centre + self->pulse_angle_90 * mp_obj_get_int(args[1]) / 90;
Damien George626f6b82014-03-22 15:52:33 +0000278#endif
Damien George0119fc72014-03-22 18:34:16 +0000279 if (n_args == 2) {
280 // set angle immediately
281 self->time_left = 0;
282 } else {
283 // set angle over a given time (given in milli seconds)
284 self->time_left = mp_obj_get_int(args[2]) / 20;
285 self->pulse_accum = 0;
286 }
287 servo_timer_irq_callback();
288 return mp_const_none;
Damien George626f6b82014-03-22 15:52:33 +0000289 }
Damien George626f6b82014-03-22 15:52:33 +0000290}
Damien George0e9d96f2014-03-24 11:48:39 +0000291STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_servo_angle_obj, 1, 3, pyb_servo_angle);
Damien George626f6b82014-03-22 15:52:33 +0000292
Damien George8d096402014-04-29 22:55:34 +0100293/// \method speed([speed, time=0])
294/// Get or set the speed of a continuous rotation servo.
295///
296/// - `speed` is the speed to move to change to, between -100 and 100.
297/// - `time` is the number of milliseconds to take to get to the specified speed.
Damien George22934712014-04-09 00:45:45 +0100298STATIC mp_obj_t pyb_servo_speed(uint n_args, const mp_obj_t *args) {
299 pyb_servo_obj_t *self = args[0];
300 if (n_args == 1) {
301 // get speed
302 return mp_obj_new_int((self->pulse_cur - self->pulse_centre) * 100 / self->pulse_speed_100);
303 } else {
304#if MICROPY_ENABLE_FLOAT
305 self->pulse_dest = self->pulse_centre + self->pulse_speed_100 * mp_obj_get_float(args[1]) / 100.0;
306#else
307 self->pulse_dest = self->pulse_centre + self->pulse_speed_100 * mp_obj_get_int(args[1]) / 100;
308#endif
309 if (n_args == 2) {
310 // set speed immediately
311 self->time_left = 0;
312 } else {
313 // set speed over a given time (given in milli seconds)
314 self->time_left = mp_obj_get_int(args[2]) / 20;
315 self->pulse_accum = 0;
316 }
317 servo_timer_irq_callback();
318 return mp_const_none;
319 }
320}
321
322STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_servo_speed_obj, 1, 3, pyb_servo_speed);
323
Damien George9b196cd2014-03-26 21:47:19 +0000324STATIC const mp_map_elem_t pyb_servo_locals_dict_table[] = {
Damien George22934712014-04-09 00:45:45 +0100325 { MP_OBJ_NEW_QSTR(MP_QSTR_pulse_width), (mp_obj_t)&pyb_servo_pulse_width_obj },
Damien Georgeaf797f42014-04-21 18:21:07 +0100326 { MP_OBJ_NEW_QSTR(MP_QSTR_calibration), (mp_obj_t)&pyb_servo_calibration_obj },
Damien George9b196cd2014-03-26 21:47:19 +0000327 { MP_OBJ_NEW_QSTR(MP_QSTR_angle), (mp_obj_t)&pyb_servo_angle_obj },
Damien George22934712014-04-09 00:45:45 +0100328 { MP_OBJ_NEW_QSTR(MP_QSTR_speed), (mp_obj_t)&pyb_servo_speed_obj },
Damien George626f6b82014-03-22 15:52:33 +0000329};
330
Damien George9b196cd2014-03-26 21:47:19 +0000331STATIC MP_DEFINE_CONST_DICT(pyb_servo_locals_dict, pyb_servo_locals_dict_table);
332
Damien George0e9d96f2014-03-24 11:48:39 +0000333const mp_obj_type_t pyb_servo_type = {
Damien George626f6b82014-03-22 15:52:33 +0000334 { &mp_type_type },
335 .name = MP_QSTR_Servo,
Damien George0e9d96f2014-03-24 11:48:39 +0000336 .print = pyb_servo_print,
337 .make_new = pyb_servo_make_new,
Damien George9b196cd2014-03-26 21:47:19 +0000338 .locals_dict = (mp_obj_t)&pyb_servo_locals_dict,
Damien George626f6b82014-03-22 15:52:33 +0000339};