Jump to content
  • Welcome to 205GTIDrivers.com!

    Hello dear visitor! Feel free to browse but we invite you to register completely free of charge in order to enjoy the full functionality of the website.

Sign in to follow this  
brumster

[Misc_Work] Sequential Gearstick & Gear Indicator

Recommended Posts

Weser

Is there scope to use a simple potentiometer set up to vary the brightness if someone wanted it. I know it means you will have some sort of dial/control somewhere which probably isnt ideal.

Share this post


Link to post
Share on other sites
brumster

I'm going to have one more play today with components and see if I can get it working, but yes, the other option is to just have a pot setup to vary the LED intensity. That would be pretty simple but it's a bit "manual"!

Share this post


Link to post
Share on other sites
Weser

Ahh yes it is quite manual. How about using an LDR in a potential divider set up? I seem to remember you already using an LDR in you original schematic. I will flick back and have a look. I take it this is not working?

Share this post


Link to post
Share on other sites
brumster

No, that's in there, but it won't drive the brightness 'directly' (as in via a voltage divider setup) - the intention was to measure it via the MCU, and then have the brightness configurable with software - the intensity being controlled by using PWM into a single transistor, itself in the line between emitter and ground of the darlington array. It would "pulse" the ground at a variable speed to control the brightness of the LEDs. You could then have a much greater variance in brightness setting... I wouldn't pretend infinite (!) but certainly somewhere to the order of 255 at best :)

 

I need to try again but I think the problem is the large darlington array that drives each segment doesn't like it's ground being "pulled from under it" so quickly and I get a faint glow on all the other LEDs, presumably as the transistors in the ULN2803 are doing something funny with such a rapidly-changing ground. Bit of googling needed I think :) see if there's an alternative.

 

One option is to pulse the MCU pins that are driving the 2803 (ie. the base on the array transistors) but since these MCU pins are just simple digital I/O, not PWM enabled, the timing will need to be controlled by simple 'wait' commands in the code. Turn it on, wait a variable amount, turn it off - in a fast-acting loop. Not as pretty as using PWM, but it's my fallback option.

Share this post


Link to post
Share on other sites
Weser

Ahh yes sorry I should of read back - being lazy.

 

I think I understand, controlling the array base is an option but like you say the code wont be nice. Is there some way you could control it with a clock pulse and then in turn control your clock frequency with your MCU pins? Again this probably wouldn't give the range of brightnesses you are after though.

 

It has been a long time since I did this sort of electronics so my thoughts are probably all wrong! :lol: But I am finding it all interesting!

 

As for dimness you are getting - its not down to the turn on voltage being required so in actual fact your geting less turn on time than your expecting? Just a thought but probably wrong.

 

I cant seem to get my head round the fact that some of the segments are bright and others dim. Your driving each segment separately but are you only pulsing some?

Share this post


Link to post
Share on other sites
brumster

Ahh yes sorry I should of read back - being lazy.

No, s'ok, good to have some input ;)

 

I think I understand, controlling the array base is an option but like you say the code wont be nice. Is there some way you could control it with a clock pulse and then in turn control your clock frequency with your MCU pins? Again this probably wouldn't give the range of brightnesses you are after though.

The PWM generator in the AVR is completely autonomous once setup, so this is why I prefer this route. Basically, you set it's range (8bit in my case) and it then starts counting up from 0 to 255 with every clock tick. You then set a register with a compare value. When the PWM count matches this value, it can set the PWM output pins to low (or high, or toggle it, depending upon your configuration). So you basically can generate a square wave signal on the pin, and vary the time it spends high or low.

 

From google :-

avr_pwm_07.jpg

The top graph is how the counter works, just counting up to 255 and wrapping back to zero. Depending upon what you set the compare value (OCRn) to decides when the output inverts - so at 128 (50% of 255) you get the 50% duty cycle square wave. At OCRn=51 (~20%) you get much shorter high peaks in the wave. This is what effectively controls the brightness - it controls how long you leave the LED On for. Obviously we're talking incredibly fast cycles here - at 1MHz (the slowest the MCU will do), it'll dount 3921 peaks so we're talking on and off almost 4000 times a second.

 

It has been a long time since I did this sort of electronics so my thoughts are probably all wrong! :lol: But I am finding it all interesting!

 

As for dimness you are getting - its not down to the turn on voltage being required so in actual fact your geting less turn on time than your expecting? Just a thought but probably wrong.

I sorted this - I was just an idiot and it was a code issue (see above). specified a binary value as hex into the registers, made for some very screwy results!

 

I cant seem to get my head round the fact that some of the segments are bright and others dim. Your driving each segment separately but are you only pulsing some?

Share this post


Link to post
Share on other sites
brumster

Well, it just goes to show the importance of breadboarding - I've hit a decision point to make on what components to utilise.

 

Initially, to switch on each of the 8 segment (7 segments and a decimal point, actually) on the display, I had intended to use a nicely-packaged Darlington array, the ULN2803A. This is a 20-pin IC (integrated circuit - a 'chip' to you and I) so it's nice and neat and saves on board space. The "darlington" aspect of it is not really needed (a darlington pair is a couple of transistors back-to-back, used to switch higher currents than a single transistor is normally capable of), because it's not like we're switching more than 30 to 40mA here, but I couldn't find a DIP package of 8 single transistors, so it was just easier.

 

Now, to control the brightness, I was going to use another separate transistor, switched via the PWM signal generator from the AVR, to control the intensity. This single transistor would sit "underneath" the 2803, by that I mean it would be in the common ground from it to earth.

 

When I rigged all this up, I found that while the main segment being lit was working and glowing to the correct intensity, all the other segments would also glow slightly, unless the chosen segment was lit to full intensity. It was almost as if the other segments were operating at the inverse of whichever segment you currently wanted to light. This had me flumoxed for a while, with lots of investigation with the oscilloscope and figuring out how on earth the transistors for the other segments could be getting switched on. And in desperation I was back to the schematics for the ULN2803, at which point I spotted what was going on.

 

10.jpg

If you look on that left-hand schematic for the 2803 you can see in dotted lines 3 diodes. The problem is that when the PWM transistor partially switches off the ground, the resistance ramps up and the output of the currently-turned-on segment feeds back through those diodes to all the other darlington pairs in the IC, effectively switching them on a little. I really need to be able to remove that left-hand diode, but since it's packaged up as part of the 2803 that's impossible. I'm stuffed with this approach.

 

To verify this, I replicated the circuit with conventional transistors instead of the ULN2803.... and it worked just as I had hoped.

 

There are two alternatives.

 

1. Pack on the board 8 conventional TO-92 package transistors, and switch those as intended.

2. Ditch the concept of PWM driven intensity, and just switch the transistors rapidly in software.

 

Option 1 will mean more cost on the board, more space taken up on the board and more complexity. But it does mean the main 8 transistors will not be switched so much, and the software can be efficient as the intensity is not controlled by putting in manual wait-states when switching the outputs.

Option 2 will reduce board complexity, free up a pin on the MCU and move the focus of the logic that drives the LED intensity into the code.

 

I'm going with option 2. First thing is to breadboard it with the ULN2803 and make sure the 2803 can switch quickly enough to drive the segments at the intensitys we want. I'll do that now. If it's all OK then option 2 it is, otherwise option 1 is the fallback.

 

Alternatively, does anyone know of a nicely packaged 8x NPN transistor array in a DIP package!? Not a darlington array, that's the problem.

Share this post


Link to post
Share on other sites
Weser

Not sure they do 8x but pretty sure they do 5x NPN not darlingtons

Share this post


Link to post
Share on other sites
brumster

Not sure they do 8x but pretty sure they do 5x NPN not darlingtons

You're right, most I can find is 5...

Share this post


Link to post
Share on other sites
Weser

Can you run two 5x chips?

Share this post


Link to post
Share on other sites
peter

What about the gearbox its self? :D or have I missed that? As I'm reading on my phone.

Share this post


Link to post
Share on other sites
brumster

Ha :) gearbox is going to be a while yet (read - long time). Long story cut short - I was all on for it, then a female change of mind resulted in plans being halted.

 

I'm still going to do this little bit of it though - just gone quiet because hectic at work and not had the energy on the nights. CNC doesn't come free until June anyway, so no rush. I'll try and move along on the electronics side a bit within the next week, do a bit of an update...

Share this post


Link to post
Share on other sites
brumster

Good news - progressing well on the code now. The brightness detection and control works, and I'm now just setting about with the code to monitor the configuration button and drive how the config options work. Basically, by holding down the button for a long period, the unit will enter config mode and display an "r" - the user selects reverse and presses the button briefly once more to program the reverse position into the unit. The unit then displays "0", so the user selects neutral and presses the button again to program the position. This continues through 1,2,3,4,5 and 6.

 

I've also got to make it store those potentiometer values into it's EEPROM so you're obviously not loosing the configuration every time you turn it off.

 

I've had some issues with squeezing everything into the 2K I've got on the ATTiny26... mainly because I had hoped to use some floating point maths operations but I'm going to have to do without them, because there's no way I can get the code small enough with them included in the code. Not a major problem. I'm up to about 1.1K so far, so keeping an eye on the final binary size is important, but we should be fine. It's not optimised code yet - I'm just getting it all working and then I'll refine it later.

 

Bad news on the CNC front - looks like my time slot on the machine won't come up. Not a major issue, I can put the gubbins into an off-the-shelf plastic enclosure, just won't look so pretty and 'custom' :)

Share this post


Link to post
Share on other sites
brumster
/*
 * gearindicator2.c
 *
 * Created: 12/02/2013 16:32:51
 *  Author: Dan Howell
 *
 * AVR Configuration details :
 * ATtiny26 @ 1MHz
 * PA0-7 : Output pins to drive darlington array, driving 7 segment display (8 active elements including decimal point)
 *         0=segment A, 1=B, 2=C, 3=D, 4=E, 5=F, 6=G, 7=DP (decimal point)
 * PB0-2 : unused (ISP)
 * PB3   : Output, OC1B - control for dim transistor using PWM
 * PB4   : Input, ADC7 - LDR for ambient light sense, 5v
 * PB5   : Input, ADC8 - gearbox position sensor, 5v
 * PB6   : Input, INT0 (PB6) digital - configuration switch, momentary operation, normally held high. Press drops low.
 * PB7   : Not used (RESET)
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/delay.h>

#include "main.h"
#include "common.h"

// Main loop counters - needs to be global to be accessible by the ISR's
u16 largeLoopCounter = 1;

// Loop tuning limit
const u16 loopLimit = 5000;

// Vars to hold current ADC reading for LDR and position sensor...
u08 LDRValue = 0;
u08 POSValue = 0;

// LDR monitoring values (temporary - production version will make these constants)
u08 LDRmin = 255;
u08 LDRmax = 0;

// Current configured ADC port
u08 currentPort = 0;

// Last recorded ADC reading from configured port
u08 ADCReading = 0;

// Array holding 8-bit values that will reflect the correct Port C configuration
// to drive the 7-segment display. Order is R,0,1,2,3,4,5,6
const u08 numPositions = 8;
const u08 displayChars[8] = { 0x31, 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d };
u08 gearIndex[8];

// The average calculated gap between gear index positions; the display will register for the gear position
// that is within this value (+/-) of the current sensor reading.
const u08 POSAllowance = 5;

// Button state (0=unpressed, 1=pressed)
u08 buttonState = 0;
// Duration of button press (in large loop cycles)
u08 buttonTime = 0;

Code walkthrough time - I'll start from the top and work my way through.

 

Firstly I always start with a comment block stating the target MCU, the pinouts and so forth, because when you come back to 12-month-old code mixed up with 10 other projects you've started and never finished, it's very hard to remember which code is tied to which hardware!

 

The #includes link in some standard WinAVR libraries; IO routines, access to interrupt routines and also EEPROM read/write routines, plus some utility functions for generating delays (pauses). This is common practise and saves you having to write all these low-level functions from scratch yourself - just use the standard Atmel libraries.

 

Now onto the next aspect. The "largeLoopCounter". The main routines runs in an infinite loop, endlessly cycling over the boring process of measuring the current gear position, the ambient light and displaying the appropriate value on the display. It also has to monitor for the pressing and holding down of the config button. There is no real-time clock inside these 8-bit AVRs, so in order to measure the passing of time you need to make up your own appropriate method.

 

One method is to use an inbuild counter linked to the MCU clock, but being 8-bit means it can only store at maximum 256 different values. This would mean at 1MHz clock speed (1 million cycles a second) you could count up to a maximum of 0.000256 of a second, whereas I had the idea of counting up to about 10 seconds to see if someone holds down the button for that period of time. So not much use! There is a prescale funtion on the counter, which allows you to divide the counter by a set value, giving you larger time periods but less resolution - the maximum prescale value is CK/1024 (clock divided by 1024), which means the value will increment every 1024 ticks of the clock rather than every tick of the clock - but 1 million divided by 1024 is still 976 increments in every second - and since we can only count up to 255, that means we could measyre about a quarter of a second max. So still useless.

 

So the use of the largeLoopCounter is to store a 16-bit value (so can count up to 65535 in decimal), and increment it every time we complete a number of iterations of the main loop. This number of iterations is set by the next value, loopLimit. So, in this example, every 5000 iterations of the main loop increments the largeLoopCounter by 1. The largeLoopCounter ends up incrementing *roughly* once every 5 seconds (it's actually a smidgen more).

 

LDRValue and POSValue are unsigned 8-bit integers for storing the measured values on the Light Dependent Resistor, and the Position sensor.

 

LDRMin and LDRMax are temporary at the moment; the unit currently auto-samples the light over time and sets the brightness according to the current measured ambient light in relation to the darkest and brightest measurements made - these store those min and max values. The final version will just have this hard-set accordind to the LDR I fit.

 

currentPort and ADCReading are used in the ADC routines, more of that later

 

numPositions is an array of 8-bit values that stores the relevant I/O pin values for port C, which drives the 7-segment display via the Darlington. Index 0 is reverse, 1 is neutral, 2 is first, and so on. So you can see that index 0 is 0x31. In binary, this equates to 00110001. This enables pins 1, 5 and 6 on port C (pin 1 is at the right hand end, remember!) which lights up 3 segments to make a lower-case "r" shape.

 

gearIndex is used to store the measured position sensor values that equate to each gear position. So an 8bit value will be stored in gearIndex[0], that value equating to the resistance measured when the gearbox is in reverse. [1] is neutral, [2] is first and so on, as per convention throughout the code really.

 

POSAllowance is a 'fudge factor' for how close a measurement needs to be, to be considered matching an entry in gearIndex. For example, if 6th gear was measured as gearIndex[7]=240, then with POSAllowance set to 5, a measurement between 235 and 245 would be considered as 'close enough', and the code will consider the measured gear as 6. This obviously assumes that all gears are at least 5/256ths apart in terms of measured value on the ADC, but since all gear position sensors I am aware of are linear potentiometers, this should be fine.

 

buttonState is used to record the last measured button state - 0 being unpressed, 1 being pressed.

Likewise, buttonTime is used to record how long the button has been pressed for - it is incremented along with the largeLoopCounter, so every 5000 iterations of the main loop (or in accordance with loopLimit, of course).

 

More to follow...!

Share this post


Link to post
Share on other sites
brumster

Now it's time for some functions....

 

//Initiates a sample of the LDR reading on ADC7 
void getLDRReading(void) {
	// Set to ADC7 (0x00000111 mask)
	ADMUX&=~(1<<MUX3); // Clear 4th bit
	ADMUX|=(1<<MUX0)|(1<<MUX1)|(1<<MUX2); // Set lower 3 bits
	currentPort = 7;
	ADCSR|=(1<<ADSC); // Start conversion
}

//Initiates a sample of the position reading on ADC8
void getPosReading(void) {
	// Set to ADC8 (0x00001000 mask)
	ADMUX&=~((1<<MUX0)|(1<<MUX1)|(1<<MUX2)); // Clear lower 3 bits
	ADMUX|=1<<MUX3; // Set 4th bit
	currentPort = 8;
	ADCSR|=(1<<ADSC); // Start conversion
}

// Interrupt service routine for when AD conversion completes...
ISR(ADC_vect)
{

	ADCReading = ADCH;
	
	// If port is 7 we're reading the LDR sensor
	if (currentPort == 7) {
		LDRValue = ADCReading;
	} else if (currentPort == 8) {
	// Port 8 is the gear position sensor...
		POSValue = ADCReading;
	}
}

// Interrupt service routine for when the config button state *changes*. Note this is a change - so it could be going from
// unpressed to pressed, or vice versa - the interrupt is generated at both times
ISR(INT0_vect) {
	
	if ( buttonState == 0) {
		// Button has been pressed down
		buttonState=1;
		buttonTime=0;
	} else {
		// Button has been released
		buttonState=0;
		buttonTime=largeLoopCounter;
		// Reset the large loop counter
		largeLoopCounter=0;
	}
}

void delay_us(int us) {
	for (int i=0; i<us; i++) {
		_delay_us(1);
	}
}

// Load gear position settings from EEPROM; ensure global interrupts are enabled!
void loadSettings() {
	for (int i=0; i<numPositions; i++) {
		gearIndex[i] = eeprom_read_byte((uint8_t*) i);
	}
}

// Save gear position settings in gearIndex[] to EEPROM; ensure global interrupts are enabled!
void saveSettings() {
	for (int i=0; i<numPositions; i++) {
		// EEPROM data register will now hold the value stored in memory...
		eeprom_write_byte((uint8_t*) i, gearIndex[i]);
	}
}

// Config routine. Steps through each gear position, allowing user to set sensor position and record current
// sensor reading into memory with a button press. Once all 8 positions are registered, stores the values in
// memory and then returns to normal run routine.
void config() {
		
	// Loop until button has been released
	while (buttonState==1) {
		display(1);
	}
	
	// Step through each gear position from index 0 upwards (reverse, neutral, 1st, 2nd, etc...)
	for (int i=0; i<numPositions; i++) {
		
		// Display gear index and wait for button press...
		while (buttonState==0) {
			display(i);			
		}
		// Display gear index, sample sensor value and wait for button release...
		while (buttonState==1) {
			display(i);
			getPosReading();
		}
		
		// Store most recent sampled value into gearIndex[]...
		gearIndex[i] = POSValue;
		PORTA=0x00;
		_delay_ms(500);
		
	}
	
	// Store settings into EEPROM
	saveSettings();
}

// Display 1 cycle on the 7-segment display, honoring current light sensor readings and setting brightness accordingly
// using simple wait-state.
void display(u08 indexPos) {
	u08 currentValue=0x01;
	for(int j=0;j<7;j++)
	{
		PORTA = displayChars[ indexPos ] & currentValue; // Turn the appropriate segment on				
		currentValue = (currentValue<<1);
                if (LDRValue > LDRmax-20 ) {
                  delay_us(2);
                }
	}
}

getLDRReading, rather unsurprisingly, initiates a measurement of the Light Dependent Resistor - ie. the ambient light. We are using 2 of the ATTiny26's ADC pins - ADC7 and ADC8. The LDR is connected to ADC7. The ADC (Analogue to Digitial Converter) can only work on one port at a time so we first configure the ADC to measure on ADC7 by setting ADMUX appropriately - the data sheets from Atmel provide the detail of these registers, so consult your data sheets from Atmel to understand this. We then initiate the conversion by setting ADSC (AD Start Conversion) bit on the ADCSR register. This is where interrupts come into play. Our routine does not have to worry about collecting the result - we initiate the conversion, and when the ADC has finished it's measurement it kicks off an "interrupt". This interrupt will stop the code wherever it may be, jump to a special routine, execute that and - when it has completed - return to the point where is was interrupted and carry on it's merry way. So we will write an interrupt service routine (ISR) to deal with the result of the ADC operation. More of that in a sec.

 

getPosReading is the same kind of thing, but sets up the ADC for ADC8, which is what the gear position sensor is connected to.

 

ISR(ADC_vect) is the interrupt service routines aluded to above. This is the routine that gets automagically called when the ADC completes a conversion and measurement. The value in the ADCH (ADC High) register, an 8bit unsigned value, is saved into the ADCReading variable. Then, depending upon whether we're looking at port 7 or 8, we set the appropriate LDR or POS value. The code's a bit inefficient, I'll clear it up, but it's obvious what it does.

 

ISR(INT0_vect) is another ISR, this time for interrupts generated by an external interrupt... an interrupt generated by a pin changing on the AVR itself. In this case we're using INT0, which on the ATTiny26 is wired to pin PB6. Every time the state of this button changes (ie. goes high OR goes low), the interrupt is generated, and this code is executed. You can see it's fairly rudimentary - it toggles the buttonState variable and also sets the buttonTime variable, depending upon how long the button has been held down.

 

delay_us() is a 'safe' delay function that will delay a set number of microseconds.

 

loadSettings() and saveSettings() are used to read and write the gearIndex[] array to the EEPROM memory in the AVR, so that values are kept during power-off and restored on power-on. They use functions from the eeprom.h library.

 

config() is the configuration routine - this is called when the config button is held down for a predetermined period of time. First, it displays "0" until the button is released by the user - to ensure we don't record any false measurement for the first position. It then steps through each index position from 0 to 7 (ie. R,N,1,2,3,4,5,6), display the gear on the display and expecting the user to shift the gearbox to that position and the press the button once more to 'record' the sensor reading for that gear. It saves the settings into memory at the end of the operation.

 

Finally, display() is the basic routine to display a number on the 7-segment display. If you recall, we can't actually turn on more than 1 segment of the display at any one time, so what we actually do is turn on one segment at a time, very very quickly - to the human eye you don't even notice; it looks like all the relevant segments are turned on. There is an additional pause in here for daytime - if it's bright, then we leave the segment turned on for 2 microseconds longer than if it's dark, thus giving the effect of a brighter display (the segment stays on fractionally longer, appearing brighter as a result).

 

Main routine next.... :)

Share this post


Link to post
Share on other sites
brumster

So, the main routine that ties it all together....

 

int main(void)
{
	// Configure ports
	// - Set data direction registers (1 for output pins) - remember RH digit is the least significant bit, is port 0 ;]
	DDRA=0b11111111;
	DDRB=0b00001000;
	// - Disable pull-up resistors / set outputs off
	PORTA=0b00000000;
	PORTB=0b00000000;
		
	// Set left adjust on ADC. ADCH will hold 8 most significant bits, and we can ignore ADCL, to give 8-bit resolution
	// which will be more than enough for what we need.
	sbi(ADMUX,ADLAR);
	
	// Set the prescaler to 128, as we don't need high resolution. 1MHz/128 = 7812Hz, assuming 1MHz clock
	ADCSR|=(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2);
	
	// Enable ADC
	sbi(ADCSR,ADEN);

	// Use interrupt-based sampling - ADC interrupt enable (requires global interripts enabled also)
	sbi(ADCSR,ADIE);
	
	sbi(MCUCR,ISC00); // Set to generate interrupt on pin change (ie. whether it goes high or low, it will interrupt)
	sbi(GIMSK,INT0); // Enable INT0 external interrupt
	
	// Enable global interrupts - essential for many operations!
	sei();
    
	u16 loopCounter = 1;
	u08 POSmin = 255;
	u08 POSmax = 0;
	u08 POSstep = 1;

	loadSettings();
	
	while(1)
    {

		// Check for config button press and hold...
		if ( buttonTime>2 ) {
			config();
			buttonTime=0;
			largeLoopCounter=0;
		}

		// Get sensor reading
		getPosReading();
		
		// Calculate which gear position index value we are closest to (first match wins)
		for(int i=0;i<numPositions;i++) {
			if ( (POSValue > gearIndex[i]-POSAllowance) & (POSValue < gearIndex[i]+POSAllowance)) {
				// Close enough - consider it a match
				POSstep=i;
				break;
			}
		}

		display(POSstep);
					
		loopCounter++;
		if (loopCounter>loopLimit) {
			
			//Time to get an LDR reading
			getLDRReading();
			if (LDRValue > LDRmax) {
				LDRmax = LDRValue;
			}
			if (LDRValue < LDRmin) {
				LDRmin = LDRValue;
			}
		
			loopCounter=1;

			if (buttonState==1) {
				largeLoopCounter++; // Increment the global large loop counter used by the config button ISR...
			}				
		}
	}	
}

First we configure the Data Direction Registers (DDR's) for ports A and B; this controls which pins on the AVR are inputs and outputs. A 1 enables the pin as an output.

We then disable all the internal pull-up resistors in the AVR for ports A and B; we don't need them (they're only relevant on digital inputs and the one pin we use as a digitial input - PB7 (INT0) - is held high using an external arrangement on the PCB, plus a de-bouncing cap, rather that relying on the AVR.

 

sbi(ADMUX,ADLAR) is a set-bit (sbi) operation, basically saying "Set bit ADLAR on the register ADMUX". The Atmel data sheet explains this, but basically I want the ADC to be left adjusted rather than right adjusted. The ADC offers 10bit resolution if I wanted it (so up to 1024 integer levels of measurement), but I don't need that level of resolution for an 8-position sensor such as the gear position indicator. The ADC stores its results in 2x8bit registers, ADCH and ADCL (high and low).

 

With right adjust, the bits are split "xxxxxx11"/"11111111" on the ADCH and ADCL registers respectively. To get your value, you have to add them together into a 16-bit result.

With left adjust, the bits are split "11111111"/"11xxxxxx" on ADCH and ADCL. In this instance, we can take the whole 8-bit value of ADCH and ignore the 2 bits on ADCL, giving us a nice simple, 8-bit reading off the ADC - plenty of accuracy for our needs, the 2 bits on ADCL only equate to +/-4 on the integer reading of the ADC, which we really don't care for.

 

We set the ADC pre-scaler to 128 (controls how much sampling the ADC does of the input), then enable the ADC and turn on interrupt-based sampling, as we have covered in a previous post.

 

Setting ISC00 on MCUCR (the MCU Control Register) enables the external interrupt on pin *change*, and we then enable INT0 as the external interrupt connected to PB7 (the "config" button).

 

Critically, we enable global interrupts with sei() - otherwise none of our interrupt service routines would work.

 

loadSettings() reads in from EEPROM previously stored gear position readings.

 

We then enter our infinite loop - while (1)... the code comments should cover this adequately but, in short, we :-

Check if the button has been held for longer than 2 largeLoopCounter increments (which equates to approximately 10 seconds) and, if so, call the config() routine.

We get a position sensor reading

We find the nearest matching gear position reading in our gearIndex[] array, give or take the "POSAllowance" value, and take that as the current gear we are in.

We display the appropriate value on the 7-segment display

We increment our small loopCounter

If the loopCounter is more than our loopLimit (currently 5000) then we :-

Get an ambient light reading

Reset the loopCounter to 1

If the button is held down, increment the largeLoopCounter

 

...and then we go round and round again. Well, until someone turns the power off, at least :)

 

That's it...

 

http://youtu.be/V7b6rmpxDJ0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×