Compare commits

...

4 Commits

9 changed files with 4003 additions and 0 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "controller-board/fw/libopencm3"]
path = controller-board/fw/libopencm3
url = https://github.com/libopencm3/libopencm3

File diff suppressed because one or more lines are too long

3
controller-board/fw/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
bin/
*.elf
*.bin

View File

@ -0,0 +1,18 @@
PROJECT = controller
BUILD_DIR = bin
#SHARED_DIR =
CFILES = main.c
# TODO - you will need to edit these two lines!
DEVICE=stm32l011f4p6
OOCD_FILE = ft232h.cfg
# You shouldn't have to edit anything below here.
VPATH += $(SHARED_DIR)
INCLUDES += $(patsubst %,-I%, . $(SHARED_DIR))
OPENCM3_DIR=libopencm3
include $(OPENCM3_DIR)/mk/genlink-config.mk
include rules.mk
include $(OPENCM3_DIR)/mk/genlink-rules.mk

View File

@ -0,0 +1,20 @@
adapter driver ftdi
transport select swd
ftdi_vid_pid 0x0403 0x6014
ftdi_layout_init 0xfff8 0xfffb
ftdi_layout_signal SWD_EN -data 0
ftdi_layout_signal nTRST -data 0x0100 -oe 0x0100
ftdi_layout_signal nSRST -data 0x0200 -oe 0x0200
source [find target/stm32l0.cfg]
$_TARGETNAME configure -event reset-end {
echo "Remapping Flash to address 0x00000000"
# RCC_APB2ENR <= 0x1
mww 0x40021034 0x1
# SYSCFG_CFGR1 <= 0x0
mww 0x40010000 0x0
}

View File

@ -0,0 +1,69 @@
EXTERN(vector_table)
ENTRY(reset_handler)
MEMORY
{
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 2K
rom (rx) : ORIGIN = 0x08000000, LENGTH = 16K
}
SECTIONS
{
.text : {
*(.vectors)
*(.text*)
. = ALIGN(4);
*(.rodata*)
. = ALIGN(4);
} >rom
.preinit_array : {
. = ALIGN(4);
__preinit_array_start = .;
KEEP (*(.preinit_array))
__preinit_array_end = .;
} >rom
.init_array : {
. = ALIGN(4);
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
} >rom
.fini_array : {
. = ALIGN(4);
__fini_array_start = .;
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
__fini_array_end = .;
} >rom
.ARM.extab : {
*(.ARM.extab*)
} >rom
.ARM.exidx : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >rom
. = ALIGN(4);
_etext = .;
.noinit (NOLOAD) : {
*(.noinit*)
} >ram
. = ALIGN(4);
.data : {
_data = .;
*(.data*)
*(.ramtext*)
. = ALIGN(4);
_edata = .;
} >ram AT >rom
_data_loadaddr = LOADADDR(.data);
.bss : {
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} >ram
/DISCARD/ : { *(.eh_frame) }
. = ALIGN(4);
end = .;
}
PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram));

@ -0,0 +1 @@
Subproject commit aeb3cee0235dd3e8784c003ff1af82ce44b7b38c

280
controller-board/fw/main.c Normal file
View File

@ -0,0 +1,280 @@
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/cm3/systick.h>
enum direction {
DIR_CLOCKWISE,
DIR_COUNTERCLOCKWISE
};
volatile struct {
bool active;
bool done;
int step_count;
enum direction dir;
int counter;
int increment;
int threshold;
int phase;
} stepper = {
true, false, 1000000, DIR_CLOCKWISE, 0, 1, 2, 0
};
volatile struct {
bool active;
bool done;
enum direction dir;
int count;
int tocount;
} brushed = {
false, false, DIR_CLOCKWISE, 0, 0
};
int main(void) {
rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_GPIOB);
rcc_periph_clock_enable(RCC_USART2);
// setup PA5-7 and PB1 for the stepper motor gates
gpio_clear(GPIOA, GPIO5|GPIO6|GPIO7);
gpio_clear(GPIOB, GPIO1);
gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO5);
gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO6);
gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO7);
gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO1);
// setup PA2/3 for USART2_RX and USART2_TX
gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2|GPIO3);
gpio_set_af(GPIOA, GPIO_AF4, GPIO2|GPIO3);
// setup USART2 stuff
usart_set_baudrate(USART2, 115200);
usart_set_databits(USART2, 8);
usart_set_stopbits(USART2, USART_STOPBITS_1);
usart_set_mode(USART2, USART_MODE_TX_RX);
usart_set_parity(USART2, USART_PARITY_NONE);
usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
usart_enable(USART2);
#if 0
// if I need more speed than the internal 2 MHz
// setup PA0 as a clock input
rcc_bypass_enable(RCC_HSE);
rcc_osc_on(RCC_HSE);
rcc_wait_for_osc_ready(RCC_HSE);
rcc_set_sysclk_source(RCC_HSE);
#endif
// setup systick interrupt
systick_interrupt_disable();
systick_counter_disable();
systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
systick_set_reload(rcc_ahb_frequency/1000-1);
systick_clear();
systick_interrupt_enable();
systick_counter_enable();
int32_t val = 0;
while (1) {
if (stepper.done) {
stepper.done = false;
usart_send_blocking(USART2, 's');
}
if (brushed.done) {
brushed.done = false;
usart_send_blocking(USART2, 'r');
}
if ((USART_ISR(USART2) & USART_ISR_RXNE) != 0) {
// TODO process byte
uint8_t rx = usart_recv(USART2);
// if it's a hexadecimal echo back and append it to val
if ((rx >= '0') && (rx <= '9')) {
usart_send_blocking(USART2, rx); // echo back
val <<= 4;
val |= (rx - '0');
} else if ((rx >= 'a') && (rx <= 'f')) {
usart_send_blocking(USART2, rx); // echo back
val <<= 4;
val |= (rx - 'a' + 10);
} else if ((rx >= 'A') && (rx <= 'F')) {
usart_send_blocking(USART2, rx); // echo back
val <<= 4;
val |= (rx - 'A' + 10);
} else {
switch (rx) {
case 's': // get status
if (stepper.active) {
usart_send_blocking(USART2, '!');
} else {
usart_send_blocking(USART2, '@');
}
if (brushed.active) {
usart_send_blocking(USART2, '#');
} else {
usart_send_blocking(USART2, '$');
}
break;
case 'w': // start stepper
usart_send_blocking(USART2, '!');
stepper.active = true;
break;
case 'W': // stop stepper
stepper.active = false;
usart_send_blocking(USART2, '@');
break;
case 'r': // start brushed motor
usart_send_blocking(USART2, '#');
brushed.count = 0;
brushed.active = true;
break;
case 'R': // stop brushed motor
usart_send_blocking(USART2, '$');
brushed.count = 0;
brushed.active = false;
break;
case 't': // read stepper counter
// TODO
break;
case 'T': // write stepper counter
// TODO
break;
case 'y': // read stepper increment
// TODO
break;
case 'Y': // write stepper increment
// TODO
break;
case 'u': // read stepper threshold
// TODO
break;
case 'U': // write stepper threshold
// TODO
break;
case 'i': // set stepper direction to clockwise
stepper.dir = DIR_CLOCKWISE;
usart_send_blocking(USART2, 'i');
break;
case 'I': // set stepper direction to counterclockwise
stepper.dir = DIR_COUNTERCLOCKWISE;
usart_send_blocking(USART2, 'I');
break;
case 'o': // write brushed counter
// TODO
break;
case 'O': // read brushed counter
// TODO
break;
case 'p': // set brushed direction to clockwise
brushed.dir = DIR_CLOCKWISE;
usart_send_blocking(USART2, 'p');
break;
case 'P': // set brushed direction to counterclockwise
brushed.dir = DIR_COUNTERCLOCKWISE;
usart_send_blocking(USART2, 'P');
break;
}
}
}
}
}
const struct {
bool active;
bool positive;
} phase_lut[][2] = {
{{true, true}, {false, true}}, // A+
//{{true, true}, {true, true}}, // A+, B+
{{false, true}, {true, true}}, // B+
//{{true, false}, {true, true}}, // A-, B+
{{true, false}, {false, true}}, // A-
//{{true, false}, {true, false}}, // A-, B-
{{false, false}, {true, false}}, // B-
//{{true, true}, {true, false}}, // A+, B-
};
void sys_tick_handler(void);
void sys_tick_handler(void) {
if (!stepper.active) {
gpio_clear(GPIOA, GPIO5|GPIO6|GPIO7);
gpio_clear(GPIOB, GPIO1);
} else {
stepper.counter += stepper.increment;
if (stepper.counter >= stepper.threshold) {
--stepper.step_count;
stepper.counter -= stepper.threshold;
if (stepper.dir == DIR_CLOCKWISE) {
++stepper.phase;
} else {
--stepper.phase;
}
if (stepper.phase < 0) {
stepper.phase = 3;
}
if (stepper.phase >= 4) {
stepper.phase = 0;
}
}
if (stepper.step_count <= 0) {
stepper.counter = 0;
stepper.active = false;
stepper.done = true;
// power down stepper motor; TODO maybe lock its position?
gpio_clear(GPIOA, GPIO5|GPIO6|GPIO7);
gpio_clear(GPIOB, GPIO1);
} else {
// write to GPIO pins
// TODO maybe use a set of OR/AND masks instead? this works but probably
// isn't constant time
if (phase_lut[stepper.phase][0].active) {
if (phase_lut[stepper.phase][0].positive) {
gpio_clear(GPIOA, GPIO6);
gpio_set(GPIOB, GPIO1);
} else {
gpio_set(GPIOA, GPIO6);
gpio_clear(GPIOB, GPIO1);
}
} else {
gpio_clear(GPIOA, GPIO6);
gpio_clear(GPIOB, GPIO1);
}
if (phase_lut[stepper.phase][1].active) {
if (phase_lut[stepper.phase][1].positive) {
gpio_clear(GPIOA, GPIO7);
gpio_set(GPIOA, GPIO5);
} else {
gpio_set(GPIOA, GPIO7);
gpio_clear(GPIOA, GPIO5);
}
} else {
gpio_clear(GPIOA, GPIO7);
gpio_clear(GPIOA, GPIO5);
}
}
}
if (brushed.active) {
if (brushed.count == 0) {
// TODO toggle pins
}
++brushed.count;
if (brushed.count >= brushed.tocount) {
// TODO toggle pins
brushed.count = 0;
brushed.active = false;
brushed.done = true;
}
}
// TODO check limit switches
}

View File

@ -0,0 +1,177 @@
# This version of rules.mk expects the following to be defined before
# inclusion..
### REQUIRED ###
# OPENCM3_DIR - duh
# PROJECT - will be the basename of the output elf, eg usb-gadget0-stm32f4disco
# CFILES - basenames only, eg main.c blah.c
# CXXFILES - same for C++ files. Must have cxx suffix!
# DEVICE - the full device name, eg stm32f405ret6
# _or_
# LDSCRIPT - full path, eg ../../examples/stm32/f4/stm32f4-discovery/stm32f4-discovery.ld
# OPENCM3_LIB - the basename, eg: opencm3_stm32f4
# OPENCM3_DEFS - the target define eg: -DSTM32F4
# ARCH_FLAGS - eg, -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
# (ie, the full set of cpu arch flags, _none_ are defined in this file)
#
### OPTIONAL ###
# INCLUDES - fully formed -I paths, if you want extra, eg -I../shared
# BUILD_DIR - defaults to bin, should set this if you are building multiarch
# OPT - full -O flag, defaults to -Os
# CSTD - defaults -std=c99
# CXXSTD - no default.
# OOCD_INTERFACE - eg stlink-v2
# OOCD_TARGET - eg stm32f4x
# both only used if you use the "make flash" target.
# OOCD_FILE - eg my.openocd.cfg
# This overrides interface/target above, and is used as just -f FILE
### TODO/FIXME/notes ###
# No support for stylecheck.
# No support for BMP/texane/random flash methods, no plans either
# No support for magically finding the library.
# C++ hasn't been actually tested with this..... sorry bout that. ;)
# Second expansion/secondary not set, add this if you need them.
BUILD_DIR ?= bin
OPT ?= -Os
CSTD ?= -std=c99
# Be silent per default, but 'make V=1' will show all compiler calls.
# If you're insane, V=99 will print out all sorts of things.
V?=0
ifeq ($(V),0)
Q := @
NULL := 2>/dev/null
endif
# Tool paths.
PREFIX ?= arm-none-eabi-
CC = $(PREFIX)gcc
CXX = $(PREFIX)g++
LD = $(PREFIX)gcc
OBJCOPY = $(PREFIX)objcopy
OBJDUMP = $(PREFIX)objdump
OOCD ?= openocd
OPENCM3_INC = $(OPENCM3_DIR)/include
# Inclusion of library header files
INCLUDES += $(patsubst %,-I%, . $(OPENCM3_INC) )
OBJS = $(CFILES:%.c=$(BUILD_DIR)/%.o)
OBJS += $(CXXFILES:%.cxx=$(BUILD_DIR)/%.o)
OBJS += $(AFILES:%.S=$(BUILD_DIR)/%.o)
GENERATED_BINS = $(PROJECT).elf $(PROJECT).bin $(PROJECT).map $(PROJECT).list $(PROJECT).lss
TGT_CPPFLAGS += -MD
TGT_CPPFLAGS += -Wall -Wundef $(INCLUDES)
TGT_CPPFLAGS += $(INCLUDES) $(OPENCM3_DEFS)
TGT_CFLAGS += $(OPT) $(CSTD) -ggdb3
TGT_CFLAGS += $(ARCH_FLAGS)
TGT_CFLAGS += -fno-common
TGT_CFLAGS += -ffunction-sections -fdata-sections
TGT_CFLAGS += -Wextra -Wshadow -Wno-unused-variable -Wimplicit-function-declaration
TGT_CFLAGS += -Wredundant-decls -Wstrict-prototypes -Wmissing-prototypes
TGT_CXXFLAGS += $(OPT) $(CXXSTD) -ggdb3
TGT_CXXFLAGS += $(ARCH_FLAGS)
TGT_CXXFLAGS += -fno-common
TGT_CXXFLAGS += -ffunction-sections -fdata-sections
TGT_CXXFLAGS += -Wextra -Wshadow -Wredundant-decls -Weffc++
TGT_ASFLAGS += $(OPT) $(ARCH_FLAGS) -ggdb3
TGT_LDFLAGS += -T$(LDSCRIPT) -L$(OPENCM3_DIR)/lib -nostartfiles
TGT_LDFLAGS += $(ARCH_FLAGS)
TGT_LDFLAGS += -specs=nano.specs
TGT_LDFLAGS += -Wl,--gc-sections
# OPTIONAL
#TGT_LDFLAGS += -Wl,-Map=$(PROJECT).map
ifeq ($(V),99)
TGT_LDFLAGS += -Wl,--print-gc-sections
endif
# Linker script generator fills this in for us.
ifeq (,$(DEVICE))
LDLIBS += -l$(OPENCM3_LIB)
endif
# nosys is only in newer gcc-arm-embedded...
#LDLIBS += -specs=nosys.specs
LDLIBS += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group
# Burn in legacy hell fortran modula pascal yacc idontevenwat
.SUFFIXES:
.SUFFIXES: .c .S .h .o .cxx .elf .bin .list .lss
# Bad make, never *ever* try to get a file out of source control by yourself.
%: %,v
%: RCS/%,v
%: RCS/%
%: s.%
%: SCCS/s.%
all: $(PROJECT).elf $(PROJECT).bin
flash: $(PROJECT).flash
# error if not using linker script generator
ifeq (,$(DEVICE))
$(LDSCRIPT):
ifeq (,$(wildcard $(LDSCRIPT)))
$(error Unable to find specified linker script: $(LDSCRIPT))
endif
else
# if linker script generator was used, make sure it's cleaned.
GENERATED_BINS += $(LDSCRIPT)
endif
# Need a special rule to have a bin dir
$(BUILD_DIR)/%.o: %.c
@printf " CC\t$<\n"
@mkdir -p $(dir $@)
$(Q)$(CC) $(TGT_CFLAGS) $(CFLAGS) $(TGT_CPPFLAGS) $(CPPFLAGS) -o $@ -c $<
$(BUILD_DIR)/%.o: %.cxx
@printf " CXX\t$<\n"
@mkdir -p $(dir $@)
$(Q)$(CXX) $(TGT_CXXFLAGS) $(CXXFLAGS) $(TGT_CPPFLAGS) $(CPPFLAGS) -o $@ -c $<
$(BUILD_DIR)/%.o: %.S
@printf " AS\t$<\n"
@mkdir -p $(dir $@)
$(Q)$(CC) $(TGT_ASFLAGS) $(ASFLAGS) $(TGT_CPPFLAGS) $(CPPFLAGS) -o $@ -c $<
$(PROJECT).elf: $(OBJS) $(LDSCRIPT) $(LIBDEPS)
@printf " LD\t$@\n"
$(Q)$(LD) $(TGT_LDFLAGS) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@
%.bin: %.elf
@printf " OBJCOPY\t$@\n"
$(Q)$(OBJCOPY) -O binary $< $@
%.lss: %.elf
$(OBJDUMP) -h -S $< > $@
%.list: %.elf
$(OBJDUMP) -S $< > $@
%.flash: %.elf
@printf " FLASH\t$<\n"
ifeq (,$(OOCD_FILE))
$(Q)(echo "halt; program $(realpath $(*).elf) verify reset" | nc -4 localhost 4444 2>/dev/null) || \
$(OOCD) -f interface/$(OOCD_INTERFACE).cfg \
-f target/$(OOCD_TARGET).cfg \
-c "program $(realpath $(*).elf) verify reset exit" \
$(NULL)
else
$(Q)(echo "halt; program $(realpath $(*).elf) verify reset" | nc -4 localhost 4444 2>/dev/null) || \
$(OOCD) -f $(OOCD_FILE) \
-c "program $(realpath $(*).elf) verify reset exit" \
$(NULL)
endif
clean:
rm -rf $(BUILD_DIR) $(GENERATED_BINS)
.PHONY: all clean flash
-include $(OBJS:.o=.d)