Перенесён из репозитория ds18b20, который подключался сабмодулем в KONOR_ds18b20. История библиотеки осталась там; сюда пришло состояние на момент переезда. Ядро на C99 без stm32f10x.h и динамической памяти. Порт — пять функций (Init, DelayUs, Reset, WriteBit, ReadBit); реализация для STM32F1 идёт в комплекте в ports/stm32f1. Хостовые тесты на модели шины с виртуальными датчиками проходят: tests/test_ds18b20.c.
306 lines
9.5 KiB
C
306 lines
9.5 KiB
C
/**
|
||
* @file fake_onewire.c
|
||
* @brief Симулятор шины 1-Wire с виртуальными датчиками DS18B20.
|
||
*
|
||
* Реализует те же пять функций, что и настоящий порт, но работает не с
|
||
* выводом, а с моделью шины. Этого достаточно, чтобы прогнать на хосте
|
||
* весь алгоритм SEARCH ROM и обмен с датчиком побитно - именно там
|
||
* прячутся ошибки, которых не видно в обзоре кода.
|
||
*/
|
||
|
||
#include "fake_onewire.h"
|
||
|
||
#include <string.h>
|
||
|
||
#include "onewire.h"
|
||
|
||
/** Состояние разбора команд шины. */
|
||
typedef enum {
|
||
FAKE_IDLE = 0, /**< После сброса, ждём код команды ROM. */
|
||
FAKE_SEARCH, /**< Идёт SEARCH ROM. */
|
||
FAKE_MATCH, /**< Принимаем 8 байт адреса. */
|
||
FAKE_FUNCTION, /**< ROM-команда отработала, ждём функцию. */
|
||
FAKE_READ_SCRATCH, /**< Отдаём scratchpad. */
|
||
FAKE_WRITE_SCRATCH, /**< Принимаем TH, TL, config. */
|
||
FAKE_READ_POWER /**< Отдаём один бит типа питания. */
|
||
} fake_state_t;
|
||
|
||
static fake_bus_t *g_bus;
|
||
static fake_state_t g_state;
|
||
|
||
/* Сборка входящих и исходящих байтов по битам. */
|
||
static uint8_t g_in_byte;
|
||
static uint8_t g_in_bits;
|
||
static uint8_t g_out_byte;
|
||
static uint8_t g_out_bits;
|
||
static uint8_t g_out_index;
|
||
|
||
/* Состояние SEARCH ROM. */
|
||
static uint8_t g_search_bit; /**< Номер бита ROM, 0..63. */
|
||
static uint8_t g_search_phase; /**< 0 - прямой, 1 - обратный, 2 - выбор. */
|
||
static uint8_t g_match_index;
|
||
static uint8_t g_write_index;
|
||
|
||
/** Активен ли датчик в текущей транзакции. */
|
||
static uint8_t g_selected[FAKE_MAX_DEVICES];
|
||
|
||
void fake_bus_attach(fake_bus_t *bus)
|
||
{
|
||
g_bus = bus;
|
||
g_state = FAKE_IDLE;
|
||
g_in_byte = 0U;
|
||
g_in_bits = 0U;
|
||
g_out_bits = 0U;
|
||
g_out_index = 0U;
|
||
bus->reset_count = 0U;
|
||
bus->convert_count = 0U;
|
||
bus->copy_count = 0U;
|
||
bus->delay_us_total = 0U;
|
||
}
|
||
|
||
uint8_t fake_bus_crc8(const uint8_t *data, uint32_t size)
|
||
{
|
||
uint8_t crc = 0U;
|
||
for (uint32_t i = 0U; i < size; i++) {
|
||
crc ^= data[i];
|
||
for (uint8_t bit = 0U; bit < 8U; bit++) {
|
||
crc = (uint8_t)((crc & 1U) ? ((crc >> 1) ^ 0x8CU) : (crc >> 1));
|
||
}
|
||
}
|
||
return crc;
|
||
}
|
||
|
||
void fake_device_init(fake_device_t *device, const uint8_t rom7[7],
|
||
int16_t raw, uint8_t parasite)
|
||
{
|
||
memcpy(device->rom, rom7, 7U);
|
||
device->rom[7] = fake_bus_crc8(device->rom, 7U);
|
||
device->present = 1U;
|
||
device->parasite = parasite;
|
||
device->accept_writes = 1U;
|
||
device->corrupt_crc = 0U;
|
||
|
||
device->scratchpad[0] = (uint8_t)((uint16_t)raw & 0xFFU);
|
||
device->scratchpad[1] = (uint8_t)(((uint16_t)raw >> 8) & 0xFFU);
|
||
device->scratchpad[2] = 0x4BU; /* TH по умолчанию */
|
||
device->scratchpad[3] = 0x46U; /* TL по умолчанию */
|
||
device->scratchpad[4] = 0x7FU; /* 12 бит */
|
||
device->scratchpad[5] = 0xFFU;
|
||
device->scratchpad[6] = 0x0CU;
|
||
device->scratchpad[7] = 0x10U;
|
||
device->scratchpad[8] = fake_bus_crc8(device->scratchpad, 8U);
|
||
}
|
||
|
||
static void refresh_crc(fake_device_t *device)
|
||
{
|
||
device->scratchpad[8] = fake_bus_crc8(device->scratchpad, 8U);
|
||
if (device->corrupt_crc != 0U) {
|
||
device->scratchpad[8] ^= 0xFFU;
|
||
}
|
||
}
|
||
|
||
/** Единственный выбранный датчик либо NULL, если их несколько или ноль. */
|
||
static fake_device_t *sole_selected(void)
|
||
{
|
||
fake_device_t *found = NULL;
|
||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||
if ((g_selected[i] != 0U) && (g_bus->devices[i].present != 0U)) {
|
||
if (found != NULL) {
|
||
return NULL;
|
||
}
|
||
found = &g_bus->devices[i];
|
||
}
|
||
}
|
||
return found;
|
||
}
|
||
|
||
/* --- Порт: пять функций, которых ждёт переносимая часть ------------------- */
|
||
|
||
void OneWire_Init(void)
|
||
{
|
||
g_state = FAKE_IDLE;
|
||
}
|
||
|
||
void OneWire_DelayUs(uint32_t microseconds)
|
||
{
|
||
if (g_bus != NULL) {
|
||
g_bus->delay_us_total += microseconds;
|
||
}
|
||
}
|
||
|
||
uint8_t OneWire_Reset(void)
|
||
{
|
||
uint8_t presence = 0U;
|
||
|
||
g_state = FAKE_IDLE;
|
||
g_in_byte = 0U;
|
||
g_in_bits = 0U;
|
||
g_out_bits = 0U;
|
||
g_out_index = 0U;
|
||
g_search_bit = 0U;
|
||
g_search_phase = 0U;
|
||
|
||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||
g_selected[i] = g_bus->devices[i].present;
|
||
if (g_bus->devices[i].present != 0U) {
|
||
presence = 1U;
|
||
}
|
||
}
|
||
g_bus->reset_count++;
|
||
return presence;
|
||
}
|
||
|
||
/** Обрабатывает собранный байт команды. */
|
||
static void dispatch_byte(uint8_t value)
|
||
{
|
||
switch (g_state) {
|
||
case FAKE_IDLE:
|
||
if (value == 0xF0U) { /* SEARCH ROM */
|
||
g_state = FAKE_SEARCH;
|
||
g_search_bit = 0U;
|
||
g_search_phase = 0U;
|
||
} else if (value == 0x55U) { /* MATCH ROM */
|
||
g_state = FAKE_MATCH;
|
||
g_match_index = 0U;
|
||
} else if (value == 0xCCU) { /* SKIP ROM */
|
||
g_state = FAKE_FUNCTION;
|
||
}
|
||
break;
|
||
|
||
case FAKE_MATCH:
|
||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||
if (g_bus->devices[i].rom[g_match_index] != value) {
|
||
g_selected[i] = 0U;
|
||
}
|
||
}
|
||
g_match_index++;
|
||
if (g_match_index >= 8U) {
|
||
g_state = FAKE_FUNCTION;
|
||
}
|
||
break;
|
||
|
||
case FAKE_FUNCTION: {
|
||
fake_device_t *device = sole_selected();
|
||
if (value == 0x44U) { /* CONVERT T */
|
||
g_bus->convert_count++;
|
||
} else if (value == 0xBEU) { /* READ SCRATCHPAD */
|
||
if (device != NULL) {
|
||
refresh_crc(device);
|
||
g_state = FAKE_READ_SCRATCH;
|
||
g_out_index = 0U;
|
||
g_out_bits = 0U;
|
||
}
|
||
} else if (value == 0x4EU) { /* WRITE SCRATCHPAD */
|
||
g_state = FAKE_WRITE_SCRATCH;
|
||
g_write_index = 0U;
|
||
} else if (value == 0x48U) { /* COPY SCRATCHPAD */
|
||
g_bus->copy_count++;
|
||
} else if (value == 0xB4U) { /* READ POWER SUPPLY */
|
||
g_state = FAKE_READ_POWER;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case FAKE_WRITE_SCRATCH: {
|
||
fake_device_t *device = sole_selected();
|
||
if ((device != NULL) && (device->accept_writes != 0U)) {
|
||
device->scratchpad[2U + g_write_index] = value;
|
||
}
|
||
g_write_index++;
|
||
if (g_write_index >= 3U) {
|
||
if (device != NULL) {
|
||
refresh_crc(device);
|
||
}
|
||
g_state = FAKE_FUNCTION;
|
||
}
|
||
break;
|
||
}
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void OneWire_WriteBit(uint8_t bit)
|
||
{
|
||
if (g_state == FAKE_SEARCH) {
|
||
/* Третья фаза SEARCH ROM: мастер сообщает выбранную ветвь. */
|
||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||
if (g_selected[i] == 0U) {
|
||
continue;
|
||
}
|
||
uint8_t rom_bit = (uint8_t)((g_bus->devices[i].rom[g_search_bit / 8U]
|
||
>> (g_search_bit % 8U)) & 1U);
|
||
if (rom_bit != (bit != 0U)) {
|
||
g_selected[i] = 0U;
|
||
}
|
||
}
|
||
g_search_bit++;
|
||
g_search_phase = 0U;
|
||
if (g_search_bit >= 64U) {
|
||
g_state = FAKE_FUNCTION;
|
||
}
|
||
return;
|
||
}
|
||
|
||
g_in_byte = (uint8_t)((g_in_byte >> 1) | ((bit != 0U) ? 0x80U : 0U));
|
||
g_in_bits++;
|
||
if (g_in_bits >= 8U) {
|
||
uint8_t value = g_in_byte;
|
||
g_in_byte = 0U;
|
||
g_in_bits = 0U;
|
||
dispatch_byte(value);
|
||
}
|
||
}
|
||
|
||
uint8_t OneWire_ReadBit(void)
|
||
{
|
||
if (g_state == FAKE_SEARCH) {
|
||
uint8_t ones = 0U;
|
||
uint8_t zeros = 0U;
|
||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||
if (g_selected[i] == 0U) {
|
||
continue;
|
||
}
|
||
uint8_t rom_bit = (uint8_t)((g_bus->devices[i].rom[g_search_bit / 8U]
|
||
>> (g_search_bit % 8U)) & 1U);
|
||
if (rom_bit != 0U) {
|
||
ones = 1U;
|
||
} else {
|
||
zeros = 1U;
|
||
}
|
||
}
|
||
if ((ones == 0U) && (zeros == 0U)) {
|
||
return 1U; /* никто не отвечает - обе фазы единичны */
|
||
}
|
||
uint8_t result = (g_search_phase == 0U) ? (uint8_t)(zeros == 0U)
|
||
: (uint8_t)(ones == 0U);
|
||
g_search_phase = (uint8_t)(g_search_phase + 1U);
|
||
return result;
|
||
}
|
||
|
||
if (g_state == FAKE_READ_POWER) {
|
||
fake_device_t *device = sole_selected();
|
||
/* Паразитное питание прижимает шину к нулю. */
|
||
return (uint8_t)((device != NULL) && (device->parasite != 0U) ? 0U : 1U);
|
||
}
|
||
|
||
if (g_state == FAKE_READ_SCRATCH) {
|
||
fake_device_t *device = sole_selected();
|
||
if (device == NULL) {
|
||
return 1U;
|
||
}
|
||
if (g_out_bits == 0U) {
|
||
g_out_byte = (g_out_index < 9U) ? device->scratchpad[g_out_index] : 0xFFU;
|
||
}
|
||
uint8_t bit = (uint8_t)((g_out_byte >> g_out_bits) & 1U);
|
||
g_out_bits++;
|
||
if (g_out_bits >= 8U) {
|
||
g_out_bits = 0U;
|
||
g_out_index++;
|
||
}
|
||
return bit;
|
||
}
|
||
return 1U;
|
||
}
|