blob: a9b37a54f34d86faf650b1e9b0334437939e3886 [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
Damien George2cf6dfa2015-01-01 21:06:20 +000031#include "py/nlr.h"
32#include "py/runtime.h"
Damien Georgea12be912014-04-02 15:09:36 +010033#include "timer.h"
Damien George626f6b82014-03-22 15:52:33 +000034#include "servo.h"
35
Damien George8d096402014-04-29 22:55:34 +010036/// \moduleref pyb
37/// \class Servo - 3-wire hobby servo driver
38///
39/// Servo controls standard hobby servos with 3-wires (ground, power, signal).
40
Damien George626f6b82014-03-22 15:52:33 +000041// this servo driver uses hardware PWM to drive servos on PA0, PA1, PA2, PA3 = X1, X2, X3, X4
42// TIM2 and TIM5 have CH1, CH2, CH3, CH4 on PA0-PA3 respectively
43// they are both 32-bit counters with 16-bit prescaler
Damien Georgea12be912014-04-02 15:09:36 +010044// we use TIM5
Damien George626f6b82014-03-22 15:52:33 +000045
Damien George0119fc72014-03-22 18:34:16 +000046#define PYB_SERVO_NUM (4)
47
48typedef struct _pyb_servo_obj_t {
49 mp_obj_base_t base;
Damien George22934712014-04-09 00:45:45 +010050 uint8_t servo_id;
51 uint8_t pulse_min; // units of 10us
52 uint8_t pulse_max; // units of 10us
53 uint8_t pulse_centre; // units of 10us
54 uint8_t pulse_angle_90; // units of 10us; pulse at 90 degrees, minus pulse_centre
55 uint8_t pulse_speed_100; // units of 10us; pulse at 100% forward speed, minus pulse_centre
56 uint16_t pulse_cur; // units of 10us
57 uint16_t pulse_dest; // units of 10us
Damien George0119fc72014-03-22 18:34:16 +000058 int16_t pulse_accum;
Damien George22934712014-04-09 00:45:45 +010059 uint16_t time_left;
Damien George0119fc72014-03-22 18:34:16 +000060} pyb_servo_obj_t;
61
Damien George0119fc72014-03-22 18:34:16 +000062STATIC pyb_servo_obj_t pyb_servo_obj[PYB_SERVO_NUM];
63
Damien George626f6b82014-03-22 15:52:33 +000064void servo_init(void) {
Damien Georgea12be912014-04-02 15:09:36 +010065 timer_tim5_init();
Damien George0119fc72014-03-22 18:34:16 +000066
67 // reset servo objects
68 for (int i = 0; i < PYB_SERVO_NUM; i++) {
Damien Georgee90eefc2014-04-02 19:55:08 +010069 pyb_servo_obj[i].base.type = &pyb_servo_type;
Damien George0119fc72014-03-22 18:34:16 +000070 pyb_servo_obj[i].servo_id = i + 1;
Damien George22934712014-04-09 00:45:45 +010071 pyb_servo_obj[i].pulse_min = 64;
72 pyb_servo_obj[i].pulse_max = 242;
73 pyb_servo_obj[i].pulse_centre = 150;
74 pyb_servo_obj[i].pulse_angle_90 = 97;
75 pyb_servo_obj[i].pulse_speed_100 = 70;
76 pyb_servo_obj[i].pulse_cur = 150;
Damien George0119fc72014-03-22 18:34:16 +000077 pyb_servo_obj[i].pulse_dest = 0;
Damien George22934712014-04-09 00:45:45 +010078 pyb_servo_obj[i].time_left = 0;
Damien George0119fc72014-03-22 18:34:16 +000079 }
Damien George626f6b82014-03-22 15:52:33 +000080}
81
Damien George0119fc72014-03-22 18:34:16 +000082void servo_timer_irq_callback(void) {
Damien George0119fc72014-03-22 18:34:16 +000083 bool need_it = false;
84 for (int i = 0; i < PYB_SERVO_NUM; i++) {
85 pyb_servo_obj_t *s = &pyb_servo_obj[i];
86 if (s->pulse_cur != s->pulse_dest) {
Damien George22934712014-04-09 00:45:45 +010087 // clamp pulse to within min/max
88 if (s->pulse_dest < s->pulse_min) {
89 s->pulse_dest = s->pulse_min;
90 } else if (s->pulse_dest > s->pulse_max) {
91 s->pulse_dest = s->pulse_max;
92 }
93 // adjust cur to get closer to dest
Damien George0119fc72014-03-22 18:34:16 +000094 if (s->time_left <= 1) {
95 s->pulse_cur = s->pulse_dest;
96 s->time_left = 0;
97 } else {
98 s->pulse_accum += s->pulse_dest - s->pulse_cur;
99 s->pulse_cur += s->pulse_accum / s->time_left;
100 s->pulse_accum %= s->time_left;
101 s->time_left--;
102 need_it = true;
103 }
Damien George22934712014-04-09 00:45:45 +0100104 // set the pulse width
Damien George0119fc72014-03-22 18:34:16 +0000105 switch (s->servo_id) {
Damien Georgea12be912014-04-02 15:09:36 +0100106 case 1: TIM5->CCR1 = s->pulse_cur; break;
107 case 2: TIM5->CCR2 = s->pulse_cur; break;
108 case 3: TIM5->CCR3 = s->pulse_cur; break;
109 case 4: TIM5->CCR4 = s->pulse_cur; break;
Damien George0119fc72014-03-22 18:34:16 +0000110 }
111 }
112 }
113 if (need_it) {
Damien Georgea12be912014-04-02 15:09:36 +0100114 __HAL_TIM_ENABLE_IT(&TIM5_Handle, TIM_IT_UPDATE);
Damien George0119fc72014-03-22 18:34:16 +0000115 } else {
Damien Georgea12be912014-04-02 15:09:36 +0100116 __HAL_TIM_DISABLE_IT(&TIM5_Handle, TIM_IT_UPDATE);
Damien George0119fc72014-03-22 18:34:16 +0000117 }
118}
119
120STATIC void servo_init_channel(pyb_servo_obj_t *s) {
Damien George626f6b82014-03-22 15:52:33 +0000121 uint32_t pin;
122 uint32_t channel;
Damien George0119fc72014-03-22 18:34:16 +0000123 switch (s->servo_id) {
Damien George626f6b82014-03-22 15:52:33 +0000124 case 1: pin = GPIO_PIN_0; channel = TIM_CHANNEL_1; break;
125 case 2: pin = GPIO_PIN_1; channel = TIM_CHANNEL_2; break;
126 case 3: pin = GPIO_PIN_2; channel = TIM_CHANNEL_3; break;
127 case 4: pin = GPIO_PIN_3; channel = TIM_CHANNEL_4; break;
128 default: return;
129 }
130
131 // GPIO configuration
132 GPIO_InitTypeDef GPIO_InitStructure;
133 GPIO_InitStructure.Pin = pin;
134 GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
135 GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
136 GPIO_InitStructure.Pull = GPIO_NOPULL;
Damien Georgea12be912014-04-02 15:09:36 +0100137 GPIO_InitStructure.Alternate = GPIO_AF2_TIM5;
Damien George626f6b82014-03-22 15:52:33 +0000138 HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
139
140 // PWM mode configuration
141 TIM_OC_InitTypeDef oc_init;
142 oc_init.OCMode = TIM_OCMODE_PWM1;
Damien George0119fc72014-03-22 18:34:16 +0000143 oc_init.Pulse = s->pulse_cur; // units of 10us
Damien George626f6b82014-03-22 15:52:33 +0000144 oc_init.OCPolarity = TIM_OCPOLARITY_HIGH;
145 oc_init.OCFastMode = TIM_OCFAST_DISABLE;
Damien Georgea12be912014-04-02 15:09:36 +0100146 HAL_TIM_PWM_ConfigChannel(&TIM5_Handle, &oc_init, channel);
Damien George626f6b82014-03-22 15:52:33 +0000147
148 // start PWM
Damien Georgea12be912014-04-02 15:09:36 +0100149 HAL_TIM_PWM_Start(&TIM5_Handle, channel);
Damien George626f6b82014-03-22 15:52:33 +0000150}
151
152/******************************************************************************/
153// Micro Python bindings
154
155STATIC mp_obj_t pyb_servo_set(mp_obj_t port, mp_obj_t value) {
156 int p = mp_obj_get_int(port);
157 int v = mp_obj_get_int(value);
158 if (v < 50) { v = 50; }
159 if (v > 250) { v = 250; }
160 switch (p) {
Damien Georgea12be912014-04-02 15:09:36 +0100161 case 1: TIM5->CCR1 = v; break;
162 case 2: TIM5->CCR2 = v; break;
163 case 3: TIM5->CCR3 = v; break;
164 case 4: TIM5->CCR4 = v; break;
Damien George626f6b82014-03-22 15:52:33 +0000165 }
166 return mp_const_none;
167}
168
169MP_DEFINE_CONST_FUN_OBJ_2(pyb_servo_set_obj, pyb_servo_set);
170
171STATIC mp_obj_t pyb_pwm_set(mp_obj_t period, mp_obj_t pulse) {
172 int pe = mp_obj_get_int(period);
173 int pu = mp_obj_get_int(pulse);
Damien Georgea12be912014-04-02 15:09:36 +0100174 TIM5->ARR = pe;
175 TIM5->CCR3 = pu;
Damien George626f6b82014-03-22 15:52:33 +0000176 return mp_const_none;
177}
178
179MP_DEFINE_CONST_FUN_OBJ_2(pyb_pwm_set_obj, pyb_pwm_set);
180
Damien George0e9d96f2014-03-24 11:48:39 +0000181STATIC 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 +0000182 pyb_servo_obj_t *self = self_in;
Damien George22934712014-04-09 00:45:45 +0100183 print(env, "<Servo %lu at %luus>", self->servo_id, 10 * self->pulse_cur);
Damien George626f6b82014-03-22 15:52:33 +0000184}
185
Damien George8d096402014-04-29 22:55:34 +0100186/// \classmethod \constructor(id)
187/// Create a servo object. `id` is 1-4.
Damien Georgeecc88e92014-08-30 00:35:11 +0100188STATIC mp_obj_t pyb_servo_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
Damien George0e9d96f2014-03-24 11:48:39 +0000189 // check arguments
Damien Georged6894302014-04-20 00:16:30 +0100190 mp_arg_check_num(n_args, n_kw, 1, 1, false);
Damien George0e9d96f2014-03-24 11:48:39 +0000191
192 // get servo number
Damien George40f3c022014-07-03 13:25:24 +0100193 mp_int_t servo_id = mp_obj_get_int(args[0]) - 1;
Damien George0e9d96f2014-03-24 11:48:39 +0000194
195 // check servo number
196 if (!(0 <= servo_id && servo_id < PYB_SERVO_NUM)) {
Damien Georgeea13f402014-04-05 18:32:08 +0100197 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 +0000198 }
199
200 // get and init servo object
201 pyb_servo_obj_t *s = &pyb_servo_obj[servo_id];
202 s->pulse_dest = s->pulse_cur;
203 s->time_left = 0;
204 servo_init_channel(s);
205
206 return s;
207}
208
Damien George8d096402014-04-29 22:55:34 +0100209/// \method pulse_width([value])
210/// Get or set the pulse width in milliseconds.
Damien Georgeecc88e92014-08-30 00:35:11 +0100211STATIC mp_obj_t pyb_servo_pulse_width(mp_uint_t n_args, const mp_obj_t *args) {
Damien George22934712014-04-09 00:45:45 +0100212 pyb_servo_obj_t *self = args[0];
213 if (n_args == 1) {
214 // get pulse width, in us
215 return mp_obj_new_int(10 * self->pulse_cur);
216 } else {
217 // set pulse width, in us
218 self->pulse_dest = mp_obj_get_int(args[1]) / 10;
219 self->time_left = 0;
220 servo_timer_irq_callback();
221 return mp_const_none;
222 }
223}
Damien George22934712014-04-09 00:45:45 +0100224STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_servo_pulse_width_obj, 1, 2, pyb_servo_pulse_width);
225
Damien George8d096402014-04-29 22:55:34 +0100226/// \method calibration([pulse_min, pulse_max, pulse_centre, [pulse_angle_90, pulse_speed_100]])
227/// Get or set the calibration of the servo timing.
Damien Georgebaa2afb2014-05-03 16:42:27 +0100228// TODO should accept 1 arg, a 5-tuple of values to set
Damien Georgeecc88e92014-08-30 00:35:11 +0100229STATIC mp_obj_t pyb_servo_calibration(mp_uint_t n_args, const mp_obj_t *args) {
Damien George22934712014-04-09 00:45:45 +0100230 pyb_servo_obj_t *self = args[0];
231 if (n_args == 1) {
232 // get calibration values
233 mp_obj_t tuple[5];
234 tuple[0] = mp_obj_new_int(10 * self->pulse_min);
235 tuple[1] = mp_obj_new_int(10 * self->pulse_max);
236 tuple[2] = mp_obj_new_int(10 * self->pulse_centre);
237 tuple[3] = mp_obj_new_int(10 * (self->pulse_angle_90 + self->pulse_centre));
238 tuple[4] = mp_obj_new_int(10 * (self->pulse_speed_100 + self->pulse_centre));
239 return mp_obj_new_tuple(5, tuple);
240 } else if (n_args >= 4) {
241 // set min, max, centre
242 self->pulse_min = mp_obj_get_int(args[1]) / 10;
243 self->pulse_max = mp_obj_get_int(args[2]) / 10;
244 self->pulse_centre = mp_obj_get_int(args[3]) / 10;
245 if (n_args == 4) {
246 return mp_const_none;
247 } else if (n_args == 6) {
248 self->pulse_angle_90 = mp_obj_get_int(args[4]) / 10 - self->pulse_centre;
249 self->pulse_speed_100 = mp_obj_get_int(args[5]) / 10 - self->pulse_centre;
250 return mp_const_none;
251 }
252 }
253
254 // bad number of arguments
Damien Georgeaf797f42014-04-21 18:21:07 +0100255 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 +0100256}
Damien Georgeaf797f42014-04-21 18:21:07 +0100257STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_servo_calibration_obj, 1, 6, pyb_servo_calibration);
Damien George22934712014-04-09 00:45:45 +0100258
Damien George8d096402014-04-29 22:55:34 +0100259/// \method angle([angle, time=0])
260/// Get or set the angle of the servo.
261///
262/// - `angle` is the angle to move to in degrees.
263/// - `time` is the number of milliseconds to take to get to the specified angle.
Damien Georgeecc88e92014-08-30 00:35:11 +0100264STATIC mp_obj_t pyb_servo_angle(mp_uint_t n_args, const mp_obj_t *args) {
Damien George0119fc72014-03-22 18:34:16 +0000265 pyb_servo_obj_t *self = args[0];
266 if (n_args == 1) {
267 // get angle
Damien George22934712014-04-09 00:45:45 +0100268 return mp_obj_new_int((self->pulse_cur - self->pulse_centre) * 90 / self->pulse_angle_90);
Damien George0119fc72014-03-22 18:34:16 +0000269 } else {
Damien Georgefb510b32014-06-01 13:32:54 +0100270#if MICROPY_PY_BUILTINS_FLOAT
Damien George22934712014-04-09 00:45:45 +0100271 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 +0000272#else
Damien George22934712014-04-09 00:45:45 +0100273 self->pulse_dest = self->pulse_centre + self->pulse_angle_90 * mp_obj_get_int(args[1]) / 90;
Damien George626f6b82014-03-22 15:52:33 +0000274#endif
Damien George0119fc72014-03-22 18:34:16 +0000275 if (n_args == 2) {
276 // set angle immediately
277 self->time_left = 0;
278 } else {
279 // set angle over a given time (given in milli seconds)
280 self->time_left = mp_obj_get_int(args[2]) / 20;
281 self->pulse_accum = 0;
282 }
283 servo_timer_irq_callback();
284 return mp_const_none;
Damien George626f6b82014-03-22 15:52:33 +0000285 }
Damien George626f6b82014-03-22 15:52:33 +0000286}
Damien George0e9d96f2014-03-24 11:48:39 +0000287STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_servo_angle_obj, 1, 3, pyb_servo_angle);
Damien George626f6b82014-03-22 15:52:33 +0000288
Damien George8d096402014-04-29 22:55:34 +0100289/// \method speed([speed, time=0])
290/// Get or set the speed of a continuous rotation servo.
291///
292/// - `speed` is the speed to move to change to, between -100 and 100.
293/// - `time` is the number of milliseconds to take to get to the specified speed.
Damien Georgeecc88e92014-08-30 00:35:11 +0100294STATIC mp_obj_t pyb_servo_speed(mp_uint_t n_args, const mp_obj_t *args) {
Damien George22934712014-04-09 00:45:45 +0100295 pyb_servo_obj_t *self = args[0];
296 if (n_args == 1) {
297 // get speed
298 return mp_obj_new_int((self->pulse_cur - self->pulse_centre) * 100 / self->pulse_speed_100);
299 } else {
Damien Georgefb510b32014-06-01 13:32:54 +0100300#if MICROPY_PY_BUILTINS_FLOAT
Damien George22934712014-04-09 00:45:45 +0100301 self->pulse_dest = self->pulse_centre + self->pulse_speed_100 * mp_obj_get_float(args[1]) / 100.0;
302#else
303 self->pulse_dest = self->pulse_centre + self->pulse_speed_100 * mp_obj_get_int(args[1]) / 100;
304#endif
305 if (n_args == 2) {
306 // set speed immediately
307 self->time_left = 0;
308 } else {
309 // set speed over a given time (given in milli seconds)
310 self->time_left = mp_obj_get_int(args[2]) / 20;
311 self->pulse_accum = 0;
312 }
313 servo_timer_irq_callback();
314 return mp_const_none;
315 }
316}
317
318STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_servo_speed_obj, 1, 3, pyb_servo_speed);
319
Damien George9b196cd2014-03-26 21:47:19 +0000320STATIC const mp_map_elem_t pyb_servo_locals_dict_table[] = {
Damien George22934712014-04-09 00:45:45 +0100321 { MP_OBJ_NEW_QSTR(MP_QSTR_pulse_width), (mp_obj_t)&pyb_servo_pulse_width_obj },
Damien Georgeaf797f42014-04-21 18:21:07 +0100322 { MP_OBJ_NEW_QSTR(MP_QSTR_calibration), (mp_obj_t)&pyb_servo_calibration_obj },
Damien George9b196cd2014-03-26 21:47:19 +0000323 { MP_OBJ_NEW_QSTR(MP_QSTR_angle), (mp_obj_t)&pyb_servo_angle_obj },
Damien George22934712014-04-09 00:45:45 +0100324 { MP_OBJ_NEW_QSTR(MP_QSTR_speed), (mp_obj_t)&pyb_servo_speed_obj },
Damien George626f6b82014-03-22 15:52:33 +0000325};
326
Damien George9b196cd2014-03-26 21:47:19 +0000327STATIC MP_DEFINE_CONST_DICT(pyb_servo_locals_dict, pyb_servo_locals_dict_table);
328
Damien George0e9d96f2014-03-24 11:48:39 +0000329const mp_obj_type_t pyb_servo_type = {
Damien George626f6b82014-03-22 15:52:33 +0000330 { &mp_type_type },
331 .name = MP_QSTR_Servo,
Damien George0e9d96f2014-03-24 11:48:39 +0000332 .print = pyb_servo_print,
333 .make_new = pyb_servo_make_new,
Damien George9b196cd2014-03-26 21:47:19 +0000334 .locals_dict = (mp_obj_t)&pyb_servo_locals_dict,
Damien George626f6b82014-03-22 15:52:33 +0000335};