354 lines
15 KiB
C
354 lines
15 KiB
C
#include "ds18b20.h"
|
|
#include "ds18b20_config.h"
|
|
#include <string.h>
|
|
|
|
#define OW_SEARCH_ROM 0xF0U
|
|
#define OW_MATCH_ROM 0x55U
|
|
#define OW_SKIP_ROM 0xCCU
|
|
#define DS_CONVERT 0x44U
|
|
#define DS_READ_SCRATCHPAD 0xBEU
|
|
#define DS_WRITE_SCRATCHPAD 0x4EU
|
|
#define DS_COPY_SCRATCHPAD 0x48U
|
|
#define DS_RECALL_E2 0xB8U
|
|
|
|
static int valid_instance(const ds18b20_t *d)
|
|
{
|
|
return d && d->ops && d->ops->drive_low && d->ops->release &&
|
|
d->ops->read && d->ops->delay_us && d->roms && d->rom_capacity;
|
|
}
|
|
|
|
static void critical(ds18b20_t *d, int enter)
|
|
{
|
|
if (enter && d->ops->critical_enter) d->ops->critical_enter(d->platform_context);
|
|
if (!enter && d->ops->critical_exit) d->ops->critical_exit(d->platform_context);
|
|
}
|
|
|
|
static void write_bit(ds18b20_t *d, uint8_t bit)
|
|
{
|
|
critical(d, 1);
|
|
d->ops->drive_low(d->platform_context);
|
|
d->ops->delay_us(d->platform_context, bit ? 6U : 60U);
|
|
d->ops->release(d->platform_context);
|
|
d->ops->delay_us(d->platform_context, bit ? 64U : 10U);
|
|
critical(d, 0);
|
|
if (d->search_active)
|
|
d->ops->delay_us(d->platform_context, DS18B20_SLOT_RECOVERY_EXTRA_US);
|
|
}
|
|
|
|
static uint8_t read_bit(ds18b20_t *d)
|
|
{
|
|
uint8_t bit;
|
|
critical(d, 1);
|
|
d->ops->drive_low(d->platform_context);
|
|
d->ops->delay_us(d->platform_context, 3U);
|
|
d->ops->release(d->platform_context);
|
|
d->ops->delay_us(d->platform_context, 10U);
|
|
bit = d->ops->read(d->platform_context) ? 1U : 0U;
|
|
d->ops->delay_us(d->platform_context, 57U);
|
|
critical(d, 0);
|
|
if (d->search_active)
|
|
d->ops->delay_us(d->platform_context, DS18B20_SLOT_RECOVERY_EXTRA_US);
|
|
return bit;
|
|
}
|
|
|
|
static void write_byte(ds18b20_t *d, uint8_t value)
|
|
{
|
|
uint8_t i;
|
|
for (i = 0; i < 8U; ++i) { write_bit(d, value & 1U); value >>= 1U; }
|
|
}
|
|
|
|
static uint8_t read_byte(ds18b20_t *d)
|
|
{
|
|
uint8_t i, value = 0U;
|
|
for (i = 0; i < 8U; ++i) value |= (uint8_t)(read_bit(d) << i);
|
|
return value;
|
|
}
|
|
|
|
static ds18b20_status_t reset(ds18b20_t *d)
|
|
{
|
|
uint8_t level;
|
|
d->ops->drive_low(d->platform_context);
|
|
d->ops->delay_us(d->platform_context, 480U);
|
|
/* Protect release-to-presence sampling from interrupt latency. */
|
|
critical(d, 1);
|
|
d->ops->release(d->platform_context);
|
|
d->ops->delay_us(d->platform_context, 70U);
|
|
level = d->ops->read(d->platform_context);
|
|
critical(d, 0);
|
|
d->ops->delay_us(d->platform_context, 410U);
|
|
return level ? DS18B20_E_NO_DEVICE : DS18B20_OK;
|
|
}
|
|
|
|
static void match(ds18b20_t *d, const uint8_t *rom)
|
|
{
|
|
uint8_t i; write_byte(d, OW_MATCH_ROM);
|
|
for (i = 0U; i < 8U; ++i) write_byte(d, rom[i]);
|
|
}
|
|
|
|
uint8_t ds18b20_crc8(const void *data, size_t length)
|
|
{
|
|
const uint8_t *p = (const uint8_t *)data; uint8_t crc = 0U;
|
|
while (length--) { uint8_t in = *p++, i; for (i = 0U; i < 8U; ++i) {
|
|
uint8_t mix = (uint8_t)((crc ^ in) & 1U); crc >>= 1U;
|
|
if (mix) crc ^= 0x8CU; in >>= 1U; } }
|
|
return crc;
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_validate_rom(const uint8_t rom[8])
|
|
{
|
|
if (!rom) return DS18B20_E_ARGUMENT;
|
|
if (rom[0] != DS18B20_FAMILY_CODE) return DS18B20_E_ROM;
|
|
return ds18b20_crc8(rom, 7U) == rom[7] ? DS18B20_OK : DS18B20_E_CRC;
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_init(ds18b20_t *d, const ds18b20_onewire_ops_t *ops,
|
|
void *ctx, uint8_t (*roms)[8], size_t capacity)
|
|
{
|
|
if (!d || !ops || !roms || !capacity || !ops->drive_low || !ops->release ||
|
|
!ops->read || !ops->delay_us) return DS18B20_E_ARGUMENT;
|
|
memset(d, 0, sizeof(*d)); d->ops = ops; d->platform_context = ctx;
|
|
d->roms = roms; d->rom_capacity = capacity; ops->release(ctx);
|
|
return DS18B20_OK;
|
|
}
|
|
|
|
static ds18b20_status_t search_next(ds18b20_t *d)
|
|
{
|
|
uint8_t bit_no=1U,last_zero=0U,byte_no=0U,mask=1U;
|
|
ds18b20_status_t reset_status;
|
|
if (d->last_device) return DS18B20_OK;
|
|
d->search_diagnostics.last_error_bit = 0U;
|
|
d->search_diagnostics.last_error_pair = 0xFFU;
|
|
reset_status=reset(d);
|
|
if (reset_status != DS18B20_OK) return reset_status;
|
|
write_byte(d, OW_SEARCH_ROM);
|
|
while (byte_no < 8U) {
|
|
uint8_t id=read_bit(d), cmp=read_bit(d), dir;
|
|
d->search_diagnostics.last_error_bit = bit_no;
|
|
d->search_diagnostics.last_error_pair = (uint8_t)((id << 1U) | cmp);
|
|
if (id && cmp) break;
|
|
if (id != cmp) dir=id; else { dir=(bit_no<d->last_discrepancy) ?
|
|
((d->search_rom[byte_no]&mask)!=0U) : (bit_no==d->last_discrepancy);
|
|
if (!dir) { last_zero=bit_no; if (last_zero<9U) d->last_family_discrepancy=last_zero; } }
|
|
if (dir) d->search_rom[byte_no]|=mask; else d->search_rom[byte_no]&=(uint8_t)~mask;
|
|
write_bit(d,dir); ++bit_no; mask<<=1U; if (!mask) { ++byte_no; mask=1U; }
|
|
}
|
|
if (bit_no < 65U || !d->search_rom[0]) {
|
|
d->last_discrepancy=0; d->last_device=0; return DS18B20_E_IO;
|
|
}
|
|
d->last_discrepancy=last_zero;
|
|
if (!last_zero) d->last_device=1U;
|
|
return DS18B20_E_BUSY;
|
|
}
|
|
|
|
/* Reset only traversal state: the union of validated ROMs survives passes. */
|
|
static void search_tree_reset(ds18b20_t *d)
|
|
{
|
|
d->last_discrepancy = 0U;
|
|
d->last_family_discrepancy = 0U;
|
|
d->last_device = 0U;
|
|
d->search_retries = 0U;
|
|
memset(d->search_rom, 0, sizeof(d->search_rom));
|
|
}
|
|
|
|
static ds18b20_status_t search_stop(ds18b20_t *d, ds18b20_status_t result)
|
|
{
|
|
d->search_active = 0U;
|
|
d->search_result = result;
|
|
return result;
|
|
}
|
|
|
|
static ds18b20_status_t search_pass_finish(ds18b20_t *d, uint8_t complete)
|
|
{
|
|
ds18b20_search_diagnostics_t *diag = &d->search_diagnostics;
|
|
++diag->passes_finished;
|
|
if (complete) ++diag->complete_passes;
|
|
if (diag->passes_finished < DS18B20_SEARCH_PASSES) {
|
|
search_tree_reset(d);
|
|
return DS18B20_E_BUSY;
|
|
}
|
|
/* A completed traversal can recover earlier faults. Without one, retain
|
|
* the partial catalog but report the failure instead of claiming success. */
|
|
if (diag->complete_passes != 0U)
|
|
return search_stop(d, d->rom_count ? DS18B20_OK : DS18B20_E_NO_DEVICE);
|
|
return search_stop(d, diag->last_error);
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_search_begin(ds18b20_t *d)
|
|
{
|
|
if (!valid_instance(d)) return DS18B20_E_ARGUMENT;
|
|
if (d->strong_pullup_active) return DS18B20_E_BUSY;
|
|
d->rom_count = 0U;
|
|
search_tree_reset(d);
|
|
memset(&d->search_diagnostics, 0, sizeof(d->search_diagnostics));
|
|
d->search_diagnostics.last_error_pair = 0xFFU;
|
|
d->search_active = 1U;
|
|
d->search_result = DS18B20_E_BUSY;
|
|
return DS18B20_E_BUSY;
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_search_step(ds18b20_t *d)
|
|
{
|
|
uint8_t previous_rom[8], previous_discrepancy, previous_family;
|
|
ds18b20_search_diagnostics_t previous_diag;
|
|
ds18b20_status_t status;
|
|
if (!valid_instance(d)) return DS18B20_E_ARGUMENT;
|
|
if (!d->search_active) return d->search_result;
|
|
if (d->search_diagnostics.attempts >= DS18B20_SEARCH_MAX_ATTEMPTS) {
|
|
d->search_diagnostics.last_error = DS18B20_E_TIMEOUT;
|
|
d->search_diagnostics.last_error_bit = 0U;
|
|
d->search_diagnostics.last_error_pair = 0xFFU;
|
|
return search_stop(d, DS18B20_E_TIMEOUT);
|
|
}
|
|
/* Snapshot the preceding validated tree path BEFORE touching the wire.
|
|
* A corrupt ROM must never become the path for the following attempt. */
|
|
memcpy(previous_rom, d->search_rom, 8U);
|
|
previous_discrepancy = d->last_discrepancy;
|
|
previous_family = d->last_family_discrepancy;
|
|
previous_diag = d->search_diagnostics;
|
|
++d->search_diagnostics.attempts;
|
|
status = search_next(d);
|
|
if (status == DS18B20_E_BUSY) {
|
|
/* CRC first: a valid non-DS18B20 family is skipped without corrupting
|
|
* the traversal; a bad family caused by noise still gets retried. */
|
|
status = ds18b20_crc8(d->search_rom, 7U) == d->search_rom[7] ?
|
|
DS18B20_OK : DS18B20_E_CRC;
|
|
if (status == DS18B20_OK && d->search_rom[0] == DS18B20_FAMILY_CODE)
|
|
status = ds18b20_add_known_rom(d, d->search_rom);
|
|
}
|
|
if (status == DS18B20_OK) {
|
|
/* Keep the last FAILURE location, even when a later retry succeeds. */
|
|
d->search_diagnostics.last_error_bit = previous_diag.last_error_bit;
|
|
d->search_diagnostics.last_error_pair = previous_diag.last_error_pair;
|
|
d->search_retries = 0U;
|
|
return d->last_device ? search_pass_finish(d, 1U) : DS18B20_E_BUSY;
|
|
}
|
|
d->search_diagnostics.last_error = status;
|
|
if (status == DS18B20_E_CAPACITY) return search_stop(d, status);
|
|
if (status == DS18B20_E_CRC) ++d->search_diagnostics.crc_errors;
|
|
else ++d->search_diagnostics.io_errors;
|
|
memcpy(d->search_rom, previous_rom, 8U);
|
|
d->last_discrepancy = previous_discrepancy;
|
|
d->last_family_discrepancy = previous_family;
|
|
d->last_device = 0U;
|
|
if (d->search_retries < DS18B20_SEARCH_RETRIES) {
|
|
++d->search_retries;
|
|
++d->search_diagnostics.retries;
|
|
return DS18B20_E_BUSY;
|
|
}
|
|
return search_pass_finish(d, 0U);
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_search(ds18b20_t *d)
|
|
{
|
|
ds18b20_status_t status;
|
|
if (!valid_instance(d)) return DS18B20_E_ARGUMENT;
|
|
if (d->strong_pullup_active) return DS18B20_E_BUSY;
|
|
status = ds18b20_search_begin(d);
|
|
while (status == DS18B20_E_BUSY) status = ds18b20_search_step(d);
|
|
return status;
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_add_known_rom(ds18b20_t *d, const uint8_t rom[8])
|
|
{
|
|
size_t i;
|
|
ds18b20_status_t status;
|
|
if (!valid_instance(d)) return DS18B20_E_ARGUMENT;
|
|
status = ds18b20_validate_rom(rom);
|
|
if (status != DS18B20_OK) return status;
|
|
for (i = 0U; i < d->rom_count; ++i)
|
|
if (memcmp(d->roms[i], rom, 8U) == 0) return DS18B20_OK;
|
|
if (d->rom_count >= d->rom_capacity) return DS18B20_E_CAPACITY;
|
|
memcpy(d->roms[d->rom_count++], rom, 8U);
|
|
return DS18B20_OK;
|
|
}
|
|
|
|
size_t ds18b20_count(const ds18b20_t *d) { return valid_instance(d) ? d->rom_count : 0U; }
|
|
const uint8_t *ds18b20_rom(const ds18b20_t *d,size_t i) { return valid_instance(d)&&i<d->rom_count?d->roms[i]:NULL; }
|
|
|
|
ds18b20_status_t ds18b20_start_all(ds18b20_t *d)
|
|
{ if(!valid_instance(d))return DS18B20_E_ARGUMENT; if(reset(d))return DS18B20_E_NO_DEVICE; write_byte(d,OW_SKIP_ROM);write_byte(d,DS_CONVERT);return DS18B20_OK; }
|
|
ds18b20_status_t ds18b20_start(ds18b20_t *d,const uint8_t *rom)
|
|
{ ds18b20_status_t s;if(!valid_instance(d)||!rom)return DS18B20_E_ARGUMENT;if(ds18b20_validate_rom(rom))return DS18B20_E_ROM;s=reset(d);if(s)return s;match(d,rom);write_byte(d,DS_CONVERT);return DS18B20_OK; }
|
|
ds18b20_status_t ds18b20_conversion_ready(ds18b20_t *d)
|
|
{ if(!valid_instance(d))return DS18B20_E_ARGUMENT;return read_bit(d)?DS18B20_OK:DS18B20_E_BUSY; }
|
|
ds18b20_status_t ds18b20_wait(ds18b20_t *d,uint32_t timeout)
|
|
{ uint32_t start;if(!valid_instance(d)||!d->ops->tick_ms)return DS18B20_E_ARGUMENT;start=d->ops->tick_ms(d->platform_context);while(!read_bit(d))if((uint32_t)(d->ops->tick_ms(d->platform_context)-start)>timeout)return DS18B20_E_TIMEOUT;return DS18B20_OK; }
|
|
ds18b20_status_t ds18b20_read_scratchpad(ds18b20_t *d,const uint8_t *rom,uint8_t *sp)
|
|
{ uint8_t i;ds18b20_status_t s;if(!valid_instance(d)||!rom||!sp)return DS18B20_E_ARGUMENT;if(ds18b20_validate_rom(rom))return DS18B20_E_ROM;s=reset(d);if(s)return s;match(d,rom);write_byte(d,DS_READ_SCRATCHPAD);for(i=0;i<9U;++i)sp[i]=read_byte(d);return ds18b20_crc8(sp,8U)==sp[8]?DS18B20_OK:DS18B20_E_CRC; }
|
|
ds18b20_status_t ds18b20_decode_temperature(const uint8_t *sp,float *out)
|
|
{ int16_t raw;uint8_t cfg;if(!sp||!out)return DS18B20_E_ARGUMENT;if(ds18b20_crc8(sp,8U)!=sp[8])return DS18B20_E_CRC;cfg=sp[4]&0x60U;raw=(int16_t)((uint16_t)sp[0]|((uint16_t)sp[1]<<8));if(cfg==0)raw&=(int16_t)~7;else if(cfg==0x20)raw&=(int16_t)~3;else if(cfg==0x40)raw&=(int16_t)~1;else if(cfg!=0x60)return DS18B20_E_IO;*out=(float)raw/16.0f;return DS18B20_OK; }
|
|
|
|
static ds18b20_status_t write_config(ds18b20_t *d,const uint8_t *rom,uint8_t th,uint8_t tl,uint8_t cfg)
|
|
{
|
|
uint8_t chunk;
|
|
ds18b20_status_t s=ds18b20_write_scratchpad(d,rom,th,tl,cfg);
|
|
if(s)return s;s=ds18b20_copy_scratchpad_start(d,rom,0U);if(s)return s;
|
|
/* TIM1 counter is 16-bit: at 72 ticks/us a single 10 ms wait can never
|
|
* satisfy the port comparison. Short chunks preserve wrap-safe timing. */
|
|
for(chunk=0U;chunk<100U;++chunk)d->ops->delay_us(d->platform_context,100U);
|
|
return ds18b20_copy_scratchpad_finish(d,0U);
|
|
}
|
|
ds18b20_status_t ds18b20_set_resolution(ds18b20_t *d,const uint8_t *rom,uint8_t bits)
|
|
{ uint8_t sp[9],cfg;ds18b20_status_t s;if(bits<9U||bits>12U)return DS18B20_E_ARGUMENT;s=ds18b20_read_scratchpad(d,rom,sp);if(s)return s;cfg=(uint8_t)(0x1FU|((bits-9U)<<5));return write_config(d,rom,sp[2],sp[3],cfg); }
|
|
ds18b20_status_t ds18b20_write_user_bytes(ds18b20_t *d,const uint8_t *rom,int16_t b12,int16_t b34,uint8_t mask)
|
|
{ uint8_t sp[9];ds18b20_status_t s=ds18b20_read_scratchpad(d,rom,sp);(void)b34;if(s)return s;if(mask&1U)sp[2]=(uint8_t)b12;if(mask&2U)sp[3]=(uint8_t)(b12>>8);/* DS18B20 физически позволяет записать только TH/TL/config; байты 6/7 read-only. */return write_config(d,rom,sp[2],sp[3],sp[4]); }
|
|
|
|
ds18b20_status_t ds18b20_write_scratchpad(ds18b20_t *d,const uint8_t *rom,
|
|
uint8_t th,uint8_t tl,uint8_t cfg)
|
|
{
|
|
ds18b20_status_t s;
|
|
if(!valid_instance(d)||!rom)return DS18B20_E_ARGUMENT;
|
|
s=ds18b20_validate_rom(rom);if(s)return s;
|
|
s=reset(d);if(s)return s;match(d,rom);write_byte(d,DS_WRITE_SCRATCHPAD);
|
|
write_byte(d,th);write_byte(d,tl);write_byte(d,cfg);return DS18B20_OK;
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_copy_scratchpad_start(ds18b20_t *d,const uint8_t *rom,
|
|
uint8_t parasite)
|
|
{
|
|
ds18b20_status_t s;
|
|
if(!valid_instance(d)||!rom)return DS18B20_E_ARGUMENT;
|
|
if(parasite && !d->ops->strong_pullup)return DS18B20_E_POWER;
|
|
s=ds18b20_validate_rom(rom);if(s)return s;
|
|
s=reset(d);if(s)return s;match(d,rom);write_byte(d,DS_COPY_SCRATCHPAD);
|
|
/* The pull-up must be asserted immediately after the command slot. */
|
|
if(parasite){d->ops->strong_pullup(d->platform_context,1U);
|
|
d->strong_pullup_active=1U;}
|
|
return DS18B20_OK;
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_copy_scratchpad_finish(ds18b20_t *d,uint8_t parasite)
|
|
{
|
|
if(!valid_instance(d))return DS18B20_E_ARGUMENT;
|
|
if(parasite){if(!d->ops->strong_pullup)return DS18B20_E_POWER;
|
|
if(d->strong_pullup_active){d->ops->strong_pullup(d->platform_context,0U);
|
|
d->strong_pullup_active=0U;}}
|
|
d->ops->release(d->platform_context);return DS18B20_OK;
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_recall_e2(ds18b20_t *d,const uint8_t *rom)
|
|
{
|
|
ds18b20_status_t s;if(!valid_instance(d)||!rom)return DS18B20_E_ARGUMENT;
|
|
s=ds18b20_validate_rom(rom);if(s)return s;s=reset(d);if(s)return s;
|
|
match(d,rom);write_byte(d,DS_RECALL_E2);return DS18B20_OK;
|
|
}
|
|
|
|
ds18b20_status_t ds18b20_recall_ready(ds18b20_t *d)
|
|
{if(!valid_instance(d))return DS18B20_E_ARGUMENT;return read_bit(d)?DS18B20_OK:DS18B20_E_BUSY;}
|
|
|
|
void ds18b20_recover_bus(ds18b20_t *d)
|
|
{
|
|
if(!valid_instance(d))return;
|
|
if(d->strong_pullup_active&&d->ops->strong_pullup){
|
|
d->ops->strong_pullup(d->platform_context,0U);d->strong_pullup_active=0U;}
|
|
d->ops->release(d->platform_context);
|
|
}
|
|
|
|
uint8_t ds18b20_search_retry_due(const ds18b20_t *d,uint8_t conversion_active,
|
|
uint32_t now,uint32_t last,uint32_t period)
|
|
{
|
|
if(!valid_instance(d)||d->rom_count!=0U||conversion_active||period==0U)return 0U;
|
|
/* Unsigned subtraction keeps the retry correct across HAL tick rollover. */
|
|
return ((uint32_t)(now-last)>=period)?1U:0U;
|
|
}
|