diff --git a/README.md b/README.md index 9d7d80b..74e5205 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ templates/ | [`c/keypad`](c/keypad) | шесть кнопок: антидребезг, автоповтор, удержание, очередь событий | `stdint.h` | чтение уровня кнопки, время в мс | | [`c/menu`](c/menu) | экранное меню: стек экранов, курсор, прокрутка, тема | `stdint.h` | заливка прямоугольника, вывод строки | | [`c/eeprom-ft24c256`](c/eeprom-ft24c256) | EEPROM 24Cxx по I²C с нарезкой записи по страницам | `stdint.h` | две I²C-транзакции, задержка | -| [`c/can-sensor`](c/can-sensor) | передача 64-битных ROM датчиков парой CAN-кадров | `stdint.h` | отправка и приём CAN-кадра | +| [`c/can-sensor`](c/can-sensor) | однокадровые SETCAN SETTINGS для 64-битных ROM | ядро: `stdint.h`; порт F1: CMSIS | callbacks либо готовый bxCAN STM32F1 | | [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порты STM32F103, STM32G431 и STM32G474 в комплекте** | | [`c/protocan-transport`](c/protocan-transport) | транспорт ProtoCAN: кадр, канал, CRC, общее адресное пространство, каталог GUI | `stdint.h` | запись в поток и запрос свободного места — **порт STM32F4 в комплекте** | | [`c/protocan-boot`](c/protocan-boot) | адресная прошивка по ProtoCAN: A/B-слоты, сессия, CRC32, verify и rollback-контракт | C99 | CAN TX, erase/write Flash, boot metadata, проверка образа и reboot | diff --git a/c/can-sensor/README.md b/c/can-sensor/README.md index 5b0b05b..4fbbe07 100644 --- a/c/can-sensor/README.md +++ b/c/can-sensor/README.md @@ -20,6 +20,7 @@ | Файл | Что делает | Зависимости | |---|---|---| | `can_sensor.h`, `can_sensor.c` | сборка и разбор SETTINGS, повторы передачи, счётчики обмена | `stdint.h` | +| `ports/stm32f1/` | опросный порт CAN1: GPIO, BTR, фильтр, mailbox/FIFO, тайм-аут ACK | CMSIS `stm32f10x.h` | `ZZ` — номер сборки, `YY` — позиция. Нулевой ROM с DLC=8 очищает локацию. @@ -47,6 +48,30 @@ CanSensor_Message message; if (CanSensor_Poll(&link, &message)) { /* принят идентификатор */ } ``` +## Порт STM32F1 + +Порт не использует STM32 HAL и не занимает прерывания. Скопируйте +`ports/stm32f1/can_sensor_stm32f1_config.f103.template.h` в приложение под +именем `can_sensor_stm32f1_config.h`, добавьте `ports/stm32f1` в include path и +соберите `can_sensor_stm32f1.c` вместе с ядром: + +```c +CanSensorStm32F1_Port bxcan = {0}; +CanSensor_Io io; + +CanSensorStm32F1_Start(&bxcan, 500000U, 0x17F00000UL, 0x1FF00000UL, 0U); +io = CanSensorStm32F1_MakeIo(&bxcan); +CanSensor_Init(&link, &io, &config); + +/* Из главного цикла: */ +CanSensorStm32F1_Task(&bxcan, now_ms, 20U); +``` + +Фильтр порта принимает только Extended data-кадры. Маска задаётся приложением: +можно пропустить только SETTINGS либо оставить открытым `MsgType` для соседних +команд того же узла. `CanSensorStm32F1_Receive()` доступна отдельно, если перед +`CanSensor_HandleFrame()` приложению нужно разобрать такие команды самому. + ## Проверено в проектах `KONOR_ds18b20` — bxCAN на STM32F103C8T6. Естественная пара — [`ds18b20`](../ds18b20): diff --git a/c/can-sensor/ports/stm32f1/can_sensor_stm32f1.c b/c/can-sensor/ports/stm32f1/can_sensor_stm32f1.c new file mode 100644 index 0000000..936d94f --- /dev/null +++ b/c/can-sensor/ports/stm32f1/can_sensor_stm32f1.c @@ -0,0 +1,330 @@ +/** + * @file can_sensor_stm32f1.c + * @brief Опросный порт can-sensor на bxCAN STM32F1 без STM32 HAL. + */ + +#include "can_sensor_stm32f1.h" + +#include "can_sensor_stm32f1_config.h" + +#if !defined(CAN_SENSOR_STM32F1_GPIO) \ + || !defined(CAN_SENSOR_STM32F1_GPIO_CLOCK) \ + || !defined(CAN_SENSOR_STM32F1_RX_PIN) \ + || !defined(CAN_SENSOR_STM32F1_TX_PIN) +#error "can_sensor_stm32f1_config.h должен задать GPIO, GPIO_CLOCK, RX_PIN и TX_PIN" +#endif + +#define CAN_STM32F1_TQ_MAX 18U +#define CAN_STM32F1_TQ_MIN 8U +#define CAN_STM32F1_TIMEOUT_CYCLES 1000000U +#define CAN_STM32F1_TSR_ALL_EMPTY (CAN_TSR_TME0 | CAN_TSR_TME1 | CAN_TSR_TME2) +#define CAN_STM32F1_TSR_ALL_ABORT (CAN_TSR_ABRQ0 | CAN_TSR_ABRQ1 | CAN_TSR_ABRQ2) +#define CAN_STM32F1_TSR_ANY_TXOK (CAN_TSR_TXOK0 | CAN_TSR_TXOK1 | CAN_TSR_TXOK2) +#define CAN_STM32F1_TSR_DONE_FLAGS \ + (CAN_TSR_RQCP0 | CAN_TSR_TXOK0 | CAN_TSR_ALST0 | CAN_TSR_TERR0 \ + | CAN_TSR_RQCP1 | CAN_TSR_TXOK1 | CAN_TSR_ALST1 | CAN_TSR_TERR1 \ + | CAN_TSR_RQCP2 | CAN_TSR_TXOK2 | CAN_TSR_ALST2 | CAN_TSR_TERR2) + +static uint32_t can_stm32f1_pclk1(void) +{ + static const uint8_t shift[8] = { 0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U }; + const uint32_t bits = (RCC->CFGR & RCC_CFGR_PPRE1) >> 8U; + + return SystemCoreClock >> shift[bits & 0x7U]; +} + +static void can_stm32f1_configure_pin(GPIO_TypeDef *gpio, uint8_t pin, + uint32_t mode) +{ + uint32_t config; + uint32_t shift; + + if (pin < 8U) { + shift = (uint32_t)pin * 4U; + config = gpio->CRL; + config = (config & ~(0xFUL << shift)) | (mode << shift); + gpio->CRL = config; + } else { + shift = ((uint32_t)pin - 8U) * 4U; + config = gpio->CRH; + config = (config & ~(0xFUL << shift)) | (mode << shift); + gpio->CRH = config; + } +} + +static void can_stm32f1_configure_pins(void) +{ + RCC->APB2ENR |= CAN_SENSOR_STM32F1_GPIO_CLOCK | RCC_APB2ENR_AFIOEN; + /* RX: input pull-up; TX: alternate function push-pull, 50 MHz. */ + can_stm32f1_configure_pin(CAN_SENSOR_STM32F1_GPIO, + CAN_SENSOR_STM32F1_RX_PIN, 0x8UL); + can_stm32f1_configure_pin(CAN_SENSOR_STM32F1_GPIO, + CAN_SENSOR_STM32F1_TX_PIN, 0xBUL); + CAN_SENSOR_STM32F1_GPIO->BSRR = + (uint32_t)(1UL << CAN_SENSOR_STM32F1_RX_PIN); +} + +static uint8_t can_stm32f1_calc_timing(uint32_t bitrate, uint32_t *btr, + CanSensorStm32F1_Timing *timing) +{ + const uint32_t pclk = can_stm32f1_pclk1(); + uint32_t total; + uint32_t tq; + + if ((bitrate == 0U) || (btr == 0) || (pclk == 0U) + || ((pclk % bitrate) != 0U)) { + return 0U; + } + total = pclk / bitrate; + for (tq = CAN_STM32F1_TQ_MAX; tq >= CAN_STM32F1_TQ_MIN; tq--) { + const uint32_t prescaler = total / tq; + + if (((total % tq) == 0U) && (prescaler >= 1U) + && (prescaler <= 1024U)) { + const uint32_t ts2 = tq / 5U; + const uint32_t ts1 = tq - 1U - ts2; + + *btr = ((ts2 - 1U) << 20U) | ((ts1 - 1U) << 16U) + | (prescaler - 1U); + if (timing != 0) { + timing->pclk_hz = pclk; + timing->prescaler = (uint16_t)prescaler; + timing->ts1 = (uint8_t)ts1; + timing->ts2 = (uint8_t)ts2; + timing->bitrate = pclk / (prescaler * tq); + timing->sample_point = + (uint8_t)(((1U + ts1) * 100U) / tq); + } + return 1U; + } + } + return 0U; +} + +uint8_t CanSensorStm32F1_CalcTiming(uint32_t bitrate, + CanSensorStm32F1_Timing *timing) +{ + uint32_t btr; + + return can_stm32f1_calc_timing(bitrate, &btr, timing); +} + +static void can_stm32f1_configure_filter(uint32_t id, uint32_t mask) +{ + const uint32_t filter_id = ((id & mask) << 3U) | CAN_RI0R_IDE; + const uint32_t filter_mask = + (mask << 3U) | CAN_RI0R_IDE | CAN_RI0R_RTR; + + CAN1->FMR |= CAN_FMR_FINIT; + CAN1->FA1R &= ~1UL; + CAN1->FS1R |= 1UL; + CAN1->FM1R &= ~1UL; + CAN1->sFilterRegister[0].FR1 = filter_id; + CAN1->sFilterRegister[0].FR2 = filter_mask; + CAN1->FFA1R &= ~1UL; + CAN1->FA1R |= 1UL; + CAN1->FMR &= ~CAN_FMR_FINIT; +} + +static void can_stm32f1_load_mailbox(uint32_t mailbox, + const CanSensor_Frame *frame) +{ + uint32_t identifier; + uint8_t index; + + if (frame->extended != 0U) { + identifier = ((frame->id & 0x1FFFFFFFUL) << 3U) | CAN_TI0R_IDE; + } else { + identifier = (frame->id & 0x7FFUL) << 21U; + } + CAN1->sTxMailBox[mailbox].TDTR = (uint32_t)(frame->length & 0x0FU); + CAN1->sTxMailBox[mailbox].TDLR = 0U; + CAN1->sTxMailBox[mailbox].TDHR = 0U; + for (index = 0U; index < frame->length; index++) { + if (index < 4U) { + CAN1->sTxMailBox[mailbox].TDLR |= + (uint32_t)frame->data[index] << (index * 8U); + } else { + CAN1->sTxMailBox[mailbox].TDHR |= + (uint32_t)frame->data[index] << ((index - 4U) * 8U); + } + } + CAN1->sTxMailBox[mailbox].TIR = identifier | CAN_TI0R_TXRQ; +} + +uint8_t CanSensorStm32F1_Start(CanSensorStm32F1_Port *port, + uint32_t bitrate, uint32_t filter_id, + uint32_t filter_mask, uint8_t loopback) +{ + uint32_t btr; + uint32_t guard; + uint32_t mode_bits; + + if ((port == 0) || (filter_id > 0x1FFFFFFFUL) + || (filter_mask > 0x1FFFFFFFUL) + || (can_stm32f1_calc_timing(bitrate, &btr, &port->timing) == 0U)) { + return 0U; + } + CanSensorStm32F1_Stop(port); + can_stm32f1_configure_pins(); + RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; + + CAN1->MCR &= ~CAN_MCR_SLEEP; + CAN1->MCR |= CAN_MCR_INRQ; + for (guard = 0U; guard < CAN_STM32F1_TIMEOUT_CYCLES; guard++) { + if ((CAN1->MSR & CAN_MSR_INAK) != 0U) { + break; + } + } + if ((CAN1->MSR & CAN_MSR_INAK) == 0U) { + return 0U; + } + + mode_bits = (loopback != 0U) ? (CAN_BTR_LBKM | CAN_BTR_SILM) : 0U; + CAN1->MCR = CAN_MCR_INRQ | CAN_MCR_ABOM | CAN_MCR_TXFP; + CAN1->BTR = btr | mode_bits; + can_stm32f1_configure_filter(filter_id, filter_mask); + CAN1->MCR &= ~CAN_MCR_INRQ; + for (guard = 0U; guard < CAN_STM32F1_TIMEOUT_CYCLES; guard++) { + if ((CAN1->MSR & CAN_MSR_INAK) == 0U) { + break; + } + } + if ((CAN1->MSR & CAN_MSR_INAK) != 0U) { + return 0U; + } + + port->bitrate = bitrate; + port->loopback = (uint8_t)(loopback != 0U); + port->no_transceiver = 0U; + port->tx_waiting = 0U; + port->ready = 1U; + return 1U; +} + +void CanSensorStm32F1_Stop(CanSensorStm32F1_Port *port) +{ + if (port == 0) { + return; + } + RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; + RCC->APB1RSTR |= RCC_APB1RSTR_CAN1RST; + RCC->APB1RSTR &= ~RCC_APB1RSTR_CAN1RST; + port->ready = 0U; + port->bitrate = 0U; + port->loopback = 0U; + port->no_transceiver = 0U; + port->tx_waiting = 0U; +} + +uint8_t CanSensorStm32F1_Send(void *context, const CanSensor_Frame *frame) +{ + CanSensorStm32F1_Port *port = (CanSensorStm32F1_Port *)context; + uint32_t mailbox; + + if ((port == 0) || (frame == 0) || (port->ready == 0U) + || (frame->length > CAN_SENSOR_MAX_DATA)) { + return 0U; + } + if ((CAN1->TSR & CAN_TSR_TME0) != 0U) { + mailbox = 0U; + } else if ((CAN1->TSR & CAN_TSR_TME1) != 0U) { + mailbox = 1U; + } else if ((CAN1->TSR & CAN_TSR_TME2) != 0U) { + mailbox = 2U; + } else { + return 0U; + } + can_stm32f1_load_mailbox(mailbox, frame); + return 1U; +} + +uint8_t CanSensorStm32F1_Receive(void *context, CanSensor_Frame *frame) +{ + CanSensorStm32F1_Port *port = (CanSensorStm32F1_Port *)context; + uint32_t identifier; + uint32_t low; + uint32_t high; + uint8_t index; + + if ((port == 0) || (frame == 0) || (port->ready == 0U) + || ((CAN1->RF0R & CAN_RF0R_FMP0) == 0U)) { + return 0U; + } + identifier = CAN1->sFIFOMailBox[0].RIR; + if ((identifier & CAN_RI0R_IDE) != 0U) { + frame->extended = 1U; + frame->id = (identifier >> 3U) & 0x1FFFFFFFUL; + } else { + frame->extended = 0U; + frame->id = (identifier >> 21U) & 0x7FFUL; + } + frame->length = (uint8_t)(CAN1->sFIFOMailBox[0].RDTR & 0x0FU); + if (frame->length > CAN_SENSOR_MAX_DATA) { + frame->length = CAN_SENSOR_MAX_DATA; + } + low = CAN1->sFIFOMailBox[0].RDLR; + high = CAN1->sFIFOMailBox[0].RDHR; + for (index = 0U; index < CAN_SENSOR_MAX_DATA; index++) { + if (index < 4U) { + frame->data[index] = + (uint8_t)((low >> (index * 8U)) & 0xFFU); + } else { + frame->data[index] = + (uint8_t)((high >> ((index - 4U) * 8U)) & 0xFFU); + } + } + CAN1->RF0R |= CAN_RF0R_RFOM0; + return 1U; +} + +CanSensor_Io CanSensorStm32F1_MakeIo(CanSensorStm32F1_Port *port) +{ + CanSensor_Io io; + + io.send = CanSensorStm32F1_Send; + io.receive = CanSensorStm32F1_Receive; + io.context = port; + return io; +} + +void CanSensorStm32F1_Task(CanSensorStm32F1_Port *port, uint32_t now_ms, + uint32_t timeout_ms) +{ + uint32_t status; + + if ((port == 0) || (port->ready == 0U)) { + return; + } + status = CAN1->TSR; + if ((status & CAN_STM32F1_TSR_ALL_EMPTY) == CAN_STM32F1_TSR_ALL_EMPTY) { + if ((status & CAN_STM32F1_TSR_ANY_TXOK) != 0U) { + port->no_transceiver = 0U; + } + CAN1->TSR = status & CAN_STM32F1_TSR_DONE_FLAGS; + port->tx_waiting = 0U; + return; + } + if ((timeout_ms == 0U) || (port->tx_waiting == 0U)) { + port->tx_waiting = (uint8_t)(timeout_ms != 0U); + port->tx_started_ms = now_ms; + return; + } + if ((now_ms - port->tx_started_ms) < timeout_ms) { + return; + } + CAN1->TSR = CAN_STM32F1_TSR_ALL_ABORT; + CAN1->TSR = CAN_STM32F1_TSR_DONE_FLAGS; + port->tx_waiting = 0U; + port->tx_timeouts++; + port->no_transceiver = 1U; +} + +uint8_t CanSensorStm32F1_BusError(const CanSensorStm32F1_Port *port) +{ + if ((port == 0) || (port->ready == 0U)) { + return 0U; + } + return (uint8_t)(((CAN1->ESR & (CAN_ESR_BOFF | CAN_ESR_EPVF)) != 0U) + ? 1U : 0U); +} diff --git a/c/can-sensor/ports/stm32f1/can_sensor_stm32f1.h b/c/can-sensor/ports/stm32f1/can_sensor_stm32f1.h new file mode 100644 index 0000000..a0a7f81 --- /dev/null +++ b/c/can-sensor/ports/stm32f1/can_sensor_stm32f1.h @@ -0,0 +1,77 @@ +/** + * @file can_sensor_stm32f1.h + * @brief Опросный порт can-sensor на bxCAN микроконтроллеров STM32F1. + */ + +#ifndef CAN_SENSOR_STM32F1_H +#define CAN_SENSOR_STM32F1_H + +#include + +#include "can_sensor.h" + +/** Разрядность бита, записанная портом в регистр BTR. */ +typedef struct { + uint32_t pclk_hz; + uint32_t bitrate; + uint16_t prescaler; + uint8_t ts1; + uint8_t ts2; + uint8_t sample_point; +} CanSensorStm32F1_Timing; + +/** Состояние одного контроллера bxCAN. Поля доступны только для диагностики. */ +typedef struct { + uint32_t bitrate; + uint32_t tx_timeouts; + uint32_t tx_started_ms; + CanSensorStm32F1_Timing timing; + uint8_t ready; + uint8_t loopback; + uint8_t no_transceiver; + uint8_t tx_waiting; +} CanSensorStm32F1_Port; + +/** + * @brief Проверяет достижимость скорости и рассчитывает поля BTR. + * @param bitrate Требуемая скорость, бит/с. + * @param timing Приёмник результата либо 0. + * @return 1, если скорость получается из текущей частоты APB1 без ошибки. + */ +uint8_t CanSensorStm32F1_CalcTiming(uint32_t bitrate, + CanSensorStm32F1_Timing *timing); + +/** + * @brief Запускает CAN1, настраивает выводы и один 32-битный фильтр FIFO 0. + * + * Фильтр сравнивает Extended ID по формуле `(id & filter_mask) == + * (filter_id & filter_mask)` и отбрасывает Standard и Remote кадры. + */ +uint8_t CanSensorStm32F1_Start(CanSensorStm32F1_Port *port, + uint32_t bitrate, uint32_t filter_id, + uint32_t filter_mask, uint8_t loopback); + +/** Останавливает CAN1 программным сбросом. */ +void CanSensorStm32F1_Stop(CanSensorStm32F1_Port *port); + +/** Помещает data-кадр в свободный mailbox. Совместима с CanSensor_Io.send. */ +uint8_t CanSensorStm32F1_Send(void *context, const CanSensor_Frame *frame); + +/** Забирает один data-кадр из FIFO 0. Совместима с CanSensor_Io.receive. */ +uint8_t CanSensorStm32F1_Receive(void *context, CanSensor_Frame *frame); + +/** Собирает таблицу callback-функций для CanSensor_Init(). */ +CanSensor_Io CanSensorStm32F1_MakeIo(CanSensorStm32F1_Port *port); + +/** + * @brief Обслуживает завершение и тайм-аут передачи без прерываний. + * @param now_ms Монотонное время приложения, мс. + * @param timeout_ms Предел ожидания ACK; 0 отключает принудительную отмену. + */ +void CanSensorStm32F1_Task(CanSensorStm32F1_Port *port, uint32_t now_ms, + uint32_t timeout_ms); + +/** Возвращает 1 при bus-off или error-passive. */ +uint8_t CanSensorStm32F1_BusError(const CanSensorStm32F1_Port *port); + +#endif /* CAN_SENSOR_STM32F1_H */ diff --git a/c/can-sensor/ports/stm32f1/can_sensor_stm32f1_config.f103.template.h b/c/can-sensor/ports/stm32f1/can_sensor_stm32f1_config.f103.template.h new file mode 100644 index 0000000..a427612 --- /dev/null +++ b/c/can-sensor/ports/stm32f1/can_sensor_stm32f1_config.f103.template.h @@ -0,0 +1,19 @@ +/** + * @file can_sensor_stm32f1_config.f103.template.h + * @brief Шаблон выводов CAN1 для STM32F103 без ремапа. + * + * Скопируйте файл в include-каталог приложения под именем + * can_sensor_stm32f1_config.h. Скорость и фильтр задаются при запуске порта. + */ + +#ifndef CAN_SENSOR_STM32F1_CONFIG_H +#define CAN_SENSOR_STM32F1_CONFIG_H + +#include "stm32f10x.h" + +#define CAN_SENSOR_STM32F1_GPIO GPIOA +#define CAN_SENSOR_STM32F1_GPIO_CLOCK RCC_APB2ENR_IOPAEN +#define CAN_SENSOR_STM32F1_RX_PIN 11U +#define CAN_SENSOR_STM32F1_TX_PIN 12U + +#endif /* CAN_SENSOR_STM32F1_CONFIG_H */ diff --git a/doc/index.html b/doc/index.html index 140eba5..1d51527 100644 --- a/doc/index.html +++ b/doc/index.html @@ -15,7 +15,7 @@ git subtree pull --prefix lib/templates https://git.rd12.ru/Andrey/templates.git

keypad

Шесть кнопок: антидребезг, удержание, автоповтор и очередь событий.

GPIOevents

Порт: чтение уровня и время в миллисекундах.

menu

Стек экранов, курсор, прокрутка и тема для динамического меню.

UIpainter

Порт: fill_rect и draw_text.

eeprom-ft24c256

EEPROM 24Cxx с нарезкой записи по страницам.

I²Cstorage

Порт: I²C write, write-read и delay.

-

can-sensor

Передача 64-битных ROM датчиков парой CAN-кадров.

CANsensor

Порт: CAN TX/RX.

+

can-sensor

Однокадровые SETCAN SETTINGS для 64-битных ROM датчиков.

CANsensor

Порт: готовый bxCAN STM32F1 либо callbacks CAN TX/RX.

ds18b20

1-Wire, CRC8, SEARCH ROM и несколько термометров.

1-Wireпорты F1/G4/F4

Порт: Init, DelayUs, Reset, WriteBit, ReadBit.

protocan-transport

ProtoCAN над байтовым потоком: CRC16, ring, GAS и каталог GUI.

UART/USBпорт STM32F4

Порт: stream write и доступный объём TX.

protocan-boot

Прошивка по ProtoCAN: A/B-слоты, resume, CRC32, verify и rollback.

CANfirmware

Порт: CAN TX, Flash, metadata, verify, reboot.