]> mj.ucw.cz Git - home-hw.git/commitdiff
DS18B20 librified
authorMartin Mares <mj@ucw.cz>
Sun, 14 Jul 2019 18:49:31 +0000 (20:49 +0200)
committerMartin Mares <mj@ucw.cz>
Sun, 14 Jul 2019 18:49:31 +0000 (20:49 +0200)
lib/ds18b20.c [new file with mode: 0644]
lib/ds18b20.h [new file with mode: 0644]

diff --git a/lib/ds18b20.c b/lib/ds18b20.c
new file mode 100644 (file)
index 0000000..8f6b550
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+ *     Interface to DS18B20 Temperature Sensors
+ *
+ *     (c) 2019 Martin Mareš <mj@ucw.cz>
+ */
+
+#include "util.h"
+#include "ds18b20.h"
+#include "ext-timer.h"
+
+#include <libopencm3/cm3/cortex.h>
+#include <libopencm3/stm32/dma.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/rcc.h>
+#include <string.h>
+
+/*** Configuration ***/
+
+// You should set the following parameters in config.h
+
+// #define DS_TIMER TIM3
+// #define DS_GPIO GPIOA
+// #define DS_PIN GPIO7
+// #define DS_DMA DMA1
+// #define DS_DMA_CH 6
+
+// #undef DS_DEBUG
+// #undef DS_DEBUG2
+
+// Maximum number of supported sensors
+// #define DS_NUM_SENSORS 8
+
+#ifdef DS_DEBUG
+#define DEBUG debug_printf
+#else
+#define DEBUG(xxx, ...) do { } while (0)
+#endif
+
+#ifdef DS_DEBUG2
+#define DEBUG2 debug_printf
+#else
+#define DEBUG2(xxx, ...) do { } while (0)
+#endif
+
+static volatile u32 ds_dma_buffer;
+
+static bool ds_reset(void)
+{
+       DEBUG2("DS18B20: Reset\n");
+       timer_disable_counter(DS_TIMER);
+       timer_one_shot_mode(DS_TIMER);
+
+       // DMA for reading pin state
+       ds_dma_buffer = 0xdeadbeef;
+       dma_set_memory_address(DS_DMA, DS_DMA_CH, (u32) &ds_dma_buffer);
+       dma_set_peripheral_address(DS_DMA, DS_DMA_CH, (u32) &GPIO_IDR(DS_GPIO));
+       dma_set_number_of_data(DS_DMA, DS_DMA_CH, 1);
+       dma_enable_channel(DS_DMA, DS_DMA_CH);
+
+       // CC1 is used to drive the DMA (read line state at specified time)
+       timer_disable_oc_output(DS_TIMER, TIM_OC1);
+       timer_set_oc_mode(DS_TIMER, TIM_OC1, TIM_OCM_FROZEN);
+       timer_set_oc_value(DS_TIMER, TIM_OC1, 560);
+       timer_set_dma_on_compare_event(DS_TIMER);
+       timer_enable_dma_cc1(DS_TIMER);
+
+       // CC2 is used to generate pulses (return line to idle state at specified time)
+       timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_FORCE_HIGH);
+       timer_enable_oc_output(DS_TIMER, TIM_OC2);
+       timer_set_oc_value(DS_TIMER, TIM_OC2, 480);
+       timer_set_oc_polarity_low(DS_TIMER, TIM_OC2);
+
+       // Set timer period to the length of the whole transaction (1 ms)
+       timer_set_period(DS_TIMER, 999);
+
+       // Pull line down and start timer
+       cm_disable_interrupts();
+       timer_enable_counter(DS_TIMER);
+       timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_INACTIVE);
+       cm_enable_interrupts();
+
+       // Wait until the timer expires
+       while (timer_is_counter_enabled(DS_TIMER))
+               ;
+       // Counter is automatically disabled at the end of cycle
+
+       // Disable DMA
+       timer_disable_dma_cc1(DS_TIMER);
+       dma_disable_channel(DS_DMA, DS_DMA_CH);
+
+       DEBUG2("Init DMA: %08x [%u] (%u remains)\n",
+              ds_dma_buffer,
+              !!(ds_dma_buffer & DS_PIN),
+              dma_get_number_of_data(DS_DMA, DS_DMA_CH));
+
+       // Did the device respond?
+       if (ds_dma_buffer & DS_PIN) {
+               DEBUG("DS18B20: Initialization failed\n");
+               return 0;
+       } else
+               return 1;
+}
+
+static void ds_send_bit(bool bit)
+{
+       timer_set_period(DS_TIMER, 99);                         // Each write slot takes 100 μs
+       timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_FORCE_HIGH);
+       timer_set_oc_value(DS_TIMER, TIM_OC2, (bit ? 3 : 89));  // 1: 3μs pulse, 0: 89μs pulse
+       cm_disable_interrupts();
+       // XXX: On STM32F1, we must configure the OC channel _after_ we enable the counter,
+       // otherwise OC triggers immediately. Reasons?
+       timer_enable_counter(DS_TIMER);
+       timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_INACTIVE);
+       cm_enable_interrupts();
+       while (timer_is_counter_enabled(DS_TIMER))
+               ;
+}
+
+static void ds_send_byte(byte b)
+{
+       DEBUG2("DS write: %02x\n", b);
+       for (uint m = 1; m < 0x100; m <<= 1)
+               ds_send_bit(b & m);
+}
+
+static bool ds_recv_bit(void)
+{
+       timer_set_period(DS_TIMER, 79);                 // Each read slot takes 80μs
+       timer_set_oc_value(DS_TIMER, TIM_OC2, 2);       // Generate 2μs pulse to start read slot
+       timer_set_oc_value(DS_TIMER, TIM_OC1, 8);       // Sample data 8μs after start of slot
+       timer_enable_dma_cc1(DS_TIMER);
+
+       ds_dma_buffer = 0xdeadbeef;
+       dma_set_number_of_data(DS_DMA, DS_DMA_CH, 1);
+       dma_enable_channel(DS_DMA, DS_DMA_CH);
+       timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_FORCE_HIGH);
+       cm_disable_interrupts();
+       timer_enable_counter(DS_TIMER);
+       timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_INACTIVE);
+       cm_enable_interrupts();
+       while (timer_is_counter_enabled(DS_TIMER))
+               ;
+       // DEBUG2("XXX %08x\n", ds_dma_buffer);
+       bool out = ds_dma_buffer & DS_PIN;
+       dma_disable_channel(DS_DMA, DS_DMA_CH);
+
+       timer_disable_dma_cc1(DS_TIMER);
+
+       return out;
+}
+
+static byte ds_recv_byte(void)
+{
+       uint out = 0;
+       for (uint m = 1; m < 0x100; m <<= 1) {
+               if (ds_recv_bit())
+                       out |= m;
+       }
+
+       DEBUG2("DS read: %02x\n", out);
+       return out;
+}
+
+static byte ds_buf[10];
+
+static byte ds_crc_block(uint n)
+{
+       /// XXX: This might be worth optimizing
+       uint crc = 0;
+
+       for (uint i = 0; i < n; i++) {
+               byte b = ds_buf[i];
+               for (uint j = 0; j < 8; j++) {
+                       uint k = (b & 1) ^ (crc >> 7);
+                       crc = (crc << 1) & 0xff;
+                       if (k)
+                               crc ^= 0x31;
+                       b >>= 1;
+               }
+       }
+
+       return crc;
+}
+
+static bool ds_recv_block(uint n)
+{
+       for (uint i = 0; i < n; i++)
+               ds_buf[i] = ds_recv_byte();
+
+       byte crc = ds_crc_block(n);
+       if (crc) {
+               DEBUG("DS18B20: Invalid CRC %02x\n", crc);
+               return 0;
+       }
+       return 1;
+}
+
+struct ds_sensor ds_sensors[DS_NUM_SENSORS];
+
+#if DS_NUM_SENSORS == 1
+
+static void ds_enumerate(void)
+{
+       if (!ds_reset())
+               return;
+
+       ds_send_byte(0x33);     // READ_ROM
+       if (!ds_recv_block(8))
+               return;
+
+       DEBUG("DS18B20: Found sensor ");
+       for (uint i = 0; i < 8; i++) {
+               DEBUG("%02x", ds_buf[i]);
+               ds_sensors[0].address[i] = ds_buf[i];
+       }
+       DEBUG("\n");
+}
+
+#else
+
+static void ds_enumerate(void)
+{
+       /*
+        *  The enumeration algorithm roughly follows the one described in the
+        *  Book of iButton Standards (Maxim Integrated Application Note 937).
+        *
+        *  It simulates depth-first search on the trie of all device IDs.
+        *  In each pass, it walks the trie from the root and recognizes branching nodes.
+        *
+        *  The old_choice variable remembers the deepest left branch taken in the
+        *  previous pass, new_choice is the same for the current pass.
+        */
+
+       DEBUG("DS18B20: Enumerate\n");
+
+       uint num_sensors = 0;
+       byte *addr = ds_buf;
+       byte old_choice = 0;
+
+       for (;;) {
+               if (!ds_reset()) {
+                       DEBUG("DS18B20: Enumeration found no sensor\n");
+                       return;
+               }
+
+               ds_send_byte(0xf0);     // SEARCH_ROM
+               byte new_choice = 0;
+               for (byte i=0; i<64; i++) {
+                       bool have_one = ds_recv_bit();
+                       bool have_zero = ds_recv_bit();
+                       bool old_bit = addr[i/8] & (1U << (i%8));
+                       bool new_bit;
+                       switch (2*have_one + have_zero) {
+                               case 3:
+                                       // This should not happen
+                                       DEBUG("DS18B20: Enumeration failed\n");
+                                       return;
+                               case 1:
+                                       // Only 0
+                                       new_bit = 0;
+                                       break;
+                               case 2:
+                                       // Only 1
+                                       new_bit = 1;
+                                       break;
+                               default:
+                                       // Both
+                                       if (i == old_choice)
+                                               new_bit = 1;
+                                       else if (i > old_choice) {
+                                               new_bit = 0;
+                                               new_choice = i;
+                                       } else {
+                                               new_bit = old_bit;
+                                               if (!new_bit)
+                                                       new_choice = i;
+                                       }
+                       }
+                       if (new_bit)
+                               addr[i/8] |= 1U << (i%8);
+                       else
+                               addr[i/8] &= ~(1U << (i%8));
+                       ds_send_bit(new_bit);
+               }
+
+               if (num_sensors >= DS_NUM_SENSORS) {
+                       DEBUG("DS18B20: Too many sensors\n");
+                       return;
+               }
+
+               DEBUG("DS18B20: Found sensor #%u: ", num_sensors);
+               for (byte i=0; i<8; i++)
+                       DEBUG("%02x", addr[i]);
+               if (ds_crc_block(8)) {
+                       DEBUG(" - invalid CRC!\n");
+               } else if (ds_buf[0] == 0x28) {
+                       DEBUG("\n");
+                       memcpy(ds_sensors[num_sensors].address, ds_buf, 8);
+                       num_sensors++;
+               } else {
+                       DEBUG(" - wrong type\n");
+               }
+
+               old_choice = new_choice;
+               if (!old_choice)
+                       break;
+       }
+}
+
+#endif
+
+void ds_init(void)
+{
+       DEBUG("DS18B20: Init\n");
+
+       for (uint i = 0; i < DS_NUM_SENSORS; i++) {
+               memset(ds_sensors[i].address, 0, 8);
+               ds_sensors[i].current_temp = DS_TEMP_UNKNOWN;
+       }
+
+       dma_set_read_from_peripheral(DS_DMA, DS_DMA_CH);
+       dma_set_priority(DS_DMA, DS_DMA_CH, DMA_CCR_PL_VERY_HIGH);
+       dma_disable_peripheral_increment_mode(DS_DMA, DS_DMA_CH);
+       dma_enable_memory_increment_mode(DS_DMA, DS_DMA_CH);
+       dma_set_peripheral_size(DS_DMA, DS_DMA_CH, DMA_CCR_PSIZE_16BIT);
+       dma_set_memory_size(DS_DMA, DS_DMA_CH, DMA_CCR_MSIZE_16BIT);
+
+       timer_set_prescaler(DS_TIMER, CPU_CLOCK_MHZ - 1);       // 1 tick = 1 μs
+       timer_set_mode(DS_TIMER, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
+       timer_disable_preload(DS_TIMER);
+
+       gpio_set_mode(DS_GPIO, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, DS_PIN);
+
+       ds_enumerate();
+
+       // FIXME: Configure precision?
+}
+
+#if DS_NUM_SENSORS == 1
+#define ds_current_id 0
+#else
+       static byte ds_current_id;
+#endif
+
+static bool ds_activate(void)
+{
+       if (!ds_reset()) {
+               DEBUG("DS18B20: Reset failed\n");
+               return false;
+       }
+#if DS_NUM_SENSORS == 1
+       ds_send_byte(0xcc);     // SKIP_ROM
+#else
+       ds_send_byte(0x55);     // MATCH_ROM
+       for (uint i = 0; i < 8; i++)
+               ds_send_byte(ds_sensors[ds_current_id].address[i]);
+#endif
+       return true;
+}
+
+void ds_step(void)
+{
+       static byte ds_running;
+       static byte ds_timeout;
+
+       if (!ds_running) {
+               // Start measurement
+#if DS_NUM_SENSORS != 1
+               uint maxn = DS_NUM_SENSORS;
+               do {
+                       if (!maxn--)
+                               return;
+                       ds_current_id++;
+                       if (ds_current_id >= DS_NUM_SENSORS) {
+                               ds_current_id = 0;
+                       }
+               } while (!ds_sensors[ds_current_id].address[0]);
+#endif
+               if (!ds_activate()) {
+                       ds_sensors[ds_current_id].current_temp = DS_TEMP_UNKNOWN;
+                       return;
+               }
+               ds_send_byte(0x44);     // CONVERT_T
+               ds_running = 1;
+               ds_timeout = 255;
+       } else {
+               // Still running?
+               if (!ds_recv_bit()) {
+                       if (!ds_timeout--) {
+                               DEBUG("DS18B20 #%u: Timeout\n", ds_current_id);
+                               ds_sensors[ds_current_id].current_temp = DS_TEMP_UNKNOWN;
+                               ds_running = 0;
+                       }
+                       return;
+               }
+               ds_running = 0;
+
+               // Read scratch pad
+               if (!ds_activate())
+                       return;
+               ds_send_byte(0xbe);     // READ_SCRATCHPAD
+               if (!ds_recv_block(9)) {
+                       ds_sensors[ds_current_id].current_temp = DS_TEMP_UNKNOWN;
+                       return;
+               }
+               int t = (int16_t) (ds_buf[0] | (ds_buf[1] << 8));
+               t = t * 1000 / 16;
+
+               DEBUG("DS18B20 #%u: %d.%03d degC\n", ds_current_id, t / 1000, t % 1000);
+               ds_sensors[ds_current_id].current_temp = t;
+       }
+}
diff --git a/lib/ds18b20.h b/lib/ds18b20.h
new file mode 100644 (file)
index 0000000..3be92f3
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ *     Interface to DS18B20 Temperature Sensors
+ *
+ *     (c) 2019 Martin Mareš <mj@ucw.cz>
+ */
+
+#ifndef _DS18B20_H
+#define _DS18B20_H
+
+struct ds_sensor {
+       byte address[8];        // All zeroes if sensor does not exist.
+                               // Otherwise, address[0] is guaranteed to be non-zero.
+       int current_temp;       // Temperature in m°C or DS_TEMP_UNKNOWN
+};
+
+extern struct ds_sensor ds_sensors[DS_NUM_SENSORS];
+
+#define DS_TEMP_UNKNOWN 0x7fffffff
+
+void ds_init(void);
+void ds_step(void);
+
+#endif