analogRead seems not working into a class constructor

I’m writing a little PRNG class that uses the Multiply-with-carry algorithm written by George Marsaglia.
The class makes a reading of the first 4 analog pin, then it combines those readings into 2 32-bit variables used by the PRNG to generate random numbers.

But I’m having a problem with my class because it seems that if I initialize the algorithm into the class constructor, when I go to ask random numbers to my class I only get a series of 0s: the class seems not initialized. If I explicitely call the initialize() function into the setup() routine, the class does work. I would to avoid an explicit call to initialize() method beacuse I would like to do that before the user can manipulate the pins.

Yu can try this by downloading the attached library, install it into your /libraries folder, then call the example that you find in Examples/simplePRNG.

Fash the sketch as it, and then decomment the row “//initialize();” into the setup() routine and flash it again.

Can anyone explain why it doesn’t work?

simplePRNG.zip (2.55 KB)

I can't look at the code at the moment as I am on my phone, but my guess is the class constructor gets called before the Arduino libraries initialize the pins to their default state, so anything you do in there gets undone.

You cannot see this but I've included Arduino.h header file in my clas so anything about the Arduino stuff should be included and ready to use.

void simplePRNG::initializePRNG() {
    m_w = ((unsigned long)analogRead(A0)<<16);
    m_w |= analogRead(A1);
    m_z = ((unsigned long)analogRead(A2)<<16);
    m_z |= analogRead(A3);
}

I’m not certain of the sequence of events, but I think that will be being run before init() and subsequent routines. I doubt that the ADC is configured at that point.

Just including “Arduino.h” doesn’t do any initialisation, it just defines all the constants and tells the compiler what functions are available. I don’t know enough about the internal AVR registers to know what work the analogRead() function does in the way of configuration, so I can’t be certain without cross-referencing the data sheet to the source code of analogRead().

As a rule of thumb I always provide a .begin() method in all my classes that need to do any form of setting up. That way you can guarantee that the whole system is configured and ready to roll at the point you want to start doing things.

Maybe

In .cpp

//global variables

unsigned long m_w;
unsigned long m_z;

In .h

    private:
        unsigned long m_w;
        unsigned long m_z;

pYro_65: Maybe

No, it doens't. I tried commenting out m_w and m_z in the .h file and it didn't solve.

So… I’ve written another constructor, doing raw readings to the ADC channels, and this code DOES work:

//class constructor
simplePRNG::simplePRNG(void) {
	//initialize the internal variables
	unsigned int tempInt[4] = {0, 0, 0, 0};
	for (byte tempByte=0; tempByte<4; tempByte++) { //loop from MUX0 to MUX4 (ADC0..ADC3)
		ADMUX |= tempByte; //choose the channel
		ADMUX &= ~((1<<REFS1) | (1<<ADLAR)); //AREF set to VCC // right-adjust result
		ADMUX |= (1<<REFS0); //AREF set to VCC
		ADCSRA |= ((1<<ADEN) | (1<<ADPS2)); //activate ADC // prescaler set to /16
		ADCSRA |= (1<<ADSC); //start convertion
		while (ADCSRA & (1<<ADSC)) {} //wait for convertion to be done
		tempInt[tempByte]= (ADCL | (ADCH<<8));
		ADCSRA &= ~(1<<ADEN);
	}
	m_w = ((unsigned long)(tempInt[0]<<16));
	m_w |= tempInt[1];
	m_z = ((unsigned long)(tempInt[2]<<16));
	m_z |= tempInt[3];
}

So the issue is into the analogRead function that, for some strange and unknown (at least to me) reasons, doesn’t work inside a class constructor. Why? :sweat_smile:

If your class instance is declared at global scope then the constructor will be called before main() and in an undefined order relative to other global constructors.

main() is where the arduino library calls init() and only after that can you use the analog functions. Therefore you cannot use many of the Arduino library functions from constructors on objects declared at global scope. If this puts you off globally declared objects then your software design skills just went up a notch ;)

If you absolutely must have a global variable then you could make it a pointer and allocate it with new in your setup() function.

In general, you should do as little as possible (typically nothing) in a constructor. Initialization should be done in setup by calling a begin method. Serial and SPI are good examples.

I'm writing a little PRNG class that uses the Multiply-with-carry algorithm written by George Marsaglia.

If you can spare the memory (20 bytes of SRAM), JKISS32 from this article works very well on AVR processors... http://www.cs.ucl.ac.uk/staff/d.jones/GoodPracticeRNG.pdf

It's smaller, faster, and [u]significantly[/u] better quality than the Libc Park-Miller function.

This is what main.cpp looks like in the Arduino core:

#include <Arduino.h>

int main(void)
{
	init();

#if defined(USBCON)
	USBDevice.attach();
#endif
	
	setup();
    
	for (;;) {
		loop();
		if (serialEventRun) serialEventRun();
	}
        
	return 0;
}

As you can see, main() for the Arduino environment is pretty simple. It calls an init() function, maybe calls a USBDevice.attach(), followed by our setup() function, then goes into an infinite loop repeatedly calling our loop() function.

init() is responsible for initializing all the AVR hardware on the micro to a state that all the Arduino core libraries expect them to be in.

If you declare an instance of a class at the global level, it’s constructor will get executed before main is executed, which means it gets executed before both init() and setup(). In particular, it’s the execution prior to init(), which initizializes all the hardware, that is the source of your… situation. Any of the Arduino libraries that interact with the hardware (most of them), will not work until init() is called.

The recommended method of handling this limitation is to move that code to an init() or begin() method that you then call during setup(). That’s the whole purpose of setup().

OK. Thanks for your explanations ;)