wiki:802.11/wlan_exp/app_notes/tutorial_hop_mac/fast_hopping

Tutorial: A Custom Hopping MAC

based on Ref. Des. v1.3

Fast Hopping Approach

Rather than have an AP decree a hopping event by including the Channel Switch Announcement, instead we can let each node on the network have an a priori known frequency hopping schedule. This will not only dramatically reduce overhead, but will also increase reliability since client stations will not have to decode an explicit announcement prior to hopping to a new channel. If we can synchronize our nodes such that don't drift apart from one another, this approach can let us considerably speed up the hopping rate as compared to the prior approach (milliseconds of dwell time vs 10 seconds of dwell time).

The 802.11 standard specifies the Timing Synchronization Function (TSF) for maintaining a common timebase among nodes in a network. The TSF requires every node implement a 64-bit counter which increments every microsecond. Each node maintains its own TSF timer and derives the microsecond clock from its own local oscillator. In an infrastructure network, the TSF synchronization scheme designates the microsecond timer at the AP as the authoritative time for the network. As part of its normal operation the AP transmits beacon frames at some pre-defined interval (typically 102400 µsec). Every beacon frame contains the 64-bit value of the AP's TSF timer at the instant the transmission occurs. Critically, the beacon's timestamp field must be set at the time of transmission, not the time of packet creation or enqueuing. This guarantees the timestamp value accurately reflects any transmission delays due to medium activity. Every STA in the network which receives a beacon from the AP must update the its local microsecond timer with the value of the timestamp contained in the beacon payload. With high probability this scheme provides a common timebase across the network, with inter-node time offsets bounded by the maximum oscillator drift in one beacon interval.

In other words, with the TSF synchronization already in place as part of the Mango 802.11 Reference Design, we do not need to worry about any kind of explicit synchronization in our fast hopping approach. We can just base our frequency selection on the TSF timer and trust that the value in that timer is synchronized with the other nodes in the network.

Example timeline for fast hopping approach

In this design, the 802.11 DCF MAC will operate independently from the hopping schedule. The channel that a transmission uses is completely determined by the start time of that transmission and the frequency hopping schedule. Multiple frames can be transmitted within a single hop interval or a single transmission can extend beyond the next hop boundary. Any ongoing transmission or reception defers the next frequency tuning event until at least after the transmission or reception completes.

Changes to the 802.11 Reference Design

To implement the frequency hopping schedule, the majority of our changes will take place in CPU_LOW. We will leave the high-level AP and STA applications generally unaware of the underlying frequency hopping behavior. The singular exception to this is that we will build in hooks to enable and disable the frequency hopping so the high-level applications can control whether or not the hopping behavior is active. For example, the STA will only enable the low-level frequency hopping once it has completed an active scan and has fully associated with the AP.

Code Common to CPU_HIGH and CPU_LOW

Changes should be made to wlan_mac_ipc_util.h in the project SDK workspace zip.


We need to define one new type of IPC message between CPU_LOW and CPU_HIGH. This message will be used to enable or disable the frequency hopping behavior. Add the following definition to the wlan_mac_ipc_util.h header file:

#define IPC_MBOX_CONFIG_HOPPING         50

The value of 50 is arbitrary. The only real requirement is that it is distinct from the other IPC definitions. You can find the other IPC_MBOX_ definitions in the same file.

MAC High Framework

Changes should be made to wlan_mac_high.c in the project SDK workspace zip.


We should create two functions to enable and disable the frequency hopping behavior for our AP and STA applications to call. These function will use the new IPC message we just created. Add the following functions to the MAC High Framework:

void wlan_mac_high_enable_hopping(){

    wlan_ipc_msg       ipc_msg_to_low;
    u32                ipc_msg_to_low_payload = 1;

    // Send message to CPU Low
    ipc_msg_to_low.msg_id            = IPC_MBOX_MSG_ID(IPC_MBOX_CONFIG_HOPPING);
    ipc_msg_to_low.num_payload_words = 1;
    ipc_msg_to_low.payload_ptr       = &(ipc_msg_to_low_payload);

    ipc_mailbox_write_msg(&ipc_msg_to_low);

}

void wlan_mac_high_disable_hopping(){

    wlan_ipc_msg       ipc_msg_to_low;
    u32                ipc_msg_to_low_payload = 0;

    // Send message to CPU Low
    ipc_msg_to_low.msg_id            = IPC_MBOX_MSG_ID(IPC_MBOX_CONFIG_HOPPING);
    ipc_msg_to_low.num_payload_words = 1;
    ipc_msg_to_low.payload_ptr       = &(ipc_msg_to_low_payload);

    ipc_mailbox_write_msg(&ipc_msg_to_low);

}

Access Point (AP)

Changes should be made to wlan_mac_ap.c in the project SDK workspace zip.


We can make our changes such that it is easy to toggle between the slow Wi-Fi-compliant approach to frequency hopping or our custom fast hopping approach by defining a new value for the HOP_MODE variable we already created. For clarity, add the following comment above the HOP_MODE global variable definition.

// 0 - No hop
// 1 - Slow hop
// 2 - Fast hop
u8 HOP_MODE;


In the main() function of the AP, we had previously set HOP_MODE to a value of 0 and then let the push button callback set the variable to 1 after the Wi-Fi stations had associated. For the fast approach, we can boot the AP directly into a configuration where fast frequency hopping is enabled. Change the assignment of HOP_MODE in main() to the following:

HOP_MODE = 2;

This will also prevent the AP from including any Channel Switch Announcement tags in outgoing beacons. Finally, just before the primary while(1) loop, add the following code snippet:

    if(HOP_MODE == 2){
        wlan_mac_high_enable_hopping();
    } else {
        wlan_mac_high_disable_hopping();
    }

Station (STA)

Changes should be made to wlan_mac_sta.c in the project SDK workspace zip.


In the upcoming changes, we will make sure that the DCF in CPU_LOW boots into a state where frequency hopping is disabled. It is our job in the STA to explicitly enable the functionality. We will do this after we have associated with an AP and have adopted its TSF timebase. Add the following line to the top of the sta_set_association_state() function:

wlan_mac_high_enable_hopping();

MAC Low Framework

Changes should be made to wlan_mac_low.c in the project SDK workspace zip.


We need to process the new IPC message we created above and notify the DCF code that it should enable or disable the fast hopping mechanism. To begin, we create two new function callbacks as global variables at the top of the MAC low framework:

static function_ptr_t        enable_hopping_callback;
static function_ptr_t        disable_hopping_callback;

These callback should be set to the nullCallback in wlan_mac_low_init(). This will prevent any CPU_LOW crashes if these callbacks are executed prior to being assigned as valid function pointers.

    enable_hopping_callback = (function_ptr_t)nullCallback;
    disable_hopping_callback = (function_ptr_t)nullCallback;

We need to provide the DCF with the ability to assign these callbacks. Add the following functions to the MAC low framework:

inline void wlan_mac_low_set_enable_hopping_callback(function_ptr_t callback){
    enable_hopping_callback = callback;
}

inline void wlan_mac_low_set_disable_hopping_callback(function_ptr_t callback){
    disable_hopping_callback = callback;
}

Finally, we need to execute one of these callbacks when we receive the new IPC message. In the process_ipc_msg_from_high() function, add the following case to the large switch statement:

        case IPC_MBOX_CONFIG_HOPPING:
            if(ipc_msg_from_high_payload[0]){
                enable_hopping_callback();
            } else {
                disable_hopping_callback();
            }
        break;

DCF

Changes should be made to wlan_mac_dcf.c in the project SDK workspace zip.


We need to define the sequence of channels that a node should traverse through over time. Here, we have created a vector of length 1024 where each element is a valid 2.4 GHz Wi-Fi channel chosen pseudorandomly from a uniform distribution over the range of [1, 11]. Add this variable definition to the top of the DCF code:

#define HOP_SEQ_LEN 1024
u8 hop_vec[HOP_SEQ_LEN] = { 2, 10, 9, 6, 1, 3, 11, 8, 3, 8, 2, 7, 9, 5, 1, 7, 8, 2, 10, 6,
 4, 4, 8, 6, 5, 4, 7, 1, 7, 11, 6, 4, 9, 2, 1, 5, 6, 9, 8, 6,
 3, 2, 5, 5, 11, 11, 4, 10, 7, 6, 10, 9, 6, 1, 5, 5, 7, 2, 7, 5,
 1, 2, 5, 6, 4, 10, 7, 3, 2, 9, 8, 4, 1, 5, 6, 6, 10, 11, 5, 3,
 11, 8, 11, 8, 8, 5, 1, 7, 9, 8, 1, 2, 9, 6, 5, 4, 3, 11, 5, 2,
 6, 1, 7, 5, 9, 2, 6, 7, 1, 6, 9, 2, 8, 10, 6, 1, 4, 11, 2, 9,
 4, 4, 6, 5, 11, 4, 2, 3, 2, 9, 7, 8, 8, 7, 4, 2, 10, 4, 7, 6,
 8, 3, 4, 8, 11, 4, 10, 10, 10, 9, 3, 11, 10, 10, 9, 6, 10, 11, 1, 9,
 6, 7, 11, 10, 11, 4, 4, 4, 4, 7, 4, 11, 8, 9, 3, 2, 1, 7, 7, 5,
 1, 2, 6, 9, 9, 11, 9, 5, 4, 2, 7, 3, 2, 9, 6, 8, 2, 5, 7, 10,
 11, 4, 5, 2, 8, 3, 10, 5, 5, 2, 1, 9, 9, 6, 10, 7, 2, 1, 2, 4,
 9, 9, 2, 9, 2, 8, 1, 5, 9, 6, 11, 4, 7, 3, 5, 11, 4, 10, 8, 1,
 6, 8, 5, 2, 7, 11, 11, 11, 10, 8, 10, 10, 9, 2, 1, 6, 2, 5, 4, 5,
 11, 10, 11, 3, 1, 10, 8, 7, 3, 6, 1, 5, 3, 4, 11, 10, 3, 5, 9, 9,
 1, 3, 1, 8, 10, 4, 8, 2, 5, 10, 8, 8, 10, 4, 8, 4, 11, 9, 10, 9,
 9, 5, 2, 2, 9, 1, 6, 10, 11, 6, 1, 6, 3, 4, 11, 2, 9, 4, 6, 2,
 1, 10, 1, 10, 5, 5, 4, 1, 8, 4, 8, 9, 8, 6, 9, 1, 1, 5, 2, 9,
 1, 2, 5, 3, 11, 5, 6, 3, 7, 6, 11, 8, 10, 1, 5, 1, 7, 9, 8, 1,
 8, 4, 4, 8, 2, 3, 5, 7, 4, 5, 4, 11, 8, 2, 8, 1, 9, 1, 5, 8,
 1, 5, 2, 7, 3, 5, 10, 1, 8, 4, 10, 4, 5, 11, 10, 10, 11, 2, 4, 8,
 7, 10, 3, 4, 3, 1, 8, 4, 1, 4, 2, 1, 7, 1, 10, 1, 6, 2, 5, 10,
 8, 11, 5, 5, 9, 10, 8, 2, 4, 3, 3, 8, 4, 11, 10, 7, 10, 4, 7, 7,
 4, 7, 2, 9, 10, 8, 1, 11, 1, 3, 5, 5, 2, 11, 2, 9, 8, 4, 2, 11,
 10, 2, 5, 6, 9, 10, 11, 8, 7, 10, 5, 10, 9, 10, 5, 1, 10, 7, 9, 7,
 11, 2, 8, 2, 1, 7, 7, 9, 1, 1, 7, 9, 3, 6, 7, 7, 1, 3, 2, 6,
 9, 11, 1, 10, 10, 6, 5, 1, 2, 8, 7, 8, 5, 9, 6, 10, 1, 7, 9, 4,
 6, 10, 6, 9, 7, 10, 5, 2, 11, 5, 6, 8, 1, 4, 9, 1, 1, 4, 4, 9,
 3, 5, 8, 10, 7, 11, 11, 7, 11, 10, 4, 11, 10, 5, 9, 4, 2, 9, 7, 7,
 3, 4, 9, 7, 4, 4, 3, 6, 7, 1, 2, 6, 6, 6, 2, 2, 5, 8, 9, 7,
 7, 11, 6, 4, 3, 3, 5, 7, 11, 4, 4, 7, 3, 2, 1, 6, 1, 7, 7, 1,
 5, 9, 7, 5, 6, 5, 1, 9, 9, 7, 7, 7, 7, 3, 9, 8, 9, 2, 1, 9,
 9, 7, 1, 4, 11, 10, 8, 2, 8, 1, 7, 6, 8, 5, 1, 5, 6, 1, 7, 4,
 10, 11, 3, 4, 2, 8, 8, 7, 5, 7, 6, 8, 10, 4, 7, 1, 3, 9, 4, 1,
 8, 8, 8, 10, 2, 8, 5, 8, 4, 8, 4, 11, 5, 8, 9, 3, 8, 9, 8, 2,
 2, 9, 11, 9, 11, 7, 6, 1, 7, 9, 9, 3, 9, 8, 11, 5, 1, 4, 4, 6,
 1, 2, 3, 9, 10, 7, 4, 3, 1, 10, 3, 11, 2, 6, 5, 9, 11, 4, 11, 2,
 1, 10, 11, 7, 1, 10, 10, 7, 6, 1, 5, 9, 6, 6, 10, 1, 3, 2, 1, 4,
 10, 2, 11, 2, 6, 5, 8, 10, 4, 4, 1, 3, 9, 11, 8, 6, 8, 1, 10, 8,
 2, 6, 6, 7, 11, 7, 7, 11, 1, 8, 2, 7, 9, 1, 6, 4, 8, 7, 1, 10,
 11, 2, 1, 11, 5, 9, 11, 10, 11, 4, 8, 10, 10, 10, 1, 10, 7, 7, 9, 6,
 10, 2, 8, 7, 6, 3, 11, 11, 8, 2, 6, 6, 1, 5, 9, 9, 2, 5, 9, 3,
 7, 1, 8, 10, 8, 4, 6, 4, 3, 9, 7, 6, 11, 9, 7, 9, 7, 1, 6, 10,
 5, 3, 6, 5, 9, 9, 4, 11, 4, 3, 2, 4, 11, 8, 3, 4, 4, 10, 6, 7,
 9, 4, 3, 10, 1, 1, 4, 10, 6, 4, 6, 8, 7, 10, 1, 3, 8, 5, 4, 1,
 4, 5, 7, 3, 10, 10, 11, 7, 4, 9, 6, 7, 8, 1, 8, 4, 3, 4, 4, 3,
 2, 8, 4, 3, 7, 1, 10, 1, 3, 4, 8, 7, 8, 4, 11, 4, 6, 2, 10, 3,
 1, 5, 11, 3, 4, 6, 7, 5, 8, 2, 1, 1, 7, 10, 7, 6, 8, 5, 3, 7,
 7, 9, 3, 4, 1, 4, 4, 8, 7, 9, 10, 10, 1, 6, 1, 9, 4, 3, 3, 2,
 3, 10, 9, 9, 3, 2, 1, 4, 8, 7, 7, 3, 5, 4, 10, 9, 10, 5, 9, 1,
 11, 8, 3, 9, 6, 10, 4, 7, 3, 2, 3, 5, 8, 1, 8, 3, 10, 7, 6, 2,
 3, 5, 11, 6, 2, 11, 6, 10, 4, 9, 1, 3, 9, 8, 10, 7, 6, 9, 10, 10,
 10, 8, 5, 1};


Next, we should create a top-level global variable that represents whether or not the frequency hopping mechanism is currently enabled. Add the following new global variable to the DCF code:

static u8 enable_hop;

Next, we'll create setter functions that control this variable. Add the following functions to the DCF code:

void enable_hopping(){
    enable_hop = 1;
}

void disable_hopping(){
    enable_hop = 0;
}

We will now attach these new functions to the callbacks we just created in the MAC Low Framework. We will also explicitly call the disable_hopping() function at boot and wait for a CPU_HIGH project to explicitly tell us when we should enable the frequency hopping behavior. Add the following lines to the initialize code in main():

    disable_hopping();
    wlan_mac_low_set_enable_hopping_callback((void*)enable_hopping);
    wlan_mac_low_set_disable_hopping_callback((void*)disable_hopping);


The biggest addition we need to make to the DCF is to create a function that polls the TSF timer and tunes to the scheduled channel when needed. The absolute TSF timer value is mapped onto an index into the hopping sequence defined in the global variable hop_vec. If the index has changed since the last iteration, the radio is immediately re-tuned to the new center frequency. The mapping of timer values to hopping sequence indexes is arbitrary as long as the mapping is the same on every node. This implementation uses a simple quantization function to implement the mapping.

Furthermore, this function should look out for a subtle race condition that might occur if a packet has been submitted to the MAC Support Core prior to the execution of this function. If a transmission is currently underway, this function should not change to a new frequency even if the schedule indicates otherwise. After the transmission is complete, it is safe to tune to the new channel.

Finally, we'll provide a return value with this function that indicates whether or not it is safe to submit new transmissions to the MAC Support Core. With this feature, we can enforce an arbitrarily long guard interval just prior to a scheduled channel hop boundary to account for any jitter caused by imperfect synchronization at various nodes in the network. Add the following function to the DCF code:

//TS_USEC_SHIFT of 12 will increment our hop index every 4096 usec.
#define TS_USEC_SHIFT 12

inline int poll_hop(){
    u64 curr_timestamp;
    static u8 curr_channel = 0;
    u8 next_channel;
    u32 curr_hop_num = 0;
    u32 mac_hw_status;

    curr_timestamp = 0;
    if(enable_hop){
        curr_timestamp = get_usec_timestamp();
        curr_hop_num = curr_timestamp>>TS_USEC_SHIFT;
        next_channel = hop_vec[((curr_hop_num)%HOP_SEQ_LEN)];

        if(next_channel != curr_channel){

            //Update DIFS to be safe
            //This will help ensure that any currently-pending transmissions don't start while we are changing channels
            wlan_mac_set_DIFS((T_DIFS+255)*10);
            wlan_mac_set_TxDIFS(((T_DIFS+255)*10) - (TX_PHY_DLY_100NSEC));

            //Even with the above DIFS tweak, there is still a race condition if a transmission just started. To handle this
            //race, we will explicitly check to see if the PHY is running and quit this function if so.
            //We should make sure the Tx PHY isn't currently running.
            mac_hw_status = wlan_mac_get_status();

            if((mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_PENDING) && (mac_hw_status & WLAN_MAC_STATUS_MASK_TX_PHY_ACTIVE)){
                //Ongoing transmission. Don't change channels

                //Reset DIFS
                wlan_mac_set_DIFS((T_DIFS)*10);
                wlan_mac_set_TxDIFS(((T_DIFS)*10) - (TX_PHY_DLY_100NSEC));

                return -1;
            }

            curr_channel = next_channel;

            //Raise the debug GPIO so we can see how long this takes
            //We will probe these pins on an oscilloscope
            REG_SET_BITS(WLAN_RX_DEBUG_GPIO, 0xFF);

            //Tune to the new frequency
            radio_controller_setCenterFrequency(RC_BASEADDR, (RC_ALL_RF), RC_24GHZ, wlan_mac_low_wlan_chan_to_rc_chan(next_channel));
            wlan_mac_reset_NAV_counter();

            //Reset DIFS
            wlan_mac_set_DIFS((T_DIFS)*10);
            wlan_mac_set_TxDIFS(((T_DIFS)*10) - (TX_PHY_DLY_100NSEC));

            //Lower the debug GPIO
            REG_CLEAR_BITS(WLAN_RX_DEBUG_GPIO, 0xFF);

        }
    }


    //Poll IPC rx
    //Here, we slice of the bottom 12 bits of the usec counter. If that value is
    //less that 4000 usec out of the 4096 usec hop dwell time, we'll return
    //a value of 0 to let the calling function know it is safe to start a new
    //transmission. If we are in the window of 96 usec at the end of the dwell,
    //we'll return a value of -1 to let the calling function know that it isn't safe
    //to start any new transmissions
    if((curr_timestamp & 0xFFF) < 4000){
        //If we are currently outside of the guard interval at the end of a hop
        return 0;
    } else {
        //If we are currently inside of the guard interval at the end of a hop
        return -1;
    }
}


Finally, we need to call the poll_hop function. The first place we should call it is the primary while(1) loop in main(). In addition to calling this function, we should use the return value of this function to condition whether or not we allow new MPDU transmissions. Since MPDU transmissions being via IPC messages from CPU_HIGH, we can simply only poll for new IPC messages if poll_hop returns a value of 0. Replace the while(1) loop with the following code snippet:

    while(1){
        //Poll PHY RX start
        wlan_mac_low_poll_frame_rx();

        //Poll the frequency hopping schedule and re-tune if needed
        if(poll_hop() == 0){
            //Poll IPC rx
            wlan_mac_low_poll_ipc_rx();
        }

    }

Next, we also need to call poll_hop in the context of the frame_transmit() function. If a transmission attempt fails (i.e. it goes un acknowledged), the DCF's error recovery system will allow a node to retransmit the frame. Between attempts, we need to be able to hop to new channel if the schedule dictates. Because the DCF code remains in the frame_transmit() context across retransmissions, the poll we added above to main() will not be called. We need to add a call to poll_hop while the transmit state machine in the MAC Support Core is pending, but not done. The below code snippet tells you where to place the new call to poll_hop():

//Wait for the MPDU Tx to finish
do { //while(tx_status & WLAN_MAC_STATUS_MASK_TX_A_PENDING)

    //Poll the DCF core status register
    mac_hw_status = wlan_mac_get_status();

    if( mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_DONE ) {
        // ...
    } else { //else for if(mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_DONE)
        if( mac_hw_status & \
            (WLAN_MAC_STATUS_MASK_RX_PHY_ACTIVE | \
            WLAN_MAC_STATUS_MASK_RX_PHY_BLOCKED_FCS_GOOD | \
            WLAN_MAC_STATUS_MASK_RX_PHY_BLOCKED) ) {
            // ...
        } else {
            //We should poll our schedule and move to a new channel if need be
            poll_hop();
        }
    }
} while( mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_PENDING );   

Characterization

Hop Synchronization and Overhead

To first characterize the design, we can use an oscilloscope to explicitly measure the frequency of frequency re-tune events as well as the duration of time it takes to re-tune the radio. In our implementation of poll_hop() above, we wrapped the call to the radio controller that tuned to a new frequency with a set and clear of the software debug GPIO signals. The following oscilloscope captures were taken by probing the software debug pins on both the AP and STA WARP v3 hardware.

Oscilloscope Plots

In the above captures, the yellow signal corresponds to the debug pin on the AP and the blue signal corresponds to the debug pin on the STA. There are three key observations from the above captures:

  1. The period between re-tune events is around 4.096 ms. This is consistent with our code above that set TS_USEC_SHIFT.
  2. The re-tune duration is about 93 µs. This time, plus our guard interval of an additional 96 µs, represents overhead in our implementation since this is time that a node can neither transmit nor receive. 189 µs out of a 4096 µs dwell time gives us an effective duty cycle of 95%.
  3. The rise of the debug signal of the STA occurs within a couple µs of the rise of the debug signal at the AP. The jitter here is an artifact of the over-the-air synchronization occurring with the 802.11's TSF synchronization. The fact this value is so small informs us that our 96 µs of guard interval above is probably over provisioned and can be significantly reduced without incurring additional loss.

Throughput Performance

The previous oscilloscope plots tell us that our boards are able to stay synchronized across frequency hops and that the overhead of re-tuning is fairly small. We can test the real-world impact of this hopping structure by simply performing a throughput test while the nodes are hopping through the available channels. To do this, we use the WLAN Experiment Framework and run the existing txrx_log_capture and txrx_log_analysis examples. We have not changed any of the parameters in these scripts, so the results are directly comparable to the stock non-hopping design presented on those pages.

Fast Hopping 802.11 Throughput vs. Time

Using the 18 Mbps PHY rate, we can see that the achieved unicast throughput is a little under 14 Mbps. This is consistent with a very slight reduction compared to the stock design's performance. As we expect, the overhead of implementing the frequency hopping structure is minimal.

Spectrogram

Finally, we can see the effect of this fast hopping strategy on the spectrum by capturing another spectrogram image while a backlogged communication link is taking place.

Standard 802.11
Slow Frequency Hopping 802.11
Fast Frequency Hopping 802.11

For completion, the above screenshots include both the standard non-hopping 802.11 as well as the slow beacon-based approach presented in the prior section. The energy appears to be spread evenly across the spectrum, since the hopping frequency is much too fast to see individual hops with this spectrum analyzer.

Video Demonstration

Last modified 9 years ago Last modified on Aug 4, 2015, 3:49:37 PM