/* * experiment.c * * Created: 01/10/2012 10:59:48 PM * Author: mdryden */ /** * @file experiment.c * @author Michael DM Dryden * @version 1.0 * * * @section DESCRIPTION * * Contains Functions for performing experiments and adjusting potentiostat settings other than DAC and ADC. */ #include "experiment.h" //Public variable definitions uint16_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 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); //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]); } 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; if (autogain_enable == 0) return 0; if (g_gain == POT_GAIN_500M) return 0; if (last_return==1){ 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){ --g_gain; 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; } if ((overcurrent*-1) == (ADS_OVER_UNDER_SAMPLES) && g_gain < POT_GAIN_3M){ if (hysteresis < ADS_OVER_UNDER_SAMPLES+3){ ++hysteresis; return 0; } ++g_gain; 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; } hysteresis = 0; return 0; } void pot_set_gain(void){ /** * 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); printf("#INFO: 500M\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_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; 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; default: printf("#WAR: Invalid pot gain.\n\r"); break; return; } } void pot_exp_start(void){ /** * Connects measurement cell to rest of circuit. */ 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 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). */ uint16_t time_old = 0; 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 if (t1 > 0){ max5443_set_voltage1(v1); rtc_set_alarm(t1); RTC.CNT = 0; pot_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); } } } up = 1; time_old = 0; if (t2 > 0){ max5443_set_voltage1(v2); rtc_set_alarm(t2); RTC.CNT = 0; pot_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); } } } aborting: pot_exp_stop(); 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. */ // check if start is [v1,v2] int8_t firstrun = 1; if((start < v1 && start < v2) || (start > v1 && start > v2)){ printf("#ERR: Start must be within [v1, v2]\n\r"); return; } while(scans > 0){ if (start != v1){ if (lsv_experiment(start,v1,slope,firstrun) == 1) return; firstrun = 0; } if (start == v2 && scans == 1) firstrun = -1; if (lsv_experiment(v1,v2,slope,firstrun) == 1) return; if (scans == 1) firstrun = -1; if (start != v2) if(lsv_experiment(v2,start,slope,firstrun) == 1) return; --scans; firstrun = 0; printf("S\n\r"); //signal end of scan } printf("D\n\r"); //signal end of experiment return; } 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. */ uint8_t ret = 0; //check experiment limits if(start<-1500 || start>=1500 ||start==stop|| stop<-1500 || stop>=1500 || slope>7000) { printf("#ERR: Experiment parameters outside limits\n\r"); return ret; } uint16_t dacindex_start = ceil(start*(65536/(double)3000)+32768); dacindex_stop = ceil(stop*(65536/(double)3000)+32768); 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); portd_int0_callback = porte_int0_lsv; //ADC read //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 ret; } //printf("Period:%lu\n\r", timer_period); 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 tc_write_clock_source(&TCC1, TC_CLKSEL_EVCH0_gc); //Experiment run with interrupts while (up != 0){ if (udi_cdc_is_rx_ready()){ if (getchar()=='a'){ tce1_ovf_callback_lsv(); ret = 1; goto aborting; } } } if (first_run == -1 || first_run == 2) { aborting: tc_disable(&TCC0); TCC0.CNT = 0x0; tc_disable(&TCC1); pot_exp_stop(); ads1255_standby(); return ret; } return ret; } static void porte_int0_lsv(void){ /** * ISR for taking LSV measurements. */ int32_t result = ads1255_read_fast24(); static uint16_t last_value = 0; uint32_t current = TCC1.CNT; 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); last_value = (uint16_t)current; printf("\n"); return; } 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 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); TCC0.CNT = 0; max5443_set_voltage1(step_dac[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]); printf("#DAC: %u\n\r", step_dac[i]); PORTD.INTCTRL = PORT_INT0LVL_LO_gc; 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); pot_exp_stop(); ads1255_standby(); return; } static void portd_int0_ca(void){ int32_t data = ads1255_read_fast24(); printf("B\n"); send_data_uint16(TCC0.CNT); send_data_uint16(RTC.CNT); send_data_int32(data); printf("\n"); } static void ca_cca_callback(void){ /** * Interrupt handler for CA. Triggers when counter matches CC to stop potential step. * */ 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, 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. */ 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 period; 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/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); 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(); 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: pot_exp_stop(); 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){ 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))); forward = ads1255_read_fast24(); if (udi_cdc_is_rx_ready()){ //check for abort signal over USB if (getchar() == 'a') return 1; } } if (direction == 1) //switch voltage to other half of cycle max5443_set_voltage1(dacindex-dacindex_pulse_height); else 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))); reverse = ads1255_read_fast24(); if (udi_cdc_is_rx_ready()){ if (getchar() == 'a') return 1; } } 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"); } return 0; } ISR(PORTD_INT0_vect){ if (portd_int0_callback) { portd_int0_callback(); } }