-// DS18B20 Temperature Sensors
-
-#include "util.h"
-#include "ds18b20.h"
-
-#include <libopencm3/cm3/cortex.h>
-#include <libopencm3/stm32/dma.h>
-#include <libopencm3/stm32/gpio.h>
-#include <libopencm3/stm32/rcc.h>
-#include <libopencm3/stm32/timer.h>
-#include <string.h>
-
-static volatile u32 ds_dma_buffer;
-
-#define DS_TIMER TIM3
-#define DS_GPIO GPIOA
-#define DS_PIN GPIO7
-#define DS_DMA DMA1
-#define DS_DMA_CH 6
-
-#define DS_DEBUG
-#undef DS_DEBUG2
-
-#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
-
-// Current temperature
-int ds_current_temp = DS_TEMP_UNKNOWN;
-
-// Auxiliary functions which are missing from libopencm3
-static inline bool timer_is_counter_enabled(u32 timer)
-{
- return TIM_CR1(timer) & TIM_CR1_CEN;
-}
-
-static inline void timer_enable_dma_cc1(u32 timer)
-{
- TIM_DIER(timer) |= TIM_DIER_CC1DE;
-}
-
-static inline void timer_disable_dma_cc1(u32 timer)
-{
- TIM_DIER(timer) &= ~TIM_DIER_CC1DE;
-}
-
-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 & GPIO7),
- dma_get_number_of_data(DS_DMA, DS_DMA_CH));
-
- // Did the device respond?
- if (ds_dma_buffer & GPIO7) {
- 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 & GPIO7;
- 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, 71); // 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, GPIO7);
-
- 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;
- }
-}