Files
templates/c/ds18b20/instance/Src/ds18b20.c

360 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Работа с термометрами DS18B20: поиск по ROM, запуск преобразования, чтение температуры и
* настройка scratchpad. ROM является адресом устройства на общей шине; результат чтения нельзя
* считать действительным до проверки статуса обмена и CRC.
*/
#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;
}