// Features:
// - Complete PID loop
// - Optional proportional feed-forward for open-loop control and wind-up minimization
// - Wind-up limiting under overload conditions
// - Torque throttling under overload
// - TWI interface with PnP support
// - Parameter storage in EEPROM including TWI address
// - Acceleration, deceleration limits
// - Speed integration to estimate travelled distance
// - Add 'go to distance' mode with trapezoid speed-profiles - Needs testing
// - Added 32-bit and 16-bit atomic reads and writes. Note: 8-bit atomic writes are NOT supported
// - Added support for duty cycle throttling (basically torque-throttling)
// - Added current reading and max current detection
// - Added per cycle over-current detection and early-termination of cycle
// - Added servo-type operation with pot-based position feedback
// TODO:
// - Add current integration for power usage estimation
// - Add serial interface
// - Add servo-type (PWM) interface
// - Add optical encoder support
// - When sampling for fast-collapse high-side and during the on-state: we only need two states - the high-side should be on already
// - Switching between high- and low-collapse modes to equalize catch-diode load - make this user-selectable
// - Detect continous-current mode and do something about it!
// TODO TEST:
// - Servo mode in all four modes
// - Freewheeling and back-EMF in all four modes
// - Braking
// BUGS:
// - back-EMF measurment is different in fast-collapse and low-collapse modes. This make control leading to different speeds in the two modes.
#define BOARD_umHBridge
#define H_BRIDGE
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include "common.h"
#include "opled.h"
#include "usart.h"
#include "twi_aap.h"
#include "eeprom.h"
void DebugStat();
// This is an 8-bit PWM
namespace PWM1 {
enum eClkSrc {
ClkNone = 0,
Clk = (1 << CS10),
ClkDiv1 = (1 << CS10),
ClkDiv8 = (1 << CS11),
ClkDiv64 = (1 << CS11) | (1 << CS10),
ClkDiv256 = (1 << CS12),
ClkDiv1024 = (1 << CS12) | (1 << CS10),
ClkExtFall = (1 << CS12) | (1 << CS11),
ClkExtRise = (1 << CS12) | (1 << CS11) | (1 << CS10)
};
void inline Init(eClkSrc aClkSrc) {
SETBIT(DDRB,0x02|0x04); // Preprare PortB to output to handle disables
CLRBIT(PORTB,0x02|0x04); // Set port bits to 0 so that disable will work correctly
CLRBIT(PRR,PRTIM0);
TCCR1A = (1 << WGM10);
TCCR1B = aClkSrc | (1 << WGM12);
OCR1A = 0x0000;
OCR1B = 0x0000;
ICR1 = 0x0000;
TCNT1 = 0x0000;
}
void inline DisableChA() { CLRBIT(TCCR1A,(1 << COM1A1)); }
void inline DisableChB() { CLRBIT(TCCR1A,(1 << COM1B1)); }
void inline DisableChAB() { CLRBIT(TCCR1A,(1 << COM1A1) | (1 << COM1B1)); }
void inline EnableChA() { SETBIT(TCCR1A,(1 << COM1A1)); }
void inline EnableChB() { SETBIT(TCCR1A,(1 << COM1B1)); }
void inline EnableChAB() { SETBIT(TCCR1A,(1 << COM1A1) | (1 << COM1B1)); }
bool inline IsChAEnabled() { return TESTBIT(TCCR1A,(1 << COM1A1)) != 0; }
bool inline IsChBEnabled() { return TESTBIT(TCCR1A,(1 << COM1B1)) != 0; }
bool inline IsChAOrBEnabled() { return TESTBIT(TCCR1A,(1 << COM1A1) | (1 << COM1B1)) != 0; }
// We never set the high bits to anything but 0, so the high-byte in the TEMP register doesn't need to be set.
void inline SetChannelA(uint8_t aValue) { OCR1AL = aValue; }
uint8_t inline GetChannelA() { return OCR1AL; }
void inline SetChannelB(uint8_t aValue) { OCR1BL = aValue; }
uint8_t inline GetChannelB() { return OCR1BL; }
void inline EnableIRQ_A() { SETBIT(TIMSK1,(1 << OCIE1A)); }
void inline EnableIRQ_B() { SETBIT(TIMSK1,(1 << OCIE1B)); }
void inline EnableIRQ_AB() { SETBIT(TIMSK1,(1 << OCIE1A) | (1 << OCIE1B)); }
void inline EnableIRQ_Overflow() { SETBIT(TIMSK1,(1 << TOIE1)); }
void inline EnableOnlyIRQ_A() { TIMSK1 = (1 << OCIE1A); }
void inline EnableOnlyIRQ_B() { TIMSK1 = (1 << OCIE1B); }
void inline EnableOnlyIRQ_Overflow() { TIMSK1 = (1 << TOIE1); }
void inline DisableIRQ_A() { CLRBIT(TIMSK1,(1 << OCIE1A)); }
void inline DisableIRQ_B() { CLRBIT(TIMSK1,(1 << OCIE1B)); }
void inline DisableIRQ_Overflow() { CLRBIT(TIMSK1,(1 << TOIE1)); }
void inline DisableIRQ_All() { TIMSK1 = 0; }
bool inline IsPendingIRQ_A() { return (TESTBIT(TIFR1,(1 << OCF1A)) != 0); }
bool inline IsPendingIRQ_B() { return (TESTBIT(TIFR1,(1 << OCF1B)) != 0); }
bool inline IsPendingIRQ_Overflow() { return (TESTBIT(TIFR1,(1 << TOV1)) != 0); }
void inline ClearPendingIRQ_A() { TIFR1 = (1 << OCF1A); }
void inline ClearPendingIRQ_B() { TIFR1 = (1 << OCF1B); }
void inline ClearPendingIRQ_Overflow() { TIFR1 = (1 << TOV1); }
void inline ClearPendingIRQ_All() { TIFR1 = (1 << OCF1A) | (1 << OCF1B) | (1 << TOV1); }
}
namespace ADConv {
enum eRef {
RefInt = (1 << REFS0) | (1 << REFS1),
RefVcc = (1 << REFS0),
RefExt = 0
};
enum eAdjust {
LeftAdjust = ADLAR,
RightAdjust = 0
};
#if defined MEGA_BRIDGE
const uint8_t Ch_MotorA = 7;
const uint8_t Ch_MotorB = 2;
const uint8_t Ch_MotorCurrent = 6;
const uint8_t Ch_ServoPot = 3;
const uint8_t Ch_Battery = 1;
const eRef Ref_MotorA = RefVcc;
const eRef Ref_MotorB = RefVcc;
const eRef Ref_MotorCurrent = RefInt;
const eRef Ref_ServoPot = RefVcc;
const eRef Ref_Battery = RefVcc;
#elif defined H_BRIDGE
const uint8_t Ch_MotorA = 7;
const uint8_t Ch_MotorB = 2;
const uint8_t Ch_MotorCurrent = 6;
const uint8_t Ch_ServoPot = 3;
const eRef Ref_MotorA = RefInt;
const eRef Ref_MotorB = RefInt;
const eRef Ref_MotorCurrent = RefInt;
const eRef Ref_ServoPot = RefVcc;
const eRef Ref_Battery = RefVcc;
#elif defined SERVO_BRAIN
const uint8_t Ch_MotorA = 0;
const uint8_t Ch_MotorB = 2;
const uint8_t Ch_MotorCurrent = 6;
const uint8_t Ch_ServoPot = 3;
const eRef Ref_MotorA = RefVcc;
const eRef Ref_MotorB = RefVcc;
const eRef Ref_MotorCurrent = RefVcc;
const eRef Ref_ServoPot = RefVcc;
const eRef Ref_Battery = RefVcc;
#else
#error HW version is not specified
#endif
const uint8_t ADCSRA_BaseValue = (1 << ADEN) | (1 << ADATE) | (1 << ADPS2) | (1 << ADPS1);
static inline void Init() {
ADMUX = (1<< REFS0) | 15; // AVCC is the default reference
ADCSRA = ADCSRA_BaseValue;
ADCSRB = 0; // Free-running mode
DIDR0 = (1 << Ch_MotorA) | (1 << Ch_MotorB) | (1 << Ch_MotorCurrent);
}
static inline void SetChannel(uint8_t aChannel) {
ADMUX = (ADMUX & 0xf0) | (aChannel & 0x0f);
}
static inline void SetChannel(uint8_t aChannel,eRef aRef) {
ADMUX = (ADMUX & 0x20) | (aChannel & 0x0f) | aRef;
}
static inline void SetChannel(uint8_t aChannel,eRef aRef,eAdjust aAdjust) {
ADMUX = (aChannel & 0x0f) | aRef | aAdjust;
}
static inline void StartConversion(uint8_t aChannel) {
SetChannel(aChannel);
ADCSRA = ADCSRA_BaseValue | (1 << ADSC) | (1 << ADIE);
}
static inline void StartConversion(uint8_t aChannel,eRef aRef) {
SetChannel(aChannel,aRef);
ADCSRA = ADCSRA_BaseValue | (1 << ADSC) | (1 << ADIE);
}
static inline void StartConversion(uint8_t aChannel,eRef aRef, eAdjust aAdjust) {
SetChannel(aChannel,aRef,aAdjust);
ADCSRA = ADCSRA_BaseValue | (1 << ADSC) | (1 << ADIE);
}
static inline void StopConversion() {
CLRBIT(ADCSRA,(1 << ADEN)); // This might delete the pending IRQ request, but we don't really care
}
static inline uint8_t GetChannel() {
return ADMUX & 0x0f;
}
static uint16_t GetSample() {
while((ADCSRA & (1 << ADIF)) == 0); // Wait until conversion finishes
uint16_t RetVal = ADC;
SETBIT(ADCSRA,ADIF); // Clear the pending interrupt request (though we're polling)
return RetVal;
}
static inline uint16_t FastGetSample() {
return ADC;
}
}
namespace EEPROM_layout {
const uint16_t DataRecord_Ofs = 0x00; // size is sizeof(HBridge::DataRecord)
const uint16_t DataValid_Ofs = 0xfe;
}
template<typename T> inline T min(T aA,T aB) { return (aA>aB)?aB:aA; }
template<typename T> inline T max(T aA,T aB) { return (aA>aB)?aA:aB; }
#define SHOOT_THROUGH_DELAY for(int i=0;i<10000;++i);
namespace HBridge {
enum eSampleStates {
SampleState_PreFastCollapse = 0,
SampleState_FastCollapse,
SampleState_PostFastCollapse,
SampleState_PreSampleBase,
SampleState_PreSampleBase2,
SampleState_SampleBase,
SampleState_PreSearchMax,
SampleState_PreSamplePot,
SampleState_SamplePot,
SampleState_SearchMax,
SampleState_SearchMin,
SampleState_PreCurrentSample,
SampleState_CurrentSample1,
SampleState_CurrentSample2,
SampleState_CurrentSample3,
// off-band battery sampling for fast-high-side collapse mode
SampleState_PreSampleBat,
SampleState_SampleBat
};
enum eOperatingModes {
OperatingMode_Speed = 0,
OperatingMode_Servo = 1
};
const int16_t RequestFreewheel = 0x4000;
const int16_t RequestBrake = 0x4001;
const uint8_t EEPROMDataValid = 0x01;
const uint8_t GuardTime = 255 - 47; // Maximum allowed duty cycle
const uint8_t ControlTime = 255 - 10;
#if defined MEGA_BRIDGE
const uint8_t LoBMask = (0x01 << 2);
const uint8_t LoAMask = (0x01 << 1);
const uint8_t HiBMask = (0x01 << 5);
const uint8_t HiAMask = (0x01 << 6);
const eOperatingModes DefaultOperatingMode = OperatingMode_Speed;
const int16_t Def_IFactor = 0;
const int16_t Def_PFactor = 0;
const int16_t Def_DFactor = 0;
const int16_t Def_PFFactor = 0x0100;
#elif defined H_BRIDGE
const uint8_t LoBMask = (0x01 << 2);
const uint8_t LoAMask = (0x01 << 1);
const uint8_t HiBMask = (0x01 << 5);
const uint8_t HiAMask = (0x01 << 6);
const eOperatingModes DefaultOperatingMode = OperatingMode_Speed;
const int16_t Def_IFactor = 0;
const int16_t Def_PFactor = 0;
const int16_t Def_DFactor = 0;
const int16_t Def_PFFactor = 0x0100;
#elif defined SERVO_BRAIN
const uint8_t LoBMask = (0x01 << 2);
const uint8_t LoAMask = (0x01 << 1);
const uint8_t HiBMask = (0x01 << 6);
const uint8_t HiAMask = (0x01 << 5);
const eOperatingModes DefaultOperatingMode = OperatingMode_Servo;
const int16_t Def_IFactor = 0;
const int16_t Def_PFactor = 0x0200;
const int16_t Def_DFactor = 0;
const int16_t Def_PFFactor = 0;
#else
#error No HW version is specified!
#endif
enum CollapseStates {
FastCollapseHighSide = 0,
FastCollapseLowSide,
SlowCollapseHighSide,
SlowCollapseLowSide,
CollapseStateMask = 0x0f,
CollapseStateAutoCycle = 0x10
};
uint8_t ADSampleIdx;
struct PublicData {
// The layout of this record IS important.
// This is the public interface that is accessible through the TWI interface
// Speed control request signal R/W (Scaled between -0x3fff and 0x3fff
// 0x4000 is freewheeling and 0x4001 is braking
int16_t RequestValue;
// PID control loop parameters R/W
int16_t IFactor;
int16_t PFactor;
int16_t DFactor;
int16_t PFFactor;
int16_t SampleOffset;
// Request change limits (acceleration limits) R/W
int16_t MaxPositiveChange;
int16_t MaxNegativeChange;
// Travel (distance counter) R/W
int32_t Distance;
// Travel cutoff R/W
int32_t FwDistanceLimit;
int32_t BwDistanceLimit;
// Current estimated distance required to stop (R/O)
int32_t DistanceToStop;
// Modified request (R/O)
int16_t CurrentRequest;
// Command given to the H-bridge R/O
int16_t Command;
// PID loop working set R/O
int16_t IValue;
int16_t LastError;
int16_t Error;
// Last Back-EMF sample R/O
int16_t VoltageSample;
// Back-EMF sampling code working set R/O
int16_t BaseValue;
int16_t SampleCnt_Snapshot;
int16_t MinValue_Snapshot;
// Private members: not part of the communication interface, but added to this structure for potential debugging
int16_t MinValue;
int16_t MaxValue;
int16_t SampleCnt;
int16_t OriginalRequestValue;
uint8_t SampleState;
uint8_t DutyCycleThrottle;
uint8_t NewData;
uint8_t IsForward;
uint16_t CurrentMax;
uint16_t CurrentDelta;
uint16_t CurrentTemp;
uint16_t CurrentMaxSearch;
uint8_t ADBufferEnable;
uint8_t ADBufferEnableHost;
uint16_t CurrentLimit;
uint8_t OperatingMode;
uint8_t CollapseState;
uint16_t ADBuffer[80];
} DataRecord;
inline uint8_t* GetDataRecord8(uint8_t aOfs) { return ((uint8_t *)&DataRecord)+aOfs; }
inline uint16_t* GetDataRecord16(uint8_t aOfs) { return (uint16_t *)(((uint8_t *)&DataRecord)+aOfs); }
inline uint32_t* GetDataRecord32(uint8_t aOfs) { return (uint32_t *)(((uint8_t *)&DataRecord)+aOfs); }
const inline size_t GetDataRecordSize() { return sizeof(DataRecord); }
uint8_t GetDataElementSize(uint8_t aOfs) {
#define FIELD_OFFSET(aField) ((uint8_t)((size_t)&(HBridge::DataRecord.aField)-(size_t)&HBridge::DataRecord))
#define OFFSET_ENTRY(aField) if (aOfs >= FIELD_OFFSET(aField)) return sizeof(DataRecord.aField); else
#define OFFSET_ENTRY_2(aField,aSize) if (aOfs >= FIELD_OFFSET(aField)) return (aSize); else
OFFSET_ENTRY_2(ADBuffer,sizeof(DataRecord.ADBuffer[0]))
OFFSET_ENTRY(CollapseState)
OFFSET_ENTRY(OperatingMode)
OFFSET_ENTRY(CurrentLimit)
OFFSET_ENTRY(ADBufferEnableHost)
OFFSET_ENTRY(ADBufferEnable)
OFFSET_ENTRY(CurrentMaxSearch)
OFFSET_ENTRY(CurrentTemp)
OFFSET_ENTRY(CurrentDelta)
OFFSET_ENTRY(CurrentMax)
OFFSET_ENTRY(IsForward)
OFFSET_ENTRY(NewData)
OFFSET_ENTRY(DutyCycleThrottle)
OFFSET_ENTRY(SampleState)
OFFSET_ENTRY(OriginalRequestValue)
OFFSET_ENTRY(SampleCnt)
OFFSET_ENTRY(MaxValue)
OFFSET_ENTRY(MinValue)
OFFSET_ENTRY(MinValue_Snapshot)
OFFSET_ENTRY(SampleCnt_Snapshot)
OFFSET_ENTRY(BaseValue)
OFFSET_ENTRY(VoltageSample)
OFFSET_ENTRY(Error)
OFFSET_ENTRY(LastError)
OFFSET_ENTRY(IValue)
OFFSET_ENTRY(Command)
OFFSET_ENTRY(CurrentRequest)
OFFSET_ENTRY(DistanceToStop)
OFFSET_ENTRY(BwDistanceLimit)
OFFSET_ENTRY(FwDistanceLimit)
OFFSET_ENTRY(Distance)
OFFSET_ENTRY(MaxNegativeChange)
OFFSET_ENTRY(MaxPositiveChange)
OFFSET_ENTRY(SampleOffset)
OFFSET_ENTRY(PFFactor)
OFFSET_ENTRY(DFactor)
OFFSET_ENTRY(PFactor)
OFFSET_ENTRY(IFactor)
OFFSET_ENTRY(RequestValue)
return 1;
#undef FIELD_OFFSET
#undef OFFSET_ENTRY
#undef OFFSET_ENTRY2
}
void FreeWheel();
inline void SaveSettings() {
uint8_t *Data = (uint8_t *)&DataRecord;
for(uint8_t i=EEPROM_layout::DataRecord_Ofs;i<EEPROM_layout::DataRecord_Ofs+sizeof(DataRecord);++i,++Data) {
EEPROM::SetByte(i,*Data);
}
EEPROM::SetByte(EEPROM_layout::DataValid_Ofs,EEPROMDataValid);
EEPROM::Wait();
}
inline void Init() {
// Init to all channels off - this is the free-wheeling state
PWM1::Init(PWM1::ClkDiv256); // Set clock to clkI/O / 256 -> full cycle is around 120Hz with an 8MHz clock
//PWM1::Init(PWM1::ClkDiv64); // Set clock to clkI/O / 64 -> full cycle is around 500Hz with an 8MHz clock
CLRBIT(PORTD,HiAMask);
CLRBIT(PORTD,HiBMask);
CLRBIT(PORTB,LoBMask);
CLRBIT(PORTB,LoBMask);
SETBIT(DDRB,LoBMask);
SETBIT(DDRB,LoAMask);
SETBIT(DDRD,HiBMask);
SETBIT(DDRD,HiAMask);
// No control loop - this is the default.
DataRecord.IFactor = Def_IFactor;
DataRecord.PFactor = Def_PFactor;
DataRecord.DFactor = Def_DFactor;
DataRecord.PFFactor = Def_PFFactor;
DataRecord.SampleOffset = 0;
DataRecord.CollapseState = FastCollapseHighSide;
DataRecord.OperatingMode = DefaultOperatingMode;
// No acceleration control - this is the default.
DataRecord.MaxPositiveChange = 0x7fff;
DataRecord.MaxNegativeChange = 0x7fff;
if (EEPROM::GetByte(EEPROM_layout::DataValid_Ofs) == EEPROMDataValid) {
uint8_t *Data = (uint8_t *)&DataRecord;
for(uint8_t i=EEPROM_layout::DataRecord_Ofs;i<EEPROM_layout::DataRecord_Ofs+sizeof(DataRecord);++i,++Data) {
*Data = EEPROM::GetByte(i);
}
}
DataRecord.IValue = 0;
DataRecord.Error = 0;
DataRecord.LastError = 0;
DataRecord.RequestValue = RequestFreewheel;
DataRecord.OriginalRequestValue = RequestFreewheel;
DataRecord.IsForward = true;
DataRecord.NewData = false;
DataRecord.Distance = 0;
DataRecord.FwDistanceLimit = 0x7fffffff;
DataRecord.BwDistanceLimit = 0x80000000;
DataRecord.DutyCycleThrottle = GuardTime;
DataRecord.CurrentLimit = 0xffff; // Anything over 0x3ff is OFF
for(uint8_t i = 0;i<sizeof(DataRecord.ADBuffer)/sizeof(DataRecord.ADBuffer[0]);++i) {
DataRecord.ADBuffer[i] = 0;
}
ADSampleIdx = 0;
DataRecord.ADBufferEnable = 0;
DataRecord.ADBufferEnableHost = true;
FreeWheel();
PWM1::EnableIRQ_AB();
DataRecord.SampleState = SampleState_CurrentSample3;
ADConv::StartConversion(ADConv::Ch_MotorCurrent);
}
// If made inline GCC generates invalid code
void Forward(uint8_t aSpeed) {
if (aSpeed > DataRecord.DutyCycleThrottle) aSpeed = DataRecord.DutyCycleThrottle;
if (aSpeed > GuardTime) aSpeed = GuardTime;
// Allways clear first, than set
CLRBIT(PORTB,LoBMask);
CLRBIT(PORTB,LoAMask);
CLRBIT(PORTD,HiBMask);
SETBIT(PORTD,HiAMask);
PWM1::SetChannelB(aSpeed);
PWM1::SetChannelA(ControlTime);
PWM1::DisableChAB();
if (aSpeed > 0) PWM1::EnableChB();
DataRecord.IsForward = true;
}
void Backward(uint8_t aSpeed) {
if (aSpeed > DataRecord.DutyCycleThrottle) aSpeed = DataRecord.DutyCycleThrottle;
if (aSpeed > GuardTime) aSpeed = GuardTime;
// Allways clear first, than set
CLRBIT(PORTB,LoBMask);
CLRBIT(PORTB,LoAMask);
CLRBIT(PORTD,HiAMask);
SETBIT(PORTD,HiBMask);
PWM1::SetChannelA(aSpeed);
PWM1::SetChannelB(ControlTime);
PWM1::DisableChAB();
if (aSpeed > 0) PWM1::EnableChA();
DataRecord.IsForward = false;
}
inline void FastFieldCollapseHighSide() {
if (PWM1::IsChAEnabled()) {
CLRBIT(PORTD,HiBMask);
CLRBIT(PORTD,HiAMask);
SETBIT(PORTD,HiAMask);
} else if (PWM1::IsChBEnabled()) {
CLRBIT(PORTD,HiAMask);
CLRBIT(PORTD,HiBMask);
SETBIT(PORTD,HiBMask);
}
}
inline void FastFieldCollapseLowSide() {
if (PWM1::IsChAEnabled()) {
CLRBIT(PORTD,HiAMask);
CLRBIT(PORTD,HiBMask);
SHOOT_THROUGH_DELAY;
CLRBIT(PORTB,LoAMask);
PWM1::DisableChAB();
SETBIT(PORTB,LoBMask);
} else if (PWM1::IsChBEnabled()) {
CLRBIT(PORTD,HiAMask);
CLRBIT(PORTD,HiBMask);
SHOOT_THROUGH_DELAY;
CLRBIT(PORTB,LoBMask);
PWM1::DisableChAB();
SETBIT(PORTB,LoAMask);
}
}
inline void SlowFieldCollapseHighSide() {
}
inline void SlowFieldCollapseLowSide() {
if (PWM1::IsChAEnabled()) {
CLRBIT(PORTD,HiAMask);
CLRBIT(PORTD,HiBMask);
SHOOT_THROUGH_DELAY;
CLRBIT(PORTB,LoBMask);
PWM1::DisableChAB();
SETBIT(PORTB,LoAMask);
} else if (PWM1::IsChBEnabled()) {
CLRBIT(PORTD,HiAMask);
CLRBIT(PORTD,HiBMask);
SHOOT_THROUGH_DELAY;
CLRBIT(PORTB,LoAMask);
PWM1::DisableChAB();
SETBIT(PORTB,LoBMask);
}
}
inline void ResetAfterFastCollapse() {
if (TESTBIT(PORTD,HiAMask) != 0) {
CLRBIT(PORTD,HiAMask);
SETBIT(PORTD,HiBMask);
} else if (TESTBIT(PORTD,HiBMask) != 0) {
CLRBIT(PORTD,HiBMask);
SETBIT(PORTD,HiAMask);
} else if (TESTBIT(PORTB,LoAMask) != 0) {
CLRBIT(PORTB,LoAMask);
SETBIT(PORTB,LoBMask);
} else if (TESTBIT(PORTB,LoBMask) != 0) {
CLRBIT(PORTB,LoBMask);
SETBIT(PORTB,LoAMask);
}
}
void CollapseField() {
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseHighSide:
FastFieldCollapseHighSide();
break;
case FastCollapseLowSide:
FastFieldCollapseLowSide();
break;
default:
case SlowCollapseHighSide:
SlowFieldCollapseHighSide();
break;
case SlowCollapseLowSide:
SlowFieldCollapseLowSide();
break;
}
}
inline void SwitchCollapseType() {
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseHighSide:
if (TESTBIT(DataRecord.CollapseState,CollapseStateAutoCycle) != 0) {
DataRecord.CollapseState = CollapseStateAutoCycle | FastCollapseLowSide;
}
break;
case FastCollapseLowSide:
if (TESTBIT(DataRecord.CollapseState,CollapseStateAutoCycle) != 0) {
DataRecord.CollapseState = CollapseStateAutoCycle | FastCollapseHighSide;
}
break;
default:
case SlowCollapseHighSide:
if (TESTBIT(DataRecord.CollapseState,CollapseStateAutoCycle) != 0) {
DataRecord.CollapseState = CollapseStateAutoCycle | SlowCollapseLowSide;
} else {
DataRecord.CollapseState = SlowCollapseHighSide;
}
break;
case SlowCollapseLowSide:
if (TESTBIT(DataRecord.CollapseState,CollapseStateAutoCycle) != 0) {
DataRecord.CollapseState = CollapseStateAutoCycle | SlowCollapseHighSide;
}
break;
}
}
inline void ResetHighSide() {
if (TESTBIT(PORTD,HiBMask)) {
CLRBIT(PORTD,HiBMask);
SETBIT(PORTD,HiBMask);
}
if (TESTBIT(PORTD,HiAMask)) {
CLRBIT(PORTD,HiAMask);
SETBIT(PORTD,HiAMask);
}
}
void FreeWheel() {
// Disable everything
CLRBIT(PORTB,LoBMask);
CLRBIT(PORTB,LoAMask);
CLRBIT(PORTD,HiBMask);
CLRBIT(PORTD,HiAMask);
PWM1::DisableChAB();
// Set up interrupts to some reasonable values
if (!DataRecord.IsForward) {
PWM1::SetChannelA(0x10);
PWM1::SetChannelB(ControlTime);
} else {
PWM1::SetChannelB(0x10);
PWM1::SetChannelA(ControlTime);
}
}
void Brake() {
// Allways clear first, than set
CLRBIT(PORTB,LoBMask);
CLRBIT(PORTB,LoAMask);
PWM1::DisableChAB();
PWM1::SetChannelB(0x10); // Set it to some reasonable value
PWM1::SetChannelA(ControlTime);
DataRecord.IsForward = true;
SETBIT(PORTD,HiAMask);
SETBIT(PORTD,HiBMask);
}
void HandleOverload() {
// Turn off both low-side FETs - this will remove the load for the rest of the cycle
CLRBIT(PORTB,LoBMask);
CLRBIT(PORTB,LoAMask);
PWM1::DisableChAB();
}
int16_t ScaledMult(int16_t aA, int16_t aB) {
return (((int32_t)aA * (int32_t)aB) >> 8);
}
static inline void DoControl() {
// Control acceleration
// Note: DoControl will not be called if RequestValue is Freewheel or Braking
int16_t SpeedDiff = DataRecord.RequestValue - DataRecord.CurrentRequest;
if (SpeedDiff > DataRecord.MaxPositiveChange) {
DataRecord.CurrentRequest += DataRecord.MaxPositiveChange;
} else if (SpeedDiff < -DataRecord.MaxNegativeChange) {
DataRecord.CurrentRequest -= DataRecord.MaxNegativeChange;
} else {
DataRecord.CurrentRequest = DataRecord.RequestValue;
}
// Limit motion to travel cutoff values. Note that we update RequestValue and not CurrentRequest
// so stop will be smooth. Since we estimage the time required to stop this also implements
// the go-to-distance functionality.
int16_t Change = (DataRecord.OriginalRequestValue > 0)?DataRecord.MaxNegativeChange:-DataRecord.MaxPositiveChange;
DataRecord.DistanceToStop = ((int32_t)DataRecord.VoltageSample * (int32_t)DataRecord.VoltageSample / (int32_t)Change) << 3;
int32_t StopPosition = DataRecord.Distance + DataRecord.DistanceToStop;
if (DataRecord.OriginalRequestValue > 0) {
if (StopPosition > DataRecord.FwDistanceLimit) {
DataRecord.RequestValue = DataRecord.CurrentRequest - min(DataRecord.MaxNegativeChange,DataRecord.CurrentRequest);
}
} else {
if (StopPosition < DataRecord.BwDistanceLimit) {
DataRecord.RequestValue = DataRecord.CurrentRequest + min(DataRecord.MaxPositiveChange,-DataRecord.CurrentRequest);
}
}
// Control loop
int16_t ScaledRequest = DataRecord.CurrentRequest >> 4;
DataRecord.LastError = DataRecord.Error;
DataRecord.Error = DataRecord.VoltageSample - ScaledRequest;
int16_t DValue = DataRecord.Error - DataRecord.LastError;
DataRecord.Command = ScaledMult(ScaledRequest,DataRecord.PFFactor) + ScaledMult(DataRecord.IValue,DataRecord.IFactor) + ScaledMult(DataRecord.Error,DataRecord.PFactor) + ScaledMult(DValue,DataRecord.DFactor);
// Limit command to valid range and limit IValue growth as well
if (DataRecord.Command >= 0x100) {
DataRecord.Command = 0xffL;
// In an overflow case allow integrator value updates if it works against the overflow (sign bits are differenet)
if (((DataRecord.IValue ^ DataRecord.Error) & 0x8000) != 0) DataRecord.IValue += DataRecord.Error;
} else if (DataRecord.Command <= -0x100) {
DataRecord.Command = -0xffL;
// In an overflow case allow integrator value updates if it works against the overflow (sign bits are differenet)
if (((DataRecord.IValue ^ DataRecord.Error) & 0x8000) != 0) DataRecord.IValue += DataRecord.Error;
} else {
// Use saturated arithmetics to avoid roll-over in the accumulator
int32_t TempIValue = (int32_t)DataRecord.IValue + (int32_t)DataRecord.Error;
if (TempIValue > 0x7fff) {
DataRecord.IValue = 0x7fffL;
} else if (TempIValue < -0x7fffL) {
DataRecord.IValue = -0x7fff;
} else {
DataRecord.IValue = TempIValue;
}
}
if (DataRecord.Command > 0) Forward(DataRecord.Command); else Backward((-DataRecord.Command));
DataRecord.NewData = true;
}
uint8_t SampleStateCnt = 0;
inline bool BatSampleWhileOn() {
uint8_t OnTime = (DataRecord.IsForward)?PWM1::GetChannelB():PWM1::GetChannelA();
return (OnTime > 128);
}
// Samples the positive pole of the motor WRT back-EMF
void SetADChannelMotorPositive() {
if (DataRecord.IsForward) {
ADConv::SetChannel(ADConv::Ch_MotorA,ADConv::Ref_MotorA,ADConv::RightAdjust);
} else {
ADConv::SetChannel(ADConv::Ch_MotorB,ADConv::Ref_MotorB,ADConv::RightAdjust);
}
}
// Samples the negative pole of the motor WRT back-EMF
void SetADChannelMotorNegative() {
if (DataRecord.IsForward) {
ADConv::SetChannel(ADConv::Ch_MotorB,ADConv::Ref_MotorB,ADConv::RightAdjust);
} else {
ADConv::SetChannel(ADConv::Ch_MotorA,ADConv::Ref_MotorA,ADConv::RightAdjust);
}
}
static void StartOffPhase(bool aWasChAOrBEnabled) {
// Start new measurements
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseLowSide:
SetADChannelMotorNegative();
if (aWasChAOrBEnabled) {
DataRecord.SampleState = SampleState_PreFastCollapse;
} else {
DataRecord.SampleState = SampleState_PostFastCollapse;
}
break;
case FastCollapseHighSide:
SetADChannelMotorPositive();
if (aWasChAOrBEnabled) {
DataRecord.SampleState = SampleState_PreFastCollapse;
} else {
DataRecord.SampleState = SampleState_PostFastCollapse;
}
break;
case SlowCollapseLowSide:
SetADChannelMotorNegative();
DataRecord.SampleState = SampleState_PreSampleBase;
break;
case SlowCollapseHighSide:
SetADChannelMotorPositive();
DataRecord.SampleState = SampleState_PreSampleBase;
break;
}
}
static inline void StartOnPhase() {
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseLowSide:
ADConv::SetChannel(ADConv::Ch_MotorCurrent,ADConv::Ref_MotorCurrent,ADConv::RightAdjust);
DataRecord.SampleState = SampleState_PreCurrentSample;
break;
case FastCollapseHighSide:
if (BatSampleWhileOn()) {
SetADChannelMotorPositive();
DataRecord.SampleState = SampleState_PreSampleBat;
SampleStateCnt = 0;
} else {
ADConv::SetChannel(ADConv::Ch_MotorCurrent,ADConv::Ref_MotorCurrent,ADConv::RightAdjust);
DataRecord.SampleState = SampleState_PreCurrentSample;
}
break;
case SlowCollapseLowSide:
ADConv::SetChannel(ADConv::Ch_MotorCurrent,ADConv::Ref_MotorCurrent,ADConv::RightAdjust);
DataRecord.SampleState = SampleState_PreCurrentSample;
break;
case SlowCollapseHighSide:
ADConv::SetChannel(ADConv::Ch_MotorCurrent,ADConv::Ref_MotorCurrent,ADConv::RightAdjust);
DataRecord.SampleState = SampleState_PreCurrentSample;
break;
}
bool InvertSample = !DataRecord.IsForward;
switch (DataRecord.OperatingMode) {
default:
case OperatingMode_Speed: {
int16_t LocalBaseValue = DataRecord.BaseValue;
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseHighSide:
case SlowCollapseHighSide:
break;
case FastCollapseLowSide:
case SlowCollapseLowSide:
InvertSample = !InvertSample;
LocalBaseValue = 0; // These are ground-based measurements...
break;
}
if (!InvertSample) {
DataRecord.VoltageSample = LocalBaseValue - DataRecord.MinValue;
} else {
DataRecord.VoltageSample = DataRecord.MinValue - LocalBaseValue;
}
}
break;
case OperatingMode_Servo:
DataRecord.VoltageSample = DataRecord.MaxValue;
break;
}
DataRecord.VoltageSample -= DataRecord.SampleOffset;
DataRecord.SampleCnt_Snapshot = DataRecord.SampleCnt;
DataRecord.MinValue_Snapshot = DataRecord.MinValue;
}
uint8_t BlinkCnt;
const uint8_t BlinkInterval = 50;
void HandleIRQ(bool IsIRQA) {
if (DataRecord.IsForward == IsIRQA) {
// Almost at the end: grab back-EMF data from sampling,
// do the control, and start sampling for current
StartOnPhase();
switch (DataRecord.RequestValue) {
case RequestFreewheel:
FreeWheel();
DataRecord.CurrentRequest = 0;
break;
case RequestBrake:
Brake();
DataRecord.CurrentRequest = 0;
break;
default:
// Update the travalled distance:
DataRecord.Distance += DataRecord.VoltageSample;
DoControl();
break;
}
SwitchCollapseType();
// Toggle user LED
++BlinkCnt;
if (BlinkCnt > BlinkInterval) {
BlinkCnt = 0;
OpLed::Toggle();
}
} else {
// At the end of the on-part: reverse voltage across motor to fast-discharge it.
bool WasChAOrBEnabled = PWM1::IsChAOrBEnabled();
CollapseField();
ADSampleIdx = 0;
if (DataRecord.ADBufferEnable == 0) {
DataRecord.ADBufferEnable = 1;
}
if (DataRecord.ADBufferEnableHost) {
DataRecord.ADBufferEnableHost = false;
DataRecord.ADBufferEnable = 0;
}
// Start sampling for voltages and back-EMF
StartOffPhase(WasChAOrBEnabled);
ResetHighSide();
}
}
uint8_t CurrentSampleCnt;
const uint8_t CollapseSearchBlank = 1;
const uint16_t CollapseSearchLowLimit = 0x30;
const uint16_t CollapseSearchHighLimit = 0x90;
void Sample() {
int16_t CurData = ADConv::FastGetSample();
// Save off samples in the ADBuffer for host-side 'scope' display.
if (DataRecord.ADBufferEnable == 0) {
if (ADSampleIdx < sizeof(DataRecord.ADBuffer)/sizeof(DataRecord.ADBuffer[0])) {
int16_t ADSampleData = (int16_t)ADConv::GetChannel() << 10;
ADSampleData |= CurData;
DataRecord.ADBuffer[ADSampleIdx] = ADSampleData;
++ADSampleIdx;
if (ADSampleIdx < sizeof(DataRecord.ADBuffer)/sizeof(DataRecord.ADBuffer[0])) {
DataRecord.ADBuffer[ADSampleIdx] = 0;
}
TOGGLEBIT(PORTD,0x10);
}
}
// State-machine for data-sampling.
switch (DataRecord.SampleState) {
case SampleState_PreFastCollapse:
DataRecord.SampleState = SampleState_FastCollapse;
// Update max current from the search field
DataRecord.CurrentMax = DataRecord.CurrentMaxSearch;
// TODO: If DataRecord.CurrentTemp isn't DataRecord.CurrentMaxSearch (more or less)
// than the over-current protection must have been activated.
SampleStateCnt = 0;
break;
case SampleState_FastCollapse:
// Wait in this state until turn-off transient is over
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseLowSide:
if (CurData < CollapseSearchLowLimit && SampleStateCnt > CollapseSearchBlank) {
ResetAfterFastCollapse();
DataRecord.SampleState = SampleState_PostFastCollapse;
} else if (CurData > CollapseSearchHighLimit) {
SampleStateCnt = CollapseSearchBlank;
}
++SampleStateCnt;
break;
case FastCollapseHighSide:
if (CurData > CollapseSearchHighLimit && SampleStateCnt > CollapseSearchBlank) {
ResetAfterFastCollapse();
DataRecord.SampleState = SampleState_PostFastCollapse;
} else if (CurData < CollapseSearchLowLimit) {
SampleStateCnt = CollapseSearchBlank;
}
++SampleStateCnt;
break;
}
break;
case SampleState_PostFastCollapse:
switch (DataRecord.OperatingMode) {
default:
case OperatingMode_Speed:
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseLowSide:
SetADChannelMotorPositive();
DataRecord.BaseValue = CurData;
DataRecord.SampleState = SampleState_PreSearchMax;
break;
case FastCollapseHighSide:
if (BatSampleWhileOn()) {
SetADChannelMotorNegative();
DataRecord.SampleState = SampleState_PreSearchMax;
} else {
SetADChannelMotorPositive();
DataRecord.SampleState = SampleState_PreSampleBat;
SampleStateCnt = 0;
}
break;
}
break;
case OperatingMode_Servo:
ADConv::SetChannel(ADConv::Ch_ServoPot,ADConv::Ref_ServoPot,ADConv::RightAdjust);
DataRecord.SampleState = SampleState_PreSamplePot;
break;
}
break;
case SampleState_PreSampleBase:
// Throw away the data, but the next one is for real!
DataRecord.SampleState = SampleState_PreSampleBase2;
switch (DataRecord.CollapseState & CollapseStateMask) {
case SlowCollapseLowSide:
SetADChannelMotorNegative();
break;
case SlowCollapseHighSide:
SetADChannelMotorPositive();
break;
}
// Update max current from the search field
DataRecord.CurrentMax = DataRecord.CurrentMaxSearch;
// TODO: If DataRecord.CurrentTemp isn't DataRecord.CurrentMaxSearch (more or less)
// than the over-current protection must have been activated.
SampleStateCnt = 0;
break;
case SampleState_PreSampleBase2:
// throw away this data, but the next one is for real!
DataRecord.SampleState = SampleState_SampleBase;
break;
case SampleState_SampleBase:
switch (DataRecord.OperatingMode) {
default:
case OperatingMode_Speed:
switch (DataRecord.CollapseState & CollapseStateMask) {
case SlowCollapseLowSide:
SetADChannelMotorPositive();
DataRecord.BaseValue = CurData;
DataRecord.SampleState = SampleState_PreSearchMax;
break;
case SlowCollapseHighSide:
SetADChannelMotorNegative();
DataRecord.BaseValue = CurData;
DataRecord.SampleState = SampleState_PreSearchMax;
break;
}
break;
case OperatingMode_Servo:
ADConv::SetChannel(ADConv::Ch_ServoPot,ADConv::Ref_ServoPot,ADConv::RightAdjust);
DataRecord.SampleState = SampleState_PreSamplePot;
break;
}
break;
case SampleState_PreSamplePot:
DataRecord.SampleState = SampleState_SamplePot;
break;
case SampleState_SamplePot:
DataRecord.MaxValue = CurData;
DataRecord.SampleCnt = 1;
break;
case SampleState_PreSearchMax:
DataRecord.SampleState = SampleState_SearchMax;
DataRecord.MaxValue = 0x0000;
DataRecord.SampleCnt = 0;
break;
case SampleState_SearchMax:
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseHighSide:
case SlowCollapseLowSide:
if (CurData <= DataRecord.MaxValue) {
DataRecord.MaxValue = CurData;
} else {
DataRecord.SampleState = SampleState_SearchMin;
DataRecord.MinValue = DataRecord.MaxValue;
}
break;
case FastCollapseLowSide:
case SlowCollapseHighSide:
if (CurData >= DataRecord.MaxValue) {
DataRecord.MaxValue = CurData;
} else {
DataRecord.SampleState = SampleState_SearchMin;
DataRecord.MinValue = DataRecord.MaxValue;
}
break;
}
break;
case SampleState_SearchMin:
switch (DataRecord.CollapseState & CollapseStateMask) {
case FastCollapseHighSide:
case SlowCollapseLowSide:
if (CurData > DataRecord.MinValue) {
DataRecord.MinValue = CurData;
}
DataRecord.SampleCnt++;
break;
case FastCollapseLowSide:
case SlowCollapseHighSide:
if (CurData < DataRecord.MinValue) {
DataRecord.MinValue = CurData;
}
DataRecord.SampleCnt++;
break;
}
break;
case SampleState_PreSampleBat:
if (SampleStateCnt > 4)
DataRecord.SampleState = SampleState_SampleBat;
++SampleStateCnt;
break;
case SampleState_SampleBat:
DataRecord.BaseValue = CurData;
if (BatSampleWhileOn()) {
ADConv::SetChannel(ADConv::Ch_MotorCurrent,ADConv::Ref_MotorCurrent,ADConv::RightAdjust);
DataRecord.SampleState = SampleState_PreCurrentSample;
} else {
SetADChannelMotorNegative();
DataRecord.SampleState = SampleState_PreSearchMax;
}
break;
case SampleState_PreCurrentSample:
ADConv::SetChannel(ADConv::Ch_MotorCurrent,ADConv::Ref_MotorCurrent,ADConv::RightAdjust);
CurrentSampleCnt = 0;
DataRecord.SampleState = SampleState_CurrentSample1;
break;
case SampleState_CurrentSample1:
if (CurData > DataRecord.CurrentLimit) HandleOverload();
if (CurrentSampleCnt == 0) {
DataRecord.CurrentTemp = CurData;
DataRecord.CurrentMaxSearch = CurData;
}
++CurrentSampleCnt;
if (CurData > DataRecord.CurrentMaxSearch) DataRecord.CurrentMaxSearch = CurData;
if (CurrentSampleCnt == 3) DataRecord.SampleState = SampleState_CurrentSample2;
break;
case SampleState_CurrentSample2:
if (CurData > DataRecord.CurrentLimit) HandleOverload();
// Sample delta-current. It is related to the back-EMF voltage, though
// this measurement is not precise enough to base control off of it
DataRecord.CurrentDelta = CurData - DataRecord.CurrentTemp;
DataRecord.CurrentTemp = CurData;
if (CurData > DataRecord.CurrentMaxSearch) DataRecord.CurrentMaxSearch = CurData;
DataRecord.SampleState = SampleState_CurrentSample3;
break;
case SampleState_CurrentSample3:
// We'll stay in this state until the on-phase ends...
if (CurData > DataRecord.CurrentLimit) HandleOverload();
DataRecord.CurrentTemp = CurData;
if (CurData > DataRecord.CurrentMaxSearch) DataRecord.CurrentMaxSearch = CurData;
break;
}
}
}
SIGNAL(SIG_OUTPUT_COMPARE1A) {HBridge::HandleIRQ(true);}
SIGNAL(SIG_OUTPUT_COMPARE1B) {HBridge::HandleIRQ(false);}
SIGNAL(SIG_ADC) {HBridge::Sample();}
namespace TWI {
uint8_t Address;
extern const sConfigRecord ConfigRecord PROGMEM __attribute__ ((weak)) = {UniqueIDUnassigned,
DevClassHBridge,
#if defined MEGA_BRIDGE
DevUmHBridge
#elif defined H_BRIDGE
DevUmHBridge
#elif defined SERVO_BRAIN
DevUmServoBrain
#else
#error No HW version is specified!
#endif
};
enum UserStates {
US_ReceiveAddr = TWI::US_Base,
US_ReceiveData,
US_SendData
};
void HandleUserReceive() {
State = US_ReceiveAddr;
}
union {
uint8_t UInt8[4];
uint16_t UInt16[2];
uint32_t UInt32[1];
} TransmitBuffer;
void SendData() __attribute__ ((noinline));
void SendData() {
switch (HBridge::GetDataElementSize(Address)) {
default:
case 1:
TWIPrevData = *HBridge::GetDataRecord8(Address);
break;
case 2:
if ((Address & 1) == 0) {
TransmitBuffer.UInt16[0] = *HBridge::GetDataRecord16(Address);
}
TWIPrevData = TransmitBuffer.UInt8[Address & 1];
break;
case 4:
if ((Address & 3) == 0) {
TransmitBuffer.UInt32[0] = *HBridge::GetDataRecord32(Address);
}
TWIPrevData = TransmitBuffer.UInt8[Address & 3];
break;
}
TWDR = TWIPrevData;
++Address;
if (Address >= HBridge::GetDataRecordSize()) Address = 0; // Wrap around
}
void GetData() {
switch (HBridge::GetDataElementSize(Address)) {
default:
case 1:
TransmitBuffer.UInt8[0] = TWIData;
*HBridge::GetDataRecord8(Address) = TransmitBuffer.UInt8[0];
break;
case 2:
TransmitBuffer.UInt8[Address & 1] = TWIData;
if ((Address & 1) != 0) {
// High-byte: store the whole word
*HBridge::GetDataRecord16(Address & (~0x01)) = TransmitBuffer.UInt16[0];
// Special-case request value, we have to save that in another spot as well...
if (Address == 1) {
HBridge::DataRecord.OriginalRequestValue = TransmitBuffer.UInt16[0];
}
}
break;
case 4:
TransmitBuffer.UInt8[Address & 3] = TWIData;
if ((Address & 3) != 0) {
// Highest byte: store the whole dword
*HBridge::GetDataRecord32(Address & (~0x03)) = TransmitBuffer.UInt32[0];
}
break;
}
++Address;
if (Address >= HBridge::GetDataRecordSize()) Address = 0; // Wrap around
}
void HandleUserTransmit() {
SendData();
State = US_SendData;
}
void HandleUserState() {
switch (State) {
case US_ReceiveAddr:
switch (TWIStatus) {
case TW_SR_DATA_ACK:
// TODO: handle different command codes here -> check the data written to this address!
if (TWIData == 0xff) {
HBridge::SaveSettings();
ResetTWI();
} else {
//if ((TWIData & 0x7f) >= HBridge::GetDataRecordSize()) Address = HBridge::GetDataRecordSize() - 1; else Address = TWIData;
if (TWIData >= HBridge::GetDataRecordSize()) Address = HBridge::GetDataRecordSize() - 1; else Address = TWIData;
State = US_ReceiveData;
}
break;
default:
ResetTWI();
break;
}
break;
case US_ReceiveData:
switch (TWIStatus) {
case TW_SR_DATA_ACK:
GetData();
// We stay in this state for any optional additional data
break;
default:
ResetTWI();
break;
}
break;
case US_SendData:
switch (TWIStatus) {
case TW_ST_DATA_ACK:
SendData();
SETBIT(TWIControl,(1 << TWEA)); // require ACK
break;
default:
ResetTWI();
break;
}
break;
default:
// This really REALLY shouldn't happen...
ResetTWI();
break;
}
}
}
#ifdef USE_SERIAL_DEBUG
// Debug interfaces
namespace UsartComm {
inline void Init(uint16_t aBaudSetting) {
USART0::Init(aBaudSetting);
}
void HandleInput() {
uint8_t CurData = USART0::FastReceiveData();
OpLed::Toggle();
}
}
SIGNAL(SIG_USART_RECV) {UsartComm::HandleInput();}
void DebugStat() {
cli();
int16_t VoltageSample = HBridge::DataRecord.VoltageSample;
uint16_t SampleCnt = HBridge::DataRecord.SampleCnt_Snapshot;
int16_t MinValue = HBridge::DataRecord.MinValue_Snapshot;
int16_t CurrentRequest = HBridge::DataRecord.CurrentRequest;
int16_t Error = HBridge::DataRecord.Error;
int16_t IValue = HBridge::DataRecord.IValue;
int16_t Command = HBridge::DataRecord.Command;
sei();
USART0::SendHexData(VoltageSample);
USART0::SendData(' '); USART0::SendData('S'); USART0::SendData(':'); USART0::SendHexData(SampleCnt);
USART0::SendData(' '); USART0::SendData('M'); USART0::SendData(':'); USART0::SendHexData(MinValue);
USART0::SendData(' '); USART0::SendData('R'); USART0::SendData(':'); USART0::SendHexData(CurrentRequest);
USART0::SendData(' '); USART0::SendData('E'); USART0::SendData(':'); USART0::SendHexData(Error);
USART0::SendData(' '); USART0::SendData('I'); USART0::SendData(':'); USART0::SendHexData(IValue);
USART0::SendData(' '); USART0::SendData('C'); USART0::SendData(':'); USART0::SendHexData(Command);
USART0::SendData('\r'); USART0::SendData('\n');
}
#endif // USE_SERIAL_DEBUG
int main() {
cli();
EEPROM::Init();
OpLed::Init();
HBridge::BlinkCnt = 0;
#ifdef USE_SERIAL_DEBUG
UsartComm::Init(USART0::baud57600_8MHz);
#endif // USE_SERIAL_DEBUG
TCCR0A = 0;
TWI::Init();
ADConv::Init();
HBridge::Init();
SETBIT(DDRD,0x08|0x10);
sei();
// Everything happens in the interrupt routines. We have nothing else to do
// here but some optional debugging
while (true) {
#ifdef USE_SERIAL_DEBUG
cli();
if (HBridge::DataRecord.NewData) {
HBridge::DataRecord.NewData = false;
DebugStat();
} else {
sei();
}
#endif // USE_SERIAL_DEBUG
}
return 0;
}