In switch/case can a range be used for case

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

Have a 7-segment led display I'm trying to obtain the tens digit for. Here is the code.

    if      (time > 89) t = _7seg_doit(9);
    else if (time > 79) t = _7seg_doit(8);
    else if (time > 69) t = _7seg_doit(7);
    else if (time > 59) t = _7seg_doit(6);
    else if (time > 49) t = _7seg_doit(5);
    else if (time > 39) t = _7seg_doit(4);
    else if (time > 29) t = _7seg_doit(3);
    else if (time > 19) t = _7seg_doit(2);
    else if (time >  9) t = _7seg_doit(1);
    else                t = _7seg_doit(0);

I though there is a more elegant way to do this using switch/case with a range for the case portion like here

  switch(time)
  {
    case 90-99:
      t = 9;
      break;
    case 80-89:
      t = 8;
      break;
    case 70-79:
      t = 7;
      break;
    case 60-69:
      t = 6;
      break;
    case 50-59:
      t = 5;
      break;
    case 40-49:
      t = 4;
      break;
    case 30-39:
      t = 3;
      break;
    case 20-29:
      t = 2;
      break;
    case 10-19:
      t = 1;
      break;
    case 0-9:
      t = 0;
      break;
    }

Seems to me I have used something like that. May be it was in Turbo Pascal or Delphi.

My understanding is that the case only can use a constant. I'm hoping a range numbers or characters can be used. Any ideas?

Mike[/code]

Quote:

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

Would you consider dividing by ten, and eliminating about 95% of your horrific code, like so:

    t = sevenSeg_doit(time / 10);

?

That's all for now, but stay tuned next week for details on how to replace a 256-case switch statement for generating numbers one greater than an arbitrary unsigned character value with a single clever use of a "+ 1" operation.

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

That's all for now, but stay tuned next week for details on how to replace a 256-case switch statement for generating numbers one greater than an arbitrary unsigned character value with a single clever use of a "+ 1" operation.

Can't wait.

Right out of the gate I used % to get the units with no problems and divide like you did to get the tens but for some reason the tens were not working. I thought (there is where I made another mistake) well, the result is being rounded up or down and dug into a sure fire way to get the result needed and down the marry path I went, do-dah do-dah. Thanks that cleaned up a nasty looking part of my code.

Oh yes, there is a Case in Pascal and it is Case without Switch and they can take a range for the argument. As in

Case Number of
1..10 : WriteLn (’Small number’);
11..100 : WriteLn (’Normal, medium number’);
else
WriteLn (’HUGE number’);

Thanks, especially for the quick reply,
Mike
:D

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

You can use div() (or ldiv() for longs) and do the divide and the mod in one step.

Regards,
Steve A.

The Board helps those that help themselves.

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

Quote:

May be it was in Turbo Pascal or Delphi.

Yup, it most likely was, since Pascal do allow what you are asking for.

Pascal and C switch/case differs in more than just that. A Pascal case-statement is more like a cascaded if-statement. A C switch-statement is more like a multiple goto.

In C, a case that does not do a break will fall through to the next case. In Pascal that will not happen.

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

As Levenkay has suggested, a simple division is all done in one line !

However, you original construction is absolutely fine for 'non-linear' lookups.

If you wanted to use a switch statement, you would need:

  switch(time) 
  { 
    case 90: 
    case 91: 
    case 92: 
    ...
    case 98: 
    case 99: 
      t = 9; 
      break;
    ... 

Some C compilers may convert your 100-case switch into very intelligent code.
You could try it with avr-gcc. I would not hold my breath.

If you are multiplexing the LED display, it is far more efficient to have a display buffer.
Write the actual pattern into each byte of the buffer. (Only executed when the display changes)
The multiplex routine simply loads display[n] into the n'th 7-segement. No divisions are involved.

David.

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

Steve A. wrote:
You can use div() (or ldiv() for longs) and do the divide and the mod in one step.
Didn't know of div() and ldiv(). Comparing 'div()' vs '/', the '/' is 52 bytes smaller so that is what I went with.
JohanEkdahl wrote:
Pascal and C switch/case differs in more than just that. A Pascal case-statement is more like a cascaded if-statement. A C switch-statement is more like a multiple goto.
I was not aware of the close but slight differences, thinking they were the same, I used whatever syntax that came to mind. You can do that but don't expect the code to compile.
david.prentice wrote:
If you are multiplexing the LED display, it is far more efficient to have a display buffer.
Write the actual pattern into each byte of the buffer. (Only executed when the display changes)
The multiplex routine simply loads display[n] into the n'th 7-segement. No divisions are involved.
Thats what I'm doing

// 7segtest.c 

//#include 
#include 
#include "/home/mike/mfile/code/include/7segment.h"

volatile unsigned int interruptscount = 0;
volatile unsigned int time = 0;
volatile unsigned int units = _0;
volatile unsigned int tens = _0;

// Timer 0 Overflow Interrupt Handler
ISR(TIMER0_OVF0_vect)
{
  interruptscount++;
  if (interruptscount == 11) 
  {
    interruptscount = 0;              // Clear interrupt counter 
    if (++time == 100) time = 0;
    units = _7seg_dispat(time % 10);  // Convert number to display pattern
    tens  = _7seg_dispat(time / 10);  // Convert number to display pattern
  }
}

int main(void)
{
  _7seg_init();

  while(1)
  {
  _7seg_display(tens, units);         // Send display pattern to 7-segment led
  }
}
// 7segment.h

// Library for 7segment.c

#include 
#include 
#include 

#define a       1                           // 7-seg 'a' pin
#define b       2                           // 7-seg 'b' pin
#define c       4                           // 7-seg 'c' pin 
#define d       8                           // 7-seg 'd' pin 
#define e       16                          // 7-seg 'e' pin 
#define f       32                          // 7-seg 'f' pin 
#define g       64                          // 7-seg 'g' pin 
#define dp      128                         // 7-seg 'dp' pin

#define _0      (a | b | c | d | e | f)     // Display Pattern 0
#define _1      ( b | c)                    // Display Pattern 1
#define _2      (a | b | d | e | g)         // Display Pattern 2
#define _3      (a | b | c | d | g)         // Display Pattern 3
#define _4      (b | c | f | g)             // Display Pattern 4
#define _5      (a | c | d | f | g)         // Display Pattern 5
#define _6      (a | c | d | e | f | g)     // Display Pattern 6
#define _7      (a | b | c )                // Display Pattern 7
#define _8      (a | b | c | d | e | f | g) // Display Pattern 8
#define _9      (a | b | c | f | g)         // Display Pattern 9
#define _tens   1                           // Tens On Pin
#define _units  2                           // Units On Pin
#define _both   0                           // Tens and Units Off

#define disPORT PORTB                       // Display PORT
#define disDDR  DDRB                        // Display DDR
#define ctlPORT PORTD                       // Control PORT
#define ctlDDR  DDRD                        // Control DDR

// Initialize CPU
void _7seg_init(void);
// Convert numberical value to Display Pattern
int  _7seg_dispat(unsigned int s);
// Send tens and units Display Patterns to 7-segment LED
void _7seg_display(int dtens, int dunits);  

// Initialize CPU
void _7seg_init(void)
{
  ctlDDR  = 0xff;   // ControlDDR
  ctlPORT = _both;  // ControlPORT
  disDDR  = 0xff;   // DisplayDDR
  disPORT = 0x00;   // DisplayPORT

  TIMSK = (1 << TOIE0);               // T/C0 Overflow Interrupt Enable
  TCCR0 = (1 << CS02) | (1 << CS00);  // CK/1024

  asm("sei");       // Enable Interrupts
}

// Convert numberical value to display pattern
int _7seg_dispat(unsigned int s)
{
  int x = 0;
  switch(s)
  {
    case 0:  x = _0;  // Display Pattern (a | b | c | d | e | f)
        break;
    case 1:  x = _1;  // Display Pattern ( b | c)
        break;
    case 2:  x = _2;  // Display Pattern (a | b | d | e | g)
        break;
    case 3:  x = _3;  // Display Pattern (a | b | c | d | g)
        break;
    case 4:  x = _4;  // Display Pattern (b | c | f | g)
        break;
    case 5:  x = _5;  // Display Pattern (a | c | d | f | g)
        break;
    case 6:  x = _6;  // Display Pattern (a | c | d | e | f | g)
        break;
    case 7:  x = _7;  // Display Pattern (a | b | c )
        break;
    case 8:  x = _8;  // Display Pattern (a | b | c | d | e | f | g)
        break;
    case 9:  x = _9;  // Display Pattern (a | b | c | f | g)
  }
  return x;
}

// Send tens and units display patterns to 7-segment led
void _7seg_display(int dtens, int dunits)
{
  int dly = 2;

  _delay_ms(dly);     // Delay
  disPORT = dtens;    // Tens to DisplayPORT
  ctlPORT = _tens;    // Tens LED On
  _delay_ms(dly);     // Delay
  ctlPORT = _both;    // Tens LED Off
  disPORT = dunits;   // Units to DisplayPORT
  ctlPORT = _units;   // Units LED On
  _delay_ms(dly);     // Delay
  ctlPORT = _both;    // Tens LED OFF
  _delay_ms(dly);     // Delay
}

Thanks guys,
Mike

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

here is mine, maybe a little bit more structured than yours:

void dig_display(unsigned char dig, unsigned char n) {		//display a number
	const unsigned char seg_table[] = {
	0xc0,										//0
	0xf9,
	0xa4,
	0xb0,
	0x99,
	0x92,
	0x82,
	0xf8,
	0x80,
	0x90
	};

	dig_off(DIGs);								//turn off all digits
	SEG_PORT = seg_table[n % 10];				//display the digit
	dig_on(dig);								//turn on the digit
	delay_ms(DIG_DLY);							//waste some time
	dig_off(DIGs);								//turn off all digits
}

void time_display(void) {						//display the current time
	//display the current second
	dig_display(DIG0, time.sec % 10);			//display the lowest digit of second
	dig_display(DIG1, time.sec/10);				//display the 10s digit of second

	//display the current min
	dig_display(DIG2, time.min % 10);			//display the current hour
	dig_display(DIG3, time.min/10);				//display the current hour

	//display the current hour
	dig_display(DIG4, time.hour % 10);
	dig_display(DIG5, time.hour/10);
}

dig_display() is a generic display function for a given digit. time_display() shows all 6 digits (or more if you want), and is fairly reconfigurable.

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

I would disagree with both of you.

Use your Timer ISR() to do the multiplexing, and updating the display buffer.

ISR(TIMER0_COMPA_vect)              // multiplex every 10ms           
{ 
  if (interruptcount & 1) {
     disPORT = dtens;    // Tens to DisplayPORT 
     ctlPORT = _tens;    // Tens LED On 
  } else {
     disPORT = dunits;    // Units to DisplayPORT 
     ctlPORT = _units;    // Unitss LED On 
  }
  interruptscount++; 
  if (interruptscount == 100)         // update display every second 
  { 
    interruptscount = 0;              // Clear interrupt counter 
    if (++time == 100) time = 0; 
    units = _7seg_dispat(time % 10);  // Convert number to display pattern 
    tens  = _7seg_dispat(time / 10);  // Convert number to display pattern 
  } 
} 

If you are multiplexing say 8 seven-segments, you would have a 8 byte display buffer:

  static uint8_t idx;     // display index 
  if (++idx > 7) idx = 0; // 0 .. 7
  disPORT = dispbuf[idx]; // send digit to DisplayPORT 
  ctlPORT = 1 << idx;     // enable selected digit 

You will find that the compiler generates very efficient code for the multiplex.

Likewise, you can have a very simple lookup table.

uint8_t lookup[10] = { _0, _1, _2, _3, _4, _5, _6, _7, _8, _9 };
 
void put_7seg(uint8_t idx, uint8_t value)
{
    dispbuf[idx] = lookup[value];
}

David.

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

no display buffer but all done in isrs:

//tmr1 isr
ISR(TIMER1_OVF_vect) {							//timer 1 overflow isr
	static unsigned long time_elapsed=0;

	time_elapsed += ISR_INTERVAL;				//time elapsed since the last interrupt
	TCNT1 += -ISR_INTERVAL;
	if (time_elapsed >= (DISP_INTERVAL*1000000ul)) {		//update time
		time_elapsed -= (DISP_INTERVAL*1000000ul);			//update time_elapsed
		time.sec+=DISP_INTERVAL;							//increase the second.
		if (time.sec>=60) {
			time.sec-=60;						//update the second
			time.min+=1;						//update the min
			if (time.min>=60) {
				time.min-=60;					//update the min
				time.hour+=1;					//update the hour
				if (time.hour>=24) time.hour-=24;
			}
		}
	}
}

//tmr3 isr
ISR(TIMER3_OVF_vect) {							//timer 3 overflow isr
	static unsigned long current_digit=0;		//remember current digit to be displayed
	unsigned char dig, n;						//for display

	const unsigned char seg_table[] = {
	0xc0,										//0
	0xf9,
	0xa4,
	0xb0,
	0x99,
	0x92,
	0x82,
	0xf8,
	0x80,
	0x90
	};

	TCNT3 += -(DIG_DLY*1000);										//load tcnt3 with offset
	switch (current_digit++) {
		case 0:	dig = DIG0; n = time.sec % 10; break;				//display digit0
		case 1:	dig = DIG1; n = time.sec / 10; break;				//display digit1
		case 2:	dig = DIG2; n = time.min % 10; break;				//display digit2
		case 3:	dig = DIG3; n = time.min / 10; break;				//display digit3
		case 4:	dig = DIG4; n = time.hour % 10; break;				//display digit4
		case 5:	dig = DIG5; n = time.hour / 10; break;				//display digit5
		default: current_digit=0;									//reset current display counter
	}

	dig_off(DIGs);								//turn off all digits
	SEG_PORT = seg_table[n % 10];				//display the digit
	dig_on(dig);								//turn on the digit
	//delay_ms(DIG_DLY);							//waste some time
	//dig_off(DIGs);								//turn off all digits

}

tmr1 keeps time; and tmr3 updates the display, at DIG_DLY ms intervals.

tmr3 is essentially the same code I provided earlier (time_display()), isr'zed.

the macros are provided to allow more flexibility.

see it in action.

Attachment(s): 

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

millwood wrote:
no display buffer but all done in isrs:

Are you sure, that only 6 bytes SRAM are to expensive?

But then you need no division inside every interrupt and thus the interrupt should be over 10 times faster. :!:

Peter

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

Brewski wrote:
I though there is a more elegant way to do this using switch/case with a range for the case portion
It's not a best choice for extracting high digit from two-digit value, but...

gcc (and avr-gcc) can handle ranges in case.
There is a GNU extension to C language (btw, for Pascal-mans, gcc extension list includes local functions)

#include 

uint8_t foo(uint8_t i)
{
        switch(i) {
                case 0 ... 5:   return 1; // spaces around '...' are mandatory
                case 10 ... 20: return 2;
                default:        return 0;
        }
}
foo:
	cpi r24,lo8(6)
	brlo .L3
	subi r24,lo8(-(-10))
	cpi r24,lo8(11)
	brsh .L2
	ldi r24,lo8(2)
	ret
.L3:
	ldi r24,lo8(1)
	ret
.L2:
	ldi r24,lo8(0)
	ret

wbr, ReAl

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

Quote:

gcc (and avr-gcc) can handle ranges in case.
There is a GNU extension to C language

Well-well, you learn something every day (hopefully :D).

If portability is at all an issue I'd stay away from both that ellipsis, and the local functions.

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

JohanEkdahl wrote:
Well-well, you learn something every day
You don't ???
All time is spent for "portability" mantra?
JohanEkdahl wrote:
If portability is at all an issue I'd stay away from both that ellipsis, and the local functions.
If

OCR1A += 100;

even less portable - just between compilers with AVR support.

for avr-gcc toolchain
atomic.h is not portable to other compilers

pgmspace.h is portable neither to other compilers, nor to other micros - shall I use strings in RAM to avoid non-portable %S format specifier?

pointer arithmetic is "not portable" to programmers that do not understand it well, so many "portability teachers" suggest to avoid it at all for "portability" with all C-"programmers":(

"range in case" is portable from any gcc-supported system to any gcc-supported system. That system count is much more than I can learn (and than even you can learn).

AVR, ARM*, may be MSP430 and PIC24 for embedded and Win/Lin for PC is enough for me. It is not planned to go back to mcs51 and pic16. So, portability of GCC features is good enough.

wbr, ReAl

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

Quote:

"range in case" is portable from any gcc-supported system to any gcc-supported system. That system count is much more than I can learn (and than even you can learn).

As is computed goto. (afaik) That doesn't mean it is a "good thing" for purists.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Quote:
Didn't know of div() and ldiv(). Comparing 'div()' vs '/', the '/' is 52 bytes smaller so that is what I went with.
But '/' only gives you the tens digit. You still need a '%' to get the ones digit. Doing 'div()' gives you both in one operation.

Regards,
Steve A.

The Board helps those that help themselves.

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

Or if this is a human-readable real-time clock, the time could be kept in human-readable form to begin with to avoid div/mod operations at all. A byte stores two decimal digits (BCD).

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

theusch wrote:
As is computed goto. (afaik)
Yes! I use it for protothreads!

theusch wrote:
That doesn't mean it is a "good thing" for purists.
Purists...
atomic.h has no "range in case", has no local functions, has no computed goto. But it is not portable to IAR/AVR. Where were purists?
"C’est La Vie"

I can write portable programs -- when it is really needed.

wbr, ReAl

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

Koshchi wrote:
Quote:
Didn't know of div() and ldiv(). Comparing 'div()' vs '/', the '/' is 52 bytes smaller so that is what I went with.
But '/' only gives you the tens digit. You still need a '%' to get the ones digit. Doing 'div()' gives you both in one operation.

On my computer using the gcc-gnu avr-libc library the code below created a program 52 bytes smaller than using div()
    units = time % 10;
    tens  = time / 10;

The time only counts up to 100. When time reaches 100 time is reset to zero.

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

div() returns a structure
"returns" mean:

  • caller creates (reserves space for) div_t object on stack
  • caller put the object address as first hidden argument to div()
  • div() fills the structure object
  • caller gets result from structure on stack
Helper functions for / and % operators returns result in registers and code size is less than for div(). But div() is faster than / plus %

For small counter you can use two bytes for counter:

uint8_t second_uints, second_tens;
if( ++second_units >= 10 ) {
    second_units = 0;
    if( ++second_tens >= 6) {
        secnod_tens = 0;
        // handle minutes
    }
}

and remove / % div() at all.

wbr, ReAl

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

ReAlAl wrote:

For small counter you can use two bytes for counter:

Or even smaller with BCD which stores one digit per nibble like my previous suggestion. The other advantages remain, like being very easy to convert time to 7-segment or ASCII.

uint8_t bcd_seconds=0;

bcd_seconds++; // increase ones

// if ones hit 0d10(0x0A), make ones 0 and increase tens 0x10(0d16)
if ((bcd_seconds&0x0F)==0x0A) bcd_seconds+=6;
// if tens hit 6 and seconds 0 (0x60) minute passes
if (bcd_seconds==0x60)
{
    bcd_seconds=0;
    // handle minutes
}

// write seconds as ASCII letters to LCD or UART
send('0'+(bcd_seconds>>4));
send('0'+(bcd_seconds&0x0F));

// write seconds to 7-segment
digits[0]=bcd_seconds>>4;
digits[1]=bcd_seconds&0x0F;
setseg(0,digittable[digits[0]];
setseg(1,digittable[digits[1]];
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I saw your suggestion so I just added another variant.

This is RAM/FLASH size balance

setseg(0,digittable[ second_tens ];
setseg(1,digittable[ second_units ]; 

requires one RAM byte more per counter but slightly shorter in flash than

setseg(0,digittable[ bcd_seconds>>4 ];
setseg(1,digittable[ bcd_seconds&0x0F ];

(assume that digittable[] is placed in flash and pgm_read_byte() is used because digittable[] consumes even more RAM :-) )

wbr, ReAl

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

I dealing with a clock that is dealing with a speed of seconds. With a 4.0MHz clock that is plenty of speed to do the seconds counts and display that count on a 2 digit, 7-segment LED display. Although it can display 0 to 99 seconds I'm only interested in 60 seconds. I don't even care about minutes I know that 90 seconds in 1 and 1/2 minutes. What I interested in how long, in seconds, until the next exercise set. "%" and "/" works very well for me taking less memory that div() plus to me the code is much cleaner than anything else that I have seen here. You guys can argue your views here until hell freezes over but it is MY code, MY program, and with or without your approval or not I WILL do what I please NOT what YOU want me to.

I don't know why my original "/" did not work, that code has be wiped out. Probably a problem somewhere else in my code. Who knows. I don't care. The problem is fixed. On to more problems to code. Thanks to all those that helped me out...

Some of you need to get a life.

Nothing like beating a horse to death,
Mike

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

Brewski wrote:
"%" and "/" works very well for me taking less memory that div()

And I explained why and how it costs in time.
When code speed will be important, maybe you remember about the div().

Brewski wrote:
it is MY code, MY program, and with or without your approval or not I WILL do what I please NOT what YOU want me to.
So, why you ask questions here?

Brewski wrote:
I don't know why my original "/" did not work, that code has be wiped out.
It is YOUR code. Good weekend to you. I'm going to work with MY code.

wbr, ReAl