Compare commits
1 Commits
codex/ump-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 795a1279b1 |
9
c/candle/CMakeLists.txt
Normal file
9
c/candle/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(candle C)
|
||||
if(NOT WIN32)
|
||||
message(FATAL_ERROR "Candle transport requires Windows WinUSB")
|
||||
endif()
|
||||
add_library(candle SHARED candle.c candle_ctrl_req.c candle.def)
|
||||
target_compile_definitions(candle PRIVATE UNICODE _UNICODE)
|
||||
target_link_libraries(candle PRIVATE setupapi winusb ole32 advapi32)
|
||||
target_include_directories(candle PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
66
c/candle/LICENSE
Normal file
66
c/candle/LICENSE
Normal file
File diff suppressed because one or more lines are too long
16
c/candle/README.md
Normal file
16
c/candle/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Candle / gs_usb — порт WinUSB
|
||||
|
||||
Общий C-порт для адаптеров candleLight/gs_usb. Перенесён из
|
||||
SETGUI `src/gui_desktop/native/candle_src`; исходные файлы и LGPLv3
|
||||
`LICENSE` сохранены без изменений.
|
||||
|
||||
Сборка из Developer Command Prompt:
|
||||
|
||||
```bat
|
||||
cmake -S c/candle -B build/candle -A x64
|
||||
cmake --build build/candle --config Release
|
||||
```
|
||||
|
||||
Для 32-битной библиотеки используйте `-A Win32` и отдельный каталог сборки.
|
||||
Python Qt-порт находится в `python/set_devices/qt_ports/candle_adapter.py`;
|
||||
путь к библиотеке задаётся через `CANDLE_LIBRARY`.
|
||||
1151
c/candle/candle.c
Normal file
1151
c/candle/candle.c
Normal file
@@ -0,0 +1,1151 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016 Hubert Denkmair <hubert@denkmair.de>
|
||||
|
||||
This file is part of the candle windows API.
|
||||
|
||||
This library is free software: you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation, either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#include "candle.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "candle_defs.h"
|
||||
#include "candle_ctrl_req.h"
|
||||
#include "ch_9.h"
|
||||
|
||||
static bool candle_dev_interal_open(candle_handle hdev);
|
||||
|
||||
candle_log_fn_t candle_log_fn = NULL;
|
||||
bool candle_log_verbose = false;
|
||||
|
||||
static void candle_logf(const wchar_t *fmt, ...)
|
||||
{
|
||||
if (candle_log_fn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t buf[512];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
HRESULT hr = StringCchVPrintfW(buf, 512, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
candle_log_fn(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void candle_logf_verbose(const wchar_t *fmt, ...)
|
||||
{
|
||||
if (candle_log_fn == NULL || !candle_log_verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t buf[512];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
HRESULT hr = StringCchVPrintfW(buf, 512, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
candle_log_fn(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static bool candle_read_di(HDEVINFO hdi, SP_DEVICE_INTERFACE_DATA interfaceData, candle_device_t *dev)
|
||||
{
|
||||
/* get required length first (this call always fails with an error) */
|
||||
ULONG requiredLength=0;
|
||||
SetupDiGetDeviceInterfaceDetail(hdi, &interfaceData, NULL, 0, &requiredLength, NULL);
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||
dev->last_error = CANDLE_ERR_SETUPDI_IF_DETAILS;
|
||||
return false;
|
||||
}
|
||||
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data =
|
||||
(PSP_DEVICE_INTERFACE_DETAIL_DATA) LocalAlloc(LMEM_FIXED, requiredLength);
|
||||
|
||||
if (detail_data != NULL) {
|
||||
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||
} else {
|
||||
dev->last_error = CANDLE_ERR_MALLOC;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool retval = true;
|
||||
ULONG length = requiredLength;
|
||||
if (!SetupDiGetDeviceInterfaceDetail(hdi, &interfaceData, detail_data, length, &requiredLength, NULL) ) {
|
||||
dev->last_error = CANDLE_ERR_SETUPDI_IF_DETAILS2;
|
||||
retval = false;
|
||||
} else if (FAILED(StringCchCopy(dev->path, sizeof(dev->path), detail_data->DevicePath))) {
|
||||
dev->last_error = CANDLE_ERR_PATH_LEN;
|
||||
retval = false;
|
||||
}
|
||||
|
||||
LocalFree(detail_data);
|
||||
|
||||
if (!retval) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* try to open to read device infos and see if it is avail */
|
||||
if (candle_dev_interal_open(dev)) {
|
||||
dev->state = CANDLE_DEVSTATE_AVAIL;
|
||||
candle_dev_close(dev);
|
||||
} else {
|
||||
dev->state = CANDLE_DEVSTATE_INUSE;
|
||||
}
|
||||
|
||||
dev->last_error = CANDLE_ERR_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return true when path already appears in l->dev[0..count-1]. */
|
||||
static bool candle_path_exists(const candle_list_t *l, unsigned count, const wchar_t *path)
|
||||
{
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
if (wcscmp(l->dev[i].path, path) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Scan one GUID and append found devices to l->dev[] starting at offset.
|
||||
* Returns the number of devices appended, or -1 on a hard error (l->last_error set). */
|
||||
static int candle_scan_guid(candle_list_t *l, const wchar_t *guid_str, unsigned offset)
|
||||
{
|
||||
GUID guid;
|
||||
if (CLSIDFromString(guid_str, &guid) != NOERROR) {
|
||||
l->last_error = CANDLE_ERR_CLSID;
|
||||
return -1;
|
||||
}
|
||||
|
||||
HDEVINFO hdi = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (hdi == INVALID_HANDLE_VALUE) {
|
||||
/* No devices with this GUID present — not a hard error. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int found = 0;
|
||||
for (unsigned i = 0; (offset + i) < CANDLE_MAX_DEVICES; i++) {
|
||||
SP_DEVICE_INTERFACE_DATA interfaceData;
|
||||
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
||||
|
||||
if (!SetupDiEnumDeviceInterfaces(hdi, NULL, &guid, i, &interfaceData)) {
|
||||
if (GetLastError() != ERROR_NO_MORE_ITEMS) {
|
||||
l->last_error = CANDLE_ERR_SETUPDI_IF_ENUM;
|
||||
found = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!candle_read_di(hdi, interfaceData, &l->dev[offset + i])) {
|
||||
l->last_error = l->dev[offset + i].last_error;
|
||||
found = -1;
|
||||
break;
|
||||
}
|
||||
found++;
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(hdi);
|
||||
return found;
|
||||
}
|
||||
|
||||
/* Scan for WinUSB devices matching vid:pid whose device interface GUID was not
|
||||
* covered by the GUID list above. For each matching USB device instance the
|
||||
* function reads DeviceInterfaceGUIDs (or DeviceInterfaceGUID) from the Windows
|
||||
* registry, re-uses candle_scan_guid() for each GUID found there, and appends
|
||||
* only those devices that are not already present in l->dev[0..existing-1].
|
||||
* Returns the number of new devices added. */
|
||||
static int candle_scan_vidpid(candle_list_t *l, uint16_t vid, uint16_t pid, unsigned existing)
|
||||
{
|
||||
wchar_t hwid_prefix[32];
|
||||
StringCchPrintfW(hwid_prefix, 32, L"USB\\VID_%04X&PID_%04X", vid, pid);
|
||||
|
||||
/* Enumerate USB device instances (not interfaces) so we can read hardware IDs. */
|
||||
HDEVINFO hdi = SetupDiGetClassDevs(NULL, L"USB", NULL,
|
||||
DIGCF_ALLCLASSES | DIGCF_PRESENT);
|
||||
if (hdi == INVALID_HANDLE_VALUE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int added = 0;
|
||||
SP_DEVINFO_DATA devInfo;
|
||||
devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||
|
||||
for (DWORD i = 0;
|
||||
SetupDiEnumDeviceInfo(hdi, i, &devInfo) && existing + added < CANDLE_MAX_DEVICES;
|
||||
i++)
|
||||
{
|
||||
|
||||
/* Hardware IDs are a REG_MULTI_SZ — check each string for our VID/PID prefix. */
|
||||
wchar_t hwids[512];
|
||||
memset(hwids, 0, sizeof(hwids));
|
||||
if (!SetupDiGetDeviceRegistryPropertyW(hdi, &devInfo, SPDRP_HARDWAREID,
|
||||
NULL, (PBYTE)hwids, sizeof(hwids) - sizeof(wchar_t), NULL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool matches = false;
|
||||
const wchar_t *p;
|
||||
for (p = hwids; *p; p += wcslen(p) + 1) {
|
||||
if (_wcsnicmp(p, hwid_prefix, wcslen(hwid_prefix)) == 0) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Open the device's software registry key (Device Parameters) and read
|
||||
* the WinUSB device interface GUID(s) stored by the driver INF. */
|
||||
HKEY hKey = SetupDiOpenDevRegKey(hdi, &devInfo, DICS_FLAG_GLOBAL, 0,
|
||||
DIREG_DEV, KEY_READ);
|
||||
if (hKey == INVALID_HANDLE_VALUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wchar_t guid_buf[256];
|
||||
memset(guid_buf, 0, sizeof(guid_buf));
|
||||
DWORD buf_len = sizeof(guid_buf) - sizeof(wchar_t);
|
||||
|
||||
/* Prefer DeviceInterfaceGUIDs (REG_MULTI_SZ, modern INFs); fall back to
|
||||
* DeviceInterfaceGUID (REG_SZ, older/zadig-generated INFs). */
|
||||
LONG reg_rc = RegQueryValueExW(hKey, L"DeviceInterfaceGUIDs", NULL, NULL,
|
||||
(LPBYTE)guid_buf, &buf_len);
|
||||
if (reg_rc != ERROR_SUCCESS) {
|
||||
buf_len = sizeof(guid_buf) - sizeof(wchar_t);
|
||||
RegQueryValueExW(hKey, L"DeviceInterfaceGUID", NULL, NULL,
|
||||
(LPBYTE)guid_buf, &buf_len);
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
|
||||
if (!guid_buf[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Iterate GUID strings. Both REG_SZ and REG_MULTI_SZ are covered by the
|
||||
* same NUL-terminated-string walk (REG_SZ just has one entry). */
|
||||
const wchar_t *g;
|
||||
for (g = guid_buf;
|
||||
*g && existing + added < CANDLE_MAX_DEVICES;
|
||||
g += wcslen(g) + 1)
|
||||
{
|
||||
unsigned base = existing + added;
|
||||
int n = candle_scan_guid(l, g, base);
|
||||
if (n <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Remove any entries whose path was already found by the GUID scan. */
|
||||
for (int ni = 0; ni < n; ) {
|
||||
if (candle_path_exists(l, base, l->dev[base + ni].path)) {
|
||||
memmove(&l->dev[base + ni], &l->dev[base + ni + 1],
|
||||
(unsigned)(n - ni - 1) * sizeof(candle_device_t));
|
||||
n--;
|
||||
} else {
|
||||
ni++;
|
||||
}
|
||||
}
|
||||
added += n;
|
||||
}
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(hdi);
|
||||
return added;
|
||||
}
|
||||
|
||||
bool __stdcall candle_list_scan(candle_list_handle *list)
|
||||
{
|
||||
if (list == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
candle_list_t *l = (candle_list_t *)calloc(1, sizeof(candle_list_t));
|
||||
*list = l;
|
||||
if (l == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* GUIDs for gs_usb-compatible devices on Windows.
|
||||
* candleLight / CANable / most gs_usb devices: */
|
||||
static const wchar_t *GUIDS[] = {
|
||||
L"{c15b4308-04d3-11e6-b3ea-6057189e6443}" /* candleLight / CANable / gs_usb standard */
|
||||
};
|
||||
static const unsigned NUM_GUIDS = sizeof(GUIDS) / sizeof(GUIDS[0]);
|
||||
|
||||
unsigned total = 0;
|
||||
for (unsigned g = 0; g < NUM_GUIDS; g++) {
|
||||
int n = candle_scan_guid(l, GUIDS[g], total);
|
||||
if (n < 0) {
|
||||
return false;
|
||||
}
|
||||
total += (unsigned)n;
|
||||
}
|
||||
|
||||
/* VID/PID scan for devices whose device interface GUID is not in the list
|
||||
* above (e.g. CANnectivity which uses its own registered interface GUID). */
|
||||
static const struct { uint16_t vid; uint16_t pid; } VIDPIDS[] = {
|
||||
{ 0x1209, 0xCA01 }, /* CANnectivity (electronut-labs) */
|
||||
};
|
||||
static const unsigned NUM_VIDPIDS = sizeof(VIDPIDS) / sizeof(VIDPIDS[0]);
|
||||
|
||||
for (unsigned v = 0; v < NUM_VIDPIDS && total < CANDLE_MAX_DEVICES; v++) {
|
||||
int n = candle_scan_vidpid(l, VIDPIDS[v].vid, VIDPIDS[v].pid, total);
|
||||
if (n > 0)
|
||||
total += (unsigned)n;
|
||||
}
|
||||
|
||||
l->num_devices = (uint8_t)total;
|
||||
l->last_error = CANDLE_ERR_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_list_free(candle_list_handle list)
|
||||
{
|
||||
free(list);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_list_length(candle_list_handle list, uint8_t *len)
|
||||
{
|
||||
candle_list_t *l = (candle_list_t *)list;
|
||||
*len = l->num_devices;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_dev_get(candle_list_handle list, uint8_t dev_num, candle_handle *hdev)
|
||||
{
|
||||
candle_list_t *l = (candle_list_t *)list;
|
||||
if (l==NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dev_num >= CANDLE_MAX_DEVICES) {
|
||||
l->last_error = CANDLE_ERR_DEV_OUT_OF_RANGE;
|
||||
return false;
|
||||
}
|
||||
|
||||
candle_device_t *dev = calloc(1, sizeof(candle_device_t));
|
||||
*hdev = dev;
|
||||
if (dev==NULL) {
|
||||
l->last_error = CANDLE_ERR_MALLOC;
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(dev, &l->dev[dev_num], sizeof(candle_device_t));
|
||||
l->last_error = CANDLE_ERR_OK;
|
||||
dev->last_error = CANDLE_ERR_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool __stdcall DLL candle_dev_get_state(candle_handle hdev, candle_devstate_t *state)
|
||||
{
|
||||
if (hdev==NULL) {
|
||||
return false;
|
||||
} else {
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
*state = dev->state;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t * __stdcall DLL candle_dev_get_path(candle_handle hdev)
|
||||
{
|
||||
if (hdev==NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
return dev->path;
|
||||
}
|
||||
}
|
||||
|
||||
static bool candle_dev_interal_open(candle_handle hdev)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
|
||||
memset(dev->rxevents, 0, sizeof(dev->rxevents));
|
||||
memset(dev->rxurbs, 0, sizeof(dev->rxurbs));
|
||||
|
||||
dev->deviceHandle = CreateFile(
|
||||
dev->path,
|
||||
GENERIC_WRITE | GENERIC_READ,
|
||||
FILE_SHARE_WRITE | FILE_SHARE_READ,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (dev->deviceHandle == INVALID_HANDLE_VALUE) {
|
||||
dev->last_error = CANDLE_ERR_CREATE_FILE;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WinUsb_Initialize(dev->deviceHandle, &dev->winUSBHandle)) {
|
||||
dev->last_error = CANDLE_ERR_WINUSB_INITIALIZE;
|
||||
goto close_handle;
|
||||
}
|
||||
|
||||
USB_INTERFACE_DESCRIPTOR ifaceDescriptor;
|
||||
if (!WinUsb_QueryInterfaceSettings(dev->winUSBHandle, 0, &ifaceDescriptor)) {
|
||||
dev->last_error = CANDLE_ERR_QUERY_INTERFACE;
|
||||
goto winusb_free;
|
||||
}
|
||||
|
||||
dev->interfaceNumber = ifaceDescriptor.bInterfaceNumber;
|
||||
bool has_in = false, has_out = false;
|
||||
|
||||
candle_logf(L"open path=%ls interface=%u endpoints=%u",
|
||||
dev->path,
|
||||
dev->interfaceNumber,
|
||||
ifaceDescriptor.bNumEndpoints);
|
||||
|
||||
for (uint8_t i=0; i<ifaceDescriptor.bNumEndpoints; i++) {
|
||||
|
||||
WINUSB_PIPE_INFORMATION pipeInfo;
|
||||
if (!WinUsb_QueryPipe(dev->winUSBHandle, 0, i, &pipeInfo)) {
|
||||
dev->last_error = CANDLE_ERR_QUERY_PIPE;
|
||||
goto winusb_free;
|
||||
}
|
||||
|
||||
if (pipeInfo.PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN(pipeInfo.PipeId)) {
|
||||
if (!has_in) {
|
||||
dev->bulkInPipe = pipeInfo.PipeId;
|
||||
has_in = true;
|
||||
candle_logf(L"selected bulk IN pipe=0x%02x maxPacket=%u interval=%u",
|
||||
pipeInfo.PipeId,
|
||||
pipeInfo.MaximumPacketSize,
|
||||
pipeInfo.Interval);
|
||||
}
|
||||
} else if (pipeInfo.PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT(pipeInfo.PipeId)) {
|
||||
if (!has_out) {
|
||||
dev->bulkOutPipe = pipeInfo.PipeId;
|
||||
has_out = true;
|
||||
candle_logf(L"selected bulk OUT pipe=0x%02x maxPacket=%u interval=%u",
|
||||
pipeInfo.PipeId,
|
||||
pipeInfo.MaximumPacketSize,
|
||||
pipeInfo.Interval);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!has_in || !has_out) {
|
||||
dev->last_error = CANDLE_ERR_PARSE_IF_DESCR;
|
||||
goto winusb_free;
|
||||
}
|
||||
|
||||
char use_raw_io = 1;
|
||||
if (!WinUsb_SetPipePolicy(dev->winUSBHandle, dev->bulkInPipe, RAW_IO, sizeof(use_raw_io), &use_raw_io)) {
|
||||
dev->last_error = CANDLE_ERR_SET_PIPE_RAW_IO;
|
||||
goto winusb_free;
|
||||
}
|
||||
|
||||
if (!candle_ctrl_set_host_format(dev)) {
|
||||
goto winusb_free;
|
||||
}
|
||||
|
||||
if (!candle_ctrl_get_config(dev, &dev->dconf)) {
|
||||
goto winusb_free;
|
||||
}
|
||||
candle_logf(L"device config channels=%u sw=0x%08x hw=0x%08x",
|
||||
dev->dconf.icount + 1,
|
||||
dev->dconf.sw_version,
|
||||
dev->dconf.hw_version);
|
||||
|
||||
if (!candle_ctrl_get_capability(dev, 0, &dev->bt_const)) {
|
||||
dev->last_error = CANDLE_ERR_GET_BITTIMING_CONST;
|
||||
goto winusb_free;
|
||||
}
|
||||
candle_logf(L"cap ch0 feature=0x%08x fclk=%u tseg1=%u..%u tseg2=%u..%u sjw=%u brp=%u..%u inc=%u",
|
||||
dev->bt_const.feature,
|
||||
dev->bt_const.fclk_can,
|
||||
dev->bt_const.tseg1_min,
|
||||
dev->bt_const.tseg1_max,
|
||||
dev->bt_const.tseg2_min,
|
||||
dev->bt_const.tseg2_max,
|
||||
dev->bt_const.sjw_max,
|
||||
dev->bt_const.brp_min,
|
||||
dev->bt_const.brp_max,
|
||||
dev->bt_const.brp_inc);
|
||||
|
||||
/* Query capabilities for each channel on multi-channel devices */
|
||||
uint8_t num_channels = dev->dconf.icount + 1;
|
||||
if (num_channels > 8) num_channels = 8;
|
||||
for (uint8_t ch = 0; ch < num_channels; ch++) {
|
||||
if (!candle_ctrl_get_capability(dev, ch, &dev->ch_caps[ch])) {
|
||||
/* Fall back to channel 0 capabilities for this channel */
|
||||
memcpy(&dev->ch_caps[ch], &dev->bt_const, sizeof(candle_capability_t));
|
||||
candle_logf(L"cap ch%u failed, falling back to ch0", ch);
|
||||
} else {
|
||||
candle_logf(L"cap ch%u feature=0x%08x fclk=%u",
|
||||
ch,
|
||||
dev->ch_caps[ch].feature,
|
||||
dev->ch_caps[ch].fclk_can);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pre-allocate a manual-reset event for timed overlapped writes. Reusing
|
||||
* one event per device (writes are serialised by writeMutex) avoids
|
||||
* per-frame CreateEvent overhead at high CAN frame rates. */
|
||||
dev->txEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (!dev->txEvent) {
|
||||
dev->last_error = CANDLE_ERR_MALLOC;
|
||||
goto winusb_free;
|
||||
}
|
||||
|
||||
dev->last_error = CANDLE_ERR_OK;
|
||||
return true;
|
||||
|
||||
winusb_free:
|
||||
WinUsb_Free(dev->winUSBHandle);
|
||||
dev->winUSBHandle = NULL;
|
||||
|
||||
close_handle:
|
||||
CloseHandle(dev->deviceHandle);
|
||||
dev->deviceHandle = NULL;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
static bool candle_prepare_read(candle_device_t *dev, unsigned urb_num)
|
||||
{
|
||||
if (dev->rxurbs[urb_num].pending) {
|
||||
dev->last_error = CANDLE_ERR_PREPARE_READ;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dev->rxurbs[urb_num].ovl.hEvent == NULL) {
|
||||
dev->last_error = CANDLE_ERR_PREPARE_READ;
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetEvent(dev->rxurbs[urb_num].ovl.hEvent);
|
||||
|
||||
BOOL rc = WinUsb_ReadPipe(
|
||||
dev->winUSBHandle,
|
||||
dev->bulkInPipe,
|
||||
dev->rxurbs[urb_num].buf,
|
||||
sizeof(dev->rxurbs[urb_num].buf),
|
||||
NULL,
|
||||
&dev->rxurbs[urb_num].ovl
|
||||
);
|
||||
|
||||
if (rc) {
|
||||
/* Synchronous completion: data is already in buf and the event is
|
||||
* signaled. WaitForMultipleObjects will return immediately on the
|
||||
* next call and GetOverlappedResult will succeed, so this is fine. */
|
||||
dev->rxurbs[urb_num].pending = true;
|
||||
dev->last_error = CANDLE_ERR_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_IO_PENDING) {
|
||||
dev->rxurbs[urb_num].pending = true;
|
||||
dev->last_error = CANDLE_ERR_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
candle_logf(L"prepare read urb=%u failed winerr=%lu", urb_num, err);
|
||||
dev->last_error = CANDLE_ERR_PREPARE_READ;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool candle_close_rxurbs(candle_device_t *dev)
|
||||
{
|
||||
if (dev->winUSBHandle != NULL) {
|
||||
WinUsb_AbortPipe(dev->winUSBHandle, dev->bulkInPipe);
|
||||
}
|
||||
|
||||
for (unsigned i=0; i<CANDLE_URB_COUNT; i++) {
|
||||
if (dev->rxurbs[i].pending) {
|
||||
CancelIoEx(dev->deviceHandle, &dev->rxurbs[i].ovl);
|
||||
|
||||
DWORD bytes_transfered;
|
||||
WinUsb_GetOverlappedResult(dev->winUSBHandle,
|
||||
&dev->rxurbs[i].ovl,
|
||||
&bytes_transfered,
|
||||
TRUE);
|
||||
dev->rxurbs[i].pending = false;
|
||||
}
|
||||
|
||||
if (dev->rxevents[i] != NULL) {
|
||||
CloseHandle(dev->rxevents[i]);
|
||||
dev->rxevents[i] = NULL;
|
||||
memset(&dev->rxurbs[i].ovl, 0, sizeof(dev->rxurbs[i].ovl));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void candle_release_open_handles(candle_device_t *dev)
|
||||
{
|
||||
candle_close_rxurbs(dev);
|
||||
|
||||
if (dev->txEvent) {
|
||||
CloseHandle(dev->txEvent);
|
||||
dev->txEvent = NULL;
|
||||
}
|
||||
|
||||
if (dev->winUSBHandle) {
|
||||
WinUsb_Free(dev->winUSBHandle);
|
||||
dev->winUSBHandle = NULL;
|
||||
}
|
||||
|
||||
if (dev->deviceHandle && dev->deviceHandle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(dev->deviceHandle);
|
||||
dev->deviceHandle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool __stdcall DLL candle_dev_open(candle_handle hdev)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
|
||||
if (candle_dev_interal_open(dev)) {
|
||||
for (unsigned i=0; i<CANDLE_URB_COUNT; i++) {
|
||||
HANDLE ev = CreateEvent(NULL, true, false, NULL);
|
||||
if (ev == NULL) {
|
||||
dev->last_error = CANDLE_ERR_MALLOC;
|
||||
candle_err_t last_error = dev->last_error;
|
||||
candle_release_open_handles(dev);
|
||||
dev->last_error = last_error;
|
||||
return false;
|
||||
}
|
||||
dev->rxevents[i] = ev;
|
||||
dev->rxurbs[i].ovl.hEvent = ev;
|
||||
if (!candle_prepare_read(dev, i)) {
|
||||
candle_err_t last_error = dev->last_error;
|
||||
candle_release_open_handles(dev);
|
||||
dev->last_error = last_error;
|
||||
return false; // keep last_error from prepare_read call
|
||||
}
|
||||
}
|
||||
dev->last_error = CANDLE_ERR_OK;
|
||||
return true;
|
||||
} else {
|
||||
return false; // keep last_error from open_device call
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_dev_get_timestamp_us(candle_handle hdev, uint32_t *timestamp_us)
|
||||
{
|
||||
return candle_ctrl_get_timestamp(hdev, timestamp_us);
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_dev_close(candle_handle hdev)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
|
||||
candle_release_open_handles(dev);
|
||||
|
||||
dev->last_error = CANDLE_ERR_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_dev_free(candle_handle hdev)
|
||||
{
|
||||
free(hdev);
|
||||
return true;
|
||||
}
|
||||
|
||||
candle_err_t __stdcall DLL candle_dev_last_error(candle_handle hdev)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
return dev->last_error;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_channel_count(candle_handle hdev, uint8_t *num_channels)
|
||||
{
|
||||
// TODO check if info was already read from device; try to do so; throw error...
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
*num_channels = dev->dconf.icount+1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_channel_get_capabilities(candle_handle hdev, uint8_t ch, candle_capability_t *cap)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
uint8_t num_channels = dev->dconf.icount + 1;
|
||||
if (ch < num_channels && ch < 8) {
|
||||
memcpy(cap, &dev->ch_caps[ch], sizeof(candle_capability_t));
|
||||
} else {
|
||||
memcpy(cap, &dev->bt_const, sizeof(candle_capability_t));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_channel_get_state(candle_handle hdev, uint8_t ch, candle_can_state_t *state)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
candle_device_state_t ds;
|
||||
if (!candle_ctrl_get_state(dev, ch, &ds)) {
|
||||
return false;
|
||||
}
|
||||
*state = (candle_can_state_t)ds.state;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_channel_bus_off_recover(candle_handle hdev, uint8_t ch)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
return candle_ctrl_bus_off_recover(dev, ch);
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_channel_set_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data)
|
||||
{
|
||||
// TODO ensure device is open, check channel count..
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
return candle_ctrl_set_bittiming(dev, ch, data);
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_channel_set_bitrate(candle_handle hdev, uint8_t ch, uint32_t bitrate)
|
||||
{
|
||||
// TODO ensure device is open, check channel count..
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
|
||||
if (dev->bt_const.fclk_can != 48000000) {
|
||||
/* this function only works for the candleLight base clock of 48MHz */
|
||||
dev->last_error = CANDLE_ERR_BITRATE_FCLK;
|
||||
return false;
|
||||
}
|
||||
|
||||
candle_bittiming_t t;
|
||||
t.prop_seg = 1;
|
||||
t.sjw = 1;
|
||||
t.phase_seg1 = 13 - t.prop_seg;
|
||||
t.phase_seg2 = 2;
|
||||
|
||||
switch (bitrate) {
|
||||
case 10000:
|
||||
t.brp = 300;
|
||||
break;
|
||||
|
||||
case 20000:
|
||||
t.brp = 150;
|
||||
break;
|
||||
|
||||
case 50000:
|
||||
t.brp = 60;
|
||||
break;
|
||||
|
||||
case 83333:
|
||||
t.brp = 36;
|
||||
break;
|
||||
|
||||
case 100000:
|
||||
t.brp = 30;
|
||||
break;
|
||||
|
||||
case 125000:
|
||||
t.brp = 24;
|
||||
break;
|
||||
|
||||
case 250000:
|
||||
t.brp = 12;
|
||||
break;
|
||||
|
||||
case 500000:
|
||||
t.brp = 6;
|
||||
break;
|
||||
|
||||
case 800000:
|
||||
t.brp = 4;
|
||||
t.phase_seg1 = 12 - t.prop_seg;
|
||||
t.phase_seg2 = 2;
|
||||
break;
|
||||
|
||||
case 1000000:
|
||||
t.brp = 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
dev->last_error = CANDLE_ERR_BITRATE_UNSUPPORTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
return candle_ctrl_set_bittiming(dev, ch, &t);
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_channel_start(candle_handle hdev, uint8_t ch, uint32_t flags)
|
||||
{
|
||||
// TODO ensure device is open, check channel count..
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
candle_capability_t *cap = (ch < 8) ? &dev->ch_caps[ch] : &dev->bt_const;
|
||||
|
||||
if (cap->feature & CANDLE_FEATURE_HW_TIMESTAMP) {
|
||||
flags |= CANDLE_MODE_HW_TIMESTAMP;
|
||||
} else {
|
||||
candle_logf(L"channel %u has no HW timestamp capability; starting without timestamp flag", ch);
|
||||
}
|
||||
|
||||
bool rc = candle_ctrl_set_device_mode(dev, ch, CANDLE_DEVMODE_START, flags);
|
||||
candle_logf(L"channel %u start flags=0x%08x result=%u err=%u",
|
||||
ch,
|
||||
flags,
|
||||
rc ? 1 : 0,
|
||||
dev->last_error);
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_channel_stop(candle_handle hdev, uint8_t ch)
|
||||
{
|
||||
// TODO ensure device is open, check channel count..
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
return candle_ctrl_set_device_mode(dev, ch, CANDLE_DEVMODE_RESET, 0);
|
||||
}
|
||||
|
||||
/* Write len bytes from buf to the OUT pipe, aborting after 300 ms.
|
||||
* Writes are serialised by writeMutex in CandleApiInterface so dev->txEvent
|
||||
* is never accessed by two threads simultaneously. */
|
||||
static bool candle_write_pipe_timed(candle_device_t *dev, uint8_t *buf, DWORD len)
|
||||
{
|
||||
OVERLAPPED ovl;
|
||||
memset(&ovl, 0, sizeof(ovl));
|
||||
ovl.hEvent = dev->txEvent;
|
||||
ResetEvent(dev->txEvent);
|
||||
|
||||
BOOL rc = WinUsb_WritePipe(dev->winUSBHandle, dev->bulkOutPipe,
|
||||
buf, len, NULL, &ovl);
|
||||
if (rc) {
|
||||
return true; /* completed synchronously */
|
||||
}
|
||||
if (GetLastError() != ERROR_IO_PENDING) {
|
||||
return false; /* hard error */
|
||||
}
|
||||
|
||||
if (WaitForSingleObject(dev->txEvent, 150) != WAIT_OBJECT_0) {
|
||||
/* Timed out: cancel the transfer and restore the pipe to a clean state. */
|
||||
WinUsb_AbortPipe(dev->winUSBHandle, dev->bulkOutPipe);
|
||||
DWORD dummy = 0;
|
||||
WinUsb_GetOverlappedResult(dev->winUSBHandle, &ovl, &dummy, TRUE);
|
||||
WinUsb_ResetPipe(dev->winUSBHandle, dev->bulkOutPipe);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD transferred = 0;
|
||||
return WinUsb_GetOverlappedResult(dev->winUSBHandle, &ovl, &transferred, FALSE) != FALSE;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_frame_send(candle_handle hdev, uint8_t ch, candle_frame_t *frame)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
frame->echo_id = 0;
|
||||
frame->channel = ch;
|
||||
bool rc = candle_write_pipe_timed(dev, (uint8_t*)frame, sizeof(*frame));
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SEND_FRAME;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_frame_read(candle_handle hdev, candle_frame_t *frame, uint32_t timeout_ms)
|
||||
{
|
||||
// TODO ensure device is open..
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
|
||||
DWORD wait_result = WaitForMultipleObjects(CANDLE_URB_COUNT, dev->rxevents, false, timeout_ms);
|
||||
if (wait_result == WAIT_TIMEOUT) {
|
||||
dev->last_error = CANDLE_ERR_READ_TIMEOUT;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( (wait_result < WAIT_OBJECT_0) || (wait_result >= WAIT_OBJECT_0 + CANDLE_URB_COUNT) ) {
|
||||
dev->last_error = CANDLE_ERR_READ_WAIT;
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD urb_num = wait_result - WAIT_OBJECT_0;
|
||||
DWORD bytes_transfered;
|
||||
|
||||
if (!WinUsb_GetOverlappedResult(dev->winUSBHandle, &dev->rxurbs[urb_num].ovl, &bytes_transfered, false)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_IO_INCOMPLETE) {
|
||||
ResetEvent(dev->rxurbs[urb_num].ovl.hEvent);
|
||||
} else {
|
||||
dev->rxurbs[urb_num].pending = false;
|
||||
candle_prepare_read(dev, urb_num);
|
||||
}
|
||||
candle_logf(L"classic read result failed urb=%u winerr=%lu", urb_num, err);
|
||||
dev->last_error = CANDLE_ERR_READ_RESULT;
|
||||
return false;
|
||||
}
|
||||
dev->rxurbs[urb_num].pending = false;
|
||||
|
||||
if (bytes_transfered < sizeof(*frame)-4) {
|
||||
candle_prepare_read(dev, urb_num);
|
||||
candle_logf(L"classic read too small urb=%u bytes=%lu min=%u",
|
||||
urb_num,
|
||||
bytes_transfered,
|
||||
(unsigned)(sizeof(*frame) - 4));
|
||||
dev->last_error = CANDLE_ERR_READ_SIZE;
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
DWORD copy_len = (bytes_transfered < sizeof(*frame)) ? bytes_transfered : sizeof(*frame);
|
||||
memcpy(frame, dev->rxurbs[urb_num].buf, copy_len);
|
||||
candle_logf_verbose(L"classic read urb=%u bytes=%lu echo=0x%08x can_id=0x%08x dlc=%u ch=%u flags=0x%02x ts=%u",
|
||||
urb_num,
|
||||
bytes_transfered,
|
||||
frame->echo_id,
|
||||
frame->can_id,
|
||||
frame->can_dlc,
|
||||
frame->channel,
|
||||
frame->flags,
|
||||
frame->timestamp_us);
|
||||
|
||||
return candle_prepare_read(dev, urb_num);
|
||||
}
|
||||
|
||||
candle_frametype_t __stdcall DLL candle_frame_type(candle_frame_t *frame)
|
||||
{
|
||||
if (frame->echo_id != 0xFFFFFFFF) {
|
||||
return CANDLE_FRAMETYPE_ECHO;
|
||||
};
|
||||
|
||||
if (frame->can_id & CANDLE_ID_ERR) {
|
||||
return CANDLE_FRAMETYPE_ERROR;
|
||||
}
|
||||
|
||||
return CANDLE_FRAMETYPE_RECEIVE;
|
||||
}
|
||||
|
||||
uint32_t __stdcall DLL candle_frame_id(candle_frame_t *frame)
|
||||
{
|
||||
return frame->can_id & 0x1FFFFFFF;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_frame_is_extended_id(candle_frame_t *frame)
|
||||
{
|
||||
return (frame->can_id & CANDLE_ID_EXTENDED) != 0;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_frame_is_rtr(candle_frame_t *frame)
|
||||
{
|
||||
return (frame->can_id & CANDLE_ID_RTR) != 0;
|
||||
}
|
||||
|
||||
uint8_t __stdcall DLL candle_frame_dlc(candle_frame_t *frame)
|
||||
{
|
||||
return frame->can_dlc;
|
||||
}
|
||||
|
||||
uint8_t * __stdcall DLL candle_frame_data(candle_frame_t *frame)
|
||||
{
|
||||
return frame->data;
|
||||
}
|
||||
|
||||
uint32_t __stdcall DLL candle_frame_timestamp_us(candle_frame_t *frame)
|
||||
{
|
||||
return frame->timestamp_us;
|
||||
}
|
||||
|
||||
/* ---- CAN FD extensions ---- */
|
||||
|
||||
bool __stdcall DLL candle_channel_set_data_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
return candle_ctrl_set_data_bittiming(dev, ch, data);
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_fd_frame_send(candle_handle hdev, uint8_t ch, candle_fd_frame_t *frame)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
frame->echo_id = 0;
|
||||
frame->channel = ch;
|
||||
bool rc = candle_write_pipe_timed(dev, (uint8_t*)frame, sizeof(*frame));
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SEND_FRAME;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_fd_frame_read(candle_handle hdev, candle_fd_frame_t *frame, uint32_t timeout_ms)
|
||||
{
|
||||
candle_device_t *dev = (candle_device_t*)hdev;
|
||||
|
||||
DWORD wait_result = WaitForMultipleObjects(CANDLE_URB_COUNT, dev->rxevents, false, timeout_ms);
|
||||
if (wait_result == WAIT_TIMEOUT) {
|
||||
dev->last_error = CANDLE_ERR_READ_TIMEOUT;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( (wait_result < WAIT_OBJECT_0) || (wait_result >= WAIT_OBJECT_0 + CANDLE_URB_COUNT) ) {
|
||||
dev->last_error = CANDLE_ERR_READ_WAIT;
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD urb_num = wait_result - WAIT_OBJECT_0;
|
||||
DWORD bytes_transfered;
|
||||
|
||||
if (!WinUsb_GetOverlappedResult(dev->winUSBHandle, &dev->rxurbs[urb_num].ovl, &bytes_transfered, false)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_IO_INCOMPLETE) {
|
||||
ResetEvent(dev->rxurbs[urb_num].ovl.hEvent);
|
||||
} else {
|
||||
dev->rxurbs[urb_num].pending = false;
|
||||
candle_prepare_read(dev, urb_num);
|
||||
}
|
||||
candle_logf(L"fd read result failed urb=%u winerr=%lu", urb_num, err);
|
||||
dev->last_error = CANDLE_ERR_READ_RESULT;
|
||||
return false;
|
||||
}
|
||||
dev->rxurbs[urb_num].pending = false;
|
||||
|
||||
/* Minimum: classic CAN header (12 bytes) + at least 8 data bytes = 20 bytes */
|
||||
static const DWORD classic_min = sizeof(candle_frame_t) - 4;
|
||||
if (bytes_transfered < classic_min) {
|
||||
candle_prepare_read(dev, urb_num);
|
||||
candle_logf(L"fd read too small urb=%u bytes=%lu min=%lu",
|
||||
urb_num,
|
||||
bytes_transfered,
|
||||
classic_min);
|
||||
dev->last_error = CANDLE_ERR_READ_SIZE;
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
|
||||
/*
|
||||
* Detect frame type from the flags byte (offset 10 in both structs).
|
||||
* Classic CAN frames: header(12) + data(8) + timestamp(4) = 24 bytes total.
|
||||
*
|
||||
* FD frames come in two wire formats:
|
||||
* - Legacy fixed (candleLight/CANable 1.x): always 80 bytes — header(12) +
|
||||
* data[64] + timestamp(4). The timestamp is ALWAYS at offset 76, regardless
|
||||
* of the actual DLC. Identified by bytes_transferred == sizeof(candle_fd_frame_t).
|
||||
* - Variable-length (CANnectivity/Zephyr): header(12) + actual_data(DLC) +
|
||||
* timestamp(4). Identified by bytes_transferred < sizeof(candle_fd_frame_t).
|
||||
*/
|
||||
bool is_fd_frame = (dev->rxurbs[urb_num].buf[10] & CANDLE_FRAME_FLAG_FD) != 0;
|
||||
|
||||
if (is_fd_frame) {
|
||||
/* can_dlc is at byte offset 8 in both classic and FD wire frames. */
|
||||
const uint8_t raw_dlc = dev->rxurbs[urb_num].buf[8];
|
||||
const DWORD data_len = candle_dlc_to_len(raw_dlc);
|
||||
const DWORD min_size = 12 + data_len; /* header + data, without timestamp */
|
||||
|
||||
if (bytes_transfered < min_size) {
|
||||
candle_prepare_read(dev, urb_num);
|
||||
candle_logf(L"fd read FD frame too small urb=%u bytes=%lu min=%lu flags=0x%02x dlc=%u",
|
||||
urb_num,
|
||||
bytes_transfered,
|
||||
min_size,
|
||||
dev->rxurbs[urb_num].buf[10],
|
||||
raw_dlc);
|
||||
dev->last_error = CANDLE_ERR_READ_SIZE;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Copy the fixed 12-byte header (echo_id … reserved). */
|
||||
memcpy(frame, dev->rxurbs[urb_num].buf, 12);
|
||||
/* Copy data at offset 12 into the struct's data field. */
|
||||
memcpy(frame->data, dev->rxurbs[urb_num].buf + 12, data_len);
|
||||
|
||||
/* Timestamp location depends on the wire format (see comment above). */
|
||||
const DWORD fixed_ts_offset = (DWORD)(sizeof(candle_fd_frame_t) - sizeof(uint32_t)); /* = 76 */
|
||||
const DWORD ts_offset = (bytes_transfered >= (DWORD)sizeof(candle_fd_frame_t))
|
||||
? fixed_ts_offset
|
||||
: min_size;
|
||||
if (bytes_transfered >= ts_offset + (DWORD)sizeof(uint32_t)) {
|
||||
memcpy(&frame->timestamp_us, dev->rxurbs[urb_num].buf + ts_offset, sizeof(uint32_t));
|
||||
}
|
||||
/* else: timestamp stays zero from memset above */
|
||||
} else {
|
||||
/* Classic CAN frame — copy into FD struct, fixing the timestamp position */
|
||||
candle_frame_t classic;
|
||||
DWORD copy_len = (bytes_transfered < sizeof(classic)) ? bytes_transfered : sizeof(classic);
|
||||
memcpy(&classic, dev->rxurbs[urb_num].buf, copy_len);
|
||||
|
||||
frame->echo_id = classic.echo_id;
|
||||
frame->can_id = classic.can_id;
|
||||
frame->can_dlc = classic.can_dlc;
|
||||
frame->channel = classic.channel;
|
||||
frame->flags = classic.flags;
|
||||
frame->reserved = classic.reserved;
|
||||
memcpy(frame->data, classic.data, 8);
|
||||
frame->timestamp_us = (bytes_transfered >= sizeof(classic)) ? classic.timestamp_us : 0;
|
||||
}
|
||||
candle_logf_verbose(L"fd read urb=%u bytes=%lu is_fd=%u echo=0x%08x can_id=0x%08x dlc=%u ch=%u flags=0x%02x ts=%u",
|
||||
urb_num,
|
||||
bytes_transfered,
|
||||
is_fd_frame ? 1 : 0,
|
||||
frame->echo_id,
|
||||
frame->can_id,
|
||||
frame->can_dlc,
|
||||
frame->channel,
|
||||
frame->flags,
|
||||
frame->timestamp_us);
|
||||
|
||||
return candle_prepare_read(dev, urb_num);
|
||||
}
|
||||
|
||||
candle_frametype_t __stdcall DLL candle_fd_frame_type(candle_fd_frame_t *frame)
|
||||
{
|
||||
if (frame->echo_id != 0xFFFFFFFF) {
|
||||
return CANDLE_FRAMETYPE_ECHO;
|
||||
}
|
||||
if (frame->can_id & CANDLE_ID_ERR) {
|
||||
return CANDLE_FRAMETYPE_ERROR;
|
||||
}
|
||||
return CANDLE_FRAMETYPE_RECEIVE;
|
||||
}
|
||||
|
||||
uint32_t __stdcall DLL candle_fd_frame_id(candle_fd_frame_t *frame)
|
||||
{
|
||||
return frame->can_id & 0x1FFFFFFF;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_fd_frame_is_extended_id(candle_fd_frame_t *frame)
|
||||
{
|
||||
return (frame->can_id & CANDLE_ID_EXTENDED) != 0;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_fd_frame_is_rtr(candle_fd_frame_t *frame)
|
||||
{
|
||||
return (frame->can_id & CANDLE_ID_RTR) != 0;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_fd_frame_is_fd(candle_fd_frame_t *frame)
|
||||
{
|
||||
return (frame->flags & CANDLE_FRAME_FLAG_FD) != 0;
|
||||
}
|
||||
|
||||
bool __stdcall DLL candle_fd_frame_is_brs(candle_fd_frame_t *frame)
|
||||
{
|
||||
return (frame->flags & CANDLE_FRAME_FLAG_BRS) != 0;
|
||||
}
|
||||
|
||||
uint8_t __stdcall DLL candle_fd_frame_dlc(candle_fd_frame_t *frame)
|
||||
{
|
||||
return frame->can_dlc;
|
||||
}
|
||||
|
||||
uint8_t * __stdcall DLL candle_fd_frame_data(candle_fd_frame_t *frame)
|
||||
{
|
||||
return frame->data;
|
||||
}
|
||||
|
||||
uint32_t __stdcall DLL candle_fd_frame_timestamp_us(candle_fd_frame_t *frame)
|
||||
{
|
||||
return frame->timestamp_us;
|
||||
}
|
||||
29
c/candle/candle.def
Normal file
29
c/candle/candle.def
Normal file
@@ -0,0 +1,29 @@
|
||||
EXPORTS
|
||||
candle_list_scan
|
||||
candle_list_free
|
||||
candle_list_length
|
||||
candle_dev_get
|
||||
candle_dev_get_state
|
||||
candle_dev_get_path
|
||||
candle_dev_open
|
||||
candle_dev_get_timestamp_us
|
||||
candle_dev_close
|
||||
candle_dev_free
|
||||
candle_channel_count
|
||||
candle_channel_get_capabilities
|
||||
candle_channel_get_state
|
||||
candle_channel_bus_off_recover
|
||||
candle_channel_set_timing
|
||||
candle_channel_set_bitrate
|
||||
candle_channel_start
|
||||
candle_channel_stop
|
||||
candle_frame_send
|
||||
candle_frame_read
|
||||
candle_frame_type
|
||||
candle_frame_id
|
||||
candle_frame_is_extended_id
|
||||
candle_frame_is_rtr
|
||||
candle_frame_dlc
|
||||
candle_frame_data
|
||||
candle_frame_timestamp_us
|
||||
candle_dev_last_error
|
||||
287
c/candle/candle.h
Normal file
287
c/candle/candle.h
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016 Hubert Denkmair <hubert@denkmair.de>
|
||||
Copyright (c) 2026 Schildkroet
|
||||
|
||||
This file is part of the candle windows API.
|
||||
|
||||
This library is free software: you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation, either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* candle_list_handle;
|
||||
typedef void* candle_handle;
|
||||
|
||||
typedef enum {
|
||||
CANDLE_DEVSTATE_AVAIL,
|
||||
CANDLE_DEVSTATE_INUSE
|
||||
} candle_devstate_t;
|
||||
|
||||
typedef enum {
|
||||
CANDLE_FRAMETYPE_UNKNOWN,
|
||||
CANDLE_FRAMETYPE_RECEIVE,
|
||||
CANDLE_FRAMETYPE_ECHO,
|
||||
CANDLE_FRAMETYPE_ERROR,
|
||||
CANDLE_FRAMETYPE_TIMESTAMP_OVFL
|
||||
} candle_frametype_t;
|
||||
|
||||
enum {
|
||||
CANDLE_ID_EXTENDED = 0x80000000,
|
||||
CANDLE_ID_RTR = 0x40000000,
|
||||
CANDLE_ID_ERR = 0x20000000
|
||||
};
|
||||
|
||||
/* Feature flags reported in candle_capability_t.feature */
|
||||
enum {
|
||||
/** CAN channel supports listen-only mode, in which it is not allowed to send dominant bits. */
|
||||
CANDLE_FEATURE_LISTEN_ONLY = 0x0001,
|
||||
/** CAN channel supports loopback mode, in which it receives own frames. */
|
||||
CANDLE_FEATURE_LOOP_BACK = 0x0002,
|
||||
/** CAN channel supports triple sampling mode */
|
||||
CANDLE_FEATURE_TRIPLE_SAMPLE = 0x0004,
|
||||
/** CAN channel supports not retransmitting in case of lost arbitration or missing ACK. */
|
||||
CANDLE_FEATURE_ONE_SHOT = 0x0008,
|
||||
/** CAN channel supports hardware timestamping of CAN frames. */
|
||||
CANDLE_FEATURE_HW_TIMESTAMP = 0x0010,
|
||||
/** CAN channel supports visual identification. */
|
||||
CANDLE_FEATURE_IDENTIFY = 0x0020,
|
||||
/** CAN channel supports user IDs (unsupported). */
|
||||
CANDLE_FEATURE_USER_ID = 0x0040,
|
||||
/** CAN channel supports padding of host frames (unsupported). */
|
||||
CANDLE_FEATURE_PAD_PKTS_TO_MAX = 0x0080,
|
||||
/** CAN channel supports transmitting/receiving CAN FD frames. */
|
||||
CANDLE_FEATURE_FD = 0x0100,
|
||||
/** CAN channel support LPC546xx specific quirks (Unused) */
|
||||
CANDLE_FEATURE_REQ_USB_QUIRK_LPC546XX = 0x0200,
|
||||
/** CAN channel supports extended bit timing limits. */
|
||||
CANDLE_FEATURE_BT_CONST_EXT = 0x0400,
|
||||
/** CAN channel supports configurable bus termination. */
|
||||
CANDLE_FEATURE_TERMINATION = 0x0800,
|
||||
/** CAN channel supports bus error reporting (Unsupported, always enabled) */
|
||||
CANDLE_FEATURE_BERR_REPORTING = 0x1000,
|
||||
/** CAN channel supports reporting of bus state. */
|
||||
CANDLE_FEATURE_GET_STATE = 0x2000,
|
||||
/** Host-controlled recovery after the controller enters bus-off. */
|
||||
CANDLE_FEATURE_BUS_OFF_RECOVERY = 0x40000,
|
||||
};
|
||||
|
||||
/* Flags in the flags byte of received/transmitted frames */
|
||||
enum {
|
||||
CANDLE_FRAME_FLAG_OVERFLOW = 0x01,
|
||||
CANDLE_FRAME_FLAG_FD = 0x02,
|
||||
CANDLE_FRAME_FLAG_BRS = 0x04,
|
||||
CANDLE_FRAME_FLAG_ESI = 0x08,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
CANDLE_STATE_ERROR_ACTIVE = 0,
|
||||
CANDLE_STATE_ERROR_WARNING = 1,
|
||||
CANDLE_STATE_ERROR_PASSIVE = 2,
|
||||
CANDLE_STATE_BUS_OFF = 3,
|
||||
CANDLE_STATE_STOPPED = 4,
|
||||
CANDLE_STATE_SLEEPING = 5,
|
||||
} candle_can_state_t;
|
||||
|
||||
typedef enum {
|
||||
CANDLE_MODE_NORMAL = 0x0000,
|
||||
CANDLE_MODE_LISTEN_ONLY = 0x0001,
|
||||
CANDLE_MODE_LOOP_BACK = 0x0002,
|
||||
CANDLE_MODE_TRIPLE_SAMPLE = 0x0004,
|
||||
CANDLE_MODE_ONE_SHOT = 0x0008,
|
||||
CANDLE_MODE_HW_TIMESTAMP = 0x0010,
|
||||
CANDLE_MODE_PAD_PKTS_TO_MAX = 0x0080,
|
||||
CANDLE_MODE_FD = 0x0100,
|
||||
} candle_mode_t;
|
||||
|
||||
typedef enum {
|
||||
CANDLE_ERR_OK = 0,
|
||||
CANDLE_ERR_CREATE_FILE = 1,
|
||||
CANDLE_ERR_WINUSB_INITIALIZE = 2,
|
||||
CANDLE_ERR_QUERY_INTERFACE = 3,
|
||||
CANDLE_ERR_QUERY_PIPE = 4,
|
||||
CANDLE_ERR_PARSE_IF_DESCR = 5,
|
||||
CANDLE_ERR_SET_HOST_FORMAT = 6,
|
||||
CANDLE_ERR_GET_DEVICE_INFO = 7,
|
||||
CANDLE_ERR_GET_BITTIMING_CONST = 8,
|
||||
CANDLE_ERR_PREPARE_READ = 9,
|
||||
CANDLE_ERR_SET_DEVICE_MODE = 10,
|
||||
CANDLE_ERR_SET_BITTIMING = 11,
|
||||
CANDLE_ERR_BITRATE_FCLK = 12,
|
||||
CANDLE_ERR_BITRATE_UNSUPPORTED = 13,
|
||||
CANDLE_ERR_SEND_FRAME = 14,
|
||||
CANDLE_ERR_READ_TIMEOUT = 15,
|
||||
CANDLE_ERR_READ_WAIT = 16,
|
||||
CANDLE_ERR_READ_RESULT = 17,
|
||||
CANDLE_ERR_READ_SIZE = 18,
|
||||
CANDLE_ERR_SETUPDI_IF_DETAILS = 19,
|
||||
CANDLE_ERR_SETUPDI_IF_DETAILS2 = 20,
|
||||
CANDLE_ERR_MALLOC = 21,
|
||||
CANDLE_ERR_PATH_LEN = 22,
|
||||
CANDLE_ERR_CLSID = 23,
|
||||
CANDLE_ERR_GET_DEVICES = 24,
|
||||
CANDLE_ERR_SETUPDI_IF_ENUM = 25,
|
||||
CANDLE_ERR_SET_TIMESTAMP_MODE = 26,
|
||||
CANDLE_ERR_DEV_OUT_OF_RANGE = 27,
|
||||
CANDLE_ERR_GET_TIMESTAMP = 28,
|
||||
CANDLE_ERR_SET_PIPE_RAW_IO = 29
|
||||
} candle_err_t;
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
typedef struct {
|
||||
uint32_t echo_id;
|
||||
uint32_t can_id;
|
||||
uint8_t can_dlc;
|
||||
uint8_t channel;
|
||||
uint8_t flags;
|
||||
uint8_t reserved;
|
||||
uint8_t data[8];
|
||||
uint32_t timestamp_us;
|
||||
} candle_frame_t;
|
||||
|
||||
/* CAN FD frame: same header as candle_frame_t but with 64-byte data payload */
|
||||
typedef struct {
|
||||
uint32_t echo_id;
|
||||
uint32_t can_id;
|
||||
uint8_t can_dlc;
|
||||
uint8_t channel;
|
||||
uint8_t flags;
|
||||
uint8_t reserved;
|
||||
uint8_t data[64];
|
||||
uint32_t timestamp_us;
|
||||
} candle_fd_frame_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t feature;
|
||||
uint32_t fclk_can;
|
||||
uint32_t tseg1_min;
|
||||
uint32_t tseg1_max;
|
||||
uint32_t tseg2_min;
|
||||
uint32_t tseg2_max;
|
||||
uint32_t sjw_max;
|
||||
uint32_t brp_min;
|
||||
uint32_t brp_max;
|
||||
uint32_t brp_inc;
|
||||
} candle_capability_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t prop_seg;
|
||||
uint32_t phase_seg1;
|
||||
uint32_t phase_seg2;
|
||||
uint32_t sjw;
|
||||
uint32_t brp;
|
||||
} candle_bittiming_t;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
/*
|
||||
* CAN FD DLC encoding:
|
||||
* DLC 0-8 → 0-8 bytes (same as classic CAN)
|
||||
* DLC 9 → 12 bytes
|
||||
* DLC 10 → 16 bytes
|
||||
* DLC 11 → 20 bytes
|
||||
* DLC 12 → 24 bytes
|
||||
* DLC 13 → 32 bytes
|
||||
* DLC 14 → 48 bytes
|
||||
* DLC 15 → 64 bytes
|
||||
*/
|
||||
static inline uint8_t candle_dlc_to_len(uint8_t dlc)
|
||||
{
|
||||
static const uint8_t tbl[16] = { 0,1,2,3,4,5,6,7,8,12,16,20,24,32,48,64 };
|
||||
return (dlc <= 15u) ? tbl[dlc] : 0u;
|
||||
}
|
||||
|
||||
static inline uint8_t candle_len_to_dlc(uint8_t len)
|
||||
{
|
||||
if (len <= 8u) return len;
|
||||
if (len <= 12u) return 9u;
|
||||
if (len <= 16u) return 10u;
|
||||
if (len <= 20u) return 11u;
|
||||
if (len <= 24u) return 12u;
|
||||
if (len <= 32u) return 13u;
|
||||
if (len <= 48u) return 14u;
|
||||
return 15u;
|
||||
}
|
||||
|
||||
#define DLL
|
||||
|
||||
/* Optional log callback — set once at startup to receive diagnostic messages.
|
||||
* If NULL (the default) no logging is performed. */
|
||||
typedef void (*candle_log_fn_t)(const wchar_t *msg);
|
||||
extern candle_log_fn_t candle_log_fn;
|
||||
|
||||
/* Set to true to enable per-frame and per-control-transfer trace logs.
|
||||
* Off by default; only error and setup messages are logged. */
|
||||
extern bool candle_log_verbose;
|
||||
|
||||
bool __stdcall DLL candle_list_scan(candle_list_handle *list);
|
||||
bool __stdcall DLL candle_list_free(candle_list_handle list);
|
||||
bool __stdcall DLL candle_list_length(candle_list_handle list, uint8_t *len);
|
||||
|
||||
bool __stdcall DLL candle_dev_get(candle_list_handle list, uint8_t dev_num, candle_handle *hdev);
|
||||
bool __stdcall DLL candle_dev_get_state(candle_handle hdev, candle_devstate_t *state);
|
||||
wchar_t * __stdcall DLL candle_dev_get_path(candle_handle hdev);
|
||||
bool __stdcall DLL candle_dev_open(candle_handle hdev);
|
||||
bool __stdcall DLL candle_dev_get_timestamp_us(candle_handle hdev, uint32_t *timestamp_us);
|
||||
bool __stdcall DLL candle_dev_close(candle_handle hdev);
|
||||
bool __stdcall DLL candle_dev_free(candle_handle hdev);
|
||||
|
||||
bool __stdcall DLL candle_channel_count(candle_handle hdev, uint8_t *num_channels);
|
||||
bool __stdcall DLL candle_channel_get_capabilities(candle_handle hdev, uint8_t ch, candle_capability_t *cap);
|
||||
bool __stdcall DLL candle_channel_get_state(candle_handle hdev, uint8_t ch, candle_can_state_t *state);
|
||||
bool __stdcall DLL candle_channel_bus_off_recover(candle_handle hdev, uint8_t ch);
|
||||
bool __stdcall DLL candle_channel_set_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data);
|
||||
bool __stdcall DLL candle_channel_set_bitrate(candle_handle hdev, uint8_t ch, uint32_t bitrate);
|
||||
bool __stdcall DLL candle_channel_start(candle_handle hdev, uint8_t ch, uint32_t flags);
|
||||
bool __stdcall DLL candle_channel_stop(candle_handle hdev, uint8_t ch);
|
||||
|
||||
bool __stdcall DLL candle_frame_send(candle_handle hdev, uint8_t ch, candle_frame_t *frame);
|
||||
bool __stdcall DLL candle_frame_read(candle_handle hdev, candle_frame_t *frame, uint32_t timeout_ms);
|
||||
|
||||
candle_frametype_t __stdcall DLL candle_frame_type(candle_frame_t *frame);
|
||||
uint32_t __stdcall DLL candle_frame_id(candle_frame_t *frame);
|
||||
bool __stdcall DLL candle_frame_is_extended_id(candle_frame_t *frame);
|
||||
bool __stdcall DLL candle_frame_is_rtr(candle_frame_t *frame);
|
||||
uint8_t __stdcall DLL candle_frame_dlc(candle_frame_t *frame);
|
||||
uint8_t * __stdcall DLL candle_frame_data(candle_frame_t *frame);
|
||||
uint32_t __stdcall DLL candle_frame_timestamp_us(candle_frame_t *frame);
|
||||
|
||||
/* CAN FD extensions */
|
||||
bool __stdcall DLL candle_channel_set_data_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data);
|
||||
bool __stdcall DLL candle_fd_frame_send(candle_handle hdev, uint8_t ch, candle_fd_frame_t *frame);
|
||||
bool __stdcall DLL candle_fd_frame_read(candle_handle hdev, candle_fd_frame_t *frame, uint32_t timeout_ms);
|
||||
|
||||
candle_frametype_t __stdcall DLL candle_fd_frame_type(candle_fd_frame_t *frame);
|
||||
uint32_t __stdcall DLL candle_fd_frame_id(candle_fd_frame_t *frame);
|
||||
bool __stdcall DLL candle_fd_frame_is_extended_id(candle_fd_frame_t *frame);
|
||||
bool __stdcall DLL candle_fd_frame_is_rtr(candle_fd_frame_t *frame);
|
||||
bool __stdcall DLL candle_fd_frame_is_fd(candle_fd_frame_t *frame);
|
||||
bool __stdcall DLL candle_fd_frame_is_brs(candle_fd_frame_t *frame);
|
||||
uint8_t __stdcall DLL candle_fd_frame_dlc(candle_fd_frame_t *frame);
|
||||
uint8_t * __stdcall DLL candle_fd_frame_data(candle_fd_frame_t *frame);
|
||||
uint32_t __stdcall DLL candle_fd_frame_timestamp_us(candle_fd_frame_t *frame);
|
||||
|
||||
candle_err_t __stdcall DLL candle_dev_last_error(candle_handle hdev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
234
c/candle/candle_ctrl_req.c
Normal file
234
c/candle/candle_ctrl_req.c
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016 Hubert Denkmair <hubert@denkmair.de>
|
||||
|
||||
This file is part of the candle windows API.
|
||||
|
||||
This library is free software: you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation, either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#include "candle_ctrl_req.h"
|
||||
#include "ch_9.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
enum {
|
||||
CANDLE_BREQ_HOST_FORMAT = 0,
|
||||
CANDLE_BREQ_BITTIMING = 1,
|
||||
CANDLE_BREQ_MODE = 2,
|
||||
CANDLE_BREQ_BERR = 3,
|
||||
CANDLE_BREQ_BT_CONST = 4,
|
||||
CANDLE_BREQ_DEVICE_CONFIG = 5,
|
||||
CANDLE_TIMESTAMP_GET = 6,
|
||||
/* 7: IDENTIFY, 8: GET_USER_ID, 9: SET_USER_ID (not used here) */
|
||||
CANDLE_BREQ_DATA_BITTIMING = 10,
|
||||
/* 11: SET_TERMINATION, not used here */
|
||||
CANDLE_BREQ_GET_STATE = 12,
|
||||
CANDLE_BREQ_BUS_OFF_RECOVERY = 32,
|
||||
};
|
||||
|
||||
static void candle_ctrl_logf(const wchar_t *fmt, ...)
|
||||
{
|
||||
if (candle_log_fn == NULL || !candle_log_verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t buf[512];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
HRESULT hr = StringCchVPrintfW(buf, 512, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
candle_log_fn(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static bool usb_control_msg(WINUSB_INTERFACE_HANDLE hnd, uint8_t request, uint8_t requesttype, uint16_t value, uint16_t index, void *data, uint16_t size)
|
||||
{
|
||||
WINUSB_SETUP_PACKET packet;
|
||||
memset(&packet, 0, sizeof(packet));
|
||||
|
||||
packet.Request = request;
|
||||
packet.RequestType = requesttype;
|
||||
packet.Value = value;
|
||||
packet.Index = index;
|
||||
packet.Length = size;
|
||||
|
||||
unsigned long bytes_sent = 0;
|
||||
BOOL rc = WinUsb_ControlTransfer(hnd, packet, (uint8_t*)data, size, &bytes_sent, 0);
|
||||
candle_ctrl_logf(L"ctrl req=0x%02x type=0x%02x value=%u index=%u size=%u rc=%u transferred=%lu winerr=%lu",
|
||||
request,
|
||||
requesttype,
|
||||
value,
|
||||
index,
|
||||
size,
|
||||
rc ? 1 : 0,
|
||||
bytes_sent,
|
||||
rc ? 0 : GetLastError());
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool candle_ctrl_set_host_format(candle_device_t *dev)
|
||||
{
|
||||
candle_host_config_t hconf;
|
||||
hconf.byte_order = 0x0000beef;
|
||||
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_BREQ_HOST_FORMAT,
|
||||
USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
1,
|
||||
dev->interfaceNumber,
|
||||
&hconf,
|
||||
sizeof(hconf)
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SET_HOST_FORMAT;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool candle_ctrl_set_device_mode(candle_device_t *dev, uint8_t channel, uint32_t mode, uint32_t flags)
|
||||
{
|
||||
candle_device_mode_t dm;
|
||||
dm.mode = mode;
|
||||
dm.flags = flags;
|
||||
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_BREQ_MODE,
|
||||
USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
channel,
|
||||
dev->interfaceNumber,
|
||||
&dm,
|
||||
sizeof(dm)
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SET_DEVICE_MODE;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
bool candle_ctrl_get_config(candle_device_t *dev, candle_device_config_t *dconf)
|
||||
{
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_BREQ_DEVICE_CONFIG,
|
||||
USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
1,
|
||||
dev->interfaceNumber,
|
||||
dconf,
|
||||
sizeof(*dconf)
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_GET_DEVICE_INFO;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool candle_ctrl_get_timestamp(candle_device_t *dev, uint32_t *current_timestamp)
|
||||
{
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_TIMESTAMP_GET,
|
||||
USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
1,
|
||||
dev->interfaceNumber,
|
||||
current_timestamp,
|
||||
sizeof(*current_timestamp)
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_GET_TIMESTAMP;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool candle_ctrl_get_capability(candle_device_t *dev, uint8_t channel, candle_capability_t *data)
|
||||
{
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_BREQ_BT_CONST,
|
||||
USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
channel,
|
||||
0,
|
||||
data,
|
||||
sizeof(*data)
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_GET_BITTIMING_CONST;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool candle_ctrl_set_bittiming(candle_device_t *dev, uint8_t channel, candle_bittiming_t *data)
|
||||
{
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_BREQ_BITTIMING,
|
||||
USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
channel,
|
||||
0,
|
||||
data,
|
||||
sizeof(*data)
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SET_BITTIMING;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool candle_ctrl_set_data_bittiming(candle_device_t *dev, uint8_t channel, candle_bittiming_t *data)
|
||||
{
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_BREQ_DATA_BITTIMING,
|
||||
USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
channel,
|
||||
0,
|
||||
data,
|
||||
sizeof(*data)
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SET_BITTIMING;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool candle_ctrl_get_state(candle_device_t *dev, uint8_t channel, candle_device_state_t *data)
|
||||
{
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_BREQ_GET_STATE,
|
||||
USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
channel,
|
||||
dev->interfaceNumber,
|
||||
data,
|
||||
sizeof(*data)
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_GET_DEVICE_INFO;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool candle_ctrl_bus_off_recover(candle_device_t *dev, uint8_t channel)
|
||||
{
|
||||
bool rc = usb_control_msg(
|
||||
dev->winUSBHandle,
|
||||
CANDLE_BREQ_BUS_OFF_RECOVERY,
|
||||
USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
|
||||
channel,
|
||||
dev->interfaceNumber,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
|
||||
dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SET_DEVICE_MODE;
|
||||
return rc;
|
||||
}
|
||||
49
c/candle/candle_ctrl_req.h
Normal file
49
c/candle/candle_ctrl_req.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016 Hubert Denkmair <hubert@denkmair.de>
|
||||
Copyright (c) 2026 Schildkroet
|
||||
|
||||
This file is part of the candle windows API.
|
||||
|
||||
This library is free software: you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation, either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "candle_defs.h"
|
||||
|
||||
enum {
|
||||
CANDLE_DEVMODE_RESET = 0,
|
||||
CANDLE_DEVMODE_START = 1
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint32_t state;
|
||||
uint32_t rxerr;
|
||||
uint32_t txerr;
|
||||
} candle_device_state_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
bool candle_ctrl_set_host_format(candle_device_t *dev);
|
||||
bool candle_ctrl_set_device_mode(candle_device_t *dev, uint8_t channel, uint32_t mode, uint32_t flags);
|
||||
bool candle_ctrl_get_config(candle_device_t *dev, candle_device_config_t *dconf);
|
||||
bool candle_ctrl_get_capability(candle_device_t *dev, uint8_t channel, candle_capability_t *data);
|
||||
bool candle_ctrl_set_bittiming(candle_device_t *dev, uint8_t channel, candle_bittiming_t *data);
|
||||
bool candle_ctrl_set_data_bittiming(candle_device_t *dev, uint8_t channel, candle_bittiming_t *data);
|
||||
bool candle_ctrl_get_timestamp(candle_device_t *dev, uint32_t *current_timestamp);
|
||||
bool candle_ctrl_get_state(candle_device_t *dev, uint8_t channel, candle_device_state_t *data);
|
||||
bool candle_ctrl_bus_off_recover(candle_device_t *dev, uint8_t channel);
|
||||
|
||||
101
c/candle/candle_defs.h
Normal file
101
c/candle/candle_defs.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016 Hubert Denkmair <hubert@denkmair.de>
|
||||
Copyright (c) 2026 Schildkroet
|
||||
|
||||
This file is part of the candle windows API.
|
||||
|
||||
This library is free software: you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation, either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <winbase.h>
|
||||
#include <winusb.h>
|
||||
#include <setupapi.h>
|
||||
#include <devguid.h>
|
||||
#include <regstr.h>
|
||||
|
||||
#undef __CRT__NO_INLINE
|
||||
#include <strsafe.h>
|
||||
#define __CRT__NO_INLINE
|
||||
|
||||
#include "candle.h"
|
||||
|
||||
#define CANDLE_MAX_DEVICES 32
|
||||
#define CANDLE_URB_COUNT 30
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
typedef struct {
|
||||
uint32_t byte_order;
|
||||
} candle_host_config_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint8_t reserved3;
|
||||
uint8_t icount;
|
||||
uint32_t sw_version;
|
||||
uint32_t hw_version;
|
||||
} candle_device_config_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t mode;
|
||||
uint32_t flags;
|
||||
} candle_device_mode_t;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
typedef struct {
|
||||
OVERLAPPED ovl;
|
||||
bool pending;
|
||||
uint8_t buf[512];
|
||||
} canlde_rx_urb;
|
||||
|
||||
typedef struct {
|
||||
wchar_t path[256];
|
||||
candle_devstate_t state;
|
||||
candle_err_t last_error;
|
||||
|
||||
HANDLE deviceHandle;
|
||||
WINUSB_INTERFACE_HANDLE winUSBHandle;
|
||||
UCHAR interfaceNumber;
|
||||
UCHAR bulkInPipe;
|
||||
UCHAR bulkOutPipe;
|
||||
HANDLE txEvent; /* pre-allocated event for timed overlapped writes */
|
||||
|
||||
candle_device_config_t dconf;
|
||||
candle_capability_t bt_const;
|
||||
/* Per-channel capabilities: index 0..dconf.icount, maximum 8 channels */
|
||||
candle_capability_t ch_caps[8];
|
||||
canlde_rx_urb rxurbs[CANDLE_URB_COUNT];
|
||||
HANDLE rxevents[CANDLE_URB_COUNT];
|
||||
} candle_device_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t num_devices;
|
||||
candle_err_t last_error;
|
||||
candle_device_t dev[CANDLE_MAX_DEVICES];
|
||||
} candle_list_t;
|
||||
|
||||
37
c/candle/ch_9.h
Normal file
37
c/candle/ch_9.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016 Hubert Denkmair <hubert@denkmair.de>
|
||||
|
||||
This file is part of the candle windows API.
|
||||
|
||||
This library is free software: you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation, either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define USB_DIR_OUT 0 /* to device */
|
||||
#define USB_DIR_IN 0x80 /* to host */
|
||||
|
||||
#define USB_TYPE_MASK (0x03 << 5)
|
||||
#define USB_TYPE_STANDARD (0x00 << 5)
|
||||
#define USB_TYPE_CLASS (0x01 << 5)
|
||||
#define USB_TYPE_VENDOR (0x02 << 5)
|
||||
#define USB_TYPE_RESERVED (0x03 << 5)
|
||||
|
||||
#define USB_RECIP_MASK 0x1f
|
||||
#define USB_RECIP_DEVICE 0x00
|
||||
#define USB_RECIP_INTERFACE 0x01
|
||||
#define USB_RECIP_ENDPOINT 0x02
|
||||
#define USB_RECIP_OTHER 0x03
|
||||
@@ -11,7 +11,6 @@ set(SETPROTOCOL_V2_SOURCES
|
||||
src/set_protocol.c
|
||||
src/set_can.c
|
||||
src/set_firmware.c
|
||||
src/set_boot.c
|
||||
src/set_telemetry.c
|
||||
src/set_plot.c
|
||||
src/set_trends.c
|
||||
@@ -37,12 +36,6 @@ set(SETPROTOCOL_LEGACY_SOURCES
|
||||
)
|
||||
|
||||
set(SETPROTOCOL_SOURCES
|
||||
src/set_regmap.c
|
||||
src/set_emu_ump.c
|
||||
src/set_emu_tms.c
|
||||
src/set_emu_igbt.c
|
||||
src/set_emu_server.c
|
||||
src/set_wavegen.c
|
||||
${SETPROTOCOL_V2_SOURCES}
|
||||
${SETPROTOCOL_LEGACY_SOURCES}
|
||||
)
|
||||
@@ -88,14 +81,6 @@ endif()
|
||||
option(SETP_BUILD_TESTS "Build host tests" ON)
|
||||
if(SETP_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_executable(test_emulator_services tests/test_emulator_services.c ports/example-emulator/service.c)
|
||||
target_link_libraries(test_emulator_services PRIVATE setprotocol_static)
|
||||
add_test(NAME emulator_services COMMAND test_emulator_services)
|
||||
if(MSVC)
|
||||
target_compile_options(test_emulator_services PRIVATE /UNDEBUG)
|
||||
else()
|
||||
target_compile_options(test_emulator_services PRIVATE -UNDEBUG)
|
||||
endif()
|
||||
add_executable(test_altera_logic tests/test_altera_logic.c)
|
||||
target_link_libraries(test_altera_logic PRIVATE setprotocol_static)
|
||||
add_test(NAME altera_logic COMMAND test_altera_logic)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# SETProtocol
|
||||
|
||||
Универсальные сервисы эмуляторов: [адресное пространство, API и порт новой платы](docs/EMULATOR_SERVICES.md).
|
||||
`set_regmap` объединяет УМП, 2812, IGBT и DAC за общим API для RTU, CAN и других оболочек.
|
||||
|
||||
Единое переносимое протокольное ядро SET для `SETGUI`, Android GUI, устройств
|
||||
и сервисных утилит. В одном C99-проекте собраны:
|
||||
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
# Универсальное адресное пространство эмуляторов
|
||||
|
||||
Общий код находится в `templates/c/set-protocol`. Прошивка платы задаёт память,
|
||||
GPIO и аппаратные callbacks; протокол и модели не включают STM32/HAL/USB/ОС,
|
||||
не выделяют heap и не читают системное время. Все вызовы одного экземпляра
|
||||
сериализуются главным циклом или блокировкой вызывающего.
|
||||
|
||||
## Слои и точка расширения
|
||||
|
||||
- `set_regmap.h/.c`: таблица непересекающихся регионов и общий вызов
|
||||
`set_regmap_request(map, function, address, value, out, capacity, &count)`.
|
||||
Function 3 читает `value` слов, function 6 пишет одно слово.
|
||||
- `set_emu_ump`, `set_emu_tms`, `set_emu_igbt`, `set_wavegen`:
|
||||
независимые экземпляры моделей. Callbacks получают **относительный offset**,
|
||||
а не внешний адрес. Один модуль можно разместить несколько раз по разным адресам.
|
||||
- `set_emu_server`: готовая композиция для текущего стенда, режим УМП/2812,
|
||||
подготовка настроек с применением через 500 тиков.
|
||||
- `set_regmap_rtu` и `set_regmap_can`: готовые транспортные кодеки над одной картой.
|
||||
`set_emu_server_rtu/can` дополнительно сохраняют legacy поведение стенда.
|
||||
USB CDC, UART, TCP или иной поток только доставляет полный RTU-кадр.
|
||||
Другой протокол разбирает собственную оболочку и вызывает `set_regmap_request`.
|
||||
|
||||
Вызов с неизвестной функцией возвращает 1; неизвестный адрес, RO-запись,
|
||||
чтение через границу региона — 2; неверное значение или размер буфера — 3;
|
||||
ошибка аппаратного порта — 4; занятый модуль — 6. При ошибке `count=0`.
|
||||
Длина успешного чтения равна запрошенной, запись возвращает адрес/значение.
|
||||
Максимум — 124 слова. RTU-обёртка требует буфер 253 байта до выполнения команды;
|
||||
CAN — 124 слова. Недостаточный буфер не может выполнить запись без ответа.
|
||||
При ошибке содержимое выходного буфера не используется.
|
||||
|
||||
`set_regmap_init` проверяет границы 16-битного пространства, пересечения,
|
||||
права и callbacks. Таблица и состояния принадлежат вызывающему и должны жить
|
||||
дольше карты. После регистрации таблица неизменна; для изменения карты
|
||||
инициализируйте её заново в момент, когда нет обслуживаемых запросов.
|
||||
|
||||
## Каталог и стандартная карта
|
||||
|
||||
Каталог доступен на адресе 0 через любой транспорт.
|
||||
|
||||
| Слово заголовка | Значение |
|
||||
|---|---|
|
||||
| 0 | 0x5345, сигнатура SE |
|
||||
| 1 | версия каталога 1 |
|
||||
| 2 | количество регионов |
|
||||
| 3 | начало дескрипторов: 8 |
|
||||
| 4 | длина дескриптора: 8 |
|
||||
| 5 | максимум слов в одном чтении: 124 |
|
||||
| 6, 7 | резерв: 0 |
|
||||
|
||||
Дескриптор: `kind, version, instance, base, words, flags, 0, 0`.
|
||||
Flags: bit0 чтение, bit1 запись. Права описывают возможности региона;
|
||||
отдельные регистры внутри могут быть только для чтения. Пропуски внутри
|
||||
региона возвращают ошибку, если модуль их не поддерживает.
|
||||
|
||||
| Kind | Стандартный адрес | Размер | Модуль |
|
||||
|---|---|---|---|
|
||||
| 1 | 0x1000 | 0x17C | УМП: статус, окно, онлайн |
|
||||
| 2 | 0x1200 | 36 | управление, конфигурация и indexed capability |
|
||||
| 3 | 0x1300 | 8 | генератор DAC |
|
||||
| 4 | 0x1400 | 112 | IGBT, версия 3 |
|
||||
| 5 | 0x1500 | 80 | 2812 через регистры |
|
||||
| 6 | 0x2000 | capacity × 40 | архив/банк УМП |
|
||||
| 7 | 0xA000 | wave capacity | таблица DAC |
|
||||
|
||||
Эта карта — готовая композиция, а не требование к новым платам.
|
||||
`set_regmap` позволяет выбирать адреса, подмножество и несколько экземпляров
|
||||
модуля; каталог сообщает фактическую карту. Настройка UART и выбор УМП/2812
|
||||
принадлежат только готовому `set_emu_server`. Максимальная ёмкость УМП
|
||||
в этой карте — 819 записей на банк; текущая STM32 использует 800.
|
||||
|
||||
В режиме 2812 прямое обращение к регистрам УМП возвращает busy, legacy
|
||||
RTU/CAN к этим диапазонам игнорируются для совместимости. Остальные сервисы
|
||||
и каталог доступны. Регистры управления 2812 принимают запись только в режиме 2812.
|
||||
Чтение его состояния не запускает модель.
|
||||
|
||||
## IGBT
|
||||
|
||||
Предыдущий локальный диапазон IGBT 0x1300 конфликтовал с DAC. Его alias
|
||||
сохранить нельзя: он обращается к генератору. Используйте каталог kind=4 или
|
||||
новый стандартный адрес 0x1400.
|
||||
|
||||
| Offset | Назначение |
|
||||
|---|---|
|
||||
| 0, 1 | 0x4947, версия 3 |
|
||||
| 2 | enable 0/1; выключение сразу очищает состояние RX и ожидаемые ACK |
|
||||
| 3 | чтение последнего TX; запись подготовленной TX-маски |
|
||||
| 4 | RX-маска |
|
||||
| 5 | резерв 0 |
|
||||
| 6 | FAULT-маска 0…255 |
|
||||
| 7 | задержка ACK 0…1000 мс |
|
||||
| 8 | ширина ACK 1…255 мс |
|
||||
| 9 | счётчик фронтов modulo 65536; запись 0 сбрасывает |
|
||||
| 10 | число каналов 8 |
|
||||
| 11 | один шаг модели = 1 мс |
|
||||
| 12 | источник TX: 0 переданные GPIO, 1 подготовленная маска |
|
||||
| 13 | подготовленная TX-маска, чтение/запись |
|
||||
| 14 | запись маски запуска одиночного TX; чтение 0 |
|
||||
| 15 | длина записи канала: 12 слов |
|
||||
|
||||
Версия 3 сохраняет первые 14 регистров. Общие delay/width при записи
|
||||
устанавливают значения для всех каналов; при чтении показывают последнее
|
||||
общее задание, а не поканальные значения. Полное состояние: FC03 0x1400 / 112 слов.
|
||||
Запуск TX требует enabled=1, source=1, разрешённого TX канала и низкого TX;
|
||||
повторный запуск высокого TX отвергается кодом 6. Длительность отсчитывает
|
||||
сама модель, не клиент COM. Ручная запись TX отменяет все текущие TX-импульсы.
|
||||
Выключение эмуляции отменяет импульсы и сбрасывает их TX; статическое задание
|
||||
TX сохраняется. PB остаются входами: тестовый TX — внутренняя подстановка,
|
||||
физическая обратная связь RX выводится на PC0…PC7.
|
||||
|
||||
Поканальные регистры: `0x1410 + 12 * channel`, channel=0…7.
|
||||
|
||||
| Offset канала | Значение |
|
||||
|---|---|
|
||||
| 0 | flags: bit0 разрешение TX, bit1 разрешение RX, bit2 ACK active-low |
|
||||
| 1 | RX mode: 0 ACK, 1 физический 0, 2 физическая 1, 3 пропуск ACK |
|
||||
| 2, 3 | задержка ACK ON 0…1000 мс, ширина 1…1000 мс |
|
||||
| 4, 5 | задержка ACK OFF 0…1000 мс, ширина 1…1000 мс |
|
||||
| 6 | одиночный TX: 1…60000 мс |
|
||||
| 7 | маска фронтов для ACK: bit0 ON, bit1 OFF |
|
||||
| 8 | ширина FAULT 1…60000 мс; 0 — удержание до снятия FAULT |
|
||||
| 9 | RO счётчик фронтов канала, modulo 65536 |
|
||||
| 10, 11 | RO оставшаяся задержка / длительность ACK в мс |
|
||||
|
||||
Изменение flags/mode отменяет ожидаемый и активный ACK канала.
|
||||
Отключённый RX и выключенная модель всегда дают физический 0.
|
||||
Инверсия применяется к автоматическому ответу и idle при пропуске ACK;
|
||||
ручные уровни 0/1 не инвертируются. Сброс общего счётчика сбрасывает и канальные.
|
||||
FAULT моделирует длинный/удерживаемый ответ после фронта; это не модель Vce,
|
||||
Vge, DESAT, внутренней защиты или блокировки конкретного силового драйвера.
|
||||
|
||||
`set_igbt_tick(state, pins)` получает маску входов от порта платы.
|
||||
Фронт на тике T запускает ACK на T+delay. Повторный фронт до начала ACK
|
||||
перезапускает задержку; это определённая модельная обработка слишком частых команд.
|
||||
Каждый из восьми каналов независим. FAULT удерживает RX после его активации;
|
||||
снятие FAULT освобождает только ранее аварийные каналы и сохраняет обычные ACK.
|
||||
Чтобы TX через регистры не перезаписывался GPIO, выберите source=1.
|
||||
|
||||
**Текущая IGBT-модель имеет шаг 1 мс.** Она проверяет логическую последовательность
|
||||
Vin/Vstat, но не воспроизводит ACK 250/380 нс и ширины профилей 1SP/1SD в DSLogic.
|
||||
Такую аппаратную точность нельзя получить переносом модели или вызовом из SysTick:
|
||||
для неё требуется отдельный быстрый аппаратный порт и соответствующая модель времени.
|
||||
|
||||
## 2812
|
||||
|
||||
Состояние едино для legacy CMD_STD и адресного API:
|
||||
|
||||
| Offset | Назначение |
|
||||
|---|---|
|
||||
| 0, 1 | 0x544D, версия 1 |
|
||||
| 2, 3, 4 | running, частота и задание (int16 ×100 Гц) |
|
||||
| 8…11 | четыре подготовленных analog int16 |
|
||||
| 12…17 | шесть подготовленных управляющих байт (значение 0…255) |
|
||||
| 18 | записать 1: атомарно применить подготовленную команду |
|
||||
| 32…45 | 28 байт дискретов; младший байт слова — первый байт |
|
||||
| 46…79 | 34 аналоговых int16 |
|
||||
| остальные | чтение 0 |
|
||||
|
||||
Модель остаётся упрощённой BALZAM_7: Go, выбор remote, Setspeed,
|
||||
разгон 10 Гц/с и синтетическая телеметрия. Legacy CMD_STD имеет отдельный
|
||||
адрес 2812, запрос 18 и ответ 101 байт, с проверкой CRC до изменения состояния.
|
||||
Буфер ответа менее 101 байта запрещает выполнение команды.
|
||||
|
||||
## УМП и DAC
|
||||
|
||||
УМП сохраняет прежние записи по 40 слов, банк/архив, фронтовой триггер,
|
||||
постинтервал 1000 мс и indexed-чтение без изменения курсора. Два разных
|
||||
буфера по `capacity*40` слов предоставляет плата. Размещение SRAM/CCM
|
||||
не является частью библиотеки.
|
||||
|
||||
DAC использует прежний `set_wave_port` с `start/stop`, транзакционную загрузку,
|
||||
commit, чтение таблицы и запрет изменений при работе. Регистровые callbacks
|
||||
и прежний `set_wave_rtu` вызывают одну реализацию. До повторного init вызывающий
|
||||
останавливает аппаратный вывод; библиотека не угадывает текущее состояние DMA.
|
||||
|
||||
## Обёртки и новая плата
|
||||
|
||||
Пример: [ports/example-emulator/service.c](../ports/example-emulator/service.c).
|
||||
Он регистрирует два экземпляра IGBT и показывает прямой, RTU и CAN вызов.
|
||||
Порт новой платы должен:
|
||||
|
||||
1. Создать состояния и таблицу регионов, вызвать init и проверить результат.
|
||||
2. Предоставить буферы и аппаратный DAC-порт, если нужен генератор.
|
||||
3. Передавать реальные тики и входы; читать результат модели и обновлять GPIO.
|
||||
4. Доставлять запросы через выбранную оболочку в одну карту, сериализуя доступ.
|
||||
|
||||
CAN-envelope совместим с UMP: BE address/value/0x1F00/token/function.
|
||||
Идентификаторы CAN и маршрутизацию задаёт порт. Ответ делится по два слова,
|
||||
нечётное чтение дополняется нулём до чётного числа; клиент отбрасывает последнее
|
||||
слово по длине исходного запроса. Токен 1…255; один незавершённый ответ на соединение.
|
||||
Проверенная копия слов хранится до отправки всех фрагментов.
|
||||
|
||||
`set_emu_stream` — только сборщик известных запросов FC03/FC06 и CMD_STD,
|
||||
по одному экземпляру на поток. Он принимает монотонные uint32 миллисекунды,
|
||||
сохраняет кадр между USB-пакетами, сбрасывает незавершённый после паузы >1000 мс
|
||||
и восстанавливается после плохого CRC. Это не универсальный Modbus-парсер
|
||||
FC16/FC23: для другого формата нужна другая тонкая оболочка.
|
||||
|
||||
## Сборка и проверки
|
||||
|
||||
C99-ядро подключается через `setprotocol_static`/DLL, определения публичного
|
||||
API экспортируются через PCAN_ABI_API. Включить `set_regmap.h` или
|
||||
`set_emu_server.h`. Для иной архитектуры проверить наличие uint8/16/32_t;
|
||||
существующий 16-битный C28x octet-порт UMP остаётся отдельным, этот новый
|
||||
байтовый API пока не проверен TI-компилятором.
|
||||
|
||||
`test_emulator_services.c` проверяет каталог, пересечения, границы и короткие
|
||||
буферы, два экземпляра, переназначение адресов, RTU/CAN/stream, совместное
|
||||
состояние 2812 и DAC, длинные задержки IGBT и освобождение FAULT.
|
||||
Существующие тесты прошивки дополнительно проверяют архив УМП, USB-фрагментацию,
|
||||
смену режима/адресов и совместимость с декодерами SETGUI.
|
||||
|
||||
## Выбор единственного эмулятора
|
||||
|
||||
В SETGUI: «Эмулятор → Настройки STM → Тип эмулятора», затем
|
||||
«Применить в STM и проверить» и «Открыть управление эмулятором».
|
||||
Конфигурация версии 3: режим 0 — УМП, 1 — 2812, 2 — IGBT, 3 — ЦАП.
|
||||
Регистр 0x1219 подготавливает режим; 0x1218 = 0xA55A применяет его через
|
||||
500 мс после подтверждения. Статус FC03 0x1210 / 10 слов содержит версию 3,
|
||||
действующий режим в слове 8 и подготовленный в слове 9.
|
||||
|
||||
При смене режима останавливаются TIM6/DMA/DAC, ЦАП устанавливается в ноль,
|
||||
IGBT отключается и его выходы обнуляются, 2812 сбрасывается, УМП останавливается.
|
||||
Новый режим не запускается автоматически. Для IGBT нужно явно включить
|
||||
эмуляцию; для ЦАП заново загрузить таблицу и дать команду пуска.
|
||||
Повторное применение того же режима не прерывает текущую работу.
|
||||
Записи невыбранным IGBT/ЦАП/2812 отклоняются с кодом 6; чтение статуса
|
||||
и явные команды остановки IGBT/ЦАП доступны. Настройки связи доступны всегда.
|
||||
После сброса выбран УМП, IGBT выключен, ЦАП не запущен.
|
||||
@@ -1,126 +0,0 @@
|
||||
# Генератор произвольного сигнала WG v1
|
||||
|
||||
Общее C99-ядро строит кривые по точкам, преобразует напряжения в коды ЦАП и
|
||||
принимает таблицы через RTU поверх USB CDC/COM. Оно не зависит от Qt, HAL или ОС.
|
||||
Память предоставляет вызывающий; скрытого heap и глобального состояния нет.
|
||||
|
||||
```text
|
||||
SETGUI: точки -> Python/ctypes -> set_signal.c -> график / CSV / C
|
||||
-> set_wavegen.c -> USB CDC
|
||||
MCU: USB stream -> set_wave_rtu -> set_wave_port -> TIM6/DMA/DAC
|
||||
```
|
||||
|
||||
| Файл | Зависимости и назначение |
|
||||
|---|---|
|
||||
| `include/set_signal.h`, `src/set_signal.c` | C99/math: МНК 1…5, linear, PCHIP, natural cubic spline, 12-bit DAC |
|
||||
| `include/set_wavegen.h`, `src/set_wavegen.c` | C99 + `set_crc.c`: транзакционная загрузка и wire codec |
|
||||
| `ports/stm32f407-wavegen` | CMSIS F407: PA4/DAC1, TIM6, DMA1 Stream5 Channel7 |
|
||||
| `ports/stm32g474-wavegen` | STM32G4 HAL: готовые DAC/TIM/DMA handles из CubeMX |
|
||||
| `python/set_devices/signal_reconstruction.py`, `waveform.py` | Модели, JSON/экспорт, ctypes; алгоритмов на Python нет |
|
||||
| `python/set_devices/wavegen_protocol.py`, `qt_ports/wavegen.py` | ctypes-кодек и Qt COM worker с проверкой чтением обратно |
|
||||
|
||||
## Интерполяция и таблица
|
||||
|
||||
`set_signal_reconstruct` принимает раздельные X/Y и workspace `14*N+128`
|
||||
элементов double. Сортировка, усреднение одинаковых времён, нормализация,
|
||||
МНК через QR и интерполяция выполняются в C. Максимум 100000 входных/10000
|
||||
выходных точек. Переданный workspace может повторно использоваться.
|
||||
Большие расчёты предназначены для хоста; на MCU можно строить малые таблицы
|
||||
или принимать готовую таблицу без затрат на интерполяцию.
|
||||
|
||||
`endpoint=1` включает последний X (анализ логов); `endpoint=0` строит один
|
||||
период `[0,T)` без дублированного отсчёта стыка. Частота Fs и период T задают
|
||||
целое `N=Fs*T`; для WG максимум N=4096, Fs=1…50000 Гц, один канал, циклический
|
||||
выход. При разных значениях первой и последней точки на стыке будет скачок.
|
||||
Напряжения 0…Vref округляются к ближайшему коду 0…4095; выход за диапазон
|
||||
отклоняется целиком. Это относится и к выбросам сплайна. Vref должен
|
||||
соответствовать фактическому VDDA/VREF+ платы; это не программируемое питание.
|
||||
|
||||
Пример хоста (DLL передаётся через `SETPROTOCOL_LIBRARY`):
|
||||
|
||||
```python
|
||||
from set_devices.waveform import generate, c_header
|
||||
points = [(0, 0), (10, 3.3), (20, 0)]
|
||||
wave = generate(points, sample_rate=10000, vref=3.3, method="linear")
|
||||
assert len(wave.codes) == 200
|
||||
with open("waveform.h", "w", encoding="utf-8") as output:
|
||||
output.write(c_header(wave))
|
||||
```
|
||||
|
||||
## Контракт порта
|
||||
|
||||
`start(context, samples, count, sample_rate)` возвращает 0 при успехе;
|
||||
`stop(context)` синхронно прекращает DMA и устанавливает нулевой выход.
|
||||
Память таблицы принадлежит `set_wave_state`, её нельзя менять до stop.
|
||||
Вызывайте RTU и обслуживание состояния из одного основного потока, не из IRQ.
|
||||
IRQ USB помещает байты в очередь; USB-пакеты не являются границами RTU.
|
||||
|
||||
```c
|
||||
static uint16_t samples[SET_WAVE_MAX]; /* DMA-accessible SRAM, not CCM */
|
||||
static set_wave_state wave;
|
||||
static set_wave_f407 hardware = {72000000};
|
||||
void application_init(void) {
|
||||
set_wave_port port = set_wave_f407_port(&hardware);
|
||||
set_wave_init(&wave, samples, SET_WAVE_MAX, &port);
|
||||
}
|
||||
size_t on_rtu(const uint8_t *frame, size_t n, uint8_t reply[256]) {
|
||||
return set_wave_rtu(&wave, 16, frame, n, reply, 256);
|
||||
}
|
||||
```
|
||||
|
||||
Для F407 DAC_CH1 — PA4, TIM6 TRGO_UPDATE, DMA1 Stream5/Channel7. Порт использует
|
||||
только нижние 16 бит DAC_CR и не меняет канал 2. Таблица лежит в SRAM1/2
|
||||
`0x20000000…0x2001FFFF`, не в CCM. Перед первым отсчётом есть один такт
|
||||
предзагрузки последнего значения предыдущего периода. Для гарантированной
|
||||
целой частоты порт принимает только делители входной частоты TIM6; при
|
||||
72 МГц подходят, например, 100, 1000, 5000, 10000, 20000 Гц.
|
||||
Частоту таймера передавайте с учётом удвоения при делителе APB1 > 1.
|
||||
Ошибку DMA/underrun проверяйте через `set_wave_f407_fault()` и останавливайте
|
||||
состояние. На F407-проекте этот вызов включён в главный цикл.
|
||||
|
||||
Для G474 настройте CubeMX: DAC1 channel1 (PA4), output buffer enabled,
|
||||
TIM6 TRGO_UPDATE, DMA memory-to-peripheral, circular, halfword/halfword,
|
||||
memory increment, запрос `DMA_REQUEST_DAC1_CHANNEL1`; подключите HAL IRQ
|
||||
для выбранного канала DMA и DAC. Передайте handles и timer_hz в
|
||||
`set_wave_g474`. Обработчики ошибок DMA/DAC в приложении должны вызвать stop
|
||||
и сбросить `wave.running/ready`. Порт проверен компиляцией с CubeG4 1.6.1;
|
||||
конкретная плата G474 здесь не прошита.
|
||||
|
||||
## USB / Modbus RTU
|
||||
|
||||
Адрес по умолчанию 16, FC03/FC06, CRC16 Modbus. Максимальный запрос — 8 байт,
|
||||
ответ — 21 байт; существующий потоковый USB RTU parser F407 подходит без
|
||||
изменения формата. Все поля строит/проверяет C-кодек, GUI не пакует байты.
|
||||
|
||||
| Регистр | Значение |
|
||||
|---|---|
|
||||
| `0x1300`, FC03, 8 слов | `0x5747`, версия 1, running, Fs, count, received, ready, capacity |
|
||||
| `0x1302`, FC06 | 0: stop и нулевой выход; 1: start только после commit |
|
||||
| `0x1303`, FC06 | Частота 1…50000; сбрасывает ready |
|
||||
| `0x1304`, FC06 | Начать загрузку, число отсчётов 2…4096; сброс received/ready |
|
||||
| `0xA000+i`, FC06 | Последовательная загрузка отсчёта 0…4095; повтор того же значения допустим |
|
||||
| `0xA000+i`, FC03, 1 слово | Чтение загруженного отсчёта для проверки |
|
||||
| `0x1306`, FC06 | `0xA55A`: commit, только если все отсчёты загружены |
|
||||
|
||||
Во время running разрешены чтение и stop; запись/перезагрузка дают исключение
|
||||
6. Неполная таблица не запускается. Повреждённый CRC и чужой адрес не меняют
|
||||
состояние. При reset ready/running=0, таблица не сохраняется во Flash.
|
||||
Профиль и регистры генератора не являются регистратором УМП или протоколом 2812.
|
||||
|
||||
Хост выполняет probe → stop → Fs → count → samples → readback каждого
|
||||
отсчёта → commit → status. Пуск — отдельная пользовательская команда.
|
||||
После потери USB уже запущенный DAC продолжает автономное воспроизведение;
|
||||
при таймауте состояние неизвестно до повторного status/stop. Одновременно
|
||||
используйте одного клиента, не открывайте этот же COM во вкладке подключения.
|
||||
|
||||
## Потребители и проверки
|
||||
|
||||
- SETGUI: «Логи и графики → Генератор», JSON, CSV и C header.
|
||||
- `home/407vet6_emul_TMS_Periph`: USB CDC и UART, штатный адрес настройки STM,
|
||||
отдельный PA4 DAC выход; HAL/CMSIS и настройки платы остаются в проекте.
|
||||
- G474: переносимый HAL-порт; board init остаётся в целевой прошивке.
|
||||
- `tests/test_signal_wave.c`: C-векторы интерполяции, DAC, CRC, неполной
|
||||
загрузки, readback и блокировки running. Общие байты проверены Python
|
||||
ctypes и фактическим USB stream parser эмулятора.
|
||||
|
||||
Аппаратная проверка амплитуды, периода и формы осциллографом не выполнена.
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef SET_BOOT_H
|
||||
#define SET_BOOT_H
|
||||
#include "set_firmware.h"
|
||||
|
||||
/* Transport-independent, single-slot updater. invalidate MUST become durable
|
||||
* before erase; commit is called only after read-back verification. */
|
||||
typedef struct {
|
||||
bool (*invalidate)(void *user);
|
||||
bool (*erase)(void *user, uint32_t size);
|
||||
bool (*write)(void *user, uint32_t offset, const uint8_t *data, uint16_t size);
|
||||
bool (*verify)(void *user, uint32_t size, uint32_t crc);
|
||||
bool (*commit)(void *user, uint32_t size, uint32_t crc);
|
||||
void (*activate)(void *user);
|
||||
} setp_boot_ops;
|
||||
typedef struct {
|
||||
setp_boot_ops ops;
|
||||
void *user;
|
||||
uint32_t base, capacity, crc;
|
||||
uint16_t block_size;
|
||||
setp_fw_status_t status;
|
||||
} setp_boot;
|
||||
void setp_boot_init(setp_boot *b, const setp_boot_ops *ops, void *user,
|
||||
uint32_t base, uint32_t capacity);
|
||||
/* Returns SET status; FW_STATUS body is encoded by the caller. */
|
||||
uint16_t setp_boot_command(setp_boot *b, uint16_t type, const uint8_t *p, uint16_t n);
|
||||
#endif
|
||||
@@ -1,32 +0,0 @@
|
||||
#ifndef SET_EMU_IGBT_H
|
||||
#define SET_EMU_IGBT_H
|
||||
#include "set_regmap.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_IGBT_CHANNELS 8u
|
||||
#define SET_IGBT_WORDS 112u
|
||||
#define SET_IGBT_CHANNEL_WORDS 12u
|
||||
typedef struct {
|
||||
uint16_t flags, mode, on_delay, on_width, off_delay, off_width;
|
||||
uint16_t tx_width, edges, fault_width, count;
|
||||
} set_igbt_channel;
|
||||
typedef struct {
|
||||
uint8_t enabled, tx, previous_tx, rx, fault, injected_tx, input_source;
|
||||
uint16_t pending[SET_IGBT_CHANNELS], active[SET_IGBT_CHANNELS];
|
||||
uint16_t delay_ms, width_ms, event_count;
|
||||
uint8_t logical_rx;
|
||||
uint16_t pulse[SET_IGBT_CHANNELS], response_width[SET_IGBT_CHANNELS];
|
||||
set_igbt_channel channel[SET_IGBT_CHANNELS];
|
||||
} set_igbt_emu;
|
||||
PCAN_ABI_API void set_igbt_init(set_igbt_emu *);
|
||||
/* One model tick = 1 ms, no system clock. This coarse logical model does NOT
|
||||
* reproduce the sub-microsecond timing of 1SP/1SD DSLogic hardware profiles.
|
||||
* input_source=0 takes the supplied pins, 1 uses register-injected TX. */
|
||||
PCAN_ABI_API void set_igbt_tick(set_igbt_emu *, uint8_t pins);
|
||||
PCAN_ABI_API unsigned set_igbt_read(void *, uint16_t offset, uint16_t count, uint16_t *);
|
||||
PCAN_ABI_API unsigned set_igbt_write(void *, uint16_t offset, uint16_t value);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,48 +0,0 @@
|
||||
/* Reference service composition for emulator boards; use set_regmap directly
|
||||
* for a different map, subset, or multiple instances of any module. */
|
||||
#ifndef SET_EMU_SERVER_H
|
||||
#define SET_EMU_SERVER_H
|
||||
#include "set_emu_ump.h"
|
||||
#include "set_emu_tms.h"
|
||||
#include "set_emu_igbt.h"
|
||||
#include "set_wavegen.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_EMU_CONTROL_BASE 0x1200u
|
||||
#define SET_EMU_IGBT_BASE 0x1400u
|
||||
#define SET_EMU_TMS_BASE 0x1500u
|
||||
#define SET_EMU_INDEXED_BASE 0x2000u
|
||||
enum { SET_EMU_KIND_UMP=1,SET_EMU_KIND_CONTROL=2,SET_EMU_KIND_WAVE=3,
|
||||
SET_EMU_KIND_IGBT=4,SET_EMU_KIND_TMS=5,SET_EMU_KIND_UMP_DATA=6,
|
||||
SET_EMU_KIND_WAVE_DATA=7 };
|
||||
typedef struct {
|
||||
set_regmap map;
|
||||
set_reg_region regions[7];
|
||||
set_ump_emu ump;
|
||||
set_tms_emu tms;
|
||||
set_igbt_emu igbt;
|
||||
set_wave_state *wave;
|
||||
/* config[3]: 0=UMP, 1=TMS2812, 2=IGBT, 3=DAC. Configuration version 3. */
|
||||
uint16_t config[4],staged[4],apply_delay;
|
||||
} set_emu_server;
|
||||
/* bank_capacity in records, each bank has capacity*40 words.
|
||||
* Wave may be NULL. UART rate index 0..4. No pins, heap or global instances.
|
||||
* Init is for a quiescent instance: caller stops hardware before reinitializing. */
|
||||
PCAN_ABI_API unsigned set_emu_server_init(set_emu_server *,uint16_t *bank0,
|
||||
uint16_t *bank1,uint16_t bank_capacity,set_wave_state *,unsigned address,
|
||||
unsigned tms_address,unsigned baud_index,unsigned can_mode,unsigned enabled);
|
||||
PCAN_ABI_API void set_emu_server_tick(set_emu_server *,uint8_t igbt_pins);
|
||||
PCAN_ABI_API uint32_t set_emu_server_baud(const set_emu_server *);
|
||||
PCAN_ABI_API size_t set_emu_server_rtu(set_emu_server *,const uint8_t *,size_t,uint8_t *,size_t);
|
||||
PCAN_ABI_API size_t set_emu_server_can(set_emu_server *,const uint8_t *,size_t,
|
||||
uint16_t *,size_t,unsigned *,unsigned *);
|
||||
/* Stream adapter for FC03/06 (8 octets) and TMS CMD_STD (18 octets). One
|
||||
* context per connection. Transport passes monotonic milliseconds. */
|
||||
typedef struct { uint8_t data[18],used;uint32_t last_byte; } set_emu_stream;
|
||||
PCAN_ABI_API size_t set_emu_stream_feed(set_emu_stream *,set_emu_server *,
|
||||
uint8_t byte,uint32_t now_ms,uint8_t *reply,size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef SET_EMU_TMS_H
|
||||
#define SET_EMU_TMS_H
|
||||
#include "set_regmap.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_TMS_WORDS 80u
|
||||
typedef struct {
|
||||
uint8_t controls[6], running;
|
||||
uint16_t commands[10], divider;
|
||||
int32_t frequency, target;
|
||||
} set_tms_emu;
|
||||
PCAN_ABI_API void set_tms_init(set_tms_emu *);
|
||||
PCAN_ABI_API void set_tms_tick(set_tms_emu *);
|
||||
PCAN_ABI_API unsigned set_tms_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_tms_write(void *,uint16_t,uint16_t);
|
||||
PCAN_ABI_API size_t set_tms_rtu(set_tms_emu *,unsigned device,const uint8_t *,
|
||||
size_t,uint8_t *,size_t);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef SET_EMU_UMP_H
|
||||
#define SET_EMU_UMP_H
|
||||
#include "set_regmap.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_UMP_WORDS 40u
|
||||
typedef struct {
|
||||
uint16_t (*banks[2])[SET_UMP_WORDS];
|
||||
uint16_t capacity,can_mode;
|
||||
uint8_t enabled;
|
||||
uint16_t state,used,next,bank,archive_bank,archive_count,archive_next,period,divider,cursor,source,generation,post,dropped,prev_command,prev_input,prev_output;
|
||||
uint32_t elapsed,sequence,scenario;
|
||||
} set_ump_emu;
|
||||
/* Two distinct caller-owned arrays, capacity*40 words EACH. No heap/MCU layout. */
|
||||
PCAN_ABI_API unsigned set_ump_init(set_ump_emu *,uint16_t *,uint16_t *,uint16_t capacity,
|
||||
uint16_t can_mode,uint8_t enabled);
|
||||
PCAN_ABI_API void set_ump_tick(set_ump_emu *); /* one millisecond */
|
||||
PCAN_ABI_API unsigned set_ump_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_ump_indexed_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_ump_write(void *,uint16_t,uint16_t);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,48 +0,0 @@
|
||||
/* Transport-independent 16-bit word address space. No heap, clocks or board headers. */
|
||||
#ifndef SET_REGMAP_H
|
||||
#define SET_REGMAP_H
|
||||
#include "pcan_abi.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_REG_MAX_WORDS 124u
|
||||
#define SET_REG_READ 1u
|
||||
#define SET_REG_WRITE 2u
|
||||
enum { SET_REG_OK=0, SET_REG_FUNCTION=1, SET_REG_ADDRESS=2,
|
||||
SET_REG_VALUE=3, SET_REG_FAILURE=4, SET_REG_BUSY=6 };
|
||||
typedef unsigned (*set_reg_read_fn)(void *, uint16_t, uint16_t, uint16_t *);
|
||||
typedef unsigned (*set_reg_write_fn)(void *, uint16_t, uint16_t);
|
||||
typedef struct {
|
||||
uint16_t base, words, kind, version, instance, flags;
|
||||
void *context;
|
||||
set_reg_read_fn read;
|
||||
set_reg_write_fn write;
|
||||
} set_reg_region;
|
||||
typedef struct {
|
||||
const set_reg_region *regions;
|
||||
size_t count;
|
||||
} set_regmap;
|
||||
/* Directory at 0: 8-word header, then 8 words per region. Reject overlaps,
|
||||
* wraparound, invalid callbacks and overlap with the directory itself.
|
||||
* Regions and contexts must outlive the map and remain unchanged. */
|
||||
PCAN_ABI_API unsigned set_regmap_init(set_regmap *, const set_reg_region *, size_t);
|
||||
/* One region per request. Preflight all bounds/capacity before calling a module.
|
||||
* Writes return absolute address/value echo. Reads must be side-effect free.
|
||||
* A module validates the entire write before mutation. Caller serializes access. */
|
||||
PCAN_ABI_API unsigned set_regmap_request(const set_regmap *, unsigned function,
|
||||
uint16_t address, uint16_t value, uint16_t *out, size_t capacity, size_t *count);
|
||||
/* Complete Modbus RTU FC03/06 frame; no broadcast. Capacity >=253 bytes.
|
||||
* CRC/address/capacity validation precedes dispatch. */
|
||||
PCAN_ABI_API size_t set_regmap_rtu(const set_regmap *, unsigned device,
|
||||
const uint8_t *, size_t, uint8_t *, size_t);
|
||||
/* Legacy UMP CAN envelope, usable by any region: BE address/value/1F00/token/fn.
|
||||
* Returns padded EVEN words, last padding word=0. Caller retains original
|
||||
* requested count; token/error accompany fragments. Output capacity >=124. */
|
||||
PCAN_ABI_API size_t set_regmap_can(const set_regmap *, const uint8_t *, size_t,
|
||||
uint16_t *, size_t, unsigned *token, unsigned *error);
|
||||
PCAN_ABI_API size_t set_regmap_can_fragment(const uint16_t *, size_t count,
|
||||
size_t index, unsigned token, unsigned error, uint8_t *, size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,59 +0,0 @@
|
||||
/** @file set_wavegen.h Transactional table upload and cyclic DAC playback. */
|
||||
#ifndef SET_WAVEGEN_H
|
||||
#define SET_WAVEGEN_H
|
||||
#include "pcan_abi.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_WAVE_MAX 4096u
|
||||
#define SET_WAVE_MAX_RATE 1000000u
|
||||
#define SET_WAVE_BLOCK_MAX 120u
|
||||
#define SET_WAVE_BASE 0x1300u
|
||||
#define SET_WAVE_DATA 0xA000u
|
||||
typedef struct {
|
||||
void *context;
|
||||
/* Return 0 on success. Samples remain owned by state until stop returns.
|
||||
* start must arrange periodic output, typically DAC + timer + DMA.
|
||||
* stop must quiesce DMA before returning and set output to zero. */
|
||||
int (*start)(void *, const uint16_t *, size_t, uint32_t);
|
||||
void (*stop)(void *);
|
||||
} set_wave_port;
|
||||
typedef struct {
|
||||
uint16_t *samples;
|
||||
size_t capacity;
|
||||
uint16_t count, received, ready, running, rate_high;
|
||||
uint32_t rate;
|
||||
set_wave_port port;
|
||||
} set_wave_state;
|
||||
void set_wave_init(set_wave_state *, uint16_t *, size_t, const set_wave_port *);
|
||||
/* Atomic sample-block transfer. values=NULL builds a read; otherwise a write.
|
||||
* At most 120 words: request <=249 bytes, response <=245 bytes. */
|
||||
PCAN_ABI_API size_t set_wave_block_request(unsigned device,unsigned index,
|
||||
const uint16_t *values,size_t count,uint8_t *out,size_t capacity);
|
||||
PCAN_ABI_API int set_wave_block_response(const uint8_t *request,size_t request_size,
|
||||
const uint8_t *reply,size_t reply_size,uint16_t *words,size_t capacity);
|
||||
size_t set_wave_block_rtu(set_wave_state *,unsigned device,const uint8_t *,size_t,
|
||||
uint8_t *,size_t);
|
||||
/* Relative register callbacks for set_regmap; status=8 words, samples=4096.
|
||||
* These are the same state/validation used by the legacy RTU wrapper. */
|
||||
PCAN_ABI_API unsigned set_wave_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_wave_write(void *,uint16_t,uint16_t);
|
||||
PCAN_ABI_API unsigned set_wave_samples_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_wave_samples_write(void *,uint16_t,uint16_t);
|
||||
/* Caller serializes calls in the main loop. Returns complete reply size,
|
||||
* 0 for unrelated/invalid frames. CRC verified before all state changes. */
|
||||
size_t set_wave_rtu(set_wave_state *, unsigned device, const uint8_t *, size_t,
|
||||
uint8_t *, size_t);
|
||||
/* Thin host transport ABI. Operations: 0=status, 1=stop, 2=sample rate,
|
||||
* 3=begin upload(count), 4=sample(index,value), 5=commit, 6=start.
|
||||
* 7=read a sample(index) for verification. Return 8 or 0. */
|
||||
PCAN_ABI_API size_t set_wave_request(unsigned device,unsigned operation,unsigned index,
|
||||
unsigned value,uint8_t *out,size_t capacity);
|
||||
/* Return 0=need more bytes, positive=word count, negative=invalid/exception.
|
||||
* Validates exact echo/read shape, CRC, address and signature on status. */
|
||||
PCAN_ABI_API int set_wave_response(const uint8_t *request,const uint8_t *reply,
|
||||
size_t size,uint16_t *words,size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -4,8 +4,6 @@
|
||||
*/
|
||||
#ifndef SETPROTOCOL_H
|
||||
#define SETPROTOCOL_H
|
||||
#include "set_regmap.h"
|
||||
#include "set_emu_server.h"
|
||||
|
||||
#include "balsam_can.h"
|
||||
#include "periph28335.h"
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/* UMP logger v2 (PM35), independent of PM67 and SET protocol v2.
|
||||
* Octets use 16-bit storage: C28x has CHAR_BIT=16 and no uint8_t.
|
||||
* All lengths below count wire octets or register words, never sizeof bytes.
|
||||
*/
|
||||
#ifndef UMP_PROTOCOL_H
|
||||
#define UMP_PROTOCOL_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define UMP_BASE 0x1000U
|
||||
#define UMP_DATA 0x1010U
|
||||
#define UMP_LIVE 0x1100U
|
||||
#define UMP_MARKER 0x1F00U
|
||||
#define UMP_MAX_WORDS 124U
|
||||
#define UMP_RTU_CAPACITY 253U
|
||||
typedef uint16_t ump_octet;
|
||||
/* Called under caller's snapshot lock. write returns 1 on success, 0 otherwise. */
|
||||
typedef struct {
|
||||
void *context;
|
||||
uint16_t (*read)(void *context, uint16_t address);
|
||||
int (*write)(void *context, uint16_t address, uint16_t value);
|
||||
} ump_backend;
|
||||
/* Returns Modbus exception 0/1/2/3. CAN has stricter whole-block reads. */
|
||||
unsigned ump_validate(unsigned function, uint16_t address, uint16_t value, int can);
|
||||
/* reply capacity is >=124 words. Errors and writes return address/value echo. */
|
||||
unsigned ump_prepare(const ump_backend *backend, unsigned function,
|
||||
uint16_t address, uint16_t value, int can, uint16_t *reply, unsigned *error);
|
||||
uint16_t ump_crc(const ump_octet *data, size_t count);
|
||||
/* Mode 1..16; kind 0=request, 1=normal telemetry, 2=logger reply; invalid ->0. */
|
||||
uint32_t ump_can_id(unsigned mode, unsigned kind);
|
||||
/* Returns 0 for non-service frames, malformed size, or zero token. */
|
||||
unsigned ump_can_request(const ump_backend *backend, const ump_octet *request,
|
||||
size_t size, uint16_t *reply, unsigned *token, unsigned *error);
|
||||
/* count must be an even number 2..124; index counts pairs. Returns 8 or 0. */
|
||||
unsigned ump_can_fragment(const uint16_t *words, unsigned count, unsigned index,
|
||||
unsigned token, unsigned error, ump_octet *reply);
|
||||
/* Complete RTU frame only, after UART framing. Ignores bad CRC/foreign address.
|
||||
* Caller provides >=253 octets; no writes occur on insufficient capacity.
|
||||
* Device address 1..247; no broadcasts or legacy padding in this extension.
|
||||
*/
|
||||
size_t ump_rtu(const ump_backend *backend, unsigned device,
|
||||
const ump_octet *request, size_t size, ump_octet *reply, size_t capacity);
|
||||
/* Response for an already CRC-checked/framed request (legacy UART receiver). */
|
||||
size_t ump_rtu_response(const ump_backend *backend, unsigned device, unsigned function,
|
||||
uint16_t address, uint16_t value, ump_octet *reply, size_t capacity);
|
||||
/* Serialize a captured reply after releasing the snapshot lock. Returns 0 on
|
||||
* invalid count/capacity; error replies need no words. */
|
||||
size_t ump_rtu_encode(unsigned device, unsigned function, unsigned error,
|
||||
const uint16_t *words, unsigned count, ump_octet *reply, size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,41 +0,0 @@
|
||||
/* Minimal board-independent composition. This compiles without any MCU headers.
|
||||
* Adapt these entry points to the selected board's network/USB/UART/CAN port.
|
||||
* Run every call in one task (or protect the entire call with the same lock). */
|
||||
#include "set_emu_igbt.h"
|
||||
static set_igbt_emu drivers[2];
|
||||
static set_reg_region regions[2];
|
||||
static set_regmap map;
|
||||
unsigned example_service_init(void)
|
||||
{
|
||||
unsigned i;
|
||||
for(i=0;i<2;++i) {
|
||||
set_reg_region *r=®ions[i];
|
||||
set_igbt_init(&drivers[i]);
|
||||
r->base=(uint16_t)(0x300+i*0x100);r->words=SET_IGBT_WORDS;
|
||||
r->kind=4;r->version=2;r->instance=(uint16_t)i;r->flags=3;
|
||||
r->context=&drivers[i];r->read=set_igbt_read;r->write=set_igbt_write;
|
||||
}
|
||||
return set_regmap_init(&map,regions,2);
|
||||
}
|
||||
void example_service_tick(uint8_t inputs0,uint8_t inputs1,uint8_t outputs[2])
|
||||
{
|
||||
set_igbt_tick(&drivers[0],inputs0);set_igbt_tick(&drivers[1],inputs1);
|
||||
outputs[0]=drivers[0].rx;outputs[1]=drivers[1].rx;
|
||||
}
|
||||
unsigned example_custom_protocol(unsigned function,uint16_t address,uint16_t value,
|
||||
uint16_t *words,size_t capacity,size_t *count)
|
||||
{
|
||||
/* Caller has already decoded/authenticated its own transport envelope. */
|
||||
return set_regmap_request(&map,function,address,value,words,capacity,count);
|
||||
}
|
||||
size_t example_uart_or_usb(const uint8_t *frame,size_t length,uint8_t *reply,size_t capacity)
|
||||
{
|
||||
return set_regmap_rtu(&map,16,frame,length,reply,capacity);
|
||||
}
|
||||
size_t example_can(const uint8_t frame[8],uint16_t *snapshot,size_t capacity,
|
||||
unsigned *token,unsigned *error)
|
||||
{
|
||||
/* Select the CAN request ID outside the model; send the returned snapshot
|
||||
* using set_regmap_can_fragment and the board's chosen response CAN ID. */
|
||||
return set_regmap_can(&map,frame,8,snapshot,capacity,token,error);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/* Shared SET USB boot dispatcher. The MCU wrapper supplies BOOT_* macros. */
|
||||
#include "usb_setp_stream.h"
|
||||
#include <string.h>
|
||||
static setp_boot boot;
|
||||
static bool reset_pending;
|
||||
static uint32_t reset_at;
|
||||
typedef struct { uint8_t *out; uint16_t size; uint32_t now; } reply_context;
|
||||
void boot_protocol_init(void) { setp_boot_init(&boot,&BOOT_OPS,0,BOOT_APP_BASE,BOOT_APP_SIZE); }
|
||||
void boot_protocol_poll(uint32_t now) {
|
||||
if(reset_pending && (int32_t)(now-reset_at)>=0) BOOT_RESET();
|
||||
}
|
||||
static void dispatch(const setp_frame_t *r,void *user) {
|
||||
reply_context *c=user;
|
||||
uint8_t body[128]; uint16_t status=SETP_STATUS_OK; size_t n=0;
|
||||
setp_frame_t response={0};
|
||||
if((r->destination!=0 && r->destination!=BOOT_NODE) ||
|
||||
(r->flags&(SETP_FLAG_RESPONSE|SETP_FLAG_EVENT))) return;
|
||||
if(r->flags&(SETP_FLAG_ERROR|SETP_FLAG_MORE)) status=SETP_STATUS_INVALID_ARGUMENT;
|
||||
else if(r->message_type>=SETP_MSG_FW_BEGIN && r->message_type<=SETP_MSG_FW_ACTIVATE) {
|
||||
status=setp_boot_command(&boot,r->message_type,r->payload,r->payload_length);
|
||||
if(!status && r->message_type==SETP_MSG_FW_STATUS)
|
||||
n=setp_fw_status_encode(&boot.status,body+2,sizeof(body)-2);
|
||||
if(!status && r->message_type==SETP_MSG_FW_ACTIVATE) {
|
||||
reset_pending=true; reset_at=c->now+500;
|
||||
}
|
||||
} else if(r->payload_length) status=SETP_STATUS_INVALID_LENGTH;
|
||||
else switch(r->message_type) {
|
||||
case SETP_MSG_PING: setp_put_u32(body+2,c->now); n=4; break;
|
||||
case SETP_MSG_DEVICE_INFO: {
|
||||
static const uint8_t model[]=BOOT_MODEL;
|
||||
const setp_device_info_t info={1,0,0,0x10000,3,0,sizeof(model)-1,model};
|
||||
n=setp_device_info_encode(&info,body+2,sizeof(body)-2); break;
|
||||
}
|
||||
case SETP_MSG_CAPABILITIES: {
|
||||
const setp_capabilities_t caps={1,SETP_MAX_PAYLOAD,
|
||||
SETP_IFACE_MASK(SETP_IFACE_USB_CDC),SETP_FEATURE_FIRMWARE,0,0,0,0};
|
||||
n=setp_capabilities_encode(&caps,body+2,sizeof(body)-2); break;
|
||||
}
|
||||
default: status=SETP_STATUS_UNSUPPORTED; break;
|
||||
}
|
||||
setp_put_u16(body,status); if(status) n=0;
|
||||
response.flags=SETP_FLAG_RESPONSE|(status ? SETP_FLAG_ERROR : 0);
|
||||
response.message_type=r->message_type; response.sequence=r->sequence;
|
||||
response.source=BOOT_NODE; response.destination=r->source;
|
||||
response.payload=body; response.payload_length=(uint16_t)(n+2);
|
||||
c->size=(uint16_t)setp_frame_encode(&response,c->out,SETP_FRAME_MAX);
|
||||
}
|
||||
void usb_setp_reset(usb_setp_stream *s) { memset(s,0,sizeof(*s)); }
|
||||
uint16_t usb_setp_feed(usb_setp_stream *s,uint8_t byte,uint32_t now,uint8_t *out) {
|
||||
reply_context c={out,0,now};
|
||||
if(now-s->last_byte>1000) setp_parser_reset(&s->parser);
|
||||
s->last_byte=now; (void)setp_parser_feed(&s->parser,&byte,1,dispatch,&c);
|
||||
return c.size;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
# Порт STM32F407 / эмулятор периферии
|
||||
|
||||
Добавьте `src/ump_protocol.c` и `ump_stm32f407.c` в сборку.
|
||||
Порт получает backend с read/write и переводит 8-битные буферы STM32 в
|
||||
октеты ядра. Приём UART, CRC framing, CAN ID/DLC/RTR-фильтры и очередь
|
||||
передачи находятся в проекте; ядро проверяет CRC полного RTU кадра.
|
||||
Вызывать из главного цикла. В эмуляторе SysTick лишь накапливает тики,
|
||||
поэтому read/write и генератор не выполняются одновременно.
|
||||
Ответ RTU требует 253 байта; CAN snapshot — 124 uint16_t. Не передавайте
|
||||
неполный snapshot в fragment, count должен быть фактическим числом слов.
|
||||
Подключено в `home/407vet6_emul_TMS_Periph/Src/shared_ump.c`.
|
||||
|
||||
[Контракт, пример и проверки](../../docs/UMP_PROTOCOL.md).
|
||||
@@ -1,30 +0,0 @@
|
||||
#include "ump_stm32f407.h"
|
||||
size_t ump_stm32f407_rtu(const ump_backend *b, unsigned device,
|
||||
const uint8_t *request, size_t length, uint8_t *reply, size_t capacity)
|
||||
{
|
||||
ump_octet r[8],out[UMP_RTU_CAPACITY];
|
||||
size_t i,n;
|
||||
if (!request || !reply || length!=8 || capacity<UMP_RTU_CAPACITY) return 0;
|
||||
for (i=0;i<8;i++) r[i]=request[i];
|
||||
n=ump_rtu(b,device,r,8,out,UMP_RTU_CAPACITY);
|
||||
for (i=0;i<n;i++) reply[i]=(uint8_t)out[i];
|
||||
return n;
|
||||
}
|
||||
unsigned ump_stm32f407_can(const ump_backend *b, const uint8_t *request,
|
||||
size_t length, uint16_t *words, unsigned *token, unsigned *error)
|
||||
{
|
||||
ump_octet r[8];
|
||||
unsigned i;
|
||||
if (!request || length!=8) return 0;
|
||||
for (i=0;i<8;i++) r[i]=request[i];
|
||||
return ump_can_request(b,r,8,words,token,error);
|
||||
}
|
||||
unsigned ump_stm32f407_fragment(const uint16_t *words, unsigned count,
|
||||
unsigned index, unsigned token, unsigned error, uint8_t reply[8])
|
||||
{
|
||||
ump_octet out[8];
|
||||
unsigned i;
|
||||
if (!reply || !ump_can_fragment(words,count,index,token,error,out)) return 0;
|
||||
for (i=0;i<8;i++) reply[i]=(uint8_t)out[i];
|
||||
return 8;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef UMP_STM32F407_H
|
||||
#define UMP_STM32F407_H
|
||||
#include "../../include/ump_protocol.h"
|
||||
/* Application owns framing, ID filtering, snapshot lock, and tx buffers. */
|
||||
size_t ump_stm32f407_rtu(const ump_backend *backend, unsigned device,
|
||||
const uint8_t *request, size_t length, uint8_t *reply, size_t capacity);
|
||||
unsigned ump_stm32f407_can(const ump_backend *backend, const uint8_t *request,
|
||||
size_t length, uint16_t *words, unsigned *token, unsigned *error);
|
||||
unsigned ump_stm32f407_fragment(const uint16_t *words, unsigned count,
|
||||
unsigned index, unsigned token, unsigned error, uint8_t reply[8]);
|
||||
#endif
|
||||
@@ -1,68 +0,0 @@
|
||||
# STM32F407VET6 USB boot port
|
||||
|
||||
SET v2 single-slot updater: `include/set_boot.h`, `src/set_boot.c` and the existing
|
||||
`set_firmware` codec. Byte transport is supplied by the application, independently
|
||||
of the Flash callbacks. The emulator project supplies the ST USB CDC stream.
|
||||
|
||||
## Integration in another F407 project
|
||||
|
||||
1. Copy `boot_config.template.h` to the project's include directory as
|
||||
`boot_config.h`: set crystal frequency, SET node and product model. Match
|
||||
the crystal frequency with HAL `HSE_VALUE`.
|
||||
2. Compile `src/set_protocol.c`, `src/set_firmware.c`, `src/set_boot.c` and this
|
||||
port's `boot_main.c`, `boot_flash.c`, `boot_request.c`, `boot_usb_stream.c`.
|
||||
3. Provide STM32F4 CMSIS/HAL, startup, the ST USB device CDC middleware and the
|
||||
board USB transport (`usb_cdc.h`, `usb_setp_stream.h` and their implementation).
|
||||
The transport calls `usb_setp_feed`/`usb_setp_reset` from this port instead of
|
||||
an application command dispatcher. Enable SET v2 for the bootloader build.
|
||||
4. Link boot code below `0x0800C000`; link the application at `0x08010000`.
|
||||
Add `boot_request.c` to the application and periodically call
|
||||
`f407_reset_poll`; its update command calls `f407_request_boot`.
|
||||
|
||||
The emulator's `Bootloader/Src/*.c` files are include-only build wrappers. Neither
|
||||
the portable updater nor this port depends on the emulator models or its
|
||||
`board_config.h`. The USB transport and HAL configuration remain board supplied.
|
||||
|
||||
Flash layout (512 KiB device): boot sectors 0..2 at `0x08000000` (48 KiB),
|
||||
commit record in sector 3 at `0x0800C000`, application sectors 4..7 at
|
||||
`0x08010000` (448 KiB). All addresses and sector operations are in this port.
|
||||
The sector programming/jump approach is adapted from climate's
|
||||
`climate_control_f407vet6_f4/Bootloader/bootloader_f407.c`.
|
||||
|
||||
BEGIN requires slot 0, base `0x08010000`, size 8..458752, block size 1..256,
|
||||
ERASE_SLOT flag only, zero SHA/key/signature. DATA must be sequential; its CRC,
|
||||
length and bounds are checked. END requires complete receipt and matching size
|
||||
and CRC. The port verifies the actual Flash CRC and vector table before writing
|
||||
the commit marker last. SHA, signatures, automatic activation and resume are
|
||||
not supported and are rejected explicitly. CRC is an integrity check.
|
||||
|
||||
STATUS returns the standard 16-byte SET firmware status. ACTIVATE accepts an
|
||||
empty payload after END; the transport adapter defers reset to let the USB
|
||||
reply finish. ABORT clears the RAM session; it does not restore erased firmware.
|
||||
On reset, SP and reset PC are checked like in climate. USB metadata is not
|
||||
required, so Keil/SWD installs and alternating SWD/USB updates work even with
|
||||
absent or stale USB metadata. Preserve the bootloader using sector erase rather
|
||||
than full-chip erase. Reflash the updated bootloader once to adopt this behavior.
|
||||
USB END/ACTIVATE still require complete receipt and CRC verification.
|
||||
|
||||
Like climate, the reset vector check cannot detect incomplete images once valid
|
||||
vectors are written: after an interrupted update and reset such an image may run.
|
||||
To recover an unresponsive app, erase sector 4 at `0x08010000` via SWD, reset, and
|
||||
upload the full image again. Erasing metadata sector 3 no longer forces bootloader
|
||||
entry. An interrupted update requires a fresh BEGIN and full retransmission.
|
||||
`test_boot_image.c` is a host-only regression test; do not add it to firmware targets.
|
||||
|
||||
`boot_request.c` is shared by the application and bootloader. Product command
|
||||
`0x1003` (empty payload) in the application sets RTC backup register 0 and
|
||||
schedules reset after 500 ms. The bootloader consumes that flag before deciding
|
||||
whether to jump. Reserve BKP0R for this port. The jump must run early after reset,
|
||||
before enabling peripheral interrupts, USB or DMA. Both images must set VTOR
|
||||
to their own link address in SystemInit.
|
||||
|
||||
Board: PA11 D-/PA12 D+, existing self-powered OTG_FS CDC port, 25 MHz HSE;
|
||||
no VBUS host drive, PA9/PA10 are not used. Flash erase uses x32 at nominal 3.3 V,
|
||||
programming uses bytes to support unaligned protocol blocks. Hardware acceptance
|
||||
must cover disconnect/power loss during erase, programming and commit.
|
||||
|
||||
The SET USB command dispatcher is shared with G474 in
|
||||
`../stm32-usb-boot/boot_usb_protocol.inc`; keep that directory when importing this port.
|
||||
@@ -1,9 +0,0 @@
|
||||
#ifndef F407_BOOT_CONFIG_H
|
||||
#define F407_BOOT_CONFIG_H
|
||||
/* Copy to the board include directory as boot_config.h.
|
||||
* HSE must be an integer MHz frequency; PLL input is 1 MHz.
|
||||
* The Flash layout in boot_port.h targets the 512 KiB STM32F407VE. */
|
||||
#define F407_BOOT_HSE_HZ 25000000UL
|
||||
#define F407_BOOT_NODE 16U
|
||||
#define F407_BOOT_MODEL "STM32F407-USB-BOOT"
|
||||
#endif
|
||||
@@ -1,88 +0,0 @@
|
||||
/* Flash geometry and jump sequence derived from climate's F407 bootloader.
|
||||
* Sector 3 is a durable commit record; sectors 4..7 hold the application.
|
||||
* Reset checks vectors like climate; USB END always verifies Flash. */
|
||||
#include "boot_port.h"
|
||||
#include "boot_image.h"
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include <string.h>
|
||||
#define META 0x0800C000UL
|
||||
#define COMMIT 0x53455442UL
|
||||
#define ERRORS (FLASH_SR_PGSERR|FLASH_SR_PGPERR|FLASH_SR_PGAERR|FLASH_SR_WRPERR|FLASH_SR_SOP)
|
||||
static void unlock(void) {
|
||||
if(FLASH->CR & FLASH_CR_LOCK) { FLASH->KEYR=0x45670123; FLASH->KEYR=0xCDEF89AB; }
|
||||
FLASH->SR=ERRORS;
|
||||
}
|
||||
static bool finish(void) {
|
||||
while(FLASH->SR & FLASH_SR_BSY) {}
|
||||
return !(FLASH->SR & ERRORS);
|
||||
}
|
||||
static void cache_reset(void) {
|
||||
FLASH->ACR &= ~(FLASH_ACR_ICEN|FLASH_ACR_DCEN);
|
||||
FLASH->ACR |= FLASH_ACR_ICRST|FLASH_ACR_DCRST;
|
||||
FLASH->ACR &= ~(FLASH_ACR_ICRST|FLASH_ACR_DCRST);
|
||||
FLASH->ACR |= FLASH_ACR_ICEN|FLASH_ACR_DCEN;
|
||||
__DSB(); __ISB();
|
||||
}
|
||||
static bool erase_sector(unsigned sector) {
|
||||
bool ok; unlock();
|
||||
FLASH->CR=(FLASH->CR & ~(FLASH_CR_SNB|FLASH_CR_PSIZE)) |
|
||||
FLASH_CR_SER | (2UL<<FLASH_CR_PSIZE_Pos) | (sector<<FLASH_CR_SNB_Pos);
|
||||
FLASH->CR|=FLASH_CR_STRT; ok=finish();
|
||||
FLASH->CR &= ~FLASH_CR_SER; FLASH->CR |= FLASH_CR_LOCK;
|
||||
cache_reset(); return ok;
|
||||
}
|
||||
static bool program(uint32_t address, const uint8_t *p, uint32_t n) {
|
||||
bool ok=true; unlock();
|
||||
/* Byte programming supports arbitrary SET block boundaries and final tails. */
|
||||
FLASH->CR=(FLASH->CR & ~FLASH_CR_PSIZE)|FLASH_CR_PG;
|
||||
while(n--) {
|
||||
*(volatile uint8_t *)address=*p;
|
||||
if(!finish()) { ok=false; break; }
|
||||
++address; ++p;
|
||||
}
|
||||
FLASH->CR &= ~FLASH_CR_PG; FLASH->CR |= FLASH_CR_LOCK;
|
||||
cache_reset(); return ok;
|
||||
}
|
||||
static bool invalidate(void *u) { (void)u; return erase_sector(3); }
|
||||
static bool erase(void *u, uint32_t size) {
|
||||
unsigned s; (void)u;
|
||||
if(size<8 || size>F407_APP_SIZE) return false;
|
||||
for(s=4;s<=7;++s) {
|
||||
uint32_t start=s==4 ? F407_APP_BASE : 0x08020000UL+(s-5)*0x20000UL;
|
||||
if(start>=F407_APP_BASE+size) break;
|
||||
if(!erase_sector(s)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool write(void *u,uint32_t off,const uint8_t *p,uint16_t n) {
|
||||
(void)u;
|
||||
if(!n || off>F407_APP_SIZE || n>F407_APP_SIZE-off) return false;
|
||||
return program(F407_APP_BASE+off,p,n) && !memcmp((const void *)(F407_APP_BASE+off),p,n);
|
||||
}
|
||||
static bool verify(void *u,uint32_t size,uint32_t crc) {
|
||||
(void)u;
|
||||
return f407_vectors_valid((const uint8_t *)F407_APP_BASE,size) &&
|
||||
setp_crc32((const uint8_t *)F407_APP_BASE,size)==crc;
|
||||
}
|
||||
static bool commit(void *u,uint32_t size,uint32_t crc) {
|
||||
uint32_t record[4]={size,crc,~size,COMMIT}; (void)u;
|
||||
return program(META,(const uint8_t *)record,sizeof(record)) &&
|
||||
!memcmp((const void *)META,record,sizeof(record));
|
||||
}
|
||||
bool f407_app_valid(void) {
|
||||
/* Like climate: direct Keil/SWD writes need no USB completion record. */
|
||||
return f407_vectors_valid((const uint8_t *)F407_APP_BASE,F407_APP_SIZE);
|
||||
}
|
||||
static void activate(void *u) { (void)u; /* main waits for USB response before reset */ }
|
||||
const setp_boot_ops f407_boot_ops={invalidate,erase,write,verify,commit,activate};
|
||||
__attribute__((naked,noreturn)) static void enter(uint32_t sp,uint32_t pc) {
|
||||
__asm volatile("msr msp, r0\nmovs r2, #0\nmsr control, r2\ndsb\nisb\ncpsie i\nbx r1\n");
|
||||
}
|
||||
void f407_jump_app(void) {
|
||||
unsigned i; uint32_t sp=*(const uint32_t *)F407_APP_BASE;
|
||||
uint32_t pc=*(const uint32_t *)(F407_APP_BASE+4);
|
||||
__disable_irq(); SysTick->CTRL=0;
|
||||
for(i=0;i<8;++i) { NVIC->ICER[i]=0xffffffff; NVIC->ICPR[i]=0xffffffff; }
|
||||
SCB->ICSR=SCB_ICSR_PENDSTCLR_Msk|SCB_ICSR_PENDSVCLR_Msk;
|
||||
SCB->VTOR=F407_APP_BASE; enter(sp,pc);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef F407_BOOT_IMAGE_H
|
||||
#define F407_BOOT_IMAGE_H
|
||||
#include "boot_port.h"
|
||||
#include <string.h>
|
||||
|
||||
static inline bool f407_vectors_valid(const uint8_t *image, uint32_t size)
|
||||
{
|
||||
uint32_t sp, pc;
|
||||
if (size < 8 || size > F407_APP_SIZE) return false;
|
||||
memcpy(&sp, image, 4);
|
||||
memcpy(&pc, image + 4, 4);
|
||||
return !(sp & 7) &&
|
||||
((sp > 0x20000000UL && sp <= 0x20020000UL) ||
|
||||
(sp > 0x10000000UL && sp <= 0x10010000UL)) &&
|
||||
(pc & 1) && (pc & ~1UL) >= F407_APP_BASE &&
|
||||
(pc & ~1UL) < F407_APP_BASE + size;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include "boot_config.h"
|
||||
#include "usb_cdc.h"
|
||||
#include "boot_port.h"
|
||||
#if F407_BOOT_HSE_HZ != HSE_VALUE
|
||||
#error boot_config.h and HAL HSE_VALUE must describe the same crystal
|
||||
#endif
|
||||
#if (F407_BOOT_HSE_HZ % 1000000UL) || F407_BOOT_HSE_HZ < 2000000UL || F407_BOOT_HSE_HZ > 50000000UL
|
||||
#error HSE must provide a valid integer PLLM divider (2..50 MHz)
|
||||
#endif
|
||||
uint32_t SystemCoreClock=16000000;
|
||||
const uint8_t AHBPrescTable[16]={0,0,0,0,0,0,0,0,1,2,3,4,6,7,8,9};
|
||||
const uint8_t APBPrescTable[8]={0,0,0,0,1,2,3,4};
|
||||
static uint8_t usb_clock_ready;
|
||||
static volatile uint32_t clock_fault;
|
||||
void SystemInit(void) { SCB->CPACR|=0xFu<<20; SCB->VTOR=0x08000000; }
|
||||
void SysTick_Handler(void) { HAL_IncTick(); }
|
||||
static void board_fatal(uint32_t code) { clock_fault=code; for(;;) {} }
|
||||
static void peripheral_clock(void)
|
||||
{
|
||||
RCC_OscInitTypeDef osc={0};
|
||||
RCC_ClkInitTypeDef clk={0};
|
||||
/* Разрешаем доступ к PWR и выбираем диапазон питания ядра перед повышением частоты. */
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSE;
|
||||
osc.HSEState=RCC_HSE_ON;
|
||||
/* Готовность HSE проверяется HAL с ограниченным временем ожидания, а не бесконечным циклом. */
|
||||
if (HAL_RCC_OscConfig(&osc)==HAL_OK) usb_clock_ready=1;
|
||||
else {
|
||||
clock_fault=1;
|
||||
osc.HSEState=RCC_HSE_OFF;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
}
|
||||
/* CPU 72 MHz, APB1 36 MHz, USB 48 MHz; USB requires the HSE crystal. */
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSI;
|
||||
osc.HSIState=RCC_HSI_ON;
|
||||
osc.HSICalibrationValue=RCC_HSICALIBRATION_DEFAULT;
|
||||
osc.PLL.PLLState=RCC_PLL_ON;
|
||||
osc.PLL.PLLSource=usb_clock_ready ? RCC_PLLSOURCE_HSE : RCC_PLLSOURCE_HSI;
|
||||
osc.PLL.PLLM=usb_clock_ready ? F407_BOOT_HSE_HZ/1000000UL : 16;
|
||||
osc.PLL.PLLN=144; osc.PLL.PLLP=RCC_PLLP_DIV2; osc.PLL.PLLQ=3;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
clk.ClockType=RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
|
||||
clk.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;
|
||||
clk.AHBCLKDivider=RCC_SYSCLK_DIV1;
|
||||
clk.APB1CLKDivider=RCC_HCLK_DIV2; clk.APB2CLKDivider=RCC_HCLK_DIV1;
|
||||
/* HAL задаёт два такта ожидания Flash и перестраивает SysTick для новой частоты CPU. */
|
||||
if (HAL_RCC_ClockConfig(&clk,FLASH_LATENCY_2)!=HAL_OK) board_fatal(3);
|
||||
}
|
||||
|
||||
|
||||
void boot_protocol_init(void);
|
||||
void boot_protocol_poll(uint32_t now);
|
||||
int main(void) {
|
||||
/* Jump from reset, before initializing USB, DMA, clocks or interrupts. */
|
||||
bool requested=f407_boot_requested();
|
||||
if(!requested && f407_app_valid()) f407_jump_app();
|
||||
if(HAL_Init()!=HAL_OK) board_fatal(3);
|
||||
peripheral_clock(); boot_protocol_init();
|
||||
if(usb_clock_ready) usb_cdc_init();
|
||||
for(;;) { uint32_t now=HAL_GetTick(); usb_cdc_poll(now); boot_protocol_poll(now); }
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef F407_BOOT_PORT_H
|
||||
#define F407_BOOT_PORT_H
|
||||
#include "set_boot.h"
|
||||
#define F407_APP_BASE 0x08010000UL
|
||||
#define F407_APP_SIZE 0x00070000UL
|
||||
#define F407_BOOT_REQUEST 0x1003U
|
||||
extern const setp_boot_ops f407_boot_ops;
|
||||
bool f407_app_valid(void);
|
||||
bool f407_boot_requested(void);
|
||||
void f407_request_boot(uint32_t now);
|
||||
void f407_reset_poll(uint32_t now);
|
||||
void f407_jump_app(void);
|
||||
#endif
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "boot_port.h"
|
||||
#include "stm32f4xx_hal.h"
|
||||
#define REQUEST_MAGIC 0x42555342UL
|
||||
static uint32_t reset_at;
|
||||
static bool reset_pending;
|
||||
static void backup_access(void)
|
||||
{
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
PWR->CR |= PWR_CR_DBP;
|
||||
__HAL_RCC_RTC_ENABLE();
|
||||
}
|
||||
bool f407_boot_requested(void)
|
||||
{
|
||||
bool requested;
|
||||
backup_access(); requested=RTC->BKP0R==REQUEST_MAGIC;
|
||||
RTC->BKP0R=0; return requested;
|
||||
}
|
||||
void f407_request_boot(uint32_t now)
|
||||
{
|
||||
backup_access(); RTC->BKP0R=REQUEST_MAGIC;
|
||||
reset_at=now+500; reset_pending=true;
|
||||
}
|
||||
void f407_reset_poll(uint32_t now)
|
||||
{
|
||||
if(reset_pending && (int32_t)(now-reset_at)>=0) NVIC_SystemReset();
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include "boot_port.h"
|
||||
#include "boot_config.h"
|
||||
#define BOOT_OPS f407_boot_ops
|
||||
#define BOOT_APP_BASE F407_APP_BASE
|
||||
#define BOOT_APP_SIZE F407_APP_SIZE
|
||||
#define BOOT_NODE F407_BOOT_NODE
|
||||
#define BOOT_MODEL F407_BOOT_MODEL
|
||||
#define BOOT_RESET() NVIC_SystemReset()
|
||||
#include "../stm32-usb-boot/boot_usb_protocol.inc"
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "boot_image.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint32_t words[32] = {0x20020000, F407_APP_BASE + 9};
|
||||
uint8_t *image = (uint8_t *)words;
|
||||
/* A Keil image needs no metadata. Replacing its body must still boot. */
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[10] ^= 1;
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = words[1] = 0xffffffff;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = 0x20020000; words[1] = 0x08000101;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 8;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 9; words[0] = 0x20020001;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = 0x10010000;
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
assert(!f407_vectors_valid(image, 8));
|
||||
assert(!f407_vectors_valid(image, 7));
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE + 1));
|
||||
words[1] = F407_APP_BASE + F407_APP_SIZE + 1;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 9;
|
||||
/* Like climate, vectors alone cannot establish image completeness. */
|
||||
memset(image + 8, 0xff, sizeof(words) - 8);
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
puts("F407 boot: Keil image replacement and vector checks passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#include "set_wave_f407.h"
|
||||
#include "stm32f4xx.h"
|
||||
static void stop(void *context) {
|
||||
unsigned limit=100000; (void)context;
|
||||
RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN|RCC_AHB1ENR_DMA1EN;
|
||||
RCC->APB1ENR|=RCC_APB1ENR_DACEN|RCC_APB1ENR_TIM6EN;
|
||||
(void)RCC->APB1ENR;
|
||||
TIM6->CR1=0;
|
||||
DAC->CR&=~(DAC_CR_DMAEN1|DAC_CR_TEN1);
|
||||
DMA1_Stream5->CR&=~DMA_SxCR_EN;
|
||||
while((DMA1_Stream5->CR&DMA_SxCR_EN)&&--limit) {}
|
||||
/* Leave other DAC channel untouched. Channel 1 outputs zero on stop. */
|
||||
DAC->DHR12R1=0;
|
||||
DMA1->HIFCR=0xF40u;
|
||||
DAC->SR=DAC_SR_DMAUDR1;
|
||||
}
|
||||
static int start(void *context,const uint16_t *samples,size_t count,uint32_t rate) {
|
||||
set_wave_f407 *c=(set_wave_f407 *)context;
|
||||
uint32_t ticks,psc,arr; uintptr_t address=(uintptr_t)samples;
|
||||
if(!c||!rate||rate>SET_WAVE_MAX_RATE||count<2||count>SET_WAVE_MAX||c->timer_hz%rate
|
||||
||address<0x20000000u||address+count*2>0x20020000u) return 1;
|
||||
ticks=c->timer_hz/rate;
|
||||
for(psc=1;psc<=65536;++psc) if(ticks%psc==0&&ticks/psc<=65536) break;
|
||||
if(psc>65536||ticks<psc) return 1;
|
||||
arr=ticks/psc;
|
||||
stop(context);
|
||||
if(DMA1_Stream5->CR&DMA_SxCR_EN) return 1;
|
||||
GPIOA->MODER|=3u<<8;
|
||||
GPIOA->PUPDR&=~(3u<<8);
|
||||
TIM6->PSC=psc-1;TIM6->ARR=arr-1;TIM6->CNT=0;
|
||||
TIM6->CR2=0;TIM6->EGR=TIM_EGR_UG;TIM6->SR=0;
|
||||
TIM6->CR2=TIM_CR2_MMS_1; /* update -> TRGO */
|
||||
DMA1_Stream5->PAR=(uint32_t)&DAC->DHR12R1;
|
||||
DMA1_Stream5->M0AR=(uint32_t)samples;DMA1_Stream5->NDTR=(uint32_t)count;
|
||||
DMA1_Stream5->FCR=0;
|
||||
DMA1_Stream5->CR=(7u<<25)|DMA_SxCR_DIR_0|DMA_SxCR_MINC|DMA_SxCR_CIRC
|
||||
|DMA_SxCR_PSIZE_0|DMA_SxCR_MSIZE_0|DMA_SxCR_PL_1;
|
||||
/* Prime the previous period's last sample; DMA supplies the next value
|
||||
* after each trigger. Cyclic order is last,0,1,... with one priming tick. */
|
||||
DAC->CR&=~0xFFFFu;
|
||||
DAC->DHR12R1=samples[count-1];
|
||||
DAC->CR|=DAC_CR_EN1|DAC_CR_TEN1|DAC_CR_DMAEN1; /* TSEL1=0: TIM6 */
|
||||
DMA1_Stream5->CR|=DMA_SxCR_EN;
|
||||
TIM6->CR1=TIM_CR1_CEN;
|
||||
return 0;
|
||||
}
|
||||
set_wave_port set_wave_f407_port(set_wave_f407 *context) {
|
||||
set_wave_port port={context,start,stop};return port;
|
||||
}
|
||||
int set_wave_f407_fault(void) {
|
||||
return (DMA1->HISR&(DMA_HISR_TEIF5|DMA_HISR_DMEIF5|DMA_HISR_FEIF5))
|
||||
||(DAC->SR&DAC_SR_DMAUDR1);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#ifndef SET_WAVE_F407_H
|
||||
#define SET_WAVE_F407_H
|
||||
#include "../../include/set_wavegen.h"
|
||||
/* Owns PA4, DAC channel 1, TIM6 and DMA1 stream 5/channel 7.
|
||||
* timer_hz is TIM6 input clock (twice APB1 when APB1 prescaler !=1).
|
||||
* Rate: 1..SET_WAVE_MAX_RATE (1 MS/s), exact divisors of timer_hz only.
|
||||
* Context and sample buffer must outlive playback; buffer must be in SRAM. */
|
||||
typedef struct { uint32_t timer_hz; } set_wave_f407;
|
||||
set_wave_port set_wave_f407_port(set_wave_f407 *context);
|
||||
/* Nonzero on DMA error or DAC underrun; caller stops state on failure. */
|
||||
int set_wave_f407_fault(void);
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
# STM32G474RB emulator peripheral port
|
||||
|
||||
Board adapters for the common UMP/TMS/IGBT server and waveform generator.
|
||||
All peripheral source files have detailed Russian comments matching the F407 port.
|
||||
The shared algorithms, codecs and register map stay in `../../src`.
|
||||
|
||||
- `board.c`: application VTOR 0x08010000; HSI16/PLL 160 MHz, APB1 80 MHz;
|
||||
HSI48/CRS USB; board GPIO and 1 ms model ticks.
|
||||
- `transport.c`: USART2 PA2/PA3 RTU, optional RS485 DE PB15;
|
||||
FDCAN1 PB8/PB9, Classic CAN, extended IDs, bounded polling and TX timeout.
|
||||
- `wave_hal.c`: PA4 DAC1_CH1, TIM6, DMA1_Channel1 with DMAMUX DAC1 request;
|
||||
2..4096 samples in SRAM1/2, exact timer division, maximum 1 MS/s.
|
||||
- `main.c`: service ordering and deferred reboot into the G474 USB bootloader.
|
||||
|
||||
IGBT input bits 0..7 use PB0/PB1/PB2/PB10/PB11/PB12/PB13/PB14;
|
||||
output bits use PC0..PC7. SWD, SWO and USB pins are preserved.
|
||||
Clock, addresses and RS485 mode come from the board-supplied `board_config.h`.
|
||||
The default internal clock does not guarantee CAN or waveform accuracy over
|
||||
all temperature conditions; use a verified 24 MHz HSE source when required.
|
||||
|
||||
The consumer supplies `board.h`, `transport.h`, `wave_hal.h`, `emulator.h`,
|
||||
G4 HAL configuration and the common model composition. The reference project
|
||||
is `Nucleo_G474RB` in the emulator repository. Compile `EMU_CAPACITY=400`:
|
||||
each 32000-byte archive bank fits the MCU's 32 KiB CCM/96 KiB SRAM layout.
|
||||
DMA samples stay in SRAM1/2. No F407 peripheral implementation is compiled.
|
||||
|
||||
Link at 0x08010000 with a 64 KiB Flash limit; install the separate G474RB
|
||||
USB bootloader first. Require 128 KiB silicon, DBANK=0, BFB2=0 and no bank swap.
|
||||
The firmware rejects mismatched geometry before peripheral initialization.
|
||||
Reuse `../stm32g474-usb-boot/boot_usb_port.c` and `boot_request.c` with the
|
||||
shared USB CDC transport. Device model: STM32G474RB-EMU-EXPERIMENT.
|
||||
|
||||
Host tests validate the shared software; peripheral timing and electrical
|
||||
behavior have not yet been validated on a physical board.
|
||||
@@ -1,109 +0,0 @@
|
||||
/**
|
||||
* Аппаратный порт STM32G474RB: векторы приложения, тактирование и GPIO.
|
||||
* Алгоритмы UMP/TMS/IGBT находятся в общем C-ядре templates; здесь только пины.
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "board_config.h"
|
||||
#include "transport.h"
|
||||
#include "emulator.h"
|
||||
#include "igbt_emulator.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
volatile uint32_t clock_fault;
|
||||
uint32_t SystemCoreClock=16000000u;
|
||||
const uint8_t AHBPrescTable[16]={0,0,0,0,0,0,0,0,1,2,3,4,6,7,8,9};
|
||||
const uint8_t APBPrescTable[8]={0,0,0,0,1,2,3,4};
|
||||
static uint8_t usb_clock_ready;
|
||||
/* Восемь входов IGBT: PB0,PB1,PB2,PB10..PB14. PB3/SWO и PB8/PB9 CAN свободны. */
|
||||
static const uint16_t input_pins[8]={GPIO_PIN_0,GPIO_PIN_1,GPIO_PIN_2,
|
||||
GPIO_PIN_10,GPIO_PIN_11,GPIO_PIN_12,GPIO_PIN_13,GPIO_PIN_14};
|
||||
/* До C runtime разрешены только регистровые операции; глобальные данные ещё не готовы. */
|
||||
void SystemInit(void) { SCB->CPACR|=0xFu<<20; SCB->VTOR=0x08010000u; }
|
||||
void SystemCoreClockUpdate(void) {
|
||||
RCC_ClkInitTypeDef config; uint32_t latency;
|
||||
HAL_RCC_GetClockConfig(&config,&latency);
|
||||
SystemCoreClock=HAL_RCC_GetSysClockFreq() >> AHBPrescTable[config.AHBCLKDivider >> RCC_CFGR_HPRE_Pos];
|
||||
}
|
||||
/* ISR только считает миллисекунды: протоколы и модели обслуживаются главным циклом. */
|
||||
void SysTick_Handler(void) { HAL_IncTick(); }
|
||||
void board_fatal(uint32_t code) { clock_fault=code; for (;;) {} }
|
||||
static void peripheral_clock(void)
|
||||
{
|
||||
RCC_OscInitTypeDef osc={0}; RCC_ClkInitTypeDef clk={0};
|
||||
RCC_PeriphCLKInitTypeDef peripheral={0}; RCC_CRSInitTypeDef crs={0};
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
/* Для 160 МГц нужен Range1 Boost; Flash 4 wait states по таблице ST. */
|
||||
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST)!=HAL_OK) board_fatal(3);
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSI48;
|
||||
osc.HSIState=RCC_HSI_ON; osc.HSICalibrationValue=RCC_HSICALIBRATION_DEFAULT;
|
||||
osc.HSI48State=RCC_HSI48_ON;
|
||||
osc.PLL.PLLState=RCC_PLL_ON; osc.PLL.PLLSource=RCC_PLLSOURCE_HSI;
|
||||
/* HSI16 / 4 * 80 / 2 = 160 МГц; PLLQ также 160 МГц, FDCAN берёт PCLK1. */
|
||||
osc.PLL.PLLM=RCC_PLLM_DIV4;
|
||||
#if EMU_USE_HSE
|
||||
#if EMU_HSE_HZ != 24000000u
|
||||
#error This board profile supports 24 MHz HSE or internal HSI16
|
||||
#endif
|
||||
osc.OscillatorType|=RCC_OSCILLATORTYPE_HSE;
|
||||
osc.HSEState=EMU_HSE_BYPASS ? RCC_HSE_BYPASS : RCC_HSE_ON;
|
||||
osc.PLL.PLLSource=RCC_PLLSOURCE_HSE; osc.PLL.PLLM=RCC_PLLM_DIV6;
|
||||
#endif
|
||||
osc.PLL.PLLN=80; osc.PLL.PLLP=RCC_PLLP_DIV2;
|
||||
osc.PLL.PLLQ=RCC_PLLQ_DIV2; osc.PLL.PLLR=RCC_PLLR_DIV2;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
clk.ClockType=RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
|
||||
clk.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK; clk.AHBCLKDivider=RCC_SYSCLK_DIV1;
|
||||
clk.APB1CLKDivider=RCC_HCLK_DIV2; clk.APB2CLKDivider=RCC_HCLK_DIV1;
|
||||
if (HAL_RCC_ClockConfig(&clk,FLASH_LATENCY_4)!=HAL_OK) board_fatal(3);
|
||||
/* USB не зависит от PLL/HSE. USART2 и FDCAN получают 80 МГц PCLK1. */
|
||||
peripheral.PeriphClockSelection=RCC_PERIPHCLK_USB|RCC_PERIPHCLK_FDCAN|RCC_PERIPHCLK_USART2;
|
||||
peripheral.UsbClockSelection=RCC_USBCLKSOURCE_HSI48;
|
||||
peripheral.FdcanClockSelection=RCC_FDCANCLKSOURCE_PCLK1;
|
||||
peripheral.Usart2ClockSelection=RCC_USART2CLKSOURCE_PCLK1;
|
||||
if (HAL_RCCEx_PeriphCLKConfig(&peripheral)!=HAL_OK) board_fatal(3);
|
||||
__HAL_RCC_CRS_CLK_ENABLE();
|
||||
crs.Prescaler=RCC_CRS_SYNC_DIV1; crs.Source=RCC_CRS_SYNC_SOURCE_USB;
|
||||
crs.Polarity=RCC_CRS_SYNC_POLARITY_RISING;
|
||||
crs.ReloadValue=__HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000u,1000u);
|
||||
crs.ErrorLimitValue=RCC_CRS_ERRORLIMIT_DEFAULT;
|
||||
crs.HSI48CalibrationValue=RCC_CRS_HSI48CALIBRATION_DEFAULT;
|
||||
HAL_RCCEx_CRSConfig(&crs); usb_clock_ready=1;
|
||||
}
|
||||
void board_init(void)
|
||||
{
|
||||
GPIO_InitTypeDef gpio={0};
|
||||
/* Этот образ связан с RB-загрузчиком: 128 КБ Flash, непрерывная карта DBANK=0. */
|
||||
__HAL_RCC_SYSCFG_CLK_ENABLE();
|
||||
if (*(const uint16_t *)FLASHSIZE_BASE!=128u ||
|
||||
(FLASH->OPTR&(FLASH_OPTR_DBANK|FLASH_OPTR_BFB2)) ||
|
||||
(SYSCFG->MEMRMP&SYSCFG_MEMRMP_FB_MODE)) board_fatal(5);
|
||||
peripheral_clock();
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE();
|
||||
__HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_FDCAN_CLK_ENABLE();
|
||||
/* ST-LINK VCP физически подключён к PA2/PA3; выбираем USART2 AF7, формат 8N1. */
|
||||
gpio.Pin=GPIO_PIN_2|GPIO_PIN_3; gpio.Mode=GPIO_MODE_AF_PP;
|
||||
gpio.Speed=GPIO_SPEED_FREQ_VERY_HIGH; gpio.Pull=GPIO_PULLUP;
|
||||
gpio.Alternate=GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA,&gpio);
|
||||
gpio.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14;
|
||||
gpio.Mode=GPIO_MODE_INPUT; gpio.Pull=GPIO_PULLDOWN; HAL_GPIO_Init(GPIOB,&gpio);
|
||||
/* PC0..PC7 — восемь ответов IGBT. Устанавливаем нули до включения выходов. */
|
||||
gpio.Pin=0xff; gpio.Mode=GPIO_MODE_OUTPUT_PP; gpio.Pull=GPIO_NOPULL;
|
||||
HAL_GPIO_WritePin(GPIOC,0xff,GPIO_PIN_RESET); HAL_GPIO_Init(GPIOC,&gpio);
|
||||
#if EMU_RS485_DE
|
||||
gpio.Pin=GPIO_PIN_15; HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET);
|
||||
HAL_GPIO_Init(GPIOB,&gpio);
|
||||
#endif
|
||||
/* FDCAN1 Classic CAN: PB8 RX, PB9 TX. PA11/PA12 остаются для USB FS. */
|
||||
gpio.Pin=GPIO_PIN_8|GPIO_PIN_9; gpio.Mode=GPIO_MODE_AF_PP;
|
||||
gpio.Pull=GPIO_PULLUP; gpio.Alternate=GPIO_AF9_FDCAN1; HAL_GPIO_Init(GPIOB,&gpio);
|
||||
transport_init();
|
||||
}
|
||||
uint8_t board_usb_ready(void) { return usb_clock_ready; }
|
||||
/* Один шаг 1 мс. Бит n соответствует элементу input_pins[n], выход — PCn. */
|
||||
void board_tick(void)
|
||||
{
|
||||
uint8_t inputs=0,outputs; unsigned i;
|
||||
for(i=0;i<8;++i) if(HAL_GPIO_ReadPin(GPIOB,input_pins[i])==GPIO_PIN_SET) inputs|=(uint8_t)(1u<<i);
|
||||
igbt_emu_set_tx(inputs); emu_tick(); outputs=igbt_emu_get_rx();
|
||||
HAL_GPIO_WritePin(GPIOC,(uint16_t)(~outputs&0xffu),GPIO_PIN_RESET);
|
||||
HAL_GPIO_WritePin(GPIOC,outputs,GPIO_PIN_SET);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Точка входа прошивки. Здесь только порядок запуска и совместное обслуживание модулей.
|
||||
* Настройки оборудования находятся в board.c, обмен — в transport.c, генератор — в wave_hal.c.
|
||||
* Все модели и разбор команд выполняются в главном цикле; ISR обслуживают HAL и передачу байтов.
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "transport.h"
|
||||
#include "emulator.h"
|
||||
#include "usb_cdc.h"
|
||||
#include "wave_output.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
|
||||
#if defined(EMU_USB_BOOT_ENABLE)
|
||||
#include "boot_port.h"
|
||||
#endif
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint32_t serviced;
|
||||
/* HAL запускает миллисекундный SysTick до настройки PLL: его счётчик нужен таймаутам HAL. */
|
||||
if (HAL_Init()!=HAL_OK) board_fatal(3);
|
||||
/* Сначала создаём модели и значения по умолчанию, затем настраиваем плату с этими параметрами. */
|
||||
emu_init(); board_init();
|
||||
/* Начинаем модельное время после настройки платы, не отрабатывая задержки её инициализации. */
|
||||
serviced=HAL_GetTick();
|
||||
/* USB использует отдельный HSI48 с CRS: внешний кварц для USB не нужен. */
|
||||
if (board_usb_ready()) usb_cdc_init();
|
||||
for (;;) {
|
||||
uint32_t now=HAL_GetTick();
|
||||
/* Один пропущенный миллисекундный шаг за проход. Между шагами всегда обслуживаем связь,
|
||||
* чтобы догоняющая обработка модели не вытесняла UART/CAN/USB. */
|
||||
if (serviced!=now) { ++serviced; board_tick(); }
|
||||
transport_uart_poll(now); transport_can_poll(now);
|
||||
usb_cdc_poll(now); wave_output_poll();
|
||||
#if defined(EMU_USB_BOOT_ENABLE)
|
||||
g474_reset_poll(now);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/**
|
||||
* USART2/VCP и FDCAN1 в режиме Classic CAN. Формат запросов совпадает с F407:
|
||||
* UART RTU, CAN extended ID 0xBA0000+mode-1; ответы 0xBA0020+mode-1.
|
||||
* Здесь нет второго кодека: все байты обрабатывают emu_rtu/emu_can из templates.
|
||||
*/
|
||||
#include "transport.h"
|
||||
#include "board.h"
|
||||
#include "board_config.h"
|
||||
#include "emulator.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
static UART_HandleTypeDef uart;
|
||||
static FDCAN_HandleTypeDef can;
|
||||
static volatile uint8_t transmitting;
|
||||
static uint8_t can_ready;
|
||||
volatile uint32_t uart_errors,uart_overflows,can_timeouts,can_overflows;
|
||||
void transport_init(void)
|
||||
{
|
||||
FDCAN_FilterTypeDef filter={0};
|
||||
uart.Instance=USART2; uart.Init.BaudRate=emu_uart_baud();
|
||||
uart.Init.WordLength=UART_WORDLENGTH_8B; uart.Init.StopBits=UART_STOPBITS_1;
|
||||
uart.Init.Parity=UART_PARITY_NONE; uart.Init.Mode=UART_MODE_TX_RX;
|
||||
uart.Init.HwFlowCtl=UART_HWCONTROL_NONE; uart.Init.OverSampling=UART_OVERSAMPLING_16;
|
||||
uart.Init.ClockPrescaler=UART_PRESCALER_DIV1;
|
||||
if(HAL_UART_Init(&uart)!=HAL_OK) board_fatal(4);
|
||||
HAL_NVIC_SetPriority(USART2_IRQn,6,0); HAL_NVIC_EnableIRQ(USART2_IRQn);
|
||||
can.Instance=FDCAN1; can.Init.ClockDivider=FDCAN_CLOCK_DIV1;
|
||||
can.Init.FrameFormat=FDCAN_FRAME_CLASSIC; can.Init.Mode=FDCAN_MODE_NORMAL;
|
||||
can.Init.AutoRetransmission=ENABLE; can.Init.TransmitPause=DISABLE;
|
||||
can.Init.ProtocolException=DISABLE;
|
||||
/* PCLK1=80 МГц, 16 квантов/бит, prescaler=20 для 250 кбит/с.
|
||||
* Sample point=(1+13)/16=87,5%; CAN FD/BRS на проводе не используется. */
|
||||
if(HAL_RCC_GetPCLK1Freq()%(EMU_CAN_BITRATE*16u)) board_fatal(2);
|
||||
can.Init.NominalPrescaler=HAL_RCC_GetPCLK1Freq()/(EMU_CAN_BITRATE*16u);
|
||||
can.Init.NominalSyncJumpWidth=1; can.Init.NominalTimeSeg1=13; can.Init.NominalTimeSeg2=2;
|
||||
can.Init.DataPrescaler=1; can.Init.DataSyncJumpWidth=1;
|
||||
can.Init.DataTimeSeg1=1; can.Init.DataTimeSeg2=1;
|
||||
can.Init.StdFiltersNbr=0; can.Init.ExtFiltersNbr=1;
|
||||
can.Init.TxFifoQueueMode=FDCAN_TX_FIFO_OPERATION;
|
||||
if(HAL_FDCAN_Init(&can)!=HAL_OK) board_fatal(2);
|
||||
filter.IdType=FDCAN_EXTENDED_ID; filter.FilterIndex=0;
|
||||
filter.FilterType=FDCAN_FILTER_MASK; filter.FilterConfig=FDCAN_FILTER_TO_RXFIFO0;
|
||||
filter.FilterID1=0xBA0000u+EMU_CAN_MODE-1; filter.FilterID2=0x1fffffffu;
|
||||
/* Отдельно запрещаем все стандартные, посторонние и remote-кадры. */
|
||||
if(HAL_FDCAN_ConfigFilter(&can,&filter)!=HAL_OK ||
|
||||
HAL_FDCAN_ConfigGlobalFilter(&can,FDCAN_REJECT,FDCAN_REJECT,
|
||||
FDCAN_REJECT_REMOTE,FDCAN_REJECT_REMOTE)!=HAL_OK) board_fatal(2);
|
||||
can_ready=(HAL_FDCAN_Start(&can)==HAL_OK);
|
||||
if(!can_ready) clock_fault=2;
|
||||
}
|
||||
/* Опрос ограничен одним RX и одним TX за проход. Статический снимок ответа
|
||||
* сохраняется до завершения всех фрагментов; новый запрос во время ответа не принимается. */
|
||||
void transport_can_poll(uint32_t now)
|
||||
{
|
||||
static uint16_t words[124],count,fragment;
|
||||
static uint8_t tag,error,pending;
|
||||
static uint32_t sent_at,mailbox;
|
||||
FDCAN_RxHeaderTypeDef rx; FDCAN_TxHeaderTypeDef tx={0};
|
||||
uint8_t data[64]; /* HAL может скопировать до 64 байт чужого FD-кадра до проверки формата. */
|
||||
if(!can_ready) return;
|
||||
/* FDCAN не делает bxCAN AutoBusOff. Снятие INIT запускает аппаратное ожидание
|
||||
* 129 последовательностей recessive перед восстановлением шины. */
|
||||
if((can.Instance->PSR&FDCAN_PSR_BO) && (can.Instance->CCCR&FDCAN_CCCR_INIT)) {
|
||||
CLEAR_BIT(can.Instance->CCCR,FDCAN_CCCR_INIT);
|
||||
count=0;
|
||||
}
|
||||
if(pending) {
|
||||
if(!HAL_FDCAN_IsTxBufferMessagePending(&can,mailbox)) {
|
||||
if(pending==1 && !(can.Instance->TXBTO&mailbox)) {count=0;++can_timeouts;}
|
||||
pending=0;
|
||||
} else if(pending==1 && (uint32_t)(now-sent_at)>=250u) {
|
||||
(void)HAL_FDCAN_AbortTxRequest(&can,mailbox);
|
||||
count=0;pending=2;++can_timeouts;
|
||||
}
|
||||
}
|
||||
if(__HAL_FDCAN_GET_FLAG(&can,FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST)) {
|
||||
__HAL_FDCAN_CLEAR_FLAG(&can,FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST);++can_overflows;
|
||||
}
|
||||
if(HAL_FDCAN_GetRxFifoFillLevel(&can,FDCAN_RX_FIFO0) &&
|
||||
HAL_FDCAN_GetRxMessage(&can,FDCAN_RX_FIFO0,&rx,data)==HAL_OK) {
|
||||
if(rx.DataLength==FDCAN_DLC_BYTES_8 && rx.IdType==FDCAN_EXTENDED_ID &&
|
||||
rx.RxFrameType==FDCAN_DATA_FRAME && rx.FDFormat==FDCAN_CLASSIC_CAN &&
|
||||
rx.Identifier==0xBA0000u+EMU_CAN_MODE-1 && !count && !pending) {
|
||||
count=emu_can(data,words,&tag,&error);fragment=0;
|
||||
}
|
||||
}
|
||||
if(count && !pending && HAL_FDCAN_GetTxFifoFreeLevel(&can)) {
|
||||
emu_can_fragment(words,fragment,tag,error,data);
|
||||
tx.Identifier=0xBA0020u+EMU_CAN_MODE-1;tx.IdType=FDCAN_EXTENDED_ID;
|
||||
tx.TxFrameType=FDCAN_DATA_FRAME;tx.DataLength=FDCAN_DLC_BYTES_8;
|
||||
tx.ErrorStateIndicator=FDCAN_ESI_ACTIVE;tx.BitRateSwitch=FDCAN_BRS_OFF;
|
||||
tx.FDFormat=FDCAN_CLASSIC_CAN;tx.TxEventFifoControl=FDCAN_NO_TX_EVENTS;
|
||||
if(HAL_FDCAN_AddMessageToTxFifoQ(&can,&tx,data)==HAL_OK) {
|
||||
mailbox=HAL_FDCAN_GetLatestTxFifoQRequestBuffer(&can);
|
||||
pending=1;sent_at=now;
|
||||
if(++fragment>=count/2)count=0;
|
||||
} else {count=0;++can_timeouts;}
|
||||
}
|
||||
}
|
||||
|
||||
/* HAL обслуживает TXE/TC и вызывает callback завершения; прикладной протокол в ISR не выполняется. */
|
||||
void USART2_IRQHandler(void) { HAL_UART_IRQHandler(&uart); }
|
||||
/* Вызывается HAL после TC: последний стоп-бит уже вышел на линию, можно отключить передатчик RS-485.
|
||||
* Также используется локально для возврата в приём, если запуск передачи завершился ошибкой. */
|
||||
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *h)
|
||||
{
|
||||
if (h!=&uart) return;
|
||||
#if EMU_RS485_DE
|
||||
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET);
|
||||
#endif
|
||||
transmitting=0;
|
||||
}
|
||||
/* Принимает доступный байт без ожидания, накапливает кадр и определяет конец по паузе RTU.
|
||||
* rx/tx имеют по 256 байт; invalid запрещает выполнение всего повреждённого кадра.
|
||||
* now и last_rx измеряются в миллисекундах; переданный HAL буфер tx живёт до завершения ISR. */
|
||||
void transport_uart_poll(uint32_t now)
|
||||
{
|
||||
static uint8_t rx[256], tx[256], invalid;
|
||||
static uint16_t received;
|
||||
static uint32_t last_rx;
|
||||
uint32_t baud=uart.Init.BaudRate;
|
||||
/* Изменение скорости применяется после окончания ответа на старой скорости.
|
||||
* Незавершённый входной кадр сбрасывается, чтобы не объединить байты разных скоростей. */
|
||||
if (!transmitting && baud!=emu_uart_baud()) {
|
||||
uart.Init.BaudRate=emu_uart_baud();
|
||||
if (HAL_UART_Init(&uart)!=HAL_OK) board_fatal(4);
|
||||
received=0; invalid=0; return;
|
||||
}
|
||||
if (__HAL_UART_GET_FLAG(&uart,UART_FLAG_ORE) || __HAL_UART_GET_FLAG(&uart,UART_FLAG_FE) ||
|
||||
__HAL_UART_GET_FLAG(&uart,UART_FLAG_NE) || __HAL_UART_GET_FLAG(&uart,UART_FLAG_PE)) {
|
||||
/* На G4 ошибки сбрасываются записью ICR. RQR удаляет ошибочный байт;
|
||||
* чтение SR/DR, применяемое на F407, здесь не используется. */
|
||||
__HAL_UART_CLEAR_FLAG(&uart,UART_CLEAR_PEF|UART_CLEAR_FEF|UART_CLEAR_NEF|UART_CLEAR_OREF);
|
||||
__HAL_UART_SEND_REQ(&uart,UART_RXDATA_FLUSH_REQUEST);
|
||||
invalid=1; ++uart_errors; last_rx=now;
|
||||
} else if (__HAL_UART_GET_FLAG(&uart,UART_FLAG_RXNE)) {
|
||||
uint8_t b;
|
||||
/* RXNE уже установлен; timeout=0 не допускает ожидания следующего байта.
|
||||
* Во время передачи входные байты вычитываются и отбрасываются, чтобы не принять собственное эхо. */
|
||||
if (HAL_UART_Receive(&uart,&b,1,0)==HAL_OK && !transmitting) {
|
||||
if (received<sizeof(rx)) rx[received++]=b;
|
||||
else { invalid=1; ++uart_overflows; }
|
||||
last_rx=now;
|
||||
}
|
||||
}
|
||||
/* At >19200 baud RTU t3.5 is 1.75 ms; 3 ticks guarantees >=2 ms. */
|
||||
/* Для 8N1 ниже порога считаем 3,5 символа по 10 бит с округлением и запасом одного тика.
|
||||
* Выше 19200 бод используем 3 тика: с учётом фазы SysTick фактическая пауза не меньше 2 мс. */
|
||||
if (received && !transmitting && (uint32_t)(now-last_rx)>=
|
||||
(baud>19200u ? 3u : (35000u+baud-1)/baud+1u)) {
|
||||
uint16_t length=invalid ? 0 : emu_rtu(rx,received,tx);
|
||||
received=0; invalid=0;
|
||||
if (length) {
|
||||
transmitting=1;
|
||||
#if EMU_RS485_DE
|
||||
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET);
|
||||
#endif
|
||||
/* Передача асинхронная: poll возвращается сразу. tx не перезаписывается, пока transmitting != 0. */
|
||||
if (HAL_UART_Transmit_IT(&uart,tx,length)!=HAL_OK) {
|
||||
++uart_errors; HAL_UART_TxCpltCallback(&uart);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/**
|
||||
* Аппаратный исполнитель генератора: таблица uint16_t в SRAM -> DMA1 -> DAC1/PA4.
|
||||
* TIM6 задаёт частоту выборок аппаратными событиями TRGO; CPU не отправляет каждый отсчёт.
|
||||
* Модуль владеет только DAC channel 1, TIM6 и DMA1 channel 1 и DMAMUX request DAC1_CHANNEL1.
|
||||
*/
|
||||
/* HAL backend owned by this board; the shared protocol remains hardware independent. */
|
||||
#include "wave_hal.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
|
||||
static DAC_HandleTypeDef dac;
|
||||
static DMA_HandleTypeDef dma;
|
||||
static TIM_HandleTypeDef timer;
|
||||
static uint8_t initialized;
|
||||
|
||||
/* Callback остановки set_wave_port. context не используется: имеется один статический комплект HAL handles.
|
||||
* Сначала останавливаем источник триггеров, затем DMA/DAC. После первого запуска держим PA4 на нулевом коде;
|
||||
* до инициализации функция ничего не делает. Второй канал DAC не перенастраивается. */
|
||||
static void stop(void *context)
|
||||
{
|
||||
(void)context;
|
||||
if (!initialized) return;
|
||||
(void)HAL_TIM_Base_Stop(&timer);
|
||||
(void)HAL_DAC_Stop_DMA(&dac,DAC_CHANNEL_1);
|
||||
/* Keep channel 1 driving zero after stop; channel 2 is untouched. */
|
||||
{
|
||||
DAC_ChannelConfTypeDef config={0};
|
||||
/* G4 при AHB=160 МГц требует HFSEL; выход направляем на внешний PA4. */
|
||||
config.DAC_HighFrequency=DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC;
|
||||
config.DAC_ConnectOnChipPeripheral=DAC_CHIPCONNECT_EXTERNAL;
|
||||
config.DAC_UserTrimming=DAC_TRIMMING_FACTORY;
|
||||
config.DAC_Trigger=DAC_TRIGGER_NONE; config.DAC_OutputBuffer=DAC_OUTPUTBUFFER_ENABLE;
|
||||
(void)HAL_DAC_ConfigChannel(&dac,&config,DAC_CHANNEL_1);
|
||||
(void)HAL_DAC_SetValue(&dac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0);
|
||||
(void)HAL_DAC_Start(&dac,DAC_CHANNEL_1);
|
||||
}
|
||||
}
|
||||
/* Callback запуска: samples — таблица кодов DAC, count — число отсчётов, rate — выборок/с.
|
||||
* Таблица должна оставаться действительной и неизменной до stop(). Возвращает 0 при успехе, 1 при ошибке.
|
||||
* Проверки формы сигнала выполняет общий генератор; здесь проверяется аппаратная выполнимость. */
|
||||
static int start(void *context,const uint16_t *samples,size_t count,uint32_t rate)
|
||||
{
|
||||
GPIO_InitTypeDef gpio={0};
|
||||
DAC_ChannelConfTypeDef config={0};
|
||||
/* G4 при AHB=160 МГц требует HFSEL; выход направляем на внешний PA4. */
|
||||
config.DAC_HighFrequency=DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC;
|
||||
config.DAC_ConnectOnChipPeripheral=DAC_CHIPCONNECT_EXTERNAL;
|
||||
config.DAC_UserTrimming=DAC_TRIMMING_FACTORY;
|
||||
TIM_MasterConfigTypeDef master={0};
|
||||
/* При делителе APB1=2 таймер получает удвоенную частоту PCLK1, то есть 160 МГц.
|
||||
* Эта формула соответствует дереву тактирования из board.c. */
|
||||
uint32_t ticks,psc,clock=HAL_RCC_GetPCLK1Freq()*2u;
|
||||
uintptr_t address=(uintptr_t)samples;
|
||||
(void)context;
|
||||
/* Разрешены 2..SET_WAVE_MAX отсчётов, частота до 1 МГц и только точное деление частоты таймера.
|
||||
* Для DMA используем SRAM1/2: буфер должен лежать в 0x20000000..0x20017FFF; CCM оставляем архиву. */
|
||||
if (!rate || rate>SET_WAVE_MAX_RATE || count<2 || count>SET_WAVE_MAX || clock%rate ||
|
||||
address<0x20000000u || address+count*2>0x20018000u) return 1;
|
||||
ticks=clock/rate;
|
||||
/* Подбираем целые (PSC+1) и (ARR+1), оба не больше 65536.
|
||||
* Частота выборок = timer_clock / ((PSC+1)*(ARR+1)); в регистры HAL передаёт значения минус один. */
|
||||
for (psc=1;psc<=65536;++psc) if (ticks%psc==0 && ticks/psc<=65536) break;
|
||||
if (psc>65536 || ticks<psc) return 1;
|
||||
stop(context);
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE();
|
||||
__HAL_RCC_DMAMUX1_CLK_ENABLE(); __HAL_RCC_DAC1_CLK_ENABLE(); __HAL_RCC_TIM6_CLK_ENABLE();
|
||||
gpio.Pin=GPIO_PIN_4; gpio.Mode=GPIO_MODE_ANALOG; gpio.Pull=GPIO_NOPULL;
|
||||
HAL_GPIO_Init(GPIOA,&gpio);
|
||||
timer.Instance=TIM6; timer.Init.Prescaler=psc-1; timer.Init.Period=ticks/psc-1;
|
||||
timer.Init.CounterMode=TIM_COUNTERMODE_UP; timer.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_DISABLE;
|
||||
/* Remove TRGO before Base_Init generates its prescaler update event. */
|
||||
master.MasterOutputTrigger=TIM_TRGO_RESET;
|
||||
master.MasterSlaveMode=TIM_MASTERSLAVEMODE_DISABLE;
|
||||
if (HAL_TIMEx_MasterConfigSynchronization(&timer,&master)!=HAL_OK ||
|
||||
HAL_TIM_Base_Init(&timer)!=HAL_OK) return 1;
|
||||
__HAL_TIM_SET_COUNTER(&timer,0); __HAL_TIM_CLEAR_FLAG(&timer,TIM_FLAG_UPDATE);
|
||||
master.MasterOutputTrigger=TIM_TRGO_UPDATE;
|
||||
if (HAL_TIMEx_MasterConfigSynchronization(&timer,&master)!=HAL_OK) return 1;
|
||||
/* DMA пишет полуслова в фиксированный регистр DAC, последовательно читая таблицу.
|
||||
* Circular автоматически возвращает адрес к началу; высокий приоритет уменьшает риск underrun. */
|
||||
dma.Instance=DMA1_Channel1; dma.Init.Request=DMA_REQUEST_DAC1_CHANNEL1;
|
||||
dma.Init.Direction=DMA_MEMORY_TO_PERIPH; dma.Init.PeriphInc=DMA_PINC_DISABLE;
|
||||
dma.Init.MemInc=DMA_MINC_ENABLE; dma.Init.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD;
|
||||
dma.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD; dma.Init.Mode=DMA_CIRCULAR;
|
||||
dma.Init.Priority=DMA_PRIORITY_HIGH;
|
||||
if (HAL_DMA_Init(&dma)!=HAL_OK) return 1;
|
||||
/* Связываем DAC с DMA handle: HAL использует эту связь при запуске и остановке передачи. */
|
||||
dac.Instance=DAC1; __HAL_LINKDMA(&dac,DMA_Handle1,dma);
|
||||
if (HAL_DAC_Init(&dac)!=HAL_OK) return 1;
|
||||
initialized=1;
|
||||
config.DAC_Trigger=DAC_TRIGGER_T6_TRGO; config.DAC_OutputBuffer=DAC_OUTPUTBUFFER_ENABLE;
|
||||
if (HAL_DAC_ConfigChannel(&dac,&config,DAC_CHANNEL_1)!=HAL_OK) goto fail;
|
||||
__HAL_DAC_CLEAR_FLAG(&dac,DAC_FLAG_DMAUDR1);
|
||||
/* DMA fills the next sample after each trigger, preserving cyclic phase. */
|
||||
/* Первым триггером выводится заранее записанный последний отсчёт; далее идут 0,1,... из DMA.
|
||||
* Таймер запускается последним, когда DAC и DMA уже готовы. Приведение к uint32_t* требуется API HAL,
|
||||
* но размер реальной передачи задаётся DMA как HALFWORD (16 бит). */
|
||||
if (HAL_DAC_SetValue(&dac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,samples[count-1])!=HAL_OK ||
|
||||
HAL_DAC_Start_DMA(&dac,DAC_CHANNEL_1,(uint32_t *)samples,(uint32_t)count,DAC_ALIGN_12B_R)!=HAL_OK ||
|
||||
HAL_TIM_Base_Start(&timer)!=HAL_OK) goto fail;
|
||||
return 0;
|
||||
/* Если запуск после инициализации DAC не завершился, возвращаем выход к нулю и сообщаем ошибку. */
|
||||
fail:
|
||||
stop(context); return 1;
|
||||
}
|
||||
/* Возвращает таблицу callback-функций для общего генератора; само оборудование ещё не запускается. */
|
||||
set_wave_port wave_hal_port(void)
|
||||
{
|
||||
set_wave_port port={0,start,stop}; return port;
|
||||
}
|
||||
/* Опрос ошибки канала DMA и underrun DAC. У G4 нет F4 stream FIFO/direct-mode flags.
|
||||
* NVIC-прерывания DMA/DAC здесь не включены: флаги проверяет основной цикл, остановку выполняет wave_output_poll(). */
|
||||
int wave_hal_fault(void)
|
||||
{
|
||||
return initialized && (__HAL_DMA_GET_FLAG(&dma,DMA_FLAG_TE1) ||
|
||||
__HAL_DAC_GET_FLAG(&dac,DAC_FLAG_DMAUDR1));
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
# STM32G474xB USB boot port
|
||||
|
||||
SET v2 USB CDC bootloader for STM32G474 with 128 KiB Flash. Uses HSI16 for
|
||||
the CPU and HSI48 with CRS USB SOF synchronization for USB; no HSE required.
|
||||
Dedicated USB pins: PA11/PA12. The board must supply VDDUSB correctly.
|
||||
|
||||
Compile the shared `src/set_protocol.c`, `src/set_firmware.c`, `src/set_boot.c`,
|
||||
this port's `boot_main.c`, `boot_flash.c`, `boot_request.c`, `boot_usb_stream.c`
|
||||
and `boot_usb_port.c`. Supply G4 CMSIS/HAL/startup, ST USB Device CDC and the
|
||||
board's CDC byte transport (`usb_cdc.h`, `usb_setp_stream.h`).
|
||||
Copy `boot_config.template.h` as `boot_config.h`. The wrapper uses the common
|
||||
`../stm32-usb-boot/boot_usb_protocol.inc`; retain that directory when importing.
|
||||
|
||||
For the shared emulator CDC transport, set `EMU_USB_SET_V2=1`,
|
||||
`USB_CDC_IRQn=USB_LP_IRQn` and optionally `USB_CDC_PRODUCT_NAME`.
|
||||
Provide `usbd_conf.h` using `stm32g4xx_hal.h`. The low-level port allocates
|
||||
non-overlapping PMA buffers for EP0, CDC IN/OUT and notifications.
|
||||
|
||||
Link boot code below `0x0800F000`, reserve `0x0800F000..0x0800FFFF` for metadata,
|
||||
and link the application at `0x08010000` with VTOR at that address.
|
||||
Application capacity is 64 KiB, ending at `0x08020000` (exclusive).
|
||||
Flash size must be 128 KiB and DBANK must be zero (single bank, 4 KiB pages).
|
||||
BFB2 and FB_MODE bank swapping must be disabled. Unsupported geometry is
|
||||
rejected before erase. On xB in dual-bank mode the upper 64 KiB starts at
|
||||
`0x08040000`, leaving a hole at the application's link address. Therefore this
|
||||
profile deliberately rejects dual-bank mode. Set DBANK=0 via SWD before the
|
||||
initial installation, then program both bootloader and application afresh.
|
||||
The port does not modify Option Bytes.
|
||||
|
||||
HAL doubleword programming plus an eight-byte staging buffer handles arbitrary
|
||||
SET block sizes without programming ECC words twice. Only the final word is
|
||||
padded with FF; CRC covers the declared image size. END checks written Flash
|
||||
and vectors, commits metadata last; ACTIVATE defers reset by 500 ms for the reply.
|
||||
|
||||
The reset policy matches F407: valid vectors alone allow SWD-installed images.
|
||||
Metadata is not required at reset, so an incomplete image with valid vectors can
|
||||
also start after an interrupted update. Erase the first application page through
|
||||
SWD to recover. No rollback, resume or signature authentication is implemented.
|
||||
|
||||
Application integration: compile `boot_request.c`, reserve `TAMP->BKP0R`, call
|
||||
`g474_request_boot(now)` for an update request and `g474_reset_poll(now)` from
|
||||
the main loop. Reset consumes and clears the backup-register request.
|
||||
|
||||
Host test `test_boot_port.c` covers the 64 KiB slot boundaries, geometry rejection, vector validation,
|
||||
every block size 1..256, all final-word tails, sequential offsets and failures.
|
||||
Compile it with this directory and `../../include` on the include path.
|
||||
Hardware USB/Flash/power-loss tests remain required on the target board.
|
||||
@@ -1,5 +0,0 @@
|
||||
#ifndef G474_BOOT_CONFIG_H
|
||||
#define G474_BOOT_CONFIG_H
|
||||
#define G474_BOOT_NODE 16U
|
||||
#define G474_BOOT_MODEL "STM32G474RB-USB-BOOT"
|
||||
#endif
|
||||
@@ -1,109 +0,0 @@
|
||||
#include "boot_port.h"
|
||||
#include "boot_image.h"
|
||||
#include "boot_writer.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
#include <string.h>
|
||||
#define COMMIT 0x53455442UL
|
||||
static g474_writer writer;
|
||||
static bool geometry_valid(void)
|
||||
{
|
||||
__HAL_RCC_SYSCFG_CLK_ENABLE();
|
||||
return g474_geometry_supported(*(const uint16_t *)FLASHSIZE_BASE,
|
||||
(FLASH->OPTR & FLASH_OPTR_DBANK) != 0,
|
||||
(FLASH->OPTR & FLASH_OPTR_BFB2) != 0 ||
|
||||
(SYSCFG->MEMRMP & SYSCFG_MEMRMP_FB_MODE) != 0);
|
||||
}
|
||||
static bool erase_page(uint32_t address)
|
||||
{
|
||||
FLASH_EraseInitTypeDef config = {0};
|
||||
uint32_t bank, page, error;
|
||||
if (!g474_flash_page(address, (FLASH->OPTR & FLASH_OPTR_DBANK) != 0,
|
||||
&bank, &page)) return false;
|
||||
config.TypeErase = FLASH_TYPEERASE_PAGES;
|
||||
config.Banks = bank == 2U ? FLASH_BANK_2 : FLASH_BANK_1;
|
||||
config.Page = page; config.NbPages = 1;
|
||||
return HAL_FLASHEx_Erase(&config, &error) == HAL_OK;
|
||||
}
|
||||
static bool invalidate(void *user)
|
||||
{
|
||||
bool ok; (void)user;
|
||||
if (!geometry_valid() || HAL_FLASH_Unlock() != HAL_OK) return false;
|
||||
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
|
||||
ok = erase_page(G474_META_BASE);
|
||||
(void)HAL_FLASH_Lock(); return ok;
|
||||
}
|
||||
static bool erase(void *user, uint32_t size)
|
||||
{
|
||||
uint32_t address, step;
|
||||
bool ok = true; (void)user;
|
||||
if (size < 8 || size > G474_APP_SIZE || !geometry_valid()) return false;
|
||||
g474_writer_init(&writer, size);
|
||||
if (HAL_FLASH_Unlock() != HAL_OK) return false;
|
||||
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
|
||||
step = (FLASH->OPTR & FLASH_OPTR_DBANK) ? 2048U : 4096U;
|
||||
for (address = G474_APP_BASE; address < G474_APP_BASE + size; address += step) {
|
||||
if (!erase_page(address)) { ok = false; break; }
|
||||
}
|
||||
(void)HAL_FLASH_Lock(); return ok;
|
||||
}
|
||||
static bool program_word(void *user, uint32_t offset, uint64_t word)
|
||||
{
|
||||
uint64_t actual; (void)user;
|
||||
if ((offset & 7U) || offset > G474_APP_SIZE - 8U) return false;
|
||||
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, G474_APP_BASE + offset,
|
||||
word) != HAL_OK) return false;
|
||||
memcpy(&actual, (const void *)(G474_APP_BASE + offset), 8);
|
||||
return actual == word;
|
||||
}
|
||||
static bool write(void *user, uint32_t offset, const uint8_t *data, uint16_t length)
|
||||
{
|
||||
bool ok; (void)user;
|
||||
if (HAL_FLASH_Unlock() != HAL_OK) return false;
|
||||
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
|
||||
ok = g474_writer_write(&writer, offset, data, length, program_word, 0);
|
||||
(void)HAL_FLASH_Lock(); return ok;
|
||||
}
|
||||
static bool verify(void *user, uint32_t size, uint32_t crc)
|
||||
{
|
||||
(void)user;
|
||||
return !writer.failed && !writer.used && writer.received == size &&
|
||||
writer.size == size &&
|
||||
g474_vectors_valid((const uint8_t *)G474_APP_BASE, size) &&
|
||||
setp_crc32((const uint8_t *)G474_APP_BASE, size) == crc;
|
||||
}
|
||||
static bool commit(void *user, uint32_t size, uint32_t crc)
|
||||
{
|
||||
uint32_t record[4] = {size, crc, ~size, COMMIT};
|
||||
uint64_t word; unsigned i; bool ok = true; (void)user;
|
||||
if (HAL_FLASH_Unlock() != HAL_OK) return false;
|
||||
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
|
||||
for (i = 0; i < sizeof(record); i += 8) {
|
||||
memcpy(&word, (const uint8_t *)record + i, 8);
|
||||
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, G474_META_BASE + i,
|
||||
word) != HAL_OK) { ok = false; break; }
|
||||
}
|
||||
(void)HAL_FLASH_Lock();
|
||||
return ok && !memcmp((const void *)G474_META_BASE, record, sizeof(record));
|
||||
}
|
||||
bool g474_app_valid(void)
|
||||
{
|
||||
/* Same reset policy as F407: SWD images need no USB commit record. */
|
||||
return geometry_valid() &&
|
||||
g474_vectors_valid((const uint8_t *)G474_APP_BASE, G474_APP_SIZE);
|
||||
}
|
||||
static void activate(void *user) { (void)user; }
|
||||
const setp_boot_ops g474_boot_ops = {invalidate, erase, write, verify, commit, activate};
|
||||
__attribute__((naked,noreturn)) static void enter(uint32_t sp, uint32_t pc)
|
||||
{
|
||||
__asm volatile("msr msp, r0\nmovs r2, #0\nmsr control, r2\ndsb\nisb\ncpsie i\nbx r1\n");
|
||||
}
|
||||
void g474_jump_app(void)
|
||||
{
|
||||
unsigned i;
|
||||
uint32_t sp = *(const uint32_t *)G474_APP_BASE;
|
||||
uint32_t pc = *(const uint32_t *)(G474_APP_BASE + 4);
|
||||
__disable_irq(); SysTick->CTRL = 0;
|
||||
for (i = 0; i < 8; ++i) { NVIC->ICER[i] = 0xffffffff; NVIC->ICPR[i] = 0xffffffff; }
|
||||
SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk | SCB_ICSR_PENDSVCLR_Msk;
|
||||
SCB->VTOR = G474_APP_BASE; enter(sp, pc);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef G474_BOOT_IMAGE_H
|
||||
#define G474_BOOT_IMAGE_H
|
||||
#include "boot_port.h"
|
||||
#include <string.h>
|
||||
/* SRAM1+SRAM2: 96 KB; CCM: 32 KB, also aliased after SRAM2. */
|
||||
static inline bool g474_vectors_valid(const uint8_t *image, uint32_t size)
|
||||
{
|
||||
uint32_t sp, pc;
|
||||
if (size < 8 || size > G474_APP_SIZE) return false;
|
||||
memcpy(&sp, image, 4); memcpy(&pc, image + 4, 4);
|
||||
return !(sp & 7U) &&
|
||||
((sp > 0x20000000UL && sp <= 0x20020000UL) ||
|
||||
(sp > 0x10000000UL && sp <= 0x10008000UL)) &&
|
||||
(pc & 1U) && (pc & ~1UL) >= G474_APP_BASE &&
|
||||
(pc & ~1UL) < G474_APP_BASE + size;
|
||||
}
|
||||
/* A contiguous xB slot at 0x08010000 exists only with DBANK=0.
|
||||
* In dual mode the upper 64 KB is at 0x08040000, not at this slot address. */
|
||||
static inline bool g474_geometry_supported(uint16_t flash_kb, bool dual, bool swapped)
|
||||
{
|
||||
return flash_kb == 128U && !dual && !swapped;
|
||||
}
|
||||
static inline bool g474_flash_page(uint32_t address, bool dual,
|
||||
uint32_t *bank, uint32_t *page)
|
||||
{
|
||||
uint32_t offset;
|
||||
if (dual || address < G474_META_BASE || address >= G474_APP_BASE + G474_APP_SIZE)
|
||||
return false;
|
||||
offset = address - 0x08000000UL;
|
||||
*bank = 1U;
|
||||
*page = offset / 4096U;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "stm32g4xx_hal.h"
|
||||
#include "boot_port.h"
|
||||
#include "usb_cdc.h"
|
||||
static volatile uint32_t clock_fault;
|
||||
void SysTick_Handler(void) { HAL_IncTick(); }
|
||||
static void fatal(uint32_t code) { clock_fault = code; for (;;) {} }
|
||||
static void usb_clock_init(void)
|
||||
{
|
||||
RCC_OscInitTypeDef osc = {0};
|
||||
RCC_PeriphCLKInitTypeDef peripheral = {0};
|
||||
RCC_CRSInitTypeDef crs = {0};
|
||||
/* CPU stays on reset HSI16. USB uses HSI48 disciplined by USB SOF. */
|
||||
osc.OscillatorType = RCC_OSCILLATORTYPE_HSI48;
|
||||
osc.HSI48State = RCC_HSI48_ON;
|
||||
if (HAL_RCC_OscConfig(&osc) != HAL_OK) fatal(2);
|
||||
peripheral.PeriphClockSelection = RCC_PERIPHCLK_USB;
|
||||
peripheral.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;
|
||||
if (HAL_RCCEx_PeriphCLKConfig(&peripheral) != HAL_OK) fatal(3);
|
||||
__HAL_RCC_CRS_CLK_ENABLE();
|
||||
crs.Prescaler = RCC_CRS_SYNC_DIV1;
|
||||
crs.Source = RCC_CRS_SYNC_SOURCE_USB;
|
||||
crs.Polarity = RCC_CRS_SYNC_POLARITY_RISING;
|
||||
crs.ReloadValue = __HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000U, 1000U);
|
||||
crs.ErrorLimitValue = RCC_CRS_ERRORLIMIT_DEFAULT;
|
||||
crs.HSI48CalibrationValue = RCC_CRS_HSI48CALIBRATION_DEFAULT;
|
||||
HAL_RCCEx_CRSConfig(&crs);
|
||||
}
|
||||
void boot_protocol_init(void);
|
||||
void boot_protocol_poll(uint32_t now);
|
||||
int main(void)
|
||||
{
|
||||
bool requested;
|
||||
SCB->VTOR = FLASH_BASE;
|
||||
requested = g474_boot_requested();
|
||||
if (!requested && g474_app_valid()) g474_jump_app();
|
||||
if (HAL_Init() != HAL_OK) fatal(1);
|
||||
usb_clock_init(); boot_protocol_init(); usb_cdc_init();
|
||||
for (;;) { uint32_t now = HAL_GetTick(); usb_cdc_poll(now); boot_protocol_poll(now); }
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef G474_BOOT_PORT_H
|
||||
#define G474_BOOT_PORT_H
|
||||
#include "set_boot.h"
|
||||
/* STM32G474xB (128 KB), single-bank mode, normal bank mapping. */
|
||||
#define G474_APP_BASE 0x08010000UL
|
||||
#define G474_APP_SIZE 0x00010000UL
|
||||
#define G474_META_BASE 0x0800F000UL
|
||||
#define G474_BOOT_REQUEST 0x1003U
|
||||
extern const setp_boot_ops g474_boot_ops;
|
||||
bool g474_app_valid(void);
|
||||
bool g474_boot_requested(void);
|
||||
void g474_request_boot(uint32_t now);
|
||||
void g474_reset_poll(uint32_t now);
|
||||
void g474_jump_app(void);
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user