Help with code for MCP23017

Go To Last Post
71 posts / 0 new

Pages

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There are some good debouncing algorithms and code examples presented in threads here at AVRfreaks. I like the one by Peter Danegger (AVRfreaks member 'danni'). I think he has uploaded it as a project. It uses a timer tick to sample the switches. Does it's work excellently. His code is for eight switches (using uint8_t's), but you can easily expand it to e.g. 16 bits (using uint16_t's instead). Go look it up!

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Yes I'm testing it in the simulator (AVR Studio 4).

Usually a waste of time though SimV2 isn't TOO bad I guess.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jan, you still havent completed the first task. We need some functions to access the display. Why?? You can have low level access code sprinkled through your code and maybe create a few defects along the way or write a couple of functions that give a convenient way to access the displays that you can test and prove. So, do you want to make it easier for yourself or wallow in defect hell?

As for debouncing, you did read my tutorials didn't you? Maybe not.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, after re-reading, I see you did half the job. Retract my abuse. I gather you haven't wired up the 3x3 matrices yet.
In setDisplay - good coding practice - you reject illegal values. Rather than do nothing if an illegal value is passed, you could display two dashes '--' that would give you clear indication that you have got an illegal value. You choose.

For debouncing - seems I didn't have an example in my tutorial. So here's a simple one:

#define DEBOUNCE_TIME 20 //ms ticks

volatile unsigned char buttons[NUM_BUTTONS];

in the timer isr:

if (PORTA & (1<<BUTTON1)
   {
   buttons[0] = DEBOUNCE_TIME; //if button not pressed
   }
else //button is pressed, decrement down to zero
   {
   if (buttons[0]
      {
      buttons[0]--;
      }
   }

Note that when the button is ON, the switch connect the port pin to 0V as we use the internal pullup resistor (be sure to turn these on) to pull the input to vcc when the switch is OFF.

When buttons[x] gets to zero, we can be pretty sure the input has been low for DEBOUNCE_TIME ticks. Write a function like button_is_pressed(char butt_num) that returns true if buttons[x] is 0. Our application would read something like:

if (button_is_pressed(UP))

....
Pretty hard to confuse what is happening here.

Not as classy as vertical counters, but simple to understand. Make a loop to cope with the rest of the inputs. If there is any question about efficiency, run in the simulator and tell me how long it takes. Express as a percentage of the total cpu time. Even if this hogged 50% of cpu, we have no other interrupts to compete with so there should be no issue for this application.

Once you have the debouncing under control, then look at my little multitasker. With this, you shouldn't need to call delay_ms as all timing is handled by the timer interrupt. You then have a framework in which to write your game application.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Johan: I already looked at "danni's" debounce program, and it looked ok. Maybe I use that.
clawson: I don't think so. I've found some errors with it by now.
Kartman: Yes, I haven't written anything for the matrix yet, I wanted to get one thing working first.
From where should I call your code? From the same ISR as the display? Or from another CTC timer ISR? Or from something completely other?

JanD

EDIT: I used Kartmans code and used OCR1B, but now even less than happens than last time. Now the display never changes. Here is the code:

#define F_CPU 8000000UL

#include 
#include 
#include 

#define NUM_DIGITS 5
#define NUM_BUTTONS 10

#define DEBOUNCE_TIME 5

uint8_t digits[10] = {0b11000000, 0b11111001, 0b10100100, 0b10110000, 0b10011001, 0b10010010, 0b10000010, 0b11111000, 0b10000000, 0b10010000};

volatile uint8_t leds[NUM_DIGITS] = {0b11111111, 0b11111111, 0b101, 0b010, 0b111};
volatile uint8_t buttonTimes[NUM_BUTTONS];
uint8_t buttons[NUM_BUTTONS];
uint8_t lastButtons[NUM_BUTTONS] = {0,0,0,0,0,0,0,0,0,0};
	
void scanKeys();
void setDisplay(uint8_t number);
void setupIO();
void setupTimer();

ISR(TIMER1_COMPB_vect){
	uint8_t i = 0;
	for(; i < 8; i++){
		if(PIND & (1<<i)){
			buttons[i] = DEBOUNCE_TIME; //if button not pressed
		}
		else //button is pressed, decrement down to zero
		{
			if(buttons[i]){
				buttons[i]--;
			}
		}
	}
	if(PINB & (1<<PB2)){
		buttons[8] = DEBOUNCE_TIME; //if button not pressed
	}
	else //button is pressed, decrement down to zero
	{
		if(buttons[8]){
			buttons[8]--;
		}
	}
	if(PINB & (1<<PB4)){
		buttons[9] = DEBOUNCE_TIME; //if button not pressed
	}
	else //button is pressed, decrement down to zero
	{
		if(buttons[9]){
			buttons[9]--;
		}
	}
	
	scanKeys();
}

ISR(TIMER1_COMPA_vect){
   static char digit = 0;

   switch(digit)
     {
     case 0: //1's digit
       PORTC |= (1<<PC3); //Disable last digit
       PORTA = leds[digit];
       PORTC &= ~(1<<PC7); //Enable new digit
       break;
     case 1: //10's digit
       PORTC |= (1<<PC7);
       PORTA = leds[digit];
       PORTC &= ~(1<<PC6);
       break;
     case 2: //matrix
       PORTC |= (1<<PC6);
       PORTA = leds[digit];
       PORTC &= ~(1<<PC5);
       break;
     case 3: //matrix
       PORTC |= (1<<PC5);
       PORTA = leds[digit];
       PORTC &= ~(1<<PC4);
       break;
     case 4: //matrix
       PORTC |= (1<<PC4);
       PORTA = leds[digit];
       PORTC &= ~(1<<PC3);
       break;
     }//end switch
	 
     digit++;
	 
     if (digit >= NUM_DIGITS)
     {
        digit = 0;
     }
}

void setupIO() 
{
	DDRA = 0xFF; //Set port A to output
	DDRC = 0xFF; //Set port B to output
	DDRD = 0x00; //Set port D to input
	PORTD = 0xFF; //Turn on pull-up resistors on port D
	DDRB &= ~(1<<PB2) & ~(1<<PB4); //Set PB2 and PB4 to input
	PORTB |= (1<<PB2) | (1<<PB4); //Turn on pull-up resistors on PB2 and PB4
}

void setDisplay(uint8_t number){
	if(number > 99)
		return;
		
	uint8_t lowDigit = number % 10; //Calculate the low digit
	uint8_t highDigit = (number - lowDigit) / 10; //Calculate the high digit
	
	leds[0] = digits[lowDigit];
	
	if(highDigit == 0)
		leds[1] = 0b11111111;
	else
		leds[1] = digits[highDigit];
}

void setupTimer() 
{
	TCCR1B |= (1<<WGM12) | (1<<CS11) | (1<<CS10); //Setup the timer in CTC mode, and a prescaler of 64
	/* Calculation of the value to pass to OCR1A:
	 *
	 * Ticks: 8Mhz / 64prescaler = 125000ticks
	 * Seconds per tick: 1 / 125000ticks = 0.000008seconds
	 * Milliseconds per tick: 0.000008s * 1000ms/s = 0.008ms
	 * Ticks for one ms: 125ticks * 0.008ms = 1ms
	 *
	 * Value for 1ms = 125
	 */
	OCR1A = 125; //Display
	OCR1B = 125; //Buttons
	TIMSK1 |= (1<<OCIE1A) | (1<<OCIE1B); //Enable timer interrupts
}


void scanKeys(){
	uint8_t i = 0;
	for(; i > NUM_BUTTONS; i++){
		if(buttonTimes[i] == 0)
			buttons[i] = 1;
	}
}

int main(){
	setupTimer();
	setupIO();
	sei();
	setDisplay(0);
	uint8_t number = 0;
	
	while(1){
		setDisplay(number);
		//scanKeys();
		uint8_t i = 0;
		for(; i < NUM_BUTTONS; i++){
			if(buttons[i] != lastButtons[i] && buttons[i] == 1){
				number++;
				lastButtons[i] = buttons[i];
			}				
		}
	}
}

I mainly changed the scanKey function and added the ISR for the OCR1B compare.

JanD

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jan...

You wrote:
But the debouncing doesn't work at all. [...]
What can I do about that?

I wrote:
There are some good debouncing algorithms and code examples presented in threads here at AVRfreaks.

Quote:

Johan: I already looked at "danni's" debounce program [...]

So, now tell me: Next time you ask a question should I assume you've looked at what I am about to suggest (in which case I'll just save my time and not answer), or should I risk serving you something that you already know.

You need to realize that it's not "a given" to get help here. If you have a question, but already has found some stuff then tell us so.

And while I'm semi-flaming you, let me comment on the last chunk of code you've posted: Once again you are faced with a specific problem but fail to reduce the code demonstrating it to an absolute minimum. We've told you before: Isolate the problem by forming a hypothesis about what is wrong - test that by throwing out all other complex code and replace it with a simple test on if the code you're left with works or not. If it works, move on to the next hypothesis.

Example: Nothing is shown on the digit displays. You form the hypothesis that it is the key scanning that fails. So, throw out everything that has to do with handling the digit displays. Replace it with some small-and-smart test code that indicates whether the key scanning works or not. Let it signal success on one simple LED (perhaps use one segment of one digit display) and failure on another. Now if it works, you know something. Go back and do the same, but this time you throw out as much as you can of the key scanning code, instead implementing something very simple to simulate them, and concentrate instead on the display code.

This way you narrow down the problem space. Let's assume you see that the key scanning is faulty. Now you take a look at that in particular to see if you can divide the problem space further. Is it the basic reading of the pins that fail, or is it the debouncing algorithm? Or can you divide it in any other way.

Chances are that you will find the error yoursel, and you will now be much more pleased with yourself than if we keep solving your bugs.

If you do find that one smaller part of your code does not work, but you can't figure out why you still have us here to help you. We will.

But, and that's a big BUT, if you keep on growing your code, and you repeatedly run into problems, and you then just post the complete code at that point, then from our view it comes though as:

1) You are not listening to our advice, so why should we keep on giving them?

2) You are trying to load over the debug work as I describe it above on us. That is not why we are here. We are here for helping you with your real problem - so isolate it for us.

Please understand this: Being good at fault seeking is not about being extremely smart in the sense of being able to read, understand and control a lot of code. Being good at fault seeking is very much about being able to divide a big problem into smaller parts, where only a few (in the best case only one) of those parts are problematic, and the rest just fine. THIS is what almost all programmers do daily. THIS is being a smart programmer.

So... Start being smart. It will mean that you will spend a lot of time, at least initially, cutting your code into pieces, re-assemblying it etc (but OTOH it means that we won't). But pretty soon you will get into the habit and do this with greater ease when you encounter a problem. And as a bonus, the experience will teach you A LOT about how to structure programs so that this divide-and-conquer method is easy to perform.

Here is your first exercise: Determine if it is the basic pin reading, the debounce algorithm or the display handling that is at fault. It's OK if you cant solve the problem, but you should be able to tell us which of those three parts that fail.

With that long rant I'll leave you to it, and will not participate here until I see a move in that direction.

Bye for now.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Johan has mirrored my thoughts.

1. What benefit is two isrs? I've mentioned at least rwice that only one interrupt is used. Think about it - timer CTC mode and the same compare time so two interrupt soyrces will fires at the same time and one will go first and the second will run when the first completes. What have you gained?

2. Scankeys. What is the use of calling this function in the isr? Then having the result in a global array. Minimise globals. Also you're doing extra work in the isr that can be done elsewhere.

3. Why do you think 5ms of debounce time is adequate? Did I not use a value of 20? Not that it will make or break your current problem, but it shows a lack of understanding.

4. What is the precedence of != and && ? Offhand I don't know, so use () the force precedence. This could be one of the problems.

5. Too many steps in one go. You havent tested the debounce code yet but added an extra layer of complexity by detecting the change of state. And maybe added a defect or two along the way. Don't try to be Bruce Lee and fight 10 black ninjas at one time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wont give answer to many of the things you sad now (firstly because it would make a very long post) mainly because I HAVE IT WORKING NOW!!!

The following code clears a LED and adds 1 to the display every timer one of the buttons are pressed:

#define F_CPU 8000000UL
#define NUM_DIGITS 5

#include 
#include 
#include 
#include 

uint8_t digits[10] = {0b11000000, 0b11111001, 0b10100100, 0b10110000, 0b10011001, 0b10010010, 0b10000010, 0b11111000, 0b10000000, 0b10010000};

volatile uint8_t leds[NUM_DIGITS] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

void setDisplay(uint8_t number);
void setupIO();
void setupTimer();
void clearLED(uint8_t led);
void setLED(uint8_t led);
uint8_t getLED(uint8_t led);

uint8_t key_state1; //Port D
uint8_t key_press1;
uint8_t key_state2; //Port B
uint8_t key_press2;

ISR(TIMER0_COMPA_vect) //For scanning the keys. Fires every 10ms.
{
	static uint8_t ct01 = 0xFF, ct11 = 0xFF;	// 8 * 2bit counters
	uint8_t i1;

	i1 = ~PIND;				// read keys (low active)
	i1 ^= key_state1;			// key changed ?
	ct01 = ~( ct01 & i1 );			// reset or count ct0
	ct11 = ct01 ^ (ct11 & i1);		// reset or count ct1
	i1 &= ct01 & ct11;			// count until roll over ?
	key_state1 ^= i1;			// then toggle debounced state
	key_press1 |= key_state1 & i1;		// 0->1: key press detect
	
	static uint8_t ct02 = 0xFF, ct12 = 0xFF;	// 8 * 2bit counters
	uint8_t i2;

	i2 = ~PINB;				// read keys (low active)
	i2 ^= key_state2;			// key changed ?
	ct02 = ~( ct02 & i2 );			// reset or count ct0
	ct12 = ct02 ^ (ct12 & i2);		// reset or count ct1
	i2 &= ct02 & ct12;			// count until roll over ?
	key_state2 ^= i2;			// then toggle debounced state
	key_press2 |= key_state2 & i2;		// 0->1: key press detect
}

ISR(TIMER1_COMPA_vect) //For the display. Fires every 1ms.
{
   static char digit = 0;

   switch(digit)
     {
     case 0: //1's digit
       PORTC |= (1<<PC3); //Disable last digit
       PORTA = leds[digit];
       PORTC &= ~(1<<PC7); //Enable new digit
       break;
     case 1: //10's digit
       PORTC |= (1<<PC7);
       PORTA = leds[digit];
       PORTC &= ~(1<<PC6);
       break;
     case 2: //matrix
       PORTC |= (1<<PC6);
       PORTA = leds[digit];
       PORTC &= ~(1<<PC5);
       break;
     case 3: //matrix
       PORTC |= (1<<PC5);
       PORTA = leds[digit];
       PORTC &= ~(1<<PC4);
       break;
     case 4: //matrix
       PORTC |= (1<<PC4);
       PORTA = leds[digit];
       PORTC &= ~(1<<PC3);
       break;
     }//end switch
	 
     digit++;
	 
     if (digit >= NUM_DIGITS)
     {
        digit = 0;
     }
}

uint8_t get_key_press1( uint8_t key_mask ) //Returns 1 if the specified key is pressed. For port D.
{
	ATOMIC_BLOCK(ATOMIC_FORCEON){		// read and clear atomic !
		key_mask &= key_press1;		// read key(s)
		key_press1 ^= key_mask;		// clear key(s)
	}
	return key_mask;
}

uint8_t get_key_press2( uint8_t key_mask ) //Same as get_key_press1, but for port B
{
	ATOMIC_BLOCK(ATOMIC_FORCEON){		// read and clear atomic !
		key_mask &= key_press2;		// read key(s)
		key_press2 ^= key_mask;		// clear key(s)
	}
	return key_mask;
}

void setupIO() //Sets DDRs, initial port values and pull-ups.
{
	DDRA = 0xFF; //Set port A to output
	PORTA = 0xFF;
	DDRC = 0xFF; //Set port B to output
	PORTC = 0xFF;
	DDRD = 0x00; //Set port D to input
	PORTD = 0xFF; //Turn on pull-up resistors on port D
	DDRB &= ~(1<<PB2) & ~(1<<PB4); //Set PB2 and PB4 to input
	DDRB |= (1<<PB0);
	PORTB |= (1<<PB2) | (1<<PB4) | (1<<PB0); //Turn on pull-up resistors on PB2 and PB4 and turn off PB0
}

void setDisplay(uint8_t number) //Sets the specified number "number" on the display.
{
	if(number > 99)
		return;
		
	uint8_t lowDigit = number % 10; //Calculate the low digit
	uint8_t highDigit = (number - lowDigit) / 10; //Calculate the high digit
	
	leds[0] = digits[lowDigit];
	
	if(highDigit == 0)
		leds[1] = 0b11111111;
	else
		leds[1] = digits[highDigit];
}

void setupTimer() //Sets the prescaler, OCRn# value etc. for timer 1 and 2.
{
	TCCR1B |= (1<<WGM12) | (1<<CS11) | (1<<CS10); //Setup the timer in CTC mode, and a prescaler of 64
	/* Calculation of the value to pass to OCR1A:
	 *
	 * Ticks: 8Mhz / 64prescaler = 125000ticks
	 * Seconds per tick: 1 / 125000ticks = 0.000008seconds
	 * Milliseconds per tick: 0.000008s * 1000ms/s = 0.008ms
	 * Ticks for one ms: 125ticks * 0.008ms = 1ms
	 *
	 * Value for 1ms = 125
	 */
	OCR1A = 125;
	TIMSK1 |= (1<<OCIE1A); //Enable timer interrupts
	
	TCCR0A = 1<<WGM01;			// T0 Mode 2: CTC
	TCCR0B = 1<<CS02^1<<CS00;		// divide by 1024
	OCR0A = F_CPU / 1024.0 * 10e-3 -1;	// 10ms
	TIMSK0 = 1<<OCIE0A;
}

void clearLED(uint8_t led) //Clears the led "led". "led" can be a value from 1 to 9.
{
	switch(led){
		case 1:
			leds[2] |= 0b00000100;
			break;
		case 2:
			leds[2] |= 0b00000010;
			break;
		case 3:
			leds[2] |= 0b00000001;
			break;
		case 4:
			leds[3] |= 0b00000100;
			break;
		case 5:
			leds[3] |= 0b00000010;
			break;
		case 6:
			leds[3] |= 0b00000001;
			break;
		case 7:
			leds[4] |= 0b00000100;
			break;
		case 8:
			leds[4] |= 0b00000010;
			break;
		case 9:
			leds[4] |= 0b00000001;
			break;
	}
}

void setLED(uint8_t led) //Sets the led "led". "led" can be a value from 1 to 9.
{
	switch(led){
		case 1:
			leds[2] &= ~0b00000100;
			break;
		case 2:
			leds[2] &= ~0b00000010;
			break;
		case 3:
			leds[2] &= ~0b00000001;
			break;
		case 4:
			leds[3] &= ~0b00000100;
			break;
		case 5:
			leds[3] &= ~0b00000010;
			break;
		case 6:
			leds[3] &= ~0b00000001;
			break;
		case 7:
			leds[4] &= ~0b00000100;
			break;
		case 8:
			leds[4] &= ~0b00000010;
			break;
		case 9:
			leds[4] &= ~0b00000001;
			break;
	}
}

uint8_t getLED(uint8_t led) //Gets the current state of "led". "led" can be a value from 1 to 9.
{
	uint8_t returnValue;
	switch(led){
		case 1:
			returnValue = leds[2] & 0b00000100;
			break;
		case 2:
			returnValue = leds[2] & 0b00000010;
			break;
		case 3:
			returnValue = leds[2] & 0b00000001;
			break;
		case 4:
			returnValue = leds[3] & 0b00000100;
			break;
		case 5:
			returnValue = leds[3] & 0b00000010;
			break;
		case 6:
			returnValue = leds[3] & 0b00000001;
			break;
		case 7:
			returnValue = leds[4] & 0b00000100;
			break;
		case 8:
			returnValue = leds[4] & 0b00000010;
			break;
		case 9:
			returnValue = leds[4] & 0b00000001;
			break;
		default:
			returnValue = 255;
			break;
	}
	if(returnValue)
		return 0;
	else
		return 1;
}

int main() //The main function.
{
	setupTimer();
	setupIO();
	
	PORTB ^= (1<<PB0);
	_delay_ms(500);
	PORTB ^= (1<<PB0);
	
	sei();
	setDisplay(0);
	for(uint8_t i = 1; i < 10; i++){
		setLED(i);
	}
	_delay_ms(10000);
	uint8_t number = 0;
	while(1){
		
		for(uint8_t i = 0; i < 8; i++){
			if(get_key_press1(1<<i)){
				number++;
				clearLED(number);
				setDisplay(number);
			}
		}
		if(get_key_press2(1<<PB2)){
			number++;
			clearLED(number);
			setDisplay(number);
		}			
		if(get_key_press2(1<<PB4)){
			number++;
			clearLED(number);
			setDisplay(number);
		}
	}
}

As you might see, I used danni's code at the end.

Now I'll write the game logic.

So, and here comes my probably last question: Why wont PD0 and PD1 (RX and TX) work? What's the only not working thing now. Is there some bit to set that disables the USART on these two pins or so?

JanD

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Again, why do you use two freaking timers? Let me see, if you count 10 1ms ticks you get 10ms. You really like to cause yourself problems!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Did I forgot to say that in my last post? (Ups, yes I did).

Ok, so here it comes: There are still many things that could be made to make the code faster, take up less space, easier to read etc. etc. etc. etc. But at the moment I only care about getting it working. And working it does.

JanD

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You really don't understand do you??

No you didn't say.

Last Edited: Sun. Apr 3, 2011 - 02:17 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you mean the problem with two interrupts firing at the same time; yes I understand that, and that's one of the highest priority things for the code now, but right now I'm working mostly at the hardware.

JanD

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But why two timers?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What's only from that danni's code used timer 0 and I used timer 1(don't know why, it only got so).

But as sad, I will fix that.

JanD

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sad = unhappy. the word you want is said
The issue is not two timers firing at once. I've given my advice.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This months award for perseverance in the face of adversity goes to ....

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
This months award for perseverance in the face of adversity goes to ....
Hugh Heffner :lol:

Ross McKenzie ValuSoft Melbourne Australia

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bob Gardner nailed it back on Mar 21st with this post in the other thread.

Quote:

Put his Dad on the phone. I bet he's told him to do something a dozen times and didn't get any better response.

I marvel at the patience of some of you guys.

@02JanDal - some wisdom for you, sir. There is something called the Golden Rule. It talks about how you should treat others. You have complained that others are not listening to you, but you have failed to listen to them, and that's why you aren't being listened to.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I recall my younger years where my ambition exceed my skill. Luckily I found a mentor who was adept at explaining complex computing topics in the most clear and concise way that a 14 y/o could understand. I did listen. I felt Jan had half an idea of what he was doing, but he is so absorbed by the problem he has now, he is only interested in solving that problem - anything else is of little consequence. A bit like being in the middle of a minefield.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A bit like being in the middle of a minefield.....surrounded by fire both friendly & foe!

Charles Darwin, Lord Kelvin & Murphy are always lurking about!
Lee -.-
Riddle me this...How did the serpent move around before the fall?

Pages