1
0
mirror of https://github.com/rfivet/stm32bringup.git synced 2024-12-18 23:06:28 -05:00
stm32bringup/docs/33_ds18b20.html

402 lines
11 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3.3 DS18B20 Digital Thermometer</title>
<link type="text/css" rel="stylesheet" href="style.css">
</head>
<body>
<h1>3.3 DS18B20 Digital Thermometer</h2>
The DS18B20 chip from <b>Maxim Integrated</b> is a digital thermometer able to
do measurement from -55℃ to 125℃ with a precision of ±0.5℃ in the range
-10℃ to 85℃.
<h2>Hardware considerations</h2>
The DS18B20 comes in several packaging where only 3 pins are used: vcc,
gnd and data io. It can be powered at 5V or 3.3V.
<p>
The io data line when idle need to be at high level, so a pull up
resistor is necessary. The small DS18B20 board I use has a pull up
resistor between vcc and io data.
<p>
<img alt="DS18B20 Board" src="img/33_ds18b20.png">
</p>
It is possible to power the chip using data io and gnd only (no vcc) in
Parasitic Power Mode if a two wire only interface is needed. I wont use
this feature for now.
<h2>Communication protocol</h2>
The data io line is a 1-Wire bus on which several 1-Wire devices can be
connected. So there is a scheme to address multiple devices, but in
simple case where there is only the host and one device, command can be
broadcasted without specifying addresses.
<p>
A typical transaction sequence goes like this
<ul>
<li> Initialization
<li> ROM Command (followed by any required data exchange)
<li> Function Command (followed by any required data exchange)
</ul>
The <b>initialization</b> is a simple timed handshake where the host
triggers a response from the device by pulling the line LOW for 480µs,
then waits for the device to assert it LOW to confirm its presence.
<pre>
static ds18b20_retv_t initialization() {
/* Reset */
output() ; /* Wire LOW */
usleep( 480) ;
input() ; /* Wire floating, HIGH by pull-up */
/* Presence */
int retries ;
wait_level( HIGH) ; /* Pull-up LOW -> HIGH, T1 */
wait_level( LOW) ; /* DS18B20 asserts line to LOW, T2, T2 - T1 = 15~60us */
wait_level( HIGH) ; /* DS18B20 releases lines, Pull-up LOW -> HIGH, T3
** T3 - T2 = 60~240us */
usleep( 405) ; /* 480 = 405 + 15 + 60 */
return DS18B20_SUCCESS ;
}
</pre>
The <b>ROM Command</b> is how the host selects the device for communication.
Writing a <b>ROM Skip</b> command addresses all devices connected.
<p>
The <b>Function Command</b> is the request to the device selected by the ROM
Command:
<ul>
<li> Read the device memory
<li> Write the device memory
<li> Start a temperature conversion
</ul>
To write command or data the host does timed pulse for each bit, there
is no acknowledge from the device and no error detection.
<pre>
static void write( unsigned char uc) {
/* Transmit byte, least significant bit first */
for( unsigned char curbit = 1 ; curbit ; curbit <<= 1) {
/* Transmit a bit takes 60us + 1us between transmit */
/* Write 1: <15us LOW */
/* Write 0: 60us LOW */
unsigned t = uc & curbit ? 13 : 60 ;
output() ; /* Wire LOW */
usleep( t) ;
input() ; /* Wire floating, HIGH by pull-up */
usleep( 61 - t) ;
}
}
</pre>
When the host expects to read some data, it can triggers a 1 bit
transmission from the device by first pulling the line LOW for 1µs then
reading the state asserted by the device.
<pre>
static iolvl_t poll( void) {
output() ; /* Wire LOW */
usleep( 1) ;
input() ; /* Wire floating, HIGH by pull-up */
usleep( 5) ;
iolvl_t bit = bread() ;
usleep( 55) ;
return bit ;
}
</pre>
Integrity of the data transmitted by the device is guaranteed by 8 bit
Cyclic Redundancy Check (CRC).
<pre>
static unsigned char read( unsigned char *p, int size) {
unsigned char crc = 0 ;
while( size--) {
/* Receive byte, least significant bit first */
unsigned char uc = 0 ;
for( unsigned char curbit = 1 ; curbit ; curbit <<= 1) {
/* read bit */
int v = poll() ;
if( v)
uc |= curbit ;
/* update CRC */
v ^= crc ;
crc >>= 1 ;
if( v & 1)
crc ^= 0x119 >> 1 ; /* reverse POLY = x^8 + x^5 + x^4 + 1 */
}
/* store byte */
*p++ = uc ;
}
return crc ;
}
</pre>
Base on this, complex transaction sequences can be coded.
<p>
The transaction to read the eight byte scratchpad (device memory) plus
CRC:
<pre>
static ds18b20_retv_t read_scratchpad( unsigned char scratchpad[]) {
ds18b20_retv_t ret = initialization() ;
if( ret != DS18B20_SUCCESS)
return ret ;
write( 0xCC) ; /* Skip ROM */
write( 0xBE) ; /* Read Scratchpad */
return read( scratchpad, 9) ? DS18B20_FAIL_CRC : DS18B20_SUCCESS ;
}
</pre>
<h2>Temperature conversion and encoding</h2>
The DS18B20 can convert the temperature measured into a 12 bit signed
digit, 8 bit integer part and 4 bit fractional part. As the time of
conversion depends of the precision of the conversion, it is possible to
select the resolution from 9 to 12 significant bits. Conversion time
range from less than 93.75ms (9 bits) to maximum 750ms (12 bits).
<p>
The host requests the conversion, waits for the conversion to end, then
fetch the device memory to read the measurement.
<P>
The host can <code>poll()</code> the device to check if the conversion is
finished.
<h2>DS18B20 API</h2>
I create the header file <b>ds18b20.h</b> with the following interface.
<pre>
/* ds18b20.h -- 1-Wire temperature sensor */
typedef enum {
DS18B20_SUCCESS,
DS18B20_FAIL_TOUT,
DS18B20_FAIL_CRC
} ds18b20_retv_t ;
void ds18b20_init( void) ;
ds18b20_retv_t ds18b20_resolution( unsigned res) ; /* 9..12 bits */
ds18b20_retv_t ds18b20_convert( void) ;
ds18b20_retv_t ds18b20_fetch( short *deciCtemp) ;/* -550~1250 = -55.0~125.0 C */
ds18b20_retv_t ds18b20_read( short *deciCtemp) ; /* -550~1250 = -55.0~125.0 C */
</pre>
Usage:
<ul>
<li> Initialization: <code>ds18b20_init()</code>, once at startup.
<li> Use <code>ds18b20_resolution()</code> to select the resolution. It can be
done before starting a conversion. The value will be kept until the ds18b20
is powered down.
<li> To measure the temperature, use <code>ds18b20_read()</code>, which will
start a conversion, wait until it finishes, fetch the value from the device
memory and deci℃ (1 deci℃ = 0.1 ℃).
<li> Alternatively, to avoid the blocking <code>ds18b20_read()</code>, call
<code>ds18b20_convert()</code> followed by <code>ds18b20_fetch()</code> once
enough time has elapsed to complete the conversion.
</ul>
Below is an application to print the temperature every second.
<pre>
/* ds18b20main.c -- sample temperature using 1-Wire temperature sensor */
#include &lt;stdio.h>
#include "system.h" /* uptime */
#include "ds18b20.h" /* ds18b20_() */
int main( void) {
unsigned last = 0 ;
ds18b20_init() ;
ds18b20_resolution( 12) ; /* Set highest resolution: 12 bits */
ds18b20_convert() ; /* start temperature conversion */
for( ;;)
if( last == uptime)
yield() ;
else {
short val ;
last = uptime ;
switch( ds18b20_fetch( &val)) {
case DS18B20_SUCCESS:
printf( "%i.%i\n", val / 10, val % 10) ;
break ;
case DS18B20_FAIL_TOUT:
puts( "Timeout") ;
break ;
case DS18B20_FAIL_CRC:
puts( "CRC Error") ;
}
ds18b20_convert() ; /* start temperature conversion */
}
}
</pre>
<h2>DS18B20 API implementation</h2>
I create <b>ds18b20.c</b>, starting with the GPIO mapping and initialization.
<pre>
/* ds18b20.c -- 1-Wire digital thermometer */
#include "ds18b20.h" /* implements DS18B20 API */
#include "system.h" /* gpioa_(), usleep() */
#define DIO 13
#define input() gpioa_input( DIO)
#define output() gpioa_output( DIO)
#define bread() gpioa_read( DIO)
#define MAX_RETRIES 999
#define wait_level( lvl) \
retries = MAX_RETRIES ; \
while( bread() != lvl) \
if( retries-- == 0) \
return DS18B20_FAIL_TOUT
void ds18b20_init( void) {
input() ; /* Wire floating, HIGH by pull-up */
}
</pre>
I add the local functions that are the building block for the
transactions (<code>initialization()</code>, <code>write()</code>,
<code>poll()</code> and <code>read()</code>) and
the <code>read_scratchpad()</code> transaction I explained before.
<p>
Start conversion transaction:
<pre>
ds18b20_retv_t ds18b20_convert( void) {
ds18b20_retv_t ret ;
ret = initialization() ;
if( ret != DS18B20_SUCCESS)
return ret ;
write( 0xCC) ; /* Skip ROM */
write( 0x44) ; /* Convert T */
return DS18B20_SUCCESS ;
}
</pre>
Fetch temperature, to be called after conversion is done.
<pre>
ds18b20_retv_t ds18b20_fetch( short *deciCtemp) { /* -550~1250 = -55.0~125.0 C $
ds18b20_retv_t ret ;
unsigned char vals[ 9] ; /* scratchpad */
ret = read_scratchpad( vals) ;
if( ret != DS18B20_SUCCESS)
return ret ;
*deciCtemp = *((short *) vals) * 10 / 16 ;
return DS18B20_SUCCESS ;
}
</pre>
Blocking temperature read, which polls the device for end of conversion.
<pre>
ds18b20_retv_t ds18b20_read( short *deciCtemp) { /* -550~1250 = -55.0~125.0 C */
ds18b20_retv_t ret ;
ret = ds18b20_convert() ;
if( ret != DS18B20_SUCCESS)
return ret ;
do
usleep( 4000) ;
while( poll() == LOW) ; /* up to 93.75ms for 9 bits, 750ms for 12 bits */
return ds18b20_fetch( deciCtemp) ;
}
</pre>
Set resolution.
<pre>
ds18b20_retv_t ds18b20_resolution( unsigned res) { /* 9..12 bits */
ds18b20_retv_t ret ;
unsigned char vals[ 9] ; /* scratchpad */
unsigned char curres ;
/* read scratchpad */
ret = read_scratchpad( vals) ;
if( ret != DS18B20_SUCCESS)
return ret ;
/* update resolution if current value is different than requested */
res = (res - 9) & 3 ;
curres = vals[ 4] >> 5 ;
if( curres != res) {
vals[ 4] = (vals[ 4] & 0x1F) | (res << 5) ;
ret = initialization() ;
if( ret != DS18B20_SUCCESS)
return ret ;
write( 0xCC) ; /* Skip ROM */
write( 0x4E) ; /* Write Scratchpad */
write( vals[ 2]) ;
write( vals[ 3]) ;
write( vals[ 4]) ;
}
return DS18B20_SUCCESS ;
}
</pre>
There is no error check when writing to the device, so it would make
sense to read back the device memory after the set to make sure there
was no error when writing in the first place.
<h2>Build and test</h2>
I add the new composition to Makefile.
<pre>SRCS = startup.txeie.c gpioa.c ds18b20main.c ds18b20.c</pre>
Build complete successfully.
<pre>
$ make
f030f4.elf from startup.txeie.o gpioa.o ds18b20main.o ds18b20.o
text data bss dec hex filename
2530 0 16 2546 9f2 f030f4.elf
f030f4.hex
f030f4.bin
</pre>
Flashing the board and starting execution, I can see a new output every
second.
<p>
<img alt="DS18B20 output" src="img/33_output.png">
<h2>Checkpoint</h2>
<a href="34_adcvnt.html">Next</a>, I will read the internal Voltage and
Temperature sensors using Analog to Digital Conversion (<b>ADC</b>).
<hr>© 2020-2024 Renaud Fivet
</body>
</html>