This is the continuation of last week's blog where I began to analyze strategies to be able to monitor the status of windows, doors, blinds and shutters.
During this week I do some tests on last week's ideas on sliding elements and I analyze strategies for pivoting elements.
Tracking System for Classroom Ventilation Routines
A STEM project for classrooms
the VenTTracker project - Blog #03 - Analyzing Window Types II
Testing ideas for monitoring sliding elements
The first tests with the design developed last week have not been entirely satisfactory. Initially, it was intended to be able to use only two reed switch sensors, but it complicates the software design quite a bit if we want to use multithreading with interruptions. So we go back to the original idea of a sensor for closing and opening detection and we made it independent from detecting the relative position of the element.
This was the previous version:
- Two Reed Switch Sensors Aligned with pull down resistors and positive logic.
And this is the new version:
In the new version:
- We can no longer detect the closing position. Although the encoder could try to detect the closed position through the relative position, since there is no absolute reference we cannot know if it is closed or not. We have to delegate the problem of detecting the closed position to a new sensor.
- The sensors are no longer aligned. Being aligned, and not being exactly the same, caused them not to switch at the right time.
- We change the ports that the sensors connect to. Now there are two ports that support interruptions to allow us to work with multithreading.
- We add two LEDs for debugging.
- We removed the pull down resistors from the old version switches and replaced them with two internal 20K pull up resistors from the microcontroller.
- This makes the logic of the switches in our design now negative. Low when the switch is closed and High when the switch is open.
Building an encoder model for testing
Let's build a real model for testing and see if our design works.
Parts
# | Part |
---|---|
1 | Arduino Nano 33 IOT |
2 | 100Ω Resistor |
2 | TILT-SWITCH-SW-200D |
4 | MAGNET |
2 | Red LED |
Connections:
Arduino Nano 33 IoT | Component |
---|---|
10 | Left Reed Switch. Other end to ground. |
9 | Right Reed Switch. Other end to ground. |
8 | Left Red LED. With a 100Ω Resistor to ground. |
7 | Right Red LED. With a 100Ω Resistor to ground. |
The pins below can be used with attachInterrupt() on Nano 33 IoT.
We are going to use pins 9 and 10:
BOARD | DIGITAL PINS USABLE FOR INTERRUPTS |
---|---|
Nano 33 IoT | 2, 3, 9, 10, 11, 13, 15, A5, A7 |
Arduino Nano 33 IoT Pinout:
Checking encoder signals.
The video shows the signals at the two switch sensors
As seen in the video we will need a low pass filter to avoid bouncing on the switches.
Zooming:
Coding a busy simple encoder.
Let's code a simple encoder without interrupts.
/* Test Reed Switch Quadrature Encoder @author Enrique Albertos @link https://www.element14.com/community/community/design-challenges/design-for-a-cause-2021/ */ // Port connections int leftSwitchPort = 10; // switch to ground + internal pull-up resistor. Negative logic LOW when switch is closed int rightSwitchPort = 9; // switch to ground + internal pull-up resistor. Negative logic LOW when switch is closed int leftLedPort = 8; // positive logic. HIGH turn on the LED int rightLedPort = 7; // positive logic. HIGH turn on the LED void setup() { // set port switches with internal 20K pull up resistors pinMode (leftSwitchPort, INPUT_PULLUP); pinMode (rightSwitchPort, INPUT_PULLUP); // set debugging LEDs ports pinMode (leftLedPort, OUTPUT); pinMode (rightLedPort, OUTPUT); Serial.begin (9600); delay(1000); // wait for serial port } void loop() { static int encoderPosition = 0; static int leftSwitchPortLast = LOW; // switches are in negative logic. Negate int leftSwitchState = !digitalRead(leftSwitchPort); int rightSwitchState = !digitalRead(rightSwitchPort); // Light leds for debugging digitalWrite(leftLedPort, leftSwitchState); digitalWrite(rightLedPort, rightSwitchState); if ((leftSwitchPortLast == LOW) && (leftSwitchState == HIGH)) { if (rightSwitchState == LOW) { encoderPosition++; } else { encoderPosition--; } Serial.println(encoderPosition); } leftSwitchPortLast = leftSwitchState; // debouncing switches delay(20); }
When the code finds a low-to-high transition on the leftSwitchState , it checks to see if the rightSwitchState channel is high or low and then increments/decrements the counter with the encoderPosition. By now the encoder position is a relative position. We will need to add an absolute reference position to calibrate our encoder.
This is the serial output going right to left and left to right with the magnet.
Going multithreading
Let's try now with interrupts
Interrupts and ISR
We want to ensure that our program always caught the pulses from our encoder, so that it never misses a pulse. With the above code it would make very tricky to do anything else, because the program would need to constantly poll the sensor lines for the encoder, in order to catch pulses when they occurred.
Using an interrupt can free the microcontroller to get some other work done while not missing the input.
To make sure variables shared between the Interrupt Service Routine (ISR) and the main program are updated correctly, we declare them as volatile.
Declaring a variable volatile is a directive to the compiler. It directs the compiler to load the variable from RAM and not from a storage register, which is a temporary memory location where program variables are stored and manipulated. Under certain conditions, the value for a variable stored in registers can be inaccurate.
Some notes about the code
In this revision of our code we will use the LEDS for monitoring the detected direction. Left LED will turn on when last direction detected is to the left and the Right LED will turn on when the last direction detected is to the right.
ISR should be as short and fast as possible so we will use events to notify changes.
We are interested in only one of the four possible transitions. We will detect only falling edges in left switch. We'll detect falling edges as the switch logic is negative. When falling edge the switch transitions from open to closed.
/* Test Reed Switch Quadrature Encoder 02 With interrupts @author Enrique Albertos @link https://www.element14.com/community/community/design-challenges/design-for-a-cause-2021/ */ // Port connections int leftSwitchPort = 10; // switch to ground + internal pull-up resistor. Negative logic LOW when switch is closed int rightSwitchPort = 9; // switch to ground + internal pull-up resistor. Negative logic LOW when switch is closed int leftLedPort = 8; // positive logic. HIGH turn on the LED int rightLedPort = 7; // positive logic. HIGH turn on the LED typedef enum direction_t {RIGHT = 0x00, LEFT = 0xFF}; volatile direction_t lastWindowDirection = LEFT; volatile int8_t encoderPosition = 0; // don't know where is our encoder, we'll need an absolute reference // let's assume we are in closed position and window opens from left to right volatile bool encoderChangePending = false; unsigned long lastLeftSwitchDebounceTime = 0; // the last time the input left encoder pin was toggled unsigned long debounceDelay = 50; // the debounce time void isrFallingLeftSwitchPort(); // ISR for leftSwitchPort void setup() { // set port switches with internal 20K pull up resistors pinMode(leftSwitchPort, INPUT_PULLUP); pinMode(rightSwitchPort, INPUT_PULLUP); // detect falling edges the switch chages from open to closed. It is negative logic attachInterrupt(digitalPinToInterrupt(leftSwitchPort), isrFallingLeftSwitchPort, FALLING); // set debugging LEDs ports pinMode(leftLedPort, OUTPUT); pinMode(rightLedPort, OUTPUT); Serial.begin(9600); delay(1000); // wait for serial port } void loop() { if (encoderChangePending) { encoderChangePending = false; // turn on left led when window direction is LEFT digitalWrite(leftLedPort, lastWindowDirection == LEFT ); // and turn on right led when window direction is RIGHT digitalWrite(rightLedPort, lastWindowDirection == RIGHT ); // log encoder position to serial port Serial.println(encoderPosition); } } /** * Interrupt Service Routine for Falling Edge in Left Switch Port */ void isrFallingLeftSwitchPort() { if ((millis() - lastLeftSwitchDebounceTime) > debounceDelay) { if (digitalRead(rightSwitchPort) == HIGH) { encoderPosition++; lastWindowDirection = RIGHT; } else { encoderPosition--; lastWindowDirection = LEFT; } lastLeftSwitchDebounceTime = millis(); encoderChangePending = true; } }
The new encoder in action.
Strategies for Casement Windows and Doors
For casement windows we are going to start using the accelerometer and the gyroscope. We build a small model to simulate the orientation of the window or door from closed to open.
A priori the results are good but the position obtained oscillates in the stop situation. This is going to require us to filter the data. It will not be a problem since our requirements for the position are not strict at all.
Testing Casement Window and Door Orientation with LSM6DS3 IMU and Madgwick filter
Arduino code
/* Testing Casement Window and Door Orientation with Arduino nano 33 IoT LSM6DS3 IMU and Madgwick filter @author Enrique Albertos @link https://www.element14.com/community/community/design-challenges/design-for-a-cause-2021/ */ #include <Arduino_LSM6DS3.h> #include <MadgwickAHRS.h> Madgwick filter; void setup() { Serial.begin(9600); delay(1000); // start the IMU and filter if (!IMU.begin()) { Serial.println("Failed to initialize IMU!"); while (1); } filter.begin(IMU.accelerationSampleRate()); } void loop() { float aix, aiy, aiz; float gix, giy, giz; float roll, pitch, heading; // read raw data from IMU if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()) { IMU.readAcceleration(aix, aiy, aiz); IMU.readGyroscope(gix, giy, giz); filter.updateIMU(0, giy, 0, 0, aiz, 0); heading = -filter.getYaw(); Serial.print("Orientation: "); Serial.println(heading); } }
For visualization in Windows we are using a java Processing script
/** Testing Casement Window and Door Orientation with Arduino nano 33 IoT LSM6DS3 IMU and Madgwick filter Processing 3D Visualizer @author Enrique Albertos @link https://www.element14.com/community/community/design-challenges/design-for-a-cause-2021/ */ import processing.serial.*; Serial myPort; float yaw = 0.0; void setup() { size(1200, 1000, P3D); myPort = new Serial(this, "COM4", 9600); textSize(16); // set text size textMode(SHAPE); // set text mode to shape } void draw() { serialEvent(); // read and parse incoming serial message background(255); // set background to white lights(); translate(width/2, height/2); // set position to centre pushMatrix(); // begin object float c1 = cos(radians(0)); float s1 = sin(radians(0)); float c2 = cos(radians(0)); float s2 = sin(radians(0)); float c3 = cos(radians(yaw)); float s3 = sin(radians(yaw)); applyMatrix( c2*c3, s1*s3+c1*c3*s2, c3*s1*s2-c1*s3, 0, -s2, c1*c2, c2*s1, 0, c2*s3, c1*s2*s3-c3*s1, c1*c3+s1*s2*s3, 0, 0, 0, 0, 1); drawCasementWindow(); popMatrix(); // end of object // Print values to console print(yaw); println(); } void serialEvent() { int newLine = 13; // new line character in ASCII String message; do { message = myPort.readStringUntil(newLine); // read from port until new line if (message != null) { String[] list = split(trim(message), " "); if (list.length >= 2 && list[0].equals("Orientation:")) { yaw = float(list[1]); // convert to float yaw } } } while (message != null); } void drawCasementWindow() { translate(0, 0, 100); stroke(255, 255, 255); fill(250, 250, 250); box(10, 300, 200); stroke(90, 90, 0); fill(180, 180, 0); translate(0, 0, -100); box(10,300,20); stroke(90, 90, 0); fill(180, 180, 0); translate(0, 0, 200); box(10,300,20); stroke(90, 90, 0); fill(180, 180, 0); translate(0, 150, -100); box(10,20,200); stroke(90, 90, 0); fill(180, 180, 0); translate(0, -300, 0); box(10,20,200); }
Next steps
- Design and build a mockup to simulate the behavior with the sliding parts.
- Investigate what ML can contribute to our goals.
- Create a game to get familiar with the accelerometer and have fun along the way.
<< Previous VenTTracker Blog | Next VenTTracker Blog >> |
---|---|
VenTTracker #02 - Analyzing window types. | VenTTracker #04 - Playing with the IMU |
Top Comments