Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup Deployment Routine #28

Open
Tracked by #15
Mikefly123 opened this issue Oct 16, 2024 · 1 comment
Open
Tracked by #15

Setup Deployment Routine #28

Mikefly123 opened this issue Oct 16, 2024 · 1 comment
Assignees
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@Mikefly123
Copy link
Member

Mikefly123 commented Oct 16, 2024

Proposed Features

When the satellite comes out of its pod various things need to happen! The following is a rough timeline of what the flight software needs to do from a cold start.
Screenshot 2024-10-16 at 10 28 06 AM
Screenshot 2024-10-16 at 10 28 31 AM

Design Notes

This is an extremely important subroutine for the flight software. It's a tricky one too because it needs to allocate time for a handling and deployment delay without creating a situation where those delays occur every time the satellite reboots (potentially creating a terrible boot loop where the satellite is stuck in an inert state forever).

Furthermore, it is a known issue in V1.X PROVES Kits that there is a fundamental design flaw with the burn wire circuit (used to deploy the antennas) where overuse of them can cause the circuit to "burn through" and cause a brown out (the whole satellite losing power) due to a trigger of the overcurrent protection circuit. If we underuse the burn wire though, the fishing line won't be cut and the antennas will not actually deploy.

In the original PyCubed codebase you just had to guess what the correct burn wire settings are and try to fire it correctly the first time. We've updated it slightly to allow for multiple attempts and try to use NVM (Non-Volatile Memory) flags to maintain a count of attempts and brownouts across resets.

Example Circuit Python Implementation

Within the pysquared.py class the following flags are setup in NVM (Non-Volatile Memory):

class Satellite:
    """
    NVM (Non-Volatile Memory) Register Definitions
    """

    # General NVM counters
    c_boot = multiBitFlag(register=_BOOTCNT, lowest_bit=0, num_bits=8)
    c_vbusrst = multiBitFlag(register=_VBUSRST, lowest_bit=0, num_bits=8)
    c_state_err = multiBitFlag(register=_STATECNT, lowest_bit=0, num_bits=8)
    c_distance = multiBitFlag(register=_DIST, lowest_bit=0, num_bits=8)
    c_ichrg = multiBitFlag(register=_ICHRG, lowest_bit=0, num_bits=8)

    # Define NVM flags
    f_softboot = bitFlag(register=_FLAG, bit=0)
    f_solar = bitFlag(register=_FLAG, bit=1)
    f_burnarm = bitFlag(register=_FLAG, bit=2)
    f_brownout = bitFlag(register=_FLAG, bit=3)
    f_triedburn = bitFlag(register=_FLAG, bit=4)
    f_shtdwn = bitFlag(register=_FLAG, bit=5)
    f_burned = bitFlag(register=_FLAG, bit=6)
    f_fsk = bitFlag(register=_FLAG, bit=7)

Of particular note are the f_burnarm, f_brownout, f_triedburn, and f_burned flags. These are set in NVM to protect against a loss of information in case of a brownout during burn wire operation.

In main.py the satellite first enters a brief radio listening period before attempting the burn wire. This is to provide "trap door" access to sending a command to the satellite that interrupts the code in case of a looping critical failure during the burn wire routine. The satellite then proceeds as such:

    while c.burned is False and tries < 3:
        debug_print("Burn attempt try: " + str(tries+1))
        if tries == 0:
            debug_print("Loitering for " + str(loiter_time) + " seconds")
            try:
                c.neopixel[0] = (0,0,0)
                
                purple = (200, 8, 200)
                led_off = (0,0,0)
                
                for step in range(0,loiter_time):
                    
                    c.neopixel[0] = purple
                    time.sleep(0.5)
                    c.neopixel[0] = led_off
                    time.sleep(0.5)
                    debug_print(f"Entering full flight software in... {loiter_time-step} seconds")
            except Exception as e:
                debug_print("Error in Loiter Sequence: " + ''.join(traceback.format_exception(e)))
        try:
            dutycycle=dutycycle+0.02
            done=c.smart_burn('1',dutycycle)
            tries+=1
        except:
            debug_print("couldnt burn on try " + str(tries+1))
        if done is True:
            debug_print("attempt passed without error!")
            if c.burned is False and tries>=2:
                debug_print("Ran Out of Smart Burn Attempts. Will Attempt automated burn...")
                wait=0
                while(wait<5):
                    wait+=1
                    time.sleep(1)
                c.burn('1',0.28,1000,2)
            else:
                pass
        else:
            debug_print("burn failed miserably!")
            break

The smart_burn() function is one of the lengthier ones on the satellite and is provided here for reference:

    def smart_burn(self,burn_num,dutycycle=0.1):
        """
        Operate burn wire circuits. Wont do anything unless the a nichrome burn wire
        has been installed.

        IMPORTANT: See "Burn Wire Info & Usage" of https://pycubed.org/resources
        before attempting to use this function!

        burn_num:  (string) which burn wire circuit to operate, must be either '1' or '2'
        dutycycle: (float) duty cycle percent, must be 0.0 to 100
        freq:      (float) frequency in Hz of the PWM pulse, default is 1000 Hz
        duration:  (float) duration in seconds the burn wire should be on
        """

        freq = 1000

        distance1=0
        distance2=0
        #self.dist=self.distance()

        try:
            # convert duty cycle % into 16-bit fractional up time
            dtycycl=int((dutycycle/100)*(0xFFFF))
            self.debug_print('----- SMART BURN WIRE CONFIGURATION -----')
            self.debug_print('\tFrequency of: {}Hz\n\tDuty cycle of: {}% (int:{})'.format(freq,(100*dtycycl/0xFFFF),dtycycl))
            # create our PWM object for the respective pin
            # not active since duty_cycle is set to 0 (for now)
            if '1' in burn_num:
                burnwire = pwmio.PWMOut(board.BURN_ENABLE, frequency=freq, duty_cycle=0)
            else:
                return False


            try:
                distance1=self.distance()
                self.debug_print(str(distance1))
                if distance1 > self.dist+2 and distance1 > 4 or self.f_triedburn == True:
                    self.burned = True
                    self.f_brownout = True
                    raise TypeError("Wire seems to have burned and satellite browned out")
                else:
                    self.dist=int(distance1)
                    self.burnarm=True
                if self.burnarm:
                    self.burnarm=False
                    self.f_triedburn = True

                    # Configure the relay control pin & open relay
                    self.RGB=(0,165,0)

                    self._relayA.drive_mode=digitalio.DriveMode.PUSH_PULL
                    self.RGB=(255,165,0)
                    self._relayA.value = 1

                    # Pause to ensure relay is open
                    time.sleep(0.5)

                    #Start the Burn
                    burnwire.duty_cycle=dtycycl

                    #Burn Timer
                    start_time = time.monotonic()

                    #Monitor the burn
                    while not self.burned:
                        distance2=self.distance()
                        self.debug_print(str(distance2))
                        if distance2 > distance1+1 or distance2 > 10:
                            self._relayA.value = 0
                            burnwire.duty_cycle = 0
                            self.burned=True
                            self.f_triedburn = False
                        else:
                            distance1=distance2
                            time_elapsed = time.monotonic() - start_time
                            print("Time Elapsed: " + str(time_elapsed))
                            if time_elapsed > 4:
                                self._relayA.value = 0
                                burnwire.duty_cycle = 0
                                self.burned=False
                                self.RGB=(0,0,255)
                                time.sleep(10)
                                self.f_triedburn = False
                                break

                    time.sleep(5)
                    distance2=self.distance()
                else:
                    pass
                if distance2 > distance1+2 or distance2 > 10:
                    self.burned=True
                    self.f_triedburn = False
            except Exception as e:
                self.debug_print("Error in Burn Sequence: " + ''.join(traceback.format_exception(e)))
                self.debug_print("Error: " + str(e))
                if "no attribute 'LiDAR'" in str(e):
                    self.debug_print("Burning without LiDAR")
                    time.sleep(120) #Set to 120 for flight
                    self.burnarm=False
                    self.burned=True
                    self.f_triedburn=True
                    self.burn("1",dutycycle,freq,4)
                    time.sleep(5)

            # Clean up
            self._relayA.value = 0
            burnwire.duty_cycle = 0
            self.RGB=(0,0,0)
            #burnwire.deinit()
            self._relayA.drive_mode=digitalio.DriveMode.OPEN_DRAIN
            return True
        except Exception as e:
            self.debug_print("Error with Burn Wire: " + ''.join(traceback.format_exception(e)))
            return False
        finally:
            self._relayA.value = 0
            burnwire.duty_cycle=0
            self.RGB=(0,0,0)
            burnwire.deinit()
            self._relayA.drive_mode=digitalio.DriveMode.OPEN_DRAIN

Development Plan

We probably need to try and get this implemented first in PicoSDK and then translate these functions over to F'.

@Mikefly123
Copy link
Member Author

We will probably set this development up as a component in #41

@Mikefly123 Mikefly123 mentioned this issue Oct 23, 2024
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants