mirror of
https://github.com/eddyem/stm32samples.git
synced 2026-03-20 08:40:57 +03:00
Compare commits
33 Commits
2fa218f695
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45559545ef | ||
|
|
e2a20a046a | ||
|
|
c80f0884f5 | ||
|
|
165780bef9 | ||
|
|
03b05051aa | ||
|
|
61262435a8 | ||
|
|
27eb723d80 | ||
|
|
57a44f1c66 | ||
|
|
65c9ae34bc | ||
|
|
14f544374a | ||
|
|
b44fd9e91f | ||
|
|
3802f7d2cb | ||
|
|
8e78a12f06 | ||
|
|
9713b10cd4 | ||
|
|
d39682b143 | ||
|
|
fb8b93b0fe | ||
|
|
760bec420b | ||
|
|
25c2f085e8 | ||
|
|
b5a4a21f51 | ||
|
|
8f6a80e2c7 | ||
|
|
71d30dd19a | ||
|
|
1f4f111c52 | ||
|
|
061fd8bec8 | ||
|
|
6c21d9f91a | ||
|
|
80c894517e | ||
|
|
68028f42a3 | ||
|
|
fb570367fa | ||
|
|
73a0eeba8f | ||
|
|
3ff87427ac | ||
|
|
cd8151c684 | ||
|
|
26e74a6b89 | ||
|
|
148c25b555 | ||
|
|
522e1e2ee3 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,3 +13,5 @@
|
|||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
*.bk
|
*.bk
|
||||||
*-bak
|
*-bak
|
||||||
|
.qtcreator
|
||||||
|
.clang-format
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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) : );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
15
F0:F030,F042,F072/usbcan_gpio/Makefile
Normal file
15
F0:F030,F042,F072/usbcan_gpio/Makefile
Normal 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)
|
||||||
406
F0:F030,F042,F072/usbcan_gpio/Readme.md
Normal file
406
F0:F030,F042,F072/usbcan_gpio/Readme.md
Normal 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 text‑based protocol used to communicate with the CAN interface of the USB‑CAN‑GPIO 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>`** – 11‑bit 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 (10–1000). Without argument, display the current speed. | `b 250` |
|
||||||
|
| `I` | Re‑initialise 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 human‑readable 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 4‑byte 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 (0–27). `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 bus‑off 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 power‑off.
|
||||||
|
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 case‑sensitive for the single‑letter 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"
|
||||||
|
```
|
||||||
236
F0:F030,F042,F072/usbcan_gpio/Readmeh.html
Normal file
236
F0:F030,F042,F072/usbcan_gpio/Readmeh.html
Normal 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>
|
||||||
158
F0:F030,F042,F072/usbcan_gpio/adc.c
Normal file
158
F0:F030,F042,F072/usbcan_gpio/adc.c
Normal 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;
|
||||||
|
}
|
||||||
30
F0:F030,F042,F072/usbcan_gpio/adc.h
Normal file
30
F0:F030,F042,F072/usbcan_gpio/adc.h
Normal 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();
|
||||||
396
F0:F030,F042,F072/usbcan_gpio/can.c
Normal file
396
F0:F030,F042,F072/usbcan_gpio/can.c
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
64
F0:F030,F042,F072/usbcan_gpio/can.h
Normal file
64
F0:F030,F042,F072/usbcan_gpio/can.h
Normal 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);
|
||||||
485
F0:F030,F042,F072/usbcan_gpio/canproto.c
Normal file
485
F0:F030,F042,F072/usbcan_gpio/canproto.c
Normal 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);
|
||||||
|
}
|
||||||
22
F0:F030,F042,F072/usbcan_gpio/canproto.h
Normal file
22
F0:F030,F042,F072/usbcan_gpio/canproto.h
Normal 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();
|
||||||
|
|
||||||
204
F0:F030,F042,F072/usbcan_gpio/flash.c
Normal file
204
F0:F030,F042,F072/usbcan_gpio/flash.c
Normal 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;
|
||||||
|
}
|
||||||
75
F0:F030,F042,F072/usbcan_gpio/flash.h
Normal file
75
F0:F030,F042,F072/usbcan_gpio/flash.h
Normal 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();
|
||||||
661
F0:F030,F042,F072/usbcan_gpio/gpio.c
Normal file
661
F0:F030,F042,F072/usbcan_gpio/gpio.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
136
F0:F030,F042,F072/usbcan_gpio/gpio.h
Normal file
136
F0:F030,F042,F072/usbcan_gpio/gpio.h
Normal 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);
|
||||||
1049
F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp
Normal file
1049
F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp
Normal file
File diff suppressed because it is too large
Load Diff
43
F0:F030,F042,F072/usbcan_gpio/gpioproto.h
Normal file
43
F0:F030,F042,F072/usbcan_gpio/gpioproto.h
Normal 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();
|
||||||
99
F0:F030,F042,F072/usbcan_gpio/hardware.c
Normal file
99
F0:F030,F042,F072/usbcan_gpio/hardware.c
Normal 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
|
||||||
51
F0:F030,F042,F072/usbcan_gpio/hardware.h
Normal file
51
F0:F030,F042,F072/usbcan_gpio/hardware.h
Normal 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
|
||||||
350
F0:F030,F042,F072/usbcan_gpio/hashparser.cpp
Normal file
350
F0:F030,F042,F072/usbcan_gpio/hashparser.cpp
Normal 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]);
|
||||||
|
;
|
||||||
|
}
|
||||||
37
F0:F030,F042,F072/usbcan_gpio/hashparser.h
Normal file
37
F0:F030,F042,F072/usbcan_gpio/hashparser.h
Normal 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);
|
||||||
214
F0:F030,F042,F072/usbcan_gpio/i2c.c
Normal file
214
F0:F030,F042,F072/usbcan_gpio/i2c.c
Normal 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, ®, 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;
|
||||||
|
}
|
||||||
47
F0:F030,F042,F072/usbcan_gpio/i2c.h
Normal file
47
F0:F030,F042,F072/usbcan_gpio/i2c.h
Normal 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);
|
||||||
|
|
||||||
51
F0:F030,F042,F072/usbcan_gpio/main.c
Normal file
51
F0:F030,F042,F072/usbcan_gpio/main.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
4
F0:F030,F042,F072/usbcan_gpio/openocd.cfg
Normal file
4
F0:F030,F042,F072/usbcan_gpio/openocd.cfg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
set FLASH_SIZE 0x8000
|
||||||
|
|
||||||
|
source [find interface/stlink-v2-1.cfg]
|
||||||
|
source [find target/stm32f0x.cfg]
|
||||||
177
F0:F030,F042,F072/usbcan_gpio/pwm.c
Normal file
177
F0:F030,F042,F072/usbcan_gpio/pwm.c
Normal 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;
|
||||||
|
}
|
||||||
49
F0:F030,F042,F072/usbcan_gpio/pwm.h
Normal file
49
F0:F030,F042,F072/usbcan_gpio/pwm.h
Normal 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);
|
||||||
203
F0:F030,F042,F072/usbcan_gpio/ringbuffer.c
Normal file
203
F0:F030,F042,F072/usbcan_gpio/ringbuffer.c
Normal 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;
|
||||||
|
}
|
||||||
36
F0:F030,F042,F072/usbcan_gpio/ringbuffer.h
Normal file
36
F0:F030,F042,F072/usbcan_gpio/ringbuffer.h
Normal 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);
|
||||||
83
F0:F030,F042,F072/usbcan_gpio/spi.c
Normal file
83
F0:F030,F042,F072/usbcan_gpio/spi.c
Normal 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;
|
||||||
|
}
|
||||||
25
F0:F030,F042,F072/usbcan_gpio/spi.h
Normal file
25
F0:F030,F042,F072/usbcan_gpio/spi.h
Normal 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);
|
||||||
265
F0:F030,F042,F072/usbcan_gpio/strfunc.c
Normal file
265
F0:F030,F042,F072/usbcan_gpio/strfunc.c
Normal 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;
|
||||||
|
}
|
||||||
53
F0:F030,F042,F072/usbcan_gpio/strfunc.h
Normal file
53
F0:F030,F042,F072/usbcan_gpio/strfunc.h
Normal 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))
|
||||||
|
|
||||||
313
F0:F030,F042,F072/usbcan_gpio/usart.c
Normal file
313
F0:F030,F042,F072/usbcan_gpio/usart.c
Normal 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();}
|
||||||
55
F0:F030,F042,F072/usbcan_gpio/usart.h
Normal file
55
F0:F030,F042,F072/usbcan_gpio/usart.h
Normal 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);
|
||||||
248
F0:F030,F042,F072/usbcan_gpio/usb_descr.c
Normal file
248
F0:F030,F042,F072/usbcan_gpio/usb_descr.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
76
F0:F030,F042,F072/usbcan_gpio/usb_descr.h
Normal file
76
F0:F030,F042,F072/usbcan_gpio/usb_descr.h
Normal 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();
|
||||||
359
F0:F030,F042,F072/usbcan_gpio/usb_dev.c
Normal file
359
F0:F030,F042,F072/usbcan_gpio/usb_dev.c
Normal 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];
|
||||||
|
}
|
||||||
|
|
||||||
69
F0:F030,F042,F072/usbcan_gpio/usb_dev.h
Normal file
69
F0:F030,F042,F072/usbcan_gpio/usb_dev.h
Normal 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);
|
||||||
450
F0:F030,F042,F072/usbcan_gpio/usb_lib.c
Normal file
450
F0:F030,F042,F072/usbcan_gpio/usb_lib.c
Normal 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
|
||||||
353
F0:F030,F042,F072/usbcan_gpio/usb_lib.h
Normal file
353
F0:F030,F042,F072/usbcan_gpio/usb_lib.h
Normal 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();
|
||||||
BIN
F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin
Executable file
BIN
F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin
Executable file
Binary file not shown.
6
F0:F030,F042,F072/usbcan_gpio/usbcangpio.config
Normal file
6
F0:F030,F042,F072/usbcan_gpio/usbcangpio.config
Normal 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
|
||||||
218
F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user
Normal file
218
F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user
Normal 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<int>" 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<int>" 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>
|
||||||
42
F0:F030,F042,F072/usbcan_gpio/usbcangpio.files
Normal file
42
F0:F030,F042,F072/usbcan_gpio/usbcangpio.files
Normal 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
|
||||||
2
F0:F030,F042,F072/usbcan_gpio/version.inc
Normal file
2
F0:F030,F042,F072/usbcan_gpio/version.inc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define BUILD_NUMBER "226"
|
||||||
|
#define BUILD_DATE "2026-03-18"
|
||||||
@@ -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
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
210
F0:F030,F042,F072/usbcan_ringbuffer/usb_descr.c
Normal file
210
F0:F030,F042,F072/usbcan_ringbuffer/usb_descr.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
F0:F030,F042,F072/usbcan_ringbuffer/usb_descr.h
Normal file
64
F0:F030,F042,F072/usbcan_ringbuffer/usb_descr.h
Normal 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);
|
||||||
252
F0:F030,F042,F072/usbcan_ringbuffer/usb_dev.c
Normal file
252
F0:F030,F042,F072/usbcan_ringbuffer/usb_dev.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)());
|
|
||||||
|
|||||||
Binary file not shown.
@@ -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<int>" 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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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();
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
#define BUILD_NUMBER "50"
|
#define BUILD_NUMBER "54"
|
||||||
#define BUILD_DATE "2024-11-18"
|
#define BUILD_DATE "2026-03-06"
|
||||||
|
|||||||
Binary file not shown.
@@ -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 18.0.2, 2026-02-09T20:28:07. -->
|
<!-- Written by QtCreator 18.0.2, 2026-02-13T20:51:37. -->
|
||||||
<qtcreator>
|
<qtcreator>
|
||||||
<data>
|
<data>
|
||||||
<variable>EnvironmentId</variable>
|
<variable>EnvironmentId</variable>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,13 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -142,7 +148,7 @@ int main(){
|
|||||||
else if(l) parse_cmd(inbuff);
|
else if(l) parse_cmd(inbuff);
|
||||||
// check if interface connected/disconnected
|
// check if interface connected/disconnected
|
||||||
// (we CAN'T do much debug output in interrupt functions like linecoding_handler etc, so do it here)
|
// (we CAN'T do much debug output in interrupt functions like linecoding_handler etc, so do it here)
|
||||||
for(int i = 0; i < bTotNumEndpoints; ++i){
|
for(int i = 1; i < bTotNumEndpoints; ++i){
|
||||||
if(oldCDCready[i] != CDCready[i]){
|
if(oldCDCready[i] != CDCready[i]){
|
||||||
CMDWR("Interface ");
|
CMDWR("Interface ");
|
||||||
CMDWR(u2str(i));
|
CMDWR(u2str(i));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -69,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
|
||||||
@@ -111,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("wrong 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]){
|
||||||
@@ -126,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,6 +161,7 @@ static void clearbufs(uint8_t ifno){
|
|||||||
while(Tms - T0 < 10){
|
while(Tms - T0 < 10){
|
||||||
if(1 == RB_clearbuf((ringbuffer*)&rbout[ifno])) break;
|
if(1 == RB_clearbuf((ringbuffer*)&rbout[ifno])) break;
|
||||||
}
|
}
|
||||||
|
rcvbuflen[ifno] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SET_CONTROL_LINE_STATE
|
// SET_CONTROL_LINE_STATE
|
||||||
@@ -161,9 +169,9 @@ 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;
|
||||||
if(val) clearbufs(ifno);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SEND_BREAK - disconnect interface and clear its buffers
|
// SEND_BREAK - disconnect interface and clear its buffers
|
||||||
@@ -173,7 +181,6 @@ void WEAK break_handler(uint8_t ifno){
|
|||||||
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()");
|
||||||
@@ -262,7 +269,15 @@ int USB_send(uint8_t ifno, const uint8_t *buf, int len){
|
|||||||
}
|
}
|
||||||
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;
|
||||||
@@ -338,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ 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
|
// as ugly CDC have no BREAK after disconnected client in non-canonical mode, we should use timeout - more than 2ms
|
||||||
#define DISCONN_TMOUT (2)
|
#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)
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#define BUILD_NUMBER "130"
|
#define BUILD_NUMBER "144"
|
||||||
#define BUILD_DATE "2026-02-09"
|
#define BUILD_DATE "2026-02-13"
|
||||||
|
|||||||
69
F3:F303/InterfaceBoard/Debug.c
Normal file
69
F3:F303/InterfaceBoard/Debug.c
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the multiiface 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 <string.h>
|
||||||
|
#include "Debug.h"
|
||||||
|
#include "hardware.h" // Config_mode
|
||||||
|
#include "ringbuffer.h"
|
||||||
|
#include "usb_dev.h"
|
||||||
|
|
||||||
|
// asinchronous debugging
|
||||||
|
// DBG(str): add filename, line, string message `str` and '\n'
|
||||||
|
// DBGs(str): only add `str`, without '\n'
|
||||||
|
// DBGn(): add just '\n'
|
||||||
|
|
||||||
|
#ifdef EBUG
|
||||||
|
|
||||||
|
#define DBGBUFSZ (1024)
|
||||||
|
|
||||||
|
static uint8_t buf[DBGBUFSZ];
|
||||||
|
static ringbuffer dbgrb = {.data = buf, .length = DBGBUFSZ};
|
||||||
|
|
||||||
|
void debug_message_text(const char *str){
|
||||||
|
if(!Config_mode) return;
|
||||||
|
RB_write(&dbgrb, (const uint8_t*)str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_message_char(char ch){
|
||||||
|
if(!Config_mode) return;
|
||||||
|
RB_write(&dbgrb, (const uint8_t*)&ch, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_newline_only(){
|
||||||
|
if(!Config_mode) return;
|
||||||
|
char nl = '\n';
|
||||||
|
RB_write(&dbgrb, (const uint8_t*)&nl, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// print by lines until there's place in USB ringbuffer
|
||||||
|
void print_debug_messages(){
|
||||||
|
if(!Config_mode) return;
|
||||||
|
uint8_t rcvbuf[256];
|
||||||
|
do{
|
||||||
|
int l = RB_datalento(&dbgrb, '\n');
|
||||||
|
if(l < 1) break;
|
||||||
|
int freesize = USB_sendbufspace(ICFG);
|
||||||
|
if(freesize < l) break;
|
||||||
|
if(l > 256) l = 256;
|
||||||
|
int n = RB_read(&dbgrb, rcvbuf, l);
|
||||||
|
if(n < 1) break; // empty or busy
|
||||||
|
USB_send(ICFG, rcvbuf, n);
|
||||||
|
}while(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
43
F3:F303/InterfaceBoard/Debug.h
Normal file
43
F3:F303/InterfaceBoard/Debug.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the multiiface 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 "usb_dev.h"
|
||||||
|
|
||||||
|
// debugging messages on config interface
|
||||||
|
#ifdef EBUG
|
||||||
|
#define STR_HELPER(s) #s
|
||||||
|
#define STR(s) STR_HELPER(s)
|
||||||
|
#define DBG(str) do{debug_message_text(__FILE__ " (L" STR(__LINE__) "): " str); debug_newline_only();}while(0)
|
||||||
|
#define DBGs(str) debug_message_text(str)
|
||||||
|
#define DBGch(ch) debug_message_char(ch)
|
||||||
|
#define DBGn() debug_newline_only()
|
||||||
|
#define DBGpri() print_debug_messages();
|
||||||
|
#else
|
||||||
|
#define DBG(str)
|
||||||
|
#define DBGs(str)
|
||||||
|
#define DBGch(ch)
|
||||||
|
#define DBGn()
|
||||||
|
#define DBGpri()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void debug_message_text(const char *str);
|
||||||
|
void debug_message_char(char ch);
|
||||||
|
void debug_newline_only();
|
||||||
|
void print_debug_messages();
|
||||||
@@ -3,6 +3,7 @@ BINARY := multiiface
|
|||||||
MCU := F303xc
|
MCU := F303xc
|
||||||
# change this linking script depending on particular MCU model,
|
# change this linking script depending on particular MCU model,
|
||||||
LDSCRIPT := stm32f303xB.ld
|
LDSCRIPT := stm32f303xB.ld
|
||||||
|
# add define "-DSPIDMA" to use DMA for SSI encoder, in that case IF1 (USART3) would be interrupt-driven!
|
||||||
DEFINES := -DUSB1_16
|
DEFINES := -DUSB1_16
|
||||||
|
|
||||||
include ../makefile.f3
|
include ../makefile.f3
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ Inner USB interfaces (IFx):
|
|||||||
| 12 | (VREF-) | | | |
|
| 12 | (VREF-) | | | |
|
||||||
| 13 | (VREF+) | | | |
|
| 13 | (VREF+) | | | |
|
||||||
| 14 | PA0 | NC | | |
|
| 14 | PA0 | NC | | |
|
||||||
| 15 | PA1 | USART2 DE | AF7 | RS-485 (3) DE |
|
| 15 | PA1 | USART2 DE | AF7 or PP | RS-485 (3) DE |
|
||||||
| 16 | PA2 | USART2 TX | AF7 | RS-485 (3) Tx |
|
| 16 | PA2 | USART2 TX | AF7 | RS-485 (3) Tx |
|
||||||
| 17 | PA3 | USART2 RX | AF7 | RS-485 (3) Rx |
|
| 17 | PA3 | USART2 RX | AF7 | RS-485 (3) Rx |
|
||||||
| 18 | PF4 | NC | | |
|
| 18 | PF4 | NC | | |
|
||||||
@@ -58,8 +58,8 @@ Inner USB interfaces (IFx):
|
|||||||
| 32 | (VDD) | | | |
|
| 32 | (VDD) | | | |
|
||||||
| 33 | PB12 | NC | | |
|
| 33 | PB12 | NC | | |
|
||||||
| 34 | PB13 | NC | | |
|
| 34 | PB13 | NC | | |
|
||||||
| 35 | PB14 | USART3 DE | AF7 | RS-485 (1) DE |
|
| 35 | PB14 | USART3 DE | AF7 or PP | RS-485 (1) DE |
|
||||||
| 36 | PB15 | | | |
|
| 36 | PB15 | NC | | |
|
||||||
| 37 | PC6 | NC | | |
|
| 37 | PC6 | NC | | |
|
||||||
| 38 | PC7 | NC | | |
|
| 38 | PC7 | NC | | |
|
||||||
| 39 | PC8 | NC | | |
|
| 39 | PC8 | NC | | |
|
||||||
@@ -91,5 +91,88 @@ Inner USB interfaces (IFx):
|
|||||||
|
|
||||||
### Some comments.
|
### Some comments.
|
||||||
|
|
||||||
### Sorted by ports (with AF numbers).
|
Interfaces:
|
||||||
|
|
||||||
|
- IF1: RS-485 over USART3.
|
||||||
|
- IF2: RS-485 over USART1.
|
||||||
|
- IF3: RS-485 over USART2.
|
||||||
|
- IF4: RS-232 over UART4.
|
||||||
|
- IF5: RS-232 or RS-422 (by jumpers "IF5" and "SSI") over UART5.
|
||||||
|
- IF6: CAN bus.
|
||||||
|
- IF7: SSI (inaccessible when RS-422 selected) or interface for device configuration (if jumper "Config mode" is opened when device started").
|
||||||
|
|
||||||
|
|
||||||
|
DMA1 channels:
|
||||||
|
|
||||||
|
- Ch2: USART3_Tx or SPI1_Rx
|
||||||
|
- Ch3: USART3_Rx
|
||||||
|
- Ch4: USART1_Tx
|
||||||
|
- Ch5: USART1_Rx
|
||||||
|
- Ch6: USART2_Rx
|
||||||
|
- Ch7: USART2_Tx
|
||||||
|
|
||||||
|
DMA2 channels:
|
||||||
|
|
||||||
|
- Ch3: UART4_Rx
|
||||||
|
- Ch5: UART4_Tx
|
||||||
|
|
||||||
|
UART5 have no DMA channels, so used in interrupts.
|
||||||
|
|
||||||
|
You can try to use hardware DE management on two of RS-485, but I decide that as I can't use hardware DE for all three, it would be
|
||||||
|
simpler to use software DE for all.
|
||||||
|
|
||||||
|
### Sorted by ports
|
||||||
|
|
||||||
|
| Pin # | Pin name | function | settings | comment |
|
||||||
|
|---------|-------------|-------------|---------------|---------------------|
|
||||||
|
| 14 | PA0 | NC | | |
|
||||||
|
| 15 | PA1 | USART2 DE | AF7 or PP | RS-485 (3) DE |
|
||||||
|
| 16 | PA2 | USART2 TX | AF7 | RS-485 (3) Tx |
|
||||||
|
| 17 | PA3 | USART2 RX | AF7 | RS-485 (3) Rx |
|
||||||
|
| 20 | PA4 | NC | | |
|
||||||
|
| 21 | PA5 | SPI1 SCK | AF5 | SSI CLK |
|
||||||
|
| 22 | PA6 | SPI1 MISO | AF5 | SSI DAT |
|
||||||
|
| 23 | PA7 | NC | | |
|
||||||
|
| 41 | PA8 | NC | | |
|
||||||
|
| 42 | PA9 | (CONF EN) | PU IN | Config jumper |
|
||||||
|
| 43 | PA10 | (USB PU) | PP OUT | USB pullup |
|
||||||
|
| 44 | PA11 | USB DM | AF14 | |
|
||||||
|
| 45 | PA12 | USB DP | AF14 | |
|
||||||
|
| 46 | PA13 | SWDIO | AF0 | |
|
||||||
|
| 49 | PA14 | SWCLK | AF0 | |
|
||||||
|
| 50 | PA15 | NC | | |
|
||||||
|
| 26 | PB0 | (USART1 DE) | PP OUT | RS-485 (2) DE |
|
||||||
|
| 27 | PB1 | NC | | |
|
||||||
|
| 28 | PB2 | NC | | |
|
||||||
|
| 55 | PB3 | NC | | |
|
||||||
|
| 56 | PB4 | NC | | |
|
||||||
|
| 57 | PB5 | NC | | |
|
||||||
|
| 58 | PB6 | NC | | |
|
||||||
|
| 59 | PB7 | NC | | |
|
||||||
|
| 61 | PB8 | CAN RX | AF9 | |
|
||||||
|
| 62 | PB9 | CAN TX | AF9 | |
|
||||||
|
| 29 | PB10 | USART3 TX | AF7 | RS-485 (1) Tx |
|
||||||
|
| 30 | PB11 | USART3 RX | AF7 | RS-485 (1) Rx |
|
||||||
|
| 33 | PB12 | NC | | |
|
||||||
|
| 34 | PB13 | NC | | |
|
||||||
|
| 35 | PB14 | USART3 DE | AF7 or PP | RS-485 (1) DE |
|
||||||
|
| 36 | PB15 | NC | | |
|
||||||
|
| 8 | PC0 | NC | | |
|
||||||
|
| 9 | PC1 | NC | | |
|
||||||
|
| 10 | PC2 | NC | | |
|
||||||
|
| 11 | PC3 | NC | | |
|
||||||
|
| 24 | PC4 | USART1 TX | AF7 | RS-485 (2) Tx |
|
||||||
|
| 25 | PC5 | USART1 RX | AF7 | RS-485 (2) Rx |
|
||||||
|
| 37 | PC6 | NC | | |
|
||||||
|
| 38 | PC7 | NC | | |
|
||||||
|
| 39 | PC8 | NC | | |
|
||||||
|
| 40 | PC9 | NC | | |
|
||||||
|
| 51 | PC10 | UART4 TX | AF5 | RS-232 (1) Tx |
|
||||||
|
| 52 | PC11 | UART4 RX | AF5 | RS-232 (1) Rx |
|
||||||
|
| 53 | PC12 | UART5 TX | AF5 | RS-232(2) / 485 Tx |
|
||||||
|
| 2 | PC13 | NC | | |
|
||||||
|
| 3 | PC14 | NC | | |
|
||||||
|
| 4 | PC15 | NC | | |
|
||||||
|
| 54 | PD2 | UART5 RX | AF5 | RS-232(2) / 485 Rx |
|
||||||
|
| 18 | PF4 | NC | | |
|
||||||
|
|
||||||
|
|||||||
1
F3:F303/InterfaceBoard/TODO
Normal file
1
F3:F303/InterfaceBoard/TODO
Normal file
@@ -0,0 +1 @@
|
|||||||
|
add fixes to ringbuffer and make EP_reset @ each reconnection (like in F0/usbcan_gpio).
|
||||||
389
F3:F303/InterfaceBoard/can.c
Normal file
389
F3:F303/InterfaceBoard/can.c
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the multiiface 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 <string.h> // memcpy
|
||||||
|
|
||||||
|
// read private defines from "canproto.h"
|
||||||
|
#define CANPRIVATE__
|
||||||
|
|
||||||
|
#include "can.h"
|
||||||
|
#include "canproto.h"
|
||||||
|
#include "Debug.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "strfunc.h"
|
||||||
|
|
||||||
|
// PB9 - Tx, PB8 - Rx !!!
|
||||||
|
|
||||||
|
// CAN settings
|
||||||
|
// CAN bus oscillator frequency: 36MHz (APB1)
|
||||||
|
#define CAN_F_OSC (36000000UL)
|
||||||
|
// timing values TBS1 and TBS2 (in BTR [TBS1-1] and [TBS2-1])
|
||||||
|
// use 3 and 2 to get 6MHz
|
||||||
|
#define CAN_TBS1 (3)
|
||||||
|
#define CAN_TBS2 (2)
|
||||||
|
// bitrate oscillator frequency
|
||||||
|
#define CAN_BIT_OSC (CAN_F_OSC / (1+CAN_TBS1+CAN_TBS2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
|
static uint32_t oldspeed = 100000; // speed of last init
|
||||||
|
uint32_t floodT = FLOOD_PERIOD_MS; // flood period in ms
|
||||||
|
static uint8_t incrflood = 0; // ==1 for incremental flooding
|
||||||
|
static uint32_t incrmessagectr = 0; // counter 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(){
|
||||||
|
int st = can_status;
|
||||||
|
can_status = CAN_OK;
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
// push next message into buffer; return 1 if buffer overfull
|
||||||
|
static int CAN_messagebuf_push(CAN_message *msg){
|
||||||
|
//DBG("PUSH");
|
||||||
|
if(first_free_idx == first_nonfree_idx){
|
||||||
|
DBG("INBUF OVERFULL");
|
||||||
|
return 1; // no free space
|
||||||
|
}
|
||||||
|
if(first_nonfree_idx < 0) first_nonfree_idx = 0; // first message in empty buffer
|
||||||
|
memcpy(&messages[first_free_idx++], msg, sizeof(CAN_message));
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
//DBG("POP");
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAN_reinit(uint32_t speed){
|
||||||
|
DBG("CAN_reinit");
|
||||||
|
CAN->TSR |= CAN_TSR_ABRQ0 | CAN_TSR_ABRQ1 | CAN_TSR_ABRQ2;
|
||||||
|
RCC->APB1RSTR |= RCC_APB1RSTR_CANRST;
|
||||||
|
RCC->APB1RSTR &= ~RCC_APB1RSTR_CANRST;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// speed - in kbps
|
||||||
|
// GPIO configured in hw_setup
|
||||||
|
void CAN_setup(uint32_t speed){
|
||||||
|
DBG("CAN_setup");
|
||||||
|
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 tmout = 10000;
|
||||||
|
// CAN clocking and pins enabled in hardware.c
|
||||||
|
/* 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: 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 (& bus off) */
|
||||||
|
CAN->MCR |= CAN_MCR_INRQ; /* (1) */
|
||||||
|
while((CAN->MSR & CAN_MSR_INAK) != CAN_MSR_INAK) /* (2) */
|
||||||
|
if(--tmout == 0) break;
|
||||||
|
if(tmout==0){ DBG("timeout!\n");}
|
||||||
|
CAN->MCR &=~ CAN_MCR_SLEEP; /* (3) */
|
||||||
|
CAN->MCR |= CAN_MCR_ABOM; /* allow automatically bus-off */
|
||||||
|
DBG("new speed:"); DBGs(u2str(speed)); DBGn();
|
||||||
|
CAN->BTR = (CAN_TBS2-1) << 20 | (CAN_TBS1-1) << 16 | (CAN_BIT_OSC/speed - 1); /* (4) */
|
||||||
|
oldspeed = CAN_BIT_OSC/(uint32_t)((CAN->BTR & CAN_BTR_BRP) + 1);
|
||||||
|
DBG("saved sped:"); DBGs(u2str(oldspeed)); DBGn();
|
||||||
|
CAN->MCR &= ~CAN_MCR_INRQ; /* (5) */
|
||||||
|
tmout = 10000;
|
||||||
|
while(CAN->MSR & CAN_MSR_INAK) /* (6) */
|
||||||
|
if(--tmout == 0) break;
|
||||||
|
if(tmout==0){ DBG("timeout!\n");}
|
||||||
|
// 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 | CAN_IER_BOFIE; /* (13) */
|
||||||
|
|
||||||
|
/* Configure IT */
|
||||||
|
NVIC_SetPriority(USB_LP_CAN_RX0_IRQn, 0); // RX FIFO0 IRQ
|
||||||
|
NVIC_SetPriority(CAN_RX1_IRQn, 0); // RX FIFO1 IRQ
|
||||||
|
NVIC_SetPriority(CAN_SCE_IRQn, 0); // RX status changed IRQ
|
||||||
|
NVIC_EnableIRQ(USB_LP_CAN_RX0_IRQn);
|
||||||
|
NVIC_EnableIRQ(CAN_RX1_IRQn);
|
||||||
|
NVIC_EnableIRQ(CAN_SCE_IRQn);
|
||||||
|
CAN->MSR = 0; // clear SLAKI, WKUI, ERRI
|
||||||
|
can_status = CAN_READY;
|
||||||
|
DBGs("CAN configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAN_printerr(){
|
||||||
|
if(!last_err_code) last_err_code = CAN->ESR;
|
||||||
|
if(!last_err_code){
|
||||||
|
PRIstr("No errors\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PRIstr("Receive error counter: ");
|
||||||
|
PRIstr(u2str((last_err_code & CAN_ESR_REC)>>24));
|
||||||
|
PRIstr("\nTransmit error counter: ");
|
||||||
|
PRIstr(u2str((last_err_code & CAN_ESR_TEC)>>16));
|
||||||
|
PRIstr("\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;
|
||||||
|
}
|
||||||
|
PRIstr(errmsg); PRIstr(" error\n");
|
||||||
|
if(last_err_code & CAN_ESR_BOFF) PRIstr("Bus off ");
|
||||||
|
if(last_err_code & CAN_ESR_EPVF) PRIstr("Passive error limit ");
|
||||||
|
if(last_err_code & CAN_ESR_EWGF) PRIstr("Error counter limit");
|
||||||
|
last_err_code = 0;
|
||||||
|
PRIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAN_proc(){
|
||||||
|
// 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
|
||||||
|
PRIstr("\nToo much errors, restarting CAN!\n");
|
||||||
|
CAN_printerr();
|
||||||
|
// 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;
|
||||||
|
if(flood_msg && (Tms - lastFloodTime) >= floodT){ // flood every ~5ms
|
||||||
|
lastFloodTime = Tms;
|
||||||
|
CAN_send(flood_msg->data, flood_msg->length, flood_msg->ID);
|
||||||
|
}else if(incrflood && (Tms - lastFloodTime) >= floodT){
|
||||||
|
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
|
||||||
|
DBG("No free mailboxes");
|
||||||
|
return CAN_BUSY;
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
DBGs("Send data. Len="); DBGs(u2str(len));
|
||||||
|
DBGs(", tagid="); DBGs(u2str(target_id));
|
||||||
|
DBGs(", data=");
|
||||||
|
for(int i = 0; i < len; ++i){
|
||||||
|
DBGs(" "); DBGs(uhex2str(msg[i]));
|
||||||
|
}
|
||||||
|
DBGn();
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return: 1 - flood is ON, 0 - flood is OFF
|
||||||
|
int CAN_flood(CAN_message *msg, int incr){
|
||||||
|
if(incr){
|
||||||
|
incrmessagectr = 0;
|
||||||
|
incrflood = 1;
|
||||||
|
return 1;
|
||||||
|
}else incrflood = 0;
|
||||||
|
if(!msg){
|
||||||
|
flood_msg = NULL;
|
||||||
|
return 0;
|
||||||
|
}else{
|
||||||
|
memcpy(&loc_flood_msg, msg, sizeof(CAN_message));
|
||||||
|
flood_msg = &loc_flood_msg;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t CAN_speed(){
|
||||||
|
return oldspeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void can_process_fifo(uint8_t fifo_num){
|
||||||
|
if(fifo_num > 1) return;
|
||||||
|
CAN_FIFOMailBox_TypeDef *box = &CAN->sFIFOMailBox[fifo_num];
|
||||||
|
volatile uint32_t *RFxR = (fifo_num) ? &CAN->RF1R : &CAN->RF0R;
|
||||||
|
// 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(msg.ID == the_conf.CANID) parseCANcommand(&msg);
|
||||||
|
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 usb_lp_can1_rx0_isr(){ // Rx FIFO0 (overrun)
|
||||||
|
if(CAN->RF0R & CAN_RF0R_FOVR0){ // FIFO overrun
|
||||||
|
CAN->RF0R = CAN_RF0R_FOVR0;
|
||||||
|
can_status = CAN_FIFO_OVERRUN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void can1_rx1_isr(){ // Rx FIFO1 (overrun)
|
||||||
|
if(CAN->RF1R & CAN_RF1R_FOVR1){
|
||||||
|
CAN->RF1R = CAN_RF1R_FOVR1;
|
||||||
|
can_status = CAN_FIFO_OVERRUN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void can1_sce_isr(){ // status changed
|
||||||
|
if(CAN->MSR & CAN_MSR_ERRI){ // Error
|
||||||
|
last_err_code = CAN->ESR;
|
||||||
|
CAN->MSR = CAN_MSR_ERRI; // clear flag
|
||||||
|
// 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;
|
||||||
|
can_status = CAN_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
F3:F303/InterfaceBoard/can.h
Normal file
63
F3:F303/InterfaceBoard/can.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the multistepper project.
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
#define CAN_MAX_SPEED ((uint32_t)3000000UL)
|
||||||
|
#define CAN_MIN_SPEED ((uint32_t)9600UL)
|
||||||
|
|
||||||
|
// amount of filter banks in STM32F
|
||||||
|
#define STM32FBANKNO 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_ERR,
|
||||||
|
CAN_FIFO_OVERRUN
|
||||||
|
} CAN_status;
|
||||||
|
|
||||||
|
CAN_status CAN_get_status();
|
||||||
|
|
||||||
|
void CAN_reinit(uint32_t speed);
|
||||||
|
void CAN_setup(uint32_t speed);
|
||||||
|
|
||||||
|
CAN_status CAN_send(uint8_t *msg, uint8_t len, uint16_t target_id);
|
||||||
|
void CAN_proc();
|
||||||
|
void CAN_printerr();
|
||||||
|
|
||||||
|
CAN_message *CAN_messagebuf_pop();
|
||||||
|
|
||||||
|
int CAN_flood(CAN_message *msg, int incr);
|
||||||
|
uint32_t CAN_speed();
|
||||||
449
F3:F303/InterfaceBoard/canproto.c
Normal file
449
F3:F303/InterfaceBoard/canproto.c
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the canusb project.
|
||||||
|
* 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 <stm32f3.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// read private defines from "canproto.h"
|
||||||
|
#define CANPRIVATE__
|
||||||
|
|
||||||
|
#include "can.h"
|
||||||
|
#include "canproto.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
|
||||||
|
#define printu(x) PRIstr(u2str(x))
|
||||||
|
#define printuhex(x) PRIstr(uhex2str(x))
|
||||||
|
|
||||||
|
// software ignore buffer size
|
||||||
|
#define IGN_SIZE 10
|
||||||
|
|
||||||
|
extern volatile uint32_t Tms;
|
||||||
|
|
||||||
|
uint8_t CANShowMsgs = 1;
|
||||||
|
// software ignore buffers
|
||||||
|
static uint16_t Ignore_IDs[IGN_SIZE];
|
||||||
|
static uint8_t IgnSz = 0;
|
||||||
|
|
||||||
|
// parse `txt` to CAN_message
|
||||||
|
static CAN_message *parseCANmsg(const char *txt){
|
||||||
|
static CAN_message canmsg;
|
||||||
|
uint32_t N;
|
||||||
|
int ctr = -1;
|
||||||
|
canmsg.ID = 0xffff;
|
||||||
|
do{
|
||||||
|
const char *n = getnum(txt, &N);
|
||||||
|
if(txt == n) break;
|
||||||
|
txt = n;
|
||||||
|
if(ctr == -1){
|
||||||
|
if(N > 0x7ff){
|
||||||
|
PRIstrn("ID should be 11-bit number!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
canmsg.ID = (uint16_t)(N&0x7ff);
|
||||||
|
ctr = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(ctr > 7){
|
||||||
|
PRIstrn("ONLY 8 data bytes allowed!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(N > 0xff){
|
||||||
|
PRIstrn("Every data portion is a byte!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
canmsg.data[ctr++] = (uint8_t)(N&0xff);
|
||||||
|
}while(1);
|
||||||
|
if(canmsg.ID == 0xffff){
|
||||||
|
PRIstrn("NO ID given, send nothing!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PRIstrn("Message parsed OK");
|
||||||
|
canmsg.length = (uint8_t) ctr;
|
||||||
|
return &canmsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send command, format: ID (hex/bin/dec) data bytes (up to 8 bytes, space-delimeted)
|
||||||
|
TRUE_INLINE void CANcommand(char *txt){
|
||||||
|
if(CAN->MSR & CAN_MSR_INAK){
|
||||||
|
PRIstrn("CAN bus is off, try to restart it");
|
||||||
|
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(const char *txt){
|
||||||
|
uint32_t N;
|
||||||
|
const char *n = getnum(txt, &N);
|
||||||
|
if(txt == n){
|
||||||
|
PRIstr("CANspeed=");
|
||||||
|
PRIstrn(u2str(CAN_speed()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(N < CAN_MIN_SPEED){
|
||||||
|
PRIstrn("Speed is too low");
|
||||||
|
return;
|
||||||
|
}else if(N > CAN_MAX_SPEED){
|
||||||
|
PRIstrn("Speed is too large");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CAN_reinit(N);
|
||||||
|
PRIstr("Reinit CAN bus with speed ");
|
||||||
|
printu(N); PRIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
TRUE_INLINE void addIGN(const char *txt){
|
||||||
|
if(IgnSz == IGN_SIZE){
|
||||||
|
PRIstrn("Ignore buffer is full");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
txt = omit_spaces(txt);
|
||||||
|
uint32_t N;
|
||||||
|
const char *n = getnum(txt, &N);
|
||||||
|
if(txt == n){
|
||||||
|
PRIstrn("No ID given");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(N > 0x7ff){
|
||||||
|
PRIstrn("ID should be 11-bit number!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ignore_IDs[IgnSz++] = (uint16_t)(N & 0x7ff);
|
||||||
|
PRIstr("Added ID "); printu(N);
|
||||||
|
PRIstr("\nIgn buffer size: "); PRIstrn(u2str(IgnSz));
|
||||||
|
}
|
||||||
|
|
||||||
|
TRUE_INLINE void print_ign_buf(){
|
||||||
|
if(IgnSz == 0){
|
||||||
|
PRIstrn("Ignore buffer is empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PRIstr("Ignored IDs:");
|
||||||
|
for(int i = 0; i < IgnSz; ++i){
|
||||||
|
printu(i);
|
||||||
|
PRIstr(": ");
|
||||||
|
PRIstrn(u2str(Ignore_IDs[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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){
|
||||||
|
PRIstr("Filter "); printu(ctr); PRIstr(", FIFO");
|
||||||
|
if(CAN->FFA1R & mask) PRIstr("1");
|
||||||
|
else PRIstr("0");
|
||||||
|
PRIstr(" in ");
|
||||||
|
if(CAN->FM1R & mask){ // up to 4 filters in LIST mode
|
||||||
|
PRIstr("LIST mode, IDs: ");
|
||||||
|
printID(CAN->sFilterRegister[ctr].FR1 & 0xffff);
|
||||||
|
PRIstr(" ");
|
||||||
|
printID(CAN->sFilterRegister[ctr].FR1 >> 16);
|
||||||
|
PRIstr(" ");
|
||||||
|
printID(CAN->sFilterRegister[ctr].FR2 & 0xffff);
|
||||||
|
PRIstr(" ");
|
||||||
|
printID(CAN->sFilterRegister[ctr].FR2 >> 16);
|
||||||
|
}else{ // up to 2 filters in MASK mode
|
||||||
|
PRIstr("MASK mode: ");
|
||||||
|
if(!(CAN->sFilterRegister[ctr].FR1&0x1f)){
|
||||||
|
PRIstr("ID="); printID(CAN->sFilterRegister[ctr].FR1 & 0xffff);
|
||||||
|
PRIstr(", MASK="); printID(CAN->sFilterRegister[ctr].FR1 >> 16);
|
||||||
|
PRIstr(" ");
|
||||||
|
}
|
||||||
|
if(!(CAN->sFilterRegister[ctr].FR2&0x1f)){
|
||||||
|
PRIstr("ID="); printID(CAN->sFilterRegister[ctr].FR2 & 0xffff);
|
||||||
|
PRIstr(", MASK="); printID(CAN->sFilterRegister[ctr].FR2 >> 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PRIn();
|
||||||
|
}
|
||||||
|
fa >>= 1;
|
||||||
|
++ctr;
|
||||||
|
mask <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TRUE_INLINE void setfloodt(const char *s){
|
||||||
|
uint32_t N;
|
||||||
|
s = omit_spaces(s);
|
||||||
|
const char *n = getnum(s, &N);
|
||||||
|
if(s != n) floodT = N;
|
||||||
|
PRIstr("t="); PRIstrn(u2str(floodT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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(const char *str){
|
||||||
|
uint32_t N;
|
||||||
|
str = omit_spaces(str);
|
||||||
|
const char *n = getnum(str, &N);
|
||||||
|
if(n == str){
|
||||||
|
PRIstrn("No bank# given");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(N > STM32FBANKNO-1){
|
||||||
|
PRIstrn("bank# too big");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t bankno = (uint8_t)N;
|
||||||
|
str = omit_spaces(n);
|
||||||
|
if(!*str){ // deactivate filter
|
||||||
|
PRIstr("Deactivate filters in bank ");
|
||||||
|
printu(bankno);
|
||||||
|
PRIn();
|
||||||
|
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'){
|
||||||
|
PRIstrn("FIFO# is 0 or 1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
str = omit_spaces(str + 1);
|
||||||
|
const char c = *str;
|
||||||
|
uint8_t mode = 0; // ID
|
||||||
|
if(c == 'M' || c == 'm') mode = 1;
|
||||||
|
else if(c != 'I' && c != 'i'){
|
||||||
|
PRIstrn("mode is 'M/m' for MASK and 'I/i' for IDLIST");
|
||||||
|
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){
|
||||||
|
PRIstrn("You should add at least one filter!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(mode && (nfilt&1)){
|
||||||
|
PRIstrn("In MASK mode you should point pairs of ID/MASK");
|
||||||
|
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;
|
||||||
|
PRIstr("Added filter with ");
|
||||||
|
printu(nfilt); PRIstrn(" parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *helpstring =
|
||||||
|
"'a' - add ID to ignore list (max 10 IDs)\n"
|
||||||
|
"'b' - reinit CAN with given baudrate\n"
|
||||||
|
"'c' - get CAN status\n"
|
||||||
|
"'d' - delete ignore list\n"
|
||||||
|
"'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"
|
||||||
|
"'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(){
|
||||||
|
PRIstr("CAN_MSR=");
|
||||||
|
printuhex(CAN->MSR);
|
||||||
|
PRIstr("\nCAN_TSR=");
|
||||||
|
printuhex(CAN->TSR);
|
||||||
|
PRIstr("\nCAN_RF0R=");
|
||||||
|
printuhex(CAN->RF0R);
|
||||||
|
PRIstr("\nCAN_RF1R=");
|
||||||
|
PRIstrn(u2str(CAN->RF1R));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief CANcmd_parser - CAN command parsing
|
||||||
|
* @param txt - buffer with commands & data
|
||||||
|
* @param isUSB - == 1 if data got from USB
|
||||||
|
*/
|
||||||
|
void CANcmd_parser(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':
|
||||||
|
CAN_flood(parseCANmsg(txt), 0);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
case 'S':
|
||||||
|
CANcommand(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':
|
||||||
|
CAN_printerr();
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
CAN_flood(NULL, 1);
|
||||||
|
PRIstrn("Incremental flooding is ON ('F' to off)");
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
CAN_reinit(0);
|
||||||
|
PRIstr("CANspeed=");
|
||||||
|
PRIstrn(u2str(CAN_speed()));
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
list_filters();
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
print_ign_buf();
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
CANShowMsgs = !CANShowMsgs;
|
||||||
|
if(CANShowMsgs) PRIstrn("Resume");
|
||||||
|
else PRIstrn("Pause");
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
PRIstrn("Soft reset");
|
||||||
|
USB_sendall(ICAN); // wait until everything will gone
|
||||||
|
NVIC_SystemReset();
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
PRIstr("Time (ms): ");
|
||||||
|
PRIstrn(u2str(Tms));
|
||||||
|
break;
|
||||||
|
default: // help
|
||||||
|
PRIstrn(helpstring);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check Ignore_IDs & return 1 if ID isn't in list
|
||||||
|
uint8_t CANsoftFilter(uint16_t ID){
|
||||||
|
for(int i = 0; i < IgnSz; ++i)
|
||||||
|
if(Ignore_IDs[i] == ID) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process incoming USB and CAN messages
|
||||||
|
void canproto_process(){
|
||||||
|
CAN_proc();
|
||||||
|
if(CAN_get_status() == CAN_FIFO_OVERRUN){
|
||||||
|
USB_sendstr(ICAN, "CAN bus fifo overrun occured!\n");
|
||||||
|
}
|
||||||
|
CAN_message *can_mesg;
|
||||||
|
while((can_mesg = CAN_messagebuf_pop())){
|
||||||
|
if(can_mesg && CANsoftFilter(can_mesg->ID)){
|
||||||
|
if(CANShowMsgs){ // display message content
|
||||||
|
IWDG->KR = IWDG_REFRESH;
|
||||||
|
uint8_t len = can_mesg->length;
|
||||||
|
USB_sendstr(ICAN, u2str(Tms));
|
||||||
|
USB_sendstr(ICAN, " #");
|
||||||
|
USB_sendstr(ICAN, uhex2str(can_mesg->ID));
|
||||||
|
for(uint8_t i = 0; i < len; ++i){
|
||||||
|
USB_putbyte(ICAN, ' ');
|
||||||
|
USB_sendstr(ICAN, uhex2str(can_mesg->data[i]));
|
||||||
|
}
|
||||||
|
newline(ICAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
F3:F303/InterfaceBoard/canproto.h
Normal file
43
F3:F303/InterfaceBoard/canproto.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the canusb project.
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
#ifdef CANPRIVATE__
|
||||||
|
|
||||||
|
#include "strfunc.h"
|
||||||
|
#include "usb_descr.h"
|
||||||
|
#include "usb_dev.h"
|
||||||
|
|
||||||
|
// USB messages to CAN interface
|
||||||
|
#define PRIstr(s) USB_sendstr(ICAN, s)
|
||||||
|
#define PRIstrn(s) do{USB_sendstr(ICAN, s); USB_putbyte(ICAN, '\n');}while(0)
|
||||||
|
#define PRIch(c) USB_putbyte(ICAN, c)
|
||||||
|
#define PRIn() USB_putbyte(ICAN, '\n')
|
||||||
|
|
||||||
|
#endif // CANPRIVATE__
|
||||||
|
|
||||||
|
void canproto_process();
|
||||||
|
void CANcmd_parser(char *txt);
|
||||||
|
/*
|
||||||
|
extern uint8_t CANShowMsgs; // show CAN messages flag
|
||||||
|
|
||||||
|
uint8_t CANsoftFilter(uint16_t ID);
|
||||||
|
*/
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
#include "usb_descr.h" // descriptors
|
#include "usb_descr.h" // descriptors
|
||||||
#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 = 2048 / sizeof(user_conf); // can't use blocksize here
|
static uint32_t maxCnum = 2048 / sizeof(user_conf); // can't use blocksize here
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ user_conf the_conf = {
|
|||||||
[ICAN] = u"usbCAN"
|
[ICAN] = u"usbCAN"
|
||||||
},
|
},
|
||||||
.iIlengths = {22,22,22,22,22,12,12},
|
.iIlengths = {22,22,22,22,22,12,12},
|
||||||
|
.CANspeed = 100000
|
||||||
};
|
};
|
||||||
|
|
||||||
int currentconfidx = -1; // index of current configuration
|
int currentconfidx = -1; // index of current configuration
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ typedef struct __attribute__((packed, aligned(4))){
|
|||||||
// we store iInterface "as is"
|
// we store iInterface "as is"
|
||||||
uint16_t iInterface[InterfacesAmount][MAX_IINTERFACE_SZ]; // hryunikod!
|
uint16_t iInterface[InterfacesAmount][MAX_IINTERFACE_SZ]; // hryunikod!
|
||||||
uint8_t iIlengths[InterfacesAmount];
|
uint8_t iIlengths[InterfacesAmount];
|
||||||
|
uint32_t CANspeed;
|
||||||
} user_conf;
|
} user_conf;
|
||||||
|
|
||||||
extern user_conf the_conf;
|
extern user_conf the_conf;
|
||||||
|
|||||||
@@ -21,15 +21,61 @@
|
|||||||
uint8_t Config_mode = 0;
|
uint8_t Config_mode = 0;
|
||||||
|
|
||||||
static inline void gpio_setup(){
|
static inline void gpio_setup(){
|
||||||
RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; // for USART and LEDs
|
RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_GPIOCEN | RCC_AHBENR_GPIODEN
|
||||||
|
| RCC_AHBENR_DMA1EN | RCC_AHBENR_DMA2EN | RCC_APB2ENR_SPI1EN;
|
||||||
|
RCC->APB1ENR |= RCC_APB1ENR_CANEN | RCC_APB1ENR_USART2EN | RCC_APB1ENR_USART3EN
|
||||||
|
| RCC_APB1ENR_UART4EN | RCC_APB1ENR_UART5EN;
|
||||||
|
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
|
||||||
for(int i = 0; i < 10000; ++i) nop();
|
for(int i = 0; i < 10000; ++i) nop();
|
||||||
// USB - alternate function 14 @ pins PA11/PA12; SWD - AF0 @PA13/14
|
/********************************************
|
||||||
|
* PA1 * USART2 DE * PP OUT *
|
||||||
|
* PA2 * USART2 TX * AF7 *
|
||||||
|
* PA3 * USART2 RX * AF7 *
|
||||||
|
* PA5 * SPI1 SCK * AF5 *
|
||||||
|
* PA6 * SPI1 MISO * AF5 *
|
||||||
|
* PA9 * (CONF EN) * PU IN *
|
||||||
|
* PA10 * (USB PU) * PP OUT *
|
||||||
|
* PA11 * USB DM * AF14 *
|
||||||
|
* PA12 * USB DP * AF14 *
|
||||||
|
* PA13 * SWDIO * AF0 *
|
||||||
|
* PA14 * SWCLK * AF0 *
|
||||||
|
********************************************/
|
||||||
|
GPIOA->AFR[0] = AFRf(7, 2) | AFRf(7, 3) | AFRf(5, 5) | AFRf(5, 6);
|
||||||
GPIOA->AFR[1] = AFRf(14, 11) | AFRf(14, 12);
|
GPIOA->AFR[1] = AFRf(14, 11) | AFRf(14, 12);
|
||||||
// PA9 - config jumper (PU in), PA10 - USB pullup (PP)
|
GPIOA->MODER = MODER_O(1) | MODER_AF(2) | MODER_AF(3) | MODER_AF(5) | MODER_AF(6) | MODER_I(9) | MODER_O(10)
|
||||||
GPIOA->MODER = MODER_I(9) | MODER_O(10) | MODER_AF(11) | MODER_AF(12) | MODER_AF(13) | MODER_AF(14);
|
| MODER_AF(11) | MODER_AF(12) | MODER_AF(13) | MODER_AF(14);
|
||||||
GPIOA->OSPEEDR = OSPEED_HI(11) | OSPEED_HI(12) | OSPEED_HI(13) | OSPEED_HI(14);
|
GPIOA->OSPEEDR = OSPEED_MED(2) | OSPEED_MED(3) | OSPEED_HI(5) | OSPEED_HI(6)
|
||||||
|
| OSPEED_HI(11) | OSPEED_HI(12) | OSPEED_HI(13) | OSPEED_HI(14);
|
||||||
GPIOA->PUPDR = PUPD_PU(9);
|
GPIOA->PUPDR = PUPD_PU(9);
|
||||||
for(int i = 0; i < 10000; ++i) nop();
|
/********************************************
|
||||||
|
* PB0 * USART1 DE * PP OUT *
|
||||||
|
* PB8 * CAN RX * AF9 *
|
||||||
|
* PB9 * CAN TX * AF9 *
|
||||||
|
* PB10 * USART3 TX * AF7 *
|
||||||
|
* PB11 * USART3 RX * AF7 *
|
||||||
|
* PB14 * USART3 DE * PP OUT *
|
||||||
|
********************************************/
|
||||||
|
GPIOB->AFR[1] = AFRf(9, 8) | AFRf(9, 9) | AFRf(7, 10) | AFRf(7, 11);
|
||||||
|
GPIOB->MODER = MODER_O(0) | MODER_AF(8) | MODER_AF(9) | MODER_AF(10) | MODER_AF(11) | MODER_O(14);
|
||||||
|
GPIOB->OSPEEDR = OSPEED_HI(8) | OSPEED_HI(9) | OSPEED_MED(10) | OSPEED_MED(11);
|
||||||
|
/********************************************
|
||||||
|
* PC4 * USART1 TX * AF7 *
|
||||||
|
* PC5 * USART1 RX * AF7 *
|
||||||
|
* PC10 * UART4 TX * AF5 *
|
||||||
|
* PC11 * UART4 RX * AF5 *
|
||||||
|
* PC12 * UART5 TX * AF5 *
|
||||||
|
********************************************/
|
||||||
|
GPIOC->AFR[0] = AFRf(7, 4) | AFRf(7, 5);
|
||||||
|
GPIOC->AFR[1] = AFRf(5, 10) | AFRf(5, 11) | AFRf(5, 12);
|
||||||
|
GPIOC->MODER = MODER_AF(4) | MODER_AF(5) | MODER_AF(10) | MODER_AF(11) | MODER_AF(12);
|
||||||
|
GPIOC->OSPEEDR = OSPEED_MED(4) | OSPEED_MED(5) | OSPEED_MED(10) | OSPEED_MED(11) | OSPEED_MED(12);
|
||||||
|
/********************************************
|
||||||
|
* PD2 * UART5 RX * AF5 *
|
||||||
|
********************************************/
|
||||||
|
GPIOD->AFR[0] = AFRf(5, 2);
|
||||||
|
GPIOD->MODER = MODER_AF(2);
|
||||||
|
GPIOD->OSPEEDR = OSPEED_MED(2);
|
||||||
|
for(int i = 0; i < 10000; ++i) nop(); // wait a little before reading CFG pin
|
||||||
if(CFG_ON()) Config_mode = 1;
|
if(CFG_ON()) Config_mode = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,10 @@
|
|||||||
#define CFG_pin (1<<9)
|
#define CFG_pin (1<<9)
|
||||||
#define CFG_ON() (CFG_port->IDR & CFG_pin)
|
#define CFG_ON() (CFG_port->IDR & CFG_pin)
|
||||||
|
|
||||||
|
// RS-485 Rx is low level, Tx - high
|
||||||
|
#define RX485(port, pin) do{port->BRR = pin;}while(0)
|
||||||
|
#define TX485(port, pin) do{port->BSRR = pin;}while(0)
|
||||||
|
|
||||||
extern volatile uint32_t Tms;
|
extern volatile uint32_t Tms;
|
||||||
extern uint8_t Config_mode;
|
extern uint8_t Config_mode;
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,16 @@
|
|||||||
* 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/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "can.h"
|
||||||
|
#include "canproto.h"
|
||||||
|
#include "Debug.h"
|
||||||
#include "flash.h"
|
#include "flash.h"
|
||||||
#include "hardware.h"
|
#include "hardware.h"
|
||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
|
#include "spi.h"
|
||||||
|
#include "strfunc.h"
|
||||||
|
#include "usart.h"
|
||||||
#include "usb_dev.h"
|
#include "usb_dev.h"
|
||||||
|
|
||||||
#define MAXSTRLEN RBINSZ
|
#define MAXSTRLEN RBINSZ
|
||||||
@@ -30,6 +37,7 @@ void sys_tick_handler(void){
|
|||||||
|
|
||||||
int main(void){
|
int main(void){
|
||||||
char inbuff[MAXSTRLEN+1];
|
char inbuff[MAXSTRLEN+1];
|
||||||
|
uint8_t oldcdc[InterfacesAmount] = {0};
|
||||||
if(StartHSE()){
|
if(StartHSE()){
|
||||||
SysTick_Config((uint32_t)72000); // 1ms
|
SysTick_Config((uint32_t)72000); // 1ms
|
||||||
}else{
|
}else{
|
||||||
@@ -40,26 +48,56 @@ int main(void){
|
|||||||
hw_setup();
|
hw_setup();
|
||||||
USBPU_OFF();
|
USBPU_OFF();
|
||||||
USB_setup();
|
USB_setup();
|
||||||
|
CAN_setup(the_conf.CANspeed);
|
||||||
|
spi_setup();
|
||||||
//uint32_t ctr = Tms;
|
//uint32_t ctr = Tms;
|
||||||
|
//usb_LineCoding lc = {9600, 0, 0, 8};
|
||||||
|
//for(int i = 0; i < 5; ++i) usart_config(i, &lc); // configure all U[S]ARTs for default data
|
||||||
USBPU_ON();
|
USBPU_ON();
|
||||||
int maxno = (Config_mode) ? ICFG : InterfacesAmount;
|
|
||||||
while(1){
|
while(1){
|
||||||
// Put here code working WITOUT USB connected
|
// Put here code working WITOUT USB connected
|
||||||
if(!usbON) continue;
|
if(!usbON) continue;
|
||||||
for(int i = 0; i < maxno; ++i){ // just echo for first time
|
// and here is code what should run when USB connected
|
||||||
|
usarts_process();
|
||||||
|
DBGpri();
|
||||||
|
/*for(int i = 0; i < 6; ++i){ // just echo for first time
|
||||||
if(!CDCready[i]) continue;
|
if(!CDCready[i]) continue;
|
||||||
int l = USB_receive(i, (uint8_t*)inbuff, MAXSTRLEN);
|
int l = USB_receive(i, (uint8_t*)inbuff, MAXSTRLEN);
|
||||||
if(l) USB_send(i, (uint8_t*)inbuff, l);
|
if(l) USB_send(i, (uint8_t*)inbuff, l);
|
||||||
|
}*/
|
||||||
|
if(CDCready[ICAN]){ // process CAN bus
|
||||||
|
int l = USB_receivestr(ICAN, inbuff, MAXSTRLEN);
|
||||||
|
if(l < 0) USB_sendstr(ICAN, "ERROR: USB buffer overflow or string was too long\n");
|
||||||
|
else if(l) CANcmd_parser(inbuff);
|
||||||
|
canproto_process();
|
||||||
|
}
|
||||||
|
if(CDCready[ISPI]){ // process encoder
|
||||||
|
if(USB_rcvlen(ISPI)){ // new data in USB - ask for new encoder data
|
||||||
|
if(spi_start_enc()) USB_receive(ISPI, (uint8_t*)inbuff, MAXSTRLEN); // clear incoming data
|
||||||
|
}
|
||||||
|
uint32_t val;
|
||||||
|
if(spi_read_enc(&val)){
|
||||||
|
USB_sendstr(ISPI, u2str(val));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// and here is code what should run when USB connected
|
|
||||||
if(Config_mode && CDCready[ICFG]){
|
if(Config_mode && CDCready[ICFG]){
|
||||||
/*if(Tms - ctr > 999){
|
/*if(Tms - ctr > 4999){
|
||||||
ctr = Tms;
|
ctr = Tms;
|
||||||
CFGWR("I'm alive\n");
|
CFGWR("I'm alive\n");
|
||||||
}*/
|
}*/
|
||||||
|
for(int i = 0; i < ICFG; ++i){
|
||||||
|
if(oldcdc[i] != CDCready[i]){
|
||||||
|
CFGWR("Interface "); USB_putbyte(ICFG, '1' + i);
|
||||||
|
USB_putbyte(ICFG, ' ');
|
||||||
|
if(!CDCready[i]) CFGWR("dis");
|
||||||
|
CFGWR("connected\n");
|
||||||
|
oldcdc[i] = CDCready[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
int l = USB_receivestr(ICFG, inbuff, MAXSTRLEN);
|
int l = USB_receivestr(ICFG, inbuff, MAXSTRLEN);
|
||||||
if(l < 0) CFGWR("ERROR: USB buffer overflow or string was too long\n");
|
if(l < 0) CFGWR("ERROR: USB buffer overflow or string was too long\n");
|
||||||
else if(l){
|
else if(l){
|
||||||
|
CFGWR("PARSING...\n");
|
||||||
const char *ans = parse_cmd(inbuff);
|
const char *ans = parse_cmd(inbuff);
|
||||||
if(ans) CFGWRn(ans);
|
if(ans) CFGWRn(ans);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -6,3 +6,4 @@
|
|||||||
#define __thumb2__ 1
|
#define __thumb2__ 1
|
||||||
#define __ARM_ARCH_7M__
|
#define __ARM_ARCH_7M__
|
||||||
#define USB1_16
|
#define USB1_16
|
||||||
|
#define CANPRIVATE__
|
||||||
|
|||||||
@@ -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 18.0.2, 2026-02-12T23:15:35. -->
|
<!-- Written by QtCreator 18.0.2, 2026-02-20T23:37:53. -->
|
||||||
<qtcreator>
|
<qtcreator>
|
||||||
<data>
|
<data>
|
||||||
<variable>EnvironmentId</variable>
|
<variable>EnvironmentId</variable>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user