feat(ds18b20): термометры DS18B20 поверх программной 1-Wire

Перенесён из репозитория ds18b20, который подключался сабмодулем в
KONOR_ds18b20. История библиотеки осталась там; сюда пришло состояние
на момент переезда.

Ядро на C99 без stm32f10x.h и динамической памяти. Порт — пять функций
(Init, DelayUs, Reset, WriteBit, ReadBit); реализация для STM32F1 идёт
в комплекте в ports/stm32f1. Хостовые тесты на модели шины с виртуальными
датчиками проходят: tests/test_ds18b20.c.
This commit is contained in:
2026-08-23 01:15:34 +03:00
parent 13f2c8fe64
commit 873ac438f3
11 changed files with 1701 additions and 0 deletions

202
c/ds18b20/src/ds18b20.c Normal file
View File

@@ -0,0 +1,202 @@
/** test
* @file ds18b20.c
* @brief Реализация драйвера DS18B20: поиск, чтение и запись scratchpad.
*/
#include "ds18b20.h"
#define DS_CMD_CONVERT_T 0x44U
#define DS_CMD_WRITE_SCRATCHPAD 0x4EU
#define DS_CMD_READ_SCRATCHPAD 0xBEU
#define DS_CMD_COPY_SCRATCHPAD 0x48U
#define DS_CMD_READ_POWER 0xB4U
/** Задержка записи EEPROM датчика по datasheet, мкс. */
#define DS_EEPROM_WRITE_US 12000U
/**
* @brief Считывает scratchpad адресованного датчика с проверкой CRC8.
*
* @param rom Идентификатор датчика.
* @param scratchpad Буфер на DS18B20_SCRATCHPAD_SIZE байт.
* @return Битовая маска ошибок DS18B20_STATUS_* без бита VALID.
*/
static uint8_t ds_read_scratchpad(const uint8_t *rom, uint8_t *scratchpad)
{
if (OneWire_SelectRom(rom) == 0U) {
return DS18B20_STATUS_NO_PRESENCE;
}
OneWire_WriteByte(DS_CMD_READ_SCRATCHPAD);
OneWire_ReadBytes(scratchpad, DS18B20_SCRATCHPAD_SIZE);
if (OneWire_Crc8(scratchpad, DS18B20_SCRATCHPAD_SIZE) != 0U) {
return DS18B20_STATUS_CRC_ERROR;
}
return 0U;
}
/**
* @brief Опрашивает тип питания датчика командой READ POWER SUPPLY.
*
* @param rom Идентификатор датчика.
* @return 1 для паразитного питания, иначе 0.
*/
static uint8_t ds_read_parasite(const uint8_t *rom)
{
if (OneWire_SelectRom(rom) == 0U) {
return 0U;
}
OneWire_WriteByte(DS_CMD_READ_POWER);
/* Датчик с паразитным питанием прижимает шину к нулю. */
return (uint8_t)(OneWire_ReadBit() == 0U);
}
uint8_t DS18B20_ConfigFromResolution(uint8_t bits)
{
if (bits < 9U) {
bits = 9U;
} else if (bits > 12U) {
bits = 12U;
}
return (uint8_t)(0x1FU | ((bits - 9U) << 5U));
}
uint16_t DS18B20_ConversionTimeMs(uint8_t config)
{
switch ((config >> 5U) & 0x03U) {
case 0U:
return 94U;
case 1U:
return 188U;
case 2U:
return 375U;
default:
return 750U;
}
}
uint8_t DS18B20_Scan(DS18B20_Bus *bus)
{
OneWire_Search search;
uint8_t index;
bus->count = 0U;
bus->parasite = 0U;
OneWire_SearchInit(&search);
while ((bus->count < DS18B20_MAX_SENSORS) && (OneWire_SearchNext(&search) != 0U)) {
if (search.rom[0] != DS18B20_FAMILY_CODE) {
continue;
}
DS18B20_Sensor *sensor = &bus->sensors[bus->count];
for (index = 0U; index < ONEWIRE_ROM_SIZE; index++) {
sensor->rom[index] = search.rom[index];
}
sensor->raw = 0;
sensor->user_byte1 = 0U;
sensor->user_byte2 = 0U;
sensor->config = DS18B20_ConfigFromResolution(12U);
sensor->status = 0U;
if (ds_read_parasite(sensor->rom) != 0U) {
sensor->status |= DS18B20_STATUS_PARASITE;
bus->parasite = 1U;
}
bus->count++;
}
return bus->count;
}
uint8_t DS18B20_StartConversion(void)
{
if (OneWire_SkipRom() == 0U) {
return 0U;
}
OneWire_WriteByte(DS_CMD_CONVERT_T);
return 1U;
}
uint8_t DS18B20_ReadAll(DS18B20_Bus *bus)
{
uint8_t scratchpad[DS18B20_SCRATCHPAD_SIZE];
uint8_t valid = 0U;
uint8_t index;
for (index = 0U; index < bus->count; index++) {
DS18B20_Sensor *sensor = &bus->sensors[index];
const uint8_t parasite = (uint8_t)(sensor->status & DS18B20_STATUS_PARASITE);
const uint8_t error = ds_read_scratchpad(sensor->rom, scratchpad);
if (error != 0U) {
sensor->status = (uint8_t)(parasite | error);
continue;
}
sensor->raw = (int16_t)((uint16_t)scratchpad[0] | ((uint16_t)scratchpad[1] << 8U));
sensor->user_byte1 = scratchpad[2];
sensor->user_byte2 = scratchpad[3];
sensor->config = scratchpad[4];
sensor->status = (uint8_t)(parasite | DS18B20_STATUS_VALID);
valid++;
}
return valid;
}
DS18B20_Sensor *DS18B20_Find(DS18B20_Bus *bus, const uint8_t *rom)
{
uint8_t index;
uint8_t byte;
for (index = 0U; index < bus->count; index++) {
for (byte = 0U; byte < ONEWIRE_ROM_SIZE; byte++) {
if (bus->sensors[index].rom[byte] != rom[byte]) {
break;
}
}
if (byte == ONEWIRE_ROM_SIZE) {
return &bus->sensors[index];
}
}
return 0;
}
uint8_t DS18B20_WriteScratchpad(DS18B20_Sensor *sensor, uint8_t user_byte1,
uint8_t user_byte2, uint8_t config,
uint8_t save_to_eeprom)
{
uint8_t scratchpad[DS18B20_SCRATCHPAD_SIZE];
if (OneWire_SelectRom(sensor->rom) == 0U) {
sensor->status = (uint8_t)((sensor->status & DS18B20_STATUS_PARASITE)
| DS18B20_STATUS_NO_PRESENCE);
return DS18B20_WRITE_BUS_ERROR;
}
OneWire_WriteByte(DS_CMD_WRITE_SCRATCHPAD);
OneWire_WriteByte(user_byte1);
OneWire_WriteByte(user_byte2);
OneWire_WriteByte(config);
/* Обратное чтение подтверждает, что датчик принял все три байта. */
if (ds_read_scratchpad(sensor->rom, scratchpad) != 0U) {
return DS18B20_WRITE_BUS_ERROR;
}
if ((scratchpad[2] != user_byte1) || (scratchpad[3] != user_byte2)
|| (scratchpad[4] != config)) {
/* Обмен состоялся, но регистры остались прежними: значение отвергнуто. */
sensor->user_byte1 = scratchpad[2];
sensor->user_byte2 = scratchpad[3];
sensor->config = scratchpad[4];
return DS18B20_WRITE_REJECTED;
}
sensor->user_byte1 = scratchpad[2];
sensor->user_byte2 = scratchpad[3];
sensor->config = scratchpad[4];
if (save_to_eeprom != 0U) {
if (OneWire_SelectRom(sensor->rom) == 0U) {
return DS18B20_WRITE_BUS_ERROR;
}
OneWire_WriteByte(DS_CMD_COPY_SCRATCHPAD);
/* Датчик с внешним питанием тоже допускает выдержку по времени. */
OneWire_DelayUs(DS_EEPROM_WRITE_US);
}
return DS18B20_WRITE_OK;
}

177
c/ds18b20/src/onewire.c Normal file
View File

@@ -0,0 +1,177 @@
/**
* @file onewire.c
* @brief Переносимая часть шины 1-Wire: побайтовый обмен, CRC8 и поиск ROM.
*
* Здесь нет ни регистров, ни задержек, ни запрета прерываний: всё это живёт
* в порте (ports/), а этот файл собирается любым C99-компилятором и
* проверяется host-тестами на симуляторе шины.
*
* От порта требуются пять функций: OneWire_Init, OneWire_DelayUs,
* OneWire_Reset, OneWire_WriteBit и OneWire_ReadBit.
*/
#include "onewire.h"
#define OW_CMD_SEARCH_ROM 0xF0U
#define OW_CMD_MATCH_ROM 0x55U
#define OW_CMD_SKIP_ROM 0xCCU
void OneWire_WriteByte(uint8_t value)
{
uint8_t index;
for (index = 0U; index < 8U; index++) {
OneWire_WriteBit((uint8_t)(value & 1U));
value = (uint8_t)(value >> 1U);
}
}
uint8_t OneWire_ReadByte(void)
{
uint8_t value = 0U;
uint8_t index;
for (index = 0U; index < 8U; index++) {
value = (uint8_t)(value >> 1U);
if (OneWire_ReadBit() != 0U) {
value |= 0x80U;
}
}
return value;
}
void OneWire_WriteBytes(const uint8_t *data, uint32_t size)
{
uint32_t index;
for (index = 0U; index < size; index++) {
OneWire_WriteByte(data[index]);
}
}
void OneWire_ReadBytes(uint8_t *data, uint32_t size)
{
uint32_t index;
for (index = 0U; index < size; index++) {
data[index] = OneWire_ReadByte();
}
}
uint8_t OneWire_Crc8(const uint8_t *data, uint32_t size)
{
uint8_t crc = 0U;
uint32_t index;
uint8_t bit;
for (index = 0U; index < size; index++) {
crc ^= data[index];
for (bit = 0U; bit < 8U; bit++) {
crc = (uint8_t)((crc & 1U) != 0U ? ((crc >> 1U) ^ 0x8CU) : (crc >> 1U));
}
}
return crc;
}
void OneWire_SearchInit(OneWire_Search *search)
{
uint32_t index;
for (index = 0U; index < ONEWIRE_ROM_SIZE; index++) {
search->rom[index] = 0U;
}
search->last_discrepancy = 0U;
search->last_family_discrepancy = 0U;
search->last_device = 0U;
}
uint8_t OneWire_SearchNext(OneWire_Search *search)
{
uint8_t id_bit_number = 1U;
uint8_t last_zero = 0U;
uint8_t rom_byte_number = 0U;
uint8_t rom_byte_mask = 1U;
uint8_t search_direction;
uint8_t id_bit;
uint8_t cmp_id_bit;
if (search->last_device != 0U) {
OneWire_SearchInit(search);
return 0U;
}
if (OneWire_Reset() == 0U) {
OneWire_SearchInit(search);
return 0U;
}
OneWire_WriteByte(OW_CMD_SEARCH_ROM);
do {
id_bit = OneWire_ReadBit();
cmp_id_bit = OneWire_ReadBit();
if ((id_bit != 0U) && (cmp_id_bit != 0U)) {
/* Ни одно устройство не ответило: обход прерван. */
OneWire_SearchInit(search);
return 0U;
}
if (id_bit != cmp_id_bit) {
search_direction = id_bit;
} else if (id_bit_number < search->last_discrepancy) {
search_direction =
(uint8_t)((search->rom[rom_byte_number] & rom_byte_mask) != 0U);
} else {
search_direction = (uint8_t)(id_bit_number == search->last_discrepancy);
}
if ((id_bit == cmp_id_bit) && (search_direction == 0U)) {
last_zero = id_bit_number;
if (last_zero < 9U) {
search->last_family_discrepancy = last_zero;
}
}
if (search_direction != 0U) {
search->rom[rom_byte_number] |= rom_byte_mask;
} else {
search->rom[rom_byte_number] &= (uint8_t)~rom_byte_mask;
}
OneWire_WriteBit(search_direction);
id_bit_number++;
rom_byte_mask = (uint8_t)(rom_byte_mask << 1U);
if (rom_byte_mask == 0U) {
rom_byte_number++;
rom_byte_mask = 1U;
}
} while (rom_byte_number < ONEWIRE_ROM_SIZE);
if (OneWire_Crc8(search->rom, ONEWIRE_ROM_SIZE) != 0U) {
OneWire_SearchInit(search);
return 0U;
}
search->last_discrepancy = last_zero;
if (search->last_discrepancy == 0U) {
search->last_device = 1U;
}
return 1U;
}
uint8_t OneWire_SelectRom(const uint8_t *rom)
{
if (OneWire_Reset() == 0U) {
return 0U;
}
OneWire_WriteByte(OW_CMD_MATCH_ROM);
OneWire_WriteBytes(rom, ONEWIRE_ROM_SIZE);
return 1U;
}
uint8_t OneWire_SkipRom(void)
{
if (OneWire_Reset() == 0U) {
return 0U;
}
OneWire_WriteByte(OW_CMD_SKIP_ROM);
return 1U;
}