- Design Overview
- Controller Hierarchy
- Main Program (turret.ino)
- State Machine Pattern
- Controller Pattern
- Notes about changes to original Chomp code
Most logic is implemented c++ classes that use the Chomp controller pattern (see below). Each controller implements a state machine (see state machine pattern below).
There are 4 global controllers
- Turret Controller
- Target Tracking Controller
- Radio Controller
- Telemetry Controller
Each of these are a global instances and they are all declared in turret.ino. Then an extern
deceleration is provided in each of those controller's class header file.
The Turret Controller and the TargetTrackingController will consturct new private instances of the controller types they need to do thier job. However, each of these private controllers are owned by a global controller, which should not expose access to those controllers.
The current controller hierachy looks like this
Global Objects Sub Objects (instantiated in global object init())
-------------- --------------------------------------------------
TurretController -------------+--- TurretRotationController --------+--- AutoAimController
|
+--- HammerController
|
+--- FlameThrowerController
|
+--- IMUController
|
+ --- AutoFireController
TargetTrackingController -----+--- TargetAcquisitionController ------+--- LeddarController
RadioController
TelemetryController
The main Arduino file is turret.ino. This file is where the 4 global controllers are declared.
TurretController Turret;
TargetTrackingController TargetTracking;
RadioController Radio;
TelemetryController Telem;
In the setup()
method, that is called when the Arduino is (re)started, the Init()
method of the 4 global controllers is called to get everything started. Then the RestoreParas()
call is issed to the global controllers
void setup()
{
Telem.Init();
Radio.Init();
TargetTracking.Init();
Turret.Init();
Telem.RestoreParams();
TargetTracking.RestoreParams();
Turret.RestoreParams();
}
Then each update that the Arduino issues by calling loop()
will call the Update()
method on each of the 4 global controllers.
void loop()
{
Radio.Update();
TargetTracking.Update();
Turret.Update();
Telem.Update();
}
Each controller will define its states in an enum
in the header file. For example this is what the flameThrowerController defines:
enum controllerState
{
EInit,
ESafe,
EDisabled,
EReadyToFire,
EPulseFlameOn,
EManualFlameOn,
EInvalid = -1
};
The state machine pattern used by all controllers basically consists of code in the main Init()
method, the Update()
method and a private setState(controllerState p_state)
method.
The Init()
method will ensure the state is set and initialize the m_lastUpdateTime
private member variable. Finally, the state will be set to the initial state, which should be name EInit
. All other initilization should happen in the code triggered by transitioning into the EInit
state.
void FlameThrowerController::Init()
{
m_state = EInvalid;
m_lastUpdateTime = micros();
setState(EInit);
}
The Update()
will first do any update calculations needed to detect state transitions, then the current state of the controller is stablized by running through all valid state transitions. Once the state has stablized, then any other update code can run.
void FlameThrowerController::Update()
{
m_lastUpdateTime = micros();
// Do any processing necessary for being able to detect state transitions
...
// Stabilize our state
while(true)
{
controllerState prevState = m_state;
switch (m_state)
{
case EInit:
{
setState(ESafe);
}
break;
case ESafe:
{
// Stay in safe mode for a minimum of k_safeStateMinDt
if (m_lastUpdateTime - m_stateStartTime > k_safeStateMinDt && Radio.IsNominal())
{
setState(EDisabled);
}
}
break;
case EDisabled:
{
if (!Radio.IsNominal())
{
setState(ESafe);
}
else if (Radio.IsFlameEnabled() || Radio.IsFlamePulseEnabled())
{
setState(EReadyToFire);
}
}
break;
...
default:
break;
}
// No more state changes, move on
if (m_state == prevState)
{
break;
}
}
// Now that the state is stable, perform any other update processing
...
}
The LEDDAR M16 produces 16 range measurements spread over a 48° horizontal by 3° vertical fan. It is possible for each measurement to return multiple reflections, we accept the one closest to the robot in each segment. The list of measurements is scanned, looking for features that project radially inwards toward the robot, with some careful handling of edge cases. These segmentations are then compared to the current state of an $\alpha-\beta$ filter and the closest one is used to update the filter state. The rules around initialization and target loss are fiddly, but seem to cope with occasional sensor noise relatively well.
The tracked target is the used for two tasks. The first is keeping the turret pointed at the tracked target. This uses a PID loop providing a velocity command to the turret rotation motor to keep the tracked target at the center of the field of view.
If auto-swing is enabled, the tracked target is also used to predict when to fire the hammer Using the IMU measured rotation speed of the turret and the estimated velocity of the target, the position of the target relative to the turret is estimated at a time in the future corresponding to the hammer swing duration. If the hammer and the target are expected to arrive at the sample place and time, the hammer is automatically triggered.
Files that were just removed because they were no longer necessary
- autodrive.cpp
- drive.cpp
- hold_down.cpp
- rc_pwm.cpp
- selfright.cpp
- telem_message_stream.h
Files that had there contents become a contoller object
New File Old File
------------------------------ ------------------------------
turretController.cpp chomp_main.cpp
autoFireController.cpp autofire.cpp
autoAimController.cpp autoaim.cpp
radioController.cpp sbus.cpp
telemetryController.cpp telem.cpp & xbee.cpp
imuController.cpp imu.cpp
leddarController.cpp leddar_io.cpp
target.cpp object.cpp
targetTrackingController.cpp track.cpp
targetAcquisitionController.cpp targeting.cpp