-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathraceon.py
216 lines (173 loc) · 6.67 KB
/
raceon.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import time
import os
class PWM(object):
"""
A class to work with the Linux PWM driver sysfs interface
"""
def __init__(self, channel=0, chip=0):
""" Specify channel and chip when creating an instance
The Linux kernel driver exports a sysfs interface like this
/sys/class/pwm/pwmchip<chip>/pwm<channel>
A <chip> can have multiple <channels>.
The channel and chip are determined by the kernel driver.
For example the two PWM timers from the RPi kernel driver
show up like this
/sys/class/pwm/pwmchip0/pwm0
/sys/class/pwm/pwmchip0/pwm1
To use the RPi timers create instances this way
pwm0 = PWM(0) or PWM(0,0)
pwm1 = PWM(1) or PWM(1,0)
"""
self._channel = channel
self._chip = chip
self.base = '/sys/class/pwm/pwmchip{:d}'.format(self._chip)
self.path = self.base + '/pwm{:d}'.format(self._channel)
if not os.path.isdir(self.base):
raise FileNotFoundError('Directory not found: ' + self.base)
# enable class as a context manager
def __enter__(self):
self.export()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.enable = False
self.inversed = False
self.unexport()
return
def export(self):
"""Export the channel for use through the sysfs interface.
Required before first use.
"""
if not os.path.isdir(self.path):
with open(self.base + '/export', 'w') as f:
f.write('{:d}'.format(self._channel))
def unexport(self):
"""Unexport the channel.
The sysfs interface is no longer usable until it is exported again.
"""
if os.path.isdir(self.path):
with open(self.base + '/unexport', 'w') as f:
f.write('{:d}'.format(self._channel))
@property
def channel(self):
"""The channel used by this instance.
Read-only, set in the constructor.
"""
return self._channel
@property
def chip(self):
"""The chip used by this instance.
Read-only, set in the constructor.
"""
return self._chip
@property
def period(self):
"""The period of the pwm timer in nanoseconds."""
with open(self.path + '/period', 'r') as f:
value = f.readline().strip()
return int(value)
@period.setter
def period(self, value):
with open(self.path + '/period', 'w') as f:
f.write('{:d}'.format(value))
@property
def duty_cycle(self):
"""The duty_cycle (the ON pulse) of the timer in nanoseconds."""
with open(self.path + '/duty_cycle', 'r') as f:
value = f.readline().strip()
return int(value)
@duty_cycle.setter
def duty_cycle(self, value):
with open(self.path + '/duty_cycle', 'w') as f:
f.write('{:d}'.format(value))
@property
def enable(self):
"""Enable or disable the timer, boolean"""
with open(self.path + '/enable', 'r') as f:
value = f.readline().strip()
return True if value == '1' else False
@enable.setter
def enable(self, value):
with open(self.path + '/enable', 'w') as f:
if value:
f.write('1')
else:
f.write('0')
@property
def inversed(self):
"""normal polarity or inversed, boolean"""
with open(self.path + '/polarity', 'r') as f:
value = f.readline().strip()
return True if value == 'inversed' else False
@inversed.setter
def inversed(self, value):
with open(self.path + '/polarity', 'w') as f:
if value:
f.write('inversed')
else:
f.write('normal')
class Car:
'''
A class to control an ackerman drive car using a motor and a servo
'''
def __init__(self, motor_pin=None, servo_pin=None, servo_left=1000, servo_mid=1500, servo_right=2000, motor_reverse=False):
assert motor_pin is not None, "motor_pin is not defined"
assert servo_pin is not None, "servo_pin is not defined"
if servo_left < servo_right:
assert servo_left < servo_mid < servo_right, "servo_mid is not in between servo_left and servo_right"
else:
assert servo_left > servo_mid > servo_right, "servo_mid is not in between servo_left and servo_right"
# setup motor
self.motor = PWM(motor_pin)
self.motor.period = 20000000
if motor_reverse:
self.MOTOR_REVERSE = True
self.MOTOR_BRAKE = 1500
self.MOTOR_MIN = 1000
self.MOTOR_MAX = 2000
else:
self.MOTOR_REVERSE = False
self.MOTOR_MIN = self.MOTOR_BRAKE = 1000
self.MOTOR_MAX = 2000
# setup servo
self.servo = PWM(servo_pin)
self.servo.period = 20000000
self.SERVO_MID = servo_mid
self.SERVO_MIN = servo_left
self.SERVO_MAX = servo_right
def _map(self, value, from_min, from_max, to_min, to_max):
from_range = from_max - from_min
to_range = to_max - to_min
scaled_value = float(value - from_min) / float(from_range)
return int(to_min + (scaled_value * to_range))
def _limit(self, value, min_, max_):
if value < min_:
return min_
elif value > max_:
return max_
else:
return value
def enable(self):
self.motor.duty_cycle = self.MOTOR_BRAKE * 1000
self.servo.duty_cycle = self.SERVO_MID * 1000
self.motor.enable = True
self.servo.enable = True
def disable(self):
self.motor.duty_cycle = self.MOTOR_BRAKE * 1000
self.servo.duty_cycle = self.SERVO_MID * 1000
self.motor.enable = False
self.servo.enable = False
def brake(self):
self.motor.duty_cycle = self.MOTOR_BRAKE * 1000
def speed(self, _speed):
if self.MOTOR_REVERSE:
_speed = self._limit(_speed, -1000, 1000)
self.motor.duty_cycle = self._map(_speed, -1000, 1000, self.MOTOR_MIN, self.MOTOR_MAX) * 1000
else:
_speed = self._limit(_speed, 0, 1000)
self.motor.duty_cycle = self._map(_speed, 0, 1000, self.MOTOR_MIN, self.MOTOR_MAX) * 1000
def steer(self, _steer):
_steer = self._limit(_steer, -1000, 1000)
if _steer < 0:
self.servo.duty_cycle = self._map(_steer, -1000, 0, self.SERVO_MIN, self.SERVO_MID) * 1000
else:
self.servo.duty_cycle = self._map(_steer, 0, 1000, self.SERVO_MID, self.SERVO_MAX) * 1000