2021-01-31 19:41:53 +00:00
# include <Arduino.h>
2021-01-31 12:08:48 +00:00
# include <Wire.h>
2021-01-31 19:41:53 +00:00
/*
TODO :
- correct speedfactor when moving at pwm = 100 for some time over opaque + clear OR implement speedfactor for up and lower state
- implement second blind
- implement drive to position
- implement failure detection
- implement homie
*/
2021-01-31 12:08:48 +00:00
# include "WEMOS_Motor.h"
//follow firmware flash guide for new wemos motor shield v1.0 https://github.com/thomasfredericks/wemos_motor_shield
2021-01-31 16:59:57 +00:00
2021-01-31 12:45:28 +00:00
# define BUTTON_DEBOUNCE 200
2021-01-31 16:59:57 +00:00
# define PIN_BUTTON1 D5
struct button {
uint8_t pin ;
unsigned long last_time_read = 0 ;
bool down = false ;
bool changed = false ;
} ;
button button1 ;
unsigned long last_sensor_read = 0 ;
# define SENSOR_READ_INTERVAL 20 //in ms
# define SENSE_FILTER_SIZE 20 //value will have a delay of td = SENSE_FILTER_SIZE/2*SENSOR_READ_INTERVAL
2021-01-31 12:45:28 +00:00
2021-01-31 19:41:53 +00:00
# define CALCULATEPOSITIONESTIMATE_INTERVAL 100
unsigned long last_calculate_position_estimate = 0 ;
2021-01-31 12:45:28 +00:00
bool manual_drive_direction = false ;
# define PIN_SENSE A0
unsigned long last_print = 0 ;
//define directions for motors
2021-01-31 19:41:53 +00:00
# define _UP _CCW
# define _DOWN _CW
2021-01-31 16:59:57 +00:00
2021-01-31 19:41:53 +00:00
# define SENSESTATUS_CLEAR 1
# define SENSESTATUS_OPAQUE 2
# define SENSESTATUS_END 3
# define SENSESTATUS_UNKNOWN 0
2021-01-31 16:59:57 +00:00
2021-01-31 19:41:53 +00:00
# define MODE_IDLE 0
# define MODE_FIND_END 1
# define MODE_MEASURE_SPEED 2
# define MODE_GO_TO_POS 3
2021-01-31 16:59:57 +00:00
//model parameters/variables
struct blindmodel
{
unsigned long lastreadtime = 0 ;
2021-01-31 19:41:53 +00:00
float position = 0 ; //0 is furthest open. positive is down (closing). unit is mm. estimated position
2021-01-31 16:59:57 +00:00
float length_clear ; //length of clear part in position units
float length_opaque ; //lengt of opaque part in position units
2021-01-31 19:41:53 +00:00
float start_first_clear ; //distance from end marker to beginning of first clear section
//end marker should be on the opaque section. So that a full clear section follows
2021-01-31 16:59:57 +00:00
2021-01-31 19:41:53 +00:00
float speedfactor = 1 ; //how much position units (mm) per second at pwm=100
2021-01-31 16:59:57 +00:00
int sense_clear_lower ; //adc value lower limit for clear part. clear is around 70
int sense_clear_upper ; //adc value upper limit for clear part
2021-01-31 12:45:28 +00:00
2021-01-31 16:59:57 +00:00
int sense_opaque_lower ; //adc value lower limit for opaque part. opaque is around 675
int sense_opaque_upper ; //adc value upper limit for opaque part
int sense_end_lower ; //adc value lower limit for end marker
int sense_end_upper ; //adc value upper limit for end marker
2021-01-31 19:41:53 +00:00
2021-01-31 16:59:57 +00:00
uint8_t sense_status = 0 ;
2021-01-31 19:41:53 +00:00
uint8_t last_sense_status = 0 ;
2021-01-31 16:59:57 +00:00
int sense_read [ SENSE_FILTER_SIZE ] = { 0 } ;
uint8_t sense_read_pos = 0 ; //position of last element written to
float set_position = 0 ;
unsigned long last_sense_ok = 0 ; //last time sensor measured class ok
//TODO: implement timeout if last_sense_ok gets too high.
2021-01-31 19:41:53 +00:00
uint8_t mode = MODE_IDLE ;
uint8_t mode_find_end_state = 0 ;
uint8_t mode_measure_speed_state = 0 ;
unsigned long timing_start ;
int speed = 0 ; //-100 to 100. commanded speed to motor. negative means upwards
int speedSimulated = 0 ; //software simulated motor speed up and slow down
float simulated_acc_dec = - 100 ; //pwm/sec^2, speed getting more negative (accelerating up). this value should be negative. better choose too small absolute values
float simulated_acc_inc = 100 ; //pwm/sec^2, speed getting more positive (accelerating down)
2021-01-31 16:59:57 +00:00
} ;
blindmodel blind1 ;
2021-01-31 12:45:28 +00:00
2021-01-31 12:08:48 +00:00
//Motor shield default I2C Address: 0x30
//PWM frequency: 1000Hz(1kHz)
Motor M1 ( 0x30 , _MOTOR_A , 1000 ) ; //Motor A
Motor M2 ( 0x30 , _MOTOR_B , 1000 ) ; //Motor B
2021-01-31 19:41:53 +00:00
unsigned long last_motor_send = 0 ;
# define MOTOR_UPDATE_INTERVAL 100
2021-01-31 12:08:48 +00:00
2021-01-31 16:59:57 +00:00
int getFitered ( int * values , uint8_t size ) ;
uint8_t classifySensorValue ( blindmodel & blind ) ;
2021-01-31 19:41:53 +00:00
void checkModes ( blindmodel & blind ) ;
2021-01-31 16:59:57 +00:00
2021-01-31 12:08:48 +00:00
void setup ( ) {
2021-01-31 19:41:53 +00:00
Serial . begin ( 115200 ) ;
2021-01-31 16:59:57 +00:00
Serial . println ( " Starting " ) ;
button1 . pin = PIN_BUTTON1 ;
2021-01-31 12:08:48 +00:00
2021-01-31 16:59:57 +00:00
pinMode ( button1 . pin , INPUT_PULLUP ) ;
2021-01-31 12:45:28 +00:00
pinMode ( PIN_SENSE , INPUT ) ;
M1 . setmotor ( _STOP ) ;
M2 . setmotor ( _STOP ) ;
2021-01-31 16:59:57 +00:00
//settings for blind
blind1 . length_clear = 50 ;
blind1 . length_opaque = 74 ;
blind1 . sense_clear_lower = 40 ;
blind1 . sense_clear_upper = 100 ;
2021-01-31 19:41:53 +00:00
blind1 . sense_opaque_lower = 500 ;
2021-01-31 16:59:57 +00:00
blind1 . sense_opaque_upper = 750 ;
blind1 . sense_end_lower = 850 ;
blind1 . sense_end_upper = 1024 ;
2021-01-31 19:41:53 +00:00
blind1 . speedfactor = 29 ;
blind1 . start_first_clear = 27 ;
blind1 . simulated_acc_dec = - 150 ;
blind1 . simulated_acc_inc = 200 ;
//Test
blind1 . mode = MODE_FIND_END ;
blind1 . mode_find_end_state = 0 ; //reset mode find state
2021-01-31 12:08:48 +00:00
}
2021-01-31 12:45:28 +00:00
void loop ( ) {
2021-01-31 16:59:57 +00:00
button1 . changed = false ;
if ( millis ( ) > button1 . last_time_read + BUTTON_DEBOUNCE ) {
bool new_pin_button_down = ! digitalRead ( button1 . pin ) ;
if ( button1 . down ! = new_pin_button_down ) { //changed
button1 . down = new_pin_button_down ; //update
button1 . changed = true ;
button1 . last_time_read = millis ( ) ; //delay next check
2021-01-31 12:45:28 +00:00
}
}
2021-01-31 16:59:57 +00:00
//Manual movement by button
if ( button1 . changed ) {
2021-01-31 19:41:53 +00:00
blind1 . mode = MODE_IDLE ;
2021-01-31 16:59:57 +00:00
if ( button1 . down ) { //changed to pressed
2021-01-31 12:45:28 +00:00
if ( manual_drive_direction ) {
2021-01-31 19:41:53 +00:00
//M1.setmotor( _CW, 100);
blind1 . speed = - 100 ;
//Serial.print("CW PWM: ");
2021-01-31 12:45:28 +00:00
} else {
2021-01-31 19:41:53 +00:00
blind1 . speed = 100 ;
//Serial.print("CCW PWM: ");
2021-01-31 12:45:28 +00:00
}
manual_drive_direction = ! manual_drive_direction ; //switch direction every new press
2021-01-31 16:59:57 +00:00
} else { //changed to released
2021-01-31 19:41:53 +00:00
//Serial.println("Motor STOP");
blind1 . speed = 0 ;
2021-01-31 12:45:28 +00:00
}
}
2021-01-31 16:59:57 +00:00
//Read sensor/encoder
if ( millis ( ) > last_sensor_read + SENSOR_READ_INTERVAL ) {
blind1 . sense_read_pos = ( blind1 . sense_read_pos + 1 ) % SENSE_FILTER_SIZE ; //next element
blind1 . sense_read [ blind1 . sense_read_pos ] = analogRead ( PIN_SENSE ) ;
classifySensorValue ( blind1 ) ; //writes to blindmodel.sense_status
last_sensor_read = millis ( ) ;
}
2021-01-31 12:45:28 +00:00
2021-01-31 19:41:53 +00:00
if ( millis ( ) > last_print + 500 ) {
Serial . print ( " SenseStatus= " ) ;
switch ( blind1 . sense_status ) {
case SENSESTATUS_UNKNOWN :
Serial . print ( " UNK " ) ;
break ;
case SENSESTATUS_CLEAR :
Serial . print ( " CLE " ) ;
break ;
case SENSESTATUS_OPAQUE :
Serial . print ( " OPA " ) ;
break ;
case SENSESTATUS_END :
Serial . print ( " END " ) ;
break ;
}
Serial . print ( " ( " ) ; Serial . print ( getFitered ( blind1 . sense_read , SENSE_FILTER_SIZE ) ) ; Serial . print ( " ) " ) ;
Serial . print ( " , mode= " ) ;
switch ( blind1 . mode ) {
case MODE_IDLE :
Serial . print ( " IDL " ) ;
break ;
case MODE_FIND_END :
Serial . print ( " FIN " ) ;
break ;
case MODE_GO_TO_POS :
Serial . print ( " POS " ) ;
break ;
case MODE_MEASURE_SPEED :
Serial . print ( " MSP " ) ;
break ;
}
Serial . print ( " , speed= " ) ;
Serial . print ( blind1 . speed ) ;
Serial . print ( " , speedSim= " ) ;
Serial . print ( blind1 . speedSimulated ) ;
Serial . print ( " , pos= " ) ;
Serial . println ( blind1 . position ) ;
2021-01-31 12:45:28 +00:00
last_print = millis ( ) ;
}
2021-01-31 19:41:53 +00:00
checkModes ( blind1 ) ;
if ( blind1 . sense_status = = SENSESTATUS_END ) {
if ( blind1 . speed < 0 ) { //stop driving up
blind1 . speed = 0 ;
}
}
//Estimate blind position and correct
if ( millis ( ) > last_calculate_position_estimate + CALCULATEPOSITIONESTIMATE_INTERVAL ) {
float _actualTime = ( millis ( ) - last_calculate_position_estimate ) / 1000.0 ; //in seconds
blind1 . speedSimulated + = constrain ( blind1 . speed - blind1 . speedSimulated , blind1 . simulated_acc_dec * _actualTime , blind1 . simulated_acc_inc * _actualTime ) ;
blind1 . position + = blind1 . speedSimulated / 100.0 * blind1 . speedfactor * _actualTime ;
last_calculate_position_estimate = millis ( ) ;
}
if ( blind1 . mode ! = MODE_FIND_END ) {
if ( blind1 . sense_status = = SENSESTATUS_END & & blind1 . last_sense_status ! = SENSESTATUS_END ) { //entered end marker
blind1 . position = 0 ;
float _filterdelay_correction = blind1 . speedSimulated / 100.0 * blind1 . speedfactor * SENSE_FILTER_SIZE / 2 * SENSOR_READ_INTERVAL / 1000.0 ;
blind1 . position + = _filterdelay_correction ; //correct for filter delay
Serial . print ( " Reached End marker. set Position= " ) ; Serial . println ( blind1 . position ) ;
}
if ( blind1 . sense_status = = SENSESTATUS_CLEAR | | blind1 . sense_status = = SENSESTATUS_OPAQUE | | blind1 . sense_status = = SENSESTATUS_END ) { //either opaque or clear (or end, only for last update used)
if ( blind1 . sense_status ! = blind1 . last_sense_status ) { //status changed
if ( blind1 . last_sense_status = = SENSESTATUS_CLEAR | | blind1 . last_sense_status = = SENSESTATUS_OPAQUE ) { //only changes from clear to opaque or opaque to clear
float _position_before = blind1 . position ; //only for text output debugging
if ( ( blind1 . speedSimulated > 0 & & blind1 . sense_status = = SENSESTATUS_OPAQUE ) | | ( blind1 . speedSimulated < 0 & & blind1 . sense_status = = SENSESTATUS_CLEAR ) ) { //moving down from and clear to opaque OR moving up and from opaque to clear
//estimated_position_difference = -blind1.position + blind1.start_first_clear+blind1.length_clear + _n*(blind1.length_clear+blind1.length_opaque); //possible occurances. let estimated_position=0 , solve for n
int _n = round ( ( blind1 . position - blind1 . start_first_clear - blind1 . length_clear ) / ( blind1 . length_clear + blind1 . length_opaque ) ) ; //find closest n
blind1 . position = blind1 . start_first_clear + blind1 . length_clear + _n * ( blind1 . length_clear + blind1 . length_opaque ) ; //calculate position from _n
float _filterdelay_correction = blind1 . speedSimulated / 100.0 * blind1 . speedfactor * SENSE_FILTER_SIZE / 2 * SENSOR_READ_INTERVAL / 1000.0 ;
blind1 . position + = _filterdelay_correction ; //correct for filter delay
Serial . print ( " n= " ) ;
Serial . println ( _n ) ;
Serial . print ( " posCorrection= " ) ; Serial . print ( _filterdelay_correction ) ;
Serial . print ( " , DOWN&CLE->OPA or UP&OPA->CLE, before= " ) ;
} else if ( ( blind1 . speedSimulated > 0 & & blind1 . sense_status = = SENSESTATUS_CLEAR ) | | ( blind1 . speedSimulated < 0 & & blind1 . sense_status = = SENSESTATUS_OPAQUE ) ) { //the other transition
//estimated_position_difference = -blind1.position + blind1.start_first_clear + _n*(blind1.length_clear+blind1.length_opaque); //possible occurances. let estimated_position=0 , solve for n
int _n = round ( ( blind1 . position - blind1 . start_first_clear ) / ( blind1 . length_clear + blind1 . length_opaque ) ) ; //find closest n
Serial . print ( " n= " ) ;
Serial . println ( _n ) ;
blind1 . position = blind1 . start_first_clear + _n * ( blind1 . length_clear + blind1 . length_opaque ) ; //calculate position from _n
float _filterdelay_correction = blind1 . speedSimulated / 100.0 * blind1 . speedfactor * SENSE_FILTER_SIZE / 2 * SENSOR_READ_INTERVAL / 1000.0 ;
blind1 . position + = _filterdelay_correction ; //correct for filter delay
Serial . print ( " posCorrectoin= " ) ; Serial . print ( _filterdelay_correction ) ;
Serial . print ( " , UP&CLE->OPA or DOWN&OPA->CLE, before= " ) ;
}
Serial . print ( _position_before ) ;
Serial . print ( " , after= " ) ;
Serial . print ( blind1 . position ) ;
Serial . print ( " , diff= " ) ;
Serial . println ( blind1 . position - _position_before ) ;
}
blind1 . last_sense_status = blind1 . sense_status ; //update only if new one is opaque or clear
}
}
}
//Update Motor Driver
if ( millis ( ) > last_motor_send + MOTOR_UPDATE_INTERVAL ) {
if ( blind1 . speed < 0 ) {
M1 . setmotor ( _UP , abs ( blind1 . speed ) ) ;
} else if ( blind1 . speed > 0 ) {
M1 . setmotor ( _DOWN , abs ( blind1 . speed ) ) ;
} else {
M1 . setmotor ( _STOP ) ;
}
last_motor_send = millis ( ) ;
}
2021-01-31 12:45:28 +00:00
}
2021-01-31 16:59:57 +00:00
int sort_desc ( const void * cmp1 , const void * cmp2 ) //compare function for qsort
{
// Need to cast the void * to int *
int a = * ( ( int * ) cmp1 ) ;
int b = * ( ( int * ) cmp2 ) ;
// The comparison
return a > b ? - 1 : ( a < b ? 1 : 0 ) ;
// A simpler, probably faster way:
2021-01-31 19:41:53 +00:00
//return b - a;
2021-01-31 16:59:57 +00:00
}
int getFitered ( int * values , uint8_t size ) {
int copied_values [ size ] ;
for ( int i = 0 ; i < size ; i + + ) {
copied_values [ i ] = values [ i ] ; //TODO: maybe some value filtering/selection here
}
int copied_values_length = sizeof ( copied_values ) / sizeof ( copied_values [ 0 ] ) ;
qsort ( copied_values , copied_values_length , sizeof ( copied_values [ 0 ] ) , sort_desc ) ;
return copied_values [ size / 2 ] ;
}
uint8_t classifySensorValue ( blindmodel & blind ) {
int filtered = getFitered ( blind . sense_read , SENSE_FILTER_SIZE ) ;
if ( filtered > = blind . sense_clear_lower & & filtered < = blind . sense_clear_upper ) {
blind . sense_status = SENSESTATUS_CLEAR ;
blind . last_sense_ok = millis ( ) ;
} else if ( filtered > = blind . sense_opaque_lower & & filtered < = blind . sense_opaque_upper ) {
blind . sense_status = SENSESTATUS_OPAQUE ;
blind . last_sense_ok = millis ( ) ;
} else if ( filtered > = blind . sense_end_lower & & filtered < = blind . sense_end_upper ) {
blind . sense_status = SENSESTATUS_END ;
blind . last_sense_ok = millis ( ) ;
}
}
2021-01-31 19:41:53 +00:00
void checkModes ( blindmodel & blind ) {
switch ( blind . mode ) {
case MODE_FIND_END :
switch ( blind . mode_find_end_state ) {
case 0 : //drive up until end sensed
blind . speed = - 100 ; //up
if ( blind1 . sense_status = = SENSESTATUS_END ) {
blind . speed = 0 ; //stop. for safety
blind . mode_find_end_state + + ;
}
break ;
case 1 : //drive down slowly until passed end maker
blind . speed = 10 ; //down slow
if ( blind1 . sense_status ! = SENSESTATUS_END ) {
blind . speed = 0 ; //stop
blind . position = 0 ;
//blind.mode=MODE_IDLE;
blind . mode = MODE_MEASURE_SPEED ; //next measure speed
blind . mode_find_end_state = 0 ; //reset
}
break ;
}
break ;
case MODE_MEASURE_SPEED :
switch ( blind . mode_measure_speed_state ) {
case 0 : //drive down, start timing at clear
blind . speed = 100 ; //down
if ( blind1 . sense_status = = SENSESTATUS_CLEAR ) {
blind . timing_start = millis ( ) ;
blind . mode_measure_speed_state + + ;
}
break ;
case 1 : //on clear section
blind . speed = 100 ; //down
if ( blind1 . sense_status = = SENSESTATUS_OPAQUE ) { //wait for opaque section
blind . mode_measure_speed_state + + ;
}
break ;
case 2 : //on opaque section
blind . speed = 100 ; //down
if ( blind1 . sense_status = = SENSESTATUS_CLEAR ) { //wait for clear section
blind . speedfactor = ( blind . length_clear + blind . length_opaque ) / ( ( millis ( ) - blind . timing_start ) / 1000.0 ) ; //calculate length per second
Serial . print ( " speedfactor= " ) ;
Serial . print ( blind . speedfactor ) ;
Serial . println ( " mm/s " ) ;
blind . position = blind . length_opaque + blind . length_clear + blind . start_first_clear ; //set position
blind . speed = 0 ; //stop
blind . mode_measure_speed_state = 0 ; // reset
blind . mode = MODE_IDLE ;
}
break ;
}
break ;
case MODE_GO_TO_POS :
break ;
}
}
2021-01-31 12:45:28 +00:00
/*
2021-01-31 12:08:48 +00:00
void loop ( ) {
int pwm ;
for ( pwm = 0 ; pwm < = 100 ; pwm + + )
{
M1 . setmotor ( _CW , pwm ) ;
M2 . setmotor ( _CW , pwm ) ;
Serial . print ( " Clockwise PWM: " ) ;
Serial . println ( pwm ) ;
delay ( 100 ) ;
}
Serial . println ( " Motor STOP " ) ;
M1 . setmotor ( _STOP ) ;
M2 . setmotor ( _STOP ) ;
delay ( 1000 ) ;
for ( pwm = 0 ; pwm < = 100 ; pwm + + )
{
M1 . setmotor ( _CCW , pwm ) ;
//delay(1);
M2 . setmotor ( _CCW , pwm ) ;
Serial . print ( " Counterclockwise PWM: " ) ;
Serial . println ( pwm ) ;
delay ( 100 ) ;
}
Serial . println ( " Motor A&B STANDBY " ) ;
M1 . setmotor ( _STANDBY ) ;
M2 . setmotor ( _STANDBY ) ;
delay ( 1000 ) ;
}
2021-01-31 12:45:28 +00:00
*/