Enkel USB-ansluten A/D-omvandlare

Berätta om dina pågående projekt.
Användarvisningsbild
PHermansson
EF Sponsor
Inlägg: 4340
Blev medlem: 22 december 2004, 00:46:38
Ort: Särestad Grästorp
Kontakt:

Enkel USB-ansluten A/D-omvandlare

Inlägg av PHermansson »

Detta projekt handlar om en USB-ansluten A/D-omvandlare till datorn. Den kan till exempel användas för att läsa av ljuset i rummet via ett LDR eller temperaturen via en analog tempgivare.
Projektet är absolut low-end, frågan är om det kan göras enklare och billigare?

Hårdvara:
Här används en AVR-processor, ATTiny25. Liten och billig och rätt söt. I övrigt består kretsen av några dioder, motstånd och kondensatorer, samt en USB-B-kontakt:

Bild

Mjukvara:
Mjukvaran består av två delar, en firmware för MCU:n samt ett litet program för PC:n. Det förstnämnda baseras på V-USB (http://www.obdev.at/products/vusb/index.html), här är huvudkoden (kräver ytterligare filer från VUSB för att fungera):

Kod: Markera allt

/* Name: main.c
The goal is to create the simplest possible USB device, this time with ADC-conversion.
Patrik Hermansson 2010
*/

#include "usbdrv.h"
#include <avr/eeprom.h>	//Eeprom read
#include <avr/io.h> 
#include <avr/wdt.h>
#include <avr/interrupt.h>	//sei()
#include <avr/pgmspace.h>
#include <util/delay.h>		//_delay_ms
#include <stdlib.h>

// Defines
#define UTIL_BIN4(x)        (uchar)((0##x & 01000)/64 + (0##x & 0100)/16 + (0##x & 010)/4 + (0##x & 1))
#define UTIL_BIN8(hi, lo)   (uchar)(UTIL_BIN4(hi) * 16 + UTIL_BIN4(lo))
#define ADVALUE2 	0
#define ADVALUE3 	1
#define ADVALUE4  	2

/*
The ATTiny 25 has the following ADC inputs:
ADC0 PB5 - pin 1, used for reset.
ADC2 PB4 - pin 3
ADC3 PB3 - pin 2
ADC1 PB2 - pin 7, used for USB-connection.
ADC4 is internal temp sensor

Thus we can use ADC2, ADC3 and internal temp sensor ADC4.

*/

//uchar ADH, ADL;

/* ------------------------------------------------------------------------- */

/* Function that runs when data is requested from host */
uchar	usbFunctionSetup(uchar data[8])
{
	usbRequest_t *request= (usbRequest_t*) data;
	//static uchar one= 1;
	static uchar    replyBuf[64];
	usbMsgPtr = replyBuf;
	
	switch (request->bRequest) {
		
	case ADVALUE2:
		//Set up ADC
		ADMUX = UTIL_BIN8(0000, 0010); 
		// Vcc as Vref -> REFS0 = REFS1 = 0. REFS2 = X.
		// ADLAR = 0 -> right adjusted.
		// MUX3:0: 0010 -> PB4, pin3, ADC2.
		
		// Start AD-conversion
                ADCSRA |= (1 << ADSC);  
		//Wait while the conversion takes place
		do {
		}while ((ADCSRA & (1 << ADSC))) ;
		
		//send AD-value to host(the computer)
		replyBuf[0] = ADCL;
		replyBuf[1] = ADCH;
		return 2;
		
		//Done
		break;		
	
	case ADVALUE3:
		PORTB&=~_BV(1);	// led ON

	       // usbMsgLen_t len = 64;                     // we return up to 64 bytes
	
		//Set up ADC, this is done in Init but changed if INTTEMP is called
		ADMUX = UTIL_BIN8(0011, 0011); 
		// Vcc as Vref -> REFS0 = REFS1 = 0. REFS2 = X.
		// ADLAR = 1 -> left adjusted.
		// ADC3 selected.
	
		// Start AD-conversion
		// (ADSC = ADC Start conversion, ADCSRA = ADC control and Status Register #A)
                ADCSRA |= (1 << ADSC);  
		//Wait while the conversion takes place
		do {
		}while ((ADCSRA & (1 << ADSC))) ;
		
		//send AD-value to host(the computer)
		
		replyBuf[0] = ADCL;
		replyBuf[1] = ADCH;
		return 2;
		
		/*
		len = 2;
		//usbMsgPtr = ADCH;
		usbMsgPtr = (uchar *)&ADCL;
		
		//return ADCH;
		return len;
		*/
		//Done
		break;
	

		
	case ADVALUE4:
		PORTB|=_BV(1);	// led OFF

		/*
		The on-chip temperature sensor is selected by writing the code "1111" to the MUX3..0 bits in
		ADMUX register when the ADC4 channel is used as an ADC input. PDF chapter 17.12
		*/
	
		//Internal 1.1V reference, ADC4
		ADMUX = UTIL_BIN8(1010, 1111); 
		// ADMUX = [REFS1 REFS0 ADLAR REFS2 MUX3 MUX2 MUX1 MUX0]. PDF chapter 17.13.1
		// 1,1V internal Vref -> REFS0 = 0, REFS1 = 1. REFS2 = 0.
		// ADLAR, ADC Left Adjust Result. ADLAR = 0 -> right adjusted.
		// MUX3:0: Analog Channel and Gain Selection Bits. 1111 -> ADC4, internal temp sensor
	
		// Start AD-conversion
		// (ADSC = ADC Start conversion, ADCSRA = ADC control and Status Register #A)
                ADCSRA |= (1 << ADSC);  
		//Wait while the conversion takes place
		do {
		}while ((ADCSRA & (1 << ADSC))) ;
		
		//send AD-value to host(the computer)
		replyBuf[0] = ADCL;
		replyBuf[1] = ADCH;
		return 2;
		//Done
		break;		
	}
	return 0;
}


/* ------------------------------------------------------------------------- */
/* ------------------------ Oscillator Calibration ------------------------- */
/* ------------------------------------------------------------------------- */

/* Calibrate the RC oscillator to 8.25 MHz. The core clock of 16.5 MHz is
 * derived from the 66 MHz peripheral clock by dividing. Our timing reference
 * is the Start Of Frame signal (a single SE0 bit) available immediately after
 * a USB RESET. We first do a binary search for the OSCCAL value and then
 * optimize this value with a neighboorhod search.
 * This algorithm may also be used to calibrate the RC oscillator directly to
 * 12 MHz (no PLL involved, can therefore be used on almost ALL AVRs), but this
 * is wide outside the spec for the OSCCAL value and the required precision for
 * the 12 MHz clock! Use the RC oscillator calibrated to 12 MHz for
 * experimental purposes only!
 */
static void calibrateOscillator(void)
{
uchar       step = 128;
uchar       trialValue = 0, optimumValue;
int         x, optimumDev, targetValue = (unsigned)(1499 * (double)F_CPU / 10.5e6 + 0.5);

    /* do a binary search: */
    do{
        OSCCAL = trialValue + step;
        x = usbMeasureFrameLength();    /* proportional to current real frequency */
        if(x < targetValue)             /* frequency still too low */
            trialValue += step;
        step >>= 1;
    }while(step > 0);
    /* We have a precision of +/- 1 for optimum OSCCAL here */
    /* now do a neighborhood search for optimum value */
    optimumValue = trialValue;
    optimumDev = x; /* this is certainly far away from optimum */
    for(OSCCAL = trialValue - 1; OSCCAL <= trialValue + 1; OSCCAL++){
        x = usbMeasureFrameLength() - targetValue;
        if(x < 0)
            x = -x;
        if(x < optimumDev){
            optimumDev = x;
            optimumValue = OSCCAL;
        }
    }
    OSCCAL = optimumValue;
}
/*
Note: This calibration algorithm may try OSCCAL values of up to 192 even if
the optimum value is far below 192. It may therefore exceed the allowed clock
frequency of the CPU in low voltage designs!
You may replace this search algorithm with any other algorithm you like if
you have additional constraints such as a maximum CPU clock.
For version 5.x RC oscillators (those with a split range of 2x128 steps, e.g.
ATTiny25, ATTiny45, ATTiny85), it may be useful to search for the optimum in
both regions.
*/

void    usbEventResetReady(void)
{
    calibrateOscillator();
    eeprom_write_byte(0, OSCCAL);   /* store the calibrated value in EEPROM */
}


//static void timerInit(void)
//{
//    TCCR1 = 0x0b;           /* select clock: 16.5M/1k -> overflow rate = 16.5M/256k = 62.94 Hz */
					// TCCR1 = Timer/Counter1 Control Register
//}

static void adcInit(void)
{
	ADMUX = UTIL_BIN8(0001, 0011); 
	// ADMUX = [REFS1 REFS0 ADLAR REFS2 MUX3 MUX2 MUX1 MUX0]. PDF chapter 17.13.1
	// Vcc as Vref -> REFS0 = REFS1 = 0. REFS2 = X.
	// ADLAR, ADC Left Adjust Result. ADLAR = 1 -> left adjusted.
	// MUX3:0: Analog Channel and Gain Selection Bits. 0011 -> PB3(pin 2), single ended.
		

	
	 /* enable ADC, not free running, interrupt disable, rate = 1/128 */
	ADCSRA = UTIL_BIN8(1000, 0111);
	
	/* 
	ADCSRA = ADC Control and Status Register A
	[ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0]
	-ADEN: The ADC is enabled by setting the ADC Enable bit, ADEN in ADCSRA
	-ADSC: ADC Start Conversion. In Single Conversion mode, write this bit to one to start each conversion. In Free Running mode,
	write this bit to one to start the first conversion. ADSC will read as one as long as a conversion is in progress. When the conversion is complete,
	it returns to zero.	
	-ADATE: ADC Auto Trigger Enable. The ADC can be trigged by a signal.
	-ADIF: ADC Interrupt Flag. This bit is set when an ADC conversion completes. 
	-ADIE: ADC Interrupt Enable. When this bit is written to one and the I-bit in SREG is set, the ADC Conversion Complete Interrupt is activated. 
	0 -> not activated.
	-ADPS2:0: ADC Prescaler Select Bits. Division factor between system clock and ADC clock. 111 -> 128. 

	
	*/
		
	// Disable digital input on PB3, ADC3
	DIDR0 = 0x04;
	
}

/* Main */
uchar main(void)
{
	// Set PB1 (pin 6) as output. DDRB - Port B Data Direction Register
	DDRB= (1<<1);	

	uchar   i;
	uchar   calibrationValue;
	
	//Clock calibration setup
	calibrationValue = eeprom_read_byte(0); /* calibration value from last time */
	if(calibrationValue != 0xff){
		OSCCAL = calibrationValue;
	}
	usbDeviceDisconnect();
	for(i=0;i<20;i++){  /* 300 ms disconnect */
		_delay_ms(15);
	}
	usbDeviceConnect();
	usbInit();
	
	//Init ADC
	adcInit();	
	
	sei();			// enable interrupts
	for(;;){   			// main event loop
		usbPoll();		// USB communication
	}
	
	//return 0;
}
På PC-sidan körs Linux med Libusb, borde gå att få igång i Windows också:

Kod: Markera allt

/*
 * V-USB-ADC
 *
 * Inspired by USB-relay, © Johannes Krude 2008 and USBTemp by Mathias Dalheimer
 *
 * This software makes it possible to use a simple ATTiny25-based design to read in two analogue values.
 * It's also possible to read the MCU:s internal temperature sensor.
 *
 * © Patrik Hermansson 2010
 *
 * V-USB-ADC  is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * V-USB-ADC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with V-USB-ADC.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <usb.h>
#include "lib/obdev_usb.h"

#define USB_SUCCESS		    	0
#define USB_ERROR_NOTFOUND  	1
#define USB_ERROR_ACCESS    	2
#define USB_ERROR_IO        		3
// Number of attempts to complete an USB operation before giving up.
#define USB_MAX_RETRIES		3

#define USBDEV_SHARED_VENDOR    0xc016  /* VOTI */
#define USBDEV_SHARED_PRODUCT   0xdf05  /* Obdev's free shared PID */
/* Use obdev's generic shared VID/PID pair and follow the rules outlined
 * in firmware/usbdrv/USBID-License.txt.
*/

#define USB_TIMEOUT 1000

/* These are the vendor specific SETUP commands implemented by our USB device */
#define USBADC2		0
#define USBADC3		1
#define USBADC4    		2

static char str_error[128]= {0};
int				i, v, r, adcl, adch;
int 				number;
unsigned char	action;

void usage()
{
	printf("Shows A/D-values from MCU\n");
	printf("usage: ./ad adc2|adc3|adc4 \n");
	exit(1);
}

int main (int argc, char *argv[])
{
	usb_dev_handle      *handle = NULL;
	unsigned char       buffer[8];
	int                 		nBytes;
	
	// Check number of arguments given at comand line
	if (argc != 2) {
		usage();
	}
	
	//Initialize and find the correct hardware
	usb_init();	
	char attempt=0, error=0;
	do {
		attempt++;
		error=usbOpenDevice(&handle, USBDEV_SHARED_VENDOR, "patrikhermansson.se", USBDEV_SHARED_PRODUCT, "usb-test");
		if(error != 0){
			fprintf(stderr, "Could not open USB device \"usbtemp\" with vid=0x%x pid=0x%x, retrying\n", USBDEV_SHARED_VENDOR, USBDEV_SHARED_PRODUCT);
			sleep(2*attempt);
		}
		else {
			printf ("Device connected\n");
		}
	} while (error && attempt < USB_MAX_RETRIES);
	if (error) {
		fprintf(stderr, "Permanent problem opening USBTemp device. Giving up.\n");
		exit(1);
	}

	// Check which argument given and act on that
	if(strcmp(argv[1], "adc2") == 0){
		printf("Reading %s\n", argv[1]);
		nBytes = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USBADC2, 0, 0, (char *)buffer, sizeof(buffer), 5000);
		adcl =  buffer[0];
		adch = buffer[1];
		printf ("buffer: %i - %i\n", buffer[0], buffer[1]);

	}
	
	else if(strcmp(argv[1], "adc3") == 0){
		printf("Reading %s\n", argv[1]);
		nBytes = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USBADC3, 0, 0, (char *)buffer, sizeof(buffer), 5000);
		adcl =  buffer[0];
		adch = buffer[1];
		printf ("buffer: %i - %i\n", buffer[0], buffer[1]);

	}
	
	else if(strcmp(argv[1], "adc4") == 0){
		printf("Reading %s\n", argv[1]);
		nBytes = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USBADC4, 0, 0, (char *)buffer, sizeof(buffer), 5000);
		adcl =  buffer[0];
		adch = buffer[1];
		printf ("buffer: %i - %i\n", buffer[0], buffer[1]);

	}
	
	// No correct argument was found
	else {
		usage();
	}
		
	if (nBytes < 0) {
		snprintf(str_error, sizeof(str_error), "usb error: %s", usb_strerror());
		return i;
	}
	return 0;
}

Det är ungefär det... när PC-mjukvaran körs ser det ut som följer:

./ad adc2
Device connected
Reading adc2
buffer: 220 - 0

Programmet visar avläsningen från MCU:ns 10-bitars ADC, låg byte följt av hög byte. Detta kan naturligtvis snyggas till, annars får man räkna om för hand (Slå ihop ADCH och ACDL, omvandla till decimalt och ta gånger 0,0032).
Nogrannheten blir hyfsad, har mätt tex 0,70 volt med MCU:n när multimetern säger 0,75.
Mindmapper
Inlägg: 6389
Blev medlem: 31 augusti 2006, 16:42:43
Ort: Jamtland

Re: Enkel USB-ansluten A/D-omvandlare

Inlägg av Mindmapper »

Du ska få det mycket nogrannare. Tyckte att de var för långt ifrån för att vara hyfsat. Frågan är var felet uppkommer. Det klassiska hårdvara eller mjukvara?
Skulle kanske kunna vara multimetern t.om.
Användarvisningsbild
PHermansson
EF Sponsor
Inlägg: 4340
Blev medlem: 22 december 2004, 00:46:38
Ort: Särestad Grästorp
Kontakt:

Re: Enkel USB-ansluten A/D-omvandlare

Inlägg av PHermansson »

Ja den är inte kalibrerad på 15-20 år så...
victor_passe
Inlägg: 2436
Blev medlem: 28 januari 2007, 18:45:40
Ort: Kungsbacka

Re: Enkel USB-ansluten A/D-omvandlare

Inlägg av victor_passe »

Kan det bero på 5 till 3v3 lösningen?
Alltså Vref inte är 3.3V utan kanske 3,4V eller 3.2V.
Användarvisningsbild
PHermansson
EF Sponsor
Inlägg: 4340
Blev medlem: 22 december 2004, 00:46:38
Ort: Särestad Grästorp
Kontakt:

Re: Enkel USB-ansluten A/D-omvandlare

Inlägg av PHermansson »

Vi ska nog ta mätmetoden i beaktande också... 0,75 volt mättes upp med en MAX6575 på A/D-ingången. Denna tempsensor ger 10mV/grad, 0,75V vid 25 grader C. Och jag tror inte jag hade 25 grader i rummet, snarare 22. Noggrannheten hos MAX6575 anges till +- 3 grader.
Ska mäta lite mer och noggrannare och med andra sensorer och räkna lite mer. Ska också försöka kolla den riktiga temperaturen i rummet.
bearing
Inlägg: 11231
Blev medlem: 2 mars 2006, 01:01:45
Ort: Ängelholm

Re: Enkel USB-ansluten A/D-omvandlare

Inlägg av bearing »

Håller med victor. Vad använder du som Vref till ADC:n? Den interna 1,2V-referensen borde vara noggrannare än matningens okända spänning 5V-2*Vf.
Användarvisningsbild
PHermansson
EF Sponsor
Inlägg: 4340
Blev medlem: 22 december 2004, 00:46:38
Ort: Särestad Grästorp
Kontakt:

Re: Enkel USB-ansluten A/D-omvandlare

Inlägg av PHermansson »

Vref är lika med Vcc, och Vcc har vid två olika tillfällen mäts upp till 3,30 respektive 3,48 volt. Så hur skulle det kunna bli nogrannt? :)
Den interna referensspänningen ger säkert ett bättre resultat, ska testa med den.
Användarvisningsbild
PHermansson
EF Sponsor
Inlägg: 4340
Blev medlem: 22 december 2004, 00:46:38
Ort: Särestad Grästorp
Kontakt:

Re: Enkel USB-ansluten A/D-omvandlare

Inlägg av PHermansson »

Har ställt om till intern Vref och gjort lite mer mätningar nu. Skillnaderna är fortfarande där, men det verkar vara konsekvent. Först två mätningar med sensorn i fri luft:

Kod: Markera allt

Låg byte   Hög byte   Binärt      Decimalt   Omräknat   Mätt
128        187        1011101110   750        0,8055   0,75
64         186        1011101001   745        0,80013  0,75
 
Sen satte jag ett finger på sensorn:

Kod: Markera allt

128         194        1100001010  778        0,83567 0,78
64          197        1100010101  789        0,8474  0,80
Det intressanta är att det skiljer 0,05V rakt över i princip, så det finns nog en diff mellan min multimeter (Metex M-3800 från tidigt 90-tal) och MCU:ns A/D.
Skriv svar