Files
ODrive/MotorControl/test.c
T
2016-11-12 22:29:17 +09:00

320 lines
9.0 KiB
C

#include "test.h"
#include "stm32f4xx_hal.h"
#include "stm32f405xx.h"
#include "assert.h"
#include "cmsis_os.h"
#include "adc.h"
#include "tim.h"
#include "spi.h"
#include "drv8301.h"
#include "math.h"
#include "stdint.h"
void start_adc_pwm(){
//Enable ADC and interrupts
__HAL_ADC_ENABLE(&hadc2);
__HAL_ADC_ENABLE(&hadc3);
//Warp field stabilize.
osDelay(2);
__HAL_ADC_ENABLE_IT(&hadc2, ADC_IT_JEOC);
__HAL_ADC_ENABLE_IT(&hadc3, ADC_IT_JEOC);
//Init PWM
int half_load = htim1.Instance->ARR/2;
htim1.Instance->CCR1 = half_load;
htim1.Instance->CCR2 = half_load;
htim1.Instance->CCR3 = half_load;
//This hardware obfustication layer really is getting on my nerves
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3);
htim1.Instance->CCR4 = 1;
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_4);
//Turn off output
//__HAL_TIM_MOE_DISABLE(&htim1);
}
typedef struct Motor_s {
DRV8301_Obj gate_driver;
float shunt_conductance;
float maxcurrent;
} Motor_t;
Motor_t motor_configs[] = {
{ //M0
.gate_driver = {
.spiHandle = &hspi3,
//Note: this board has the EN_Gate pin shared!
.EngpioHandle = EN_GATE_GPIO_Port,
.EngpioNumber = EN_GATE_Pin,
.nCSgpioHandle = M0_nCS_GPIO_Port,
.nCSgpioNumber = M0_nCS_Pin,
.RxTimeOut = false,
.enableTimeOut = false
},
.shunt_conductance = 1.0f/0.0005f, //[S]
.maxcurrent = 75.0f //[A] //Note: consistent with 40v/v gain
}
};
static const int num_motors = sizeof(motor_configs)/sizeof(motor_configs[0]);
//Local view of DRV registers
static DRV_SPI_8301_Vars_t gate_driver_regs[1/*num_motors*/];
void test_DRV8301_setup() {
for (int i = 0; i < num_motors; ++i) {
DRV8301_enable(&motor_configs[i].gate_driver);
DRV8301_setupSpi(&motor_configs[i].gate_driver, &gate_driver_regs[i]);
//@TODO we can use reporting only if we actually wire up the nOCTW pin
gate_driver_regs[i].Ctrl_Reg_1.OC_MODE = DRV8301_OcMode_LatchShutDown;
//Overcurrent set to approximately 150A at 100degC. This may need tweaking.
gate_driver_regs[i].Ctrl_Reg_1.OC_ADJ_SET = DRV8301_VdsLevel_0p730_V;
//20V/V on 500uOhm gives a range of +/- 150A
//40V/V on 500uOhm gives a range of +/- 75A
gate_driver_regs[i].Ctrl_Reg_2.GAIN = DRV8301_ShuntAmpGain_40VpV;
gate_driver_regs[i].SndCmd = true;
DRV8301_writeData(&motor_configs[i].gate_driver, &gate_driver_regs[i]);
gate_driver_regs[i].RcvCmd = true;
DRV8301_readData(&motor_configs[i].gate_driver, &gate_driver_regs[i]);
}
}
/////////////////////////////////////////////////
//Test adc conversion latency and triggering
void test_adc_trigger() {
//Set trigger to mid phase to check for trigger polarity
htim1.Instance->CCR4 = 2048;
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_4);
__HAL_ADC_ENABLE(&hadc2);
//Warp field stabilize.
osDelay(2);
__HAL_ADC_ENABLE_IT(&hadc2, ADC_IT_JEOC);
}
static int test = 0;
static int test2 = 0;
static uint32_t testcnt[16];
static int tcidx = 0;
void test_adc_trigger_cb() {
uint32_t cnt = htim1.Instance->CNT;
int dir = htim1.Instance->CR1 & TIM_CR1_DIR;
if(dir){
test++;
} else {
test2++;
testcnt[tcidx] = cnt - 2048;
if(++tcidx == 16)
tcidx = 0;
}
}
/////////////////////////////////////////////////
static int cbcnt = 0;
void test_cb_count(){
++cbcnt;
}
/////////////////////////////////////////////////
//Histogram test
static float alpha = 1/(5000.0f);
static float avg = 2048.0f;
static float var = 0.0f;
static uint32_t hist_countdown = 40000;
static uint32_t errhist[20];
static uint32_t neg_errhist[20];
void test_adc_hist_cb(ADC_HandleTypeDef* hadc) {
//float unknown_ch_volts = read_ADC_volts(hadc, 1);
uint32_t ADCValue = HAL_ADCEx_InjectedGetValue(hadc, 1);
float val = (float)ADCValue;
avg *= (1.0f - alpha);
avg += alpha * val;
float dval = val-avg;
var *= (1.0f - alpha);
var += alpha * (dval * dval);
if (hist_countdown) {
--hist_countdown;
} else {
int idval = (int)dval;
int pos = (idval >= 0);
if (!pos)
idval = -idval;
if (idval >= 20)
idval = 19;
if (pos)
++errhist[idval];
else
++neg_errhist[idval];
}
}
/////////////////////////////////////////////////
float phase_current_from_adcval(uint32_t ADCValue, int motornum) {
float rev_gain;
switch (gate_driver_regs[motornum].Ctrl_Reg_2.GAIN) {
case DRV8301_ShuntAmpGain_10VpV:
rev_gain = 1.0f/10.0f;
break;
case DRV8301_ShuntAmpGain_20VpV:
rev_gain = 1.0f/20.0f;
break;
case DRV8301_ShuntAmpGain_40VpV:
rev_gain = 1.0f/40.0f;
break;
case DRV8301_ShuntAmpGain_80VpV:
rev_gain = 1.0f/80.0f;
break;
}
int adcval_bal = (int)ADCValue - (1<<11);
float amp_out_volt = (3.3f/(float)(1<<12)) * (float)adcval_bal;
float shunt_volt = amp_out_volt * rev_gain;
float current = shunt_volt * motor_configs[motornum].shunt_conductance;
return current;
}
void assertt(int arg) {
if(!arg) {
int test = 3;
for(;;);
}
}
// current sense queue from ADC to motor control task
typedef struct {
float current_phB;
float current_phC;
} Iph_BC_queue_item_t;
osMailQDef (Iph_queue_def, 2, Iph_BC_queue_item_t);
osMailQId (M0_Iph_queue);
void test_pwm_from_adc_cb(ADC_HandleTypeDef* hadc) {
// ADC2 and ADC3 record the phB and phC currents concurrently,
// and their interrupts have the same priorities so they can complete
// in any order. Because they cannot preempt each other, it is safe
// to store the result of the first interrupt without risk of a race condition.
// Then we send both into the queue.
typedef enum ADC_sync_e {NONE_STORED, PHB_STORED, PHC_STORED} ADC_sync_t;
static ADC_sync_t adc_sync = NONE_STORED;
static float stored_current;
//Only one conversion in sequence, so only rank1
uint32_t ADCValue = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1);
//Store and return, or fetch and continue
float M0_phB_current, M0_phC_current;
if (hadc == &hadc2) {
M0_phB_current = phase_current_from_adcval(ADCValue, 0);
if (adc_sync == NONE_STORED) {
stored_current = M0_phB_current;
adc_sync = PHB_STORED;
return;
} else {
assertt(adc_sync == PHC_STORED);
M0_phC_current = stored_current;
adc_sync = NONE_STORED;
}
} else if (hadc == &hadc3) {
M0_phC_current = phase_current_from_adcval(ADCValue, 0);
if (adc_sync == NONE_STORED) {
stored_current = M0_phC_current;
adc_sync = PHC_STORED;
return;
} else {
assertt(adc_sync == PHB_STORED);
M0_phB_current = stored_current;
adc_sync = NONE_STORED;
}
} else {
//hadc is something else, not expected
assertt(0);
}
//Allocate mail queue storage
Iph_BC_queue_item_t* mail_ptr;
mail_ptr = (Iph_BC_queue_item_t*) osMailAlloc(M0_Iph_queue, 0);
if (mail_ptr == NULL) {
return;
}
//Write contents and send mail
mail_ptr->current_phB = M0_phB_current;
mail_ptr->current_phC = M0_phC_current;
osMailPut(M0_Iph_queue, mail_ptr);
}
void test_motor_thread(void const * argument) {
//Allocate the queues
M0_Iph_queue = osMailCreate(osMailQ(Iph_queue_def), NULL);
//Init gate drivers
test_DRV8301_setup();
osDelay(1000);
// Start PWM and enable adc interrupts/callbacks
start_adc_pwm();
for(;;) {
//Current measurements not occurring in a timely manner can be handled by the watchdog
//@TODO Actually make watchdog
//Hence we can use osWaitForever
osEvent evt = osMailGet(M0_Iph_queue, osWaitForever);
//Since we wait forever, we do not expect timeouts here.
assertt(evt.status == osEventMail);
Iph_BC_queue_item_t* mail_ptr = evt.value.p;
float M0_phB_current = mail_ptr->current_phB;
float M0_phC_current = mail_ptr->current_phC;
osMailFree(M0_Iph_queue, mail_ptr);
// WARNING: Only gimbal motors!!
int half_load = htim1.Instance->ARR/2;
htim1.Instance->CCR1 = half_load - 400;
htim1.Instance->CCR2 = half_load + 400;
htim1.Instance->CCR3 = half_load + 400;
}
}
//Test setup: Setup tests in main, and set callbacks
void test_main(void) {
//test_adc_trigger();
}
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc) {
//test_adc_trigger_cb(hadc);
//test_cb_count();
//test_adc_hist_cb(hadc);
test_pwm_from_adc_cb(hadc);
}