multiplexed 4-digit 7-segment display

Hi all,

I had constructed a thermometer, using a single DS18B20, a 4-digit 7-segment display and a attiny2313.

The c program was written in AVR studio4.

Multiplexing the display was good when displaying simulated data.

Eventually, an interrupt routine, generated by timer0 overflow, was written.

DS18B20 was read after a number of interrupts, about every second, by design.

Temperature appeared to be correct too.

Since 750ms was required by the default 12bit temperature conversion,

the display would freeze at the last digit addressed, momentarily.

It was apparent that my software strategy was questionable.

Would appreciate any advise.

Perform the temperature measurement outside of the interrupt processor (e.g., in the main program loop) and store the result in variables that your interrupt handler will display. Your handler should be as light (quick) as possible. That is the approach that I used in my PIC based monitor.

Hi Ralph,

Thank you for your advise.

I have now moved the DS18B20 routines to the main loop.

The interrupt routine, is now nothing more than putting out the correct

segment data and switching on the appropriate 7-segment led.

Result:

Stable display with correct temperature.

However, 255 is displayed every few seconds.

255, in my program, indicates DS18B20 is not responding to reset.

Not sure why the interrupt should “stretch” the delay so badly, the Attiny2313 is

already running on the 20mhz xtal.

It looks like you’re making progress!

Once the command is given the DS18B20, all the processor has to do is to either wait a fixed time or look for the conversion complete bit to be set. Are you powering the DS18B20 with a 5v supply or are you using parasitic mode that powers the chip using only the data line? Could you tell us a little bit about your main loop (i.e., how is it interacting with the chip)?

The DS18B20 is powered by +5V and common from the perf. board. 4K7 is connected from the +5V to the PD6(the bus) at the perf. board side.

The DS18B20 is connected to the perf. board via about 6M of burglar alarm wire.

The program starts with initialising timer0 to interrupt on overflow.

main loop is as follows:

while(1)

{

uint8_t i;

i = reset_1wire_bus();

if(i==0) DS18b20_read_temperature();

//The DS18b20_read_temperature() updates global variable “digit”.

else digit=255;

d[4]=digit/100;

d[5]=(digit-d[4]*100)/10;

d[6]=digit-d[4]*100-d[5]*10;

d[0]= seg[d[4]];

d[1]= seg[d[5]];

d[2]= seg[d[6]];

d[3]= seg[10];

_delay_ms(30);

}

for those interested, the DS18B20 routines are taken from research document by Gerard Marull Paretas.

You may be picking up noise or experiencing effects from cable capacitance. Are you using twisted pair cable? I have runs of up to 30m using CAT5 cable and everything works fine with external power. Try connecting the DS18B20 closer to the board and see if that clears up the problem.

If you still have problems after shortening the run, can you provide a link to the paper you referenced or the code for your reset and read routines?

If you are displaying from interrupt, you have to make sure that the variable(s) you use to store value to display is updated atomically:

IT:

Get value, do display work.

Main loop:

Start reading ds1820

If result is ok: convert value to value to display

or: set value to error (but why?)

disabling IT

update display variable(s)

reenable IT

IT disable/enable is probably not mandatory if display variable is one byte (update is mostly atomic). Your actual code is not (interupt could fire in digit computation).

For the failure of 1wire reading, it could comes from side effect of interupt firing during conversion.

Hi all,

The DS18B20 routines are taken from:

http://teslabs.com/openplayer/docs/docs … 0_pre1.pdf

My program is as below:

#include <avr/io.h>

#include <avr/interrupt.h>

#define F_CPU 20000000

#include <util/delay.h>

//prototypes

void DS18b20_read_temperature();

uint8_t reset_1wire_bus();

void DS18b20_write_bit(uint8_t bit);

uint8_t DS18b20_read_bit(void);

uint8_t DS18b20_read_byte(void);

void DS18b20_write_byte(uint8_t byte);

void display_temperature();

//defines

#define DS18b20_cmd_converttemp 0x44

#define DS18b20_cmd_rscratchpad 0xbe

#define DS18b20_cmd_wscratchpad 0x4e

#define DS18b20_cmd_cpyscratchpad 0x48

#define DS18b20_cmd_receeprom 0xb8

#define DS18b20_cmd_rpwrsupply 0xb4

#define DS18b20_cmd_searchrom 0xf0

#define DS18b20_cmd_readrom 0x33

#define DS18b20_cmd_matchrom 0x55

#define DS18b20_cmd_skiprom 0xcc

#define DS18b20_cmd_alarmsearch 0xec

#define DS18b20_decimal_steps_12bit 625

#define SEG_a 0x01

#define SEG_b 0x02

#define SEG_c 0x04

#define SEG_d 0x08

#define SEG_e 0x10

#define SEG_f 0x20

#define SEG_g 0x40

#define SEG_dot 0x80

unsigned char seg={

(SEG_a|SEG_b|SEG_c|SEG_d|SEG_e|SEG_f), // 0

(SEG_b|SEG_c), // 1

(SEG_a|SEG_b|SEG_d|SEG_e|SEG_g), // 2

(SEG_a|SEG_b|SEG_c|SEG_d|SEG_g), // 3

(SEG_b|SEG_c|SEG_c|SEG_f|SEG_g), // 4

(SEG_a|SEG_c|SEG_d|SEG_f|SEG_g), // 5

(SEG_a|SEG_c|SEG_d|SEG_e|SEG_f|SEG_g), // 6

(SEG_a|SEG_b|SEG_c), // 7

(SEG_a|SEG_b|SEG_c|SEG_d|SEG_e|SEG_f|SEG_g), // 8

(SEG_a|SEG_b|SEG_c|SEG_d|SEG_f|SEG_g), // 9

(SEG_a|SEG_d|SEG_e|SEG_f), // 10

};

//global variables

uint8_t digit, decimal, digit_addressed=0;

uint8_t d[7];

void DS18b20_read_temperature()

{

uint8_t temperature[2];

reset_1wire_bus();

DS18b20_write_byte(DS18b20_cmd_skiprom); //0xcc

DS18b20_write_byte(DS18b20_cmd_converttemp); //0x44

while(!DS18b20_read_bit()); DS18B20 will pull bus low until conversion is over

reset_1wire_bus();

DS18b20_write_byte(DS18b20_cmd_skiprom);

DS18b20_write_byte(DS18b20_cmd_rscratchpad);

temperature[0]=DS18b20_read_byte();

temperature[1]=DS18b20_read_byte();

reset_1wire_bus();

digit=temperature[0]>>4;

digit|=(temperature[1]&0x07)<<4;

decimal=temperature[0]&0xf;

decimal*=DS18b20_decimal_steps_12bit;

}

uint8_t reset_1wire_bus()

{ uint8_t i;

PORTD&=~_BV(6); //pull bus to zero

DDRD|=_BV(6); //make sure the PD6 is an output

_delay_us(520); //I’ve choosen to add 10% above the minimum of 480us

DDRD&=~_BV(6); //change bus to read 18b20’s response

_delay_us(80); //bus should be stabilised by 60us

i=(PIND & 0b01000000); //PD6 is read

_delay_us(520-80);

return i; //when i=0, a 18B20 is responding.

}

void DS18b20_write_bit(uint8_t bit)

{

PORTD&=~_BV(6); //bus low required

DDRD|=_BV(6); //change PD6 to work as output

_delay_us(1);

if(bit) DDRD&=~_BV(6); //if we are req’d to write a “one”, just change PD6 to input, the pullup will create the “one” automatically.

_delay_us(69); // tweaking in progress

DDRD&=~_BV(6); //complete the routine by changing PD6 to read.

//my total time = 1+69=70us, minimum is 60us.

}

uint8_t DS18b20_read_bit(void)

{

uint8_t bit=0;

PORTD&=~_BV(6); //will be pulling bus low

DDRD|=_BV(6); //change PD6 to work as output

_delay_us(1); //delay 1us

cli(); //my tweaking

DDRD&=~_BV(6); //get ready to read the coming info

//do not read after 15us

// _delay_us(15);

_delay_us(9); //tweaking — no improvement

if(PIND & 0b01000000) bit=1; //read the bit

_delay_us(70-9-1); //minimum time slot = 60us

sei();

return bit;

}

uint8_t DS18b20_read_byte(void)

{

uint8_t i=8, n=0;

while(i–)

{

n>>=1;

n|=(DS18b20_read_bit()<<7);

}

return n;

}

void DS18b20_write_byte(uint8_t byte)

{

uint8_t i=8;

while(i–)

{

DS18b20_write_bit(byte&1);

byte>>=1;

}

}

ISR(TIMER0_OVF_vect)

{

PORTB = ~(d[digit_addressed]);

switch (digit_addressed)

{

case 0: PORTD=0b11111011;

break;

case 1: PORTD=0b11110111;

break;

case 2: PORTD=0b11101111;

break;

default:PORTD=0b11011111;

}

digit_addressed++;

if(digit_addressed>=4) digit_addressed=0;

}

int main()

{

TCNT0 = 0x00 ;

TCCR0B = 0x04; //prescaler of 256

TIMSK = 0x02; //overflow irq req’d

sei();

PORTD|=_BV(2)|_BV(3)|_BV(4)|_BV(5);

DDRD |=_BV(2)|_BV(3)|_BV(4)|_BV(5);

PORTB=0xff;

DDRB =0xff;

while(1)

{

uint8_t i;

i = reset_1wire_bus();

if(i==0) DS18b20_read_temperature();

else digit=255;

d[4]=digit/100; //digit is updated in the DS18B20_read_temperature();

d[5]=(digit-d[4]*100)/10;

d[6]=digit-d[4]*100-d[5]*10;

d[0]= seg[d[4]];

d[1]= seg[d[5]];

d[2]= seg[d[6]];

d[3]= seg[10];

_delay_ms(30);

}

}

Hi, Ralph,

I have now shortened the lead from the DS18B20 to the connector, its about 100mm(4 inches) now.

Currently, display is stable with the correct temperature.

255 and 127 are still being displayed, occassionally.

BTW the circuit diagram was taken from:

http://chaokhun.kmitl.ac.th/~kswichit/a … hermo.html

The difference is xtal and sensor used.

His version is 4mhz/DS1820, whereas I’m using 20mhz/DS18B20.

Hi, Mac

Yes, removing “else digit=255” and the DS18B20 will miss just that round to read temperature.

However, occassionally 127 is displayed, indicating that there is a problem somewhere.

I have disabled/enabled the interrupt in the DS18B20_read_bit(), but no improvement is observed.

Thinking about why the 127:… it has to be the DS18B20 is not “managing” the bus in the DS18B20_read_bit(),

but why occassionally?..interesting.

I have swapped in another DS18B20 but there is no improvement.

I remember visiting that site a few years ago when I was experimenting with the AT89C2051. I remember throwing together a PIC version of the line-follower robot project that was published on the site. Worked very well, if I recall.

It appears that you have a cable problem to some degree. I’d run the DS18B20 with CAT5 (or similar) twisted cable. One pair to supply the +5v and another for the data. I know that works for me for fairly long runs. But get the other problem fixed first.

Maybe your timing is off with the one-wire drivers. I have been gone for the weekend and I don’t have time to reference the datasheet at the moment. However, I have example code for the PIC that you could use as a reference for the timings that work for me. I recall that when I wrote the code, I wanted to be sure that I was following specifications. Assuming that your timing constants honor your F_CPU directive, the 20 MHz crystal shouldn’t make a difference (my PIC is running with a 48 MHz internal clock).

I apologize in advance for the somewhat complicated code. I used this piece of code to drive four separate one-wire networks that fan out in different directions. So whenever you see “channel” it just means when I/O pin to use. DQ_LOW means to drive the pin connected to the DS18B20 to logic 0, DQ_HIGH means to drive it to logic 1, and DQ_RELEASE means to make the I/O pin an input. Hopefully the rest of the code will be self-explanatory.

[One-wire code in C18

[One-wire header file](http://www.creativewidgetworks.com/cww/PIC/doc/1wire.h)](http://www.creativewidgetworks.com/cww/PIC/doc/1wire.c)

Thank You so much Ralph.

I shall go through your program thoroughly, but it’ll take some time for me to digest it.

cheers

Focus on the ReadBit/ReadByte and WriteBit/WriteByte functions checking the sequence and most importantly, the delays. You should see similarities between your code and the PIC code.

Hi Ralph,

I’m happy to report that I have a stable & accurate thermometer finally.

It has been running over an hour now and I have yet to see a hiccup.

The delays in my program, are within the ballpark of yours and several others… on paper at least.

The problem is, they got “stretched” by the interrupt and becomes unstable.

My solution now is to disable interrupt on entering all time sensitive routines and enabling it again on leaving.

Thank you again.

yanctl