├── src ├── main.h ├── axis.h ├── axis.cpp └── main.cpp ├── .gitignore ├── .vscode └── extensions.json ├── platformio.ini ├── test └── README ├── LICENSE ├── lib └── README ├── include └── README └── README.md /src/main.h: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:nanoatmega328] 12 | platform = atmelavr 13 | board = nanoatmega328 14 | framework = arduino 15 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /src/axis.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern int midPoint; 4 | 5 | class Axis{ 6 | public: 7 | int dir; 8 | int step; 9 | 10 | Axis(); 11 | Axis(int dirPin, int stepPin, int deadzone, int slowModeSpeed, bool turboAxis); 12 | void reverse(); 13 | void setDirection(bool newDirection); 14 | void update(int input, bool mode); 15 | 16 | private: 17 | int deadzone; 18 | float range; 19 | int previousState; 20 | bool stepping; 21 | bool reversed; 22 | int slowModeSpeed; 23 | unsigned long timer; 24 | bool turbo; 25 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Copyright 2024 Birdbrain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cheap microscope upgrade project 2 | 3 | This repository contains the Arduino code as well as shell commands for my cheap, educational microscope upgrade project, for which a [video on YouTube also exists](https://www.youtube.com/watch?v=QV-GfkwQU_8). 4 | 5 | The code in here is essentially just a botched way to control three stepper motors from a control panel (of which, two controls are potentiometers). For this project I did not want to go down the rabbithole of trying to add proper computer-numeric control, but maybe once I get myself a proper research microscope I would look into redoing this project and includign computer-numeric control as well. 6 | 7 | ### How to stream 1440p footage from a Raspberry Pi 8 | 9 | One nice part of the upgrades was the addition of a 1440p live camera feed from the microscope to my PC, where I can better view the sample, measure distances as well as record what the microscope sees for videos. 10 | 11 | To do that, I used a Raspberry Pi Cam 3, driven by a Raspberry Pi 3. 12 | 13 | First step is to install Raspbian (Raspberry Pi OS) on the Raspberry Pi. 14 | 15 | Then make a `stream.sh` file in your home directory on the Raspberry Pi with the following contents. 16 | ``` 17 | libcamera-vid -t 0 --inline -o udp://239.42.135.169:5555 --mode 2304:1296:8:P --width 2560 --height 1440 --vflip 1 --framerate 10 --codec mjpeg 18 | ``` 19 | 20 | After that, make a systemd unit on the Raspberry Pi. 21 | ``` 22 | [Unit] 23 | Description=Camera stream 24 | After=multi-user.target 25 | 26 | [Service] 27 | Type=idle 28 | ExecStart=/usr/bin/bash /home/user/stream.sh 29 | 30 | [Install] 31 | WantedBy=multi-user.target 32 | ``` 33 | 34 | Finally you will need to run the following commands on the Raspberry Pi. 35 | 36 | `sudo systemctl daemon-reload` 37 | 38 | `sudo systemctl enable stream` 39 | 40 | And optionally, if you do not wish to restart the Raspberry Pi for the stream to start, run the following on the Raspberry Pi. 41 | 42 | `sudo systemctl start stream` 43 | 44 | And that's it! Now, if you run the following command on a device that is connected to the same local network as the Raspberry Pi, you should be able to see the stream. 45 | 46 | ``` 47 | ffplay udp://239.42.135.169:5555 -fflags nobuffer -flags low_delay -framedrop 48 | ``` -------------------------------------------------------------------------------- /src/axis.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Axis::Axis() { 4 | this->reversed = false; 5 | this->previousState = midPoint; 6 | this->deadzone = 100; 7 | this->timer = 0; 8 | this->slowModeSpeed = 200; 9 | this->turbo = false; 10 | } 11 | 12 | Axis::Axis(int dirPin, int stepPin, int deadzone, int slowModeSpeed, bool turboAxis) { 13 | this->dir = dirPin; 14 | this->step = stepPin; 15 | this->deadzone = deadzone; 16 | this->range = float(midPoint - deadzone); 17 | this->reversed = false; 18 | this->previousState = midPoint; 19 | this->timer = 0; 20 | this->slowModeSpeed = slowModeSpeed; 21 | this->turbo = turboAxis; 22 | } 23 | 24 | void Axis::reverse() { 25 | this->reversed = !this->reversed; 26 | } 27 | 28 | void Axis::setDirection(bool newDirection) { 29 | this->reversed = newDirection; 30 | } 31 | 32 | void Axis::update(int input, bool mode) { 33 | unsigned long currentTime = millis(); 34 | if(this->stepping){ 35 | if(currentTime - this->timer == 0){ return; } 36 | else { 37 | this->stepping = false; 38 | this->timer = currentTime; 39 | digitalWrite(this->step, LOW); 40 | return; 41 | } 42 | } 43 | int amount = input - midPoint; 44 | if(abs(amount) > this->deadzone){ 45 | if(this->reversed){ amount = -amount; } 46 | float scaler = amount / (midPoint - this->deadzone); 47 | scaler = scaler > 0.0 ? 1.0 - scaler : -(1.0 + scaler); 48 | unsigned int stepLength = 1; 49 | if(mode){ 50 | stepLength = this->slowModeSpeed + (int)(abs(scaler) * 1000.0); 51 | } 52 | else{ 53 | stepLength = 1 + (int)(abs(scaler) * (float)slowModeSpeed); 54 | } 55 | if(this->turbo && !mode){ 56 | digitalWrite(this->dir, amount > 0 ? HIGH : LOW); 57 | digitalWrite(this->step, HIGH); 58 | delayMicroseconds(100); 59 | digitalWrite(this->step, LOW); 60 | } 61 | if(currentTime - this->timer > stepLength){ 62 | digitalWrite(this->dir, amount > 0 ? HIGH : LOW); 63 | digitalWrite(this->step, HIGH); 64 | this->timer = currentTime; 65 | this->stepping = true; 66 | } 67 | } 68 | else { return; } 69 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void getInputs(); 5 | void modeInterrupt(); 6 | 7 | const bool zAxisDirection = false; 8 | const int axisDeadzone = 60; 9 | int midPoint = 512; 10 | 11 | int xInPin = A6; 12 | int yInPin = A5; 13 | int zUpPin = 8; 14 | int zDownPin = 9; 15 | int xInvertPin = 6; 16 | int yInvertPin = 7; 17 | int modePin = 2; 18 | 19 | int xDirOut = A1; 20 | int xStepOut = A0; 21 | int yDirOut = A4; 22 | int yStepOut = A3; 23 | int zDirOut = 11; 24 | int zStepOut = 12; 25 | 26 | int xCurrentState = 0; 27 | int yCurrentState = 0; 28 | int zCurrentState = 0; 29 | volatile bool currentMode = false; 30 | 31 | unsigned long xTimer = 0; 32 | unsigned long yTimer = 0; 33 | unsigned long zTimer = 0; 34 | unsigned long continuousTimer = 0; 35 | volatile unsigned long modeTimer = 0; 36 | 37 | bool zUpPressed = false; 38 | bool zDownPressed = false; 39 | bool xUsed = false; 40 | bool yUsed = false; 41 | bool xInverted = false; 42 | bool yInverted = false; 43 | 44 | Axis* xAxis; 45 | Axis* yAxis; 46 | Axis* zAxis; 47 | 48 | 49 | void setup() { 50 | pinMode(xInPin, INPUT); 51 | pinMode(yInPin, INPUT); 52 | pinMode(zUpPin, INPUT_PULLUP); 53 | pinMode(zDownPin, INPUT_PULLUP); 54 | pinMode(xInvertPin, INPUT_PULLUP); 55 | pinMode(yInvertPin, INPUT_PULLUP); 56 | pinMode(modePin, INPUT_PULLUP); 57 | 58 | pinMode(xDirOut, OUTPUT); 59 | pinMode(xStepOut, OUTPUT); 60 | pinMode(yDirOut, OUTPUT); 61 | pinMode(yStepOut, OUTPUT); 62 | pinMode(zDirOut, OUTPUT); 63 | pinMode(zStepOut, OUTPUT); 64 | 65 | xAxis = new Axis(xDirOut, xStepOut, axisDeadzone, 100, false); 66 | yAxis = new Axis(yDirOut, yStepOut, axisDeadzone, 100, false); 67 | zAxis = new Axis(zDirOut, zStepOut, axisDeadzone, 200, true); 68 | zAxis->setDirection(zAxisDirection); 69 | 70 | attachInterrupt(digitalPinToInterrupt(modePin), modeInterrupt, FALLING); 71 | 72 | interrupts(); 73 | } 74 | 75 | void loop() { 76 | while(true){ 77 | getInputs(); 78 | xAxis->update(xCurrentState, currentMode); 79 | yAxis->update(yCurrentState, currentMode); 80 | zAxis->update(zCurrentState, currentMode); 81 | } 82 | } 83 | 84 | void getInputs(){ 85 | xCurrentState = analogRead(xInPin); 86 | yCurrentState = analogRead(yInPin); 87 | zCurrentState = digitalRead(zUpPin) == LOW ? 1024 : digitalRead(zDownPin) == LOW ? 0 : 512; 88 | bool xInvertedIn = digitalRead(xInvertPin) == LOW ? true : false; 89 | if(xInvertedIn != xInverted){ 90 | xInverted = xInvertedIn; 91 | xAxis->reverse(); 92 | } 93 | bool yInvertedIn = digitalRead(yInvertPin) == LOW ? true : false; 94 | if(yInvertedIn != yInverted){ 95 | yInverted = yInvertedIn; 96 | yAxis->reverse(); 97 | } 98 | } 99 | 100 | void modeInterrupt(){ 101 | unsigned long currentTime = millis(); 102 | if(currentTime - modeTimer > 100){ 103 | currentMode = !currentMode; 104 | modeTimer = currentTime; 105 | } 106 | } --------------------------------------------------------------------------------