Fejemis Teensy
Back to fejemis
Drive Teensy
This unit calculates the odometry pose and controls the driving wheels.
Meta info
The default messages from the USB connection is the hbt (heartbeat), like:
hbt 44.97199 90 110 24.95 10 0 hbt 45.37199 90 110 24.95 10 0 hbt 45.77198 90 110 24.95 10 0 hbt 46.17198 90 110 24.95 10 0 hbt 46.57199 90 110 24.96 10 0 hbt 46.97199 90 110 24.91 10 0
The first parameter is Teensy time (seconds); the next is the ID (drive is 90, front is 91); the next is the version (here 110) and battery voltage, here 24.9V.
Steer position from front processor
The command needs to know the steering wheel angle. The steering angle is provided from the front processor by the command (every 15ms):
sub fwl 15
The result is like this (one line every 15ms)
fwl 10.50 0x0 0x3c2 fwl 10.37 0x0 0x3c2 fwl 10.06 0x0 0x13c2 fwl 8.44 0x0 0x13c2 fwl 7.56 0x0 0x3c2 fwl 5.52 0x0 0x3c2 fwl 4.24 0x0 0x13c2 fwl 1.58 0x0 0x13c2 fwl 0.29 0x0 0x3c2 fwl -1.80 0x0 0x3c2 fwl -2.61 0x0 0x13c2 fwl -3.71 0x0 0x13c2 fwl -4.00 0x0 0x3c2 fwl -4.42 0x0 0x3c2 fwl -4.53 0x0 0x13c2 fwl -4.66 0x0 0x13c2 fwl -4.75 0x0 0x3c2 fwl -4.92 0x0 0x3c2 fwl -5.05 0x0 0x13c2 fwl -5.45 0x0 0x13c2 fwl -5.69 0x0 0x3c2 fwl -6.11 0x0 0x3c2 fwl -6.37 0x0 0x13c2 fwl -6.79 0x0 0x13c2
The first value is the steering wheel angle in degrees (positive is left), the next value is error flags (should be zero). The third value is diagnosis (should end with '3C2'):
Bit 11:12 SPI frame counter Bit 10 Error flag broken Hall element (should be 0) Bit 9 Initial AGC settling finished (should be 1) Bit 8 Error flag offset compensation finished (should be 1) Bit 7 Sine offset compensation finished (should be 1) Bit 6 Cosine offset compensation finished (should be 1) Bit 5 Error flag magnitude is below half of target value (should be 0) Bit 4 Warning flag AGC high (should be 0) Bit 3 Warning flag AGC low (should be 0) Bit 2 Error flag CORDIC overflow (should be 0) Bit 1 All magneto core loops finished (should be 1) Bit 0 VDD supply mode: 0: 3V; 1: 5V (should be 0 for 3.3V supply)
Drive command
Both the drive processor and the front processor should be given the same command, like:
rc 1 0.5 65
The first value 1 is intended as the command source for priority (from 1 to 10, where 1 is highest priority), this is not (pt) implemented. The second value 0.5 is the desired velocity in m/s (forward is positive). The third value 65 is the desired turn rate in degrees/s (positive is left turn).
USB Interface
The following list shows the result of sending the fejemis_drive a 'help' command (taken from the GUI when connected to the USB directly):
------ Commands available for drive ------- veri and 'sub ver N' get version number and version string (and 'sub ver N') leave Stop all subscribed data - stop sending data help This help text. Sensor ------- adci and 'sub adc N' Get raw ADC values (12V, current, IR1, IR2, motorV) inti and 'sub int N' Get number of interrupts for ADC1 and ADC2 bati and 'sub bat N' Get battery voltage and current (volt [V], amp [A], lipo cells [N] timei and 'sub time N' Get timing info (cycle time[us], sense [us], +control[us], +logetc [us], adc cycle [us], adc cycles/control) ampi and 'sub amp N' Get current [A], ad4, ad5, adFactor, offset0, offset1 Logger help ----------- log XXX m Start control log every m ms (XXX = pose, time, usb, vel) log pose m Start pose control log every m ms (>= 1 ms) log get Get log (has=0/10 entries) log usb log USB io log stop Stop current log (no effect if log is full already) Drive control ------ rc e v t Set control reference e=1:gamepad, e=2:run, v=velocity (m/s), t=turnrate degree/sec fwl a Tell current front wheel angle (in degrees, 0 is forward, left is positive) confs B F Set steering configuration (B=wheelbase, F=dist to steering wheel [m]) cvdrivei and 'sub cvdrive N' Get current control values cpdrivei and 'sub cpdrive N' Get control parameters. N = use,Kp,useI,taui,ilimit,1,useLeadf,tz,tp,useLeadb,tz,tp,usePre,tz,tp,useULim Ulim confsi and 'sub confs N' Get robot steer config 'confs steer-base wheel-bae' (meters) State settings ------- setid N Set device ID to N (id=90, part of hbt) id=0 for factory settigs. setname string Set device name to string (< 32 chars) should be 'drive'. stop Stop. No control allowed until a start is received start Allow control (default) rcSource=10 (-1=stopped) off T Turn off power (cuts power after T seconds) on Power on (works only if not on battery) hbti and 'sub hbt N' Get time and state (heartbeat) idi and 'sub id N' Get device name and number IMU help ------- gyroc Start gyro calibration (finished=1) magcal Set magnetometer calibration values (offset[3] rotate/scale[9]) acccal Set accelerometer calibration values (offset[3] scale[3]) acccal2 make simple acceleration calibration with horizontal board gyroi and 'sub gyro N' Get current gyro value as 'gyro gx gy gz' (deg/s) gyrooi and 'sub gyroo N' Get gyro offset 'gyroo ox oy oz' acci and 'sub acc N' Get accelerometer values 'acc ax ay az' (m/s^2) accoi and 'sub acco N' Get accelerometer offset values 'acco xo yo zo' (m/s^2) magi and 'sub mag N' Get magnetometer values 'mag mx my mz' (uT) magwi and 'sub magw N' Get magnetometer raw values 'magw mx my mz' (uT) magoi and 'sub mago N' Get magnetometer offset values 'mago xo yo zo r11 r12 r13 r21 ... r33' (uT) imuposei and 'sub imupose N' Get IMU-based pose 'imupose roll pitch yaw (radians) Encoder help ------- enc0 Reset pose to (0,0,0) confw rl rr g t wb Set configuration (radius [m] gear [>=1] encTick [n/rev] wheelbase [m]) enci and 'sub enc N' Get encoder value 'enc encoder interrupts' (integer) posei and 'sub pose N' Get current pose 'pose t x y h' (sec,m,m,rad) veli and 'sub vel N' Get velocity 'left right' (m/s) confi and 'sub conf N' Get robot conf (radius, radius, gear, pulsPerRev, wheelbase, sample-time) TOF distance help ------- tofdi and 'sub tofd N' Get distance in meters from time of flight (TOF) sensor tof1i and 'sub tof1 N' Make TOF distance measurement HC-SR04 distance help ------- hcsr Trigger new measurement sr04i and 'sub sr04 N' Get distance in meters from HC-SR04 sensor EE (configuration flash) -------- eew save configuration to EE-Prom (flash) eer Read configuration from EE-Prom
Each section relates to a software module.
Turning control
The turning in the drive unit receives two messages:
- Linear velocity desired (v in m/sec) and turnrate w in radians/sec (RC message)
- Angle of front-wheel (in degrees) (from fwl message)
The angle of the front wheel is used to calculate a turning radius d (for the other wheel).
d = f/tan(theta) + B/2
where f is the distance to the steering wheel, theta is the angle of the front wheel and B is the drive base, the distance between the driving wheels.
The turn radius d is from the turning centre to the other wheel (the fastest wheel). This wheel should have the desired velocity v2. The outer wheel velocity should not be zero. When the desired velocity v is zero, the speed of the outer wheel is therefore combined with the turn rate w as:
v2 = sqrt(v^2 + w^2)
With the sign from the linear velocity v. w is here assumed to be in radians, and with the robot's wheelbase (B = 0.4m), it seems OK.
The inner wheel should then have a velocity v1 dependent on the turn radius d, the wheelbase and the velocity of the other wheel v2
v1 = v2 - v2 * B / d
The sign of theta determines which wheel should have the higher velocity.
Front Teensy
The front Teensy controls the steering angle of the front wheel.
Meta info
The default messages from the USB connection is the hbt (heartbeat), like:
hbt 34.7925 91 100 0.00 1 0.011 hbt 35.1923 91 100 0.00 1 0.011 hbt 35.5921 91 100 0.00 1 0.009 hbt 35.9918 91 100 0.00 1 0.009 hbt 36.3916 91 100 0.00 1 0.009
The first parameter is Teensy time (seconds); the next is the ID (drive is 90, front is 91), followed by the version number (100); see the source code for the rest.
Steering command
The steering command is the same as for the drive processor:
rc 1 0.5 65
The first value 1 is intended as the command source for priority (from 1 to 10, where 1 is highest priority), this is not (pt) implemented. The second value 0.5 is the desired velocity in m/s (forward is positive). The third value 65 is the desired turn rate in degrees/s (positive is left turn).
Conversion to angle
The desired front wheel angle is then
- angle = atan2(S * w, v);
Where w is the turn rate in radians/s, v is the desired velocity in m/s and S is the distance to the steering wheel.
The steering angle is then converted back to degrees/s as control reference.
Steer controller
MATLAB estimates the steer controller transfer function with an added ZOH approximation of 25 ms. This estimate fits reasonably with how fast the servo (a HerculeX DRS0101) can handle commands.
The resulting Bodeplot is:
Where input is velocity control value for the Herculex in range -1000..1000 and output is steering angle in degrees.
A P-Lead controller is designed with a phase margin of 80 degrees and a Lead aplha=0.3 to give
- Crossover frequency 12 radians/s
- Kp = 43
- tau_d = 0.16 s
Actual steering angle
The actual steering angle should be used by the drive system. This angle subscribed by the command every 15 ms by:
sub fwl 15
See the drive command above for more details.
Lift brush system
A linear actuator controls the height of the steering wheel.
The command that controls the motor is, e.g.:
motv 6 4
Where 6 is the motor voltage (-12..12V), a positive value lifts the wheel. The second parameter 4 is the time in seconds for the motor voltage.
It takes about 12 seconds from end to end at 6V actuator voltage.
The lead (brush battery) voltage sensing is not implemented.
USB interface
The allowed commands to the front Teensy are:
help This help text.
------ State settings -------
setid N Set device index to N (id=91, part of hbt).
setname string Set device name to string (< 32 chars) should be 'front'.
brush 0/1 Turn on/off for power to the brush
hbti and 'sub hbt N' Get time and state (heartbeat)
idi and 'sub id N' Get device ID (name and number)
----- LED control -----
led N R G B Set led number N (0..11) to this RGB value (0..255).
ledb R G B Set band color RGB value (0..255).
leds Set to default values
ledon V Turn LED strip op V=1 or off V=0
---- EE (configuration flash) --------
eew save configuration to EE-Prom (flash)
eer Read configuration from EE-Prom
------ Motor help -------
motv m1 time Set motor voltage -12.0..12.0 for this time (in seconds)
pwmf F Set PWM frequency (is=15000)
moti and 'sub mot N' Get motor voltage 'mot m1(V) 0 0 0 0 PWM-frq(Hz)'
------ Encoder help -------
enc0 Reset position and encoder to 0
------ Sensor -------
adci and 'sub adc N' Get raw ADC values (12V, current, IR1, IR2, motorV)
inti and 'sub int N' Get number of interrupts for ADC1 and ADC2
bati and 'sub bat N' Get battery voltage and current (volt [V], amp [A], lipo cells [N]
timei and 'sub time N' Get timing info (cycle time[us], sense [us], +control[us], +logetc [us], adc cycle [us], adc cycles/control)
curi and 'sub cur N' Get current [A], ad4, ad5, adFactor, offset0, offset1
------ IR distance -------
irc A1 B1 A2 B2 Set calibration values A=13cm, B=50cm (sensor 1 and 2)
iri and 'sub ir N' Get IR and calibration data 'irc dist1 dist2 ... (meter)
------ Steering servo -------
svp P T Set servo position (P=0..1000, time T 0..254).
svv V T Set servo velocity (V=0..1000, acc-time T= 0..254).
svat T Set acceleration time (T=0 rectangle, M>0 is M*11.2ms) - no effect in vel mode?.
svar R Set acceleration ratio (R=0 fast, R>slower) - no effect in vel mode?.
svjt T Set servo jog time (T=0..255 x 11.2ms)
svb B Set servo communication Baud rate (default=115200)
svscan Id Scan for servos on serial bus from Id = 0..254.
svpi and 'sub svp N' Get current servo position
svjti and 'sub svjt N' Get jog time, accRatio, accMax time ('svjt jog ratio max')
------ Front wheel position -------
pds Print debug string from AS driver.
read V Read angle or not (debug).
fwoffset A I Set front wheel offset (deg), I=1 invert angle.
fwli and 'sub fwl N' Get current front wheel angle 'fwl ang(deg) err disg'
fwoi and 'sub fwo N' Get front wheel offset 'fwo ofset reverse'
------ Steering control -------
rc S v w Drive command: S is source 1..10, v: velocity m/s, w: turnrate degrees/s (positive is left)
cssteer E ... Set front angle controller (see ucontrolbase.cpp for params).
confs B S Set wheel base and steer base (in meters)
cvsteeri and 'sub cvsteer N' Get current control values
cpsteeri and 'sub cpsteer N' Get control parameters
confsi and 'sub confs N' Get robot steer config 'confs steer-base wheel-bae' (meters)
Each section relates to a software module.
Turning control
The front wheel receives the movement command (RC) with
- desired linear velocity v
- desired turn rate w
The associated turn angle theta of the front wheel can then be calculated as
theta = atan2(f * w, v)
where f is the distance to the front wheel, w is the desired turn rate, B is the wheelbase between the driving wheels and v is the desired linear velocity.
The angle is presented to a servo control loop and the actual steering angle is measured and send to the drive Teensy (through the bridge).
The theta angle is allowed in the range of -90 deg to +90 deg.
Teensy Software structure
Figure: The Teensy software is structured with a main loop and a number of units. After reset all units are initialized in a setup function, after that the main loop is entered. The main loop services the USB and send commands to the units for decoding. At regular intervals, the sample clock tick, all units are called to execute any sample time function. Most units are interfaces to external devices such as IMU, motor drive or distance sensor. There is further support units for e.g. control.
Setup - loop overview
The setup and loop structure follows the Arduino sketch format. The file is a C++ file as the compilation is using a Makefile rather then the Arduino IDE.
The main file (main.cpp) code has this structure (shortened for clarity)
void setup() // INITIALIZATION
{
usb.setup();
led.setup();
imu.setup();
enc.setup();
sensor.setup();
motor.setup();
state.setup();
}
void loop(void)
{
usb.send("# Starting main loop\n");
while ( true )
{ // main loop
usb.tick(); // incoming command service
if ( startNewCycle ) // start of new control cycle
{ // mostly every 1ms
imu.tick(); // for heading estimate
sensor.tick(); // AD converter, e.g. battery maintenance
irdist.tick(); // distance sensor
enc.tick(); // wheel encoder and odometry
state.tick(); // e.g. emergency stop
control.tick(); // feedback control
motor.tick(); // motor control
}
}
}
Unit structure
Figure: Each unit is coded in a separate file and has about the same structure. The unit is a class that holds the data from or to the device. The device is configured when the setup is called. The data from the device, or calculated from the data, is made available for subscription using a subscription class called USubss. The device is accessed using a standard Arduino-type library, for e.g. a I2C or an A/D interface. The unit may have configuration that need to be saved, e.g. calibration data or device mode that can be set from the interface, but needs to be saved.
Command decode
Commands from the USB connection is line oriented and consist of one line of text starting with a keyword. The line ends with a "new-line" (\n) or a combination if "carriage-return" (\r) and "new-line".
Request of data can be just the keyword, e.g.:
help (for on-line help - list of available commands) gyroi (for one-time information about gyro measurement) bati (for one-time info about battery voltage)
or a keyword with parameters
ctrli vel (to get all the PID control parameters for velocity control)
Issue commands or send data, e.g.:
motv m1 m2 (to set motor 1 (left) and motor 2 (right) to a specified voltage. rc e v t (to do remote drive control with velocity "v" and turn-rate "t") "e" should be 2 for non-manual control.
Subscription
From the USB most data can be subscribed with the a line starting with the keyword "sub", e.g.:
sub gyro 10 (for subscription to gyro data every 10ms) sub bat 200 (for subscription to battery status every 200ms)
The available subscriptions are generated in the "setup" part of the unit and holds also the on-line help text.
E.g. the wheel encoder unit has this subscription setup code (in enc.cpp):
void setup()
{
...
addPublistItem("enc", "Get encoder value 'enc encoder interrupts' (integer)");
addPublistItem("pose", "Get current pose 'pose t x y h' (sec,m,m,rad)");
addPublistItem("vel", "Get velocity 'left right' (m/s)");
addPublistItem("conf", "Get robot configuration (radius, radius, gear, pulsPerRev, wheelbase, sample-time)");
}
At tick-time subscriptions are serviced, and if it is time, the "sendData(item)" is called, e.g. for the wheel encoder unit:
void UEncoder::sendData(int item)
{
if (item == 0)
sendEncStatus();
else if (item == 1)
sendPose();
else if (item == 2)
sendVelocity();
else if (item == 3)
sendRobotConfig();
}
Configuration save
The Teensy has 4096 bytes of configuration flash memory, this is used to store settings and calibration values.
The use of this memory is controlled by the "config save" unit in the file "eeconfig.cpp".
The unit is controlled by two commands "eew" (save) and "eer" (load). On reset the configuration is loaded.
If the ID number is set to "0", all configurations are reset (to factory settings). To save any configuration, set ID number with the command "setid N" where N is a number > 0.
Configuration is saved as a stack, and each unit must therefore save and load exactly the same amount of data. Add load and save to the list in "eeconfig.cpp" when new units are added that need configuration saved.
The load and save are like e.g.:
void UImu::eePromSave()
{
eeConfig.push32(offsetGyro[0]);
eeConfig.push32(offsetGyro[1]);
eeConfig.push32(offsetGyro[2]);
// accelerometer
for (int i = 0; i < 3; i++)
eeConfig.pushFloat(accOffset[i]);
}
void UImu::eePromLoad()
{
offsetGyro[0] = eeConfig.read32();
offsetGyro[1] = eeConfig.read32();
offsetGyro[2] = eeConfig.read32();
gyroOffsetDone = true;
//
for (int i = 0; i < 3; i++)
accOffset[i] = eeConfig.readFloat();
}
Update tick
The timing is based on a 10 us timer and the following is defined (in "main.cpp")
// control period is time between control calls // and is in units of 10us, ie 125 is 1.25ms or 800Hz #define CONTROL_PERIOD_10us 100 // sample time in seconds #define SAMPLETIME (0.00001 * CONTROL_PERIOD_10us)
The SAMPLETIME is a floating point number in seconds. The example above is for 1ms sample time.
All units tick() function is called at this interval. For new units the call must be added in the main code, like for the motor unit:
void UMotor::tick()
{ //
implementMotorVoltage();
// subscription
subscribeTick();
}
It there is data subscription associated with this unit, then the subscribeTick() must be called to service the subscriptions.

