diff --git a/c/ds18b20/README.md b/c/ds18b20/README.md index c267a20..2ba9c5a 100644 --- a/c/ds18b20/README.md +++ b/c/ds18b20/README.md @@ -24,6 +24,8 @@ | `include/ds18b20.h`, `src/ds18b20.c` | перечень датчиков, измерения, запись TH/TL/разрешения | `onewire.h`, `stdint.h` | | `include/onewire.h`, `src/onewire.c` | байты и блоки, CRC8 Dallas/Maxim, обход SEARCH ROM | `stdint.h` | | `ports/stm32f1/` | тайминги Maxim, вывод с открытым стоком, задержки по DWT | CMSIS `stm32f10x.h` | +| `ports/stm32g4/` | то же для STM32G431 и STM32G474 | CMSIS `stm32g4xx.h` | +| `ports/stm32f4/` | то же для STM32F407VET6 и родственных F4 | CMSIS `stm32f4xx.h` | | `tests/` | симулятор шины с виртуальными датчиками и host-тесты | — | ## Порт: что нужно от платформы @@ -60,6 +62,33 @@ uint8_t OneWire_ReadBit(void); Требуется внешняя подтяжка 4.7 кОм к питанию. +### Готовые порты STM32G4 и STM32F4 + +Логика та же, что и в порте STM32F1: открытый сток, чтение через `IDR`, +задержки по `DWT->CYCCNT`, запрет прерываний только на один тайм-слот. +Отличается лишь настройка вывода — у G4 и F4 это `MODER`/`OTYPER`/ +`OSPEEDR`/`PUPDR` вместо `CRL`/`CRH`, а низкий уровень выставляется +через верхнюю половину `BSRR`. + +STM32G431 и STM32G474 обслуживает один порт `ports/stm32g4/`: блок GPIO и +шина тактирования у них совпадают, различается только CMSIS-заголовок в +`onewire_config.h`. Шаблоны лежат рядом — по одному на каждый камень: + +```c +/* ports/stm32g4/onewire_config.g474.template.h */ +#include "stm32g4xx.h" + +#define ONEWIRE_GPIO GPIOA +#define ONEWIRE_PIN 1U +#define ONEWIRE_GPIO_CLOCK RCC_AHB2ENR_GPIOAEN /* у F4 — RCC_AHB1ENR_GPIOAEN */ +``` + +Порт `ports/stm32f4/` написан под STM32F407VET6 и подходит любому F4 с тем +же блоком GPIO; тактирование включается в `RCC->AHB1ENR`. + +Подтяжка 4.7 кОм — внешняя и здесь: внутренняя `PUPDR` намеренно +оставлена выключенной, её сопротивления для шины не хватает. + ## Использование ```c diff --git a/c/ds18b20/ports/stm32f4/onewire_config.f407.template.h b/c/ds18b20/ports/stm32f4/onewire_config.f407.template.h new file mode 100644 index 0000000..dadfe17 --- /dev/null +++ b/c/ds18b20/ports/stm32f4/onewire_config.f407.template.h @@ -0,0 +1,26 @@ +/** + * @file onewire_config.f407.template.h + * @brief Шаблон привязки шины 1-Wire для STM32F407VET6 (порт stm32f4). + * + * Скопируйте в своё приложение под именем onewire_config.h и положите на + * путь поиска заголовков. Библиотека сама этот файл не подставляет: + * привязка выводов принадлежит плате, а не драйверу. + * + * Требуется внешняя подтяжка 4.7 кОм к питанию. + */ + +#ifndef ONEWIRE_CONFIG_H +#define ONEWIRE_CONFIG_H + +#include "stm32f4xx.h" + +/** Порт вывода шины. */ +#define ONEWIRE_GPIO GPIOA + +/** Номер вывода в порту, 0..15. */ +#define ONEWIRE_PIN 1U + +/** Бит включения тактирования порта в RCC->AHB1ENR. */ +#define ONEWIRE_GPIO_CLOCK RCC_AHB1ENR_GPIOAEN + +#endif /* ONEWIRE_CONFIG_H */ diff --git a/c/ds18b20/ports/stm32f4/onewire_stm32f4.c b/c/ds18b20/ports/stm32f4/onewire_stm32f4.c new file mode 100644 index 0000000..42203fb --- /dev/null +++ b/c/ds18b20/ports/stm32f4/onewire_stm32f4.c @@ -0,0 +1,170 @@ +/** + * @file onewire_stm32f4.c + * @brief Порт шины 1-Wire на STM32F4: тайминги, вывод и задержки. + * + * Написан под STM32F407VET6 и подходит любому STM32F4 с тем же блоком GPIO: + * различается только CMSIS-заголовок, который подставляет onewire_config.h. + * + * Вывод постоянно настроен как выход с открытым стоком: чтение уровня + * выполняется через регистр IDR, поэтому переключать режим не требуется. + * Прерывания запрещаются только на длительность одного тайм-слота, чтобы не + * терять байты USART во время обмена. + * + * Привязка вывода приходит из onewire_config.h, который предоставляет + * приложение. Шаблон — onewire_config.f407.template.h рядом с этим файлом. + */ + +#include "onewire.h" + +#include "onewire_config.h" + +#if !defined(ONEWIRE_GPIO) || !defined(ONEWIRE_PIN) || !defined(ONEWIRE_GPIO_CLOCK) +#error "onewire_config.h должен задать ONEWIRE_GPIO, ONEWIRE_PIN и ONEWIRE_GPIO_CLOCK" +#endif + +/** Длительности тайм-слотов в микросекундах согласно рекомендациям Maxim. */ +#define OW_RESET_LOW_US 480U +#define OW_PRESENCE_WAIT_US 70U +#define OW_RESET_TAIL_US 410U +#define OW_WRITE1_LOW_US 6U +#define OW_WRITE1_TAIL_US 64U +#define OW_WRITE0_LOW_US 60U +#define OW_WRITE0_TAIL_US 10U +#define OW_READ_LOW_US 6U +#define OW_READ_SAMPLE_US 9U +#define OW_READ_TAIL_US 55U + +/** Число тактов ядра на микросекунду; заполняется в OneWire_Init. */ +static uint32_t g_cycles_per_us = 168U; + +/** + * @brief Переводит вывод шины в активный низкий уровень. + */ +static void ow_drive_low(void) +{ + ONEWIRE_GPIO->BSRR = (uint32_t)(1UL << (ONEWIRE_PIN + 16U)); +} + +/** + * @brief Отпускает шину; уровень поднимает внешний резистор подтяжки. + */ +static void ow_release(void) +{ + ONEWIRE_GPIO->BSRR = (uint32_t)(1UL << ONEWIRE_PIN); +} + +/** + * @brief Читает текущий уровень на выводе шины. + * + * @return 1 для высокого уровня, 0 для низкого. + */ +static uint8_t ow_read_pin(void) +{ + return (uint8_t)((ONEWIRE_GPIO->IDR >> ONEWIRE_PIN) & 1U); +} + +void OneWire_DelayUs(uint32_t microseconds) +{ + const uint32_t start = DWT->CYCCNT; + const uint32_t ticks = microseconds * g_cycles_per_us; + + while ((DWT->CYCCNT - start) < ticks) { + __NOP(); + } +} + +void OneWire_Init(void) +{ + uint32_t config; + + g_cycles_per_us = SystemCoreClock / 1000000U; + if (g_cycles_per_us == 0U) { + g_cycles_per_us = 1U; + } + + /* Счётчик тактов ядра — единственный источник микросекундных задержек. */ + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CYCCNT = 0U; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + + RCC->AHB1ENR |= ONEWIRE_GPIO_CLOCK; + + /* Открытый сток: OTYPER = 1. */ + ONEWIRE_GPIO->OTYPER |= (uint32_t)(1UL << ONEWIRE_PIN); + + /* Подтяжка внешняя, внутренняя не нужна: PUPDR = 00. */ + config = ONEWIRE_GPIO->PUPDR; + config &= ~(0x3UL << (ONEWIRE_PIN * 2U)); + ONEWIRE_GPIO->PUPDR = config; + + /* Максимальная скорость фронта: OSPEEDR = 11. */ + config = ONEWIRE_GPIO->OSPEEDR; + config |= (0x3UL << (ONEWIRE_PIN * 2U)); + ONEWIRE_GPIO->OSPEEDR = config; + + /* Уровень отпускаем до перевода в выход, чтобы не дёрнуть шину вниз. */ + ow_release(); + + /* Обычный выход: MODER = 01. */ + config = ONEWIRE_GPIO->MODER; + config &= ~(0x3UL << (ONEWIRE_PIN * 2U)); + config |= (0x1UL << (ONEWIRE_PIN * 2U)); + ONEWIRE_GPIO->MODER = config; + + OneWire_DelayUs(OW_RESET_LOW_US); +} + +uint8_t OneWire_Reset(void) +{ + uint32_t primask; + uint8_t presence; + + /* Удлинение импульса сброса безопасно, поэтому прерывания не запрещаем. */ + ow_drive_low(); + OneWire_DelayUs(OW_RESET_LOW_US); + + primask = __get_PRIMASK(); + __disable_irq(); + ow_release(); + OneWire_DelayUs(OW_PRESENCE_WAIT_US); + presence = (uint8_t)(ow_read_pin() == 0U); + __set_PRIMASK(primask); + + OneWire_DelayUs(OW_RESET_TAIL_US); + return (uint8_t)(presence && (ow_read_pin() != 0U)); +} + +void OneWire_WriteBit(uint8_t bit) +{ + const uint32_t primask = __get_PRIMASK(); + + __disable_irq(); + ow_drive_low(); + if (bit != 0U) { + OneWire_DelayUs(OW_WRITE1_LOW_US); + ow_release(); + OneWire_DelayUs(OW_WRITE1_TAIL_US); + } else { + OneWire_DelayUs(OW_WRITE0_LOW_US); + ow_release(); + OneWire_DelayUs(OW_WRITE0_TAIL_US); + } + __set_PRIMASK(primask); +} + +uint8_t OneWire_ReadBit(void) +{ + const uint32_t primask = __get_PRIMASK(); + uint8_t bit; + + __disable_irq(); + ow_drive_low(); + OneWire_DelayUs(OW_READ_LOW_US); + ow_release(); + OneWire_DelayUs(OW_READ_SAMPLE_US); + bit = ow_read_pin(); + __set_PRIMASK(primask); + + OneWire_DelayUs(OW_READ_TAIL_US); + return bit; +} diff --git a/c/ds18b20/ports/stm32g4/onewire_config.g431.template.h b/c/ds18b20/ports/stm32g4/onewire_config.g431.template.h new file mode 100644 index 0000000..4b02b91 --- /dev/null +++ b/c/ds18b20/ports/stm32g4/onewire_config.g431.template.h @@ -0,0 +1,26 @@ +/** + * @file onewire_config.g431.template.h + * @brief Шаблон привязки шины 1-Wire для STM32G431 (порт stm32g4). + * + * Скопируйте в своё приложение под именем onewire_config.h и положите на + * путь поиска заголовков. Библиотека сама этот файл не подставляет: + * привязка выводов принадлежит плате, а не драйверу. + * + * Требуется внешняя подтяжка 4.7 кОм к питанию. + */ + +#ifndef ONEWIRE_CONFIG_H +#define ONEWIRE_CONFIG_H + +#include "stm32g4xx.h" + +/** Порт вывода шины. */ +#define ONEWIRE_GPIO GPIOA + +/** Номер вывода в порту, 0..15. */ +#define ONEWIRE_PIN 1U + +/** Бит включения тактирования порта в RCC->AHB2ENR. */ +#define ONEWIRE_GPIO_CLOCK RCC_AHB2ENR_GPIOAEN + +#endif /* ONEWIRE_CONFIG_H */ diff --git a/c/ds18b20/ports/stm32g4/onewire_config.g474.template.h b/c/ds18b20/ports/stm32g4/onewire_config.g474.template.h new file mode 100644 index 0000000..c659d60 --- /dev/null +++ b/c/ds18b20/ports/stm32g4/onewire_config.g474.template.h @@ -0,0 +1,26 @@ +/** + * @file onewire_config.g474.template.h + * @brief Шаблон привязки шины 1-Wire для STM32G474 (порт stm32g4). + * + * Скопируйте в своё приложение под именем onewire_config.h и положите на + * путь поиска заголовков. Библиотека сама этот файл не подставляет: + * привязка выводов принадлежит плате, а не драйверу. + * + * Требуется внешняя подтяжка 4.7 кОм к питанию. + */ + +#ifndef ONEWIRE_CONFIG_H +#define ONEWIRE_CONFIG_H + +#include "stm32g4xx.h" + +/** Порт вывода шины. */ +#define ONEWIRE_GPIO GPIOA + +/** Номер вывода в порту, 0..15. */ +#define ONEWIRE_PIN 1U + +/** Бит включения тактирования порта в RCC->AHB2ENR. */ +#define ONEWIRE_GPIO_CLOCK RCC_AHB2ENR_GPIOAEN + +#endif /* ONEWIRE_CONFIG_H */ diff --git a/c/ds18b20/ports/stm32g4/onewire_stm32g4.c b/c/ds18b20/ports/stm32g4/onewire_stm32g4.c new file mode 100644 index 0000000..9506376 --- /dev/null +++ b/c/ds18b20/ports/stm32g4/onewire_stm32g4.c @@ -0,0 +1,172 @@ +/** + * @file onewire_stm32g4.c + * @brief Порт шины 1-Wire на STM32G4: тайминги, вывод и задержки. + * + * Подходит и STM32G431, и STM32G474 — периферия GPIO и шина тактирования + * у них одинаковы, различается только CMSIS-заголовок, который подставляет + * onewire_config.h. + * + * Вывод постоянно настроен как выход с открытым стоком: чтение уровня + * выполняется через регистр IDR, поэтому переключать режим не требуется. + * Прерывания запрещаются только на длительность одного тайм-слота, чтобы не + * терять байты USART во время обмена. + * + * Привязка вывода приходит из onewire_config.h, который предоставляет + * приложение. Шаблоны — onewire_config.g431.template.h и + * onewire_config.g474.template.h рядом с этим файлом. + */ + +#include "onewire.h" + +#include "onewire_config.h" + +#if !defined(ONEWIRE_GPIO) || !defined(ONEWIRE_PIN) || !defined(ONEWIRE_GPIO_CLOCK) +#error "onewire_config.h должен задать ONEWIRE_GPIO, ONEWIRE_PIN и ONEWIRE_GPIO_CLOCK" +#endif + +/** Длительности тайм-слотов в микросекундах согласно рекомендациям Maxim. */ +#define OW_RESET_LOW_US 480U +#define OW_PRESENCE_WAIT_US 70U +#define OW_RESET_TAIL_US 410U +#define OW_WRITE1_LOW_US 6U +#define OW_WRITE1_TAIL_US 64U +#define OW_WRITE0_LOW_US 60U +#define OW_WRITE0_TAIL_US 10U +#define OW_READ_LOW_US 6U +#define OW_READ_SAMPLE_US 9U +#define OW_READ_TAIL_US 55U + +/** Число тактов ядра на микросекунду; заполняется в OneWire_Init. */ +static uint32_t g_cycles_per_us = 170U; + +/** + * @brief Переводит вывод шины в активный низкий уровень. + */ +static void ow_drive_low(void) +{ + ONEWIRE_GPIO->BSRR = (uint32_t)(1UL << (ONEWIRE_PIN + 16U)); +} + +/** + * @brief Отпускает шину; уровень поднимает внешний резистор подтяжки. + */ +static void ow_release(void) +{ + ONEWIRE_GPIO->BSRR = (uint32_t)(1UL << ONEWIRE_PIN); +} + +/** + * @brief Читает текущий уровень на выводе шины. + * + * @return 1 для высокого уровня, 0 для низкого. + */ +static uint8_t ow_read_pin(void) +{ + return (uint8_t)((ONEWIRE_GPIO->IDR >> ONEWIRE_PIN) & 1U); +} + +void OneWire_DelayUs(uint32_t microseconds) +{ + const uint32_t start = DWT->CYCCNT; + const uint32_t ticks = microseconds * g_cycles_per_us; + + while ((DWT->CYCCNT - start) < ticks) { + __NOP(); + } +} + +void OneWire_Init(void) +{ + uint32_t config; + + g_cycles_per_us = SystemCoreClock / 1000000U; + if (g_cycles_per_us == 0U) { + g_cycles_per_us = 1U; + } + + /* Счётчик тактов ядра — единственный источник микросекундных задержек. */ + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CYCCNT = 0U; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + + RCC->AHB2ENR |= ONEWIRE_GPIO_CLOCK; + + /* Открытый сток: OTYPER = 1. */ + ONEWIRE_GPIO->OTYPER |= (uint32_t)(1UL << ONEWIRE_PIN); + + /* Подтяжка внешняя, внутренняя не нужна: PUPDR = 00. */ + config = ONEWIRE_GPIO->PUPDR; + config &= ~(0x3UL << (ONEWIRE_PIN * 2U)); + ONEWIRE_GPIO->PUPDR = config; + + /* Максимальная скорость фронта: OSPEEDR = 11. */ + config = ONEWIRE_GPIO->OSPEEDR; + config |= (0x3UL << (ONEWIRE_PIN * 2U)); + ONEWIRE_GPIO->OSPEEDR = config; + + /* Уровень отпускаем до перевода в выход, чтобы не дёрнуть шину вниз. */ + ow_release(); + + /* Обычный выход: MODER = 01. */ + config = ONEWIRE_GPIO->MODER; + config &= ~(0x3UL << (ONEWIRE_PIN * 2U)); + config |= (0x1UL << (ONEWIRE_PIN * 2U)); + ONEWIRE_GPIO->MODER = config; + + OneWire_DelayUs(OW_RESET_LOW_US); +} + +uint8_t OneWire_Reset(void) +{ + uint32_t primask; + uint8_t presence; + + /* Удлинение импульса сброса безопасно, поэтому прерывания не запрещаем. */ + ow_drive_low(); + OneWire_DelayUs(OW_RESET_LOW_US); + + primask = __get_PRIMASK(); + __disable_irq(); + ow_release(); + OneWire_DelayUs(OW_PRESENCE_WAIT_US); + presence = (uint8_t)(ow_read_pin() == 0U); + __set_PRIMASK(primask); + + OneWire_DelayUs(OW_RESET_TAIL_US); + return (uint8_t)(presence && (ow_read_pin() != 0U)); +} + +void OneWire_WriteBit(uint8_t bit) +{ + const uint32_t primask = __get_PRIMASK(); + + __disable_irq(); + ow_drive_low(); + if (bit != 0U) { + OneWire_DelayUs(OW_WRITE1_LOW_US); + ow_release(); + OneWire_DelayUs(OW_WRITE1_TAIL_US); + } else { + OneWire_DelayUs(OW_WRITE0_LOW_US); + ow_release(); + OneWire_DelayUs(OW_WRITE0_TAIL_US); + } + __set_PRIMASK(primask); +} + +uint8_t OneWire_ReadBit(void) +{ + const uint32_t primask = __get_PRIMASK(); + uint8_t bit; + + __disable_irq(); + ow_drive_low(); + OneWire_DelayUs(OW_READ_LOW_US); + ow_release(); + OneWire_DelayUs(OW_READ_SAMPLE_US); + bit = ow_read_pin(); + __set_PRIMASK(primask); + + OneWire_DelayUs(OW_READ_TAIL_US); + return bit; +}