experiment.c 28.9 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.
 */
//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 precond_rtc_callback(uint32_t time);
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 lsv_dma_callback(void);
static void lsv_dma_callback1(void);
static void ca_cca_callback(void);
static void portd_int0_ca(void);
static uint8_t _swv_singledir(uint16_t dacindex, uint16_t dacindex_stop, uint16_t dacindex_pulse_height, uint16_t dacindex_step, uint8_t direction);
static uint8_t _dpv_singledir(uint16_t dacindex, uint16_t dacindex_stop, uint16_t dacindex_pulse_height, uint16_t dacindex_step, uint8_t direction);

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

static port_callback_t portd_int0_callback;
static port_callback_t portd_int1_callback;

void send_data_uint16(uint16_t data){
	/**
	 * Sends uint16 data over USB in order in memory (should be LSB first for AVR - little endian)
	 *
	 * @param data 16-bit data
	 * @return Nothing.
	 */
	udi_cdc_putc(((unsigned char *)(&data))[0]);
	udi_cdc_putc(((unsigned char *)(&data))[1]);
}
void send_data_int32(int32_t data){
	/**
	 * Sends int32 data over USB in order in memory (should be LSB first for AVR - little endian)
	 *
	 * @param data 32-bit data
	 * @return Nothing.
	 */
	udi_cdc_putc(((unsigned char *)(&data))[0]);
	udi_cdc_putc(((unsigned char *)(&data))[1]);
	udi_cdc_putc(((unsigned char *)(&data))[2]);
	udi_cdc_putc(((unsigned char *)(&data))[3]);
}

	/**
	 * Initializes AVR port directions and levels
	 *
	 * @return Nothing.
	 */
Michael DM Dryden's avatar
Michael DM Dryden committed
	#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR == 1
		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);
	#endif
	#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR == 2
		arch_ioport_set_port_dir(IOPORT_PORTB, PIN2_bm|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, PIN2_bm|PIN3_bm|PIN4_bm|PIN5_bm|PIN6_bm|PIN7_bm, PIN3_bm|PIN6_bm|PIN7_bm);
		arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, PIN4_bm);
	#endif
	/**
	 * Sets iV gain according to current g_gain value
	 *
	 * @return Nothing.
	 */
	switch (g_gain){
Michael DM Dryden's avatar
Michael DM Dryden committed
		#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR == 1
			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);
Michael DM Dryden's avatar
Michael DM Dryden committed
				printf("#INFO: 500M\n\r");
				break;
Michael DM Dryden's avatar
Michael DM Dryden committed
			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);
				printf("#INFO: 30M\n\r");
				break;
Michael DM Dryden's avatar
Michael DM Dryden committed
			case POT_GAIN_3M:	
				arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN7_bm);
				arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, 0);
				printf("#INFO: 3M\n\r");
				break;
Michael DM Dryden's avatar
Michael DM Dryden committed
			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);
				printf("#INFO: 300k\n\r");
				break;
Michael DM Dryden's avatar
Michael DM Dryden committed
			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);
				printf("#INFO: 30k\n\r");
				break;
Michael DM Dryden's avatar
Michael DM Dryden committed
			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);
				printf("#INFO: 3k\n\r");
				break;
Michael DM Dryden's avatar
Michael DM Dryden committed
			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);
				printf("#INFO: 300\n\r");
				break;
Michael DM Dryden's avatar
Michael DM Dryden committed
			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);
				printf("#INFO: 100\n\r");
				break;
		#endif
		
		#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR == 2
			case POT_GAIN_100M:
				arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, 0);
				arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, 0);
				printf("#INFO: 100M\n\r");
				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);
				printf("#INFO: 30M\n\r");
				break;
				
			case POT_GAIN_3M:
				arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN7_bm);
				arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, 0);
				printf("#INFO: 3M\n\r");
				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);
				printf("#INFO: 300k\n\r");
				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);
				printf("#INFO: 30k\n\r");
				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);
				printf("#INFO: 3k\n\r");
				break;
				
			case POT_GAIN_0:
				arch_ioport_set_port_level(IOPORT_PORTB, PIN6_bm|PIN7_bm, PIN7_bm);
				arch_ioport_set_port_level(IOPORT_PORTD, PIN4_bm, PIN4_bm);
				printf("#INFO: 0\n\r");
				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);
				printf("#INFO: 100\n\r");
				break;
		#endif
Michael DM Dryden's avatar
Michael DM Dryden committed
			default:
				printf("#WAR: Invalid pot gain.\n\r");
				break;
void volt_exp_start(void){
Michael DM Dryden's avatar
Michael DM Dryden committed
	#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR == 1
		arch_ioport_set_port_level(IOPORT_PORTB, PIN3_bm|PIN4_bm|PIN5_bm, PIN3_bm|PIN4_bm|PIN5_bm);
	#endif
	
	#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR == 2
	arch_ioport_set_port_level(IOPORT_PORTB, PIN2_bm|PIN3_bm|PIN4_bm|PIN5_bm, PIN2_bm|PIN4_bm|PIN5_bm);
	#endif
	
Michael DM Dryden's avatar
Michael DM Dryden committed
	 * Disconnects measurement cell.
Michael DM Dryden's avatar
Michael DM Dryden committed
	#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR == 1
		arch_ioport_set_port_level(IOPORT_PORTB, PIN3_bm|PIN4_bm|PIN5_bm, 0);
	#endif
	#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR == 2
Michael DM Dryden's avatar
Michael DM Dryden committed
		arch_ioport_set_port_level(IOPORT_PORTB, PIN2_bm|PIN3_bm|PIN4_bm|PIN5_bm, 0);
Michael DM Dryden's avatar
Michael DM Dryden committed
	#endif
#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR >= 2
void pot_exp_start(void){
	/**
	 * All switches open.
	 */
	arch_ioport_set_port_level(IOPORT_PORTB, PIN2_bm|PIN3_bm|PIN4_bm|PIN5_bm, 0);

}

void ocp_exp_start(void){
	/**
	 * U3C closed
	 */
	arch_ioport_set_port_level(IOPORT_PORTB, PIN2_bm|PIN3_bm|PIN4_bm|PIN5_bm, PIN4_bm);
void precond(int16_t v1, uint16_t t1, int16_t v2, uint16_t t2){ //assumes potentiostat switches are already set
	/**
	 * Performs experiment preconditioning.
	 *
	 * @param v1 First potential (DAC index).
	 * @param t1 First duration (s).
	 * @param v2 Second potential (DAC index).
	 * @param t2 Second duration (s).
	 */
	
	while (RTC.STATUS & RTC_SYNCBUSY_bm);
	RTC.PER = 65535;
	while (RTC.STATUS & RTC_SYNCBUSY_bm);
	RTC.CTRL = RTC_PRESCALER_DIV1024_gc; //1s tick
	rtc_set_callback((rtc_callback_t)precond_rtc_callback);
	
	up = 1;
	
	//first potential
		max5443_set_voltage1(v1);
		rtc_set_alarm(t1);
		RTC.CNT = 0;
		volt_exp_start();
		while (up){
			if (udi_cdc_is_rx_ready()){
				if (getchar() == 'a'){
					precond_rtc_callback(t1);
					printf("##ABORT\n\r");
					goto aborting;
				}
			}
			if (time_old != RTC.CNT){
				time_old = RTC.CNT;
				printf("#%u\n\r",time_old);
			}
		max5443_set_voltage1(v2);
		rtc_set_alarm(t2);
		RTC.CNT = 0;
		volt_exp_start();
		while (up){
			if (udi_cdc_is_rx_ready()){
				if (getchar() == 'a'){
					precond_rtc_callback(t2);
					printf("##ABORT\n\r");
					goto aborting;
				}
			}
			if (time_old != RTC.CNT){
				time_old = RTC.CNT;
				printf("#%u\n\r",time_old);
			}
		return;
}

static void precond_rtc_callback(uint32_t time){
	up = 0;
	RTC.INTCTRL |= RTC_COMPINTLVL_OFF_gc;
}

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");
			if (lsv_experiment(start,v1,slope,firstrun) == 1)
				return;
		if (lsv_experiment(v1,v2,slope,firstrun) == 1)
			return;
			if(lsv_experiment(v2,start,slope,firstrun) == 1)
				return;
        printf("S\n\r"); //signal end of scan
	printf("D\n\r"); //signal end of experiment
	
uint8_t 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");
	uint16_t dacindex_start = ceil(start*(65536/(double)3000)+32768);
	dacindex_stop = ceil(stop*(65536/(double)3000)+32768);
	max5443_set_voltage1(dacindex_start);
	
	if (first_run == 1 || first_run == 2){
		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");
		ads1255_wakeup();
		tc_write_period(&TCC1, 0xffff);
		tc_write_period(&TCC0, (uint16_t)timer_period);
// 		//DMA
// 		struct dma_channel_config dmach_conf;
// 		memset(&dmach_conf, 0, sizeof(dmach_conf));
// 
// 		dma_channel_set_burst_length(&dmach_conf, DMA_CH_BURSTLEN_1BYTE_gc);
// 		dma_channel_set_transfer_count(&dmach_conf, 1);
// 
// 		dma_channel_set_src_reload_mode(&dmach_conf, DMA_CH_SRCRELOAD_TRANSACTION_gc);
// 		dma_channel_set_dest_reload_mode(&dmach_conf, DMA_CH_DESTRELOAD_NONE_gc);
// 
// 		dma_channel_set_src_dir_mode(&dmach_conf, DMA_CH_SRCDIR_FIXED_gc);
// 		dma_channel_set_source_address(&dmach_conf, (uint16_t)(uintptr_t)&TCC1.CNTH);
// 
// 		dma_channel_set_dest_dir_mode(&dmach_conf, DMA_CH_DESTDIR_FIXED_gc);
// 		dma_channel_set_destination_address(&dmach_conf, (uint16_t)(uintptr_t)&USARTC1.DATA);
// 
//         dma_channel_set_trigger_source(&dmach_conf, DMA_CH_TRIGSRC_OFF_gc);
//         dma_channel_set_single_shot(&dmach_conf);
// 		
// 		dma_set_callback(0, (dma_callback_t)lsv_dma_callback);
// 		dma_channel_set_interrupt_level(&dmach_conf, DMA_INT_LVL_HI);
// 
// 		dma_channel_write_config(0, &dmach_conf);
// 		
// 		dma_set_callback(1, (dma_callback_t)lsv_dma_callback1);
// 		dma_channel_set_source_address(&dmach_conf, (uint16_t)(uintptr_t)&TCC1.CNTL);
// 		dma_channel_write_config(1, &dmach_conf);
 	}
// 	
// 	dma_channel_enable(0);
	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
	//Experiment run with interrupts
	while (up != 0){
		if (udi_cdc_is_rx_ready()){
			if (getchar()=='a'){
				tce1_ovf_callback_lsv();
				ret = 1;
				goto aborting;
		aborting:
			tc_disable(&TCC0);
			TCC0.CNT = 0x0;
			tc_disable(&TCC1);
Michael DM Dryden's avatar
Michael DM Dryden committed
 	int32_t result = ads1255_read_fast24();

	static uint16_t last_value = 0;
	uint32_t current = TCC1.CNT;
Michael DM Dryden's avatar
Michael DM Dryden committed
	printf("B\n");
	send_data_uint16((current+last_value)>>1); //DAC value is average of current and last timer - approximation of center of averaging window
	send_data_int32(result); 
Michael DM Dryden's avatar
Michael DM Dryden committed
	printf("\n");
	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;
#if BOARD_VER_MAJOR == 1 && BOARD_VER_MINOR >= 2
void pot_experiment(uint16_t time_seconds, uint8_t exp_type){
	 * @param time_seconds Time until automatic stop. If 0, can only be canceled by abort signal.
	 * @param exp_type Type of experiment, POT_OCP for OCP, POT_POTENT for potentiometry
	 */
	while (RTC.STATUS & RTC_SYNCBUSY_bm);
	RTC.PER = 999;
	while (RTC.STATUS & RTC_SYNCBUSY_bm);
	RTC.CTRL = RTC_PRESCALER_DIV1_gc; //1ms tick
	RTC.CNT = 0;
	
	EVSYS.CH0MUX = EVSYS_CHMUX_RTC_OVF_gc; //EV CH0 -- RTC overflow 1s
	
	portd_int0_callback = portd_int0_ca; //ADC interrupt
	
	tc_enable(&TCC0);
	
	ads1255_mux(ADS_MUX_POT);
	ads1255_rdatac();
	ads1255_wakeup();
	
	tc_write_period(&TCC0,0xffff);
	tc_write_clock_source(&TCC0, TC_CLKSEL_EVCH0_gc);
	tc_set_direction(&TCC0, TC_UP);
	
	up = 1;
	if (time_seconds >= 1){ //only enable interrupt if non-zero timeout specified
		tc_set_cca_interrupt_callback(&TCC0, ca_cca_callback);
		tc_write_cc(&TCC0, TC_CCA, time_seconds-1);
		tc_enable_cc_channels(&TCC0, TC_CCAEN);
		tc_set_cca_interrupt_level(&TCC0, TC_INT_LVL_MED);
	}
	
	if (exp_type == POT_OCP)
	{
		ocp_exp_start();
	}
	else if (exp_type == POT_POTENT)
	{
		pot_exp_start();
	}
	
	while (up !=0){
		if (udi_cdc_is_rx_ready()){
			if (getchar() == 'a'){
				ca_cca_callback();
				printf("##ABORT\n\r");
				goto aborting;
			}
		}
	}
	
	aborting:
		tc_set_cca_interrupt_level(&TCC0, TC_INT_LVL_OFF);
		tc_write_clock_source(&TCC0, TC_CLKSEL_OFF_gc);
		tc_disable(&TCC0);
		volt_exp_stop();
		ads1255_standby();

		return;
}
#endif

void ca_experiment(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; //1ms tick
	RTC.CNT = 0;
	EVSYS.CH0MUX = EVSYS_CHMUX_RTC_OVF_gc; //EV CH0 -- RTC overflow 1s
	portd_int0_callback = portd_int0_ca; //ADC interrupt
	
	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_MED);
	
	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]);
		while (up !=0){
			if (udi_cdc_is_rx_ready()){
				if (getchar() == 'a'){
					ca_cca_callback();
					printf("##ABORT\n\r");
					goto aborting;
				}
			}
		}
	aborting:
		tc_set_cca_interrupt_level(&TCC0, TC_INT_LVL_OFF);
		tc_write_clock_source(&TCC0, TC_CLKSEL_OFF_gc);
		tc_disable(&TCC0);
Michael DM Dryden's avatar
Michael DM Dryden committed
	int32_t data = ads1255_read_fast24();
	printf("B\n");
	send_data_uint16(TCC0.CNT);
	send_data_uint16(RTC.CNT);
Michael DM Dryden's avatar
Michael DM Dryden committed
	send_data_int32(data);
	/**
	 * Interrupt handler for CA. Triggers when counter matches CC to stop potential step.
	 *
	 */
void swv_experiment(int16_t start, int16_t stop, uint16_t step, uint16_t pulse_height, uint16_t frequency, uint16_t scans){
	/**
	 * 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.
	 * @param scans Number of scans (0 for single direction mode)
	uint16_t dacindex_start = ceil((start)*(65536/(double)3000))+32768;
	uint16_t dacindex_stop = ceil(stop*(65536/(double)3000))+32768;
	uint16_t dacindex_step = ceil(step*(65536/(double)3000));
	uint16_t dacindex_pulse_height = ceil(pulse_height*(65536/(double)3000));
	uint16_t dacindex = dacindex_start;
	uint32_t period;
	
	if (start < stop)
	
	tc_enable(&TCF0);
	
	frequency *= 2; //compensate for half-period triggers
	
	//calculate time to ADC trigger
	period = ceil((1/(double)frequency)*F_CPU);
	uint16_t temp_div = ceil((double)period/65536);
		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);
		max5443_set_voltage1(dacindex-dacindex_pulse_height);
	do{
		TCF0.CNT = 0;
		while (!tc_is_overflow(&TCF0));
		ads1255_wakeup(); //synchronize ADC
		TCF0.CNT = 0;
	
		if (_swv_singledir(dacindex_start, dacindex_stop, dacindex_pulse_height, dacindex_step, direction))
			goto aborting; //function will return non-zero if abort called over USB
		if (scans > 0){ //non-cyclic mode skips out after one direction
			TCF0.CNT = 0;
			if (_swv_singledir(dacindex_stop, dacindex_start, dacindex_pulse_height, dacindex_step, !direction)) //swap start and stop and invert direction for second half of scan
				goto aborting;
		}
		printf("S\n\r"); //signal end of scan
		
	} while (scans-- > 1); //will underflow after comparison for scans = 0 , but shouldn't matter 
	
	printf("D\n\r"); //signal end of experiment
	
	aborting:
		tc_write_clock_source(&TCF0, TC_CLKSEL_OFF_gc);
		tc_disable(&TCF0);
		TCF0.CNT = 0;
		ads1255_standby();
	
		return;
}

uint8_t _swv_singledir (uint16_t dacindex, uint16_t dacindex_stop, uint16_t dacindex_pulse_height, uint16_t dacindex_step, uint8_t direction){
	/**
	 * Internal function that performs a single direction sweep for SWV
	 *
	 * @param dacindex Starting voltage as dac index
	 * @param dacindex_stop Stop voltage in dac index.
	 * @param dacindex_step Step voltage in dac index.
	 * @param dacindex_pulse_height Pulse amplitude in dac index.
	 * @param direction Scan direction - 1 for up, 0 for down.
	 */
	int32_t forward = 0;
	int32_t reverse = 0;
	uint16_t lastindex = 0;
	
	if (direction == 1)
		max5443_set_voltage1(dacindex+dacindex_pulse_height);
	else
		max5443_set_voltage1(dacindex-dacindex_pulse_height);
	
	while ((dacindex <= dacindex_stop && direction == 1) || (dacindex >= dacindex_stop && direction == 0)){
		tc_clear_overflow(&TCF0);
				
		while (!tc_is_overflow(&TCF0)){ //convert continuously until tc overflow - datum is last collected point
			while (ioport_pin_is_low(IOPORT_CREATE_PIN(PORTD, 5)));  //wait for next valid datum
			while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTD, 5)));
			if (udi_cdc_is_rx_ready()){ //check for abort signal over USB
		if (direction == 1) //switch voltage to other half of cycle
			max5443_set_voltage1(dacindex-dacindex_pulse_height);
			max5443_set_voltage1(dacindex+dacindex_pulse_height);
		tc_clear_overflow(&TCF0); //reset timer OVF
		while (!tc_is_overflow(&TCF0)){ //wait for tc overflow
			while (ioport_pin_is_low(IOPORT_CREATE_PIN(PORTD, 5)));  //wait for next valid datum
			while (ioport_pin_is_high(IOPORT_CREATE_PIN(PORTD, 5)));
			if (udi_cdc_is_rx_ready()){
				if (getchar() == 'a')
			
		lastindex = dacindex;
			
		//increment dacindex
		if (direction == 1){
			dacindex += dacindex_step;
			max5443_set_voltage1(dacindex+dacindex_pulse_height);
		else{
			dacindex -= dacindex_step;
			max5443_set_voltage1(dacindex-dacindex_pulse_height);
		}
			
		//data output
		printf("B\n");
		send_data_uint16(lastindex);
		send_data_int32(forward);
		send_data_int32(reverse);
		printf("\n");
void dpv_experiment(int16_t start, int16_t stop, uint16_t step, uint16_t pulse_height, uint16_t pulse_period, uint16_t pulse_width){
	/**
	 * Perform a DPV 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 pulse_period Pulse period in ms.
	 * @param pulse_width Pulse width in ms.
	 */

	uint8_t direction;
	uint16_t dacindex_start = ceil((start)*(65536/(double)3000))+32768;
	uint16_t dacindex_stop = ceil(stop*(65536/(double)3000))+32768;
	uint16_t dacindex_step = ceil(step*(65536/(double)3000));
	uint16_t dacindex_pulse_height = ceil(pulse_height*(65536/(double)3000));
	uint16_t dacindex = dacindex_start;
	uint32_t cpu_period;
	uint32_t cpu_width;
	
	if (start < stop)
		direction = 1;
	else
		direction = 0;
	
	tc_enable(&TCF0);
	
	//calculate time to ADC trigger
	cpu_period = ceil((double)pulse_period*1e-3*F_CPU);
	uint16_t temp_div = ceil((double)cpu_period/65536);
	
	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);
	else if (temp_div <= 4){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV4_gc);
		temp_div = 4;
	}
	else if (temp_div <= 8){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV8_gc);
		temp_div = 8;
	}
	else if (temp_div <= 64){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV64_gc);
		temp_div = 64;
	}
	else if (temp_div <= 256){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV256_gc);
		temp_div = 256;
	}
	else if (temp_div <= 1024){
		tc_write_clock_source(&TCF0,TC_CLKSEL_DIV1024_gc);
		temp_div = 1024;
	}
	else{
		printf("#Frequency/ADC rate is too low\n\r");
		return;
	}
	
	tc_write_period(&TCF0, (uint16_t)(cpu_period/temp_div));
	cpu_width = (double)pulse_width*1e-3*F_CPU;
	tc_write_cc(&TCF0, TC_CCA, (uint16_t)(cpu_width/temp_div));
	tc_enable_cc_channels(&TCF0, TC_CCAEN);
	
	if (direction == 1)
		max5443_set_voltage1(dacindex+dacindex_pulse_height);
	else
		max5443_set_voltage1(dacindex-dacindex_pulse_height);
	
	ads1255_wakeup();
	ads1255_rdatac();
	ads1255_sync();
	volt_exp_start();
	
	TCF0.CNT = 0;
	while (!tc_is_overflow(&TCF0));
	ads1255_wakeup(); //synchronize ADC
	TCF0.CNT = 0;