Add embedded storage drivers and extend firmware metadata
This commit is contained in:
31
c/ds18b20/instance/Tests/run_host_tests.ps1
Normal file
31
c/ds18b20/instance/Tests/run_host_tests.ps1
Normal file
@@ -0,0 +1,31 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
$out = Join-Path $env:TEMP "test_ds18b20_copy_delay.exe"
|
||||
$test = Join-Path $PSScriptRoot "test_ds18b20_copy_delay.c"
|
||||
$core = Join-Path $PSScriptRoot "..\Src\ds18b20.c"
|
||||
$inc = Join-Path $PSScriptRoot "..\Inc"
|
||||
$gcc = Get-Command gcc -ErrorAction SilentlyContinue
|
||||
if ($gcc) {
|
||||
& $gcc.Source -std=c99 -Wall -Wextra -Werror -I $inc $test $core -o $out
|
||||
} else {
|
||||
$vcvars = "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
if (-not (Test-Path $vcvars)) { throw "Neither gcc nor MSVC was found" }
|
||||
$command = "call `"$vcvars`" >nul && pushd `"$env:TEMP`" && cl /nologo /std:c11 /W4 /WX /I `"$inc`" `"$test`" `"$core`" /Fe:`"$out`""
|
||||
& cmd.exe /d /c $command
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { throw "DS18B20 regression compilation failed" }
|
||||
& $out
|
||||
if ($LASTEXITCODE -ne 0) { throw "DS18B20 regression failed" }
|
||||
Remove-Item -LiteralPath $out -Force
|
||||
|
||||
$searchOut = Join-Path $env:TEMP "test_ds18b20_incremental_search.exe"
|
||||
$searchTest = Join-Path $PSScriptRoot "test_ds18b20_incremental_search.c"
|
||||
if ($gcc) {
|
||||
& $gcc.Source -std=c99 -Wall -Wextra -Werror -I $inc $searchTest $core -o $searchOut
|
||||
} else {
|
||||
$command = "call `"$vcvars`" >nul && pushd `"$env:TEMP`" && cl /nologo /std:c11 /W4 /WX /I `"$inc`" `"$searchTest`" `"$core`" /Fe:`"$searchOut`""
|
||||
& cmd.exe /d /c $command
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { throw "DS18B20 incremental search compilation failed" }
|
||||
& $searchOut
|
||||
if ($LASTEXITCODE -ne 0) { throw "DS18B20 incremental search failed" }
|
||||
Remove-Item -LiteralPath $searchOut -Force
|
||||
92
c/ds18b20/instance/Tests/test_ds18b20_copy_delay.c
Normal file
92
c/ds18b20/instance/Tests/test_ds18b20_copy_delay.c
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "ds18b20.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t scratchpad[DS18B20_SCRATCHPAD_SIZE];
|
||||
uint16_t read_call;
|
||||
uint32_t max_delay_us;
|
||||
uint16_t copy_delay_chunks;
|
||||
uint8_t critical_active;
|
||||
} mock_port_t;
|
||||
|
||||
static void drive_low(void *context) { (void)context; }
|
||||
static void release_line(void *context) { (void)context; }
|
||||
|
||||
static uint8_t read_line(void *context)
|
||||
{
|
||||
mock_port_t *port = (mock_port_t *)context;
|
||||
uint16_t call = port->read_call++;
|
||||
assert(port->critical_active); /* Presence and data sampling must be protected. */
|
||||
if (call == 0U) return 0U; /* Read Scratchpad reset presence. */
|
||||
if (call <= 72U) {
|
||||
uint16_t bit = (uint16_t)(call - 1U);
|
||||
return (uint8_t)((port->scratchpad[bit / 8U] >> (bit % 8U)) & 1U);
|
||||
}
|
||||
/* Write Scratchpad and Copy Scratchpad reset presence pulses. */
|
||||
return 0U;
|
||||
}
|
||||
|
||||
static void delay_us(void *context, uint32_t us)
|
||||
{
|
||||
mock_port_t *port = (mock_port_t *)context;
|
||||
if (us > port->max_delay_us) port->max_delay_us = us;
|
||||
if (us == 100U) port->copy_delay_chunks++;
|
||||
}
|
||||
|
||||
static void critical_enter(void *context)
|
||||
{
|
||||
mock_port_t *port = (mock_port_t *)context;
|
||||
assert(!port->critical_active);
|
||||
port->critical_active = 1U;
|
||||
}
|
||||
|
||||
static void critical_exit(void *context)
|
||||
{
|
||||
mock_port_t *port = (mock_port_t *)context;
|
||||
assert(port->critical_active);
|
||||
port->critical_active = 0U;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const ds18b20_onewire_ops_t ops = {
|
||||
drive_low, release_line, read_line, delay_us, NULL, critical_enter, critical_exit, NULL
|
||||
};
|
||||
ds18b20_t bus;
|
||||
mock_port_t port;
|
||||
uint8_t roms[1][DS18B20_ROM_SIZE] = {{0}};
|
||||
|
||||
memset(&port, 0, sizeof(port));
|
||||
roms[0][0] = 0x28U;
|
||||
roms[0][1] = 0x11U;
|
||||
roms[0][7] = ds18b20_crc8(roms[0], 7U);
|
||||
port.scratchpad[0] = 0x50U;
|
||||
port.scratchpad[1] = 0x05U;
|
||||
port.scratchpad[2] = 0x4BU;
|
||||
port.scratchpad[3] = 0x46U;
|
||||
port.scratchpad[4] = 0x7FU;
|
||||
port.scratchpad[5] = 0xFFU;
|
||||
port.scratchpad[6] = 0x0CU;
|
||||
port.scratchpad[7] = 0x10U;
|
||||
port.scratchpad[8] = ds18b20_crc8(port.scratchpad, 8U);
|
||||
|
||||
assert(ds18b20_init(&bus, &ops, &port, roms, 1U) == DS18B20_OK);
|
||||
bus.rom_count = 1U;
|
||||
assert(ds18b20_set_resolution(&bus, roms[0], 9U) == DS18B20_OK);
|
||||
/* Regression: a 10 ms callback overflows the 16-bit 72 MHz timer port. */
|
||||
assert(port.max_delay_us <= 480U);
|
||||
assert(port.copy_delay_chunks >= 100U);
|
||||
bus.rom_count = 0U;
|
||||
assert(ds18b20_search_retry_due(&bus, 0U, 1999U, 0U, 2000U) == 0U);
|
||||
assert(ds18b20_search_retry_due(&bus, 0U, 2000U, 0U, 2000U) == 1U);
|
||||
assert(ds18b20_search_retry_due(&bus, 1U, 4000U, 0U, 2000U) == 0U);
|
||||
/* Wrap-safe deadline: 0x20 - 0xFFFFFF00 = 0x120 ms. */
|
||||
assert(ds18b20_search_retry_due(&bus, 0U, 0x20U, 0xFFFFFF00U, 0x120U) == 1U);
|
||||
bus.rom_count = 1U;
|
||||
assert(ds18b20_search_retry_due(&bus, 0U, 4000U, 0U, 2000U) == 0U);
|
||||
puts("DS18B20 copy delay regression: OK");
|
||||
return 0;
|
||||
}
|
||||
152
c/ds18b20/instance/Tests/test_ds18b20_incremental_search.c
Normal file
152
c/ds18b20/instance/Tests/test_ds18b20_incremental_search.c
Normal file
@@ -0,0 +1,152 @@
|
||||
/* Model real SEARCH ROM participation: each branch filters the active slaves.
|
||||
* Fault injection tests transport recovery, not analog cable characteristics. */
|
||||
#include "ds18b20.h"
|
||||
#include "ds18b20_config.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
ds18b20_t *bus;
|
||||
uint8_t roms[3][8];
|
||||
uint8_t masks[3];
|
||||
unsigned active, command_bits, bit, pair, presence, attempts;
|
||||
unsigned low_time, elapsed, low, critical, recovery;
|
||||
unsigned fault_attempt, fault_bit, persistent_fault, crc_fault;
|
||||
} port_t;
|
||||
|
||||
static void low(void *ctx) {
|
||||
port_t *p = ctx; p->low = 1U; p->low_time = 0U; p->elapsed = 0U;
|
||||
}
|
||||
static unsigned rom_bit(port_t *p, unsigned i) {
|
||||
return (p->roms[i][p->bit / 8U] >> (p->bit % 8U)) & 1U;
|
||||
}
|
||||
static unsigned fault(port_t *p) {
|
||||
return p->attempts == p->fault_attempt ||
|
||||
(p->persistent_fault && p->attempts >= p->fault_attempt);
|
||||
}
|
||||
static void release_line(void *ctx) {
|
||||
port_t *p = ctx; unsigned i, direction;
|
||||
if (!p->low) return;
|
||||
p->low = 0U;
|
||||
if (p->low_time == 480U) {
|
||||
++p->attempts;
|
||||
p->active = p->masks[p->bus->search_diagnostics.passes_finished];
|
||||
p->presence = 1U; p->command_bits = 0U; p->bit = 0U; p->pair = 0U;
|
||||
} else if (p->low_time != 3U) {
|
||||
assert(p->low_time == 6U || p->low_time == 60U);
|
||||
if (p->command_bits < 8U) { ++p->command_bits; return; }
|
||||
direction = p->low_time == 6U;
|
||||
for (i = 0U; i < 3U; ++i)
|
||||
if (rom_bit(p, i) != direction) p->active &= ~(1U << i);
|
||||
++p->bit; p->pair = 0U;
|
||||
}
|
||||
}
|
||||
static uint8_t read_line(void *ctx) {
|
||||
port_t *p = ctx; unsigned i, zeros = 0U, ones = 0U, value;
|
||||
assert(p->critical);
|
||||
if (p->presence) {
|
||||
assert(p->elapsed == 550U); p->presence = 0U;
|
||||
return p->active ? 0U : 1U;
|
||||
}
|
||||
assert(p->elapsed == 13U); /* The read sample did not move. */
|
||||
assert(p->bit < 64U);
|
||||
for (i = 0U; i < 3U; ++i) if (p->active & (1U << i)) {
|
||||
if (rom_bit(p, i)) ++ones; else ++zeros;
|
||||
}
|
||||
value = p->pair++ == 0U ? !zeros : !ones;
|
||||
if (fault(p) && p->bit + 1U == p->fault_bit)
|
||||
value = p->crc_fault ? !value : 1U;
|
||||
return (uint8_t)value;
|
||||
}
|
||||
static void delay(void *ctx, uint32_t us) {
|
||||
port_t *p = ctx; p->elapsed += us;
|
||||
if (p->low) p->low_time += us;
|
||||
if (us == DS18B20_SLOT_RECOVERY_EXTRA_US) {
|
||||
assert(!p->critical && !p->low); ++p->recovery;
|
||||
}
|
||||
}
|
||||
static void enter(void *ctx) { port_t *p = ctx; assert(!p->critical); p->critical = 1U; }
|
||||
static void leave(void *ctx) { port_t *p = ctx; assert(p->critical); p->critical = 0U; }
|
||||
static const ds18b20_onewire_ops_t ops = {low, release_line, read_line, delay, NULL, enter, leave, NULL};
|
||||
|
||||
static void setup(ds18b20_t *bus, port_t *p, uint8_t storage[][8], size_t cap) {
|
||||
unsigned i;
|
||||
memset(p, 0, sizeof(*p)); p->bus = bus;
|
||||
for (i = 0U; i < 3U; ++i) {
|
||||
p->roms[i][0] = 0x28U; p->roms[i][1] = (uint8_t)(i + 1U);
|
||||
p->roms[i][7] = ds18b20_crc8(p->roms[i], 7U);
|
||||
p->masks[i] = 7U;
|
||||
}
|
||||
assert(ds18b20_init(bus, &ops, p, storage, cap) == DS18B20_OK);
|
||||
}
|
||||
static ds18b20_status_t finish(ds18b20_t *bus, port_t *p) {
|
||||
ds18b20_status_t s; unsigned calls = 0U, before;
|
||||
do {
|
||||
before = p->attempts; s = ds18b20_search_step(bus);
|
||||
assert(p->attempts <= before + 1U); /* Never retries in a tight loop. */
|
||||
assert(++calls <= DS18B20_SEARCH_MAX_ATTEMPTS + 1U);
|
||||
} while (s == DS18B20_E_BUSY);
|
||||
before = p->attempts;
|
||||
assert(ds18b20_search_step(bus) == s && p->attempts == before);
|
||||
return s;
|
||||
}
|
||||
int main(void) {
|
||||
ds18b20_t bus; port_t p; uint8_t storage[3][8], saved[8], discrepancy, family;
|
||||
setup(&bus, &p, storage, 3U);
|
||||
assert(ds18b20_search(&bus) == DS18B20_OK);
|
||||
assert(bus.rom_count == 3U && p.attempts == 9U);
|
||||
assert(bus.search_diagnostics.complete_passes == 3U && p.recovery != 0U);
|
||||
assert(bus.search_diagnostics.last_error == DS18B20_OK);
|
||||
|
||||
/* A fault on the second branch must restore the PREVIOUS tree path. */
|
||||
setup(&bus, &p, storage, 3U); p.fault_attempt = 2U; p.fault_bit = 17U;
|
||||
ds18b20_search_begin(&bus);
|
||||
assert(ds18b20_search_step(&bus) == DS18B20_E_BUSY);
|
||||
memcpy(saved, bus.search_rom, 8U); discrepancy = bus.last_discrepancy;
|
||||
family = bus.last_family_discrepancy;
|
||||
assert(ds18b20_search_step(&bus) == DS18B20_E_BUSY);
|
||||
assert(memcmp(saved, bus.search_rom, 8U) == 0);
|
||||
assert(bus.last_discrepancy == discrepancy && bus.last_family_discrepancy == family);
|
||||
assert(finish(&bus, &p) == DS18B20_OK && bus.rom_count == 3U);
|
||||
assert(bus.search_diagnostics.retries == 1U);
|
||||
assert(bus.search_diagnostics.last_error_bit == 17U && bus.search_diagnostics.last_error_pair == 3U);
|
||||
|
||||
setup(&bus, &p, storage, 3U); p.fault_attempt = 2U; p.fault_bit = 64U; p.crc_fault = 1U;
|
||||
assert(ds18b20_search(&bus) == DS18B20_OK && bus.rom_count == 3U);
|
||||
assert(bus.search_diagnostics.crc_errors == 1U && bus.search_diagnostics.retries == 1U);
|
||||
assert(bus.search_diagnostics.last_error_bit == 64U);
|
||||
|
||||
/* Each pass sees a different device; the validated union retains all three. */
|
||||
setup(&bus, &p, storage, 3U); p.masks[0] = 1U; p.masks[1] = 2U; p.masks[2] = 4U;
|
||||
assert(ds18b20_search(&bus) == DS18B20_OK && bus.rom_count == 3U);
|
||||
assert(p.attempts == 3U);
|
||||
|
||||
setup(&bus, &p, storage, 3U); memset(p.masks, 0, sizeof(p.masks));
|
||||
assert(ds18b20_search(&bus) == DS18B20_E_NO_DEVICE);
|
||||
assert(p.attempts == 15U && bus.search_diagnostics.retries == 12U);
|
||||
assert(bus.search_diagnostics.last_error_bit == 0U);
|
||||
assert(ds18b20_add_known_rom(&bus, p.roms[0]) == DS18B20_OK);
|
||||
assert(ds18b20_add_known_rom(&bus, p.roms[0]) == DS18B20_OK && bus.rom_count == 1U);
|
||||
assert(ds18b20_add_known_rom(&bus, NULL) == DS18B20_E_ARGUMENT);
|
||||
p.roms[1][7] ^= 1U;
|
||||
assert(ds18b20_add_known_rom(&bus, p.roms[1]) == DS18B20_E_CRC);
|
||||
|
||||
setup(&bus, &p, storage, 3U); p.fault_attempt = 2U; p.fault_bit = 17U; p.persistent_fault = 1U;
|
||||
assert(ds18b20_search(&bus) == DS18B20_E_IO && bus.rom_count == 1U);
|
||||
assert(p.attempts == 16U && bus.search_diagnostics.retries == 12U);
|
||||
assert(bus.search_diagnostics.complete_passes == 0U);
|
||||
|
||||
setup(&bus, &p, storage, 3U); p.fault_attempt = 1U; p.fault_bit = 64U;
|
||||
p.persistent_fault = 1U; p.crc_fault = 1U;
|
||||
assert(ds18b20_search(&bus) == DS18B20_E_CRC && bus.rom_count == 0U);
|
||||
assert(bus.search_diagnostics.crc_errors == 15U);
|
||||
|
||||
setup(&bus, &p, storage, 1U);
|
||||
assert(ds18b20_search(&bus) == DS18B20_E_CAPACITY && bus.rom_count == 1U);
|
||||
setup(&bus, &p, storage, 3U); ds18b20_search_begin(&bus);
|
||||
bus.search_diagnostics.attempts = DS18B20_SEARCH_MAX_ATTEMPTS;
|
||||
assert(finish(&bus, &p) == DS18B20_E_TIMEOUT && p.attempts == 0U);
|
||||
puts("DS18B20 robust search, retries, CRC, union, limits and timing: OK");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user