Verison 1.0.3 Release
Based on Buildbotics 0.4.14
This commit is contained in:
14
src/avr/.gitignore
vendored
Normal file
14
src/avr/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Backup files
|
||||
*~
|
||||
\#*
|
||||
|
||||
build
|
||||
.dep
|
||||
|
||||
/*.eep
|
||||
/*.hex
|
||||
/*.elf
|
||||
/*.lss
|
||||
/*.map
|
||||
|
||||
*.o
|
||||
56
src/avr/BezierMath.md
Normal file
56
src/avr/BezierMath.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Cubic Bezier
|
||||
|
||||
f(x) = A(1 - x)^3 + 3B(1 - x)^2 x + 3C(1 - x) x^2 + Dx^3
|
||||
|
||||
-Ax^3 + 3Ax^2 - 3Ax + A
|
||||
3Bx^3 - 6Bx^2 + 3Bx
|
||||
-3Cx^3 + 3Cx^2
|
||||
Dx^3
|
||||
|
||||
|
||||
f(x) = (-A + 3B -3C + D)x^3 + (3A - 6B + 3C)x^2 + (-3A + 3B)x + A
|
||||
|
||||
a = -A + 3B - 3C + D
|
||||
b = 3A - 6B + 3C
|
||||
c = -3A + 3B
|
||||
d = A
|
||||
|
||||
f(x) = ax^3 + bx^2 + cx + d
|
||||
|
||||
integral f(x) dx = a/4 x^4 + b/3 x^3 + c/2 x^2 + dx + E
|
||||
|
||||
= (-A + 3B - 3C + D)/4 x^4 + (A - 2B + B) x^3 + 3/2 (B - A) x^2 + Ax + E
|
||||
|
||||
# Quintic Bezier
|
||||
|
||||
A(1 - x)^5 + 5A(1 - x)^4 x + 10A(1 - x)^3 x^2 + 10B(1 - x)^2 x^3 +
|
||||
5B(1 - x)x^4 + Bx^5
|
||||
|
||||
(-6A + 6B)x^5 + (15A - 15B)x^4 + (-10A + 10B)x^3 + A
|
||||
|
||||
6(B - A)x^5 + 15(A - B)x^4 + 10(B - A)x^3 + A
|
||||
|
||||
x^3 (6(B - A)x^2 + 15(A - B)x + 10(B - A)) + A
|
||||
|
||||
a = 6(B - A)
|
||||
b = -15(B - A)
|
||||
c = 10(B - A)
|
||||
d = A
|
||||
|
||||
f(x) = ax^5 + bx^4 + cx^3 + d
|
||||
|
||||
f(x) = (ax^2 + bx + c)x^3 + d
|
||||
|
||||
|
||||
integral f(x) = a/6 x^6 + b/5 x^5 + c/4 x^4 + dx + e
|
||||
|
||||
= (B - A)x^6 - 3(B - A)x^5 + 5/2(B - A)x^4 + Ax + e
|
||||
|
||||
= (B - A)x^4 (x^2 - 3x + 5/2) + Ax + e
|
||||
|
||||
A = 0
|
||||
B = 1
|
||||
e = 0
|
||||
|
||||
f(x) = 6x^5 -15x^4 + 10x^3
|
||||
int f(x) dx = x^6 - 3x^5 + 5/2x^4 + C
|
||||
40
src/avr/Makefile
Normal file
40
src/avr/Makefile
Normal file
@@ -0,0 +1,40 @@
|
||||
# Makefile for the project Bulidbotics firmware
|
||||
PROJECT = bbctrl-avr-firmware
|
||||
MCU = atxmega192a3u
|
||||
CLOCK = 32000000
|
||||
|
||||
# SRC
|
||||
SRC = $(wildcard src/*.c) $(wildcard src/*.cpp) $(wildcard src/vfd/*.c)
|
||||
OBJ := $(patsubst src/%.c,build/%.o,$(SRC))
|
||||
OBJ := $(patsubst src/%.cpp,build/%.o,$(OBJ))
|
||||
OBJ := $(patsubst src/vfd/%.c,build/vfd/%.o,$(OBJ))
|
||||
JSON = vars command messages
|
||||
JSON := $(patsubst %,build/%.json,$(JSON))
|
||||
|
||||
all: $(PROJECT).hex $(JSON) size
|
||||
|
||||
include Makefile.common
|
||||
|
||||
CFLAGS += -Isrc
|
||||
|
||||
# Build
|
||||
$(PROJECT).elf: $(OBJ)
|
||||
$(CC) $(LDFLAGS) $(OBJ) $(LIBS) -o $@
|
||||
|
||||
|
||||
# JSON
|
||||
build/%.json: src/%.json.in src/%.def
|
||||
cpp -Isrc $< | sed "/^#.*$$/d;s/'\(.\)'/\"\1\"/g" > $@
|
||||
|
||||
# Program
|
||||
init:
|
||||
$(MAKE) erase
|
||||
-$(MAKE) fuses
|
||||
$(MAKE) fuses
|
||||
$(MAKE) program-boot
|
||||
$(MAKE) program
|
||||
|
||||
program-boot:
|
||||
$(MAKE) -C ../boot program
|
||||
|
||||
.PHONY: all init program-boot
|
||||
115
src/avr/Makefile.common
Normal file
115
src/avr/Makefile.common
Normal file
@@ -0,0 +1,115 @@
|
||||
# Compile flags
|
||||
CC = avr-g++
|
||||
|
||||
COMMON = -mmcu=$(MCU) -flto -fwhole-program
|
||||
|
||||
CFLAGS += $(COMMON)
|
||||
CFLAGS += -Wall -Werror
|
||||
CFLAGS += -Wno-error=strict-aliasing # for _invsqrt
|
||||
CFLAGS += -std=gnu++98 -DF_CPU=$(CLOCK)UL -O3
|
||||
CFLAGS += -funsigned-bitfields -fpack-struct -fshort-enums -funsigned-char
|
||||
CFLAGS += -MD -MP -MT $@ -MF build/dep/$(@F).d
|
||||
CFLAGS += -D__STDC_LIMIT_MACROS
|
||||
|
||||
# Linker flags
|
||||
LDFLAGS += $(COMMON) -Wl,-u,vfprintf -lprintf_flt -lm
|
||||
LIBS += -lm
|
||||
|
||||
# EEPROM flags
|
||||
EEFLAGS += -j .eeprom
|
||||
EEFLAGS += --set-section-flags=.eeprom="alloc,load"
|
||||
EEFLAGS += --change-section-lma .eeprom=0 --no-change-warnings
|
||||
|
||||
# Programming flags
|
||||
ifndef (PROGRAMMER)
|
||||
PROGRAMMER = avrispmkII
|
||||
#PROGRAMMER = jtag3pdi
|
||||
endif
|
||||
PDEV = usb
|
||||
AVRDUDE_OPTS = -c $(PROGRAMMER) -p $(MCU) -P $(PDEV)
|
||||
|
||||
FUSE0=0xff
|
||||
FUSE1=0x00
|
||||
FUSE2=0xbe
|
||||
FUSE4=0xff
|
||||
FUSE5=0xeb
|
||||
|
||||
# Compile
|
||||
build/%.o: src/%.c
|
||||
@mkdir -p $(shell dirname $@)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
build/%.o: src/%.cpp
|
||||
@mkdir -p $(shell dirname $@)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
build/%.o: src/%.S
|
||||
@mkdir -p $(shell dirname $@)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
# Link
|
||||
%.hex: %.elf
|
||||
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature $< $@
|
||||
|
||||
%.eep: %.elf
|
||||
avr-objcopy $(EEFLAGS) -O ihex $< $@
|
||||
|
||||
%.lss: %.elf
|
||||
avr-objdump -h -S $< > $@
|
||||
|
||||
size: $(PROJECT).elf
|
||||
@for X in A B C; do\
|
||||
echo '****************************************************************' ;\
|
||||
avr-size -$$X --mcu=$(MCU) $(PROJECT).elf ;\
|
||||
done
|
||||
|
||||
data-usage: $(PROJECT).elf
|
||||
avr-nm -CS --size-sort -t decimal $(PROJECT).elf | grep ' [BbDd] '
|
||||
|
||||
|
||||
prog-usage: $(PROJECT).elf
|
||||
avr-nm -CS --size-sort -t decimal $(PROJECT).elf | grep -v ' [BbDd] '
|
||||
|
||||
# Program
|
||||
reset:
|
||||
avrdude $(AVRDUDE_OPTS)
|
||||
|
||||
erase:
|
||||
avrdude $(AVRDUDE_OPTS) -e
|
||||
|
||||
program: $(PROJECT).hex
|
||||
avrdude $(AVRDUDE_OPTS) -U flash:w:$(PROJECT).hex:i
|
||||
|
||||
verify: $(PROJECT).hex
|
||||
avrdude $(AVRDUDE_OPTS) -U flash:v:$(PROJECT).hex:i
|
||||
|
||||
fuses:
|
||||
avrdude $(AVRDUDE_OPTS) -U fuse0:w:$(FUSE0):m -U fuse1:w:$(FUSE1):m \
|
||||
-U fuse2:w:$(FUSE2):m -U fuse4:w:$(FUSE4):m -U fuse5:w:$(FUSE5):m
|
||||
|
||||
read_fuses:
|
||||
avrdude $(AVRDUDE_OPTS) -q -q -U fuse0:r:-:h -U fuse1:r:-:h -U fuse2:r:-:h \
|
||||
-U fuse4:r:-:h -U fuse5:r:-:h
|
||||
|
||||
signature:
|
||||
avrdude $(AVRDUDE_OPTS) -q -q -U signature:r:-:h
|
||||
|
||||
prodsig:
|
||||
avrdude $(AVRDUDE_OPTS) -q -q -U prodsig:r:-:h
|
||||
|
||||
usersig:
|
||||
avrdude $(AVRDUDE_OPTS) -q -q -U usersig:r:-:h
|
||||
|
||||
# Clean
|
||||
tidy:
|
||||
rm -f $(shell find -name \*~ -o -name \#\*)
|
||||
|
||||
clean: tidy
|
||||
rm -rf $(PROJECT).elf $(PROJECT).hex $(PROJECT).eep $(PROJECT).lss \
|
||||
$(PROJECT).map build
|
||||
|
||||
.PHONY: tidy clean size reset erase program fuses read_fuses prodsig
|
||||
.PHONY: signature usersig data-usage prog-usage
|
||||
|
||||
# Dependencies
|
||||
-include $(shell mkdir -p build/dep) $(wildcard build/dep/*)
|
||||
18
src/avr/README.md
Normal file
18
src/avr/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
The Buildbotics firmware is a 4 axis motion control system designed for
|
||||
high-performance on small to mid-sized machines. It operates in conjunction
|
||||
with the Buildbotics RaspberryPi firmware.
|
||||
|
||||
# Build Instructions
|
||||
To build in Linux run:
|
||||
|
||||
make
|
||||
|
||||
Other make commands are:
|
||||
|
||||
* **size** - Display program and data sizes
|
||||
* **program** - program using AVR dude and an avrispmkII
|
||||
* **erase** - Erase chip
|
||||
* **fuses** - Write AVR fuses bytes
|
||||
* **read_fuses** - Read and print AVR fuse bytes
|
||||
* **clean** - Remove build files
|
||||
* **tidy** - Remove backup files
|
||||
63
src/avr/data_usage.py
Normal file
63
src/avr/data_usage.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
################################################################################
|
||||
# #
|
||||
# This file is part of the Buildbotics firmware. #
|
||||
# #
|
||||
# Copyright (c) 2015 - 2018, Buildbotics LLC #
|
||||
# All rights reserved. #
|
||||
# #
|
||||
# This file ("the software") is free software: you can redistribute it #
|
||||
# and/or modify it under the terms of the GNU General Public License, #
|
||||
# version 2 as published by the Free Software Foundation. You should #
|
||||
# have received a copy of the GNU General Public License, version 2 #
|
||||
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# The software is distributed in the hope that it will be useful, but #
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
|
||||
# Lesser General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU Lesser General Public #
|
||||
# License along with the software. If not, see #
|
||||
# <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# For information regarding this software email: #
|
||||
# "Joseph Coffland" <joseph@buildbotics.com> #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
|
||||
lineRE = r'%(addr)s '
|
||||
command = 'avr-objdump -j .bss -t buildbotics.elf'
|
||||
|
||||
proc = subprocess.Popen(shlex.split(command), stdout = subprocess.PIPE)
|
||||
|
||||
out, err = proc.communicate()
|
||||
|
||||
if proc.returncode:
|
||||
print(out)
|
||||
raise Exception('command failed')
|
||||
|
||||
def get_sizes(data):
|
||||
for line in data.decode().split('\n'):
|
||||
if not re.match(r'^[0-9a-f]{8} .*', line): continue
|
||||
|
||||
size, name = int(line[22:30], 16), line[31:]
|
||||
if not size: continue
|
||||
|
||||
yield (size, name)
|
||||
|
||||
sizes = sorted(get_sizes(out))
|
||||
total = sum(x[0] for x in sizes)
|
||||
|
||||
for size, name in sizes:
|
||||
print('% 6d %5.2f%% %s' % (size, size / total * 100, name))
|
||||
|
||||
print('-' * 40)
|
||||
print('% 6d Total' % total)
|
||||
1
src/avr/emu/.gitignore
vendored
Normal file
1
src/avr/emu/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
bbemu
|
||||
38
src/avr/emu/Makefile
Normal file
38
src/avr/emu/Makefile
Normal file
@@ -0,0 +1,38 @@
|
||||
TARGET = bbemu
|
||||
|
||||
SRC:=$(wildcard ../src/*.c) $(wildcard ../src/*.cpp)
|
||||
OBJ:=$(patsubst %.cpp,%.o,$(patsubst %.c,%.o,$(SRC)))
|
||||
OBJ:=$(patsubst ../src/%,build/%,$(OBJ))
|
||||
SRC+=src/emu.c
|
||||
OBJ+=build/emu.o
|
||||
|
||||
CFLAGS = -I../src -Isrc -Wall -Werror -DDEBUG -g -std=gnu++98
|
||||
CFLAGS += -MD -MP -MT $@ -MF build/$(@F).d
|
||||
CFLAGS += -DF_CPU=32000000 -Wno-class-memaccess -pthread
|
||||
LDFLAGS = -lm -pthread
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
g++ -o $@ $(OBJ) $(LDFLAGS)
|
||||
|
||||
build/%.o: ../src/%.c
|
||||
g++ -c -o $@ $(CFLAGS) $<
|
||||
|
||||
build/%.o: src/%.c
|
||||
g++ -c -o $@ $(CFLAGS) $<
|
||||
|
||||
build/%.o: ../src/%.cpp
|
||||
g++ -c -o $@ $(CFLAGS) $<
|
||||
|
||||
# Clean
|
||||
tidy:
|
||||
rm -f $(shell find -name \*~ -o -name \#\*)
|
||||
|
||||
clean: tidy
|
||||
rm -rf $(TARGET) build
|
||||
|
||||
.PHONY: tidy clean all
|
||||
|
||||
# Dependencies
|
||||
-include $(shell mkdir -p build) $(wildcard build/*.d)
|
||||
34
src/avr/emu/src/avr/eeprom.h
Normal file
34
src/avr/emu/src/avr/eeprom.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define EEMEM
|
||||
|
||||
#define eeprom_update_word(PTR, VAL) *(PTR) = (VAL)
|
||||
#define eeprom_read_word(PTR) *(PTR)
|
||||
#define eeprom_is_ready() true
|
||||
35
src/avr/emu/src/avr/interrupt.h
Normal file
35
src/avr/emu/src/avr/interrupt.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "io.h"
|
||||
|
||||
void cli();
|
||||
void sei();
|
||||
|
||||
#define ISR(X) void __##X()
|
||||
7718
src/avr/emu/src/avr/io.h
Normal file
7718
src/avr/emu/src/avr/io.h
Normal file
File diff suppressed because it is too large
Load Diff
41
src/avr/emu/src/avr/pgmspace.h
Normal file
41
src/avr/emu/src/avr/pgmspace.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define PRPSTR "s"
|
||||
#define PROGMEM
|
||||
#define PGM_P const char *
|
||||
#define PSTR(X) X
|
||||
#define vfprintf_P vfprintf
|
||||
#define printf_P printf
|
||||
#define puts_P puts
|
||||
#define sprintf_P sprintf
|
||||
#define strcmp_P strcmp
|
||||
#define pgm_read_ptr(x) *(x)
|
||||
#define pgm_read_word(x) *(x)
|
||||
#define pgm_read_byte(x) *(x)
|
||||
33
src/avr/emu/src/avr/wdt.h
Normal file
33
src/avr/emu/src/avr/wdt.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define WDTO_250MS 0
|
||||
#define wdt_enable(...)
|
||||
#define wdt_disable()
|
||||
#define wdt_reset()
|
||||
163
src/avr/emu/src/emu.c
Normal file
163
src/avr/emu/src/emu.c
Normal file
@@ -0,0 +1,163 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <avr/io.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
|
||||
void __SPIC_INT_vect(); // DRV8711 SPI
|
||||
void __I2C_ISR(); // I2C from RPi
|
||||
void __ADCA_CH0_vect(); // Analog input
|
||||
void __ADCA_CH1_vect(); // Analog input
|
||||
void __RS485_DRE_vect(); // RS848
|
||||
void __RS485_TXC_vect(); // RS848
|
||||
void __RS485_RXC_vect(); // RS848
|
||||
void __SERIAL_DRE_vect(); // Serial to RPi
|
||||
void __SERIAL_RXC_vect(); // Serial from RPi
|
||||
void __STEP_LOW_LEVEL_ISR(); // Stepper lo interrupt
|
||||
void __STEP_TIMER_ISR(); // Stepper hi interrupt
|
||||
void __RTC_OVF_vect(); // RTC tick
|
||||
|
||||
void motor_emulate_steps(int motor);
|
||||
|
||||
extern int __argc;
|
||||
extern char **__argv;
|
||||
|
||||
|
||||
volatile uint8_t io_mem[4096] = {0};
|
||||
|
||||
|
||||
bool fast = false;
|
||||
int serialByte = -1;
|
||||
uint8_t i2cData[I2C_MAX_DATA];
|
||||
int i2cIndex = 0;
|
||||
bool haveI2C = false;
|
||||
fd_set readFDs;
|
||||
|
||||
|
||||
void cli() {}
|
||||
void sei() {}
|
||||
|
||||
|
||||
void emu_init() {
|
||||
// Parse command line args
|
||||
for (int i = 0; i < __argc; i++)
|
||||
if (strcmp(__argv[i], "--fast") == 0) fast = true;
|
||||
|
||||
// Mark clocks ready
|
||||
OSC.STATUS = OSC_XOSCRDY_bm | OSC_PLLRDY_bm | OSC_RC32KRDY_bm;
|
||||
|
||||
// So usart_flush() returns
|
||||
SERIAL_PORT.STATUS = USART_DREIF_bm | USART_TXCIF_bm;
|
||||
|
||||
// Clear motor fault
|
||||
PIN_PORT(MOTOR_FAULT_PIN)->IN |= PIN_BM(MOTOR_FAULT_PIN);
|
||||
|
||||
FD_ZERO(&readFDs);
|
||||
}
|
||||
|
||||
|
||||
void emu_callback() {
|
||||
fflush(stdout);
|
||||
|
||||
if (RST.CTRL == RST_SWRST_bm) exit(0);
|
||||
|
||||
struct timeval t = {0, fast ? 0 : 1000};
|
||||
bool readData = true;
|
||||
while (readData) {
|
||||
readData = false;
|
||||
|
||||
// Try to read
|
||||
FD_SET(0, &readFDs);
|
||||
if (fcntl(3, F_GETFL) != -1) FD_SET(3, &readFDs);
|
||||
|
||||
if (0 < select(4, &readFDs, 0, 0, &t)) {
|
||||
uint8_t data;
|
||||
|
||||
if (serialByte == -1 && FD_ISSET(0, &readFDs) && read(0, &data, 1) == 1)
|
||||
serialByte = data;
|
||||
|
||||
if (!haveI2C && FD_ISSET(3, &readFDs) && read(3, &data, 1) == 1) {
|
||||
if (data == '\n') haveI2C = true;
|
||||
else if (i2cIndex < I2C_MAX_DATA) i2cData[i2cIndex++] = data;
|
||||
}
|
||||
}
|
||||
|
||||
// Send message to i2c port
|
||||
if (haveI2C && (I2C_DEV.SLAVE.CTRLA & TWI_SLAVE_INTLVL_LO_gc)) {
|
||||
// START
|
||||
I2C_DEV.SLAVE.STATUS = TWI_SLAVE_APIF_bm | TWI_SLAVE_AP_bm;
|
||||
__I2C_ISR();
|
||||
|
||||
// DATA
|
||||
for (int i = 0; i < i2cIndex; i++) {
|
||||
I2C_DEV.SLAVE.STATUS = TWI_SLAVE_DIF_bm;
|
||||
I2C_DEV.SLAVE.DATA = i2cData[i];
|
||||
__I2C_ISR();
|
||||
}
|
||||
|
||||
// STOP
|
||||
I2C_DEV.SLAVE.STATUS = TWI_SLAVE_APIF_bm;
|
||||
__I2C_ISR();
|
||||
|
||||
i2cIndex = 0;
|
||||
haveI2C = false;
|
||||
readData = true;
|
||||
}
|
||||
|
||||
// Send byte to serial port
|
||||
if (serialByte != -1 && SERIAL_PORT.CTRLA & USART_RXCINTLVL_MED_gc) {
|
||||
SERIAL_PORT.DATA = (uint8_t)serialByte;
|
||||
__SERIAL_RXC_vect();
|
||||
|
||||
if (SERIAL_PORT.CTRLA & USART_RXCINTLVL_MED_gc) {
|
||||
serialByte = -1;
|
||||
readData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call stepper ISRs
|
||||
if (ADCB_CH0_INTCTRL == ADC_CH_INTLVL_LO_gc) __STEP_LOW_LEVEL_ISR();
|
||||
for (int motor = 0; motor < 4; motor++) motor_emulate_steps(motor);
|
||||
__STEP_TIMER_ISR();
|
||||
|
||||
// Call RTC
|
||||
__RTC_OVF_vect();
|
||||
|
||||
// Throttle with remaining time
|
||||
if (t.tv_usec) usleep(t.tv_usec);
|
||||
}
|
||||
31
src/avr/emu/src/util/atomic.h
Normal file
31
src/avr/emu/src/util/atomic.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define ATOMIC_BLOCK(x)
|
||||
#define ATOMIC_RESTORESTATE
|
||||
30
src/avr/emu/src/util/crc16.h
Normal file
30
src/avr/emu/src/util/crc16.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define _crc16_update(...) 0
|
||||
33
src/avr/emu/src/util/delay.h
Normal file
33
src/avr/emu/src/util/delay.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#define _delay_ms(x) usleep((x) * 1000)
|
||||
#define _delay_us(x) usleep(x)
|
||||
175
src/avr/src/SCurve.cpp
Normal file
175
src/avr/src/SCurve.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
|
||||
#include "SCurve.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
|
||||
SCurve::SCurve(float maxV, float maxA, float maxJ) :
|
||||
maxV(maxV), maxA(maxA), maxJ(maxJ), v(0), a(0), j(0) {}
|
||||
|
||||
|
||||
unsigned SCurve::getPhase() const {
|
||||
if (!v) return 0;
|
||||
|
||||
// Handle negative velocity
|
||||
float v = this->v;
|
||||
float a = this->a;
|
||||
if (v < 0) {
|
||||
v = -v;
|
||||
a = -a;
|
||||
}
|
||||
|
||||
if (0 < a) {
|
||||
if (0 < j) return 1;
|
||||
if (!j) return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (!a) return 4;
|
||||
if (j < 0) return 5;
|
||||
if (!j) return 6;
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
|
||||
float SCurve::getStoppingDist() const {return stoppingDist(v, a, maxA, maxJ);}
|
||||
|
||||
|
||||
float SCurve::next(float t, float targetV) {
|
||||
// Compute next acceleration
|
||||
float nextA = nextAccel(t, targetV, v, a, maxA, maxJ);
|
||||
|
||||
// Compute next velocity
|
||||
float deltaV = nextA * t;
|
||||
if ((deltaV < 0 && targetV < v && v + deltaV < targetV) ||
|
||||
(0 < deltaV && v < targetV && targetV < v + deltaV)) {
|
||||
nextA = (targetV - v) / t;
|
||||
v = targetV;
|
||||
|
||||
} else v += deltaV;
|
||||
|
||||
// Compute jerk = delta accel / time
|
||||
j = (nextA - a) / t;
|
||||
a = nextA;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
float SCurve::stoppingDist(float v, float a, float maxA, float maxJ) {
|
||||
// Already stopped
|
||||
if (!v) return 0;
|
||||
|
||||
// Handle negative velocity
|
||||
if (v < 0) {
|
||||
v = -v;
|
||||
a = -a;
|
||||
}
|
||||
|
||||
float d = 0;
|
||||
|
||||
// Compute distance and velocity change to accel = 0
|
||||
if (0 < a) {
|
||||
// Compute distance to decrease accel to zero
|
||||
float t = a / maxJ;
|
||||
d += distance(t, v, a, -maxJ);
|
||||
v += velocity(t, a, -maxJ);
|
||||
a = 0;
|
||||
}
|
||||
|
||||
// Compute max deccel
|
||||
float maxDeccel = -sqrt(v * maxJ + 0.5 * a * a);
|
||||
if (maxDeccel < -maxA) maxDeccel = -maxA;
|
||||
|
||||
// Compute distance and velocity change to max deccel
|
||||
if (maxDeccel < a) {
|
||||
float t = (a - maxDeccel) / maxJ;
|
||||
d += distance(t, v, a, -maxJ);
|
||||
v += velocity(t, a, -maxJ);
|
||||
a = maxDeccel;
|
||||
}
|
||||
|
||||
// Compute velocity change over remaining accel
|
||||
float deltaV = 0.5 * a * a / maxJ;
|
||||
|
||||
// Compute constant deccel period
|
||||
if (deltaV < v) {
|
||||
float t = (v - deltaV) / -a;
|
||||
d += distance(t, v, a, 0);
|
||||
v += velocity(t, a, 0);
|
||||
}
|
||||
|
||||
// Compute distance to zero vel
|
||||
d += distance(-a / maxJ, v, a, maxJ);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
float SCurve::nextAccel(float t, float targetV, float v, float a, float maxA,
|
||||
float maxJ) {
|
||||
bool increasing = v < targetV;
|
||||
float deltaA = acceleration(t, maxJ);
|
||||
|
||||
if (increasing && a < -deltaA)
|
||||
return a + deltaA; // negative accel, increasing speed
|
||||
|
||||
if (!increasing && deltaA < a)
|
||||
return a - deltaA; // positive accel, decreasing speed
|
||||
|
||||
float deltaV = fabs(targetV - v);
|
||||
float targetA = sqrt(2 * deltaV * maxJ);
|
||||
if (maxA < targetA) targetA = maxA;
|
||||
|
||||
if (increasing) {
|
||||
if (targetA < a + deltaA) return targetA;
|
||||
return a + deltaA;
|
||||
|
||||
} else {
|
||||
if (a - deltaA < -targetA) return -targetA;
|
||||
return a - deltaA;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float SCurve::distance(float t, float v, float a, float j) {
|
||||
// v * t + 1/2 * a * t^2 + 1/6 * j * t^3
|
||||
return t * (v + t * (0.5 * a + 1.0 / 6.0 * j * t));
|
||||
}
|
||||
|
||||
|
||||
float SCurve::velocity(float t, float a, float j) {
|
||||
// a * t + 1/2 * j * t^2
|
||||
return t * (a + 0.5 * j * t);
|
||||
}
|
||||
|
||||
|
||||
float SCurve::acceleration(float t, float j) {return j * t;}
|
||||
66
src/avr/src/SCurve.h
Normal file
66
src/avr/src/SCurve.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
|
||||
|
||||
class SCurve {
|
||||
float maxV;
|
||||
float maxA;
|
||||
float maxJ;
|
||||
|
||||
float v;
|
||||
float a;
|
||||
float j;
|
||||
|
||||
public:
|
||||
SCurve(float maxV = 0, float maxA = 0, float maxJ = 0);
|
||||
|
||||
float getMaxVelocity() const {return maxV;}
|
||||
void setMaxVelocity(float v) {maxV = v;}
|
||||
float getMaxAcceleration() const {return maxA;}
|
||||
void setMaxAcceleration(float a) {maxA = a;}
|
||||
float getMaxJerk() const {return maxJ;}
|
||||
void setMaxJerk(float j) {maxJ = j;}
|
||||
|
||||
float getVelocity() const {return v;}
|
||||
float getAcceleration() const {return a;}
|
||||
float getJerk() const {return j;}
|
||||
|
||||
unsigned getPhase() const;
|
||||
float getStoppingDist() const;
|
||||
float next(float t, float targetV);
|
||||
|
||||
static float stoppingDist(float v, float a, float maxA, float maxJ);
|
||||
static float nextAccel(float t, float targetV, float v, float a, float maxA,
|
||||
float maxJ);
|
||||
static float distance(float t, float v, float a, float j);
|
||||
static float velocity(float t, float a, float j);
|
||||
static float acceleration(float t, float j);
|
||||
};
|
||||
90
src/avr/src/analog.c
Normal file
90
src/avr/src/analog.c
Normal file
@@ -0,0 +1,90 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "analog.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t pin;
|
||||
uint16_t value;
|
||||
} analog_port_t;
|
||||
|
||||
|
||||
analog_port_t ports[] = {
|
||||
{.pin = ANALOG_1_PIN},
|
||||
{.pin = ANALOG_2_PIN},
|
||||
};
|
||||
|
||||
|
||||
ISR(ADCA_CH0_vect) {ports[0].value = ADCA.CH0.RES;}
|
||||
ISR(ADCA_CH1_vect) {ports[1].value = ADCA.CH1.RES;}
|
||||
|
||||
|
||||
void analog_init() {
|
||||
// Channel 0
|
||||
ADCA.CH0.CTRL = ADC_CH_GAIN_1X_gc | ADC_CH_INPUTMODE_SINGLEENDED_gc;
|
||||
ADCA.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN6_gc;
|
||||
ADCA.CH0.INTCTRL = ADC_CH_INTLVL_LO_gc;
|
||||
|
||||
// Channel 1
|
||||
ADCA.CH1.CTRL = ADC_CH_GAIN_1X_gc | ADC_CH_INPUTMODE_SINGLEENDED_gc;
|
||||
ADCA.CH1.MUXCTRL = ADC_CH_MUXPOS_PIN7_gc;
|
||||
ADCA.CH1.INTCTRL = ADC_CH_INTLVL_LO_gc;
|
||||
|
||||
// ADC
|
||||
ADCA.REFCTRL = ADC_REFSEL_INTVCC_gc; // 3.3V / 1.6 = 2.06V
|
||||
ADCA.PRESCALER = ADC_PRESCALER_DIV512_gc;
|
||||
ADCA.EVCTRL = ADC_SWEEP_01_gc;
|
||||
ADCA.CTRLA = ADC_FLUSH_bm | ADC_ENABLE_bm;
|
||||
}
|
||||
|
||||
|
||||
float analog_get(unsigned port) {
|
||||
if (1 < port) return 0;
|
||||
return ports[port].value * (1.0 / 0x1000);
|
||||
}
|
||||
|
||||
|
||||
void analog_rtc_callback() {
|
||||
static uint8_t count = 0;
|
||||
|
||||
// Every 1/4 sec
|
||||
if (++count == 250) {
|
||||
count = 0;
|
||||
ADCA.CTRLA |= ADC_CH0START_bm | ADC_CH1START_bm;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
float get_analog_input(int port) {return analog_get(port);}
|
||||
33
src/avr/src/analog.h
Normal file
33
src/avr/src/analog.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
void analog_init();
|
||||
float analog_get(unsigned port);
|
||||
void analog_rtc_callback();
|
||||
124
src/avr/src/axis.c
Normal file
124
src/avr/src/axis.c
Normal file
@@ -0,0 +1,124 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "axis.h"
|
||||
#include "motor.h"
|
||||
#include "switch.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
int motor_map[AXES] = {-1, -1, -1, -1, -1, -1};
|
||||
|
||||
|
||||
typedef struct {
|
||||
float velocity_max; // max velocity in mm/min or deg/min
|
||||
float accel_max; // max acceleration in mm/min^2
|
||||
float jerk_max; // max jerk (Jm) in km/min^3
|
||||
} axis_t;
|
||||
|
||||
|
||||
axis_t axes[MOTORS] = {};
|
||||
|
||||
|
||||
bool axis_is_enabled(int axis) {
|
||||
int motor = axis_get_motor(axis);
|
||||
return motor != -1 && motor_is_enabled(motor) &&
|
||||
!fp_ZERO(axis_get_velocity_max(axis));
|
||||
}
|
||||
|
||||
|
||||
int axis_get_id(char axis) {
|
||||
const char *axes = "XYZABCUVW";
|
||||
const char *ptr = strchr(axes, toupper(axis));
|
||||
return ptr == 0 ? -1 : (ptr - axes);
|
||||
}
|
||||
|
||||
|
||||
int axis_get_motor(int axis) {return motor_map[axis];}
|
||||
|
||||
|
||||
bool axis_get_homed(int axis) {
|
||||
return axis_is_enabled(axis) ? motor_get_homed(axis_get_motor(axis)) : false;
|
||||
}
|
||||
|
||||
|
||||
float axis_get_soft_limit(int axis, bool min) {
|
||||
if (!axis_is_enabled(axis)) return min ? -INFINITY : INFINITY;
|
||||
return motor_get_soft_limit(axis_get_motor(axis), min);
|
||||
}
|
||||
|
||||
|
||||
// Map axes to first matching motor
|
||||
void axis_map_motors() {
|
||||
for (int axis = 0; axis < AXES; axis++)
|
||||
for (int motor = 0; motor < MOTORS; motor++)
|
||||
if (motor_get_axis(motor) == axis) {
|
||||
motor_map[axis] = motor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define AXIS_VAR_GET(NAME, TYPE) \
|
||||
TYPE get_##NAME(int axis) {return axes[axis].NAME;}
|
||||
|
||||
|
||||
#define AXIS_VAR_SET(NAME, TYPE) \
|
||||
void set_##NAME(int axis, TYPE value) {axes[axis].NAME = value;}
|
||||
|
||||
|
||||
/// Velocity is scaled by 1,000
|
||||
float axis_get_velocity_max(int axis) {
|
||||
int motor = axis_get_motor(axis);
|
||||
return motor == -1 ? 0 : axes[motor].velocity_max * VELOCITY_MULTIPLIER;
|
||||
}
|
||||
AXIS_VAR_GET(velocity_max, float)
|
||||
|
||||
|
||||
/// Acceleration is scaled by 1,000
|
||||
float axis_get_accel_max(int axis) {
|
||||
int motor = axis_get_motor(axis);
|
||||
return motor == -1 ? 0 : axes[motor].accel_max * ACCEL_MULTIPLIER;
|
||||
}
|
||||
AXIS_VAR_GET(accel_max, float)
|
||||
|
||||
|
||||
/// Jerk is scaled by 1,000,000
|
||||
float axis_get_jerk_max(int axis) {
|
||||
int motor = axis_get_motor(axis);
|
||||
return motor == -1 ? 0 : axes[motor].jerk_max * JERK_MULTIPLIER;
|
||||
}
|
||||
AXIS_VAR_GET(jerk_max, float)
|
||||
|
||||
|
||||
AXIS_VAR_SET(velocity_max, float)
|
||||
AXIS_VAR_SET(accel_max, float)
|
||||
AXIS_VAR_SET(jerk_max, float)
|
||||
50
src/avr/src/axis.h
Normal file
50
src/avr/src/axis.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
enum {
|
||||
AXIS_X, AXIS_Y, AXIS_Z,
|
||||
AXIS_A, AXIS_B, AXIS_C,
|
||||
};
|
||||
|
||||
|
||||
bool axis_is_enabled(int axis);
|
||||
int axis_get_id(char axis);
|
||||
int axis_get_motor(int axis);
|
||||
bool axis_get_homed(int axis);
|
||||
float axis_get_soft_limit(int axis, bool min);
|
||||
void axis_map_motors();
|
||||
|
||||
float axis_get_velocity_max(int axis);
|
||||
float axis_get_accel_max(int axis);
|
||||
float axis_get_jerk_max(int axis);
|
||||
151
src/avr/src/base64.c
Normal file
151
src/avr/src/base64.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
static const char *_b64_encode =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
|
||||
static const int8_t _b64_decode[] = { // 43-122
|
||||
62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
};
|
||||
|
||||
|
||||
static int8_t _decode(char c) {
|
||||
return (c < 43 || 122 < c) ? -1 : _b64_decode[c - 43];
|
||||
}
|
||||
|
||||
|
||||
static char _encode(uint8_t b) {return _b64_encode[b & 63];}
|
||||
|
||||
|
||||
static void _skip_space(const char **s, const char *end) {
|
||||
while (*s < end && isspace(**s)) (*s)++;
|
||||
}
|
||||
|
||||
|
||||
static char _next(const char **s, const char *end) {
|
||||
char c = *(*s)++;
|
||||
_skip_space(s, end);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
unsigned b64_encoded_length(unsigned len, bool pad) {
|
||||
unsigned elen = len / 3 * 4;
|
||||
|
||||
switch (len % 3) {
|
||||
case 1: elen += pad ? 4 : 2; break;
|
||||
case 2: elen += pad ? 4 : 3; break;
|
||||
}
|
||||
|
||||
return elen;
|
||||
}
|
||||
|
||||
|
||||
void b64_encode(const uint8_t *in, unsigned len, char *out, bool pad) {
|
||||
const uint8_t *end = in + len;
|
||||
int padding = 0;
|
||||
uint8_t a, b, c;
|
||||
|
||||
while (in < end) {
|
||||
a = *in++;
|
||||
|
||||
if (in < end) {
|
||||
b = *in++;
|
||||
|
||||
if (in < end) c = *in++;
|
||||
else {c = 0; padding = 1;}
|
||||
|
||||
} else {c = b = 0; padding = 2;}
|
||||
|
||||
*out++ = _encode(63 & (a >> 2));
|
||||
*out++ = _encode(63 & (a << 4 | b >> 4));
|
||||
|
||||
if (pad && padding == 2) *out++ = '=';
|
||||
else *out++ = _encode(63 & (b << 2 | c >> 6));
|
||||
|
||||
if (pad && padding) *out++ = '=';
|
||||
else *out++ = _encode(63 & c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool b64_decode(const char *in, unsigned len, uint8_t *out) {
|
||||
const char *end = in + len;
|
||||
|
||||
_skip_space(&in, end);
|
||||
|
||||
while (in < end) {
|
||||
int8_t w = _decode(_next(&in, end));
|
||||
int8_t x = in < end ? _decode(_next(&in, end)) : -2;
|
||||
int8_t y = in < end ? _decode(_next(&in, end)) : -2;
|
||||
int8_t z = in < end ? _decode(_next(&in, end)) : -2;
|
||||
|
||||
if (w == -2 || x == -2 || w == -1 || x == -1 || y == -1 || z == -1)
|
||||
return false;
|
||||
|
||||
*out++ = (uint8_t)(w << 2 | x >> 4);
|
||||
if (y != -2) {
|
||||
*out++ = (uint8_t)(x << 4 | y >> 2);
|
||||
if (z != -2) *out++ = (uint8_t)(y << 6 | z);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool b64_decode_float(const char *s, float *f) {
|
||||
union {
|
||||
float f;
|
||||
uint8_t b[4];
|
||||
} u;
|
||||
|
||||
if (!b64_decode(s, 6, u.b)) return false;
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
#define XORSWAP(a, b) a ^= (b ^ (b ^= a)
|
||||
XORSWAP(u.b[0], u.b[3]);
|
||||
XORSWAP(u.b[1], u.b[2]);
|
||||
#endif
|
||||
|
||||
*f = u.f;
|
||||
|
||||
return true;
|
||||
}
|
||||
37
src/avr/src/base64.h
Normal file
37
src/avr/src/base64.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
unsigned b64_encoded_length(unsigned len, bool pad);
|
||||
void b64_encode(const uint8_t *in, unsigned len, char *out, bool pad);
|
||||
bool b64_decode(const char *in, unsigned len, uint8_t *out);
|
||||
bool b64_decode_float(const char *s, float *f);
|
||||
278
src/avr/src/command.c
Normal file
278
src/avr/src/command.c
Normal file
@@ -0,0 +1,278 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "command.h"
|
||||
|
||||
#include "usart.h"
|
||||
#include "hardware.h"
|
||||
#include "vars.h"
|
||||
#include "estop.h"
|
||||
#include "i2c.h"
|
||||
#include "config.h"
|
||||
#include "pgmspace.h"
|
||||
#include "state.h"
|
||||
#include "exec.h"
|
||||
#include "base64.h"
|
||||
#include "rtc.h"
|
||||
#include "stepper.h"
|
||||
#include "cpp_magic.h"
|
||||
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
#define RING_BUF_NAME sync_q
|
||||
#define RING_BUF_TYPE uint8_t
|
||||
#define RING_BUF_INDEX_TYPE volatile uint16_t
|
||||
#define RING_BUF_SIZE SYNC_QUEUE_SIZE
|
||||
#define RING_BUF_ATOMIC_COPY 1
|
||||
#include "ringbuf.def"
|
||||
|
||||
|
||||
static struct {
|
||||
bool active;
|
||||
uint16_t id;
|
||||
uint32_t last_empty;
|
||||
volatile uint16_t count;
|
||||
float position[AXES];
|
||||
} cmd = {0,};
|
||||
|
||||
|
||||
// Define command callbacks
|
||||
#define CMD(CODE, NAME, SYNC) \
|
||||
stat_t command_##NAME(char *); \
|
||||
IF(SYNC)(unsigned command_##NAME##_size();) \
|
||||
IF(SYNC)(void command_##NAME##_exec(void *);)
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
|
||||
|
||||
// Name
|
||||
#define CMD(CODE, NAME, SYNC) \
|
||||
static const char command_##NAME##_name[] PROGMEM = #NAME;
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
|
||||
|
||||
static bool _is_synchronous(char code) {
|
||||
switch (code) {
|
||||
#define CMD(CODE, NAME, SYNC, ...) case COMMAND_##NAME: return SYNC;
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static stat_t _dispatch(char *s) {
|
||||
switch (*s) {
|
||||
#define CMD(CODE, NAME, SYNC, ...) \
|
||||
case COMMAND_##NAME: return command_##NAME(s);
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
}
|
||||
|
||||
return STAT_INVALID_COMMAND;
|
||||
}
|
||||
|
||||
|
||||
static unsigned _size(char code) {
|
||||
switch (code) {
|
||||
#define CMD(CODE, NAME, SYNC, ...) \
|
||||
IF(SYNC)(case COMMAND_##NAME: return command_##NAME##_size();)
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void _exec_cb(char code, uint8_t *data) {
|
||||
switch (code) {
|
||||
#define CMD(CODE, NAME, SYNC, ...) \
|
||||
IF(SYNC)(case COMMAND_##NAME: command_##NAME##_exec(data); break;)
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _i2c_cb(uint8_t *data, uint8_t length) {
|
||||
stat_t status = _dispatch((char *)data);
|
||||
if (status) STATUS_ERROR(status, "i2c: %s", data);
|
||||
}
|
||||
|
||||
|
||||
void command_init() {i2c_set_read_callback(_i2c_cb);}
|
||||
bool command_is_active() {return cmd.active;}
|
||||
unsigned command_get_count() {return cmd.count;}
|
||||
|
||||
|
||||
void command_print_json() {
|
||||
bool first = true;
|
||||
static const char fmt[] PROGMEM = "\"%c\":{\"name\":\"%" PRPSTR "\"}";
|
||||
|
||||
#define CMD(CODE, NAME, SYNC) \
|
||||
if (first) first = false; else putchar(','); \
|
||||
printf_P(fmt, CODE, command_##NAME##_name);
|
||||
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
}
|
||||
|
||||
|
||||
void command_flush_queue() {
|
||||
sync_q_init();
|
||||
cmd.count = 0;
|
||||
command_reset_position();
|
||||
}
|
||||
|
||||
|
||||
void command_push(char code, void *_data) {
|
||||
uint8_t *data = (uint8_t *)_data;
|
||||
unsigned size = _size(code);
|
||||
|
||||
if (!_is_synchronous(code)) estop_trigger(STAT_Q_INVALID_PUSH);
|
||||
if (sync_q_space() <= size) estop_trigger(STAT_Q_OVERRUN);
|
||||
|
||||
sync_q_push(code);
|
||||
for (unsigned i = 0; i < size; i++) sync_q_push(*data++);
|
||||
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) cmd.count++;
|
||||
}
|
||||
|
||||
|
||||
bool command_callback() {
|
||||
static char *block = 0;
|
||||
|
||||
if (!block) block = usart_readline();
|
||||
if (!block) return false; // No command
|
||||
|
||||
stat_t status = STAT_OK;
|
||||
|
||||
// Special processing for synchronous commands
|
||||
if (_is_synchronous(*block)) {
|
||||
if (estop_triggered()) status = STAT_MACHINE_ALARMED;
|
||||
else if (state_is_flushing()) status = STAT_NOP; // Flush command
|
||||
else if (state_is_resuming() || sync_q_space() <= _size(*block))
|
||||
return false; // Wait
|
||||
}
|
||||
|
||||
// Dispatch non-empty commands
|
||||
if (*block && status == STAT_OK) {
|
||||
status = _dispatch(block);
|
||||
if (status == STAT_OK) cmd.active = true; // Disables LCD booting message
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case STAT_OK: break;
|
||||
case STAT_NOP: break;
|
||||
case STAT_MACHINE_ALARMED: STATUS_WARNING(status, ""); break;
|
||||
default: STATUS_ERROR(status, "%s", block); break;
|
||||
}
|
||||
|
||||
block = 0; // Command consumed
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void command_set_axis_position(int axis, const float p) {
|
||||
cmd.position[axis] = p;
|
||||
}
|
||||
|
||||
|
||||
void command_set_position(const float position[AXES]) {
|
||||
memcpy(cmd.position, position, sizeof(cmd.position));
|
||||
}
|
||||
|
||||
|
||||
void command_get_position(float position[AXES]) {
|
||||
memcpy(position, cmd.position, sizeof(cmd.position));
|
||||
}
|
||||
|
||||
|
||||
void command_reset_position() {
|
||||
float position[AXES];
|
||||
exec_get_position(position);
|
||||
command_set_position(position);
|
||||
}
|
||||
|
||||
|
||||
char command_peek() {return (char)(cmd.count ? sync_q_peek() : 0);}
|
||||
|
||||
|
||||
uint8_t *command_next() {
|
||||
if (!cmd.count) return 0;
|
||||
cmd.count--;
|
||||
|
||||
if (sync_q_empty()) estop_trigger(STAT_Q_UNDERRUN);
|
||||
|
||||
static uint8_t data[INPUT_BUFFER_LEN];
|
||||
|
||||
data[0] = sync_q_next();
|
||||
|
||||
if (!_is_synchronous((char)data[0])) estop_trigger(STAT_INVALID_QCMD);
|
||||
|
||||
unsigned size = _size((char)data[0]);
|
||||
for (unsigned i = 0; i < size; i++)
|
||||
data[i + 1] = sync_q_next();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
// Returns true if command queued
|
||||
// Called by exec.c from low-level interrupt
|
||||
bool command_exec() {
|
||||
if (!cmd.count) {
|
||||
cmd.last_empty = rtc_get_time();
|
||||
state_idle();
|
||||
return false;
|
||||
}
|
||||
|
||||
// On restart wait a bit to give queue a chance to fill
|
||||
if (cmd.count < EXEC_FILL_TARGET &&
|
||||
!rtc_expired(cmd.last_empty + EXEC_DELAY)) return false;
|
||||
|
||||
uint8_t *data = command_next();
|
||||
state_running();
|
||||
|
||||
_exec_cb((char)*data, data + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
uint16_t get_id() {return cmd.id;}
|
||||
void set_id(uint16_t id) {cmd.id = id;}
|
||||
50
src/avr/src/command.def
Normal file
50
src/avr/src/command.def
Normal file
@@ -0,0 +1,50 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
//(CODE, NAME, SYNC)
|
||||
CMD('$', var, 0) // Set or get variable
|
||||
CMD('#', sync_var, 1) // Set variable synchronous
|
||||
CMD('s', seek, 1) // [switch][flags:active|error]
|
||||
CMD('a', set_axis, 1) // [axis][position] Set axis position
|
||||
CMD('l', line, 1) // [targetVel][maxJerk][axes][times]
|
||||
CMD('%', sync_speed, 1) // [offset][speed] Command synchronized speed
|
||||
CMD('p', speed, 1) // [speed] Spindle speed
|
||||
CMD('I', input, 1) // [a|d][port][mode][timeout] Read input
|
||||
CMD('d', dwell, 1) // [seconds]
|
||||
CMD('P', pause, 1) // [type] Pause control
|
||||
CMD('S', stop, 0) // Stop move, spindle and load outputs
|
||||
CMD('U', unpause, 0) // Unpause
|
||||
CMD('j', jog, 0) // [axes]
|
||||
CMD('r', report, 0) // <0|1>[var] Enable or disable var reporting
|
||||
CMD('R', reboot, 0) // Reboot the controller
|
||||
CMD('c', resume, 0) // Continue processing after a flush
|
||||
CMD('E', estop, 0) // Emergency stop
|
||||
CMD('X', shutdown, 0) // Power shutdown
|
||||
CMD('C', clear, 0) // Clear estop
|
||||
CMD('F', flush, 0) // Flush command queue
|
||||
CMD('D', dump, 0) // Report all variables
|
||||
CMD('h', help, 0) // Print this help screen
|
||||
58
src/avr/src/command.h
Normal file
58
src/avr/src/command.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
#include "status.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// Commands
|
||||
typedef enum {
|
||||
#define CMD(CODE, NAME, ...) COMMAND_##NAME = CODE,
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
} command_t;
|
||||
|
||||
|
||||
void command_init();
|
||||
bool command_is_active();
|
||||
unsigned command_get_count();
|
||||
void command_print_json();
|
||||
void command_flush_queue();
|
||||
void command_push(char code, void *data);
|
||||
bool command_callback();
|
||||
void command_set_axis_position(int axis, const float p);
|
||||
void command_set_position(const float position[AXES]);
|
||||
void command_get_position(float position[AXES]);
|
||||
void command_reset_position();
|
||||
char command_peek();
|
||||
uint8_t *command_next();
|
||||
bool command_exec();
|
||||
11
src/avr/src/command.json.in
Normal file
11
src/avr/src/command.json.in
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "cpp_magic.h"
|
||||
{
|
||||
#define CMD(CODE, NAME, SYNC) \
|
||||
#NAME: { \
|
||||
"code": CODE, \
|
||||
"sync": IF_ELSE(SYNC)(true, false) \
|
||||
},
|
||||
#include "command.def"
|
||||
#undef CMD
|
||||
"_": {}
|
||||
}
|
||||
83
src/avr/src/commands.c
Normal file
83
src/avr/src/commands.c
Normal file
@@ -0,0 +1,83 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
#include "stepper.h"
|
||||
#include "command.h"
|
||||
#include "vars.h"
|
||||
#include "base64.h"
|
||||
#include "hardware.h"
|
||||
#include "report.h"
|
||||
#include "exec.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
stat_t command_dwell(char *cmd) {
|
||||
float seconds;
|
||||
if (!b64_decode_float(cmd + 1, &seconds)) return STAT_BAD_FLOAT;
|
||||
command_push(*cmd, &seconds);
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
static stat_t _dwell_exec() {
|
||||
exec_set_cb(0); // Immediately clear the callback
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_dwell_size() {return sizeof(float);}
|
||||
|
||||
|
||||
void command_dwell_exec(void *seconds) {
|
||||
st_prep_dwell(*(float *)seconds);
|
||||
exec_set_cb(_dwell_exec); // Command must set an exec callback
|
||||
}
|
||||
|
||||
|
||||
stat_t command_help(char *cmd) {
|
||||
printf_P(PSTR("\n{\"commands\":{"));
|
||||
command_print_json();
|
||||
printf_P(PSTR("},\"variables\":{"));
|
||||
vars_print_json();
|
||||
printf_P(PSTR("}}\n"));
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t command_reboot(char *cmd) {
|
||||
hw_request_hard_reset();
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t command_dump(char *cmd) {
|
||||
report_request_full();
|
||||
return STAT_OK;
|
||||
}
|
||||
222
src/avr/src/config.h
Normal file
222
src/avr/src/config.h
Normal file
@@ -0,0 +1,222 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pins.h"
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
|
||||
// Pins
|
||||
enum {
|
||||
STALL_0_PIN = PIN_ID(PORT_A, 0),
|
||||
STALL_1_PIN,
|
||||
STALL_2_PIN,
|
||||
STALL_3_PIN,
|
||||
TOOL_DIR_PIN,
|
||||
TOOL_ENABLE_PIN,
|
||||
ANALOG_1_PIN,
|
||||
ANALOG_2_PIN,
|
||||
|
||||
MIN_0_PIN = PIN_ID(PORT_B, 0),
|
||||
MAX_0_PIN,
|
||||
MIN_3_PIN,
|
||||
MAX_3_PIN,
|
||||
MIN_1_PIN,
|
||||
MAX_1_PIN,
|
||||
MIN_2_PIN,
|
||||
MAX_2_PIN,
|
||||
|
||||
SDA_PIN = PIN_ID(PORT_C, 0),
|
||||
SCL_PIN,
|
||||
SERIAL_RX_PIN,
|
||||
SERIAL_TX_PIN,
|
||||
SERIAL_CTS_PIN,
|
||||
SPI_CLK_PIN,
|
||||
SPI_MISO_PIN,
|
||||
SPI_MOSI_PIN,
|
||||
|
||||
STEP_0_PIN = PIN_ID(PORT_D, 0),
|
||||
SPI_CS_0_PIN,
|
||||
SPI_CS_3_PIN,
|
||||
SPI_CS_2_PIN,
|
||||
PWM_PIN,
|
||||
SWITCH_2_PIN,
|
||||
RS485_RO_PIN,
|
||||
RS485_DI_PIN,
|
||||
|
||||
STEP_1_PIN = PIN_ID(PORT_E, 0),
|
||||
SPI_CS_1_PIN,
|
||||
DIR_0_PIN,
|
||||
DIR_1_PIN,
|
||||
STEP_3_PIN,
|
||||
SWITCH_1_PIN,
|
||||
DIR_2_PIN,
|
||||
DIR_3_PIN,
|
||||
|
||||
STEP_2_PIN = PIN_ID(PORT_F, 0),
|
||||
RS485_RW_PIN,
|
||||
FAULT_PIN,
|
||||
ESTOP_PIN,
|
||||
MOTOR_FAULT_PIN,
|
||||
MOTOR_ENABLE_PIN,
|
||||
TEST_PIN,
|
||||
PROBE_PIN,
|
||||
};
|
||||
|
||||
#define SPI_SS_PIN SERIAL_CTS_PIN // Needed for SPI configuration
|
||||
|
||||
|
||||
#define AXES 6 // number of axes
|
||||
#define MOTORS 4 // number of motors on the board
|
||||
#define OUTS 6 // number of supported pin outputs
|
||||
#define ANALOG 2 // number of supported analog inputs
|
||||
#define VFDREG 32 // number of supported VFD modbus registers
|
||||
|
||||
// Switch settings. See switch.c
|
||||
#define SWITCH_DEBOUNCE 5 // ms, default value
|
||||
#define SWITCH_LOCKOUT 250 // ms, default value
|
||||
#define SWITCH_MAX_DEBOUNCE 5000 // ms
|
||||
#define SWITCH_MAX_LOCKOUT 60000 // ms
|
||||
|
||||
|
||||
// Motor ISRs
|
||||
#define STALL_ISR_vect PORTA_INT1_vect
|
||||
#define FAULT_ISR_vect PORTF_INT1_vect
|
||||
|
||||
|
||||
/* Interrupt usage:
|
||||
*
|
||||
* HI Step timers stepper.c
|
||||
* HI Serial RX usart.c
|
||||
* MED Serial TX usart.c (* see note)
|
||||
* MED Modbus serial interrupts modbus.c
|
||||
* LO Segment execution SW interrupt stepper.c
|
||||
* LO I2C Slave i2c.c
|
||||
* LO Real-time clock interrupt rtc.c
|
||||
* LO DRV8711 SPI drv8711.c
|
||||
* LO A2D interrupts analog.c
|
||||
*
|
||||
* (*) The TX cannot run at LO level or exception reports and other prints
|
||||
* called from a LO interrupt (as in prep_line()) will kill the system
|
||||
* in a permanent loop call in usart_putc() (usart.c).
|
||||
*/
|
||||
|
||||
// Timer assignments
|
||||
// NOTE, TCC1 is unused
|
||||
#define TIMER_STEP TCC0 // Step timer (see stepper.h)
|
||||
#define TIMER_PWM TCD1 // PWM timer (see pwm.c)
|
||||
|
||||
|
||||
// Timer setup for stepper and dwells
|
||||
#define STEP_TIMER_DIV 8
|
||||
#define STEP_TIMER_FREQ (F_CPU / STEP_TIMER_DIV)
|
||||
#define STEP_TIMER_POLL ((uint16_t)(STEP_TIMER_FREQ * 0.001)) // 1ms
|
||||
#define STEP_TIMER_ISR TCC0_OVF_vect
|
||||
#define STEP_LOW_LEVEL_ISR ADCB_CH0_vect
|
||||
#define STEP_PULSE_WIDTH (F_CPU * 0.000002) // 2uS w/ clk/1
|
||||
#define SEGMENT_MS 4
|
||||
#define SEGMENT_TIME (SEGMENT_MS / 60000.0) // mins
|
||||
|
||||
|
||||
// DRV8711 settings
|
||||
// NOTE, PWM frequency = 1 / (2 * DTIME + TBLANK + TOFF)
|
||||
// We have PWM frequency = 1 / (2 * 850nS + 1uS + 6.5uS) ~= 110kHz
|
||||
#define DRV8711_OFF 12
|
||||
#define DRV8711_BLANK (0x32 | DRV8711_BLANK_ABT_bm)
|
||||
#define DRV8711_DECAY (DRV8711_DECAY_DECMOD_MIXED | 16)
|
||||
|
||||
#define DRV8711_DRIVE (DRV8711_DRIVE_IDRIVEP_50 | \
|
||||
DRV8711_DRIVE_IDRIVEN_100 | \
|
||||
DRV8711_DRIVE_TDRIVEP_500 | \
|
||||
DRV8711_DRIVE_TDRIVEN_500 | \
|
||||
DRV8711_DRIVE_OCPDEG_1 | \
|
||||
DRV8711_DRIVE_OCPTH_500)
|
||||
#define DRV8711_TORQUE DRV8711_TORQUE_SMPLTH_200
|
||||
// NOTE, Datasheet suggests 850ns DTIME with the optional gate resistor
|
||||
// installed. See page 30 section 8.1.2 of DRV8711 datasheet.
|
||||
#define DRV8711_CTRL (DRV8711_CTRL_ISGAIN_5 | \
|
||||
DRV8711_CTRL_DTIME_850)
|
||||
|
||||
|
||||
// RS485 settings
|
||||
#define RS485_PORT USARTD1
|
||||
#define RS485_DRE_vect USARTD1_DRE_vect
|
||||
#define RS485_TXC_vect USARTD1_TXC_vect
|
||||
#define RS485_RXC_vect USARTD1_RXC_vect
|
||||
|
||||
|
||||
// Modbus settings
|
||||
#define MODBUS_TIMEOUT 100 // ms. response timeout
|
||||
#define MODBUS_RETRIES 4 // Number of retries before failure
|
||||
#define MODBUS_BUF_SIZE 18 // Max bytes in rx/tx buffers
|
||||
#define VFD_QUERY_DELAY 100 // ms
|
||||
|
||||
|
||||
// Serial settings
|
||||
#define SERIAL_BAUD USART_BAUD_230400 // 115200
|
||||
#define SERIAL_PORT USARTC0
|
||||
#define SERIAL_DRE_vect USARTC0_DRE_vect
|
||||
#define SERIAL_RXC_vect USARTC0_RXC_vect
|
||||
#define SERIAL_CTS_THRESH 4
|
||||
|
||||
|
||||
// PWM settings
|
||||
#define POWER_MAX_UPDATES SEGMENT_MS
|
||||
|
||||
// Input
|
||||
#define INPUT_BUFFER_LEN 128 // text buffer size (255 max)
|
||||
|
||||
|
||||
// Report
|
||||
#define REPORT_RATE 250 // ms
|
||||
|
||||
|
||||
// I2C
|
||||
#define I2C_DEV TWIC
|
||||
#define I2C_ISR TWIC_TWIS_vect
|
||||
#define I2C_ADDR 0x2b
|
||||
#define I2C_MAX_DATA 8
|
||||
|
||||
|
||||
// Motor
|
||||
#define MOTOR_IDLE_TIMEOUT 0.25 // secs, motor off after this time
|
||||
#define MIN_STEP_CORRECTION 2
|
||||
|
||||
#define MIN_VELOCITY 10 // mm/min
|
||||
#define CURRENT_SENSE_RESISTOR 0.05 // ohms
|
||||
#define CURRENT_SENSE_REF 2.75 // volts
|
||||
#define MAX_CURRENT 6 // amps
|
||||
#define MAX_IDLE_CURRENT 2 // amps
|
||||
#define VELOCITY_MULTIPLIER 1000.0
|
||||
#define ACCEL_MULTIPLIER 1000000.0
|
||||
#define JERK_MULTIPLIER 1000000.0
|
||||
#define SYNC_QUEUE_SIZE 4096
|
||||
#define EXEC_FILL_TARGET 8
|
||||
#define EXEC_DELAY 250 // ms
|
||||
#define JOG_STOPPING_UNDERSHOOT 1 // % of stopping distance
|
||||
507
src/avr/src/cpp_magic.h
Normal file
507
src/avr/src/cpp_magic.h
Normal file
@@ -0,0 +1,507 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
/* This header file contains a library of advanced C Pre-Processor (CPP) macros
|
||||
* which implement various useful functions, such as iteration, in the
|
||||
* pre-processor.
|
||||
*
|
||||
* Though the file name (quite validly) labels this as magic, there should be
|
||||
* enough documentation in the comments for a reader only casually familiar
|
||||
* with the CPP to be able to understand how everything works.
|
||||
*
|
||||
* The majority of the magic tricks used in this file are based on those
|
||||
* described by pfultz2 in his "Cloak" library:
|
||||
*
|
||||
* https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms
|
||||
*
|
||||
* Major differences are a greater level of detailed explanation in this
|
||||
* implementation and also a refusal to include any macros which require a O(N)
|
||||
* macro definitions to handle O(N) arguments (with the exception of DEFERn).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Force the pre-processor to expand the macro a large number of times.
|
||||
* Usage:
|
||||
*
|
||||
* EVAL(expression)
|
||||
*
|
||||
* This is useful when you have a macro which evaluates to a valid
|
||||
* macro expression which is not subsequently expanded in the same
|
||||
* pass. A contrived, but easy to understand, example of such a macro
|
||||
* follows. Note that though this example is contrived, this behaviour
|
||||
* is abused to implement bounded recursion in macros such as FOR.
|
||||
*
|
||||
* #define A(x) x+1
|
||||
* #define EMPTY
|
||||
* #define NOT_QUITE_RIGHT(x) A EMPTY (x)
|
||||
* NOT_QUITE_RIGHT(999)
|
||||
*
|
||||
* Here's what happens inside the C preprocessor:
|
||||
*
|
||||
* 1. It sees a macro "NOT_QUITE_RIGHT" and performs a single macro
|
||||
* expansion pass on its arguments. Since the argument is "999" and
|
||||
* this isn't a macro, this is a boring step resulting in no
|
||||
* change.
|
||||
*
|
||||
* 2. The NOT_QUITE_RIGHT macro is substituted for its definition
|
||||
* giving "A EMPTY() (x)".
|
||||
*
|
||||
* 3. The expander moves from left-to-right trying to expand the
|
||||
* macro: The first token, A, cannot be expanded since there are no
|
||||
* brackets immediately following it. The second token EMPTY(),
|
||||
* however, can be expanded (recursively in this manner) and is
|
||||
* replaced with "".
|
||||
*
|
||||
* 4. Expansion continues from the start of the substituted test
|
||||
* (which in this case is just empty), and sees "(999)" but since
|
||||
* no macro name is present, nothing is done. This results in a
|
||||
* final expansion of "A (999)".
|
||||
*
|
||||
* Unfortunately, this doesn't quite meet expectations since you may
|
||||
* expect that "A (999)" would have been expanded into
|
||||
* "999+1". Unfortunately this requires a second expansion pass but
|
||||
* luckily we can force the macro processor to make more passes by
|
||||
* abusing the first step of macro expansion: the preprocessor expands
|
||||
* arguments in their own pass. If we define a macro which does
|
||||
* nothing except produce its arguments e.g.:
|
||||
*
|
||||
* #define PASS_THROUGH(...) __VA_ARGS__
|
||||
*
|
||||
* We can now do "PASS_THROUGH(NOT_QUITE_RIGHT(999))" causing
|
||||
* "NOT_QUITE_RIGHT" to be expanded to "A (999)", as described above,
|
||||
* when the arguments are expanded. Now when the body of PASS_THROUGH
|
||||
* is expanded, "A (999)" gets expanded to "999+1".
|
||||
*
|
||||
* The EVAL defined below is essentially equivalent to a large nesting
|
||||
* of "PASS_THROUGH(PASS_THROUGH(PASS_THROUGH(..." which results in
|
||||
* the preprocessor making a large number of expansion passes over the
|
||||
* given expression.
|
||||
*/
|
||||
#define EVAL(...) EVAL1024(__VA_ARGS__)
|
||||
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
|
||||
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
|
||||
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
|
||||
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
|
||||
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
|
||||
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
|
||||
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
|
||||
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
|
||||
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
|
||||
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
|
||||
#define EVAL1(...) __VA_ARGS__
|
||||
|
||||
|
||||
// Macros which expand to common values
|
||||
#define PASS(...) __VA_ARGS__
|
||||
#define EMPTY()
|
||||
#define COMMA() ,
|
||||
#define SEMI() ;
|
||||
#define PLUS() +
|
||||
#define ZERO() 0
|
||||
#define ONE() 1
|
||||
|
||||
/**
|
||||
* Causes a function-style macro to require an additional pass to be expanded.
|
||||
*
|
||||
* This is useful, for example, when trying to implement recursion since the
|
||||
* recursive step must not be expanded in a single pass as the pre-processor
|
||||
* will catch it and prevent it.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* DEFER1(IN_NEXT_PASS)(args, to, the, macro)
|
||||
*
|
||||
* How it works:
|
||||
*
|
||||
* 1. When DEFER1 is expanded, first its arguments are expanded which are
|
||||
* simply IN_NEXT_PASS. Since this is a function-style macro and it has no
|
||||
* arguments, nothing will happen.
|
||||
* 2. The body of DEFER1 will now be expanded resulting in EMPTY() being
|
||||
* deleted. This results in "IN_NEXT_PASS (args, to, the macro)". Note that
|
||||
* since the macro expander has already passed IN_NEXT_PASS by the time it
|
||||
* expands EMPTY() and so it won't spot that the brackets which remain can be
|
||||
* applied to IN_NEXT_PASS.
|
||||
* 3. At this point the macro expansion completes. If one more pass is made,
|
||||
* IN_NEXT_PASS(args, to, the, macro) will be expanded as desired.
|
||||
*/
|
||||
#define DEFER1(id) id EMPTY()
|
||||
|
||||
/**
|
||||
* As with DEFER1 except here n additional passes are required for DEFERn.
|
||||
*
|
||||
* The mechanism is analogous.
|
||||
*
|
||||
* Note that there doesn't appear to be a way of combining DEFERn macros in
|
||||
* order to achieve exponentially increasing defers e.g. as is done by EVAL.
|
||||
*/
|
||||
#define DEFER2(id) id EMPTY EMPTY()()
|
||||
#define DEFER3(id) id EMPTY EMPTY EMPTY()()()
|
||||
#define DEFER4(id) id EMPTY EMPTY EMPTY EMPTY()()()()
|
||||
#define DEFER5(id) id EMPTY EMPTY EMPTY EMPTY EMPTY()()()()()
|
||||
#define DEFER6(id) id EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY()()()()()()
|
||||
#define DEFER7(id) id EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY()()()()()()()
|
||||
#define DEFER8(id) \
|
||||
id EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY()()()()()()()()
|
||||
|
||||
|
||||
/**
|
||||
* Indirection around the standard ## concatenation operator. This simply
|
||||
* ensures that the arguments are expanded (once) before concatenation.
|
||||
*/
|
||||
#define CAT(a, ...) a ## __VA_ARGS__
|
||||
#define CAT3(a, b, ...) a ## b ## __VA_ARGS__
|
||||
|
||||
|
||||
/**
|
||||
* Get the first argument and ignore the rest.
|
||||
*/
|
||||
#define FIRST(a, ...) a
|
||||
|
||||
/**
|
||||
* Get the second argument and ignore the rest.
|
||||
*/
|
||||
#define SECOND(a, b, ...) b
|
||||
|
||||
/**
|
||||
* Expects a single input (not containing commas). Returns 1 if the input is
|
||||
* PROBE() and otherwise returns 0.
|
||||
*
|
||||
* This can be useful as the basis of a NOT function.
|
||||
*
|
||||
* This macro abuses the fact that PROBE() contains a comma while other valid
|
||||
* inputs must not.
|
||||
*/
|
||||
#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
|
||||
#define PROBE() ~, 1
|
||||
|
||||
|
||||
/**
|
||||
* Logical negation. 0 is defined as false and everything else as true.
|
||||
*
|
||||
* When 0, _NOT_0 will be found which evaluates to the PROBE. When 1
|
||||
* (or any other value) is given, an appropriately named macro won't
|
||||
* be found and the concatenated string will be produced. IS_PROBE
|
||||
* then simply checks to see if the PROBE was returned, cleanly
|
||||
* converting the argument into a 1 or 0.
|
||||
*/
|
||||
#define NOT(x) IS_PROBE(CAT(_NOT_, x))
|
||||
#define _NOT_0 PROBE()
|
||||
|
||||
/**
|
||||
* Macro version of C's famous "cast to bool" operator (i.e. !!) which takes
|
||||
* anything and casts it to 0 if it is 0 and 1 otherwise.
|
||||
*/
|
||||
#define BOOL(x) NOT(NOT(x))
|
||||
|
||||
/**
|
||||
* Logical OR. Simply performs a lookup.
|
||||
*/
|
||||
#define OR(a,b) CAT3(_OR_, a, b)
|
||||
#define _OR_00 0
|
||||
#define _OR_01 1
|
||||
#define _OR_10 1
|
||||
#define _OR_11 1
|
||||
|
||||
/**
|
||||
* Logical AND. Simply performs a lookup.
|
||||
*/
|
||||
#define AND(a,b) CAT3(_AND_, a, b)
|
||||
#define _AND_00 0
|
||||
#define _AND_01 0
|
||||
#define _AND_10 0
|
||||
#define _AND_11 1
|
||||
|
||||
|
||||
/**
|
||||
* Macro if statement. Usage:
|
||||
*
|
||||
* IF(c)(expansion when true)
|
||||
*
|
||||
* Here's how:
|
||||
*
|
||||
* 1. The preprocessor expands the arguments to _IF casting the condition to '0'
|
||||
* or '1'.
|
||||
* 2. The casted condition is concatencated with _IF_ giving _IF_0 or _IF_1.
|
||||
* 3. The _IF_0 and _IF_1 macros either returns the argument or doesn't (e.g.
|
||||
* they implement the "choice selection" part of the macro).
|
||||
* 4. Note that the "true" clause is in the extra set of brackets; thus these
|
||||
* become the arguments to _IF_0 or _IF_1 and thus a selection is made!
|
||||
*/
|
||||
#define IF(c) _IF(BOOL(c))
|
||||
#define _IF(c) CAT(_IF_,c)
|
||||
#define _IF_0(...)
|
||||
#define _IF_1(...) __VA_ARGS__
|
||||
|
||||
/**
|
||||
* Macro if/else statement. Usage:
|
||||
*
|
||||
* IF_ELSE(c)( \
|
||||
* expansion when true, \
|
||||
* expansion when false \
|
||||
* )
|
||||
*
|
||||
* The mechanism is analogous to IF.
|
||||
*/
|
||||
#define IF_ELSE(c) _IF_ELSE(BOOL(c))
|
||||
#define _IF_ELSE(c) CAT(_IF_ELSE_,c)
|
||||
#define _IF_ELSE_0(t,f) f
|
||||
#define _IF_ELSE_1(t,f) t
|
||||
|
||||
|
||||
/**
|
||||
* Macro which checks if it has any arguments. Returns '0' if there are no
|
||||
* arguments, '1' otherwise.
|
||||
*
|
||||
* Limitation: HAS_ARGS(,1,2,3) returns 0 -- this check essentially only checks
|
||||
* that the first argument exists.
|
||||
*
|
||||
* This macro works as follows:
|
||||
*
|
||||
* 1. _END_OF_ARGUMENTS_ is concatenated with the first argument.
|
||||
* 2. If the first argument is not present then only "_END_OF_ARGUMENTS_" will
|
||||
* remain, otherwise "_END_OF_ARGUMENTS something_here" will remain.
|
||||
* 3. In the former case, the _END_OF_ARGUMENTS_() macro expands to a
|
||||
* 0 when it is expanded. In the latter, a non-zero result remains.
|
||||
* 4. BOOL is used to force non-zero results into 1 giving the clean 0 or 1
|
||||
* output required.
|
||||
*/
|
||||
#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
|
||||
#define _END_OF_ARGUMENTS_() 0
|
||||
|
||||
|
||||
/**
|
||||
* Macro map/list comprehension. Usage:
|
||||
*
|
||||
* MAP(op, sep, ...)
|
||||
*
|
||||
* Produces a 'sep()'-separated list of the result of op(arg) for each arg.
|
||||
*
|
||||
* Example Usage:
|
||||
*
|
||||
* #define MAKE_HAPPY(x) happy_##x
|
||||
* #define COMMA() ,
|
||||
* MAP(MAKE_HAPPY, COMMA, 1,2,3)
|
||||
*
|
||||
* Which expands to:
|
||||
*
|
||||
* happy_1 , happy_2 , happy_3
|
||||
*
|
||||
* How it works:
|
||||
*
|
||||
* 1. The MAP macro simply maps the inner MAP_INNER function in an EVAL which
|
||||
* forces it to be expanded a large number of times, thus enabling many steps
|
||||
* of iteration (see step 6).
|
||||
* 2. The MAP_INNER macro is substituted for its body.
|
||||
* 3. In the body, op(cur_val) is substituted giving the value for this
|
||||
* iteration.
|
||||
* 4. The IF macro expands according to whether further iterations are required.
|
||||
* This expansion either produces _IF_0 or _IF_1.
|
||||
* 5. Since the IF is followed by a set of brackets containing the "if true"
|
||||
* clause, these become the argument to _IF_0 or _IF_1. At this point, the
|
||||
* macro in the brackets will be expanded giving the separator followed by
|
||||
* _MAP_INNER EMPTY()()(op, sep, __VA_ARGS__).
|
||||
* 5. If the IF was not taken, the above will simply be discarded and everything
|
||||
* stops. If the IF is taken, The expression is then processed a second time
|
||||
* yielding "_MAP_INNER()(op, sep, __VA_ARGS__)". Note that this call looks
|
||||
* very similar to the essentially the same as the original call except the
|
||||
* first argument has been dropped.
|
||||
* 6. At this point expansion of MAP_INNER will terminate. However, since we can
|
||||
* force more rounds of expansion using EVAL1. In the argument-expansion pass
|
||||
* of the EVAL1, _MAP_INNER() is expanded to MAP_INNER which is then expanded
|
||||
* using the arguments which follow it as in step 2-5. This is followed by a
|
||||
* second expansion pass as the substitution of EVAL1() is expanded executing
|
||||
* 2-5 a second time. This results in up to two iterations occurring. Using
|
||||
* many nested EVAL1 macros, i.e. the very-deeply-nested EVAL macro, will in
|
||||
* this manner produce further iterations, hence the outer MAP macro doing
|
||||
* this for us.
|
||||
*
|
||||
* Important tricks used:
|
||||
*
|
||||
* * If we directly produce "MAP_INNER" in an expansion of MAP_INNER,
|
||||
* a special case in the preprocessor will prevent it being expanded
|
||||
* in the future, even if we EVAL. As a result, the MAP_INNER macro
|
||||
* carefully only expands to something containing "_MAP_INNER()"
|
||||
* which requires a further expansion step to invoke MAP_INNER and
|
||||
* thus implementing the recursion.
|
||||
*
|
||||
* * To prevent _MAP_INNER being expanded within the macro we must
|
||||
* first defer its expansion during its initial pass as an argument
|
||||
* to _IF_0 or _IF_1. We must then defer its expansion a second time
|
||||
* as part of the body of the _IF_0. As a result hence the DEFER2.
|
||||
*
|
||||
* * _MAP_INNER seemingly gets away with producing itself because it
|
||||
* actually only produces MAP_INNER. It just happens that when
|
||||
* _MAP_INNER() is expanded in this case it is followed by some
|
||||
* arguments which get consumed by MAP_INNER and produce a
|
||||
* _MAP_INNER. As such, the macro expander never marks _MAP_INNER
|
||||
* as expanding to itself and thus it will still be expanded in
|
||||
* future productions of itself.
|
||||
*/
|
||||
#define MAP(...) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))(EVAL(MAP_INNER(__VA_ARGS__)))
|
||||
#define MAP_INNER(op,sep,cur_val, ...) \
|
||||
op(cur_val) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))( \
|
||||
sep() DEFER2(_MAP_INNER)()(op, sep, ##__VA_ARGS__) \
|
||||
)
|
||||
#define _MAP_INNER() MAP_INNER
|
||||
|
||||
|
||||
/**
|
||||
* This is a variant of the MAP macro which also includes as an argument to the
|
||||
* operation a valid C variable name which is different for each iteration.
|
||||
*
|
||||
* Usage:
|
||||
* MAP_WITH_ID(op, sep, ...)
|
||||
*
|
||||
* Where op is a macro op(val, id) which takes a list value and an ID. This ID
|
||||
* will simply be a unary number using the digit "I", that is, I, II, III, IIII,
|
||||
* and so on.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* #define MAKE_STATIC_VAR(type, name) static type name;
|
||||
* MAP_WITH_ID(MAKE_STATIC_VAR, EMPTY, int, int, int, bool, char)
|
||||
*
|
||||
* Which expands to:
|
||||
*
|
||||
* static int I; static int II; static int III; static bool IIII;
|
||||
* static char IIIII;
|
||||
*
|
||||
* The mechanism is analogous to the MAP macro.
|
||||
*/
|
||||
#define MAP_WITH_ID(op,sep,...) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))(EVAL(MAP_WITH_ID_INNER(op,sep,I, ##__VA_ARGS__)))
|
||||
#define MAP_WITH_ID_INNER(op,sep,id,cur_val, ...) \
|
||||
op(cur_val,id) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))( \
|
||||
sep() DEFER2(_MAP_WITH_ID_INNER)()(op, sep, CAT(id,I), ##__VA_ARGS__) \
|
||||
)
|
||||
#define _MAP_WITH_ID_INNER() MAP_WITH_ID_INNER
|
||||
|
||||
|
||||
/**
|
||||
* This is a variant of the MAP macro which iterates over pairs rather than
|
||||
* singletons.
|
||||
*
|
||||
* Usage:
|
||||
* MAP_PAIRS(op, sep, ...)
|
||||
*
|
||||
* Where op is a macro op(val_1, val_2) which takes two list values.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* #define MAKE_STATIC_VAR(type, name) static type name;
|
||||
* MAP_PAIRS(MAKE_STATIC_VAR, EMPTY, char, my_char, int, my_int)
|
||||
*
|
||||
* Which expands to:
|
||||
*
|
||||
* static char my_char; static int my_int;
|
||||
*
|
||||
* The mechanism is analogous to the MAP macro.
|
||||
*/
|
||||
#define MAP_PAIRS(op,sep,...) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))(EVAL(MAP_PAIRS_INNER(op,sep,__VA_ARGS__)))
|
||||
#define MAP_PAIRS_INNER(op,sep,cur_val_1, cur_val_2, ...) \
|
||||
op(cur_val_1,cur_val_2) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))( \
|
||||
sep() DEFER2(_MAP_PAIRS_INNER)()(op, sep, __VA_ARGS__) \
|
||||
)
|
||||
#define _MAP_PAIRS_INNER() MAP_PAIRS_INNER
|
||||
|
||||
/**
|
||||
* This is a variant of the MAP macro which iterates over a two-element sliding
|
||||
* window.
|
||||
*
|
||||
* Usage:
|
||||
* MAP_SLIDE(op, last_op, sep, ...)
|
||||
*
|
||||
* Where op is a macro op(val_1, val_2) which takes the two list values
|
||||
* currently in the window. last_op is a macro taking a single value which is
|
||||
* called for the last argument.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* #define SIMON_SAYS_OP(simon, next) IF(NOT(simon()))(next)
|
||||
* #define SIMON_SAYS_LAST_OP(val) last_but_not_least_##val
|
||||
* #define SIMON_SAYS() 0
|
||||
*
|
||||
* MAP_SLIDE(SIMON_SAYS_OP, SIMON_SAYS_LAST_OP, EMPTY, wiggle, SIMON_SAYS,
|
||||
* dance, move, SIMON_SAYS, boogie, stop)
|
||||
*
|
||||
* Which expands to:
|
||||
*
|
||||
* dance boogie last_but_not_least_stop
|
||||
*
|
||||
* The mechanism is analogous to the MAP macro.
|
||||
*/
|
||||
#define MAP_SLIDE(op,last_op,sep,...) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))(EVAL(MAP_SLIDE_INNER(op,last_op,sep,__VA_ARGS__)))
|
||||
#define MAP_SLIDE_INNER(op,last_op,sep,cur_val, ...) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))(op(cur_val,FIRST(__VA_ARGS__))) \
|
||||
IF(NOT(HAS_ARGS(__VA_ARGS__)))(last_op(cur_val)) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))( \
|
||||
sep() DEFER2(_MAP_SLIDE_INNER)()(op, last_op, sep, __VA_ARGS__) \
|
||||
)
|
||||
#define _MAP_SLIDE_INNER() MAP_SLIDE_INNER
|
||||
|
||||
|
||||
/**
|
||||
* Strip any excess commas from a set of arguments.
|
||||
*/
|
||||
#define REMOVE_TRAILING_COMMAS(...) \
|
||||
MAP(PASS, COMMA, __VA_ARGS__)
|
||||
|
||||
|
||||
/**
|
||||
* Evaluates an array of macros passing 1 argument
|
||||
*/
|
||||
#define EMAP1(...) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))(EVAL(EMAP1_INNER(__VA_ARGS__)))
|
||||
|
||||
#define EMAP1_INNER(ARG1, OP, ...) \
|
||||
_##OP(ARG1) \
|
||||
IF(HAS_ARGS(__VA_ARGS__)) \
|
||||
(DEFER2(_EMAP1_INNER)()(ARG1, ##__VA_ARGS__))
|
||||
|
||||
#define _EMAP1_INNER() EMAP1_INNER
|
||||
|
||||
|
||||
/**
|
||||
* Evaluates an array of macros passing 2 arguments
|
||||
*/
|
||||
#define EMAP2(...) \
|
||||
IF(HAS_ARGS(__VA_ARGS__))(EVAL(EMAP2_INNER(__VA_ARGS__)))
|
||||
|
||||
#define EMAP2_INNER(ARG1, ARG2, OP, ...) \
|
||||
_##OP(ARG1, ARG2) \
|
||||
IF(HAS_ARGS(__VA_ARGS__)) \
|
||||
(DEFER2(_EMAP2_INNER)()(ARG1, ARG2, ##__VA_ARGS__))
|
||||
|
||||
#define _EMAP2_INNER() EMAP2_INNER
|
||||
546
src/avr/src/drv8711.c
Normal file
546
src/avr/src/drv8711.c
Normal file
@@ -0,0 +1,546 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "drv8711.h"
|
||||
#include "status.h"
|
||||
#include "stepper.h"
|
||||
#include "switch.h"
|
||||
#include "estop.h"
|
||||
#include "seek.h"
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <util/delay.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
#define DRIVERS MOTORS
|
||||
|
||||
|
||||
#define DRV8711_WORD_BYTE_PTR(WORD, LOW) (((uint8_t *)&(WORD)) + !(LOW))
|
||||
|
||||
|
||||
typedef enum {
|
||||
SS_WRITE_OFF,
|
||||
SS_WRITE_BLANK,
|
||||
SS_WRITE_DECAY,
|
||||
SS_WRITE_STALL,
|
||||
SS_WRITE_DRIVE,
|
||||
SS_WRITE_TORQUE,
|
||||
SS_WRITE_CTRL,
|
||||
SS_READ_OFF,
|
||||
SS_READ_STATUS,
|
||||
SS_CLEAR_STATUS,
|
||||
} spi_state_t;
|
||||
|
||||
|
||||
bool motor_fault = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
float current;
|
||||
uint8_t torque;
|
||||
} current_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t cs_pin;
|
||||
switch_id_t stall_sw;
|
||||
|
||||
uint8_t status;
|
||||
uint16_t flags;
|
||||
bool reset_flags;
|
||||
bool stalled;
|
||||
|
||||
drv8711_state_t state;
|
||||
current_t drive;
|
||||
current_t idle;
|
||||
current_t stall;
|
||||
uint16_t stall_vdiv;
|
||||
uint8_t stall_thresh;
|
||||
uint8_t last_thresh;
|
||||
uint16_t sample_time;
|
||||
//float stall_current;
|
||||
|
||||
uint8_t microstep;
|
||||
stall_callback_t stall_cb;
|
||||
|
||||
uint8_t last_torque;
|
||||
uint8_t last_microstep;
|
||||
|
||||
spi_state_t spi_state;
|
||||
} drv8711_driver_t;
|
||||
|
||||
|
||||
static drv8711_driver_t drivers[DRIVERS] = {
|
||||
{.cs_pin = SPI_CS_0_PIN, .stall_sw = SW_STALL_0},
|
||||
{.cs_pin = SPI_CS_1_PIN, .stall_sw = SW_STALL_1},
|
||||
{.cs_pin = SPI_CS_2_PIN, .stall_sw = SW_STALL_2},
|
||||
{.cs_pin = SPI_CS_3_PIN, .stall_sw = SW_STALL_3},
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool advance;
|
||||
uint8_t disable_cs_pin;
|
||||
|
||||
uint16_t command;
|
||||
uint16_t response;
|
||||
uint8_t driver;
|
||||
bool low_byte;
|
||||
} spi_t;
|
||||
|
||||
static spi_t spi = {0};
|
||||
|
||||
|
||||
static void _current_set(current_t *c, float current) {
|
||||
const float gain = DRV8711_CTRL_GAIN(DRV8711_CTRL);
|
||||
const float max_current = CURRENT_SENSE_REF / (CURRENT_SENSE_RESISTOR * gain);
|
||||
|
||||
// Limit to max configurable current (11A)
|
||||
if (max_current < current) current = max_current;
|
||||
|
||||
c->current = current;
|
||||
c->torque = round(current * CURRENT_SENSE_RESISTOR / CURRENT_SENSE_REF *
|
||||
gain * 255);
|
||||
}
|
||||
|
||||
|
||||
static bool _driver_fault(drv8711_driver_t *drv) {return drv->flags & 0x1f;}
|
||||
|
||||
|
||||
static bool _driver_active(drv8711_driver_t *drv) {
|
||||
return drv->state == DRV8711_ACTIVE;
|
||||
}
|
||||
|
||||
|
||||
static float _driver_get_current(drv8711_driver_t *drv) {
|
||||
if (_driver_fault(drv)) return 0;
|
||||
|
||||
switch (drv->state) {
|
||||
case DRV8711_IDLE: return drv->idle.current;
|
||||
case DRV8711_ACTIVE: return drv->drive.current;
|
||||
default: return 0; // Off
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint8_t _driver_get_torque(drv8711_driver_t *drv) {
|
||||
if (estop_triggered()) return 0;
|
||||
|
||||
if(seek_active()) return drv->stall.torque;
|
||||
|
||||
switch (drv->state) {
|
||||
case DRV8711_IDLE: return drv->idle.torque;
|
||||
case DRV8711_ACTIVE: return drv->drive.torque;
|
||||
default: return 0; // Off
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint16_t _driver_spi_command(drv8711_driver_t *drv) {
|
||||
switch (drv->spi_state) {
|
||||
case SS_WRITE_OFF: return DRV8711_WRITE(DRV8711_OFF_REG, DRV8711_OFF);
|
||||
case SS_WRITE_BLANK: return DRV8711_WRITE(DRV8711_BLANK_REG, DRV8711_BLANK);
|
||||
case SS_WRITE_DECAY: return DRV8711_WRITE(DRV8711_DECAY_REG, DRV8711_DECAY);
|
||||
|
||||
case SS_WRITE_STALL: {
|
||||
//uint16_t reg = drv->stall_thresh | DRV8711_STALL_SDCNT_2 | drv->stall_vdiv; //ORIGINAL
|
||||
//uint16_t reg = 50 | DRV8711_STALL_SDCNT_8 | DRV8711_STALL_VDIV_4; //WORKS
|
||||
uint16_t reg = drv->stall_thresh | DRV8711_STALL_SDCNT_8 | drv->stall_vdiv;
|
||||
drv->last_thresh = drv->stall_thresh;
|
||||
return DRV8711_WRITE(DRV8711_STALL_REG, reg);
|
||||
}
|
||||
|
||||
case SS_WRITE_DRIVE: return DRV8711_WRITE(DRV8711_DRIVE_REG, DRV8711_DRIVE);
|
||||
|
||||
case SS_WRITE_TORQUE:
|
||||
drv->last_torque = _driver_get_torque(drv);
|
||||
return DRV8711_WRITE(DRV8711_TORQUE_REG, DRV8711_TORQUE | drv->last_torque);
|
||||
|
||||
case SS_WRITE_CTRL: {
|
||||
// NOTE, we disable the driver if it's not active. The chip gets hot
|
||||
// idling with the driver enabled.
|
||||
bool enable = _driver_get_torque(drv);
|
||||
drv->last_microstep = drv->microstep;
|
||||
return DRV8711_WRITE(DRV8711_CTRL_REG, DRV8711_CTRL |
|
||||
(drv->microstep << 3) |
|
||||
(enable ? DRV8711_CTRL_ENBL_bm : 0));
|
||||
}
|
||||
|
||||
case SS_READ_OFF: return DRV8711_READ(DRV8711_OFF_REG);
|
||||
case SS_READ_STATUS: return DRV8711_READ(DRV8711_STATUS_REG);
|
||||
|
||||
case SS_CLEAR_STATUS:
|
||||
drv->reset_flags = false;
|
||||
drv->flags = 0;
|
||||
return DRV8711_WRITE(DRV8711_STATUS_REG, 0x0fff & ~drv->status);
|
||||
}
|
||||
|
||||
return 0; // Should not get here
|
||||
}
|
||||
|
||||
|
||||
static spi_state_t _driver_spi_next(drv8711_driver_t *drv) {
|
||||
// Process response
|
||||
switch (drv->spi_state) {
|
||||
case SS_READ_OFF:
|
||||
// We read back the OFF register to test for communication failure.
|
||||
if ((spi.response & 0x1ff) != DRV8711_OFF)
|
||||
drv->flags |= DRV8711_COMM_ERROR_bm;
|
||||
else drv->flags &= ~DRV8711_COMM_ERROR_bm;
|
||||
break;
|
||||
|
||||
case SS_READ_STATUS: {
|
||||
drv->status = spi.response;
|
||||
|
||||
// NOTE If there is a power fault and the drivers are not powered
|
||||
// then the status flags will read 0xff but the motor fault line will
|
||||
// not be asserted. So, fault flags are not valid with out motor fault.
|
||||
// Also, a real stall cannot occur if the driver is inactive.
|
||||
bool active = _driver_active(drv);
|
||||
uint8_t mask =
|
||||
((motor_fault && !drv->reset_flags) ? 0xff : 0) | (active ? 0xc0 : 0);
|
||||
drv->flags = (drv->flags & 0xff00) | (mask & drv->status);
|
||||
|
||||
// EStop on fatal driver faults
|
||||
if (_driver_fault(drv)) estop_trigger(STAT_MOTOR_FAULT);
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
switch (drv->spi_state) {
|
||||
case SS_READ_OFF:
|
||||
if (drv->flags & DRV8711_COMM_ERROR_bm) return SS_WRITE_OFF; // Retry
|
||||
break;
|
||||
|
||||
case SS_READ_STATUS:
|
||||
if (drv->reset_flags) return SS_CLEAR_STATUS;
|
||||
if (drv->last_torque != _driver_get_torque(drv)) return SS_WRITE_TORQUE;
|
||||
if (drv->last_microstep != drv->microstep) return SS_WRITE_CTRL;
|
||||
if (drv->last_thresh != drv->stall_thresh) return SS_WRITE_STALL;
|
||||
// Fall through
|
||||
|
||||
case SS_CLEAR_STATUS: return SS_READ_OFF;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
return (spi_state_t)(drv->spi_state + 1); // Next
|
||||
}
|
||||
|
||||
|
||||
static void _spi_send() {
|
||||
drv8711_driver_t *drv = &drivers[spi.driver];
|
||||
|
||||
// Flush any status errors (TODO check SPI errors)
|
||||
uint8_t x = SPIC.STATUS;
|
||||
x = x;
|
||||
|
||||
// Read byte
|
||||
*DRV8711_WORD_BYTE_PTR(spi.response, !spi.low_byte) = SPIC.DATA;
|
||||
|
||||
// Advance state and process response
|
||||
if (spi.advance) {
|
||||
spi.advance = false;
|
||||
|
||||
// Handle response and set next state
|
||||
drv->spi_state = _driver_spi_next(drv);
|
||||
|
||||
// Next driver
|
||||
if (++spi.driver == DRIVERS) spi.driver = 0; // Wrap around
|
||||
drv = &drivers[spi.driver];
|
||||
}
|
||||
|
||||
// Disable CS
|
||||
if (spi.disable_cs_pin) {
|
||||
OUTCLR_PIN(spi.disable_cs_pin); // Set low (inactive)
|
||||
_delay_us(1);
|
||||
spi.disable_cs_pin = 0;
|
||||
}
|
||||
|
||||
if (spi.low_byte) {
|
||||
spi.disable_cs_pin = drv->cs_pin; // Schedule next CS disable
|
||||
spi.advance = true; // Word complete
|
||||
|
||||
} else {
|
||||
// Enable CS
|
||||
OUTSET_PIN(drv->cs_pin); // Set high (active)
|
||||
_delay_us(1);
|
||||
|
||||
// Get next command
|
||||
spi.command = _driver_spi_command(drv);
|
||||
}
|
||||
|
||||
// Write byte and prep next read
|
||||
SPIC.DATA = *DRV8711_WORD_BYTE_PTR(spi.command, spi.low_byte);
|
||||
|
||||
// Next byte
|
||||
spi.low_byte = !spi.low_byte;
|
||||
}
|
||||
|
||||
|
||||
ISR(SPIC_INT_vect) {_spi_send();}
|
||||
|
||||
|
||||
static void _stall_change(int driver, bool stalled) {
|
||||
drivers[driver].stalled = stalled;
|
||||
|
||||
// Call stall callback
|
||||
if (stalled && drivers[driver].stall_cb)
|
||||
drivers[driver].stall_cb(driver);
|
||||
}
|
||||
|
||||
|
||||
static void _stall_switch_cb(switch_id_t sw, bool active) {
|
||||
switch (sw) {
|
||||
case SW_STALL_0: _stall_change(0, active); break;
|
||||
case SW_STALL_1: _stall_change(1, active); break;
|
||||
case SW_STALL_2: _stall_change(2, active); break;
|
||||
case SW_STALL_3: _stall_change(3, active); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _motor_fault_switch_cb(switch_id_t sw, bool active) {
|
||||
motor_fault = active;
|
||||
}
|
||||
|
||||
|
||||
void drv8711_init() {
|
||||
// Setup pins
|
||||
// Must set the SS pin either in/high or out/any for master mode to work
|
||||
// Note, this pin is also used by the USART as the CTS line
|
||||
DIRSET_PIN(SPI_SS_PIN); // Output
|
||||
OUTSET_PIN(SPI_CLK_PIN); // High
|
||||
DIRSET_PIN(SPI_CLK_PIN); // Output
|
||||
DIRCLR_PIN(SPI_MISO_PIN); // Input
|
||||
OUTSET_PIN(SPI_MOSI_PIN); // High
|
||||
DIRSET_PIN(SPI_MOSI_PIN); // Output
|
||||
|
||||
// Motor driver enable
|
||||
OUTSET_PIN(MOTOR_ENABLE_PIN); // Active high
|
||||
DIRSET_PIN(MOTOR_ENABLE_PIN); // Output
|
||||
|
||||
for (int i = 0; i < DRIVERS; i++) {
|
||||
uint8_t cs_pin = drivers[i].cs_pin;
|
||||
OUTSET_PIN(cs_pin); // High
|
||||
DIRSET_PIN(cs_pin); // Output
|
||||
|
||||
switch_id_t stall_sw = drivers[i].stall_sw;
|
||||
switch_set_type(stall_sw, SW_NORMALLY_OPEN);
|
||||
switch_set_callback(stall_sw, _stall_switch_cb);
|
||||
|
||||
drivers[i].reset_flags = true; // Reset flags once on startup
|
||||
}
|
||||
|
||||
switch_set_type(SW_MOTOR_FAULT, SW_NORMALLY_OPEN);
|
||||
switch_set_callback(SW_MOTOR_FAULT, _motor_fault_switch_cb);
|
||||
|
||||
// Configure SPI
|
||||
PR.PRPC &= ~PR_SPI_bm; // Disable power reduction
|
||||
SPIC.CTRL = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_MODE_0_gc |
|
||||
SPI_PRESCALER_DIV16_gc; // enable, big endian, master, mode, clock div
|
||||
PIN_PORT(SPI_CLK_PIN)->REMAP = PORT_SPI_bm; // Swap SCK and MOSI
|
||||
SPIC.INTCTRL = SPI_INTLVL_LO_gc; // interupt level
|
||||
|
||||
_spi_send(); // Kick it off
|
||||
}
|
||||
|
||||
|
||||
drv8711_state_t drv8711_get_state(int driver) {
|
||||
if (driver < 0 || DRIVERS <= driver) return DRV8711_DISABLED;
|
||||
return drivers[driver].state;
|
||||
}
|
||||
|
||||
|
||||
void drv8711_set_state(int driver, drv8711_state_t state) {
|
||||
if (driver < 0 || DRIVERS <= driver) return;
|
||||
drivers[driver].state = state;
|
||||
}
|
||||
|
||||
|
||||
void drv8711_set_microsteps(int driver, uint16_t msteps) {
|
||||
if (driver < 0 || DRIVERS <= driver) return;
|
||||
switch (msteps) {
|
||||
case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: case 256:
|
||||
break;
|
||||
default: return; // Invalid
|
||||
}
|
||||
|
||||
drivers[driver].microstep = round(logf(msteps) / logf(2));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void drv8711_set_stall_callback(int driver, stall_callback_t cb) {
|
||||
drivers[driver].stall_cb = cb;
|
||||
}
|
||||
|
||||
|
||||
float get_drive_current(int driver) {
|
||||
if (driver < 0 || DRIVERS <= driver) return 0;
|
||||
return drivers[driver].drive.current;
|
||||
}
|
||||
|
||||
|
||||
void set_drive_current(int driver, float value) {
|
||||
if (driver < 0 || DRIVERS <= driver || value < 0) return;
|
||||
if (MAX_CURRENT < value) value = MAX_CURRENT;
|
||||
_current_set(&drivers[driver].drive, value);
|
||||
}
|
||||
|
||||
|
||||
float get_idle_current(int driver) {
|
||||
if (driver < 0 || DRIVERS <= driver) return 0;
|
||||
return drivers[driver].idle.current;
|
||||
}
|
||||
|
||||
|
||||
void set_idle_current(int driver, float value) {
|
||||
if (driver < 0 || DRIVERS <= driver || value < 0 || MAX_IDLE_CURRENT < value)
|
||||
return;
|
||||
|
||||
_current_set(&drivers[driver].idle, value);
|
||||
}
|
||||
|
||||
|
||||
float get_active_current(int driver) {
|
||||
if (driver < 0 || DRIVERS <= driver) return 0;
|
||||
return _driver_get_current(&drivers[driver]);
|
||||
}
|
||||
|
||||
|
||||
bool get_motor_fault() {return motor_fault;}
|
||||
|
||||
|
||||
void set_driver_flags(int driver, uint16_t flags) {
|
||||
drivers[driver].reset_flags = true;
|
||||
}
|
||||
|
||||
|
||||
uint16_t get_driver_flags(int driver) {return drivers[driver].flags;}
|
||||
bool get_driver_stalled(int driver) {return drivers[driver].stalled;}
|
||||
|
||||
|
||||
float get_stall_volts(int driver) {
|
||||
if (driver < 0 || DRIVERS <= driver) return 0;
|
||||
|
||||
float vdiv;
|
||||
switch (drivers[driver].stall_vdiv) {
|
||||
case DRV8711_STALL_VDIV_4: vdiv = 4; break;
|
||||
case DRV8711_STALL_VDIV_8: vdiv = 8; break;
|
||||
case DRV8711_STALL_VDIV_16: vdiv = 16; break;
|
||||
default: vdiv = 32; break;
|
||||
}
|
||||
|
||||
return 1.8 / 256 * vdiv * drivers[driver].stall_thresh;
|
||||
}
|
||||
|
||||
|
||||
void set_stall_volts(int driver, float volts) {
|
||||
if (driver < 0 || DRIVERS <= driver) return;
|
||||
|
||||
uint16_t vdiv = DRV8711_STALL_VDIV_32;
|
||||
uint16_t thresh = (uint16_t)(volts * 256 / 1.8);
|
||||
|
||||
if (thresh < 4 << 8) {
|
||||
thresh >>= 2;
|
||||
vdiv = DRV8711_STALL_VDIV_4;
|
||||
|
||||
} else if (thresh < 8 << 8) {
|
||||
thresh >>= 3;
|
||||
vdiv = DRV8711_STALL_VDIV_8;
|
||||
|
||||
} else if (thresh < 16 << 8) {
|
||||
thresh >>= 4;
|
||||
vdiv = DRV8711_STALL_VDIV_16;
|
||||
|
||||
} else {
|
||||
if (thresh < 32 << 8) thresh >>= 5;
|
||||
else thresh = 255;
|
||||
}
|
||||
|
||||
drivers[driver].stall_vdiv = vdiv;
|
||||
drivers[driver].stall_thresh = thresh;
|
||||
}
|
||||
|
||||
void set_stall_sample_time(int driver, uint16_t sample_time)
|
||||
{
|
||||
if (driver < 0 || DRIVERS <= driver) return;
|
||||
|
||||
switch (sample_time) {
|
||||
case 50: drivers[driver].sample_time = DRV8711_TORQUE_SMPLTH_50; break;
|
||||
case 100: drivers[driver].sample_time = DRV8711_TORQUE_SMPLTH_100; break;
|
||||
case 200: drivers[driver].sample_time = DRV8711_TORQUE_SMPLTH_200; break;
|
||||
case 300: drivers[driver].sample_time = DRV8711_TORQUE_SMPLTH_300; break;
|
||||
case 400: drivers[driver].sample_time = DRV8711_TORQUE_SMPLTH_400; break;
|
||||
case 600: drivers[driver].sample_time = DRV8711_TORQUE_SMPLTH_600; break;
|
||||
case 800: drivers[driver].sample_time = DRV8711_TORQUE_SMPLTH_800; break;
|
||||
case 1000: drivers[driver].sample_time = DRV8711_TORQUE_SMPLTH_1000; break;
|
||||
default: return; // Invalid
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uint16_t get_stall_sample_time(int driver)
|
||||
{
|
||||
if (driver < 0 || DRIVERS <= driver) return 0;
|
||||
|
||||
uint16_t sample_time = 0;
|
||||
switch (drivers[driver].sample_time) {
|
||||
case DRV8711_TORQUE_SMPLTH_50: sample_time = 50; break;
|
||||
case DRV8711_TORQUE_SMPLTH_100: sample_time = 100; break;
|
||||
case DRV8711_TORQUE_SMPLTH_200: sample_time = 200; break;
|
||||
case DRV8711_TORQUE_SMPLTH_300: sample_time = 300; break;
|
||||
case DRV8711_TORQUE_SMPLTH_400: sample_time = 400; break;
|
||||
case DRV8711_TORQUE_SMPLTH_600: sample_time = 600; break;
|
||||
case DRV8711_TORQUE_SMPLTH_800: sample_time = 800; break;
|
||||
case DRV8711_TORQUE_SMPLTH_1000: sample_time = 1000; break;
|
||||
default: sample_time = 50; break;
|
||||
}
|
||||
|
||||
return sample_time;
|
||||
}
|
||||
|
||||
float get_stall_current(int driver) {
|
||||
if (driver < 0 || DRIVERS <= driver) return 0;
|
||||
return drivers[driver].stall.current;
|
||||
}
|
||||
|
||||
|
||||
void set_stall_current(int driver, float value) {
|
||||
if (driver < 0 || DRIVERS <= driver || value < 0) return;
|
||||
if (MAX_CURRENT < value) value = MAX_CURRENT;
|
||||
_current_set(&drivers[driver].stall, value);
|
||||
}
|
||||
188
src/avr/src/drv8711.h
Normal file
188
src/avr/src/drv8711.h
Normal file
@@ -0,0 +1,188 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "config.h"
|
||||
#include "status.h"
|
||||
#include "motor.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum {
|
||||
DRV8711_CTRL_REG,
|
||||
DRV8711_TORQUE_REG,
|
||||
DRV8711_OFF_REG,
|
||||
DRV8711_BLANK_REG,
|
||||
DRV8711_DECAY_REG,
|
||||
DRV8711_STALL_REG,
|
||||
DRV8711_DRIVE_REG,
|
||||
DRV8711_STATUS_REG,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DRV8711_CTRL_ENBL_bm = 1 << 0,
|
||||
DRV8711_CTRL_RDIR_bm = 1 << 1,
|
||||
DRV8711_CTRL_RSTEP_bm = 1 << 2,
|
||||
DRV8711_CTRL_MODE_1 = 0 << 3,
|
||||
DRV8711_CTRL_MODE_2 = 1 << 3,
|
||||
DRV8711_CTRL_MODE_4 = 2 << 3,
|
||||
DRV8711_CTRL_MODE_8 = 3 << 3,
|
||||
DRV8711_CTRL_MODE_16 = 4 << 3,
|
||||
DRV8711_CTRL_MODE_32 = 5 << 3,
|
||||
DRV8711_CTRL_MODE_64 = 6 << 3,
|
||||
DRV8711_CTRL_MODE_128 = 7 << 3,
|
||||
DRV8711_CTRL_MODE_256 = 8 << 3,
|
||||
DRV8711_CTRL_EXSTALL_bm = 1 << 7,
|
||||
DRV8711_CTRL_ISGAIN_5 = 0 << 8,
|
||||
DRV8711_CTRL_ISGAIN_10 = 1 << 8,
|
||||
DRV8711_CTRL_ISGAIN_20 = 2 << 8,
|
||||
DRV8711_CTRL_ISGAIN_40 = 3 << 8,
|
||||
DRV8711_CTRL_DTIME_400 = 0 << 10,
|
||||
DRV8711_CTRL_DTIME_450 = 1 << 10,
|
||||
DRV8711_CTRL_DTIME_650 = 2 << 10,
|
||||
DRV8711_CTRL_DTIME_850 = 3 << 10,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DRV8711_TORQUE_SMPLTH_50 = 0 << 8,
|
||||
DRV8711_TORQUE_SMPLTH_100 = 1 << 8,
|
||||
DRV8711_TORQUE_SMPLTH_200 = 2 << 8,
|
||||
DRV8711_TORQUE_SMPLTH_300 = 3 << 8,
|
||||
DRV8711_TORQUE_SMPLTH_400 = 4 << 8,
|
||||
DRV8711_TORQUE_SMPLTH_600 = 5 << 8,
|
||||
DRV8711_TORQUE_SMPLTH_800 = 6 << 8,
|
||||
DRV8711_TORQUE_SMPLTH_1000 = 7 << 8,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DRV8711_OFF_PWMMODE_bm = 1 << 8,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DRV8711_BLANK_ABT_bm = 1 << 8,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DRV8711_DECAY_DECMOD_SLOW = 0 << 8,
|
||||
DRV8711_DECAY_DECMOD_OPT = 1 << 8,
|
||||
DRV8711_DECAY_DECMOD_FAST = 2 << 8,
|
||||
DRV8711_DECAY_DECMOD_MIXED = 3 << 8,
|
||||
DRV8711_DECAY_DECMOD_AUTO_OPT = 4 << 8,
|
||||
DRV8711_DECAY_DECMOD_AUTO_MIXED = 5 << 8,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DRV8711_STALL_SDCNT_1 = 0 << 8,
|
||||
DRV8711_STALL_SDCNT_2 = 1 << 8,
|
||||
DRV8711_STALL_SDCNT_4 = 2 << 8,
|
||||
DRV8711_STALL_SDCNT_8 = 3 << 8,
|
||||
DRV8711_STALL_VDIV_32 = 0 << 10,
|
||||
DRV8711_STALL_VDIV_16 = 1 << 10,
|
||||
DRV8711_STALL_VDIV_8 = 2 << 10,
|
||||
DRV8711_STALL_VDIV_4 = 3 << 10,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DRV8711_DRIVE_OCPTH_250 = 0 << 0,
|
||||
DRV8711_DRIVE_OCPTH_500 = 1 << 0,
|
||||
DRV8711_DRIVE_OCPTH_750 = 2 << 0,
|
||||
DRV8711_DRIVE_OCPTH_1000 = 3 << 0,
|
||||
DRV8711_DRIVE_OCPDEG_1 = 0 << 2,
|
||||
DRV8711_DRIVE_OCPDEG_2 = 1 << 2,
|
||||
DRV8711_DRIVE_OCPDEG_4 = 2 << 2,
|
||||
DRV8711_DRIVE_OCPDEG_8 = 3 << 2,
|
||||
DRV8711_DRIVE_TDRIVEN_250 = 0 << 4,
|
||||
DRV8711_DRIVE_TDRIVEN_500 = 1 << 4,
|
||||
DRV8711_DRIVE_TDRIVEN_1000 = 2 << 4,
|
||||
DRV8711_DRIVE_TDRIVEN_2000 = 3 << 4,
|
||||
DRV8711_DRIVE_TDRIVEP_250 = 0 << 6,
|
||||
DRV8711_DRIVE_TDRIVEP_500 = 1 << 6,
|
||||
DRV8711_DRIVE_TDRIVEP_1000 = 2 << 6,
|
||||
DRV8711_DRIVE_TDRIVEP_2000 = 3 << 6,
|
||||
DRV8711_DRIVE_IDRIVEN_100 = 0 << 8,
|
||||
DRV8711_DRIVE_IDRIVEN_200 = 1 << 8,
|
||||
DRV8711_DRIVE_IDRIVEN_300 = 2 << 8,
|
||||
DRV8711_DRIVE_IDRIVEN_400 = 3 << 8,
|
||||
DRV8711_DRIVE_IDRIVEP_50 = 0 << 10,
|
||||
DRV8711_DRIVE_IDRIVEP_100 = 1 << 10,
|
||||
DRV8711_DRIVE_IDRIVEP_150 = 2 << 10,
|
||||
DRV8711_DRIVE_IDRIVEP_200 = 3 << 10,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DRV8711_STATUS_OTS_bm = 1 << 0,
|
||||
DRV8711_STATUS_AOCP_bm = 1 << 1,
|
||||
DRV8711_STATUS_BOCP_bm = 1 << 2,
|
||||
DRV8711_STATUS_APDF_bm = 1 << 3,
|
||||
DRV8711_STATUS_BPDF_bm = 1 << 4,
|
||||
DRV8711_STATUS_UVLO_bm = 1 << 5,
|
||||
DRV8711_STATUS_STD_bm = 1 << 6,
|
||||
DRV8711_STATUS_STDLAT_bm = 1 << 7,
|
||||
DRV8711_COMM_ERROR_bm = 1 << 8,
|
||||
};
|
||||
|
||||
|
||||
#define DRV8711_READ(ADDR) ((1 << 15) | ((ADDR) << 12))
|
||||
#define DRV8711_WRITE(ADDR, DATA) (((ADDR) << 12) | ((DATA) & 0xfff))
|
||||
#define DRV8711_CMD_ADDR(CMD) (((CMD) >> 12) & 7)
|
||||
#define DRV8711_CMD_IS_READ(CMD) ((1 << 15) & (CMD))
|
||||
|
||||
#define DRV8711_CTRL_ISGAIN_BM (3 << 8)
|
||||
#define DRV8711_CTRL_GET_GAIN(CTRL) ((CTRL) & DRV8711_CTRL_ISGAIN_BM)
|
||||
|
||||
#define DRV8711_CTRL_GAIN(CTRL) \
|
||||
(DRV8711_CTRL_GET_GAIN(CTRL) == DRV8711_CTRL_ISGAIN_5 ? 5 : \
|
||||
(DRV8711_CTRL_GET_GAIN(CTRL) == DRV8711_CTRL_ISGAIN_10 ? 10 : \
|
||||
(DRV8711_CTRL_GET_GAIN(CTRL) == DRV8711_CTRL_ISGAIN_20 ? 20 : 40)))
|
||||
|
||||
|
||||
typedef enum {
|
||||
DRV8711_DISABLED,
|
||||
DRV8711_IDLE,
|
||||
DRV8711_ACTIVE,
|
||||
} drv8711_state_t;
|
||||
|
||||
|
||||
typedef void (*stall_callback_t)(int driver);
|
||||
|
||||
|
||||
void drv8711_init();
|
||||
drv8711_state_t drv8711_get_state(int driver);
|
||||
void drv8711_set_state(int driver, drv8711_state_t state);
|
||||
void drv8711_set_microsteps(int driver, uint16_t msteps);
|
||||
void drv8711_set_stall_callback(int driver, stall_callback_t cb);
|
||||
36
src/avr/src/emu.h
Normal file
36
src/avr/src/emu.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#ifdef __AVR__
|
||||
#define emu_init()
|
||||
#define emu_callback()
|
||||
|
||||
#else
|
||||
void emu_init();
|
||||
void emu_callback();
|
||||
|
||||
#endif
|
||||
126
src/avr/src/estop.c
Normal file
126
src/avr/src/estop.c
Normal file
@@ -0,0 +1,126 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "estop.h"
|
||||
#include "motor.h"
|
||||
#include "stepper.h"
|
||||
#include "spindle.h"
|
||||
#include "switch.h"
|
||||
#include "hardware.h"
|
||||
#include "config.h"
|
||||
#include "state.h"
|
||||
#include "outputs.h"
|
||||
#include "jog.h"
|
||||
#include "exec.h"
|
||||
|
||||
|
||||
static stat_t estop_reason = STAT_OK;
|
||||
|
||||
|
||||
static void _switch_callback(switch_id_t id, bool active) {
|
||||
if (active) estop_trigger(STAT_ESTOP_SWITCH);
|
||||
else estop_clear();
|
||||
}
|
||||
|
||||
|
||||
void estop_init() {
|
||||
switch_set_callback(SW_ESTOP, _switch_callback);
|
||||
if (switch_is_active(SW_ESTOP)) estop_trigger(STAT_ESTOP_SWITCH);
|
||||
}
|
||||
|
||||
|
||||
bool estop_triggered() {return estop_reason != STAT_OK;}
|
||||
|
||||
|
||||
void estop_trigger(stat_t reason) {
|
||||
if (estop_triggered()) return;
|
||||
estop_reason = reason;
|
||||
|
||||
// Set fault signal
|
||||
outputs_set_active(FAULT_PIN, true);
|
||||
|
||||
// Shutdown peripherals
|
||||
st_shutdown();
|
||||
spindle_estop();
|
||||
jog_stop();
|
||||
outputs_stop();
|
||||
|
||||
// Set machine state
|
||||
state_estop();
|
||||
}
|
||||
|
||||
|
||||
void estop_clear() {
|
||||
// It is important that we don't clear the estop if it's not set because
|
||||
// it can cause a reboot loop.
|
||||
if (!estop_triggered()) return;
|
||||
|
||||
// Check if estop switch is set
|
||||
if (switch_is_active(SW_ESTOP)) {
|
||||
estop_reason = STAT_ESTOP_SWITCH;
|
||||
return; // Can't clear while estop switch is still active
|
||||
}
|
||||
|
||||
// Clear fault signal
|
||||
outputs_set_active(FAULT_PIN, false);
|
||||
|
||||
estop_reason = STAT_OK;
|
||||
|
||||
// Reboot
|
||||
// Note, hardware.c waits until any spindle stop command has been delivered
|
||||
hw_request_hard_reset();
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
bool get_estop() {return estop_triggered();}
|
||||
|
||||
|
||||
void set_estop(bool value) {
|
||||
if (value == estop_triggered()) return;
|
||||
if (value) estop_trigger(STAT_ESTOP_USER);
|
||||
else estop_clear();
|
||||
}
|
||||
|
||||
|
||||
PGM_P get_estop_reason() {return status_to_pgmstr(estop_reason);}
|
||||
|
||||
|
||||
// Command callbacks
|
||||
stat_t command_estop(char *cmd) {
|
||||
estop_trigger(STAT_ESTOP_USER);
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t command_shutdown(char *cmd) {
|
||||
estop_trigger(STAT_POWER_SHUTDOWN);
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t command_clear(char *cmd) {estop_clear(); return STAT_OK;}
|
||||
42
src/avr/src/estop.h
Normal file
42
src/avr/src/estop.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "status.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
void estop_init();
|
||||
bool estop_triggered();
|
||||
void estop_trigger(stat_t reason);
|
||||
void estop_clear();
|
||||
|
||||
|
||||
#define ESTOP_ASSERT(COND, CODE) \
|
||||
do {if (!(COND)) estop_trigger(CODE);} while (0)
|
||||
306
src/avr/src/exec.c
Normal file
306
src/avr/src/exec.c
Normal file
@@ -0,0 +1,306 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "exec.h"
|
||||
|
||||
#include "stepper.h"
|
||||
#include "motor.h"
|
||||
#include "axis.h"
|
||||
#include "util.h"
|
||||
#include "command.h"
|
||||
#include "switch.h"
|
||||
#include "seek.h"
|
||||
#include "estop.h"
|
||||
#include "state.h"
|
||||
#include "spindle.h"
|
||||
#include "config.h"
|
||||
#include "SCurve.h"
|
||||
|
||||
|
||||
static struct {
|
||||
exec_cb_t cb;
|
||||
|
||||
float position[AXES];
|
||||
float velocity;
|
||||
float accel;
|
||||
float jerk;
|
||||
float peak_vel;
|
||||
float peak_accel;
|
||||
|
||||
float feed_override;
|
||||
|
||||
struct {
|
||||
float target[AXES];
|
||||
float time;
|
||||
float vel;
|
||||
float accel;
|
||||
float max_accel;
|
||||
float max_jerk;
|
||||
power_update_t power_updates[2 * POWER_MAX_UPDATES];
|
||||
exec_cb_t cb;
|
||||
} seg;
|
||||
} ex;
|
||||
|
||||
|
||||
static void _limit_switch_cb(switch_id_t sw, bool active) {
|
||||
if (sw == seek_get_switch()) return;
|
||||
if (ex.velocity && active) estop_trigger(STAT_ESTOP_SWITCH);
|
||||
}
|
||||
|
||||
|
||||
void exec_init() {
|
||||
memset(&ex, 0, sizeof(ex));
|
||||
ex.feed_override = 1; // TODO implement feed override
|
||||
|
||||
// Set callback for limit switches
|
||||
for (int sw = SW_MIN_0; sw <= SW_MAX_3; sw++)
|
||||
switch_set_callback((switch_id_t)sw, _limit_switch_cb);
|
||||
}
|
||||
|
||||
|
||||
void exec_get_position(float p[AXES]) {
|
||||
memcpy(p, ex.position, sizeof(ex.position));
|
||||
}
|
||||
|
||||
|
||||
float exec_get_axis_position(int axis) {return ex.position[axis];}
|
||||
|
||||
|
||||
void exec_set_velocity(float v) {
|
||||
ex.velocity = v;
|
||||
if (ex.peak_vel < v) ex.peak_vel = v;
|
||||
}
|
||||
|
||||
|
||||
float exec_get_velocity() {return ex.velocity;}
|
||||
|
||||
|
||||
void exec_set_acceleration(float a) {
|
||||
ex.accel = a;
|
||||
if (ex.peak_accel < a) ex.peak_accel = a;
|
||||
}
|
||||
|
||||
|
||||
float exec_get_acceleration() {return ex.accel;}
|
||||
void exec_set_jerk(float j) {ex.jerk = j;}
|
||||
|
||||
|
||||
void exec_set_cb(exec_cb_t cb) {ex.cb = cb;}
|
||||
|
||||
|
||||
void exec_move_to_target(const float target[]) {
|
||||
ESTOP_ASSERT(isfinite(target[AXIS_X]) && isfinite(target[AXIS_Y]) &&
|
||||
isfinite(target[AXIS_Z]) && isfinite(target[AXIS_A]) &&
|
||||
isfinite(target[AXIS_B]) && isfinite(target[AXIS_C]),
|
||||
STAT_BAD_FLOAT);
|
||||
|
||||
// Prep power updates
|
||||
st_prep_power(ex.seg.power_updates);
|
||||
|
||||
// Shift power updates
|
||||
for (unsigned i = 0; i < POWER_MAX_UPDATES; i++) {
|
||||
ex.seg.power_updates[i] = ex.seg.power_updates[i + POWER_MAX_UPDATES];
|
||||
ex.seg.power_updates[i + POWER_MAX_UPDATES].state = POWER_IGNORE;
|
||||
}
|
||||
|
||||
// Update position
|
||||
copy_vector(ex.position, target);
|
||||
|
||||
// Call the stepper prep function
|
||||
st_prep_line(target);
|
||||
}
|
||||
|
||||
|
||||
stat_t _segment_exec() {
|
||||
float t = ex.seg.time;
|
||||
float v = ex.seg.vel;
|
||||
float a = ex.seg.accel;
|
||||
|
||||
// Handle pause
|
||||
if (state_get() == STATE_STOPPING) {
|
||||
a = SCurve::nextAccel(SEGMENT_TIME, 0, ex.velocity, ex.accel,
|
||||
ex.seg.max_accel, ex.seg.max_jerk);
|
||||
v = ex.velocity + SEGMENT_TIME * a;
|
||||
t *= ex.seg.vel / v;
|
||||
|
||||
if (v < MIN_VELOCITY || seek_switch_found()) {
|
||||
t = v = 0;
|
||||
ex.seg.cb = 0;
|
||||
command_reset_position();
|
||||
state_holding();
|
||||
seek_end();
|
||||
spindle_update_speed();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for next seg if time is too short and we are still moving
|
||||
if (t < SEGMENT_TIME && (!t || v)) {
|
||||
if (!v) {
|
||||
exec_set_velocity(0);
|
||||
exec_set_acceleration(0);
|
||||
exec_set_jerk(0);
|
||||
ex.seg.time = 0;
|
||||
}
|
||||
ex.cb = ex.seg.cb;
|
||||
return STAT_AGAIN;
|
||||
}
|
||||
|
||||
// Update velocity and accel
|
||||
exec_set_velocity(v);
|
||||
exec_set_acceleration(a);
|
||||
|
||||
if (t <= SEGMENT_TIME) {
|
||||
// Move
|
||||
exec_move_to_target(ex.seg.target);
|
||||
ex.seg.time = 0;
|
||||
|
||||
} else {
|
||||
// Compute next target
|
||||
float ratio = SEGMENT_TIME / t;
|
||||
float target[AXES];
|
||||
for (int axis = 0; axis < AXES; axis++) {
|
||||
float diff = ex.seg.target[axis] - ex.position[axis];
|
||||
target[axis] = ex.position[axis] + ratio * diff;
|
||||
}
|
||||
|
||||
// Move
|
||||
exec_move_to_target(target);
|
||||
|
||||
// Update time
|
||||
if (t == ex.seg.time) ex.seg.time -= SEGMENT_TIME;
|
||||
else ex.seg.time -= SEGMENT_TIME * v / ex.seg.vel;
|
||||
}
|
||||
|
||||
// Check switch
|
||||
if (seek_switch_found()) state_seek_hold();
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t exec_segment(float time, const float target[], float vel, float accel,
|
||||
float maxAccel, float maxJerk,
|
||||
const power_update_t power_updates[]) {
|
||||
// Copy power updates in to the correct position given the time offset
|
||||
float nextT = ex.seg.time + time;
|
||||
const float stepT = 1.0 / 60000; // 1ms in mins
|
||||
float t = 0.5 / 60000; // 0.5ms in mins
|
||||
unsigned j = 0;
|
||||
for (unsigned i = 0; t < nextT && j < POWER_MAX_UPDATES; i++) {
|
||||
if (ex.seg.time < t) ex.seg.power_updates[i] = power_updates[j++];
|
||||
t += stepT;
|
||||
}
|
||||
|
||||
copy_vector(ex.seg.target, target);
|
||||
ex.seg.time = nextT;
|
||||
ex.seg.vel = vel;
|
||||
ex.seg.accel = accel;
|
||||
ex.seg.max_accel = maxAccel;
|
||||
ex.seg.max_jerk = maxJerk;
|
||||
ex.seg.cb = ex.cb;
|
||||
ex.cb = _segment_exec;
|
||||
|
||||
// TODO To be precise, seek_end() should not be called until the current
|
||||
// segment has completed execution.
|
||||
if (!ex.seg.cb) seek_end(); // No callback when at line end
|
||||
|
||||
return _segment_exec();
|
||||
}
|
||||
|
||||
|
||||
// Called by stepper.c from low-level interrupt
|
||||
stat_t exec_next() {
|
||||
// Hold if we've reached zero velocity between commands and stopping
|
||||
if (!ex.cb && !exec_get_velocity() && state_get() == STATE_STOPPING)
|
||||
state_holding();
|
||||
|
||||
if (state_get() == STATE_HOLDING) return STAT_NOP;
|
||||
if (!ex.cb && !command_exec()) return STAT_NOP; // Queue empty
|
||||
if (!ex.cb) return STAT_AGAIN; // Non-exec command
|
||||
return ex.cb(); // Exec
|
||||
}
|
||||
|
||||
|
||||
// Variable callbacks
|
||||
float get_axis_position(int axis) {return ex.position[axis];}
|
||||
float get_velocity() {return ex.velocity / VELOCITY_MULTIPLIER;}
|
||||
float get_acceleration() {return ex.accel / ACCEL_MULTIPLIER;}
|
||||
float get_jerk() {return ex.jerk / JERK_MULTIPLIER;}
|
||||
float get_peak_vel() {return ex.peak_vel / VELOCITY_MULTIPLIER;}
|
||||
void set_peak_vel(float x) {ex.peak_vel = 0;}
|
||||
float get_peak_accel() {return ex.peak_accel / ACCEL_MULTIPLIER;}
|
||||
void set_peak_accel(float x) {ex.peak_accel = 0;}
|
||||
uint16_t get_feed_override() {return ex.feed_override * 1000;}
|
||||
void set_feed_override(uint16_t value) {ex.feed_override = value / 1000.0;}
|
||||
|
||||
|
||||
// Command callbacks
|
||||
typedef struct {
|
||||
uint8_t axis;
|
||||
float position;
|
||||
} set_axis_t;
|
||||
|
||||
|
||||
stat_t command_set_axis(char *cmd) {
|
||||
cmd++; // Skip command name
|
||||
|
||||
// Decode axis
|
||||
int axis = axis_get_id(*cmd++);
|
||||
if (axis < 0) return STAT_INVALID_ARGUMENTS;
|
||||
|
||||
// Decode position
|
||||
float position;
|
||||
if (!decode_float(&cmd, &position)) return STAT_BAD_FLOAT;
|
||||
|
||||
// Check for end of command
|
||||
if (*cmd) return STAT_INVALID_ARGUMENTS;
|
||||
|
||||
// Update command
|
||||
command_set_axis_position((uint8_t)axis, position);
|
||||
|
||||
// Queue
|
||||
set_axis_t set_axis = {(uint8_t)axis, position};
|
||||
command_push(COMMAND_set_axis, &set_axis);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_set_axis_size() {return sizeof(set_axis_t);}
|
||||
|
||||
|
||||
void command_set_axis_exec(void *data) {
|
||||
set_axis_t *cmd = (set_axis_t *)data;
|
||||
|
||||
// Update exec
|
||||
ex.position[cmd->axis] = cmd->position;
|
||||
|
||||
// Update motors
|
||||
for (int motor = 0; motor < MOTORS; motor++)
|
||||
if (motor_get_axis(motor) == cmd->axis)
|
||||
motor_set_position(motor, cmd->position);
|
||||
}
|
||||
59
src/avr/src/exec.h
Normal file
59
src/avr/src/exec.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "config.h"
|
||||
#include "spindle.h"
|
||||
#include "status.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef stat_t (*exec_cb_t)();
|
||||
|
||||
|
||||
void exec_init();
|
||||
|
||||
void exec_get_position(float p[AXES]);
|
||||
float exec_get_axis_position(int axis);
|
||||
float exec_get_power_scale();
|
||||
void exec_set_velocity(float v);
|
||||
float exec_get_velocity();
|
||||
void exec_set_acceleration(float a);
|
||||
float exec_get_acceleration();
|
||||
void exec_set_jerk(float j);
|
||||
|
||||
void exec_set_cb(exec_cb_t cb);
|
||||
|
||||
void exec_move_to_target(const float target[]);
|
||||
stat_t exec_segment(float time, const float target[], float vel, float accel,
|
||||
float maxAccel, float maxJerk,
|
||||
const power_update_t power_updates[]);
|
||||
stat_t exec_next();
|
||||
158
src/avr/src/hardware.c
Normal file
158
src/avr/src/hardware.c
Normal file
@@ -0,0 +1,158 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "hardware.h"
|
||||
#include "rtc.h"
|
||||
#include "usart.h"
|
||||
#include "config.h"
|
||||
#include "pgmspace.h"
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/eeprom.h>
|
||||
#include <avr/wdt.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
char id[26];
|
||||
bool hard_reset; // flag to perform a hard reset
|
||||
} hw_t;
|
||||
|
||||
static hw_t hw = {{0}};
|
||||
|
||||
|
||||
#define PROD_SIGS (*(NVM_PROD_SIGNATURES_t *)0x0000)
|
||||
#define HEXNIB(x) "0123456789abcdef"[(x) & 0xf]
|
||||
|
||||
|
||||
static void _init_clock() {
|
||||
#if 0 // 32Mhz Int RC
|
||||
OSC.CTRL |= OSC_RC32MEN_bm | OSC_RC32KEN_bm; // Enable 32MHz & 32KHz osc
|
||||
while (!(OSC.STATUS & OSC_RC32KRDY_bm)); // Wait for 32Khz oscillator
|
||||
while (!(OSC.STATUS & OSC_RC32MRDY_bm)); // Wait for 32MHz oscillator
|
||||
|
||||
// Defaults to calibrate against internal 32Khz clock
|
||||
DFLLRC32M.CTRL = DFLL_ENABLE_bm; // Enable DFLL
|
||||
CCP = CCP_IOREG_gc; // Disable register security
|
||||
CLK.CTRL = CLK_SCLKSEL_RC32M_gc; // Switch to 32MHz clock
|
||||
|
||||
#else
|
||||
// 12-16 MHz crystal; 0.4-16 MHz XTAL w/ 16K CLK startup
|
||||
OSC.XOSCCTRL = OSC_FRQRANGE_12TO16_gc | OSC_XOSCSEL_XTAL_16KCLK_gc;
|
||||
OSC.CTRL = OSC_XOSCEN_bm; // enable external crystal oscillator
|
||||
while (!(OSC.STATUS & OSC_XOSCRDY_bm)); // wait for oscillator ready
|
||||
|
||||
OSC.PLLCTRL = OSC_PLLSRC_XOSC_gc | 2; // PLL source, 2x (32 MHz sys clock)
|
||||
OSC.CTRL = OSC_PLLEN_bm | OSC_XOSCEN_bm; // Enable PLL & External Oscillator
|
||||
while (!(OSC.STATUS & OSC_PLLRDY_bm)); // wait for PLL ready
|
||||
|
||||
CCP = CCP_IOREG_gc;
|
||||
CLK.CTRL = CLK_SCLKSEL_PLL_gc; // switch to PLL clock
|
||||
#endif
|
||||
|
||||
OSC.CTRL &= ~OSC_RC2MEN_bm; // disable internal 2 MHz clock
|
||||
}
|
||||
|
||||
|
||||
#ifdef __AVR__
|
||||
static void _load_hw_id_byte(int i, register8_t *reg) {
|
||||
NVM.CMD = NVM_CMD_READ_CALIB_ROW_gc;
|
||||
uint8_t byte = pgm_read_byte(reg);
|
||||
NVM.CMD = NVM_CMD_NO_OPERATION_gc;
|
||||
|
||||
hw.id[i] = HEXNIB(byte >> 4);
|
||||
hw.id[i + 1] = HEXNIB(byte);
|
||||
}
|
||||
#endif // __AVR__
|
||||
|
||||
|
||||
static void _read_hw_id() {
|
||||
#ifdef __AVR__
|
||||
int i = 0;
|
||||
_load_hw_id_byte(i, &PROD_SIGS.LOTNUM5); i += 2;
|
||||
_load_hw_id_byte(i, &PROD_SIGS.LOTNUM4); i += 2;
|
||||
_load_hw_id_byte(i, &PROD_SIGS.LOTNUM3); i += 2;
|
||||
_load_hw_id_byte(i, &PROD_SIGS.LOTNUM2); i += 2;
|
||||
_load_hw_id_byte(i, &PROD_SIGS.LOTNUM1); i += 2;
|
||||
_load_hw_id_byte(i, &PROD_SIGS.LOTNUM0); i += 2;
|
||||
hw.id[i++] = '-';
|
||||
_load_hw_id_byte(i, &PROD_SIGS.WAFNUM); i += 2;
|
||||
hw.id[i++] = '-';
|
||||
_load_hw_id_byte(i, &PROD_SIGS.COORDX1); i += 2;
|
||||
_load_hw_id_byte(i, &PROD_SIGS.COORDX0); i += 2;
|
||||
hw.id[i++] = '-';
|
||||
_load_hw_id_byte(i, &PROD_SIGS.COORDY1); i += 2;
|
||||
_load_hw_id_byte(i, &PROD_SIGS.COORDY0); i += 2;
|
||||
hw.id[i] = 0;
|
||||
|
||||
#else // __AVR__
|
||||
for (int i = 0; i < 25; i++)
|
||||
hw.id[i] = '0';
|
||||
hw.id[25] = 0;
|
||||
#endif // __AVR__
|
||||
}
|
||||
|
||||
|
||||
/// Lowest level hardware init
|
||||
void hw_init() {
|
||||
_init_clock(); // set system clock
|
||||
rtc_init(); // real time counter
|
||||
_read_hw_id();
|
||||
|
||||
// Round-robin, interrupts in application section, all interupts levels
|
||||
CCP = CCP_IOREG_gc;
|
||||
PMIC.CTRL =
|
||||
PMIC_RREN_bm | PMIC_HILVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_LOLVLEN_bm;
|
||||
}
|
||||
|
||||
|
||||
void hw_request_hard_reset() {hw.hard_reset = true;}
|
||||
|
||||
|
||||
static void _hard_reset() {
|
||||
usart_flush();
|
||||
cli();
|
||||
CCP = CCP_IOREG_gc;
|
||||
RST.CTRL = RST_SWRST_bm;
|
||||
}
|
||||
|
||||
|
||||
/// Controller's rest handler
|
||||
void hw_reset_handler() {
|
||||
if (!hw.hard_reset) return;
|
||||
|
||||
// Flush serial port and wait for EEPROM writes to finish
|
||||
while (!usart_tx_empty() || !eeprom_is_ready())
|
||||
continue;
|
||||
|
||||
_hard_reset();
|
||||
}
|
||||
|
||||
|
||||
const char *get_hw_id() {return hw.id;}
|
||||
40
src/avr/src/hardware.h
Normal file
40
src/avr/src/hardware.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "status.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
void hw_init();
|
||||
void hw_request_hard_reset();
|
||||
void hw_reset_handler();
|
||||
|
||||
uint8_t hw_disable_watchdog();
|
||||
void hw_restore_watchdog(uint8_t state);
|
||||
301
src/avr/src/huanyang.c
Normal file
301
src/avr/src/huanyang.c
Normal file
@@ -0,0 +1,301 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "huanyang.h"
|
||||
#include "config.h"
|
||||
#include "modbus.h"
|
||||
#include "estop.h"
|
||||
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/*
|
||||
Huanyang is not quite Modbus compliant.
|
||||
|
||||
Message format is:
|
||||
|
||||
[id][func][length][data][checksum]
|
||||
|
||||
Where:
|
||||
|
||||
id - 1-byte Peer ID
|
||||
func - 1-byte One of hy_func_t
|
||||
length - 1-byte Length data in bytes
|
||||
data - length bytes - Command arguments
|
||||
checksum - 16-bit CRC: x^16 + x^15 + x^2 + 1 (0xa001) initial: 0xffff
|
||||
*/
|
||||
|
||||
|
||||
// See VFD manual pg56 3.1.3
|
||||
typedef enum {
|
||||
HUANYANG_FUNC_READ = 1, // [len=1][hy_addr_t]
|
||||
HUANYANG_FUNC_WRITE, // [len=3][hy_addr_t][data]
|
||||
HUANYANG_CTRL_WRITE, // [len=1][hy_ctrl_state_t]
|
||||
HUANYANG_CTRL_READ, // [len=1][hy_ctrl_addr_t]
|
||||
HUANYANG_FREQ_WRITE, // [len=2][freq]
|
||||
HUANYANG_RESERVED_1,
|
||||
HUANYANG_RESERVED_2,
|
||||
HUANYANG_LOOP_TEST,
|
||||
} hy_func_t;
|
||||
|
||||
|
||||
// Sent in HUANYANG_CTRL_WRITE
|
||||
// See VFD manual pg57 3.1.3.c
|
||||
typedef enum {
|
||||
HUANYANG_RUN = 1 << 0,
|
||||
HUANYANG_FORWARD = 1 << 1,
|
||||
HUANYANG_REVERSE = 1 << 2,
|
||||
HUANYANG_STOP = 1 << 3,
|
||||
HUANYANG_REV_FWD = 1 << 4,
|
||||
HUANYANG_JOG = 1 << 5,
|
||||
HUANYANG_JOG_FORWARD = 1 << 6,
|
||||
HUANYANG_JOG_REVERSE = 1 << 7,
|
||||
} hy_ctrl_state_t;
|
||||
|
||||
|
||||
// Returned by HUANYANG_CTRL_WRITE
|
||||
// See VFD manual pg57 3.1.3.c
|
||||
typedef enum {
|
||||
HUANYANG_STATUS_RUN = 1 << 0,
|
||||
HUANYANG_STATUS_JOG = 1 << 1,
|
||||
HUANYANG_STATUS_COMMAND_REV = 1 << 2,
|
||||
HUANYANG_STATUS_RUNNING = 1 << 3,
|
||||
HUANYANG_STATUS_JOGGING = 1 << 4,
|
||||
HUANYANG_STATUS_JOGGING_REV = 1 << 5,
|
||||
HUANYANG_STATUS_BRAKING = 1 << 6,
|
||||
HUANYANG_STATUS_TRACK_START = 1 << 7,
|
||||
} hy_ctrl_status_t;
|
||||
|
||||
|
||||
// Sent in HUANYANG_CTRL_READ
|
||||
// See VFD manual pg57 3.1.3.d
|
||||
typedef enum {
|
||||
HUANYANG_TARGET_FREQ,
|
||||
HUANYANG_ACTUAL_FREQ,
|
||||
HUANYANG_ACTUAL_CURRENT,
|
||||
HUANYANG_ACTUAL_RPM,
|
||||
HUANYANG_DCV,
|
||||
HUANYANG_ACV,
|
||||
HUANYANG_COUNTER,
|
||||
HUANYANG_TEMPERATURE,
|
||||
} hy_ctrl_addr_t;
|
||||
|
||||
|
||||
static struct {
|
||||
uint8_t state;
|
||||
|
||||
deinit_cb_t deinit_cb;
|
||||
bool shutdown;
|
||||
bool changed;
|
||||
float power;
|
||||
|
||||
float actual_freq;
|
||||
float actual_current;
|
||||
uint16_t actual_rpm;
|
||||
uint16_t temperature;
|
||||
|
||||
float max_freq;
|
||||
float min_freq;
|
||||
uint16_t rated_rpm;
|
||||
|
||||
uint8_t status;
|
||||
} hy;
|
||||
|
||||
|
||||
static void _func_read_response(hy_addr_t addr, uint16_t value) {
|
||||
switch (addr) {
|
||||
case HY_PD005_MAX_FREQUENCY: hy.max_freq = value * 0.01; break;
|
||||
case HY_PD011_FREQUENCY_LOWER_LIMIT: hy.min_freq = value * 0.01; break;
|
||||
case HY_PD144_RATED_MOTOR_RPM: hy.rated_rpm = value; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _ctrl_read_response(hy_ctrl_addr_t addr, uint16_t value) {
|
||||
switch (addr) {
|
||||
case HUANYANG_ACTUAL_FREQ: hy.actual_freq = value * 0.01; break;
|
||||
case HUANYANG_ACTUAL_CURRENT: hy.actual_current = value * 0.01; break;
|
||||
case HUANYANG_ACTUAL_RPM: hy.actual_rpm = value; break;
|
||||
case HUANYANG_TEMPERATURE: hy.temperature = value; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint16_t _read_word(const uint8_t *data) {
|
||||
return (uint16_t)data[0] << 8 | data[1];
|
||||
}
|
||||
|
||||
|
||||
static void _handle_response(hy_func_t func, const uint8_t *data) {
|
||||
switch (func) {
|
||||
case HUANYANG_FUNC_READ:
|
||||
_func_read_response((hy_addr_t)*data, _read_word(data + 1));
|
||||
break;
|
||||
|
||||
case HUANYANG_FUNC_WRITE: break;
|
||||
case HUANYANG_CTRL_WRITE: hy.status = *data; break;
|
||||
|
||||
case HUANYANG_CTRL_READ:
|
||||
_ctrl_read_response((hy_ctrl_addr_t)*data, _read_word(data + 1));
|
||||
break;
|
||||
|
||||
case HUANYANG_FREQ_WRITE: break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _next_command();
|
||||
|
||||
|
||||
static bool _shutdown() {
|
||||
if (!hy.shutdown && !estop_triggered()) return false;
|
||||
modbus_deinit();
|
||||
if (hy.deinit_cb) hy.deinit_cb();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void _modbus_cb(uint8_t func, uint8_t bytes, const uint8_t *data) {
|
||||
if (!data) { // Modbus command failed
|
||||
if (_shutdown()) return;
|
||||
hy.state = 0;
|
||||
|
||||
} else if (bytes == *data + 1) {
|
||||
_handle_response((hy_func_t)func, data + 1);
|
||||
|
||||
if (func == HUANYANG_CTRL_WRITE && _shutdown()) return;
|
||||
|
||||
// Next command
|
||||
if (++hy.state == 9) {
|
||||
if (hy.shutdown || hy.changed) hy.state = 0;
|
||||
else hy.state = 5;
|
||||
hy.changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
_next_command();
|
||||
}
|
||||
|
||||
|
||||
static void _func_read(hy_addr_t addr) {
|
||||
uint8_t data[2] = {1, addr};
|
||||
modbus_func(HUANYANG_FUNC_READ, 2, data, 4, _modbus_cb);
|
||||
}
|
||||
|
||||
|
||||
static void _ctrl_write(hy_ctrl_state_t state) {
|
||||
uint8_t data[2] = {1, state};
|
||||
modbus_func(HUANYANG_CTRL_WRITE, 2, data, 2, _modbus_cb);
|
||||
}
|
||||
|
||||
|
||||
static void _ctrl_read(hy_ctrl_addr_t addr) {
|
||||
uint8_t data[2] = {1, addr};
|
||||
modbus_func(HUANYANG_CTRL_READ, 2, data, 4, _modbus_cb);
|
||||
}
|
||||
|
||||
|
||||
static void _freq_write(uint16_t freq) {
|
||||
uint8_t data[3] = {2, (uint8_t)(freq >> 8), (uint8_t)freq};
|
||||
modbus_func(HUANYANG_FREQ_WRITE, 3, data, 3, _modbus_cb);
|
||||
}
|
||||
|
||||
|
||||
static void _next_command() {
|
||||
switch (hy.state) {
|
||||
case 0: { // Update direction
|
||||
hy_ctrl_state_t state = HUANYANG_STOP;
|
||||
if (!hy.shutdown && !estop_triggered()) {
|
||||
if (0 < hy.power)
|
||||
state = (hy_ctrl_state_t)(HUANYANG_RUN | HUANYANG_FORWARD);
|
||||
else if (hy.power < 0)
|
||||
state = (hy_ctrl_state_t)(HUANYANG_RUN | HUANYANG_REV_FWD);
|
||||
}
|
||||
|
||||
_ctrl_write(state);
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: _func_read(HY_PD005_MAX_FREQUENCY); break;
|
||||
case 2: _func_read(HY_PD011_FREQUENCY_LOWER_LIMIT); break;
|
||||
case 3: _func_read(HY_PD144_RATED_MOTOR_RPM); break;
|
||||
|
||||
case 4: { // Update freqency
|
||||
// Compute frequency in Hz
|
||||
float freq = fabs(hy.power * hy.max_freq);
|
||||
|
||||
// Frequency write command
|
||||
_freq_write(freq * 100);
|
||||
break;
|
||||
}
|
||||
|
||||
case 5: _ctrl_read(HUANYANG_ACTUAL_FREQ); break;
|
||||
case 6: _ctrl_read(HUANYANG_ACTUAL_CURRENT); break;
|
||||
case 7: _ctrl_read(HUANYANG_ACTUAL_RPM); break;
|
||||
case 8: _ctrl_read(HUANYANG_TEMPERATURE); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void huanyang_init() {
|
||||
modbus_init();
|
||||
memset(&hy, 0, sizeof(hy));
|
||||
_next_command();
|
||||
}
|
||||
|
||||
|
||||
void huanyang_deinit(deinit_cb_t cb) {
|
||||
hy.deinit_cb = cb;
|
||||
hy.shutdown = true;
|
||||
}
|
||||
|
||||
|
||||
void huanyang_set(float power) {
|
||||
if (hy.power != power && !hy.shutdown)
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
hy.power = power;
|
||||
hy.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float huanyang_get() {return hy.actual_freq / hy.max_freq;}
|
||||
uint8_t huanyang_get_status() {return hy.status;}
|
||||
|
||||
// Variable callbacks
|
||||
float get_hy_freq() {return hy.actual_freq;}
|
||||
float get_hy_current() {return hy.actual_current;}
|
||||
uint16_t get_hy_temp() {return hy.temperature;}
|
||||
float get_hy_max_freq() {return hy.max_freq;}
|
||||
float get_hy_min_freq() {return hy.min_freq;}
|
||||
uint16_t get_hy_rated_rpm() {return hy.rated_rpm;}
|
||||
226
src/avr/src/huanyang.h
Normal file
226
src/avr/src/huanyang.h
Normal file
@@ -0,0 +1,226 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spindle.h"
|
||||
|
||||
|
||||
void huanyang_init();
|
||||
void huanyang_deinit(deinit_cb_t cb);
|
||||
void huanyang_set(float power);
|
||||
float huanyang_get();
|
||||
uint8_t huanyang_get_status();
|
||||
|
||||
|
||||
/// See Huanyang VFD user manual
|
||||
typedef enum {
|
||||
HY_PD000_PARAMETER_LOCK,
|
||||
HY_PD001_SOURCE_OF_OPERATION_COMMANDS,
|
||||
HY_PD002_SOURCE_OF_OPERATING_FREQUENCY,
|
||||
HY_PD003_MAIN_FREQUENCY,
|
||||
HY_PD004_BASE_FREQUENCY,
|
||||
HY_PD005_MAX_FREQUENCY,
|
||||
HY_PD006_INTERMEDIATE_FREQUENCY,
|
||||
HY_PD007_MIN_FREQUENCY,
|
||||
HY_PD008_MAX_VOLTAGE,
|
||||
HY_PD009_INTERMEDIATE_VOLTAGE,
|
||||
HY_PD010_MIN_VOLTAGE,
|
||||
HY_PD011_FREQUENCY_LOWER_LIMIT,
|
||||
HY_PD012_RESERVED,
|
||||
HY_PD013_PARAMETER_RESET,
|
||||
HY_PD014_ACCEL_TIME_1,
|
||||
HY_PD015_DECEL_TIME_1,
|
||||
HY_PD016_ACCEL_TIME_2,
|
||||
HY_PD017_DECEL_TIME_2,
|
||||
HY_PD018_ACCEL_TIME_3,
|
||||
HY_PD019_DECEL_TIME_3,
|
||||
HY_PD020_ACCEL_TIME_4,
|
||||
HY_PD021_DECEL_TIME_4,
|
||||
HY_PD022_FACTORY_RESERVED,
|
||||
HY_PD023_REV_ROTATION_SELECT,
|
||||
HY_PD024_STOP_KEY,
|
||||
HY_PD025_STARTING_MODE,
|
||||
HY_PD026_STOPPING_MODE,
|
||||
HY_PD027_STARTING_FREQUENCY,
|
||||
HY_PD028_STOPPING_FREQUENCY,
|
||||
HY_PD029_DC_BRAKING_TIME_AT_START,
|
||||
HY_PD030_DC_BRAKING_TIME_AT_STOP,
|
||||
HY_PD031_DC_BRAKING_VOLTAGE_LEVEL,
|
||||
HY_PD032_FREQUENCY_TRACK_TIME,
|
||||
HY_PD033_CURRENT_LEVEL_FOR_FREQUENCY_TRACK,
|
||||
HY_PD034_VOLTAGE_RISE_TIME_FOR_FREQUENCY_TRACK,
|
||||
HY_PD035_FREQUENCY_STEP_LENGTH,
|
||||
HY_PD036,
|
||||
HY_PD037,
|
||||
HY_PD038,
|
||||
HY_PD039,
|
||||
HY_PD040,
|
||||
HY_PD041_CARRIER_FREQUENCY,
|
||||
HY_PD042_JOGGING_FREQUENCY,
|
||||
HY_PD043_S_CURVE_TIME,
|
||||
HY_PD044_MULTI_INPUT_FOR,
|
||||
HY_PD045_MULTI_INPUT_REV,
|
||||
HY_PD046_MULTI_INPUT_RST,
|
||||
HY_PD047_MULTI_INPUT_SPH,
|
||||
HY_PD048_MULTI_INPUT_SPM,
|
||||
HY_PD049_MULTI_INPUT_SPL,
|
||||
HY_PD050_MULTI_OUTPUT_DRV,
|
||||
HY_PD051_MULTI_OUTPUT_UPF,
|
||||
HY_PD052_MULTI_OUTPUT_FA_FB_FC,
|
||||
HY_PD053_MULTI_OUTPUT_KA_KB,
|
||||
HY_PD054_MULTI_OUTPUT_AM,
|
||||
HY_PD055_AM_ANALOG_OUTPUT_GAIN,
|
||||
HY_PD056_SKIP_FREQUENCY_1,
|
||||
HY_PD057_SKIP_FREQUENCY_2,
|
||||
HY_PD058_SKIP_FREQUENCY_3,
|
||||
HY_PD059_SKIP_FREQUENCY_RANGE,
|
||||
HY_PD060_UNIFORM_FREQUENCY_1,
|
||||
HY_PD061_UNIFORM_FREQUENCY_2,
|
||||
HY_PD062_UNIFORM_FREQUENCY_RANGE,
|
||||
HY_PD063_TIMER_1_TIME,
|
||||
HY_PD064_TIMER_2_TIME,
|
||||
HY_PD065_COUNTING_VALUE,
|
||||
HY_PD066_INTERMEDIATE_COUNTER,
|
||||
HY_PD067,
|
||||
HY_PD068,
|
||||
HY_PD069,
|
||||
HY_PD070_ANALOG_INPUT,
|
||||
HY_PD071_ANALOG_FILTERING_CONSTANT,
|
||||
HY_PD072_HIGHER_ANALOG_FREQUENCY,
|
||||
HY_PD073_LOWER_ANALOG_FREQUENCY,
|
||||
HY_PD074_BIAS_DIRECTION_AT_HIGHER_FREQUENCY,
|
||||
HY_PD075_BIAS_DIRECTION_AT_LOWER_FREQUENCY,
|
||||
HY_PD076_ANALOG_NEGATIVE_BIAS_REVERSE,
|
||||
HY_PD077_UP_DOWN_FUNCTION,
|
||||
HY_PD078_UP_DOWN_SPEED,
|
||||
HY_PD079,
|
||||
HY_PD080_PLC_OPERATION,
|
||||
HY_PD081_AUTO_PLC,
|
||||
HY_PD082_PLC_RUNNING_DIRECTION,
|
||||
HY_PD083,
|
||||
HY_PD084_PLC_RAMP_TIME,
|
||||
HY_PD085,
|
||||
HY_PD086_FREQUENCY_2,
|
||||
HY_PD087_FREQUENCY_3,
|
||||
HY_PD088_FREQUENCY_4,
|
||||
HY_PD089_FREQUENCY_5,
|
||||
HY_PD090_FREQUENCY_6,
|
||||
HY_PD091_FREQUENCY_7,
|
||||
HY_PD092_FREQUENCY_8,
|
||||
HY_PD093,
|
||||
HY_PD094,
|
||||
HY_PD095,
|
||||
HY_PD096,
|
||||
HY_PD097,
|
||||
HY_PD098,
|
||||
HY_PD099,
|
||||
HY_PD100,
|
||||
HY_PD101_TIMER_1,
|
||||
HY_PD102_TIMER_2,
|
||||
HY_PD103_TIMER_3,
|
||||
HY_PD104_TIMER_4,
|
||||
HY_PD105_TIMER_5,
|
||||
HY_PD106_TIMER_6,
|
||||
HY_PD107_TIMER_7,
|
||||
HY_PD108_TIMER_8,
|
||||
HY_PD109,
|
||||
HY_PD110,
|
||||
HY_PD111,
|
||||
HY_PD112,
|
||||
HY_PD113,
|
||||
HY_PD114,
|
||||
HY_PD115,
|
||||
HY_PD116,
|
||||
HY_PD117_AUTOPLC_MEMORY_FUNCTION,
|
||||
HY_PD118_OVER_VOLTAGE_STALL_PREVENTION,
|
||||
HY_PD119_STALL_PREVENTION_LEVEL_AT_RAMP_UP,
|
||||
HY_PD120_STALL_PREVENTION_LEVEL_AT_CONSTANT_SPEED,
|
||||
HY_PD121_DECEL_TIME_FOR_STALL_PREVENTION_AT_CONSTANT_SPEED,
|
||||
HY_PD122_STALL_PREVENTION_LEVEL_AT_DECELERATION,
|
||||
HY_PD123_OVER_TORQUE_DETECT_MODE,
|
||||
HY_PD124_OVER_TORQUE_DETECT_LEVEL,
|
||||
HY_PD125_OVER_TORQUE_DETECT_TIME,
|
||||
HY_PD126,
|
||||
HY_PD127,
|
||||
HY_PD128,
|
||||
HY_PD129,
|
||||
HY_PD130_NUMBER_OF_AUXILIARY_PUMP,
|
||||
HY_PD131_CONTINUOUS_RUNNING_TIME_OF_AUXILIARY_PUMPS,
|
||||
HY_PD132_INTERLOCKING_TIME_OF_AUXILIARY_PUMP,
|
||||
HY_PD133_HIGH_SPEED_RUNNING_TIME,
|
||||
HY_PD134_LOW_SPEED_RUNNING_TIME,
|
||||
HY_PD135_STOPPING_VOLTAGE_LEVEL,
|
||||
HY_PD136_LASTING_TIME_OF_STOPPING_VOLTAGE_LEVEL,
|
||||
HY_PD137_WAKEUP_VOLTAGE_LEVEL,
|
||||
HY_PD138_SLEEP_FREQUENCY,
|
||||
HY_PD139_LASTING_TIME_OF_SLEEP_FREQUENCY,
|
||||
HY_PD140,
|
||||
HY_PD141_RATED_MOTOR_VOLTAGE,
|
||||
HY_PD142_RATED_MOTOR_CURRENT,
|
||||
HY_PD143_MOTOR_POLE_NUMBER,
|
||||
HY_PD144_RATED_MOTOR_RPM,
|
||||
HY_PD145_AUTO_TORQUE_COMPENSATION,
|
||||
HY_PD146_MOTOR_NO_LOAD_CURRENT,
|
||||
HY_PD147_MOTOR_SLIP_COMPENSATION,
|
||||
HY_PD148,
|
||||
HY_PD149,
|
||||
HY_PD150_AUTO_VOLTAGE_REGULATION,
|
||||
HY_PD151_AUTO_ENERGY_SAVING,
|
||||
HY_PD152_FAULT_RESTART_TIME,
|
||||
HY_PD153_RESTART_AFTER_INSTANTANEOUS_STOP,
|
||||
HY_PD154_ALLOWABLE_POWER_BREAKDOWN_TIME,
|
||||
HY_PD155_NUMBER_OF_ABNORMAL_RESTART,
|
||||
HY_PD156_PROPORTIONAL_CONSTANT,
|
||||
HY_PD157_INTEGRAL_TIME,
|
||||
HY_PD158_DIFFERENTIAL_TIME,
|
||||
HY_PD159_TARGET_VALUE,
|
||||
HY_PD160_PID_TARGET_VALUE,
|
||||
HY_PD161_PID_UPPER_LIMIT,
|
||||
HY_PD162_PID_LOWER_LIMIT,
|
||||
HY_PD163_COMMUNICATION_ADDRESSES,
|
||||
HY_PD164_COMMUNICATION_BAUD_RATE,
|
||||
HY_PD165_COMMUNICATION_DATA_METHOD,
|
||||
HY_PD166,
|
||||
HY_PD167,
|
||||
HY_PD168,
|
||||
HY_PD169,
|
||||
HY_PD170_DISPLAY_ITEMS,
|
||||
HY_PD171_DISPLAY_ITEMS_OPEN,
|
||||
HY_PD172_FAULT_CLEAR,
|
||||
HY_PD173,
|
||||
HY_PD174_RATED_CURRENT_OF_INVERTER,
|
||||
HY_PD175_INVERTER_MODEL,
|
||||
HY_PD176_INVERTER_FREQUENCY_STANDARD,
|
||||
HY_PD177_FAULT_RECORD_1,
|
||||
HY_PD178_FAULT_RECORD_2,
|
||||
HY_PD179_FAULT_RECORD_3,
|
||||
HY_PD180_FAULT_RECORD_4,
|
||||
HY_PD181_SOFTWARE_VERSION,
|
||||
HY_PD182_MANUFACTURE_DATE,
|
||||
HY_PD183_SERIAL_NO,
|
||||
} hy_addr_t;
|
||||
129
src/avr/src/i2c.c
Normal file
129
src/avr/src/i2c.c
Normal file
@@ -0,0 +1,129 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "i2c.h"
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
i2c_read_cb_t read_cb;
|
||||
i2c_write_cb_t write_cb;
|
||||
uint8_t data[I2C_MAX_DATA + 1];
|
||||
uint8_t length;
|
||||
bool done;
|
||||
bool write;
|
||||
} i2c_t;
|
||||
|
||||
static i2c_t i2c = {0};
|
||||
|
||||
|
||||
static void _i2c_reset_command() {
|
||||
i2c.length = 0;
|
||||
i2c.done = true;
|
||||
i2c.write = false;
|
||||
}
|
||||
|
||||
|
||||
static void _i2c_end_command() {
|
||||
if (i2c.length && !i2c.write && i2c.read_cb) {
|
||||
i2c.data[i2c.length] = 0; // Null terminate
|
||||
i2c.read_cb(i2c.data, i2c.length);
|
||||
}
|
||||
|
||||
_i2c_reset_command();
|
||||
}
|
||||
|
||||
|
||||
static void _i2c_command_byte(uint8_t byte) {
|
||||
i2c.data[i2c.length++] = byte;
|
||||
}
|
||||
|
||||
|
||||
ISR(I2C_ISR) {
|
||||
uint8_t status = I2C_DEV.SLAVE.STATUS;
|
||||
|
||||
// Error or collision
|
||||
if (status & (TWI_SLAVE_BUSERR_bm | TWI_SLAVE_COLL_bm)) {
|
||||
_i2c_reset_command();
|
||||
return; // Ignore
|
||||
|
||||
} else if ((status & TWI_SLAVE_APIF_bm) && (status & TWI_SLAVE_AP_bm)) {
|
||||
// START + address match
|
||||
I2C_DEV.SLAVE.CTRLB = TWI_SLAVE_CMD_RESPONSE_gc; // ACK address byte
|
||||
_i2c_end_command(); // Handle repeated START
|
||||
|
||||
} else if (status & TWI_SLAVE_APIF_bm) {
|
||||
// STOP
|
||||
I2C_DEV.SLAVE.STATUS = TWI_SLAVE_APIF_bm; // Clear interrupt flag
|
||||
_i2c_end_command();
|
||||
|
||||
} else if (status & TWI_SLAVE_DIF_bm) {
|
||||
i2c.write = status & TWI_SLAVE_DIR_bm;
|
||||
|
||||
// DATA
|
||||
if (i2c.write) { // Write
|
||||
// Check if master ACKed last byte sent
|
||||
if (i2c.length && (status & TWI_SLAVE_RXACK_bm || i2c.done))
|
||||
I2C_DEV.SLAVE.CTRLB = TWI_SLAVE_CMD_COMPTRANS_gc; // End transaction
|
||||
|
||||
else {
|
||||
// Send some data
|
||||
i2c.done = false;
|
||||
I2C_DEV.SLAVE.DATA = i2c.write_cb(i2c.length++, &i2c.done);
|
||||
I2C_DEV.SLAVE.CTRLB = TWI_SLAVE_CMD_RESPONSE_gc; // Continue transaction
|
||||
}
|
||||
|
||||
} else { // Read
|
||||
_i2c_command_byte(I2C_DEV.SLAVE.DATA);
|
||||
|
||||
// ACK and continue transaction
|
||||
I2C_DEV.SLAVE.CTRLB = TWI_SLAVE_CMD_RESPONSE_gc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint8_t _i2c_default_write_cb(uint8_t offset, bool *done) {
|
||||
*done = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void i2c_init() {
|
||||
i2c_set_write_callback(_i2c_default_write_cb);
|
||||
|
||||
I2C_DEV.SLAVE.CTRLA = TWI_SLAVE_INTLVL_LO_gc | TWI_SLAVE_DIEN_bm |
|
||||
TWI_SLAVE_ENABLE_bm | TWI_SLAVE_APIEN_bm | TWI_SLAVE_PIEN_bm;
|
||||
I2C_DEV.SLAVE.ADDR = I2C_ADDR << 1;
|
||||
}
|
||||
|
||||
|
||||
void i2c_set_read_callback(i2c_read_cb_t cb) {i2c.read_cb = cb;}
|
||||
void i2c_set_write_callback(i2c_write_cb_t cb) {i2c.write_cb = cb;}
|
||||
41
src/avr/src/i2c.h
Normal file
41
src/avr/src/i2c.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef void (*i2c_read_cb_t)(uint8_t *data, uint8_t length);
|
||||
typedef uint8_t (*i2c_write_cb_t)(uint8_t offset, bool *done);
|
||||
|
||||
|
||||
void i2c_init();
|
||||
void i2c_set_read_callback(i2c_read_cb_t cb);
|
||||
void i2c_set_write_callback(i2c_write_cb_t cb);
|
||||
118
src/avr/src/io.c
Normal file
118
src/avr/src/io.c
Normal file
@@ -0,0 +1,118 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "io.h"
|
||||
|
||||
#include "status.h"
|
||||
#include "util.h"
|
||||
#include "command.h"
|
||||
#include "exec.h"
|
||||
#include "rtc.h"
|
||||
#include "analog.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
int8_t port;
|
||||
bool digital;
|
||||
input_mode_t mode;
|
||||
float timeout;
|
||||
} input_cmd_t;
|
||||
|
||||
|
||||
static input_cmd_t active_cmd = {-1,};
|
||||
static uint32_t timeout;
|
||||
|
||||
|
||||
void io_callback() {
|
||||
if (active_cmd.port == -1) return;
|
||||
|
||||
bool done = false;
|
||||
float result = 0;
|
||||
if (active_cmd.mode == INPUT_IMMEDIATE || rtc_expired(timeout)) done = true;
|
||||
|
||||
// TODO handle modes
|
||||
|
||||
if (done) {
|
||||
if (active_cmd.digital) { // TODO
|
||||
} else result = analog_get(active_cmd.port);
|
||||
|
||||
printf_P(PSTR("{\"result\": %f}\n"), (double)result);
|
||||
active_cmd.port = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static stat_t _exec_cb() {
|
||||
if (active_cmd.port == -1) exec_set_cb(0);
|
||||
return STAT_NOP;
|
||||
}
|
||||
|
||||
|
||||
// Command callbacks
|
||||
stat_t command_input(char *cmd) {
|
||||
input_cmd_t input_cmd;
|
||||
cmd++; // Skip command
|
||||
|
||||
// Analog or digital
|
||||
if (*cmd == 'd') input_cmd.digital = true;
|
||||
else if (*cmd == 'a') input_cmd.digital = false;
|
||||
else return STAT_INVALID_ARGUMENTS;
|
||||
cmd++;
|
||||
|
||||
// Port index
|
||||
if (!isdigit(*cmd)) return STAT_INVALID_ARGUMENTS;
|
||||
input_cmd.port = *cmd - '0';
|
||||
cmd++;
|
||||
|
||||
// Mode
|
||||
if (!isdigit(*cmd)) return STAT_INVALID_ARGUMENTS;
|
||||
input_cmd.mode = (input_mode_t)(*cmd - '0');
|
||||
if (INPUT_LOW < input_cmd.mode) return STAT_INVALID_ARGUMENTS;
|
||||
cmd++;
|
||||
|
||||
// Timeout
|
||||
if (!decode_float(&cmd, &input_cmd.timeout)) return STAT_BAD_FLOAT;
|
||||
|
||||
command_push(COMMAND_input, &input_cmd);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_input_size() {return sizeof(input_cmd_t);}
|
||||
|
||||
|
||||
void command_input_exec(void *data) {
|
||||
active_cmd = *(input_cmd_t *)data;
|
||||
|
||||
timeout = rtc_get_time() + active_cmd.timeout * 1000;
|
||||
exec_set_cb(_exec_cb);
|
||||
}
|
||||
40
src/avr/src/io.h
Normal file
40
src/avr/src/io.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
typedef enum {
|
||||
INPUT_IMMEDIATE,
|
||||
INPUT_RISE,
|
||||
INPUT_FALL,
|
||||
INPUT_HIGH,
|
||||
INPUT_LOW,
|
||||
} input_mode_t;
|
||||
|
||||
|
||||
void io_callback();
|
||||
169
src/avr/src/jog.c
Normal file
169
src/avr/src/jog.c
Normal file
@@ -0,0 +1,169 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "jog.h"
|
||||
|
||||
#include "axis.h"
|
||||
#include "util.h"
|
||||
#include "exec.h"
|
||||
#include "state.h"
|
||||
#include "command.h"
|
||||
#include "config.h"
|
||||
#include "SCurve.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool holding;
|
||||
bool writing;
|
||||
|
||||
SCurve scurves[AXES];
|
||||
float next[AXES];
|
||||
float targetV[AXES];
|
||||
} jr_t;
|
||||
|
||||
jr_t jr = {0};
|
||||
|
||||
|
||||
stat_t jog_exec() {
|
||||
bool done = true;
|
||||
|
||||
// Compute per axis velocities and target positions
|
||||
float target[AXES] = {0,};
|
||||
float velocity_sqr = 0;
|
||||
|
||||
for (int axis = 0; axis < AXES; axis++) {
|
||||
if (!axis_is_enabled(axis)) continue;
|
||||
|
||||
// Load next velocity
|
||||
if (!jr.writing)
|
||||
jr.targetV[axis] = jr.next[axis] * axis_get_velocity_max(axis);
|
||||
|
||||
float p = exec_get_axis_position(axis);
|
||||
float vel = jr.scurves[axis].getVelocity();
|
||||
float targetV = jr.targetV[axis];
|
||||
float min = axis_get_soft_limit(axis, true);
|
||||
float max = axis_get_soft_limit(axis, false);
|
||||
bool softLimited = min != max && axis_get_homed(axis);
|
||||
|
||||
// Apply soft limits, if enabled and homed
|
||||
if (softLimited && MIN_VELOCITY < fabs(targetV)) {
|
||||
float dist = jr.scurves[axis].getStoppingDist() *
|
||||
(1 + (JOG_STOPPING_UNDERSHOOT / 100.0));
|
||||
|
||||
if (vel < 0 && p - dist <= min) targetV = -MIN_VELOCITY;
|
||||
if (0 < vel && max <= p + dist) targetV = MIN_VELOCITY;
|
||||
}
|
||||
|
||||
// Compute next velocity
|
||||
float v = jr.scurves[axis].next(SEGMENT_TIME, targetV);
|
||||
|
||||
// Don't overshoot soft limits
|
||||
float deltaP = v * SEGMENT_TIME;
|
||||
if (softLimited && 0 < deltaP && max < p + deltaP) p = max;
|
||||
else if (softLimited && deltaP < 0 && p + deltaP < min) p = min;
|
||||
else p += deltaP;
|
||||
|
||||
// Not done jogging if still moving
|
||||
if (MIN_VELOCITY < fabs(v) || MIN_VELOCITY < fabs(targetV)) done = false;
|
||||
|
||||
velocity_sqr += square(v);
|
||||
target[axis] = p;
|
||||
}
|
||||
|
||||
// Check if we are done
|
||||
if (done) {
|
||||
command_reset_position();
|
||||
exec_set_velocity(0);
|
||||
exec_set_cb(0);
|
||||
if (jr.holding) state_holding();
|
||||
else state_idle();
|
||||
|
||||
return STAT_NOP; // Done, no move executed
|
||||
}
|
||||
|
||||
// Set velocity and target
|
||||
exec_set_velocity(sqrt(velocity_sqr));
|
||||
exec_move_to_target(target);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
void jog_stop() {
|
||||
if (state_get() != STATE_JOGGING) return;
|
||||
jr.writing = true;
|
||||
for (int axis = 0; axis < AXES; axis++) jr.next[axis] = 0;
|
||||
jr.writing = false;
|
||||
}
|
||||
|
||||
|
||||
stat_t command_jog(char *cmd) {
|
||||
// Ignore jog commands when not READY, HOLDING or JOGGING
|
||||
if (state_get() != STATE_READY && state_get() != STATE_HOLDING &&
|
||||
state_get() != STATE_JOGGING)
|
||||
return STAT_NOP;
|
||||
|
||||
// Skip over command code
|
||||
cmd++;
|
||||
|
||||
// Get velocities
|
||||
float velocity[AXES] = {0,};
|
||||
stat_t status = decode_axes(&cmd, velocity);
|
||||
if (status) return status;
|
||||
|
||||
// Check for end of command
|
||||
if (*cmd) return STAT_INVALID_ARGUMENTS;
|
||||
|
||||
// Start jogging
|
||||
if (state_get() != STATE_JOGGING) {
|
||||
memset(&jr, 0, sizeof(jr));
|
||||
|
||||
jr.holding = state_get() == STATE_HOLDING;
|
||||
|
||||
for (int axis = 0; axis < AXES; axis++)
|
||||
if (axis_is_enabled(axis))
|
||||
jr.scurves[axis] =
|
||||
SCurve(axis_get_velocity_max(axis), axis_get_accel_max(axis),
|
||||
axis_get_jerk_max(axis));
|
||||
|
||||
state_jogging();
|
||||
exec_set_cb(jog_exec);
|
||||
}
|
||||
|
||||
// Set next velocities
|
||||
jr.writing = true;
|
||||
for (int axis = 0; axis < AXES; axis++) jr.next[axis] = velocity[axis];
|
||||
jr.writing = false;
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
34
src/avr/src/jog.h
Normal file
34
src/avr/src/jog.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "status.h"
|
||||
|
||||
|
||||
stat_t jog_exec();
|
||||
void jog_stop();
|
||||
143
src/avr/src/lcd.c
Normal file
143
src/avr/src/lcd.c
Normal file
@@ -0,0 +1,143 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "lcd.h"
|
||||
#include "rtc.h"
|
||||
#include "hardware.h"
|
||||
#include "command.h"
|
||||
|
||||
#include <avr/io.h>
|
||||
#include <avr/wdt.h>
|
||||
#include <util/delay.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
void lcd_init(uint8_t addr) {
|
||||
// Enable I2C master
|
||||
TWIC.MASTER.BAUD = F_CPU / 2 / 100000 - 5; // 100 KHz
|
||||
TWIC.MASTER.CTRLA = TWI_MASTER_ENABLE_bm;
|
||||
TWIC.MASTER.CTRLB = TWI_MASTER_TIMEOUT_DISABLED_gc;
|
||||
TWIC.MASTER.STATUS = TWI_MASTER_BUSSTATE_IDLE_gc;
|
||||
|
||||
_delay_ms(50);
|
||||
lcd_nibble(addr, 3 << 4); // Home
|
||||
_delay_ms(50);
|
||||
lcd_nibble(addr, 3 << 4); // Home
|
||||
_delay_ms(50);
|
||||
lcd_nibble(addr, 3 << 4); // Home
|
||||
lcd_nibble(addr, 2 << 4); // 4-bit
|
||||
|
||||
lcd_write(addr,
|
||||
LCD_FUNCTION_SET | LCD_2_LINE | LCD_5x8_DOTS | LCD_4_BIT_MODE, 0);
|
||||
lcd_write(addr, LCD_DISPLAY_CONTROL | LCD_DISPLAY_ON, 0);
|
||||
lcd_write(addr, LCD_ENTRY_MODE_SET | LCD_ENTRY_SHIFT_INC, 0);
|
||||
|
||||
lcd_write(addr, LCD_CLEAR_DISPLAY, 0);
|
||||
lcd_write(addr, LCD_RETURN_HOME, 0);
|
||||
}
|
||||
|
||||
|
||||
static void _master_wait() {
|
||||
#ifdef __AVR__
|
||||
while (!(TWIC.MASTER.STATUS & TWI_MASTER_WIF_bm)) continue;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void _write_i2c(uint8_t addr, uint8_t data) {
|
||||
data |= BACKLIGHT_BIT;
|
||||
|
||||
TWIC.MASTER.ADDR = addr << 1;
|
||||
_master_wait();
|
||||
|
||||
TWIC.MASTER.DATA = data;
|
||||
_master_wait();
|
||||
|
||||
TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc;
|
||||
|
||||
_delay_us(100);
|
||||
}
|
||||
|
||||
|
||||
void lcd_nibble(uint8_t addr, uint8_t data) {
|
||||
_write_i2c(addr, data);
|
||||
_write_i2c(addr, data | ENABLE_BIT);
|
||||
_delay_us(500);
|
||||
_write_i2c(addr, data & ~ENABLE_BIT);
|
||||
_delay_us(100);
|
||||
}
|
||||
|
||||
|
||||
void lcd_write(uint8_t addr, uint8_t cmd, uint8_t flags) {
|
||||
lcd_nibble(addr, flags | (cmd & 0xf0));
|
||||
lcd_nibble(addr, flags | ((cmd << 4) & 0xf0));
|
||||
}
|
||||
|
||||
|
||||
void lcd_goto(uint8_t addr, uint8_t x, uint8_t y) {
|
||||
static uint8_t row[] = {0, 64, 20, 84};
|
||||
lcd_write(addr, LCD_SET_DDRAM_ADDR | (row[y] + x), 0);
|
||||
}
|
||||
|
||||
|
||||
void lcd_putchar(uint8_t addr, uint8_t c) {
|
||||
lcd_write(addr, c, REG_SELECT_BIT);
|
||||
}
|
||||
|
||||
|
||||
void lcd_pgmstr(uint8_t addr, const char *s) {
|
||||
while (true) {
|
||||
char c = pgm_read_byte(s++);
|
||||
if (!c) break;
|
||||
lcd_putchar(addr, c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _splash(uint8_t addr) {
|
||||
lcd_init(addr);
|
||||
lcd_goto(addr, 1, 1);
|
||||
lcd_pgmstr(addr, PSTR("Controller booting"));
|
||||
lcd_goto(addr, 3, 2);
|
||||
lcd_pgmstr(addr, PSTR("Please wait..."));
|
||||
}
|
||||
|
||||
|
||||
void lcd_splash() {
|
||||
wdt_disable();
|
||||
_splash(0x27);
|
||||
_splash(0x3f);
|
||||
wdt_enable(WDTO_250MS);
|
||||
}
|
||||
|
||||
|
||||
void lcd_rtc_callback() {
|
||||
// Display the splash if we haven't gotten any commands in 1sec since boot
|
||||
if (!command_is_active() && rtc_get_time() == 1000)
|
||||
lcd_splash();
|
||||
}
|
||||
103
src/avr/src/lcd.h
Normal file
103
src/avr/src/lcd.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pgmspace.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// Control flags
|
||||
enum {
|
||||
REG_SELECT_BIT = 1 << 0,
|
||||
READ_BIT = 1 << 1,
|
||||
ENABLE_BIT = 1 << 2,
|
||||
BACKLIGHT_BIT = 1 << 3,
|
||||
};
|
||||
|
||||
|
||||
// Commands
|
||||
enum {
|
||||
LCD_CLEAR_DISPLAY = 1 << 0,
|
||||
LCD_RETURN_HOME = 1 << 1,
|
||||
LCD_ENTRY_MODE_SET = 1 << 2,
|
||||
LCD_DISPLAY_CONTROL = 1 << 3,
|
||||
LCD_CURSOR_SHIFT = 1 << 4,
|
||||
LCD_FUNCTION_SET = 1 << 5,
|
||||
LCD_SET_CGRAM_ADDR = 1 << 6,
|
||||
LCD_SET_DDRAM_ADDR = 1 << 7,
|
||||
};
|
||||
|
||||
|
||||
// Entry Mode Set flags
|
||||
#define LCD_ENTRY_SHIFT_DISPLAY (1 << 0)
|
||||
#define LCD_ENTRY_SHIFT_INC (1 << 1)
|
||||
#define LCD_ENTRY_SHIFT_DEC (0 << 1)
|
||||
|
||||
|
||||
// Display Control flags
|
||||
#define LCD_BLINK_ON (1 << 0)
|
||||
#define LCD_BLINK_OFF (0 << 0)
|
||||
#define LCD_CURSOR_ON (1 << 1)
|
||||
#define LCD_CURSOR_OFF (0 << 1)
|
||||
#define LCD_DISPLAY_ON (1 << 2)
|
||||
#define LCD_DISPLAY_OFF (0 << 2)
|
||||
|
||||
|
||||
// Cursor Shift flags
|
||||
#define LCD_SHIFT_RIGHT (1 << 2)
|
||||
#define LCD_SHIFT_LEFT (0 << 2)
|
||||
#define LCD_SHIFT_DISPLAY (1 << 3)
|
||||
#define LCD_SHIFT_CURSOR (0 << 3)
|
||||
|
||||
|
||||
// Function Set flags
|
||||
#define LCD_5x11_DOTS (1 << 2)
|
||||
#define LCD_5x8_DOTS (0 << 2)
|
||||
#define LCD_2_LINE (1 << 3)
|
||||
#define LCD_1_LINE (0 << 3)
|
||||
#define LCD_8_BIT_MODE (1 << 4)
|
||||
#define LCD_4_BIT_MODE (0 << 4)
|
||||
|
||||
|
||||
// Text justification flags
|
||||
enum {
|
||||
JUSTIFY_LEFT = 0,
|
||||
JUSTIFY_RIGHT = 1,
|
||||
JUSTIFY_CENTER = 2,
|
||||
};
|
||||
|
||||
|
||||
void lcd_init(uint8_t addr);
|
||||
void lcd_nibble(uint8_t addr, uint8_t data);
|
||||
void lcd_write(uint8_t addr, uint8_t cmd, uint8_t flags);
|
||||
void lcd_goto(uint8_t addr, uint8_t x, uint8_t y);
|
||||
void lcd_putchar(uint8_t addr, uint8_t c);
|
||||
void lcd_pgmstr(uint8_t addr, const char *s);
|
||||
void lcd_splash();
|
||||
void lcd_rtc_callback();
|
||||
274
src/avr/src/line.c
Normal file
274
src/avr/src/line.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
#include "exec.h"
|
||||
#include "command.h"
|
||||
#include "spindle.h"
|
||||
#include "util.h"
|
||||
#include "SCurve.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
float start[AXES];
|
||||
float target[AXES];
|
||||
float times[7];
|
||||
float target_vel;
|
||||
float max_accel;
|
||||
float max_jerk;
|
||||
|
||||
float unit[AXES];
|
||||
float length;
|
||||
} line_t;
|
||||
|
||||
|
||||
static struct {
|
||||
line_t line;
|
||||
|
||||
int section;
|
||||
int seg;
|
||||
|
||||
float iD; // Initial section distance
|
||||
float iV; // Initial section velocity
|
||||
float iA; // Initial section acceleration
|
||||
float jerk;
|
||||
float lV; // Last velocity
|
||||
float lD; // Last distance
|
||||
|
||||
power_update_t power_updates[POWER_MAX_UPDATES];
|
||||
} l;
|
||||
|
||||
|
||||
static void _segment_target(float target[AXES], float d) {
|
||||
for (int axis = 0; axis < AXES; axis++)
|
||||
target[axis] = l.line.start[axis] + l.line.unit[axis] * d;
|
||||
}
|
||||
|
||||
|
||||
static float _segment_distance(float t) {
|
||||
return l.iD + SCurve::distance(t, l.iV, l.iA, l.jerk);
|
||||
}
|
||||
|
||||
|
||||
static float _segment_velocity(float t) {
|
||||
return l.iV + SCurve::velocity(t, l.iA, l.jerk);
|
||||
}
|
||||
|
||||
|
||||
static float _segment_accel(float t) {
|
||||
return l.iA + SCurve::acceleration(t, l.jerk);
|
||||
}
|
||||
|
||||
|
||||
static bool _section_next() {
|
||||
while (++l.section < 7) {
|
||||
if (!l.line.times[l.section]) continue;
|
||||
|
||||
// Jerk
|
||||
switch (l.section) {
|
||||
case 0: case 6: l.jerk = l.line.max_jerk; break;
|
||||
case 2: case 4: l.jerk = -l.line.max_jerk; break;
|
||||
default: l.jerk = 0;
|
||||
}
|
||||
exec_set_jerk(l.jerk);
|
||||
|
||||
// Acceleration
|
||||
switch (l.section) {
|
||||
case 1: case 2: l.iA = l.line.max_jerk * l.line.times[0]; break;
|
||||
case 5: case 6: l.iA = -l.line.max_jerk * l.line.times[4]; break;
|
||||
default: l.iA = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static stat_t _exec_segment(float time, const float target[], float vel,
|
||||
float accel) {
|
||||
return exec_segment(time, target, vel, accel, l.line.max_accel,
|
||||
l.line.max_jerk, l.power_updates);
|
||||
}
|
||||
|
||||
|
||||
static stat_t _line_exec() {
|
||||
// Compute times
|
||||
float section_time = l.line.times[l.section];
|
||||
float seg_time = SEGMENT_TIME;
|
||||
float t = ++l.seg * SEGMENT_TIME;
|
||||
|
||||
// Don't exceed section time
|
||||
if (section_time < t) {
|
||||
seg_time = section_time - (l.seg - 1) * SEGMENT_TIME;
|
||||
t = section_time;
|
||||
}
|
||||
|
||||
// Compute distance and velocity
|
||||
float d = _segment_distance(t);
|
||||
float v = _segment_velocity(t);
|
||||
float a = _segment_accel(t);
|
||||
|
||||
// Don't allow overshoot
|
||||
if (l.line.length < d) d = l.line.length;
|
||||
|
||||
// Handle synchronous speeds
|
||||
spindle_load_power_updates(l.power_updates, l.lD, d);
|
||||
l.lD = d;
|
||||
|
||||
// Check if section complete
|
||||
if (t == section_time) {
|
||||
if (_section_next()) {
|
||||
// Setup next section
|
||||
l.seg = 0;
|
||||
l.iD = d;
|
||||
l.iV = v;
|
||||
|
||||
} else {
|
||||
exec_set_cb(0);
|
||||
|
||||
// Last segment of last section
|
||||
// Use exact target values to correct for floating-point errors
|
||||
return _exec_segment(seg_time, l.line.target, l.line.target_vel, a);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute target position from distance
|
||||
float target[AXES];
|
||||
_segment_target(target, d);
|
||||
|
||||
// Segment move
|
||||
return _exec_segment(seg_time, target, v, a);
|
||||
}
|
||||
|
||||
|
||||
stat_t command_line(char *cmd) {
|
||||
line_t line = {};
|
||||
|
||||
cmd++; // Skip command code
|
||||
|
||||
// Get start position
|
||||
command_get_position(line.start);
|
||||
|
||||
// Get target velocity
|
||||
if (!decode_float(&cmd, &line.target_vel)) return STAT_BAD_FLOAT;
|
||||
if (line.target_vel < 0) return STAT_INVALID_ARGUMENTS;
|
||||
|
||||
// Get max accel
|
||||
if (!decode_float(&cmd, &line.max_accel)) return STAT_BAD_FLOAT;
|
||||
if (line.max_accel < 0) return STAT_INVALID_ARGUMENTS;
|
||||
|
||||
// Get max jerk
|
||||
if (!decode_float(&cmd, &line.max_jerk)) return STAT_BAD_FLOAT;
|
||||
if (line.max_jerk < 0) return STAT_INVALID_ARGUMENTS;
|
||||
|
||||
// Get target position
|
||||
copy_vector(line.target, line.start);
|
||||
stat_t status = decode_axes(&cmd, line.target);
|
||||
if (status) return status;
|
||||
|
||||
// Get times
|
||||
bool has_time = false;
|
||||
while (*cmd) {
|
||||
if (*cmd < '0' || '6' < *cmd) break;
|
||||
int section = *cmd - '0';
|
||||
cmd++;
|
||||
|
||||
float time;
|
||||
if (!decode_float(&cmd, &time)) return STAT_BAD_FLOAT;
|
||||
|
||||
if (time < 0) return STAT_NEGATIVE_SCURVE_TIME;
|
||||
line.times[section] = time;
|
||||
if (time) has_time = true;
|
||||
}
|
||||
|
||||
if (!has_time) return STAT_ALL_ZERO_SCURVE_TIMES;
|
||||
|
||||
// Check for end of command
|
||||
if (*cmd) return STAT_INVALID_ARGUMENTS;
|
||||
|
||||
// Set next start position
|
||||
command_set_position(line.target);
|
||||
|
||||
// Compute direction vector
|
||||
for (int axis = 0; axis < AXES; axis++) {
|
||||
line.unit[axis] = line.target[axis] - line.start[axis];
|
||||
line.length += line.unit[axis] * line.unit[axis];
|
||||
}
|
||||
|
||||
line.length = sqrt(line.length);
|
||||
for (int axis = 0; axis < AXES; axis++)
|
||||
if (line.unit[axis]) line.unit[axis] /= line.length;
|
||||
|
||||
// Queue
|
||||
command_push(COMMAND_line, &line);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_line_size() {return sizeof(line_t);}
|
||||
|
||||
|
||||
void command_line_exec(void *data) {
|
||||
l.line = *(line_t *)data;
|
||||
|
||||
// Setup first section
|
||||
l.seg = 0;
|
||||
l.iD = 0;
|
||||
l.lD = 0;
|
||||
// If current velocity is non-zero use last target velocity
|
||||
l.iV = exec_get_velocity() ? l.lV : 0;
|
||||
l.lV = l.line.target_vel;
|
||||
|
||||
// Find first section
|
||||
l.section = -1;
|
||||
if (!_section_next()) return;
|
||||
|
||||
#if 0
|
||||
// Compare start position to actual position
|
||||
float diff[AXES];
|
||||
bool report = false;
|
||||
exec_get_position(diff);
|
||||
for (int i = 0; i < AXES; i++) {
|
||||
diff[i] -= l.line.start[i];
|
||||
if (0.1 < fabs(diff[i])) report = true;
|
||||
}
|
||||
|
||||
if (report)
|
||||
STATUS_DEBUG("diff: %.4f %.4f %.4f %.4f",
|
||||
diff[0], diff[1], diff[2], diff[3]);
|
||||
#endif
|
||||
|
||||
// Set callback
|
||||
exec_set_cb(_line_exec);
|
||||
}
|
||||
101
src/avr/src/main.c
Normal file
101
src/avr/src/main.c
Normal file
@@ -0,0 +1,101 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "hardware.h"
|
||||
#include "stepper.h"
|
||||
#include "motor.h"
|
||||
#include "switch.h"
|
||||
#include "usart.h"
|
||||
#include "drv8711.h"
|
||||
#include "vars.h"
|
||||
#include "rtc.h"
|
||||
#include "report.h"
|
||||
#include "command.h"
|
||||
#include "estop.h"
|
||||
#include "i2c.h"
|
||||
#include "pgmspace.h"
|
||||
#include "outputs.h"
|
||||
#include "analog.h"
|
||||
#include "modbus.h"
|
||||
#include "io.h"
|
||||
#include "exec.h"
|
||||
#include "state.h"
|
||||
#include "emu.h"
|
||||
|
||||
#include <avr/wdt.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
// For emu
|
||||
int __argc;
|
||||
char **__argv;
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
__argc = argc;
|
||||
__argv = argv;
|
||||
|
||||
wdt_enable(WDTO_250MS);
|
||||
|
||||
// Init
|
||||
cli(); // disable interrupts
|
||||
|
||||
emu_init(); // Init emulator
|
||||
hw_init(); // hardware setup - must be first
|
||||
outputs_init(); // output pins
|
||||
switch_init(); // switches
|
||||
estop_init(); // emergency stop handler
|
||||
analog_init(); // analog input pins
|
||||
usart_init(); // serial port
|
||||
i2c_init(); // i2c port
|
||||
drv8711_init(); // motor drivers
|
||||
stepper_init(); // steppers
|
||||
motor_init(); // motors
|
||||
exec_init(); // motion exec
|
||||
vars_init(); // configuration variables
|
||||
command_init(); // command queue
|
||||
|
||||
sei(); // enable interrupts
|
||||
|
||||
// Splash
|
||||
printf_P(PSTR("\n{\"firmware\":\"Buildbotics AVR\"}\n"));
|
||||
|
||||
// Main loop
|
||||
while (true) {
|
||||
emu_callback(); // Emulator callback
|
||||
hw_reset_handler(); // handle hard reset requests
|
||||
state_callback(); // manage state
|
||||
command_callback(); // process next command
|
||||
modbus_callback(); // handle modbus events
|
||||
io_callback(); // handle io input
|
||||
report_callback(); // report changes
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
62
src/avr/src/messages.def
Normal file
62
src/avr/src/messages.def
Normal file
@@ -0,0 +1,62 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
STAT_MSG(OK, "OK")
|
||||
STAT_MSG(AGAIN, "Run command again")
|
||||
STAT_MSG(NOP, "No op")
|
||||
STAT_MSG(INTERNAL_ERROR, "Internal error")
|
||||
STAT_MSG(ESTOP_USER, "User triggered EStop")
|
||||
STAT_MSG(ESTOP_SWITCH, "Switch triggered EStop")
|
||||
STAT_MSG(POWER_SHUTDOWN, "Power shutdown")
|
||||
STAT_MSG(UNRECOGNIZED_NAME, "Unrecognized command or variable name")
|
||||
STAT_MSG(INVALID_COMMAND, "Invalid command")
|
||||
STAT_MSG(INVALID_ARGUMENTS, "Invalid arguments")
|
||||
STAT_MSG(TOO_MANY_ARGUMENTS, "Too many arguments")
|
||||
STAT_MSG(TOO_FEW_ARGUMENTS, "Too few arguments")
|
||||
STAT_MSG(MACHINE_ALARMED, "Machine alarmed - Command not processed")
|
||||
STAT_MSG(EXPECTED_MOVE, "A move expected but none queued")
|
||||
STAT_MSG(BAD_FLOAT, "Failed to parse float")
|
||||
STAT_MSG(BAD_INT, "Failed to parse integer")
|
||||
STAT_MSG(INVALID_VALUE, "Invalid value")
|
||||
STAT_MSG(INVALID_TYPE, "Invalid type")
|
||||
STAT_MSG(READ_ONLY, "Variable is read only")
|
||||
STAT_MSG(ALL_ZERO_SCURVE_TIMES, "All zero s-curve times")
|
||||
STAT_MSG(NEGATIVE_SCURVE_TIME, "Negative s-curve time")
|
||||
STAT_MSG(SEEK_NOT_ENABLED, "Switch not enabled")
|
||||
STAT_MSG(SEEK_NOT_FOUND, "Switch not found")
|
||||
STAT_MSG(MOTOR_ID_INVALID, "Invalid motor ID")
|
||||
STAT_MSG(MOTOR_NOT_PREPPED, "Motor move not prepped")
|
||||
STAT_MSG(MOTOR_NOT_READY, "Motor not ready for move")
|
||||
STAT_MSG(MOTOR_FAULT, "Motor fault")
|
||||
STAT_MSG(STEPPER_NULL_MOVE, "Null move in stepper driver")
|
||||
STAT_MSG(STEPPER_NOT_READY, "Stepper driver not ready for move")
|
||||
STAT_MSG(LONG_SEG_TIME, "Long segment time")
|
||||
STAT_MSG(MODBUS_BUF_LENGTH, "Modbus invalid buffer length")
|
||||
STAT_MSG(INVALID_QCMD, "Invalid command in queue")
|
||||
STAT_MSG(Q_OVERRUN, "Command queue overrun")
|
||||
STAT_MSG(Q_UNDERRUN, "Command queue underrun")
|
||||
STAT_MSG(Q_INVALID_PUSH, "Invalid command pushed to queue")
|
||||
7
src/avr/src/messages.json.in
Normal file
7
src/avr/src/messages.json.in
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "cpp_magic.h"
|
||||
[
|
||||
#define STAT_MSG(NAME, MSG) [#NAME, MSG],
|
||||
#include "messages.def"
|
||||
#undef CMD
|
||||
[]
|
||||
]
|
||||
486
src/avr/src/modbus.c
Normal file
486
src/avr/src/modbus.c
Normal file
@@ -0,0 +1,486 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "modbus.h"
|
||||
#include "usart.h"
|
||||
#include "status.h"
|
||||
#include "rtc.h"
|
||||
#include "util.h"
|
||||
#include "estop.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <util/crc16.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
MODBUS_DISCONNECTED,
|
||||
MODBUS_OK,
|
||||
MODBUS_CRC,
|
||||
MODBUS_INVALID,
|
||||
MODBUS_TIMEDOUT,
|
||||
} modbus_status_t;
|
||||
|
||||
|
||||
static struct {
|
||||
bool debug;
|
||||
uint8_t id;
|
||||
baud_t baud;
|
||||
parity_t parity;
|
||||
} cfg = {false, 1, USART_BAUD_9600, USART_NONE};
|
||||
|
||||
|
||||
static struct {
|
||||
uint8_t bytes;
|
||||
uint8_t command[MODBUS_BUF_SIZE];
|
||||
uint8_t command_length;
|
||||
uint8_t response[MODBUS_BUF_SIZE];
|
||||
uint8_t response_length;
|
||||
|
||||
uint16_t addr;
|
||||
modbus_rw_cb_t rw_cb;
|
||||
modbus_cb_t receive_cb;
|
||||
|
||||
uint32_t last_write;
|
||||
uint32_t last_read;
|
||||
uint8_t retry;
|
||||
uint8_t status;
|
||||
uint16_t crc_errs;
|
||||
bool write_ready;
|
||||
bool response_ready;
|
||||
bool transmit_complete;
|
||||
bool busy;
|
||||
} state = {0};
|
||||
|
||||
|
||||
static uint16_t _crc16(const uint8_t *buffer, unsigned length) {
|
||||
uint16_t crc = 0xffff;
|
||||
|
||||
for (unsigned i = 0; i < length; i++)
|
||||
crc = _crc16_update(crc, buffer[i]);
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
static void _set_write(bool x) {SET_PIN(RS485_RW_PIN, x);}
|
||||
|
||||
|
||||
#define INTLVL_SET(REG, NAME, LEVEL) \
|
||||
REG = ((REG) & ~NAME##INTLVL_gm) | NAME##INTLVL_##LEVEL##_gc
|
||||
|
||||
|
||||
#define INTLVL_ENABLE(REG, NAME, LEVEL, ENABLE) do { \
|
||||
if (ENABLE) INTLVL_SET(REG, NAME, LEVEL); \
|
||||
else INTLVL_SET(REG, NAME, OFF); \
|
||||
} while (0)
|
||||
|
||||
|
||||
static void _set_dre_interrupt(bool enable) {
|
||||
INTLVL_ENABLE(RS485_PORT.CTRLA, USART_DRE, MED, enable);
|
||||
}
|
||||
|
||||
|
||||
static void _set_txc_interrupt(bool enable) {
|
||||
INTLVL_ENABLE(RS485_PORT.CTRLA, USART_TXC, MED, enable);
|
||||
}
|
||||
|
||||
|
||||
static void _set_rxc_interrupt(bool enable) {
|
||||
INTLVL_ENABLE(RS485_PORT.CTRLA, USART_RXC, MED, enable);
|
||||
}
|
||||
|
||||
|
||||
static void _write_word(uint8_t *dst, uint16_t value, bool little_endian) {
|
||||
dst[!little_endian] = value;
|
||||
dst[little_endian] = value >> 8;
|
||||
}
|
||||
|
||||
|
||||
static uint16_t _read_word(const uint8_t *data, bool little_endian) {
|
||||
return (uint16_t)data[little_endian] << 8 | data[!little_endian];
|
||||
}
|
||||
|
||||
|
||||
static bool _check_response() {
|
||||
// Check CRC
|
||||
uint16_t computed = _crc16(state.response, state.response_length - 2);
|
||||
uint16_t expected =
|
||||
_read_word(state.response + state.response_length - 2, true);
|
||||
|
||||
if (computed != expected) {
|
||||
if (cfg.debug) {
|
||||
char sent[state.command_length * 2 + 1];
|
||||
char response[state.response_length * 2 + 1];
|
||||
format_hex_buf(sent, state.command, state.command_length);
|
||||
format_hex_buf(response, state.response, state.response_length);
|
||||
|
||||
STATUS_WARNING(STAT_OK, "modbus: invalid CRC, received=0x%04x "
|
||||
"computed=0x%04x sent=0x%s received=0x%s",
|
||||
expected, computed, sent, response);
|
||||
}
|
||||
|
||||
state.crc_errs++;
|
||||
state.status = MODBUS_CRC;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that slave id matches
|
||||
if (state.command[0] != state.response[0]) {
|
||||
STATUS_WARNING(STAT_OK, "modbus: unexpected slave id, expected=%u got=%u",
|
||||
state.command[0], state.response[0]);
|
||||
state.status = MODBUS_INVALID;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that function code matches
|
||||
if (state.command[1] != state.response[1]) {
|
||||
STATUS_WARNING(STAT_OK, "modbus: invalid function code, expected=%u got=%u",
|
||||
state.command[1], state.response[1]);
|
||||
state.status = MODBUS_INVALID;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void _notify(uint8_t func, uint8_t bytes, const uint8_t *data) {
|
||||
if (!state.receive_cb) return;
|
||||
|
||||
modbus_cb_t cb = state.receive_cb;
|
||||
state.receive_cb = 0; // May be set in callback
|
||||
|
||||
cb(func, bytes, data);
|
||||
}
|
||||
|
||||
|
||||
static void _handle_response() {
|
||||
if (!state.response_ready) return;
|
||||
state.response_ready = false;
|
||||
|
||||
if (!_check_response()) return;
|
||||
|
||||
state.last_write = 0; // Clear timeout timer
|
||||
state.last_read = rtc_get_time(); // Set delay timer
|
||||
state.retry = 0; // Reset retry counter
|
||||
state.status = MODBUS_OK;
|
||||
state.busy = false;
|
||||
|
||||
_notify(state.response[1], state.response_length - 4, state.response + 2);
|
||||
}
|
||||
|
||||
|
||||
/// Data register empty interrupt
|
||||
ISR(RS485_DRE_vect) {
|
||||
RS485_PORT.DATA = state.command[state.bytes++];
|
||||
|
||||
if (state.bytes == state.command_length) {
|
||||
_set_dre_interrupt(false);
|
||||
_set_txc_interrupt(true);
|
||||
state.bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Transmit complete interrupt
|
||||
ISR(RS485_TXC_vect) {
|
||||
_set_txc_interrupt(false);
|
||||
_set_rxc_interrupt(true);
|
||||
_set_write(false); // Switch to read mode
|
||||
state.transmit_complete = true;
|
||||
}
|
||||
|
||||
|
||||
/// Data received interrupt
|
||||
ISR(RS485_RXC_vect) {
|
||||
state.response[state.bytes] = RS485_PORT.DATA;
|
||||
|
||||
// Ignore leading zeros
|
||||
if (state.bytes || state.response[0]) state.bytes++;
|
||||
|
||||
if (state.bytes == state.response_length) {
|
||||
_set_rxc_interrupt(false);
|
||||
_set_write(true); // Back to write mode
|
||||
state.bytes = 0;
|
||||
state.response_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _read_cb(uint8_t func, uint8_t bytes, const uint8_t *data) {
|
||||
if (func == MODBUS_READ_OUTPUT_REG && data[0] == bytes - 1) {
|
||||
if (state.rw_cb)
|
||||
for (uint8_t i = 0; i < bytes >> 1; i++)
|
||||
state.rw_cb(true, state.addr + i, _read_word(data + i * 2 + 1, false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.rw_cb) state.rw_cb(false, state.addr, 0);
|
||||
}
|
||||
|
||||
|
||||
static void _write_cb(uint8_t func, uint8_t bytes, const uint8_t *data) {
|
||||
if ((func == MODBUS_WRITE_OUTPUT_REG ||
|
||||
func == MODBUS_WRITE_OUTPUT_REGS) && bytes == 4 &&
|
||||
_read_word(data, false) == state.addr) {
|
||||
if (state.rw_cb)
|
||||
state.rw_cb(true, state.addr, _read_word(state.command + 4, false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.rw_cb) state.rw_cb(false, state.addr, 0);
|
||||
}
|
||||
|
||||
|
||||
static void _reset() {
|
||||
_set_dre_interrupt(false);
|
||||
_set_txc_interrupt(false);
|
||||
_set_rxc_interrupt(false);
|
||||
_set_write(true); // RS485 write mode
|
||||
|
||||
// Flush USART
|
||||
uint8_t x = RS485_PORT.DATA;
|
||||
x = RS485_PORT.STATUS;
|
||||
x = x;
|
||||
|
||||
// Clear state
|
||||
state.write_ready = false;
|
||||
state.busy = false;
|
||||
}
|
||||
|
||||
|
||||
static void _retry() {
|
||||
state.last_write = 0;
|
||||
state.bytes = 0;
|
||||
state.retry++;
|
||||
|
||||
_set_write(true); // RS485 write mode
|
||||
|
||||
_set_txc_interrupt(false);
|
||||
_set_rxc_interrupt(false);
|
||||
_set_dre_interrupt(true);
|
||||
|
||||
// Try changing pin polarity
|
||||
if (state.retry == MODBUS_RETRIES) {
|
||||
PINCTRL_PIN(RS485_RO_PIN) ^= PORT_INVEN_bm;
|
||||
PINCTRL_PIN(RS485_DI_PIN) ^= PORT_INVEN_bm;
|
||||
}
|
||||
|
||||
if (cfg.debug) STATUS_DEBUG("modbus: retry %d", state.retry);
|
||||
}
|
||||
|
||||
|
||||
static void _timeout() {
|
||||
if (cfg.debug) STATUS_DEBUG("modbus: timedout");
|
||||
|
||||
if (state.status == MODBUS_OK || state.status == MODBUS_DISCONNECTED)
|
||||
state.status = MODBUS_TIMEDOUT;
|
||||
|
||||
_reset();
|
||||
_notify(state.command[1], 0, 0);
|
||||
}
|
||||
|
||||
|
||||
static stop_t _get_stop() {
|
||||
// RTU mode characters must always be 11-bits long
|
||||
return cfg.parity == USART_NONE ? USART_2STOP : USART_1STOP;
|
||||
}
|
||||
|
||||
|
||||
void modbus_init() {
|
||||
PR.PRPD &= ~PR_USART1_bm; // Disable power reduction
|
||||
|
||||
DIRCLR_PIN(RS485_RO_PIN); // Input
|
||||
OUTSET_PIN(RS485_DI_PIN); // High
|
||||
DIRSET_PIN(RS485_DI_PIN); // Output
|
||||
OUTSET_PIN(RS485_RW_PIN); // High
|
||||
DIRSET_PIN(RS485_RW_PIN); // Output
|
||||
|
||||
_reset();
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.status = MODBUS_DISCONNECTED;
|
||||
|
||||
usart_init_port(&RS485_PORT, cfg.baud, cfg.parity, USART_8BITS, _get_stop());
|
||||
}
|
||||
|
||||
|
||||
void modbus_deinit() {
|
||||
_reset();
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.status = MODBUS_DISCONNECTED;
|
||||
|
||||
// Disable USART
|
||||
RS485_PORT.CTRLB &= ~(USART_RXEN_bm | USART_TXEN_bm);
|
||||
|
||||
// Float write pins
|
||||
DIRCLR_PIN(RS485_DI_PIN);
|
||||
DIRCLR_PIN(RS485_RW_PIN);
|
||||
}
|
||||
|
||||
|
||||
bool modbus_busy() {return state.busy;}
|
||||
|
||||
|
||||
static void _start_write() {
|
||||
if (!state.write_ready) return;
|
||||
|
||||
// The minimum delay between modbus messages is 3.5 characters. There are
|
||||
// 11-bits per character in RTU mode. Character time is calculated as
|
||||
// follows:
|
||||
//
|
||||
// char time = 11-bits / baud * 3.5
|
||||
//
|
||||
// At 9600 baud the minimum delay is 4.01ms. At 19200 it's 2.005ms. All
|
||||
// supported higher baud rates require the 1.75ms minimum delay. We round up
|
||||
// and add 1ms to ensure the delay is never less than the required minimum.
|
||||
if (state.last_read &&
|
||||
!rtc_expired(state.last_read + (cfg.baud == USART_BAUD_9600 ? 5 : 3)))
|
||||
return;
|
||||
state.last_read = 0;
|
||||
|
||||
state.write_ready = false;
|
||||
_set_dre_interrupt(true);
|
||||
}
|
||||
|
||||
|
||||
void modbus_func(uint8_t func, uint8_t send, const uint8_t *data,
|
||||
uint8_t receive, modbus_cb_t receive_cb) {
|
||||
state.bytes = 0;
|
||||
state.command_length = send + 4;
|
||||
state.response_length = receive + 4;
|
||||
state.receive_cb = receive_cb;
|
||||
state.last_write = 0;
|
||||
state.retry = 0;
|
||||
|
||||
ESTOP_ASSERT(state.command_length <= MODBUS_BUF_SIZE, STAT_MODBUS_BUF_LENGTH);
|
||||
ESTOP_ASSERT(state.response_length <= MODBUS_BUF_SIZE,
|
||||
STAT_MODBUS_BUF_LENGTH);
|
||||
|
||||
state.command[0] = cfg.id;
|
||||
state.command[1] = func;
|
||||
memcpy(state.command + 2, data, send);
|
||||
_write_word(state.command + send + 2, _crc16(state.command, send + 2), true);
|
||||
|
||||
state.busy = true;
|
||||
state.write_ready = true;
|
||||
_start_write();
|
||||
}
|
||||
|
||||
|
||||
void modbus_read(uint16_t addr, uint16_t count, modbus_rw_cb_t cb) {
|
||||
state.rw_cb = cb;
|
||||
state.addr = addr;
|
||||
uint8_t cmd[4];
|
||||
_write_word(cmd, addr, false);
|
||||
_write_word(cmd + 2, count, false);
|
||||
modbus_func(MODBUS_READ_OUTPUT_REG, 4, cmd, 2 * count + 1, _read_cb);
|
||||
}
|
||||
|
||||
|
||||
void modbus_write(uint16_t addr, uint16_t value, modbus_rw_cb_t cb) {
|
||||
state.rw_cb = cb;
|
||||
state.addr = addr;
|
||||
uint8_t cmd[4];
|
||||
_write_word(cmd, addr, false);
|
||||
_write_word(cmd + 2, value, false);
|
||||
modbus_func(MODBUS_WRITE_OUTPUT_REG, 4, cmd, 4, _write_cb);
|
||||
}
|
||||
|
||||
|
||||
void modbus_multi_write(uint16_t addr, uint16_t value, modbus_rw_cb_t cb) {
|
||||
state.rw_cb = cb;
|
||||
state.addr = addr;
|
||||
uint8_t cmd[7];
|
||||
_write_word(cmd, addr, false); // Start address
|
||||
_write_word(cmd + 2, 1, false); // Number of regs
|
||||
cmd[4] = 2; // Number of bytes
|
||||
_write_word(cmd + 5, value, false); // Value
|
||||
modbus_func(MODBUS_WRITE_OUTPUT_REGS, 7, cmd, 4, _write_cb);
|
||||
}
|
||||
|
||||
|
||||
void modbus_callback() {
|
||||
if (state.transmit_complete) {
|
||||
state.last_write = rtc_get_time();
|
||||
state.transmit_complete = false;
|
||||
}
|
||||
|
||||
_handle_response();
|
||||
_start_write();
|
||||
|
||||
// Timeout out writes
|
||||
if (!state.last_write || !rtc_expired(state.last_write + MODBUS_TIMEOUT))
|
||||
return;
|
||||
state.last_write = 0;
|
||||
|
||||
if (cfg.debug && state.bytes) {
|
||||
char sent[state.command_length * 2 + 1];
|
||||
char received[state.bytes * 2 + 1];
|
||||
|
||||
format_hex_buf(sent, state.command, state.command_length);
|
||||
format_hex_buf(received, state.response, state.bytes);
|
||||
|
||||
STATUS_DEBUG("modbus: sent 0x%s received 0x%s expected %u bytes",
|
||||
sent, received, state.response_length);
|
||||
}
|
||||
|
||||
if (state.retry < 2 * MODBUS_RETRIES) _retry();
|
||||
else _timeout();
|
||||
}
|
||||
|
||||
|
||||
// Variable callbacks
|
||||
bool get_mb_debug() {return cfg.debug;}
|
||||
void set_mb_debug(bool value) {cfg.debug = value;}
|
||||
uint8_t get_mb_id() {return cfg.id;}
|
||||
void set_mb_id(uint8_t id) {cfg.id = id;}
|
||||
uint8_t get_mb_baud() {return cfg.baud;}
|
||||
|
||||
|
||||
void set_mb_baud(uint8_t baud) {
|
||||
cfg.baud = (baud_t)baud;
|
||||
usart_set_baud(&RS485_PORT, cfg.baud);
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_mb_parity() {return cfg.parity;}
|
||||
|
||||
|
||||
void set_mb_parity(uint8_t parity) {
|
||||
cfg.parity = (parity_t)parity;
|
||||
usart_set_parity(&RS485_PORT, cfg.parity);
|
||||
usart_set_stop(&RS485_PORT, _get_stop());
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_mb_status() {return state.status;}
|
||||
uint16_t get_mb_crc_errs() {return state.crc_errs;}
|
||||
112
src/avr/src/modbus.h
Normal file
112
src/avr/src/modbus.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
/******************************************************************************\
|
||||
|
||||
Modbus RTU command format is as follows:
|
||||
|
||||
Function 01: Read Coil Status
|
||||
Function 02: Read Input Status
|
||||
|
||||
Send: [id][func][addr][count][crc]
|
||||
Receive: [id][func][bytes][flags][crc]
|
||||
|
||||
Function 03: Read Holding Registers
|
||||
Function 04: Read Input Registers
|
||||
|
||||
Send: [id][func][addr][count][crc]
|
||||
Receive: [id][func][bytes][regs][crc]
|
||||
|
||||
Function 05: Force Single Coil
|
||||
Function 06: Preset Single Register
|
||||
|
||||
Send: [id][func][addr][value][crc]
|
||||
Receive: [id][func][addr][value][crc]
|
||||
|
||||
Function 15: Force Multiple Coils
|
||||
|
||||
Send: [id][func][addr][count][bytes][flags][crc]
|
||||
Receive: [id][func][addr][count][crc]
|
||||
|
||||
Function 16: Preset Multiple Registers
|
||||
|
||||
Send: [id][func][addr][count][bytes][regs][crc]
|
||||
Receive: [id][func][addr][count][crc]
|
||||
|
||||
Where:
|
||||
|
||||
id: 1-byte Slave ID
|
||||
func: 1-byte Function code
|
||||
addr: 2-byte Data address
|
||||
count: 2-byte Address count
|
||||
bytes: 1-byte Number of bytes to follow
|
||||
value: 2-byte Value read or written
|
||||
bits: n-bytes Flags indicating on/off
|
||||
regs: n-bytes register values to write
|
||||
checksum: 16-bit CRC: x^16 + x^15 + x^2 + 1 (0x8005) initial: 0xffff
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
MODBUS_READ_COILS = 1,
|
||||
MODBUS_READ_CONTACTS = 2,
|
||||
MODBUS_READ_OUTPUT_REG = 3,
|
||||
MODBUS_READ_INPUT_REG = 4,
|
||||
MODBUS_WRITE_COIL = 5,
|
||||
MODBUS_WRITE_OUTPUT_REG = 6,
|
||||
MODBUS_WRITE_COILS = 15,
|
||||
MODBUS_WRITE_OUTPUT_REGS = 16,
|
||||
} modbus_function_code_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
MODBUS_COIL_BASE = 0,
|
||||
MODBUS_CONTACT_BASE = 10000,
|
||||
MODBUS_INPUT_REG_BASE = 30000,
|
||||
MODBUS_OUTPUT_REG_BASE = 40000,
|
||||
} modbus_base_addrs_t;
|
||||
|
||||
|
||||
typedef void (*modbus_cb_t)(uint8_t func, uint8_t bytes, const uint8_t *data);
|
||||
typedef void (*modbus_rw_cb_t)(bool ok, uint16_t addr, uint16_t value);
|
||||
|
||||
void modbus_init();
|
||||
void modbus_deinit();
|
||||
bool modbus_busy();
|
||||
void modbus_func(uint8_t func, uint8_t send, const uint8_t *data,
|
||||
uint8_t receive, modbus_cb_t cb);
|
||||
void modbus_read(uint16_t addr, uint16_t count, modbus_rw_cb_t cb);
|
||||
void modbus_write(uint16_t addr, uint16_t value, modbus_rw_cb_t cb);
|
||||
void modbus_multi_write(uint16_t addr, uint16_t value, modbus_rw_cb_t cb);
|
||||
void modbus_callback();
|
||||
497
src/avr/src/motor.c
Normal file
497
src/avr/src/motor.c
Normal file
@@ -0,0 +1,497 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "motor.h"
|
||||
#include "config.h"
|
||||
#include "hardware.h"
|
||||
#include "cpp_magic.h"
|
||||
#include "rtc.h"
|
||||
#include "report.h"
|
||||
#include "stepper.h"
|
||||
#include "drv8711.h"
|
||||
#include "estop.h"
|
||||
#include "axis.h"
|
||||
#include "util.h"
|
||||
#include "pgmspace.h"
|
||||
#include "exec.h"
|
||||
|
||||
#include <util/delay.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
// Config
|
||||
uint8_t axis; // map motor to axis
|
||||
uint8_t step_pin;
|
||||
uint8_t dir_pin;
|
||||
TC0_t *timer;
|
||||
DMA_CH_t *dma;
|
||||
uint8_t dma_trigger;
|
||||
|
||||
bool slave;
|
||||
uint16_t microsteps; // microsteps per full step
|
||||
uint16_t stall_microsteps;
|
||||
bool reverse;
|
||||
bool enabled;
|
||||
float step_angle; // degrees per whole step
|
||||
float travel_rev; // mm or deg of travel per motor revolution
|
||||
float min_soft_limit;
|
||||
float max_soft_limit;
|
||||
bool homed;
|
||||
|
||||
// Computed
|
||||
float steps_per_unit;
|
||||
|
||||
// Runtime state
|
||||
uint32_t power_timeout;
|
||||
int32_t commanded;
|
||||
int32_t encoder;
|
||||
int16_t error;
|
||||
bool last_negative;
|
||||
|
||||
// Move prep
|
||||
bool prepped;
|
||||
uint8_t clock;
|
||||
uint16_t timer_period;
|
||||
bool negative;
|
||||
int32_t position;
|
||||
} motor_t;
|
||||
|
||||
|
||||
static motor_t motors[MOTORS] = {
|
||||
{
|
||||
.axis = AXIS_X,
|
||||
.step_pin = STEP_0_PIN,
|
||||
.dir_pin = DIR_0_PIN,
|
||||
.timer = &TCD0,
|
||||
.dma = &DMA.CH0,
|
||||
.dma_trigger = DMA_CH_TRIGSRC_TCD0_CCA_gc,
|
||||
}, {
|
||||
.axis = AXIS_Y,
|
||||
.step_pin = STEP_1_PIN,
|
||||
.dir_pin = DIR_1_PIN,
|
||||
.timer = &TCE0,
|
||||
.dma = &DMA.CH1,
|
||||
.dma_trigger = DMA_CH_TRIGSRC_TCE0_CCA_gc,
|
||||
}, {
|
||||
.axis = AXIS_Z,
|
||||
.step_pin = STEP_2_PIN,
|
||||
.dir_pin = DIR_2_PIN,
|
||||
.timer = &TCF0,
|
||||
.dma = &DMA.CH2,
|
||||
.dma_trigger = DMA_CH_TRIGSRC_TCF0_CCA_gc,
|
||||
}, {
|
||||
.axis = AXIS_A,
|
||||
.step_pin = STEP_3_PIN,
|
||||
.dir_pin = DIR_3_PIN,
|
||||
.timer = (TC0_t *)&TCE1,
|
||||
.dma = &DMA.CH3,
|
||||
.dma_trigger = DMA_CH_TRIGSRC_TCE1_CCA_gc,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static uint8_t _dummy;
|
||||
|
||||
|
||||
static void _update_config(int motor) {
|
||||
motor_t *m = &motors[motor];
|
||||
|
||||
m->steps_per_unit = 360.0 * m->microsteps / m->travel_rev / m->step_angle;
|
||||
}
|
||||
|
||||
|
||||
void motor_init() {
|
||||
// Enable DMA
|
||||
DMA.CTRL = DMA_RESET_bm;
|
||||
DMA.CTRL = DMA_ENABLE_bm;
|
||||
DMA.INTFLAGS = 0xff; // clear all pending interrupts
|
||||
|
||||
for (int motor = 0; motor < MOTORS; motor++) {
|
||||
motor_t *m = &motors[motor];
|
||||
|
||||
// Default soft limits
|
||||
m->min_soft_limit = -INFINITY;
|
||||
m->max_soft_limit = INFINITY;
|
||||
|
||||
_update_config(motor);
|
||||
|
||||
// Setup motor timer
|
||||
m->timer->CTRLB = TC_WGMODE_SINGLESLOPE_gc | TC1_CCAEN_bm;
|
||||
m->timer->CCA = STEP_PULSE_WIDTH;
|
||||
|
||||
// Setup DMA channel as timer event counter
|
||||
m->dma->ADDRCTRL = DMA_CH_SRCDIR_FIXED_gc | DMA_CH_DESTDIR_FIXED_gc;
|
||||
m->dma->TRIGSRC = m->dma_trigger;
|
||||
|
||||
// Note, the DMA transfer must read CCA to clear the trigger
|
||||
m->dma->SRCADDR0 = (((uintptr_t)&m->timer->CCA) >> 0) & 0xff;
|
||||
m->dma->SRCADDR1 = (((uintptr_t)&m->timer->CCA) >> 8) & 0xff;
|
||||
m->dma->SRCADDR2 = 0;
|
||||
|
||||
m->dma->DESTADDR0 = (((uintptr_t)&_dummy) >> 0) & 0xff;
|
||||
m->dma->DESTADDR1 = (((uintptr_t)&_dummy) >> 8) & 0xff;
|
||||
m->dma->DESTADDR2 = 0;
|
||||
|
||||
m->dma->CTRLA = DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc;
|
||||
|
||||
// IO pins
|
||||
PINCTRL_PIN(m->step_pin) = PORT_INVEN_bm; // Inverted output
|
||||
DIRSET_PIN(m->step_pin); // Output
|
||||
DIRSET_PIN(m->dir_pin); // Output
|
||||
}
|
||||
|
||||
axis_map_motors();
|
||||
}
|
||||
|
||||
|
||||
bool motor_is_enabled(int motor) {return motors[motor].enabled;}
|
||||
int motor_get_axis(int motor) {return motors[motor].axis;}
|
||||
|
||||
|
||||
static int32_t _position_to_steps(int motor, float position) {
|
||||
return (int32_t)round(position * motors[motor].steps_per_unit);
|
||||
}
|
||||
|
||||
|
||||
void motor_set_position(int motor, float position) {
|
||||
motor_t *m = &motors[motor];
|
||||
m->commanded = m->encoder = m->position = _position_to_steps(motor, position);
|
||||
m->error = 0;
|
||||
}
|
||||
|
||||
|
||||
float motor_get_soft_limit(int motor, bool min) {
|
||||
return min ? motors[motor].min_soft_limit : motors[motor].max_soft_limit;
|
||||
}
|
||||
|
||||
|
||||
bool motor_get_homed(int motor) {return motors[motor].homed;}
|
||||
|
||||
|
||||
static void _update_power(int motor) {
|
||||
motor_t *m = &motors[motor];
|
||||
|
||||
if (m->enabled) {
|
||||
bool timedout = rtc_expired(m->power_timeout);
|
||||
// NOTE, we have ~5ms to update the driver config
|
||||
drv8711_set_state(motor, timedout ? DRV8711_IDLE : DRV8711_ACTIVE);
|
||||
|
||||
} else drv8711_set_state(motor, DRV8711_DISABLED);
|
||||
}
|
||||
|
||||
|
||||
/// Callback to manage motor power sequencing and power-down timing.
|
||||
stat_t motor_rtc_callback() { // called by controller
|
||||
for (int motor = 0; motor < MOTORS; motor++)
|
||||
_update_power(motor);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
void motor_emulate_steps(int motor) {
|
||||
motor_t *m = &motors[motor];
|
||||
m->dma->TRFCNT = 0xffff - abs(m->commanded - m->encoder);
|
||||
}
|
||||
|
||||
|
||||
void motor_end_move(int motor) {
|
||||
motor_t &m = motors[motor];
|
||||
|
||||
if (!m.timer->CTRLA) return;
|
||||
|
||||
// Stop clock
|
||||
m.timer->CTRLA = 0;
|
||||
|
||||
// Wait for pending DMA transfers
|
||||
while (m.dma->CTRLB & DMA_CH_CHPEND_bm) continue;
|
||||
|
||||
// Get actual step count from DMA channel
|
||||
const int32_t steps = 0xffff - m.dma->TRFCNT;
|
||||
|
||||
// Accumulate encoder & compute error
|
||||
m.encoder += m.last_negative ? -steps : steps;
|
||||
m.error = m.commanded - m.encoder;
|
||||
}
|
||||
|
||||
|
||||
void motor_load_move(int motor) {
|
||||
motor_t &m = motors[motor];
|
||||
|
||||
// Clear move
|
||||
ESTOP_ASSERT(m.prepped, STAT_MOTOR_NOT_PREPPED);
|
||||
m.prepped = false;
|
||||
|
||||
motor_end_move(motor);
|
||||
|
||||
if (!m.timer_period) return; // Leave clock stopped
|
||||
|
||||
// Set direction, compensating for polarity but only when moving
|
||||
const bool dir = m.negative ^ m.reverse;
|
||||
if (dir != IN_PIN(m.dir_pin)) {
|
||||
SET_PIN(m.dir_pin, dir);
|
||||
|
||||
// We need at least 200ns between direction change and next step.
|
||||
if (m.timer->CCA < m.timer->CNT) m.timer->CNT = m.timer->CCA + 1;
|
||||
}
|
||||
|
||||
// Reset DMA step counter
|
||||
m.dma->CTRLA &= ~DMA_CH_ENABLE_bm;
|
||||
m.dma->TRFCNT = 0xffff;
|
||||
m.dma->CTRLA |= DMA_CH_ENABLE_bm;
|
||||
|
||||
// To avoid causing couter wrap around, it is important to start the clock
|
||||
// before setting PERBUF. If PERBUF is set before the clock is started PER
|
||||
// updates immediately and possibly mid step.
|
||||
|
||||
// Set clock and period
|
||||
m.timer->CTRLA = m.clock; // Start clock
|
||||
m.timer->PERBUF = m.timer_period; // Set next frequency
|
||||
m.last_negative = m.negative;
|
||||
m.commanded = m.position;
|
||||
}
|
||||
|
||||
|
||||
void motor_prep_move(int motor, float target) {
|
||||
// Validate input
|
||||
ESTOP_ASSERT(0 <= motor && motor < MOTORS, STAT_MOTOR_ID_INVALID);
|
||||
ESTOP_ASSERT(isfinite(target), STAT_BAD_FLOAT);
|
||||
|
||||
motor_t &m = motors[motor];
|
||||
ESTOP_ASSERT(!m.prepped, STAT_MOTOR_NOT_READY);
|
||||
|
||||
// Travel in steps
|
||||
int32_t position = _position_to_steps(motor, target);
|
||||
int24_t steps = position - m.position;
|
||||
m.position = position;
|
||||
|
||||
// Error correction
|
||||
int16_t correction = abs(m.error);
|
||||
if (MIN_STEP_CORRECTION <= correction) {
|
||||
// Dampen correction oscillation
|
||||
correction >>= 1;
|
||||
|
||||
// Make correction
|
||||
steps += m.error < 0 ? -correction : correction;
|
||||
}
|
||||
|
||||
// Positive steps from here on
|
||||
m.negative = steps < 0;
|
||||
if (m.negative) steps = -steps;
|
||||
|
||||
// Start with clock / 2
|
||||
const float seg_clocks = SEGMENT_TIME * (F_CPU * 60 / 2);
|
||||
float ticks_per_step = seg_clocks / steps;
|
||||
|
||||
// Use faster clock with faster step rates for increased resolution.
|
||||
if (ticks_per_step < 0x7fff) {
|
||||
ticks_per_step *= 2;
|
||||
m.clock = TC_CLKSEL_DIV1_gc;
|
||||
|
||||
// Limit clock if step rate is too fast
|
||||
// We allow a slight fudge here (i.e. 1.9 instead 2) because the motor
|
||||
// driver is able to handle it and otherwise we could not actually hit
|
||||
// an average rate of 250k usteps/sec.
|
||||
if (ticks_per_step < STEP_PULSE_WIDTH * 1.9)
|
||||
ticks_per_step = STEP_PULSE_WIDTH * 1.9; // Too fast
|
||||
|
||||
} else m.clock = TC_CLKSEL_DIV2_gc; // NOTE, pulse width will be twice as long
|
||||
|
||||
// Disable clock if too slow
|
||||
if (0xffff <= ticks_per_step) ticks_per_step = 0;
|
||||
|
||||
m.timer_period = steps ? round(ticks_per_step) : 0;
|
||||
|
||||
// Power motor
|
||||
if (!m.enabled) {
|
||||
m.timer_period = 0;
|
||||
m.encoder = m.commanded = m.position;
|
||||
m.error = 0;
|
||||
|
||||
} else if (m.timer_period) // Motor is moving so reset power timeout
|
||||
m.power_timeout = rtc_get_time() + MOTOR_IDLE_TIMEOUT * 1000;
|
||||
_update_power(motor);
|
||||
|
||||
// Queue move
|
||||
m.prepped = true;
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
bool get_motor_enabled(int motor) {return motors[motor].enabled;}
|
||||
|
||||
|
||||
void set_motor_enabled(int motor, bool enabled) {
|
||||
if (motors[motor].slave) return;
|
||||
|
||||
for (int m = motor; m < MOTORS; m++)
|
||||
if (motors[m].axis == motors[motor].axis)
|
||||
motors[m].enabled = enabled;
|
||||
}
|
||||
|
||||
|
||||
float get_step_angle(int motor) {return motors[motor].step_angle;}
|
||||
|
||||
|
||||
void set_step_angle(int motor, float value) {
|
||||
if (motors[motor].slave) return;
|
||||
|
||||
for (int m = motor; m < MOTORS; m++)
|
||||
if (motors[m].axis == motors[motor].axis) {
|
||||
motors[m].step_angle = value;
|
||||
_update_config(m);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float get_travel(int motor) {return motors[motor].travel_rev;}
|
||||
|
||||
|
||||
void set_travel(int motor, float value) {
|
||||
if (motors[motor].slave) return;
|
||||
|
||||
for (int m = motor; m < MOTORS; m++)
|
||||
if (motors[m].axis == motors[motor].axis) {
|
||||
motors[m].travel_rev = value;
|
||||
_update_config(m);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t get_microstep(int motor) {return motors[motor].microsteps;}
|
||||
|
||||
void set_microstep(int motor, uint16_t value) {
|
||||
switch (value) {
|
||||
case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: case 256:
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
if (motors[motor].slave) return;
|
||||
|
||||
for (int m = motor; m < MOTORS; m++)
|
||||
if (motors[m].axis == motors[motor].axis) {
|
||||
motors[m].microsteps = value;
|
||||
_update_config(m);
|
||||
drv8711_set_microsteps(m, value);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t get_stall_microstep(int motor) {return motors[motor].stall_microsteps;}
|
||||
|
||||
void set_stall_microstep(int motor, uint16_t value) {
|
||||
switch (value) {
|
||||
case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: case 256:
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
if (motors[motor].slave) return;
|
||||
|
||||
for (int m = motor; m < MOTORS; m++)
|
||||
if (motors[m].axis == motors[motor].axis) {
|
||||
motors[m].stall_microsteps = value;
|
||||
//_update_config(m);
|
||||
//drv8711_set_stall_microsteps(m, value);
|
||||
}
|
||||
}
|
||||
|
||||
void enable_stall_microstep(int m)
|
||||
{
|
||||
//for (int m = 0; m < MOTORS; m++)
|
||||
//{
|
||||
motor_t *motorTemp = &motors[m];
|
||||
motorTemp->steps_per_unit = 360.0 * motorTemp->stall_microsteps / motorTemp->travel_rev / motorTemp->step_angle;
|
||||
drv8711_set_microsteps(m, motors[m].stall_microsteps);
|
||||
//}
|
||||
}
|
||||
|
||||
void disable_stall_microstep()
|
||||
{
|
||||
for(int m =0; m < MOTORS; m++)
|
||||
{
|
||||
_update_config(m); //Restore the steps_per_unit
|
||||
drv8711_set_microsteps(m, motors[m].microsteps);
|
||||
}
|
||||
}
|
||||
|
||||
char get_motor_axis(int motor) {return motors[motor].axis;}
|
||||
|
||||
|
||||
void set_motor_axis(int motor, uint8_t axis) {
|
||||
if (MOTORS <= motor || AXES <= axis || axis == motors[motor].axis) return;
|
||||
motors[motor].axis = axis;
|
||||
axis_map_motors();
|
||||
|
||||
// Reset encoder
|
||||
motor_set_position(motor, exec_get_axis_position(axis));
|
||||
|
||||
// Check if this is now a slave motor
|
||||
motors[motor].slave = false;
|
||||
for (int m = 0; m < motor; m++)
|
||||
if (motors[m].axis == motors[motor].axis) {
|
||||
// Sync with master
|
||||
set_step_angle(motor, motors[m].step_angle);
|
||||
set_travel(motor, motors[m].travel_rev);
|
||||
set_microstep(motor, motors[m].microsteps);
|
||||
set_stall_microstep(motor, motors[m].stall_microsteps);
|
||||
set_motor_enabled(motor, motors[m].enabled);
|
||||
motors[motor].slave = true; // Must be last
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool get_reverse(int motor) {return motors[motor].reverse;}
|
||||
void set_reverse(int motor, bool value) {motors[motor].reverse = value;}
|
||||
|
||||
|
||||
float get_min_soft_limit(int motor) {return motors[motor].min_soft_limit;}
|
||||
float get_max_soft_limit(int motor) {return motors[motor].max_soft_limit;}
|
||||
|
||||
|
||||
void set_min_soft_limit(int motor, float limit) {
|
||||
motors[motor].min_soft_limit = limit;
|
||||
}
|
||||
|
||||
|
||||
void set_max_soft_limit(int motor, float limit) {
|
||||
motors[motor].max_soft_limit = limit;
|
||||
}
|
||||
|
||||
|
||||
bool get_homed(int motor) {return motors[motor].homed;}
|
||||
void set_homed(int motor, bool homed) {motors[motor].homed = homed;}
|
||||
|
||||
|
||||
int32_t get_encoder(int m) {return motors[m].encoder;}
|
||||
int32_t get_error(int m) {return motors[m].error;}
|
||||
61
src/avr/src/motor.h
Normal file
61
src/avr/src/motor.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "status.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
MOTOR_DISABLED, // motor enable is deactivated
|
||||
MOTOR_ALWAYS_POWERED, // motor is always powered while machine is ON
|
||||
MOTOR_POWERED_IN_CYCLE, // motor fully powered during cycles,
|
||||
// de-powered out of cycle
|
||||
MOTOR_POWERED_ONLY_WHEN_MOVING, // idles shortly after stopped, even in cycle
|
||||
} motor_power_mode_t;
|
||||
|
||||
|
||||
void motor_init();
|
||||
|
||||
bool motor_is_enabled(int motor);
|
||||
int motor_get_axis(int motor);
|
||||
void motor_set_position(int motor, float position);
|
||||
float motor_get_soft_limit(int motor, bool min);
|
||||
bool motor_get_homed(int motor);
|
||||
|
||||
stat_t motor_rtc_callback();
|
||||
|
||||
void motor_end_move(int motor);
|
||||
void motor_load_move(int motor);
|
||||
void motor_prep_move(int motor, float target);
|
||||
|
||||
void enable_stall_microstep(int m);
|
||||
void disable_stall_microstep();
|
||||
|
||||
161
src/avr/src/outputs.c
Normal file
161
src/avr/src/outputs.c
Normal file
@@ -0,0 +1,161 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "outputs.h"
|
||||
#include "config.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t pin;
|
||||
bool active;
|
||||
output_state_t state;
|
||||
output_mode_t mode;
|
||||
} output_t;
|
||||
|
||||
|
||||
output_t outputs[OUTS] = {
|
||||
{TOOL_ENABLE_PIN},
|
||||
{TOOL_DIR_PIN},
|
||||
{SWITCH_1_PIN},
|
||||
{SWITCH_2_PIN},
|
||||
{FAULT_PIN},
|
||||
{TEST_PIN},
|
||||
};
|
||||
|
||||
|
||||
static output_t *_get_output(uint8_t pin) {
|
||||
switch (pin) {
|
||||
case TOOL_ENABLE_PIN: return &outputs[0];
|
||||
case TOOL_DIR_PIN: return &outputs[1];
|
||||
case SWITCH_1_PIN: return &outputs[2];
|
||||
case SWITCH_2_PIN: return &outputs[3];
|
||||
case FAULT_PIN: return &outputs[4];
|
||||
case TEST_PIN: return &outputs[5];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void _update_state(output_t *output) {
|
||||
switch (output->mode) {
|
||||
case OUT_DISABLED: output->state = OUT_TRI; break;
|
||||
case OUT_LO_HI: output->state = output->active ? OUT_HI : OUT_LO; break;
|
||||
case OUT_HI_LO: output->state = output->active ? OUT_LO : OUT_HI; break;
|
||||
case OUT_TRI_LO: output->state = output->active ? OUT_LO : OUT_TRI; break;
|
||||
case OUT_TRI_HI: output->state = output->active ? OUT_HI : OUT_TRI; break;
|
||||
case OUT_LO_TRI: output->state = output->active ? OUT_TRI : OUT_LO; break;
|
||||
case OUT_HI_TRI: output->state = output->active ? OUT_TRI : OUT_HI; break;
|
||||
}
|
||||
|
||||
switch (output->state) {
|
||||
case OUT_TRI: DIRCLR_PIN(output->pin); break;
|
||||
case OUT_HI: OUTSET_PIN(output->pin); DIRSET_PIN(output->pin); break;
|
||||
case OUT_LO: OUTCLR_PIN(output->pin); DIRSET_PIN(output->pin); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void outputs_init() {
|
||||
for (int i = 0; i < OUTS; i++) _update_state(&outputs[i]);
|
||||
}
|
||||
|
||||
|
||||
bool outputs_is_active(uint8_t pin) {
|
||||
output_t *output = _get_output(pin);
|
||||
return output ? output->active : false;
|
||||
}
|
||||
|
||||
|
||||
void outputs_set_active(uint8_t pin, bool active) {
|
||||
output_t *output = _get_output(pin);
|
||||
if (!output) return;
|
||||
|
||||
output->active = active;
|
||||
_update_state(output);
|
||||
}
|
||||
|
||||
|
||||
bool outputs_toggle(uint8_t pin) {
|
||||
output_t *output = _get_output(pin);
|
||||
if (!output) return false;
|
||||
|
||||
output->active = !output->active;
|
||||
_update_state(output);
|
||||
return output->active;
|
||||
}
|
||||
|
||||
|
||||
void outputs_set_mode(uint8_t pin, output_mode_t mode) {
|
||||
output_t *output = _get_output(pin);
|
||||
if (!output) return;
|
||||
output->mode = mode;
|
||||
_update_state(output);
|
||||
}
|
||||
|
||||
|
||||
output_state_t outputs_get_state(uint8_t pin) {
|
||||
output_t *output = _get_output(pin);
|
||||
if (output) return OUT_TRI;
|
||||
return output->state;
|
||||
}
|
||||
|
||||
|
||||
void outputs_stop() {
|
||||
outputs_set_active(SWITCH_1_PIN, false);
|
||||
outputs_set_active(SWITCH_2_PIN, false);
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
uint8_t get_output_state(int id) {
|
||||
return OUTS <= id ? OUT_TRI : outputs[id].state;
|
||||
}
|
||||
|
||||
|
||||
bool get_output_active(int id) {
|
||||
return OUTS <= id ? false : outputs[id].active;
|
||||
}
|
||||
|
||||
|
||||
void set_output_active(int id, bool active) {
|
||||
if (OUTS <= id) return;
|
||||
outputs[id].active = active;
|
||||
_update_state(&outputs[id]);
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_output_mode(int id) {
|
||||
return OUTS <= id ? OUT_DISABLED : outputs[id].mode;
|
||||
}
|
||||
|
||||
|
||||
void set_output_mode(int id, uint8_t mode) {
|
||||
if (OUTS <= id) return;
|
||||
outputs[id].mode = (output_mode_t)mode;
|
||||
_update_state(&outputs[id]);
|
||||
}
|
||||
59
src/avr/src/outputs.h
Normal file
59
src/avr/src/outputs.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
OUT_LO,
|
||||
OUT_HI,
|
||||
OUT_TRI,
|
||||
} output_state_t;
|
||||
|
||||
|
||||
/// OUT_<inactive>_<active>
|
||||
typedef enum {
|
||||
OUT_DISABLED,
|
||||
OUT_LO_HI,
|
||||
OUT_HI_LO,
|
||||
OUT_TRI_LO,
|
||||
OUT_TRI_HI,
|
||||
OUT_LO_TRI,
|
||||
OUT_HI_TRI,
|
||||
} output_mode_t;
|
||||
|
||||
|
||||
void outputs_init();
|
||||
bool outputs_is_active(uint8_t pin);
|
||||
void outputs_set_active(uint8_t pin, bool active);
|
||||
bool outputs_toggle(uint8_t pin);
|
||||
void outputs_set_mode(uint8_t pin, output_mode_t mode);
|
||||
output_state_t outputs_get_state(uint8_t pin);
|
||||
void outputs_stop();
|
||||
36
src/avr/src/pgmspace.h
Normal file
36
src/avr/src/pgmspace.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
#ifdef __AVR__
|
||||
#define PRPSTR "S"
|
||||
#else
|
||||
#define PRPSTR "s"
|
||||
#endif
|
||||
31
src/avr/src/pins.c
Normal file
31
src/avr/src/pins.c
Normal file
@@ -0,0 +1,31 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "pins.h"
|
||||
|
||||
|
||||
PORT_t *pin_ports[] = {&PORTA, &PORTB, &PORTC, &PORTD, &PORTE, &PORTF};
|
||||
57
src/avr/src/pins.h
Normal file
57
src/avr/src/pins.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
enum {PORT_A = 1, PORT_B, PORT_C, PORT_D, PORT_E, PORT_F};
|
||||
|
||||
#define PIN_INDEX(PIN) (PIN & 7)
|
||||
#define PORT_INDEX(PIN) ((PIN >> 3) - 1)
|
||||
|
||||
#define PIN_PORT(PIN) pin_ports[PORT_INDEX(PIN)]
|
||||
#define PIN_BM(PIN) (1 << PIN_INDEX(PIN))
|
||||
|
||||
#define PIN_ID(PORT, PIN) (PORT << 3 | (PIN & 7))
|
||||
|
||||
#include <avr/io.h>
|
||||
|
||||
extern PORT_t *pin_ports[];
|
||||
|
||||
#define DIRSET_PIN(PIN) PIN_PORT(PIN)->DIRSET = PIN_BM(PIN)
|
||||
#define DIRCLR_PIN(PIN) PIN_PORT(PIN)->DIRCLR = PIN_BM(PIN)
|
||||
#define OUTCLR_PIN(PIN) PIN_PORT(PIN)->OUTCLR = PIN_BM(PIN)
|
||||
#define OUTSET_PIN(PIN) PIN_PORT(PIN)->OUTSET = PIN_BM(PIN)
|
||||
#define OUTTGL_PIN(PIN) PIN_PORT(PIN)->OUTTGL = PIN_BM(PIN)
|
||||
#define OUT_PIN(PIN) (!!(PIN_PORT(PIN)->OUT & PIN_BM(PIN)))
|
||||
#define IN_PIN(PIN) (!!(PIN_PORT(PIN)->IN & PIN_BM(PIN)))
|
||||
#define PINCTRL_PIN(PIN) ((&PIN_PORT(PIN)->PIN0CTRL)[PIN_INDEX(PIN)])
|
||||
|
||||
#define SET_PIN(PIN, X) \
|
||||
do {if (X) OUTSET_PIN(PIN); else OUTCLR_PIN(PIN);} while (0);
|
||||
|
||||
#define PIN_EVSYS_CHMUX(PIN) \
|
||||
(EVSYS_CHMUX_PORTA_PIN0_gc + (8 * PORT_INDEX(PIN)) + PIN_INDEX(PIN))
|
||||
221
src/avr/src/pwm.c
Normal file
221
src/avr/src/pwm.c
Normal file
@@ -0,0 +1,221 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "pwm.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "estop.h"
|
||||
#include "outputs.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
float freq; // base frequency for PWM driver, in Hz
|
||||
float min_duty;
|
||||
float max_duty;
|
||||
float power;
|
||||
} pwm_t;
|
||||
|
||||
|
||||
static pwm_t pwm = {0};
|
||||
|
||||
|
||||
static void _set_enable(bool enable) {
|
||||
outputs_set_active(TOOL_ENABLE_PIN, enable);
|
||||
}
|
||||
|
||||
|
||||
static void _set_dir(bool clockwise) {
|
||||
outputs_set_active(TOOL_DIR_PIN, !clockwise);
|
||||
}
|
||||
|
||||
|
||||
static void _update_clock(uint16_t period) {
|
||||
if (estop_triggered()) period = 0;
|
||||
|
||||
// Disable
|
||||
if (!period) {
|
||||
TIMER_PWM.CTRLB = 0; // Disable clock control of pin
|
||||
OUTCLR_PIN(PWM_PIN);
|
||||
_set_enable(false);
|
||||
return;
|
||||
}
|
||||
_set_enable(true);
|
||||
|
||||
// 100% duty
|
||||
if (period == 0xffff) {
|
||||
TIMER_PWM.CTRLB = 0; // Disable clock control of pin
|
||||
OUTSET_PIN(PWM_PIN);
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure clock
|
||||
TIMER_PWM.CTRLB = TC1_CCAEN_bm | TC_WGMODE_SINGLESLOPE_gc;
|
||||
TIMER_PWM.CCA = period;
|
||||
}
|
||||
|
||||
|
||||
static float _compute_duty(float power) {
|
||||
power = fabsf(power);
|
||||
if (!power) return 0; // 0% duty
|
||||
if (power == 1 && pwm.max_duty == 1) return 1; // 100% duty
|
||||
return power * (pwm.max_duty - pwm.min_duty) + pwm.min_duty;
|
||||
}
|
||||
|
||||
|
||||
static uint16_t _compute_period(float duty) {return TIMER_PWM.PER * duty;}
|
||||
|
||||
|
||||
static void _update_pwm() {
|
||||
if (pwm.initialized)
|
||||
_update_clock(_compute_period(_compute_duty(pwm.power)));
|
||||
}
|
||||
|
||||
|
||||
static void _update_freq() {
|
||||
// Set clock period and optimal prescaler value
|
||||
float prescale = (F_CPU >> 16) / pwm.freq;
|
||||
|
||||
if (prescale <= 1) {
|
||||
TIMER_PWM.PER = F_CPU / pwm.freq;
|
||||
TIMER_PWM.CTRLA = TC_CLKSEL_DIV1_gc;
|
||||
|
||||
} else if (prescale <= 2) {
|
||||
TIMER_PWM.PER = F_CPU / 2 / pwm.freq;
|
||||
TIMER_PWM.CTRLA = TC_CLKSEL_DIV2_gc;
|
||||
|
||||
} else if (prescale <= 4) {
|
||||
TIMER_PWM.PER = F_CPU / 4 / pwm.freq;
|
||||
TIMER_PWM.CTRLA = TC_CLKSEL_DIV4_gc;
|
||||
|
||||
} else if (prescale <= 8) {
|
||||
TIMER_PWM.PER = F_CPU / 8 / pwm.freq;
|
||||
TIMER_PWM.CTRLA = TC_CLKSEL_DIV8_gc;
|
||||
|
||||
} else if (prescale <= 64) {
|
||||
TIMER_PWM.PER = F_CPU / 64 / pwm.freq;
|
||||
TIMER_PWM.CTRLA = TC_CLKSEL_DIV64_gc;
|
||||
|
||||
} else TIMER_PWM.CTRLA = 0;
|
||||
|
||||
_update_pwm();
|
||||
}
|
||||
|
||||
|
||||
void pwm_init() {
|
||||
pwm.initialized = true;
|
||||
pwm.power = 0;
|
||||
|
||||
// Configure IO
|
||||
_set_dir(true);
|
||||
_set_enable(false);
|
||||
_update_freq();
|
||||
|
||||
// PWM output
|
||||
OUTCLR_PIN(PWM_PIN);
|
||||
DIRSET_PIN(PWM_PIN);
|
||||
}
|
||||
|
||||
|
||||
float pwm_get() {return pwm.power;}
|
||||
|
||||
|
||||
void pwm_deinit(deinit_cb_t cb) {
|
||||
pwm.initialized = false;
|
||||
|
||||
_set_enable(false);
|
||||
|
||||
// Float PWM output pin
|
||||
DIRCLR_PIN(PWM_PIN);
|
||||
|
||||
// Disable clock
|
||||
TIMER_PWM.CTRLA = 0;
|
||||
|
||||
cb();
|
||||
}
|
||||
|
||||
|
||||
power_update_t pwm_get_update(float power) {
|
||||
power_update_t update = {
|
||||
0 <= power ? POWER_FORWARD : POWER_REVERSE,
|
||||
power,
|
||||
_compute_period(_compute_duty(power))
|
||||
};
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
|
||||
// Called from hi-priority stepper interrupt, must be very fast
|
||||
void pwm_update(const power_update_t &update) {
|
||||
if (!pwm.initialized || update.state == POWER_IGNORE) return;
|
||||
_update_clock(update.period);
|
||||
if (update.period) _set_dir(update.state == POWER_FORWARD);
|
||||
pwm.power = update.power;
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
float get_pwm_min_duty() {return pwm.min_duty * 100;}
|
||||
|
||||
|
||||
void set_pwm_min_duty(float value) {
|
||||
pwm.min_duty = value * 0.01;
|
||||
_update_pwm();
|
||||
}
|
||||
|
||||
|
||||
float get_pwm_max_duty() {return pwm.max_duty * 100;}
|
||||
|
||||
|
||||
void set_pwm_max_duty(float value) {
|
||||
pwm.max_duty = value * 0.01;
|
||||
_update_pwm();
|
||||
}
|
||||
|
||||
|
||||
float get_pwm_duty() {return _compute_duty(pwm.power);}
|
||||
float get_pwm_freq() {return pwm.freq;}
|
||||
|
||||
|
||||
void set_pwm_freq(float value) {
|
||||
if (value < 8) value = 8;
|
||||
if (320000 < value) value = 320000;
|
||||
pwm.freq = value;
|
||||
_update_freq();
|
||||
}
|
||||
|
||||
|
||||
bool get_pwm_invert() {return PINCTRL_PIN(PWM_PIN) & PORT_INVEN_bm;}
|
||||
|
||||
|
||||
void set_pwm_invert(bool invert) {
|
||||
if (invert) PINCTRL_PIN(PWM_PIN) |= PORT_INVEN_bm;
|
||||
else PINCTRL_PIN(PWM_PIN) &= ~PORT_INVEN_bm;
|
||||
}
|
||||
37
src/avr/src/pwm.h
Normal file
37
src/avr/src/pwm.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spindle.h"
|
||||
|
||||
|
||||
void pwm_init();
|
||||
float pwm_get();
|
||||
void pwm_deinit(deinit_cb_t cb);
|
||||
power_update_t pwm_get_update(float power);
|
||||
void pwm_update(const power_update_t &update);
|
||||
54
src/avr/src/report.c
Normal file
54
src/avr/src/report.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "report.h"
|
||||
#include "config.h"
|
||||
#include "usart.h"
|
||||
#include "rtc.h"
|
||||
#include "vars.h"
|
||||
|
||||
|
||||
static bool _full = false;
|
||||
static uint32_t _last = 0;
|
||||
|
||||
|
||||
void report_request_full() {_full = true;}
|
||||
|
||||
|
||||
void report_callback() {
|
||||
// Wait until output buffer is empty
|
||||
if (!usart_tx_empty()) return;
|
||||
|
||||
// Limit frequency
|
||||
uint32_t now = rtc_get_time();
|
||||
if (now - _last < REPORT_RATE) return;
|
||||
_last = now;
|
||||
|
||||
// Report vars
|
||||
vars_report(_full);
|
||||
_full = false;
|
||||
}
|
||||
34
src/avr/src/report.h
Normal file
34
src/avr/src/report.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "status.h"
|
||||
|
||||
void report_request_full();
|
||||
void report_callback();
|
||||
225
src/avr/src/ringbuf.def
Normal file
225
src/avr/src/ringbuf.def
Normal file
@@ -0,0 +1,225 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
/* This file defines an X-Macro ring buffer. It can be used like this:
|
||||
*
|
||||
* #define RING_BUF_NAME tx_buf
|
||||
* #define RING_BUF_SIZE 256
|
||||
* #include "ringbuf.def"
|
||||
*
|
||||
* This will define the following functions:
|
||||
*
|
||||
* void <name>_init();
|
||||
* int <name>_empty();
|
||||
* int <name>_full();
|
||||
* <type> <name>_peek();
|
||||
* void <name>_pop();
|
||||
* void <name>_push(<type> data);
|
||||
*
|
||||
* Where <name> is defined by RING_BUF_NAME and <type> by RING_BUF_TYPE.
|
||||
* RING_BUF_SIZE defines the length of the ring buffer and must be a power of 2.
|
||||
*
|
||||
* The data type and index type both default to uint8_t but can be changed by
|
||||
* defining RING_BUF_TYPE and RING_BUF_INDEX_TYPE respectively.
|
||||
*
|
||||
* By default these functions are declared static inline but this can be changed
|
||||
* by defining RING_BUF_FUNC.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <util/atomic.h>
|
||||
|
||||
|
||||
#ifndef RING_BUF_NAME
|
||||
#error Must define RING_BUF_NAME
|
||||
#endif
|
||||
|
||||
#ifndef RING_BUF_SIZE
|
||||
#error Must define RING_BUF_SIZE
|
||||
#endif
|
||||
|
||||
#ifndef RING_BUF_TYPE
|
||||
#define RING_BUF_TYPE uint8_t
|
||||
#endif
|
||||
|
||||
#ifndef RING_BUF_INDEX_TYPE
|
||||
#define RING_BUF_INDEX_TYPE volatile uint8_t
|
||||
#endif
|
||||
|
||||
#ifndef RING_BUF_FUNC
|
||||
#define RING_BUF_FUNC static inline
|
||||
#endif
|
||||
|
||||
#define RING_BUF_MASK (RING_BUF_SIZE - 1)
|
||||
#if (RING_BUF_SIZE & RING_BUF_MASK)
|
||||
#error RING_BUF_SIZE is not a power of 2
|
||||
#endif
|
||||
|
||||
#ifndef CONCAT
|
||||
#define _CONCAT(prefix, name) prefix##name
|
||||
#define CONCAT(prefix, name) _CONCAT(prefix, name)
|
||||
#endif
|
||||
|
||||
#define RING_BUF_STRUCT CONCAT(RING_BUF_NAME, _ring_buf_t)
|
||||
#define RING_BUF RING_BUF_NAME
|
||||
|
||||
typedef struct {
|
||||
RING_BUF_TYPE buf[RING_BUF_SIZE];
|
||||
RING_BUF_INDEX_TYPE head;
|
||||
RING_BUF_INDEX_TYPE tail;
|
||||
} RING_BUF_STRUCT;
|
||||
|
||||
static RING_BUF_STRUCT RING_BUF;
|
||||
|
||||
|
||||
RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _init)() {
|
||||
RING_BUF.head = RING_BUF.tail = 0;
|
||||
}
|
||||
|
||||
|
||||
#define RING_BUF_INC(x) (((x) + 1) & RING_BUF_MASK)
|
||||
#define RING_BUF_DEC(x) ((x) ? x - 1 : (RING_BUF_SIZE - 1))
|
||||
|
||||
|
||||
#ifdef RING_BUF_ATOMIC_COPY
|
||||
|
||||
#undef RING_BUF_ATOMIC_COPY
|
||||
#define RING_BUF_ATOMIC_COPY(TO, FROM) \
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) TO = FROM
|
||||
|
||||
|
||||
#define RING_BUF_ATOMIC_READ_INDEX(INDEX) \
|
||||
RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _read_##INDEX)() { \
|
||||
RING_BUF_INDEX_TYPE index; \
|
||||
RING_BUF_ATOMIC_COPY(index, RING_BUF.INDEX); \
|
||||
return index; \
|
||||
}
|
||||
|
||||
|
||||
#define RING_BUF_ATOMIC_WRITE_INDEX(INDEX) \
|
||||
RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _write_##INDEX) \
|
||||
(RING_BUF_INDEX_TYPE value) { \
|
||||
RING_BUF_ATOMIC_COPY(RING_BUF.INDEX, value); \
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_ATOMIC_READ_INDEX(head);
|
||||
RING_BUF_ATOMIC_READ_INDEX(tail);
|
||||
RING_BUF_ATOMIC_WRITE_INDEX(head);
|
||||
RING_BUF_ATOMIC_WRITE_INDEX(tail);
|
||||
|
||||
#define RING_BUF_READ_INDEX(INDEX) CONCAT(RING_BUF_NAME, _read_##INDEX)()
|
||||
#define RING_BUF_WRITE_INDEX(INDEX, VALUE) \
|
||||
CONCAT(RING_BUF_NAME, _write_##INDEX)(VALUE)
|
||||
|
||||
|
||||
#else // RING_BUF_ATOMIC_COPY
|
||||
#define RING_BUF_READ_INDEX(INDEX) RING_BUF.INDEX
|
||||
#define RING_BUF_WRITE_INDEX(INDEX, VALUE) RING_BUF.INDEX = VALUE
|
||||
#endif // RING_BUF_ATOMIC_COPY
|
||||
|
||||
|
||||
RING_BUF_FUNC bool CONCAT(RING_BUF_NAME, _empty)() {
|
||||
return RING_BUF_READ_INDEX(head) == RING_BUF_READ_INDEX(tail);
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC bool CONCAT(RING_BUF_NAME, _full)() {
|
||||
return RING_BUF_READ_INDEX(head) == RING_BUF_INC(RING_BUF_READ_INDEX(tail));
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _fill)() {
|
||||
return
|
||||
(RING_BUF_READ_INDEX(tail) - RING_BUF_READ_INDEX(head)) & RING_BUF_MASK;
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _space)() {
|
||||
return (RING_BUF_SIZE - 1) - CONCAT(RING_BUF_NAME, _fill)();
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _peek)() {
|
||||
return RING_BUF.buf[RING_BUF_READ_INDEX(head)];
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC RING_BUF_TYPE *CONCAT(RING_BUF_NAME, _front)() {
|
||||
return &RING_BUF.buf[RING_BUF_READ_INDEX(head)];
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC RING_BUF_TYPE *CONCAT(RING_BUF_NAME, _back)() {
|
||||
return &RING_BUF.buf[RING_BUF_DEC(RING_BUF_READ_INDEX(tail))];
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _get)(int offset) {
|
||||
return RING_BUF.buf[(RING_BUF_READ_INDEX(head) + offset) & RING_BUF_MASK];
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _pop)() {
|
||||
RING_BUF_WRITE_INDEX(head, RING_BUF_INC(RING_BUF_READ_INDEX(head)));
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _next)() {
|
||||
RING_BUF_TYPE x = CONCAT(RING_BUF_NAME, _peek)();
|
||||
CONCAT(RING_BUF_NAME, _pop)();
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _push)(RING_BUF_TYPE data) {
|
||||
RING_BUF.buf[RING_BUF_READ_INDEX(tail)] = data;
|
||||
RING_BUF_WRITE_INDEX(tail, RING_BUF_INC(RING_BUF_READ_INDEX(tail)));
|
||||
}
|
||||
|
||||
|
||||
#undef RING_BUF
|
||||
#undef RING_BUF_STRUCT
|
||||
#undef RING_BUF_INC
|
||||
#undef RING_BUF_MASK
|
||||
|
||||
#undef RING_BUF_NAME
|
||||
#undef RING_BUF_SIZE
|
||||
#undef RING_BUF_TYPE
|
||||
#undef RING_BUF_INDEX_TYPE
|
||||
#undef RING_BUF_FUNC
|
||||
|
||||
#undef RING_BUF_READ_INDEX
|
||||
#undef RING_BUF_WRITE_INDEX
|
||||
|
||||
#ifdef RING_BUF_ATOMIC_COPY
|
||||
#undef RING_BUF_ATOMIC_COPY
|
||||
#undef RING_BUF_ATOMIC_READ_INDEX
|
||||
#undef RING_BUF_ATOMIC_WRITE_INDEX
|
||||
#endif // RING_BUF_ATOMIC_COPY
|
||||
78
src/avr/src/rtc.c
Normal file
78
src/avr/src/rtc.c
Normal file
@@ -0,0 +1,78 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "rtc.h"
|
||||
|
||||
#include "switch.h"
|
||||
#include "analog.h"
|
||||
#include "motor.h"
|
||||
#include "lcd.h"
|
||||
#include "vfd_spindle.h"
|
||||
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/wdt.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
static uint32_t ticks;
|
||||
|
||||
|
||||
ISR(RTC_OVF_vect) {
|
||||
ticks++;
|
||||
|
||||
lcd_rtc_callback();
|
||||
switch_rtc_callback();
|
||||
analog_rtc_callback();
|
||||
vfd_spindle_rtc_callback();
|
||||
if (!(ticks & 255)) motor_rtc_callback();
|
||||
wdt_reset();
|
||||
}
|
||||
|
||||
|
||||
/// Initialize and start the clock
|
||||
/// This routine follows the code in app note AVR1314.
|
||||
void rtc_init() {
|
||||
ticks = 0;
|
||||
|
||||
OSC.CTRL |= OSC_RC32KEN_bm; // enable internal 32kHz.
|
||||
while (!(OSC.STATUS & OSC_RC32KRDY_bm)); // 32kHz osc stabilize
|
||||
while (RTC.STATUS & RTC_SYNCBUSY_bm); // wait RTC not busy
|
||||
|
||||
CLK.RTCCTRL = CLK_RTCSRC_RCOSC32_gc | CLK_RTCEN_bm; // 32kHz clock as RTC src
|
||||
while (RTC.STATUS & RTC_SYNCBUSY_bm); // wait RTC not busy
|
||||
|
||||
// the following must be in this order or it doesn't work
|
||||
RTC.PER = 33; // overflow period ~1ms
|
||||
RTC.INTCTRL = RTC_OVFINTLVL_LO_gc; // overflow LO interrupt
|
||||
RTC.CTRL = RTC_PRESCALER_DIV1_gc; // no prescale
|
||||
}
|
||||
|
||||
|
||||
uint32_t rtc_get_time() {return ticks;}
|
||||
bool rtc_expired(uint32_t t) {return 0 <= (int32_t)(ticks - t);}
|
||||
37
src/avr/src/rtc.h
Normal file
37
src/avr/src/rtc.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void rtc_init();
|
||||
uint32_t rtc_get_time();
|
||||
int32_t rtc_diff(uint32_t t);
|
||||
bool rtc_expired(uint32_t t);
|
||||
139
src/avr/src/seek.c
Normal file
139
src/avr/src/seek.c
Normal file
@@ -0,0 +1,139 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "seek.h"
|
||||
|
||||
#include "command.h"
|
||||
#include "switch.h"
|
||||
#include "estop.h"
|
||||
#include "util.h"
|
||||
#include "state.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "motor.h"
|
||||
|
||||
enum {
|
||||
SEEK_ACTIVE = 1 << 0,
|
||||
SEEK_ERROR = 1 << 1,
|
||||
SEEK_FOUND = 1 << 2,
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool active;
|
||||
switch_id_t sw;
|
||||
uint8_t flags;
|
||||
} seek_t;
|
||||
|
||||
|
||||
static seek_t seek = {false, SW_INVALID, 0};
|
||||
|
||||
|
||||
switch_id_t seek_get_switch() {return seek.active ? seek.sw : SW_INVALID;}
|
||||
|
||||
bool seek_active() {
|
||||
return seek.active;
|
||||
}
|
||||
|
||||
bool seek_switch_found() {
|
||||
if (!seek.active) return false;
|
||||
|
||||
bool inactive = !(seek.flags & SEEK_ACTIVE);
|
||||
|
||||
if (switch_is_active(seek.sw) ^ inactive) {
|
||||
seek.flags |= SEEK_FOUND;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void seek_end() {
|
||||
switch_id_t seekMode;
|
||||
|
||||
seekMode = seek_get_switch();
|
||||
|
||||
if (!seek.active) return;
|
||||
|
||||
if (!(SEEK_FOUND & seek.flags) && (SEEK_ERROR & seek.flags))
|
||||
estop_trigger(STAT_SEEK_NOT_FOUND);
|
||||
|
||||
if((seekMode == SW_STALL_0) || (seekMode == SW_STALL_1) || (seekMode ==SW_STALL_2) || (seekMode == SW_STALL_3))
|
||||
disable_stall_microstep();
|
||||
|
||||
seek.active = false;
|
||||
}
|
||||
|
||||
|
||||
void seek_cancel() {
|
||||
switch_id_t seekMode;
|
||||
|
||||
seekMode = seek_get_switch();
|
||||
|
||||
|
||||
if((seekMode == SW_STALL_0) || (seekMode == SW_STALL_1) || (seekMode ==SW_STALL_2) || (seekMode == SW_STALL_3))
|
||||
disable_stall_microstep();
|
||||
|
||||
seek.active = false;
|
||||
}
|
||||
|
||||
|
||||
// Command callbacks
|
||||
stat_t command_seek(char *cmd) {
|
||||
switch_id_t sw = (switch_id_t)decode_hex_nibble(cmd[1]);
|
||||
if (sw <= 0) return STAT_INVALID_ARGUMENTS; // Don't allow seek to ESTOP
|
||||
if (!switch_is_enabled(sw)) return STAT_SEEK_NOT_ENABLED;
|
||||
|
||||
uint8_t flags = decode_hex_nibble(cmd[2]);
|
||||
if (flags & 0xfc) return STAT_INVALID_ARGUMENTS;
|
||||
|
||||
//////////////
|
||||
if(sw == SW_STALL_0) enable_stall_microstep(0);
|
||||
if(sw == SW_STALL_1) enable_stall_microstep(1);
|
||||
if(sw == SW_STALL_2) enable_stall_microstep(2);
|
||||
if(sw == SW_STALL_3) enable_stall_microstep(3);
|
||||
|
||||
seek_t seek = {true, sw, flags};
|
||||
command_push(*cmd, &seek);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_seek_size() {return sizeof(seek_t);}
|
||||
void command_seek_exec(void *data) {
|
||||
//switch_id_t seekMode;
|
||||
|
||||
//seekMode = seek_get_switch();
|
||||
|
||||
//if((seekMode == SW_STALL_0) || (seekMode == SW_STALL_1) || (seekMode ==SW_STALL_2) || (seekMode == SW_STALL_3))
|
||||
// enable_stall_microstep();
|
||||
|
||||
seek = *(seek_t *)data;
|
||||
}
|
||||
39
src/avr/src/seek.h
Normal file
39
src/avr/src/seek.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "switch.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
switch_id_t seek_get_switch();
|
||||
bool seek_switch_found();
|
||||
void seek_end();
|
||||
void seek_cancel();
|
||||
bool seek_active();
|
||||
334
src/avr/src/spindle.c
Normal file
334
src/avr/src/spindle.c
Normal file
@@ -0,0 +1,334 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "spindle.h"
|
||||
#include "pwm.h"
|
||||
#include "huanyang.h"
|
||||
#include "vfd_spindle.h"
|
||||
#include "stepper.h"
|
||||
#include "config.h"
|
||||
#include "command.h"
|
||||
#include "exec.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
float dist;
|
||||
float speed;
|
||||
} sync_speed_t;
|
||||
|
||||
|
||||
static struct {
|
||||
spindle_type_t type;
|
||||
float override;
|
||||
sync_speed_t sync_speed;
|
||||
float speed;
|
||||
bool reversed;
|
||||
float min_rpm;
|
||||
float max_rpm;
|
||||
float inv_max_rpm;
|
||||
|
||||
bool dynamic_power;
|
||||
float inv_feed;
|
||||
|
||||
spindle_type_t next_type;
|
||||
|
||||
} spindle = {
|
||||
.type = SPINDLE_TYPE_DISABLED,
|
||||
.override = 1,
|
||||
.sync_speed = {-1, 0}
|
||||
};
|
||||
|
||||
|
||||
static float _get_power() {
|
||||
switch (spindle.type) {
|
||||
case SPINDLE_TYPE_DISABLED: return 0;
|
||||
case SPINDLE_TYPE_PWM: return pwm_get();
|
||||
case SPINDLE_TYPE_HUANYANG: return huanyang_get();
|
||||
default: return vfd_spindle_get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static float _speed_to_power(float speed) {
|
||||
bool negative = speed < 0;
|
||||
float power = fabs(speed * spindle.override);
|
||||
|
||||
if (power < spindle.min_rpm) power = 0;
|
||||
else if (spindle.max_rpm <= power) power = 1;
|
||||
else power *= spindle.inv_max_rpm;
|
||||
|
||||
return (negative ^ spindle.reversed) ? -power : power;
|
||||
}
|
||||
|
||||
|
||||
static void _set_speed(float speed) {
|
||||
spindle.speed = speed;
|
||||
|
||||
float power = _speed_to_power(speed);
|
||||
|
||||
switch (spindle.type) {
|
||||
case SPINDLE_TYPE_DISABLED: break;
|
||||
|
||||
case SPINDLE_TYPE_PWM: {
|
||||
// PWM speed updates must be synchronized with stepper movement
|
||||
spindle.sync_speed.dist = 0;
|
||||
spindle.sync_speed.speed = speed;
|
||||
break;
|
||||
}
|
||||
|
||||
case SPINDLE_TYPE_HUANYANG: huanyang_set(power); break;
|
||||
default: vfd_spindle_set(power); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _deinit_cb() {
|
||||
spindle.type = spindle.next_type;
|
||||
spindle.next_type = SPINDLE_TYPE_DISABLED;
|
||||
|
||||
switch (spindle.type) {
|
||||
case SPINDLE_TYPE_DISABLED: break;
|
||||
case SPINDLE_TYPE_PWM: pwm_init(); break;
|
||||
case SPINDLE_TYPE_HUANYANG: huanyang_init(); break;
|
||||
default: vfd_spindle_init(); break;
|
||||
}
|
||||
|
||||
spindle_update_speed();
|
||||
}
|
||||
|
||||
|
||||
static void _set_type(spindle_type_t type) {
|
||||
if (type == spindle.type) return;
|
||||
|
||||
spindle_type_t old_type = spindle.type;
|
||||
spindle.next_type = type;
|
||||
spindle.type = SPINDLE_TYPE_DISABLED;
|
||||
|
||||
switch (old_type) {
|
||||
case SPINDLE_TYPE_DISABLED: _deinit_cb(); break;
|
||||
case SPINDLE_TYPE_PWM: pwm_deinit(_deinit_cb); break;
|
||||
case SPINDLE_TYPE_HUANYANG: huanyang_deinit(_deinit_cb); break;
|
||||
default: vfd_spindle_deinit(_deinit_cb); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
spindle_type_t spindle_get_type() {return spindle.type;}
|
||||
|
||||
|
||||
static power_update_t _get_power_update() {
|
||||
float power = _speed_to_power(spindle.speed);
|
||||
|
||||
// Handle dynamic power
|
||||
if (spindle.dynamic_power && spindle.inv_feed) {
|
||||
float scale = spindle.inv_feed * exec_get_velocity();
|
||||
if (scale < 1) power *= scale;
|
||||
}
|
||||
|
||||
return pwm_get_update(power);
|
||||
}
|
||||
|
||||
|
||||
void spindle_load_power_updates(power_update_t updates[], float minD,
|
||||
float maxD) {
|
||||
float stepD = (maxD - minD) * (1.0 / POWER_MAX_UPDATES);
|
||||
float d = minD + 1e-3; // Starting distance
|
||||
|
||||
for (unsigned i = 0; i < POWER_MAX_UPDATES; i++) {
|
||||
bool changed = false;
|
||||
d += stepD; // Ending distance for this power step
|
||||
|
||||
while (true) {
|
||||
// Load new sync speed if needed and available
|
||||
if (spindle.sync_speed.dist < 0 && command_peek() == COMMAND_sync_speed)
|
||||
spindle.sync_speed = *(sync_speed_t *)(command_next() + 1);
|
||||
|
||||
// Exit if we don't have a speed or it's not ready to be set
|
||||
if (spindle.sync_speed.dist == -1 || d < spindle.sync_speed.dist) break;
|
||||
|
||||
// Load sync speed
|
||||
spindle.sync_speed.dist = -1; // Mark done
|
||||
spindle.speed = spindle.sync_speed.speed;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (spindle.type == SPINDLE_TYPE_PWM) updates[i] = _get_power_update();
|
||||
else {
|
||||
updates[i].state = POWER_IGNORE;
|
||||
if (changed) spindle_update_speed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called from hi-priority stepper interrupt
|
||||
void spindle_update(const power_update_t &update) {pwm_update(update);}
|
||||
void spindle_update_speed() {_set_speed(spindle.speed);}
|
||||
|
||||
|
||||
// Called from lo-priority stepper interrupt
|
||||
void spindle_idle() {
|
||||
if (spindle.sync_speed.dist != -1) {
|
||||
spindle.sync_speed.dist = -1; // Mark done
|
||||
spindle.speed = spindle.sync_speed.speed;
|
||||
|
||||
if (spindle.type == SPINDLE_TYPE_PWM) spindle_update(_get_power_update());
|
||||
else spindle_update_speed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void spindle_stop() {_set_speed(0);} // Only called when steppers have halted
|
||||
void spindle_estop() {_set_type(SPINDLE_TYPE_DISABLED);}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
uint8_t get_tool_type() {return spindle.type;}
|
||||
void set_tool_type(uint8_t value) {_set_type((spindle_type_t)value);}
|
||||
bool get_tool_reversed() {return spindle.reversed;}
|
||||
|
||||
|
||||
void set_tool_reversed(bool reversed) {
|
||||
if (spindle.reversed == reversed) return;
|
||||
spindle.reversed = reversed;
|
||||
spindle_update_speed();
|
||||
}
|
||||
|
||||
|
||||
float get_speed() {return _get_power() * spindle.max_rpm;}
|
||||
float get_max_spin() {return spindle.max_rpm;}
|
||||
|
||||
|
||||
void set_max_spin(float value) {
|
||||
if (spindle.max_rpm != value) {
|
||||
spindle.max_rpm = value;
|
||||
spindle.inv_max_rpm = 1 / value;
|
||||
spindle_update_speed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float get_min_spin() {return spindle.min_rpm;}
|
||||
|
||||
|
||||
void set_min_spin(float value) {
|
||||
if (spindle.min_rpm != value) {
|
||||
spindle.min_rpm = value;
|
||||
spindle_update_speed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t get_spindle_status() {
|
||||
switch (spindle.type) {
|
||||
case SPINDLE_TYPE_DISABLED: return 0;
|
||||
case SPINDLE_TYPE_PWM: return 0;
|
||||
case SPINDLE_TYPE_HUANYANG: return huanyang_get_status();
|
||||
default: return vfd_get_status();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t get_speed_override() {return spindle.override * 1000;}
|
||||
|
||||
|
||||
void set_speed_override(uint16_t value) {
|
||||
value *= 0.001;
|
||||
|
||||
if (spindle.override != value) {
|
||||
spindle.override = value;
|
||||
spindle_update_speed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool get_dynamic_power() {return spindle.dynamic_power;}
|
||||
|
||||
|
||||
void set_dynamic_power(bool enable) {
|
||||
if (spindle.dynamic_power != enable) {
|
||||
spindle.dynamic_power = enable;
|
||||
spindle_update_speed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float get_inverse_feed() {return spindle.inv_feed;}
|
||||
|
||||
|
||||
void set_inverse_feed(float iF) {
|
||||
if (spindle.inv_feed != iF) {
|
||||
spindle.inv_feed = iF;
|
||||
spindle_update_speed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Command callbacks
|
||||
stat_t command_sync_speed(char *cmd) {
|
||||
sync_speed_t s;
|
||||
|
||||
cmd++; // Skip command code
|
||||
|
||||
// Get distance and speed
|
||||
if (!decode_float(&cmd, &s.dist) || s.dist < 0) return STAT_BAD_FLOAT;
|
||||
if (!decode_float(&cmd, &s.speed)) return STAT_BAD_FLOAT;
|
||||
|
||||
// Queue
|
||||
command_push(COMMAND_sync_speed, &s);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_sync_speed_size() {return sizeof(sync_speed_t);}
|
||||
|
||||
|
||||
void command_sync_speed_exec(void *data) {
|
||||
_set_speed(((sync_speed_t *)data)->speed);
|
||||
}
|
||||
|
||||
|
||||
stat_t command_speed(char *cmd) {
|
||||
cmd++; // Skip command code
|
||||
|
||||
// Get speed
|
||||
float speed;
|
||||
if (!decode_float(&cmd, &speed)) return STAT_BAD_FLOAT;
|
||||
|
||||
// Queue
|
||||
command_push(COMMAND_speed, &speed);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_speed_size() {return sizeof(float);}
|
||||
void command_speed_exec(void *data) {_set_speed(*(float *)data);}
|
||||
74
src/avr/src/spindle.h
Normal file
74
src/avr/src/spindle.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
POWER_IGNORE,
|
||||
POWER_FORWARD,
|
||||
POWER_REVERSE
|
||||
} power_state_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
power_state_t state;
|
||||
float power;
|
||||
uint16_t period; // Used by PWM
|
||||
} power_update_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
SPINDLE_TYPE_DISABLED,
|
||||
SPINDLE_TYPE_PWM,
|
||||
SPINDLE_TYPE_HUANYANG,
|
||||
SPINDLE_TYPE_CUSTOM,
|
||||
SPINDLE_TYPE_AC_TECH,
|
||||
SPINDLE_TYPE_NOWFOREVER,
|
||||
SPINDLE_TYPE_DELTA_VFD015M21A,
|
||||
SPINDLE_TYPE_YL600,
|
||||
SPINDLE_TYPE_FR_D700,
|
||||
SPINDLE_TYPE_SUNFAR_E300,
|
||||
SPINDLE_TYPE_OMRON_MX2,
|
||||
SPINDLE_TYPE_V70,
|
||||
} spindle_type_t;
|
||||
|
||||
|
||||
typedef void (*deinit_cb_t)();
|
||||
|
||||
|
||||
spindle_type_t spindle_get_type();
|
||||
void spindle_stop();
|
||||
void spindle_estop();
|
||||
void spindle_load_power_updates(power_update_t updates[], float minD,
|
||||
float maxD);
|
||||
void spindle_update(const power_update_t &update);
|
||||
void spindle_update_speed();
|
||||
void spindle_idle();
|
||||
259
src/avr/src/state.c
Normal file
259
src/avr/src/state.c
Normal file
@@ -0,0 +1,259 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "state.h"
|
||||
|
||||
#include "exec.h"
|
||||
#include "command.h"
|
||||
#include "stepper.h"
|
||||
#include "spindle.h"
|
||||
#include "outputs.h"
|
||||
#include "jog.h"
|
||||
#include "estop.h"
|
||||
#include "seek.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
static struct {
|
||||
bool flushing;
|
||||
bool resuming;
|
||||
bool stop_requested;
|
||||
bool pause_requested;
|
||||
bool unpause_requested;
|
||||
|
||||
state_t state;
|
||||
uint16_t state_count;
|
||||
hold_reason_t hold_reason;
|
||||
} s = {
|
||||
.flushing = true, // Start out flushing
|
||||
};
|
||||
|
||||
|
||||
PGM_P state_get_pgmstr(state_t state) {
|
||||
switch (state) {
|
||||
case STATE_READY: return PSTR("READY");
|
||||
case STATE_ESTOPPED: return PSTR("ESTOPPED");
|
||||
case STATE_RUNNING: return PSTR("RUNNING");
|
||||
case STATE_JOGGING: return PSTR("JOGGING");
|
||||
case STATE_STOPPING: return PSTR("STOPPING");
|
||||
case STATE_HOLDING: return PSTR("HOLDING");
|
||||
}
|
||||
|
||||
return PSTR("INVALID");
|
||||
}
|
||||
|
||||
|
||||
PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason) {
|
||||
switch (reason) {
|
||||
case HOLD_REASON_USER_PAUSE: return PSTR("User pause");
|
||||
case HOLD_REASON_USER_STOP: return PSTR("User stop");
|
||||
case HOLD_REASON_PROGRAM_PAUSE: return PSTR("Program pause");
|
||||
case HOLD_REASON_OPTIONAL_PAUSE: return PSTR("Optional pause");
|
||||
case HOLD_REASON_SWITCH_FOUND: return PSTR("Switch found");
|
||||
}
|
||||
|
||||
return PSTR("INVALID");
|
||||
}
|
||||
|
||||
|
||||
state_t state_get() {return s.state;}
|
||||
|
||||
|
||||
static void _set_state(state_t state) {
|
||||
if (s.state == state) return; // No change
|
||||
if (s.state == STATE_ESTOPPED) return; // Can't leave EStop state
|
||||
s.state = state;
|
||||
s.state_count++;
|
||||
}
|
||||
|
||||
|
||||
static void _set_hold_reason(hold_reason_t reason) {s.hold_reason = reason;}
|
||||
bool state_is_flushing() {return s.flushing && !s.resuming;}
|
||||
bool state_is_resuming() {return s.resuming;}
|
||||
|
||||
|
||||
static bool _is_idle() {
|
||||
return (state_get() == STATE_READY || state_get() == STATE_HOLDING) &&
|
||||
!st_is_busy();
|
||||
}
|
||||
|
||||
|
||||
void state_seek_hold() {
|
||||
if (state_get() == STATE_RUNNING) {
|
||||
_set_hold_reason(HOLD_REASON_SWITCH_FOUND);
|
||||
_set_state(STATE_STOPPING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _stop() {
|
||||
_set_hold_reason(HOLD_REASON_USER_STOP);
|
||||
|
||||
switch (state_get()) {
|
||||
case STATE_RUNNING:
|
||||
_set_state(STATE_STOPPING);
|
||||
break;
|
||||
|
||||
case STATE_JOGGING:
|
||||
case STATE_READY:
|
||||
case STATE_HOLDING:
|
||||
jog_stop();
|
||||
spindle_stop();
|
||||
outputs_stop();
|
||||
seek_cancel();
|
||||
break;
|
||||
|
||||
case STATE_STOPPING:
|
||||
case STATE_ESTOPPED:
|
||||
break; // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void state_holding() {
|
||||
_set_state(STATE_HOLDING);
|
||||
if (s.hold_reason == HOLD_REASON_USER_STOP) _stop();
|
||||
}
|
||||
|
||||
|
||||
void state_running() {
|
||||
if (state_get() == STATE_READY) _set_state(STATE_RUNNING);
|
||||
}
|
||||
|
||||
|
||||
void state_jogging() {
|
||||
if (state_get() == STATE_READY || state_get() == STATE_HOLDING)
|
||||
_set_state(STATE_JOGGING);
|
||||
}
|
||||
|
||||
|
||||
void state_idle() {
|
||||
if (state_get() == STATE_RUNNING || state_get() == STATE_JOGGING)
|
||||
_set_state(STATE_READY);
|
||||
}
|
||||
|
||||
|
||||
void state_estop() {_set_state(STATE_ESTOPPED);}
|
||||
|
||||
|
||||
void state_callback() {
|
||||
if (estop_triggered()) return;
|
||||
|
||||
// Pause
|
||||
if (s.pause_requested) {
|
||||
if (state_get() == STATE_RUNNING) {
|
||||
if (s.pause_requested) _set_hold_reason(HOLD_REASON_USER_PAUSE);
|
||||
_set_state(STATE_STOPPING);
|
||||
}
|
||||
|
||||
s.pause_requested = false;
|
||||
}
|
||||
|
||||
// Stop
|
||||
if (s.stop_requested) {
|
||||
_stop();
|
||||
s.stop_requested = false;
|
||||
}
|
||||
|
||||
// Only flush queue when idle (READY or HOLDING)
|
||||
if (s.flushing && _is_idle()) {
|
||||
command_flush_queue();
|
||||
|
||||
// Resume
|
||||
if (s.resuming) s.flushing = s.resuming = false;
|
||||
}
|
||||
|
||||
// Unpause
|
||||
if (s.unpause_requested && !s.flushing && state_get() != STATE_STOPPING) {
|
||||
s.unpause_requested = false;
|
||||
|
||||
if (state_get() == STATE_HOLDING) {
|
||||
// Check if any moves are buffered
|
||||
if (command_get_count()) _set_state(STATE_RUNNING);
|
||||
else _set_state(STATE_READY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
PGM_P get_state() {return state_get_pgmstr(state_get());}
|
||||
uint16_t get_state_count() {return s.state_count;}
|
||||
PGM_P get_hold_reason() {return state_get_hold_reason_pgmstr(s.hold_reason);}
|
||||
|
||||
|
||||
// Command callbacks
|
||||
stat_t command_pause(char *cmd) {
|
||||
pause_t type = (pause_t)(cmd[1] - '0');
|
||||
|
||||
if (type == PAUSE_USER) s.pause_requested = true;
|
||||
else command_push(cmd[0], &type);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_pause_size() {return sizeof(pause_t);}
|
||||
|
||||
|
||||
void command_pause_exec(void *data) {
|
||||
switch (*(pause_t *)data) {
|
||||
case PAUSE_PROGRAM_OPTIONAL:
|
||||
_set_hold_reason(HOLD_REASON_OPTIONAL_PAUSE);
|
||||
break;
|
||||
|
||||
case PAUSE_PROGRAM: _set_hold_reason(HOLD_REASON_PROGRAM_PAUSE); break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
state_holding();
|
||||
}
|
||||
|
||||
|
||||
stat_t command_stop(char *cmd) {
|
||||
s.stop_requested = true;
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t command_unpause(char *cmd) {
|
||||
s.unpause_requested = true;
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t command_resume(char *cmd) {
|
||||
if (s.flushing) s.resuming = true;
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t command_flush(char *cmd) {
|
||||
s.flushing = true;
|
||||
return STAT_OK;
|
||||
}
|
||||
76
src/avr/src/state.h
Normal file
76
src/avr/src/state.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pgmspace.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
STATE_READY,
|
||||
STATE_ESTOPPED,
|
||||
STATE_RUNNING,
|
||||
STATE_JOGGING,
|
||||
STATE_STOPPING,
|
||||
STATE_HOLDING,
|
||||
} state_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
HOLD_REASON_USER_PAUSE,
|
||||
HOLD_REASON_USER_STOP,
|
||||
HOLD_REASON_PROGRAM_PAUSE,
|
||||
HOLD_REASON_OPTIONAL_PAUSE,
|
||||
HOLD_REASON_SWITCH_FOUND,
|
||||
} hold_reason_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
PAUSE_USER,
|
||||
PAUSE_PROGRAM,
|
||||
PAUSE_PROGRAM_OPTIONAL,
|
||||
} pause_t;
|
||||
|
||||
|
||||
PGM_P state_get_pgmstr(state_t state);
|
||||
PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason);
|
||||
|
||||
state_t state_get();
|
||||
|
||||
bool state_is_flushing();
|
||||
bool state_is_resuming();
|
||||
|
||||
void state_seek_hold();
|
||||
void state_holding();
|
||||
void state_running();
|
||||
void state_jogging();
|
||||
void state_idle();
|
||||
void state_estop();
|
||||
|
||||
void state_callback();
|
||||
96
src/avr/src/status.c
Normal file
96
src/avr/src/status.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "status.h"
|
||||
#include "estop.h"
|
||||
#include "usart.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
|
||||
#define STAT_MSG(NAME, TEXT) static const char stat_##NAME[] PROGMEM = TEXT;
|
||||
#include "messages.def"
|
||||
#undef STAT_MSG
|
||||
|
||||
|
||||
static const char *const stat_msg[] PROGMEM = {
|
||||
#define STAT_MSG(NAME, TEXT) stat_##NAME,
|
||||
#include "messages.def"
|
||||
#undef STAT_MSG
|
||||
};
|
||||
|
||||
|
||||
const char *status_to_pgmstr(stat_t code) {
|
||||
return (const char *)pgm_read_ptr(&stat_msg[code]);
|
||||
}
|
||||
|
||||
|
||||
const char *status_level_pgmstr(status_level_t level) {
|
||||
switch (level) {
|
||||
case STAT_LEVEL_INFO: return PSTR("info");
|
||||
case STAT_LEVEL_DEBUG: return PSTR("debug");
|
||||
case STAT_LEVEL_WARNING: return PSTR("warning");
|
||||
default: return PSTR("error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stat_t status_message_P(const char *location, status_level_t level,
|
||||
stat_t code, const char *msg, ...) {
|
||||
va_list args;
|
||||
|
||||
// Type
|
||||
printf_P(PSTR("\n{\"level\":\"%" PRPSTR "\",\"msg\":\""),
|
||||
status_level_pgmstr(level));
|
||||
|
||||
// Message
|
||||
printf_P(PSTR("%" PRPSTR), status_to_pgmstr(code));
|
||||
|
||||
if (msg && pgm_read_byte(msg)) {
|
||||
putchar(':');
|
||||
putchar(' ');
|
||||
|
||||
// TODO escape invalid chars
|
||||
va_start(args, msg);
|
||||
vfprintf_P(stdout, msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
putchar('"');
|
||||
|
||||
// Code
|
||||
if (code) printf_P(PSTR(",\"code\":%d"), code);
|
||||
|
||||
// Location
|
||||
if (location) printf_P(PSTR(",\"where\":\"%" PRPSTR "\""), location);
|
||||
|
||||
putchar('}');
|
||||
putchar('\n');
|
||||
|
||||
return code;
|
||||
}
|
||||
84
src/avr/src/status.h
Normal file
84
src/avr/src/status.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pgmspace.h"
|
||||
|
||||
|
||||
typedef enum {
|
||||
#define STAT_MSG(NAME, TEXT) STAT_##NAME,
|
||||
#include "messages.def"
|
||||
#undef STAT_MSG
|
||||
|
||||
STAT_MAX,
|
||||
STAT_DO_NOT_EXCEED = 255 // Do not exceed 255
|
||||
} stat_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
STAT_LEVEL_INFO,
|
||||
STAT_LEVEL_DEBUG,
|
||||
STAT_LEVEL_WARNING,
|
||||
STAT_LEVEL_ERROR,
|
||||
} status_level_t;
|
||||
|
||||
|
||||
extern stat_t status_code;
|
||||
|
||||
const char *status_to_pgmstr(stat_t code);
|
||||
const char *status_level_pgmstr(status_level_t level);
|
||||
stat_t status_message_P(const char *location, status_level_t level,
|
||||
stat_t code, const char *msg, ...);
|
||||
|
||||
#define TO_STRING(x) _TO_STRING(x)
|
||||
#define _TO_STRING(x) #x
|
||||
|
||||
#define STATUS_LOCATION PSTR(__FILE__ ":" TO_STRING(__LINE__))
|
||||
|
||||
#define STATUS_MESSAGE(LEVEL, CODE, MSG, ...) \
|
||||
status_message_P(STATUS_LOCATION, LEVEL, CODE, PSTR(MSG), ##__VA_ARGS__)
|
||||
|
||||
#define STATUS_INFO(MSG, ...) \
|
||||
STATUS_MESSAGE(STAT_LEVEL_INFO, STAT_OK, MSG, ##__VA_ARGS__)
|
||||
|
||||
#define STATUS_DEBUG(MSG, ...) \
|
||||
STATUS_MESSAGE(STAT_LEVEL_DEBUG, STAT_OK, MSG, ##__VA_ARGS__)
|
||||
|
||||
#define STATUS_WARNING(CODE, MSG, ...) \
|
||||
STATUS_MESSAGE(STAT_LEVEL_WARNING, CODE, MSG, ##__VA_ARGS__)
|
||||
|
||||
#define STATUS_ERROR(CODE, MSG, ...) \
|
||||
STATUS_MESSAGE(STAT_LEVEL_ERROR, CODE, MSG, ##__VA_ARGS__)
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_CALL(FMT, ...) \
|
||||
printf_P(PSTR("%s(" FMT ")\n"), __FUNCTION__, ##__VA_ARGS__)
|
||||
#else // DEBUG
|
||||
#define DEBUG_CALL(...)
|
||||
#endif // DEBUG
|
||||
247
src/avr/src/stepper.c
Normal file
247
src/avr/src/stepper.c
Normal file
@@ -0,0 +1,247 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "stepper.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "motor.h"
|
||||
#include "hardware.h"
|
||||
#include "estop.h"
|
||||
#include "util.h"
|
||||
#include "cpp_magic.h"
|
||||
#include "exec.h"
|
||||
#include "drv8711.h"
|
||||
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
// Runtime
|
||||
bool busy;
|
||||
bool requesting;
|
||||
float dwell;
|
||||
uint8_t power_buf;
|
||||
uint8_t power_index;
|
||||
|
||||
// Move prep
|
||||
bool move_ready; // Prepped move ready for loader
|
||||
bool move_queued; // Prepped move queued
|
||||
float prep_dwell;
|
||||
int8_t power_next;
|
||||
|
||||
power_update_t powers[2][POWER_MAX_UPDATES];
|
||||
|
||||
uint32_t underrun;
|
||||
} stepper_t;
|
||||
|
||||
|
||||
static stepper_t st = {0};
|
||||
|
||||
|
||||
void stepper_init() {
|
||||
// Setup step timer
|
||||
TIMER_STEP.CTRLB = TC_WGMODE_NORMAL_gc; // Count to TOP & rollover
|
||||
TIMER_STEP.INTCTRLA = TC_OVFINTLVL_HI_gc; // Interrupt level
|
||||
TIMER_STEP.PER = STEP_TIMER_POLL; // Timer rate
|
||||
TIMER_STEP.CTRLA = TC_CLKSEL_DIV8_gc; // Start step timer
|
||||
}
|
||||
|
||||
|
||||
static void _end_move() {
|
||||
for (int motor = 0; motor < MOTORS; motor++)
|
||||
motor_end_move(motor);
|
||||
}
|
||||
|
||||
|
||||
static void _load_move() {
|
||||
for (int motor = 0; motor < MOTORS; motor++)
|
||||
motor_load_move(motor);
|
||||
}
|
||||
|
||||
|
||||
void st_shutdown() {
|
||||
TIMER_STEP.CTRLA = 0; // Stop stepper clock
|
||||
_end_move(); // Stop motor clocks
|
||||
ADCB_CH0_INTCTRL = 0; // Disable next move interrupt
|
||||
}
|
||||
|
||||
|
||||
/// Return true if motors or dwell are running
|
||||
bool st_is_busy() {return st.busy;}
|
||||
|
||||
|
||||
/// Interrupt handler for calling move exec function.
|
||||
/// ADC channel 0 triggered by load ISR as a "software" interrupt.
|
||||
ISR(STEP_LOW_LEVEL_ISR) {
|
||||
while (true) {
|
||||
stat_t status = exec_next();
|
||||
|
||||
switch (status) {
|
||||
case STAT_NOP: // No move executed, idle
|
||||
if (!st.busy) {
|
||||
if (MIN_VELOCITY < exec_get_velocity()) st.underrun++;
|
||||
exec_set_velocity(0); // Velocity is zero if there are no moves
|
||||
|
||||
spindle_idle();
|
||||
}
|
||||
break;
|
||||
|
||||
case STAT_AGAIN: continue; // No command executed, try again
|
||||
|
||||
case STAT_OK: // Move executed
|
||||
if (!st.move_queued)
|
||||
estop_trigger(STAT_EXPECTED_MOVE); // No move was queued
|
||||
st.move_queued = false;
|
||||
st.move_ready = true;
|
||||
break;
|
||||
|
||||
default: estop_trigger(status); break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ADCB_CH0_INTCTRL = 0;
|
||||
st.requesting = false;
|
||||
}
|
||||
|
||||
|
||||
static void _request_exec_move() {
|
||||
if (st.requesting) return;
|
||||
st.requesting = true;
|
||||
|
||||
// Use ADC as "software" interrupt to trigger next move exec as low interrupt.
|
||||
ADCB_CH0_INTCTRL = ADC_CH_INTLVL_LO_gc;
|
||||
ADCB_CTRLA = ADC_ENABLE_bm | ADC_CH0START_bm;
|
||||
}
|
||||
|
||||
|
||||
static void _update_power() {
|
||||
if (st.power_index < POWER_MAX_UPDATES)
|
||||
spindle_update(st.powers[st.power_buf][st.power_index++]);
|
||||
}
|
||||
|
||||
|
||||
/// Step timer interrupt routine.
|
||||
/// Dwell or dequeue and load next move.
|
||||
ISR(STEP_TIMER_ISR) {
|
||||
static uint8_t tick = 0;
|
||||
|
||||
// Update spindle power on every tick
|
||||
_update_power();
|
||||
|
||||
// Dwell
|
||||
if (0 < st.dwell) {
|
||||
st.dwell -= 0.001; // 1ms
|
||||
return;
|
||||
}
|
||||
st.dwell = 0;
|
||||
|
||||
if (tick++ & 3) return; // Proceed every 4 ticks
|
||||
|
||||
// If the next move is not ready try to load it
|
||||
if (!st.move_ready) {
|
||||
_request_exec_move();
|
||||
_end_move();
|
||||
tick = 0; // Try again in 1ms
|
||||
st.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (st.prep_dwell) {
|
||||
// End last move, if any
|
||||
_end_move();
|
||||
|
||||
// Start dwell
|
||||
st.dwell = st.prep_dwell;
|
||||
st.prep_dwell = 0;
|
||||
|
||||
} else {
|
||||
// Start move
|
||||
_load_move();
|
||||
|
||||
// Request next move when not in a dwell. Requesting the next move may
|
||||
// power up motors which should not be powered up during a dwell.
|
||||
_request_exec_move();
|
||||
}
|
||||
|
||||
// Handle power updates
|
||||
if (st.power_next != -1) {
|
||||
st.power_index = 0;
|
||||
st.power_buf = st.power_next;
|
||||
st.power_next = -1;
|
||||
_update_power();
|
||||
}
|
||||
|
||||
st.busy = true; // Executing move so mark busy
|
||||
st.move_ready = false; // We are done with this move, flip the flag back
|
||||
}
|
||||
|
||||
|
||||
void st_prep_power(const power_update_t powers[]) {
|
||||
ESTOP_ASSERT(!st.move_ready, STAT_STEPPER_NOT_READY);
|
||||
st.power_next = !st.power_buf;
|
||||
memcpy(st.powers[st.power_next], powers,
|
||||
sizeof(power_update_t) * POWER_MAX_UPDATES);
|
||||
}
|
||||
|
||||
|
||||
void st_prep_line(const float target[]) {
|
||||
// Trap conditions that would prevent queuing the line
|
||||
ESTOP_ASSERT(!st.move_ready, STAT_STEPPER_NOT_READY);
|
||||
|
||||
// Prepare motor moves
|
||||
for (int motor = 0; motor < MOTORS; motor++)
|
||||
motor_prep_move(motor, target[motor_get_axis(motor)]);
|
||||
|
||||
st.move_queued = true; // signal prep buffer ready (do this last)
|
||||
}
|
||||
|
||||
|
||||
/// Add a dwell to the move buffer
|
||||
void st_prep_dwell(float seconds) {
|
||||
ESTOP_ASSERT(!st.move_ready, STAT_STEPPER_NOT_READY);
|
||||
if (seconds <= 1e-4) seconds = 1e-4; // Min dwell
|
||||
st.power_next = !st.power_buf;
|
||||
spindle_load_power_updates(st.powers[st.power_next], 0, 0);
|
||||
st.prep_dwell = seconds;
|
||||
st.move_queued = true; // signal prep buffer ready
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
uint32_t get_underrun() {return st.underrun;}
|
||||
|
||||
|
||||
float get_dwell_time() {
|
||||
float dwell;
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) dwell = st.dwell;
|
||||
return dwell;
|
||||
}
|
||||
42
src/avr/src/stepper.h
Normal file
42
src/avr/src/stepper.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spindle.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
void stepper_init();
|
||||
void st_shutdown();
|
||||
bool st_is_busy();
|
||||
void st_set_power_scale(float scale);
|
||||
void st_prep_power(const power_update_t powers[]);
|
||||
void st_prep_line(const float target[]);
|
||||
void st_prep_dwell(float seconds);
|
||||
209
src/avr/src/switch.c
Normal file
209
src/avr/src/switch.c
Normal file
@@ -0,0 +1,209 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "switch.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
static struct {
|
||||
uint16_t debounce;
|
||||
uint16_t lockout;
|
||||
} sw = {
|
||||
.debounce = SWITCH_DEBOUNCE,
|
||||
.lockout = SWITCH_LOCKOUT,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t pin;
|
||||
switch_type_t type;
|
||||
|
||||
switch_callback_t cb;
|
||||
bool state;
|
||||
uint16_t debounce;
|
||||
uint16_t lockout;
|
||||
bool initialized;
|
||||
} switch_t;
|
||||
|
||||
|
||||
// Order must match indices in var functions below
|
||||
static switch_t switches[] = {
|
||||
{.pin = ESTOP_PIN, .type = SW_DISABLED},
|
||||
{.pin = PROBE_PIN, .type = SW_DISABLED},
|
||||
{.pin = MIN_0_PIN, .type = SW_DISABLED},
|
||||
{.pin = MAX_0_PIN, .type = SW_DISABLED},
|
||||
{.pin = MIN_1_PIN, .type = SW_DISABLED},
|
||||
{.pin = MAX_1_PIN, .type = SW_DISABLED},
|
||||
{.pin = MIN_2_PIN, .type = SW_DISABLED},
|
||||
{.pin = MAX_2_PIN, .type = SW_DISABLED},
|
||||
{.pin = MIN_3_PIN, .type = SW_DISABLED},
|
||||
{.pin = MAX_3_PIN, .type = SW_DISABLED},
|
||||
{.pin = STALL_0_PIN, .type = SW_DISABLED},
|
||||
{.pin = STALL_1_PIN, .type = SW_DISABLED},
|
||||
{.pin = STALL_2_PIN, .type = SW_DISABLED},
|
||||
{.pin = STALL_3_PIN, .type = SW_DISABLED},
|
||||
{.pin = MOTOR_FAULT_PIN, .type = SW_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
static const int num_switches = sizeof(switches) / sizeof (switch_t);
|
||||
|
||||
|
||||
void switch_init() {
|
||||
for (int i = 0; i < num_switches; i++) {
|
||||
switch_t *s = &switches[i];
|
||||
PINCTRL_PIN(s->pin) = PORT_OPC_PULLUP_gc; // Pull up
|
||||
DIRCLR_PIN(s->pin); // Input
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Called from RTC on each tick
|
||||
void switch_rtc_callback() {
|
||||
for (int i = 0; i < num_switches; i++) {
|
||||
switch_t *s = &switches[i];
|
||||
|
||||
if (s->type == SW_DISABLED) continue;
|
||||
if (s->lockout && --s->lockout) continue;
|
||||
|
||||
// Debounce switch
|
||||
bool state = IN_PIN(s->pin);
|
||||
if (state == s->state && s->initialized) s->debounce = 0;
|
||||
else if (++s->debounce == sw.debounce) {
|
||||
s->state = state;
|
||||
s->debounce = 0;
|
||||
s->initialized = true;
|
||||
s->lockout = sw.lockout;
|
||||
if (s->cb) s->cb((switch_id_t)i, switch_is_active((switch_id_t)i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool switch_is_active(switch_id_t sw) {
|
||||
if (sw < 0 || num_switches <= sw || !switches[sw].initialized) return false;
|
||||
|
||||
// NOTE, switch inputs are active lo
|
||||
switch (switches[sw].type) {
|
||||
case SW_DISABLED: break; // A disabled switch cannot be active
|
||||
case SW_NORMALLY_OPEN: return !switches[sw].state;
|
||||
case SW_NORMALLY_CLOSED: return switches[sw].state;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool switch_is_enabled(switch_id_t sw) {
|
||||
return switch_get_type(sw) != SW_DISABLED;
|
||||
}
|
||||
|
||||
|
||||
switch_type_t switch_get_type(switch_id_t sw) {
|
||||
return (sw < 0 || num_switches <= sw) ? SW_DISABLED : switches[sw].type;
|
||||
}
|
||||
|
||||
|
||||
void switch_set_type(switch_id_t sw, switch_type_t type) {
|
||||
if (sw < 0 || num_switches <= sw) return;
|
||||
switch_t *s = &switches[sw];
|
||||
|
||||
if (s->type != type) {
|
||||
bool wasActive = switch_is_active(sw);
|
||||
s->type = type;
|
||||
bool isActive = switch_is_active(sw);
|
||||
if (wasActive != isActive && s->cb) s->cb(sw, isActive);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void switch_set_callback(switch_id_t sw, switch_callback_t cb) {
|
||||
switches[sw].cb = cb;
|
||||
}
|
||||
|
||||
|
||||
// Var callbacks
|
||||
uint8_t get_min_sw_mode(int index) {return switch_get_type(MIN_SWITCH(index));}
|
||||
|
||||
|
||||
void set_min_sw_mode(int index, uint8_t value) {
|
||||
switch_set_type(MIN_SWITCH(index), (switch_type_t)value);
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_max_sw_mode(int index) {return switch_get_type(MAX_SWITCH(index));}
|
||||
|
||||
|
||||
void set_max_sw_mode(int index, uint8_t value) {
|
||||
switch_set_type(MAX_SWITCH(index), (switch_type_t)value);
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_estop_mode() {return switch_get_type(SW_ESTOP);}
|
||||
|
||||
|
||||
void set_estop_mode(uint8_t value) {
|
||||
switch_set_type(SW_ESTOP, (switch_type_t)value);
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_probe_mode() {return switch_get_type(SW_PROBE);}
|
||||
|
||||
|
||||
void set_probe_mode(uint8_t value) {
|
||||
switch_set_type(SW_PROBE, (switch_type_t)value);
|
||||
}
|
||||
|
||||
|
||||
static uint8_t _get_state(int index) {
|
||||
if (!switch_is_enabled((switch_id_t)index)) return 2; // Disabled
|
||||
return switches[index].state;
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_min_switch(int index) {return _get_state(MIN_SWITCH(index));}
|
||||
uint8_t get_max_switch(int index) {return _get_state(MAX_SWITCH(index));}
|
||||
uint8_t get_estop_switch() {return _get_state(SW_ESTOP);}
|
||||
uint8_t get_probe_switch() {return _get_state(SW_PROBE);}
|
||||
|
||||
|
||||
void set_switch_debounce(uint16_t debounce) {
|
||||
sw.debounce = SWITCH_MAX_DEBOUNCE < debounce ? SWITCH_DEBOUNCE : debounce;
|
||||
}
|
||||
|
||||
|
||||
uint16_t get_switch_debounce() {return sw.debounce;}
|
||||
|
||||
|
||||
void set_switch_lockout(uint16_t lockout) {
|
||||
sw.lockout = SWITCH_MAX_LOCKOUT < lockout ? SWITCH_LOCKOUT : lockout;
|
||||
}
|
||||
|
||||
|
||||
uint16_t get_switch_lockout() {return sw.lockout;}
|
||||
72
src/avr/src/switch.h
Normal file
72
src/avr/src/switch.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
// macros for finding the index into the switch table give the axis number
|
||||
#define MIN_SWITCH(axis) ((switch_id_t)(2 + axis * 2))
|
||||
#define MAX_SWITCH(axis) ((switch_id_t)(2 + axis * 2 + 1))
|
||||
|
||||
|
||||
typedef enum {
|
||||
SW_DISABLED,
|
||||
SW_NORMALLY_OPEN,
|
||||
SW_NORMALLY_CLOSED,
|
||||
} switch_type_t;
|
||||
|
||||
|
||||
/// Switch IDs
|
||||
typedef enum {
|
||||
SW_INVALID = -1,
|
||||
SW_ESTOP, SW_PROBE,
|
||||
SW_MIN_0, SW_MAX_0,
|
||||
SW_MIN_1, SW_MAX_1,
|
||||
SW_MIN_2, SW_MAX_2,
|
||||
SW_MIN_3, SW_MAX_3,
|
||||
SW_STALL_0, SW_STALL_1,
|
||||
SW_STALL_2, SW_STALL_3,
|
||||
SW_MOTOR_FAULT,
|
||||
} switch_id_t;
|
||||
|
||||
|
||||
typedef void (*switch_callback_t)(switch_id_t sw, bool active);
|
||||
|
||||
|
||||
void switch_init();
|
||||
void switch_rtc_callback();
|
||||
bool switch_is_active(switch_id_t sw);
|
||||
bool switch_is_enabled(switch_id_t sw);
|
||||
switch_type_t switch_get_type(switch_id_t sw);
|
||||
void switch_set_type(switch_id_t sw, switch_type_t type);
|
||||
void switch_set_callback(switch_id_t sw, switch_callback_t cb);
|
||||
216
src/avr/src/type.c
Normal file
216
src/avr/src/type.c
Normal file
@@ -0,0 +1,216 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "type.h"
|
||||
#include "base64.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
|
||||
#define TYPEDEF(TYPE, DEF) \
|
||||
static const char TYPE##_name [] PROGMEM = "<" #TYPE ">"; \
|
||||
pstr type_get_##TYPE##_name_pgm() {return TYPE##_name;}
|
||||
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
|
||||
|
||||
// String
|
||||
bool type_eq_str(str a, str b) {return a == b;}
|
||||
void type_print_str(str s) {printf_P(PSTR("\"%s\""), s);}
|
||||
str type_parse_str(const char *s, stat_t *) {return s;}
|
||||
|
||||
// Program string
|
||||
bool type_eq_pstr(pstr a, pstr b) {return a == b;}
|
||||
void type_print_pstr(pstr s) {printf_P(PSTR("\"%" PRPSTR "\""), s);}
|
||||
const char *type_parse_pstr(const char *value, stat_t *) {return value;}
|
||||
|
||||
|
||||
// Float
|
||||
bool type_eq_f32(float a, float b) {return a == b || (isnan(a) && isnan(b));}
|
||||
|
||||
|
||||
void type_print_f32(float x) {
|
||||
if (isnan(x)) printf_P(PSTR("\"nan\""));
|
||||
else if (isinf(x)) printf_P(PSTR("\"%cinf\""), x < 0 ? '-' : '+');
|
||||
|
||||
else {
|
||||
char buf[20];
|
||||
|
||||
int len = sprintf_P(buf, PSTR("%.3f"), x);
|
||||
|
||||
// Remove trailing zeros
|
||||
for (int i = len; 0 < i; i--) {
|
||||
if (buf[i - 1] == '.') buf[i - 1] = 0;
|
||||
else if (buf[i - 1] == '0') {
|
||||
buf[i - 1] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
printf(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float type_parse_f32(const char *value, stat_t *status) {
|
||||
while (*value && isspace(*value)) value++;
|
||||
|
||||
if (*value == ':') {
|
||||
value++;
|
||||
if (strnlen(value, 6) != 6) {
|
||||
if (status) *status = STAT_INVALID_VALUE;
|
||||
return NAN;
|
||||
}
|
||||
|
||||
float f;
|
||||
if (b64_decode_float(value, &f)) return f;
|
||||
else {
|
||||
if (status) *status = STAT_BAD_FLOAT;
|
||||
return NAN;
|
||||
}
|
||||
}
|
||||
|
||||
char *endptr = 0;
|
||||
float x = strtod(value, &endptr);
|
||||
|
||||
if (endptr == value) {
|
||||
if (status) *status = STAT_BAD_FLOAT;
|
||||
return NAN;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
// bool
|
||||
bool type_eq_b8(bool a, bool b) {return a == b;}
|
||||
void type_print_b8(bool x) {printf_P(x ? PSTR("true") : PSTR("false"));}
|
||||
|
||||
|
||||
bool type_parse_b8(const char *value, stat_t *status) {
|
||||
return !strcasecmp(value, "true") || type_parse_f32(value, status);
|
||||
}
|
||||
|
||||
|
||||
// s8
|
||||
bool type_eq_s8(s8 a, s8 b) {return a == b;}
|
||||
void type_print_s8(s8 x) {printf_P(PSTR("%" PRIi8), x);}
|
||||
|
||||
|
||||
s8 type_parse_s8(const char *value, stat_t *status) {
|
||||
char *endptr = 0;
|
||||
s8 x = strtol(value, &endptr, 0);
|
||||
if (endptr == value && status) *status = STAT_BAD_INT;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
// u8
|
||||
bool type_eq_u8(u8 a, u8 b) {return a == b;}
|
||||
void type_print_u8(u8 x) {printf_P(PSTR("%" PRIu8), x);}
|
||||
|
||||
|
||||
u8 type_parse_u8(const char *value, stat_t *status) {
|
||||
char *endptr = 0;
|
||||
u8 x = strtoul(value, &endptr, 0);
|
||||
if (endptr == value && status) *status = STAT_BAD_INT;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
// u16
|
||||
bool type_eq_u16(u16 a, u16 b) {return a == b;}
|
||||
void type_print_u16(u16 x) {printf_P(PSTR("%" PRIu16), x);}
|
||||
|
||||
|
||||
u16 type_parse_u16(const char *value, stat_t *status) {
|
||||
char *endptr = 0;
|
||||
u16 x = strtoul(value, &endptr, 0);
|
||||
if (endptr == value && status) *status = STAT_BAD_INT;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
// s32
|
||||
bool type_eq_s32(s32 a, s32 b) {return a == b;}
|
||||
void type_print_s32(s32 x) {printf_P(PSTR("%" PRIi32), x);}
|
||||
|
||||
|
||||
s32 type_parse_s32(const char *value, stat_t *status) {
|
||||
char *endptr = 0;
|
||||
s32 x = strtol(value, &endptr, 0);
|
||||
if (endptr == value && status) *status = STAT_BAD_INT;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
// u32
|
||||
bool type_eq_u32(u32 a, u32 b) {return a == b;}
|
||||
void type_print_u32(u32 x) {printf_P(PSTR("%" PRIu32), x);}
|
||||
|
||||
|
||||
u32 type_parse_u32(const char *value, stat_t *status) {
|
||||
char *endptr = 0;
|
||||
u32 x = strtoul(value, &endptr, 0);
|
||||
if (endptr == value && status) *status = STAT_BAD_INT;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
type_u type_parse(type_t type, const char *s, stat_t *status) {
|
||||
type_u value;
|
||||
|
||||
if (status) *status = STAT_OK;
|
||||
|
||||
switch (type) {
|
||||
#define TYPEDEF(TYPE, ...) \
|
||||
case TYPE_##TYPE: value._##TYPE = type_parse_##TYPE(s, status); break;
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
default: if (status) *status = STAT_INVALID_TYPE;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
void type_print(type_t type, type_u value) {
|
||||
switch (type) {
|
||||
#define TYPEDEF(TYPE, ...) \
|
||||
case TYPE_##TYPE: type_print_##TYPE(value._##TYPE); break;
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
}
|
||||
}
|
||||
37
src/avr/src/type.def
Normal file
37
src/avr/src/type.def
Normal file
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
// TYPE DEF
|
||||
TYPEDEF(str, const char *)
|
||||
TYPEDEF(pstr, PGM_P)
|
||||
TYPEDEF(f32, float)
|
||||
TYPEDEF(u8, uint8_t)
|
||||
TYPEDEF(s8, int8_t)
|
||||
TYPEDEF(u16, uint16_t)
|
||||
TYPEDEF(s32, int32_t)
|
||||
TYPEDEF(u32, uint32_t)
|
||||
TYPEDEF(b8, bool)
|
||||
67
src/avr/src/type.h
Normal file
67
src/avr/src/type.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pgmspace.h"
|
||||
#include "status.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
// Define types
|
||||
#define TYPEDEF(TYPE, DEF) typedef DEF TYPE;
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
|
||||
typedef enum {
|
||||
#define TYPEDEF(TYPE, ...) TYPE_##TYPE,
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
} type_t;
|
||||
|
||||
|
||||
typedef union {
|
||||
#define TYPEDEF(TYPE, ...) TYPE _##TYPE;
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
} type_u;
|
||||
|
||||
|
||||
// Define functions
|
||||
#define TYPEDEF(TYPE, DEF) \
|
||||
pstr type_get_##TYPE##_name_pgm(); \
|
||||
bool type_eq_##TYPE(TYPE a, TYPE b); \
|
||||
TYPE type_parse_##TYPE(const char *s, stat_t *status); \
|
||||
void type_print_##TYPE(TYPE x);
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
|
||||
|
||||
type_u type_parse(type_t type, const char *s, stat_t *status);
|
||||
void type_print(type_t type, type_u value);
|
||||
285
src/avr/src/usart.c
Normal file
285
src/avr/src/usart.c
Normal file
@@ -0,0 +1,285 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "usart.h"
|
||||
#include "cpp_magic.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
// Ring buffers
|
||||
#define RING_BUF_INDEX_TYPE volatile uint16_t
|
||||
#define RING_BUF_NAME tx_buf
|
||||
#define RING_BUF_SIZE USART_TX_RING_BUF_SIZE
|
||||
#define RING_BUF_ATOMIC_COPY 1
|
||||
#include "ringbuf.def"
|
||||
|
||||
#define RING_BUF_INDEX_TYPE volatile uint16_t
|
||||
#define RING_BUF_NAME rx_buf
|
||||
#define RING_BUF_SIZE USART_RX_RING_BUF_SIZE
|
||||
#define RING_BUF_ATOMIC_COPY 1
|
||||
#include "ringbuf.def"
|
||||
|
||||
static bool _flush = false;
|
||||
|
||||
|
||||
static void _set_dre_interrupt(bool enable) {
|
||||
if (enable) SERIAL_PORT.CTRLA |= USART_DREINTLVL_MED_gc;
|
||||
else SERIAL_PORT.CTRLA &= ~USART_DREINTLVL_MED_gc;
|
||||
}
|
||||
|
||||
|
||||
static void _set_rxc_interrupt(bool enable) {
|
||||
if (enable) {
|
||||
if (SERIAL_CTS_THRESH <= rx_buf_space())
|
||||
OUTCLR_PIN(SERIAL_CTS_PIN); // CTS Lo (enable)
|
||||
|
||||
SERIAL_PORT.CTRLA |= USART_RXCINTLVL_HI_gc;
|
||||
|
||||
} else SERIAL_PORT.CTRLA &= ~USART_RXCINTLVL_HI_gc;
|
||||
}
|
||||
|
||||
|
||||
// Data register empty interrupt vector
|
||||
ISR(SERIAL_DRE_vect) {
|
||||
if (tx_buf_empty()) _set_dre_interrupt(false); // Disable interrupt
|
||||
|
||||
else {
|
||||
SERIAL_PORT.DATA = tx_buf_peek();
|
||||
tx_buf_pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Data received interrupt vector
|
||||
ISR(SERIAL_RXC_vect) {
|
||||
if (rx_buf_full()) _set_rxc_interrupt(false); // Disable interrupt
|
||||
else rx_buf_push(SERIAL_PORT.DATA);
|
||||
|
||||
if (rx_buf_space() < SERIAL_CTS_THRESH)
|
||||
OUTSET_PIN(SERIAL_CTS_PIN); // CTS Hi (disable)
|
||||
}
|
||||
|
||||
|
||||
#ifdef __AVR__
|
||||
static int _usart_putchar(char c, FILE *f) {
|
||||
usart_putc(c);
|
||||
return 0;
|
||||
}
|
||||
#endif // __AVR__
|
||||
|
||||
|
||||
static void _set_baud(USART_t *port, uint16_t bsel, uint8_t bscale) {
|
||||
port->BAUDCTRLB = (uint8_t)((bscale << 4) | (bsel >> 8));
|
||||
port->BAUDCTRLA = bsel;
|
||||
port->CTRLB |= USART_CLK2X_bm;
|
||||
}
|
||||
|
||||
|
||||
void usart_set_baud(USART_t *port, baud_t baud) {
|
||||
// The BSEL / BSCALE values provided below assume a 32 Mhz clock
|
||||
// With CTRLB CLK2X is set
|
||||
// See http://www.avrcalc.elektronik-projekt.de/xmega/baud_rate_calculator
|
||||
|
||||
switch (baud) {
|
||||
case USART_BAUD_9600: _set_baud(port, 3325, 0b1101); break;
|
||||
case USART_BAUD_19200: _set_baud(port, 3317, 0b1100); break;
|
||||
case USART_BAUD_38400: _set_baud(port, 3301, 0b1011); break;
|
||||
case USART_BAUD_57600: _set_baud(port, 1095, 0b1100); break;
|
||||
case USART_BAUD_115200: _set_baud(port, 1079, 0b1011); break;
|
||||
case USART_BAUD_230400: _set_baud(port, 1047, 0b1010); break;
|
||||
case USART_BAUD_460800: _set_baud(port, 983, 0b1001); break;
|
||||
case USART_BAUD_921600: _set_baud(port, 107, 0b1011); break;
|
||||
case USART_BAUD_500000: _set_baud(port, 1, 0b0010); break;
|
||||
case USART_BAUD_1000000: _set_baud(port, 1, 0b0001); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void usart_set_parity(USART_t *port, parity_t parity) {
|
||||
uint8_t reg = port->CTRLC & ~USART_PMODE_gm;
|
||||
|
||||
switch (parity) {
|
||||
case USART_NONE: reg |= USART_PMODE_DISABLED_gc; break;
|
||||
case USART_EVEN: reg |= USART_PMODE_EVEN_gc; break;
|
||||
case USART_ODD: reg |= USART_PMODE_ODD_gc; break;
|
||||
}
|
||||
|
||||
port->CTRLC = reg;
|
||||
}
|
||||
|
||||
|
||||
void usart_set_stop(USART_t *port, stop_t stop) {
|
||||
switch (stop) {
|
||||
case USART_1STOP: port->CTRLC &= ~USART_SBMODE_bm; break;
|
||||
case USART_2STOP: port->CTRLC |= USART_SBMODE_bm; break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void usart_set_bits(USART_t *port, bits_t bits) {
|
||||
uint8_t reg = port->CTRLC & ~USART_CHSIZE_gm;
|
||||
|
||||
switch (bits) {
|
||||
case USART_5BITS: reg |= USART_CHSIZE_5BIT_gc; break;
|
||||
case USART_6BITS: reg |= USART_CHSIZE_6BIT_gc; break;
|
||||
case USART_7BITS: reg |= USART_CHSIZE_7BIT_gc; break;
|
||||
case USART_8BITS: reg |= USART_CHSIZE_8BIT_gc; break;
|
||||
case USART_9BITS: reg |= USART_CHSIZE_9BIT_gc; break;
|
||||
}
|
||||
|
||||
port->CTRLC = reg;
|
||||
}
|
||||
|
||||
|
||||
void usart_init_port(USART_t *port, baud_t baud, parity_t parity, bits_t bits,
|
||||
stop_t stop) {
|
||||
// Set baud rate
|
||||
usart_set_baud(port, baud);
|
||||
|
||||
// Async, no parity, 8 data bits, 1 stop bit
|
||||
port->CTRLC = USART_CMODE_ASYNCHRONOUS_gc;
|
||||
usart_set_parity(port, parity);
|
||||
usart_set_bits(port, bits);
|
||||
usart_set_stop(port, stop);
|
||||
|
||||
// Configure receiver and transmitter
|
||||
port->CTRLB |= USART_RXEN_bm | USART_TXEN_bm;
|
||||
}
|
||||
|
||||
|
||||
void usart_init() {
|
||||
// Setup ring buffer
|
||||
tx_buf_init();
|
||||
rx_buf_init();
|
||||
|
||||
PR.PRPC &= ~PR_USART0_bm; // Disable power reduction
|
||||
|
||||
// Setup pins
|
||||
OUTSET_PIN(SERIAL_CTS_PIN); // CTS Hi (disable)
|
||||
DIRSET_PIN(SERIAL_CTS_PIN); // CTS Output
|
||||
OUTSET_PIN(SERIAL_TX_PIN); // Tx High
|
||||
DIRSET_PIN(SERIAL_TX_PIN); // Tx Output
|
||||
DIRCLR_PIN(SERIAL_RX_PIN); // Rx Input
|
||||
|
||||
// Configure port
|
||||
usart_init_port(&SERIAL_PORT, SERIAL_BAUD, USART_NONE, USART_8BITS,
|
||||
USART_1STOP);
|
||||
|
||||
PMIC.CTRL |= PMIC_HILVLEN_bm; // Interrupt level on
|
||||
|
||||
#ifdef __AVR__
|
||||
// Connect IO
|
||||
static FILE _stdout;
|
||||
memset(&_stdout, 0, sizeof(FILE));
|
||||
_stdout.put = _usart_putchar;
|
||||
_stdout.flags = _FDEV_SETUP_WRITE;
|
||||
|
||||
stdout = &_stdout;
|
||||
stderr = &_stdout;
|
||||
#endif // __AVR__
|
||||
|
||||
// Enable Rx
|
||||
_set_rxc_interrupt(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void usart_putc(char c) {
|
||||
while (tx_buf_full() || _flush) continue;
|
||||
tx_buf_push(c);
|
||||
_set_dre_interrupt(true); // Enable interrupt
|
||||
}
|
||||
|
||||
|
||||
void usart_puts(const char *s) {while (*s) usart_putc(*s++);}
|
||||
|
||||
|
||||
int8_t usart_getc() {
|
||||
while (rx_buf_empty()) continue;
|
||||
uint8_t data = rx_buf_next();
|
||||
_set_rxc_interrupt(true); // Enable interrupt
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/*** Line editing features:
|
||||
*
|
||||
* ENTER Submit current command line.
|
||||
* BS Backspace, delete last character.
|
||||
* CTRL-X Cancel current line entry.
|
||||
*/
|
||||
char *usart_readline() {
|
||||
static char line[INPUT_BUFFER_LEN];
|
||||
static int i = 0;
|
||||
bool eol = false;
|
||||
|
||||
while (!rx_buf_empty()) {
|
||||
char data = usart_getc();
|
||||
|
||||
switch (data) {
|
||||
case '\r': case '\n': eol = true; break;
|
||||
case '\b': if (i) i--; break; // BS - backspace
|
||||
case 0x18: i = 0; break; // CAN - Cancel or CTRL-X
|
||||
|
||||
default:
|
||||
line[i++] = data;
|
||||
if (i == INPUT_BUFFER_LEN - 1) eol = true; // Line buffer full
|
||||
break;
|
||||
}
|
||||
|
||||
if (eol) {
|
||||
line[i] = 0;
|
||||
i = 0;
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void usart_flush() {
|
||||
_flush = true;
|
||||
|
||||
while (!tx_buf_empty() || !(SERIAL_PORT.STATUS & USART_DREIF_bm) ||
|
||||
!(SERIAL_PORT.STATUS & USART_TXCIF_bm))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
void usart_rx_flush() {rx_buf_init();}
|
||||
int16_t usart_rx_space() {return rx_buf_space();}
|
||||
int16_t usart_rx_fill() {return rx_buf_fill();}
|
||||
int16_t usart_tx_space() {return tx_buf_space();}
|
||||
int16_t usart_tx_fill() {return tx_buf_fill();}
|
||||
100
src/avr/src/usart.h
Normal file
100
src/avr/src/usart.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <avr/io.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
// NOTE, RING_BUF_INDEX_TYPE must be be large enough to cover the buffer
|
||||
#define USART_TX_RING_BUF_SIZE 1024
|
||||
#define USART_RX_RING_BUF_SIZE 1024
|
||||
|
||||
|
||||
typedef enum {
|
||||
USART_BAUD_9600,
|
||||
USART_BAUD_19200,
|
||||
USART_BAUD_38400,
|
||||
USART_BAUD_57600,
|
||||
USART_BAUD_115200,
|
||||
USART_BAUD_230400,
|
||||
USART_BAUD_460800,
|
||||
USART_BAUD_921600,
|
||||
USART_BAUD_500000,
|
||||
USART_BAUD_1000000
|
||||
} baud_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
USART_NONE,
|
||||
USART_EVEN,
|
||||
USART_ODD,
|
||||
} parity_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
USART_1STOP,
|
||||
USART_2STOP,
|
||||
} stop_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
USART_5BITS,
|
||||
USART_6BITS,
|
||||
USART_7BITS,
|
||||
USART_8BITS,
|
||||
USART_9BITS,
|
||||
} bits_t;
|
||||
|
||||
|
||||
void usart_set_baud(USART_t *port, baud_t baud);
|
||||
void usart_set_parity(USART_t *port, parity_t parity);
|
||||
void usart_set_stop(USART_t *port, stop_t stop);
|
||||
void usart_set_bits(USART_t *port, bits_t bits);
|
||||
void usart_init_port(USART_t *port, baud_t baud, parity_t parity, bits_t bits,
|
||||
stop_t stop);
|
||||
|
||||
void usart_init();
|
||||
void usart_putc(char c);
|
||||
void usart_puts(const char *s);
|
||||
int8_t usart_getc();
|
||||
char *usart_readline();
|
||||
void usart_flush();
|
||||
|
||||
void usart_rx_flush();
|
||||
int16_t usart_rx_fill();
|
||||
int16_t usart_rx_space();
|
||||
inline bool usart_rx_empty() {return !usart_rx_fill();}
|
||||
inline bool usart_rx_full() {return !usart_rx_space();}
|
||||
|
||||
int16_t usart_tx_fill();
|
||||
int16_t usart_tx_space();
|
||||
inline bool usart_tx_empty() {return !usart_tx_fill();}
|
||||
inline bool usart_tx_full() {return !usart_tx_space();}
|
||||
95
src/avr/src/util.c
Normal file
95
src/avr/src/util.c
Normal file
@@ -0,0 +1,95 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/// Fast inverse square root originally from Quake III Arena code. Original
|
||||
/// comments left intact.
|
||||
/// See: https://en.wikipedia.org/wiki/Fast_inverse_square_root
|
||||
float invsqrt(float x) {
|
||||
// evil floating point bit level hacking
|
||||
union {
|
||||
float f;
|
||||
int32_t i;
|
||||
} u;
|
||||
|
||||
const float xhalf = x * 0.5f;
|
||||
u.f = x;
|
||||
u.i = 0x5f3759df - (u.i >> 1); // what the fuck?
|
||||
u.f = u.f * (1.5f - xhalf * u.f * u.f); // 1st iteration
|
||||
u.f = u.f * (1.5f - xhalf * u.f * u.f); // 2nd iteration, can be removed
|
||||
|
||||
return u.f;
|
||||
}
|
||||
|
||||
|
||||
int8_t decode_hex_nibble(char c) {
|
||||
if ('0' <= c && c <= '9') return c - '0';
|
||||
if ('a' <= c && c <= 'f') return c - 'a' + 10;
|
||||
if ('A' <= c && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
bool decode_float(char **s, float *f) {
|
||||
bool ok = b64_decode_float(*s, f) && isfinite(*f);
|
||||
*s += 6;
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
stat_t decode_axes(char **cmd, float axes[AXES]) {
|
||||
while (**cmd) {
|
||||
const char *names = "xyzabc";
|
||||
const char *match = strchr(names, **cmd);
|
||||
if (!match) break;
|
||||
char *s = *cmd + 1;
|
||||
if (!decode_float(&s, &axes[match - names])) return STAT_BAD_FLOAT;
|
||||
*cmd = s;
|
||||
}
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
// Assumes the caller provide format buffer length is @param len * 2 + 1.
|
||||
void format_hex_buf(char *buf, const uint8_t *data, unsigned len) {
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
sprintf(buf + i * 2, "%02x", data[i]);
|
||||
|
||||
buf[i * 2] = 0;
|
||||
}
|
||||
85
src/avr/src/util.h
Normal file
85
src/avr/src/util.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "config.h"
|
||||
#include "status.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
// Vector utilities
|
||||
#define clear_vector(a) memset(a, 0, sizeof(a))
|
||||
#define copy_vector(d, s) memcpy(d, s, sizeof(d))
|
||||
|
||||
// Math utilities
|
||||
inline static float min(float a, float b) {return a < b ? a : b;}
|
||||
inline static float max(float a, float b) {return a < b ? b : a;}
|
||||
inline static float min3(float a, float b, float c) {return min(min(a, b), c);}
|
||||
inline static float max3(float a, float b, float c) {return max(max(a, b), c);}
|
||||
inline static float min4(float a, float b, float c, float d)
|
||||
{return min(min(a, b), min(c, d));}
|
||||
inline static float max4(float a, float b, float c, float d)
|
||||
{return max(max(a, b), max(c, d));}
|
||||
|
||||
float invsqrt(float number);
|
||||
|
||||
#ifndef __AVR__
|
||||
inline static float square(float x) {return x * x;}
|
||||
#endif
|
||||
|
||||
// Floating-point utilities
|
||||
#define EPSILON 0.00001 // allowable rounding error for floats
|
||||
inline static bool fp_EQ(float a, float b) {return fabs(a - b) < EPSILON;}
|
||||
inline static bool fp_NE(float a, float b) {return fabs(a - b) > EPSILON;}
|
||||
inline static bool fp_ZERO(float a) {return fabs(a) < EPSILON;}
|
||||
inline static bool fp_FALSE(float a) {return fp_ZERO(a);}
|
||||
inline static bool fp_TRUE(float a) {return !fp_ZERO(a);}
|
||||
|
||||
int8_t decode_hex_nibble(char c);
|
||||
bool decode_float(char **s, float *f);
|
||||
stat_t decode_axes(char **cmd, float axes[AXES]);
|
||||
void format_hex_buf(char *buf, const uint8_t *data, unsigned len);
|
||||
|
||||
// Constants
|
||||
#define MM_PER_INCH 25.4
|
||||
#define INCHES_PER_MM (1 / 25.4)
|
||||
#define MICROSECONDS_PER_MINUTE 60000000.0
|
||||
|
||||
// 24bit integers
|
||||
#ifdef __AVR__
|
||||
typedef __int24 int24_t;
|
||||
typedef __uint24 uint24_t;
|
||||
#else
|
||||
typedef int32_t int24_t;
|
||||
typedef uint32_t uint24_t;
|
||||
#endif
|
||||
411
src/avr/src/vars.c
Normal file
411
src/avr/src/vars.c
Normal file
@@ -0,0 +1,411 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "vars.h"
|
||||
|
||||
#include "type.h"
|
||||
#include "status.h"
|
||||
#include "hardware.h"
|
||||
#include "config.h"
|
||||
#include "axis.h"
|
||||
#include "cpp_magic.h"
|
||||
#include "report.h"
|
||||
#include "command.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Format strings
|
||||
static const char code_fmt[] PROGMEM = "\"%s\":";
|
||||
static const char indexed_code_fmt[] PROGMEM = "\"%c%s\":";
|
||||
|
||||
|
||||
// Ensure no var code is used more than once
|
||||
enum {
|
||||
#define VAR(NAME, CODE, ...) var_code_##CODE,
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
var_code_count
|
||||
};
|
||||
|
||||
|
||||
// Var forward declarations
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, SET, ...) \
|
||||
TYPE get_##NAME(IF(INDEX)(int index)); \
|
||||
IF(SET) \
|
||||
(void set_##NAME(IF(INDEX)(int index,) TYPE value);)
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
|
||||
|
||||
// Set callback union
|
||||
typedef union {
|
||||
void *ptr;
|
||||
#define TYPEDEF(TYPE, ...) void (*set_##TYPE)(TYPE);
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
|
||||
#define TYPEDEF(TYPE, ...) void (*set_##TYPE##_index)(int i, TYPE);
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
} set_cb_u;
|
||||
|
||||
|
||||
// Get callback union
|
||||
typedef union {
|
||||
void *ptr;
|
||||
#define TYPEDEF(TYPE, ...) TYPE (*get_##TYPE)();
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
|
||||
#define TYPEDEF(TYPE, ...) TYPE (*get_##TYPE##_index)(int i);
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
} get_cb_u;
|
||||
|
||||
|
||||
typedef struct {
|
||||
type_t type;
|
||||
char name[5];
|
||||
int8_t index;
|
||||
get_cb_u get;
|
||||
set_cb_u set;
|
||||
} var_info_t;
|
||||
|
||||
|
||||
// Var names
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, SET, REPORT) \
|
||||
static const char NAME##_name[] PROGMEM = #NAME;
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
|
||||
|
||||
// Last value
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, ...) \
|
||||
static TYPE NAME##_state IF(INDEX)([INDEX]);
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
|
||||
|
||||
// Report
|
||||
static uint8_t _report_var[(var_code_count >> 3) + 1] = {0,};
|
||||
|
||||
|
||||
static bool _get_report_var(int index) {
|
||||
return _report_var[index >> 3] & (1 << (index & 7));
|
||||
}
|
||||
|
||||
|
||||
static void _set_report_var(int index, bool enable) {
|
||||
if (enable) _report_var[index >> 3] |= 1 << (index & 7);
|
||||
else _report_var[index >> 3] &= ~(1 << (index & 7));
|
||||
}
|
||||
|
||||
|
||||
static int _find_code(const char *code) {
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, ...) \
|
||||
if (!strcmp(code, #CODE)) return var_code_##CODE; \
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void vars_init() {
|
||||
// Initialize var state
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, ...) \
|
||||
IF(INDEX)(for (int i = 0; i < INDEX; i++)) \
|
||||
(NAME##_state)IF(INDEX)([i]) = get_##NAME(IF(INDEX)(i));
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
|
||||
// Report
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, SET, REPORT, ...) \
|
||||
_set_report_var(var_code_##CODE, REPORT);
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
}
|
||||
|
||||
|
||||
void vars_report(bool full) {
|
||||
bool reported = false;
|
||||
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, ...) \
|
||||
if (_get_report_var(var_code_##CODE)) { \
|
||||
IF(INDEX)(for (int i = 0; i < (INDEX ? INDEX : 1); i++)) { \
|
||||
TYPE value = get_##NAME(IF(INDEX)(i)); \
|
||||
TYPE last = (NAME##_state)IF(INDEX)([i]); \
|
||||
\
|
||||
if (full || (!type_eq_##TYPE(value, last))) { \
|
||||
(NAME##_state)IF(INDEX)([i]) = value; \
|
||||
\
|
||||
if (!reported) { \
|
||||
reported = true; \
|
||||
putchar('{'); \
|
||||
} else putchar(','); \
|
||||
\
|
||||
printf_P \
|
||||
(IF_ELSE(INDEX)(indexed_code_fmt, code_fmt), \
|
||||
IF(INDEX)(INDEX##_LABEL[i],) #CODE); \
|
||||
\
|
||||
type_print_##TYPE(value); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
|
||||
if (reported) printf("}\n");
|
||||
}
|
||||
|
||||
void vars_report_all(bool enable) {
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, SET, REPORT, ...) \
|
||||
_set_report_var(var_code_##CODE, enable);
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
}
|
||||
|
||||
|
||||
void vars_report_var(const char *code, bool enable) {
|
||||
int index = _find_code(code);
|
||||
if (index != -1) _set_report_var(index, enable);
|
||||
}
|
||||
|
||||
|
||||
static char *_resolve_name(const char *_name) {
|
||||
unsigned len = strlen(_name);
|
||||
|
||||
if (!len || 4 < len) return 0;
|
||||
|
||||
static char name[5];
|
||||
strncpy(name, _name, 4);
|
||||
name[4] = 0;
|
||||
|
||||
// Handle axis to motor mapping
|
||||
if (2 < len && name[1] == '.') {
|
||||
int axis = axis_get_id(name[0]);
|
||||
if (axis < 0) return 0;
|
||||
int motor = axis_get_motor(axis);
|
||||
if (motor < 0) return 0;
|
||||
|
||||
name[0] = MOTORS_LABEL[motor];
|
||||
for (int i = 1; _name[i]; i++)
|
||||
name[i] = _name[i + 1];
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
static int _index(char c, const char *s) {
|
||||
const char *index = strchr(s, c);
|
||||
return index ? index - s : -1;
|
||||
}
|
||||
|
||||
|
||||
static bool _find_var(const char *_name, var_info_t *info) {
|
||||
char *name = _resolve_name(_name);
|
||||
if (!name) return false;
|
||||
|
||||
int i = -1;
|
||||
memset(info, 0, sizeof(var_info_t));
|
||||
strcpy(info->name, name);
|
||||
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, SET, ...) \
|
||||
if (!strcmp(IF_ELSE(INDEX)(name + 1, name), #CODE) \
|
||||
IF(INDEX)(&& (i = _index(name[0], INDEX##_LABEL)) != -1) \
|
||||
) { \
|
||||
\
|
||||
info->type = TYPE_##TYPE; \
|
||||
info->index = i; \
|
||||
info->get.IF_ELSE(INDEX)(get_##TYPE##_index, get_##TYPE) = \
|
||||
get_##NAME; \
|
||||
\
|
||||
IF(SET)(info->set.IF_ELSE(INDEX) \
|
||||
(set_##TYPE##_index, set_##TYPE) = set_##NAME;) \
|
||||
\
|
||||
return true; \
|
||||
}
|
||||
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static type_u _get(type_t type, int8_t index, get_cb_u cb) {
|
||||
type_u value;
|
||||
|
||||
switch (type) {
|
||||
#define TYPEDEF(TYPE, ...) \
|
||||
case TYPE_##TYPE: \
|
||||
if (index == -1) value._##TYPE = cb.get_##TYPE(); \
|
||||
value._##TYPE = cb.get_##TYPE##_index(index); \
|
||||
break;
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
static void _set(type_t type, int8_t index, set_cb_u cb, type_u value) {
|
||||
switch (type) {
|
||||
#define TYPEDEF(TYPE, ...) \
|
||||
case TYPE_##TYPE: \
|
||||
if (index == -1) cb.set_##TYPE(value._##TYPE); \
|
||||
else cb.set_##TYPE##_index(index, value._##TYPE); \
|
||||
break;
|
||||
#include "type.def"
|
||||
#undef TYPEDEF
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stat_t vars_print(const char *name) {
|
||||
var_info_t info;
|
||||
if (!_find_var(name, &info)) return STAT_UNRECOGNIZED_NAME;
|
||||
|
||||
printf_P(PSTR("{\"%s\":"), info.name);
|
||||
type_print(info.type, _get(info.type, info.index, info.get));
|
||||
putchar('}');
|
||||
putchar('\n');
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
stat_t vars_set(const char *name, const char *value) {
|
||||
var_info_t info;
|
||||
if (!_find_var(name, &info)) return STAT_UNRECOGNIZED_NAME;
|
||||
if (!info.set.ptr) return STAT_READ_ONLY;
|
||||
|
||||
stat_t status;
|
||||
type_u x = type_parse(info.type, value, &status);
|
||||
if (status == STAT_OK) _set(info.type, info.index, info.set, x);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
void vars_print_json() {
|
||||
bool first = true;
|
||||
static const char fmt[] PROGMEM =
|
||||
"\"%s\":{\"name\":\"%" PRPSTR "\",\"type\":\"%" PRPSTR "\"";
|
||||
static const char index_fmt[] PROGMEM = ",\"index\":\"%s\"";
|
||||
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, ...) \
|
||||
if (first) first = false; else putchar(','); \
|
||||
printf_P(fmt, #CODE, NAME##_name, type_get_##TYPE##_name_pgm()); \
|
||||
IF(INDEX)(printf_P(index_fmt, INDEX##_LABEL)); \
|
||||
putchar('}');
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
}
|
||||
|
||||
|
||||
// Command callbacks
|
||||
stat_t command_var(char *cmd) {
|
||||
cmd++; // Skip command code
|
||||
|
||||
if (*cmd == '$' && !cmd[1]) {
|
||||
report_request_full();
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
// Get or set variable
|
||||
char *value = strchr(cmd, '=');
|
||||
if (value) {
|
||||
*value++ = 0;
|
||||
stat_t status = vars_set(cmd, value);
|
||||
value[-1] = '='; // For error reporting
|
||||
return status;
|
||||
}
|
||||
|
||||
return vars_print(cmd);
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
type_t type;
|
||||
int8_t index;
|
||||
set_cb_u set;
|
||||
type_u value;
|
||||
} var_cmd_t;
|
||||
|
||||
|
||||
stat_t command_sync_var(char *cmd) {
|
||||
// Get value
|
||||
char *value = strchr(cmd + 1, '=');
|
||||
if (!value) return STAT_INVALID_COMMAND;
|
||||
*value++ = 0;
|
||||
|
||||
var_info_t info;
|
||||
if (!_find_var(cmd + 1, &info)) return STAT_UNRECOGNIZED_NAME;
|
||||
if (!info.set.ptr) return STAT_READ_ONLY;
|
||||
|
||||
stat_t status;
|
||||
var_cmd_t buffer;
|
||||
|
||||
buffer.type = info.type;
|
||||
buffer.index = info.index;
|
||||
buffer.set = info.set;
|
||||
buffer.value = type_parse(info.type, value, &status);
|
||||
|
||||
if (status == STAT_OK) command_push(*cmd, &buffer);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
unsigned command_sync_var_size() {return sizeof(var_cmd_t);}
|
||||
|
||||
|
||||
void command_sync_var_exec(void *data) {
|
||||
var_cmd_t *cmd = (var_cmd_t *)data;
|
||||
_set(cmd->type, cmd->index, cmd->set, cmd->value);
|
||||
}
|
||||
|
||||
|
||||
stat_t command_report(char *cmd) {
|
||||
bool enable = cmd[1] != '0';
|
||||
|
||||
if (cmd[2]) vars_report_var(cmd + 2, enable);
|
||||
else vars_report_all(enable);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
150
src/avr/src/vars.def
Normal file
150
src/avr/src/vars.def
Normal file
@@ -0,0 +1,150 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#define AXES_LABEL "xyzabc"
|
||||
#define MOTORS_LABEL "0123"
|
||||
#define OUTS_LABEL "ed12ft"
|
||||
#define ANALOG_LABEL "12"
|
||||
#define VFDREG_LABEL "0123456789abcdefghijklmnopqrstuv"
|
||||
|
||||
// VAR(name, code, type, index, settable, report)
|
||||
|
||||
// Motor
|
||||
VAR(motor_axis, an, u8, MOTORS, 1, 1) // Maps motor to axis
|
||||
|
||||
VAR(motor_enabled, me, b8, MOTORS, 1, 1) // Motor enabled
|
||||
VAR(drive_current, dc, f32, MOTORS, 1, 1) // Max motor drive current
|
||||
VAR(idle_current, ic, f32, MOTORS, 1, 1) // Motor idle current
|
||||
|
||||
VAR(reverse, rv, b8, MOTORS, 1, 1) // Reverse motor polarity
|
||||
VAR(microstep, mi, u16, MOTORS, 1, 1) // Microsteps per full step
|
||||
VAR(velocity_max, vm, f32, MOTORS, 1, 1) // Maxium vel in mm/min
|
||||
VAR(accel_max, am, f32, MOTORS, 1, 1) // Maxium accel in mm/min^2
|
||||
VAR(jerk_max, jm, f32, MOTORS, 1, 1) // Maxium jerk in mm/min^3
|
||||
VAR(step_angle, sa, f32, MOTORS, 1, 1) // In degrees per full step
|
||||
VAR(travel, tr, f32, MOTORS, 1, 1) // Travel in mm/rev
|
||||
|
||||
VAR(min_soft_limit, tn, f32, MOTORS, 1, 1) // Min soft limit
|
||||
VAR(max_soft_limit, tm, f32, MOTORS, 1, 1) // Max soft limit
|
||||
VAR(homed, h, b8, MOTORS, 1, 1) // Motor homed status
|
||||
|
||||
VAR(active_current, ac, f32, MOTORS, 0, 0) // Motor current now
|
||||
VAR(driver_flags, df, u16, MOTORS, 1, 1) // Motor driver flags
|
||||
VAR(driver_stalled, sl, b8, MOTORS, 0, 0) // Motor driver status
|
||||
VAR(stall_volts, tv, f32, MOTORS, 1, 1) // Motor BEMF threshold voltage
|
||||
VAR(encoder, en, s32, MOTORS, 0, 0) // Motor encoder
|
||||
VAR(error, ee, s32, MOTORS, 0, 0) // Motor position error
|
||||
|
||||
VAR(stall_microstep, lm, u16, MOTORS, 1, 1) // Motor stall microsteps
|
||||
VAR(stall_sample_time,sp,u16, MOTORS, 1, 1) // Motor stall sample time
|
||||
VAR(stall_current, tc, f32, MOTORS, 1, 1) // Motor stall current
|
||||
|
||||
VAR(motor_fault, fa, b8, 0, 0, 1) // Motor fault status
|
||||
|
||||
// Switches
|
||||
VAR(min_sw_mode, ls, u8, MOTORS, 1, 1) // Minimum switch mode
|
||||
VAR(max_sw_mode, xs, u8, MOTORS, 1, 1) // Maximum switch mode
|
||||
VAR(estop_mode, et, u8, 0, 1, 1) // Estop switch mode
|
||||
VAR(probe_mode, pt, u8, 0, 1, 1) // Probe switch mode
|
||||
VAR(min_switch, lw, u8, MOTORS, 0, 1) // Minimum switch state
|
||||
VAR(max_switch, xw, u8, MOTORS, 0, 1) // Maximum switch state
|
||||
VAR(estop_switch, ew, u8, 0, 0, 1) // Estop switch state
|
||||
VAR(probe_switch, pw, u8, 0, 0, 1) // Probe switch state
|
||||
VAR(switch_debounce, sd, u16, 0, 1, 1) // Switch debounce time in ms
|
||||
VAR(switch_lockout, sc, u16, 0, 1, 1) // Switch lockout time in ms
|
||||
|
||||
// Axis
|
||||
VAR(axis_position, p, f32, AXES, 0, 1) // Axis position
|
||||
|
||||
// Outputs
|
||||
VAR(output_active, oa, b8, OUTS, 1, 1) // Output pin active
|
||||
VAR(output_state, os, u8, OUTS, 0, 1) // Output pin state
|
||||
VAR(output_mode, om, u8, OUTS, 1, 1) // Output pin mode
|
||||
|
||||
// Analog
|
||||
VAR(analog_input, ai, f32, ANALOG, 0, 0) // Analog input pins
|
||||
|
||||
// Spindle
|
||||
VAR(tool_type, st, u8, 0, 1, 1) // See spindle.c
|
||||
VAR(speed, s, f32, 0, 0, 1) // Current spindle speed
|
||||
VAR(tool_reversed, sr, b8, 0, 1, 1) // Reverse tool
|
||||
VAR(max_spin, sx, f32, 0, 1, 1) // Maximum spindle speed
|
||||
VAR(min_spin, sm, f32, 0, 1, 1) // Minimum spindle speed
|
||||
VAR(spindle_status, ss, u16, 0, 0, 1) // Spindle status code
|
||||
|
||||
// PWM spindle
|
||||
VAR(pwm_invert, pi, b8, 0, 1, 1) // Inverted spindle PWM
|
||||
VAR(pwm_min_duty, nd, f32, 0, 1, 1) // Minimum PWM duty cycle
|
||||
VAR(pwm_max_duty, md, f32, 0, 1, 1) // Maximum PWM duty cycle
|
||||
VAR(pwm_duty, pd, f32, 0, 0, 0) // Current PWM duty cycle
|
||||
VAR(pwm_freq, sf, f32, 0, 1, 0) // Spindle PWM frequency in Hz
|
||||
|
||||
// Modbus spindle
|
||||
VAR(mb_debug, hb, b8, 0, 1, 1) // Modbus debugging
|
||||
VAR(mb_id, hi, u8, 0, 1, 1) // Modbus ID
|
||||
VAR(mb_baud, mb, u8, 0, 1, 1) // Modbus BAUD rate
|
||||
VAR(mb_parity, ma, u8, 0, 1, 1) // Modbus parity
|
||||
VAR(mb_status, mx, u8, 0, 0, 1) // Modbus status
|
||||
VAR(mb_crc_errs, cr, u16, 0, 0, 1) // Modbus CRC error counter
|
||||
|
||||
// VFD spindle
|
||||
VAR(vfd_max_freq, vf, u16, 0, 1, 1) // VFD maximum frequency
|
||||
VAR(vfd_multi_write, mw, b8, 0, 1, 1) // Use Modbus multi write mode
|
||||
VAR(vfd_reg_type, vt, u8, VFDREG, 1, 1) // VFD register type
|
||||
VAR(vfd_reg_addr, va, u16, VFDREG, 1, 1) // VFD register address
|
||||
VAR(vfd_reg_val, vv, u16, VFDREG, 1, 1) // VFD register value
|
||||
VAR(vfd_reg_fails, vr, u8, VFDREG, 1, 1) // VFD register fail count
|
||||
|
||||
// Huanyang spindle
|
||||
VAR(hy_freq, hz, f32, 0, 0, 0) // Huanyang actual freq
|
||||
VAR(hy_current, hc, f32, 0, 0, 0) // Huanyang actual current
|
||||
VAR(hy_temp, ht, u16, 0, 0, 0) // Huanyang temperature
|
||||
VAR(hy_max_freq, hx, f32, 0, 0, 1) // Huanyang max freq
|
||||
VAR(hy_min_freq, hm, f32, 0, 0, 1) // Huanyang min freq
|
||||
VAR(hy_rated_rpm, hq, u16, 0, 0, 1) // Huanyang rated RPM
|
||||
|
||||
// Machine state
|
||||
VAR(id, id, u16, 0, 1, 1) // Last executed command ID
|
||||
VAR(feed_override, fo, u16, 0, 1, 1) // Feed rate override
|
||||
VAR(speed_override, so, u16, 0, 1, 1) // Spindle speed override
|
||||
|
||||
// System
|
||||
VAR(velocity, v, f32, 0, 0, 1) // Current velocity
|
||||
VAR(acceleration, ax, f32, 0, 0, 0) // Current acceleration
|
||||
VAR(jerk, j, f32, 0, 0, 0) // Current jerk
|
||||
VAR(peak_vel, pv, f32, 0, 1, 1) // Peak velocity, set to clear
|
||||
VAR(peak_accel, pa, f32, 0, 1, 1) // Peak accel, set to clear
|
||||
VAR(dynamic_power, dp, b8, 0, 1, 1) // Dynamic power
|
||||
VAR(inverse_feed, if, f32, 0, 1, 1) // Inverse feed rate
|
||||
VAR(hw_id, hid, str, 0, 0, 1) // Hardware ID
|
||||
VAR(estop, es, b8, 0, 1, 1) // Emergency stop
|
||||
VAR(estop_reason, er, pstr, 0, 0, 1) // Emergency stop reason
|
||||
VAR(state, xx, pstr, 0, 0, 1) // Machine state
|
||||
VAR(state_count, xc, u16, 0, 0, 1) // Machine state change count
|
||||
VAR(hold_reason, pr, pstr, 0, 0, 1) // Machine pause reason
|
||||
VAR(underrun, un, u32, 0, 0, 1) // Stepper buffer underrun count
|
||||
VAR(dwell_time, dt, f32, 0, 0, 1) // Dwell timer
|
||||
45
src/avr/src/vars.h
Normal file
45
src/avr/src/vars.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "status.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
float var_decode_float(const char *value);
|
||||
bool var_parse_bool(const char *value);
|
||||
|
||||
void vars_init();
|
||||
|
||||
void vars_report(bool full);
|
||||
void vars_report_all(bool enable);
|
||||
void vars_report_var(const char *code, bool enable);
|
||||
stat_t vars_print(const char *name);
|
||||
stat_t vars_set(const char *name, const char *value);
|
||||
void vars_print_json();
|
||||
14
src/avr/src/vars.json.in
Normal file
14
src/avr/src/vars.json.in
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "cpp_magic.h"
|
||||
{
|
||||
#define VAR(NAME, CODE, TYPE, INDEX, SET, REPORT) \
|
||||
#CODE: { \
|
||||
"name": #NAME, \
|
||||
"type": #TYPE, \
|
||||
"index": IF_ELSE(INDEX)(true, false), \
|
||||
"setable": IF_ELSE(SET)(true, false), \
|
||||
"report": IF_ELSE(REPORT)(true, false) \
|
||||
},
|
||||
#include "vars.def"
|
||||
#undef VAR
|
||||
"_": {}
|
||||
}
|
||||
478
src/avr/src/vfd_spindle.c
Normal file
478
src/avr/src/vfd_spindle.c
Normal file
@@ -0,0 +1,478 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "vfd_spindle.h"
|
||||
#include "modbus.h"
|
||||
#include "rtc.h"
|
||||
#include "config.h"
|
||||
#include "estop.h"
|
||||
#include "pgmspace.h"
|
||||
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
REG_DISABLED,
|
||||
|
||||
REG_CONNECT_WRITE,
|
||||
|
||||
REG_MAX_FREQ_READ,
|
||||
REG_MAX_FREQ_FIXED,
|
||||
|
||||
REG_FREQ_SET,
|
||||
REG_FREQ_SIGN_SET,
|
||||
|
||||
REG_STOP_WRITE,
|
||||
REG_FWD_WRITE,
|
||||
REG_REV_WRITE,
|
||||
|
||||
REG_FREQ_READ,
|
||||
REG_FREQ_SIGN_READ,
|
||||
REG_FREQ_ACTECH_READ,
|
||||
|
||||
REG_STATUS_READ,
|
||||
|
||||
REG_DISCONNECT_WRITE,
|
||||
} vfd_reg_type_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
vfd_reg_type_t type;
|
||||
uint16_t addr;
|
||||
uint16_t value;
|
||||
uint8_t fails;
|
||||
} vfd_reg_t;
|
||||
|
||||
|
||||
#define P(H, L) ((H) << 8 | (L))
|
||||
|
||||
|
||||
// NOTE, Modbus reg = AC Tech reg + 1
|
||||
const vfd_reg_t ac_tech_regs[] PROGMEM = {
|
||||
{REG_CONNECT_WRITE, 48, 19}, // Password unlock
|
||||
{REG_CONNECT_WRITE, 1, 512}, // Manual mode
|
||||
{REG_MAX_FREQ_READ, 62, 0}, // Max frequency
|
||||
{REG_FREQ_SET, 40, 0}, // Frequency
|
||||
{REG_STOP_WRITE, 1, 4}, // Stop drive
|
||||
{REG_FWD_WRITE, 1, 128}, // Forward
|
||||
{REG_FWD_WRITE, 1, 8}, // Start drive
|
||||
{REG_REV_WRITE, 1, 64}, // Reverse
|
||||
{REG_REV_WRITE, 1, 8}, // Start drive
|
||||
{REG_FREQ_ACTECH_READ, 24, 0}, // Actual freq
|
||||
{REG_DISCONNECT_WRITE, 1, 2}, // Lock controls and parameters
|
||||
{REG_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
const vfd_reg_t nowforever_regs[] PROGMEM = {
|
||||
{REG_MAX_FREQ_READ, 7, 0}, // Max frequency
|
||||
{REG_FREQ_SET, 2305, 0}, // Frequency
|
||||
{REG_STOP_WRITE, 2304, 0}, // Stop drive
|
||||
{REG_FWD_WRITE, 2304, 1}, // Forward
|
||||
{REG_REV_WRITE, 2304, 3}, // Reverse
|
||||
{REG_FREQ_READ, 1282, 0}, // Output freq
|
||||
{REG_STATUS_READ, 768, 0}, // Status
|
||||
{REG_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
const vfd_reg_t delta_vfd015m21a_regs[] PROGMEM = {
|
||||
{REG_CONNECT_WRITE, 0x2002, 2}, // Reset fault
|
||||
{REG_MAX_FREQ_READ, 3, 0}, // Max frequency
|
||||
{REG_FREQ_SET, 0x2001, 0}, // Frequency
|
||||
{REG_STOP_WRITE, 0x2000, 1}, // Stop drive
|
||||
{REG_FWD_WRITE, 0x2000, 18}, // Forward
|
||||
{REG_REV_WRITE, 0x2000, 34}, // Reverse
|
||||
{REG_FREQ_READ, 0x2103, 0}, // Output freq
|
||||
{REG_STATUS_READ, 0x2100, 0}, // Status
|
||||
{REG_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
const vfd_reg_t yl600_regs[] PROGMEM = {
|
||||
{REG_CONNECT_WRITE, 0x2000, 128}, // Reset all errors
|
||||
{REG_MAX_FREQ_READ, 0x0004, 0}, // Max frequency
|
||||
{REG_FREQ_SET, 0x2001, 0}, // Frequency
|
||||
{REG_STOP_WRITE, 0x2000, 1}, // Stop drive
|
||||
{REG_FWD_WRITE, 0x2000, 18}, // Forward
|
||||
{REG_REV_WRITE, 0x2000, 34}, // Reverse
|
||||
{REG_FREQ_READ, 0x200b, 0}, // Output freq
|
||||
{REG_STATUS_READ, 0x2008, 0}, // Status
|
||||
{REG_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
const vfd_reg_t fr_d700_regs[] PROGMEM = {
|
||||
{REG_MAX_FREQ_READ, 1000, 0}, // Max frequency
|
||||
{REG_FREQ_SET, 13, 0}, // Frequency
|
||||
{REG_STOP_WRITE, 8, 1}, // Stop drive
|
||||
{REG_FWD_WRITE, 8, 2}, // Forward
|
||||
{REG_REV_WRITE, 8, 4}, // Reverse
|
||||
{REG_FREQ_READ, 200, 0}, // Output freq
|
||||
{REG_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
const vfd_reg_t sunfar_e300_regs[] PROGMEM = {
|
||||
{REG_CONNECT_WRITE, 0x1001, 32}, // Reset all errors
|
||||
{REG_MAX_FREQ_READ, 0xf004, 0}, // Max frequency F0.4
|
||||
{REG_FREQ_SET, 0x1002, 0}, // Frequency
|
||||
{REG_STOP_WRITE, 0x1001, 3}, // Stop drive
|
||||
{REG_FWD_WRITE, 0x1001, 1}, // Forward
|
||||
{REG_REV_WRITE, 0x1001, 2}, // Reverse
|
||||
{REG_FREQ_READ, 0xd000, 0}, // Output freq d.0
|
||||
{REG_STATUS_READ, 0x2000, 0}, // Status
|
||||
{REG_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
const vfd_reg_t omron_mx2_regs[] PROGMEM = {
|
||||
{REG_CONNECT_WRITE, 0x1200, 3}, // A001 Frequency reference modbus
|
||||
{REG_CONNECT_WRITE, 0x1201, 3}, // A002 Run command modbus
|
||||
{REG_MAX_FREQ_FIXED, 0, 40000}, // TODO Want to use A004 max frequency
|
||||
{REG_FREQ_SET, 0x0001, 0}, // F001 Frequency
|
||||
{REG_STOP_WRITE, 0x1f00, 0}, // Stop drive
|
||||
{REG_FWD_WRITE, 0x1f00, 2}, // Forward
|
||||
{REG_REV_WRITE, 0x1f00, 6}, // Reverse
|
||||
{REG_FREQ_READ, 0x1001, 0}, // D001 Output freq
|
||||
{REG_STATUS_READ, 0x0004, 0}, // Status A
|
||||
{REG_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
const vfd_reg_t v70_regs[] PROGMEM = {
|
||||
{REG_MAX_FREQ_READ, 0x0005, 0}, // Maximum operating frequency
|
||||
{REG_FREQ_SET, 0x0201, 0}, // Set frequency in 0.1Hz
|
||||
{REG_STOP_WRITE, 0x0200, 0}, // Stop
|
||||
{REG_FWD_WRITE, 0x0200, 1}, // Run forward
|
||||
{REG_REV_WRITE, 0x0200, 5}, // Run reverse
|
||||
{REG_FREQ_READ, 0x0220, 0}, // Read operating frequency
|
||||
{REG_STATUS_READ, 0x0210, 0}, // Read status
|
||||
{REG_DISABLED},
|
||||
};
|
||||
|
||||
|
||||
static vfd_reg_t regs[VFDREG];
|
||||
static vfd_reg_t custom_regs[VFDREG];
|
||||
|
||||
static struct {
|
||||
vfd_reg_type_t state;
|
||||
int8_t reg;
|
||||
uint8_t read_count;
|
||||
bool changed;
|
||||
bool shutdown;
|
||||
|
||||
float power;
|
||||
uint16_t max_freq;
|
||||
bool user_multi_write;
|
||||
float actual_power;
|
||||
uint16_t status;
|
||||
|
||||
uint32_t wait;
|
||||
deinit_cb_t deinit_cb;
|
||||
} vfd;
|
||||
|
||||
|
||||
static void _disconnected() {
|
||||
modbus_deinit();
|
||||
if (vfd.deinit_cb) vfd.deinit_cb();
|
||||
vfd.deinit_cb = 0;
|
||||
}
|
||||
|
||||
|
||||
static bool _next_state() {
|
||||
switch (vfd.state) {
|
||||
case REG_MAX_FREQ_FIXED:
|
||||
if (!vfd.power) vfd.state = REG_STOP_WRITE;
|
||||
else vfd.state = REG_FREQ_SET;
|
||||
break;
|
||||
|
||||
case REG_FREQ_SIGN_SET:
|
||||
if (vfd.power < 0) vfd.state = REG_REV_WRITE;
|
||||
else if (0 < vfd.power) vfd.state = REG_FWD_WRITE;
|
||||
else vfd.state = REG_STOP_WRITE;
|
||||
break;
|
||||
|
||||
case REG_STOP_WRITE: case REG_FWD_WRITE: case REG_REV_WRITE:
|
||||
vfd.state = REG_FREQ_READ;
|
||||
break;
|
||||
|
||||
case REG_STATUS_READ:
|
||||
if (vfd.shutdown || estop_triggered()) vfd.state = REG_DISCONNECT_WRITE;
|
||||
|
||||
else if (vfd.changed) {
|
||||
// Update frequency and state
|
||||
vfd.changed = false;
|
||||
vfd.state = REG_MAX_FREQ_READ;
|
||||
|
||||
} else {
|
||||
// Continue querying after delay
|
||||
vfd.state = REG_FREQ_READ;
|
||||
vfd.wait = rtc_get_time() + VFD_QUERY_DELAY;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case REG_DISCONNECT_WRITE:
|
||||
_disconnected();
|
||||
return false;
|
||||
|
||||
default:
|
||||
vfd.state = (vfd_reg_type_t)(vfd.state + 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool _exec_command();
|
||||
|
||||
|
||||
static void _next_reg() {
|
||||
while (true) {
|
||||
vfd.reg++;
|
||||
|
||||
if (vfd.reg == VFDREG) {
|
||||
vfd.reg = -1;
|
||||
vfd.read_count = 0;
|
||||
if (!_next_state()) break;
|
||||
|
||||
} else if (regs[vfd.reg].type == vfd.state && _exec_command()) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _connect() {
|
||||
vfd.state = REG_CONNECT_WRITE;
|
||||
vfd.reg = -1;
|
||||
_next_reg();
|
||||
}
|
||||
|
||||
|
||||
static void _modbus_cb(bool ok, uint16_t addr, uint16_t value) {
|
||||
// Handle error
|
||||
if (!ok) {
|
||||
if (regs[vfd.reg].fails < 255) regs[vfd.reg].fails++;
|
||||
if (vfd.shutdown || estop_triggered()) _disconnected();
|
||||
else _connect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle read result
|
||||
vfd.read_count++;
|
||||
|
||||
switch (regs[vfd.reg].type) {
|
||||
case REG_MAX_FREQ_READ: vfd.max_freq = value; break;
|
||||
case REG_FREQ_READ: vfd.actual_power = value / (float)vfd.max_freq; break;
|
||||
|
||||
case REG_FREQ_SIGN_READ:
|
||||
vfd.actual_power = (int16_t)value / (float)vfd.max_freq;
|
||||
break;
|
||||
|
||||
case REG_FREQ_ACTECH_READ:
|
||||
if (vfd.read_count == 2) vfd.actual_power = value / (float)vfd.max_freq;
|
||||
if (vfd.read_count < 6) return;
|
||||
break;
|
||||
|
||||
case REG_STATUS_READ: vfd.status = value; break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Next
|
||||
_next_reg();
|
||||
}
|
||||
|
||||
|
||||
static bool _use_multi_write() {
|
||||
switch (spindle_get_type()) {
|
||||
case SPINDLE_TYPE_CUSTOM: return vfd.user_multi_write;
|
||||
case SPINDLE_TYPE_NOWFOREVER: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool _exec_command() {
|
||||
if (vfd.wait) return true;
|
||||
|
||||
vfd_reg_t reg = regs[vfd.reg];
|
||||
uint16_t words = 1;
|
||||
bool read = false;
|
||||
bool write = false;
|
||||
|
||||
switch (reg.type) {
|
||||
case REG_DISABLED: break;
|
||||
|
||||
case REG_MAX_FREQ_FIXED: vfd.max_freq = reg.value; break;
|
||||
|
||||
case REG_FREQ_SET:
|
||||
write = true;
|
||||
reg.value = fabs(vfd.power) * vfd.max_freq;
|
||||
break;
|
||||
|
||||
case REG_FREQ_SIGN_SET:
|
||||
write = true;
|
||||
reg.value = vfd.power * vfd.max_freq;
|
||||
break;
|
||||
|
||||
case REG_CONNECT_WRITE:
|
||||
case REG_STOP_WRITE:
|
||||
case REG_FWD_WRITE:
|
||||
case REG_REV_WRITE:
|
||||
case REG_DISCONNECT_WRITE:
|
||||
write = true;
|
||||
break;
|
||||
|
||||
case REG_FREQ_ACTECH_READ:
|
||||
words = 6;
|
||||
|
||||
case REG_FREQ_READ:
|
||||
case REG_FREQ_SIGN_READ:
|
||||
case REG_MAX_FREQ_READ:
|
||||
case REG_STATUS_READ:
|
||||
read = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (read) modbus_read(reg.addr, words, _modbus_cb);
|
||||
else if (write) (_use_multi_write() ? modbus_multi_write : modbus_write)
|
||||
(reg.addr, reg.value, _modbus_cb);
|
||||
else return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void _load(const vfd_reg_t *_regs) {
|
||||
memset(®s, 0, sizeof(regs));
|
||||
|
||||
for (int i = 0; i < VFDREG; i++) {
|
||||
regs[i].type = (vfd_reg_type_t)pgm_read_byte(&_regs[i].type);
|
||||
if (!regs[i].type) break;
|
||||
regs[i].addr = pgm_read_word(&_regs[i].addr);
|
||||
regs[i].value = pgm_read_word(&_regs[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void vfd_spindle_init() {
|
||||
memset(&vfd, 0, sizeof(vfd));
|
||||
for (int i = 0; i < VFDREG; i++) regs[i].fails = 0;
|
||||
modbus_init();
|
||||
|
||||
switch (spindle_get_type()) {
|
||||
case SPINDLE_TYPE_CUSTOM: memcpy(regs, custom_regs, sizeof(regs)); break;
|
||||
case SPINDLE_TYPE_AC_TECH: _load(ac_tech_regs); break;
|
||||
case SPINDLE_TYPE_NOWFOREVER: _load(nowforever_regs); break;
|
||||
case SPINDLE_TYPE_DELTA_VFD015M21A: _load(delta_vfd015m21a_regs); break;
|
||||
case SPINDLE_TYPE_YL600: _load(yl600_regs); break;
|
||||
case SPINDLE_TYPE_FR_D700: _load(fr_d700_regs); break;
|
||||
case SPINDLE_TYPE_SUNFAR_E300: _load(sunfar_e300_regs); break;
|
||||
case SPINDLE_TYPE_OMRON_MX2: _load(omron_mx2_regs); break;
|
||||
case SPINDLE_TYPE_V70: _load(v70_regs); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
_connect();
|
||||
}
|
||||
|
||||
|
||||
void vfd_spindle_deinit(deinit_cb_t cb) {
|
||||
vfd.shutdown = true;
|
||||
vfd.deinit_cb = cb;
|
||||
}
|
||||
|
||||
|
||||
void vfd_spindle_set(float power) {
|
||||
if (vfd.power != power)
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
vfd.power = power;
|
||||
vfd.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float vfd_spindle_get() {return vfd.actual_power;}
|
||||
uint16_t vfd_get_status() {return vfd.status;}
|
||||
|
||||
|
||||
void vfd_spindle_rtc_callback() {
|
||||
if (!vfd.wait || !rtc_expired(vfd.wait)) return;
|
||||
vfd.wait = 0;
|
||||
_next_reg();
|
||||
}
|
||||
|
||||
|
||||
// Variable callbacks
|
||||
uint16_t get_vfd_max_freq() {return vfd.max_freq;}
|
||||
void set_vfd_max_freq(uint16_t max_freq) {vfd.max_freq = max_freq;}
|
||||
bool get_vfd_multi_write() {return vfd.user_multi_write;}
|
||||
void set_vfd_multi_write(bool value) {vfd.user_multi_write = value;}
|
||||
uint8_t get_vfd_reg_type(int reg) {return regs[reg].type;}
|
||||
|
||||
|
||||
void set_vfd_reg_type(int reg, uint8_t type) {
|
||||
custom_regs[reg].type = (vfd_reg_type_t)type;
|
||||
if (spindle_get_type() == SPINDLE_TYPE_CUSTOM)
|
||||
regs[reg].type = custom_regs[reg].type;
|
||||
vfd.changed = true;
|
||||
}
|
||||
|
||||
|
||||
uint16_t get_vfd_reg_addr(int reg) {return regs[reg].addr;}
|
||||
|
||||
|
||||
void set_vfd_reg_addr(int reg, uint16_t addr) {
|
||||
custom_regs[reg].addr = addr;
|
||||
if (spindle_get_type() == SPINDLE_TYPE_CUSTOM)
|
||||
regs[reg].addr = custom_regs[reg].addr;
|
||||
vfd.changed = true;
|
||||
}
|
||||
|
||||
|
||||
uint16_t get_vfd_reg_val(int reg) {return regs[reg].value;}
|
||||
|
||||
|
||||
void set_vfd_reg_val(int reg, uint16_t value) {
|
||||
custom_regs[reg].value = value;
|
||||
if (spindle_get_type() == SPINDLE_TYPE_CUSTOM)
|
||||
regs[reg].value = custom_regs[reg].value;
|
||||
vfd.changed = true;
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_vfd_reg_fails(int reg) {return regs[reg].fails;}
|
||||
|
||||
|
||||
void set_vfd_reg_fails(int reg, uint8_t value) {
|
||||
regs[reg].fails = value;
|
||||
}
|
||||
38
src/avr/src/vfd_spindle.h
Normal file
38
src/avr/src/vfd_spindle.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spindle.h"
|
||||
|
||||
|
||||
void vfd_spindle_init();
|
||||
void vfd_spindle_deinit(deinit_cb_t cb);
|
||||
void vfd_spindle_set(float power);
|
||||
float vfd_spindle_get();
|
||||
uint16_t vfd_get_status();
|
||||
void vfd_spindle_rtc_callback();
|
||||
20
src/avr/step-test/Makefile
Normal file
20
src/avr/step-test/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Makefile for Bulidbotics step-test
|
||||
PROJECT = step-test
|
||||
MCU = atxmega192a3u
|
||||
CLOCK = 32000000
|
||||
|
||||
# SRC
|
||||
SRC = usart.c lcd.c pins.c hardware.c
|
||||
SRC := $(wildcard *.c) $(patsubst %,../src/%,$(SRC))
|
||||
|
||||
include ../Makefile.common
|
||||
|
||||
CFLAGS += -I../src -I.
|
||||
|
||||
# Build
|
||||
all: $(PROJECT).hex size
|
||||
|
||||
$(PROJECT).elf: $(SRC)
|
||||
$(CC) $(SRC) $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@
|
||||
|
||||
.PHONY: all
|
||||
187
src/avr/step-test/plot_velocity.py
Normal file
187
src/avr/step-test/plot_velocity.py
Normal file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
################################################################################
|
||||
# #
|
||||
# This file is part of the Buildbotics firmware. #
|
||||
# #
|
||||
# Copyright (c) 2015 - 2018, Buildbotics LLC #
|
||||
# All rights reserved. #
|
||||
# #
|
||||
# This file ("the software") is free software: you can redistribute it #
|
||||
# and/or modify it under the terms of the GNU General Public License, #
|
||||
# version 2 as published by the Free Software Foundation. You should #
|
||||
# have received a copy of the GNU General Public License, version 2 #
|
||||
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# The software is distributed in the hope that it will be useful, but #
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
|
||||
# Lesser General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU Lesser General Public #
|
||||
# License along with the software. If not, see #
|
||||
# <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# For information regarding this software email: #
|
||||
# "Joseph Coffland" <joseph@buildbotics.com> #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
import sys, serial, argparse
|
||||
import numpy as np
|
||||
import math
|
||||
import json
|
||||
from time import sleep
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.animation as animation
|
||||
|
||||
|
||||
MM_PER_STEP = 5 * 1.8 / 360 / 32
|
||||
SAMPLES_PER_MIN = 6000
|
||||
|
||||
|
||||
class Plot:
|
||||
def __init__(self, port, baud, max_len):
|
||||
# Open serial port
|
||||
self.sp = serial.Serial(port, baud)
|
||||
|
||||
# Create data series
|
||||
self.series = []
|
||||
for i in range(5):
|
||||
self.series.append(deque([0.0] * max_len))
|
||||
|
||||
# Init vars
|
||||
self.max_len = max_len
|
||||
self.incoming = ''
|
||||
self.last = [None] * 4
|
||||
|
||||
# Open velocity log
|
||||
ts = datetime.now().strftime('%Y-%m-%d-%H:%M:%S')
|
||||
self.log = open('velocity-%s.log' % ts, 'w')
|
||||
|
||||
|
||||
# Add new series data
|
||||
def add(self, i, value):
|
||||
self.series[i].pop()
|
||||
self.series[i].appendleft(value)
|
||||
|
||||
|
||||
def update_text(self, text, vel, data):
|
||||
text[4].set_text('V {0:8,.2f}'.format(vel))
|
||||
|
||||
for i in range(4):
|
||||
text[i].set_text('{} {:11,}'.format('XYZA'[i], int(data[i])))
|
||||
|
||||
|
||||
def update(self, frame, axes, text):
|
||||
# Read new data
|
||||
try:
|
||||
data = self.sp.read(self.sp.in_waiting)
|
||||
self.incoming += data.decode('utf-8')
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
|
||||
while True:
|
||||
# Parse lines
|
||||
i = self.incoming.find('\n')
|
||||
if i == -1: break
|
||||
line = self.incoming[0:i]
|
||||
self.incoming = self.incoming[i + 1:]
|
||||
|
||||
# Handle reset
|
||||
if line.find('RESET') != -1:
|
||||
self.update_text(text, 0, [0] * 4)
|
||||
self.log.write(line + '\n')
|
||||
self.last = [None] * 4
|
||||
continue
|
||||
|
||||
# Parse data
|
||||
try:
|
||||
data = [float(value) for value in line.split(',')]
|
||||
except ValueError: continue
|
||||
|
||||
if len(data) != 4: continue
|
||||
|
||||
# Compute axis velocities
|
||||
v = [] # Axis velocity
|
||||
totalV = 0 # Tool velocity
|
||||
|
||||
for i in range(4):
|
||||
if self.last[i] is not None:
|
||||
delta = self.last[i] - data[i]
|
||||
v.append(delta * MM_PER_STEP * SAMPLES_PER_MIN) # mm/min
|
||||
totalV += math.pow(v[i], 2)
|
||||
|
||||
self.last[i] = data[i]
|
||||
|
||||
# Compute tool velocity
|
||||
totalV = math.sqrt(totalV)
|
||||
|
||||
# Update position and velocity text
|
||||
self.update_text(text, totalV, data)
|
||||
|
||||
# Don't update plots when not moving
|
||||
if totalV == 0: continue
|
||||
|
||||
# Add new data
|
||||
for i in range(4): self.add(i, v[i])
|
||||
self.add(4, totalV)
|
||||
|
||||
# Update plots
|
||||
for i in range(5):
|
||||
axes[i].set_data(range(self.max_len), self.series[i])
|
||||
|
||||
self.log.write(line + '\n')
|
||||
|
||||
|
||||
def close(self):
|
||||
self.sp.flush()
|
||||
self.sp.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Parse command line arguments
|
||||
description = "Plot velocity data in real-time"
|
||||
parser = argparse.ArgumentParser(description = description)
|
||||
parser.add_argument('-p', '--port', default = '/dev/ttyUSB0')
|
||||
parser.add_argument('-b', '--baud', default = 115200, type = int)
|
||||
parser.add_argument('-m', '--max-width', default = 2000, type = int)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Create plot
|
||||
plot = Plot(args.port, args.baud, args.max_width)
|
||||
fig = plt.figure()
|
||||
ax = plt.axes(xlim = (0, args.max_width), ylim = (-10000, 10000))
|
||||
axes = []
|
||||
axes_text = []
|
||||
|
||||
# Setup position and velocity text fields
|
||||
font = dict(fontsize = 14, family = 'monospace')
|
||||
|
||||
axes_text.append(plt.text(0, 11700, '', **font))
|
||||
axes_text.append(plt.text(800, 11700, '', **font))
|
||||
axes_text.append(plt.text(0, 10500, '', **font))
|
||||
axes_text.append(plt.text(800, 10500, '', **font))
|
||||
axes_text.append(plt.text(1500, 11700, '', **font))
|
||||
|
||||
# Create axes
|
||||
for i in range(5):
|
||||
axes.append(ax.plot([], [])[0])
|
||||
# Set text color to match axis color
|
||||
axes_text[i].set_color(axes[i].get_color())
|
||||
|
||||
# Initial text views
|
||||
plot.update_text(axes_text, 0, [0] * 4)
|
||||
|
||||
# Set up animation
|
||||
anim = animation.FuncAnimation(fig, plot.update, fargs = [axes, axes_text],
|
||||
interval = 100)
|
||||
|
||||
# Run
|
||||
plt.show()
|
||||
plot.close()
|
||||
230
src/avr/step-test/step-test.c
Normal file
230
src/avr/step-test/step-test.c
Normal file
@@ -0,0 +1,230 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
#include "hardware.h"
|
||||
#include "usart.h"
|
||||
#include "lcd.h"
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
#define RESET_PIN SPI_MOSI_PIN
|
||||
|
||||
|
||||
void rtc_init() {}
|
||||
|
||||
|
||||
static struct {
|
||||
uint8_t step_pin;
|
||||
uint8_t dir_pin;
|
||||
TC0_t *timer;
|
||||
volatile int16_t high;
|
||||
volatile bool reading;
|
||||
|
||||
} channel[4] = {
|
||||
{STEP_X_PIN, DIR_X_PIN, &TCC0, 0},
|
||||
{STEP_Y_PIN, DIR_Y_PIN, &TCD0, 0},
|
||||
{STEP_Z_PIN, DIR_Z_PIN, &TCE0, 0},
|
||||
{STEP_A_PIN, DIR_A_PIN, &TCF0, 0},
|
||||
};
|
||||
|
||||
|
||||
static int reset = 0;
|
||||
|
||||
|
||||
void channel_reset(int i) {channel[i].timer->CNT = channel[i].high = 0;}
|
||||
|
||||
|
||||
#define EVSYS_CHMUX(CH) (&EVSYS_CH0MUX)[CH]
|
||||
#define EVSYS_CHCTRL(CH) (&EVSYS_CH0CTRL)[CH]
|
||||
|
||||
|
||||
void channel_overflow(int i) {
|
||||
if (IN_PIN(channel[i].dir_pin)) channel[i].high--;
|
||||
else channel[i].high++;
|
||||
channel[i].reading = false;
|
||||
}
|
||||
|
||||
|
||||
ISR(TCC0_OVF_vect) {channel_overflow(0);}
|
||||
ISR(TCD0_OVF_vect) {channel_overflow(1);}
|
||||
ISR(TCE0_OVF_vect) {channel_overflow(2);}
|
||||
ISR(TCF0_OVF_vect) {channel_overflow(3);}
|
||||
|
||||
|
||||
void channel_update_dir(int i) {
|
||||
if (IN_PIN(channel[i].dir_pin)) channel[i].timer->CTRLFSET = TC0_DIR_bm;
|
||||
else channel[i].timer->CTRLFCLR = TC0_DIR_bm;
|
||||
}
|
||||
|
||||
|
||||
ISR(PORTE_INT0_vect) {for (int i = 0; i < 4; i++) channel_update_dir(i);}
|
||||
|
||||
|
||||
int32_t __attribute__ ((noinline)) _channel_read(int i) {
|
||||
return (int32_t)channel[i].high << 16 | channel[i].timer->CNT;
|
||||
}
|
||||
|
||||
|
||||
int32_t channel_read(int i) {
|
||||
while (true) {
|
||||
channel[i].reading = true;
|
||||
|
||||
int32_t x = _channel_read(i);
|
||||
int32_t y = _channel_read(i);
|
||||
|
||||
if (x != y || !channel[i].reading) continue;
|
||||
|
||||
channel[i].reading = false;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void channel_update_enable(int i) {
|
||||
if (IN_PIN(MOTOR_ENABLE_PIN))
|
||||
channel[i].timer->CTRLA = TC_CLKSEL_EVCH0_gc + i;
|
||||
else channel[i].timer->CTRLA = 0;
|
||||
}
|
||||
|
||||
|
||||
ISR(PORTF_INT0_vect) {
|
||||
for (int i = 0; i < 4; i++) channel_update_enable(i);
|
||||
if (!IN_PIN(MOTOR_ENABLE_PIN)) reset = 2;
|
||||
}
|
||||
|
||||
|
||||
ISR(PORTC_INT0_vect) {reset = 32;}
|
||||
|
||||
|
||||
ISR(TCC1_OVF_vect) {
|
||||
if (reset) reset--;
|
||||
|
||||
// Report measured steps
|
||||
static int32_t counts[4] = {0, 0, 0, 0};
|
||||
bool moving = false;
|
||||
bool zero = true;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int32_t count = channel_read(i);
|
||||
if (count != counts[i]) moving = true;
|
||||
if (count) zero = false;
|
||||
counts[i] = count;
|
||||
}
|
||||
|
||||
if (reset && !zero) {
|
||||
for (int i = 0; i < 4; i++) channel_reset(i);
|
||||
printf("RESET\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (moving)
|
||||
printf("%ld,%ld,%ld,%ld\n", counts[0], counts[1], counts[2], counts[3]);
|
||||
}
|
||||
|
||||
|
||||
static void _splash(uint8_t addr) {
|
||||
lcd_init(addr);
|
||||
lcd_goto(addr, 5, 1);
|
||||
lcd_pgmstr(addr, PSTR("Step Test"));
|
||||
}
|
||||
|
||||
|
||||
void channel_init(int i) {
|
||||
uint8_t step_pin = channel[i].step_pin;
|
||||
uint8_t dir_pin = channel[i].dir_pin;
|
||||
|
||||
// Configure I/O
|
||||
DIRCLR_PIN(step_pin);
|
||||
DIRCLR_PIN(dir_pin);
|
||||
PINCTRL_PIN(step_pin) = PORT_SRLEN_bm | PORT_ISC_RISING_gc;
|
||||
PINCTRL_PIN(dir_pin) = PORT_SRLEN_bm | PORT_ISC_BOTHEDGES_gc;
|
||||
|
||||
// Dir change interrupt
|
||||
PIN_PORT(dir_pin)->INTCTRL |= PORT_INT0LVL_HI_gc;
|
||||
PIN_PORT(dir_pin)->INT0MASK |= PIN_BM(dir_pin);
|
||||
|
||||
// Events
|
||||
EVSYS_CHMUX(i) = PIN_EVSYS_CHMUX(step_pin);
|
||||
EVSYS_CHCTRL(i) = EVSYS_DIGFILT_8SAMPLES_gc;
|
||||
|
||||
// Clock
|
||||
channel_update_enable(i);
|
||||
channel[i].timer->INTCTRLA = TC_OVFINTLVL_HI_gc;
|
||||
|
||||
// Set initial clock direction
|
||||
channel_update_dir(i);
|
||||
}
|
||||
|
||||
|
||||
static void init() {
|
||||
cli();
|
||||
|
||||
hw_init();
|
||||
usart_init();
|
||||
|
||||
// Motor channels
|
||||
for (int i = 0; i < 4; i++) channel_init(i);
|
||||
|
||||
// Motor enable
|
||||
DIRCLR_PIN(MOTOR_ENABLE_PIN);
|
||||
PINCTRL_PIN(MOTOR_ENABLE_PIN) =
|
||||
PORT_SRLEN_bm | PORT_ISC_BOTHEDGES_gc | PORT_OPC_PULLUP_gc;
|
||||
PIN_PORT(MOTOR_ENABLE_PIN)->INTCTRL |= PORT_INT0LVL_HI_gc;
|
||||
PIN_PORT(MOTOR_ENABLE_PIN)->INT0MASK |= PIN_BM(MOTOR_ENABLE_PIN);
|
||||
|
||||
// Configure report clock
|
||||
TCC1.INTCTRLA = TC_OVFINTLVL_LO_gc;
|
||||
TCC1.PER = F_CPU / 256 * 0.01; // 10ms
|
||||
TCC1.CTRLA = TC_CLKSEL_DIV256_gc;
|
||||
|
||||
// Reset switch
|
||||
DIRCLR_PIN(RESET_PIN);
|
||||
PINCTRL_PIN(RESET_PIN) =
|
||||
PORT_SRLEN_bm | PORT_ISC_RISING_gc | PORT_OPC_PULLUP_gc;
|
||||
PIN_PORT(RESET_PIN)->INTCTRL |= PORT_INT0LVL_LO_gc;
|
||||
PIN_PORT(RESET_PIN)->INT0MASK |= PIN_BM(RESET_PIN);
|
||||
|
||||
printf("RESET\n");
|
||||
|
||||
sei();
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
_splash(0x27);
|
||||
_splash(0x3f);
|
||||
|
||||
while (true) continue;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user