Reading multiple analog input from Arduino

Hey, I’m working on a project where I have to read from two analog sensors on an Arduino board. The problem is that I would like to read from the two of them separately but it looks like it is not possible.

This is what I have tried:

    
    ard.sendAnalogPinReporting (0,ARD_ANALOG); 
    ard.sendAnalogPinReporting (2,ARD_ANALOG);     
    ofAddListener(ard.EAnalogPinChanged, this, &ofApp::analogPinChanged); 
}

void ofApp::analogPinChanged (const int &pinNum){
    if (pinNum == 0) {
        int tmpAnalogValue =ard.getAnalog(pinNum);
        if (tmpAnalogValue != analogValue) {
            analogValue = tmpAnalogValue;
             ofLog() << "0 changed!";
        }
    }
    
    if(pinNum == 2){
        int tmpAnalogValue =ard.getAnalog(pinNum);
        if(tmpAnalogValue != analogValue2){
            analogValue2 = tmpAnalogValue;
            ofLog() << "2 changed!";
        }
        
    }
}

But this does not work because analogPinChanged get called everytime one of the sensor get touched, and it reads both sensors.

Any idea about how can I detect when a user is interacting with the analog input 0?

I am guessing you are using the standard firmata example and have uploaded the standard firmata to your arduino?

I cannot check this right now, but I can verify I have used 2 sensors and read 2 different analogue pins in the way you are doing it here, this was a few months ago with the github master, so not 0.9.8 or 0.10.

Yes, you are guessing it right.

Do you think that it will be different with the latest master?

I did something like this - I was reporting the values from the Arduino over serial and I was reading them from oF with ofxSerial and ofxIO. I can dig that code up if you want to work that way.

It would be nice, thanks

Hi, it’s all here.

The Arduino (Teensy for this project, but the same thing basically), is constantly reporting it’s analog inputs with a Serial.println(value) and I’m getting them into my oF app and doing stuff with it.

The ofxIO and ofxSerial based Arduino communication was based on an example I found somewhere, can’t remember where. But basically there’s two parts to it, in the ofApp.cpp.

Everything is in the oF app in the repo, but just for some explanations, ofApp::setup() is opening up the communication and the ofApp::update() is constantly reading the values.

One small “hack” that I’ve done in this project is remap all the analogIn values that are printed to serial to unique ranges, so depending on the range that my app gets the values, it assigns them automatically to my relevant floats. Of course, you could print one string with comma separated values and parse them in the oF app, or in any other way you wish to get them.

I wasn’t doing two way communication here, obviously, but you can also send Serial to the Arduino and use Serial.read() in the Arduino and use that to do stuff.

From your answer, it is not clear to me why ofSerial should be better than ofArduino when it comes to read multiple analog values from multiple sensors. Did I miss something?

ofArduino should be better. However I never got it to work successfully, specially with bigger Arduinos like the Mega, Due and/or the Teensy (my micro controller of choice when it comes to building projects).

For what its worth, I’ve used OSC to message amongst various components (Pis, microcontrollers). Of course you have to have wifi for this, but what’s nice about it is that the incoming/outgoing messages can be broadcast to everything on the network, and different components can interpret messages in different ways. The Pis are nice for applications with a small number of components, because I can use the GPIO directly (I2C, SPI) instead of serial communication thru the USB port. Some microcontrollers come with wifi these days (ESP32 for instance). OSC can also be understood by other applications, like SuperCollider or some midi devices.

When was the last time you used ofArduino since I was the one who last updated it and I have never had a problem addressing every pin on every board I have available to me (this is using the code on the master branch on github). It should be relatively in line with the the firmata protocol though its not as up to date as it was when I rewrote it.

As for @edapx, do your sensors have a lot of noise from them? like an IR depth sensor or something? I only ask because if the analog value from one sensor doesn’t change it wouldn’t seem like a problem. As for a solution you would probably be better served looking for a degree of change that is greater than the noise you are receiving. Something like:

    if (pinNum == 0) {
        int tmpAnalogValue =ard.getAnalog(pinNum);
        if (abs(tmpAnalogValue - analogValue) > 5) {
            analogValue = tmpAnalogValue;
             ofLog() << "0 changed!";
        }
    }

I would also make sure your ofArduino code is up to date since the version that existed before the most recent update had issues like not initializing correctly.

Tried it last year around November. Had posted about it too, but didn’t get any response.

I posted in that thread but since I can’t see the error im not sure what the cause is exactly. Here is some code using the ofarduino from master that works with an arduino mega that I did just last week and is live in a touring exhibit:

void ofApp::setupArduino(const int & version) {

	// remove listener because we don't need it anymore
	ofRemoveListener(ard.EInitialized, this, &ofApp::setupArduino);

	// it is now safe to send commands to the Arduino
	bSetupArduino = true;

	// printf( firmware name and version to the console
	ofLogNotice() << ard.getFirmwareName();
	ofLogNotice() << "firmata v" << ard.getMajorFirmwareVersion() << "." << ard.getMinorFirmwareVersion();

	ard.sendDigitalPinMode(config["Joystick_Up"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Joystick_Right"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Joystick_Down"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Joystick_Left"].asInt(), ARD_INPUT_PULLUP);

	ard.sendDigitalPinMode(config["Reed_Switch_0_degrees"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_45_degrees"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_90_degrees"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_135_degrees"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_180_degrees"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_225_degrees"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_270_degrees"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_315_degrees"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_Filter_1"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_Filter_2"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_Filter_3"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_Filter_4"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Reed_Switch_Filter_5"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Layer_Select_1"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Layer_Select_2"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Layer_Select_3"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Layer_Select_4"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Layer_Visibility_1"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Layer_Visibility_2"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Layer_Visibility_3"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Layer_Visibility_4"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Reset"].asInt(), ARD_INPUT_PULLUP);
	ard.sendDigitalPinMode(config["Button_Image_Select"].asInt(), ARD_INPUT_PULLUP);

	ard.sendDigitalPinMode(config["Encoder_1_Reset"].asInt(), ARD_INPUT);
	ard.sendDigitalPinMode(config["Encoder_2_Reset"].asInt(), ARD_INPUT);
	ard.sendDigitalPinMode(config["Encoder_3_Reset"].asInt(), ARD_INPUT);

	ard.sendAnalogPinReporting(config["IR_Sensor_Transparency"].asInt(), ARD_ANALOG);
	ard.sendAnalogPinReporting(config["IR_Sensor_Scale"].asInt(), ARD_ANALOG);
	ard.sendAnalogPinReporting(config["IR_Sensor_Repetition"].asInt(), ARD_ANALOG);

	ard.attachEncoder(config["Encoder_1_Pin_A"].asInt(), config["Encoder_1_Pin_B"].asInt()); //Hue
	ard.attachEncoder(config["Encoder_2_Pin_A"].asInt(), config["Encoder_2_Pin_B"].asInt()); //Saturation
	ard.attachEncoder(config["Encoder_3_Pin_A"].asInt(), config["Encoder_3_Pin_B"].asInt()); //Brightness
	ard.attachEncoder(config["Encoder_4_Pin_A"].asInt(), config["Encoder_4_Pin_B"].asInt()); //Rotation
	ard.attachEncoder(config["Encoder_5_Pin_A"].asInt(), config["Encoder_5_Pin_B"].asInt()); //Image Picker

	ard.enableEncoderReporting();

	// Listen for changes on the digital and analog pins
	ofAddListener(ard.EDigitalPinChanged, this, &ofApp::digitalPinChanged);
	ofAddListener(ard.EAnalogPinChanged, this, &ofApp::analogPinChanged);
	ofAddListener(ard.EEncoderDataReceived, this, &ofApp::encoderDataRecieved);

	reset();
}

//--------------------------------------------------------------
void ofApp::updateArduino() {
	ard.update();
}

// digital pin event handler, called whenever a digital pin value has changed
// note: if an analog pin has been set as a digital pin, it will be handled
// by the digitalPinChanged function rather than the analogPinChanged function.

//--------------------------------------------------------------
void ofApp::digitalPinChanged(const int & pinNum) {
	if (pinNum == config["Reed_Switch_0_degrees"].asInt()) {
		ard.getDigital(pinNum) == ARD_LOW ? rotation[layerSelected] = 0 : 0;
		ard.resetEncoderPosition(3);
	}
	else if (pinNum == config["Reed_Switch_45_degrees"].asInt()) {
		ard.getDigital(pinNum) == ARD_LOW ? rotation[layerSelected] = 45 : 0;
		ard.resetEncoderPosition(3);
	}
	else if (pinNum == config["Reed_Switch_90_degrees"].asInt()) {
		ard.getDigital(pinNum) == ARD_LOW ? rotation[layerSelected] = 90 : 0;
		ard.resetEncoderPosition(3);
	}
	else if (pinNum == config["Reed_Switch_135_degrees"].asInt()) {
		ard.getDigital(pinNum) == ARD_LOW ? rotation[layerSelected] = 135 : 0;
		ard.resetEncoderPosition(3);
	}
	else if (pinNum == config["Reed_Switch_180_degrees"].asInt()) {
		ard.getDigital(pinNum) == ARD_LOW ? rotation[layerSelected] = 180 : 0;
		ard.resetEncoderPosition(3);
	}
	else if (pinNum == config["Reed_Switch_225_degrees"].asInt()) {
		ard.getDigital(pinNum) == ARD_LOW ? rotation[layerSelected] = 225 : 0;
		ard.resetEncoderPosition(3);
	}
	else if (pinNum == config["Reed_Switch_270_degrees"].asInt()) {
		ard.getDigital(pinNum) == ARD_LOW ? rotation[layerSelected] = 270 : 0;
		ard.resetEncoderPosition(3);
	}
	else if (pinNum == config["Reed_Switch_315_degrees"].asInt()) {
		ard.getDigital(pinNum) == ARD_LOW ? rotation[layerSelected] = 315 : 0;
		ard.resetEncoderPosition(3);
	}

	else if (pinNum == config["Button_Image_Select"].asInt()) {
		switch (layerSelected) {
		case 0:
			layerCounter[0] += imgSelIndex;
			imgLayer[0].load(imgDir[0].getPath(flooredModulo(layerCounter[0], int(imgDir[0].size()))));
			imgLayer[0].mirror(true, false);
			imgSelIndex = 0;
			break;
		case 1:
			layerCounter[1] += imgSelIndex;
			imgLayer[1].load(imgDir[1].getPath(flooredModulo(layerCounter[1], int(imgDir[1].size()))));
			imgLayer[1].mirror(true, false);
			imgSelIndex = 0;
			break;
		case 2:
			layerCounter[2] += imgSelIndex;
			imgLayer[2].load(imgDir[2].getPath(flooredModulo(layerCounter[2], int(imgDir[2].size()))));
			imgLayer[2].mirror(true, false); 
			imgSelIndex = 0;
			break;
		case 3:
			layerCounter[3] += imgSelIndex;
			imgLayer[3].load(imgDir[3].getPath(flooredModulo(layerCounter[3], int(imgDir[3].size()))));
			imgLayer[3].mirror(true, false); 
			imgSelIndex = 0;
			break;
		default:
			break;
		}
	}
	else if (pinNum == config["Button_Layer_Visibility_1"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW && ofGetElapsedTimeMillis() - visTimer > 250) {
			visTimer = ofGetElapsedTimeMillis();
			layerVisibility[3] = !layerVisibility[3];
			ard.sendByte(START_SYSEX);
			ard.sendByte(LED_VISIBILITY);
			ard.sendByte(0);
			ard.sendByte(layerVisibility[3] ? 1 : 0);
			ard.sendByte(END_SYSEX);
		}
	}
	else if (pinNum == config["Button_Layer_Visibility_2"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW && ofGetElapsedTimeMillis() - visTimer > 250) {
			visTimer = ofGetElapsedTimeMillis();
			layerVisibility[2] = !layerVisibility[2];
			ard.sendByte(START_SYSEX);
			ard.sendByte(LED_VISIBILITY);
			ard.sendByte(1);
			ard.sendByte(layerVisibility[2] ? 1 : 0);
			ard.sendByte(END_SYSEX);
		}
	}
	else if (pinNum == config["Button_Layer_Visibility_3"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW && ofGetElapsedTimeMillis() - visTimer > 250) {
			visTimer = ofGetElapsedTimeMillis();
			layerVisibility[1] = !layerVisibility[1];
			ard.sendByte(START_SYSEX);
			ard.sendByte(LED_VISIBILITY);
			ard.sendByte(2);
			ard.sendByte(layerVisibility[1] ? 1 : 0);
			ard.sendByte(END_SYSEX);
		}
	}
	else if (pinNum == config["Button_Layer_Visibility_4"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW && ofGetElapsedTimeMillis() - visTimer > 250) {
			visTimer = ofGetElapsedTimeMillis();
			layerVisibility[0] = !layerVisibility[0];
			ard.sendByte(START_SYSEX);
			ard.sendByte(LED_VISIBILITY);
			ard.sendByte(3);
			ard.sendByte(layerVisibility[0] ? 1 : 0);
			ard.sendByte(END_SYSEX);
		}
	}


	else if (pinNum == config["Button_Layer_Select_1"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW) {
			layerSelected = 3;
			imgDisplayIndex = 3;
			listenForAlpha = false;
			listenForScale = false;
			imgSelIndex = 0;
			ard.resetEncoderPosition(3);
			ard.sendByte(START_SYSEX);
			ard.sendByte(LAYER_SELECTED);
			ard.sendByte(0);
			ard.sendByte(END_SYSEX);
		}
	}
	else if (pinNum == config["Button_Layer_Select_2"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW) {
			layerSelected = 2;
			imgDisplayIndex = 2;
			listenForAlpha = false;
			listenForScale = false;
			imgSelIndex = 0;
			ard.resetEncoderPosition(3);
			ard.sendByte(START_SYSEX);
			ard.sendByte(LAYER_SELECTED);
			ard.sendByte(1);
			ard.sendByte(END_SYSEX);
		}
	}
	else if (pinNum == config["Button_Layer_Select_3"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW) {
			layerSelected = 1;
			imgDisplayIndex = 1;
			listenForAlpha = false;
			listenForScale = false;
			imgSelIndex = 0;
			ard.resetEncoderPosition(3);
			ard.sendByte(START_SYSEX);
			ard.sendByte(LAYER_SELECTED);
			ard.sendByte(2);
			ard.sendByte(END_SYSEX);
		}
	}
	else if (pinNum == config["Button_Layer_Select_4"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW) {
			layerSelected = 0;
			imgDisplayIndex = 0;
			listenForAlpha = false;
			listenForScale = false;
			imgSelIndex = 0;
			ard.resetEncoderPosition(3);
			ard.sendByte(START_SYSEX);
			ard.sendByte(LAYER_SELECTED);
			ard.sendByte(3);
			ard.sendByte(END_SYSEX);
		}
	}
	else if (pinNum == config["Joystick_Up"].asInt()) {
		joyUp = ard.getDigital(pinNum) == ARD_LOW ? true : false;
	}
	else if (pinNum == config["Joystick_Right"].asInt()) {
		joyRt = ard.getDigital(pinNum) == ARD_LOW ? true : false;
	}
	else if (pinNum == config["Joystick_Down"].asInt()) {
		joyDn = ard.getDigital(pinNum) == ARD_LOW ? true : false;
	}
	else if (pinNum == config["Joystick_Left"].asInt()) {
		joyLt = ard.getDigital(pinNum) == ARD_LOW ? true : false;
	}
	else if (pinNum == config["Encoder_1_Reset"].asInt()) {
		ard.resetEncoderPosition(0);
	}
	else if (pinNum == config["Encoder_2_Reset"].asInt()) {
		ard.resetEncoderPosition(1);
	}
	else if (pinNum == config["Encoder_3_Reset"].asInt()) {
		ard.resetEncoderPosition(2);
	}
	else if (pinNum == config["Reed_Switch_Filter_1"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW)
			filterType[layerSelected] = 0;
	}
	else if (pinNum == config["Reed_Switch_Filter_2"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW)
			filterType[layerSelected] = 3;
	}
	else if (pinNum == config["Reed_Switch_Filter_3"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW)
			filterType[layerSelected] = 2;
	}
	else if (pinNum == config["Reed_Switch_Filter_4"].asInt()) {
		if (ard.getDigital(pinNum) == ARD_LOW)
			filterType[layerSelected] = 1;
	}

	if (pinNum == config["Button_Reset"].asInt()) {
		reset();
	}
	else {
		layerDirty = true;
	}
}

// 
// analog pin event handler, called whenever an analog pin value has changed
//--------------------------------------------------------------
void ofApp::analogPinChanged(const int & pinNum) {
	// do something with the analog input. here we're simply going to printf( the pin number and
	// value to the screen each time it changes
	float sum = 0;
	layerDirty = true;
	if (pinNum == config["IR_Sensor_Transparency"].asInt()) {
		for (int i = 0; i < 24; i++)
			alphaBuffer[i] = alphaBuffer[i + 1];
		alphaBuffer[24] = ofMap(ard.getAnalog(pinNum), 130, 590, 255, 80, true);
		for (int i = 0; i < 25; i++)
			sum += alphaBuffer[i];
		sum /= 25;
		if (listenForAlpha || abs(prevAlpha - sum) > 20) {
			if (abs(prevAlpha - sum) > 20)
				listenForAlpha = true;
			prevAlpha = sum;
			switch (layerSelected) {
			case 0:
				alpha[layerSelected] = sum;
				break;
			case 1:
				alpha[layerSelected] = sum;
				break;
			case 2:
				alpha[layerSelected] = sum;
				break;
			case 3:
				alpha[layerSelected] = sum;
				break;
			}
		}

	}
	else if (pinNum == config["IR_Sensor_Scale"].asInt()) {
		for (int i = 0; i < 24; i++)
			scaleBuffer[i] = scaleBuffer[i + 1];
		scaleBuffer[24] = ofMap(ard.getAnalog(pinNum), 230, 470, .1, 4, true);
		for (int i = 0; i < 25; i++)
			sum += scaleBuffer[i];
		sum /= 25;
		if (listenForScale || abs(prevScale - sum) > .25) {
			if (abs(prevScale - sum) > .25)
				listenForScale = true;
			prevScale = sum;
			scale[layerSelected] = sum;
		}

	}
	else if (pinNum == config["IR_Sensor_Repetition"].asInt()) {
		if (ard.getAnalog(pinNum) < 250)
			pattern[layerSelected] = 10;
		if (ard.getAnalog(pinNum) >= 250 && ard.getAnalog(pinNum) < 325)
			pattern[layerSelected] = 5;
		if (ard.getAnalog(pinNum) >= 325 && ard.getAnalog(pinNum) < 400)
			pattern[layerSelected] = 3;
		if (ard.getAnalog(pinNum) >= 400)
			pattern[layerSelected] = 0;
	}
}

void ofApp::encoderDataRecieved(const vector<Firmata_Encoder_Data> & data) {
	layerDirty = true;
	for (int i = 0; i < data.size(); i++) {
		switch (data[i].ID) {
		case 0:
			hue[layerSelected] = ofMap(data[i].direction ? data[i].position : max(120 - data[i].position, 0), 0, 120, 0, 255, true);
			break;
		case 1:
			saturation[layerSelected] = ofMap(data[i].direction ? data[i].position : max(120 - data[i].position, 0), 0, 120, 255, 0, true);
			break;
		case 2:
			brightness[layerSelected] = ofMap(data[i].direction ? data[i].position : max(120 - data[i].position, 0), 0, 120, 0, 255, true);
			break;
		case 3:
			fineRotation[layerSelected] = data[i].direction ? -data[i].position : data[i].position;
			break;
		case 4:
			if (abs(data[i].position - imgSelPos) >= 20) {
				if (data[i].position - imgSelPos > 0) {
					imgSelIndex++;
				}
				else {
					imgSelIndex--;
				}
				imgSelPos = data[i].position;
			}
			break;
		}
	}
}

I’m not sure that will be helpful for the problems discussed here but just showing that it should be capable of communicating with pins beyond 22.

Hey thanks, I’ll look into it when I need to work with an Arduino again - the issues I faced last time I managed with ofxSerial so it’s not something I need to do now.

But I really appreciate the help! :slight_smile:

Many thanks, I will give a try tomorrow and let you know.

Use the photon-pixel coupling method, it is a new approach in science for sampling an unlimited number of sensors in parallel .

Basically, each sensor output is an LED. If you have 10000 sensors, all of them are inserted in a LED array, a LED matrix as the authors say. After that, the LED array is filmed by a video camera and the images are processed in real time by a computer. A software reads one pixel from each LED from the LED array and converts it to numerical values. So, your LED array will be converted in a matrix (with 10000 elements) filled with numbers that can be processed as you wish in your software. I don’t know if I was explicit but you can read their article here: https://www.sciencedirect.com/science/article/pii/S2215016119300901

Note that classic multiplexing is serial, this approach is parallel.

The photon-pixel coupling method is truly ingenious because it solves two main problems in engineering: an unlimited number of sensors and their parallel sampling at video rate frequencies . Just imagine, we can read as many sensors as we wish. What I wander is if we can adapt the photon-pixel coupling to Arduino. I am new in the world of microcontrollers but I know Arduino can support a cam, so it should be possible.

If you are a PhD student then: P.A. Gagniuc, C. Ionescu-Tirgoviste, R.G. Serban, E. Gagniuc. Photon-pixel coupling: A method for parallel acquisition of electrical signals in scientific investigations . MethodsX, 6:968-979, 2019.

Arduino will not support a camera that will be useful for this. You will need to make sure you have a camera with a global shutter (vs rolling - like your serial vs parallel sampling problem in the first place), precise manual control of the camera settings, controlled light (so a lightproof box to do this in) and high resolution, it will be very difficult to align each LED with one pixel on the incoming camera frame. You will also most likely need some kind of synchronisation to make sure the camera grabs the frame at exactly the right time (or the lights give values at the right time). Look at a machine vision camera, they have the features you need, and allow a lot more pixels that you have LEDs. Also beware of cameras that output compressed streams, like h264, you will probably even want to avoid 420 and 422 colour spaces and go for full RGB or 444, otherwise the encoding can give you bad readings. Bit depth is also important, video is mostly only 8 bit, 10 at a push, 12 is possible but very expensive. You may even want to do the whole thing with IR.

LEDs also do not turn off and on instantly, so getting the actual reading may be quite tough, how do you know when the light is actually showing the brightness that relates to the reading?

It seems like a very trouble prone way of reading data and other methods of parallelisation, like arrays of fast microcontrollers with buffers that can time stamp data may work much more accuratley.

From the previous comment up to the current comment, i replicated the photo-pixel coupling method, but with a mini-PC :P. I still think that the Arduino can do it but I am not a specialist in microcontrollers. What I did, was to sample 80 humidity sensors in parallel, and it WORKS :slight_smile: The thing with the arduino is that it allows for full independence from the PC, which is preferable in agriculture, on the fields … in my case.

“it will be very difficult to align each LED with one pixel on the incoming camera frame”

In photon-pixel coupling the alignment is solved by storing the pixel position from the middle of each LED prior to the sampling of the sensors (so you have a map, you don’t scan the entire image).

They also solved the calibration in an ingenious way, just read the paper.

The whole idea of this method is to grab one pixel per sensor, so the resolution of the camera is less important (I used an old webcam). That is why I said that the Arduino cam may work. Of Course the Arduino cam has low fps, so the sensors will be sampled at whatever fps the Arduino cam functions (i think 2 fps - which is twice per second).

Regarding the LEDs instantly off or on: the LEDs have a pretty fast response but it does not matter in the end because we don’t sample at MHz or GHz frequencies. Even the multiplexer is unreliable when it comes to array of sensors (actually the multiplexer is really unreliable for high number of sensors). If I have to choose between sampling of a few milli or nano seconds per sensor with a multiplexer and the LED response, I will go with the LED sampling.

Regarding the compresion, I think it does not make a real difference in the end since the cam captures the same setup always. I have to say, to me this method is huge, it solves many dream projects in my case.

“You may even want to do the whole thing with IR” - What you said is interesting ! Why IR ?