commit c667006ca0be3ad8036dd7d6d2195d5fcc704c55 Author: Peter Cai Date: Thu Jun 10 12:56:57 2021 +0800 initial commit: basic control working diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eef5389 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +out \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cde4b4a --- /dev/null +++ b/Makefile @@ -0,0 +1,106 @@ +# Makefile to build an Arduino project +# Thanks to +# Determine path of arduino core libraries +ARDUINO_HW := /usr/share/arduino/hardware +# On Arch Linux, the path is /usr/share/arduino/hardware/archlinux-arduino, so +# we use a wildcard to handle this case +# TODO: allow overriding ARDUINO_LIB from env variables +ARDUINO_LIB := $(firstword $(wildcard ${ARDUINO_HW}/*arduino)) + +# Arduino core libraries +VPATH := \ + ${ARDUINO_LIB}/avr/cores/arduino \ + ${ARDUINO_LIB}/avr/libraries/Wire/src \ + +VARIANTS := ${ARDUINO_LIB}/avr/variants/standard +# Only used for VSCode Intellisense configuration +AVR_INC := /usr/avr/include + +# Include all subdirectories from libraries we need +VPATH := $(foreach path, ${VPATH}, ${path} $(shell find ${path} -type d)) + +# Add the local directory into VPATH. Potential local subdirectories also go here +# This is to prevent the command above from including directories like .git into +# the VPATH list +VPATH += ${PWD} \ + +# Build parameters +BUILD_DIR := out +CC := avr-gcc +CXX := avr-g++ +OBJCOPY := avr-objcopy +AVRDUDE := avrdude +MMCU := -mmcu=atmega328p +INCS := \ + $(foreach path, ${VPATH}, -I ${path}) \ + $(foreach path, ${VARIANTS}, -I ${path}) \ + +CFLAGS := -Os -DF_CPU=16000000UL ${MMCU} ${INCS} +# Generate sections for each function and variable +# so that LD can eliminate unused functions and variables +CFLAGS += -ffunction-sections -fdata-sections +# GCC LD options to eliminate unused sections +LDFLAGS := ${MMCU} -Wl,--gc-sections + +# List all source files +SOURCES := $(foreach path, $(VPATH), \ + $(wildcard ${path}/*.c) $(wildcard ${path}/*.cpp) $(wildcard ${path}/*.S)) +# Remove main.cpp from the Arduino core libraries (instead of the local project) +# This allows us to use the proper main() function instead of setup() and loop() +SOURCES := $(filter-out ${ARDUINO_HW}%main.cpp, ${SOURCES}) +# Strip path from the file names, since they are in VPATH, which means +# we don't need to include the full paths +SOURCES := $(foreach file, ${SOURCES}, $(notdir ${file})) +# Translate everything to target object files +OBJS := ${SOURCES} +OBJS := $(patsubst %.c, ${BUILD_DIR}/%.o, ${OBJS}) +OBJS := $(patsubst %.cpp, ${BUILD_DIR}/%.o, ${OBJS}) +OBJS := $(patsubst %.S, ${BUILD_DIR}/%_asm.o, ${OBJS}) + +PROGRAM := arduino-otp + +# Build rules for different source types +${BUILD_DIR}/%.o: %.c + mkdir -p ${BUILD_DIR} + ${CC} ${CFLAGS} -c $< -o $@ + +${BUILD_DIR}/%.o: %.cpp + mkdir -p ${BUILD_DIR} + ${CXX} ${CFLAGS} -c $< -o $@ + +${BUILD_DIR}/%_asm.o: %.S + mkdir -p ${BUILD_DIR} + ${CC} ${CFLAGS} -c $< -o $@ + +${BUILD_DIR}/${PROGRAM}.elf: ${OBJS} + ${CC} ${LDFLAGS} $^ -o $@ + +${BUILD_DIR}/${PROGRAM}.hex: ${BUILD_DIR}/${PROGRAM}.elf + ${OBJCOPY} -O ihex -R .eeprom $< $@ + +define to-json-array + $(shell echo '$(foreach path, $1, "${path}",)' | sed "s/,$$//") +endef + +# vscode intellisense configuration file +# note that vscode will automatically format the generated file +.vscode/c_cpp_properties.json: c_cpp_properties.json.template + mkdir -p .vscode + sed 's@__includePaths__@$(call to-json-array, ${AVR_INC} ${VPATH} ${VARIANTS})@' c_cpp_properties.json.template > .vscode/c_cpp_properties.json + +.PHONY: all clean upload vscode + +all: ${BUILD_DIR}/${PROGRAM}.hex + +clean: + rm -rf ${BUILD_DIR} + +upload: all +ifndef PORT + $(error PORT is undefined) +endif + ${AVRDUDE} -F -V -c arduino -p ATMEGA328P -P ${PORT} -b 115200 -D -U flash:w:${BUILD_DIR}/${PROGRAM}.hex + +vscode: + rm -rf .vscode/c_cpp_properties.json + make .vscode/c_cpp_properties.json \ No newline at end of file diff --git a/c_cpp_properties.json.template b/c_cpp_properties.json.template new file mode 100644 index 0000000..9474bf5 --- /dev/null +++ b/c_cpp_properties.json.template @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "AVR", + "includePath": [__includePaths__], + "intelliSenseMode": "gcc-x64", + "compilerPath": "/usr/bin/avr-g++", + "compilerArgs": [ + "-mmcu=atmega328p" + ], + "defines": [ + "__ATmega328P__", + "__AVR_ATmega328P__" + ] + } + ] +} \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..da0e749 --- /dev/null +++ b/main.cpp @@ -0,0 +1,46 @@ +#include +#include "motor_control.h" + +MotorControl motors[4] = { + MotorControl(5, 2), + MotorControl(6, 3), + MotorControl(7, 4), + MotorControl(13, 12) +}; + +int main() { + init(); + // Enable the motor drivers + pinMode(8, OUTPUT); + digitalWrite(8, LOW); + + // Initialize the controllers + for (int i = 0; i < 4; i++) { + motors[i].Init(); + } + + // Test: play A4 (440 Hz) on each successive motor every second + int cur_motor = -1; + + while (true) { + unsigned long cur_micros = micros(); + + for (int i = 0; i < 4; i++) { + motors[i].Tick(cur_micros); + } + + int new_motor = (cur_micros / 1000 / 1000) % 4; + + if (new_motor != cur_motor) { + motors[new_motor].TickOn(2272); + + if (cur_motor != -1) { + motors[cur_motor].TickOff(); + } + + cur_motor = new_motor; + } + } + + return 0; +} \ No newline at end of file diff --git a/motor_control.cpp b/motor_control.cpp new file mode 100644 index 0000000..dd1313d --- /dev/null +++ b/motor_control.cpp @@ -0,0 +1,48 @@ +#include +#include "motor_control.h" + +MotorControl::MotorControl(int pin_dir, int pin_step) : + pin_dir(pin_dir), pin_step(pin_step), + last_tick_micros(0), tick_period_micros(0) +{ + // No actual constructor logic -- initialization is in Init() +} + +void MotorControl::Init() { + // Initialize the pin state + pinMode(pin_dir, OUTPUT); + pinMode(pin_step, OUTPUT); + digitalWrite(pin_dir, LOW); + digitalWrite(pin_step, LOW); +} + +void MotorControl::TickOn(unsigned long period_micros) { + tick_period_micros = period_micros; + // Force the next tick to happen + last_tick_micros = 0; +} + +void MotorControl::TickOff() { + tick_period_micros = 0; +} + +void MotorControl::Tick(unsigned long cur_micros) { + if (cur_micros == 0) { + return; + } + + if (tick_period_micros == 0) { + return; + } + + if (last_tick_micros == 0 || (cur_micros - last_tick_micros) >= tick_period_micros) { + DoTick(); + last_tick_micros = cur_micros; + } +} + +void MotorControl::DoTick() { + // Pulse the step pin once + digitalWrite(pin_step, HIGH); + digitalWrite(pin_step, LOW); +} \ No newline at end of file diff --git a/motor_control.h b/motor_control.h new file mode 100644 index 0000000..75ae93a --- /dev/null +++ b/motor_control.h @@ -0,0 +1,25 @@ +#pragma once + +class MotorControl { + private: + // Control pins of the stepper motor + int pin_dir, pin_step; + // Timestamp of the last motor tick + unsigned long last_tick_micros; + // Interval of motor ticking; 0 to disable the motor + unsigned long tick_period_micros; + + void DoTick(); + public: + // Constructor + MotorControl(int pin_dir, int pin_step); + // Must be called before using any other functionality (but after enabling the controllers) + void Init(); + // Set the motor to tick at a given interval (inverse of frequency) + void TickOn(unsigned long period_micros); + // Turn off the motor + void TickOff(); + // Perform a tick if necessary + // This should be called from the main event loop + void Tick(unsigned long cur_micros); +}; \ No newline at end of file