Compare commits

...

70 Commits

Author SHA1 Message Date
Edward Emelianov
45559545ef added russian manual for USB-GPIO 2026-03-19 23:52:09 +03:00
Edward Emelianov
e2a20a046a SPI works, may say it's over 2026-03-18 23:49:29 +03:00
Edward Emelianov
c80f0884f5 add SPI, not working yet (also need to set SPOL/SPHA etc) 2026-03-17 23:57:08 +03:00
Edward Emelianov
165780bef9 I2C works 2026-03-17 21:17:45 +03:00
Edward Emelianov
03b05051aa PWM works 2026-03-15 23:44:58 +03:00
Edward Emelianov
61262435a8 start adding PWM 2026-03-15 02:24:58 +03:00
Edward Emelianov
27eb723d80 add "hex+text" input 2026-03-14 23:44:54 +03:00
Edward Emelianov
57a44f1c66 OK, USART works, maybe add HEX input? 2026-03-14 22:46:28 +03:00
Edward Emelianov
65c9ae34bc USART almost working (but hangs on long messages) 2026-03-14 01:16:00 +03:00
Edward Emelianov
14f544374a cont 2026-03-13 00:15:18 +03:00
Edward Emelianov
b44fd9e91f start with USART (now DEBUG-compile is unavialable) 2026-03-11 23:40:39 +03:00
Edward Emelianov
3802f7d2cb add ADC & ADC monitoring 2026-03-10 22:40:45 +03:00
Edward Emelianov
8e78a12f06 GPIO tested; add monitoring. TODO: USB/SPI/I2C 2026-03-10 00:20:43 +03:00
Edward Emelianov
9713b10cd4 continue developing GPIO config; not tested yet 2026-03-09 01:24:02 +03:00
Edward Emelianov
d39682b143 tried to parse string functions using constexpr in C++ 2026-03-08 01:16:10 +03:00
Edward Emelianov
fb8b93b0fe usbcan_gpio: both USB interfaces works; start adding GPIO functions 2026-03-07 14:37:42 +03:00
Edward Emelianov
760bec420b starting CAN-GPIO, have a lot of errors 2026-03-07 01:20:06 +03:00
Edward Emelianov
25c2f085e8 New USB-CDC 2026-03-06 19:46:41 +03:00
Edward Emelianov
b5a4a21f51 USB CDC on STM32G0B1 works 2026-03-04 23:02:47 +03:00
Edward Emelianov
8f6a80e2c7 USART1 @ STM32G0B1 2026-03-02 23:21:31 +03:00
Edward Emelianov
71d30dd19a rename G0:G070 2026-03-01 23:12:53 +03:00
Edward Emelianov
1f4f111c52 added blink for STM32G0B1, next -> add USART 2026-03-01 23:11:22 +03:00
Edward Emelianov
061fd8bec8 add SPI (not tested yet) 2026-02-20 23:38:29 +03:00
Edward Emelianov
6c21d9f91a CAN works 2026-02-19 23:11:11 +03:00
Edward Emelianov
80c894517e add CAN, not tested yet 2026-02-18 22:54:20 +03:00
Edward Emelianov
68028f42a3 Seems like all U[S]ARTs works well. Tested on speeds up to 2Mbaud (as CH340 can). 2026-02-17 23:37:17 +03:00
Edward Emelianov
fb570367fa forgot to add 2026-02-16 23:59:00 +03:00
Edward Emelianov
73a0eeba8f almost working U[S]ARTs 2026-02-16 23:58:29 +03:00
Edward Emelianov
3ff87427ac partial work for interrupt-driven 2026-02-15 23:32:29 +03:00
Edward Emelianov
cd8151c684 seems like U[S]ARTS OK, not tested yet 2026-02-15 00:24:26 +03:00
Edward Emelianov
26e74a6b89 start adding U[S]ART support (need DMA) 2026-02-14 00:57:13 +03:00
Edward Emelianov
148c25b555 copy InterfaceBoard as 7CDCs template 2026-02-13 22:35:10 +03:00
Edward Emelianov
522e1e2ee3 some USB fixes 2026-02-13 20:55:37 +03:00
Edward Emelianov
2fa218f695 all interfaces works! 2026-02-12 23:16:48 +03:00
Edward Emelianov
e17058b2de all descriptors read - OK, interfaces work - bad (only first two works - why? need to debug) 2026-02-11 23:34:14 +03:00
Edward Emelianov
3a903d7d8c add timeouts 2026-02-09 21:04:47 +03:00
87683a12e6 fixed some bugs 2026-01-20 16:35:21 +03:00
3697adba41 add nocheck flags 2026-01-16 15:04:03 +03:00
9c0152d08f fixed bug with wrong DIAG output tracking 2026-01-15 17:27:31 +03:00
Edward Emelianov
e00aac01ec start 2025-12-28 20:26:27 +03:00
dafb0ab065 ODS BOM 2025-11-26 13:55:02 +03:00
Edward Emelianov
c2ac1b76a4 new kicad format 2025-11-23 17:53:32 +03:00
59c6e0c038 PCB, gerbers 2025-11-13 09:25:21 +03:00
90f79da8af Almost finish PCB 2025-11-12 18:07:19 +03:00
328cf42667 add schematics, start drawing PCB 2025-11-11 18:01:31 +03:00
bd62eba7da fixed some minor bugs (NO_PARNO for sending values of all ESW, remove inverting ESP pins..) 2025-11-06 11:03:38 +03:00
Edward Emelianov
24ddc7e1a6 add ADC/DAC and PWM 2025-10-06 23:27:30 +03:00
Edward Emelianov
17b4d714e0 Add BME280 over SPI 2025-10-05 00:10:02 +03:00
Edward Emelianov
f994799353 fixed bug when sending data larger than buffer size 2025-10-05 00:00:22 +03:00
Edward Emelianov
c7f6718d2e SPI works 2025-10-04 00:23:18 +03:00
Edward Emelianov
6059c3a798 start 2025-10-02 21:42:37 +03:00
Edward Emelianov
83b9fd552b OK; next stage: a pair of NTC, BME280, sensors' power management 2025-09-29 22:12:31 +03:00
Edward Emelianov
b082f4ea46 test USART with ringbuffer - OK; TODO: rewrite all proto for usage USB & USART 2025-09-28 23:15:23 +03:00
Edward Emelianov
96d0b3987a fixed header in help 2025-09-24 23:39:44 +03:00
Edward Emelianov
ca0b52493f Works for 5 sensors 2025-09-24 23:36:46 +03:00
Edward Emelianov
02c6bd124f cont 2025-09-23 22:25:07 +03:00
Edward Emelianov
2465cd9bd4 start coding for multi-sensor variant: gather data of all sensors and process image[N] only by request 2025-09-22 23:17:39 +03:00
Edward Emelianov
c29508df6c fixed address changing 2025-09-22 20:34:14 +03:00
Edward Emelianov
dc2897e783 It works! 2025-09-21 21:32:01 +03:00
Edward Emelianov
9b0ee87891 continue 2025-09-20 23:26:46 +03:00
Edward Emelianov
f3c3bf451b start 2025-09-19 23:30:21 +03:00
Edward Emelianov
080be6a1ec OK, I2C read by DMA works 2025-09-19 21:24:19 +03:00
Edward Emelianov
934f4fbddd start working with DMA 2025-09-18 23:54:01 +03:00
Edward Emelianov
3e701f147f continue writing 2025-09-16 22:53:15 +03:00
Edward Emelianov
ec8d56c4ae Start fixing code for real usage 2025-09-14 23:15:57 +03:00
Edward Emelianov
d790a1610d seems like I2C start works right, need some fixes for large data amount 2025-09-14 02:18:36 +03:00
Edward Emelianov
93e0b16f79 Add MLXtest, 4.8ms for parsing image in standard range 2025-09-11 20:13:53 +03:00
Edward Emelianov
8be4f0975e add new concept USB-CDC template for STM32F303 2025-09-04 21:25:58 +03:00
Edward Emelianov
962d181e89 add debug flag, remove one bug (echo to one interface in another's interrupt); still didn't found why there's a trash of output in input buffer of interface 0 2025-08-24 23:41:10 +03:00
Edward Emelianov
233eed8b12 add timeout for USB writting on CDC 2025-07-15 19:52:44 +03:00
689 changed files with 352153 additions and 185618 deletions

2
.gitignore vendored
View File

@@ -13,3 +13,5 @@
*.sublime-workspace *.sublime-workspace
*.bk *.bk
*-bak *-bak
.qtcreator
.clang-format

View File

@@ -1,155 +0,0 @@
# make debug adds -DEBUG -Werror
# make ADDEFS="additional defs"
BINARY = canstepper
BOOTPORT ?= /dev/ttyUSB0
BOOTSPEED ?= 115200
# MCU FAMILY
FAMILY ?= F0
# MCU code (STM32F072xx)
MCU ?= F072xB
# change this linking script depending on particular MCU model,
LDSCRIPT ?= stm32f0728.ld
DEFS = ${ADDEFS} -DVERSION=\"0.0.1\" -DUSARTNUM=1
TARGET := RELEASE
FP_FLAGS ?= -msoft-float
ASM_FLAGS ?= -mthumb -mcpu=cortex-m0 -march=armv6-m -mtune=cortex-m0
ARCH_FLAGS = $(ASM_FLAGS) $(FP_FLAGS)
###############################################################################
# Executables
#PREFIX ?= arm-none-eabi
# gcc from arm web site
PREFIX ?= /opt/bin/arm-none-eabi
RM := rm -f
RMDIR := rmdir
CC := $(PREFIX)-gcc
LD := $(PREFIX)-gcc
AR := $(PREFIX)-ar
AS := $(PREFIX)-as
SIZE := $(PREFIX)-size
OBJCOPY := $(PREFIX)-objcopy
OBJDUMP := $(PREFIX)-objdump
GDB := $(PREFIX)-gdb
STFLASH := $(shell which st-flash)
STBOOT := $(shell which stm32flash)
DFUUTIL := $(shell which dfu-util)
###############################################################################
# Source files
OBJDIR = mk
SRC := $(wildcard *.c)
OBJS := $(addprefix $(OBJDIR)/, $(SRC:%.c=%.o))
STARTUP = $(OBJDIR)/startup.o
OBJS += $(STARTUP)
# dependencies: we need them to recompile files if their headers-dependencies changed
DEPS := $(OBJS:.o=.d)
INC_DIR ?= ../../inc
INCLUDE := -I$(INC_DIR)/Fx -I$(INC_DIR)/cm
LIB_DIR := $(INC_DIR)/ld
###############################################################################
# C flags
CFLAGS += -O2 -g -MD -D__thumb2__=1
CFLAGS += -Wall -Wextra -Wshadow
CFLAGS += -fno-common -ffunction-sections -fdata-sections
###############################################################################
# Linker flags
LDFLAGS += --static -nostartfiles --specs=nano.specs
LDFLAGS += -L$(LIB_DIR)
LDFLAGS += -T$(LDSCRIPT)
LDFLAGS += -Wl,-Map=$(OBJDIR)/$(BINARY).map
LDFLAGS += -Wl,--gc-sections
###############################################################################
# Used libraries
LDLIBS += -Wl,--start-group -lc -lgcc -Wl,--end-group
LDLIBS += $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)
DEFS += -DSTM32$(FAMILY) -DSTM32$(MCU)
ELF := $(OBJDIR)/$(BINARY).elf
LIST := $(OBJDIR)/$(BINARY).list
BIN := $(BINARY).bin
HEX := $(BINARY).hex
all: $(OBJDIR)/RELEASE bin list size
release: all
debug: CFLAGS += -DEBUG -Werror -W -Wimplicit-function-declaration
debug: $(OBJDIR)/DEBUG bin list size
$(OBJDIR)/DEBUG:
@rm -rf $(OBJDIR)
@mkdir $(OBJDIR)
@> $(OBJDIR)/DEBUG
@echo "TARGET: DEBUG"
echo "CFLAGS += -DEBUG -Werror -W -Wimplicit-function-declaration" > $(OBJDIR)/CFLAGS
$(OBJDIR)/RELEASE:
@rm -rf $(OBJDIR)
@mkdir $(OBJDIR)
@> $(OBJDIR)/RELEASE
@echo "TARGET: RELEASE"
echo "" > $(OBJDIR)/CFLAGS
elf: $(ELF)
bin: $(BIN)
hex: $(HEX)
list: $(LIST)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
-include $(OBJDIR)/CFLAGS
endif
$(OBJDIR):
mkdir $(OBJDIR)
$(STARTUP): $(INC_DIR)/startup/vector.c
@echo " CC startup"
$(CC) $(CFLAGS) $(DEFS) $(INCLUDE) $(ARCH_FLAGS) -o $@ -c $<
$(OBJDIR)/%.o: %.c
@echo " CC $<"
$(CC) $(CFLAGS) $(DEFS) $(INCLUDE) $(ARCH_FLAGS) -o $@ -c $<
$(BIN): $(ELF)
@echo " OBJCOPY $(BIN)"
$(OBJCOPY) -Obinary $(ELF) $(BIN)
$(HEX): $(ELF)
@echo " OBJCOPY $(HEX)"
$(OBJCOPY) -Oihex $(ELF) $(HEX)
$(LIST): $(ELF)
@echo " OBJDUMP $(LIST)"
$(OBJDUMP) -S $(ELF) > $(LIST)
$(ELF): $(OBJDIR) $(OBJS)
@echo " LD $(ELF)"
$(LD) $(LDFLAGS) $(ARCH_FLAGS) $(OBJS) $(LDLIBS) -o $(ELF)
size: $(ELF)
$(SIZE) $(ELF)
clean:
@echo " CLEAN"
@$(RM) $(HEX)
@$(RM) -rf $(OBJDIR) 2>/dev/null || true
flash: $(BIN)
@echo " FLASH $(BIN)"
$(STFLASH) --reset write $(BIN) 0x8000000
boot: $(BIN)
@echo " LOAD $(BIN) through bootloader"
$(STBOOT) -b$(BOOTSPEED) $(BOOTPORT) -w $(BIN)
dfuboot: $(BIN)
@echo " LOAD $(BIN) THROUGH DFU"
$(DFUUTIL) -a0 -D $(BIN) -s 0x08000000
.PHONY: clean flash boot

View File

@@ -1,154 +0,0 @@
BINARY = PL2303
BOOTPORT ?= /dev/ttyUSB0
BOOTSPEED ?= 115200
INDEPENDENT_HEADERS=
# MCU FAMILY
FAMILY ?= F0
# MCU code
MCU ?= F042x6
# change this linking script depending on particular MCU model,
LDSCRIPT ?= stm32f042x6.ld
# autoincremental version & build date
VERSION_FILE = version.inc
NEXTVER := $(shell expr $$(awk '/#define BUILD_NUMBER/' $(VERSION_FILE) | tr -cd "[0-9]") + 1)
BUILDDATE := $(shell date +%Y-%m-%d)
FP_FLAGS ?= -msoft-float
# -mfloat-abi=soft
ASM_FLAGS ?= -mthumb -mcpu=cortex-m0 -march=armv6-m -mtune=cortex-m0
ARCH_FLAGS = $(ASM_FLAGS) $(FP_FLAGS)
###############################################################################
# Executables
#PREFIX ?= arm-none-eabi
# gcc from arm web site
PREFIX ?= /opt/bin/arm-none-eabi
TOOLCHLIB ?= /opt/arm-none-eabi/lib
RM := rm -f
RMDIR := rmdir
CC := $(PREFIX)-gcc
LD := $(PREFIX)-gcc
AR := $(PREFIX)-ar
AS := $(PREFIX)-as
SIZE := $(PREFIX)-size
OBJCOPY := $(PREFIX)-objcopy
OBJDUMP := $(PREFIX)-objdump
GDB := $(PREFIX)-gdb
STFLASH := $(shell which st-flash)
STBOOT := $(shell which stm32flash)
DFUUTIL := $(shell which dfu-util)
###############################################################################
# Source files
OBJDIR := mk
SRC := $(wildcard *.c)
OBJS := $(addprefix $(OBJDIR)/, $(SRC:%.c=%.o))
STARTUP = $(OBJDIR)/startup.o
OBJS += $(STARTUP)
MAP = $(OBJDIR)/$(BINARY).map
# dependencies: we need them to recompile files if their headers-dependencies changed
DEPS := $(OBJS:.o=.d)
INC_DIR ?= ../inc
INCLUDE := -I$(INC_DIR)/Fx -I$(INC_DIR)/cm
LIB_DIR := $(INC_DIR)/ld
###############################################################################
# C flags
CFLAGS += -O2 -g -MD -D__thumb2__=1
CFLAGS += -Wall -Werror -Wextra -Wshadow -Wimplicit-function-declaration -Wredundant-decls
CFLAGS += -fno-common -ffunction-sections -fdata-sections
# -fno-stack-protector -fshort-enums
CFLAGS += $(ARCH_FLAGS) $(INCLUDE)
###############################################################################
# Linker flags
LDFLAGS += -nostartfiles --static -Wl,--print-memory-usage
# --specs=nano.specs -nostdlib
LDFLAGS += -Wl,-Map=$(MAP) -Wl,--gc-sections
LDFLAGS += -L$(LIB_DIR) -L$(TOOLCHLIB)
LDFLAGS += -T$(LDSCRIPT) $(ARCH_FLAGS)
###############################################################################
# Used libraries
LDLIBS += -Wl,--start-group -lc -lgcc -Wl,--end-group $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)
DEFS += -DSTM32$(FAMILY) -DSTM32$(MCU)
ELF := $(OBJDIR)/$(BINARY).elf
LIST := $(OBJDIR)/$(BINARY).list
BIN := $(BINARY).bin
HEX := $(BINARY).hex
all: bin list size
elf: $(ELF)
bin: $(BIN)
hex: $(HEX)
list: $(LIST)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
$(OBJDIR):
mkdir $(OBJDIR)
$(STARTUP): $(INC_DIR)/startup/vector.c
$(CC) $(CFLAGS) $(DEFS) $(INCLUDE) -o $@ -c $<
$(VERSION_FILE): *.[ch]
@echo " Generate version: $(NEXTVER) for date $(BUILDDATE)"
@sed -i "s/#define BUILD_NUMBER.*/#define BUILD_NUMBER \"$(NEXTVER)\"/" $(VERSION_FILE)
@sed -i "s/#define BUILD_DATE.*/#define BUILD_DATE \"$(BUILDDATE)\"/" $(VERSION_FILE)
$(OBJDIR)/proto.o: proto.c $(VERSION_FILE)
$(OBJDIR)/%.o: %.c
@echo " CC $<"
$(CC) $(CFLAGS) $(DEFS) $(INCLUDE) -o $@ -c $<
$(BIN): $(ELF)
@echo " OBJCOPY $(BIN)"
$(OBJCOPY) -Obinary $(ELF) $(BIN)
$(HEX): $(ELF)
@echo " OBJCOPY $(HEX)"
$(OBJCOPY) -Oihex $(ELF) $(HEX)
$(LIST): $(ELF)
@echo " OBJDUMP $(LIST)"
$(OBJDUMP) -S $(ELF) > $(LIST)
$(ELF): $(OBJDIR) $(OBJS)
@echo " LD $(ELF)"
$(LD) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $(ELF)
size: $(ELF)
$(SIZE) $(ELF)
clean:
@echo " CLEAN"
$(RM) $(OBJS) $(DEPS) $(ELF) $(HEX) $(LIST) $(MAP)
@rmdir $(OBJDIR) 2>/dev/null || true
flash: $(BIN)
@echo " FLASH $(BIN)"
$(STFLASH) --reset write $(BIN) 0x8000000
boot: $(BIN)
@echo " LOAD $(BIN) through bootloader"
$(STBOOT) -b$(BOOTSPEED) $(BOOTPORT) -w $(BIN)
dfuboot: $(BIN)
@echo " LOAD $(BIN) THROUGH DFU"
$(DFUUTIL) -a0 -D $(BIN) -s 0x08000000
openocd:
openocd -f openocd.cfg
dbg:
arm-none-eabi-gdb $(ELF) -ex 'target remote localhost:3333' -ex 'monitor reset halt'
.PHONY: clean flash boot dfuboot size dbg openocd

View File

@@ -41,7 +41,6 @@
11, 11,
12, 12,
13, 13,
14,
15, 15,
16, 16,
17, 17,

View File

@@ -75,6 +75,7 @@
"footprint_type_mismatch": "ignore", "footprint_type_mismatch": "ignore",
"hole_clearance": "error", "hole_clearance": "error",
"hole_near_hole": "error", "hole_near_hole": "error",
"holes_co_located": "warning",
"invalid_outline": "error", "invalid_outline": "error",
"isolated_copper": "warning", "isolated_copper": "warning",
"item_on_disabled_layer": "error", "item_on_disabled_layer": "error",

View File

@@ -426,7 +426,7 @@ __attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __get_PSP(void)
*/ */
__attribute__( ( always_inline ) ) __STATIC_INLINE void __set_PSP(uint32_t topOfProcStack) __attribute__( ( always_inline ) ) __STATIC_INLINE void __set_PSP(uint32_t topOfProcStack)
{ {
__ASM volatile ("MSR psp, %0\n" : : "r" (topOfProcStack) : "sp"); __ASM volatile ("MSR psp, %0\n" : : "r" (topOfProcStack) : );
} }
@@ -453,7 +453,7 @@ __attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __get_MSP(void)
*/ */
__attribute__( ( always_inline ) ) __STATIC_INLINE void __set_MSP(uint32_t topOfMainStack) __attribute__( ( always_inline ) ) __STATIC_INLINE void __set_MSP(uint32_t topOfMainStack)
{ {
__ASM volatile ("MSR msp, %0\n" : : "r" (topOfMainStack)); __ASM volatile ("MSR msp, %0\n" : : "r" (topOfMainStack) : );
} }

View File

@@ -0,0 +1,15 @@
BINARY := usbcangpio
# MCU code
MCU := F072xB
#MCU := F042x6
# change this linking script depending on particular MCU model,
LDSCRIPT := stm32f072x8.ld
#LDSCRIPT := stm32f042x6.ld
DEFINES := -DUSB2_16
include ../makefile.f0
include ../../makefile.stm32
$(OBJDIR)/gpioproto.o: gpioproto.cpp $(VERSION_FILE)
$(OBJDIR)/canproto.o: canproto.c $(VERSION_FILE)

View File

@@ -0,0 +1,406 @@
# USB-CAN-GPIO Adapter Command Protocol
Based on [USB-CAN](https://github.com/eddyem/stm32samples/tree/master/F0%3AF030%2CF042%2CF072/usbcan_ringbuffer).
Unlike original (only USB-CAN) adapter, in spite of it have GPIO contacts on board, this firmware allows to use both
USB-CAN and GPIO.
The old USB-CAN is available as earlier by /dev/USB-CANx, also you can see new device: /dev/USB-GPIOx.
New interface allows you to configure GPIO and use it's base functions.
---
# GPIO Interface Protocol for `/dev/USB-CANx`
---
## Overview
This firmware implements a versatile USB device with two CDC ACM interfaces:
- **ICAN** (interface 0) — for CAN bus communication
- **IGPIO** (interface 1) — for GPIO, USART, I2C, SPI, PWM control and monitoring
The device can be configured dynamically via text commands sent over the USB virtual COM ports. All settings can be saved to internal flash and restored on startup.
---
## General Command Format
- Commands are case-sensitive, up to 15 characters long.
- Parameters are separated by spaces or commas.
- Numeric values can be decimal, hexadecimal (`0x...`), octal (`0...`), or binary (`b...`).
- For `USART=...` command the part after `=` is interpreted according to the current `hexinput` mode (see `hexinput` command).
Specific numeric values (as in `SPI` and `I2C` send) are hex-only without `0x` prefix.
---
## Global Commands
| Command | Description |
|---------|-------------|
| `canspeed [=value]` | Get or set CAN bus speed (kBaud, 10─1000). |
| `curcanspeed` | Show the actual CAN speed currently used by the interface. |
| `curpinconf` | Dump the *current* (working) pin configuration ─ may differ from saved config. |
| `dumpconf` | Dump the global configuration stored in RAM (including CAN speed, interface names, pin settings, USART/I2C/SPI parameters). |
| `eraseflash` | Erase the entire user configuration storage area. |
| `help` | Show help message. |
| `hexinput [=0/1]` | Set input mode: `0` ─ plain text, `1` ─ hex bytes + quoted text. Affects commands like `USART` and `sendcan`. |
| `iic=addr data...` | Write data over I2C. Address and data bytes are given in hex. Example: `iic=50 01 02` |
| `iicread=addr nbytes` | Read `nbytes` from I2C device at `addr` and display as hex. Both addr and nbytes are hex numbers. |
| `iicreadreg=addr reg nbytes` | Read `nbytes` from I2C device register `reg`. Also hex. |
| `iicscan` | Start scanning I2C bus; found addresses are printed asynchronously. |
| `mcutemp` | Show MCU internal temperature in ℃*10. |
| `mcureset` | Reset the microcontroller. |
| `PAx [= ...]` | Configure or read GPIOA pin `x` (0-15). See **Pin Configuration** below. |
| `PBx [= ...]` | Configure or read GPIOB pin `x`. |
| `pinout [=function1,function2,...]` | List all pins and their available alternate functions. If a space- or comma-separated list of functions is given, only pins supporting any of them are shown. |
| `pwmmap` | Show pins capable of PWM and indicate collisions (pins sharing the same timer channel). |
| `readconf` | Re-read configuration from flash into RAM. |
| `reinit` | Apply the current pin configuration to hardware. Must be issued after any pin changes. |
| `saveconf` | Save current RAM configuration to flash. |
| `sendcan=...` | Send data over the CAN USB interface. If `hexinput=1`, the argument is parsed as hex bytes with plain text in quotes; otherwise as plain text (a newline is appended). |
| `setiface=N [=name]` | Set or get the name of interface `N` (0 = CAN, 1 = GPIO). |
| `SPI=data...` | Perform SPI transfer: send hex bytes, receive and display the received bytes. |
| `time` | Show system time in milliseconds since start. |
| `USART[=...]` | If `=...` is given, send data over USART (text or hex depending on `hexinput`). Without argument, read and display any received USART data (as text or hex, according to USART's `TEXT`/`HEX` setting). |
| `vdd` | Show approximate supply voltage (Vdd, V*100). |
---
## Pin Configuration (Commands `PAx`, `PBx`)
Pins are configured with the syntax:
```
PAx = MODE PULL OTYPE FUNC MISC ...
```
or for a simple digital output:
```
PAx = 0 # set pin low
PAx = 1 # set pin high
```
or for PWM output:
```
PAx = value # set PWM duty cycle (0-255)
```
### Available Keywords (in any order)
| Group | Keyword | Meaning |
|---------|----------|---------|
| **MODE** | `AIN` | Analog input (ADC) |
| | `IN` | Digital input |
| | `OUT` | Digital output |
| | `AF` | Alternate function ─ automatically set when a function like `USART`, `SPI`, `I2C`, `PWM` is selected. |
| **PULL** | `PU` | Pull-up enabled (GPIO-only group, don't affect on functions) |
| | `PD` | Pull-down enabled |
| | `FL` | Floating (no pull) |
| **OTYPE**| `PP` | Push-pull output |
| | `OD` | Open-drain output |
| **FUNC** | `USART` | Enable USART alternate function |
| | `SPI` | Enable SPI alternate function |
| | `I2C` | Enable I2C alternate function |
| | `PWM` | Enable PWM alternate function |
| **MISC** | `MONITOR`| Enable asynchronous monitoring of pin changes (for GPIO input or ADC) or USART incoming message. When the pin changes, its new value is sent over USB automatically. |
| | `THRESHOLD n` | Set ADC threshold (0-4095). Only meaningful with `AIN`. A change larger than this triggers an async report (if `MONITOR` enabled on this pin). |
| | `SPEED n` | Set interface speed/frequency (baud for USART, Hz for SPI, speed index for I2C). |
| | `TEXT` | USART operates in text mode (lines terminated by `\n`). |
| | `HEX` | USART operates in binary mode (data output as hex dump, data input by IDLE-separated portions if `MONITOR` enabled). |
### Notes
- `AF` is automatically set when any `FUNC` is selected; you do not need to type it explicitly.
- For `PWM`, the duty cycle can be set by assigning a number (0-255) directly, e.g. `PA1=128`.
- For `OUT`, assigning `0` or `1` sets/clears the pin.
- For `ADC` (`AIN`), `MONITOR` uses the `THRESHOLD` value; if not given, any change triggers a report.
- Conflicting configurations (e.g., two different USARTs on the same pins, or missing SCL/SDA for I2C) are detected and rejected with an error message.
After changing pin settings, you must issue the `reinit` command for the changes to take effect.
---
## USART
### Pin Assignment
USART1 can be used on:
- PA9 (TX), PA10 (RX)
- PB6 (TX), PB7 (RX)
USART2 on:
- PA2 (TX), PA3 (RX)
Both USARTs share the same DMA channels, so only one USART can be active at a time.
### Configuration via Pin Commands
When you set a pin to `FUNC_USART`, you can also specify:
- `SPEED n` ─ baud rate (default 9600)
- `TEXT` ─ enable line-oriented mode (data is buffered until `\n`)
- `HEX` ─ binary mode (data is sent/received as raw bytes; asynchronous output appears as hex dump)
- `MONITOR` ─ enable asynchronous reception; received data will be sent over USB automatically (as text lines or hex dump depending on mode)
Example:
```
PA9 = USART SPEED 115200 TEXT MONITOR
PA10 = USART
reinit
```
Now USART1 is active at 115200 baud, text mode, with monitoring. Any incoming line will be printed as `USART = ...`.
### Sending Data
```
USART=Hello, world! # if hexinput=0, sends plain text
USART=48 65 6c 6c 6f # if hexinput=1, sends 5 bytes
```
If `TEXT` mode is enabled, a newline is automatically appended to the transmitted string.
### Receiving Data
Without `=`, the command reads and displays any data waiting in the ring buffer, like:
```
USART = 48 65 6c 6c 6f
```
If in `TEXT` mode, only complete lines are returned. In `HEX` mode, all received bytes are shown as a hex dump.
If `MONITOR` disabled, but incoming data flow is too large for buffering between consequent `USART` calls,
some "old" data would be printed asynchroneously.
---
## I2C
### Pin Assignment
I2C1 is available on any mix of:
- PB6 (SCL), PB7 (SDA)
- PB10 (SCL), PB11 (SDA)
You must configure both SCL and SDA pins for I2C.
### Configuration via Pin Commands
- `I2C` ─ selects I2C alternate function
- `SPEED n` ─ speed index: 0=10kHz, 1=100kHz, 2=400kHz, 3=1MHz (default 100kHz if omitted)
- `MONITOR` is not used for I2C.
Example:
```
PB6 = I2C SPEED 2
PB7 = I2C
reinit
```
This sets up I2C at 400 kHz.
### Commands
- `iic=addr data...` ─ write bytes to device at 7-bit address `addr`. Address and data are given in hex, e.g. `iic=50 01 02 03`.
- `iicread=addr nbytes` ─ read `nbytes` (both numbers are hex) from device and display as hex dump.
- `iicreadreg=addr reg nbytes` ─ read `nbytes` from register `reg` (all numbers are hex).
- `iicscan` ─ start scanning all possible 7-bit addresses (1─127). As devices respond, messages like `foundaddr = 0x50` are printed asynchronously.
---
## SPI
### Pin Assignment
SPI1 can be used on any mix of:
- **PA5** (SCK), **PA6** (MISO), **PA7** (MOSI)
- **PB3** (SCK), **PB4** (MISO), **PB5** (MOSI)
All three pins are not strictly required; you may configure only SCK+MOSI (TX only) or SCK+MISO (RX only). The SPI peripheral will be set to the appropriate mode automatically.
### Configuration via Pin Commands
- `SPI` ─ selects SPI alternate function
- `SPEED n` ─ desired frequency in Hz (actual nearest prescaler will be chosen automatically)
- `CPOL` ─ clock polarity (1 = idle high)
- `CPHA` ─ clock phase (1 = second edge)
- `LSBFIRST` ─ LSB first transmission.
If `CPOL`/`CPHA` are not given, they default to 0 (mode 0). `LSBFIRST` defaults to MSB first.
Example:
```
PA5 = SPI SPEED 2000000 CPOL CPHA
PA6 = SPI
PA7 = SPI
reinit
```
Configures SPI at ~2 MHz, mode 3 (CPOL=1, CPHA=1), full duplex.
### Commands
- `SPI=data...` ─ send the given hex bytes, and display the received bytes. Works in full-duplex or write-only modes. Example:
```
SPI=01 02 03
```
will send three bytes and output the three bytes received simultaneously.
- `SPI=n` — reads `n` bytest of data (n should be decimal, binary or hex with prefixes `0b` or `0x`).
---
## PWM
### Pin Assignment
PWM is available on many pins; see the output of `pwmmap` (or `pinout=PWM`) for a complete list with possible conflicts (pins sharing the same timer channel).
Conflicts are automatically detected ─ if you try to use two conflicting pins, one will be reset to default.
### Configuration via Pin Commands
- `PWM` ─ selects PWM alternate function
- No additional parameters are needed; the duty cycle is set by writing a number directly to the pin.
Example:
```
PA1 = PWM
reinit
PA1=128 # set 50% duty cycle
```
### Reading PWM Value
```
PA1
```
returns the current duty cycle (0─255).
---
## Monitoring and Asynchronous Messages
When a pin is configured with `MONITOR` and is not in AF mode (i.e., GPIO input or ADC), any change in its state triggers an automatic USB message. The format is the same as the pin getter: `PAx = value`. For ADC, the value is the ADC reading; the message is sent only when the change exceeds the programmed `THRESHOLD` (if any).
USART monitoring (if enabled with `MONITOR`) sends received data asynchronously, using the same output format as the `USART` command.
I2C scan results are also printed asynchronously while scan mode is active.
---
## Saving Configuration
The entire configuration (interface names, CAN speed, pin settings, USART/I2C/SPI parameters) can be saved to flash with `saveconf`.
On startup, the last saved configuration is automatically loaded. The flash storage uses a simple rotating scheme, so many previous configurations are preserved until the storage area is erased with `eraseflash`.
---
## Error Codes
Most commands return one of the following status strings (if not silent):
| Code | Meaning |
|-------------|---------|
| `OK` | Command executed successfully. |
| `BADCMD` | Unknown command. |
| `BADPAR` | Invalid parameter. |
| `BADVAL` | Value out of range or unacceptable. |
| `WRONGLEN` | Message length incorrect. |
| `CANTRUN` | Cannot execute due to current state (e.g., peripheral not configured). |
| `BUSY` | Resource busy (e.g., USART TX busy). |
| `OVERFLOW` | Input string too long. |
Commands that produce no direct output (e.g., getters) return nothing (silent) and only print the requested value.
---
# CAN Interface Protocol for `/dev/USB-CANx`
---
This part describes the simple textbased protocol used to communicate with the CAN interface of the USBCANGPIO adapter.
The interface appears as a standard CDC ACM virtual COM port (e.g. `/dev/ttyACM0` or `/dev/USB-CANx`).
All commands are terminated by a newline character (`\n`). Numeric parameters can be entered in decimal, hexadecimal (prefix `0x`), octal (prefix `0`), or binary (prefix `b`).
---
## Received CAN Message Format
When a CAN message is received (and display is not paused), it is printed as:
```
<timestamp_ms> #<ID> <data0> <data1> ... <dataN-1>
```
- **`<timestamp_ms>`** system time in milliseconds since start.
- **`<ID>`** 11bit CAN identifier in hexadecimal (e.g. `0x123`).
- **`<dataX>`** data bytes in hexadecimal, separated by spaces. If the message has no data, only the timestamp and ID are printed.
Example:
```
12345 #0x123 0xDE 0xAD 0xBE 0xEF
```
---
## Commands
### General
| Command | Description | Example |
|---------|-------------|---------|
| `?` | Show a brief help message. | `?` |
### CAN Configuration & Control
| Command | Description | Example |
|---------|-------------|---------|
| `b [speed]` | Set CAN bus speed in kBaud (101000). Without argument, display the current speed. | `b 250` |
| `I` | Reinitialise the CAN controller with the last set speed. | `I` |
| `c` | Show CAN status registers (MSR, TSR, RF0R, RF1R). | `c` |
| `e` | Show the CAN error register (ESR) with a humanreadable description. | `e` |
### Sending Messages
| Command | Description | Example |
|---------|-------------|---------|
| `s ID [byte0 ... byte7]` | Send a CAN message with the given ID and up to 8 data bytes. Returns immediately after queuing. | `s 0x123 0xAA 0xBB` |
| `S ID [byte0 ... byte7]` | Same as `s`. | `S 0x100 0x01` |
### Flood (Periodic Transmission)
| Command | Description | Example |
|---------|-------------|---------|
| `F ID [byte0 ... byte7]` | Set a message to be transmitted repeatedly. The message is stored and sent every `t` milliseconds. | `F 0x200 0x55 0xAA` |
| `i` | Enable *incremental* flood mode. Sends a 4byte counter (increasing by 1 each time) using the ID from the last `F` command. | `i` |
| `t <ms>` | Set the flood period in milliseconds (default 5 ms). | `t 10` |
### Filtering
| Command | Description | Example |
|---------|-------------|---------|
| `f bank fifo mode num0 [num1 [num2 [num3]]]` | Configure a hardware filter. `bank` — filter bank number (027). `fifo` — FIFO assignment (0 or 1). `mode` — `I` for ID list mode, `M` for mask mode. `numX` — IDs or ID/mask pairs (for mask mode). | `f 0 1 I 0x123 0x456` two IDs in list mode. `f 1 0 M 0x123 0x7FF` ID 0x123 with mask 0x7FF (accept all). |
| `l` | List all active filters with their configuration. | `l` |
| `a <ID>` | Add an ID to the software ignore list (up to 10 IDs). Messages with this ID will not be printed. | `a 0x321` |
| `p` | Print the current ignore list. | `p` |
| `d` | Clear the entire ignore list. | `d` |
| `P` | Pause/resume printing of incoming CAN messages. Toggles between paused and running. | `P` |
### LEDs & Misc
| Command | Description | Example |
|---------|-------------|---------|
| `o` | Turn both LEDs off. | `o` |
| `O` | Turn both LEDs on. | `O` |
| `T` | Show the system time in milliseconds since start. | `T` |
| `R` | Perform a software reset of the microcontroller. | `R` |
---
## Error Reporting
When an error occurs, the device may print one of the following messages:
| Message | Meaning |
|-----------------|---------|
| `ERROR: ...` | General error with a descriptive text. |
| `NAK` | A transmitted message was not acknowledged (e.g., no receiver on the bus). |
| `FIFO overrun` | A CAN FIFO overrun occurred messages were lost. |
| `CAN bus is off`| The controller entered the busoff state; try to restart it with `I`. |
Additionally, the `e` command gives a detailed breakdown of the error register.
---
## Notes
- All settings (baud rate, filters, ignore list, flood message) are stored in RAM only and are lost after a reset or poweroff.
To make changes (only speed available) permanent, use the **GPIO interface** commands `saveconf`/`storeconf` after configuring CAN.
- The device can only handle one active USART at a time, but the CAN interface operates independently.
- The command parser is casesensitive for the singleletter commands (they are expected in lower case, except where noted).
---
## How to distinguish between identical device
In the **GPIO interface** you can setup custom interface name (`setiface=...`) for both USB-CAN and USB-GPIO interfaces.
After storing them in flash, reconnect and `lsusb -v` for given device will show your saved names on `iInterface` fields.
These fields could be used to create human-readable symlinks in `/dev`.
To see symlink in `/dev` to your `/dev/ttyACMx` device based on `iInterface` field, add this udev-script to `/etc/udev/rules.d/usbids.rules`
```
ACTION=="add", DRIVERS=="usb", ENV{USB_IDS}="%s{idVendor}:%s{idProduct}"
ACTION=="add", ENV{USB_IDS}=="067b:2303", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty"
ACTION=="add", ENV{USB_IDS}=="0483:5740", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty"
```

View File

@@ -0,0 +1,236 @@
Устройство основано на <a href=https://github.com/eddyem/stm32samples/tree/master/F0%3AF030%2CF042%2CF072/usbcan_ringbuffer>USB-CAN</a>
переходнике.
Функционал платы расширен: от части контактов МК выведены площадки, к которым возможно подпаять проводники, позволяющие задействовать функции GPIO (вход, выход push-pull или opendrain; с подтяжками или без), АЦП, I2C, USART и SPI.
При подключении устройство создает два интерфейса. При использовании соответствующего udev-скрипта (<a href="#udev">см. в конце</a>) автоматически возникают ссылки, по умолчанию: <tt>/dev/USB-CANx</tt> для "старого" интерфейса CAU-USB и <tt>/dev/USB-GPIOx</tt> для "нового".
Устройство эмулирует два CDC-ACM, имея простейший текстовый протокол.
<hr>
<h2>Общий формат команд</h2>
<ul>
<li>Команды чувствительны к регистру, команды-геттеры и команды-сеттеры отличаются наличием в последних знака "=".</li>
<li>Параметры сеттера разделяются пробелом или запятой.</li>
<li>Если не оговорено иное, числовые значения параметров могут иметь десятичный, шестнадцатеричный (<tt>0x...</tt>), восьмеричный (<tt>0x...</tt>) или двоичный (<tt>b...</tt>) формат.</li>
<li>В таких командах, как <tt>SPI</tt> или <tt>I2C</tt> все данные в шестнадцатеричном формате без префикса.</li>
<li>Ввод <b>всегда</b> построчный: символ '\n' означает конец строки и парсер, встретив этот символ, начинает разбор предыдущих символов.</li>
</ul>
<h2>Список команд</h2>
<dl>
<dt><tt>canspeed [=value]</tt></dt><dd>установить скорость CAN-интерфейса по умолчанию (при запуске CAN будет работать на этой скорости). В процессе работы с CAN-USB ее можно временно изменить в соответствии с протоколом (описан на страничке оригинального устройства).</dd>
<dt><tt>curcanspeed</tt></dt><dd>Отобразить текущую скорость CAN.</dd>
<dt><tt>curpinconf</tt></dt><dd>Текущая конфигурация выводов МК (может отличаться от сохраненной в конфигурации в случае ошибки при запуске команды <tt>reinit</tt>.</dd>
<dt><tt>dumpconf</tt></dt><dd>Отображение текущей глобальной конфигурации (в т.ч. параметров CAN, SPI и т.п.). Внимание! Эта конфигурация содержит обновленные данные, которые вы могли изменять в рантайме, поэтому она может не соответствовать актуальной. Для сохранения во флеш-память используйте <tt>saveconf</tt>.</dd>
<dt><tt>eraseflash</tt></dt><dd>Полная очистка области хранения конфигурации во флеш-памяти.</dd>
<dt><tt>help</tt></dt><dd>Отображение справки.</dd>
<dt><tt>hexinput [=0/1]</tt></dt><dd>Изменение режима ввода:
<ul>
<li>0 — простой текст (данные вводятся и выводятся построчно);</li>
<li>1 — шестнадцатеричный ввод (без префикса <tt>0x</tt>) с возможностью ввода текста в кавычках.</li>
</ul>Данный режим влияет лишь на команды <tt>USART</tt> и <tt>sendcan</tt>.</dd>
<dt><tt>iic=addr data...</tt></dt><dd>Запись данных в I2C. Адрес <tt>addr</tt> — несмещенный 7-битный адрес устрйства, данные в шестнадцатеричном формате без разделителя, например: <tt>iic=50 01 02</tt></dd>
<dt><tt>iicread=addr nbytes</tt></dt><dd>Чтение <tt>nbytes</tt> данных по I2C. Данные выводятся в режиме "hexdump".</dd>
<dt><tt>iicreadreg=addr reg nbytes</tt></dt><dd>Считать <tt>nbytes</tt> данных с регистра <tt>reg</tt>.</dd>
<dt><tt>iicscan</tt></dt><dd>Сканирование шины I2C в поиске активных устройств. Найденные рабочие устройства отображаются асинхронно.</dd>
<dt><tt>mcutemp</tt></dt><dd>Условная температура МК, ℃ с множителем 10.</dd>
<dt><tt>mcureset</tt></dt><dd>Перезагрузка микроконтроллера (будьте внимательны: это отразится и на интерфейсе CAN-USB).</dd>
<dt><tt>PAx [= ...]</tt> и <tt>PBx [= ...]</tt></dt><dd>Геттеры и сеттеры для выводов портов GPIOA и GPIOB соответственно (см. раздел «<a href="#pinconfig">Конфигурация выводов</a>»).</dd>
<dt><tt>pinout [=function1,function2,...]</tt></dt><dd>Отображение всех настраиваемых выводов. В случае сеттера отобразятся лишь соответствующие функциональные группы (например, <tt>pinout=ADC, PWM</tt> отобразит выводы, доступные для конфигурирования как входы АЦП или выходы ШИМ). Если вы видите одинаковые функции (например, <tt>PWM2_2</tt> на разных выводах, это — потенциальные конфликты: использовать данный функционал можно лишь на одном из этих выводов.</dd>
<dt><tt>pwmmap</tt></dt><dd>вариант команды <tt>pinout</tt> только для выводов с поддержкой ШИМ, конфликты отмечены явно.</dd>
<dt><tt>readconf</tt></dt><dd>Перечитать последнюю сохраненную в флеш-памяти конфигурацию (полезно, если вы что-то испортили в текущей, но не успели ее сохранить, и хотите "откатиться"). Если устройство "девственно", т.е. ни одной конфигурации не сохранено, это не сработает.</dd>
<dt><tt>reinit</tt></dt><dd>Проверка текущей конфигурации выводов и полная переинициализация. В случае ошибок настройки проблемных выводов будут сброшены в <tt>FL IN</tt>, что можно будет увидеть по вызову <tt>curpinconf</tt>.</dd>
<dt><tt>saveconf</tt></dt><dd>Сохранить текущие настройки во флеш-память.</dd>
<dt><tt>sendcan</tt></dt><dd>Отправить в <b>USB-интерфейс</b> устройства CAN-USB данные. Внимание! Эта команда не пересылает данные в CAN-шину (используйте для этого непосредственно соответствующий интерфейс), но позволяет передать какую-либо информацию процессу, работающую с этим интерфейсом.</dd>
<dt><tt>setiface=N [=name]</tt></dt><dd>Команда позволяет переименовать интерфейсы (0 = CAN, 1 = GPIO). Используйте удобные для вас наименования, не превышающие 16 символов. Помните, что udev-скрипт будет создавать ссылки с соответствующими именами, поэтому не используйте недопустимых символов (пробелы, слэши и прочее, что не допустимо в именах файлов).</dd>
<dt><tt>SPI=data...</tt> или <tt>SPI=size</tt></dt><dd>Обмен данными с устройством SPI. Первая команда работает в полнодуплексном режиме или режиме TX-only, передавая все введенные в шестнадцатеричном формате данные. В случае полнодуплексного режима считывается такое же количество данных с MISO и выводится как "hexdump". В режиме Rx-only используется второй вариант команды, считывая <tt>size</tt> байт с SPI.</dd>
<dt><tt>time</tt></dt><dd>Отображение условного времени (в мс), прошедшего с последней перезагрузки МК.</dd>
<dt><tt>USART[=...]</tt></dt><dd>Геттер считывает данные из приемного кольцевого буфера USART, сеттер отправляет перечисленные данные (их формат зависит от выбранного командой <tt>hexinput</tt> режима).</dd>
<dt><tt>vdd</tt></dt><dd>Примерное напряжение питания МК (В с множителем 100).</dd>
</dl>
<hr>
<h2 id="pinconfig">Конфигурация выводов (команды <tt>PAx</tt>/<tt>PBx</tt>)</h2>
Общий синтаксис настройки выводов следующий:
<pre>PXx = MODE PULL OTYPE FUNC MISC ...</pre>
Здесь <tt>PX</tt> — соответствующий порт (<tt>PA</tt> или <tt>PB</tt>), <tt>x</tt> — номер вывода. После знака равенства идет последовательность ключевых слов с возможными числовыми аргументами.
Кроме того, эта же команда работает как сеттер/геттер значения, если соответствующий вывод настроен как GPIO, АЦП (только геттер) или ШИМ. Например, если PA1 настроен как <tt>OUT</tt>, команда <tt>PA1=1</tt> установит его значение в высокое состояние, геттер же отобразит текущее состояние (из регистра <tt>GPIOX->IDR</tt>, т.е. для выхода с открытым стоком в состоянии "1" можно детектировать внешнюю верхнюю или нижнюю подтяжку).
Для настройки ШИМ (<tt>PWM</tt>) можно изменять заполнение выходного сигнала, например: <tt>PB2=128</tt> установит заполнение в 50%.
<h3>Ключевые слова</h3>
Ключевые слова могут идти в любой последовательности.
<h4>MODE</h4>
<dl>
<dt><tt>AIN</tt></dt><dd>Аналоговый вход (АЦП).</dd>
<dt><tt>IN</tt></dt><dd>Цифровой вход.</dd>
<dt><tt>OUT</tt></dt><dd>Цифровой выход.</dd>
<dt><tt>AF</tt></dt><dd>Альтернативная функция (данное ключевое слово не обязательно указывать, т.к. этот флаг установится автоматически при выборе любой функции вроде <tt>USART</tt> и т.п.).</dd>
</dl>
<h4>PULL</h4>
(только для режимов <tt>GPIO</tt>)
<dl>
<dt><tt>PU</tt></dt><dd>Включена верхняя подтяжка (как для входов, так и для выходов).</dd>
<dt><tt>PD</tt></dt><dd>Включена нижняя подтяжка (-//-, хоть для выходов это бессмысленно).</dd>
<dt><tt>FL</tt></dt><dd>Подтяжки выключены.</dd>
</dl>
Верхняя и нижняя подтяжки взаимоисключающие.
<h4>OTYPE</h4>
(только для режимов <tt>GPIO</tt>)
Настройка типа выхода (для входов не актуальна):
<dl>
<dt><tt>PP</tt></dt><dd>Push-pull.</dd>
<dt><tt>OD</tt></dt><dd>Открытый сток.</dd>
</dl>
<h4>FUNC</h4>
Выбор альтернативной функции:
<dl>
<dt><tt>USART</tt></dt><dd>Вывод будет частью USART (можно выбрать только Rx или Tx).</dd>
<dt><tt>SPI</tt></dt><dd>Вывод — часть SPI (обязательно выбрать SCK и хотя бы один из MISO или MOSI).</dd>
<dt><tt>I2C</tt></dt><dd>Вывод — часть I2C (обязательно выбрать как SCL, так и SDA).</dd>
<dt><tt>PWM</tt></dt><dd>Вывод с функцией ШИМ (частота ШИМ около 23.5кГц, заполнение от 0 до 255).</dd>
</dl>
<h4>MISC</h4>
<dl>
<dt><tt>MONITOR</tt></dt><dd>Асинхронный мониторинг функционала данного вывода: без запроса пользователя передаются данные, как только значение GPIO входа или АЦП изменится, либо же в случае USART — появятся новые данные.</dd>
<dt><tt>THRESHOLD</tt></dt><dd>Пороговое значение (0..4095) для мониторинга аналогового входа. Изменение асинхронно отображается лишь при превышении текущим значением данного порога относительно предыдущего.</dd>
<dt><tt>SPEED</tt></dt><dd>Установка скорости интерфейса (USART, SPI, I2C), в бодах, Гц или индексе.</dd>
<dt><tt>TEXT</tt></dt><dd>(только USART) Текстовый режим USART: входные и выходные данные буферизуются построчно. В случае включенного мониторинга отображаются лишь новые строки целиком, но не их части (в случае, если длина строки не превышает размера входного буфера).</dd>
<dt><tt>HEX</tt></dt><dd>(только USART) Шестнадцатеричный режим USART: блоки входных данных (не превышающих размер внутреннего буфера) разделяются по сигналу IDLE. Вывод — в формате "hexdump".</dd>
<dt><tt>LSBFIRST</tt></dt><dd>(только SPI) Передача младшего бита первым. По умолчанию — MSBFIRST.</dd>
<dt><tt>CPOL</tt></dt><dd>(только SPI) полярность данных.</dd>
<dt><tt>CPHA</tt></dt><dd>(только SPI) выбор фронта активности данных.</dd>
</dl>
При выборе функционала, недоступного на данном выводе, сразу же будет выведена ошибка. В случае, если вы неправильно настроите выводы интерфейсов (скажем, забудете указать SCK для SPI), после вызова команды <tt>reinit</tt> появится сообщение об ошибке настройки, а проблемные места будут сброшены в значения по умолчанию.
<hr>
<h2>Альтернативные функции</h2>
<h3>USART</h3>
Данный интерфейс доступен на контактах PA9/PB6 (TX USART1), PA10/PB7 (RX USART1) или PA2 (TX USART2) и PA2 (RX USART2). Несмотря на наличие двух модулей USART, допускается использовать лишь один из них. Соответственно, нельзя настроить смесь для разных интерфейсов (скажем, PA9 и PA3). Прием и передача данных использует DMA, поэтому большие объемы данных, передаваемые по этому интерфейсу, не будут сказываться на работе преобразователя CAN-USB.
Для данного интерфейса при конфигурации используются такие ключевые слова, как SPEED, TEXT/HEX и MONITOR. Например,
<pre>PA9 = USART SPEED 115200 TEXT MONITOR
PA10 = USART
reinit</pre>
Данная последовательность команд настроит USART1 на выводах PA9/PA10. Скорость 115200 бод, текстовый режим с асинхронным выводом поступающих строк текста. Каждая новая строка будет выведена как <tt>USART = ...</tt>.
Обратите внимание: для отличия реального символа конца строки от переноса строки в случае вывода неполного буфера (например, при переполнении входного буфера), "реальный" символ '\n' удваивается.
Передача строк зависит от формата, например, в формате TEXT команда <tt>USART=Hello, world!\n</tt> отправит по USART строку "Hello, world!\n". А в формате HEX команда <tt>USART=48 65 6c 6c 6f\n</tt> отправит пять байт данных "Hello".
Команда-геттер <tt>USART</tt> отображает содержимое приемного кольцевого буфера. В "текстовом" режиме оно будет выводиться построчно на каждый запрос. В "шестнадцатеричном" режиме — порциями по 256 байт. Если кольцевой буфер пуст, никакого ответа на эту команду не последует. Даже вне режима MONITOR возможен асинхронный вывод данных в случае переполнения буфера: вызывайте геттер чаще, чтобы этого не допустить.
Длина входного буфера DMA ограничена, поэтому если пользователь настроит USART на слишком высокую скорость, возможна потеря данных (особенно при активной работе второго интерфейса — CAN-USB), т.к. опрос буфера DMA производится без прерываний.
<h3>I2C</h3>
Вы можете выбрать интерфейс на контактах PB6/PB10 (SCL) и PB7/PB11 (SDA). Будьте внимательны: если вместо STM32F042 используется STM32F072, на контактах PB7/PB11 доступен лишь I2C2, но не I2C1. В этом случае настроить I2C на данных контактах будет невозможным (в коде нет поддержки I2C2).
I2C не допускает активации только с одним из этих выводов, поэтому обязательно настроить и SCL, и SDA.
Для данного интерфейса доступно лишь ключевое слово SPEED. Обратите внимание, что, в отличие от USART и SPI, здесь скорость задается <b>индексом</b>: 0=10kHz, 1=100kHz, 2=400kHz, 3=1MHz.
Пример:
<pre>PB6 = I2C SPEED 2
PB7 = I2C
reinit</pre>
Активирует I2C на PB6/PB7 со скоростью 400кГц.
Прием и передача данных производится при помощи команд <tt>iic</tt>, <tt>iicread</tt>, <tt>iicreadreg</tt>. Командой <tt>iicscan</tt> можно произвести поиск активных устройств на шине.
Все данные вводятся и выводятся в двоичном формате без префикса, в том числе адрес и длина данных.
<h3>SPI</h3>
Доступен SPI на выводах PA5/PB3 (SCK), PA6/PB4 (MISO) и PA7/PB5 (MOSI).
Не обязательно настраивать все три вывода, если вам нужна односторонняя передача данных.
Ключевые слова:
<ul>
<li><tt>SPEED</tt> — скорость, Гц, при настройке будет подобран актуальный делитель системной тактовой частоты, наиболее близкий к заданной скорости (не ожидайте получить скорость 30МГц при тактовой 48МГц),</li>
<li><tt>CPOL</tt> — полярность SCK (1 — высокий в неактивном состоянии),</li>
<li><tt>CPHA</tt> — фронт SCK на актуальный уровень сигнала (1 — второй фронт),</li>
<li><tt>LSBFIRST</tt> — передача первым младшего бита.</li>
</ul>
По умолчанию флаги CPOL/CPHA/LSBFIRST считаются равными нулю.
Пример, последовательность команд:
<pre>PA5 = SPI SPEED 2000000 CPOL CPHA
PA6 = SPI
PA7 = SPI
reinit</pre>
настроит SPI на контактах PA5/PA6/PA7, скорость 2МГц, режим 3 (CPOL=CPHA=1), полный дуплекс.
В режиме полного дуплекса сеттер <tt>SPI=...</tt> передает перечисленные после знака "равно" данные (в шестнадцатеричном формате без разделителя) и принимает такое же количество данных. Принятые данные будут выведены после завершения операции в виде <tt>SPI = ...</tt>.
В RX-only режиме (т.е. когда не сконфигурирован вывод MOSI), формат геттера другой: <tt>SPI = datalen</tt>, где указывается количеств байт для считывания (<b>в любом формате</b> числа!). Скажем, <tt>SPI = 0x10</tt> считает 16 байт данных, равно как и <tt>SPI=16</tt>, <tt>SPI = 020</tt> или <tt>SPI=b10000</tt>.
<h3>ШИМ (PWM)</h3>
Доступные для работы в режиме ШИМ выводы можно отобразить запросом <tt>pwmmap</tt> или <tt>pinout=PWM</tt>. При настройке новых режимов командой <tt>reinit</tt> будет проведен анализ конфликтов, поэтому в случае выбора ШИМ на конфликтующих контактах (т.е. если один и тот же канал одного и того же таймера выбран в качестве ШИМ-выхода на разных кнтактах) будет отображено сообщение об ошибке со сбросом конфликтов в значение по умолчанию.
Для конфигурации никаких дополнительных параметров указывать не нужно, лишь <tt>PXx = PWM</tt>. Сеттер позволяет установить требуемое заполнение (0..255), а геттер указывает текущее.
Например,
<pre>PA1 = PWM
reinit
PA1=128</pre>
установит PA1 в ШИМ-режим с заполнением 50%. Частота ШИМ фиксирована: около 23.5кГц и настройке не подлежит.
<hr>
<h2>Асинхронный мониторинг</h2>
При указании флага <tt>MONITOR</tt> в настройке контакта в случае, если выбранная функция это поддерживает, по USB будут выдаваться сообщения с новыми данными на этом контакте вне зависимости от запроса пользователя.
Для входа или выхода с открытым стоком изменение значения будет отображаться, как, например <tt>PB3 = 1</tt> при смене низкого уровня сигнала на высокий.
В случае входа АЦП дополнительным параметром <tt>THRESHOLD</tt> задается пороговое значение: если уровень сигнала на входе отличается от предыдущего больше, чем на это пороговое значение, будет выведено соответствующее сообщение, например <tt>PA1 = 3456</tt>.
Также "мониторинг" доступен для асинхронного отображения входящих по USART сообщений (в текстовом режиме данные выводятся построчно, в "hex" — с буферизацией по блокам, разделенным IDLE). Если режим мониторинга отключен, то пользователь должен регулярными вызовами геттера <tt>USART</tt> считывать пришедшие данные из кольцевого буфера. В случае переполнения буфера он будет автоматически очищаться, а имеющаяся в нем информация — асинхронно передаваться пользователю.
Интерфейсы I2C и SPI работают исключительно с блокирующими операциями, поэтому флаг мониторинга к ним не применим.
<hr>
<h2>Сохранение настроек</h2>
Если вы окончательно настроили все интерфейсы и контакты, и хотите, чтобы при следующем включении устройство был сконфигурировано точно так же, сохраните настройки во флеш-память при помощи команды <tt>saveconf</tt>. Если произойдет ошибка сохранения, попробуйте стереть весь отведенный под хранение настроек блок данных при помощи <tt>eraseflash</tt>, а затем сохранить снова.
При включении устройства МК автоматически загружает последние сохраненные настройки, пользуясь бинарным поиском (что позволяет не тратить слишком много времени на перебор всего объема доступного хранилища, хотя, конечно, для STM32F042 это неактуально).
Настройки во флеш-памяти хранятся последовательно. При достижении последней записи в отведенном регионе (все незанятые блоки флеш-памяти после микрокода устройства), стирается все хранилище, а затем новая запись пишется первой. В такие моменты будьте осторожными, т.к. сбой питания может повредить настройки, и их придется выставлять заново.
<hr>
<h2>Внутренние коды ошибок</h2>
Часть сеттеров, не возвращающих в свою очередь геттеры, выдает следующие коды ошибок:
<dl>
<dt><tt>OK</tt></dt><dd>Выполнение команды прошло успешно.</dd>
<dt><tt>BADCMD</tt></dt><dd>Введена неправильная команда.</dd>
<dt><tt>BADPAR</tt></dt><dd>Неправильный параметр команды (например, номер контакта вне диапазона 0..15).</dd>
<dt><tt>BADVAL</tt></dt><dd>Неправильное значение сеттера (например, вы ошиблись в написании ключевого слова или ввели неверную скорость и т.п.).</dd>
<dt><tt>WRONGLEN</tt></dt><dd>Неправильная длина сообщения (слишком большое или нулевой длины).</dd>
<dt><tt>CANTRUN</tt></dt><dd>Ошибка выполнения команды (например, при отсутствии на шине I2C устройства с идентификатором, в который пытаются отправить данные).</dd>
<dt><tt>BUSY</tt></dt><dd>Попробуйте вызвать команду позже (актуально лишь для USART: если вы попытаетесь послать следующее сообщение, пока еще не передано предыдущее).</dd>
<dt><tt>OVERFLOW</tt></dt><dd>Переполнение длины входного буфера.</dd>
</dl>
Некоторые команды не возвращают вообще ничего (например, геттер <tt>USART</tt>, если в буфере ничего нет). По запросу геттеров выводится имя команды и данные после знака "равно".
<hr>
<hr>
<h2 id="udev">Как различать несколько одинаковых подключенных устройств</h2>
Данное устройство представляет собой "составное устройство USB", состоящее из двух CDC-ACM интерфейсов. Поэтому по умолчанию при его включении в <tt>/dev</tt> вы увидите появление устройств <tt>/dev/ttyACMx</tt> и <tt>/dev/ttyACM(x+1)</tt> (где x зависит от количества уже подключенных устройств). Чтобы различать интерфейсы (особенно если вы включили несколько одинаковых таких преобразователей CAN-USB-GPIO), в USB-дескрипторах сохраняются символьные имена для соответствующих полей <tt>iInterface</tt>. Изменить их вы можете при помощи команды <tt>setiface</tt>.
В терминале при помощи <tt>lsusb -v</tt> можно увидеть эти поля.
Т.к. при подключении USB-устройств udev может "перехватить" их и выполнить какие-то действия, приведенный скрипт поможет создавать человекочитаемые симлинки.
Например, если вы задаете интерфейсам названия «myCAN» и «myGPIO» и сохраняете конфигурацию во флеш-памяти МК, после переподключения устройства вы увидите симлинки: <tt>/dev/ttymyCAN0</tt> и <tt>/dev/ttymyGPIO0</tt>, что позволит быстро их отождествить (особенно это актуально для служб, запускаемых при старте системы).
Просто добавьте этот файл в <tt>/etc/udev/rules.d</tt>:
<code lang="C"><pre style="border-left: 4px solid; border-top: 1px dashed; border-bottom: 1px dashed; max-height: 300px; overflow: auto; padding: 5px" title="Code block">
ACTION=="add", DRIVERS=="usb", ENV{USB_IDS}="%s{idVendor}:%s{idProduct}"
ACTION=="add", ENV{USB_IDS}=="067b:2303", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty"
ACTION=="add", ENV{USB_IDS}=="0483:5740", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty"
</pre></code>

View File

@@ -0,0 +1,158 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
#include "adc.h"
/**
* @brief ADC_array - array for ADC channels with median filtering:
* 0..3 - external channels
* 4 - internal Tsens
* 5 - Vref
*/
#define TSENS_CHAN (NUM_EXT_ADC_CH)
#define VREF_CHAN (NUM_EXT_ADC_CH + 1)
static uint16_t ADC_array[MAX_ADC_CHANNELS*9];
/*
* ADC channels:
* IN0 - V12
* IN1 - V5
* IN16- temperature sensor
* IN17- vref
*/
void adc_setup(){
uint16_t ctr = 0; // 0xfff0 - more than 1.3ms
// Enable clocking
/* (1) Enable the peripheral clock of the ADC */
/* (2) Start HSI14 RC oscillator */
/* (3) Wait HSI14 is ready */
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; /* (1) */
RCC->CR2 |= RCC_CR2_HSI14ON; /* (2) */
while ((RCC->CR2 & RCC_CR2_HSI14RDY) == 0 && ++ctr < 0xfff0){}; /* (3) */
// calibration
/* (1) Ensure that ADEN = 0 */
/* (2) Clear ADEN */
/* (3) Launch the calibration by setting ADCAL */
/* (4) Wait until ADCAL=0 */
if ((ADC1->CR & ADC_CR_ADEN) != 0){ /* (1) */
ADC1->CR &= (uint32_t)(~ADC_CR_ADEN); /* (2) */
}
ADC1->CR |= ADC_CR_ADCAL; /* (3) */
ctr = 0; // ADC calibration time is 5.9us
while ((ADC1->CR & ADC_CR_ADCAL) != 0 && ++ctr < 0xfff0){}; /* (4) */
// enable ADC
ctr = 0;
do{
ADC1->CR |= ADC_CR_ADEN;
}while ((ADC1->ISR & ADC_ISR_ADRDY) == 0 && ++ctr < 0xfff0);
// configure ADC
/* (1) Select HSI14 by writing 00 in CKMODE (reset value) */
/* (2) Select the continuous mode */
/* (3) Select CHSEL0-9 - all ADC inputs, 16,17 - t. sensor and vref */
/* (4) Select a sampling mode of 111 i.e. 239.5 ADC clk to be greater than 17.1us */
/* (5) Wake-up the VREFINT and Temperature sensor (only for VBAT, Temp sensor and VRefInt) */
// ADC1->CFGR2 &= ~ADC_CFGR2_CKMODE; /* (1) */
ADC1->CFGR1 |= ADC_CFGR1_CONT; /* (2)*/
ADC1->CHSELR = (0x3FF << 0) | ADC_CHSELR_CHSEL16 | ADC_CHSELR_CHSEL17; /* (3)*/
ADC1->SMPR |= ADC_SMPR_SMP_0 | ADC_SMPR_SMP_1 | ADC_SMPR_SMP_2; /* (4) */
ADC->CCR |= ADC_CCR_TSEN | ADC_CCR_VREFEN; /* (5) */
// configure DMA for ADC
// DMA for AIN
/* (1) Enable the peripheral clock on DMA */
/* (2) Enable DMA transfer on ADC and circular mode */
/* (3) Configure the peripheral data register address */
/* (4) Configure the memory address */
/* (5) Configure the number of DMA tranfer to be performs on DMA channel 1 */
/* (6) Configure increment, size, interrupts and circular mode */
/* (7) Enable DMA Channel 1 */
RCC->AHBENR |= RCC_AHBENR_DMA1EN; /* (1) */
ADC1->CFGR1 |= ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG; /* (2) */
DMA1_Channel1->CPAR = (uint32_t) (&(ADC1->DR)); /* (3) */
DMA1_Channel1->CMAR = (uint32_t)(ADC_array); /* (4) */
DMA1_Channel1->CNDTR = MAX_ADC_CHANNELS * 9; /* (5) */
DMA1_Channel1->CCR |= DMA_CCR_MINC | DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 | DMA_CCR_CIRC; /* (6) */
DMA1_Channel1->CCR |= DMA_CCR_EN; /* (7) */
ADC1->CR |= ADC_CR_ADSTART; /* start the ADC conversions */
}
/**
* @brief getADCval - calculate median value for `nch` channel
* @param nch - number of channel
* @return
*/
uint16_t getADCval(int nch){
int i, addr = nch;
register uint16_t temp;
#define PIX_SORT(a,b) { if ((a)>(b)) PIX_SWAP((a),(b)); }
#define PIX_SWAP(a,b) { temp=(a);(a)=(b);(b)=temp; }
uint16_t p[9];
for(i = 0; i < 9; ++i, addr += MAX_ADC_CHANNELS) // first we should prepare array for optmed
p[i] = ADC_array[addr];
PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ;
PIX_SORT(p[0], p[1]) ; PIX_SORT(p[3], p[4]) ; PIX_SORT(p[6], p[7]) ;
PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ;
PIX_SORT(p[0], p[3]) ; PIX_SORT(p[5], p[8]) ; PIX_SORT(p[4], p[7]) ;
PIX_SORT(p[3], p[6]) ; PIX_SORT(p[1], p[4]) ; PIX_SORT(p[2], p[5]) ;
PIX_SORT(p[4], p[7]) ; PIX_SORT(p[4], p[2]) ; PIX_SORT(p[6], p[4]) ;
PIX_SORT(p[4], p[2]) ;
return p[4];
#undef PIX_SORT
#undef PIX_SWAP
}
// return MCU temperature (degrees of celsius * 10)
int32_t getMCUtemp(){
int32_t ADval = getADCval(TSENS_CHAN);
int32_t temperature = (int32_t) *TEMP30_CAL_ADDR - ADval;
temperature *= (int32_t)(1100 - 300);
temperature /= (int32_t)(*TEMP30_CAL_ADDR - *TEMP110_CAL_ADDR);
temperature += 300;
return(temperature);
}
// return Vdd * 100 (V)
uint32_t getVdd(){
uint32_t vdd = ((uint32_t) *VREFINT_CAL_ADDR) * (uint32_t)330; // 3.3V
vdd /= getADCval(VREF_CHAN);
return vdd;
}
static inline uint32_t Ufromadu(uint8_t nch, uint32_t vdd){
uint32_t ADU = getADCval(nch);
ADU *= vdd;
ADU >>= 12; // /4096
return ADU;
}
/**
* @brief getUval - calculate U12/U5
* @return array with members:
* 0 - V12 * 100V (U12 = 12Vin/4.93)
* 1 - V5 * 100V (U5 = 5Vin /2)
*/
uint16_t *getUval(){
static uint16_t Uval[4];
uint32_t vdd = getVdd();
uint32_t val = Ufromadu(0, vdd) * 493;
Uval[0] = (uint16_t)(val / 100);
Uval[1] = (uint16_t)(Ufromadu(1, vdd) << 1);
return Uval;
}

View File

@@ -0,0 +1,30 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
// 10 ADC ins + VDD + MCUt
#define NUM_EXT_ADC_CH 10
#define MAX_ADC_CHANNELS (NUM_EXT_ADC_CH+2)
int32_t getMCUtemp();
uint32_t getVdd();
uint16_t getADCval(int nch);
void adc_setup();
uint16_t *getUval();

View File

@@ -0,0 +1,396 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "can.h"
#include "hardware.h"
#include "canproto.h"
#define USBIF ICAN
#include "strfunc.h"
// circular buffer for received messages
static CAN_message messages[CAN_INMESSAGE_SIZE];
static uint8_t first_free_idx = 0; // index of first empty cell
static int8_t first_nonfree_idx = -1; // index of first data cell
uint32_t floodT = FLOOD_PERIOD_MS; // flood period in ms
static uint8_t incrflood = 0; // ==1 for incremental flooding
static uint32_t last_err_code = 0;
static CAN_status can_status = CAN_STOP;
static void can_process_fifo(uint8_t fifo_num);
static CAN_message loc_flood_msg;
static CAN_message *flood_msg = NULL; // == loc_flood_msg - to flood
CAN_status CAN_get_status(){
CAN_status st = can_status;
// give overrun message only once
if(st == CAN_FIFO_OVERRUN){
SEND("FIFO overrun\n");
can_status = CAN_READY;
}
return st;
}
// push next message into buffer; return 1 if buffer overfull
static int CAN_messagebuf_push(CAN_message *msg){
#ifdef EBUG
SEND("push\n");
#endif
if(first_free_idx == first_nonfree_idx){
#ifdef EBUG
SEND("INBUF OVERFULL\n");
#endif
return 1; // no free space
}
if(first_nonfree_idx < 0) first_nonfree_idx = 0; // first message in empty buffer
messages[first_free_idx++] = *msg;
// need to roll?
if(first_free_idx == CAN_INMESSAGE_SIZE) first_free_idx = 0;
return 0;
}
// pop message from buffer
CAN_message *CAN_messagebuf_pop(){
if(first_nonfree_idx < 0) return NULL;
CAN_message *msg = &messages[first_nonfree_idx++];
if(first_nonfree_idx == CAN_INMESSAGE_SIZE) first_nonfree_idx = 0;
if(first_nonfree_idx == first_free_idx){ // buffer is empty - refresh it
first_nonfree_idx = -1;
first_free_idx = 0;
}
return msg;
}
int CAN_reinit(uint16_t speed){
CAN->TSR |= CAN_TSR_ABRQ0 | CAN_TSR_ABRQ1 | CAN_TSR_ABRQ2;
RCC->APB1RSTR |= RCC_APB1RSTR_CANRST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_CANRST;
return CAN_setup(speed);
}
/*
Can filtering: FSCx=0 (CAN->FS1R) -> 16-bit identifiers
MASK: FBMx=0 (CAN->FM1R), two filters (n in FR1 and n+1 in FR2)
ID: CAN->sFilterRegister[x].FRn[0..15]
MASK: CAN->sFilterRegister[x].FRn[16..31]
FR bits: STID[10:0] RTR IDE EXID[17:15]
LIST: FBMx=1, four filters (n&n+1 in FR1, n+2&n+3 in FR2)
IDn: CAN->sFilterRegister[x].FRn[0..15]
IDn+1: CAN->sFilterRegister[x].FRn[16..31]
*/
/*
Can timing: main freq - APB (PLL=48MHz)
segment = 1sync + TBS1 + TBS2, sample point is between TBS1 and TBS2,
so if TBS1=4 and TBS2=3, sum=8, bit sampling freq is 48/8 = 6MHz
-> to get 100kbps we need prescaler=60
250kbps - 24
500kbps - 12
1MBps - 6
*/
static uint16_t oldspeed = 100; // speed of last init
uint16_t CAN_getspeed(){
return oldspeed;
}
// speed - in kbps. @return - current speed
int CAN_setup(uint16_t speed){
LED_off(LED1);
flood_msg = NULL; // clear for not flood in terminal
incrflood = 0;
if(speed == 0) speed = oldspeed;
else if(speed < CAN_MIN_SPEED) speed = CAN_MIN_SPEED;
else if(speed > CAN_MAX_SPEED) speed = CAN_MAX_SPEED;
uint32_t regval = 6000/speed - 1;
oldspeed = 6000 / (regval + 1); // new speed due to register value
uint32_t tmout = 16000000;
// Configure GPIO: PB8 - CAN_Rx, PB9 - CAN_Tx
/* (1) Select AF mode (10) on PB8 and PB9 */
/* (2) AF4 for CAN signals */
GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER8 | GPIO_MODER_MODER9))
| (GPIO_MODER_MODER8_AF | GPIO_MODER_MODER9_AF); /* (1) */
GPIOB->AFR[1] = (GPIOB->AFR[1] &~ (GPIO_AFRH_AFRH0 | GPIO_AFRH_AFRH1))\
| (4 << (0 * 4)) | (4 << (1 * 4)); /* (2) */
/* Enable the peripheral clock CAN */
RCC->APB1ENR |= RCC_APB1ENR_CANEN;
/* Configure CAN */
/* (1) Enter CAN init mode to write the configuration */
/* (2) Wait the init mode entering */
/* (3) Exit sleep mode */
/* (4) Normal mode, set timing to 100kb/s: TBS1 = 4, TBS2 = 3, prescaler = 60 */
/* (5) Leave init mode */
/* (6) Wait the init mode leaving */
/* (7) Enter filter init mode, (16-bit + mask, bank 0 for FIFO 0) */
/* (8) Acivate filter 0 for two IDs */
/* (9) Identifier list mode */
/* (10) Set the Id list */
/* (12) Leave filter init */
/* (13) Set error interrupts enable */
CAN->MCR |= CAN_MCR_INRQ; /* (1) */
while((CAN->MSR & CAN_MSR_INAK)!=CAN_MSR_INAK) /* (2) */
if(--tmout == 0) break;
CAN->MCR &=~ CAN_MCR_SLEEP; /* (3) */
CAN->MCR |= CAN_MCR_ABOM; /* allow automatically bus-off */
CAN->BTR = 2 << 20 | 3 << 16 | regval; /* (4) */
CAN->MCR &=~ CAN_MCR_INRQ; /* (5) */
tmout = 16000000;
while((CAN->MSR & CAN_MSR_INAK)==CAN_MSR_INAK) if(--tmout == 0) break; /* (6) */
// accept ALL
CAN->FMR = CAN_FMR_FINIT; /* (7) */
CAN->FA1R = CAN_FA1R_FACT0 | CAN_FA1R_FACT1; /* (8) */
// set to 1 all needed bits of CAN->FFA1R to switch given filters to FIFO1
CAN->sFilterRegister[0].FR1 = (1<<21)|(1<<5); // all odd IDs
CAN->FFA1R = 2; // filter 1 for FIFO1, filter 0 - for FIFO0
CAN->sFilterRegister[1].FR1 = (1<<21); // all even IDs
CAN->FMR &= ~CAN_FMR_FINIT; /* (12) */
CAN->IER |= CAN_IER_ERRIE | CAN_IER_FOVIE0 | CAN_IER_FOVIE1; /* (13) */
/* Configure IT */
/* (14) Set priority for CAN_IRQn */
/* (15) Enable CAN_IRQn */
NVIC_SetPriority(CEC_CAN_IRQn, 0); /* (14) */
NVIC_EnableIRQ(CEC_CAN_IRQn); /* (15) */
can_status = CAN_READY;
return oldspeed;
}
void printCANerr(){
if(!last_err_code) last_err_code = CAN->ESR;
if(!last_err_code){
SEND("No errors\n");
return;
}
SEND("Receive error counter: ");
printu((last_err_code & CAN_ESR_REC)>>24);
SEND("\nTransmit error counter: ");
printu((last_err_code & CAN_ESR_TEC)>>16);
SEND("\nLast error code: ");
int lec = (last_err_code & CAN_ESR_LEC) >> 4;
const char *errmsg = "No";
switch(lec){
case 1: errmsg = "Stuff"; break;
case 2: errmsg = "Form"; break;
case 3: errmsg = "Ack"; break;
case 4: errmsg = "Bit recessive"; break;
case 5: errmsg = "Bit dominant"; break;
case 6: errmsg = "CRC"; break;
case 7: errmsg = "(set by software)"; break;
}
SEND(errmsg); SEND(" error\n");
if(last_err_code & CAN_ESR_BOFF) SEND("Bus off ");
if(last_err_code & CAN_ESR_EPVF) SEND("Passive error limit ");
if(last_err_code & CAN_ESR_EWGF) SEND("Error counter limit");
last_err_code = 0;
NL();
}
void can_proc(){
#ifdef EBUG
if(last_err_code){
SEND("Error, ESR=");
printu(last_err_code);
NL();
last_err_code = 0;
}
#endif
// check for messages in FIFO0 & FIFO1
if(CAN->RF0R & CAN_RF0R_FMP0){
can_process_fifo(0);
}
if(CAN->RF1R & CAN_RF1R_FMP1){
can_process_fifo(1);
}
IWDG->KR = IWDG_REFRESH;
if(CAN->ESR & (CAN_ESR_BOFF | CAN_ESR_EPVF | CAN_ESR_EWGF)){ // much errors - restart CAN BUS
SEND("\nToo much errors, restarting CAN!\n");
printCANerr();
// request abort for all mailboxes
CAN->TSR |= CAN_TSR_ABRQ0 | CAN_TSR_ABRQ1 | CAN_TSR_ABRQ2;
// reset CAN bus
RCC->APB1RSTR |= RCC_APB1RSTR_CANRST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_CANRST;
CAN_setup(0);
}
static uint32_t lastFloodTime = 0;
static uint32_t incrmessagectr = 0;
if(flood_msg && (Tms - lastFloodTime) >= (floodT)){ // send message every floodT ms
lastFloodTime = Tms;
can_send(flood_msg->data, flood_msg->length, flood_msg->ID);
}else if(incrflood && (Tms - lastFloodTime) >= floodT){ // incremental flood message
lastFloodTime = Tms;
if(CAN_OK == can_send((uint8_t*)&incrmessagectr, 4, loc_flood_msg.ID)) ++incrmessagectr;
}
}
CAN_status can_send(uint8_t *msg, uint8_t len, uint16_t target_id){
uint8_t mailbox = 0;
// check first free mailbox
if(CAN->TSR & (CAN_TSR_TME)){
mailbox = (CAN->TSR & CAN_TSR_CODE) >> 24;
}else{ // no free mailboxes
#ifdef EBUG
SEND("No free mailboxes\n");
#endif
return CAN_BUSY;
}
#ifdef EBUG
SEND("Send data. Len="); printu(len);
SEND(", tagid="); printuhex(target_id);
SEND(", data=");
for(int i = 0; i < len; ++i){
SEND(" "); printuhex(msg[i]);
}
NL();
#endif
CAN_TxMailBox_TypeDef *box = &CAN->sTxMailBox[mailbox];
uint32_t lb = 0, hb = 0;
switch(len){
case 8:
hb |= (uint32_t)msg[7] << 24;
// fallthrough
case 7:
hb |= (uint32_t)msg[6] << 16;
// fallthrough
case 6:
hb |= (uint32_t)msg[5] << 8;
// fallthrough
case 5:
hb |= (uint32_t)msg[4];
// fallthrough
case 4:
lb |= (uint32_t)msg[3] << 24;
// fallthrough
case 3:
lb |= (uint32_t)msg[2] << 16;
// fallthrough
case 2:
lb |= (uint32_t)msg[1] << 8;
// fallthrough
default:
lb |= (uint32_t)msg[0];
}
box->TDLR = lb;
box->TDHR = hb;
box->TDTR = len;
box->TIR = (target_id & 0x7FF) << 21 | CAN_TI0R_TXRQ;
return CAN_OK;
}
void set_flood(CAN_message *msg, int incr){
if(incr){ incrflood = 1; return; }
incrflood = 0;
if(!msg) flood_msg = NULL;
else{
#ifdef EBUG
SEND("flood msg: #");
printuhex(msg->ID);
for(uint8_t ctr = 0; ctr < msg->length; ++ctr){
PUTCHAR(' ');
printuhex(msg->data[ctr]);
}
NL();
#endif
// I meet strange problems with `loc_flood_msg = *msg` and system memcpy, so..
loc_flood_msg.ID = msg->ID;
loc_flood_msg.length = msg->length;
*((uint32_t*)loc_flood_msg.data) = *((uint32_t*)msg->data);
if(loc_flood_msg.length > 4)
*((uint32_t*)&loc_flood_msg.data[4]) = *((uint32_t*)&msg->data[4]);
flood_msg = &loc_flood_msg;
}
}
static void can_process_fifo(uint8_t fifo_num){
if(fifo_num > 1) return;
LED_on(LED1); // Turn on LED1 - message received
CAN_FIFOMailBox_TypeDef *box = &CAN->sFIFOMailBox[fifo_num];
volatile uint32_t *RFxR = (fifo_num) ? &CAN->RF1R : &CAN->RF0R;
#ifdef EBUG
printu(*RFxR & CAN_RF0R_FMP0); SEND(" messages in FIFO\n");
#endif
// read all
while(*RFxR & CAN_RF0R_FMP0){ // amount of messages pending
// CAN_RDTxR: (16-31) - timestamp, (8-15) - filter match index, (0-3) - data length
/* TODO: check filter match index if more than one ID can receive */
CAN_message msg;
uint8_t *dat = msg.data;
uint8_t len = box->RDTR & 0x0f;
msg.length = len;
msg.ID = box->RIR >> 21;
//msg.filterNo = (box->RDTR >> 8) & 0xff;
//msg.fifoNum = fifo_num;
if(len){ // message can be without data
uint32_t hb = box->RDHR, lb = box->RDLR;
switch(len){
case 8:
dat[7] = hb>>24;
// fallthrough
case 7:
dat[6] = (hb>>16) & 0xff;
// fallthrough
case 6:
dat[5] = (hb>>8) & 0xff;
// fallthrough
case 5:
dat[4] = hb & 0xff;
// fallthrough
case 4:
dat[3] = lb>>24;
// fallthrough
case 3:
dat[2] = (lb>>16) & 0xff;
// fallthrough
case 2:
dat[1] = (lb>>8) & 0xff;
// fallthrough
case 1:
dat[0] = lb & 0xff;
}
}
if(CAN_messagebuf_push(&msg)) return; // error: buffer is full, try later
*RFxR = CAN_RF0R_RFOM0; // release fifo for access to next message
}
//if(*RFxR & CAN_RF0R_FULL0) *RFxR &= ~CAN_RF0R_FULL0;
*RFxR = 0; // clear FOVR & FULL
}
void cec_can_isr(){
if(CAN->RF0R & CAN_RF0R_FOVR0){ // FIFO overrun
CAN->RF0R = CAN_RF0R_FOVR0;
can_status = CAN_FIFO_OVERRUN;
}
if(CAN->RF1R & CAN_RF1R_FOVR1){
CAN->RF1R = CAN_RF1R_FOVR1;
can_status = CAN_FIFO_OVERRUN;
}
if(CAN->MSR & CAN_MSR_ERRI){ // Error
CAN->MSR &= ~CAN_MSR_ERRI;
// request abort for problem mailbox
if(CAN->TSR & CAN_TSR_TERR0) CAN->TSR |= CAN_TSR_ABRQ0;
if(CAN->TSR & CAN_TSR_TERR1) CAN->TSR |= CAN_TSR_ABRQ1;
if(CAN->TSR & CAN_TSR_TERR2) CAN->TSR |= CAN_TSR_ABRQ2;
#ifdef EBUG
last_err_code = CAN->ESR;
#endif
}
}

View File

@@ -0,0 +1,64 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
// min/max CAN speed (kBaud)
#define CAN_MIN_SPEED 10
#define CAN_MAX_SPEED 1000
// amount of filter banks in STM32F0
#define STM32F0FBANKNO 28
// flood period in milliseconds
#define FLOOD_PERIOD_MS 5
// incoming message buffer size
#define CAN_INMESSAGE_SIZE (8)
extern uint32_t floodT;
// CAN message
typedef struct{
uint8_t data[8]; // up to 8 bytes of data
uint8_t length; // data length
uint16_t ID; // ID of receiver
} CAN_message;
typedef enum{
CAN_STOP,
CAN_READY,
CAN_BUSY,
CAN_OK,
CAN_FIFO_OVERRUN
} CAN_status;
CAN_status CAN_get_status();
int CAN_reinit(uint16_t speed);
int CAN_setup(uint16_t speed);
uint16_t CAN_getspeed();
CAN_status can_send(uint8_t *msg, uint8_t len, uint16_t target_id);
void can_proc();
void printCANerr();
CAN_message *CAN_messagebuf_pop();
void set_flood(CAN_message *msg, int incr);

View File

@@ -0,0 +1,485 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
#include "can.h"
#include "hardware.h"
#include "canproto.h"
#define USBIF ICAN
#include "strfunc.h"
#define IGN_SIZE 10
extern volatile uint8_t canerror;
static uint8_t ShowMsgs = 1;
static uint16_t Ignore_IDs[IGN_SIZE];
static uint8_t IgnSz = 0;
// parse `txt` to CAN_message
static CAN_message *parseCANmsg(char *txt){
static CAN_message canmsg;
uint32_t N;
char *n;
int ctr = -1;
canmsg.ID = 0xffff;
do{
n = getnum(txt, &N);
if(txt == n) break;
txt = n;
if(ctr == -1){
if(N > 0x7ff){
SEND("ID should be 11-bit number!\n");
return NULL;
}
canmsg.ID = (uint16_t)(N&0x7ff);
ctr = 0;
continue;
}
if(ctr > 7){
SEND("ONLY 8 data bytes allowed!\n");
return NULL;
}
if(N > 0xff){
SEND("Every data portion is a byte!\n");
return NULL;
}
canmsg.data[ctr++] = (uint8_t)(N&0xff);
}while(1);
if(canmsg.ID == 0xffff){
SEND("NO ID given, send nothing!\n");
return NULL;
}
SEND("Message parsed OK\n");
canmsg.length = (uint8_t) ctr;
return &canmsg;
}
// USB_sendstr command, format: ID (hex/bin/dec) data bytes (up to 8 bytes, space-delimeted)
TRUE_INLINE void USB_sendstrCANcommand(char *txt){
if(CAN->MSR & CAN_MSR_INAK){
SEND("CAN bus is off, try to restart it\n");
return;
}
CAN_message *msg = parseCANmsg(txt);
if(!msg) return;
uint32_t N = 5;
while(CAN_BUSY == can_send(msg->data, msg->length, msg->ID)){
if(--N == 0) break;
}
}
TRUE_INLINE void CANini(char *txt){
uint32_t N;
char *n = getnum(txt, &N);
if(txt == n){
printu(CAN_getspeed());
SEND("kbps\n");
return;
}
if(N < CAN_MIN_SPEED){
SEND("Speed is too low\n");
return;
}else if(N > CAN_MAX_SPEED){
SEND("Speed is too high\n");
return;
}
CAN_reinit((uint16_t)N);
SEND("Reinit CAN bus with speed ");
printu(N); SEND("kbps\n");
}
TRUE_INLINE void addIGN(char *txt){
if(IgnSz == IGN_SIZE){
SEND("Ignore buffer is full\n");
return;
}
txt = omit_spaces(txt);
uint32_t N;
char *n = getnum(txt, &N);
if(txt == n){
SEND("No ID given\n");
return;
}
if(N > 0x7ff){
SEND("ID should be 11-bit number!\n");
return;
}
Ignore_IDs[IgnSz++] = (uint16_t)(N & 0x7ff);
SEND("Added ID "); printu(N);
SEND("\nIgn buffer size: "); printu(IgnSz);
NL();
}
TRUE_INLINE void print_ign_buf(){
if(IgnSz == 0){
SEND("Ignore buffer is empty\n");
return;
}
SEND("Ignored IDs:\n");
for(int i = 0; i < IgnSz; ++i){
printu(i);
SEND(": ");
printuhex(Ignore_IDs[i]);
NL();
}
}
// print ID/mask of CAN->sFilterRegister[x] half
static void printID(uint16_t FRn){
if(FRn & 0x1f) return; // trash
printuhex(FRn >> 5);
}
/*
Can filtering: FSCx=0 (CAN->FS1R) -> 16-bit identifiers
CAN->FMR = (sb)<<8 | FINIT - init filter in starting bank sb
CAN->FFA1R FFAx = 1 -> FIFO1, 0 -> FIFO0
CAN->FA1R FACTx=1 - filter active
MASK: FBMx=0 (CAN->FM1R), two filters (n in FR1 and n+1 in FR2)
ID: CAN->sFilterRegister[x].FRn[0..15]
MASK: CAN->sFilterRegister[x].FRn[16..31]
FR bits: STID[10:0] RTR IDE EXID[17:15]
LIST: FBMx=1, four filters (n&n+1 in FR1, n+2&n+3 in FR2)
IDn: CAN->sFilterRegister[x].FRn[0..15]
IDn+1: CAN->sFilterRegister[x].FRn[16..31]
*/
TRUE_INLINE void list_filters(){
uint32_t fa = CAN->FA1R, ctr = 0, mask = 1;
while(fa){
if(fa & 1){
SEND("Filter "); printu(ctr); SEND(", FIFO");
if(CAN->FFA1R & mask) SEND("1");
else SEND("0");
SEND(" in ");
if(CAN->FM1R & mask){ // up to 4 filters in LIST mode
SEND("LIST mode, IDs: ");
printID(CAN->sFilterRegister[ctr].FR1 & 0xffff);
SEND(" ");
printID(CAN->sFilterRegister[ctr].FR1 >> 16);
SEND(" ");
printID(CAN->sFilterRegister[ctr].FR2 & 0xffff);
SEND(" ");
printID(CAN->sFilterRegister[ctr].FR2 >> 16);
}else{ // up to 2 filters in MASK mode
SEND("MASK mode: ");
if(!(CAN->sFilterRegister[ctr].FR1&0x1f)){
SEND("ID="); printID(CAN->sFilterRegister[ctr].FR1 & 0xffff);
SEND(", MASK="); printID(CAN->sFilterRegister[ctr].FR1 >> 16);
SEND(" ");
}
if(!(CAN->sFilterRegister[ctr].FR2&0x1f)){
SEND("ID="); printID(CAN->sFilterRegister[ctr].FR2 & 0xffff);
SEND(", MASK="); printID(CAN->sFilterRegister[ctr].FR2 >> 16);
}
}
NL();
}
fa >>= 1;
++ctr;
mask <<= 1;
}
}
TRUE_INLINE void setfloodt(char *s){
uint32_t N;
s = omit_spaces(s);
char *n = getnum(s, &N);
if(s != n){
floodT = N;
}
SEND("t="); printu(floodT); NL();
}
/**
* @brief add_filter - add/modify filter
* @param str - string in format "bank# FIFO# mode num0 .. num3"
* where bank# - 0..27
* if there's nothing after bank# - delete filter
* FIFO# - 0,1
* mode - 'I' for ID, 'M' for mask
* num0..num3 - IDs in ID mode, ID/MASK for mask mode
*/
static void add_filter(char *str){
uint32_t N;
str = omit_spaces(str);
char *n = getnum(str, &N);
if(n == str){
SEND("No bank# given\n");
return;
}
if(N > STM32F0FBANKNO-1){
SEND("bank# > 27\n");
return;
}
uint8_t bankno = (uint8_t)N;
str = omit_spaces(n);
if(!*str){ // deactivate filter
SEND("Deactivate filters in bank ");
printu(bankno); NL();
CAN->FMR = CAN_FMR_FINIT;
CAN->FA1R &= ~(1<<bankno);
CAN->FMR &=~ CAN_FMR_FINIT;
return;
}
uint8_t fifono = 0;
if(*str == '1') fifono = 1;
else if(*str != '0'){
SEND("FIFO# is 0 or 1\n");
return;
}
str = omit_spaces(str + 1);
char c = *str;
uint8_t mode = 0; // ID
if(c == 'M' || c == 'm') mode = 1;
else if(c != 'I' && c != 'i'){
SEND("mode is 'M/m' for MASK and 'I/i' for IDLIST\n");
return;
}
str = omit_spaces(str + 1);
uint32_t filters[4];
uint32_t nfilt;
for(nfilt = 0; nfilt < 4; ++nfilt){
n = getnum(str, &N);
if(n == str) break;
filters[nfilt] = N;
str = omit_spaces(n);
}
if(nfilt == 0){
SEND("You should add at least one filter!\n");
return;
}
if(mode && (nfilt&1)){
SEND("In MASK mode you should point pairs of ID/MASK\n");
return;
}
CAN->FMR = CAN_FMR_FINIT;
uint32_t mask = 1<<bankno;
CAN->FA1R |= mask; // activate given filter
if(fifono) CAN->FFA1R |= mask; // set FIFO number
else CAN->FFA1R &= ~mask;
if(mode) CAN->FM1R &= ~mask; // MASK
else CAN->FM1R |= mask; // LIST
uint32_t F1 = (0x8f<<16);
uint32_t F2 = (0x8f<<16);
// reset filter registers to wrong value
CAN->sFilterRegister[bankno].FR1 = (0x8f<<16) | 0x8f;
CAN->sFilterRegister[bankno].FR2 = (0x8f<<16) | 0x8f;
switch(nfilt){
case 4:
F2 = filters[3] << 21;
// fallthrough
case 3:
CAN->sFilterRegister[bankno].FR2 = (F2 & 0xffff0000) | (filters[2] << 5);
// fallthrough
case 2:
F1 = filters[1] << 21;
// fallthrough
case 1:
CAN->sFilterRegister[bankno].FR1 = (F1 & 0xffff0000) | (filters[0] << 5);
}
CAN->FMR &=~ CAN_FMR_FINIT;
SEND("Added filter with ");
printu(nfilt); SEND(" parameters\n");
}
static const char *helpmsg =
REPOURL
"'a' - add ID to ignore list (max 10 IDs)\n"
"'b' - reinit CAN with given baudrate or get current\n"
"'c' - get CAN status\n"
"'d' - delete ignore list\n"
#ifdef STM32F072xB
"'D' - activate DFU mode\n"
#endif
"'e' - get CAN errcodes\n"
"'f' - add/delete filter, format: bank# FIFO# mode(M/I) num0 [num1 [num2 [num3]]]\n"
"'F' - send/clear flood message: F ID byte0 ... byteN\n"
"'i' - send incremental flood message (ID == ID for `F`)\n"
"'I' - reinit CAN\n"
"'l' - list all active filters\n"
"'o' - turn LEDs OFF\n"
"'O' - turn LEDs ON\n"
"'p' - print ignore buffer\n"
"'P' - pause/resume in packets displaying\n"
"'R' - software reset\n"
"'s/S' - send data over CAN: s ID byte0 .. byteN\n"
"'t' - change flood period (>=0ms)\n"
"'T' - get time from start (ms)\n"
;
TRUE_INLINE void getcanstat(){
SEND("CAN_MSR=");
printuhex(CAN->MSR);
SEND("\nCAN_TSR=");
printuhex(CAN->TSR);
SEND("\nCAN_RF0R=");
printuhex(CAN->RF0R);
SEND("\nCAN_RF1R=");
printuhex(CAN->RF1R);
NL();
}
/**
* @brief CommandParser - command parsing
* @param txt - buffer with commands & data
* @param isUSB - == 1 if data got from USB
*/
static void CommandParser(char *txt){
char _1st = txt[0];
++txt;
/*
* parse long commands here
*/
switch(_1st){
case 'a':
addIGN(txt);
return;
break;
case 'b':
CANini(txt);
return;
break;
case 'f':
add_filter(txt);
return;
break;
case 'F':
set_flood(parseCANmsg(txt), 0);
return;
break;
case 's':
case 'S':
USB_sendstrCANcommand(txt);
return;
break;
case 't':
setfloodt(txt);
return;
break;
}
if(*txt) _1st = '?'; // help for wrong message length
switch(_1st){
case 'c':
getcanstat();
break;
case 'd':
IgnSz = 0;
break;
case 'e':
printCANerr();
break;
#ifdef STM32F072xB
case 'D':
SEND("Go into DFU mode\n");
USB_sendall(ICAN);
uint32_t t = Tms;
while(Tms - t < 2000){IWDG->KR = IWDG_REFRESH;}
Jump2Boot();
break;
#endif
case 'i':
set_flood(NULL, 1);
SEND("Incremental flooding is ON ('F' to off)\n");
break;
case 'I':
SEND("CANspeed=");
printu(CAN_reinit(0));
NL();
break;
case 'l':
list_filters();
break;
case 'o':
ledsON = 0;
LED_off(LED0);
LED_off(LED1);
SEND("LEDS=0\n");
break;
case 'O':
ledsON = 1;
SEND("LEDS=1\n");
break;
case 'p':
print_ign_buf();
break;
case 'P':
ShowMsgs = !ShowMsgs;
if(ShowMsgs) SEND("Resume\n");
else SEND("Pause\n");
break;
case 'R':
SEND("Soft reset\n");
USB_sendall(ICAN); // wait until everything will gone
NVIC_SystemReset();
break;
case 'T':
SEND("Time (ms): ");
printu(Tms);
NL();
break;
default: // help
SEND(helpmsg);
break;
}
}
// check Ignore_IDs & return 1 if ID isn't in list
static uint8_t isgood(uint16_t ID){
for(int i = 0; i < IgnSz; ++i)
if(Ignore_IDs[i] == ID) return 0;
return 1;
}
void CANUSB_process(){
char inbuff[MAXSTRLEN];
CAN_message *can_mesg;
uint32_t lastT = 0;
can_proc();
if(CAN_get_status() == CAN_FIFO_OVERRUN){
SEND("CAN bus fifo overrun occured!\n");
}
while((can_mesg = CAN_messagebuf_pop())){
IWDG->KR = IWDG_REFRESH;
if(can_mesg && isgood(can_mesg->ID)){
LED_on(LED0);
lastT = Tms;
if(!lastT) lastT = 1;
if(ShowMsgs){ // new data in buff
IWDG->KR = IWDG_REFRESH;
uint8_t len = can_mesg->length;
printu(Tms);
SEND(" #");
printuhex(can_mesg->ID);
for(uint8_t ctr = 0; ctr < len; ++ctr){
PUTCHAR(' ');
printuhex(can_mesg->data[ctr]);
}
NL();
}
}
}
if(lastT && (Tms - lastT > 199)){
LED_off(LED0);
lastT = 0;
}
int l = RECV(inbuff, MAXSTRLEN);
if(l < 0) SEND("ERROR: USB buffer overflow or string was too long\n");
else if(l) CommandParser(inbuff);
}

View File

@@ -0,0 +1,22 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
void CANUSB_process();

View File

@@ -0,0 +1,204 @@
/*
* flash.c
*
* Copyright 2017 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include <stm32f0.h>
#include <string.h> // memcpy
#include "can.h"
#include "flash.h"
//#include "strfunc.h"
extern const uint32_t __varsstart, _BLOCKSIZE;
static const uint32_t blocksize = (uint32_t)&_BLOCKSIZE;
// max amount of Config records stored (will be recalculate in flashstorage_init()
static uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here
static int erase_flash(const void*, const void*);
static int write2flash(const void*, const void*, uint32_t);
// don't write `static` here, or get error:
// 'memcpy' forming offset 8 is out of the bounds [0, 4] of object '__varsstart' with type 'uint32_t'
const user_conf *Flash_Data = (const user_conf *)(&__varsstart);
// default pin config
// simple FL IN
#define PINEN {.enable = 1}
// USART1 @9600 with monitoring
#define U1 {.enable = 1, .mode = MODE_AF, .speed = SPEED_HIGH, .afno = 1, .af = FUNC_USART, .monitor = 1}
#define S1 {.enable = 1, .mode = MODE_AF, .speed = SPEED_HIGH, .afno = 0, .af = FUNC_SPI}
// GPIOA, enabled: PA0-PA3, PA5-PA7, PA9, PA10
#define PACONF \
[0] = PINEN, [1] = PINEN, [2] = PINEN, [3] = PINEN, [5] = PINEN, \
[6] = PINEN, [7] = PINEN, [9] = U1, [10] = U1
// GPIOB, enabled: PB0-PB7, PB10, PB11
#define PBCONF \
[0] = PINEN, [1] = PINEN, [2] = PINEN, [3] = S1, [4] = S1, \
[5] = S1, [6] = PINEN, [7] = PINEN, [10] = PINEN, [11] = PINEN
user_conf the_conf = {
.userconf_sz = sizeof(user_conf),
.CANspeed = 100,
.iInterface = {
[ICAN] = u"USB-CAN",
[IGPIO] = u"USB-GPIO",
},
.iIlengths = {14, 16},
.pinconfig = {[0] = {PACONF}, [1] = {PBCONF}},
.usartconfig = {.speed = 9600, .idx = 0, .RXen = 1, .TXen = 1, .textproto = 1, .monitor = 1},
.spiconfig = {.speed = 1000000, .cpol = 1, .cpha = 1},
};
int currentconfidx = -1; // index of current configuration
/**
* @brief binarySearch - binary search in flash for last non-empty cell
* any struct searched should have its sizeof() @ the first field!!!
* @param l - left index
* @param r - right index (should be @1 less than last index!)
* @param start - starting address
* @param stor_size - size of structure to search
* @return index of non-empty cell or -1
*/
static int binarySearch(int r, const uint8_t *start, int stor_size){
int l = 0;
while(r >= l){
int mid = l + (r - l) / 2;
const uint8_t *s = start + mid * stor_size;
if(*((const uint16_t*)s) == stor_size){
if(*((const uint16_t*)(s + stor_size)) == 0xffff){ // next is free
return mid;
}else{ // element is to the right
l = mid + 1;
}
}else{ // element is to the left
r = mid - 1;
}
}
return -1; // not found
}
/**
* @brief flashstorage_init - initialization of user conf storage
* run in once @ start
*/
void flashstorage_init(){
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
uint32_t flsz = FLASH_SIZE * blocksize; // size in bytes
flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
maxCnum = flsz / sizeof(user_conf);
}
// -1 if there's no data at all & flash is clear; maxnum-1 if flash is full
currentconfidx = binarySearch((int)maxCnum-2, (const uint8_t*)Flash_Data, sizeof(user_conf));
if(currentconfidx > -1){
memcpy(&the_conf, &Flash_Data[currentconfidx], sizeof(user_conf));
}
}
// store new configuration
// @return 0 if all OK
int store_userconf(){
// maxnum - 3 means that there always should be at least one empty record after last data
// for binarySearch() checking that there's nothing more after it!
if(currentconfidx > (int)maxCnum - 3){ // there's no more place
currentconfidx = 0;
if(erase_flash(Flash_Data, NULL)) return 1;
}else ++currentconfidx; // take next data position (0 - within first run after firmware flashing)
return write2flash((const void*)&Flash_Data[currentconfidx], &the_conf, sizeof(the_conf));
}
static int write2flash(const void *start, const void *wrdata, uint32_t stor_size){
int ret = 0;
if (FLASH->CR & FLASH_CR_LOCK){ // unloch flash
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
while (FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; // clear all flags
FLASH->CR |= FLASH_CR_PG;
const uint16_t *data = (const uint16_t*) wrdata;
volatile uint16_t *address = (volatile uint16_t*) start;
uint32_t i, count = (stor_size + 1) / 2;
for (i = 0; i < count; ++i){
IWDG->KR = IWDG_REFRESH;
*(volatile uint16_t*)(address + i) = data[i];
while (FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
if(FLASH->SR & FLASH_SR_PGERR){
//SEND("Prog err\n");
ret = 1; // program error - meet not 0xffff
break;
}
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR;
}
FLASH->CR &= ~(FLASH_CR_PG);
return ret;
}
/**
* @brief erase_flash - erase N pages of flash memory
* @param start - first address
* @param end - last address (or NULL if need to erase all flash remaining)
* @return 0 if succeed
*/
static int erase_flash(const void *start, const void *end){
int ret = 0;
uint32_t nblocks = 1, flsz = 0;
if(!end){ // erase all remaining
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
flsz = FLASH_SIZE * blocksize; // size in bytes
flsz -= (uint32_t)start - FLASH_BASE;
}
}else{ // erase a part
flsz = (uint32_t)end - (uint32_t)start;
}
nblocks = flsz / blocksize;
if(nblocks == 0 || nblocks >= FLASH_SIZE) return 1;
if((FLASH->CR & FLASH_CR_LOCK) != 0){
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR;
FLASH->CR |= FLASH_CR_PER;
for(uint32_t i = 0; i < nblocks; ++i){
//SEND("Erase block #"); printu(i); newline();
FLASH->AR = (uint32_t)Flash_Data + i * blocksize;
FLASH->CR |= FLASH_CR_STRT;
while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP;
if(FLASH->SR & FLASH_SR_WRPRTERR){
ret = 1;
FLASH->SR = FLASH_SR_WRPRTERR;
break;
}
}
FLASH->CR &= ~FLASH_CR_PER;
return ret;
}
int erase_storage(){
return erase_flash(Flash_Data, NULL);
}
uint32_t storage_capacity(){
return maxCnum;
}

View File

@@ -0,0 +1,75 @@
/*
* flash.h
*
* Copyright 2017 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#pragma once
#include <stdint.h>
#include "gpio.h"
#include "usart.h"
#include "usb_descr.h"
// register with flash size (in blocks)
#ifndef FLASH_SIZE_REG
#define FLASH_SIZE_REG ((uint32_t)0x1FFFF7CC)
#endif
#define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG)
// maximal size (in letters, ASCII, no ending \0) of iInterface for settings
#define MAX_IINTERFACE_SZ (16)
typedef struct {
uint32_t speed;
uint8_t cpol : 1;
uint8_t cpha : 1;
uint8_t lsbfirst : 1;
// these flags - only for data in/out formatting
uint8_t rxonly : 1; // use format SPI=len instead of SPI=data
uint8_t txonly : 1; // don't receive data
} spiconfig_t;
/*
* struct to save user configurations
*/
typedef struct __attribute__((packed, aligned(4))){
uint16_t userconf_sz; // "magick number"
uint16_t CANspeed; // default CAN speed (in kBaud!!!)
spiconfig_t spiconfig;
uint8_t iIlengths[InterfacesAmount]; // length in BYTES (symbols amount x2)!
uint16_t iInterface[InterfacesAmount][MAX_IINTERFACE_SZ]; // we store Interface name here in UTF!
// gpio settings
pinconfig_t pinconfig[2][16]; // GPIOA, GPIOB
usartconf_t usartconfig;
uint8_t I2Cspeed; // I2C speed index
} user_conf;
extern user_conf the_conf; // global user config (read from FLASH to RAM)
extern int currentconfidx;
// data from ld-file: start address of storage
void flashstorage_init();
int store_userconf();
void dump_userconf();
int erase_storage();
uint32_t storage_capacity();

View File

@@ -0,0 +1,661 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
#include <string.h>
#include "adc.h"
#include "flash.h"
#include "gpio.h"
#include "i2c.h"
#include "pwm.h"
#include "spi.h"
#include "usart.h"
static uint16_t monitor_mask[2] = {0}; // pins to monitor == 1 (ONLY GPIO and ADC)
static uint16_t oldstates[2][16] = {0}; // previous state (16 bits - as some pins could be analog)
// intermediate buffer to change pin's settings by user request; after checking in will be copied to the_conf
static pinconfig_t pinconfig[2][16] = {0};
static uint8_t pinconfig_notinited = 1; // ==0 after first memcpy from the_conf to pinconfig
#define CHKPINCONFIG() do{if(pinconfig_notinited) chkpinconf();}while(0)
// TODO: remove AFmask, make function to get right AF number by pin's FuncValues
typedef struct{
uint8_t funcs; // bitmask according to enum FuncNames
uint8_t AF[FUNC_AMOUNT]; // alternate function number for corresponding `FuncNames` number
} pinprops_t;
// AF for USART, SPI, I2C:
#define _U(x) [FUNC_USART] = x
// _S(0) or _U(0) have no sence, but lets understand that this pin have SPI or USART
#define _S(x) [FUNC_SPI] = x
#define _I(x) [FUNC_I2C] = x
#define _P(x) [FUNC_PWM] = x
// Here included only common AF for STM32F042 and STM32F072 and without negative timer outputs (stars - collisions)
static const pinprops_t pin_props[2][16] = {
[0] = { // PORT A
[0] = { .funcs = 0b00000001, .AF = {0}}, // PA0: ADC0
[1] = { .funcs = 0b00010001, .AF = {_P(2)}}, // PA1: ADC1, AF2 (TIM2_CH2*)
[2] = { .funcs = 0b00010011, .AF = {_U(1), _P(2)}}, // PA2: ADC2, AF2 (TIM2_CH3**), AF1 (USART2_TX)
[3] = { .funcs = 0b00010011, .AF = {_U(1), _P(2)}}, // PA3: ADC3, AF2 (TIM2_CH4***), AF1 (USART2_RX)
[5] = { .funcs = 0b00000101, .AF = {_S(0)}}, // PA5: ADC5, AF9 (SPI1_SCK)
[6] = { .funcs = 0b00010101, .AF = {_S(0), _P(5)}}, // PA6: ADC6, AF0 (SPI1_MISO), AF5 (TIM16_CH1)
[7] = { .funcs = 0b00010101, .AF = {_S(0), _P(4)}}, // PA7: ADC7, AF0 (SPI1_MOSI), AF4 (TIM14_CH1)
[9] = { .funcs = 0b00010010, .AF = {_U(1), _P(2)}}, // PA9: AF1 (USART1_TX), AF2 (TIM1_CH2)
[10] = { .funcs = 0b00010010, .AF = {_U(1), _P(2)}}, // PA10: AF1 (USART1_RX), AF2 (TIM1_CH3)
},
[1] = { // PORT B
[0] = { .funcs = 0b00010001, .AF = {_P(1)}}, // PB0: ADC8, AF1 (TIM3_CH3)
[1] = { .funcs = 0b00010001, .AF = {_P(1)}}, // PB1: ADC9, AF1 (TIM3_CH4)
[2] = { .funcs = 0b00000000, .AF = {0}}, // PB2: nothing except GPIO
[3] = { .funcs = 0b00010100, .AF = {_S(0), _P(2)}}, // PB3: AF0, (SPI1_SCK), AF2 (TIM2_CH2*)
[4] = { .funcs = 0b00010100, .AF = {_S(0), _P(1)}}, // PB4: AF0 (SPI1_MISO), AF1 (TIM3_CH1)
[5] = { .funcs = 0b00010100, .AF = {_S(0), _P(1)}}, // PB5: AF0 (SPI1_MOSI), AF1 (TIM3_CH2)
[6] = { .funcs = 0b00001010, .AF = {_U(0), _I(1)}}, // PB6: AF0 (USART1_TX), AF1 (I2C1_SCL)
[7] = { .funcs = 0b00001010, .AF = {_U(0), _I(1)}}, // PB7: AF0 (USART1_RX), AF1 (I2C1_SDA)
[10] = { .funcs = 0b00011000, .AF = {_I(1), _P(2)}}, // PB10: AF1 (I2C1_SCL), AF2 (TIM2_CH3**)
[11] = { .funcs = 0b00011000, .AF = {_I(1), _P(2)}}, // PB11: AF1 (I2C1_SDA), AF2 (TIM2_CH4***)
}
};
#undef _U
#undef _S
#undef _I
#define CANADC(x) ((x) & (1<<FUNC_AIN))
#define CANUSART(x) ((x) & (1<<FUNC_USART))
#define CANSPI(x) ((x) & (1<<FUNC_SPI))
#define CANI2C(x) ((x) & (1<<FUNC_I2C))
#define CANPWM(x) ((x) & (1<<FUNC_PWM))
static uint8_t haveI2C = 0; // ==1 if chkpinconf found I2C
static uint8_t haveSPI = 0;
// return pin_props[port][pin].funcs for listing or -1 if disabled
int pinfuncs(uint8_t port, uint8_t pin){
if(is_disabled(port, pin)) return -1;
return (int) pin_props[port][pin].funcs;
}
/**
* @brief get_usart_index - get USART index (0 or 1 for USART1 or USART2) by given pin
* @param port
* @param pin
* @return -1 if error
*/
int get_usart_index(uint8_t port, uint8_t pin, usart_props_t *p){
if(port > 1 || pin > 15 || !CANUSART(pin_props[port][pin].funcs)) return -1;
int idx = -1;
usart_props_t curprops = {0};
if(port == 0){ // GPIOA
switch(pin){
case 2: // USART2_TX
idx = 1;
curprops.istx = 1;
break;
case 3: // USART2_RX
idx = 1;
curprops.isrx = 1;
break;
case 9: // USART1_TX
idx = 0;
curprops.istx = 1;
break;
case 10: // USART1_RX
idx = 0;
curprops.isrx = 1;
break;
default:
break;
}
}else if(port == 1){ // GPIOB
switch(pin){
case 6: // USART1_TX
idx = 0;
curprops.istx = 1;
break;
case 7: // USART1_RX
idx = 0;
curprops.isrx = 1;
break;
default:
break;
}
}
if(p) *p = curprops;
return idx;
}
// return -1 if pin can't I2C, or return 0 and fill `p`
int get_i2c_index(uint8_t port, uint8_t pin, i2c_props_t *p){
if(port > 1 || pin > 15 || !CANI2C(pin_props[port][pin].funcs)) return -1;
int idx = -1; // later we can add SPI2 support
i2c_props_t curprops = {0};
if(port == 1){ // only GPIOB
switch(pin){
case 6: // PB6 - I2C1_SCL
idx = 0;
curprops.isscl = 1;
break;
case 7: // PB7 - I2C1_SDA
idx = 0;
curprops.issda = 1;
break;
case 10: // PB10 - I2C1_SCL
idx = 0;
curprops.isscl = 1;
break;
case 11: // PB11 - I2C1_SDA
idx = 0;
curprops.issda = 1;
break;
default:
break;
}
}
if(p) *p = curprops;
return idx;
}
int get_spi_index(uint8_t port, uint8_t pin, spi_props_t *p){
if(port > 1 || pin > 15 || !CANSPI(pin_props[port][pin].funcs)) return -1;
int idx = -1;
spi_props_t curprops = {0};
if(port == 0){ // PA5-7 (SCK-MISO-MOSI)
switch(pin){
case 5:
idx = 0;
curprops.issck =1;
break;
case 6:
idx = 0;
curprops.ismiso = 1;
break;
case 7:
idx = 0;
curprops.ismosi = 1;
break;
default: break;
}
}else if(port == 1){ // PB3-5 (SCK-MISO-MOSI)
switch(pin){
case 3:
idx = 0;
curprops.issck =1;
break;
case 4:
idx = 0;
curprops.ismiso = 1;
break;
case 5:
idx = 0;
curprops.ismosi = 1;
break;
default: break;
}
}
if(p) *p = curprops;
return idx;
}
// default config
static void defconfig(pinconfig_t *cfg){
if(!cfg) return;
cfg->af = FUNC_AIN;
cfg->afno = 0;
cfg->mode = MODE_INPUT;
cfg->monitor = 0;
cfg->speed = SPEED_LOW;
cfg->pull = PULL_NONE;
}
// check current pin configuration; in case of errors set default values (floating input)
int chkpinconf(){
int ret = TRUE;
if(pinconfig_notinited){
memcpy(pinconfig, the_conf.pinconfig, sizeof(pinconfig));
pinconfig_notinited = 0;
}
usartconf_t UC;
if(!get_curusartconf(&UC)){
get_defusartconf(&UC);
}else{ // got current config -> clear `RXen`, `TXen` and `monitor`: if none appeared, turn OFF USART!
UC.RXen = 0; UC.TXen = 0; UC.monitor = 0;
}
int active_usart = -1; // number of USART if user selects it (we can't check it by UC->idx)
int active_i2c = -1;
int active_spi = -1;
i2c_props_t i2cprops = {0};
spi_props_t spiprops = {0};
for(int port = 0; port < 2; ++port){
for(int pin = 0; pin < 16; ++pin){
pinconfig_t *cfg = &pinconfig[port][pin];
if(!cfg->enable) continue;
const pinprops_t *props = &pin_props[port][pin];
// wrong configuration -> don't mind AF, make FLIN
if(cfg->mode == MODE_AF){
if(cfg->af >= FUNC_AMOUNT || !((1<<cfg->af) & props->funcs)){
DBG("Wrong AF config -> FL IN\n");
defconfig(cfg);
ret = FALSE;
}else{ // set afno to proper number
cfg->afno = props->AF[cfg->af];
switch(cfg->af){
case FUNC_USART:{
usart_props_t up;
int usart_idx = get_usart_index(port, pin, &up);
if(usart_idx < 0){ // error -> defaults
DBG("no USART on this pin\n");
defconfig(cfg);
ret = FALSE;
break;
}
if(active_usart == -1){
active_usart = usart_idx;
}else if(active_usart != usart_idx){
// User tries to configure Rx/Tx on different USARTs
DBG("USART conflicted!\n");
defconfig(cfg);
ret = FALSE;
break;
}
if(up.isrx) UC.RXen = 1;
else if(up.istx) UC.TXen = 1;
if(cfg->monitor) UC.monitor = 1;
}
break;
case FUNC_PWM:{
pwmtimer_t pwm;
if(!canPWM(port, pin, &pwm)){
DBG("Can't PWM\n");
defconfig(cfg);
ret = FALSE;
break;
}
if(pwm.collision && pinconfig[pwm.collport][pwm.collpin].af == FUNC_PWM){
DBG("Found collision -> remove\n");
defconfig(&pinconfig[pwm.collport][pwm.collpin]); // set later collision to defaults
ret = FALSE;
break;
}
}
break;
case FUNC_I2C:{
i2c_props_t ip;
int i2c_idx = get_i2c_index(port, pin, &ip);
if(i2c_idx < 0){
defconfig(cfg);
ret = FALSE;
break;
}
// maybe for 2 I2Cs
if(active_i2c == -1) active_i2c = i2c_idx;
else if(active_i2c != i2c_idx){
// collision
defconfig(cfg);
ret = FALSE;
break;
}
if(ip.isscl){
if(i2cprops.isscl){ // two SCLs
defconfig(cfg);
ret = FALSE;
break;
}
i2cprops.isscl = 1;
}
if(ip.issda){
if(i2cprops.issda){ // two SDAs
defconfig(cfg);
ret = FALSE;
break;
}
i2cprops.issda = 1;
}
}
break;
case FUNC_SPI:{
spi_props_t sp;
int spi_idx = get_spi_index(port, pin, &sp);
if(spi_idx < 0){
defconfig(cfg);
ret = FALSE;
break;
}
if(active_spi == -1) active_spi = spi_idx;
else if(active_spi != spi_idx){
defconfig(cfg);
ret = FALSE;
break;
}
if(sp.issck){
if(spiprops.issck){
defconfig(cfg);
ret = FALSE;
break;
}
spiprops.issck = 1;
}
if(sp.ismiso){
if(spiprops.ismiso){
defconfig(cfg);
ret = FALSE;
break;
}
spiprops.ismiso = 1;
}
if(sp.ismosi){
if(spiprops.ismosi){
defconfig(cfg);
ret = FALSE;
break;
}
spiprops.ismosi = 1;
}
}
break;
default: break; // later fill other functions
}
}
}
}
}
// now check USART configuration
if(active_usart != -1){
DBG("Got active USART\n");
UC.idx = active_usart;
if(!chkusartconf(&UC)) ret = FALSE;
}else{
DBG("No active USARTs\n");
get_defusartconf(&UC); // clear global configuration
the_conf.usartconfig = UC;
}
// check active I2C
if(active_i2c != -1){
if(i2cprops.isscl && i2cprops.issda){
haveI2C = 1;
}else{
DBG("Need two pins for I2C\n");
ret = FALSE;
haveI2C = 0;
}
}
if(active_spi != -1){
if(spiprops.issck && (spiprops.ismiso || spiprops.ismosi)){
haveSPI = 1;
if(!spiprops.ismosi) the_conf.spiconfig.rxonly = 1;
else the_conf.spiconfig.rxonly = 0;
if(!spiprops.ismiso) the_conf.spiconfig.txonly = 1;
else the_conf.spiconfig.txonly = 0;
}else{
DBG("SPI needs SCK and MOSI or MISO\n");
ret = FALSE;
haveSPI = 0;
}
}
return ret;
}
int is_disabled(uint8_t port, uint8_t pin){
if(port > 1 || pin > 15) return TRUE;
if(the_conf.pinconfig[port][pin].enable) return FALSE;
return TRUE;
}
// return current conf from local `pinconfig`
int get_curpinconf(uint8_t port, uint8_t pin, pinconfig_t *c){
CHKPINCONFIG();
if(!c || port > 1 || pin > 15) return FALSE;
*c = pinconfig[port][pin];
return TRUE;
}
/**
* @brief set_pinfunc - check if alternate function `afno` allowed on given pin
* @param port - 0 for GPIOA and 1 for GPIOB
* @param pin - 0..15
* @param pcfg (io) - pin configuration
* @return TRUE if all OK
*/
int set_pinfunc(uint8_t port, uint8_t pin, pinconfig_t *pcfg){
DBG("set_pinfunc()\n");
CHKPINCONFIG();
if(is_disabled(port, pin) || !pcfg){
DBG("Disabled?\n");
return FALSE;
}
const pinprops_t *props = &pin_props[port][pin];
switch(pcfg->mode){
case MODE_ANALOG:
DBG("Analog\n");
if(!CANADC(props->funcs)){
DBG("Can't ADC\n");
return FALSE;
}
pcfg->pull = PULL_NONE; // no PullUp for analog mode
break;
case MODE_AF:
DBG("Altfun\n");
// here af is one of enum FuncValues !!! we should change `af` later
if(pcfg->af >= FUNC_AMOUNT || !((1<<pcfg->af) & props->funcs)){
DBG("Wrong AF\n");
return FALSE;
}
pcfg->afno = props->AF[pcfg->af];
pcfg->speed = SPEED_HIGH; // many AF needs high speed
pcfg->otype = OUTPUT_PP; // no OD for AF
break;
case MODE_INPUT: // no limits
DBG("Input\n");
break;
case MODE_OUTPUT: // remove pullup/pulldown for PP
DBG("Output\n");
if(pcfg->otype == OUTPUT_PP) pcfg->pull = PULL_NONE;
break;
default:
DBG("Wrong\n");
return FALSE;
}
pcfg->enable = 1; // don't forget to set enable flag!
pinconfig[port][pin] = *pcfg;
DBG("All OK\n");
return TRUE;
}
/**
* @brief get_adc_channel - get ADC channel number for given pin
* @param port - 0/1 (GPIOA/GPIOB)
* @param pin - 0..16
* @return ADC channel number or -1
*/
TRUE_INLINE int8_t get_adc_channel(uint8_t port, uint8_t pin){
if(port == 0){ // GPIOA
if (pin <= 7) return pin; // PA0..PA7 -> IN0..IN7
}else{ // GPIOB
if(pin == 0) return 8; // PB0 -> IN8
if(pin == 1) return 9; // PB1 -> IN9
}
return -1; // No ADC channel on this pin
}
// reinit all GPIO registers due to config; also configure (if need) USART1/2, SPI1 and I2C1
// return FALSE if found some errors in current configuration (and it was fixed to default)
int gpio_reinit(){
bzero(monitor_mask, sizeof(monitor_mask));
bzero(oldstates, sizeof(oldstates));
int ret = TRUE;
int tocopy = chkpinconf(); // if config is wrong, don't copy it to flash
for(int port = 0; port < 2; port++){
GPIO_TypeDef *gpio = (port == 0) ? GPIOA : GPIOB;
for(int pin = 0; pin < 16; pin++){
pinconfig_t *cfg = &pinconfig[port][pin];
if(!cfg->enable) continue;
int shift2 = pin << 1;
gpio->MODER = (gpio->MODER & ~(3 << shift2))| (cfg->mode << shift2);
gpio->OTYPER = (gpio->OTYPER & ~(1 << pin)) | (cfg->otype << pin);
gpio->OSPEEDR = (gpio->OSPEEDR & ~(3 << shift2)) | (cfg->speed << shift2);
gpio->PUPDR = (gpio->PUPDR & ~(3 << shift2)) | (cfg->pull << shift2);
if(pin < 8){
int shift4 = pin << 2;
gpio->AFR[0] = (gpio->AFR[0] & ~(0xf << shift4)) | (cfg->afno << shift4);
}else{
int shift4 = (pin - 8) << 2;
gpio->AFR[1] = (gpio->AFR[1] & ~(0xf << shift4)) | (cfg->afno << shift4);
}
if(cfg->monitor && cfg->mode != MODE_AF){
monitor_mask[port] |= (1 << pin);
if(cfg->mode == MODE_ANALOG){
if(cfg->threshold > 4095) cfg->threshold = 4095; // no threshold
int8_t chan = get_adc_channel(port, pin);
if(chan >= 0){
oldstates[port][pin] = getADCval(chan);
}
}else{
// save old state for regular GPIO
oldstates[port][pin] = (gpio->IDR >> pin) & 1;
}
}
// start/stop PWM on this pin
if(cfg->mode == MODE_AF && cfg->af == FUNC_PWM){
if(!startPWM(port, pin)) ret = FALSE;
}else{ // check for collisions
pwmtimer_t t;
if(canPWM(port, pin, &t)){
if(t.collision){ // stop PWM only if "earlier" channel don't set on this
if((t.collport < port || t.collpin < pin) && (pinconfig[t.collport][t.collpin].af != FUNC_PWM))
stopPWM(port, pin);
}else stopPWM(port, pin);
}
}
}
}
// if all OK, copy to the_conf
if(tocopy) memcpy(the_conf.pinconfig, pinconfig, sizeof(pinconfig));
else ret = FALSE;
// TODO: configure SPI etc
usartconf_t usc;
if(get_curusartconf(&usc) && (usc.RXen | usc.TXen)){
if(!usart_config(NULL)) ret = FALSE;
else if(!usart_start()) ret = FALSE;
}else usart_stop();
if(haveI2C) i2c_setup((i2c_speed_t) the_conf.I2Cspeed);
else i2c_stop();
if(haveSPI) spi_setup();
else spi_stop();
return ret;
}
// get MODER for current pin
TRUE_INLINE uint32_t get_moder(volatile GPIO_TypeDef * GPIOx, uint8_t pin){
return (GPIOx->MODER >> (pin << 1)) & 3;
}
/**
* @brief pin_out - change pin value
* @param port - 0 for GPIOA, 1 for GPIOB
* @param pin - 0..15
* @param newval - 0 or 1 (reset/set)
* @return FALSE if pin isn't OUT or other err
* here I check real current settings by GPIOx->MODER
*/
int pin_out(uint8_t port, uint8_t pin, uint8_t newval){
if(port > 1 || pin > 15) return FALSE;
volatile GPIO_TypeDef * GPIOx = (port == 0) ? GPIOA : GPIOB;
uint16_t mask = 1 << pin;
uint32_t moder = get_moder(GPIOx, pin);
if(moder != MODE_OUTPUT) return FALSE;
if(newval) GPIOx->BSRR = mask;
else GPIOx->BRR = mask;
return TRUE;
}
/**
* @brief pin_in - get current pin's value (0/1 for regular GPIO, 0..4095 for ADC)
* @param port - 0..1
* @param pin - 0..15
* @return value or -1 if pin have AF or don't used
*/
int16_t pin_in(uint8_t port, uint8_t pin){
if(port > 1 || pin > 15) return -1;
volatile GPIO_TypeDef * GPIOx = (port == 0) ? GPIOA : GPIOB;
uint32_t moder = get_moder(GPIOx, pin);
int16_t val = -1;
switch(moder){ // check REAL pin config
case MODE_INPUT:
case MODE_OUTPUT:
if(GPIOx->IDR & (1<<pin)) val = 1;
else val = 0;
break;
case MODE_ANALOG:{
int8_t chan = get_adc_channel(port, pin);
if(chan >= 0)
val = (int16_t) getADCval(chan);
}
break;
case MODE_AF:{
pinconfig_t curconf;
if(!get_curpinconf(port, pin, &curconf)) return -1;
if(curconf.af == FUNC_PWM)
val = getPWM(port, pin);
}
break;
default:
break;
}
return val;
}
/**
* @brief gpio_alert - return bitmask for alerted pins (whos state changed over last check)
* AF don't checked! Use appropriate function for them
* @param port - 0 for GPIOA, 1 for GPIOB
* @return pin mask where 1 is for changed state
*/
uint16_t gpio_alert(uint8_t port){
if(port > 1) return 0;
if(0 == monitor_mask[port]) return 0; // nothing to monitor
volatile GPIO_TypeDef * GPIOx = (port == 0) ? GPIOA : GPIOB;
uint32_t moder = GPIOx->MODER;
uint16_t curpinbit = 1; // shift each iteration
uint16_t *oldstate = oldstates[port];
uint16_t alert = 0;
for(int pin = 0; pin < 16; ++pin, curpinbit <<= 1, moder >>= 2){
uint8_t curm = moder & 3;
if((curm == MODE_AF) || 0 == (monitor_mask[port] & curpinbit)) continue; // monitor also OUT (if OD)
// TODO: add AIN
if(curm == MODE_ANALOG){
int8_t chan = get_adc_channel(port, pin);
if(chan < 0) continue; // can't be in normal case
uint16_t cur = getADCval(chan);
uint16_t thresh = the_conf.pinconfig[port][pin].threshold;
uint16_t diff = (cur > oldstate[pin]) ? (cur - oldstate[pin]) : (oldstate[pin] - cur);
if(diff > thresh){
oldstate[pin] = cur;
alert |= curpinbit;
}
}else{
uint16_t curval = (GPIOx->IDR & curpinbit) ? 1 : 0;
if(oldstate[pin] != curval){
oldstate[pin] = curval;
alert |= curpinbit;
}
}
}
return alert;
}

View File

@@ -0,0 +1,136 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include <stm32f0.h>
#ifdef EBUG
#define USBIF IGPIO
#include "strfunc.h"
#define DBG(x) SEND(x)
#define DBGNL() NL()
#else
#define DBG(x)
#define DBGNL()
#endif
// MODER
typedef enum{
MODE_INPUT = 0,
MODE_OUTPUT = 1,
MODE_AF = 2,
MODE_ANALOG = 3
} pinmode_t;
// PUPDR
typedef enum{
PULL_NONE = 0,
PULL_UP = 1,
PULL_DOWN = 2
} pinpull_t;
// OTYPER
typedef enum{
OUTPUT_PP = 0,
OUTPUT_OD = 1
} pinout_t;
// OSPEEDR
typedef enum{
SPEED_LOW = 0,
SPEED_MEDIUM = 1,
SPEED_HIGH = 3
} pinspeed_t;
// !!! FuncNames means position of bit in funcvalues_t.flags!
typedef enum FuncNames{ // shift 1 by this to get "canUSART" etc; not more than 7!
FUNC_AIN = 0,
FUNC_USART = 1,
FUNC_SPI = 2,
FUNC_I2C = 3,
FUNC_PWM = 4,
FUNC_AMOUNT // just for arrays' sizes
} funcnames_t;
/*
typedef union{
struct{
uint8_t canADC : 1;
uint8_t canUSART : 1;
uint8_t canSPI : 1;
};
uint8_t flags;
} funcvalues_t;
*/
typedef struct{
uint8_t enable : 1; // [immutable!] pin config avialable (==1 for PA0-PA3, PA5-PA7, PA9, PA10, PB0-PB7, PB10, PB11, ==0 for rest)
pinmode_t mode : 2;
pinpull_t pull : 2;
pinout_t otype : 1;
pinspeed_t speed : 2;
uint8_t afno : 3; // alternate function number (only if mode == MODE_AF)
funcnames_t af : 3; // alternate function name (`FuncNames`)
uint8_t monitor : 1; // monitor changes
uint16_t threshold; // threshold for ADC measurement
} pinconfig_t;
typedef struct{
uint8_t isrx : 1;
uint8_t istx : 1;
} usart_props_t;
typedef struct{
uint8_t isscl : 1;
uint8_t issda : 1;
} i2c_props_t;
typedef struct{
uint8_t issck : 1;
uint8_t ismiso : 1;
uint8_t ismosi : 1;
} spi_props_t;
/*
typedef struct{
uint32_t speed;
uint8_t cpol : 1;
uint8_t cpha : 1;
uint8_t lsbfirst : 1;
uint8_t enabled : 1;
} spiconfig_t;
*/
int is_disabled(uint8_t port, uint8_t pin);
int pinfuncs(uint8_t port, uint8_t pin);
int chkpinconf();
int set_pinfunc(uint8_t port, uint8_t pin, pinconfig_t *pcfg);
int get_curpinconf(uint8_t port, uint8_t pin, pinconfig_t *c);
int get_usart_index(uint8_t port, uint8_t pin, usart_props_t *p);
int get_i2c_index(uint8_t port, uint8_t pin, i2c_props_t *p);
int get_spi_index(uint8_t port, uint8_t pin, spi_props_t *p);
int gpio_reinit();
int pin_out(uint8_t port, uint8_t pin, uint8_t newval);
int16_t pin_in(uint8_t port, uint8_t pin);
uint16_t gpio_alert(uint8_t port);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// error codes for answer message
typedef enum{
ERR_OK, // all OK
ERR_BADCMD, // wrong command
ERR_BADPAR, // wrong parameter
ERR_BADVAL, // wrong value (for setter)
ERR_WRONGLEN, // wrong message length
ERR_CANTRUN, // can't run given command due to bad parameters or other
ERR_BUSY, // target interface busy, try later
ERR_OVERFLOW, // string was too long -> overflow
ERR_AMOUNT // amount of error codes or "send nothing"
} errcodes_t;
// maximal length of command (without trailing zero)
#define CMD_MAXLEN 15
// maximal available parameter number
#define MAXPARNO 255
// default threshold for monitoring
#define ADC_THRES_DEFAULT 100
void GPIO_process();
void GPIO_init();

View File

@@ -0,0 +1,99 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "adc.h"
#include "gpioproto.h"
#include "hardware.h"
const uint32_t peripherial_clock = 48000000;
uint8_t ledsON = 0;
TRUE_INLINE void pins_setup(){ // setup some common GPIO
// Set LEDS (PB15/PA8) as output
pin_set(LED0_port, LED0_pin); // clear LEDs
pin_set(LED1_port, LED1_pin);
GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER15)) |
GPIO_MODER_MODER15_O;
GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER8)) |
GPIO_MODER_MODER8_O;
}
void hardware_setup(){
// enable all active GPIO clocking
RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_DMA1EN;
RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_SYSCFGEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_TIM16EN |
RCC_APB2ENR_SPI1EN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM14EN |
RCC_APB1ENR_I2C1EN;
pins_setup();
adc_setup();
GPIO_init();
}
void iwdg_setup(){
uint32_t tmout = 16000000;
/* Enable the peripheral clock RTC */
/* (1) Enable the LSI (40kHz) */
/* (2) Wait while it is not ready */
RCC->CSR |= RCC_CSR_LSION; /* (1) */
while((RCC->CSR & RCC_CSR_LSIRDY) != RCC_CSR_LSIRDY){if(--tmout == 0) break;} /* (2) */
/* Configure IWDG */
/* (1) Activate IWDG (not needed if done in option bytes) */
/* (2) Enable write access to IWDG registers */
/* (3) Set prescaler by 64 (1.6ms for each tick) */
/* (4) Set reload value to have a rollover each 2s */
/* (5) Check if flags are reset */
/* (6) Refresh counter */
IWDG->KR = IWDG_START; /* (1) */
IWDG->KR = IWDG_WRITE_ACCESS; /* (2) */
IWDG->PR = IWDG_PR_PR_1; /* (3) */
IWDG->RLR = 1250; /* (4) */
tmout = 16000000;
while(IWDG->SR){if(--tmout == 0) break;} /* (5) */
IWDG->KR = IWDG_REFRESH; /* (6) */
}
#ifdef STM32F072xB
void Jump2Boot(){
void (*SysMemBootJump)(void);
volatile uint32_t addr = 0x1FFFC800;
// reset systick
SysTick->CTRL = 0;
// reset clocks
RCC->APB1RSTR = RCC_APB1RSTR_CECRST | RCC_APB1RSTR_DACRST | RCC_APB1RSTR_PWRRST | RCC_APB1RSTR_CRSRST |
RCC_APB1RSTR_CANRST | RCC_APB1RSTR_USBRST | RCC_APB1RSTR_I2C2RST | RCC_APB1RSTR_I2C1RST |
RCC_APB1RSTR_USART4RST | RCC_APB1RSTR_USART3RST | RCC_APB1RSTR_USART2RST | RCC_APB1RSTR_SPI2RST |
RCC_APB1RSTR_WWDGRST | RCC_APB1RSTR_TIM14RST |
RCC_APB1RSTR_TIM7RST | RCC_APB1RSTR_TIM6RST |
RCC_APB1RSTR_TIM3RST | RCC_APB1RSTR_TIM2RST;
RCC->APB2RSTR = RCC_APB2RSTR_DBGMCURST | RCC_APB2RSTR_TIM17RST | RCC_APB2RSTR_TIM16RST |
RCC_APB2RSTR_TIM15RST |
RCC_APB2RSTR_USART1RST | RCC_APB2RSTR_SPI1RST | RCC_APB2RSTR_TIM1RST | RCC_APB2RSTR_ADCRST | RCC_APB2RSTR_SYSCFGRST;
RCC->AHBRSTR = 0;
RCC->APB1RSTR = 0;
RCC->APB2RSTR = 0;
// remap memory to 0 (only for STM32F0)
SYSCFG->CFGR1 = 0x01; __DSB(); __ISB();
SysMemBootJump = (void (*)(void)) (*((uint32_t *)(addr + 4)));
// set main stack pointer
__set_MSP(*((uint32_t *)addr));
// jump to bootloader
SysMemBootJump();
}
#endif

View File

@@ -0,0 +1,51 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stm32f0.h>
#define CONCAT(a,b) a ## b
#define STR_HELPER(s) #s
#define STR(s) STR_HELPER(s)
#define FORMUSART(X) CONCAT(USART, X)
#define USARTX FORMUSART(USARTNUM)
// LEDS: 0 - PB15, 1 - PA8
// LED0
#define LED0_port GPIOB
#define LED0_pin (1<<15)
// LED1
#define LED1_port GPIOA
#define LED1_pin (1<<8)
#define LED_blink(x) do{if(ledsON) pin_toggle(x ## _port, x ## _pin);}while(0)
#define LED_on(x) do{if(ledsON) pin_clear(x ## _port, x ## _pin);}while(0)
#define LED_off(x) do{pin_set(x ## _port, x ## _pin);}while(0)
extern volatile uint32_t Tms;
extern const uint32_t peripherial_clock;
extern uint8_t ledsON;
void hardware_setup();
void iwdg_setup();
#ifdef STM32F072xB
void Jump2Boot();
#endif

View File

@@ -0,0 +1,350 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// !!! Some commands could change icoming string, so don't try to use it after function call !!!
#include <stdint.h>
extern "C"{
#include <stm32f0.h>
#include "can.h"
#include "flash.h"
#include "hashparser.h"
#include "gpio.h"
#include "gpioproto.h"
#define USBIF IGPIO
#include "strfunc.h"
}
extern uint32_t Tms;
// list of all commands and handlers
#define COMMAND_TABLE \
COMMAND(canspeed, "CAN bus speed setter/getter (kBaud, 10..1000)") \
COMMAND(dumpflash, "flash config dump") \
COMMAND(help, "Show this help") \
COMMAND(PA, "GPIOA setter/getter (type PAx=help for further info)") \
COMMAND(PB, "GPIOB setter/getter") \
COMMAND(reinit, "apply pin config") \
COMMAND(storeconf, "save config to flash") \
COMMAND(time, "show current time (ms)")
// COMMAND(USART, "Read USART data or send (USART=hex)")
// COMMAND(usartconf, "set USART params (e.g. usartconf=115200 8N1)")
// COMMAND(SPI, "Read SPI data or send (SPI=hex)")
// COMMAND(spiconf, "set SPI params")
typedef struct {
const char *name;
const char *desc;
} CmdInfo;
// prototypes
#define COMMAND(name, desc) static errcodes_t cmd_ ## name(const char*, char*);
COMMAND_TABLE
#undef COMMAND
static const CmdInfo cmdInfo[] = { // command name, description - for `help`
#define COMMAND(name, desc) { #name, desc },
COMMAND_TABLE
#undef COMMAND
};
// pin settings parser
struct Keyword {
const char *name;
uint8_t group;
uint8_t value;
};
enum KeywordGroup {
GROUP_MODE,
GROUP_PULL,
GROUP_OTYPE,
GROUP_FUNC,
GROUP_MISC
};
enum MiscValues{
MISC_MONITOR = 1,
};
static constexpr Keyword keywords[] = {
{"AIN", GROUP_MODE, MODE_ANALOG},
{"IN", GROUP_MODE, MODE_INPUT},
{"OUT", GROUP_MODE, MODE_OUTPUT},
{"AF", GROUP_MODE, MODE_AF},
{"PU", GROUP_PULL, PULL_UP},
{"PD", GROUP_PULL, PULL_DOWN},
{"FL", GROUP_PULL, PULL_NONE},
{"PP", GROUP_OTYPE, OUTPUT_PP},
{"OD", GROUP_OTYPE, OUTPUT_OD},
{"USART", GROUP_FUNC, FUNC_USART},
{"SPI", GROUP_FUNC, FUNC_SPI},
{"MONITOR",GROUP_MISC, MISC_MONITOR}, // monitor flag
};
#define NUM_KEYWORDS (sizeof(keywords)/sizeof(keywords[0]))
static const char* errtxt[ERR_AMOUNT] = {
[ERR_OK] = "OK",
[ERR_BADCMD] = "BADCMD",
[ERR_BADPAR] = "BADPAR",
[ERR_BADVAL] = "BADVAL",
[ERR_WRONGLEN] = "WRONGLEN",
[ERR_CANTRUN] = "CANTRUN",
};
static const char *EQ = " = "; // equal sign for getters
/**
* @brief splitargs - get command parameter and setter from `args`
* @param args (i) - rest of string after command (like `1 = PU OD OUT`)
* @param parno (o) - parameter number or -1 if none
* @return setter (part after `=` without leading spaces) or NULL if none
*/
static char *splitargs(char *args, int32_t *parno){
if(!args) return NULL;
uint32_t U32;
char *next = getnum(args, &U32);
int p = -1;
if(next != args && U32 <= MAXPARNO) p = U32;
if(parno) *parno = p;
next = strchr(next, '=');
if(next){
if(*(++next)) next = omit_spaces(next);
if(*(++next) == 0) next = NULL;
}
return next;
}
#if 0
/**
* @brief argsvals - split `args` into `parno` and setter's value
* @param args - rest of string after command
* @param parno (o) - parameter number or -1 if none
* @param parval - integer setter's value
* @return false if no setter or it's not a number, true - got setter's num
*/
static bool argsvals(char *args, int32_t *parno, int32_t *parval){
char *setter = splitargs(args, parno);
if(!setter) return false;
int32_t I32;
char *next = getint(setter, &I32);
if(next != setter && parval){
*parval = I32;
return true;
}
return false;
}
#endif
// `port` and `pin` are checked in `parse_pin_command`
// `PAx = ` also printed there
static void pin_getter(uint8_t port, uint8_t pin){
pinconfig_t *pcfg = &the_conf.pinconfig[port][pin];
switch(pcfg->mode){
case MODE_INPUT:
case MODE_OUTPUT: {
uint32_t idr = (port == 0) ? GPIOA->IDR : GPIOB->IDR;
uint8_t bit = (idr >> pin) & 1;
PUTCHAR(bit ? '1' : '0');
NL();
}
break;
case MODE_ANALOG:
SENDn("TODO");
// TODO: read ADC channel #pin
//SENDn(u2str(get_adc_value(port, pin)));
break;
case MODE_AF:
SENDn("ERR: pin in AF mode, use USART/SPI commands");
break;
default:
break;
}
}
// `port` and `pin` are checked in `parse_pin_command`
// set GPIO values (if *setter is 0/1) or configure it
static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){
pinconfig_t *pcfg = &the_conf.pinconfig[port][pin];
char _1st = *setter;
if(_1st == '0' || _1st == '1'){ // just set/clear pin state; throw out all text after "1"/"0"
if(pcfg->mode != MODE_OUTPUT) return ERR_CANTRUN;
volatile GPIO_TypeDef * GPIOx = (port == 0) ? GPIOA : GPIOB;
if(_1st == '1') GPIOx->BSRR = (1 << pin);
else GPIOx->BRR = (1 << pin);
return ERR_OK;
}
// complex setter: parse properties
uint8_t mode_set = 0xFF, pull_set = 0xFF, otype_set = 0xFF, func_set = 0xFF;
bool monitor = false;
char *saveptr, *token = strtok_r(setter, " ,", &saveptr);
while(token){
size_t i = 0;
for(; i < NUM_KEYWORDS; i++){
if(strcmp(token, keywords[i].name) == 0){
switch(keywords[i].group){
case GROUP_MODE:
if(mode_set != 0xFF) return ERR_BADVAL; // repeated similar group parameter
mode_set = keywords[i].value;
break;
case GROUP_PULL:
if(pull_set != 0xFF) return ERR_BADVAL;
pull_set = keywords[i].value;
break;
case GROUP_OTYPE:
if(otype_set != 0xFF) return ERR_BADVAL;
otype_set = keywords[i].value;
break;
case GROUP_FUNC:
if(func_set != 0xFF) return ERR_BADVAL;
func_set = keywords[i].value;
break;
case GROUP_MISC:
if(keywords[i].value == MISC_MONITOR) monitor = true;
break;
}
break;
}
}
if(i == NUM_KEYWORDS) return ERR_BADVAL; // not found
token = strtok_r(NULL, " ,", &saveptr);
}
if(func_set != 0xFF) mode_set = MODE_AF;
if(mode_set == 0xFF) return ERR_BADVAL; // user forgot to set mode
// set defaults
if(pull_set == 0xFF) pull_set = PULL_NONE;
if(otype_set == 0xFF) otype_set = OUTPUT_PP;
// can also do something with `speed_set`, then remove SPEED_MEDIUM from `curconfig`
// check that current parameters combination is acceptable for current pin
pinconfig_t curconf;
curconf.mode = static_cast <pinmode_t> (mode_set);
curconf.pull = static_cast <pinpull_t> (pull_set);
curconf.otype = static_cast <pinout_t> (otype_set);
curconf.speed = SPEED_MEDIUM;
curconf.af = func_set;
curconf.monitor = monitor;
if(!is_func_allowed(port, pin, &curconf)) return ERR_BADVAL;
*pcfg = curconf;
return ERR_OK;
}
// PAx [= aa], PBx [= bb]
static errcodes_t parse_pin_command(const char *cmd, char *args){
if(!args) return ERR_BADPAR; // or maybe add list for all pins?
char port_char = cmd[1];
if(port_char != 'A' && port_char != 'B') return ERR_BADCMD;
uint8_t port = (port_char == 'A') ? 0 : 1;
int32_t pin = -1;
char *setter = splitargs(args, &pin);
if(pin < 0 || pin > 15) return ERR_BADPAR;
pinconfig_t *pcfg = &the_conf.pinconfig[port][pin]; // just to check if pin can be configured
if(!pcfg->enable) return ERR_CANTRUN; // prohibited pin
if(!setter){ // simple getter -> get value and return ERR_AMOUNT as silence
SEND(cmd); SEND(u2str((uint32_t)pin)); SEND(EQ);
pin_getter(port, pin);
return ERR_AMOUNT;
}
return pin_setter(port, pin, setter);
}
static errcodes_t cmd_PA(const char *cmd, char *args){
return parse_pin_command(cmd, args);
}
static errcodes_t cmd_PB(const char *cmd, char *args){
return parse_pin_command(cmd, args);
}
static errcodes_t cmd_reinit(const char _U_ *cmd, char _U_ *args){
if(gpio_reinit()) return ERR_OK;
SEND("Can't reinit: check your configuration!\n");
return ERR_AMOUNT;
}
static errcodes_t cmd_storeconf(const char _U_ *cmd, char _U_ *args){
if(!store_userconf()) return ERR_CANTRUN;
return ERR_OK;
}
// canspeed = baudrate (kBaud)
static errcodes_t cmd_canspeed(const char *cmd, char _U_ *args){
SEND(cmd); PUTCHAR('='); SENDn(u2str(CAN_getspeed()));
if(args && *args){SEND("You entered: "); SENDn(args);}
return ERR_AMOUNT;
}
static errcodes_t cmd_dumpflash(const char _U_ *cmd, char _U_ *args){
SEND("userconf_sz="); SEND(u2str(the_conf.userconf_sz));
SEND("\ncurrentconfidx="); SENDn(i2str(currentconfidx));
for(int i = 0; i < InterfacesAmount; ++i){
SEND("interface"); PUTCHAR('0' + i);
PUTCHAR('=');
int l = the_conf.iIlengths[i] / 2;
char *ptr = (char*) the_conf.iInterface[i];
for(int j = 0; j < l; ++j){
PUTCHAR(*ptr);
ptr += 2;
}
NL();
}
SEND("canspeed="); SENDn(u2str(the_conf.CANspeed));
return ERR_AMOUNT;
}
static errcodes_t cmd_time(const char* cmd, char _U_ *args){
SEND(cmd); PUTCHAR('='); SENDn(u2str(Tms));
return ERR_AMOUNT;
}
static errcodes_t cmd_help(const char _U_ *cmd, char _U_ *args){
SEND(REPOURL);
for(size_t i = 0; i < sizeof(cmdInfo)/sizeof(cmdInfo[0]); i++){
SEND(cmdInfo[i].name);
SEND(" - ");
SENDn(cmdInfo[i].desc);
}
return ERR_AMOUNT;
}
constexpr uint32_t hash(const char* str, uint32_t h = 0){
return *str ? hash(str + 1, h + ((h << 7) ^ *str)) : h;
}
// TODO: add checking real command length!
void chk(char *str){
if(!str || !*str) return;
char command[CMD_MAXLEN+1];
int i = 0;
while(*str > '@' && i < CMD_MAXLEN){ command[i++] = *str++; }
command[i] = 0;
while(*str && *str <= ' ') ++str;
char *restof = (char*) str;
uint32_t h = hash(command);
errcodes_t ecode = ERR_AMOUNT;
switch(h){
#define COMMAND(name, desc) case hash(#name): ecode = cmd_ ## name(command, restof); break;
COMMAND_TABLE
#undef COMMAND
default: SEND("Unknown command, try 'help'\n"); break;
}
if(ecode < ERR_AMOUNT) SENDn(errtxt[ecode]);
;
}

View File

@@ -0,0 +1,37 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// error codes for answer message
typedef enum{
ERR_OK, // all OK
ERR_BADCMD, // wrong command
ERR_BADPAR, // wrong parameter
ERR_BADVAL, // wrong value (for setter)
ERR_WRONGLEN, // wrong message length
ERR_CANTRUN, // can't run given command due to bad parameters or other
ERR_AMOUNT // amount of error codes or "send nothing"
} errcodes_t;
// maximal length of command (without trailing zero)
#define CMD_MAXLEN 15
// maximal available parameter number
#define MAXPARNO 255
void chk(char *str);

View File

@@ -0,0 +1,214 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gpio.h"
#include "i2c.h"
// fields position in I2C1->TIMINGR
#define I2C_TIMINGR_PRESC_Pos 28
#define I2C_TIMINGR_SCLDEL_Pos 20
#define I2C_TIMINGR_SDADEL_Pos 16
#define I2C_TIMINGR_SCLH_Pos 8
#define I2C_TIMINGR_SCLL_Pos 0
i2c_speed_t curI2Cspeed = I2C_SPEED_10K;
extern volatile uint32_t Tms;
static uint32_t cntr;
volatile uint8_t I2C_scan_mode = 0; // == 1 when I2C is in scan mode
static uint8_t i2caddr = I2C_ADDREND; // address for `scan`, not active
void i2c_setup(i2c_speed_t speed){
if(speed >= I2C_SPEED_1M) speed = curI2Cspeed;
else curI2Cspeed = speed;
uint8_t PRESC, SCLDEL = 4, SDADEL = 2, SCLH, SCLL;
I2C1->CR1 = 0;
// I2C
RCC->CFGR3 |= RCC_CFGR3_I2C1SW; // use sysclock for timing
switch(curI2Cspeed){
case I2C_SPEED_10K:
PRESC = 0x0B;
SCLH = 0xC3;
SCLL = 0xC7;
break;
case I2C_SPEED_100K:
PRESC = 0x0B;
SCLH = 0x0F;
SCLL = 0x13;
break;
case I2C_SPEED_400K:
SDADEL = 3;
SCLDEL = 3;
PRESC = 5;
SCLH = 3;
SCLL = 9;
break;
case I2C_SPEED_1M:
default:
SDADEL = 0;
SCLDEL = 1;
PRESC = 5;
SCLH = 1;
SCLL = 3;
break;
}
I2C1->TIMINGR = (PRESC<<I2C_TIMINGR_PRESC_Pos) | (SCLDEL<<I2C_TIMINGR_SCLDEL_Pos) |
(SDADEL<<I2C_TIMINGR_SDADEL_Pos) | (SCLH<<I2C_TIMINGR_SCLH_Pos) | (SCLL<< I2C_TIMINGR_SCLL_Pos);
if(speed < I2C_SPEED_400K){
SYSCFG->CFGR1 &= ~SYSCFG_CFGR1_I2C_FMP_I2C1;
}else{ // activate FM+
SYSCFG->CFGR1 |= SYSCFG_CFGR1_I2C_FMP_I2C1;
}
I2C1->ICR = 0xffff; // clear all errors
I2C1->CR1 = I2C_CR1_PE;
}
void i2c_stop(){
I2C1->CR1 = 0;
}
/**
* write command byte to I2C
* @param addr - device address (TSYS01_ADDR0 or TSYS01_ADDR1)
* @param data - bytes to write
* @param nbytes - amount of bytes to write
* @param stop - to set STOP
* @return 0 if error
*/
static uint8_t i2c_writes(uint8_t addr, uint8_t *data, uint8_t nbytes, uint8_t stop){
cntr = Tms;
I2C1->CR1 = 0; // clear busy flag
I2C1->ICR = 0x3f38; // clear all errors
I2C1->CR1 = I2C_CR1_PE;
while(I2C1->ISR & I2C_ISR_BUSY){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){
DBG("Line busy\n");
return 0; // check busy
}}
cntr = Tms;
while(I2C1->CR2 & I2C_CR2_START){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){
return 0; // check start
}}
//I2C1->ICR = 0x3f38; // clear all errors
I2C1->CR2 = nbytes << 16 | addr;
if(stop) I2C1->CR2 |= I2C_CR2_AUTOEND; // autoend
// now start transfer
I2C1->CR2 |= I2C_CR2_START;
for(int i = 0; i < nbytes; ++i){
cntr = Tms;
while(!(I2C1->ISR & I2C_ISR_TXIS)){ // ready to transmit
IWDG->KR = IWDG_REFRESH;
if(I2C1->ISR & I2C_ISR_NACKF){
I2C1->ICR |= I2C_ICR_NACKCF;
DBG("NAK\n");
return 0;
}
if(Tms - cntr > I2C_TIMEOUT){
DBG("Timeout\n");
return 0;
}
}
I2C1->TXDR = data[i]; // send data
}
// wait for data gone
while(I2C1->ISR & I2C_ISR_BUSY){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){break;}
}
return 1;
}
uint8_t i2c_write(uint8_t addr, uint8_t *data, uint8_t nbytes){
return i2c_writes(addr, data, nbytes, 1);
}
/**
* read nbytes of data from I2C line
* `data` should be an array with at least `nbytes` length
* @return 1 if all OK, 0 if NACK or no device found
*/
static uint8_t i2c_readb(uint8_t addr, uint8_t *data, uint8_t nbytes, uint8_t busychk){
if(busychk){
cntr = Tms;
while(I2C1->ISR & I2C_ISR_BUSY){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){
DBG("Line busy\n");
return 0; // check busy
}}
}
cntr = Tms;
while(I2C1->CR2 & I2C_CR2_START){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){
DBG("No start\n");
return 0; // check start
}}
// read N bytes
I2C1->CR2 = (nbytes<<16) | addr | 1 | I2C_CR2_AUTOEND | I2C_CR2_RD_WRN;
I2C1->CR2 |= I2C_CR2_START;
uint8_t i;
for(i = 0; i < nbytes; ++i){
cntr = Tms;
while(!(I2C1->ISR & I2C_ISR_RXNE)){ // wait for data
IWDG->KR = IWDG_REFRESH;
if(I2C1->ISR & I2C_ISR_NACKF){
I2C1->ICR |= I2C_ICR_NACKCF;
DBG("NAK\n");
return 0;
}
if(Tms - cntr > I2C_TIMEOUT){
DBG("Timeout\n");
return 0;
}
}
*data++ = I2C1->RXDR;
}
return 1;
}
uint8_t i2c_read(uint8_t addr, uint8_t *data, uint8_t nbytes){
return i2c_readb(addr, data, nbytes, 1);
}
// read register reg
uint8_t i2c_read_reg(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t nbytes){
if(!i2c_writes(addr, &reg, 1, 0)) return 0;
return i2c_readb(addr, data, nbytes, 0);
}
void i2c_init_scan_mode(){
i2caddr = 1;
I2C_scan_mode = 1;
}
// return 1 if next addr is active & return in as `addr`
// if addresses are over, return 1 and set addr to I2C_NOADDR
// if scan mode inactive, return 0 and set addr to I2C_NOADDR
int i2c_scan_next_addr(uint8_t *addr){
*addr = i2caddr;
if(i2caddr == I2C_ADDREND){
*addr = I2C_ADDREND;
I2C_scan_mode = 0;
return 0;
}
if(!i2c_read_reg((i2caddr++)<<1, 0, NULL, 0)) return 0;
return 1;
}

View File

@@ -0,0 +1,47 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stm32f0.h>
#define I2C_ADDREND (0x80)
typedef enum{
I2C_SPEED_10K,
I2C_SPEED_100K,
I2C_SPEED_400K,
I2C_SPEED_1M,
I2C_SPEED_AMOUNT
} i2c_speed_t;
extern i2c_speed_t curI2Cspeed;
extern volatile uint8_t I2C_scan_mode;
// timeout of I2C bus in ms
#define I2C_TIMEOUT (100)
void i2c_setup(i2c_speed_t speed);
void i2c_stop();
uint8_t i2c_read(uint8_t addr, uint8_t *data, uint8_t nbytes);
uint8_t i2c_read_reg(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t nbytes);
uint8_t i2c_write(uint8_t addr, uint8_t *data, uint8_t nbytes);
void i2c_init_scan_mode();
int i2c_scan_next_addr(uint8_t *addr);

View File

@@ -0,0 +1,51 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "can.h"
#include "canproto.h"
#include "flash.h"
#include "gpioproto.h"
#include "hardware.h"
volatile uint32_t Tms = 0;
/* Called when systick fires */
void sys_tick_handler(void){
++Tms;
}
int main(void){
sysreset();
SysTick_Config(6000, 1);
StartHSE();
flashstorage_init();
hardware_setup();
USB_setup();
CAN_setup(the_conf.CANspeed);
RCC->CSR |= RCC_CSR_RMVF; // remove reset flags
#ifndef EBUG
iwdg_setup();
#endif
while (1){
IWDG->KR = IWDG_REFRESH; // refresh watchdog
CANUSB_process();
GPIO_process();
}
return 0;
}

View File

@@ -0,0 +1,4 @@
set FLASH_SIZE 0x8000
source [find interface/stlink-v2-1.cfg]
source [find target/stm32f0x.cfg]

View File

@@ -0,0 +1,177 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
#include <string.h>
#include "pwm.h"
static volatile TIM_TypeDef * const timers[TIMERS_AMOUNT] = {
[TIM1_IDX] = TIM1,
[TIM2_IDX] = TIM2,
[TIM3_IDX] = TIM3,
[TIM14_IDX] = TIM14,
[TIM16_IDX] = TIM16,
};
#if 0
PWM (start - collisions):
PxN XY (XY: TIMX_CHY)
PA1 22 *
PA2 23 **
PA3 24 ***
PA6 161
PA7 141
PA9 12
PA10 13
PB0 33
PB1 34
PB3 22 *
PB4 31
PB5 32
PB10 23 **
PB11 24 ***
-> need to set up timers / channels
TIM1 / 2 3
TIM2 / 2 3 4
TIM3 / 1 2 3 4
TIM14 / 1
TIM16 / 1
#endif
#define PT(i, ch) {.timidx = i, .chidx = ch}
#define PTC(i, ch, P, p) {.timidx = i, .chidx = ch, .collision = 1, .collport = P, .collpin = p}
static const pwmtimer_t timer_map[2][16] = {
[0] = {
[1] = PTC(TIM2_IDX, 1, 1, 3),
[2] = PTC(TIM2_IDX, 2, 1, 10),
[3] = PTC(TIM2_IDX, 3, 1, 11),
[6] = PT(TIM16_IDX, 0),
[7] = PT(TIM14_IDX, 0),
[9] = PT(TIM1_IDX, 1),
[10] = PT(TIM1_IDX, 2)
},
[1] = {
[0] = PT(TIM3_IDX, 2),
[1] = PT(TIM3_IDX, 3),
[3] = PTC(TIM2_IDX, 1, 0, 1),
[4] = PT(TIM3_IDX, 0),
[5] = PT(TIM3_IDX, 1),
[10] = PTC(TIM2_IDX, 2, 0, 2),
[11] = PTC(TIM2_IDX, 3, 0, 3)
}
};
#undef PT
#undef PTC
// counter of used channels (0 - timer OFF)
static uint8_t channel_counter[TIMERS_AMOUNT] = {0};
void pwm_setup(){
// setup; start/stop only by user request
for(int i = 1; i < TIMERS_AMOUNT; ++i){ // start from 1 as 0 forbidden
volatile TIM_TypeDef *timer = timers[i];
timer->CR1 = 0;
timer->PSC = 7; // 6MHz for 23.4kHz PWM
timer->ARR = 254; // 255 == 100%
// PWM mode 1, preload enable
timer->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE |
TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE;
timer->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE |
TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE;
timer->BDTR |= TIM_BDTR_MOE; // enable main output (need for some timers)
timer->EGR |= TIM_EGR_UG; // force update generation
}
bzero(channel_counter, sizeof(channel_counter));
}
/**
* @brief canPWM - check if pin have PWM ability
* @param port - port (0/1 for GPIOA/GPIOB)
* @param pin - pin (0..15)
* @param t (o) - struct for pin's PWM timer
* @return TRUE if can, FALSE if no
*/
int canPWM(uint8_t port, uint8_t pin, pwmtimer_t *t){
if(port > 1 || pin > 15) return 0;
if(t) *t = timer_map[port][pin];
if(timer_map[port][pin].timidx == TIM_UNSUPPORTED) return FALSE;
return TRUE;
}
/**
* @brief startPWM - run PWM on given port/pin
* @param port
* @param pin
* @return FALSE if unsupported
*/
int startPWM(uint8_t port, uint8_t pin){
timidx_t idx = timer_map[port][pin].timidx;
if(idx == TIM_UNSUPPORTED) return FALSE;
volatile TIM_TypeDef *timer = timers[idx];
uint8_t chidx = timer_map[port][pin].chidx;
uint32_t chen = TIM_CCER_CC1E << (chidx<<2);
volatile uint32_t *CCR = &timers[idx]->CCR1 + timer_map[port][pin].chidx;
*CCR = 0; // set initial value to zero
if(0 == (timer->CCER & chen)){
if(0 == channel_counter[idx]++) timer->CR1 |= TIM_CR1_CEN; // start timer if need
timer->CCER |= chen; // enable channel
}
return TRUE;
}
// stop given PWM channel and stop timer if there's no used channels
void stopPWM(uint8_t port, uint8_t pin){
timidx_t idx = timer_map[port][pin].timidx;
if(idx == TIM_UNSUPPORTED) return;
volatile TIM_TypeDef *timer = timers[idx];
uint8_t chidx = timer_map[port][pin].chidx;
uint32_t chen = TIM_CCER_CC1E << (chidx<<2);
if(timer->CCER & chen){
if(0 == --channel_counter[idx]) timer->CR1 &= ~TIM_CR1_CEN; // stop timer
timer->CCER &= ~chen;
}
}
/**
* @brief setPWM - set PWM value for given pin on given port
* @param port
* @param pin
* @param val - 0..255
* @return FALSE if pin can't PWM
*/
int setPWM(uint8_t port, uint8_t pin, uint8_t val){
timidx_t idx = timer_map[port][pin].timidx;
if(idx == TIM_UNSUPPORTED) return FALSE;
volatile uint32_t *CCR = &timers[idx]->CCR1 + timer_map[port][pin].chidx;
*CCR = val;
return TRUE;
}
/**
* @brief getPWM - get PWM value for given pin on given port
* @param port
* @param pin
* @return -1 if there's no PWM on that pin
*/
int16_t getPWM(uint8_t port, uint8_t pin){
timidx_t idx = timer_map[port][pin].timidx;
if(idx == TIM_UNSUPPORTED) return -1;
volatile uint32_t *CCR = &timers[idx]->CCR1 + timer_map[port][pin].chidx;
return (int16_t) *CCR;
}

View File

@@ -0,0 +1,49 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
// used timers
typedef enum{
TIM_UNSUPPORTED = 0,
TIM1_IDX,
TIM2_IDX,
TIM3_IDX,
TIM14_IDX,
TIM16_IDX,
TIMERS_AMOUNT
}timidx_t;
// Timers for PWM
typedef struct{
timidx_t timidx : 3; // timer index from array of timers used
uint8_t chidx : 2; // channel index (0..3)
uint8_t collision : 1; // have collision with other channel (1)
uint8_t collport : 1; // collision port index (0 - GPIOA, 1 - GPIOB)
uint8_t collpin : 4; // collision pin index (0..15)
} pwmtimer_t;
void pwm_setup();
int canPWM(uint8_t port, uint8_t pin, pwmtimer_t *t);
int startPWM(uint8_t port, uint8_t pin);
void stopPWM(uint8_t port, uint8_t pin);
int setPWM(uint8_t port, uint8_t pin, uint8_t val);
int16_t getPWM(uint8_t port, uint8_t pin);

View File

@@ -0,0 +1,203 @@
/*
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stm32f0.h>
#include "ringbuffer.h"
#define CHK(b) do{if(!b) return -1;}while(0)
static int datalen(ringbuffer *b){
if(b->tail >= b->head) return (b->tail - b->head);
else return (b->length - b->head + b->tail);
}
// stored data length
int RB_datalen(ringbuffer *b){
CHK(b);
if(0 == datalen(b)) return 0; // don't block for empty RO operations
if(b->busy) return -1;
b->busy = 1;
int l = datalen(b);
b->busy = 0;
return l;
}
static int hasbyte(ringbuffer *b, uint8_t byte){
if(b->head == b->tail) return -1; // no data in buffer
int startidx = b->head;
if(b->head > b->tail){ //
for(int found = b->head; found < b->length; ++found)
if(b->data[found] == byte) return found;
startidx = 0;
}
for(int found = startidx; found < b->tail; ++found)
if(b->data[found] == byte) return found;
return -1;
}
/**
* @brief RB_hasbyte - check if buffer has given byte stored
* @param b - buffer
* @param byte - byte to find
* @return index if found, -1 if none or busy
*/
int RB_hasbyte(ringbuffer *b, uint8_t byte){
CHK(b);
if(b->busy) return -1;
b->busy = 1;
int ret = hasbyte(b, byte);
b->busy = 0;
return ret;
}
// increment head or tail
TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){
*what += n;
if(*what >= b->length) *what -= b->length;
}
static int read(ringbuffer *b, uint8_t *s, int len){
int l = datalen(b);
if(!l) return 0;
if(l > len) l = len;
int _1st = b->length - b->head;
if(_1st > l) _1st = l;
if(_1st > len) _1st = len;
memcpy(s, b->data + b->head, _1st);
if(_1st < len && l > _1st){
memcpy(s+_1st, b->data, l - _1st);
incr(b, &b->head, l);
return l;
}
incr(b, &b->head, _1st);
return _1st;
}
/**
* @brief RB_read - read data from ringbuffer
* @param b - buffer
* @param s - array to write data
* @param len - max len of `s`
* @return bytes read or -1 if busy
*/
int RB_read(ringbuffer *b, uint8_t *s, int len){
CHK(b);
if(!s || len < 1) return -1;
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int r = read(b, s, len);
b->busy = 0;
return r;
}
// length of data from current position to `byte` (including byte)
static int lento(ringbuffer *b, uint8_t byte){
int idx = hasbyte(b, byte);
if(idx < 0) return 0;
int partlen = idx + 1 - b->head;
// now calculate length of new data portion
if(idx < b->head) partlen += b->length;
return partlen;
}
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
int partlen = lento(b, byte);
if(!partlen) return 0;
if(partlen > len) return -1;
return read(b, s, partlen);
}
/**
* @brief RB_readto fill array `s` with data until byte `byte` (with it)
* @param b - ringbuffer
* @param byte - check byte
* @param s - buffer to write data or NULL to clear data
* @param len - length of `s` or 0 to clear data
* @return amount of bytes written (negative, if len<data in buffer or buffer is busy)
*/
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
CHK(b);
if(!s || len < 1) return -1;
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int n = 0;
if(s && len > 0){
n = readto(b, byte, s, len);
}else{
incr(b, &b->head, lento(b, byte)); // just throw data out
}
b->busy = 0;
return n;
}
int RB_datalento(ringbuffer *b, uint8_t byte){
CHK(b);
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int n = lento(b, byte);
b->busy = 0;
return n;
}
// if l < rest of buffer, truncate and return actually written bytes
static int write(ringbuffer *b, const uint8_t *str, int l){
int r = b->length - 1 - datalen(b); // rest length
if(r < 1) return 0;
if(l > r) l = r;
int _1st = b->length - b->tail;
if(_1st > l) _1st = l;
memcpy(b->data + b->tail, str, _1st);
if(_1st < l){ // add another piece from start
memcpy(b->data, str+_1st, l-_1st);
}
incr(b, &b->tail, l);
return l;
}
/**
* @brief RB_write - write some data to ringbuffer
* @param b - buffer
* @param str - data
* @param l - length
* @return amount of bytes written or -1 if busy
*/
int RB_write(ringbuffer *b, const uint8_t *str, int l){
CHK(b);
if(!str || l < 1) return -1;
if(b->length - datalen(b) < 2) return 0;
if(b->busy) return -1;
b->busy = 1;
int w = write(b, str, l);
b->busy = 0;
return w;
}
// just delete all information in buffer `b`
int RB_clearbuf(ringbuffer *b){
CHK(b);
if(b->busy) return -1;
b->busy = 1;
b->head = 0;
b->tail = 0;
bzero(b->data, b->length);
b->busy = 0;
return 1;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
typedef struct{
uint8_t *data; // data buffer
const int length; // its length
int head; // head index
int tail; // tail index
volatile int busy; // == TRUE if buffer is busy now
} ringbuffer;
int RB_read(ringbuffer *b, uint8_t *s, int len);
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len);
int RB_hasbyte(ringbuffer *b, uint8_t byte);
int RB_write(ringbuffer *b, const uint8_t *str, int l);
int RB_datalen(ringbuffer *b);
int RB_datalento(ringbuffer *b, uint8_t byte);
int RB_clearbuf(ringbuffer *b);

View File

@@ -0,0 +1,83 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
#include "flash.h"
#include "hardware.h"
#include "spi.h"
// get best prescaler to fit given frequency
static uint16_t get_baudrate_prescaler(uint32_t speed_hz){
uint32_t freq = peripherial_clock;
uint32_t best_i = 7;
uint32_t best_err = 0xFFFFFFFF;
for(int i = 0; i < 8; i++){
freq >>= 1;
uint32_t err = (freq > speed_hz) ? (freq - speed_hz) : (speed_hz - freq);
if(err < best_err){
best_err = err;
best_i = i;
}else if(err > best_err) break;
}
return best_i;
}
void spi_setup(){
RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
RCC->APB2RSTR = 0;
SPI1->CR1 = 0;
uint16_t br = get_baudrate_prescaler(the_conf.spiconfig.speed);
uint32_t cr1 = SPI_CR1_MSTR | (br << 3) | SPI_CR1_SSM | SPI_CR1_SSI;
if(the_conf.spiconfig.cpol) cr1 |= SPI_CR1_CPOL;
if(the_conf.spiconfig.cpha) cr1 |= SPI_CR1_CPHA;
if(the_conf.spiconfig.lsbfirst) cr1 |= SPI_CR1_LSBFIRST;
// there would be a lot of problem to set rxonly!
//if(the_conf.spiconfig.rxonly) cr1 |= SPI_CR1_RXONLY;
SPI1->CR1 = cr1;
// rxne after 8bits, ds 8bit
SPI1->CR2 = /*SPI_CR2_SSOE | */ SPI_CR2_FRXTH| SPI_CR2_DS_2|SPI_CR2_DS_1|SPI_CR2_DS_0;
SPI1->CR1 |= SPI_CR1_SPE;
}
void spi_stop(){
SPI1->CR1 &= ~SPI_CR1_SPE;
}
// return -1 if SPI isn't run or got error
int spi_transfer(const uint8_t *tx, uint8_t *rx, int len){
if(len < 1 || !(SPI1->CR1 & SPI_CR1_SPE)) return -1;
int i;
for(i = 0; i < len; ++i){
uint32_t timeout = 1000000;
while(!(SPI1->SR & SPI_SR_TXE)){
if (--timeout == 0) return -1; // error by timeout: TX isn't ready
}
uint8_t out = (tx) ? tx[i] : 0;
*((volatile uint8_t*)&SPI1->DR) = out;
timeout = 1000000;
while(!(SPI1->SR & SPI_SR_RXNE)){
if(--timeout == 0) return 0;
}
uint8_t in = *((volatile uint8_t*)&SPI1->DR);
if(rx) rx[i] = in;
}
//while(SPI1->SR & SPI_SR_BSY){ }
return i;
}

View File

@@ -0,0 +1,25 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
void spi_setup();
void spi_stop();
int spi_transfer(const uint8_t *tx, uint8_t *rx, int len);

View File

@@ -0,0 +1,265 @@
/*
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
/**
* @brief hexdump - dump hex array by 16 bytes in string
* @param sendfun - function to send data
* @param arr - array to dump
* @param len - length of `arr`
*/
void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){
char buf[52], *bptr = buf;
for(uint16_t l = 0; l < len; ++l, ++arr){
for(int16_t j = 1; j > -1; --j){
register uint8_t half = (*arr >> (4*j)) & 0x0f;
if(half < 10) *bptr++ = half + '0';
else *bptr++ = half - 10 + 'a';
}
if(l % 16 == 15){
*bptr++ = '\n';
*bptr = 0;
sendfun(buf);
bptr = buf;
}else *bptr++ = ' ';
}
if(bptr != buf){
*bptr++ = '\n';
*bptr = 0;
sendfun(buf);
}
}
/**
* @brief _2str - convert value into string buffer
* @param val - |value|
* @param minus - ==0 if value > 0
* @return buffer with number
*/
static char *_2str(uint32_t val, uint8_t minus){
static char strbuf[12];
char *bufptr = &strbuf[11];
*bufptr = 0;
if(!val){
*(--bufptr) = '0';
}else{
while(val){
uint32_t x = val / 10;
*(--bufptr) = (val - 10*x) + '0';
val = x;
}
}
if(minus) *(--bufptr) = '-';
return bufptr;
}
// return string with number `val`
char *u2str(uint32_t val){
return _2str(val, 0);
}
char *i2str(int32_t i){
uint8_t minus = 0;
uint32_t val;
if(i < 0){
minus = 1;
val = -i;
}else val = i;
return _2str(val, minus);
}
/**
* @brief uhex2str - print 32bit unsigned int as hex
* @param val - value
* @return string with number
*/
char *uhex2str(uint32_t val){
static char buf[12] = "0x";
int npos = 2;
uint8_t *ptr = (uint8_t*)&val + 3;
int8_t i, j, z=1;
for(i = 0; i < 4; ++i, --ptr){
if(*ptr == 0){ // omit leading zeros
if(i == 3) z = 0;
if(z) continue;
}
else z = 0;
for(j = 1; j > -1; --j){
uint8_t half = (*ptr >> (4*j)) & 0x0f;
if(half < 10) buf[npos++] = half + '0';
else buf[npos++] = half - 10 + 'a';
}
}
buf[npos] = 0;
return buf;
}
/**
* @brief omit_spaces - eliminate leading spaces and other trash in string
* @param buf - string
* @return - pointer to first character in `buf` > ' '
*/
const char *omit_spaces(const char *buf){
while(*buf){
if(*buf > ' ') break;
++buf;
}
return buf;
}
/**
* @brief getdec - read decimal number & return pointer to next non-number symbol
* @param buf - string
* @param N - number read
* @return Next non-number symbol. In case of overflow return `buf` and N==0xffffffff
*/
static const char *getdec(const char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '9'){
break;
}
if(num > 429496729 || (num == 429496729 && c > '5')){ // overflow
*N = 0xffffff;
return start;
}
num *= 10;
num += c - '0';
++buf;
}
*N = num;
return buf;
}
// read hexadecimal number (without 0x prefix!)
const char *gethex(const char *buf, uint32_t *N){
if(!buf || !N) return NULL;
const char *start = buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
uint8_t M = 0;
if(c >= '0' && c <= '9'){
M = '0';
}else if(c >= 'A' && c <= 'F'){
M = 'A' - 10;
}else if(c >= 'a' && c <= 'f'){
M = 'a' - 10;
}
if(M){
if(num & 0xf0000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 4;
num += c - M;
}else{
break;
}
++buf;
}
*N = num;
return buf;
}
// read octal number (without 0 prefix!)
static const char *getoct(const char *buf, uint32_t *N){
const char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '7'){
break;
}
if(num & 0xe0000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 3;
num += c - '0';
++buf;
}
*N = num;
return buf;
}
// read binary number (without b prefix!)
static const char *getbin(const char *buf, uint32_t *N){
const char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '1'){
break;
}
if(num & 0x80000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 1;
if(c == '1') num |= 1;
++buf;
}
*N = num;
return buf;
}
/**
* @brief getnum - read uint32_t from string (dec, hex or bin: 127, 0x7f, 0b1111111)
* @param buf - buffer with number and so on
* @param N - the number read
* @return pointer to first non-number symbol in buf
* (if it is == buf, there's no number or if *N==0xffffffff there was overflow)
*/
const char *getnum(const char *txt, uint32_t *N){
const char *nxt = NULL;
const char *s = omit_spaces(txt);
if(*s == '0'){ // hex, oct or 0
if(s[1] == 'x' || s[1] == 'X'){ // hex
nxt = gethex(s+2, N);
if(nxt == s+2) nxt = (char*)txt;
}else if(s[1] > '0'-1 && s[1] < '8'){ // oct
nxt = getoct(s+1, N);
if(nxt == s+1) nxt = (char*)txt;
}else{ // 0
nxt = s+1;
*N = 0;
}
}else if(*s == 'b' || *s == 'B'){
nxt = getbin(s+1, N);
if(nxt == s+1) nxt = (char*)txt;
}else{
nxt = getdec(s, N);
if(nxt == s) nxt = (char*)txt;
}
return nxt;
}
// get signed integer
const char *getint(const char *txt, int32_t *I){
const char *s = omit_spaces(txt);
int32_t sign = 1;
uint32_t U;
if(*s == '-'){
sign = -1;
++s;
}
const char *nxt = getnum(s, &U);
if(nxt == s) return txt;
if(U & 0x80000000) return txt; // overfull
*I = sign * (int32_t)U;
return nxt;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef USBIF
#error "Define USBIF to used interface before including this!"
#endif
#include <stdint.h>
#include <string.h>
#include "usb_dev.h"
#include "usb_descr.h"
#include "version.inc"
// DEBUG/RELEASE build
#ifdef EBUG
#define RLSDBG "debug"
#else
#define RLSDBG "release"
#endif
#define REPOURL "https://github.com/eddyem/stm32samples/tree/master/F0:F030,F042,F072/usbcan_gpio " RLSDBG " build #" BUILD_NUMBER "@" BUILD_DATE "\n"
// max string len to '\n' (including '\0')
#define MAXSTRLEN 256
void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len);
const char *u2str(uint32_t val);
const char *i2str(int32_t i);
const char *uhex2str(uint32_t val);
const char *gethex(const char *buf, uint32_t *N);
char *getnum(const char *txt, uint32_t *N);
char *omit_spaces(const char *buf);
char *getint(const char *txt, int32_t *I);
#define printu(u) USB_sendstr(USBIF, u2str(u))
#define printuhex(u) USB_sendstr(USBIF, uhex2str(u))

View File

@@ -0,0 +1,313 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
#include <string.h>
#include "flash.h"
#include "hardware.h"
#include "ringbuffer.h"
#include "usart.h"
// We share DMAch4/5 for both USARTs, so only one can work in a time!!!
#define USARTSNO 2
// buffers for DMA or interrupt-driven data management
// here we use INDEX (usart number minus 1)
static uint8_t inbuffer[DMARXBUFSZ]; // DMA in buffer
static uint8_t rbbuffer[RXRBSZ]; // for in ringbuffer
static uint8_t outbuffer[DMATXBUFSZ]; // DMA out buffer
static volatile uint8_t TXrdy = 1; // TX DMA ready
static volatile uint8_t RXrdy = 0; // == 1 when got IDLE or '\n' (only when `monitoring` is on
static uint8_t textformat = 0; // out by '\n'-terminated lines
static uint8_t monitor = 0; // monitor USART rx
static int dma_read_idx = 0; // start of data in DMA inbuffers
static int curUSARTidx = -1; // working USART index (0/1), -1 if unconfigured
static ringbuffer RBin = {.data = rbbuffer, .length = RXRBSZ};
static volatile USART_TypeDef* const Usarts[USARTSNO] = {USART1, USART2};
static uint8_t const UIRQs[USARTSNO] = {USART1_IRQn, USART2_IRQn};
static usartconf_t usartconfig;
static uint8_t usartconfig_notinited = 1;
#define CHKUSARTCONFIG() do{if(usartconfig_notinited) chkusartconf(NULL);}while(0)
// check config and if all OK, copy to local; if c == NULL, check local config and set defaults to wrong values
// return FALSE if some parameters was changed to default (in this case not copy to local)
int chkusartconf(usartconf_t *c){
int ret = TRUE;
if(usartconfig_notinited){
usartconfig = the_conf.usartconfig;
usartconfig_notinited = 0;
}
uint8_t copyto = TRUE;
if(!c){
copyto = FALSE;
c = &usartconfig;
}
if(c->speed < USART_MIN_SPEED || c->speed > USART_MAX_SPEED){
c->speed = USART_DEFAULT_SPEED;
ret = FALSE;
}
// another tests could be here (like stopbits etc, if you wish)
if(ret && copyto) usartconfig = *c;
return ret;
}
// just give default speed
void get_defusartconf(usartconf_t *c){
if(!c) return;
bzero(c, sizeof(usartconf_t));
c->speed = USART_DEFAULT_SPEED;
}
int get_curusartconf(usartconf_t *c){
CHKUSARTCONFIG();
if(!c) return FALSE;
*c = usartconfig;
return TRUE;
}
/**
* @brief usart_config - configure US[A]RT based on usb_LineCoding
* @param usartconf (io) - (modified to real speeds); if NULL - get current
* @return TRUE if all OK
*/
int usart_config(usartconf_t *uc){
CHKUSARTCONFIG();
if(uc && !chkusartconf(uc)) return FALSE;
if(0 == usartconfig.RXen && 0 == usartconfig.TXen){ // no Rx/Tx
usart_stop();
return FALSE;
}
SYSCFG->CFGR1 |= SYSCFG_CFGR1_USART1RX_DMA_RMP | SYSCFG_CFGR1_USART1TX_DMA_RMP; // both USARTs on DMA1ch4(tx)/5(rx)
// all clocking and GPIO config should be done in hardware_setup() & gpio_reinit()!
if(curUSARTidx != -1) usart_stop(); // disable previous USART if enabled
uint8_t No = usartconfig.idx;
volatile USART_TypeDef *U = Usarts[No];
// Disable USART while configuring
U->CR1 = 0;
U->ICR = 0xFFFFFFFF; // Clear all interrupt flags
// Assuming oversampling by 16 (default after reset). For higher baud rates you might use by 8.
U->BRR = peripherial_clock / (usartconfig.speed);
usartconfig.speed = peripherial_clock / U->BRR; // fix for real speed
uint32_t cr1 = 0, cr3 = 0;
textformat = usartconfig.textproto;
monitor = usartconfig.monitor;
// Enable transmitter, receiver, and interrupts (optional)
if(usartconfig.RXen){
cr1 |= USART_CR1_RE;
cr3 |= USART_CR3_DMAR;
// format: 8N1, so CR2 used only for character match (if need)
if(usartconfig.textproto){
U->CR2 = USART_CR2_ADD_VAL('\n'); // buffer text data by EOL
cr1 |= USART_CR1_CMIE;
}else cr1 |= USART_CR1_IDLEIE; // buffer binary data by IDLE flag
}
if(usartconfig.TXen){
cr1 |= USART_CR1_TE;
// DMA Tx
volatile DMA_Channel_TypeDef *T = DMA1_Channel4;
T->CCR = 0;
T->CPAR = (uint32_t) &U->TDR;
T->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE;
cr3 |= USART_CR3_DMAT;
}
// Main config
U->CR1 = cr1;
U->CR3 = cr3;
curUSARTidx = No;
// all OK -> copy to global config
the_conf.usartconfig = usartconfig;
return TRUE;
}
// start NUMBER
int usart_start(){
if(curUSARTidx == -1) return FALSE;
volatile USART_TypeDef *U = Usarts[curUSARTidx];
NVIC_EnableIRQ(UIRQs[curUSARTidx]); // copy to ring buffer after each '\n' in text mode or IDLE in binary
NVIC_EnableIRQ(DMA1_Channel4_5_IRQn);
// reset Rx DMA
if(U->CR1 & USART_CR1_RE){
volatile DMA_Channel_TypeDef *R = DMA1_Channel5;
dma_read_idx = 0;
R->CCR = 0;
RB_clearbuf(&RBin);
R->CPAR = (uint32_t) &U->RDR;
R->CMAR = (uint32_t) inbuffer;
R->CNDTR = DMARXBUFSZ;
R->CCR = DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_EN;
}
U->CR1 |= USART_CR1_UE; // enable USARTx
U->ICR = 0xFFFFFFFF; // Clear flags
TXrdy = 1;
return TRUE;
}
/**
* @brief usart_stop - turn off U[S]ART for given interface
* @param ifNo - interface number
*/
void usart_stop(){
if(curUSARTidx == -1) return;
Usarts[curUSARTidx]->CR1 &= ~USART_CR1_UE;
NVIC_DisableIRQ(DMA1_Channel4_5_IRQn);
NVIC_DisableIRQ(UIRQs[curUSARTidx]);
curUSARTidx = -1;
}
/**
* @brief usart_receive - receive data from USART by user request (return none if monitor == 1)
* @param buf (io) - user buffer
* @param len - `buf` length
* @return -1 if USART not configured or amount of data from ringbuffer
*/
int usart_receive(uint8_t *buf, int len){
if(curUSARTidx == -1) return -1;
if(textformat){
int toN = RB_datalento(&RBin, '\n');
if(toN == 0) return 0;
if(toN < len) len = toN; // read only until '\n' in text format
}
int got = RB_read(&RBin, buf, len);
if(got < 0) got = 0;
return got;
}
/**
* @brief usart_process - send/receive processing
* Try to send data from output ringbuffer, check input DMA buffer and full input ringbuffer
*/
/**
* @brief usart_process - USART data processing
* @param buf - buffer for "async" messages (when monitor==1)
* @param len - length of `buf`
* @return amount of bytes read or -1 if USART isn't active
*/
int usart_process(uint8_t *buf, int len){
if(curUSARTidx == -1 || !(Usarts[curUSARTidx]->CR1 & USART_CR1_UE)) return -1; // none activated or started
int ret = 0; // returned value
// Input data
int remained = DMA1_Channel5->CNDTR;
int write_idx = DMARXBUFSZ - remained; // next symbol to be written
int available = (write_idx - dma_read_idx); // length of data available
if(available < 0) available += DMARXBUFSZ; // write to the left of read
if(available == 0){
RXrdy = 0; // clear old ready flag if got no data
return 0;
}
int monitored_len = available;
uint8_t locmonitor = monitor; // if `buf` not pointed, set this flag to zero
if(available > 0){
if(locmonitor){
if(buf && len > 0){
if(len < monitored_len) monitored_len = len;
}else locmonitor = 0;
}
// TODO: force copying data to "async" buffer in case of overflow danger
if(available >= (DMARXBUFSZ/2) || RXrdy){ // enough data or lonely couple of bytes but need to show
// copy data in one or two chunks (wrap handling)
int wrOK = FALSE;
// check if we can write to RB `available` bytes
int canRB = TRUE;
if(!locmonitor){
int rballow = RBin.length - 1 - RB_datalen(&RBin);
if(rballow < available) canRB = FALSE;
}
if(dma_read_idx + available <= DMARXBUFSZ){ // head before tail
if(locmonitor){
memcpy(buf, &inbuffer[dma_read_idx], monitored_len);
ret = monitored_len;
wrOK = TRUE;
}else{
if(canRB && available == RB_write(&RBin, &inbuffer[dma_read_idx], available)) wrOK = TRUE;
else if(buf && len > 0) ret = RB_read(&RBin, buf, len); // ringbuffer overfull -> emerge clearing
}
}else{ // head after tail - two chunks
int first = DMARXBUFSZ - dma_read_idx;
if(locmonitor){
memcpy(buf, &inbuffer[dma_read_idx], first);
memcpy(buf + first, inbuffer, monitored_len - first);
ret = monitored_len;
wrOK = TRUE;
}else{
if(canRB && (first == RB_write(&RBin, &inbuffer[dma_read_idx], first)) &&
(available - first) == RB_write(&RBin, inbuffer, available - first)) wrOK = TRUE;
else if(buf && len > 0) ret = RB_read(&RBin, buf, len);
}
}
if(wrOK){
RXrdy = 0;
dma_read_idx = write_idx; // update read pointer
}
}
}else if(available < 0){ // das ist fantastisch!
if(buf && len > 0) ret = RB_read(&RBin, buf, len);
DBG("WTF? USART's `available` < 0!!!\n");
}
// we can work with RBout to send more than `usart_send` can
// here we can send next data portion
return ret;
}
// send data buffer
errcodes_t usart_send(const uint8_t *data, int len){
if(curUSARTidx == -1 || !data || len < 1) return ERR_CANTRUN;
if(TXrdy == 0) return ERR_BUSY;
if(len > DMATXBUFSZ) return ERR_OVERFLOW;
memcpy(outbuffer, data, len);
volatile DMA_Channel_TypeDef *T = DMA1_Channel4;
T->CCR &= ~DMA_CCR_EN;
T->CMAR = (uint32_t) outbuffer;
T->CNDTR = len;
TXrdy = 0;
T->CCR |= DMA_CCR_EN; // start new transmission
return ERR_OK;
}
/**
* @brief usart_isr - U[S]ART interrupt: IDLE (for DMA-driven) or
* @param ifno - interface index
*/
static void usart_isr(){
if(curUSARTidx == -1) return; // WTF???
volatile USART_TypeDef *U = Usarts[curUSARTidx];
// IDLE active when we monitor binary data
if((U->ISR & USART_ISR_IDLE) && (U->CR1 & USART_CR1_IDLEIE)){ // try to send collected data (DMA-driven)
RXrdy = 1; // seems like data portion is over - try to send it
}
if((U->ISR & USART_ISR_CMF) && (U->CR1 & USART_CR1_CMIE)){ // character match -> the same for text data
RXrdy = 1;
}
U->ICR = 0xffffffff; // clear all flags
}
void dma1_channel4_5_isr(){ // TX ready, channel5
if(DMA1->ISR & DMA_ISR_TCIF4){
TXrdy = 1;
DMA1->IFCR = DMA_IFCR_CTCIF4;
DMA1_Channel4->CCR &= ~DMA_CCR_EN; // disable DMA channel until next send
}
}
// U[S]ART interrupts
void usart1_isr() {usart_isr();}
void usart2_isr() {usart_isr();}

View File

@@ -0,0 +1,55 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include "gpioproto.h"
// DMA linear buffers for Rx/Tx
#define DMARXBUFSZ 192
#define DMATXBUFSZ 192
// incoming ring buffer - only if there's a lot of data in DMA RX buffer
#define RXRBSZ 384
#define USART_MIN_SPEED 1024
#define USART_MAX_SPEED 1000000
#define USART_DEFAULT_SPEED 9600
typedef struct{
uint32_t speed; // baudrate
uint8_t idx : 1; // Usart idx (0/1 for USART1/USART2)
uint8_t RXen : 1; // enable rx
uint8_t TXen : 1; // enable tx
uint8_t textproto : 1; // match '\n' and force output by lines (if there's enough place in buffers and monitor == 1)
uint8_t monitor : 1; // async output by incoming (over '\n', IDLE or buffer full)
} usartconf_t;
int usart_config(usartconf_t *config);
int chkusartconf(usartconf_t *c);
int usart_start();
void usart_stop();
int get_curusartconf(usartconf_t *c);
void get_defusartconf(usartconf_t *c);
int usart_process(uint8_t *buf, int len);
int usart_receive(uint8_t *buf, int len);
errcodes_t usart_send(const uint8_t *data, int len);

View File

@@ -0,0 +1,248 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h> // memcpy
#include "flash.h" // MAX_IINTERFACE_SZ
#include "usb_descr.h"
// low/high for uint16_t
#define L16(x) (x & 0xff)
#define H16(x) (x >> 8)
static const uint8_t USB_DeviceDescriptor[] = {
USB_DT_DEVICE_SIZE, // bLength
USB_DT_DEVICE, // bDescriptorType
L16(bcdUSB), // bcdUSB_L
H16(bcdUSB), // bcdUSB_H
USB_CLASS_MISC, // bDeviceClass
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0BUFSZ, // bMaxPacketSize
L16(idVendor), // idVendor_L
H16(idVendor), // idVendor_H
L16(idProduct), // idProduct_L
H16(idProduct), // idProduct_H
L16(bcdDevice_Ver), // bcdDevice_Ver_L
H16(bcdDevice_Ver), // bcdDevice_Ver_H
iMANUFACTURER_DESCR, // iManufacturer - indexes of string descriptors in array
iPRODUCT_DESCR, // iProduct
iSERIAL_DESCR, // iSerial
bNumConfigurations // bNumConfigurations
};
static const uint8_t USB_DeviceQualifierDescriptor[] = {
USB_DT_QUALIFIER_SIZE, //bLength
USB_DT_QUALIFIER, // bDescriptorType
L16(bcdUSB), // bcdUSB_L
H16(bcdUSB), // bcdUSB_H
USB_CLASS_PER_INTERFACE, // bDeviceClass
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0BUFSZ, // bMaxPacketSize0
bNumConfigurations, // bNumConfigurations
0 // Reserved
};
#define wTotalLength (USB_DT_CONFIG_SIZE + (bTotNumEndpoints * 66))
/*
* _1stI - number of first interface
* IDidx - 0 or index of string descriptor for given interface
* EPx - number of virtual interrupt EP
* EP - number of real EP in/out
*/
#define USB_IAD(_1stI, IDidx, EPx, EP) \
USB_DT_IAD_SIZE, /* bLength: IAD size */ \
USB_DT_IAD, /* bDescriptorType: IAD */ \
_1stI, /* bFirstInterface */ \
2, /* bInterfaceCount */ \
2, /* bFunctionClass: CDC */ \
2, /* bFunctionSubClass */ \
0, /* bFunctionProtocol */ \
0, /* iFunction */ \
/* Interface Descriptor */ \
USB_DT_INTERFACE_SIZE, /* bLength: Interface Descriptor size */ \
USB_DT_INTERFACE, /* bDescriptorType: Interface */ \
_1stI, /* bInterfaceNumber: Number of Interface */ \
0, /* bAlternateSetting: Alternate setting */ \
1, /* bNumEndpoints: one for this */ \
USB_CLASS_COMM, /* bInterfaceClass */ \
2, /* bInterfaceSubClass: ACM */ \
0, /* bInterfaceProtocol */ \
IDidx, /* iInterface */ \
/* CDC descriptor */ \
USB_DT_CS_INTERFACE_SIZE, /* bLength */ \
USB_DT_CS_INTERFACE, /* bDescriptorType: CS_INTERFACE */ \
0, /* bDescriptorSubtype: Header Func Desc */ \
0x10, /* bcdCDC: spec release number */ \
1, /* bDataInterface */ \
USB_DT_CS_INTERFACE_SIZE, /* bLength */ \
USB_DT_CS_INTERFACE, /* bDescriptorType: CS_INTERFACE */ \
1, /* bDescriptorSubtype: Call Management Func Desc */ \
0, /* bmCapabilities: D0+D1 */ \
(_1stI+1), /* bDataInterface */ \
USB_DT_CS_INTERFACE_SIZE-1, /* bLength */ \
USB_DT_CS_INTERFACE, /* bDescriptorType: CS_INTERFACE */ \
2, /* bDescriptorSubtype: Abstract Control Management desc */ \
2, /* bmCapabilities */ \
USB_DT_CS_INTERFACE_SIZE, /* bLength */ \
USB_DT_CS_INTERFACE, /* bDescriptorType: CS_INTERFACE */ \
6, /* bDescriptorSubtype: Union func desc */ \
_1stI, /* bMasterInterface: Communication class interface */ \
(_1stI+1), /* bSlaveInterface0: Data Class Interface */ \
/* Virtual endpoint 1 Descriptor */ \
USB_DT_ENDPOINT_SIZE, /* bLength: Endpoint Descriptor size */ \
USB_DT_ENDPOINT, /* bDescriptorType: Endpoint */ \
(0x80+EPx), /* bEndpointAddress IN8 - non-existant */ \
USB_BM_ATTR_INTERRUPT, /* bmAttributes: Interrupt */ \
L16(USB_EP1BUFSZ), /* wMaxPacketSize LO */ \
H16(USB_EP1BUFSZ), /* wMaxPacketSize HI */ \
0x10, /* bInterval: 16ms */ \
/* DATA endpoint */ \
USB_DT_INTERFACE_SIZE, /* bLength: Interface Descriptor size */ \
USB_DT_INTERFACE, /* bDescriptorType: Interface */ \
(_1stI+1), /* bInterfaceNumber: Number of Interface */ \
0, /* bAlternateSetting: Alternate setting */ \
2, /* bNumEndpoints: in and out */ \
USB_CLASS_DATA, /* bInterfaceClass */ \
2, /* bInterfaceSubClass: ACM */ \
0, /* bInterfaceProtocol */ \
0, /* iInterface */ \
/*Endpoint IN1 Descriptor */ \
USB_DT_ENDPOINT_SIZE, /* bLength: Endpoint Descriptor size */ \
USB_DT_ENDPOINT, /* bDescriptorType: Endpoint */ \
(0x80+EP), /* bEndpointAddress: IN1 */ \
USB_BM_ATTR_BULK, /* bmAttributes: Bulk */ \
L16(USB_TXBUFSZ), /* wMaxPacketSize LO */ \
H16(USB_TXBUFSZ), /* wMaxPacketSize HI */ \
0, /* bInterval: ignore for Bulk transfer */ \
/* Endpoint OUT1 Descriptor */ \
USB_DT_ENDPOINT_SIZE, /* bLength: Endpoint Descriptor size */ \
USB_DT_ENDPOINT, /* bDescriptorType: Endpoint */ \
EP, /* bEndpointAddress: OUT1 */ \
USB_BM_ATTR_BULK, /* bmAttributes: Bulk */ \
L16(USB_RXBUFSZ), /* wMaxPacketSize LO */ \
H16(USB_RXBUFSZ), /* wMaxPacketSize HI */ \
0 /* bInterval: ignore for Bulk transfer */
static const uint8_t USB_ConfigDescriptor[] = {
// Configuration Descriptor
USB_DT_CONFIG_SIZE, // bLength: Configuration Descriptor size
USB_DT_CONFIG, // bDescriptorType: Configuration
L16(wTotalLength), // wTotalLength.L :no of returned bytes
H16(wTotalLength), // wTotalLength.H
bNumInterfaces, // bNumInterfaces
1, // bConfigurationValue: Current configuration value
0, // iConfiguration: Index of string descriptor describing the configuration or 0
BusPowered, // bmAttributes - Bus powered
50, // MaxPower in 2mA units
//--IADs-------------------------------------------------------------------------
USB_IAD(0, iINTERFACE_DESCR1, 8, 1),
USB_IAD(2, iINTERFACE_DESCR2, 9, 2),
};
_USB_LANG_ID_(LD, LANG_US);
_USB_STRING_(SD, u"0.0.2");
_USB_STRING_(MD, u"eddy@sao.ru");
_USB_STRING_(PD, u"USB-Serial Controller");
// iInterface will change on initialisation by config
#define _USB_IIDESCR_(str) {sizeof(str), 0x03, str}
typedef struct{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bString[MAX_IINTERFACE_SZ];
}iidescr_t;
static iidescr_t iids[InterfacesAmount] = {
_USB_IIDESCR_(u"iface0"),
_USB_IIDESCR_(u"iface1"),
};
static const void* const StringDescriptor[iDESCR_AMOUNT] = {
[iLANGUAGE_DESCR] = &LD,
[iMANUFACTURER_DESCR] = &MD,
[iPRODUCT_DESCR] = &PD,
[iSERIAL_DESCR] = &SD,
[iINTERFACE_DESCR1] = &iids[0],
[iINTERFACE_DESCR2] = &iids[1],
};
static void wr0(const uint8_t *buf, uint16_t size, uint16_t askedsize){
if(askedsize < size) size = askedsize; // shortened request
if(size < USB_EP0BUFSZ){
EP_WriteIRQ(0, buf, size);
return;
}
while(size){
uint16_t l = size;
if(l > USB_EP0BUFSZ) l = USB_EP0BUFSZ;
EP_WriteIRQ(0, buf, l);
buf += l;
size -= l;
uint8_t needzlp = (l == USB_EP0BUFSZ) ? 1 : 0;
if(size || needzlp){ // send last data buffer
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]);
// keep DTOGs, clear CTR_RX,TX, set TX VALID, leave stat_Rx
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX|USB_EPnR_STAT_RX))
^ USB_EPnR_STAT_TX;
uint32_t ctr = 1000000;
while(--ctr && (USB->ISTR & USB_ISTR_CTR) == 0){IWDG->KR = IWDG_REFRESH;};
if((USB->ISTR & USB_ISTR_CTR) == 0){
return;
}
if(needzlp) EP_WriteIRQ(0, NULL, 0);
}
}
}
void get_descriptor(config_pack_t *pack){
uint8_t descrtype = pack->wValue >> 8,
descridx = pack->wValue & 0xff;
switch(descrtype){
case DEVICE_DESCRIPTOR:
wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor), pack->wLength);
break;
case CONFIGURATION_DESCRIPTOR:
wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor), pack->wLength);
break;
case STRING_DESCRIPTOR:
if(descridx < iDESCR_AMOUNT){
wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx]), pack->wLength);
}else{
EP_WriteIRQ(0, NULL, 0);
}
break;
case DEVICE_QUALIFIER_DESCRIPTOR:
wr0(USB_DeviceQualifierDescriptor, sizeof(USB_DeviceQualifierDescriptor), pack->wLength);
break;
default:
break;
}
}
// change values of iInterface by content of global config
void setup_interfaces(){
for(int i = 0; i < InterfacesAmount; ++i){
if(the_conf.iIlengths[i]){
iids[i].bLength = the_conf.iIlengths[i] + 2; // +2 - for bLength and bDescriptorType
memcpy(iids[i].bString, the_conf.iInterface[i], the_conf.iIlengths[i]);
}
iids[i].bDescriptorType = 0x03;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include "usb_lib.h"
// amount of interfaces
#define InterfacesAmount 2
// index of interface (ep number minus one): IF1..IF7
#define ICAN 0
#define IGPIO 1
// EP number of interface
#define EPNO(i) (i + 1)
// interface number of EPno
#define IFNO(e) (e - 1)
// amount of interfaces (including virtual) except 0
#define bNumInterfaces (2*InterfacesAmount)
// amount of endpoints used
#define bTotNumEndpoints (1+InterfacesAmount)
// definition of parts common for USB_DeviceDescriptor & USB_DeviceQualifierDescriptor
// bcdUSB: 1.10
#define bcdUSB 0x0110
// Class - Misc (EF), subclass - common (2), protocol - interface association descr (1)
#define bDeviceSubClass 0x02
#define bDeviceProtocol 0x01
#define idVendor 0x0483
#define idProduct 0x5740
#define bcdDevice_Ver 0x0200
#define bNumConfigurations 1
// powered
#define BusPowered (1<<7)
#define SelfPowered (1<<6)
#define RemoteWakeup (1<<5)
// buffer sizes
// Rx buffer should be not more than 64 and multiple of 2 (or 4 for STM32G0), or multiple of 32
// Tx buffer should be multiple of 2 (or 4 for STM32G0)
// for USB FS EP0 buffers are from 8 to 64 bytes long
#define USB_EP0BUFSZ 64
#define USB_EP1BUFSZ 10
// Rx/Tx EPs (not more than 64 bytes for CDC)
#define USB_RXBUFSZ 64
#define USB_TXBUFSZ 64
// string descriptors
enum{
iLANGUAGE_DESCR,
iMANUFACTURER_DESCR,
iPRODUCT_DESCR,
iSERIAL_DESCR,
iINTERFACE_DESCR1,
iINTERFACE_DESCR2,
iDESCR_AMOUNT
};
void get_descriptor(config_pack_t *pack);
void setup_interfaces();

View File

@@ -0,0 +1,359 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "ringbuffer.h"
#include "usb_descr.h"
#include "usb_dev.h"
// Class-Specific Control Requests
#define SEND_ENCAPSULATED_COMMAND 0x00 // unused
#define GET_ENCAPSULATED_RESPONSE 0x01 // unused
#define SET_COMM_FEATURE 0x02 // unused
#define GET_COMM_FEATURE 0x03 // unused
#define CLEAR_COMM_FEATURE 0x04 // unused
#define SET_LINE_CODING 0x20
#define GET_LINE_CODING 0x21
#define SET_CONTROL_LINE_STATE 0x22
#define SEND_BREAK 0x23
// control line states
#define CONTROL_DTR 0x01
#define CONTROL_RTS 0x02
// inbuf overflow when receiving
static volatile uint8_t bufovrfl[InterfacesAmount] = {0};
extern volatile uint32_t Tms;
// receive buffer: hold data until chkin() call
static uint8_t volatile rcvbuf[InterfacesAmount][USB_RXBUFSZ] __attribute__((aligned(4)));
static uint8_t volatile rcvbuflen[InterfacesAmount] = {0};
// line coding
#define DEFL {9600, 0, 0, 8}
static usb_LineCoding lineCoding[InterfacesAmount] = {DEFL,DEFL};
// CDC configured and ready to use
volatile uint8_t CDCready[InterfacesAmount] = {0};
// also we need to check timeouts of sending data to detect disconnection as
// stupid CDC have no `break` signal and, of course, as we have more than one
// interface, there's no interrupt on disconnection
// ring buffers for incoming and outgoing data
static uint8_t obuf[InterfacesAmount][RBOUTSZ], ibuf[InterfacesAmount][RBINSZ];
#define OBUF(N) {.data = obuf[N], .length = RBOUTSZ, .head = 0, .tail = 0}
static volatile ringbuffer rbout[InterfacesAmount] = {OBUF(0), OBUF(1)};
#define IBUF(N) {.data = ibuf[N], .length = RBINSZ, .head = 0, .tail = 0}
static volatile ringbuffer rbin[InterfacesAmount] = {IBUF(0), IBUF(1)};
// last send data size (<0 if USB transfer ready)
static volatile int lastdsz[InterfacesAmount] = {-1, -1};
// check incoming data and set ACK if need
static void chkin(uint8_t ifno){
if(bufovrfl[ifno]) return; // allow user to know that previous buffer was overflowed and cleared
if(!rcvbuflen[ifno]) return;
int w = RB_write((ringbuffer*)&rbin[ifno], (uint8_t*)rcvbuf[ifno], rcvbuflen[ifno]);
if(w < 0){
return;
}
if(w != rcvbuflen[ifno]) bufovrfl[ifno] = 1;
rcvbuflen[ifno] = 0;
uint16_t status = KEEP_DTOG(USB->EPnR[EPNO(ifno)]); // don't change DTOG
USB->EPnR[EPNO(ifno)] = (status & ~(USB_EPnR_STAT_TX|USB_EPnR_CTR_RX)) ^ USB_EPnR_STAT_RX; // prepare to get next data portion
}
// called from transmit EP to send next data portion or by user - when new transmission starts
static void send_next(uint8_t ifno){
uint8_t usbbuff[USB_TXBUFSZ];
int buflen = RB_read((ringbuffer*)&rbout[ifno], (uint8_t*)usbbuff, USB_TXBUFSZ);
if(!CDCready[ifno]){
lastdsz[ifno] = -1;
return;
}
if(buflen == 0){
if(lastdsz[ifno] == USB_TXBUFSZ){
EP_Write(EPNO(ifno), NULL, 0); // send ZLP after 64 bits packet when nothing more to send
lastdsz[ifno] = 0;
}else lastdsz[ifno] = -1; // OK. User can start sending data
return;
}else if(buflen < 0){
lastdsz[ifno] = -1;
return;
}
EP_Write(EPNO(ifno), (uint8_t*)usbbuff, buflen);
lastdsz[ifno] = buflen;
}
// data IN/OUT handler
static void rxtx_handler(){
uint8_t epno = (USB->ISTR & USB_ISTR_EPID), ifno = IFNO(epno);
if(ifno > InterfacesAmount-1){
return;
}
uint16_t epstatus = KEEP_DTOG(USB->EPnR[epno]);
if(RX_FLAG(epstatus)){ // receive data
if(rcvbuflen[ifno]){
bufovrfl[ifno] = 1; // lost last data
rcvbuflen[ifno] = 0;
}
rcvbuflen[ifno] = EP_Read(epno, (uint8_t*)rcvbuf[ifno]);
USB->EPnR[epno] = epstatus & ~(USB_EPnR_CTR_RX | USB_EPnR_STAT_RX | USB_EPnR_STAT_TX); // keep RX in STALL state until read data
chkin(ifno); // try to write current data into RXbuf if it's not busy
}else{ // tx successfull
USB->EPnR[epno] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_TX)) ^ USB_EPnR_STAT_RX;
send_next(ifno);
}
}
static void clearRbuf(uint8_t ifno){
uint32_t T0 = Tms;
while(Tms - T0 < 10){ // wait no more than 10ms
if(1 == RB_clearbuf((ringbuffer*)&rbin[ifno])) break;
}
}
static void clearTbuf(uint8_t ifno){
uint32_t T0 = Tms;
while(Tms - T0 < 10){
if(1 == RB_clearbuf((ringbuffer*)&rbout[ifno])) break;
}
}
// SET_LINE_CODING
void linecoding_handler(uint8_t ifno, usb_LineCoding *lc){
lineCoding[ifno] = *lc;
// next lines could be using to add direct USART-USB interface
//usart_config(ifno, &lineCoding[ifno]); // lc would be real speed!
//usart_start(ifno); // restart again with new configuration
}
// SET_CONTROL_LINE_STATE
void clstate_handler(uint8_t ifno, uint16_t val){
CDCready[ifno] = val; // CONTROL_DTR | CONTROL_RTS -> interface connected; 0 -> disconnected
lastdsz[ifno] = -1;
if(val){
clearRbuf(ifno);
clearTbuf(ifno);
EP_reset(EPNO(ifno));
// usart_start(ifno);
}//else usart_stop(ifno); // turn of USART (if it is @ this interface)
}
// SEND_BREAK - disconnect interface and clear its buffers
// this is a fake handler as classic CDC ACM never receives this
void break_handler(uint8_t ifno){
CDCready[ifno] = 0;
// usart_stop(ifno); // turn of USART (if it is @ this interface)
}
// Interface is configured: setup endpoints
void set_configuration(){
for(int i = 0; i < InterfacesAmount; ++i){
IWDG->KR = IWDG_REFRESH;
int r = EP_Init(EPNO(i), EP_TYPE_BULK, USB_TXBUFSZ, USB_RXBUFSZ, rxtx_handler);
if(r){
// OOPS, can't init EP. What to do? Cry?
break;
}
}
}
// USB CDC CLASS request
void usb_class_request(config_pack_t *req, uint8_t *data, uint16_t datalen){
uint8_t recipient = REQUEST_RECIPIENT(req->bmRequestType);
uint8_t dev2host = (req->bmRequestType & 0x80) ? 1 : 0;
uint8_t ifno = req->wIndex >> 1;
if(ifno > InterfacesAmount-1){ // wrong interface number
EP_WriteIRQ(0, NULL, 0);
return;
}
switch(recipient){
case REQ_RECIPIENT_INTERFACE:
switch(req->bRequest){
case SET_LINE_CODING:
if(!data || !datalen) break; // wait for data
if(datalen == sizeof(usb_LineCoding))
linecoding_handler(ifno, (usb_LineCoding*)data);
break;
case GET_LINE_CODING:
EP_WriteIRQ(0, (uint8_t*)&lineCoding[ifno], sizeof(lineCoding));
break;
case SET_CONTROL_LINE_STATE:
clstate_handler(ifno, req->wValue);
break;
case SEND_BREAK:
break_handler(ifno);
break;
default: // WTF?
break;
}
break;
default: // WTF?
if(dev2host) EP_WriteIRQ(0, NULL, 0);
}
if(!dev2host) EP_WriteIRQ(0, NULL, 0);
}
// blocking send full content of ring buffer
int USB_sendall(uint8_t ifno){
uint32_t T0 = Tms;
while(lastdsz[ifno] > 0){
if(Tms - T0 > DISCONN_TMOUT){
//break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]) return FALSE;
IWDG->KR = IWDG_REFRESH;
}
return TRUE;
}
// return amount of free space in buffer
int USB_sendbufspace(uint8_t ifno){
if(!CDCready[ifno]) return 0;
return rbout[ifno].length - RB_datalen((ringbuffer*)&rbout[ifno]);
}
// put `buf` into queue to send
int USB_send(uint8_t ifno, const uint8_t *buf, int len){
if(!buf || !CDCready[ifno] || !len){
return FALSE;
}
uint32_t T0 = Tms;
while(len){
if(Tms - T0 > DISCONN_TMOUT){
//break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]){
return FALSE;
}
IWDG->KR = IWDG_REFRESH;
int l = RB_datalen((ringbuffer*)&rbout[ifno]);
if(l < 0) continue;
int portion = rbout[ifno].length - 1 - l;
if(portion < 1){
if(lastdsz[ifno] < 0) send_next(ifno);
continue;
}
if(portion > len) portion = len;
int a = RB_write((ringbuffer*)&rbout[ifno], buf, portion);
if(a > 0){
len -= a;
buf += a;
}else if(a == 0){ // overfull
if(lastdsz[ifno] < 0) send_next(ifno);
}
}
if(buf[len-1] == '\n' && lastdsz[ifno] < 0){
send_next(ifno);
}
return TRUE;
}
int USB_putbyte(uint8_t ifno, uint8_t byte){
if(!CDCready[ifno]) return FALSE;
int l = 0;
uint32_t T0 = Tms;
while((l = RB_write((ringbuffer*)&rbout[ifno], &byte, 1)) != 1){
if(Tms - T0 > DISCONN_TMOUT){
//break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]) return FALSE;
IWDG->KR = IWDG_REFRESH;
if(l == 0){ // overfull
if(lastdsz[ifno] < 0) send_next(ifno);
continue;
}
}
// send line if got EOL
if(byte == '\n' && lastdsz[ifno] < 0){
send_next(ifno);
}
return TRUE;
}
int USB_sendstr(uint8_t ifno, const char *string){
if(!string || !CDCready[ifno]) return FALSE;
int len = strlen(string);
if(!len) return FALSE;
return USB_send(ifno, (const uint8_t*)string, len);
}
int USB_rcvlen(uint8_t ifno){
return RB_datalen((ringbuffer*)&rbin[ifno]);
}
/**
* @brief USB_receive - get binary data from receiving ring-buffer
* @param buf (i) - buffer for received data
* @param len - length of `buf`
* @return amount of received bytes (negative, if overfull happened)
*/
int USB_receive(uint8_t ifno, uint8_t *buf, int len){
if(!CDCready[ifno]) return 0;
chkin(ifno); // rxtx_handler could leave last message unwritten if buffer was busy
if(bufovrfl[ifno]){
clearRbuf(ifno);
bufovrfl[ifno] = 0;
return -1;
}
int sz = RB_read((ringbuffer*)&rbin[ifno], buf, len);
if(sz < 0) return 0; // buffer in writting state
return sz;
}
/**
* @brief USB_receivestr - get string up to '\n' and replace '\n' with 0
* @param buf - receiving buffer
* @param len - its length
* @return strlen or negative value indicating overflow (if so, string won't be ends with 0 and buffer should be cleared)
*/
int USB_receivestr(uint8_t ifno, char *buf, int len){
if(!CDCready[ifno]) return 0;
chkin(ifno); // rxtx_handler could leave last message unwritten if buffer was busy
if(bufovrfl[ifno]){
clearRbuf(ifno);
bufovrfl[ifno] = 0;
return -1;
}
int l = RB_readto((ringbuffer*)&rbin[ifno], '\n', (uint8_t*)buf, len);
if(l < 1){
if((rbin[ifno].length <= RB_datalen((ringbuffer*)&rbin[ifno]) + 1) ||
(RB_datalento((ringbuffer*)&rbin[ifno], '\n') > len - 1)){ // buffer is full but no '\n' found or string too long
clearRbuf(ifno);
return -1;
}
return 0;
}
buf[l-1] = 0; // replace '\n' with strend
return l;
}
/**
* @brief IFconfig - interface configuration
* @param ifno - index
* @param l (o, user allocated) - settings
* @return value of CDCready
*/
uint8_t IFconfig(uint8_t ifno, usb_LineCoding *l){
if(ifno >= InterfacesAmount) return 0;
if(l) *l = lineCoding[ifno];
return CDCready[ifno];
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include "usb_lib.h"
typedef struct {
uint32_t dwDTERate;
uint8_t bCharFormat;
#define USB_CDC_1_STOP_BITS 0
#define USB_CDC_1_5_STOP_BITS 1
#define USB_CDC_2_STOP_BITS 2
uint8_t bParityType;
#define USB_CDC_NO_PARITY 0
#define USB_CDC_ODD_PARITY 1
#define USB_CDC_EVEN_PARITY 2
#define USB_CDC_MARK_PARITY 3
#define USB_CDC_SPACE_PARITY 4
uint8_t bDataBits;
} __attribute__ ((packed)) usb_LineCoding;
extern volatile uint8_t CDCready[];
void break_handler(uint8_t ifno);
void clstate_handler(uint8_t ifno, uint16_t val);
void linecoding_handler(uint8_t ifno, usb_LineCoding *lc);
// as ugly CDC have no BREAK after disconnected client in non-canonical mode, we should use timeout - near 2s
#define DISCONN_TMOUT (2)
// sizes of ringbuffers for outgoing and incoming data
#define RBOUTSZ (512)
#define RBINSZ (256)
#define newline(ifno) USB_putbyte(ifno, '\n')
#define USND(ifno, s) do{USB_sendstr(ifno, s); USB_putbyte(ifno, '\n');}while(0)
// 'shortcuts' for files used only one interface "USBIF"
#ifdef USBIF
#define SEND(x) USB_sendstr(USBIF, x)
#define RECV(b,l) USB_receivestr(USBIF, b, l)
#define PUTCHAR(x) USB_putbyte(USBIF, x)
#define SENDn(x) do{USB_sendstr(USBIF, x); USB_putbyte(USBIF, '\n');}while(0)
#define NL(x) USB_putbyte(USBIF, '\n')
#endif
int USB_sendbufspace(uint8_t ifno);
int USB_sendall(uint8_t ifno);
int USB_send(uint8_t ifno, const uint8_t *buf, int len);
int USB_putbyte(uint8_t ifno, uint8_t byte);
int USB_sendstr(uint8_t ifno, const char *string);
int USB_rcvlen(uint8_t ifno);
int USB_receive(uint8_t ifno, uint8_t *buf, int len);
int USB_receivestr(uint8_t ifno, char *buf, int len);
uint8_t IFconfig(uint8_t ifno, usb_LineCoding *l);

View File

@@ -0,0 +1,450 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include "usb_lib.h"
#include "usb_descr.h"
#include "usb_dev.h"
static ep_t endpoints[STM32ENDPOINTS];
static uint16_t USB_Addr = 0;
static uint8_t setupdatabuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
static config_pack_t *setup_packet = (config_pack_t*) setupdatabuf;
volatile uint8_t usbON = 0; // device is configured and active
static uint16_t configuration = 0; // reply for GET_CONFIGURATION (==1 if configured)
static inline void std_d2h_req(){
uint16_t st = 0;
switch(setup_packet->bRequest){
case GET_DESCRIPTOR:
get_descriptor(setup_packet);
break;
case GET_STATUS:
EP_WriteIRQ(0, (uint8_t *)&st, 2); // send status: Bus Powered
break;
case GET_CONFIGURATION:
EP_WriteIRQ(0, (uint8_t*)&configuration, 1);
break;
default:
EP_WriteIRQ(0, NULL, 0);
break;
}
}
static inline void std_h2d_req(){
switch(setup_packet->bRequest){
case SET_ADDRESS:
// new address will be assigned later - after acknowlegement or request to host
USB_Addr = setup_packet->wValue;
break;
case SET_CONFIGURATION:
// Now device configured
configuration = setup_packet->wValue;
set_configuration();
usbON = 1;
break;
default:
break;
}
}
void WEAK usb_standard_request(){
uint8_t recipient = REQUEST_RECIPIENT(setup_packet->bmRequestType);
uint8_t dev2host = (setup_packet->bmRequestType & 0x80) ? 1 : 0;
switch(recipient){
case REQ_RECIPIENT_DEVICE:
if(dev2host){
std_d2h_req();
}else{
std_h2d_req();
}
break;
case REQ_RECIPIENT_INTERFACE:
if(dev2host && setup_packet->bRequest == GET_DESCRIPTOR){
get_descriptor(setup_packet);
}
break;
case REQ_RECIPIENT_ENDPOINT:
if(setup_packet->bRequest == CLEAR_FEATURE){
}else{ /* wrong */ }
break;
default:
break;
}
if(!dev2host) EP_WriteIRQ(0, NULL, 0);
}
void WEAK usb_class_request(config_pack_t *req, uint8_t _U_ *data, uint16_t _U_ datalen){
switch(req->bRequest){
case GET_INTERFACE:
break;
case SET_CONFIGURATION: // set featuring by req->wValue
break;
default:
break;
}
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
}
void WEAK usb_vendor_request(config_pack_t _U_ *packet, uint8_t _U_ *data, uint16_t _U_ datalen){
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
}
/*
bmRequestType: 76543210
7 direction: 0 - host->device, 1 - device->host
65 type: 0 - standard, 1 - class, 2 - vendor
4..0 getter: 0 - device, 1 - interface, 2 - endpoint, 3 - other
*/
/**
* Endpoint0 (control) handler
*/
static void EP0_Handler(){
uint8_t ep0dbuflen = 0;
uint8_t ep0databuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]); // EP0R on input -> return this value after modifications
int rxflag = RX_FLAG(epstatus);
// check direction
if(USB->ISTR & USB_ISTR_DIR){ // OUT interrupt - receive data, CTR_RX==1 (if CTR_TX == 1 - two pending transactions: receive following by transmit)
if(epstatus & USB_EPnR_SETUP){ // setup packet -> copy data to conf_pack
EP_Read(0, setupdatabuf);
// interrupt handler will be called later
}else if(epstatus & USB_EPnR_CTR_RX){ // data packet -> push received data to ep0databuf
//if(endpoints[0].rx_cnt){ }
ep0dbuflen = EP_Read(0, ep0databuf);
}
}
if(rxflag){
uint8_t reqtype = REQUEST_TYPE(setup_packet->bmRequestType);
switch(reqtype){
case REQ_TYPE_STANDARD:
if(SETUP_FLAG(epstatus)){
usb_standard_request();
}else{ }
break;
case REQ_TYPE_CLASS:
usb_class_request(setup_packet, ep0databuf, ep0dbuflen);
break;
case REQ_TYPE_VENDOR:
usb_vendor_request(setup_packet, ep0databuf, ep0dbuflen);
break;
default:
EP_WriteIRQ(0, NULL, 0);
break;
}
}
if(TX_FLAG(epstatus)){
// now we can change address after enumeration
if ((USB->DADDR & USB_DADDR_ADD) != USB_Addr){
USB->DADDR = USB_DADDR_EF | USB_Addr;
usbON = 0;
}
}
//epstatus = KEEP_DTOG(USB->EPnR[0]);
if(rxflag) epstatus ^= USB_EPnR_STAT_TX; // start ZLP or data transmission
else epstatus &= ~USB_EPnR_STAT_TX; // or leave unchanged
// keep DTOGs, clear CTR_RX,TX, set RX VALID
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX)) ^ USB_EPnR_STAT_RX;
}
/**
* Write data to EP buffer (called from IRQ handler)
* @param number - EP number
* @param *buf - array with data
* @param size - its size
*/
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
if(size > endpoints[number].txbufsz) size = endpoints[number].txbufsz;
#ifndef USB32
uint16_t N2 = (size + 1) >> 1;
// the buffer is 16-bit, so we should copy data as it would be uint16_t
uint16_t *buf16 = (uint16_t *)buf;
#else
int N4 = (size + 3) >> 2;
uint32_t *buf32 = (uint32_t *)buf;
#endif
#if defined USB1_16
// very bad: what if `size` is odd?
uint32_t *out = (uint32_t *)endpoints[number].tx_buf;
for(int i = 0; i < N2; ++i, ++out){
*out = buf16[i];
}
#elif defined USB2_16
// use memcpy instead?
for(int i = 0; i < N2; ++i){
endpoints[number].tx_buf[i] = buf16[i];
}
#elif defined USB32
for(int i = 0; i < N4; ++i) endpoints[number].tx_buf[i] = buf32[i];
#else
#error "Define USB1_16 / USB2_16 / USB32"
#endif
#ifndef USB32
USB_BTABLE->EP[number].USB_COUNT_TX = size;
#else
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (USB_BTABLE->EP[number].USB_ADDR_COUNT_TX & 0xffff) | (size << 16);
#endif
}
/**
* Write data to EP buffer (called outside IRQ handler)
* @param number - EP number
* @param *buf - array with data
* @param size - its size
*/
void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size){
EP_WriteIRQ(number, buf, size);
uint16_t epstatus = KEEP_DTOG(USB->EPnR[number]);
// keep DTOGs and RX stat, clear CTR_TX & set TX VALID to start transmission
USB->EPnR[number] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_RX)) ^ USB_EPnR_STAT_TX;
}
/*
* Copy data from EP buffer into user buffer area
* @param *buf - user array for data
* @return amount of data read
*/
int EP_Read(uint8_t number, uint8_t *buf){
int sz = endpoints[number].rx_cnt;
if(!sz) return 0;
endpoints[number].rx_cnt = 0;
#if defined USB1_16
int n = (sz + 1) >> 1;
uint32_t *in = (uint32_t*)endpoints[number].rx_buf;
uint16_t *out = (uint16_t*)buf;
for(int i = 0; i < n; ++i, ++in)
out[i] = *(uint16_t*)in;
#elif defined USB2_16
// use memcpy instead?
for(int i = 0; i < sz; ++i)
buf[i] = endpoints[number].rx_buf[i];
#elif defined USB32
uint32_t *u32buf = (uint32_t*) buf;
int N4 = (sz + 3) >> 2;
for(int i = 0; i < N4; ++i) u32buf[i] = endpoints[number].rx_buf[i];
#else
#error "Define USB1_16 / USB2_16 / USB32"
#endif
return sz;
}
static uint16_t lastaddr = LASTADDR_DEFAULT;
/**
* Endpoint initialisation
* @param number - EP num (0...7)
* @param type - EP type (EP_TYPE_BULK, EP_TYPE_CONTROL, EP_TYPE_ISO, EP_TYPE_INTERRUPT)
* @param txsz - transmission buffer size @ USB/CAN buffer
* @param rxsz - reception buffer size @ USB/CAN buffer
* @param uint16_t (*func)(ep_t *ep) - EP handler function
* @return 0 if all OK
*/
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)(ep_t ep)){
#ifdef STM32G0
// in STM32G0 all buffers should be aligned by 32 bits
if(txsz & 3) txsz = ((txsz >> 2)+1) << 2;
if(rxsz & 3) rxsz = ((rxsz >> 2)+1) << 2;
#endif
if(number >= STM32ENDPOINTS) return 4; // out of configured amount
if(txsz > USB_BTABLE_SIZE/ACCESSZ || rxsz > USB_BTABLE_SIZE/ACCESSZ) return 1; // buffer too large
if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE/ACCESSZ) return 2; // out of btable
USB->EPnR[number] = (type << 9) | (number & USB_EPnR_EA);
USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX;
if(rxsz & 1) return 3; // wrong rx buffer size
uint16_t countrx = 0;
if(rxsz < 64) countrx = rxsz / 2;
else{
if(rxsz & 0x1f) return 3; // should be multiple of 32
countrx = 31 + rxsz / 32;
}
#ifdef USB32
endpoints[number].tx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#else
endpoints[number].tx_buf = (uint16_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#endif
endpoints[number].txbufsz = txsz;
#ifdef USB32
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (uint32_t) lastaddr;
#else
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_TX = 0;
#endif
lastaddr += txsz;
#ifdef USB32
endpoints[number].rx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
USB_BTABLE->EP[number].USB_ADDR_COUNT_RX = (uint32_t) lastaddr | countrx << 26;
#else
endpoints[number].rx_buf = (uint8_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_RX = countrx << 10;
#endif
lastaddr += rxsz;
endpoints[number].func = func;
return 0;
}
// refresh EP after interface reconnected
void EP_reset(uint8_t epno){
if(epno >= STM32ENDPOINTS) return;
// keep DTOGs (don't write 1 to them), clear CTR (write 0 to them)
// and set STAT to VALID (write 1 where was 0)
uint16_t epstatus = KEEP_DTOG(USB->EPnR[epno]);
USB->EPnR[epno] = (epstatus & ~(USB_EPnR_CTR_TX|USB_EPnR_CTR_RX)) ^
(USB_EPnR_STAT_RX | USB_EPnR_STAT_TX);
USB_BTABLE->EP[epno].USB_COUNT_TX = 0;
}
// standard IRQ handler
void USB_IRQ(){
uint32_t CNTR = USB->CNTR;
USB->CNTR = 0;
uint32_t istr = USB->ISTR;
if(istr & USB_ISTR_RESET){
usbON = 0;
// Reinit registers
CNTR = USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM;
// Endpoint 0 - CONTROL
// ON USB LS size of EP0 may be 8 bytes, but on FS it should be 64 bytes!
lastaddr = LASTADDR_DEFAULT;
// clear address, leave only enable bit
USB->DADDR = USB_DADDR_EF;
//USB->ISTR = ~(USB_ISTR_RESET); // clear all flags
if(EP_Init(0, EP_TYPE_CONTROL, USB_EP0BUFSZ, USB_EP0BUFSZ, EP0_Handler)){
return;
};
}
if(istr & USB_ISTR_CTR){
// EP number
uint8_t n = istr & USB_ISTR_EPID;
if (istr & USB_ISTR_DIR){ // OUT
}else{ // IN
}
// copy received bytes amount
endpoints[n].rx_cnt =
#ifdef USB32
(USB_BTABLE->EP[n].USB_ADDR_COUNT_RX >> 16) & 0x3FF;
#else
USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
#endif
// call EP handler
if(endpoints[n].func) endpoints[n].func();
}
if(istr & USB_ISTR_WKUP){ // wakeup
#if defined STM32F0
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM);
#elif defined STM32G0
CNTR &= ~(USB_CNTR_SUSPEN | USB_CNTR_PDWN | USB_CNTR_WKUPM);
#else
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM); // clear suspend flags
#endif
//USB->ISTR = ~USB_ISTR_WKUP;
}
if(istr & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep
usbON = 0;
#if defined STM32F0
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM;
#elif defined STM32G0
CNTR |= USB_CNTR_SUSPEN | USB_CNTR_WKUPM;
#else
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM;
#endif
CNTR &= ~(USB_CNTR_SUSPM);
//USB->ISTR = ~USB_ISTR_SUSP;
}
USB->ISTR = 0; // clear all flags
USB->CNTR = CNTR; // rewoke interrupts
}
// here we suppose that all PIN settings done in hw_setup earlier
void USB_setup(){
lastaddr = LASTADDR_DEFAULT; // clear last address settings
#if defined STM32F3
NVIC_DisableIRQ(USB_LP_IRQn);
// remap USB LP & Wakeup interrupts to 75 and 76 - works only on pure F303
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // enable tacting of SYSCFG
SYSCFG->CFGR1 |= SYSCFG_CFGR1_USB_IT_RMP;
#elif defined STM32F1
NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn);
NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn);
#elif defined STM32F0
// All is clocking from HSI48
NVIC_DisableIRQ(USB_IRQn);
RCC->APB1ENR |= RCC_APB1ENR_CRSEN;
RCC->CFGR3 &= ~RCC_CFGR3_USBSW; // reset USB
RCC->CR2 |= RCC_CR2_HSI48ON; // turn ON HSI48
uint32_t tmout = 16000000;
while(!(RCC->CR2 & RCC_CR2_HSI48RDY)){if(--tmout == 0) break;}
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
CRS->CFGR &= ~CRS_CFGR_SYNCSRC;
CRS->CFGR |= CRS_CFGR_SYNCSRC_1; // USB SOF selected as sync source
CRS->CR |= CRS_CR_AUTOTRIMEN; // enable auto trim
CRS->CR |= CRS_CR_CEN; // enable freq counter & block CRS->CFGR as read-only
RCC->CFGR |= RCC_CFGR_SW;
#elif defined STM32G0
NVIC_DisableIRQ(USB_UCPD1_2_IRQn);
PWR->CR2 |= PWR_CR2_USV; // enable USB powering
//RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN; // enable tacting of SYSCFG
// independent clocking of USB from HSI48
RCC->CR |= RCC_CR_HSI48ON;
uint32_t tmout = 16000000;
while(!(RCC->CR & RCC_CR_HSI48RDY)) if(--tmout == 0) break;
RCC->CCIPR2 &= ~RCC_CCIPR2_USBSEL; // select HSI48 for USB
RCC->APBENR1 |= RCC_APBENR1_CRSEN; // CRS clocking
CRS->CFGR = (31LL << CRS_CFGR_FELIM_Pos) | // tolerance (usually 31)
(48000LL / 1LL - 1LL) << CRS_CFGR_RELOAD_Pos | // 48MHz / 1kHZ (SOF)
CRS_CFGR_SYNCSRC_1; // USB SOF as sync source (0x2)
CRS->CR |= CRS_CR_AUTOTRIMEN | CRS_CR_CEN; // Enable autotrim and turn on Clock Recovery System
RCC->APBENR1 |= RCC_APBENR1_USBEN;
#endif
#ifndef STM32G0
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
USB->CNTR = USB_CNTR_FRES; // Force USB Reset
USB->BTABLE = 0;
#else
USB->CNTR = USB_CNTR_USBRST;
#endif
for(uint32_t ctr = 0; ctr < 72000; ++ctr) nop(); // wait >1ms
USB->CNTR = USB_CNTR_RESETM; // allow only reset interrupts
USB->DADDR = 0;
USB->ISTR = 0;
#if defined STM32F3
NVIC_EnableIRQ(USB_LP_IRQn);
#elif defined STM32F1
NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
#elif defined STM32F0
USB->BCDR |= USB_BCDR_DPPU;
NVIC_EnableIRQ(USB_IRQn);
#elif defined STM32G0
USB->BCDR |= USB_BCDR_DPPU; // turn ON DP pullup
NVIC_EnableIRQ(USB_UCPD1_2_IRQn);
#endif
setup_interfaces(); // refresh interfaces' names
}
#if defined STM32F3
void usb_lp_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32F1
void usb_lp_can_rx0_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32F0
void usb_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32G0
void usb_ucpd1_2_isr() __attribute__ ((alias ("USB_IRQ")));
#endif

View File

@@ -0,0 +1,353 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include <wchar.h>
#ifndef _U_
#define _U_ __attribute__((unused))
#endif
/******************************************************************
* Hardware registers etc *
*****************************************************************/
#if defined STM32F0
#include <stm32f0.h>
#elif defined STM32F1
#include <stm32f1.h>
// there's no this define in standard header
#define USB_BASE ((uint32_t)0x40005C00)
#elif defined STM32F3
#include <stm32f3.h>
#elif defined STM32G0
#include <stm32g0.h>
#endif
// max endpoints number
#define STM32ENDPOINTS 8
/**
* Buffers size definition
**/
// F0 - USB2_16; F1 - USB1_16; F3 - 1/2 depending on series; G0 - USB32
#if !defined USB1_16 && !defined USB2_16 && !defined USB32
#if defined STM32F0
#define USB2_16
#elif defined STM32F1
#define USB1_16
#elif defined STM32G0
#define USB32
#else
#error "Can't determine USB1_16/USB2_16/USB32, define by hands"
#endif
#endif
// BTABLE_SIZE FOR STM32F3:
// In STM32F303/302xB/C, 512 bytes SRAM is not shared with CAN.
// In STM32F302x6/x8 and STM32F30xxD/E, 726 bytes dedicated SRAM and 256 bytes shared SRAM with CAN i.e.
// 1Kbytes dedicated SRAM in case CAN is disabled.
// remember, that USB_BTABLE_SIZE will be divided by ACCESSZ, so don't divide it twice for 32-bit addressing
#ifdef NOCAN
#if defined STM32F0
#define USB_BTABLE_SIZE 1024
#elif defined STM32F3
#define USB_BTABLE_SIZE 1024
//#warning "Please, check real buffer size due to docs"
#else
#error "define STM32F0 or STM32F3"
#endif
#else // !NOCAN: F0/F3 with CAN or F1 (can't simultaneously run CAN and USB)
#if defined STM32F0
#define USB_BTABLE_SIZE 768
#elif defined STM32F3
#define USB_BTABLE_SIZE 768
#elif defined STM32G0
#define USB_BTABLE_SIZE 2048
//#warning "Please, check real buffer size due to docs"
#else // STM32F103: 1024 bytes but with 32-bit addressing
#define USB_BTABLE_SIZE 1024
#endif
#endif // NOCAN
// first 64 bytes of USB_BTABLE are registers!
#ifndef STM32G0
#define USB_BTABLE_BASE 0x40006000
#else
#define USB_BTABLE_BASE 0x40009800
#endif
#define USB ((USB_TypeDef *) USB_BASE)
#ifdef USB_BTABLE
#undef USB_BTABLE
#endif
#define USB_BTABLE ((USB_BtableDef *)(USB_BTABLE_BASE))
#define USB_ISTR_EPID 0x0000000F
#define USB_FNR_LSOF_0 0x00000800
#define USB_FNR_lSOF_1 0x00001000
#define USB_LPMCSR_BESL_0 0x00000010
#define USB_LPMCSR_BESL_1 0x00000020
#define USB_LPMCSR_BESL_2 0x00000040
#define USB_LPMCSR_BESL_3 0x00000080
#define USB_EPnR_CTR_RX 0x00008000
#define USB_EPnR_DTOG_RX 0x00004000
#define USB_EPnR_STAT_RX 0x00003000
#define USB_EPnR_STAT_RX_0 0x00001000
#define USB_EPnR_STAT_RX_1 0x00002000
#define USB_EPnR_SETUP 0x00000800
#define USB_EPnR_EP_TYPE 0x00000600
#define USB_EPnR_EP_TYPE_0 0x00000200
#define USB_EPnR_EP_TYPE_1 0x00000400
#define USB_EPnR_EP_KIND 0x00000100
#define USB_EPnR_CTR_TX 0x00000080
#define USB_EPnR_DTOG_TX 0x00000040
#define USB_EPnR_STAT_TX 0x00000030
#define USB_EPnR_STAT_TX_0 0x00000010
#define USB_EPnR_STAT_TX_1 0x00000020
#define USB_EPnR_EA 0x0000000F
#define USB_COUNTn_RX_BLSIZE 0x00008000
#define USB_COUNTn_NUM_BLOCK 0x00007C00
#define USB_COUNTn_RX 0x0000003F
#define USB_TypeDef USB_TypeDef_custom
typedef struct {
__IO uint32_t EPnR[STM32ENDPOINTS];
__IO uint32_t RESERVED[STM32ENDPOINTS];
__IO uint32_t CNTR;
__IO uint32_t ISTR;
__IO uint32_t FNR;
__IO uint32_t DADDR;
#ifndef USB32
__IO uint32_t BTABLE;
#else
__IO uint32_t RESERVED1; // there's no BTABLE register in STM32G0
#endif
#if defined STM32F0 || defined USB32
__IO uint32_t LPMCSR;
__IO uint32_t BCDR;
#endif
} USB_TypeDef;
// F303 D/E have 2x16 access scheme
typedef struct{
#if defined USB2_16
__IO uint16_t USB_ADDR_TX;
__IO uint16_t USB_COUNT_TX;
__IO uint16_t USB_ADDR_RX;
__IO uint16_t USB_COUNT_RX;
#define ACCESSZ (1)
#elif defined USB1_16
__IO uint32_t USB_ADDR_TX;
__IO uint32_t USB_COUNT_TX;
__IO uint32_t USB_ADDR_RX;
__IO uint32_t USB_COUNT_RX;
#define ACCESSZ (2)
#elif defined USB32
// 32-bit registers: addr & count in one!
__IO uint32_t USB_ADDR_COUNT_TX;
__IO uint32_t USB_ADDR_COUNT_RX;
#define ACCESSZ (1)
#else
#error "Define USB1_16 (16 bits over 32bit register), USB2_16 (16 bits over 16 bit register) or USB32 (32 bist over 32 bit register)"
#endif
} USB_EPDATA_TypeDef;
typedef struct{
__IO USB_EPDATA_TypeDef EP[STM32ENDPOINTS];
} USB_BtableDef;
#define EP0DATABUF_SIZE (64)
#define LASTADDR_DEFAULT (STM32ENDPOINTS * 8)
/******************************************************************
* Defines from usb.h *
*****************************************************************/
/*
* Device and/or Interface Class codes
*/
#define USB_CLASS_PER_INTERFACE 0
#define USB_CLASS_AUDIO 1
#define USB_CLASS_COMM 2
#define USB_CLASS_HID 3
#define USB_CLASS_PRINTER 7
#define USB_CLASS_PTP 6
#define USB_CLASS_MASS_STORAGE 8
#define USB_CLASS_HUB 9
#define USB_CLASS_DATA 10
#define USB_CLASS_MISC 0xef
#define USB_CLASS_VENDOR_SPEC 0xff
/*
* Descriptor types
*/
#define USB_DT_DEVICE 0x01
#define USB_DT_CONFIG 0x02
#define USB_DT_STRING 0x03
#define USB_DT_INTERFACE 0x04
#define USB_DT_ENDPOINT 0x05
#define USB_DT_QUALIFIER 0x06
#define USB_DT_IAD 0x0B
#define USB_DT_HID 0x21
#define USB_DT_REPORT 0x22
#define USB_DT_PHYSICAL 0x23
#define USB_DT_CS_INTERFACE 0x24
#define USB_DT_HUB 0x29
/*
* Descriptor sizes per descriptor type
*/
#define USB_DT_DEVICE_SIZE 18
#define USB_DT_CONFIG_SIZE 9
#define USB_DT_INTERFACE_SIZE 9
#define USB_DT_HID_SIZE 9
#define USB_DT_ENDPOINT_SIZE 7
#define USB_DT_QUALIFIER_SIZE 10
#define USB_DT_CS_INTERFACE_SIZE 5
#define USB_DT_IAD_SIZE 8
// bmRequestType & 0x80 == dev2host (1) or host2dev (0)
// recipient: bmRequestType & 0x1f
#define REQUEST_RECIPIENT(b) (b & 0x1f)
#define REQ_RECIPIENT_DEVICE 0
#define REQ_RECIPIENT_INTERFACE 1
#define REQ_RECIPIENT_ENDPOINT 2
#define REQ_RECIPIENT_OTHER 3
// type: [bmRequestType & 0x60 >> 5]
#define REQUEST_TYPE(b) ((b&0x60)>>5)
#define REQ_TYPE_STANDARD 0
#define REQ_TYPE_CLASS 1
#define REQ_TYPE_VENDOR 2
#define REQ_TYPE_RESERVED 3
//#define VENDOR_REQUEST 0x01
// standard device requests
#define GET_STATUS 0x00
#define CLEAR_FEATURE 0x01
#define SET_FEATURE 0x03
#define SET_ADDRESS 0x05
#define GET_DESCRIPTOR 0x06
#define SET_DESCRIPTOR 0x07
#define GET_CONFIGURATION 0x08
#define SET_CONFIGURATION 0x09
// and some standard interface requests
#define GET_INTERFACE 0x0A
#define SET_INTERFACE 0x0B
// and some standard endpoint requests
#define SYNC_FRAME 0x0C
// Types of descriptors
#define DEVICE_DESCRIPTOR 0x01
#define CONFIGURATION_DESCRIPTOR 0x02
#define STRING_DESCRIPTOR 0x03
#define DEVICE_QUALIFIER_DESCRIPTOR 0x06
#define DEBUG_DESCRIPTOR 0x0a
#define HID_REPORT_DESCRIPTOR 0x22
// EP types for EP_init
#define EP_TYPE_BULK 0x00
#define EP_TYPE_CONTROL 0x01
#define EP_TYPE_ISO 0x02
#define EP_TYPE_INTERRUPT 0x03
// EP types for descriptors
#define USB_BM_ATTR_CONTROL 0x00
#define USB_BM_ATTR_ISO 0x01
#define USB_BM_ATTR_BULK 0x02
#define USB_BM_ATTR_INTERRUPT 0x03
/******************************************************************
* Other stuff *
*****************************************************************/
#define RX_FLAG(epstat) (epstat & USB_EPnR_CTR_RX)
#define TX_FLAG(epstat) (epstat & USB_EPnR_CTR_TX)
#define SETUP_FLAG(epstat) (epstat & USB_EPnR_SETUP)
// EPnR bits manipulation
#define KEEP_DTOG_STAT(EPnR) (EPnR & ~(USB_EPnR_STAT_RX|USB_EPnR_STAT_TX|USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX))
#define KEEP_DTOG(EPnR) (EPnR & ~(USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX))
#define LANG_US (uint16_t)0x0409
#define _USB_STRING_(name, str) \
static const struct name \
{ \
uint8_t bLength; \
uint8_t bDescriptorType; \
uint16_t bString[(sizeof(str) - 2) / 2]; \
\
} \
name = {sizeof(name), 0x03, str}
#define _USB_LANG_ID_(name, lng_id) \
static const struct name \
{ \
uint8_t bLength; \
uint8_t bDescriptorType; \
uint16_t bString; \
\
} \
name = {0x04, 0x03, lng_id}
// EP0 configuration packet
typedef struct {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} config_pack_t;
// endpoints state
typedef struct{
#ifdef USB32
uint32_t *tx_buf; // transmission buffer address
#else
uint16_t *tx_buf; // transmission buffer address
#endif
uint16_t txbufsz; // transmission buffer size
#ifdef USB32
uint32_t *rx_buf; // reception buffer address
#else
uint8_t *rx_buf; // reception buffer address
#endif
void (*func)(); // endpoint action function
unsigned rx_cnt : 10; // received data counter
} ep_t;
extern volatile uint8_t usbON;
void USB_setup();
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)());
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size);
void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size);
int EP_Read(uint8_t number, uint8_t *buf);
void EP_reset(uint8_t epno);
// could be [re]defined in usb_dev.c
extern void usb_class_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);
extern void usb_vendor_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);
extern void set_configuration();

Binary file not shown.

View File

@@ -0,0 +1,6 @@
// Add predefined macros for your project here. For example:
// #define THE_ANSWER 42
#define EBUG
#define STM32F0
#define STM32F042x6
#define USB2_16

View File

@@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.2, 2026-03-18T23:46:41. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">8</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Big/Data/00__Electronics/STM32/F0-nolib/usbcan_ringbuffer</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Default</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -0,0 +1,42 @@
Readme.md
adc.c
adc.h
can.c
can.h
canproto.c
canproto.h
flash.c
flash.h
gpio.c
gpio.h
gpioproto.cpp
gpioproto.h
hardware.c
hardware.h
hashgen/Readme
hashgen/hashgen.c
hashgen/mktestdic
i2c.c
i2c.h
main.c
pwm.c
pwm.h
ringbuffer.c
ringbuffer.h
spi.c
spi.h
strfunc.c
strfunc.h
usart.c
usart.h
usb.c
usb.h
usb_defs.h
usb_descr.c
usb_descr.h
usb_dev.c
usb_dev.h
usb_lib.c
usb_lib.h
usbhw.c
usbhw.h

View File

@@ -0,0 +1,2 @@
#define BUILD_NUMBER "226"
#define BUILD_DATE "2026-03-18"

View File

@@ -19,7 +19,7 @@
#include "can.h" #include "can.h"
#include "hardware.h" #include "hardware.h"
#include "proto.h" #include "proto.h"
#include "usb.h" #include "usb_dev.h"
// circular buffer for received messages // circular buffer for received messages
static CAN_message messages[CAN_INMESSAGE_SIZE]; static CAN_message messages[CAN_INMESSAGE_SIZE];

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@
"opacity": { "opacity": {
"images": 0.6, "images": 0.6,
"pads": 1.0, "pads": 1.0,
"shapes": 1.0,
"tracks": 1.0, "tracks": 1.0,
"vias": 1.0, "vias": 1.0,
"zones": 0.6 "zones": 0.6
@@ -29,42 +30,25 @@
"zones": true "zones": true
}, },
"visible_items": [ "visible_items": [
0, "vias",
1, "footprint_text",
2, "footprint_anchors",
3, "ratsnest",
4, "grid",
5, "footprints_front",
8, "footprints_back",
9, "footprint_values",
10, "footprint_references",
11, "tracks",
12, "drc_errors",
13, "drawing_sheet",
14, "bitmaps",
15, "pads",
16, "zones",
17, "drc_warnings",
18, "shapes"
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36
], ],
"visible_layers": "00290a0_00000000", "visible_layers": "00000000_00000000_00000008_82000022",
"zone_display_mode": 1 "zone_display_mode": 1
}, },
"git": { "git": {
@@ -75,9 +59,72 @@
}, },
"meta": { "meta": {
"filename": "stm32.kicad_prl", "filename": "stm32.kicad_prl",
"version": 3 "version": 5
}, },
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"col_widths": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": 0
},
"open_jobsets": [],
"project": { "project": {
"files": [] "files": []
},
"schematic": {
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"symbols": true,
"text": true,
"wires": true
}
} }
} }

View File

@@ -3,14 +3,17 @@
"3dviewports": [], "3dviewports": [],
"design_settings": { "design_settings": {
"defaults": { "defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.15, "board_outline_line_width": 0.15,
"copper_line_width": 0.19999999999999998, "copper_line_width": 0.2,
"copper_text_italic": false, "copper_text_italic": false,
"copper_text_size_h": 1.5, "copper_text_size_h": 1.5,
"copper_text_size_v": 1.5, "copper_text_size_v": 1.5,
"copper_text_thickness": 0.3, "copper_text_thickness": 0.3,
"copper_text_upright": false, "copper_text_upright": false,
"courtyard_line_width": 0.049999999999999996, "courtyard_line_width": 0.05,
"dimension_precision": 4, "dimension_precision": 4,
"dimension_units": 3, "dimension_units": 3,
"dimensions": { "dimensions": {
@@ -21,13 +24,13 @@
"text_position": 0, "text_position": 0,
"units_format": 1 "units_format": 1
}, },
"fab_line_width": 0.09999999999999999, "fab_line_width": 0.1,
"fab_text_italic": false, "fab_text_italic": false,
"fab_text_size_h": 1.0, "fab_text_size_h": 1.0,
"fab_text_size_v": 1.0, "fab_text_size_v": 1.0,
"fab_text_thickness": 0.15, "fab_text_thickness": 0.15,
"fab_text_upright": false, "fab_text_upright": false,
"other_line_width": 0.09999999999999999, "other_line_width": 0.1,
"other_text_italic": false, "other_text_italic": false,
"other_text_size_h": 1.0, "other_text_size_h": 1.0,
"other_text_size_v": 1.0, "other_text_size_v": 1.0,
@@ -68,15 +71,20 @@
"copper_edge_clearance": "error", "copper_edge_clearance": "error",
"copper_sliver": "warning", "copper_sliver": "warning",
"courtyards_overlap": "warning", "courtyards_overlap": "warning",
"creepage": "error",
"diff_pair_gap_out_of_range": "error", "diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error", "diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error", "drill_out_of_range": "error",
"duplicate_footprints": "warning", "duplicate_footprints": "warning",
"extra_footprint": "warning", "extra_footprint": "warning",
"footprint": "error", "footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "error", "footprint_type_mismatch": "error",
"hole_clearance": "error", "hole_clearance": "error",
"hole_near_hole": "error", "hole_near_hole": "error",
"hole_to_hole": "error",
"holes_co_located": "warning",
"invalid_outline": "error", "invalid_outline": "error",
"isolated_copper": "warning", "isolated_copper": "warning",
"item_on_disabled_layer": "error", "item_on_disabled_layer": "error",
@@ -86,9 +94,11 @@
"lib_footprint_mismatch": "warning", "lib_footprint_mismatch": "warning",
"malformed_courtyard": "error", "malformed_courtyard": "error",
"microvia_drill_out_of_range": "error", "microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore", "missing_courtyard": "ignore",
"missing_footprint": "warning", "missing_footprint": "warning",
"net_conflict": "warning", "net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore", "npth_inside_courtyard": "ignore",
"padstack": "error", "padstack": "error",
"pth_inside_courtyard": "ignore", "pth_inside_courtyard": "ignore",
@@ -100,10 +110,13 @@
"solder_mask_bridge": "error", "solder_mask_bridge": "error",
"starved_thermal": "error", "starved_thermal": "error",
"text_height": "warning", "text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning", "text_thickness": "warning",
"through_hole_pad_without_hole": "error", "through_hole_pad_without_hole": "error",
"too_many_vias": "error", "too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning", "track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error", "track_width": "error",
"tracks_crossing": "error", "tracks_crossing": "error",
"unconnected_items": "error", "unconnected_items": "error",
@@ -119,58 +132,63 @@
"max_error": 0.005, "max_error": 0.005,
"min_clearance": 0.0, "min_clearance": 0.0,
"min_connection": 0.0, "min_connection": 0.0,
"min_copper_edge_clearance": 0.09999999999999999, "min_copper_edge_clearance": 0.1,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25, "min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25, "min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.19999999999999998, "min_microvia_diameter": 0.2,
"min_microvia_drill": 0.09999999999999999, "min_microvia_drill": 0.1,
"min_resolved_spokes": 2, "min_resolved_spokes": 2,
"min_silk_clearance": 0.0, "min_silk_clearance": 0.0,
"min_text_height": 0.7999999999999999, "min_text_height": 0.8,
"min_text_thickness": 0.08, "min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3, "min_through_hole_diameter": 0.3,
"min_track_width": 0.15, "min_track_width": 0.15,
"min_via_annular_width": 0.049999999999999996, "min_via_annular_width": 0.05,
"min_via_diameter": 0.39999999999999997, "min_via_diameter": 0.4,
"solder_mask_to_copper_clearance": 0.0, "solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true "use_height_for_length_calcs": true
}, },
"teardrop_options": [ "teardrop_options": [
{ {
"td_allow_use_two_tracks": true, "td_onpthpad": true,
"td_curve_segcount": 5,
"td_on_pad_in_zone": false,
"td_onpadsmd": true,
"td_onroundshapesonly": false, "td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false, "td_ontrackend": false,
"td_onviapad": true "td_onvia": true
} }
], ],
"teardrop_parameters": [ "teardrop_parameters": [
{ {
"td_curve_segcount": 0, "td_allow_use_two_tracks": true,
"td_curve_segcount": 1,
"td_height_ratio": 1.0, "td_height_ratio": 1.0,
"td_length_ratio": 0.5, "td_length_ratio": 0.5,
"td_maxheight": 2.0, "td_maxheight": 2.0,
"td_maxlen": 1.0, "td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape", "td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9 "td_width_to_size_filter_ratio": 0.9
}, },
{ {
"td_curve_segcount": 0, "td_allow_use_two_tracks": true,
"td_curve_segcount": 1,
"td_height_ratio": 1.0, "td_height_ratio": 1.0,
"td_length_ratio": 0.5, "td_length_ratio": 0.5,
"td_maxheight": 2.0, "td_maxheight": 2.0,
"td_maxlen": 1.0, "td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape", "td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9 "td_width_to_size_filter_ratio": 0.9
}, },
{ {
"td_curve_segcount": 0, "td_allow_use_two_tracks": true,
"td_curve_segcount": 1,
"td_height_ratio": 1.0, "td_height_ratio": 1.0,
"td_length_ratio": 0.5, "td_length_ratio": 0.5,
"td_maxheight": 2.0, "td_maxheight": 2.0,
"td_maxlen": 1.0, "td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end", "td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9 "td_width_to_size_filter_ratio": 0.9
} }
@@ -181,6 +199,32 @@
0.25, 0.25,
0.5 0.5
], ],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [ "via_dimensions": [
{ {
"diameter": 0.0, "diameter": 0.0,
@@ -209,6 +253,7 @@
"mfg": "", "mfg": "",
"mpn": "" "mpn": ""
}, },
"layer_pairs": [],
"layer_presets": [], "layer_presets": [],
"viewports": [] "viewports": []
}, },
@@ -403,10 +448,15 @@
"duplicate_sheet_names": "error", "duplicate_sheet_names": "error",
"endpoint_off_grid": "warning", "endpoint_off_grid": "warning",
"extra_units": "error", "extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning", "global_label_dangling": "warning",
"hier_label_mismatch": "error", "hier_label_mismatch": "error",
"label_dangling": "error", "label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning", "lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning", "missing_bidi_pin": "warning",
"missing_input_pin": "warning", "missing_input_pin": "warning",
"missing_power_pin": "error", "missing_power_pin": "error",
@@ -419,9 +469,15 @@
"pin_not_driven": "error", "pin_not_driven": "error",
"pin_to_pin": "warning", "pin_to_pin": "warning",
"power_pin_not_driven": "error", "power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning", "similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore", "simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error", "unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error", "unit_value_mismatch": "error",
"unresolved_variable": "error", "unresolved_variable": "error",
"wire_dangling": "error" "wire_dangling": "error"
@@ -433,7 +489,7 @@
}, },
"meta": { "meta": {
"filename": "stm32.kicad_pro", "filename": "stm32.kicad_pro",
"version": 1 "version": 3
}, },
"net_settings": { "net_settings": {
"classes": [ "classes": [
@@ -448,6 +504,7 @@
"microvia_drill": 0.1, "microvia_drill": 0.1,
"name": "Default", "name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)", "pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)", "schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2, "track_width": 0.2,
"via_diameter": 0.8, "via_diameter": 0.8,
@@ -465,6 +522,7 @@
"microvia_drill": 0.1, "microvia_drill": 0.1,
"name": "0.25", "name": "0.25",
"pcb_color": "rgba(0, 0, 0, 0.000)", "pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 0,
"schematic_color": "rgba(0, 0, 0, 0.000)", "schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.25, "track_width": 0.25,
"via_diameter": 1.0, "via_diameter": 1.0,
@@ -482,6 +540,7 @@
"microvia_drill": 0.1, "microvia_drill": 0.1,
"name": "0.5", "name": "0.5",
"pcb_color": "rgba(0, 0, 0, 0.000)", "pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 1,
"schematic_color": "rgba(0, 0, 0, 0.000)", "schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.5, "track_width": 0.5,
"via_diameter": 1.4, "via_diameter": 1.4,
@@ -490,7 +549,7 @@
} }
], ],
"meta": { "meta": {
"version": 3 "version": 4
}, },
"net_colors": null, "net_colors": null,
"netclass_assignments": null, "netclass_assignments": null,
@@ -512,6 +571,7 @@
}, },
"schematic": { "schematic": {
"annotate_start_num": 0, "annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [], "bom_fmt_presets": [],
"bom_fmt_settings": { "bom_fmt_settings": {
"field_delimiter": ",", "field_delimiter": ",",
@@ -565,6 +625,7 @@
], ],
"filter_string": "", "filter_string": "",
"group_symbols": true, "group_symbols": true,
"include_excluded_from_bom": false,
"name": "Grouped By Value", "name": "Grouped By Value",
"sort_asc": true, "sort_asc": true,
"sort_field": "Обозначение" "sort_field": "Обозначение"
@@ -611,6 +672,7 @@
}, },
"page_layout_descr_file": "", "page_layout_descr_file": "",
"plot_directory": "", "plot_directory": "",
"space_save_all_events": true,
"spice_adjust_passive_values": false, "spice_adjust_passive_values": false,
"spice_current_sheet_as_root": false, "spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"", "spice_external_command": "spice \"%I\"",
@@ -624,7 +686,7 @@
"sheets": [ "sheets": [
[ [
"ce45f0ed-5b96-460f-9f82-e8a7af322e24", "ce45f0ed-5b96-460f-9f82-e8a7af322e24",
"Корневой лист" "Root"
] ]
], ],
"text_variables": {} "text_variables": {}

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,7 @@
#include "can.h" #include "can.h"
#include "hardware.h" #include "hardware.h"
#include "proto.h" #include "proto.h"
#include "usb.h" #include "usb_dev.h"
#include "usb_lib.h"
#define MAXSTRLEN 128 #define MAXSTRLEN 128

View File

@@ -1,96 +1,4 @@
# script for stm32f0x family set FLASH_SIZE 0x8000
#
# stm32 devices support SWD transports only.
#
source [find interface/stlink-v2-1.cfg] source [find interface/stlink-v2-1.cfg]
source [find target/swj-dp.tcl] source [find target/stm32f0x.cfg]
source [find mem_helper.tcl]
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME stm32f0x
}
set _ENDIAN little
# Work-area is a space in RAM used for flash programming
# By default use 4kB
if { [info exists WORKAREASIZE] } {
set _WORKAREASIZE $WORKAREASIZE
} else {
set _WORKAREASIZE 0x1000
}
# Allow overriding the Flash bank size
if { [info exists FLASH_SIZE] } {
set _FLASH_SIZE $FLASH_SIZE
} else {
# autodetect size
set _FLASH_SIZE 0
}
#jtag scan chain
if { [info exists CPUTAPID] } {
set _CPUTAPID $CPUTAPID
} else {
# See STM Document RM0091
# Section 29.5.3
set _CPUTAPID 0x0bb11477
}
swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME cortex_m -endian $_ENDIAN -dap $_CHIPNAME.dap
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
# flash size will be probed
set _FLASHNAME $_CHIPNAME.flash
flash bank $_FLASHNAME stm32f1x 0x08000000 $_FLASH_SIZE 0 0 $_TARGETNAME
# adapter speed should be <= F_CPU/6. F_CPU after reset is 8MHz, so use F_JTAG = 1MHz
adapter speed 1000
adapter srst delay 100
reset_config srst_nogate
if {![using_hla]} {
# if srst is not fitted use SYSRESETREQ to
# perform a soft reset
cortex_m reset_config sysresetreq
}
proc stm32f0x_default_reset_start {} {
# Reset clock is HSI (8 MHz)
adapter speed 1000
}
proc stm32f0x_default_examine_end {} {
# Enable debug during low power modes (uses more power)
mmw 0x40015804 0x00000006 0 ;# DBGMCU_CR |= DBG_STANDBY | DBG_STOP
# Stop watchdog counters during halt
mmw 0x40015808 0x00001800 0 ;# DBGMCU_APB1_FZ |= DBG_IWDG_STOP | DBG_WWDG_STOP
}
proc stm32f0x_default_reset_init {} {
# Configure PLL to boost clock to HSI x 6 (48 MHz)
mww 0x40021004 0x00100000 ;# RCC_CFGR = PLLMUL[2]
mmw 0x40021000 0x01000000 0 ;# RCC_CR[31:16] |= PLLON
mww 0x40022000 0x00000011 ;# FLASH_ACR = PRFTBE | LATENCY[0]
sleep 10 ;# Wait for PLL to lock
mmw 0x40021004 0x00000002 0 ;# RCC_CFGR |= SW[1]
# Boost JTAG frequency
adapter speed 8000
}
# Default hooks
$_TARGETNAME configure -event examine-end { stm32f0x_default_examine_end }
$_TARGETNAME configure -event reset-start { stm32f0x_default_reset_start }
$_TARGETNAME configure -event reset-init { stm32f0x_default_reset_init }

View File

@@ -19,7 +19,7 @@
#include "can.h" #include "can.h"
#include "hardware.h" #include "hardware.h"
#include "proto.h" #include "proto.h"
#include "usb.h" #include "usb_dev.h"
#include "version.inc" #include "version.inc"
extern volatile uint8_t canerror; extern volatile uint8_t canerror;

View File

@@ -15,6 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <string.h>
#include <stm32f0.h>
#include "ringbuffer.h" #include "ringbuffer.h"
static int datalen(ringbuffer *b){ static int datalen(ringbuffer *b){
@@ -24,7 +26,7 @@ static int datalen(ringbuffer *b){
// stored data length // stored data length
int RB_datalen(ringbuffer *b){ int RB_datalen(ringbuffer *b){
if(b->busy) return -1; if(!b || b->busy) return -1;
b->busy = 1; b->busy = 1;
int l = datalen(b); int l = datalen(b);
b->busy = 0; b->busy = 0;
@@ -32,7 +34,7 @@ int RB_datalen(ringbuffer *b){
} }
static int hasbyte(ringbuffer *b, uint8_t byte){ static int hasbyte(ringbuffer *b, uint8_t byte){
if(b->head == b->tail) return -1; // no data in buffer if(!b || b->head == b->tail) return -1; // no data in buffer
int startidx = b->head; int startidx = b->head;
if(b->head > b->tail){ // if(b->head > b->tail){ //
for(int found = b->head; found < b->length; ++found) for(int found = b->head; found < b->length; ++found)
@@ -51,18 +53,13 @@ static int hasbyte(ringbuffer *b, uint8_t byte){
* @return index if found, -1 if none or busy * @return index if found, -1 if none or busy
*/ */
int RB_hasbyte(ringbuffer *b, uint8_t byte){ int RB_hasbyte(ringbuffer *b, uint8_t byte){
if(b->busy) return -1; if(!b || b->busy) return -1;
b->busy = 1; b->busy = 1;
int ret = hasbyte(b, byte); int ret = hasbyte(b, byte);
b->busy = 0; b->busy = 0;
return ret; return ret;
} }
// poor memcpy
static void mcpy(uint8_t *targ, const uint8_t *src, int l){
while(l--) *targ++ = *src++;
}
// increment head or tail // increment head or tail
TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){ TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){
*what += n; *what += n;
@@ -76,9 +73,9 @@ static int read(ringbuffer *b, uint8_t *s, int len){
int _1st = b->length - b->head; int _1st = b->length - b->head;
if(_1st > l) _1st = l; if(_1st > l) _1st = l;
if(_1st > len) _1st = len; if(_1st > len) _1st = len;
mcpy(s, b->data + b->head, _1st); memcpy(s, b->data + b->head, _1st);
if(_1st < len && l > _1st){ if(_1st < len && l > _1st){
mcpy(s+_1st, b->data, l - _1st); memcpy(s+_1st, b->data, l - _1st);
incr(b, &b->head, l); incr(b, &b->head, l);
return l; return l;
} }
@@ -94,20 +91,27 @@ static int read(ringbuffer *b, uint8_t *s, int len){
* @return bytes read or -1 if busy * @return bytes read or -1 if busy
*/ */
int RB_read(ringbuffer *b, uint8_t *s, int len){ int RB_read(ringbuffer *b, uint8_t *s, int len){
if(b->busy) return -1; if(!b || b->busy || !s || len < 1) return -1;
b->busy = 1; b->busy = 1;
int r = read(b, s, len); int r = read(b, s, len);
b->busy = 0; b->busy = 0;
return r; return r;
} }
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){ // length of data from current position to `byte` (including byte)
static int lento(ringbuffer *b, uint8_t byte){
int idx = hasbyte(b, byte); int idx = hasbyte(b, byte);
if(idx < 0) return 0; if(idx < 0) return 0;
int partlen = idx + 1 - b->head; int partlen = idx + 1 - b->head;
// now calculate length of new data portion // now calculate length of new data portion
if(idx < b->head) partlen += b->length; if(idx < b->head) partlen += b->length;
if(partlen > len) return -read(b, s, len); return partlen;
}
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
int partlen = lento(b, byte);
if(!partlen) return 0;
if(partlen > len) return -1;
return read(b, s, partlen); return read(b, s, partlen);
} }
@@ -115,27 +119,41 @@ static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
* @brief RB_readto fill array `s` with data until byte `byte` (with it) * @brief RB_readto fill array `s` with data until byte `byte` (with it)
* @param b - ringbuffer * @param b - ringbuffer
* @param byte - check byte * @param byte - check byte
* @param s - buffer to write data * @param s - buffer to write data or NULL to clear data
* @param len - length of `s` * @param len - length of `s` or 0 to clear data
* @return amount of bytes written (negative, if len<data in buffer or buffer is busy) * @return amount of bytes written (negative, if len<data in buffer or buffer is busy)
*/ */
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){ int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
if(b->busy) return -1; if(!b || b->busy) return -1;
b->busy = 1; b->busy = 1;
int n = readto(b, byte, s, len); int n = 0;
if(s && len > 0){
n = readto(b, byte, s, len);
}else{
incr(b, &b->head, lento(b, byte)); // just throw data out
}
b->busy = 0; b->busy = 0;
return n; return n;
} }
int RB_datalento(ringbuffer *b, uint8_t byte){
if(!b || b->busy) return -1;
b->busy = 1;
int n = lento(b, byte);
b->busy = 0;
return n;
}
// if l < rest of buffer, truncate and return actually written bytes
static int write(ringbuffer *b, const uint8_t *str, int l){ static int write(ringbuffer *b, const uint8_t *str, int l){
int r = b->length - 1 - datalen(b); // rest length int r = b->length - 1 - datalen(b); // rest length
if(r < 1) return 0;
if(l > r) l = r; if(l > r) l = r;
if(!l) return 0;
int _1st = b->length - b->tail; int _1st = b->length - b->tail;
if(_1st > l) _1st = l; if(_1st > l) _1st = l;
mcpy(b->data + b->tail, str, _1st); memcpy(b->data + b->tail, str, _1st);
if(_1st < l){ // add another piece from start if(_1st < l){ // add another piece from start
mcpy(b->data, str+_1st, l-_1st); memcpy(b->data, str+_1st, l-_1st);
} }
incr(b, &b->tail, l); incr(b, &b->tail, l);
return l; return l;
@@ -149,7 +167,7 @@ static int write(ringbuffer *b, const uint8_t *str, int l){
* @return amount of bytes written or -1 if busy * @return amount of bytes written or -1 if busy
*/ */
int RB_write(ringbuffer *b, const uint8_t *str, int l){ int RB_write(ringbuffer *b, const uint8_t *str, int l){
if(b->busy) return -1; if(!b || b->busy || !str || l < 1) return -1;
b->busy = 1; b->busy = 1;
int w = write(b, str, l); int w = write(b, str, l);
b->busy = 0; b->busy = 0;
@@ -158,7 +176,7 @@ int RB_write(ringbuffer *b, const uint8_t *str, int l){
// just delete all information in buffer `b` // just delete all information in buffer `b`
int RB_clearbuf(ringbuffer *b){ int RB_clearbuf(ringbuffer *b){
if(b->busy) return -1; if(!b || b->busy) return -1;
b->busy = 1; b->busy = 1;
b->head = 0; b->head = 0;
b->tail = 0; b->tail = 0;

View File

@@ -17,13 +17,7 @@
#pragma once #pragma once
#if defined STM32F0 #include <stdint.h>
#include <stm32f0.h>
#elif defined STM32F1
#include <stm32f1.h>
#elif defined STM32F3
#include <stm32f3.h>
#endif
typedef struct{ typedef struct{
uint8_t *data; // data buffer uint8_t *data; // data buffer
@@ -38,4 +32,5 @@ int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len);
int RB_hasbyte(ringbuffer *b, uint8_t byte); int RB_hasbyte(ringbuffer *b, uint8_t byte);
int RB_write(ringbuffer *b, const uint8_t *str, int l); int RB_write(ringbuffer *b, const uint8_t *str, int l);
int RB_datalen(ringbuffer *b); int RB_datalen(ringbuffer *b);
int RB_datalento(ringbuffer *b, uint8_t byte);
int RB_clearbuf(ringbuffer *b); int RB_clearbuf(ringbuffer *b);

View File

@@ -1,139 +0,0 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "hardware.h"
#include "usb.h"
#include "usb_lib.h"
static volatile uint8_t usbbuff[USB_TXBUFSZ]; // temporary buffer for sending data
// ring buffers for incoming and outgoing data
static uint8_t obuf[RBOUTSZ], ibuf[RBINSZ];
volatile ringbuffer rbout = {.data = obuf, .length = RBOUTSZ, .head = 0, .tail = 0};
volatile ringbuffer rbin = {.data = ibuf, .length = RBINSZ, .head = 0, .tail = 0};
// inbuf overflow when receiving
volatile uint8_t bufovrfl = 0;
// last send data size
static volatile int lastdsz = 0;
// called from transmit EP
void send_next(){
IWDG->KR = IWDG_REFRESH;
int buflen = RB_read((ringbuffer*)&rbout, (uint8_t*)usbbuff, USB_TXBUFSZ);
if(buflen == 0){
if(lastdsz == 64) EP_Write(3, NULL, 0); // send ZLP after 64 bits packet when nothing more to send
lastdsz = 0;
return;
}else if(buflen < 0){
lastdsz = 0;
// Uncomment next line if you want 4Mbit/s instead of 6Mbit/s
//EP_Write(3, NULL, 0); // send ZLP if buffer is in writting state now
return;
}
EP_Write(3, (uint8_t*)usbbuff, buflen);
lastdsz = buflen;
}
// blocking send full content of ring buffer
int USB_sendall(){
while(lastdsz > 0){
IWDG->KR = IWDG_REFRESH;
if(!usbON) return FALSE;
}
return TRUE;
}
// put `buf` into queue to send
int USB_send(const uint8_t *buf, int len){
if(!buf || !usbON || !len) return FALSE;
while(len){
IWDG->KR = IWDG_REFRESH;
int a = RB_write((ringbuffer*)&rbout, buf, len);
if(a > 0){
len -= a;
buf += a;
} else if (a < 0) continue; // do nothing if buffer is in reading state
if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN
}
return TRUE;
}
int USB_putbyte(uint8_t byte){
if(!usbON) return FALSE;
int l = 0;
while((l = RB_write((ringbuffer*)&rbout, &byte, 1)) != 1){
IWDG->KR = IWDG_REFRESH;
if(l < 0) continue;
}
if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN
return TRUE;
}
int USB_sendstr(const char *string){
if(!string || !usbON) return FALSE;
int len = 0;
const char *b = string;
while(*b++) ++len;
if(!len) return FALSE;
return USB_send((const uint8_t*)string, len);
}
/**
* @brief USB_receive - get binary data from receiving ring-buffer
* @param buf (i) - buffer for received data
* @param len - length of `buf`
* @return amount of received bytes (negative, if overfull happened)
*/
int USB_receive(uint8_t *buf, int len){
chkin();
if(bufovrfl){
while(1 != RB_clearbuf((ringbuffer*)&rbin)) IWDG->KR = IWDG_REFRESH;
bufovrfl = 0;
return -1;
}
int sz = RB_read((ringbuffer*)&rbin, buf, len);
if(sz < 0) return 0; // buffer in writting state
return sz;
}
/**
* @brief USB_receivestr - get string up to '\n' and replace '\n' with 0
* @param buf - receiving buffer
* @param len - its length
* @return strlen or negative value indicating overflow (if so, string won't be ends with 0 and buffer should be cleared)
*/
int USB_receivestr(char *buf, int len){
chkin();
if(bufovrfl){
while(1 != RB_clearbuf((ringbuffer*)&rbin)) IWDG->KR = IWDG_REFRESH;
bufovrfl = 0;
return -1;
}
int l = RB_readto((ringbuffer*)&rbin, '\n', (uint8_t*)buf, len);
if(l < 1){
if(rbin.length == RB_datalen((ringbuffer*)&rbin)){ // buffer is full but no '\n' found
while(1 != RB_clearbuf((ringbuffer*)&rbin)) IWDG->KR = IWDG_REFRESH;
return -1;
}
return 0;
}
if(l == 0) return 0;
buf[l-1] = 0; // replace '\n' with strend
return l;
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "usb_descr.h"
// low/high for uint16_t
#define L16(x) (x & 0xff)
#define H16(x) (x >> 8)
static const uint8_t USB_DeviceDescriptor[] = {
USB_DT_DEVICE_SIZE, // bLength
USB_DT_DEVICE, // bDescriptorType
L16(bcdUSB), // bcdUSB_L
H16(bcdUSB), // bcdUSB_H
USB_CLASS_MISC, // bDeviceClass
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0BUFSZ, // bMaxPacketSize
L16(idVendor), // idVendor_L
H16(idVendor), // idVendor_H
L16(idProduct), // idProduct_L
H16(idProduct), // idProduct_H
L16(bcdDevice_Ver), // bcdDevice_Ver_L
H16(bcdDevice_Ver), // bcdDevice_Ver_H
iMANUFACTURER_DESCR, // iManufacturer - indexes of string descriptors in array
iPRODUCT_DESCR, // iProduct
iSERIAL_DESCR, // iSerial
bNumConfigurations // bNumConfigurations
};
static const uint8_t USB_DeviceQualifierDescriptor[] = {
USB_DT_QUALIFIER_SIZE, //bLength
USB_DT_QUALIFIER, // bDescriptorType
L16(bcdUSB), // bcdUSB_L
H16(bcdUSB), // bcdUSB_H
USB_CLASS_PER_INTERFACE, // bDeviceClass
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0BUFSZ, // bMaxPacketSize0
bNumConfigurations, // bNumConfigurations
0 // Reserved
};
#define wTotalLength (USB_DT_CONFIG_SIZE + (bNumInterfaces * USB_DT_INTERFACE_SIZE) + (bTotNumEndpoints * USB_DT_ENDPOINT_SIZE) + (bNumCsInterfaces * USB_DT_CS_INTERFACE_SIZE) - 1)
static const uint8_t USB_ConfigDescriptor[] = {
// Configuration Descriptor
USB_DT_CONFIG_SIZE, // bLength: Configuration Descriptor size
USB_DT_CONFIG, // bDescriptorType: Configuration
L16(wTotalLength), // wTotalLength.L :no of returned bytes
H16(wTotalLength), // wTotalLength.H
bNumInterfaces, // bNumInterfaces
1, // bConfigurationValue: Current configuration value
0, // iConfiguration: Index of string descriptor describing the configuration or 0
BusPowered, // bmAttributes - Bus powered
50, // MaxPower in 2mA units
//---------------------------------------------------------------------------
// Virtual command Interface Descriptor
USB_DT_INTERFACE_SIZE, // bLength: Interface Descriptor size
USB_DT_INTERFACE, // bDescriptorType: Interface
0, // bInterfaceNumber: Number of Interface
0, // bAlternateSetting: Alternate setting
1, // bNumEndpoints: one for this
USB_CLASS_COMM, // bInterfaceClass
2, // bInterfaceSubClass: ACM
1, // bInterfaceProtocol: Common AT commands
iINTERFACE_DESCR1, // iInterface
// ---- CS Interfaces
USB_DT_CS_INTERFACE_SIZE, // bLength
USB_DT_CS_INTERFACE, // bDescriptorType: CS_INTERFACE
0, // bDescriptorSubtype: Header Func Desc
0x10, // bcdCDC: spec release number
1, // bDataInterface
USB_DT_CS_INTERFACE_SIZE, // bLength
USB_DT_CS_INTERFACE, // bDescriptorType: CS_INTERFACE
1, // bDescriptorSubtype: Call Management Func Desc
0, // bmCapabilities: D0+D1
1, // bDataInterface
USB_DT_CS_INTERFACE_SIZE-1, // bLength
USB_DT_CS_INTERFACE, // bDescriptorType: CS_INTERFACE
2, // bDescriptorSubtype: Abstract Control Management desc
2, // bmCapabilities
USB_DT_CS_INTERFACE_SIZE, // bLength
USB_DT_CS_INTERFACE, // bDescriptorType: CS_INTERFACE
6, // bDescriptorSubtype: Union func desc
0, // bMasterInterface: Communication class interface
1, // bSlaveInterface0: Data Class Interface
// Virtual endpoint 1 Descriptor
USB_DT_ENDPOINT_SIZE, // bLength: Endpoint Descriptor size
USB_DT_ENDPOINT, // bDescriptorType: Endpoint
0x8A, // bEndpointAddress IN10
USB_BM_ATTR_INTERRUPT, // bmAttributes: Interrupt
L16(USB_EP1BUFSZ), // wMaxPacketSize LO
H16(USB_EP1BUFSZ), // wMaxPacketSize HI
0x10, // bInterval: 16ms
//---------------------------------------------------------------------------
// Data interface
USB_DT_INTERFACE_SIZE, // bLength: Interface Descriptor size
USB_DT_INTERFACE, // bDescriptorType: Interface
1, // bInterfaceNumber: Number of Interface
0, // bAlternateSetting: Alternate setting
2, // bNumEndpoints: in and out
USB_CLASS_DATA, // bInterfaceClass
2, // bInterfaceSubClass: ACM
0, // bInterfaceProtocol
0, // iInterface
//Endpoint IN1 Descriptor
USB_DT_ENDPOINT_SIZE, // bLength: Endpoint Descriptor size
USB_DT_ENDPOINT, // bDescriptorType: Endpoint
0x81, // bEndpointAddress: IN1
USB_BM_ATTR_BULK, // bmAttributes: Bulk
L16(USB_TXBUFSZ), // wMaxPacketSize LO
H16(USB_TXBUFSZ), // wMaxPacketSize HI
0, // bInterval: ignore for Bulk transfer
// Endpoint OUT1 Descriptor
USB_DT_ENDPOINT_SIZE, // bLength: Endpoint Descriptor size
USB_DT_ENDPOINT, // bDescriptorType: Endpoint
0x01, // bEndpointAddress: OUT1
USB_BM_ATTR_BULK, // bmAttributes: Bulk
L16(USB_RXBUFSZ), // wMaxPacketSize LO
H16(USB_RXBUFSZ), // wMaxPacketSize HI
0, // bInterval: ignore for Bulk transfer
};
//const uint8_t HID_ReportDescriptor[];
_USB_LANG_ID_(LD, LANG_US);
_USB_STRING_(SD, u"0.0.2");
_USB_STRING_(MD, u"eddy@sao.ru");
_USB_STRING_(PD, u"USB-Serial Controller");
_USB_STRING_(ID, u"USB-STM32");
static const void* const StringDescriptor[iDESCR_AMOUNT] = {
[iLANGUAGE_DESCR] = &LD,
[iMANUFACTURER_DESCR] = &MD,
[iPRODUCT_DESCR] = &PD,
[iSERIAL_DESCR] = &SD,
[iINTERFACE_DESCR1] = &ID
};
static void wr0(const uint8_t *buf, uint16_t size, uint16_t askedsize){
if(askedsize < size) size = askedsize; // shortened request
if(size < USB_EP0BUFSZ){
EP_WriteIRQ(0, buf, size);
return;
}
while(size){
uint16_t l = size;
if(l > USB_EP0BUFSZ) l = USB_EP0BUFSZ;
EP_WriteIRQ(0, buf, l);
buf += l;
size -= l;
uint8_t needzlp = (l == USB_EP0BUFSZ) ? 1 : 0;
if(size || needzlp){ // send last data buffer
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]);
// keep DTOGs, clear CTR_RX,TX, set TX VALID, leave stat_Rx
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX|USB_EPnR_STAT_RX))
^ USB_EPnR_STAT_TX;
uint32_t ctr = 1000000;
while(--ctr && (USB->ISTR & USB_ISTR_CTR) == 0){IWDG->KR = IWDG_REFRESH;};
if((USB->ISTR & USB_ISTR_CTR) == 0){
return;
}
if(needzlp) EP_WriteIRQ(0, NULL, 0);
}
}
}
void get_descriptor(config_pack_t *pack){
uint8_t descrtype = pack->wValue >> 8,
descridx = pack->wValue & 0xff;
switch(descrtype){
case DEVICE_DESCRIPTOR:
wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor), pack->wLength);
break;
case CONFIGURATION_DESCRIPTOR:
wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor), pack->wLength);
break;
case STRING_DESCRIPTOR:
if(descridx < iDESCR_AMOUNT){
wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx]), pack->wLength);
}else{
EP_WriteIRQ(0, NULL, 0);
}
break;
case DEVICE_QUALIFIER_DESCRIPTOR:
wr0(USB_DeviceQualifierDescriptor, sizeof(USB_DeviceQualifierDescriptor), pack->wLength);
break;
/* case HID_REPORT_DESCRIPTOR:
wr0(HID_ReportDescriptor, sizeof(HID_ReportDescriptor), pack->wLength);
break;*/
default:
break;
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include "usb_lib.h"
// definition of parts common for USB_DeviceDescriptor & USB_DeviceQualifierDescriptor
// bcdUSB: 1.10
#define bcdUSB 0x0110
// Class - Misc (EF), subclass - common (2), protocol - interface association descr (1)
#define bDeviceSubClass 0x02
#define bDeviceProtocol 0x01
#define idVendor 0x0483
#define idProduct 0x5740
#define bcdDevice_Ver 0x0200
#define bNumConfigurations 1
// amount of interfaces and endpoints (except 0) used
#define bNumInterfaces 2
#define bTotNumEndpoints 3
#define bNumCsInterfaces 4
// powered
#define BusPowered (1<<7)
#define SelfPowered (1<<6)
#define RemoteWakeup (1<<5)
// buffer sizes
// Rx buffer should be not more than 64 and multiple of 2 (or 4 for STM32G0), or multiple of 32
// Tx buffer should be multiple of 2 (or 4 for STM32G0)
// for USB FS EP0 buffers are from 8 to 64 bytes long
#define USB_EP0BUFSZ 64
#define USB_EP1BUFSZ 10
// Rx/Tx EPs (not more than 64 bytes for CDC)
#define USB_RXBUFSZ 64
#define USB_TXBUFSZ 64
// string descriptors
enum{
iLANGUAGE_DESCR,
iMANUFACTURER_DESCR,
iPRODUCT_DESCR,
iSERIAL_DESCR,
iINTERFACE_DESCR1,
iDESCR_AMOUNT
};
void get_descriptor(config_pack_t *pack);

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "ringbuffer.h"
#include "usb_descr.h"
#include "usb_dev.h"
// Class-Specific Control Requests
#define SEND_ENCAPSULATED_COMMAND 0x00 // unused
#define GET_ENCAPSULATED_RESPONSE 0x01 // unused
#define SET_COMM_FEATURE 0x02 // unused
#define GET_COMM_FEATURE 0x03 // unused
#define CLEAR_COMM_FEATURE 0x04 // unused
#define SET_LINE_CODING 0x20
#define GET_LINE_CODING 0x21
#define SET_CONTROL_LINE_STATE 0x22
#define SEND_BREAK 0x23
// control line states
#define CONTROL_DTR 0x01
#define CONTROL_RTS 0x02
// inbuf overflow when receiving
static volatile uint8_t bufovrfl = 0;
// receive buffer: hold data until chkin() call
static uint8_t volatile rcvbuf[USB_RXBUFSZ] __attribute__((aligned(4)));
static uint8_t volatile rcvbuflen = 0;
// line coding
usb_LineCoding lineCoding = {115200, 0, 0, 8};
// CDC configured and ready to use
volatile uint8_t CDCready = 0;
// ring buffers for incoming and outgoing data
static uint8_t obuf[RBOUTSZ], ibuf[RBINSZ];
static volatile ringbuffer rbout = {.data = obuf, .length = RBOUTSZ, .head = 0, .tail = 0};
static volatile ringbuffer rbin = {.data = ibuf, .length = RBINSZ, .head = 0, .tail = 0};
// last send data size
static volatile int lastdsz = 0;
static void chkin(){
if(bufovrfl) return; // allow user to know that previous buffer was overflowed and cleared
if(!rcvbuflen) return;
int w = RB_write((ringbuffer*)&rbin, (uint8_t*)rcvbuf, rcvbuflen);
if(w < 0){
return;
}
if(w != rcvbuflen) bufovrfl = 1;
rcvbuflen = 0;
uint16_t status = KEEP_DTOG(USB->EPnR[1]); // don't change DTOG
USB->EPnR[1] = (status & ~(USB_EPnR_STAT_TX|USB_EPnR_CTR_RX)) ^ USB_EPnR_STAT_RX; // prepare to get next data portion
}
// called from transmit EP to send next data portion or by user - when new transmission starts
static void send_next(){
uint8_t usbbuff[USB_TXBUFSZ] __attribute__((aligned(4)));
int buflen = RB_read((ringbuffer*)&rbout, (uint8_t*)usbbuff, USB_TXBUFSZ);
if(buflen == 0){
if(lastdsz == USB_TXBUFSZ) EP_Write(1, NULL, 0); // send ZLP after USB_TXBUFSZ bits packet when nothing more to send
lastdsz = 0;
return;
}else if(buflen < 0){
lastdsz = 0;
return;
}
EP_Write(1, (uint8_t*)usbbuff, buflen);
lastdsz = buflen;
}
// data IN/OUT handler
static void rxtx_handler(){
uint16_t epstatus = KEEP_DTOG(USB->EPnR[1]);
if(RX_FLAG(epstatus)){ // receive data
if(rcvbuflen){
bufovrfl = 1; // lost last data
rcvbuflen = 0;
}
rcvbuflen = EP_Read(1, (uint8_t*)rcvbuf);
USB->EPnR[1] = epstatus & ~(USB_EPnR_CTR_RX | USB_EPnR_STAT_RX | USB_EPnR_STAT_TX); // keep RX in STALL state until read data
chkin(); // try to write current data into RXbuf if it's not busy
}else{ // tx successfull
USB->EPnR[1] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_TX)) ^ USB_EPnR_STAT_RX;
send_next();
}
}
// weak handlers: change them somewhere else if you want to setup USART
// SET_LINE_CODING
void linecoding_handler(usb_LineCoding *lc){
lineCoding = *lc;
}
// SET_CONTROL_LINE_STATE
void clstate_handler(uint16_t val){
CDCready = val; // CONTROL_DTR | CONTROL_RTS -> interface connected; 0 -> disconnected
}
// SEND_BREAK
void break_handler(){
CDCready = 0;
}
// USB is configured: setup endpoints
void set_configuration(){
EP_Init(1, EP_TYPE_BULK, USB_TXBUFSZ, USB_RXBUFSZ, rxtx_handler); // IN1 and OUT1
}
// PL2303 CLASS request
void usb_class_request(config_pack_t *req, uint8_t *data, uint16_t datalen){
uint8_t recipient = REQUEST_RECIPIENT(req->bmRequestType);
uint8_t dev2host = (req->bmRequestType & 0x80) ? 1 : 0;
switch(recipient){
case REQ_RECIPIENT_INTERFACE:
switch(req->bRequest){
case SET_LINE_CODING:
if(!data || !datalen) break; // wait for data
if(datalen == sizeof(usb_LineCoding))
linecoding_handler((usb_LineCoding*)data);
break;
case GET_LINE_CODING:
EP_WriteIRQ(0, (uint8_t*)&lineCoding, sizeof(lineCoding));
break;
case SET_CONTROL_LINE_STATE:
clstate_handler(req->wValue);
break;
case SEND_BREAK:
break_handler();
break;
default:
break;
}
break;
default:
if(dev2host) EP_WriteIRQ(0, NULL, 0);
}
if(!dev2host) EP_WriteIRQ(0, NULL, 0);
}
// blocking send full content of ring buffer
int USB_sendall(){
while(lastdsz > 0){
if(!CDCready) return FALSE;
}
return TRUE;
}
// put `buf` into queue to send
int USB_send(const uint8_t *buf, int len){
if(!buf || !CDCready || !len) return FALSE;
while(len){
IWDG->KR = IWDG_REFRESH;
int l = RB_datalen((ringbuffer*)&rbout);
if(l < 0) continue;
int portion = rbout.length - 1 - l;
if(portion < 1){
if(lastdsz == 0) send_next();
continue;
}
if(portion > len) portion = len;
int a = RB_write((ringbuffer*)&rbout, buf, portion);
if(a > 0){
len -= a;
buf += a;
} else if (a < 0) continue; // do nothing if buffer is in reading state
if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN
}
return TRUE;
}
int USB_putbyte(uint8_t byte){
if(!CDCready) return FALSE;
int l = 0;
while((l = RB_write((ringbuffer*)&rbout, &byte, 1)) != 1){
if(l < 0) continue;
}
if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN
return TRUE;
}
int USB_sendstr(const char *string){
if(!string || !CDCready) return FALSE;
int len = 0;
const char *b = string;
while(*b++) ++len;
if(!len) return FALSE;
return USB_send((const uint8_t*)string, len);
}
/**
* @brief USB_receive - get binary data from receiving ring-buffer
* @param buf (i) - buffer for received data
* @param len - length of `buf`
* @return amount of received bytes (negative, if overfull happened)
*/
int USB_receive(uint8_t *buf, int len){
chkin();
if(bufovrfl){
while(1 != RB_clearbuf((ringbuffer*)&rbin));
bufovrfl = 0;
return -1;
}
int sz = RB_read((ringbuffer*)&rbin, buf, len);
if(sz < 0) return 0; // buffer in writting state
return sz;
}
/**
* @brief USB_receivestr - get string up to '\n' and replace '\n' with 0
* @param buf - receiving buffer
* @param len - its length
* @return strlen or negative value indicating overflow
*/
int USB_receivestr(char *buf, int len){
chkin();
if(bufovrfl){
while(1 != RB_clearbuf((ringbuffer*)&rbin));
bufovrfl = 0;
return -1;
}
int l = RB_datalento((ringbuffer*)&rbin, '\n');
if(l > len){ // can't read: line too long -> clear it
RB_readto((ringbuffer*)&rbin, '\n', NULL, 0);
return -1;
}else if(l < 1){ // nothing or no '\n' ?
if(rbin.length == RB_datalen((ringbuffer*)&rbin)){ // buffer is full but no '\n' found
while(1 != RB_clearbuf((ringbuffer*)&rbin));
return -1;
}
return 0;
}
l = RB_readto((ringbuffer*)&rbin, '\n', (uint8_t*)buf, len);
if(l == 0) return 0;
buf[l-1] = 0; // replace '\n' with strend
return l;
}

View File

@@ -14,32 +14,41 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#pragma once #pragma once
#include "ringbuffer.h" #include <stdint.h>
#include "usbhw.h" #include "usb_lib.h"
typedef struct {
uint32_t dwDTERate;
uint8_t bCharFormat;
#define USB_CDC_1_STOP_BITS 0
#define USB_CDC_1_5_STOP_BITS 1
#define USB_CDC_2_STOP_BITS 2
uint8_t bParityType;
#define USB_CDC_NO_PARITY 0
#define USB_CDC_ODD_PARITY 1
#define USB_CDC_EVEN_PARITY 2
#define USB_CDC_MARK_PARITY 3
#define USB_CDC_SPACE_PARITY 4
uint8_t bDataBits;
} __attribute__ ((packed)) usb_LineCoding;
extern usb_LineCoding lineCoding;
extern volatile uint8_t CDCready;
void break_handler();
void clstate_handler(uint16_t val);
void linecoding_handler(usb_LineCoding *lc);
// sizes of ringbuffers for outgoing and incoming data // sizes of ringbuffers for outgoing and incoming data
#define RBOUTSZ (512) #define RBOUTSZ (1024)
#define RBINSZ (256) #define RBINSZ (1024)
#define newline() USB_putbyte('\n') #define newline() USB_putbyte('\n')
#define USND(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0) #define USND(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0)
#define STR_HELPER(s) #s
#define STR(s) STR_HELPER(s)
#ifdef EBUG
#define DBG(str) do{USB_sendstr(__FILE__ " (L" STR(__LINE__) "): " str); newline();}while(0)
#else
#define DBG(str)
#endif
extern volatile ringbuffer rbout, rbin;
extern volatile uint8_t bufisempty, bufovrfl;
void send_next();
int USB_sendall(); int USB_sendall();
int USB_send(const uint8_t *buf, int len); int USB_send(const uint8_t *buf, int len);
int USB_putbyte(uint8_t byte); int USB_putbyte(uint8_t byte);

View File

@@ -15,280 +15,37 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <stdint.h> #include <stdint.h>
#include "usb.h"
#include "usb_lib.h"
#include "usbhw.h"
ep_t endpoints[STM32ENDPOINTS]; #include "usb_lib.h"
#include "usb_descr.h"
#include "usb_dev.h"
static ep_t endpoints[STM32ENDPOINTS];
static uint16_t USB_Addr = 0; static uint16_t USB_Addr = 0;
static usb_LineCoding lineCoding = {115200, 0, 0, 8}; static uint8_t setupdatabuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
uint8_t ep0databuf[EP0DATABUF_SIZE], setupdatabuf[EP0DATABUF_SIZE]; static config_pack_t *setup_packet = (config_pack_t*) setupdatabuf;
config_pack_t *setup_packet = (config_pack_t*) setupdatabuf; volatile uint8_t usbON = 0; // device is configured and active
usb_LineCoding getLineCoding(){return lineCoding;}
volatile uint8_t usbON = 0; // device disconnected from terminal
// definition of parts common for USB_DeviceDescriptor & USB_DeviceQualifierDescriptor
#define bcdUSB_L 0x10
#define bcdUSB_H 0x01
#define bDeviceClass 0
#define bDeviceSubClass 0
#define bDeviceProtocol 0
#define bNumConfigurations 1
static const uint8_t USB_DeviceDescriptor[] = {
18, // bLength
0x01, // bDescriptorType - Device descriptor
bcdUSB_L, // bcdUSB_L - 1.10
bcdUSB_H, // bcdUSB_H
bDeviceClass, // bDeviceClass - USB_COMM
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0_BUFSZ, // bMaxPacketSize
0x7b, // idVendor_L PL2303: VID=0x067b, PID=0x2303
0x06, // idVendor_H
0x03, // idProduct_L
0x23, // idProduct_H
0x00, // bcdDevice_Ver_L
0x03, // bcdDevice_Ver_H
iMANUFACTURER_DESCR, // iManufacturer
iPRODUCT_DESCR, // iProduct
iSERIAL_DESCR, // iSerialNumber
bNumConfigurations // bNumConfigurations
};
static const uint8_t USB_DeviceQualifierDescriptor[] = {
10, //bLength
0x06, // bDescriptorType - Device qualifier
bcdUSB_L, // bcdUSB_L
bcdUSB_H, // bcdUSB_H
bDeviceClass, // bDeviceClass
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0_BUFSZ, // bMaxPacketSize0
bNumConfigurations, // bNumConfigurations
0x00 // Reserved
};
static const uint8_t USB_ConfigDescriptor[] = {
/*Configuration Descriptor*/
0x09, /* bLength: Configuration Descriptor size */
0x02, /* bDescriptorType: Configuration */
39, /* wTotalLength:no of returned bytes */
0x00,
0x01, /* bNumInterfaces: 1 interface */
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
0xa0, /* bmAttributes - Bus powered, Remote wakeup */
0x32, /* MaxPower 100 mA */
/*---------------------------------------------------------------------------*/
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
0x04, /* bDescriptorType: Interface */
0x00, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x03, /* bNumEndpoints: 3 endpoints used */
0xff, /* bInterfaceClass */
0x00, /* bInterfaceSubClass */
0x00, /* bInterfaceProtocol */
iINTERFACE_DESCR, /* iInterface: */
///////////////////////////////////////////////////
/*Endpoint 1 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
0x05, /* bDescriptorType: Endpoint */
0x81, /* bEndpointAddress IN1 */
0x03, /* bmAttributes: Interrupt */
0x0a, /* wMaxPacketSize LO: */
0x00, /* wMaxPacketSize HI: */
0x01, /* bInterval: */
/*Endpoint OUT2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
0x05, /* bDescriptorType: Endpoint */
0x02, /* bEndpointAddress: OUT2 */
0x02, /* bmAttributes: Bulk */
(USB_RXBUFSZ & 0xff), /* wMaxPacketSize: 64 */
(USB_RXBUFSZ >> 8),
0x00, /* bInterval: ignore for Bulk transfer */
/*Endpoint IN3 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
0x05, /* bDescriptorType: Endpoint */
0x83, /* bEndpointAddress IN3 */
0x02, /* bmAttributes: Bulk */
(USB_TXBUFSZ & 0xff), /* wMaxPacketSize: 64 */
(USB_TXBUFSZ >> 8),
0x00, /* bInterval: ignore for Bulk transfer */
};
_USB_LANG_ID_(LD, LANG_US);
_USB_STRING_(SD, u"0.0.1");
_USB_STRING_(MD, u"Prolific Technology Inc.");
_USB_STRING_(PD, u"USB-Serial Controller");
_USB_STRING_(ID, u"USB-STM32");
static void const *StringDescriptor[iDESCR_AMOUNT] = {
[iLANGUAGE_DESCR] = &LD,
[iMANUFACTURER_DESCR] = &MD,
[iPRODUCT_DESCR] = &PD,
[iSERIAL_DESCR] = &SD,
[iINTERFACE_DESCR] = &ID
};
/*
* default handlers
*/
// SET_LINE_CODING
void WEAK linecoding_handler(usb_LineCoding __attribute__((unused)) *lc){
}
// SET_CONTROL_LINE_STATE
void WEAK clstate_handler(uint16_t __attribute__((unused)) val){
}
// SEND_BREAK
void WEAK break_handler(){
}
// handler of vendor requests
void WEAK vendor_handler(config_pack_t *packet){
uint16_t c;
if(packet->bmRequestType & 0x80){ // read
switch(packet->wValue){
case 0x8484:
c = 2;
break;
case 0x0080:
c = 1;
break;
case 0x8686:
c = 0xaa;
break;
default:
c = 0;
}
EP_WriteIRQ(0, (uint8_t*)&c, 1);
}else{ // write ZLP
c = 0;
EP_WriteIRQ(0, (uint8_t *)&c, 0);
}
}
static void wr0(const uint8_t *buf, uint16_t size){
if(setup_packet->wLength < size) size = setup_packet->wLength; // shortened request
if(size < endpoints[0].txbufsz){
EP_WriteIRQ(0, buf, size);
return;
}
while(size){
uint16_t l = size;
if(l > endpoints[0].txbufsz) l = endpoints[0].txbufsz;
EP_WriteIRQ(0, buf, l);
buf += l;
size -= l;
uint8_t needzlp = (l == endpoints[0].txbufsz) ? 1 : 0;
if(size || needzlp){ // send last data buffer
uint16_t status = KEEP_DTOG(USB->EPnR[0]);
// keep DTOGs, clear CTR_RX,TX, set TX VALID, leave stat_Rx
USB->EPnR[0] = (status & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX|USB_EPnR_STAT_RX))
^ USB_EPnR_STAT_TX;
uint32_t ctr = 1000000;
while(--ctr && (USB->ISTR & USB_ISTR_CTR) == 0){IWDG->KR = IWDG_REFRESH;};
if((USB->ISTR & USB_ISTR_CTR) == 0){
return;
}
if(needzlp) EP_WriteIRQ(0, (uint8_t*)0, 0);
}
}
}
static inline void get_descriptor(){
uint8_t descrtype = setup_packet->wValue >> 8,
descridx = setup_packet->wValue & 0xff;
switch(descrtype){
case DEVICE_DESCRIPTOR:
wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor));
break;
case CONFIGURATION_DESCRIPTOR:
wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor));
break;
case STRING_DESCRIPTOR:
if(descridx < iDESCR_AMOUNT) wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx]));
else EP_WriteIRQ(0, (uint8_t*)0, 0);
break;
case DEVICE_QUALIFIER_DESCRIPTOR:
wr0(USB_DeviceQualifierDescriptor, USB_DeviceQualifierDescriptor[0]);
break;
default:
break;
}
}
static uint16_t configuration = 0; // reply for GET_CONFIGURATION (==1 if configured) static uint16_t configuration = 0; // reply for GET_CONFIGURATION (==1 if configured)
static inline void std_d2h_req(){ static inline void std_d2h_req(){
uint16_t status = 0; // bus powered uint16_t st = 0;
switch(setup_packet->bRequest){ switch(setup_packet->bRequest){
case GET_DESCRIPTOR: case GET_DESCRIPTOR:
get_descriptor(); get_descriptor(setup_packet);
break; break;
case GET_STATUS: case GET_STATUS:
EP_WriteIRQ(0, (uint8_t *)&status, 2); // send status: Bus Powered EP_WriteIRQ(0, (uint8_t *)&st, 2); // send status: Bus Powered
break; break;
case GET_CONFIGURATION: case GET_CONFIGURATION:
EP_WriteIRQ(0, (uint8_t*)&configuration, 1); EP_WriteIRQ(0, (uint8_t*)&configuration, 1);
break; break;
default: default:
EP_WriteIRQ(0, NULL, 0);
break; break;
} }
} }
// interrupt IN handler (never used?)
static void EP1_Handler(){
uint16_t epstatus = KEEP_DTOG(USB->EPnR[1]);
if(RX_FLAG(epstatus)) epstatus = (epstatus & ~USB_EPnR_STAT_TX) ^ USB_EPnR_STAT_RX; // set valid RX
else epstatus = epstatus & ~(USB_EPnR_STAT_TX|USB_EPnR_STAT_RX);
// clear CTR
epstatus = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX));
USB->EPnR[1] = epstatus;
}
// data IN/OUT handlers
static void transmit_Handler(){ // EP3IN
uint16_t epstatus = KEEP_DTOG_STAT(USB->EPnR[3]);
// clear CTR keep DTOGs & STATs
USB->EPnR[3] = (epstatus & ~(USB_EPnR_CTR_TX)); // clear TX ctr
send_next();
}
static uint8_t volatile rcvbuf[USB_RXBUFSZ];
static uint8_t volatile rcvbuflen = 0;
void chkin(){
if(bufovrfl) return;
if(!rcvbuflen) return;
int w = RB_write((ringbuffer*)&rbin, (uint8_t*)rcvbuf, rcvbuflen);
if(w < 0) return;
if(w != rcvbuflen) bufovrfl = 1;
rcvbuflen = 0;
uint16_t status = KEEP_DTOG(USB->EPnR[2]); // don't change DTOG
USB->EPnR[2] = status ^ USB_EPnR_STAT_RX;
}
// receiver reads data from local buffer and only then ACK'ed
static void receive_Handler(){ // EP2OUT
uint16_t status = KEEP_DTOG_STAT(USB->EPnR[2]); // don't change DTOG and NACK
if(rcvbuflen){
bufovrfl = 1; // lost last data
rcvbuflen = 0;
}
rcvbuflen = EP_Read(2, (uint8_t*)rcvbuf);
USB->EPnR[2] = status & ~USB_EPnR_CTR_RX;
}
static inline void std_h2d_req(){ static inline void std_h2d_req(){
switch(setup_packet->bRequest){ switch(setup_packet->bRequest){
case SET_ADDRESS: case SET_ADDRESS:
@@ -298,15 +55,58 @@ static inline void std_h2d_req(){
case SET_CONFIGURATION: case SET_CONFIGURATION:
// Now device configured // Now device configured
configuration = setup_packet->wValue; configuration = setup_packet->wValue;
EP_Init(1, EP_TYPE_INTERRUPT, USB_EP1BUFSZ, 0, EP1_Handler); // IN1 - transmit set_configuration();
EP_Init(2, EP_TYPE_BULK, 0, USB_RXBUFSZ, receive_Handler); // OUT2 - receive data usbON = 1;
EP_Init(3, EP_TYPE_BULK, USB_TXBUFSZ, 0, transmit_Handler); // IN3 - transmit data
break; break;
default: default:
break; break;
} }
} }
void WEAK usb_standard_request(){
uint8_t recipient = REQUEST_RECIPIENT(setup_packet->bmRequestType);
uint8_t dev2host = (setup_packet->bmRequestType & 0x80) ? 1 : 0;
switch(recipient){
case REQ_RECIPIENT_DEVICE:
if(dev2host){
std_d2h_req();
}else{
std_h2d_req();
}
break;
case REQ_RECIPIENT_INTERFACE:
if(dev2host && setup_packet->bRequest == GET_DESCRIPTOR){
get_descriptor(setup_packet);
}
break;
case REQ_RECIPIENT_ENDPOINT:
if(setup_packet->bRequest == CLEAR_FEATURE){
}else{ /* wrong */ }
break;
default:
break;
}
if(!dev2host) EP_WriteIRQ(0, NULL, 0);
}
void WEAK usb_class_request(config_pack_t *req, uint8_t _U_ *data, uint16_t _U_ datalen){
switch(req->bRequest){
case GET_INTERFACE:
break;
case SET_CONFIGURATION: // set featuring by req->wValue
break;
default:
break;
}
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
}
void WEAK usb_vendor_request(config_pack_t _U_ *packet, uint8_t _U_ *data, uint16_t _U_ datalen){
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
}
/* /*
bmRequestType: 76543210 bmRequestType: 76543210
7 direction: 0 - host->device, 1 - device->host 7 direction: 0 - host->device, 1 - device->host
@@ -316,67 +116,49 @@ bmRequestType: 76543210
/** /**
* Endpoint0 (control) handler * Endpoint0 (control) handler
*/ */
void EP0_Handler(){ static void EP0_Handler(){
uint16_t epstatus = USB->EPnR[0]; // EP0R on input -> return this value after modifications uint8_t ep0dbuflen = 0;
uint8_t reqtype = setup_packet->bmRequestType & 0x7f; uint8_t ep0databuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
uint8_t dev2host = (setup_packet->bmRequestType & 0x80) ? 1 : 0; uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]); // EP0R on input -> return this value after modifications
int rxflag = RX_FLAG(epstatus); int rxflag = RX_FLAG(epstatus);
if(rxflag && SETUP_FLAG(epstatus)){ // check direction
switch(reqtype){ if(USB->ISTR & USB_ISTR_DIR){ // OUT interrupt - receive data, CTR_RX==1 (if CTR_TX == 1 - two pending transactions: receive following by transmit)
case STANDARD_DEVICE_REQUEST_TYPE: // standard device request if(epstatus & USB_EPnR_SETUP){ // setup packet -> copy data to conf_pack
if(dev2host){ EP_Read(0, setupdatabuf);
std_d2h_req(); // interrupt handler will be called later
}else{ }else if(epstatus & USB_EPnR_CTR_RX){ // data packet -> push received data to ep0databuf
std_h2d_req(); //if(endpoints[0].rx_cnt){ }
EP_WriteIRQ(0, (uint8_t *)0, 0); ep0dbuflen = EP_Read(0, ep0databuf);
}
break;
case STANDARD_ENDPOINT_REQUEST_TYPE: // standard endpoint request
if(setup_packet->bRequest == CLEAR_FEATURE){
EP_WriteIRQ(0, (uint8_t *)0, 0);
}
break;
case VENDOR_REQUEST_TYPE:
vendor_handler(setup_packet);
break;
case CONTROL_REQUEST_TYPE:
switch(setup_packet->bRequest){
case GET_LINE_CODING:
EP_WriteIRQ(0, (uint8_t*)&lineCoding, sizeof(lineCoding));
break;
case SET_LINE_CODING: // omit this for next stage, when data will come
break;
case SET_CONTROL_LINE_STATE:
usbON = 1;
clstate_handler(setup_packet->wValue);
break;
case SEND_BREAK:
usbON = 0;
break_handler();
break;
default:
break;
}
if(setup_packet->bRequest != GET_LINE_CODING) EP_WriteIRQ(0, (uint8_t *)0, 0); // write acknowledgement
break;
default:
EP_WriteIRQ(0, (uint8_t *)0, 0);
}
}else if(rxflag){ // got data over EP0 or host acknowlegement
if(endpoints[0].rx_cnt){
if(setup_packet->bRequest == SET_LINE_CODING){
linecoding_handler((usb_LineCoding*)ep0databuf);
} }
}
if(rxflag){
uint8_t reqtype = REQUEST_TYPE(setup_packet->bmRequestType);
switch(reqtype){
case REQ_TYPE_STANDARD:
if(SETUP_FLAG(epstatus)){
usb_standard_request();
}else{ }
break;
case REQ_TYPE_CLASS:
usb_class_request(setup_packet, ep0databuf, ep0dbuflen);
break;
case REQ_TYPE_VENDOR:
usb_vendor_request(setup_packet, ep0databuf, ep0dbuflen);
break;
default:
EP_WriteIRQ(0, NULL, 0);
break;
} }
} else if(TX_FLAG(epstatus)){ // package transmitted }
if(TX_FLAG(epstatus)){
// now we can change address after enumeration // now we can change address after enumeration
if ((USB->DADDR & USB_DADDR_ADD) != USB_Addr){ if ((USB->DADDR & USB_DADDR_ADD) != USB_Addr){
USB->DADDR = USB_DADDR_EF | USB_Addr; USB->DADDR = USB_DADDR_EF | USB_Addr;
usbON = 0; usbON = 0;
} }
} }
epstatus = KEEP_DTOG(USB->EPnR[0]); //epstatus = KEEP_DTOG(USB->EPnR[0]);
if(rxflag) epstatus ^= USB_EPnR_STAT_TX; // start ZLP/data transmission if(rxflag) epstatus ^= USB_EPnR_STAT_TX; // start ZLP or data transmission
else epstatus &= ~USB_EPnR_STAT_TX; // or leave unchanged else epstatus &= ~USB_EPnR_STAT_TX; // or leave unchanged
// keep DTOGs, clear CTR_RX,TX, set RX VALID // keep DTOGs, clear CTR_RX,TX, set RX VALID
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX)) ^ USB_EPnR_STAT_RX; USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX)) ^ USB_EPnR_STAT_RX;
@@ -390,9 +172,14 @@ void EP0_Handler(){
*/ */
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){ void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
if(size > endpoints[number].txbufsz) size = endpoints[number].txbufsz; if(size > endpoints[number].txbufsz) size = endpoints[number].txbufsz;
#ifndef USB32
uint16_t N2 = (size + 1) >> 1; uint16_t N2 = (size + 1) >> 1;
// the buffer is 16-bit, so we should copy data as it would be uint16_t // the buffer is 16-bit, so we should copy data as it would be uint16_t
uint16_t *buf16 = (uint16_t *)buf; uint16_t *buf16 = (uint16_t *)buf;
#else
int N4 = (size + 3) >> 2;
uint32_t *buf32 = (uint32_t *)buf;
#endif
#if defined USB1_16 #if defined USB1_16
// very bad: what if `size` is odd? // very bad: what if `size` is odd?
uint32_t *out = (uint32_t *)endpoints[number].tx_buf; uint32_t *out = (uint32_t *)endpoints[number].tx_buf;
@@ -401,13 +188,19 @@ void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
} }
#elif defined USB2_16 #elif defined USB2_16
// use memcpy instead? // use memcpy instead?
for(int i = 0; i < N2; i++){ for(int i = 0; i < N2; ++i){
endpoints[number].tx_buf[i] = buf16[i]; endpoints[number].tx_buf[i] = buf16[i];
} }
#elif defined USB32
for(int i = 0; i < N4; ++i) endpoints[number].tx_buf[i] = buf32[i];
#else #else
#error "Define USB1_16 or USB2_16" #error "Define USB1_16 / USB2_16 / USB32"
#endif #endif
#ifndef USB32
USB_BTABLE->EP[number].USB_COUNT_TX = size; USB_BTABLE->EP[number].USB_COUNT_TX = size;
#else
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (USB_BTABLE->EP[number].USB_ADDR_COUNT_TX & 0xffff) | (size << 16);
#endif
} }
/** /**
@@ -418,9 +211,9 @@ void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
*/ */
void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size){ void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size){
EP_WriteIRQ(number, buf, size); EP_WriteIRQ(number, buf, size);
uint16_t status = KEEP_DTOG(USB->EPnR[number]); uint16_t epstatus = KEEP_DTOG(USB->EPnR[number]);
// keep DTOGs, clear CTR_TX & set TX VALID to start transmission // keep DTOGs and RX stat, clear CTR_TX & set TX VALID to start transmission
USB->EPnR[number] = (status & ~(USB_EPnR_CTR_TX)) ^ USB_EPnR_STAT_TX; USB->EPnR[number] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_RX)) ^ USB_EPnR_STAT_TX;
} }
/* /*
@@ -442,8 +235,12 @@ int EP_Read(uint8_t number, uint8_t *buf){
// use memcpy instead? // use memcpy instead?
for(int i = 0; i < sz; ++i) for(int i = 0; i < sz; ++i)
buf[i] = endpoints[number].rx_buf[i]; buf[i] = endpoints[number].rx_buf[i];
#elif defined USB32
uint32_t *u32buf = (uint32_t*) buf;
int N4 = (sz + 3) >> 2;
for(int i = 0; i < N4; ++i) u32buf[i] = endpoints[number].rx_buf[i];
#else #else
#error "Define USB1_16 or USB2_16" #error "Define USB1_16 / USB2_16 / USB32"
#endif #endif
return sz; return sz;
} }
@@ -460,91 +257,182 @@ static uint16_t lastaddr = LASTADDR_DEFAULT;
* @return 0 if all OK * @return 0 if all OK
*/ */
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)(ep_t ep)){ int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)(ep_t ep)){
#ifdef STM32G0
// in STM32G0 all buffers should be aligned by 32 bits
if(txsz & 3) txsz = ((txsz >> 2)+1) << 2;
if(rxsz & 3) rxsz = ((rxsz >> 2)+1) << 2;
#endif
if(number >= STM32ENDPOINTS) return 4; // out of configured amount if(number >= STM32ENDPOINTS) return 4; // out of configured amount
if(txsz > USB_BTABLE_SIZE || rxsz > USB_BTABLE_SIZE) return 1; // buffer too large if(txsz > USB_BTABLE_SIZE/ACCESSZ || rxsz > USB_BTABLE_SIZE/ACCESSZ) return 1; // buffer too large
if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE/ACCESSZ) return 2; // out of btable if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE/ACCESSZ) return 2; // out of btable
USB->EPnR[number] = (type << 9) | (number & USB_EPnR_EA); USB->EPnR[number] = (type << 9) | (number & USB_EPnR_EA);
USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX_1; USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX;
if(rxsz & 1 || rxsz > USB_BTABLE_SIZE) return 3; // wrong rx buffer size if(rxsz & 1) return 3; // wrong rx buffer size
uint16_t countrx = 0; uint16_t countrx = 0;
if(rxsz < 64) countrx = rxsz / 2; if(rxsz < 64) countrx = rxsz / 2;
else{ else{
if(rxsz & 0x1f) return 3; // should be multiple of 32 if(rxsz & 0x1f) return 3; // should be multiple of 32
countrx = 31 + rxsz / 32; countrx = 31 + rxsz / 32;
} }
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr; #ifdef USB32
endpoints[number].tx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#else
endpoints[number].tx_buf = (uint16_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ); endpoints[number].tx_buf = (uint16_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#endif
endpoints[number].txbufsz = txsz; endpoints[number].txbufsz = txsz;
lastaddr += txsz; #ifdef USB32
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (uint32_t) lastaddr;
#else
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_TX = 0; USB_BTABLE->EP[number].USB_COUNT_TX = 0;
USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr; #endif
lastaddr += txsz;
#ifdef USB32
endpoints[number].rx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
USB_BTABLE->EP[number].USB_ADDR_COUNT_RX = (uint32_t) lastaddr | countrx << 26;
#else
endpoints[number].rx_buf = (uint8_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ); endpoints[number].rx_buf = (uint8_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
lastaddr += rxsz; USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_RX = countrx << 10; USB_BTABLE->EP[number].USB_COUNT_RX = countrx << 10;
#endif
lastaddr += rxsz;
endpoints[number].func = func; endpoints[number].func = func;
return 0; return 0;
} }
// standard IRQ handler // standard IRQ handler
void USB_IRQ(){ void USB_IRQ(){
if(USB->ISTR & USB_ISTR_RESET){ uint32_t CNTR = USB->CNTR;
USB->CNTR = 0;
uint32_t istr = USB->ISTR;
if(istr & USB_ISTR_RESET){
usbON = 0; usbON = 0;
// Reinit registers // Reinit registers
USB->CNTR = USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM | USB_CNTR_WKUPM; CNTR = USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM;
// Endpoint 0 - CONTROL // Endpoint 0 - CONTROL
// ON USB LS size of EP0 may be 8 bytes, but on FS it should be 64 bytes! // ON USB LS size of EP0 may be 8 bytes, but on FS it should be 64 bytes!
lastaddr = LASTADDR_DEFAULT; lastaddr = LASTADDR_DEFAULT;
// clear address, leave only enable bit // clear address, leave only enable bit
USB->DADDR = USB_DADDR_EF; USB->DADDR = USB_DADDR_EF;
if(EP_Init(0, EP_TYPE_CONTROL, USB_EP0_BUFSZ, USB_EP0_BUFSZ, EP0_Handler)){ //USB->ISTR = ~(USB_ISTR_RESET); // clear all flags
if(EP_Init(0, EP_TYPE_CONTROL, USB_EP0BUFSZ, USB_EP0BUFSZ, EP0_Handler)){
return; return;
} };
USB->ISTR = ~USB_ISTR_RESET;
} }
if(USB->ISTR & USB_ISTR_CTR){ if(istr & USB_ISTR_CTR){
// EP number // EP number
uint8_t n = USB->ISTR & USB_ISTR_EPID; uint8_t n = istr & USB_ISTR_EPID;
// copy status register if (istr & USB_ISTR_DIR){ // OUT
uint16_t epstatus = USB->EPnR[n]; }else{ // IN
// copy received bytes amount
endpoints[n].rx_cnt = USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
// check direction
if(USB->ISTR & USB_ISTR_DIR){ // OUT interrupt - receive data, CTR_RX==1 (if CTR_TX == 1 - two pending transactions: receive following by transmit)
if(n == 0){ // control endpoint
if(epstatus & USB_EPnR_SETUP){ // setup packet -> copy data to conf_pack
EP_Read(0, setupdatabuf);
// interrupt handler will be called later
}else if(epstatus & USB_EPnR_CTR_RX){ // data packet -> push received data to ep0databuf
EP_Read(0, ep0databuf);
}
}
} }
// copy received bytes amount
endpoints[n].rx_cnt =
#ifdef USB32
(USB_BTABLE->EP[n].USB_ADDR_COUNT_RX >> 16) & 0x3FF;
#else
USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
#endif
// call EP handler // call EP handler
if(endpoints[n].func) endpoints[n].func(endpoints[n]); if(endpoints[n].func) endpoints[n].func();
} }
if(USB->ISTR & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep if(istr & USB_ISTR_WKUP){ // wakeup
#if defined STM32F0
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM);
#elif defined STM32G0
CNTR &= ~(USB_CNTR_SUSPEN | USB_CNTR_PDWN | USB_CNTR_WKUPM);
#else
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM); // clear suspend flags
#endif
//USB->ISTR = ~USB_ISTR_WKUP;
}
if(istr & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep
usbON = 0; usbON = 0;
#ifndef STM32F0 #if defined STM32F0
USB->CNTR |= USB_CNTR_FSUSP | USB_CNTR_LP_MODE; CNTR |= USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM;
#elif defined STM32G0
CNTR |= USB_CNTR_SUSPEN | USB_CNTR_WKUPM;
#else #else
USB->CNTR |= USB_CNTR_FSUSP | USB_CNTR_LPMODE; CNTR |= USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM;
#endif #endif
USB->ISTR = ~USB_ISTR_SUSP; CNTR &= ~(USB_CNTR_SUSPM);
} //USB->ISTR = ~USB_ISTR_SUSP;
if(USB->ISTR & USB_ISTR_WKUP){ // wakeup
#ifndef STM32F0
USB->CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LP_MODE); // clear suspend flags
#else
USB->CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LPMODE);
#endif
USB->ISTR = ~USB_ISTR_WKUP;
} }
USB->ISTR = 0; // clear all flags
USB->CNTR = CNTR; // rewoke interrupts
} }
// here we suppose that all PIN settings done in hw_setup earlier
void USB_setup(){
lastaddr = LASTADDR_DEFAULT; // clear last address settings
#if defined STM32F3
NVIC_DisableIRQ(USB_LP_IRQn);
// remap USB LP & Wakeup interrupts to 75 and 76 - works only on pure F303
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // enable tacting of SYSCFG
SYSCFG->CFGR1 |= SYSCFG_CFGR1_USB_IT_RMP;
#elif defined STM32F1
NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn);
NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn);
#elif defined STM32F0
// All is clocking from HSI48
NVIC_DisableIRQ(USB_IRQn);
RCC->APB1ENR |= RCC_APB1ENR_CRSEN;
RCC->CFGR3 &= ~RCC_CFGR3_USBSW; // reset USB
RCC->CR2 |= RCC_CR2_HSI48ON; // turn ON HSI48
uint32_t tmout = 16000000;
while(!(RCC->CR2 & RCC_CR2_HSI48RDY)){if(--tmout == 0) break;}
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
CRS->CFGR &= ~CRS_CFGR_SYNCSRC;
CRS->CFGR |= CRS_CFGR_SYNCSRC_1; // USB SOF selected as sync source
CRS->CR |= CRS_CR_AUTOTRIMEN; // enable auto trim
CRS->CR |= CRS_CR_CEN; // enable freq counter & block CRS->CFGR as read-only
RCC->CFGR |= RCC_CFGR_SW;
#elif defined STM32G0
NVIC_DisableIRQ(USB_UCPD1_2_IRQn);
PWR->CR2 |= PWR_CR2_USV; // enable USB powering
//RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN; // enable tacting of SYSCFG
// independent clocking of USB from HSI48
RCC->CR |= RCC_CR_HSI48ON;
uint32_t tmout = 16000000;
while(!(RCC->CR & RCC_CR_HSI48RDY)) if(--tmout == 0) break;
RCC->CCIPR2 &= ~RCC_CCIPR2_USBSEL; // select HSI48 for USB
RCC->APBENR1 |= RCC_APBENR1_CRSEN; // CRS clocking
CRS->CFGR = (31LL << CRS_CFGR_FELIM_Pos) | // tolerance (usually 31)
(48000LL / 1LL - 1LL) << CRS_CFGR_RELOAD_Pos | // 48MHz / 1kHZ (SOF)
CRS_CFGR_SYNCSRC_1; // USB SOF as sync source (0x2)
CRS->CR |= CRS_CR_AUTOTRIMEN | CRS_CR_CEN; // Enable autotrim and turn on Clock Recovery System
RCC->APBENR1 |= RCC_APBENR1_USBEN;
#endif
#ifndef STM32G0
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
USB->CNTR = USB_CNTR_FRES; // Force USB Reset
USB->BTABLE = 0;
#else
USB->CNTR = USB_CNTR_USBRST;
#endif
for(uint32_t ctr = 0; ctr < 72000; ++ctr) nop(); // wait >1ms
USB->CNTR = USB_CNTR_RESETM; // allow only reset interrupts
USB->DADDR = 0;
USB->ISTR = 0;
#if defined STM32F3
NVIC_EnableIRQ(USB_LP_IRQn);
#elif defined STM32F1
NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
#elif defined STM32F0
USB->BCDR |= USB_BCDR_DPPU;
NVIC_EnableIRQ(USB_IRQn);
#elif defined STM32G0
USB->BCDR |= USB_BCDR_DPPU; // turn ON DP pullup
NVIC_EnableIRQ(USB_UCPD1_2_IRQn);
#endif
}
#if defined STM32F3 #if defined STM32F3
void usb_lp_isr() __attribute__ ((alias ("USB_IRQ"))); void usb_lp_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32F1 #elif defined STM32F1
void usb_lp_can_rx0_isr() __attribute__ ((alias ("USB_IRQ"))); void usb_lp_can_rx0_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32F0 #elif defined STM32F0
void usb_isr() __attribute__ ((alias ("USB_IRQ"))); void usb_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32G0
void usb_ucpd1_2_isr() __attribute__ ((alias ("USB_IRQ")));
#endif #endif

View File

@@ -16,63 +16,271 @@
*/ */
#pragma once #pragma once
#include <stdint.h>
#include <wchar.h> #include <wchar.h>
#include "usbhw.h"
#ifndef _U_
#define _U_ __attribute__((unused))
#endif
/******************************************************************
* Hardware registers etc *
*****************************************************************/
#if defined STM32F0
#include <stm32f0.h>
#elif defined STM32F1
#include <stm32f1.h>
// there's no this define in standard header
#define USB_BASE ((uint32_t)0x40005C00)
#elif defined STM32F3
#include <stm32f3.h>
#elif defined STM32G0
#include <stm32g0.h>
#endif
// max endpoints number
#define STM32ENDPOINTS 8
/**
* Buffers size definition
**/
// F0 - USB2_16; F1 - USB1_16; F3 - 1/2 depending on series; G0 - USB32
#if !defined USB1_16 && !defined USB2_16 && !defined USB32
#if defined STM32F0
#define USB2_16
#elif defined STM32F1
#define USB1_16
#elif defined STM32G0
#define USB32
#else
#error "Can't determine USB1_16/USB2_16/USB32, define by hands"
#endif
#endif
// BTABLE_SIZE FOR STM32F3:
// In STM32F303/302xB/C, 512 bytes SRAM is not shared with CAN.
// In STM32F302x6/x8 and STM32F30xxD/E, 726 bytes dedicated SRAM and 256 bytes shared SRAM with CAN i.e.
// 1Kbytes dedicated SRAM in case CAN is disabled.
// remember, that USB_BTABLE_SIZE will be divided by ACCESSZ, so don't divide it twice for 32-bit addressing
#ifdef NOCAN
#if defined STM32F0
#define USB_BTABLE_SIZE 1024
#elif defined STM32F3
#define USB_BTABLE_SIZE 1024
//#warning "Please, check real buffer size due to docs"
#else
#error "define STM32F0 or STM32F3"
#endif
#else // !NOCAN: F0/F3 with CAN or F1 (can't simultaneously run CAN and USB)
#if defined STM32F0
#define USB_BTABLE_SIZE 768
#elif defined STM32F3
#define USB_BTABLE_SIZE 768
#elif defined STM32G0
#define USB_BTABLE_SIZE 2048
//#warning "Please, check real buffer size due to docs"
#else // STM32F103: 1024 bytes but with 32-bit addressing
#define USB_BTABLE_SIZE 1024
#endif
#endif // NOCAN
// first 64 bytes of USB_BTABLE are registers!
#ifndef STM32G0
#define USB_BTABLE_BASE 0x40006000
#else
#define USB_BTABLE_BASE 0x40009800
#endif
#define USB ((USB_TypeDef *) USB_BASE)
#ifdef USB_BTABLE
#undef USB_BTABLE
#endif
#define USB_BTABLE ((USB_BtableDef *)(USB_BTABLE_BASE))
#define USB_ISTR_EPID 0x0000000F
#define USB_FNR_LSOF_0 0x00000800
#define USB_FNR_lSOF_1 0x00001000
#define USB_LPMCSR_BESL_0 0x00000010
#define USB_LPMCSR_BESL_1 0x00000020
#define USB_LPMCSR_BESL_2 0x00000040
#define USB_LPMCSR_BESL_3 0x00000080
#define USB_EPnR_CTR_RX 0x00008000
#define USB_EPnR_DTOG_RX 0x00004000
#define USB_EPnR_STAT_RX 0x00003000
#define USB_EPnR_STAT_RX_0 0x00001000
#define USB_EPnR_STAT_RX_1 0x00002000
#define USB_EPnR_SETUP 0x00000800
#define USB_EPnR_EP_TYPE 0x00000600
#define USB_EPnR_EP_TYPE_0 0x00000200
#define USB_EPnR_EP_TYPE_1 0x00000400
#define USB_EPnR_EP_KIND 0x00000100
#define USB_EPnR_CTR_TX 0x00000080
#define USB_EPnR_DTOG_TX 0x00000040
#define USB_EPnR_STAT_TX 0x00000030
#define USB_EPnR_STAT_TX_0 0x00000010
#define USB_EPnR_STAT_TX_1 0x00000020
#define USB_EPnR_EA 0x0000000F
#define USB_COUNTn_RX_BLSIZE 0x00008000
#define USB_COUNTn_NUM_BLOCK 0x00007C00
#define USB_COUNTn_RX 0x0000003F
#define USB_TypeDef USB_TypeDef_custom
typedef struct {
__IO uint32_t EPnR[STM32ENDPOINTS];
__IO uint32_t RESERVED[STM32ENDPOINTS];
__IO uint32_t CNTR;
__IO uint32_t ISTR;
__IO uint32_t FNR;
__IO uint32_t DADDR;
#ifndef USB32
__IO uint32_t BTABLE;
#else
__IO uint32_t RESERVED1; // there's no BTABLE register in STM32G0
#endif
#if defined STM32F0 || defined USB32
__IO uint32_t LPMCSR;
__IO uint32_t BCDR;
#endif
} USB_TypeDef;
// F303 D/E have 2x16 access scheme
typedef struct{
#if defined USB2_16
__IO uint16_t USB_ADDR_TX;
__IO uint16_t USB_COUNT_TX;
__IO uint16_t USB_ADDR_RX;
__IO uint16_t USB_COUNT_RX;
#define ACCESSZ (1)
#elif defined USB1_16
__IO uint32_t USB_ADDR_TX;
__IO uint32_t USB_COUNT_TX;
__IO uint32_t USB_ADDR_RX;
__IO uint32_t USB_COUNT_RX;
#define ACCESSZ (2)
#elif defined USB32
// 32-bit registers: addr & count in one!
__IO uint32_t USB_ADDR_COUNT_TX;
__IO uint32_t USB_ADDR_COUNT_RX;
#define ACCESSZ (1)
#else
#error "Define USB1_16 (16 bits over 32bit register), USB2_16 (16 bits over 16 bit register) or USB32 (32 bist over 32 bit register)"
#endif
} USB_EPDATA_TypeDef;
typedef struct{
__IO USB_EPDATA_TypeDef EP[STM32ENDPOINTS];
} USB_BtableDef;
#define EP0DATABUF_SIZE (64) #define EP0DATABUF_SIZE (64)
#define LASTADDR_DEFAULT (STM32ENDPOINTS * 8) #define LASTADDR_DEFAULT (STM32ENDPOINTS * 8)
// bmRequestType & 0x7f /******************************************************************
#define STANDARD_DEVICE_REQUEST_TYPE 0 * Defines from usb.h *
#define STANDARD_ENDPOINT_REQUEST_TYPE 2 *****************************************************************/
#define VENDOR_REQUEST_TYPE 0x40
#define CONTROL_REQUEST_TYPE 0x21 /*
// bRequest, standard; for bmRequestType == 0x80 * Device and/or Interface Class codes
*/
#define USB_CLASS_PER_INTERFACE 0
#define USB_CLASS_AUDIO 1
#define USB_CLASS_COMM 2
#define USB_CLASS_HID 3
#define USB_CLASS_PRINTER 7
#define USB_CLASS_PTP 6
#define USB_CLASS_MASS_STORAGE 8
#define USB_CLASS_HUB 9
#define USB_CLASS_DATA 10
#define USB_CLASS_MISC 0xef
#define USB_CLASS_VENDOR_SPEC 0xff
/*
* Descriptor types
*/
#define USB_DT_DEVICE 0x01
#define USB_DT_CONFIG 0x02
#define USB_DT_STRING 0x03
#define USB_DT_INTERFACE 0x04
#define USB_DT_ENDPOINT 0x05
#define USB_DT_QUALIFIER 0x06
#define USB_DT_IAD 0x0B
#define USB_DT_HID 0x21
#define USB_DT_REPORT 0x22
#define USB_DT_PHYSICAL 0x23
#define USB_DT_CS_INTERFACE 0x24
#define USB_DT_HUB 0x29
/*
* Descriptor sizes per descriptor type
*/
#define USB_DT_DEVICE_SIZE 18
#define USB_DT_CONFIG_SIZE 9
#define USB_DT_INTERFACE_SIZE 9
#define USB_DT_HID_SIZE 9
#define USB_DT_ENDPOINT_SIZE 7
#define USB_DT_QUALIFIER_SIZE 10
#define USB_DT_CS_INTERFACE_SIZE 5
#define USB_DT_IAD_SIZE 8
// bmRequestType & 0x80 == dev2host (1) or host2dev (0)
// recipient: bmRequestType & 0x1f
#define REQUEST_RECIPIENT(b) (b & 0x1f)
#define REQ_RECIPIENT_DEVICE 0
#define REQ_RECIPIENT_INTERFACE 1
#define REQ_RECIPIENT_ENDPOINT 2
#define REQ_RECIPIENT_OTHER 3
// type: [bmRequestType & 0x60 >> 5]
#define REQUEST_TYPE(b) ((b&0x60)>>5)
#define REQ_TYPE_STANDARD 0
#define REQ_TYPE_CLASS 1
#define REQ_TYPE_VENDOR 2
#define REQ_TYPE_RESERVED 3
//#define VENDOR_REQUEST 0x01
// standard device requests
#define GET_STATUS 0x00 #define GET_STATUS 0x00
#define GET_DESCRIPTOR 0x06
#define GET_CONFIGURATION 0x08
// for bmRequestType == 0
#define CLEAR_FEATURE 0x01 #define CLEAR_FEATURE 0x01
#define SET_FEATURE 0x03 // unused #define SET_FEATURE 0x03
#define SET_ADDRESS 0x05 #define SET_ADDRESS 0x05
#define SET_DESCRIPTOR 0x07 // unused #define GET_DESCRIPTOR 0x06
#define SET_DESCRIPTOR 0x07
#define GET_CONFIGURATION 0x08
#define SET_CONFIGURATION 0x09 #define SET_CONFIGURATION 0x09
// for bmRequestType == 0x81, 1 or 0xB2 // and some standard interface requests
#define GET_INTERFACE 0x0A // unused #define GET_INTERFACE 0x0A
#define SET_INTERFACE 0x0B // unused #define SET_INTERFACE 0x0B
#define SYNC_FRAME 0x0C // unused // and some standard endpoint requests
#define VENDOR_REQUEST 0x01 // unused #define SYNC_FRAME 0x0C
// Class-Specific Control Requests
#define SEND_ENCAPSULATED_COMMAND 0x00 // unused
#define GET_ENCAPSULATED_RESPONSE 0x01 // unused
#define SET_COMM_FEATURE 0x02 // unused
#define GET_COMM_FEATURE 0x03 // unused
#define CLEAR_COMM_FEATURE 0x04 // unused
#define SET_LINE_CODING 0x20
#define GET_LINE_CODING 0x21
#define SET_CONTROL_LINE_STATE 0x22
#define SEND_BREAK 0x23
// control line states
#define CONTROL_DTR 0x01
#define CONTROL_RTS 0x02
// string descriptors
enum{
iLANGUAGE_DESCR,
iMANUFACTURER_DESCR,
iPRODUCT_DESCR,
iSERIAL_DESCR,
iINTERFACE_DESCR,
iDESCR_AMOUNT
};
// Types of descriptors // Types of descriptors
#define DEVICE_DESCRIPTOR 0x01 #define DEVICE_DESCRIPTOR 0x01
#define CONFIGURATION_DESCRIPTOR 0x02 #define CONFIGURATION_DESCRIPTOR 0x02
#define STRING_DESCRIPTOR 0x03 #define STRING_DESCRIPTOR 0x03
#define DEVICE_QUALIFIER_DESCRIPTOR 0x06 #define DEVICE_QUALIFIER_DESCRIPTOR 0x06
#define DEBUG_DESCRIPTOR 0x0a
#define HID_REPORT_DESCRIPTOR 0x22
// EP types for EP_init
#define EP_TYPE_BULK 0x00
#define EP_TYPE_CONTROL 0x01
#define EP_TYPE_ISO 0x02
#define EP_TYPE_INTERRUPT 0x03
// EP types for descriptors
#define USB_BM_ATTR_CONTROL 0x00
#define USB_BM_ATTR_ISO 0x01
#define USB_BM_ATTR_BULK 0x02
#define USB_BM_ATTR_INTERRUPT 0x03
/******************************************************************
* Other stuff *
*****************************************************************/
#define RX_FLAG(epstat) (epstat & USB_EPnR_CTR_RX) #define RX_FLAG(epstat) (epstat & USB_EPnR_CTR_RX)
#define TX_FLAG(epstat) (epstat & USB_EPnR_CTR_TX) #define TX_FLAG(epstat) (epstat & USB_EPnR_CTR_TX)
@@ -82,12 +290,6 @@ enum{
#define KEEP_DTOG_STAT(EPnR) (EPnR & ~(USB_EPnR_STAT_RX|USB_EPnR_STAT_TX|USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX)) #define KEEP_DTOG_STAT(EPnR) (EPnR & ~(USB_EPnR_STAT_RX|USB_EPnR_STAT_TX|USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX))
#define KEEP_DTOG(EPnR) (EPnR & ~(USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX)) #define KEEP_DTOG(EPnR) (EPnR & ~(USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX))
// EP types
#define EP_TYPE_BULK 0x00
#define EP_TYPE_CONTROL 0x01
#define EP_TYPE_ISO 0x02
#define EP_TYPE_INTERRUPT 0x03
#define LANG_US (uint16_t)0x0409 #define LANG_US (uint16_t)0x0409
#define _USB_STRING_(name, str) \ #define _USB_STRING_(name, str) \
@@ -101,7 +303,6 @@ static const struct name \
name = {sizeof(name), 0x03, str} name = {sizeof(name), 0x03, str}
#define _USB_LANG_ID_(name, lng_id) \ #define _USB_LANG_ID_(name, lng_id) \
\
static const struct name \ static const struct name \
{ \ { \
uint8_t bLength; \ uint8_t bLength; \
@@ -122,51 +323,30 @@ typedef struct {
// endpoints state // endpoints state
typedef struct{ typedef struct{
#ifdef USB32
uint32_t *tx_buf; // transmission buffer address
#else
uint16_t *tx_buf; // transmission buffer address uint16_t *tx_buf; // transmission buffer address
#endif
uint16_t txbufsz; // transmission buffer size uint16_t txbufsz; // transmission buffer size
#ifdef USB32
uint32_t *rx_buf; // reception buffer address
#else
uint8_t *rx_buf; // reception buffer address uint8_t *rx_buf; // reception buffer address
#endif
void (*func)(); // endpoint action function void (*func)(); // endpoint action function
unsigned rx_cnt : 10; // received data counter unsigned rx_cnt : 10; // received data counter
} ep_t; } ep_t;
typedef struct {
uint32_t dwDTERate;
uint8_t bCharFormat;
#define USB_CDC_1_STOP_BITS 0
#define USB_CDC_1_5_STOP_BITS 1
#define USB_CDC_2_STOP_BITS 2
uint8_t bParityType;
#define USB_CDC_NO_PARITY 0
#define USB_CDC_ODD_PARITY 1
#define USB_CDC_EVEN_PARITY 2
#define USB_CDC_MARK_PARITY 3
#define USB_CDC_SPACE_PARITY 4
uint8_t bDataBits;
} __attribute__ ((packed)) usb_LineCoding;
typedef struct {
uint8_t bmRequestType;
uint8_t bNotificationType;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} __attribute__ ((packed)) usb_cdc_notification;
extern ep_t endpoints[];
extern volatile uint8_t usbON; extern volatile uint8_t usbON;
extern config_pack_t *setup_packet;
extern uint8_t ep0databuf[], setupdatabuf[];
void EP0_Handler();
void USB_setup();
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)());
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size); void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size);
void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size); void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size);
int EP_Read(uint8_t number, uint8_t *buf); int EP_Read(uint8_t number, uint8_t *buf);
usb_LineCoding getLineCoding();
void linecoding_handler(usb_LineCoding *lc); // could be [re]defined in usb_dev.c
void clstate_handler(uint16_t val); extern void usb_class_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);
void break_handler(); extern void usb_vendor_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);
void vendor_handler(config_pack_t *packet); extern void set_configuration();
void chkin();
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)());

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 14.0.1, 2024-09-02T18:14:33. --> <!-- Written by QtCreator 18.0.2, 2026-03-06T19:44:05. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>
@@ -13,8 +13,8 @@
<data> <data>
<variable>ProjectExplorer.Project.EditorSettings</variable> <variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap"> <valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value> <value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value> <value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0"> <valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value> <value type="QString" key="language">Cpp</value>
@@ -33,6 +33,7 @@
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value> <value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value> <value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value> <value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value> <value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value> <value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value> <value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
@@ -85,12 +86,14 @@
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/> <valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value> <value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap> </valuemap>
<value type="int" key="RcSync">0</value>
</valuemap> </valuemap>
</data> </data>
<data> <data>
<variable>ProjectExplorer.Project.Target.0</variable> <variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap"> <valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value> <value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
@@ -132,6 +135,41 @@
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/> <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Default</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Default</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value> <value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0"> <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
@@ -162,6 +200,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value> <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value> <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap> </valuemap>
@@ -172,10 +211,6 @@
<variable>ProjectExplorer.Project.TargetCount</variable> <variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value> <value type="qlonglong">1</value>
</data> </data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data> <data>
<variable>Version</variable> <variable>Version</variable>
<value type="int">22</value> <value type="int">22</value>

View File

@@ -10,6 +10,10 @@ ringbuffer.h
usb.c usb.c
usb.h usb.h
usb_defs.h usb_defs.h
usb_descr.c
usb_descr.h
usb_dev.c
usb_dev.h
usb_lib.c usb_lib.c
usb_lib.h usb_lib.h
usbhw.c usbhw.c

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "usb.h"
#include "usb_lib.h"
// here we suppose that all PIN settings done in hw_setup earlier
void USB_setup(){
#if defined STM32F3
NVIC_DisableIRQ(USB_LP_IRQn);
// remap USB LP & Wakeup interrupts to 75 and 76 - works only on pure F303
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // enable tacting of SYSCFG
SYSCFG->CFGR1 |= SYSCFG_CFGR1_USB_IT_RMP;
#elif defined STM32F1
NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn);
NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn);
#elif defined STM32F0
NVIC_DisableIRQ(USB_IRQn);
RCC->APB1ENR |= RCC_APB1ENR_CRSEN;
RCC->CFGR3 &= ~RCC_CFGR3_USBSW; // reset USB
RCC->CR2 |= RCC_CR2_HSI48ON; // turn ON HSI48
uint32_t tmout = 16000000;
while(!(RCC->CR2 & RCC_CR2_HSI48RDY)){if(--tmout == 0) break;}
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
CRS->CFGR &= ~CRS_CFGR_SYNCSRC;
CRS->CFGR |= CRS_CFGR_SYNCSRC_1; // USB SOF selected as sync source
CRS->CR |= CRS_CR_AUTOTRIMEN; // enable auto trim
CRS->CR |= CRS_CR_CEN; // enable freq counter & block CRS->CFGR as read-only
RCC->CFGR |= RCC_CFGR_SW;
#endif
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
//??
USB->CNTR = USB_CNTR_FRES; // Force USB Reset
for(uint32_t ctr = 0; ctr < 72000; ++ctr) nop(); // wait >1ms
USB->CNTR = 0;
USB->BTABLE = 0;
USB->DADDR = 0;
USB->ISTR = 0;
USB->CNTR = USB_CNTR_RESETM | USB_CNTR_WKUPM; // allow only wakeup & reset interrupts
#if defined STM32F3
NVIC_EnableIRQ(USB_LP_IRQn);
#elif defined STM32F1
NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
#elif defined STM32F0
USB->BCDR |= USB_BCDR_DPPU;
NVIC_EnableIRQ(USB_IRQn);
#endif
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#if defined STM32F0
#include <stm32f0.h>
#elif defined STM32F1
#include <stm32f1.h>
// there's no this define in standard header
#define USB_BASE ((uint32_t)0x40005C00)
#elif defined STM32F3
#include <stm32f3.h>
#endif
// max endpoints number
#define STM32ENDPOINTS 8
/**
* Buffers size definition
**/
// F0 - USB2_16; F1 - USB1_16; F3 - 1/2 depending on series
#if !defined USB1_16 && !defined USB2_16
#if defined STM32F0
#define USB2_16
#elif defined STM32F1
#define USB1_16
#else
#error "Can't determine USB1_16 or USB2_16, define by hands"
#endif
#endif
// BTABLE_SIZE FOR STM32F3:
// In STM32F303/302xB/C, 512 bytes SRAM is not shared with CAN.
// In STM32F302x6/x8 and STM32F30xxD/E, 726 bytes dedicated SRAM and 256 bytes shared SRAM with CAN i.e.
// 1Kbytes dedicated SRAM in case CAN is disabled.
// remember, that USB_BTABLE_SIZE will be divided by ACCESSZ, so don't divide it twice for 32-bit addressing
#ifdef NOCAN
#if defined STM32F0
#define USB_BTABLE_SIZE 1024
#elif defined STM32F3
#define USB_BTABLE_SIZE 512
#warning "Please, check real buffer size due to docs"
#else
#error "define STM32F0 or STM32F3"
#endif
#else // !NOCAN: F0/F3 with CAN or F1 (can't simultaneously run CAN and USB)
#if defined STM32F0
#define USB_BTABLE_SIZE 768
#elif defined STM32F3
#define USB_BTABLE_SIZE 512
#warning "Please, check real buffer size due to docs"
#else // STM32F103: 1024 bytes but with 32-bit addressing
#define USB_BTABLE_SIZE 1024
#endif
#endif // NOCAN
// first 64 bytes of USB_BTABLE are registers!
//#define USB_EP0_BASEADDR 64
// for USB FS EP0 buffers are from 8 to 64 bytes long (64 for PL2303)
#define USB_EP0_BUFSZ 64
// USB transmit buffer size (64 for PL2303)
#define USB_TXBUFSZ 64
// USB receive buffer size (64 for PL2303)
#define USB_RXBUFSZ 64
// EP1 - interrupt - buffer size
#define USB_EP1BUFSZ 8
#define USB_BTABLE_BASE 0x40006000
#define USB ((USB_TypeDef *) USB_BASE)
#ifdef USB_BTABLE
#undef USB_BTABLE
#endif
#define USB_BTABLE ((USB_BtableDef *)(USB_BTABLE_BASE))
#define USB_ISTR_EPID 0x0000000F
#define USB_FNR_LSOF_0 0x00000800
#define USB_FNR_lSOF_1 0x00001000
#define USB_LPMCSR_BESL_0 0x00000010
#define USB_LPMCSR_BESL_1 0x00000020
#define USB_LPMCSR_BESL_2 0x00000040
#define USB_LPMCSR_BESL_3 0x00000080
#define USB_EPnR_CTR_RX 0x00008000
#define USB_EPnR_DTOG_RX 0x00004000
#define USB_EPnR_STAT_RX 0x00003000
#define USB_EPnR_STAT_RX_0 0x00001000
#define USB_EPnR_STAT_RX_1 0x00002000
#define USB_EPnR_SETUP 0x00000800
#define USB_EPnR_EP_TYPE 0x00000600
#define USB_EPnR_EP_TYPE_0 0x00000200
#define USB_EPnR_EP_TYPE_1 0x00000400
#define USB_EPnR_EP_KIND 0x00000100
#define USB_EPnR_CTR_TX 0x00000080
#define USB_EPnR_DTOG_TX 0x00000040
#define USB_EPnR_STAT_TX 0x00000030
#define USB_EPnR_STAT_TX_0 0x00000010
#define USB_EPnR_STAT_TX_1 0x00000020
#define USB_EPnR_EA 0x0000000F
#define USB_COUNTn_RX_BLSIZE 0x00008000
#define USB_COUNTn_NUM_BLOCK 0x00007C00
#define USB_COUNTn_RX 0x0000003F
#define USB_TypeDef USB_TypeDef_custom
typedef struct {
__IO uint32_t EPnR[STM32ENDPOINTS];
__IO uint32_t RESERVED[STM32ENDPOINTS];
__IO uint32_t CNTR;
__IO uint32_t ISTR;
__IO uint32_t FNR;
__IO uint32_t DADDR;
__IO uint32_t BTABLE;
#ifdef STM32F0
__IO uint32_t LPMCSR;
__IO uint32_t BCDR;
#endif
} USB_TypeDef;
// F303 D/E have 2x16 access scheme
typedef struct{
#if defined USB2_16
__IO uint16_t USB_ADDR_TX;
__IO uint16_t USB_COUNT_TX;
__IO uint16_t USB_ADDR_RX;
__IO uint16_t USB_COUNT_RX;
#define ACCESSZ (1)
#define BUFTYPE uint8_t
#elif defined USB1_16
__IO uint32_t USB_ADDR_TX;
__IO uint32_t USB_COUNT_TX;
__IO uint32_t USB_ADDR_RX;
__IO uint32_t USB_COUNT_RX;
#define ACCESSZ (2)
#define BUFTYPE uint16_t
#else
#error "Define USB1_16 or USB2_16"
#endif
} USB_EPDATA_TypeDef;
typedef struct{
__IO USB_EPDATA_TypeDef EP[STM32ENDPOINTS];
} USB_BtableDef;
void USB_setup();

View File

@@ -1,2 +1,2 @@
#define BUILD_NUMBER "50" #define BUILD_NUMBER "54"
#define BUILD_DATE "2024-11-18" #define BUILD_DATE "2026-03-06"

View File

@@ -6,4 +6,4 @@ LDSCRIPT ?= stm32f103xB.ld
DEFINES := -DSTM32F10X_MD DEFINES := -DSTM32F10X_MD
include ../makefile.f1 include ../makefile.f1
include ../../makefile.stm32 include ../makefile.stm32

View File

@@ -0,0 +1 @@
fix error in USB_send (RB_write returns 0 in case of overflow!)

Binary file not shown.

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 16.0.2, 2025-06-02T17:02:57. --> <!-- Written by QtCreator 18.0.2, 2026-02-13T20:51:37. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value> <value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
</data> </data>
<data> <data>
<variable>ProjectExplorer.Project.ActiveTarget</variable> <variable>ProjectExplorer.Project.ActiveTarget</variable>
@@ -40,9 +40,9 @@
<value type="int" key="EditorConfiguration.PaddingMode">1</value> <value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value> <value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value> <value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value> <value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value> <value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value> <value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value> <value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value> <value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value> <value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
@@ -51,10 +51,10 @@
<value type="bool" key="EditorConfiguration.UseIndenter">false</value> <value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value> <value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value> <value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value> <value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value> <value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value> <value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value> <value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value> <value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value> <value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap> </valuemap>
@@ -79,27 +79,28 @@
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value> <value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value> <value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value> <value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">4</value> <value type="int" key="ClangTools.ParallelJobs">8</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value> <value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/> <valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/> <valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/> <valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value> <value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap> </valuemap>
<value type="int" key="RcSync">0</value>
</valuemap> </valuemap>
</data> </data>
<data> <data>
<variable>ProjectExplorer.Project.Target.0</variable> <variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap"> <valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value> <value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value> <value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value> <value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value> <value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0"> <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/eddy/Docs/SAO/ELECTRONICS/STM32/F1-srcs/BISS_C_encoders</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets"> <valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
@@ -109,8 +110,8 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Сборка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap> </valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
@@ -122,8 +123,8 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Очистка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap> </valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value> <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
@@ -131,15 +132,50 @@
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/> <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value> <value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/> <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Default</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value> <value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0"> <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Развёртывание</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Развёртывание</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap> </valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value> <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
@@ -163,6 +199,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value> <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value> <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap> </valuemap>
@@ -173,10 +210,6 @@
<variable>ProjectExplorer.Project.TargetCount</variable> <variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value> <value type="qlonglong">1</value>
</data> </data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data> <data>
<variable>Version</variable> <variable>Version</variable>
<value type="int">22</value> <value type="int">22</value>

View File

@@ -0,0 +1,184 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 16.0.2, 2025-06-02T17:02:57. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">4</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/eddy/Docs/SAO/ELECTRONICS/STM32/F1-srcs/BISS_C_encoders</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -23,7 +23,9 @@
#include "usb_dev.h" // printout #include "usb_dev.h" // printout
#include <string.h> // memcpy #include <string.h> // memcpy
extern const uint32_t __varsstart, _BLOCKSIZE; extern const uint32_t _BLOCKSIZE;
extern const user_conf __varsstart;
static const uint32_t FLASH_blocksize = (uint32_t)&_BLOCKSIZE; static const uint32_t FLASH_blocksize = (uint32_t)&_BLOCKSIZE;
static uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here static uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here

View File

@@ -38,6 +38,7 @@ typedef struct{
uint8_t CPHA : 1; uint8_t CPHA : 1;
uint8_t BR : 3; uint8_t BR : 3;
uint8_t monit: 1; // auto monitoring of encoder each `monittime` milliseconds uint8_t monit: 1; // auto monitoring of encoder each `monittime` milliseconds
uint8_t debug: 1; // debug output of read data
} flags_t; } flags_t;
/* /*

View File

@@ -54,7 +54,7 @@ static void proc_enc(uint8_t idx){
char ifacechr = idx ? 'Y' : 'X'; char ifacechr = idx ? 'Y' : 'X';
if(CDCready[iface]){ if(CDCready[iface]){
int l = USB_receivestr(iface, inbuff, RBINSZ); int l = USB_receivestr(iface, inbuff, RBINSZ);
if(CDCready[I_CMD]){ if(CDCready[I_CMD] && the_conf.flags.debug){
if(l){ if(l){
CMDWR("Enc"); USB_putbyte(I_CMD, ifacechr); CMDWR("Enc"); USB_putbyte(I_CMD, ifacechr);
CMDWR(": "); CMDWR(": ");
@@ -96,7 +96,7 @@ static void proc_enc(uint8_t idx){
if(str) ++gotgood[idx]; if(str) ++gotgood[idx];
else ++gotwrong[idx]; else ++gotwrong[idx];
} }
}else if(!the_conf.flags.monit){ }else if(!the_conf.flags.monit && the_conf.flags.debug){
printResult(&result); printResult(&result);
CMDWR("ENC"); USB_putbyte(I_CMD, ifacechr); CMDWR("ENC"); USB_putbyte(I_CMD, ifacechr);
USB_putbyte(I_CMD, '='); USB_putbyte(I_CMD, '=');
@@ -111,13 +111,20 @@ static void proc_enc(uint8_t idx){
USB_sendstr(iface, str); USB_sendstr(iface, str);
USB_putbyte(iface, '\n'); USB_putbyte(iface, '\n');
} }
if(result.error) spi_setup(1+idx); // reinit SPI in case of error if(result.error){
spi_setup(1+idx); // reinit SPI in case of error
if(CDCready[I_CMD] && the_conf.flags.debug){
CMDWR("Err, restart SPI "); USB_putbyte(I_CMD, '1'+idx); CMDn();
}
spi_start_enc(idx); // restart measurement
}
if(the_conf.flags.monit) monitT[idx] = Tms; if(the_conf.flags.monit) monitT[idx] = Tms;
else if(testflag) spi_start_enc(idx); else if(testflag) spi_start_enc(idx);
} }
int main(){ int main(){
uint32_t lastT = 0, usartT = 0; uint32_t lastT = 0, usartT = 0;
uint8_t oldCDCready[bTotNumEndpoints] = {0};
StartHSE(); StartHSE();
flashstorage_init(); flashstorage_init();
hw_setup(); hw_setup();
@@ -139,6 +146,18 @@ int main(){
int l = USB_receivestr(I_CMD, inbuff, RBINSZ); int l = USB_receivestr(I_CMD, inbuff, RBINSZ);
if(l < 0) CMDWRn("ERROR: CMD USB buffer overflow or string was too long"); if(l < 0) CMDWRn("ERROR: CMD USB buffer overflow or string was too long");
else if(l) parse_cmd(inbuff); else if(l) parse_cmd(inbuff);
// check if interface connected/disconnected
// (we CAN'T do much debug output in interrupt functions like linecoding_handler etc, so do it here)
for(int i = 1; i < bTotNumEndpoints; ++i){
if(oldCDCready[i] != CDCready[i]){
CMDWR("Interface ");
CMDWR(u2str(i));
USB_putbyte(I_CMD, ' ');
if(CDCready[i] == 0) CMDWR("dis");
CMDWRn("connected");
oldCDCready[i] = CDCready[i];
}
}
} }
proc_enc(0); proc_enc(0);
proc_enc(1); proc_enc(1);

View File

@@ -80,6 +80,7 @@ typedef enum{
C_amperiod, C_amperiod,
C_usart, C_usart,
C_ssii, C_ssii,
C_debug,
C_AMOUNT C_AMOUNT
} cmd_e; } cmd_e;
@@ -268,8 +269,6 @@ static errcode_e setuintpar(cmd_e idx, char *par){
return ERR_BADCMD; return ERR_BADCMD;
} }
} }
CMDWR(commands[idx].cmd);
USB_putbyte(I_CMD, '=');
switch(idx){ switch(idx){
case C_br: case C_br:
val = the_conf.flags.BR; val = the_conf.flags.BR;
@@ -295,6 +294,8 @@ static errcode_e setuintpar(cmd_e idx, char *par){
default: default:
return ERR_BADCMD; return ERR_BADCMD;
} }
CMDWR(commands[idx].cmd);
USB_putbyte(I_CMD, '=');
CMDWR(u2str(val)); CMDWR(u2str(val));
CMDn(); CMDn();
return ERR_SILENCE; return ERR_SILENCE;
@@ -323,6 +324,9 @@ static errcode_e setboolpar(cmd_e idx, char *par){
case C_autom: case C_autom:
the_conf.flags.monit = val; the_conf.flags.monit = val;
break; break;
case C_debug:
the_conf.flags.debug = val;
break;
default: default:
return ERR_BADCMD; return ERR_BADCMD;
} }
@@ -343,6 +347,9 @@ static errcode_e setboolpar(cmd_e idx, char *par){
case C_autom: case C_autom:
val = the_conf.flags.monit; val = the_conf.flags.monit;
break; break;
case C_debug:
val = the_conf.flags.debug;
break;
default: default:
return ERR_BADCMD; return ERR_BADCMD;
} }
@@ -364,6 +371,7 @@ static errcode_e dumpconf(cmd_e _U_ idx, char _U_ *par){
setuintpar(C_br, NULL); setuintpar(C_br, NULL);
setboolpar(C_cpha, NULL); setboolpar(C_cpha, NULL);
setboolpar(C_cpol, NULL); setboolpar(C_cpol, NULL);
setboolpar(C_debug, NULL);
setuintpar(C_encbits, NULL); setuintpar(C_encbits, NULL);
setuintpar(C_encbufsz, NULL); setuintpar(C_encbufsz, NULL);
setuintpar(C_maxzeros, NULL); setuintpar(C_maxzeros, NULL);
@@ -410,6 +418,7 @@ static const funcdescr_t commands[C_AMOUNT] = {
[C_amperiod] = {"amperiod", setuintpar}, [C_amperiod] = {"amperiod", setuintpar},
[C_usart] = {"usart", usart}, [C_usart] = {"usart", usart},
[C_ssii] = {"ssii", setuintpar}, [C_ssii] = {"ssii", setuintpar},
[C_debug] = {"debug", setboolpar},
}; };
typedef struct{ typedef struct{
@@ -434,6 +443,7 @@ static const help_t helpmessages[] = {
{C_br, "change SPI BR register (1 - 18MHz ... 7 - 281kHz)"}, {C_br, "change SPI BR register (1 - 18MHz ... 7 - 281kHz)"},
{C_cpha, "change CPHA value (0/1)"}, {C_cpha, "change CPHA value (0/1)"},
{C_cpol, "change CPOL value (0/1)"}, {C_cpol, "change CPOL value (0/1)"},
{C_debug, "turn on debugging output of read encoders' values"},
{C_dumpconf, "dump current configuration"}, {C_dumpconf, "dump current configuration"},
{C_encbits, "set encoder data bits amount (26/32)"}, {C_encbits, "set encoder data bits amount (26/32)"},
{C_encbufsz, "change encoder auxiliary buffer size (8..32 bytes)"}, {C_encbufsz, "change encoder auxiliary buffer size (8..32 bytes)"},
@@ -486,6 +496,7 @@ static errcode_e help(_U_ cmd_e idx, _U_ char* par){
void parse_cmd(char *cmd){ void parse_cmd(char *cmd){
errcode_e ecode = ERR_BADCMD; errcode_e ecode = ERR_BADCMD;
// command and its parameter // command and its parameter
//CMDWRn(cmd);
char *cmdstart = omit_spaces(cmd), *parstart = NULL; char *cmdstart = omit_spaces(cmd), *parstart = NULL;
if(!cmdstart) goto retn; if(!cmdstart) goto retn;
char *ptr = cmdstart; char *ptr = cmdstart;
@@ -506,5 +517,9 @@ void parse_cmd(char *cmd){
if(idx >= C_AMOUNT) goto retn; // not found if(idx >= C_AMOUNT) goto retn; // not found
ecode = commands[idx].handler(idx, parstart); ecode = commands[idx].handler(idx, parstart);
retn: retn:
if(ecode != ERR_SILENCE) CMDWRn(errors[ecode]); if(ecode == ERR_BADCMD){
CMDWR(cmd);
USB_putbyte(I_CMD, '=');
CMDWRn(errors[ERR_BADCMD]);
} else if(ecode != ERR_SILENCE) CMDWRn(errors[ecode]);
} }

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <string.h> // memcpy
#include "ringbuffer.h" #include "ringbuffer.h"
static int datalen(ringbuffer *b){ static int datalen(ringbuffer *b){
@@ -57,11 +58,11 @@ int RB_hasbyte(ringbuffer *b, uint8_t byte){
b->busy = 0; b->busy = 0;
return ret; return ret;
} }
/*
// poor memcpy // poor memcpy
static void mcpy(uint8_t *targ, const uint8_t *src, int l){ static void mcpy(uint8_t *targ, const uint8_t *src, int l){
while(l--) *targ++ = *src++; while(l--) *targ++ = *src++;
} }*/
// increment head or tail // increment head or tail
TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){ TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){
@@ -76,9 +77,11 @@ static int read(ringbuffer *b, uint8_t *s, int len){
int _1st = b->length - b->head; int _1st = b->length - b->head;
if(_1st > l) _1st = l; if(_1st > l) _1st = l;
if(_1st > len) _1st = len; if(_1st > len) _1st = len;
mcpy(s, b->data + b->head, _1st); //mcpy(s, b->data + b->head, _1st);
memcpy(s, b->data + b->head, _1st);
if(_1st < len && l > _1st){ if(_1st < len && l > _1st){
mcpy(s+_1st, b->data, l - _1st); //mcpy(s+_1st, b->data, l - _1st);
memcpy(s+_1st, b->data, l - _1st);
incr(b, &b->head, l); incr(b, &b->head, l);
return l; return l;
} }
@@ -132,9 +135,11 @@ static int write(ringbuffer *b, const uint8_t *str, int l){
if(l > r || !l) return 0; if(l > r || !l) return 0;
int _1st = b->length - b->tail; int _1st = b->length - b->tail;
if(_1st > l) _1st = l; if(_1st > l) _1st = l;
mcpy(b->data + b->tail, str, _1st); //mcpy(b->data + b->tail, str, _1st);
memcpy(b->data + b->tail, str, _1st);
if(_1st < l){ // add another piece from start if(_1st < l){ // add another piece from start
mcpy(b->data, str+_1st, l-_1st); //mcpy(b->data, str+_1st, l-_1st);
memcpy(b->data, str+_1st, l-_1st);
} }
incr(b, &b->tail, l); incr(b, &b->tail, l);
return l; return l;

View File

@@ -127,6 +127,7 @@ uint8_t *spi_read_enc(uint8_t encno){
// @return FALSE if SPI is busy // @return FALSE if SPI is busy
// here `encodernum` is 0 (SPI1) or 1 (SPI2), not 1/2 as SPI index! // here `encodernum` is 0 (SPI1) or 1 (SPI2), not 1/2 as SPI index!
int spi_start_enc(int encodernum){ int spi_start_enc(int encodernum){
// CMDWR("startenc: "); USB_putbyte(I_CMD, '0'+encodernum); newline(I_CMD);
int spiidx = encodernum + 1; int spiidx = encodernum + 1;
if(spiidx < 1 || spiidx > AMOUNT_OF_SPI) return FALSE; if(spiidx < 1 || spiidx > AMOUNT_OF_SPI) return FALSE;
if(spi_status[spiidx] != SPI_READY) return FALSE; if(spi_status[spiidx] != SPI_READY) return FALSE;

View File

@@ -19,6 +19,7 @@
#include <string.h> #include <string.h>
#include "ringbuffer.h" #include "ringbuffer.h"
#include "strfunc.h" // for cmd interface
#include "usb_descr.h" #include "usb_descr.h"
#include "usb_dev.h" #include "usb_dev.h"
@@ -37,11 +38,15 @@
#define CONTROL_DTR 0x01 #define CONTROL_DTR 0x01
#define CONTROL_RTS 0x02 #define CONTROL_RTS 0x02
// It's good to use debug here ONLY to debug into USART!
// never try to debug USB into USB!!!
#undef DBG #undef DBG
#define DBG(x) #define DBG(x)
#undef DBGs #undef DBGs
#define DBGs(x) #define DBGs(x)
extern volatile uint32_t Tms;
// inbuf overflow when receiving // inbuf overflow when receiving
static volatile uint8_t bufovrfl[bTotNumEndpoints] = {0}; static volatile uint8_t bufovrfl[bTotNumEndpoints] = {0};
@@ -64,14 +69,21 @@ static volatile ringbuffer rbin[bTotNumEndpoints] = {IBUF(0), IBUF(1), IBUF(2)};
static volatile int lastdsz[bTotNumEndpoints] = {-1, -1, -1}; static volatile int lastdsz[bTotNumEndpoints] = {-1, -1, -1};
static void chkin(uint8_t ifno){ static void chkin(uint8_t ifno){
static int ovrflctr = 0; // "antistall" counter
if(bufovrfl[ifno]) return; // allow user to know that previous buffer was overflowed and cleared if(bufovrfl[ifno]) return; // allow user to know that previous buffer was overflowed and cleared
if(!rcvbuflen[ifno]) return; if(!rcvbuflen[ifno]) return;
int w = RB_write((ringbuffer*)&rbin[ifno], (uint8_t*)rcvbuf[ifno], rcvbuflen[ifno]); int w = RB_write((ringbuffer*)&rbin[ifno], (uint8_t*)rcvbuf[ifno], rcvbuflen[ifno]);
if(w < 0){ if(w < 0){ // buffer busy
DBG("Can't write into buffer"); DBG("Can't write into buffer: busy");
return; return;
}else if(w == 0){ // no enough space or (WTF) incoming string larger than buffer size
if(rcvbuflen[ifno] > rbin[ifno].length || ++ovrflctr > 9999){
bufovrfl[ifno] = 1; // real overflow in case if ringbuffer's size less than USB buffer
ovrflctr = 0;
}else{
return; // not enough space
}
} }
if(w != rcvbuflen[ifno]) bufovrfl[ifno] = 1;
DBG("Put data into buffer"); DBG("Put data into buffer");
rcvbuflen[ifno] = 0; rcvbuflen[ifno] = 0;
uint16_t status = KEEP_DTOG(USB->EPnR[1+ifno]); // don't change DTOG uint16_t status = KEEP_DTOG(USB->EPnR[1+ifno]); // don't change DTOG
@@ -106,14 +118,14 @@ static void send_next(uint8_t ifno){
// data IN/OUT handler // data IN/OUT handler
static void rxtx_handler(){ static void rxtx_handler(){
uint8_t ifno = (USB->ISTR & USB_ISTR_EPID) - 1; uint8_t epno = (USB->ISTR & USB_ISTR_EPID), ifno = epno - 1;
DBG("rxtx_handler"); DBG("rxtx_handler");
DBGs(uhex2str(ifno)); DBGs(uhex2str(ifno));
if(ifno > bTotNumEndpoints-1){ if(epno > bTotNumEndpoints){
DBG("wront ifno"); DBG("wrong ifno");
return; return;
} }
uint16_t epstatus = KEEP_DTOG(USB->EPnR[1+ifno]); uint16_t epstatus = KEEP_DTOG(USB->EPnR[epno]);
if(RX_FLAG(epstatus)){ // receive data if(RX_FLAG(epstatus)){ // receive data
DBG("Got data"); DBG("Got data");
if(rcvbuflen[ifno]){ if(rcvbuflen[ifno]){
@@ -121,13 +133,13 @@ static void rxtx_handler(){
rcvbuflen[ifno] = 0; rcvbuflen[ifno] = 0;
DBG("OVERFULL"); DBG("OVERFULL");
} }
rcvbuflen[ifno] = EP_Read(1+ifno, (uint8_t*)rcvbuf[ifno]); rcvbuflen[ifno] = EP_Read(epno, (uint8_t*)rcvbuf[ifno]);
DBGs(uhex2str(rcvbuflen[ifno])); DBGs(uhex2str(rcvbuflen[ifno]));
USB->EPnR[1+ifno] = epstatus & ~(USB_EPnR_CTR_RX | USB_EPnR_STAT_RX | USB_EPnR_STAT_TX); // keep RX in STALL state until read data USB->EPnR[epno] = epstatus & ~(USB_EPnR_CTR_RX | USB_EPnR_STAT_RX | USB_EPnR_STAT_TX); // keep RX in STALL state until read data
chkin(ifno); // try to write current data into RXbuf if it's not busy chkin(ifno); // try to write current data into RXbuf if it's not busy
}else{ // tx successfull }else{ // tx successfull
DBG("Tx OK"); DBG("Tx OK");
USB->EPnR[1+ifno] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_TX)) ^ USB_EPnR_STAT_RX; USB->EPnR[epno] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_TX)) ^ USB_EPnR_STAT_RX;
send_next(ifno); send_next(ifno);
} }
} }
@@ -140,24 +152,35 @@ void WEAK linecoding_handler(uint8_t ifno, usb_LineCoding *lc){
DBGs(uhex2str(ifno)); DBGs(uhex2str(ifno));
} }
static void clearbufs(uint8_t ifno){
uint32_t T0 = Tms;
while(Tms - T0 < 10){ // wait no more than 10ms
if(1 == RB_clearbuf((ringbuffer*)&rbin[ifno])) break;
}
T0 = Tms;
while(Tms - T0 < 10){
if(1 == RB_clearbuf((ringbuffer*)&rbout[ifno])) break;
}
rcvbuflen[ifno] = 0;
}
// SET_CONTROL_LINE_STATE // SET_CONTROL_LINE_STATE
void WEAK clstate_handler(uint8_t ifno, uint16_t val){ void WEAK clstate_handler(uint8_t ifno, uint16_t val){
DBG("clstate_handler"); DBG("clstate_handler");
DBGs(uhex2str(ifno)); DBGs(uhex2str(ifno));
DBGs(uhex2str(val)); DBGs(uhex2str(val));
if(val) clearbufs(ifno); // clear buffers on connect
CDCready[ifno] = val; // CONTROL_DTR | CONTROL_RTS -> interface connected; 0 -> disconnected CDCready[ifno] = val; // CONTROL_DTR | CONTROL_RTS -> interface connected; 0 -> disconnected
lastdsz[ifno] = -1; lastdsz[ifno] = -1;
} }
// SEND_BREAK // SEND_BREAK - disconnect interface and clear its buffers
void WEAK break_handler(uint8_t ifno){ void WEAK break_handler(uint8_t ifno){
CDCready[ifno] = 0; CDCready[ifno] = 0;
lastdsz[ifno] = -1;
DBG("break_handler()"); DBG("break_handler()");
DBGs(uhex2str(ifno)); DBGs(uhex2str(ifno));
} }
// USB is configured: setup endpoints // USB is configured: setup endpoints
void set_configuration(){ void set_configuration(){
DBG("set_configuration()"); DBG("set_configuration()");
@@ -172,13 +195,13 @@ void set_configuration(){
} }
} }
// PL2303 CLASS request // USB CDC CLASS request
void usb_class_request(config_pack_t *req, uint8_t *data, uint16_t datalen){ void usb_class_request(config_pack_t *req, uint8_t *data, uint16_t datalen){
uint8_t recipient = REQUEST_RECIPIENT(req->bmRequestType); uint8_t recipient = REQUEST_RECIPIENT(req->bmRequestType);
uint8_t dev2host = (req->bmRequestType & 0x80) ? 1 : 0; uint8_t dev2host = (req->bmRequestType & 0x80) ? 1 : 0;
uint8_t ifno = req->wIndex >> 1; uint8_t ifno = req->wIndex >> 1;
if(ifno > bTotNumEndpoints-1 && ifno != 0xff){ if(ifno > bTotNumEndpoints-1 && ifno != 0xff){
DBG("wront ifno"); DBG("wrong ifno");
return; return;
} }
DBG("usb_class_request"); DBG("usb_class_request");
@@ -222,7 +245,12 @@ void usb_class_request(config_pack_t *req, uint8_t *data, uint16_t datalen){
// blocking send full content of ring buffer // blocking send full content of ring buffer
int USB_sendall(uint8_t ifno){ int USB_sendall(uint8_t ifno){
uint32_t T0 = Tms;
while(lastdsz[ifno] > 0){ while(lastdsz[ifno] > 0){
if(Tms - T0 > DISCONN_TMOUT){
break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]) return FALSE; if(!CDCready[ifno]) return FALSE;
IWDG->KR = IWDG_REFRESH; IWDG->KR = IWDG_REFRESH;
} }
@@ -233,10 +261,23 @@ int USB_sendall(uint8_t ifno){
int USB_send(uint8_t ifno, const uint8_t *buf, int len){ int USB_send(uint8_t ifno, const uint8_t *buf, int len){
if(!buf || !CDCready[ifno] || !len) return FALSE; if(!buf || !CDCready[ifno] || !len) return FALSE;
DBG("USB_send"); DBG("USB_send");
uint32_t T0 = Tms;
while(len){ while(len){
if(Tms - T0 > DISCONN_TMOUT){
break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]) return FALSE; if(!CDCready[ifno]) return FALSE;
IWDG->KR = IWDG_REFRESH; IWDG->KR = IWDG_REFRESH;
int a = RB_write((ringbuffer*)&rbout[ifno], buf, len); int l = RB_datalen((ringbuffer*)&rbout[ifno]);
if(l < 0) continue;
int portion = rbout[ifno].length - 1 - l;
if(portion < 1){
if(lastdsz[ifno] < 0) send_next(ifno);
continue;
}
if(portion > len) portion = len;
int a = RB_write((ringbuffer*)&rbout[ifno], buf, portion);
if(a > 0){ if(a > 0){
len -= a; len -= a;
buf += a; buf += a;
@@ -251,7 +292,12 @@ int USB_send(uint8_t ifno, const uint8_t *buf, int len){
int USB_putbyte(uint8_t ifno, uint8_t byte){ int USB_putbyte(uint8_t ifno, uint8_t byte){
if(!CDCready[ifno]) return FALSE; if(!CDCready[ifno]) return FALSE;
int l = 0; int l = 0;
uint32_t T0 = Tms;
while((l = RB_write((ringbuffer*)&rbout[ifno], &byte, 1)) != 1){ while((l = RB_write((ringbuffer*)&rbout[ifno], &byte, 1)) != 1){
if(Tms - T0 > DISCONN_TMOUT){
break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]) return FALSE; if(!CDCready[ifno]) return FALSE;
IWDG->KR = IWDG_REFRESH; IWDG->KR = IWDG_REFRESH;
if(l == 0){ // overfull if(l == 0){ // overfull
@@ -278,11 +324,11 @@ int USB_sendstr(uint8_t ifno, const char *string){
* @return amount of received bytes (negative, if overfull happened) * @return amount of received bytes (negative, if overfull happened)
*/ */
int USB_receive(uint8_t ifno, uint8_t *buf, int len){ int USB_receive(uint8_t ifno, uint8_t *buf, int len){
chkin(ifno); chkin(ifno); // rxtx_handler could leave last message unwritten if buffer was busy
if(bufovrfl[ifno]){ if(bufovrfl[ifno]){
DBG("Buffer overflow"); DBG("Buffer overflow");
DBGs(uhex2str(ifno)); DBGs(uhex2str(ifno));
while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno])); while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno])); // run watchdog in case of problems
bufovrfl[ifno] = 0; bufovrfl[ifno] = 0;
return -1; return -1;
} }
@@ -299,7 +345,7 @@ int USB_receive(uint8_t ifno, uint8_t *buf, int len){
* @return strlen or negative value indicating overflow (if so, string won't be ends with 0 and buffer should be cleared) * @return strlen or negative value indicating overflow (if so, string won't be ends with 0 and buffer should be cleared)
*/ */
int USB_receivestr(uint8_t ifno, char *buf, int len){ int USB_receivestr(uint8_t ifno, char *buf, int len){
chkin(ifno); chkin(ifno); // rxtx_handler could leave last message unwritten if buffer was busy
if(bufovrfl[ifno]){ if(bufovrfl[ifno]){
while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno])); while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno]));
bufovrfl[ifno] = 0; bufovrfl[ifno] = 0;
@@ -307,7 +353,9 @@ int USB_receivestr(uint8_t ifno, char *buf, int len){
} }
int l = RB_readto((ringbuffer*)&rbin[ifno], '\n', (uint8_t*)buf, len); int l = RB_readto((ringbuffer*)&rbin[ifno], '\n', (uint8_t*)buf, len);
if(l < 1){ if(l < 1){
if(rbin[ifno].length == RB_datalen((ringbuffer*)&rbin[ifno])){ // buffer is full but no '\n' found //if(rbin[ifno].length < 1 + RB_datalen((ringbuffer*)&rbin[ifno])){ // buffer is full but no '\n' found
if(RB_datalen((ringbuffer*)&rbin[ifno]) >= len){
CMDWRn("OVERFULL!");
while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno])); while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno]));
return -1; return -1;
} }

View File

@@ -46,6 +46,8 @@ void break_handler(uint8_t ifno);
void clstate_handler(uint8_t ifno, uint16_t val); void clstate_handler(uint8_t ifno, uint16_t val);
void linecoding_handler(uint8_t ifno, usb_LineCoding *lc); void linecoding_handler(uint8_t ifno, usb_LineCoding *lc);
// as ugly CDC have no BREAK after disconnected client in non-canonical mode, we should use timeout - more than 2ms
#define DISCONN_TMOUT (1000)
// sizes of ringbuffers for outgoing and incoming data // sizes of ringbuffers for outgoing and incoming data
#define RBOUTSZ (512) #define RBOUTSZ (512)

View File

@@ -1,2 +1,2 @@
#define BUILD_NUMBER "108" #define BUILD_NUMBER "144"
#define BUILD_DATE "2025-06-03" #define BUILD_DATE "2026-02-13"

View File

@@ -1,897 +1,8 @@
212010875057830 1672845343065
Package_TO_SOT_SMD pins
ATPAK-2 Pin1.5x2mm
ATPAK SMD package, http://www.onsemi.com/pub/Collateral/ENA2192-D.PDF THT pad as test Point, diameter 2.0mm, hole diameter 1.0mm
ATPAK test point THT pad
0 0
7 1
3 1
Package_TO_SOT_SMD
Analog_KS-4
Analog Devices KS-4, http://www.analog.com/media/en/package-pcb-resources/package/pkg_pdf/sc70ks/ks_4.pdf
Analog Devices KS-4 (like EIAJ SC-82)
0
4
4
Package_TO_SOT_SMD
Diodes_SOT-553
Diodes SOT-553, https://www.diodes.com/assets/Package-Files/SOT553.pdf
SOT-553
0
5
5
Package_TO_SOT_SMD
HVSOF5
HVSOF5, http://rohmfs.rohm.com/en/techdata_basic/ic/package/hvsof5_1-e.pdf, http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/hall/bu52001gul-e.pdf
HVSOF5
0
5
5
Package_TO_SOT_SMD
HVSOF6
HVSOF6, http://rohmfs.rohm.com/en/techdata_basic/ic/package/hvsof6_1-e.pdf, http://rohmfs.rohm.com/en/products/databook/datasheet/ic/audio_video/video_amplifier/bh76106hfv-e.pdf
HVSOF6
0
7
7
Package_TO_SOT_SMD
Infineon_PG-HDSOP-10-1
Infineon PG-HDSOP-10-1 (DDPAK), 20.96x6.5x2.3mm, slug up (https://www.infineon.com/cms/en/product/packages/PG-HDSOP/PG-HDSOP-10-1/)
hdsop 10 ddpak
0
10
10
Package_TO_SOT_SMD
Infineon_PG-HSOF-8-1
HSOF-8-1 [TOLL] power MOSFET (http://www.infineon.com/cms/en/product/packages/PG-HSOF/PG-HSOF-8-1/)
mosfet hsof toll
0
53
3
Package_TO_SOT_SMD
Infineon_PG-HSOF-8-1_ThermalVias
HSOF-8-1 [TOLL] power MOSFET (http://www.infineon.com/cms/en/product/packages/PG-HSOF/PG-HSOF-8-1/)
mosfet hsof toll thermal vias
0
96
3
Package_TO_SOT_SMD
Infineon_PG-HSOF-8-2
HSOF-8-2 [TOLL] power MOSFET (http://www.infineon.com/cms/en/product/packages/PG-HSOF/PG-HSOF-8-2/)
mosfet hsof toll
0
12
4
Package_TO_SOT_SMD
Infineon_PG-HSOF-8-2_ThermalVias
HSOF-8-2 [TOLL] power MOSFET (http://www.infineon.com/cms/en/product/packages/PG-HSOF/PG-HSOF-8-2/)
mosfet hsof toll thermal vias
0
97
4
Package_TO_SOT_SMD
Infineon_PG-HSOF-8-2_ThermalVias2
HSOF-8-2 [TOLL] power MOSFET (http://www.infineon.com/cms/en/product/packages/PG-HSOF/PG-HSOF-8-2/, https://www.infineon.com/dgdl/Infineon-ApplicationNote_600V_CoolMOS_C7_Gold_TOLL-AN-v01_00-EN.pdf?fileId=5546d4625b10283a015b144a1af70df6)
mosfet hsof toll thermal vias
0
159
4
Package_TO_SOT_SMD
Infineon_PG-HSOF-8-3
HSOF-8-3 power MOSFET (http://www.infineon.com/cms/en/product/packages/PG-HSOF/PG-HSOF-8-3/)
mosfet hsof
0
25
4
Package_TO_SOT_SMD
Infineon_PG-HSOF-8-3_ThermalVias
HSOF-8-3 power MOSFET (http://www.infineon.com/cms/en/product/packages/PG-HSOF/PG-HSOF-8-3/)
mosfet hsof
0
64
4
Package_TO_SOT_SMD
Infineon_PG-TO-220-7Lead_TabPin8
Infineon PG-TO-220-7, Tab as Pin 8, see e.g. https://www.infineon.com/dgdl/Infineon-BTS50055-1TMC-DS-v01_00-EN.pdf?fileId=5546d4625a888733015aa9b0007235e9
Infineon PG-TO-220-7
0
12
8
Package_TO_SOT_SMD
LFPAK33
LFPAK33 SOT-1210 https://assets.nexperia.com/documents/outline-drawing/SOT1210.pdf
LFPAK33 SOT-1210
0
17
5
Package_TO_SOT_SMD
LFPAK56
LFPAK56 https://assets.nexperia.com/documents/outline-drawing/SOT669.pdf
LFPAK56 SOT-669 Power-SO8
0
18
5
Package_TO_SOT_SMD
Nexperia_CFP15_SOT-1289
Nexperia CFP15 (SOT-1289), https://assets.nexperia.com/documents/outline-drawing/SOT1289.pdf
SOT-1289 CFP15
0
8
3
Package_TO_SOT_SMD
OnSemi_ECH8
On Semiconductor ECH8, https://www.onsemi.com/pub/Collateral/318BF.PDF
ECH8 SOT28-FL SOT-28-FL
0
8
8
Package_TO_SOT_SMD
PQFN_8x8
Low Profile 8x8mm PQFN, Dual Cool 88, https://www.onsemi.com/pub/Collateral/FDMT80080DC-D.pdf
pqfn vdfn mosfet
0
9
3
Package_TO_SOT_SMD
PowerMacro_M234_NoHole
TO-50-4 Power Macro Package Style M234
TO-50-4 Power Macro Package Style M234
0
4
4
Package_TO_SOT_SMD
PowerMacro_M234_WithHole
TO-50-4 Power Macro Package Style M234
TO-50-4 Power Macro Package Style M234
0
4
4
Package_TO_SOT_SMD
Rohm_HRP7
Rohm HRP7 SMD package, http://rohmfs.rohm.com/en/techdata_basic/ic/package/hrp7_1-e.pdf, http://rohmfs.rohm.com/en/products/databook/datasheet/ic/motor/dc/bd621x-e.pdf
Rohm HRP7 SMD
0
69
7
Package_TO_SOT_SMD
SC-59
SC-59, https://lib.chipdip.ru/images/import_diod/original/SOT-23_SC-59.jpg
SC-59
0
3
3
Package_TO_SOT_SMD
SC-59_Handsoldering
SC-59, hand-soldering varaint, https://lib.chipdip.ru/images/import_diod/original/SOT-23_SC-59.jpg
SC-59 hand-soldering
0
3
3
Package_TO_SOT_SMD
SC-70-8
SC70-8
SC70-8
0
8
8
Package_TO_SOT_SMD
SC-70-8_Handsoldering
SC70-8, Handsoldering
SC70-8 Handsoldering
0
8
8
Package_TO_SOT_SMD
SC-82AA
SC-82AA
SC-82AA
0
4
4
Package_TO_SOT_SMD
SC-82AA_Handsoldering
SC-82AA
SC-82AA
0
4
4
Package_TO_SOT_SMD
SC-82AB
SC-82AB
SC-82AB
0
4
4
Package_TO_SOT_SMD
SC-82AB_Handsoldering
SC-82AB
SC-82AB
0
4
4
Package_TO_SOT_SMD
SOT-23
SOT, 3 Pin (https://www.jedec.org/system/files/docs/to-236h.pdf variant AB), generated with kicad-footprint-generator ipc_gullwing_generator.py
SOT TO_SOT_SMD
0
3
3
Package_TO_SOT_SMD
SOT-23-5
SOT, 5 Pin (https://www.jedec.org/sites/default/files/docs/Mo-178c.PDF variant AA), generated with kicad-footprint-generator ipc_gullwing_generator.py
SOT TO_SOT_SMD
0
5
5
Package_TO_SOT_SMD
SOT-23-5_HandSoldering
5-pin SOT23 package
SOT-23-5 hand-soldering
0
5
5
Package_TO_SOT_SMD
SOT-23-6
SOT, 6 Pin (https://www.jedec.org/sites/default/files/docs/Mo-178c.PDF variant AB), generated with kicad-footprint-generator ipc_gullwing_generator.py
SOT TO_SOT_SMD
0
6
6
Package_TO_SOT_SMD
SOT-23-6_Handsoldering
6-pin SOT-23 package, Handsoldering
SOT-23-6 Handsoldering
0
6
6
Package_TO_SOT_SMD
SOT-23-8
SOT, 8 Pin (https://www.jedec.org/sites/default/files/docs/Mo-178c.PDF variant BA), generated with kicad-footprint-generator ipc_gullwing_generator.py
SOT TO_SOT_SMD
0
8
8
Package_TO_SOT_SMD
SOT-23-8_Handsoldering
8-pin SOT-23 package, Handsoldering, http://www.analog.com/media/en/package-pcb-resources/package/pkg_pdf/sot-23rj/rj_8.pdf
SOT-23-8 Handsoldering
0
8
8
Package_TO_SOT_SMD
SOT-23W
SOT-23W http://www.allegromicro.com/~/media/Files/Datasheets/A112x-Datasheet.ashx?la=en&hash=7BC461E058CC246E0BAB62433B2F1ECA104CA9D3
SOT-23W
0
3
3
Package_TO_SOT_SMD
SOT-23W_Handsoldering
SOT-23W http://www.allegromicro.com/~/media/Files/Datasheets/A112x-Datasheet.ashx?la=en&hash=7BC461E058CC246E0BAB62433B2F1ECA104CA9D3
SOT-23W for handsoldering
0
3
3
Package_TO_SOT_SMD
SOT-23_Handsoldering
SOT-23, Handsoldering
SOT-23
0
3
3
Package_TO_SOT_SMD
SOT-89-3
SOT-89-3, http://ww1.microchip.com/downloads/en/DeviceDoc/3L_SOT-89_MB_C04-029C.pdf
SOT-89-3
0
3
3
Package_TO_SOT_SMD
SOT-89-3_Handsoldering
SOT-89-3 Handsoldering
SOT-89-3 Handsoldering
0
3
3
Package_TO_SOT_SMD
SOT-89-5
SOT-89-5, http://www.e-devices.ricoh.co.jp/en/products/product_power/pkg/sot-89-5.pdf
SOT-89-5
0
5
5
Package_TO_SOT_SMD
SOT-89-5_Handsoldering
SOT-89-5, http://www.e-devices.ricoh.co.jp/en/products/product_power/pkg/sot-89-5.pdf
SOT-89-5
0
5
5
Package_TO_SOT_SMD
SOT-143
SOT-143 https://www.nxp.com/docs/en/package-information/SOT143B.pdf
SOT-143
0
4
4
Package_TO_SOT_SMD
SOT-143R
SOT-143R, reverse pinning, https://www.nxp.com/docs/en/package-information/SOT143R.pdf
SOT-143R Reverse
0
4
4
Package_TO_SOT_SMD
SOT-143R_Handsoldering
SOT-143R, reverse pinning, Handsoldering, https://www.nxp.com/docs/en/package-information/SOT143R.pdf
SOT-143 Reverse Handsoldering
0
4
4
Package_TO_SOT_SMD
SOT-143_Handsoldering
SOT-143 Handsoldering https://www.nxp.com/docs/en/package-information/SOT143B.pdf
SOT-143 Handsoldering
0
4
4
Package_TO_SOT_SMD
SOT-223
module CMS SOT223 4 pins
CMS SOT
0
4
4
Package_TO_SOT_SMD
SOT-223-3_TabPin2
module CMS SOT223 4 pins
CMS SOT
0
4
3
Package_TO_SOT_SMD
SOT-223-5
module CMS SOT223 5 pins, http://ww1.microchip.com/downloads/en/DeviceDoc/51751a.pdf
CMS SOT
0
5
5
Package_TO_SOT_SMD
SOT-223-6
module CMS SOT223 6 pins, http://www.ti.com/lit/ds/symlink/tps737.pdf
CMS SOT
0
6
6
Package_TO_SOT_SMD
SOT-223-6_TabPin3
module CMS SOT223 6 pins, http://www.ti.com/lit/ds/symlink/tps737.pdf
CMS SOT
0
6
5
Package_TO_SOT_SMD
SOT-223-8
module CMS SOT223 8 pins, https://www.diodes.com/assets/Datasheets/ZXSBMR16PT8.pdf
CMS SOT
0
8
8
Package_TO_SOT_SMD
SOT-323_SC-70
SOT-323, SC-70
SOT-323 SC-70
0
3
3
Package_TO_SOT_SMD
SOT-323_SC-70_Handsoldering
SOT-323, SC-70 Handsoldering
SOT-323 SC-70 Handsoldering
0
3
3
Package_TO_SOT_SMD
SOT-343_SC-70-4
SOT-343, SC-70-4
SOT-343 SC-70-4
0
4
4
Package_TO_SOT_SMD
SOT-343_SC-70-4_Handsoldering
SOT-343, SC-70-4, Handsoldering
SOT-343 SC-70-4 Handsoldering
0
4
4
Package_TO_SOT_SMD
SOT-353_SC-70-5
SOT-353, SC-70-5
SOT-353 SC-70-5
0
5
5
Package_TO_SOT_SMD
SOT-353_SC-70-5_Handsoldering
SOT-353, SC-70-5, Handsoldering
SOT-353 SC-70-5 Handsoldering
0
5
5
Package_TO_SOT_SMD
SOT-363_SC-70-6
SOT-363, SC-70-6
SOT-363 SC-70-6
0
6
6
Package_TO_SOT_SMD
SOT-363_SC-70-6_Handsoldering
SOT-363, SC-70-6, Handsoldering
SOT-363 SC-70-6 Handsoldering
0
6
6
Package_TO_SOT_SMD
SOT-383F
8-pin SOT-383F, http://www.mouser.com/ds/2/80/CPDVR085V0C-HF-RevB-10783.pdf
SOT-383F
0
9
9
Package_TO_SOT_SMD
SOT-383FL
8-pin SOT-383FL package, http://www.onsemi.com/pub_link/Collateral/ENA2267-D.PDF
SOT-383FL
0
8
8
Package_TO_SOT_SMD
SOT-416
SOT-416, https://www.nxp.com/docs/en/package-information/SOT416.pdf
SOT-416
0
3
3
Package_TO_SOT_SMD
SOT-523
SOT523, https://www.diodes.com/assets/Package-Files/SOT523.pdf
SOT-523
0
3
3
Package_TO_SOT_SMD
SOT-543
SOT-543 4 lead surface package
SOT-543 SC-107A EMD4
0
4
4
Package_TO_SOT_SMD
SOT-553
SOT553
SOT-553
0
5
5
Package_TO_SOT_SMD
SOT-563
SOT563
SOT-563
0
6
6
Package_TO_SOT_SMD
SOT-665
SOT665
SOT-665
0
5
5
Package_TO_SOT_SMD
SOT-666
SOT666
SOT-666
0
6
6
Package_TO_SOT_SMD
SOT-723
http://toshiba.semicon-storage.com/info/docget.jsp?did=5879&prodName=RN1104MFV
sot 723
0
3
3
Package_TO_SOT_SMD
SOT-883
SOT-883, https://assets.nexperia.com/documents/outline-drawing/SOT883.pdf
SOT-883
0
3
3
Package_TO_SOT_SMD
SOT-886
SOT-886
SOT-886
0
6
6
Package_TO_SOT_SMD
SOT-963
SOT 963 6 pins package 1x0.8mm pitch 0.35mm
SOT 963 6 pins package 1x0.8mm pitch 0.35mm
0
6
6
Package_TO_SOT_SMD
SOT-1123
SOT-1123 small outline transistor (see http://www.onsemi.com/pub/Collateral/NST3906F3-D.PDF)
SOT-1123 transistor
0
3
3
Package_TO_SOT_SMD
SOT-1333-1
SOT-1333-1
SOT-1333-1
0
9
9
Package_TO_SOT_SMD
SOT-1334-1
SOT-1334-1
SOT-1334-1
0
14
14
Package_TO_SOT_SMD
SuperSOT-3
3-pin SuperSOT package https://www.fairchildsemi.com/package-drawings/MA/MA03B.pdf
SuperSOT-3 SSOT-3
0
3
3
Package_TO_SOT_SMD
SuperSOT-6
6-pin SuperSOT package http://www.mouser.com/ds/2/149/FMB5551-889214.pdf
SuperSOT-6 SSOT-6
0
6
6
Package_TO_SOT_SMD
SuperSOT-8
8-pin SuperSOT package, http://www.icbank.com/icbank_data/semi_package/ssot8_dim.pdf
SuperSOT-8 SSOT-8
0
8
8
Package_TO_SOT_SMD
TDSON-8-1
Power MOSFET package, TDSON-8-1, 5.15x5.9mm (https://www.infineon.com/cms/en/product/packages/PG-TDSON/PG-TDSON-8-1/)
tdson
0
14
5
Package_TO_SOT_SMD
TO-50-3_LongPad-NoHole_Housing
TO-50-3 Macro T Package Style M236
TO-50-3 Macro T Package Style M236
0
3
3
Package_TO_SOT_SMD
TO-50-3_LongPad-WithHole_Housing
TO-50-3 Macro T Package Style M236
TO-50-3 Macro T Package Style M236
0
3
3
Package_TO_SOT_SMD
TO-50-3_ShortPad-NoHole_Housing
TO-50-3 Macro T Package Style M236
TO-50-3 Macro T Package Style M236
0
3
3
Package_TO_SOT_SMD
TO-50-3_ShortPad-WithHole_Housing
TO-50-3 Macro T Package Style M236
TO-50-3 Macro T Package Style M236
0
3
3
Package_TO_SOT_SMD
TO-50-4_LongPad-NoHole_Housing
TO-50-4 Macro X Package Style M238
TO-50-4 Macro X Package Style M238
0
4
4
Package_TO_SOT_SMD
TO-50-4_LongPad-WithHole_Housing
TO-50-4 Macro X Package Style M238
TO-50-4 Macro X Package Style M238
0
4
4
Package_TO_SOT_SMD
TO-50-4_ShortPad-NoHole_Housing
TO-50-4 Macro X Package Style M238
TO-50-4 Macro X Package Style M238
0
4
4
Package_TO_SOT_SMD
TO-50-4_ShortPad-WithHole_Housing
TO-50-4 Macro X Package Style M238
TO-50-4 Macro X Package Style M238
0
4
4
Package_TO_SOT_SMD
TO-252-2
TO-252 / DPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO252/PG-TO252-3-1/
DPAK TO-252 DPAK-3 TO-252-3 SOT-428
0
7
3
Package_TO_SOT_SMD
TO-252-2_TabPin1
TO-252-2, tab to pin 1 https://www.wolfspeed.com/media/downloads/87/CSD01060.pdf
TO-252-2 diode
0
7
2
Package_TO_SOT_SMD
TO-252-3_TabPin2
TO-252 / DPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO252/PG-TO252-3-1/
DPAK TO-252 DPAK-3 TO-252-3 SOT-428
0
8
3
Package_TO_SOT_SMD
TO-252-3_TabPin4
TO-252 / DPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO252/PG-TO252-3-1/
DPAK TO-252 DPAK-3 TO-252-3 SOT-428
0
8
4
Package_TO_SOT_SMD
TO-252-4
TO-252 / DPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO252/PG-TO252-5-11/
DPAK TO-252 DPAK-5 TO-252-5
0
9
5
Package_TO_SOT_SMD
TO-252-5_TabPin3
TO-252 / DPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO252/PG-TO252-5-11/
DPAK TO-252 DPAK-5 TO-252-5
0
10
5
Package_TO_SOT_SMD
TO-252-5_TabPin6
TO-252 / DPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO252/PG-TO252-5-11/
DPAK TO-252 DPAK-5 TO-252-5
0
10
6
Package_TO_SOT_SMD
TO-263-2
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-3-1/
D2PAK DDPAK TO-263 D2PAK-3 TO-263-3 SOT-404
0
7
3
Package_TO_SOT_SMD
TO-263-2_TabPin1
TO-263 / D2PAK / DDPAK SMD package, tab to pin 1, https://www.wolfspeed.com/media/downloads/137/C3D06060G.pdf
D2PAK DDPAK TO-263 D2PAK-3 TO-263-3 SOT-404 diode
0
7
2
Package_TO_SOT_SMD
TO-263-3_TabPin2
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-3-1/
D2PAK DDPAK TO-263 D2PAK-3 TO-263-3 SOT-404
0
8
3
Package_TO_SOT_SMD
TO-263-3_TabPin4
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-3-1/
D2PAK DDPAK TO-263 D2PAK-3 TO-263-3 SOT-404
0
8
4
Package_TO_SOT_SMD
TO-263-4
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-5-1/
D2PAK DDPAK TO-263 D2PAK-5 TO-263-5 SOT-426
0
9
5
Package_TO_SOT_SMD
TO-263-5_TabPin3
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-5-1/
D2PAK DDPAK TO-263 D2PAK-5 TO-263-5 SOT-426
0
10
5
Package_TO_SOT_SMD
TO-263-5_TabPin6
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-5-1/
D2PAK DDPAK TO-263 D2PAK-5 TO-263-5 SOT-426
0
10
6
Package_TO_SOT_SMD
TO-263-6
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-7-1/
D2PAK DDPAK TO-263 D2PAK-7 TO-263-7 SOT-427
0
11
7
Package_TO_SOT_SMD
TO-263-7_TabPin4
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-7-1/
D2PAK DDPAK TO-263 D2PAK-7 TO-263-7 SOT-427
0
12
7
Package_TO_SOT_SMD
TO-263-7_TabPin8
TO-263 / D2PAK / DDPAK SMD package, http://www.infineon.com/cms/en/product/packages/PG-TO263/PG-TO263-7-1/
D2PAK DDPAK TO-263 D2PAK-7 TO-263-7 SOT-427
0
12
8
Package_TO_SOT_SMD
TO-263-9_TabPin5
TO-263 / D2PAK / DDPAK SMD package, http://www.ti.com/lit/ds/symlink/lm4755.pdf
D2PAK DDPAK TO-263 D2PAK-9 TO-263-9
0
14
9
Package_TO_SOT_SMD
TO-263-9_TabPin10
TO-263 / D2PAK / DDPAK SMD package, http://www.ti.com/lit/ds/symlink/lm4755.pdf
D2PAK DDPAK TO-263 D2PAK-9 TO-263-9
0
14
10
Package_TO_SOT_SMD
TO-268-2
TO-268/D3PAK SMD package, http://www.icbank.com/icbank_data/semi_package/to268aa_dim.pdf
D3PAK TO-268 D3PAK-3 TO-268-3
0
7
3
Package_TO_SOT_SMD
TO-269AA
SMD package TO-269AA (e.g. diode bridge), see http://www.vishay.com/docs/88854/padlayouts.pdf
TO-269AA MBS diode bridge
0
4
4
Package_TO_SOT_SMD
TO-277A
Thermal enhanced ultra thin SMD package; 3 leads; body: 4.3x6.1x0.43mm, https://www.vishay.com/docs/95570/to-277asmpc.pdf
TO-277A SMPC
0
12
3
Package_TO_SOT_SMD
TO-277B
TO-227B https://media.digikey.com/pdf/Data%20Sheets/Littelfuse%20PDFs/DST2050S.pdf
TO-277B
0
9
3
Package_TO_SOT_SMD
TSOT-23
3-pin TSOT23 package, http://www.analog.com.tw/pdf/All_In_One.pdf
TSOT-23
0
3
3
Package_TO_SOT_SMD
TSOT-23-5
TSOT, 5 Pin (https://www.jedec.org/sites/default/files/docs/MO-193D.pdf variant AB), generated with kicad-footprint-generator ipc_gullwing_generator.py
TSOT TO_SOT_SMD
0
5
5
Package_TO_SOT_SMD
TSOT-23-5_HandSoldering
5-pin TSOT23 package, http://cds.linear.com/docs/en/packaging/SOT_5_05-08-1635.pdf
TSOT-23-5 Hand-soldering
0
5
5
Package_TO_SOT_SMD
TSOT-23-6
TSOT, 6 Pin (https://www.jedec.org/sites/default/files/docs/MO-193D.pdf variant AA), generated with kicad-footprint-generator ipc_gullwing_generator.py
TSOT TO_SOT_SMD
0
6
6
Package_TO_SOT_SMD
TSOT-23-6_HandSoldering
6-pin TSOT23 package, http://cds.linear.com/docs/en/packaging/SOT_6_05-08-1636.pdf
TSOT-23-6 MK06A TSOT-6 Hand-soldering
0
6
6
Package_TO_SOT_SMD
TSOT-23-8
TSOT, 8 Pin (https://www.jedec.org/sites/default/files/docs/MO-193D.pdf variant BA), generated with kicad-footprint-generator ipc_gullwing_generator.py
TSOT TO_SOT_SMD
0
8
8
Package_TO_SOT_SMD
TSOT-23-8_HandSoldering
8-pin TSOT23 package, http://cds.linear.com/docs/en/packaging/SOT_8_05-08-1637.pdf
TSOT-23-8 Hand-soldering
0
8
8
Package_TO_SOT_SMD
TSOT-23_HandSoldering
5-pin TSOT23 package, http://cds.linear.com/docs/en/packaging/SOT_5_05-08-1635.pdf
TSOT-23 Hand-soldering
0
3
3
Package_TO_SOT_SMD
Texas_DRT-3
Texas Instrument DRT-3 1x0.8mm Pitch 0.7mm http://www.ti.com/lit/ds/symlink/tpd2eusb30.pdf
DRT-3 1x0.8mm Pitch 0.7mm
0
3
3
Package_TO_SOT_SMD
Texas_NDQ
Texas Instruments, NDQ, 5 pin (https://www.ti.com/lit/ml/mmsf022/mmsf022.pdf)
ti pfm dap
0
6
6
Package_TO_SOT_SMD
Texas_NDY0011A
TO-PMOD-11 11-pin switching regulator package, http://www.ti.com/lit/ml/mmsf025/mmsf025.pdf
Texas TO-PMOD NDY00011A
0
12
12
Package_TO_SOT_SMD
Texas_R-PDSO-G6
R-PDSO-G6, http://www.ti.com/lit/ds/slis144b/slis144b.pdf
R-PDSO-G6 SC-70-6
0
6
6
Package_TO_SOT_SMD
VSOF5
VSOF5
VSOF5
0
5
5
Package_TO_SOT_SMD
Vishay_PowerPAK_SC70-6L_Dual
Vishay PowerPAK SC70 dual transistor package http://www.vishay.com/docs/70487/70487.pdf
powerpak sc70 sc-70 dual
0
8
6
Package_TO_SOT_SMD
Vishay_PowerPAK_SC70-6L_Single
Vishay PowerPAK SC70 single transistor package http://www.vishay.com/docs/70486/70486.pdf
powerpak sc70 sc-70
0
6
3

View File

@@ -139,7 +139,7 @@ static errcodes eswg(CAN_message *msg){
} }
uint32_t curval = get_ab_esw(); uint32_t curval = get_ab_esw();
if(no > INMAX) MSGP_SET_U32(msg, curval); if(no > INMAX) MSGP_SET_U32(msg, curval);
else MSGP_SET_U32(msg, (curval & (1<<no)) ? 0 : 1); else MSGP_SET_U32(msg, (curval & (1<<no)) ? 1 : 0);
FIXDL(msg); FIXDL(msg);
return ERR_OK; return ERR_OK;
} }

Binary file not shown.

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 14.0.1, 2024-09-27T15:44:53. --> <!-- Written by QtCreator 18.0.0, 2025-11-06T11:01:40. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>
@@ -13,8 +13,8 @@
<data> <data>
<variable>ProjectExplorer.Project.EditorSettings</variable> <variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap"> <valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value> <value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value> <value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0"> <valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value> <value type="QString" key="language">Cpp</value>
@@ -33,6 +33,7 @@
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value> <value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value> <value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value> <value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value> <value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value> <value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value> <value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
@@ -85,12 +86,14 @@
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/> <valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value> <value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap> </valuemap>
<value type="int" key="RcSync">0</value>
</valuemap> </valuemap>
</data> </data>
<data> <data>
<variable>ProjectExplorer.Project.Target.0</variable> <variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap"> <valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value> <value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
@@ -108,8 +111,8 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Сборка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap> </valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
@@ -121,8 +124,8 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Очистка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap> </valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value> <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
@@ -132,13 +135,46 @@
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/> <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value> <value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0"> <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Развёртывание</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Развёртывание</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap> </valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value> <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
@@ -160,6 +196,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value> <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value> <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap> </valuemap>
@@ -170,10 +207,6 @@
<variable>ProjectExplorer.Project.TargetCount</variable> <variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value> <value type="qlonglong">1</value>
</data> </data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data> <data>
<variable>Version</variable> <variable>Version</variable>
<value type="int">22</value> <value type="int">22</value>

View File

@@ -227,7 +227,7 @@ static int readpins(uint8_t Nch, const pin_t *pins, uint8_t max){
int p = gpin(i); int p = gpin(i);
if(p == 1){ if(p == 1){
//usart_send("pin"); usart_send(u2str(i)); usart_send("=1\n"); //usart_send("pin"); usart_send(u2str(i)); usart_send("=1\n");
val |= p; val |= 1;
} }
} }
//usart_send("readpins, val="); usart_send(i2str(val)); newline(); //usart_send("readpins, val="); usart_send(i2str(val)); newline();
@@ -274,6 +274,7 @@ void proc_esw(){
CAN_message msg = {.ID = the_conf.CANIDin, .length = 8, .data = {0}}; CAN_message msg = {.ID = the_conf.CANIDin, .length = 8, .data = {0}};
MSG_SET_CMD(msg, CMD_GETESW); MSG_SET_CMD(msg, CMD_GETESW);
MSG_SET_U32(msg, ESW_ab_values); MSG_SET_U32(msg, ESW_ab_values);
MSG_SET_PARNO(msg, NO_PARNO); // all ESW
uint32_t Tstart = Tms; uint32_t Tstart = Tms;
while(Tms - Tstart < SEND_TIMEOUT_MS){ while(Tms - Tstart < SEND_TIMEOUT_MS){
IWDG->KR = IWDG_REFRESH; IWDG->KR = IWDG_REFRESH;

View File

@@ -1,14 +1,18 @@
(kicad_sch (kicad_sch
(version 20231120) (version 20250114)
(generator "eeschema") (generator "eeschema")
(generator_version "8.0") (generator_version "9.0")
(uuid "f7e482ac-f727-4de9-9906-b985e4eb38f7") (uuid "f7e482ac-f727-4de9-9906-b985e4eb38f7")
(paper "A4") (paper "A4")
(lib_symbols (lib_symbols
(symbol "Device:LED" (symbol "Device:LED"
(pin_numbers hide) (pin_numbers
(hide yes)
)
(pin_names (pin_names
(offset 1.016) hide) (offset 1.016)
(hide yes)
)
(exclude_from_sim no) (exclude_from_sim no)
(in_bom yes) (in_bom yes)
(on_board yes) (on_board yes)
@@ -76,10 +80,22 @@
(symbol "LED_0_1" (symbol "LED_0_1"
(polyline (polyline
(pts (pts
(xy -1.27 -1.27) (xy -1.27 1.27) (xy -3.048 -0.762) (xy -4.572 -2.286) (xy -3.81 -2.286) (xy -4.572 -2.286) (xy -4.572 -1.524)
) )
(stroke (stroke
(width 0.254) (width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 -0.762) (xy -3.302 -2.286) (xy -2.54 -2.286) (xy -3.302 -2.286) (xy -3.302 -1.524)
)
(stroke
(width 0)
(type default) (type default)
) )
(fill (fill
@@ -100,7 +116,7 @@
) )
(polyline (polyline
(pts (pts
(xy 1.27 -1.27) (xy 1.27 1.27) (xy -1.27 0) (xy 1.27 -1.27) (xy -1.27 -1.27) (xy -1.27 1.27)
) )
(stroke (stroke
(width 0.254) (width 0.254)
@@ -112,22 +128,10 @@
) )
(polyline (polyline
(pts (pts
(xy -3.048 -0.762) (xy -4.572 -2.286) (xy -3.81 -2.286) (xy -4.572 -2.286) (xy -4.572 -1.524) (xy 1.27 -1.27) (xy 1.27 1.27) (xy -1.27 0) (xy 1.27 -1.27)
) )
(stroke (stroke
(width 0) (width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 -0.762) (xy -3.302 -2.286) (xy -2.54 -2.286) (xy -3.302 -2.286) (xy -3.302 -1.524)
)
(stroke
(width 0)
(type default) (type default)
) )
(fill (fill
@@ -173,9 +177,12 @@
) )
) )
) )
(embedded_fonts no)
) )
(symbol "Device:R" (symbol "Device:R"
(pin_numbers hide) (pin_numbers
(hide yes)
)
(pin_names (pin_names
(offset 0) (offset 0)
) )
@@ -294,6 +301,7 @@
) )
) )
) )
(embedded_fonts no)
) )
(symbol "Isolator:EL817" (symbol "Isolator:EL817"
(pin_names (pin_names
@@ -380,6 +388,18 @@
(type background) (type background)
) )
) )
(polyline
(pts
(xy -5.08 2.54) (xy -2.54 2.54) (xy -2.54 -0.635)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline (polyline
(pts (pts
(xy -3.175 -0.635) (xy -1.905 -0.635) (xy -3.175 -0.635) (xy -1.905 -0.635)
@@ -392,6 +412,66 @@
(type none) (type none)
) )
) )
(polyline
(pts
(xy -2.54 -0.635) (xy -2.54 -2.54) (xy -5.08 -2.54)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -2.54 -0.635) (xy -3.175 0.635) (xy -1.905 0.635) (xy -2.54 -0.635)
)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.508 0.508) (xy 0.762 0.508) (xy 0.381 0.381) (xy 0.381 0.635) (xy 0.762 0.508)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.508 -0.508) (xy 0.762 -0.508) (xy 0.381 -0.635) (xy 0.381 -0.381) (xy 0.762 -0.508)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 2.54 1.905) (xy 2.54 -1.905) (xy 2.54 -1.905)
)
(stroke
(width 0.508)
(type default)
)
(fill
(type none)
)
)
(polyline (polyline
(pts (pts
(xy 2.54 0.635) (xy 4.445 2.54) (xy 2.54 0.635) (xy 4.445 2.54)
@@ -404,6 +484,30 @@
(type none) (type none)
) )
) )
(polyline
(pts
(xy 3.048 -1.651) (xy 3.556 -1.143) (xy 4.064 -2.159) (xy 3.048 -1.651) (xy 3.048 -1.651)
)
(stroke
(width 0)
(type default)
)
(fill
(type outline)
)
)
(polyline
(pts
(xy 4.445 2.54) (xy 5.08 2.54)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline (polyline
(pts (pts
(xy 4.445 -2.54) (xy 2.54 -0.635) (xy 4.445 -2.54) (xy 2.54 -0.635)
@@ -428,102 +532,6 @@
(type none) (type none)
) )
) )
(polyline
(pts
(xy 4.445 2.54) (xy 5.08 2.54)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -5.08 2.54) (xy -2.54 2.54) (xy -2.54 -0.635)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -2.54 -0.635) (xy -2.54 -2.54) (xy -5.08 -2.54)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 2.54 1.905) (xy 2.54 -1.905) (xy 2.54 -1.905)
)
(stroke
(width 0.508)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -2.54 -0.635) (xy -3.175 0.635) (xy -1.905 0.635) (xy -2.54 -0.635)
)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.508 -0.508) (xy 0.762 -0.508) (xy 0.381 -0.635) (xy 0.381 -0.381) (xy 0.762 -0.508)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.508 0.508) (xy 0.762 0.508) (xy 0.381 0.381) (xy 0.381 0.635) (xy 0.762 0.508)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 3.048 -1.651) (xy 3.556 -1.143) (xy 4.064 -2.159) (xy 3.048 -1.651) (xy 3.048 -1.651)
)
(stroke
(width 0)
(type default)
)
(fill
(type outline)
)
)
) )
(symbol "EL817_1_1" (symbol "EL817_1_1"
(pin passive line (pin passive line
@@ -562,24 +570,6 @@
) )
) )
) )
(pin passive line
(at 7.62 -2.54 180)
(length 2.54)
(name "~"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line (pin passive line
(at 7.62 2.54 180) (at 7.62 2.54 180)
(length 2.54) (length 2.54)
@@ -598,13 +588,36 @@
) )
) )
) )
(pin passive line
(at 7.62 -2.54 180)
(length 2.54)
(name "~"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
) )
(embedded_fonts no)
) )
(symbol "power:+3.3V" (symbol "power:+3.3V"
(power) (power)
(pin_numbers hide) (pin_numbers
(hide yes)
)
(pin_names (pin_names
(offset 0) hide) (offset 0)
(hide yes)
)
(exclude_from_sim no) (exclude_from_sim no)
(in_bom yes) (in_bom yes)
(on_board yes) (on_board yes)
@@ -676,7 +689,7 @@
) )
(polyline (polyline
(pts (pts
(xy 0 0) (xy 0 2.54) (xy 0 2.54) (xy 0.762 1.27)
) )
(stroke (stroke
(width 0) (width 0)
@@ -688,7 +701,7 @@
) )
(polyline (polyline
(pts (pts
(xy 0 2.54) (xy 0.762 1.27) (xy 0 0) (xy 0 2.54)
) )
(stroke (stroke
(width 0) (width 0)
@@ -719,12 +732,17 @@
) )
) )
) )
(embedded_fonts no)
) )
(symbol "power:GND" (symbol "power:GND"
(power) (power)
(pin_numbers hide) (pin_numbers
(hide yes)
)
(pin_names (pin_names
(offset 0) hide) (offset 0)
(hide yes)
)
(exclude_from_sim no) (exclude_from_sim no)
(in_bom yes) (in_bom yes)
(on_board yes) (on_board yes)
@@ -815,8 +833,19 @@
) )
) )
) )
(embedded_fonts no)
) )
) )
(text "Absent on channels 6÷15"
(exclude_from_sim no)
(at 90.17 31.75 0)
(effects
(font
(size 1.27 1.27)
)
)
(uuid "d2d03f2b-b161-4da5-a446-e6e3ee8f386a")
)
(junction (junction
(at 69.85 47.625) (at 69.85 47.625)
(diameter 0) (diameter 0)
@@ -925,20 +954,9 @@
) )
(uuid "d5f32554-f144-4e79-a00e-41f44da65237") (uuid "d5f32554-f144-4e79-a00e-41f44da65237")
) )
(text "Absent on channels 6÷15"
(exclude_from_sim no)
(at 90.17 31.75 0)
(effects
(font
(size 1.27 1.27)
)
)
(uuid "d2d03f2b-b161-4da5-a446-e6e3ee8f386a")
)
(hierarchical_label "Xn" (hierarchical_label "Xn"
(shape output) (shape output)
(at 75.565 47.5335 0) (at 75.565 47.5335 0)
(fields_autoplaced yes)
(effects (effects
(font (font
(size 1.27 1.27) (size 1.27 1.27)
@@ -950,7 +968,6 @@
(hierarchical_label "Xin" (hierarchical_label "Xin"
(shape input) (shape input)
(at 47.625 40.005 180) (at 47.625 40.005 180)
(fields_autoplaced yes)
(effects (effects
(font (font
(size 1.27 1.27) (size 1.27 1.27)
@@ -962,7 +979,6 @@
(hierarchical_label "Xcom" (hierarchical_label "Xcom"
(shape input) (shape input)
(at 49.53 55.245 180) (at 49.53 55.245 180)
(fields_autoplaced yes)
(effects (effects
(font (font
(size 1.27 1.27) (size 1.27 1.27)
@@ -980,7 +996,7 @@
(on_board yes) (on_board yes)
(dnp no) (dnp no)
(uuid "14323fc5-37e6-41cb-a8aa-ede36cbb5ab8") (uuid "14323fc5-37e6-41cb-a8aa-ede36cbb5ab8")
(property "Reference" "R39" (property "Reference" "R5"
(at 66.04 33.655 0) (at 66.04 33.655 0)
(effects (effects
(font (font
@@ -1099,7 +1115,7 @@
(on_board yes) (on_board yes)
(dnp no) (dnp no)
(uuid "19951d65-483e-40c0-a8c6-ec4b281351e4") (uuid "19951d65-483e-40c0-a8c6-ec4b281351e4")
(property "Reference" "R69" (property "Reference" "R58"
(at 46.355 50.8 0) (at 46.355 50.8 0)
(effects (effects
(font (font
@@ -1218,7 +1234,7 @@
(on_board yes) (on_board yes)
(dnp no) (dnp no)
(uuid "1b91600b-0a9e-40dc-bcd2-b50f5c62c02c") (uuid "1b91600b-0a9e-40dc-bcd2-b50f5c62c02c")
(property "Reference" "R38" (property "Reference" "R7"
(at 46.355 43.815 0) (at 46.355 43.815 0)
(effects (effects
(font (font
@@ -1338,7 +1354,7 @@
(dnp no) (dnp no)
(fields_autoplaced yes) (fields_autoplaced yes)
(uuid "2e0cec5b-0b81-42ef-b00e-3a2e0cbd2428") (uuid "2e0cec5b-0b81-42ef-b00e-3a2e0cbd2428")
(property "Reference" "#PWR038" (property "Reference" "#PWR0106"
(at 66.675 59.055 0) (at 66.675 59.055 0)
(effects (effects
(font (font
@@ -1456,7 +1472,7 @@
(dnp no) (dnp no)
(fields_autoplaced yes) (fields_autoplaced yes)
(uuid "56a5cef8-e556-4693-b820-14c50d49a3f0") (uuid "56a5cef8-e556-4693-b820-14c50d49a3f0")
(property "Reference" "#PWR039" (property "Reference" "#PWR017"
(at 69.85 33.655 0) (at 69.85 33.655 0)
(effects (effects
(font (font
@@ -1574,7 +1590,7 @@
(dnp no) (dnp no)
(fields_autoplaced yes) (fields_autoplaced yes)
(uuid "95ba6c0a-2e27-4538-9ddf-4ec443c24047") (uuid "95ba6c0a-2e27-4538-9ddf-4ec443c24047")
(property "Reference" "U16" (property "Reference" "U2"
(at 59.055 42.2105 0) (at 59.055 42.2105 0)
(effects (effects
(font (font
@@ -1702,7 +1718,7 @@
(on_board yes) (on_board yes)
(dnp no) (dnp no)
(uuid "daa714d5-bcb6-44ab-9f40-17688d45e9a0") (uuid "daa714d5-bcb6-44ab-9f40-17688d45e9a0")
(property "Reference" "R40" (property "Reference" "R6"
(at 78.74 33.655 0) (at 78.74 33.655 0)
(effects (effects
(font (font
@@ -1821,7 +1837,7 @@
(on_board yes) (on_board yes)
(dnp no) (dnp no)
(uuid "daf48ae0-ac7f-46a0-a638-f3504bcc658c") (uuid "daf48ae0-ac7f-46a0-a638-f3504bcc658c")
(property "Reference" "D14" (property "Reference" "D3"
(at 66.675 41.275 90) (at 66.675 41.275 90)
(effects (effects
(font (font

View File

@@ -1,14 +1,18 @@
(kicad_sch (kicad_sch
(version 20231120) (version 20250114)
(generator "eeschema") (generator "eeschema")
(generator_version "8.0") (generator_version "9.0")
(uuid "074a896d-9a24-48e3-9020-953a028f0cd7") (uuid "074a896d-9a24-48e3-9020-953a028f0cd7")
(paper "A4") (paper "A4")
(lib_symbols (lib_symbols
(symbol "Device:LED" (symbol "Device:LED"
(pin_numbers hide) (pin_numbers
(hide yes)
)
(pin_names (pin_names
(offset 1.016) hide) (offset 1.016)
(hide yes)
)
(exclude_from_sim no) (exclude_from_sim no)
(in_bom yes) (in_bom yes)
(on_board yes) (on_board yes)
@@ -76,10 +80,22 @@
(symbol "LED_0_1" (symbol "LED_0_1"
(polyline (polyline
(pts (pts
(xy -1.27 -1.27) (xy -1.27 1.27) (xy -3.048 -0.762) (xy -4.572 -2.286) (xy -3.81 -2.286) (xy -4.572 -2.286) (xy -4.572 -1.524)
) )
(stroke (stroke
(width 0.254) (width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 -0.762) (xy -3.302 -2.286) (xy -2.54 -2.286) (xy -3.302 -2.286) (xy -3.302 -1.524)
)
(stroke
(width 0)
(type default) (type default)
) )
(fill (fill
@@ -100,7 +116,7 @@
) )
(polyline (polyline
(pts (pts
(xy 1.27 -1.27) (xy 1.27 1.27) (xy -1.27 0) (xy 1.27 -1.27) (xy -1.27 -1.27) (xy -1.27 1.27)
) )
(stroke (stroke
(width 0.254) (width 0.254)
@@ -112,22 +128,10 @@
) )
(polyline (polyline
(pts (pts
(xy -3.048 -0.762) (xy -4.572 -2.286) (xy -3.81 -2.286) (xy -4.572 -2.286) (xy -4.572 -1.524) (xy 1.27 -1.27) (xy 1.27 1.27) (xy -1.27 0) (xy 1.27 -1.27)
) )
(stroke (stroke
(width 0) (width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 -0.762) (xy -3.302 -2.286) (xy -2.54 -2.286) (xy -3.302 -2.286) (xy -3.302 -1.524)
)
(stroke
(width 0)
(type default) (type default)
) )
(fill (fill
@@ -173,9 +177,12 @@
) )
) )
) )
(embedded_fonts no)
) )
(symbol "Device:R" (symbol "Device:R"
(pin_numbers hide) (pin_numbers
(hide yes)
)
(pin_names (pin_names
(offset 0) (offset 0)
) )
@@ -294,6 +301,7 @@
) )
) )
) )
(embedded_fonts no)
) )
(symbol "Relay:G5NB" (symbol "Relay:G5NB"
(exclude_from_sim no) (exclude_from_sim no)
@@ -415,7 +423,7 @@
) )
(polyline (polyline
(pts (pts
(xy -5.08 -5.08) (xy -5.08 -1.905) (xy -5.08 5.08) (xy -5.08 1.905)
) )
(stroke (stroke
(width 0) (width 0)
@@ -427,7 +435,7 @@
) )
(polyline (polyline
(pts (pts
(xy -5.08 5.08) (xy -5.08 1.905) (xy -5.08 -5.08) (xy -5.08 -1.905)
) )
(stroke (stroke
(width 0) (width 0)
@@ -523,42 +531,6 @@
) )
) )
(symbol "G5NB_1_1" (symbol "G5NB_1_1"
(pin passive line
(at 5.08 -7.62 90)
(length 2.54)
(name "~"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "13"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line
(at 7.62 7.62 270)
(length 2.54)
(name "~"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "14"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line (pin passive line
(at -5.08 7.62 270) (at -5.08 7.62 270)
(length 2.54) (length 2.54)
@@ -595,7 +567,44 @@
) )
) )
) )
(pin passive line
(at 5.08 -7.62 90)
(length 2.54)
(name "~"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "13"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line
(at 7.62 7.62 270)
(length 2.54)
(name "~"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "14"
(effects
(font
(size 1.27 1.27)
)
)
)
)
) )
(embedded_fonts no)
) )
) )
(junction (junction
@@ -687,7 +696,6 @@
(hierarchical_label "Vcc" (hierarchical_label "Vcc"
(shape input) (shape input)
(at 60.325 41.91 180) (at 60.325 41.91 180)
(fields_autoplaced yes)
(effects (effects
(font (font
(size 1.27 1.27) (size 1.27 1.27)
@@ -699,7 +707,6 @@
(hierarchical_label "On" (hierarchical_label "On"
(shape input) (shape input)
(at 60.3276 57.15 180) (at 60.3276 57.15 180)
(fields_autoplaced yes)
(effects (effects
(font (font
(size 1.27 1.27) (size 1.27 1.27)
@@ -711,7 +718,6 @@
(hierarchical_label "C0" (hierarchical_label "C0"
(shape output) (shape output)
(at 106.68 41.91 0) (at 106.68 41.91 0)
(fields_autoplaced yes)
(effects (effects
(font (font
(size 1.27 1.27) (size 1.27 1.27)
@@ -723,7 +729,6 @@
(hierarchical_label "C1" (hierarchical_label "C1"
(shape output) (shape output)
(at 106.68 57.15 0) (at 106.68 57.15 0)
(fields_autoplaced yes)
(effects (effects
(font (font
(size 1.27 1.27) (size 1.27 1.27)
@@ -741,7 +746,7 @@
(on_board yes) (on_board yes)
(dnp no) (dnp no)
(uuid "62146abf-e230-4aac-bb36-e62083f815a6") (uuid "62146abf-e230-4aac-bb36-e62083f815a6")
(property "Reference" "R75" (property "Reference" "R3"
(at 64.135 46.99 90) (at 64.135 46.99 90)
(effects (effects
(font (font
@@ -845,7 +850,7 @@
(dnp no) (dnp no)
(fields_autoplaced yes) (fields_autoplaced yes)
(uuid "9d61be36-460d-4a49-aff0-87a033aa1c97") (uuid "9d61be36-460d-4a49-aff0-87a033aa1c97")
(property "Reference" "K6" (property "Reference" "K1"
(at 99.187 48.3178 0) (at 99.187 48.3178 0)
(effects (effects
(font (font
@@ -958,7 +963,7 @@
(on_board yes) (on_board yes)
(dnp no) (dnp no)
(uuid "dab08edf-4862-4de9-abff-3bc791b4e2e4") (uuid "dab08edf-4862-4de9-abff-3bc791b4e2e4")
(property "Reference" "D30" (property "Reference" "D25"
(at 71.755 52.07 0) (at 71.755 52.07 0)
(effects (effects
(font (font

View File

@@ -10,6 +10,7 @@
"opacity": { "opacity": {
"images": 0.6, "images": 0.6,
"pads": 1.0, "pads": 1.0,
"shapes": 1.0,
"tracks": 1.0, "tracks": 1.0,
"vias": 1.0, "vias": 1.0,
"zones": 0.6 "zones": 0.6
@@ -28,43 +29,27 @@
"zones": true "zones": true
}, },
"visible_items": [ "visible_items": [
0, "vias",
1, "footprint_text",
2, "footprint_anchors",
3, "ratsnest",
4, "grid",
5, "footprints_front",
8, "footprints_back",
9, "footprint_values",
10, "footprint_references",
11, "tracks",
12, "drc_errors",
13, "drawing_sheet",
15, "bitmaps",
16, "pads",
17, "zones",
18, "drc_warnings",
19, "locked_item_shadows",
20, "conflict_shadows",
21, "shapes"
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36,
39,
40
], ],
"visible_layers": "fffffff_ffffffff", "visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 0 "zone_display_mode": 0
}, },
"git": { "git": {
@@ -75,9 +60,72 @@
}, },
"meta": { "meta": {
"filename": "fx3u_24mr.kicad_prl", "filename": "fx3u_24mr.kicad_prl",
"version": 3 "version": 5
}, },
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"col_widths": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": 0
},
"open_jobsets": [],
"project": { "project": {
"files": [] "files": []
},
"schematic": {
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"symbols": true,
"text": true,
"wires": true
}
} }
} }

View File

@@ -63,16 +63,19 @@
"copper_edge_clearance": "error", "copper_edge_clearance": "error",
"copper_sliver": "warning", "copper_sliver": "warning",
"courtyards_overlap": "error", "courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error", "diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error", "diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error", "drill_out_of_range": "error",
"duplicate_footprints": "warning", "duplicate_footprints": "warning",
"extra_footprint": "warning", "extra_footprint": "warning",
"footprint": "error", "footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning", "footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore", "footprint_type_mismatch": "ignore",
"hole_clearance": "error", "hole_clearance": "error",
"hole_near_hole": "error", "hole_near_hole": "error",
"hole_to_hole": "error",
"holes_co_located": "warning", "holes_co_located": "warning",
"invalid_outline": "error", "invalid_outline": "error",
"isolated_copper": "warning", "isolated_copper": "warning",
@@ -83,9 +86,11 @@
"lib_footprint_mismatch": "warning", "lib_footprint_mismatch": "warning",
"malformed_courtyard": "error", "malformed_courtyard": "error",
"microvia_drill_out_of_range": "error", "microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore", "missing_courtyard": "ignore",
"missing_footprint": "warning", "missing_footprint": "warning",
"net_conflict": "warning", "net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore", "npth_inside_courtyard": "ignore",
"padstack": "warning", "padstack": "warning",
"pth_inside_courtyard": "ignore", "pth_inside_courtyard": "ignore",
@@ -97,10 +102,13 @@
"solder_mask_bridge": "error", "solder_mask_bridge": "error",
"starved_thermal": "error", "starved_thermal": "error",
"text_height": "warning", "text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning", "text_thickness": "warning",
"through_hole_pad_without_hole": "error", "through_hole_pad_without_hole": "error",
"too_many_vias": "error", "too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning", "track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error", "track_width": "error",
"tracks_crossing": "error", "tracks_crossing": "error",
"unconnected_items": "error", "unconnected_items": "error",
@@ -113,6 +121,7 @@
"min_clearance": 0.0, "min_clearance": 0.0,
"min_connection": 0.0, "min_connection": 0.0,
"min_copper_edge_clearance": 0.5, "min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25, "min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25, "min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2, "min_microvia_diameter": 0.2,
@@ -130,10 +139,11 @@
}, },
"teardrop_options": [ "teardrop_options": [
{ {
"td_onpadsmd": true, "td_onpthpad": true,
"td_onroundshapesonly": false, "td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false, "td_ontrackend": false,
"td_onviapad": true "td_onvia": true
} }
], ],
"teardrop_parameters": [ "teardrop_parameters": [
@@ -208,6 +218,7 @@
"mfg": "", "mfg": "",
"mpn": "" "mpn": ""
}, },
"layer_pairs": [],
"layer_presets": [], "layer_presets": [],
"viewports": [] "viewports": []
}, },
@@ -402,10 +413,15 @@
"duplicate_sheet_names": "error", "duplicate_sheet_names": "error",
"endpoint_off_grid": "warning", "endpoint_off_grid": "warning",
"extra_units": "error", "extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning", "global_label_dangling": "warning",
"hier_label_mismatch": "error", "hier_label_mismatch": "error",
"label_dangling": "error", "label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning", "lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning", "missing_bidi_pin": "warning",
"missing_input_pin": "warning", "missing_input_pin": "warning",
"missing_power_pin": "error", "missing_power_pin": "error",
@@ -418,9 +434,15 @@
"pin_not_driven": "error", "pin_not_driven": "error",
"pin_to_pin": "error", "pin_to_pin": "error",
"power_pin_not_driven": "error", "power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning", "similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore", "simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error", "unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error", "unit_value_mismatch": "error",
"unresolved_variable": "error", "unresolved_variable": "error",
"wire_dangling": "error" "wire_dangling": "error"
@@ -432,7 +454,7 @@
}, },
"meta": { "meta": {
"filename": "fx3u_24mr.kicad_pro", "filename": "fx3u_24mr.kicad_pro",
"version": 1 "version": 3
}, },
"net_settings": { "net_settings": {
"classes": [ "classes": [
@@ -447,6 +469,7 @@
"microvia_drill": 0.1, "microvia_drill": 0.1,
"name": "Default", "name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)", "pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)", "schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2, "track_width": 0.2,
"via_diameter": 0.6, "via_diameter": 0.6,
@@ -455,7 +478,7 @@
} }
], ],
"meta": { "meta": {
"version": 3 "version": 4
}, },
"net_colors": null, "net_colors": null,
"netclass_assignments": null, "netclass_assignments": null,
@@ -531,6 +554,7 @@
], ],
"filter_string": "", "filter_string": "",
"group_symbols": true, "group_symbols": true,
"include_excluded_from_bom": false,
"name": "Grouped By Value", "name": "Grouped By Value",
"sort_asc": true, "sort_asc": true,
"sort_field": "Обозначение" "sort_field": "Обозначение"
@@ -565,6 +589,7 @@
"net_format_name": "", "net_format_name": "",
"page_layout_descr_file": "", "page_layout_descr_file": "",
"plot_directory": "", "plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false, "spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"", "spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true, "spice_model_current_sheet_as_root": true,
@@ -577,7 +602,7 @@
"sheets": [ "sheets": [
[ [
"b583aae5-8eb0-4221-b485-3f768bd7f89b", "b583aae5-8eb0-4221-b485-3f768bd7f89b",
"Корневой лист" "Root"
], ],
[ [
"f287e225-c1fa-4c9b-bbf3-2592c44400ac", "f287e225-c1fa-4c9b-bbf3-2592c44400ac",

Some files were not shown because too many files have changed in this diff Show More