diff --git a/conf/monocoque.config b/conf/monocoque.config index da7f716..c480c67 100644 --- a/conf/monocoque.config +++ b/conf/monocoque.config @@ -220,6 +220,7 @@ configs = ( config = "None"; baud = 115200; devpath = "/dev/simdev1"; + fanpower = 0.6; }, { @@ -228,6 +229,7 @@ configs = ( config = "None"; baud = 115200; devpath = "/dev/simdev1"; + fanpower = 0.6; }, { diff --git a/src/arduino/simwind_pwm/Makefile b/src/arduino/simwind_pwm/Makefile new file mode 100644 index 0000000..bfd2e90 --- /dev/null +++ b/src/arduino/simwind_pwm/Makefile @@ -0,0 +1,166 @@ +# Makefile for Arduino based scketches +# +# Copyright 2020 Valerio Di Giampietro http://va.ler.io v@ler.io +# MIT License - see License.txt file +# +# This Makefile uses the arduino-cli, the Arduino command line interface +# and has been designed and tested to run on Linux, not on Windows. +# Probably it will run on a Mac, but it has not been tested. +# +# Please note that: +# +# 1. each sketch must reside in his own folder with this Makefile +# +# 2. the main make targets are: +# - all compiles and upload +# - compile compiles only +# - upload upload via serial port, compile if the binary file is +# not available +# - ota upload Over The Air, automatically find the device +# IP address using the IOT_NAME (device hostname) +# - clean clean the build directory +# - find find OTA updatable devices on the local subnet +# - requirements it the file "requirements.txt" exists it will +# install the libraries listed in this file +# +# default is "all" +# +# 3. it gets the name of the sketch using the wildcard make command; +# the name is *.ino; this means that you must have ONLY a file +# with .ino extension, otherwise this makefile will break. This +# also means that you can use this Makefile, almost unmodified, +# for any sketch as long as you keep a single .ino file in each +# folder +# +# 4. you can split your project in multiple files, if you wish, +# using a single .ino file and multiple .h files, that you can +# include in the .ino file with an '#include "myfile.h"' +# directive +# +# Optionally some environment variables can be set: +# +# FQBN Fully Qualified Board Name; if not set in the environment +# it will be assigned a value in this makefile +# +# SERIAL_DEV Serial device to upload the sketch; if not set in the +# environment it will be assigned: +# /dev/ttyUSB0 if it exists, or +# /dev/ttyACM0 if it exists, or +# unknown +# +# IOT_NAME Name of the IOT device; if not set in the environment +# it will be assigned a value in this makefile. This is +# very useful for OTA update, the device will be searched +# on the local subnet using this name +# +# OTA_PORT Port used by OTA update; if not set in the environment +# it will be assigned the default value of 8266 in this +# makefile +# +# OTA_PASS Password used for OTA update; if not set in the environment +# it will be assigned the default value of an empty string +# +# V verbose flag; can be 0 (quiet) or 1 (verbose); if not set +# in the environment it will be assigned a default value +# in this makefile + + +MAKE_DIR := $(PWD) +# +# ----- setup wor Wemos D1 mini ----- +#FQBN ?= esp8266:esp8266:d1_mini +#IOT_NAME ?= esp8266-meteo +#OTA_PORT ?= 8266 +#OTA_PASS ?= +# ----- setup for Arduino Uno +FQBN ?= arduino:avr:uno +# ----- --------------------- +V ?= 0 +VFLAG = + +ifeq "$(V)" "1" +VFLAG =-v +endif + +ifndef SERIAL_DEV + ifneq (,$(wildcard /dev/ttyUSB0)) + SERIAL_DEV = /dev/ttyUSB0 + else ifneq (,$(wildcard /dev/ttyACM0)) + SERIAL_DEV = /dev/ttyACM0 + else + SERIAL_DEV = unknown + endif +endif + +BUILD_DIR := $(subst :,.,build/$(FQBN)) + +SRC := $(wildcard *.ino) +HDRS := $(wildcard *.h) +BIN := $(BUILD_DIR)/$(SRC).bin +ELF := $(BUILD_DIR)/$(SRC).elf + +$(info FQBN is [${FQBN}]) +$(info IOT_NAME is [${IOT_NAME}]) +$(info OTA_PORT is [${OTA_PORT}]) +$(info OTA_PASS is [${OTA_PASS}]) +$(info V is [${V}]) +$(info VFLAG is [${VFLAG}]) +$(info MAKE_DIR is [${MAKE_DIR}]) +$(info BUILD_DIR is [${BUILD_DIR}]) +$(info SRC is [${SRC}]) +$(info HDRS is [${HDRS}]) +$(info BIN is [${BIN}]) +$(info SERIAL_DEV is [${SERIAL_DEV}]) + +all: $(ELF) upload +.PHONY: all + +compile: $(ELF) +.PHONY: compile + +$(ELF): $(SRC) $(HDRS) + arduino-cli compile -b $(FQBN) $(VFLAG) + @if which arduino-manifest.pl; \ + then echo "---> Generating manifest.txt"; \ + arduino-manifest.pl -b $(FQBN) $(SRC) $(HDRS) > manifest.txt; \ + else echo "---> If you want to generate manifest.txt, listing used libraries and their versions,"; \ + echo "---> please install arduino-manifest, see https://github.com/digiampietro/arduino-manifest"; \ + fi + +upload: + @if [ ! -c $(SERIAL_DEV) ] ; \ + then echo "---> ERROR: Serial Device not available, please set the SERIAL_DEV environment variable" ; \ + else echo "---> Uploading sketch\n"; \ + arduino-cli upload -b $(FQBN) -p $(SERIAL_DEV) $(VFLAG); \ + fi + +ota: + @PLAT_PATH=`arduino-cli compile -b $(FQBN) --show-properties | grep '^runtime.platform.path' | awk -F= '{print $$2}'` ; \ + PY_PATH=`arduino-cli compile -b $(FQBN) --show-properties | grep '^runtime.tools.python3.path' | awk -F= '{print $$2}'` ; \ + IOT_IP=`avahi-browse _arduino._tcp --resolve --parsable --terminate|grep -i ';$(IOT_NAME);'|grep ';$(OTA_PORT);'| awk -F\; '{print $$8}'|head -1`; \ + BINFILE=$(wildcard $(BUILD_DIR)/$(SRC)*bin); \ + echo "PLAT_PATH is [$$PLAT_PATH]" ; \ + echo "PY_PATH: is [$$PY_PATH]" ; \ + echo "IOT_IP: is [$$IOT_IP]" ; \ + echo "BINFILE: is [$$BINFILE]" ; \ + if [ "$$IOT_IP" = "" ] ; \ + then echo "Unable to find device IP. Check that the IOT_NAME environment variable is correctly set. Use 'make find' to search devices"; \ + else echo "---> Uploading Over The Air"; \ + $$PY_PATH/python3 $$PLAT_PATH/tools/espota.py -i $$IOT_IP -p $(OTA_PORT) --auth=$(OTA_PASS) -f $$BINFILE ;\ + fi + +clean: + @echo "---> Cleaning the build directory" + rm -rf build + +find: + avahi-browse _arduino._tcp --resolve --parsable --terminate + +requirements: + @if [ -e requirements.txt ]; \ + then while read -r i ; do echo ; \ + echo "---> Installing " '"'$$i'"' ; \ + arduino-cli lib install "$$i" ; \ + done < requirements.txt ; \ + else echo "---> MISSING requirements.txt file"; \ + fi diff --git a/src/arduino/simwind_pwm/simwind.h b/src/arduino/simwind_pwm/simwind.h new file mode 100755 index 0000000..e2f36d0 --- /dev/null +++ b/src/arduino/simwind_pwm/simwind.h @@ -0,0 +1,15 @@ +#ifndef _SIMWINDDATA_H +#define _SIMWINDDATA_H + +#include +#include + +typedef struct +{ + uint8_t velocity; + uint8_t fanpower; +} +SimWindData; + + +#endif diff --git a/src/arduino/simwind_pwm/simwind_pwm.ino b/src/arduino/simwind_pwm/simwind_pwm.ino new file mode 100644 index 0000000..d7fa7b6 --- /dev/null +++ b/src/arduino/simwind_pwm/simwind_pwm.ino @@ -0,0 +1,56 @@ +#include "simwind.h" + +// ============================================================ +// Configuration +// ============================================================ +#define BAUD_RATE 115200 +#define PWM_PIN_1 9 // Fan PWM output pins +#define PWM_PIN_2 10 // +#define PWM_MAX 320 // Max PWM value for 25kHz (16MHz / 25kHz / 2) +#define SPEED_MIN 0 // Min speed (mph) — fans off below this +#define SPEED_MAX 100 // Max speed (mph) — full fan speed at this +#define SPEED_THRESHOLD 5 // Below this speed fans are off +// ============================================================ + +#define BYTE_SIZE sizeof(SimWindData) + +int velocity = 0; +int fanpower = 0; + +void setup() { + Serial.begin(BAUD_RATE); + + // Configure Timer1 for Phase Correct PWM at 25kHz duty cycle + // Mode 10: Phase Correct PWM, TOP = ICR1 + // WGM13:WGM12:WGM11:WGM10 = 1:0:1:0 + TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11); + TCCR1B = _BV(WGM13) | _BV(CS10); // <-- WGM13 restored, no prescaler + ICR1 = PWM_MAX; // TOP = 320 → 16MHz / (2 * 320) = 25kHz + pinMode(PWM_PIN_1, OUTPUT); + pinMode(PWM_PIN_2, OUTPUT); + OCR1A = 0; + OCR1B = 0; +} + +void loop() { + char buff[BYTE_SIZE]; + if (Serial.available() >= BYTE_SIZE) { + if (Serial.readBytes(buff, BYTE_SIZE) != BYTE_SIZE) return; + union { SimWindData data; char bytes[BYTE_SIZE]; } u; + memcpy(u.bytes, buff, BYTE_SIZE); + velocity = u.data.velocity; + fanpower = u.data.fanpower; + } + + int pwm = 0; + if (velocity > SPEED_THRESHOLD) { + // Square root scaling for slightly stronger sensation at lower speeds + // fanpower (0-255) scales the overall intensity + float powerScale = (float)fanpower / 255.0; + pwm = (int)(sqrt((float)velocity / SPEED_MAX) * PWM_MAX * powerScale); + pwm = constrain(pwm, 0, PWM_MAX); + } + + OCR1A = pwm; + OCR1B = pwm; +} diff --git a/src/monocoque/devices/serialdevice.c b/src/monocoque/devices/serialdevice.c index 78f9b18..5c3478c 100644 --- a/src/monocoque/devices/serialdevice.c +++ b/src/monocoque/devices/serialdevice.c @@ -118,8 +118,9 @@ int arduino_simwind_update(SimDevice* this, SimData* simdata) serialdevice->u.simwinddata.velocity = ceil(simdata->velocity*KPHTOMPH); slogt("Updating arduino device speed to %i", serialdevice->u.simwinddata.velocity); - // this can be added to the config, all config dependent can be added to init - serialdevice->u.simwinddata.fanpower = (int)0.6 * 255; + + serialdevice->u.simwinddata.fanpower = (int)(serialdevice->fanpower * 255); + slogt("Sending fanpower: %i (from config: %f)", serialdevice->u.simwinddata.fanpower, serialdevice->fanpower); size_t size = sizeof(SimWindData); arduino_update(serialdevice, &serialdevice->u.simwinddata, size); @@ -318,7 +319,8 @@ SerialDevice* new_serial_device(DeviceSettings* ds, MonocoqueSettings* ms, SimIn case (SIMDEVTYPE_SIMWIND): this->devicetype = ARDUINODEV__SIMWIND; this->m.vtable = &arduino_simwind_vtable; - slogi("Initializing arduino devices for sim wind."); + this->fanpower = ds->serialdevsettings.fanpower; + slogi("Initializing arduino devices for sim wind with fanpower: %f", this->fanpower); break; case (SIMDEVTYPE_ARDUINOCUSTOM): this->devicetype = ARDUINODEV__CUSTOM; @@ -342,6 +344,7 @@ SerialDevice* new_serial_device(DeviceSettings* ds, MonocoqueSettings* ms, SimIn this->u.simhapticdata.effect4 = 0; this->state = 0; this->ampfactor = ds->serialdevsettings.ampfactor; + this->fanpower = ds->serialdevsettings.fanpower; slogi("Initializing arduino device for haptic effects."); break; case (SIMDEVTYPE_SERIALWHEEL): diff --git a/src/monocoque/devices/simdevice.h b/src/monocoque/devices/simdevice.h index cec1329..2ad0a87 100644 --- a/src/monocoque/devices/simdevice.h +++ b/src/monocoque/devices/simdevice.h @@ -66,6 +66,7 @@ typedef struct int endled; int baudrate; double ampfactor; + double fanpower; double state; union { diff --git a/src/monocoque/helper/confighelper.c b/src/monocoque/helper/confighelper.c index b25e86a..b9bea5a 100644 --- a/src/monocoque/helper/confighelper.c +++ b/src/monocoque/helper/confighelper.c @@ -861,7 +861,11 @@ int devsetup(const char* device_type, const char* device_subtype, const char* co found = config_setting_lookup_float(device_settings, "ampfactor", &factor); ds->serialdevsettings.ampfactor = ampfactor; - slogt("set port baud rate to %i, ampfactor %f", baud, ampfactor); + double fanpower = 0.6; + config_setting_lookup_float(device_settings, "fanpower", &fanpower); + ds->serialdevsettings.fanpower = fanpower; + + slogt("set port baud rate to %i, ampfactor %f, fanpower %f", baud, ampfactor, fanpower); } diff --git a/src/monocoque/helper/confighelper.h b/src/monocoque/helper/confighelper.h index 81ac684..2fb27bf 100644 --- a/src/monocoque/helper/confighelper.h +++ b/src/monocoque/helper/confighelper.h @@ -168,6 +168,7 @@ typedef struct int startled; int endled; float ampfactor; + float fanpower; int baud; } SerialDeviceSettings;