mirror of
https://github.com/rfivet/stm32bringup.git
synced 2024-12-18 23:06:28 -05:00
402 lines
11 KiB
HTML
402 lines
11 KiB
HTML
|
<!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 won’t 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 <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>
|