Skip to content

SchrodingerError/SET-Control-System

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Table of Conents

Purpose

The Texas A&M Sounding Rocketry Team designs, builds, and flies its own hybrid rocket. As part of its design cycle, static cold flows and static engine tests are performed to chacterize the engine's oxidizer tank, fluid system, and combustion process.

Overall System Design

During a static engine test, we load solid HTPB fuel into the combustion chamber. As part of the test procedure, we load nitrous oxide into the run tank via the fluid panel. We then ignite the starter, and open the ball valve actuation system (BVAS) to begin our combustion cycle.

Concept of Operation

Control Box

The control box is responsible for taking in operator inputs and sending them to the actuator box over CAN. The control box also receives feedback from the actuator box and updates the assortment of LEDs and the LCD screen on the indicator panel.

Logic Diagram

Power Diagram

Actuator Box

The actuator box is responsible for actuating valves on the oxidizer fluid panel, main throttle valve on the engine, and igniting the starter system. During a test when avionics is deployed, the avionics stack handles engine control while the actuator box controls ground support systems.

Logic Diagram

Power Diagram

Physical Design

Control Box

Display PCB

3D Render

PCB Design

Schematic

Main PCB

3D Render

PCB Design

Schematic

Integrated Assembly

Actuator Box

Actuator PCB

3D Render

PCB Design

Schematic

Full Schematic

Non-Latching Relay Schematic

Latching Relay Schematic

Igniter Relay Schematic

Integrated Assembly

Arduino Software

Common Software

CAN Comms

For the peer to peer communication between the control box and the actuator box, the Controller Area Network (CAN) Bus was chosen for its amazing signal integrity and simple to use libraries with Arduino. Key features in this use case are the 8 byte frames sending unsigned chars and the can_id unsigned integer which designates the type of packet being sent.

On startup (and communication loss from a power outage, random restart, or CAN harness disconnect), the devices go through a 2-way handshake that consists of the control box sending a ping, the actuator box replying to the ping, and the control box acknowledging the reply.

For the packet structure, the formatting can be found here.

Control Box

Logic Diagram

Custom LCD Class

To help manage the text on the 20x4 LCD, a custom class was created.

class LCD {
    private:
        // An array of strings. Each element represents what is currently being shown on the row
        String current_text[4] = {"", "", "", ""};

        // What we will want to update the LCD with
        String desired_text[4] = {"", "", "", ""};

        // Keep track of any blank lines
        bool blank_rows[4] = {true, true, true, true};

    public:
        LCD();

        // Clear the entire LCD
        void clear_lcd();

        // Clear only the line as specified in int row (indexed at 0)
        void clear_lcd_line(int row);

        // Update the physical LCD with the text stored in self->desired_text
        void set_lcd_to_desired_text();

        // Add a string to the self->desired_text arraw at the specified row
        int add_string_to_desired_text(String to_add, int row = -1);

        // Helper function to check string is in self->desired_text
        int check_if_already_in_desired_text(String to_add);

        // Clear the self->desired_text
        void clear_desired_text(int row = -1);
};

LED Groups Based on Relay Type

With the display board having multiple LEDs for different types of relays, as well as a slightly different byte structure being reported from the actuator box to signify different states or fails, custom classes were made to handle the groups of LEDs and writing LCD messages.

Non-Latching Relay Display

class NonLatchingRelayDisplays {
    private: 
        // Represents if the AB saw the on signal and set the pin HIGH
        unsigned int relay_led_pin;

        // Represents if the AB actually saw a voltage across the relay and thinks the valve was opened
        unsigned int state_led_pin;

        // So our LCD messages have meaning
        String name;

        // If we come across a pin error state, update this string so the LCD can find it and print it
        String pin_error_message = "";

        // If we come across a relay error state, update this string so the LCD can find it and print it
        String relay_error_message = "";

    public:
        // The row that the error message is on the LCD. -1 means it is not on the LCD
        int pin_error_message_row = -1;

        // The row that the error message is on the LCD. -1 means it is not on the LCD
        int relay_error_message_row = -1;

        NonLatchingRelayDisplays (unsigned int relay_led_pin, 
                                  unsigned int state_led_pin, 
                                  String name);

        String get_pin_error_message();
        String get_relay_error_message();

        // Sets the desired and actual LEDs as given by the status byte from CAN
        // If there is an error signified in the byte, write to the LCD
        void control_leds(unsigned int status);
};

Latching Relay Display

The latching relay is the exact same as the non-latching relay besides some differences in error messages. For example, the latching relay could have a pin failure for the SET or RESET pin while the non-latching relay can only have a SET pin.

class LatchingRelayDisplays {
    private: 
        // Represents if the AB saw the on signal and set the pin HIGH
        unsigned int relay_led_pin;

        // Represents if the AB actually saw a voltage across the relay and thinks the valve was opened
        unsigned int state_led_pin;

        // So our LCD messages have meaning
        String name;

        // If we come across a pin error state, update this string so the LCD can find it and print it
        String pin_error_message = "";

        // If we come across a relay error state, update this string so the LCD can find it and print it
        String relay_error_message = "";
    
    public:
        // The row that the error message is on the LCD. -1 means it is not on the LCD
        int pin_error_message_row = -1;

        // The row that the error message is on the LCD. -1 means it is not on the LCD
        int relay_error_message_row = -1;

        LatchingRelayDisplays (unsigned int relay_led_pin, 
                               unsigned int state_led_pin, 
                               String name);

        String get_pin_error_message();
        String get_relay_error_message();

        // Sets the desired and actual LEDs as given by the status byte from CAN
        // If there is an error signified in the byte, write to the LCD
        void control_leds(unsigned int status);
};

Latching Solenoid Display

The latching solenoid controls 2 relays, so it gets 2 bytes from CAN and has more complex error messages.

class LatchingSolenoidDisplays {
    private: 
        // Represents if the AB saw the on signal and set the pin HIGH
        unsigned int relay_led_pin;

        // Represents if the AB actually saw a voltage across the relay and thinks the valve was opened
        unsigned int state_led_pin;

        // So our LCD messages have meaning
        String name;

        // If we come across a pin error state, update this string so the LCD can find it and print it
        String pin_error_message = "";

        // If we come across a relay error state, update this string so the LCD can find it and print it
        String relay_error_message = "";
    
    public:
        // The row that the error message is on the LCD. -1 means it is not on the LCD
        int pin_error_message_row = -1;

        // The row that the error message is on the LCD. -1 means it is not on the LCD
        int relay_error_message_row = -1;

        LatchingSolenoidDisplays (unsigned int relay_led_pin, 
                                  unsigned int state_led_pin, 
                                  String name);

        String get_pin_error_message();
        String get_relay_error_message();

        // Sets the desired and actual LEDs as given by the 2 statuss bytes from CAN
        // If there is an error signified in the bytes, write to the LCD
        void control_leds(unsigned int on_relay_status, unsigned int off_relay_status);
};

Actuator Box

Logic Diagram

There currently is no logic diagram for the actuator box.

Custom Relay Control Classes

The actuator box features digital pin failure detection and relay failure detection. It accomplishes this through a spare digital pin set as an input to monitor the output pin, as well as a analog input to measure the relay contacts' status.

In addition to failure detection, it is important for anything latching to send as short as possible pulses to set and reset the latch. For the latching relays and latching solenoids, the system monitors how long it takes to set or reset and automatically turns off based on rising and falling edge rather than simple pulse duration.

Non-Latching Relay Class

class NonLatchingRelay {
    private:
        String name = "Non-Latching Relay";
        unsigned int pin;
        unsigned int pin_readback;
        // The desired state of the pin
        bool pin_desired_state;  
        // The actual state of the pin per the readback
        bool pin_actual_state;  
        bool pin_working;

        unsigned int voltage_read_pin;

        // The desired state of the relay, set by the control program
        bool desired_state = false;  
        // The expected state of the relay based off of digital spin readback
        bool expected_state;  
        // Whether or not there actually is a voltage across the relay or not
        bool actual_state;  
        bool relay_working;
        // This is used so that the object knows whether or not to use the normal or startup function to control
        bool startup = true;  

        // The max time in ms that we would expect the Arduino to see a voltage change across the relay after turning on the pin
        const unsigned long relay_delay = 500;  
        // The millis() that the relay was turned on. Useful for knowing if it is operating correctly
        unsigned long relay_turned_on_time;  
        // The millis() that the relay was turned off. Useful for knowing if it is operating correctly
        unsigned long relay_turned_off_time ;  
        // The millis() that we first saw a voltage across the relay after trying to turn it on
        unsigned long relay_first_voltage_time;  
        // The millis() that we first saw no voltage across the relay after trying to turn it off
        unsigned long relay_first_no_voltage_time;  
    
        // How many times we should read the relay voltage and average
        const unsigned int num_voltage_reads = 3;  

        // Get a single voltag reading across the relay
        float read_relay_voltage();  
        // Average a few voltage readings and determine if the relay has ~12V across it
        void update_actual_state();  

        void update_if_pin_working();
        void update_if_relay_working();

        void turn_on_relay(); 
        void turn_off_relay();

        // Turns on relay as well as setting values that may have been uninitialized
        void turn_on_relay_startup();  
        // Turns off relay as well as setting values that may have been uninitialized
        void turn_off_relay_startup();  

    public:
        NonLatchingRelay(String name, 
                         unsigned int pin, 
                         unsigned int pin_readback, 
                         unsigned int voltage_read_pin);

        NonLatchingRelay(unsigned int pin, 
                         unsigned int pin_readback, 
                         unsigned int voltage_read_pin);

        // Gets the state of the pin_working
        bool get_pin_working() const {return pin_working;}  

        // Gets the desired state of the relay
        bool get_desired_state() const {return desired_state;}  
        // Gets the expected state of the relay
        bool get_expected_state() const {return expected_state;}  
        // Gets the actual state of the relay
        bool get_actual_state() const {return actual_state;}  
        // Gets whether or not the relay is working correctly
        bool get_relay_working() const {return relay_working;}  

        // Gets the time that the first voltage was seen after trying to turn on
        unsigned long get_relay_first_voltage_time() const {return relay_first_voltage_time;} 
        
        // Gets the time that the first no voltage was seen after trying to turn off
        unsigned long get_relay_first_no_voltage_time() const {return relay_first_no_voltage_time;}

        // Set the desired state of the relay
        void set_desired_state(unsigned char new_desired_state);  

        // Controls the relay including pins to get relay to desired state, checking if the pin needs to be turned off, and error detection
        void control_relay();  
};

Latching Relay Class

class LatchingRelay {
    private:
        String name = "Latching Relay";

        unsigned int setpin;
        unsigned int setpin_readback;
        // What we are telling the pin to be (HIGH vs LOW)
        bool setpin_desired_state = false;
        // The current state of the setpin as per the readback
        bool setpin_actual_state;  
        bool setpin_working;

        unsigned int resetpin;
        unsigned int resetpin_readback;
        // What we are telling the pin to be (HIGH vs LOW)
        bool resetpin_desired_state = false;  
        // The current state of the resetpin
        bool resetpin_actual_state;  
        bool resetpin_working;

        unsigned int voltage_read_pin;

        // The time in ms that the coil should stay on AFTER seeing a voltage change
        const unsigned long extra_pin_duration = 100; 
        // The millis() that the setpin was turned on 
        unsigned long setpin_turned_on_time;  
        // The millis() that the resetpin was turned on
        unsigned long resetpin_turned_on_time;  
        
        // The desired state of the relay, set by the control program
        bool desired_state = false;  
        // The expected state of the relay based off of digital setpin and resetpin readbacks
        bool expected_state;  
        // Whether or not there actually is a voltage across the relay or not
        bool actual_state;  
        bool relay_working;
        // This is used so that the object knows whether or not to use the normal or startup function to control
        bool startup = true;  

        // The max time in ms that we would expect the Arduino to see a voltage change across the relay after turning on the pin
        const unsigned long relay_delay = 500;  
        // The millis() that the relay was turned on. Useful for knowing if it is operating correctly
        unsigned long relay_turned_on_time;  
        // The millis() that the relay was turned off. Useful for knowing if it is operating correctly
        unsigned long relay_turned_off_time;  
        // The millis() that we first saw a voltage across the relay after trying to turn it on
        unsigned long relay_first_voltage_time;  
        // The millis() that we first saw no voltage across the relay after trying to turn it off
        unsigned long relay_first_no_voltage_time;  

        // How many times we should read the relay voltage and average
        const unsigned int num_voltage_reads = 3;  

        // Get a single voltag reading across the relay
        float read_relay_voltage();  
        // Average a few voltage readings and determine if the relay has ~12V across it
        void update_actual_state();  

        // Perform a check to see if the pins are working based on readbacks
        void update_if_pins_working();  
        // Check if the relay is working based on expected vs actual state
        void update_if_relay_working();  

        // Turn the pins off after the appropriate time
        void turn_off_pins_after_duration();  

        void turn_on_relay(); 
        void turn_off_relay();

        // Turns on relay as well as setting values that may have been uninitialized
        void turn_on_relay_startup();  
        // Turns off relay as well as setting values that may have been uninitialized
        void turn_off_relay_startup();  

        // Only called when the expected state != actual state due to some unkown failure (vibration, transistor for some reason turning on, etc)
        void turn_on_relay_from_fail();  
        // Only called when the expected state != actual state due to some unkown failure (vibration, transistor for some reason turning on, etc)
        void turn_off_relay_from_fail();  

    public:
        LatchingRelay(String name, 
                      unsigned int setpin, 
                      unsigned int setpin_readback, 
                      unsigned int resetpin, 
                      unsigned int resetpin_readback, 
                      unsigned int voltage_read_pin);

        LatchingRelay(unsigned int setpin, 
                      unsigned int setpin_readback, 
                      unsigned int resetpin, 
                      unsigned int resetpin_readback, 
                      unsigned int voltage_read_pin);

        bool get_setpin_working() const {return setpin_working;}
        bool get_resetpin_working() const {return resetpin_working;}

        bool get_desired_state() const {return desired_state;}
        bool get_expected_state() const {return expected_state;}
        bool get_actual_state() const {return actual_state;}
        bool get_relay_working() const {return relay_working;}

        // Set the desired state of the relay
        void set_desired_state(unsigned char new_desired_state);  

        // Controls the relay including pins to get relay to desired state, checking if pins need to be turned off, and error detection
        void control_relay();  
};

Latching Solenoid Class

class LatchingSolenoid {
    private:
        String name = "Latching Solenoid";
        unsigned int onpin;
        unsigned int onpin_readback;
        bool onpin_working;

        unsigned int offpin;
        unsigned int offpin_readback;
        bool offpin_working;

        unsigned int on_voltage_read_pin;
        unsigned int off_voltage_read_pin;

        // The millis() time that we first saw a voltage across
        unsigned long on_relay_first_voltage_time;  

        // The millis() time that we first saw a voltage across
        unsigned long off_relay_first_voltage_time;  

        // How many ms the relay should be turned on to move the solenoid position
        const unsigned long set_duration = 1000;  
        // We want to see at least this long to assume the solenoid turned on
        const unsigned long set_duration_minimum = 100;  

        // The current state of the on relay
        bool on_relay_actual_state;  
        bool on_relay_working;
        // The current state of the off relay
        bool off_relay_actual_state;  
        bool off_relay_working;

        // The desired state of the solenoid, set by the control program
        bool desired_state = false;  
        // The expected state of the solenoid based off of voltage readbacks across the relays
        bool expected_state;  
        // This is used so that the object knows whether or not to use the normal or startup function to control
        bool startup = true;  
        
        NonLatchingRelay on_relay;
        NonLatchingRelay off_relay;

        void update_first_voltage_times();
        void update_relay_actual_states();

        void update_if_pins_working();
        void update_if_relays_working();

        void update_solenoid_expected_state();

        void turn_off_relays_after_duration();

        void turn_on_solenoid();
        void turn_off_solenoid();

        void turn_on_solenoid_startup();
        void turn_off_solenoid_startup();

    public:
        LatchingSolenoid(String name, 
                         unsigned int onpin, 
                         unsigned int onpin_readback, 
                         unsigned int offpin, 
                         unsigned int offpin_readback, 
                         unsigned int on_voltage_read_pin, 
                         unsigned int off_voltage_read_pin);

        LatchingSolenoid(unsigned int onpin, 
                         unsigned int onpin_readback, 
                         unsigned int offpin, 
                         unsigned int offpin_readback, 
                         unsigned int on_voltage_read_pin, 
                         unsigned int off_voltage_read_pin);

        bool get_onpin_working() const {return onpin_working;}
        bool get_offpin_working() const {return offpin_working;}

        bool get_on_relay_actual_state() const {return on_relay_actual_state;}
        bool get_off_relay_actual_state() const {return off_relay_actual_state;}
        
        bool get_desired_state() const {return desired_state;}
        bool get_expected_state() const {return expected_state;}
        bool get_on_relay_working() const {return on_relay_working;}
        bool get_off_relay_working() const {return off_relay_working;}
        
        void set_desired_solenoid_state(unsigned char new_desired_state);

        void control_solenoid();
};

System Upgrades After Initial Design

Servo Motor Main Valve for Throttling

The BVAS (ball valve actuator system) that uses 2 solenoids to control a pneumatic valve for the main oxidizer valve for the engine lacks throttlability and other nice to have features. The team shifted towards using a servo motor controller via PWM (pulse width modulation) to give us more granular control of the mass flow rate of oxidizer.

Because the actuator box has the expansion ports setup, a jumper cable was connected to a panel mount and commands the servo. In the code, one can either set pre-determined values for the servo angles or define a function of time to control the servo angle.

MODBUS TCP Communication for Integration in Ignition SCADA

Not included in the code in this repository, but MODBUS TCP was integrated into the actuator box using the W5500 ethernet module so that it can more easily tie into Ignition SCADA (supervisory control and data acquisition). The shift to Ignition came with adding off the shelf PLCs to our control stack and other equipment.

The actuator box acts as a MODBUS server and has coils setup for each relay as well as input registers for all of the different status and fail bits. There is also a holding register setup for the servo motor control.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages