feat(can-sensor): add STM32F1 bxCAN port

This commit is contained in:
2026-08-31 18:50:52 +03:00
parent 648457e587
commit de180750aa
6 changed files with 453 additions and 2 deletions

View File

@@ -28,7 +28,7 @@ templates/
| [`c/keypad`](c/keypad) | шесть кнопок: антидребезг, автоповтор, удержание, очередь событий | `stdint.h` | чтение уровня кнопки, время в мс | | [`c/keypad`](c/keypad) | шесть кнопок: антидребезг, автоповтор, удержание, очередь событий | `stdint.h` | чтение уровня кнопки, время в мс |
| [`c/menu`](c/menu) | экранное меню: стек экранов, курсор, прокрутка, тема | `stdint.h` | заливка прямоугольника, вывод строки | | [`c/menu`](c/menu) | экранное меню: стек экранов, курсор, прокрутка, тема | `stdint.h` | заливка прямоугольника, вывод строки |
| [`c/eeprom-ft24c256`](c/eeprom-ft24c256) | EEPROM 24Cxx по I²C с нарезкой записи по страницам | `stdint.h` | две I²C-транзакции, задержка | | [`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/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-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 | | [`c/protocan-boot`](c/protocan-boot) | адресная прошивка по ProtoCAN: A/B-слоты, сессия, CRC32, verify и rollback-контракт | C99 | CAN TX, erase/write Flash, boot metadata, проверка образа и reboot |

View File

@@ -20,6 +20,7 @@
| Файл | Что делает | Зависимости | | Файл | Что делает | Зависимости |
|---|---|---| |---|---|---|
| `can_sensor.h`, `can_sensor.c` | сборка и разбор SETTINGS, повторы передачи, счётчики обмена | `stdint.h` | | `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 очищает локацию. `ZZ` — номер сборки, `YY` — позиция. Нулевой ROM с DLC=8 очищает локацию.
@@ -47,6 +48,30 @@ CanSensor_Message message;
if (CanSensor_Poll(&link, &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): `KONOR_ds18b20` — bxCAN на STM32F103C8T6. Естественная пара — [`ds18b20`](../ds18b20):

View File

@@ -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);
}

View File

@@ -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 <stdint.h>
#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 */

View File

@@ -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 */

File diff suppressed because one or more lines are too long