experiment.c 17.1 KiB
Newer Older
/*
 * experiment.c
 *
 * Created: 01/10/2012 10:59:48 PM
 *  Author: mdryden
 */ 
/**
 * @file experiment.c
 * @author  Michael DM Dryden <mdryden@chem.utoronto.ca>
 * @version 1.0
 *
 *
 * @section DESCRIPTION
 *
 * Contains Functions for performing experiments and adjusting potentiostat settings other than DAC and ADC.
 */
//Public variable definitions
uint8_t g_gain = POT_GAIN_30k;
uint8_t autogain_enable = 1;

//Private variables
volatile int32_t voltage = 0;
volatile uint16_t dacindex = 0;
uint16_t dacindex_stop = 0;
volatile int8_t up = 1;
volatile uint16_t iter = 0;
uint16_t* eis_ptr = 0;
volatile uint16_t cycles = 0;
volatile uint16_t samples = 0;
volatile uint16_t tcf0period = 0;
uint32_t skip_samples = 0;

//Private function declarations
static void porte_int0_lsv(void);
static void tcf0_ovf_callback(void);
static void tce1_ovf_callback_lsv(void);
static void lsv_cca_callback(void);
static void ca_cca_callback(void);
static void portd_int0_ca(void);

//interrupt callback setup
typedef void (*port_callback_t) (void);

static port_callback_t portd_int0_callback;
static port_callback_t portd_int1_callback;

void pot_init(void){
	/**
	 * Initializes AVR port directions and levels
	 *
	 * @return Nothing.
	 */
	arch_ioport_set_port_dir(IOPORT_PORTB, PIN3_bm|PIN4_bm|PIN5_bm|PIN6_bm|PIN7_bm, IOPORT_DIR_OUTPUT);
	arch_ioport_set_port_dir(IOPORT_PORTD, PIN4_bm, IOPORT_DIR_OUTPUT);
	arch_ioport_set_port_level(IOPORT_PORTB, PIN3_bm|PIN4_bm|PIN5_bm|PIN6_bm|PIN7_bm, PIN6_bm|PIN7_bm);
	arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, PIN4_bm);
}

int8_t autogainswitch(void){
	/**
	 * Automatic gain switching.
	 *
	 * Reads last samples from over_under var from ads1255.h and changes gain if more than ADS_OVER_UNDER_SAMPLES off scale.
	 * Uses g_gain global variable.
	 * @return 0 when no switch occurs (due to hysteresis internal static var or if 500M resistor chosen). 1 if gain switch.
	 */
	//Need to change this to set variable max/min gains
	extern int8_t over_under[ADS_OVER_UNDER_SAMPLES]; //from ads1255.h
	int8_t overcurrent = 0;
	static uint8_t hysteresis = 0;
	static uint8_t last_return = 0;
	
		last_return=0;
		return 1;
	}
	
	if (hysteresis < ADS_OVER_UNDER_SAMPLES-1){
		++hysteresis;
		return 0;
	}
	
	for (uint16_t i = 0; i < ADS_OVER_UNDER_SAMPLES; ++i)
		overcurrent += over_under[i];
	
	if (overcurrent == ADS_OVER_UNDER_SAMPLES && g_gain > POT_GAIN_100){	
		pot_set_gain();
		last_return=1;
		hysteresis = 0;
		for (uint16_t i = 0; i < ADS_OVER_UNDER_SAMPLES; ++i)
	if ((overcurrent*-1) == (ADS_OVER_UNDER_SAMPLES) && g_gain < POT_GAIN_3M){
		if (hysteresis < ADS_OVER_UNDER_SAMPLES+3){
			++hysteresis;
			return 0;
		}
		
		pot_set_gain();
		last_return=1;
		hysteresis = 0;
		for (uint16_t i = 0; i < ADS_OVER_UNDER_SAMPLES; ++i)
		over_under[i]=0;
		
		return 1;
	}
	/**
	 * Sets iV gain according to current g_gain value
	 *
	 * @return Nothing.
	 */
	switch (g_gain){
		case POT_GAIN_500M:
		arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, 0);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, 0);
		
		break;
		
		case POT_GAIN_30M:
		arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN6_bm);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, 0);
		arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN7_bm);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, 0);
		break;
		
		case POT_GAIN_300k:
		arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN6_bm|PIN7_bm);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, 0);
		break;
		
		case POT_GAIN_30k:
		arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, 0);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, PIN4_bm);
		break;
		
		case POT_GAIN_3k:
		arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN6_bm);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, PIN4_bm);
		break;
		
		case POT_GAIN_300:
		arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN7_bm);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, PIN4_bm);
		break;
		
		case POT_GAIN_100:
		arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN6_bm|PIN7_bm);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, PIN4_bm);
	arch_ioport_set_port_level(IOPORT_PORTB, PIN3_bm|PIN4_bm|PIN5_bm, PIN3_bm|PIN4_bm|PIN5_bm);
}

void pot_exp_stop(void){
	/**
	 * Disconnects measurement cell and shorts RE and CE terminals.
	 */
	arch_ioport_set_port_level(IOPORT_PORTB, PIN3_bm|PIN4_bm|PIN5_bm, 0);
}

void cv_experiment(int16_t v1, int16_t v2, int16_t start, uint8_t scans, uint16_t slope){
	/**
	 * Perform a CV experiment.
	 *
	 * Calls lsv_experiment several times to make a CV experiment.
	 * @param v1 Vertex 1 in mV.
	 * @param v2 Vertex 2 in mV.
	 * @param start Start voltage in mV.
	 * @param scans Number of scans.
	 * @param slope Scan rate in mV/s.
	 */
	if((start < v1 && start < v2) || (start > v1 && start > v2)){
		printf("ERR: Start must be within [v1, v2]\n\r");
		return;
	}
	
// 	RTC.CTRL = RTC_PRESCALER_OFF_gc;
// 	while (RTC.STATUS & RTC_SYNCBUSY_bm);
// 	RTC.CNT = 0;
// 	RTC.PER = 0xffff;
// 	RTC.CTRL = RTC_PRESCALER_DIV1024_gc; // 1 s tick
	
	while(scans > 0){
		if (start != v1){
			lsv_experiment(start,v1,slope,firstrun);
			firstrun = 0;
		}
		if (start == v2 && scans == 1)
			firstrun = -1;
		lsv_experiment(v1,v2,slope,firstrun);
		if (scans == 1)
			firstrun = -1;
		if (start != v2)
			lsv_experiment(v2,start,slope,firstrun);
		--scans;
		firstrun = 0;
	}
	
//	printf("Time: %lu s \n\r", rtc_get_time());
	
	return;
}

void lsv_experiment(int16_t start, int16_t stop, uint16_t slope, int8_t first_run){
	/**
	 * Perform a LSV experiment.
	 *
	 * Uses porte_int0_lsv to output to USB.
	 * @param start Start potential in mV.
	 * @param stop Stop potential in mV.
	 * @param slope Scan rate in mV/s.
	 * @param first_run Keeps track of number of scans so potentiostat isn't initialized twice or disconnected when doing CV. Set to 2 for normal LSV.
	 */
	
	if(start<-1500 || start>=1500 ||start==stop|| stop<-1500 || stop>=1500 || slope>7000)
	{
		printf("ERR: Experiment parameters outside limits\n\r");
		return;
	}
	
	uint16_t dacindex_start = ceil(start*(65536/(double)3000)+32768);
	dacindex_stop = ceil(stop*(65536/(double)3000)+32768);
	//	uint16_t period;
	uint32_t timer_period;
	uint16_t temp_div;

	max5443_set_voltage1(dacindex_start);
	
	if (first_run == 1 || first_run == 2){
		pot_exp_start();
		ads1255_rdatac();
		tc_enable(&TCC1);
		
		ads1255_sync();

		tc_enable(&TCC0);
		tc_set_overflow_interrupt_callback(&TCC0, tcf0_ovf_callback);
		tc_set_overflow_interrupt_callback(&TCC1, tce1_ovf_callback_lsv);
		tc_set_cca_interrupt_callback(&TCC1, lsv_cca_callback);

		//set EVCH0 event
		EVSYS.CH0MUX = EVSYS_CHMUX_TCC0_OVF_gc;
		EVSYS.CH0CTRL = 0;

		timer_period = ceil(1/((double)slope/(3000./65536))*(F_CPU));
		temp_div = ceil(timer_period/65536.);
		
		if (temp_div <= 1)
			tc_write_clock_source(&TCC0,TC_CLKSEL_DIV1_gc);
		else if (temp_div == 2){
			tc_write_clock_source(&TCC0,TC_CLKSEL_DIV2_gc);
			timer_period /= 2;
		}
		else if (temp_div <= 4){
			tc_write_clock_source(&TCC0,TC_CLKSEL_DIV4_gc);
			timer_period /= 4;
		}
		else if (temp_div <= 8){
			tc_write_clock_source(&TCC0,TC_CLKSEL_DIV8_gc);
			timer_period /= 8;
		}
		else if (temp_div <= 64){
			tc_write_clock_source(&TCC0,TC_CLKSEL_DIV64_gc);
			timer_period /= 64;
		}
		else if (temp_div <= 256){
			tc_write_clock_source(&TCC0,TC_CLKSEL_DIV256_gc);
			timer_period /= 256;
		}
		else if (temp_div <= 1024){
			tc_write_clock_source(&TCC0,TC_CLKSEL_DIV1024_gc);
			timer_period /= 1024;
		}
		else{
			printf("ERR: Frequency/ADC rate is too low\n\r");
			return;
		}
		
		ads1255_wakeup();
		tc_write_period(&TCC1, 0xffff);
		tc_write_period(&TCC0, (uint16_t)timer_period);
	}
	
	TCC1.CNT = dacindex_start;
	
	if (stop > start)
	{
		up = 1;
		tc_set_direction(&TCC1, TC_UP);
	}
	else
	{
		up = -1;
		tc_set_direction(&TCC1, TC_DOWN);
	}
	
	tc_write_cc(&TCC1, TC_CCA, dacindex_stop);
	tc_enable_cc_channels(&TCC1, TC_CCAEN);
	TCC0.CNT = 0;
	
	tc_set_cca_interrupt_level(&TCC1, TC_INT_LVL_HI); //Stop experiment
	tc_set_overflow_interrupt_level(&TCC0, TC_OVFINTLVL_LO_gc); //Set DAC
	PORTD.INTCTRL = PORT_INT0LVL_MED_gc; //ADC read
	
	tc_write_clock_source(&TCC1, TC_CLKSEL_EVCH0_gc);
	
	while (up != 0); //Experiment run with interrupts
	
	if (first_run == -1 || first_run == 2)
	{
		tc_disable(&TCC0);
		TCC0.CNT = 0x0;
		tc_disable(&TCC1);
		pot_exp_stop();
		ads1255_standby();
	}
	
	return;
}

static void porte_int0_lsv(void){
	int32_t result;
	
	if (autogain_enable == 0){
//		while (arch_ioport_get_pin_level(IOPORT_CREATE_PIN(PORTD, 5)));
		result=ads1255_read_fast24();
		goto noauto;
	}
	
	else{
		while (arch_ioport_get_pin_level(IOPORT_CREATE_PIN(PORTD, 5)));
// 		if (g_gain == POT_GAIN_300)
// 		printf("%u %ld\n\r", TCC1.CNT,result);
// 		else if (g_gain == POT_GAIN_3k)
// 		printf("%u %ld\n\r", TCC1.CNT, result);
// 		else if (g_gain == POT_GAIN_30k)
// 		printf("%u %ld000\n\r", TCC1.CNT, result);
// 		else if (g_gain == POT_GAIN_300k)
// 		printf("%u %ld00\n\r", TCC1.CNT, result);
// 		else if (g_gain == POT_GAIN_3M)
// 		printf("%u %ld0\n\r", TCC1.CNT, result);
// 		else if (g_gain == POT_GAIN_30M)
// 		printf("%u %ld\n\r", TCC1.CNT, result);
// 		else
}

static void tcf0_ovf_callback(void){
	max5443_set_voltage1(TCC1.CNT);
}

static void tce1_ovf_callback_lsv(void){
	PORTD.INTCTRL = PORT_INT0LVL_OFF_gc;
	tc_set_overflow_interrupt_level(&TCC0, TC_OVFINTLVL_OFF_gc);
	tc_set_overflow_interrupt_level(&TCC1, TC_OVFINTLVL_OFF_gc);
	up = 0;
	return;
}

static void lsv_cca_callback(void){
	PORTD.INTCTRL = PORT_INT0LVL_OFF_gc;
	tc_set_overflow_interrupt_level(&TCC0, TC_OVFINTLVL_OFF_gc);
	tc_set_cca_interrupt_level(&TCC1, TC_INT_LVL_OFF);
	up = 0;
	return;
}

void chronoamp(uint16_t steps, uint16_t step_dac[], uint16_t step_seconds[]){
	/**
	 * Performs a chronoamperometry experiment.
	 *
	 * @param steps Total number of steps.
	 * @param step_dac[] Array containing DAC indices.
	 * @param step_seconds[] Array containing step durations in seconds.
	 */
	while (RTC.STATUS & RTC_SYNCBUSY_bm);
	RTC.PER = 999;
	while (RTC.STATUS & RTC_SYNCBUSY_bm);
	RTC.CTRL = RTC_PRESCALER_DIV1_gc;
	
	EVSYS.CH0MUX = EVSYS_CHMUX_RTC_OVF_gc;
	
	portd_int0_callback = portd_int0_ca;
	
	tc_enable(&TCC0);
	tc_set_cca_interrupt_callback(&TCC0, ca_cca_callback);
	
	ads1255_rdatac();
	ads1255_wakeup();
	
	tc_write_period(&TCC0,0xffff);
	tc_write_clock_source(&TCC0, TC_CLKSEL_EVCH0_gc);
	tc_set_direction(&TCC0, TC_UP);
	tc_enable_cc_channels(&TCC0, TC_CCAEN);
	tc_set_cca_interrupt_level(&TCC0, TC_INT_LVL_HI);
	TCC0.CNT = 0;
	
	pot_exp_start();
	
	for (uint8_t i = 0; i < steps; ++i)
	{
		up = 1;
		tc_write_cc(&TCC0, TC_CCA, TCC0.CNT+step_seconds[i]-1);
		RTC.CNT=0;
		max5443_set_voltage1(step_dac[i]);
		//PORTE.INTCTRL = PORT_INT0LVL_LO_gc;
		PORTD.INTCTRL = PORT_INT0LVL_LO_gc;
		while (up !=0);
	}
	
	tc_set_cca_interrupt_level(&TCC0, TC_INT_LVL_OFF);
	tc_write_clock_source(&TCC0, TC_CLKSEL_OFF_gc);
	tc_disable(&TCC0);
	pot_exp_stop();
	ads1255_standby();

	return;
}

static void portd_int0_ca(void){
	if (autogainswitch()==0){
		//while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTE, 3)));
		while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTD, 5)));
		printf("%u, %ld00000\n\r", TCC1.CNT, ads1255_read());
		printf("%u, %ld0000\n\r", TCC1.CNT, ads1255_read());
		printf("%u, %ld000\n\r", TCC1.CNT, ads1255_read());
		printf("%u, %ld00\n\r", TCC1.CNT, ads1255_read());
		printf("%u, %ld0\n\r", TCC1.CNT, ads1255_read());
		printf("%u, %ld\n\r", TCC1.CNT, ads1255_read());
		else
		printf("%u, %lu\n\r", TCC1.CNT, ads1255_read());
	}
	else{
		//while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTE, 3)));
		while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTD, 5)));
		ads1255_read();
	}
}

static void ca_cca_callback(void){
	//PORTE.INTCTRL = PORT_INT0LVL_OFF_gc;
	PORTD.INTCTRL = PORT_INT0LVL_OFF_gc;
	up = 0;
	return;
}

void swv_experiment(int16_t start, int16_t stop, uint16_t step, uint16_t pulse_height, uint16_t frequency){
	/**
	 * Perform a SWV experiment
	 *
	 * @param start Start voltage in mV.
	 * @param stop Stop voltage in mV.
	 * @param step Step voltage in mV.
	 * @param pulse_height Pulse amplitude in mV.
	 * @param frequency Frequency in Hz.
	 */
	//check experiment limits
	// 	if((start-pulse_height)<-1500 || (start+pulse_height)>(1500)|| start == stop || stop<-1500 || (stop+pulse_height)>1500 || step<1 || step>1500 || pulse_height > 1500 || pulse_height < 1)
	// 	{
		// 		printf("ERR: Experiment parameters outside limits\n\r");
		// 		//return;
	// 	}

	int32_t forward = 0;
	int32_t reverse = 0;
	int32_t data_buffer;
	uint8_t direction;
	uint16_t dacindex_start = ceil((start)*(65535/(double)3000))+32767;
	uint16_t dacindex_stop = ceil(stop*(65535/(double)3000))+32767;
	uint16_t dacindex_step = ceil(step*(65535/(double)3000));
	uint16_t dacindex_pulse_height = ceil(pulse_height*(65535/(double)3000));
	uint16_t dacindex = dacindex_start;
	uint32_t period;
	int16_t fgain = -1;
	int16_t rgain = -1;

	
	if (start < stop)
	direction = 1;
	else
	direction = 0;
	
	tc_enable(&TCF0);
	
	frequency *= 2; //compensate for half-period triggers
	
	//calculate time to ADC trigger
	period = ceil((1/(double)frequency)*F_CPU);
	uint8_t temp_div = ceil((double)period/UINT16_MAX);
	
	if (temp_div == 1)
	tc_write_clock_source(&TCF0,TC_CLKSEL_DIV1_gc);
	else if (temp_div == 2){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV2_gc);
		period /= 2;
	}
	else if (temp_div <= 4){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV4_gc);
		period /= 4;
	}
	else if (temp_div <= 8){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV8_gc);
		period /= 8;
	}
	else if (temp_div <= 64){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV64_gc);
		period /= 64;
	}
	else if (temp_div <= 256){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV256_gc);
		period /= 256;
	}
	else if (temp_div <= 1024){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV1024_gc);
		period /= 1024;
	}
	else{
		printf("Frequency/ADC rate is too low\n\r");
		return;
	}
	
	tc_write_period(&TCF0, (uint16_t)period);
	
	if (direction == 1)
	max5443_set_voltage1(dacindex+dacindex_pulse_height);
	else
	max5443_set_voltage1(dacindex-dacindex_pulse_height);
	
	ads1255_wakeup();
	ads1255_rdatac();
	ads1255_sync();
	
	pot_exp_start();
	TCF0.CNT = 0;
	while (!tc_is_overflow(&TCF0));
	ads1255_wakeup();
	TCF0.CNT = 0;
	
	while ((dacindex <= dacindex_stop && direction == 1) || (dacindex >= dacindex_stop && direction == 0)){
		tc_clear_overflow(&TCF0);
		
		if (fgain > -1)
		//printf("%u, %li, %li\n\r", dacindex, forward*fgain, reverse*rgain);
		printf("%u, %li%0*lu, %li%0*lu\n\r", dacindex, fgain, 0, forward, rgain, 0, reverse);
		
		forward = 0;
		reverse = 0;
		
		while (!tc_is_overflow(&TCF0)){
			autogainswitch();

			
			while (ioport_pin_is_low(IOPORT_CREATE_PIN(PORTD, 5)));
			while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTD, 5)));
			
			// 			while (ioport_pin_is_low(IOPORT_CREATE_PIN(PORTE, 3)));
			// 			while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTE, 3)));
			//			forward = ads1255_read_fast24();
			forward = ads1255_read();
		}
		
		
		if (direction == 1)
		max5443_set_voltage1(dacindex-dacindex_pulse_height);
		else
		max5443_set_voltage1(dacindex+dacindex_pulse_height);

		
		tc_clear_overflow(&TCF0);
		if (direction == 1)
		dacindex += dacindex_step;
		else
		dacindex -= dacindex_step;
		
		while (!tc_is_overflow(&TCF0)){
			autogainswitch();
			
			while (ioport_pin_is_low(IOPORT_CREATE_PIN(PORTD, 5)));
			while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTD, 5)));
			
			// 			while (ioport_pin_is_low(IOPORT_CREATE_PIN(PORTE, 3)));
			// 			while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTE, 3)));
			//			reverse = ads1255_read_fast24();
			reverse = ads1255_read();
		}
		
		if (direction == 1)
		max5443_set_voltage1(dacindex+dacindex_pulse_height);
		else
		max5443_set_voltage1(dacindex-dacindex_pulse_height);
		
	}
	
	pot_exp_stop();
	tc_write_clock_source(&TCF0, TC_CLKSEL_OFF_gc);
	tc_disable(&TCF0);
	tc_write_count(&TCF0,0);
	ads1255_standby();
	
	return;
}

ISR(PORTD_INT0_vect){
	if (portd_int0_callback) {
		portd_int0_callback();
	}
}