Workshop o mikrokontrolérech na SKSP 2024.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
9.5 KiB

/*
* 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);
// XXX: We do not know why this is needed...
static bool once;
if (!once) {
for (int i=0; i<10000; i++) __asm__ volatile ("nop");
once = 1;
}
// Pull line down and start timer
timer_generate_event(DS_TIMER, TIM_EGR_UG);
timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_INACTIVE);
timer_enable_counter(DS_TIMER);
// 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
timer_generate_event(DS_TIMER, TIM_EGR_UG);
timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_INACTIVE);
timer_enable_counter(DS_TIMER);
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);
timer_generate_event(DS_TIMER, TIM_EGR_UG);
timer_set_oc_mode(DS_TIMER, TIM_OC2, TIM_OCM_INACTIVE);
timer_enable_counter(DS_TIMER);
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;
}
}