diff --git a/nippleremote_firmware/.gitignore b/nippleremote_firmware/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/nippleremote_firmware/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/nippleremote_firmware/include/README b/nippleremote_firmware/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/nippleremote_firmware/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/nippleremote_firmware/lib/README b/nippleremote_firmware/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/nippleremote_firmware/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/nippleremote_firmware/lib/RF24 b/nippleremote_firmware/lib/RF24 new file mode 160000 index 0000000..ebcd0d1 --- /dev/null +++ b/nippleremote_firmware/lib/RF24 @@ -0,0 +1 @@ +Subproject commit ebcd0d1d0b3061fcb57444e1dbe5829ef25705cd diff --git a/nippleremote_firmware/platformio.ini b/nippleremote_firmware/platformio.ini new file mode 100644 index 0000000..4d0ebd1 --- /dev/null +++ b/nippleremote_firmware/platformio.ini @@ -0,0 +1,19 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:pro16MHzatmega328] +platform = atmelavr +board = pro16MHzatmega328 +framework = arduino + +monitor_speed=115200 + +lib_deps= + https://github.com/feklee/arduino-trackpoint \ No newline at end of file diff --git a/nippleremote_firmware/src/main.cpp b/nippleremote_firmware/src/main.cpp new file mode 100644 index 0000000..e8c0dae --- /dev/null +++ b/nippleremote_firmware/src/main.cpp @@ -0,0 +1,541 @@ +#include + + +//from left to right. pins at bottom. chips on top +//1 GND (black) +//2 Data +//3 Clock +//4 Reset +//5 +5V (red) +//6 Right BTN +//7 Middle BTN +//8 Left BTN + +//Arduino Pro Mini 328P 5V 16MHz +//hold power button pressed during flashing + + +//pinout: https://martin-prochnow.de/projects/thinkpad_keyboard + +//see also https://github.com/feklee/usb-trackpoint/blob/master/code/code.ino + +//#define DEBUG + +#include "Trackpoint.h" + +//Default: +/*Trackpoint trackpoint(8, // CLK + 9, // DATA + 12); // RESET +*/ + +/*funktioniert + * Trackpoint trackpoint(2, // CLK (rosa) + 3, // DATA (gelb) + 4); // RESET (gruen) +*/ +Trackpoint trackpoint(3, // CLK (rosa, TP3) + 4, // DATA (gelb, TP2) + 2); // RESET (gruen, TP4) + +#include +#include "nRF24L01.h" +#include "RF24.h" +#include "printf.h" +bool radioOk=false; //true, if sending was successfull. can be false, even if data was send and received + +RF24 radio(9,10); //ce, cs +//SCK D13 +//Miso D12 +//Mosi D11 +// Radio pipe addresses for the 2 nodes to communicate. +const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL }; //0xF0F0F0F0xxLL, xx is 1-byte address +#define NRF24CHANNEL 75 + +struct nrfdata { + uint8_t steer; //between 0 and 255, 127 is stop. will be scaled to -1000 to 1000 + uint8_t speed; //between 0 and 255, 127 is stop. will be scaled to -1000 to 1000 + uint8_t commands; //bit 0 set = motor enable + uint8_t checksum; +}; + +long last_sendNRF=0; +#define NRFSEND_DELAY 20 //ms + +#define PIN_TOUCH 5 +long last_touch=0; +#define TOUCH_TIMEOUT 100 + +//command variables +boolean motorenabled=false; + +#define PIN_LED A1 + +#define PIN_BUTTON 6 + +#define PIN_POWERON 7 + +#define TRACKPOINT_MAX 70 //value for maximum stick movement +float speedscale=0.0; +float steerscale=0.0; + +int16_t last_xin=0; +int16_t last_yin=0; +int16_t xin_smooth=0; +int16_t yin_smooth=0; +int16_t maxacc=0; +int16_t maxacc_brake=0; +int16_t maxaccsteer=0; +int16_t maxaccsteer_brake=0; + +#define SETUP_NONE 0 +#define SETUP_WAIT 1 //waiting for input +#define SETUP_DONE 2 //waiting after input (do not move motors) +uint8_t setupmode=SETUP_NONE; +long setupmode_waitstarttime=0; //starttime of SETUP_WAIT mode +#define SETUP_WAIT_TIMEOUT 10000 //maximum time to wait for input before canceling +#define SETUP_HOLD_POWEROFF 2000 //if button held down after x ms in setup mode, power off +#define SETUP_DONE_TIME 1000 //time to keep motors disabled after exiting setup +#define SETUP_MOVE_THRESHOLD 275 //500*TRACKPOINT_MAX/127 +uint8_t speedmode=1; //0 (slow), 1(medium), 2(fast) +#define SETUP_SPEEDMODE_MAX 2 + +uint16_t led_ton=0; //never. time in ms for on time +uint16_t led_toff=65535; //always +long led_nextswitch=0; + +#define TIME_INACTIVITY_POWEROFF 120000 +long time_lastactivity=0; +#define ACTIVITYMOVEMENT 5//Stick movement for activity recognition + +boolean touching=false; + +int voltage=4000; +#define VOLTAGE_WARN 3400 +/* + * 3681=3.725V +*/ + +void sendRF(nrfdata senddata); +long readVcc(); +void setup_updateSpeedmode(); + +void setup() { + //Mouse.begin(); + + pinMode(PIN_TOUCH, INPUT_PULLUP); + pinMode(PIN_LED, OUTPUT); + pinMode(PIN_BUTTON, INPUT_PULLUP); + pinMode(PIN_POWERON, OUTPUT); + digitalWrite(PIN_LED, LOW); + + digitalWrite(PIN_POWERON, HIGH); //keep unit powered on + + + Serial.begin(115200); + printf_begin(); + Serial.println("Booting"); + + + + radio.begin(); + //Serial.print("CRC Length="); + //Serial.println(radio.getCRCLength()); + + radio.setDataRate( RF24_250KBPS ); //set to slow data rate. default was 1MBPS + //radio.setDataRate( RF24_1MBPS ); + + radio.setChannel(NRF24CHANNEL); //0 to 124 (inclusive) + + radio.setRetries(15,15); // optionally, increase the delay between retries & # of retries + radio.setPayloadSize(8); // optionally, reduce the payload size. seems to improve reliability + + //radio.openWritingPipe(pipes[0]); //write on pipe 0 + //radio.openReadingPipe(1,pipes[1]); //read on pipe 1 + + radio.openWritingPipe(pipes[1]); //write on pipe 1 + radio.openReadingPipe(1,pipes[0]); //read on pipe 0 + + radio.printDetails(); + + radio.startListening(); + + #ifdef DEBUG + Serial.println("Radio initialized"); + #endif + + trackpoint.reset(); + trackpoint.setRemoteMode(); + trackpoint.setSensitivityFactor(0xc0); // more sensitive than by default + + #ifdef DEBUG + Serial.println("Trackpoint initialized"); + #endif + + voltage=readVcc(); + Serial.print("Voltage="); + Serial.println( voltage, DEC ); + + setup_updateSpeedmode(); //set speeds + +} + + +/* +void sendButtonState(byte state) { + static const char hidStates[] = {MOUSE_LEFT, MOUSE_RIGHT}; + + for (byte i = 0; i < sizeof(hidStates); i++) { + byte hidState = hidStates[i]; + if (state & (1 << i)) { + Mouse.press(hidState); + } else if (Mouse.isPressed(hidState)) { + Mouse.release(hidState); + } + } +} +*/ + +// Reads TrackPoint data and sends data to computer. +void loop() { + + + if (millis()-last_sendNRF >= NRFSEND_DELAY) + { + voltage=readVcc(); //read own voltage + + last_sendNRF=millis(); + trackpoint.readData(); //discard last value. otherwise values are scales way too high + Trackpoint::DataReport d = trackpoint.readData(); //d.x and d.y between 128 to 255=-1000 to 0, and 0 to 127=0 to +1000 + + #ifdef DEBUG + Serial.print("DataReport: "); + Serial.print(d.x); + Serial.print(", "); + Serial.println(d.y); + #endif + + + nrfdata senddata; + + //senddata.steer=map(constrain((uint8_t)(d.x+127),127-TRACKPOINT_MAX,127+TRACKPOINT_MAX) , 127-TRACKPOINT_MAX,127+TRACKPOINT_MAX, 127+(127*steerscale), 127-(127*steerscale) ); //steer + //senddata.speed=map(constrain((uint8_t)(d.y+127),127-TRACKPOINT_MAX,127+TRACKPOINT_MAX) , 127-TRACKPOINT_MAX,127+TRACKPOINT_MAX, 127-(127*speedscale), 127+(127*speedscale) ); //speed + + + //map x and y to -1000 to 1000 + int16_t xin; + if (d.x>=0 && d.x<=127){ //positive range + xin=map(constrain((int16_t)d.x,0,TRACKPOINT_MAX) , 0,TRACKPOINT_MAX, 0, 1000 ); + }else{ //negative range 128(=-1000) to 255(0) + xin=map(constrain((int16_t)d.x,127+TRACKPOINT_MAX,255) , 127+TRACKPOINT_MAX,255, -1000, 0 ); + } + int16_t yin; + if (d.y>=0 && d.y<=127){ //positive range + yin=map(constrain((float)d.y,0,TRACKPOINT_MAX) , 0,TRACKPOINT_MAX, 0, 1000 ); + }else{ //negative range 128(=-1000) to 255(0) + yin=map(constrain((float)d.y,127+TRACKPOINT_MAX,255) , 127+TRACKPOINT_MAX,255, -1000, 0 ); + } + + last_xin=xin; //save position values for other stuff than control + last_yin=yin; + + if (abs(xin)>ACTIVITYMOVEMENT){ + time_lastactivity=millis(); //reset activity timeout + }else if (abs(yin)>ACTIVITYMOVEMENT){ + time_lastactivity=millis(); //reset activity timeout + } + + /* + float r=sqrt((xin*xin) + (yin*yin)); + float phi=atan2(yin,xin); // arc tangent of y/x. 0 is right + + Serial.print(xin,0); + Serial.print(", "); + Serial.print(yin,0); + Serial.print(": "); + Serial.print(r,0); + Serial.print(", "); + Serial.println(phi,4);*/ + + + //xin_smooth=smoothfilter*xin_smooth + (1-smoothfilter)*xin; + //yin_smooth=smoothfilter*yin_smooth + (1-smoothfilter)*yin; + if (maxaccsteer>0){ + // ### X ### + int16_t _xaccel=xin_smooth-xin; + if ((xin_smooth>0 && xin<-2) || (xin_smooth<0 && xin>2) ){ //if actively braking + if (_xaccel<-maxaccsteer_brake){ //limit deceleration + _xaccel=-maxaccsteer_brake; + }else if (_xaccel>maxaccsteer_brake){ + _xaccel=maxaccsteer_brake; + } + }else{ //not braking + if (_xaccel<-maxaccsteer){ //limit acceleration + _xaccel=-maxaccsteer; + }else if (_xaccel>maxaccsteer){ + _xaccel=maxaccsteer; + } + } + xin_smooth-=_xaccel; //update value + + }else{ //no acc limit + xin_smooth=xin; //update immediately + } + if (maxacc>0){ + // ### Y ### + int16_t _yaccel=yin_smooth-yin; + if ((yin_smooth>0 && yin<-2) || (yin_smooth<0 && yin>2) ){ //if actively braking + if (_yaccel<-maxacc_brake){ //limit deceleration + _yaccel=-maxacc_brake; + }else if (_yaccel>maxacc_brake){ + _yaccel=maxacc_brake; + } + }else{ //not braking + if (_yaccel<-maxacc){ //limit acceleration + _yaccel=-maxacc; + }else if (_yaccel>maxacc){ + _yaccel=maxacc; + } + } + yin_smooth-=_yaccel; //update value + }else{ //no acc limit + yin_smooth=yin; + } + + senddata.steer=map(xin_smooth, -1000,1000, 127+(128*steerscale), 127-(127*steerscale) ); //steer + senddata.speed=map(yin_smooth, -1000,1000, 127-(127*speedscale), 127+(128*speedscale) ); //speed + + senddata.commands=0; //reset + + if (!radioOk || setupmode!=SETUP_NONE){ //if last transmission failed or in setup mode + //senddata.steer=127; //stop + //senddata.speed=127; + senddata.commands|= 0 << 0; //motorenabled send false + xin_smooth=0; //reset smooth value + yin_smooth=0; + }else{ + senddata.commands|= motorenabled << 0; //motorenabled is bit 0 + } + #ifdef DEBUG + Serial.print(senddata.steer); + Serial.print(", "); + Serial.println(senddata.speed); + #endif + + + senddata.checksum=(uint8_t)((senddata.steer+3)*(senddata.speed+13)); + sendRF(senddata); + + #ifdef DEBUG + Serial.println( readVcc(), DEC ); + #endif + } + + + if(!digitalRead(PIN_TOUCH)){ //check touch + last_touch=millis(); + } + + + if(millis()-last_touch <= TOUCH_TIMEOUT){ //is touched + if (!touching && setupmode!=SETUP_DONE) { //was false, is touching again (and not during setup_done wait) + Serial.println("touching was false"); + if (last_xin==0 && last_yin==0) { //stick at center position + touching=true; //enable only if stick is at center again + motorenabled=true; + Serial.println("touching reactivated"); + } + }else{ + motorenabled=true; + } + }else{ + touching=false; + motorenabled=false; + } + + if (!digitalRead(PIN_BUTTON) && setupmode==SETUP_NONE){ //Button pressed, and not in setup mode + setupmode=SETUP_WAIT; + setupmode_waitstarttime=millis(); + Serial.println("Entering Setup"); + //digitalWrite(PIN_POWERON, LOW); //Power off + } + + //inactivity poweroff + if (millis()>time_lastactivity+TIME_INACTIVITY_POWEROFF){ + Serial.println("Inactivity Poweroff"); + delay(100); + digitalWrite(PIN_POWERON, LOW); //Power off + } + + switch(setupmode){ + case SETUP_WAIT: + if (millis()>setupmode_waitstarttime+SETUP_WAIT_TIMEOUT){ //waittime over + setupmode=SETUP_NONE; //exit setup + }else if(last_yin > SETUP_MOVE_THRESHOLD){ //y moved up + Serial.print("Moved Up"); + if (speedmode0){ //if not already at minimum + speedmode-=1; + } + setup_updateSpeedmode(); + setupmode=SETUP_DONE; //exit setupmode + setupmode_waitstarttime=millis();//use this value for done timer + }else if(last_xin < -SETUP_MOVE_THRESHOLD){ //y moved left + Serial.print("Moved Left"); + maxacc=20; //the higher the snappier + maxacc_brake=30; + maxaccsteer=30; + maxaccsteer_brake=70; + setupmode=SETUP_DONE; //exit setupmode + setupmode_waitstarttime=millis();//use this value for done timer + }else if(last_xin > SETUP_MOVE_THRESHOLD){ //y moved right + Serial.print("Moved Right"); + maxacc=0; + maxacc_brake=0; + maxaccsteer=0; + maxaccsteer_brake=0; + setupmode=SETUP_DONE; //exit setupmode + setupmode_waitstarttime=millis();//use this value for done timer + }else if (millis()>setupmode_waitstarttime+SETUP_HOLD_POWEROFF && !digitalRead(PIN_BUTTON)){ //if button held down after SETUP_HOLD_POWEROFF + Serial.println("Manual Power Off"); + delay(100); + digitalWrite(PIN_POWERON, LOW); //Power off + } + + + + if (!touching){ //remote got put away (not touch) + Serial.println("Poweroff"); + delay(100); + digitalWrite(PIN_POWERON, LOW); //Power off + } + break; + case SETUP_DONE: + touching=false; + motorenabled=false; + Serial.println("touching set false"); + if (millis()>setupmode_waitstarttime+SETUP_DONE_TIME){ + setupmode=SETUP_NONE; //return to control mode, allows enabling motors + } + break; + } + + + //LED Blink Codes + switch(setupmode){ + case SETUP_NONE: + if (radioOk){ + if (touching){ //=touching + led_ton=500; //always on + led_toff=0; + }else{ + led_ton=1000; //blink slowly regulary + led_toff=1000; + } + }else{ //not connected + if (touching){ //=touching + led_ton=5; //short flash + led_toff=200; + }else{ + led_ton=0; //off + led_toff=500; + } + } + if (voltage<=VOLTAGE_WARN){ + led_ton=25; //flash on fast + led_toff=75; + } + + break; + case SETUP_WAIT: + led_ton=200; //blink fast + led_toff=200; + break; + case SETUP_DONE: + led_ton=20; //blink fast + led_toff=20; + break; + + } + if (millis()>=led_nextswitch){ //Set LED State by timings + if (digitalRead(PIN_LED)){ //led was on + if (led_toff>0){ + digitalWrite(PIN_LED, LOW); //led off + } + led_nextswitch=millis()+led_toff; + }else{ + if (led_ton>0){ + digitalWrite(PIN_LED, HIGH); //led on + } + led_nextswitch=millis()+led_ton; + } + } + + + + + +} + + +void sendRF(nrfdata senddata){ + #ifdef DEBUG + Serial.println("Transmitting..."); + #endif + + radio.stopListening(); //stop listening to be able to transmit + radioOk = radio.write( &senddata, sizeof(nrfdata) ); + if (radioOk){ + #ifdef DEBUG + Serial.println("ok"); + #endif + + }else{ + #ifdef DEBUG + Serial.println("failed"); + #endif + } + radio.startListening(); + +} + + +long readVcc() { + long result; // Read 1.1V reference against AVcc + ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); + delay(2); // Wait for Vref to settle + ADCSRA |= _BV(ADSC); // Convert + while (bit_is_set(ADCSRA,ADSC)); + result = ADCL; + result |= ADCH<<8; + result = 1126400L / result; // Back-calculate AVcc in mV + return result; +} + +void setup_updateSpeedmode(){ + switch(speedmode){ + case 0: //slow + speedscale=0.15; + steerscale=0.2; + break; + case 1: //medium + speedscale=0.4; + steerscale=0.45; + break; + case 2: //fast + speedscale=1.0; + steerscale=0.8; + break; + default: + speedscale=0.1; + steerscale=0.1; + break; + } +} \ No newline at end of file diff --git a/nippleremote_firmware/src/printf.h b/nippleremote_firmware/src/printf.h new file mode 100644 index 0000000..40fabf8 --- /dev/null +++ b/nippleremote_firmware/src/printf.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2011 J. Coliz + + This program 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. + */ + +/** + * @file printf.h + * + * Setup necessary to direct stdout to the Arduino Serial library, which + * enables 'printf' + */ + +#ifndef __PRINTF_H__ +#define __PRINTF_H__ + +#ifdef ARDUINO + +int serial_putc( char c, FILE * ) +{ + Serial.write( c ); + + return c; +} + +void printf_begin(void) +{ + fdevopen( &serial_putc, 0 ); +} + +#else +#error This example is only for use on Arduino. +#endif // ARDUINO + +#endif // __PRINTF_H__ \ No newline at end of file diff --git a/nippleremote_firmware/nippleremote_firmware.ino b/nonpio_nippleremote_firmware/nippleremote_firmware.ino similarity index 100% rename from nippleremote_firmware/nippleremote_firmware.ino rename to nonpio_nippleremote_firmware/nippleremote_firmware.ino diff --git a/nippleremote_firmware/printf.h b/nonpio_nippleremote_firmware/printf.h similarity index 100% rename from nippleremote_firmware/printf.h rename to nonpio_nippleremote_firmware/printf.h