Multiplexing

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, so after much head scratching i've come to the guru's for help!!!

So heres the picture, i am writing (or trying to write!) a nixie clock. i need to have four data lines and 6 control lines as its a common anode design, 6 digits and the 74141 chip i'm using decodes 4 bits of data. As i turn on one control line it allow current to flow through one of the tubes, through the 74141 abnd turn on the relevant digit.

So, i figure i need a few functions. one to cycle through the control lines, one to work out which number to put on the tube depending on the time, and one to ouput that number on the data lines.

What i would like to know is how i can use a for loop to automatically cycle through the control / data lines. there must be a way!!! heres what i have got so far:

#include 
#include 
#include 

#define bit_get(p,m) ((p) & (m))
#define bit_set(p,m) ((p) |= (m))
#define bit_clear(p,m) ((p) &= ~(m))
#define bit_flip(p,m) ((p) ^= (m))
#define bit_write(c,p,m) (c ? bit_set(p,m) : bit_clear(p,m))
#define BIT(x) (0x01 << (x))
#define LONGBIT(x) ((unsigned long)0x00000001 << (x))

#define CON_PORT PORTC
#define DATA_PORT PORTD

#define SECS 0
#define SECS_TEN 1
#define MINS 2
#define MINS_TEN 3
#define HRS	4
#define HRS_TEN 5

int i;
char req;
int sec = 0;
int min = 0;
int hr = 0;
int C[6];
char port;
int num_to_send;
int num;
//prototypes
int get_num(char requested);

// MAIN PROGRAM
int main (void) {

for (;;) {

	for (i = 0; i <=5; i++) {
		//turn on Control line
		//bit_set(CON_PORT; C[i]);
	
		// Get num to display
		switch (i) {
		
			case 0 : num = get_num(SECS); //Get Sec Unit
			case 1 : num = get_num(SECS_TEN);
			case 2 : num = get_num(MINS);
			case 3 : num = get_num(MINS_TEN);
			case 4 : num = get_num(HRS);
			case 5 : num = get_num(HRS_TEN);
			
		}
	}

		//Send number to DATA_PORT
		send_data(DATA_PORT, num);


} //end forever loop
//end main		
}

int get_num(char req) {

switch (req) {

	case SECS 	: 	return sec % 10;
	case SECS_TEN 	:	if (sec <10 ) {
						return 0;
					}
					return sec / 10;
					 
	case MINS 	: 	return hr % 10;
	case MINS_TEN 	:	if (min <10 ) {
						return 0;
					}
					return min / 10;
	case HRS 	: 	return hr % 10;
	case HRS_TEN 	:	if (hr <10 ) {
						return 0;
					}
					return hr / 10;
//end of function
}
}

void send_data(char port, int num) {

	switch(num) {
		
		case 0 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 0;  
		case 1 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 1;
		case 2 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 1; DATA[3] = 0;  
		case 3 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 1; DATA[3] = 1;  
		case 4 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 0; DATA[3] = 0;
		case 5 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 0; DATA[3] = 1;
		case 6 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 1; DATA[3] = 0;  
		case 7 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 1; DATA[3] = 1;
		case 8 : DATA[0] = 1;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 0;
		case 9 : DATA[0] = 1;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 1;
	}
	for (x=0; x <=9; x++) {
		if (!DATA[x]) {
			clear_bit(port, DATAPIN[x]) ;
		}
		else {
			set_bit(port, DATAPIN[x]);
		}
	}


	}
}

Now what i'm having trouble with is this :

 
void send_data(char port, int num) {

	switch(num) {
		
		case 0 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 0;  
		case 1 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 1;
		case 2 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 1; DATA[3] = 0;  
		case 3 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 1; DATA[3] = 1;  
		case 4 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 0; DATA[3] = 0;
		case 5 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 0; DATA[3] = 1;
		case 6 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 1; DATA[3] = 0;  
		case 7 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 1; DATA[3] = 1;
		case 8 : DATA[0] = 1;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 0;
		case 9 : DATA[0] = 1;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 1;
	}
	for (x=0; x <=3; x++) {
		if (!DATA[x]) {
			clear_bit(port, DATAPIN[x]) ;
		}
		else {
			set_bit(port, DATAPIN[x]);
		}
	}


	}
}

The for loop is reasonably simple. the function works out what the data bits should be by using a switch() command. (maybe theres a better way to do this?)
So i go onto the for() loop. this should count from 0 to 3, working out wether the correspondng data bit should be a one or a zero and setting the pin to that value. Using an array like this won't work (i think?) becasue basically that value needs to be 'PDx' where x is the actual number of the pin i want to control. using an array like this will only allow me to have DATAPIN as 0, 1, 2 or 3.

Please can you inform me if i am thinking along the right lines, and suggest any possible solutions!!!

I am a newbie, just trying to muddle along so any helpful thoughts are greatly appreciated. i've already used alot of info from this site to great effect.

Thanks,

Dave

Dave Harrod

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

no worries, i have seen it but its not trying to achieve what i have in mind. still an excellent tutorial though!

Dave Harrod

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
  switch(num)
    {
    case 0: 
      set_bit(PORTA,0);
      clr_bit(PORTA,1);
      clr_bit(PORTA,2);
      clr_bit(PORTA,3);
      break;
    case 1: 
      clr_bit(PORTA,0);
      set_bit(PORTA,1);
      clr_bit(PORTA,2);
      clr_bit(PORTA,3);
      break;
....
      }

Remember to use 'break' in your switch cases - otherwise the code drops through to the next one.
The above code is a lot of typing - the generated machine code is fairly compact.

Another method is:

static char mask = 0x01;

 PORTA &= 0xf0; //clear bits 0..3
 PORTA |= mask; //set the required port bit
 mask <<=1;     //shift mask bit along
 if (mask > 0x08)  //assuming we have 4 select lines
     {
     mask = 0x01;  //go back to select 1
     }
  


Or the other way is to use an array of bit masks:

static masks[] = {0x01,0x02,0x04,0x08);

 PORTA &= 0xf0; //clear bits 0..3
 PORTA |= masks[num]; //set the required port bit

So choose your favourite. Each has its advantages/disadvantages. The first snippet allows you to use any port and any bit in any sequence.
The second assumes all you bits you want to twiddle are on the one port group and are in sequence.
The third again assumes one port group bit any bit sequence you want.

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

am i right in thinking that this would produce the desired effect?

void send_data(char port, int num) {

	switch(num) {
		
		case 0 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 0; break;  
		case 1 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 1; break;
		case 2 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 1; DATA[3] = 0; break; 
		case 3 : DATA[0] = 0;  DATA[1]= 0; DATA[2] = 1; DATA[3] = 1; break; 
		case 4 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 0; DATA[3] = 0; break;
		case 5 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 0; DATA[3] = 1; break;
		case 6 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 1; DATA[3] = 0; break; 
		case 7 : DATA[0] = 0;  DATA[1]= 1; DATA[2] = 1; DATA[3] = 1; break;
		case 8 : DATA[0] = 1;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 0; break;
		case 9 : DATA[0] = 1;  DATA[1]= 0; DATA[2] = 0; DATA[3] = 1; break;
	}
	port &= 0xf0; //clear bits 0..3
	static masks[] = (0x01,0x02,0x04,0x08);
	
	for (x=0; x <=9; x++) {
	if (DATA[x]) {
	port |= masks[x]; //set the required port bit
	}
	
}

}

If i'm right whats happening is that the DATA[] array gets loaded with the binary to be put onto the data port according to the number required.

the data port is then set to 0000,

the for() loop then goes from 0 to 3, each time checking to see if the DATA[x] is a one, if it is it sets that pin to 1. if not it just moves onto the next one.

Am i right?

thanks for your help.

Dave Harrod

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

and that should be <=3 not <=9 in the for loop

Dave Harrod

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

It seems like you're 'double handling' the data. My original interpretation of what you were doing was the digit selection, it seems you want to do the output to the bcd->digit driver. In this case I would:

assuming the 4bits to the digit driver are sequential: ie 0,1,2,3. And I'm assuming PORTA for the example.

PORTA &=0xf0; //clear bits 0..3
PORTA |= num; //make sure num is 0..15!

basically what we're doing is copying the bits from the num variable out to the PORTA but not touching PORTA bits 4..7

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

I'm not too sure of the exact layout of your circuitry. I had to refresh my memory on Nixie tubes (these were obsolete when I got into electronics in the 70's). I understand you have 6 nixies, but I don't understand how you are driving them. Do you have 6 74141 (1 per nixie tube) or 1 74141 and have 6 drivers for the anodes on each nixie?

In the first instance you would need 6 *4bits of i/o on the AVR. The second instance you would need 4 + 6 bits (or 4 + 3bits if you use a 1:n decoder for the digit selects).

Anyway, if you can explain your circuitry, I'm sure we can provide a simple coding solution.

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

Its a basic circuit, i've got 6 control lines that are connected to the Anode of each nixie through a transistor. This effectively will turn on each tube in turn.

4 data lines are connected to one 74141 which is a binary to hex decoder, allowing whichever nixie is turned on to have a line to GND for whatever digit is required.

Dave Harrod

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

Ok, so i've finally got what i think is a program that'll work. its probably not efficient, but i am a newbie!!!
Unfortunately i haven't got the final boards so all i can do is prototype it with flashing lights!!!

Please scan over it and tell me if i've done anything stupid.

Thankyou all so much for your help. This is probably the friendliest forum i've ever used.

#include 
#include 
#include 

#define DATAPORT PORTB
#define CONPORT PORTC


#define bit_get(p,m) ((p) & (m))
#define bit_set(p,m) ((p) |= (m))
#define bit_clear(p,m) ((p) &= ~(m))
#define bit_flip(p,m) ((p) ^= (m))
#define bit_write(c,p,m) (c ? bit_set(p,m) : bit_clear(p,m))
#define BIT(x) (0x01 << (x))
#define LONGBIT(x) ((unsigned long)0x00000001 << (x))



int num = 0;
int conbit = 0;

int i = 0;
int sec = 0;
int min = 0;
int hr = 0;
int digit = 0;

int get_num(int unit);
void send_data(int num);

// Set Ports

int main(void) {

init();

for (;;) {

	for (i = 0; i<=5; i++) { // loop control lines
		
		CONPORT &= 0x0; // Set PortC to 0
		switch (i) { // Decide which bit to turn on 
			case 0 : conbit = 1;break;
			case 1 : conbit = 2;break;
			case 2 : conbit = 4;break;
			case 3 : conbit = 8;break;
			case 4 : conbit = 16;break;
			case 5 : conbit = 32;break;
		}
		
		CONPORT |= conbit; // Turn on relevant bit
		num = get_num(i); // get number to display from sec, min or hr register
		send_data(num); // send that number to PORTB (data bits)
		_delay_ms(10); // Small delay
		}

	}
}


int get_num(int unit) {

switch(unit) { // work out which tube will be lit, then get relevent digit
	case 0 :   return sec % 10; 
   	case 1 :   if (sec <10 ) {
               	return 0;
               }	
               return sec / 10; 
			   break;  
   	case 2 :   return hr % 10; 
   	case 3 :   if (min <10 ) {
            	   return 0;
               }
               return min / 10;
			   
   	case 4 :    return hr % 10; 
   	case 5 :   if (hr <10 ) {
                  return 0;
               }
               return hr / 10;
	}

}
void send_data(int num) {
 
 PORTB &= 0xf0; //clear bits 0..3
 PORTB |= num; //set the required port bit
 _delay_ms(10); // small delay
 PORTB &= 0xF0; // clear port again to avoid the next tube displaying this number
 }


int init(void) {
DDRD = 1; //PORTD PIN 0 AS OUTPUT
DDRB = 15; // PORTB as output
DDRC = 127; //PORTC as output
   TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
   TIMSK |= (1 << OCIE1A); // Enable CTC interrupt
   sei(); //  Enable global interrupts
   OCR1A   = 512; // Set CTC compare value to 1Hz at 32.768khz AVR clock, with a prescaler of 64
   TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64

MCUCR |= ((1<< ISC11) | (1<< ISC01)); // Set INT1, INT0 to trigger on negative edge
GICR |= ((1<<INT1) | (1<< INT0)); // Enable INT1, INT0



}

ISR(TIMER1_COMPA_vect)
{
 	bit_flip(PORTD, 0x01); //flip PD0 to provide 1hz pulse
   if ( ++sec == 60 ) { // increment secs to 60 then reset
   		sec = 0;
		if (++min == 60 ) { //increment mins to 60 then reset
			min = 0;
			if (++hr == 24) { //increment hrs to 24 then reset
				hr = 0; 
			}
		}
	}
}



ISR(INT0_vect) {
//HR increment and switch debounce
 if (++hr ==24) {
 	hr = 0;
	_delay_ms(10);
 }
}

ISR(INT1_vect) {


//Min increment and switch debounce

 if (++min ==60) {
 	min = 0;
	_delay_ms(10);
 }
}

Dave Harrod

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

I can't help noticing that hr and min are shared between ISR and main() line code. See FAQ#1 (both below and in the avr-libc manual)

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

Ok, point taken but i dont really understand why. Can you explain?

Also this seems to work, but everyhting is inverted.

As it stands the outputs are creating a path to ground when they are turned on (1). This is basicaly causing all of my outputs to be a 1 when a 0 is needed and vice versa.

How can i correct this?

Dave Harrod

Last Edited: Thu. Mar 13, 2008 - 07:10 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well the FAQ in the manual has a pretty good explanation or a search here will find the tons of previous occasions when it's been explained.

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

I think you'll find some issues when you get the hardware operating. Nevertheless, the first thing is to get it working, then fix the problems, rather than bog you down in the technicalities.

How I would approach the problem would be:
Have 1 timer ISR to do the multiplexing, read the pushbutton switches and to increment the time. Do not use any delays in the ISR! This method would ensure you have a constant multiplexing rate so you don't get brightness modulation when buttons are pressed. Chose a timer tick rate that satisfies all the requirements - I'd say around 1 - 5mS. You multiplexing code might look something like this:

volatile char switch1_count = 0;

timer_isr()
{
static char mux_state = 0;

 switch(mux_state)
   {
   case 0:
     output & select first digit;
     mux_ state = 1;
     break;
   case 1:
     deselect first digit; //so we don't ghost
     mux_state = 2;
     break;
   case 2:
     output and select second digit;
     mux_state = 3;
     break;

   ....for the rest of the digits

   case x:
     mux_state = 0; //do it all again..
     break;
   }//end switch

// debounce switch

  if (switch is OFF)
     {
     switch1_count = 30; //30 ms if tick is 1ms
     }
   else //switch is on
     {
     if (switch1_count)
         {
         switch1_count--;  //switch is on, decrement the count
         }
     }
     }
}


....

 if (switch1_count == 0)
     {
      //switch is pressed and debounced!
     }

Here's some words I wrote regarding the use of interrupts. I explain the requirement of the 'volatile' keyword and common traps.

https://www.avrfreaks.net/index.p...

Here's some other words about bit manipulation that should help you:

https://www.avrfreaks.net/index.p...