예제 #1
0
/*  This function get the calibration data from the production calibration.
 *
 *  The calibration data is loaded from flash and stored in the calibration
 *  register. The calibration data reduces the gain error in the adc.
 *
 *  \param  adc          Pointer to ADC module register section.
 */
 void ADC_CalibrationValues_Set(ADC_t * adc)
{
	if(&ADCA == adc){
		 // Get ADCCAL0 from byte address 0x20 (Word address 0x10.  
		adc->CAL = SP_ReadCalibrationByte(0x20);
	}else {
		// Get ADCCAL0 from byte address 0x24 (Word address 0x12.  
		adc->CAL = SP_ReadCalibrationByte(0x24);
	}
} 
예제 #2
0
/*
	Read factory calibration from NVM:
*/
void read_calibration(void){	
	//Get the factory temperature value, at 85 Deg. C.		
	ad_temp_ref = SP_ReadCalibrationByte(PRODSIGNATURES_ADCACAL0) ;
	ad_temp_ref |= SP_ReadCalibrationByte(PRODSIGNATURES_ADCACAL1) <<8;
}
// Init everything
void init(void)
{
        // clock
        OSC.CTRL |= OSC_RC32MEN_bm; // turn on 32 MHz oscillator
        while (!(OSC.STATUS & OSC_RC32MRDY_bm)) { }; // wait for it to start
        CCP = CCP_IOREG_gc;
        CLK.CTRL = CLK_SCLKSEL_RC32M_gc; // switch osc
        DFLLRC32M.CTRL = DFLL_ENABLE_bm; // turn on DFLL
        
        // disable JTAG
        CCP = CCP_IOREG_gc;
        MCU.MCUCR = 1;
        
        // Init pins
        LED_PORT.OUTCLR = LED_USR_0_PIN_bm | LED_USR_1_PIN_bm | LED_USR_2_PIN_bm;
        LED_PORT.DIRSET = LED_USR_0_PIN_bm | LED_USR_1_PIN_bm | LED_USR_2_PIN_bm;
        
        // Init buttons
        BTN_PORT.DIRCLR = BTN_PIN_bm;
        
        // UARTs
        usart.set_tx_buffer(usart_txbuf, USART_TX_BUF_SIZE);
        usart.set_rx_buffer(usart_rxbuf, USART_RX_BUF_SIZE);
        usart.begin(UART_BAUD_RATE);
        usart.setup_stream(&usart_stream);
        
        usart_n0.set_tx_buffer(usart_n0_txbuf, NODE_TX_BUF_SIZE);
        usart_n0.set_rx_buffer(usart_n0_rxbuf, NODE_RX_BUF_SIZE);
        usart_n0.begin(NODE_BAUD_RATE);
        usart_n[0] = &usart_n0;
        xgrid.add_node(&usart_n0);
        usart_n1.set_tx_buffer(usart_n1_txbuf, NODE_TX_BUF_SIZE);
        usart_n1.set_rx_buffer(usart_n1_rxbuf, NODE_RX_BUF_SIZE);
        usart_n1.begin(NODE_BAUD_RATE);
        usart_n[1] = &usart_n1;
        xgrid.add_node(&usart_n1);
        usart_n2.set_tx_buffer(usart_n2_txbuf, NODE_TX_BUF_SIZE);
        usart_n2.set_rx_buffer(usart_n2_rxbuf, NODE_RX_BUF_SIZE);
        usart_n2.begin(NODE_BAUD_RATE);
        usart_n[2] = &usart_n2;
        xgrid.add_node(&usart_n2);
        usart_n3.set_tx_buffer(usart_n3_txbuf, NODE_TX_BUF_SIZE);
        usart_n3.set_rx_buffer(usart_n3_rxbuf, NODE_RX_BUF_SIZE);
        usart_n3.begin(NODE_BAUD_RATE);
        usart_n[3] = &usart_n3;
        xgrid.add_node(&usart_n3);
        usart_n4.set_tx_buffer(usart_n4_txbuf, NODE_TX_BUF_SIZE);
        usart_n4.set_rx_buffer(usart_n4_rxbuf, NODE_RX_BUF_SIZE);
        usart_n4.begin(NODE_BAUD_RATE);
        usart_n[4] = &usart_n4;
        xgrid.add_node(&usart_n4);
        usart_n5.set_tx_buffer(usart_n5_txbuf, NODE_TX_BUF_SIZE);
        usart_n5.set_rx_buffer(usart_n5_rxbuf, NODE_RX_BUF_SIZE);
        usart_n5.begin(NODE_BAUD_RATE);
        usart_n[5] = &usart_n5;
        xgrid.add_node(&usart_n5);
        
        // ADC setup
        ADCA.CTRLA = ADC_DMASEL_OFF_gc | ADC_FLUSH_bm;
        ADCA.CTRLB = ADC_CONMODE_bm | ADC_RESOLUTION_12BIT_gc;
        ADCA.REFCTRL = ADC_REFSEL_INT1V_gc | ADC_BANDGAP_bm;
        ADCA.EVCTRL = ADC_SWEEP_0123_gc | ADC_EVSEL_0123_gc | ADC_EVACT_SWEEP_gc;
        ADCA.PRESCALER = ADC_PRESCALER_DIV64_gc;
        ADCA.CALL = SP_ReadCalibrationByte(PROD_SIGNATURES_START + ADCACAL0_offset);
        ADCA.CALH = SP_ReadCalibrationByte(PROD_SIGNATURES_START + ADCACAL1_offset);
        
        ADCA.CH0.CTRL = ADC_CH_GAIN_1X_gc | ADC_CH_INPUTMODE_SINGLEENDED_gc;
        ADCA.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN1_gc;
        ADCA.CH0.INTCTRL = ADC_CH_INTMODE_COMPLETE_gc | ADC_CH_INTLVL_LO_gc;
        ADCA.CH0.RES = 0;
        
        ADCA.CH1.CTRL = ADC_CH_GAIN_1X_gc | ADC_CH_INPUTMODE_SINGLEENDED_gc;
        ADCA.CH1.MUXCTRL = ADC_CH_MUXPOS_PIN2_gc;
        ADCA.CH1.INTCTRL = ADC_CH_INTMODE_COMPLETE_gc | ADC_CH_INTLVL_LO_gc;
        ADCA.CH1.RES = 0;
        
        ADCA.CH2.CTRL = ADC_CH_GAIN_1X_gc | ADC_CH_INPUTMODE_SINGLEENDED_gc;
        ADCA.CH2.MUXCTRL = ADC_CH_MUXPOS_PIN3_gc;
        ADCA.CH2.INTCTRL = ADC_CH_INTMODE_COMPLETE_gc | ADC_CH_INTLVL_LO_gc;
        ADCA.CH2.RES = 0;
        
        ADCA.CH3.CTRL = ADC_CH_GAIN_1X_gc | ADC_CH_INPUTMODE_SINGLEENDED_gc;
        ADCA.CH3.MUXCTRL = ADC_CH_MUXPOS_PIN4_gc;
        ADCA.CH3.INTCTRL = ADC_CH_INTMODE_COMPLETE_gc | ADC_CH_INTLVL_LO_gc;
        ADCA.CH3.RES = 0;
        
        //ADCA.CTRLA |= ADC_ENABLE_bm;
        //ADCA.CTRLB |= ADC_FREERUN_bm;
        
        // TCC
        TCC0.CTRLA = TC_CLKSEL_DIV256_gc;
        TCC0.CTRLB = 0;
        TCC0.CTRLC = 0;
        TCC0.CTRLD = 0;
        TCC0.CTRLE = 0;
        TCC0.INTCTRLA = TC_OVFINTLVL_LO_gc;
        TCC0.INTCTRLB = 0;
        TCC0.CNT = 0;
        TCC0.PER = 125;
        
        // ADC trigger on TCC0 overflow
        //EVSYS.CH0MUX = EVSYS_CHMUX_TCC0_OVF_gc;
        //EVSYS.CH0CTRL = 0;
        
        // I2C
        //i2c.begin(400000L);
        
        // SPI
        //spi.begin(SPI_MODE_2_gc, SPI_PRESCALER_DIV4_gc, 1);
        
        // CS line
        //SPI_CS_PORT.OUTSET = SPI_CS_DEV_PIN_bm;
        //SPI_CS_PORT.DIRSET = SPI_CS_DEV_PIN_bm;
        
        // Interrupts
        PMIC.CTRL = PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm;
        
        sei();
		
		//LED_PORT.OUTTGL = LED_USR_1_PIN_bm;
}
예제 #4
0
void Sensors_Init(void){
	
	
	ADCA.CALL = SP_ReadCalibrationByte( PROD_SIGNATURES_START + ADCACAL0_offset );
	ADCA.CALH = SP_ReadCalibrationByte( PROD_SIGNATURES_START + ADCACAL1_offset );
	
	ADCB.CALL = SP_ReadCalibrationByte( PROD_SIGNATURES_START + ADCBCAL0_offset );
	ADCB.CALH = SP_ReadCalibrationByte( PROD_SIGNATURES_START + ADCBCAL1_offset );
	
    // Port A = ekg, temperature, humdity
	
    ADCA.EKGChannel.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;	// set input mode
    ADCA.EKGChannel.MUXCTRL = EKGMUXPos;
	
    ADCA.temperatureChannel.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;	// set input mode
    ADCA.temperatureChannel.MUXCTRL = temperatureMUXPos;
	
    ADCA.humidityChannel.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;	// set input mode
    ADCA.humidityChannel.MUXCTRL = humidityMUXPos;
	
	ADCA.groundChannel.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;	// set input mode
    ADCA.groundChannel.MUXCTRL = groundMUxPos;
	
    ADCA.PRESCALER = (ADCA.PRESCALER & (~ADC_PRESCALER_gm)) | ADC_PRESCALER_DIV64_gc;
	
    ADCA.REFCTRL = ADC_REFSEL_AREFA_gc;
    ADCA.EVCTRL = (ADCA.EVCTRL & (~ADC_SWEEP_gm)) | ADC_SWEEP_0123_gc;
	
    ADCA.CTRLB |= ADC_FREERUN_bm; // free running mode
	
    ADCA.temperatureChannel.CTRL |= ADC_CH_START_bm;
    ADCA.humidityChannel.CTRL |= ADC_CH_START_bm;
    ADCA.EKGChannel.CTRL |= ADC_CH_START_bm;
	ADCA.groundChannel.CTRL |= ADC_CH_START_bm;
    ADCA.CTRLA = ADC_ENABLE_bm;							// enable adc w/o calibrating
	
	_delay_ms(10);
	zeroOffsetA = ADCA.groundResult;
	
	// Port B = Respiration
	
	ADCB.respirationChannel.CTRL = ADC_CH_INPUTMODE_DIFFWGAIN_gc | ADC_CH_GAIN_8X_gc;	// set input mode
	ADCB.respirationChannel.MUXCTRL = respirationMUXPos | neg_resipirationMUXPos;
	
	ADCB.PRESCALER = (ADCB.PRESCALER & (~ADC_PRESCALER_gm)) | ADC_PRESCALER_DIV64_gc;
	
	ADCB.REFCTRL = ADC_REFSEL_AREFA_gc;
	ADCB.EVCTRL = (ADCB.EVCTRL & (~ADC_SWEEP_gm)) | ADC_SWEEP_0123_gc;
	
	ADCB.CTRLB |= ADC_FREERUN_bm | ADC_CONMODE_bm; // free running mode & signed
	
	ADCB.respirationChannel.CTRL |= ADC_CH_START_bm;
	
	ADCB.CTRLA = ADC_ENABLE_bm;							// enable adc w/o calibrating
	
	_delay_ms(10);
	zeroOffsetB = ADCB.groundResult;
	
	respirationPort.DIRSET = 1<<respirationDriver;
	respirationPort.OUTSET = 1<<respirationDriver;
	
	
	// ekg @ 300hz
	// fclk = 14745600
	// div  = 64
	// per  = 768 (remember to subtract 1)
	// => 300 samples per second
	
	// Set period/TOP value
	Sensors_Timer_300HZ.PER = 767;
	//Sensors_Timer_300HZ.PER = 2303;		// 100 hz
	
	// Select clock source
	Sensors_Timer_300HZ.CTRLA = (TCD1.CTRLA & ~TC0_CLKSEL_gm) |  TC_CLKSEL_DIV64_gc;
	
	// Enable CCA interrupt
	Sensors_Timer_300HZ.INTCTRLA = (TCD1.INTCTRLA & ~TC0_OVFINTLVL_gm) | TC_CCAINTLVL_HI_gc;
	
	Sensors_ResetTemperatureBuffers();
	Sensors_ResetRespirationBuffers();
	Sensors_ResetEKGBuffers();
	Sensors_ResetHumidityBuffers();
}
예제 #5
0
void IR_sensor_init()
{
	// IR sensors use ADCB channel 0, all the time

	/* SET INPUT PINS AS INPUTS */
	
	IR_SENSOR_PORT.DIRCLR = IR_SENSOR_0_PIN_bm | IR_SENSOR_1_PIN_bm | IR_SENSOR_2_PIN_bm | IR_SENSOR_3_PIN_bm | IR_SENSOR_4_PIN_bm | IR_SENSOR_5_PIN_bm;
	
	// 28.16.3 REFCTRL – Reference Control register
	//
	// Bit 1 – BANDGAP: Bandgap enable
	// Setting this bit enables the Bandgap for ADC measurement. Note that if any other functions are
	// using the Bandgap already, this bit does not need to be set when the internal 1.00V reference is
	// used in ADC or DAC, or if the Brown-out Detector is enabled.
	//
	// Bits 6:4 – REFSEL[2:0]: ADC Reference Selection
	// These bits selects the reference for the ADC

	//ADCB.REFCTRL = ADC_REFSEL_VCC_gc;								// Vcc/1.6
	//ADCB.REFCTRL = 0b01000000;									// Vcc/2
	//ADCB.REFCTRL = 0b00100000;									// AREFA = 0.71 V
	//ADCB.REFCTRL = 0b00110000;									// AREFB = 0.54 V

	// 28.16.2 CTRLB – ADC Control Register B
	//
	// Bit 7 – IMPMODE: Gain Stage Impedance Mode
	// This bit controls the impedance mode of the gain stage. See GAIN setting with ADC Channel
	// Register description for more information.
	//
	// Bit 6:5 – CURRLIMIT[1:0]: Current Limitation
	// These bits can be used to limit the maximum current consumption of the ADC. Setting these bits
	// will also reduce the maximum sampling rate. The available settings is shown in Table 28-3 on
	// page 367. The indicated current limitations are nominal values, refer to device datasheet for
	// actual current limitation for each setting.
	//
	// Bit 4 – CONVMODE: ADC Conversion ModePlot
	// This bit controls whether the ADC will work in signed or unsigned mode. By default this bit is
	// cleared and the ADC is configured for unsigned mode. When this bit is set the ADC is configured
	// for signed mode.
	//
	// Bit 3 – FREERUN: ADC Free Running Mode
	// When the bit is set to one, the ADC is in free running mode and ADC channels defined in the
	// EVCTRL register are swept repeatedly.
	//
	// Bit 2:1 – RESOLUTION[1:0]: ADC Conversion Result Resolution
	// These bits define whether the ADC completes the conversion at 12- or 8-bit result. They also
	// define whether the 12-bit result is left or right oriented in the 16-bit result registers. See Table
	// 28-4 on page 367 for possible settings.

	ADCB.CTRLB = ADC_RESOLUTION_8BIT_gc;		// use 8 bit resolution
	ADCB.CTRLB |= ADC_CONMODE_bm;		// switch to signed mode
	
	ADCB.PRESCALER = ADC_PRESCALER_DIV512_gc;
	
	/* WARNING! WHEN DIFFERENTIAL INPUT IS USED, SIGNED MODE MUST BE USED (sec. 28.6 of Manual) */

	//ADCB.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;				// can use either signed or unsigned mode
	ADCB.CH0.CTRL = ADC_CH_INPUTMODE_DIFF_gc;						// requires selecting signed mode
	//ADCB.CH0.CTRL = ADC_CH_INPUTMODE_DIFFWGAIN_gc;				// requires selecting signed mode
	
	/* SELECT MUXNEG */
	
	// selecting the negative input sets where the zero of the signed output is
	// note that this is NOT a negative voltage, no negative voltages should ever be applied the the XMEGA
	
	ADCB.CH0.MUXCTRL = ADC_CH_MUXNEG_PIN0_gc;		// use VREF_IN for the negative input (0.54 V)


	/* READ & LOAD CALIBRATION (PRODUCTION SIGNATURE ROW) */	//TODO (unlikely will make any difference)
	
	ADCBcalibration0 = SP_ReadCalibrationByte(offsetof( NVM_PROD_SIGNATURES_t, ADCBCAL0 ));
	ADCBcalibration1 = SP_ReadCalibrationByte(offsetof( NVM_PROD_SIGNATURES_t, ADCBCAL1 ));
	ADCAcalibration0 = SP_ReadCalibrationByte(offsetof( NVM_PROD_SIGNATURES_t, ADCACAL0 ));
	ADCAcalibration1 = SP_ReadCalibrationByte(offsetof( NVM_PROD_SIGNATURES_t, ADCACAL1 ));

	// for Droplet3, the correct values are:
	//	ADCACAL0	68
	//	ADCACAL1	4
	//	ADCBCAL0	68
	//	ADCBCAL1	4

	// SHOULD WE WRITE THEM???
	
	//ADCB.CALL = ADCBcalibration0;
	//ADCB.CALH = ADCBcalibration1;
	//ADCB.CALL = 68;
	//ADCB.CALH = 4;
	
	ADCB.CTRLA = ADC_ENABLE_bm;

	/* FIND AND RECORD THE ZERO-OFFSET OF EACH IR DIRECTION */

	PORTB.DIRSET = 0b11111100;		// set the IR sense pins as OUTPUT
	PORTB.OUTSET = 0b00000000;		// put a low voltage on these pins (typically, this will be about 15 mV)

	ADCB.CH0.MUXCTRL |= IR_SENSOR_0_FOR_ADCB_MUXPOS;
	//_delay_ms(1);	// may have to wait a bit before the new multiplexer (MUX) connection is made in hardware?		TODO ???
	ADCB.CTRLA |= ADC_CH0START_bm;
	while (ADCB.CH0.INTFLAGS==0){};		// wait for 'complete flag' to be set
	ADCB.CH0.INTFLAGS = 1;				// clear the complete flag
	ADC_offset[0] = ADCB.CH0.RES*(-1);

	ADCB.CH0.MUXCTRL &= ~0b01111000;	// clear out the old value
	ADCB.CH0.MUXCTRL |= IR_SENSOR_1_FOR_ADCB_MUXPOS;
	//_delay_ms(1);	// may have to wait a bit before the new multiplexer (MUX) connection is made in hardware?		TODO ???
	ADCB.CTRLA |= ADC_CH0START_bm;
	while (ADCB.CH0.INTFLAGS==0){};		// wait for 'complete flag' to be set
	ADCB.CH0.INTFLAGS = 1;				// clear the complete flag
	ADC_offset[1] = ADCB.CH0.RES*(-1);

	ADCB.CH0.MUXCTRL &= ~0b01111000;	// clear out the old value
	ADCB.CH0.MUXCTRL |= IR_SENSOR_2_FOR_ADCB_MUXPOS;
	//_delay_ms(1);	// may have to wait a bit before the new multiplexer (MUX) connection is made in hardware?		TODO ???
	ADCB.CTRLA |= ADC_CH0START_bm;
	while (ADCB.CH0.INTFLAGS==0){};		// wait for 'complete flag' to be set
	ADCB.CH0.INTFLAGS = 1;				// clear the complete flag
	ADC_offset[2] = ADCB.CH0.RES*(-1);

	ADCB.CH0.MUXCTRL &= ~0b01111000;	// clear out the old value
	ADCB.CH0.MUXCTRL |= IR_SENSOR_3_FOR_ADCB_MUXPOS;
	//_delay_ms(1);	// may have to wait a bit before the new multiplexer (MUX) connection is made in hardware?		TODO ???
	ADCB.CTRLA |= ADC_CH0START_bm;
	while (ADCB.CH0.INTFLAGS==0){};		// wait for 'complete flag' to be set
	ADCB.CH0.INTFLAGS = 1;				// clear the complete flag
	ADC_offset[3] = ADCB.CH0.RES*(-1);

	ADCB.CH0.MUXCTRL &= ~0b01111000;	// clear out the old value
	ADCB.CH0.MUXCTRL |= IR_SENSOR_4_FOR_ADCB_MUXPOS;
	//_delay_ms(1);	// may have to wait a bit before the new multiplexer (MUX) connection is made in hardware?		TODO ???
	ADCB.CTRLA |= ADC_CH0START_bm;
	while (ADCB.CH0.INTFLAGS==0){};		// wait for 'complete flag' to be set
	ADCB.CH0.INTFLAGS = 1;				// clear the complete flag
	ADC_offset[4] = ADCB.CH0.RES*(-1);

	ADCB.CH0.MUXCTRL &= ~0b01111000;	// clear out the old value
	ADCB.CH0.MUXCTRL |= IR_SENSOR_5_FOR_ADCB_MUXPOS;
	//_delay_ms(1);	// may have to wait a bit before the new multiplexer (MUX) connection is made in hardware?		TODO ???
	ADCB.CTRLA |= ADC_CH0START_bm;
	while (ADCB.CH0.INTFLAGS==0){};		// wait for 'complete flag' to be set
	ADCB.CH0.INTFLAGS = 1;				// clear the complete flag
	ADC_offset[5] = ADCB.CH0.RES*(-1);
/*
	printf("ADC offset 0: %i\r\n",ADC_offset[0]);
	printf("ADC offset 1: %i\r\n",ADC_offset[1]);
	printf("ADC offset 2: %i\r\n",ADC_offset[2]);
	printf("ADC offset 3: %i\r\n",ADC_offset[3]);
	printf("ADC offset 4: %i\r\n",ADC_offset[4]);
	printf("ADC offset 5: %i\r\n",ADC_offset[5]);
*/
	PORTB.DIRCLR = 0xFF;			// return the IR sense pins back to inputs
	
	//printf("Offsets: [0: %i, 1: %i, 2: %i, 3: %i, 4: %i, 5: %i\r\n",ADC_offset[0],ADC_offset[1],ADC_offset[2],ADC_offset[3],ADC_offset[4],ADC_offset[5]);
}
예제 #6
0
/**************************************************************************************************
* Handle received HID set feature reports
*/
void HID_set_feature_report_out(uint8_t *report)
{
	uint8_t		response[UDI_HID_REPORT_OUT_SIZE];
	response[0] = report[0] | 0x80;
	response[1] = report[1];
	response[2] = report[2];
	
	uint16_t	addr;
	addr = *(uint16_t *)(report+1);

	switch(report[0])
	{
		// no-op
		case CMD_NOP:
			break;
		
		// write to RAM page buffer
		case CMD_RESET_POINTER:
			page_ptr = 0;
			return;

		// read from RAM page buffer
		case CMD_READ_BUFFER:
			memcpy(response, &page_buffer[page_ptr], UDI_HID_REPORT_OUT_SIZE);
			page_ptr += UDI_HID_REPORT_OUT_SIZE;
			page_ptr &= APP_SECTION_PAGE_SIZE-1;
			break;

		// erase entire application section
		case CMD_ERASE_APP_SECTION:
			SP_WaitForSPM();
			SP_EraseApplicationSection();
			return;

		// calculate application and bootloader section CRCs
		case CMD_READ_FLASH_CRCS:
			SP_WaitForSPM();
			*(uint32_t *)&response[3] = SP_ApplicationCRC();
			*(uint32_t *)&response[7] = SP_BootCRC();
			break;

		// read MCU IDs
		case CMD_READ_MCU_IDS:
			response[3] = MCU.DEVID0;
			response[4] = MCU.DEVID1;
			response[5] = MCU.DEVID2;
			response[6] = MCU.REVID;
			break;
		
		// read fuses
		case CMD_READ_FUSES:
			response[3] = SP_ReadFuseByte(0);
			response[4] = SP_ReadFuseByte(1);
			response[5] = SP_ReadFuseByte(2);
			response[6] = 0xFF;
			response[7] = SP_ReadFuseByte(4);
			response[8] = SP_ReadFuseByte(5);
			break;
		
		// write RAM page buffer to application section page
		case CMD_WRITE_PAGE:
			if (addr > (APP_SECTION_SIZE / APP_SECTION_PAGE_SIZE))	// out of range
			{
				response[1] = 0xFF;
				response[2] = 0xFF;
				break;
			}
			SP_WaitForSPM();
			SP_LoadFlashPage(page_buffer);
			SP_WriteApplicationPage(APP_SECTION_START + ((uint32_t)addr * APP_SECTION_PAGE_SIZE));
			page_ptr = 0;
			break;

		// read application page to RAM buffer and return first 32 bytes
		case CMD_READ_PAGE:
			if (addr > (APP_SECTION_SIZE / APP_SECTION_PAGE_SIZE))	// out of range
			{
				response[1] = 0xFF;
				response[2] = 0xFF;
			}
			else
			{
				memcpy_P(page_buffer, (const void *)(APP_SECTION_START + (APP_SECTION_PAGE_SIZE * addr)), APP_SECTION_PAGE_SIZE);
				memcpy(&response[3], page_buffer, 32);
				page_ptr = 0;
			}
			break;
		
		// erase user signature row
		case CMD_ERASE_USER_SIG_ROW:
			SP_WaitForSPM();
			SP_EraseUserSignatureRow();
			break;
		
		// write RAM buffer to user signature row
		case CMD_WRITE_USER_SIG_ROW:
			SP_WaitForSPM();
			SP_LoadFlashPage(page_buffer);
			SP_WriteUserSignatureRow();
			break;

		// read user signature row to RAM buffer and return first 32 bytes
		case CMD_READ_USER_SIG_ROW:
			if (addr > (USER_SIGNATURES_PAGE_SIZE - 32))
			{
				response[1] = 0xFF;
				response[2] = 0xFF;
			}
			else
			{
				memcpy_P(page_buffer, (const void *)(USER_SIGNATURES_START + addr), USER_SIGNATURES_SIZE);
				memcpy(&response[3], page_buffer, 32);
				page_ptr = 0;
			}
			break;

		case CMD_READ_SERIAL:
			{
				uint8_t	i;
				uint8_t	j = 3;
				uint8_t b;
	
				for (i = 0; i < 6; i++)
				{
					b = SP_ReadCalibrationByte(offsetof(NVM_PROD_SIGNATURES_t, LOTNUM0) + i);
					response[j++] = hex_to_char(b >> 4);
					response[j++] = hex_to_char(b & 0x0F);
				}
				response[j++] = '-';
				b = SP_ReadCalibrationByte(offsetof(NVM_PROD_SIGNATURES_t, LOTNUM0) + 6);
				response[j++] = hex_to_char(b >> 4);
				response[j++] = hex_to_char(b & 0x0F);
				response[j++] = '-';

				for (i = 7; i < 11; i++)
				{
					b = SP_ReadCalibrationByte(offsetof(NVM_PROD_SIGNATURES_t, LOTNUM0) + i);
					response[j++] = hex_to_char(b >> 4);
					response[j++] = hex_to_char(b & 0x0F);
				}

				response[j] = '\0';
				break;
			}
		
		case CMD_READ_BOOTLOADER_VERSION:
			response[3] = BOOTLOADER_VERSION;
			break;
		
		case CMD_RESET_MCU:
			reset_do_soft_reset();
			response[1] = 0xFF;	// failed
			break;
		
		case CMD_READ_EEPROM:
			if (addr > (EEPROM_SIZE - 32))
			{
				response[1] = 0xFF;
				response[2] = 0xFF;
			}
			else
			{
				EEP_EnableMapping();
				memcpy_P(page_buffer, (const void *)(MAPPED_EEPROM_START + addr), APP_SECTION_PAGE_SIZE);
				EEP_DisableMapping();
				memcpy(&response[3], page_buffer, 32);
				page_ptr = 0;
			}
			break;

		case CMD_WRITE_EEPROM:
			if (addr > (EEPROM_SIZE / EEPROM_PAGE_SIZE))
			{
				response[1] = 0xFF;
				response[2] = 0xFF;
			}
			else
			{
				EEP_LoadPageBuffer(&report[3], EEPROM_PAGE_SIZE);
				EEP_AtomicWritePage(addr);
			}
			break;
		
		// unknown command
		default:
			response[0] = 0xFF;
			break;
	}

	udi_hid_generic_send_report_in(response);
}