diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 82c06d3b3b25a6fbb87fb0ac5f38c0e69e354adc..8aa73488fbdb581fe3c51c652246e443175391cf 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -26,6 +26,8 @@ set(SRCS
   detectors/bmon/UnpackBmon.cxx
   detectors/trd/TrdReadoutConfig.cxx
   detectors/trd/UnpackTrd.cxx
+  detectors/trd2d/Trd2dReadoutConfig.cxx
+  detectors/trd2d/UnpackTrd2d.cxx
 )
 
 set(BUILD_INFO_CXX ${CMAKE_CURRENT_BINARY_DIR}/base/BuildInfo.cxx)
@@ -59,6 +61,7 @@ target_include_directories(Algo
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors/tof
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors/bmon
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors/trd
+         ${CMAKE_CURRENT_SOURCE_DIR}/detectors/trd2d
  )
 
 target_link_libraries(Algo
diff --git a/algo/detectors/trd2d/Trd2dReadoutConfig.cxx b/algo/detectors/trd2d/Trd2dReadoutConfig.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..115f4bc699c4116a6d6fabc502bb1752ce8c2768
--- /dev/null
+++ b/algo/detectors/trd2d/Trd2dReadoutConfig.cxx
@@ -0,0 +1,169 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#include "Trd2dReadoutConfig.h"
+
+//#include "CbmTrdAddress.h"
+
+#include <cassert>
+#include <iomanip>
+#include <iostream>
+#include <iterator>
+
+using std::pair;
+using std::setw;
+
+namespace cbm::algo
+{
+
+  // ---  Constructor  ------------------------------------------------------------------
+  Trd2dReadoutConfig::Trd2dReadoutConfig() {}
+
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Destructor   -----------------------------------------------------------------
+  Trd2dReadoutConfig::~Trd2dReadoutConfig() {}
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Equipment IDs   --------------------------------------------------------------
+  std::vector<uint16_t> Trd2dReadoutConfig::GetEquipmentIds()
+  {
+    std::vector<uint16_t> result;
+    for (auto& entry : fReadoutMap)
+      result.push_back(entry.first);
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Number of Asics for a component / equipment   -------------------------------
+  size_t Trd2dReadoutConfig::GetNumAsics(uint16_t equipmentId)
+  {
+    size_t result = 0;
+    auto it       = fChannelMap.find(equipmentId);
+    if (it != fChannelMap.end()) result = fChannelMap[equipmentId].size();
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Number of Channels for a component / equipment, asic pair  ---------------------
+  size_t Trd2dReadoutConfig::GetNumChans(uint16_t equipmentId, uint16_t asicId)
+  {
+    size_t result = 0;
+    auto it       = fChannelMap.find(equipmentId);
+    if (it != fChannelMap.end()) {
+      if (asicId < fChannelMap[equipmentId].size()) { result = fChannelMap[equipmentId][asicId].size(); }
+    }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Initialise the component mapping structure   ----------------------------------
+  void Trd2dReadoutConfig::InitComponentMap(const std::map<uint32_t, uint16_t[NCROBMOD]>& map)
+  {
+    // Receive map (moduleId, crobId) -> (equipId)
+    // Invert to obtain component map (equipId) -> (module iq, crob id)
+    for (auto& entry : map) {
+      uint16_t mod_id = entry.first;
+      for (uint8_t crob_id = 0; crob_id < NCROBMOD; crob_id++) {
+        uint16_t eq_id     = entry.second[crob_id];
+        fReadoutMap[eq_id] = std::make_pair(mod_id, crob_id);
+      }
+    }
+  }
+
+  // ---  Initialise the mapping structure   --------------------------------------------
+  void Trd2dReadoutConfig::InitChannelMap(
+    const std::map<size_t, std::map<size_t, std::map<size_t, std::tuple<int32_t, bool, uint64_t>>>>& channelMap)
+  {
+    // Constructing the map (equipId, asicId, chanId) -> (pad address, R pairing flag, daq offset)
+    for (auto compMap : channelMap) {
+      uint16_t equipmentId = compMap.first;
+      uint16_t numAsics    = compMap.second.size();
+      fChannelMap[equipmentId].resize(numAsics);
+
+      for (auto asicMap : compMap.second) {
+        uint16_t asicId   = asicMap.first;
+        uint16_t numChans = asicMap.second.size();
+        fChannelMap[equipmentId][asicId].resize(numChans);
+
+        for (auto chanMap : asicMap.second) {
+          uint16_t chanId                              = chanMap.first;
+          std::tuple<int32_t, bool, uint64_t> chanPars = chanMap.second;
+          fChannelMap[equipmentId][asicId][chanId]     = chanPars;
+        }
+      }
+    }
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Mapping (equimentId, asicId, channel) -> (pad address, R pairing flag, daq offset)  -----
+  std::tuple<int32_t, bool, uint64_t> Trd2dReadoutConfig::ChanMap(uint16_t equipId, uint16_t asic, uint16_t chan)
+  {
+    std::tuple<int32_t, bool, uint64_t> result = std::make_tuple(-1, false, 0);
+    auto it                                    = fChannelMap.find(equipId);
+    if (it != fChannelMap.end()) {
+      if (asic < fChannelMap[equipId].size()) {
+        if (chan < fChannelMap[equipId][asic].size()) { result = fChannelMap[equipId][asic][chan]; }
+      }
+    }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Mapping (equimentId) -> (module id, crob id)  ---------------------------------
+  std::pair<uint16_t, uint8_t> Trd2dReadoutConfig::CompMap(uint16_t equipId)
+  {
+    std::pair<uint16_t, uint8_t> result(0, 0);
+    auto equipIter = fReadoutMap.find(equipId);
+    if (equipIter != fReadoutMap.end()) { result = equipIter->second; }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // -----   Print readout map   ------------------------------------------------
+  std::string Trd2dReadoutConfig::PrintReadoutMap()
+  {
+    std::stringstream ss;
+    for (auto comp : fReadoutMap) {
+      uint16_t equipmentId = comp.first;
+      auto value           = comp.second;
+      uint16_t moduleId    = value.first;
+      uint16_t crobId      = value.second;
+      ss << "Equipment " << equipmentId << " Module " << moduleId << " Crob " << crobId << "\n";
+    }
+    ss << "\n";
+
+    for (auto asicMap : fChannelMap) {
+      uint16_t equipmentId = asicMap.first;
+      uint16_t numAsics    = asicMap.second.size();
+      ss << "\n Equipment " << equipmentId << " nAsics " << numAsics;
+      for (size_t asicId = 0; asicId < numAsics; asicId++) {
+
+        uint16_t numChans = asicMap.second.at(asicId).size();
+        ss << "\n Equipment " << equipmentId << " AsicId " << asicId << " nChans " << numChans;
+        for (size_t chanId = 0; chanId < numChans; chanId++) {
+          auto entry         = asicMap.second.at(asicId).at(chanId);
+          int32_t address    = std::get<0>(entry);
+          bool hasPairingR   = std::get<1>(entry);
+          uint64_t daqOffset = std::get<2>(entry);
+          ss << "\n Equipment " << equipmentId << " AsicId " << asicId << " chanID " << chanId << " pad address "
+             << address << " pairingR " << hasPairingR << " daq offset " << daqOffset;
+        }
+      }
+    }
+    ss << "\n";
+    return ss.str();
+  }
+  // ----------------------------------------------------------------------------
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/trd2d/Trd2dReadoutConfig.h b/algo/detectors/trd2d/Trd2dReadoutConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..2737a0b7cc467f29f7eb3d2041dce4ad75dd9b1f
--- /dev/null
+++ b/algo/detectors/trd2d/Trd2dReadoutConfig.h
@@ -0,0 +1,102 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese, Dominik Smith [committer] */
+
+#ifndef ALGO_DETECTORS_TRD2D_TRD2DREADOUTCONFIG_H
+#define ALGO_DETECTORS_TRD2D_TRD2DREADOUTCONFIG_H
+
+#include <map>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include "UnpackTrd2d.h"
+
+namespace cbm::algo
+{
+
+
+  /** @class Trd2dReadoutConfig
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 3 March 2022
+   ** @brief Provides the hardware-to-software address mapping for the CBM-TRD2D
+   **
+   ** The hardware address as provided in the raw data stream is specified in terms of the
+   ** equipment identifier (specific to one FLES component) and the elink number with in
+   ** component. This is to be translated into the module address and the ASIC number within
+   ** the module.
+   ** The mapping of the two address spaces is hard-coded in this class.
+   **/
+
+  class Trd2dReadoutConfig {
+
+  public:
+    /** @brief Constructor **/
+    Trd2dReadoutConfig();
+
+
+    /** @brief Destructor **/
+    virtual ~Trd2dReadoutConfig();
+
+
+    /** @brief Equipment in the configuration
+     ** @return Vector of equipment IDs
+     **/
+    std::vector<uint16_t> GetEquipmentIds();
+
+
+    /** @brief Number of ASICS of a component
+     ** @param Equipment ID
+     ** @return Number of ASICS
+     **/
+    size_t GetNumAsics(uint16_t equipmentId);
+
+
+    /** @brief Number of channels of a component - ASIC pair
+     ** @param Equipment ID
+     ** @param ASIC ID
+     ** @return Number of channels
+     **/
+    size_t GetNumChans(uint16_t equipmentId, uint16_t asicId);
+
+    //// TO DO: Check uint sizes (for asic Id etc).
+
+    /** @brief API: Mapping from component to pair (module id, crob id)
+     ** @param equipId     Equipment identifier (component)
+     ** @return pair (module id, crob id)
+     */
+    std::pair<uint16_t, uint8_t> CompMap(uint16_t equipId);
+
+
+    /** @brief API: Mapping from component, asic and channel to tuple (pad address, R pairing flag, daq offset)
+     ** @param equipId     Equipment identifier (component)
+     ** @param asic        ASIC number within component
+     ** @param channel     Channel number within CROB
+     ** @return tuple (pad address, R pairing flag, daq offset)
+     */
+    std::tuple<int32_t, bool, uint64_t> ChanMap(uint16_t equipId, uint16_t asic, uint16_t chan);
+
+
+    /** @brief Debug output of readout map **/
+    std::string PrintReadoutMap();
+
+    /** @brief Initialisation of readout map **/
+    void InitComponentMap(const std::map<uint32_t, uint16_t[NCROBMOD]>& crob_map);
+
+    /** @brief Initialisation of channel map **/
+    void InitChannelMap(
+      const std::map<size_t, std::map<size_t, std::map<size_t, std::tuple<int32_t, bool, uint64_t>>>>& channelMap);
+
+  private:
+    // --- TRD2D readout map
+    // --- Map index: (equipment), map value: (module id, crob id)
+    std::map<uint16_t, std::pair<uint16_t, uint8_t>> fReadoutMap = {};  //!
+
+    // --- TRD2D channel map
+    // --- Map index: (equipment, asic, chan), map value: (pad address, R pairing flag, daq offset)
+    std::map<uint16_t, std::vector<std::vector<std::tuple<int32_t, bool, uint64_t>>>> fChannelMap = {};  //!
+  };
+
+} /* namespace cbm::algo */
+
+#endif /* ALGO_DETECTORS_TRD2D_TRD2DREADOUTCONFIG_H_ */
diff --git a/algo/detectors/trd2d/UnpackTrd2d.cxx b/algo/detectors/trd2d/UnpackTrd2d.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..087f4a0b2cbbd3db48b424b75105aa0523ea2924
--- /dev/null
+++ b/algo/detectors/trd2d/UnpackTrd2d.cxx
@@ -0,0 +1,193 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pascal Raisig, Dominik Smith [committer] */
+
+#include "UnpackTrd2d.h"
+
+#include <Logger.h>
+
+#include <algorithm>
+#include <cassert>
+#include <vector>
+
+using std::unique_ptr;
+
+namespace cbm::algo
+{
+  // ----   Fasp message constructor  ----------------------------------------
+  CbmTrdFaspMessage::CbmTrdFaspMessage(uint8_t c, uint8_t typ, uint8_t t, uint16_t d, uint8_t rob, uint8_t asic)
+    : ch(c)
+    , type(typ)
+    , tlab(t)
+    , data(d)
+    , crob(rob)
+    , fasp(asic)
+  {
+  }
+
+  // ----   Algorithm execution   ---------------------------------------------
+  UnpackTrd2d::resultType UnpackTrd2d::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                                                  const uint64_t tTimeslice)
+  {
+    // --- Output data
+    resultType result = {};
+
+    // define time wrt start of time slice in TRD/FASP clks [80 MHz]. Contains:
+    //  - relative offset of the MS wrt the TS
+    //  - FASP epoch offset for current CROB
+    //  - TRD2D system offset wrt to experiment time (e.g. T0)
+    uint64_t time = uint64_t((msDescr.idx - tTimeslice - fSystemTimeOffset) / 12.5);
+
+    // Get parameters for current eq id.
+    const uint16_t mod_id = fParams.fModId;
+    const uint8_t crob_id = fParams.fCrobId;
+
+    // Get the number of complete words in the input MS buffer.
+    const uint32_t nwords = msDescr.size / 4;
+
+    // We have 32 bit spadic frames in this readout version
+    const uint32_t* wd = reinterpret_cast<const uint32_t*>(msContent);
+
+    unsigned char lFaspOld(0xff);
+    std::vector<CbmTrdFaspMessage> vMess;
+    for (uint64_t j = 0; j < nwords; j++, wd++) {
+      uint32_t w    = *wd;
+      uint8_t ch_id = w & 0xf;
+      uint8_t isaux = (w >> 4) & 0x1;
+      uint8_t slice = (w >> 5) & 0x7f;
+      uint16_t data = (w >> 12) & 0x3fff;
+      // uint8_t fasp_id = ((w >> 26) & 0x3f) + crob_id * NFASPCROB;
+      uint8_t fasp_id = ((w >> 26) & 0x3f);
+
+      if (isaux) {
+        if (ch_id == 0) {
+          // clear buffer
+          if (vMess.size()) { pushDigis(vMess, time); }
+          vMess.clear();
+
+          lFaspOld = 0xff;
+          time += FASP_EPOCH_LENGTH;
+        }
+        continue;
+      }
+      //if (fFaspMap) fasp_id = ((*fFaspMap)[mod_id])[fasp_id];
+
+      if (lFaspOld != fasp_id) {
+        if (vMess.size()) { pushDigis(vMess, time); }
+        vMess.clear();
+        lFaspOld = fasp_id;
+      }
+      if (data & 0x1) {
+        LOG(warn) << "UnpackTrd2d - Data corrupted : detect end bit set.";
+        continue;
+      }
+      if (data & 0x2000) {
+        LOG(debug) << "UnpackTrd2d - Self-triggered data.";
+        data &= 0x1fff;
+      }
+      vMess.emplace_back(ch_id, kData, slice, data >> 1, crob_id, lFaspOld);
+    }
+    result.first = FinalizeComponent();
+
+    return result;
+  }
+
+  //_________________________________________________________________________________
+  bool UnpackTrd2d::pushDigis(std::vector<CbmTrdFaspMessage> messes, const uint64_t time)
+  {
+    const uint16_t mod_id             = fParams.fModId;
+    const UnpackTrd2dAsicPar& asicPar = fParams.fAsicParams[messes[0].fasp];
+    const uint64_t tdaqOffset         = asicPar.fChanParams[messes[0].ch].fDaqOffset;
+
+    for (auto imess : messes) {
+      const int32_t pad                   = asicPar.fChanParams[imess.ch].fPadAddress;
+      const bool hasPairingR              = asicPar.fChanParams[imess.ch].fHasPairingR;
+      const uint64_t lTime                = time + tdaqOffset + imess.tlab;
+      const uint16_t lchR                 = hasPairingR ? imess.data : 0;
+      const uint16_t lchT                 = hasPairingR ? 0 : imess.data;
+      std::vector<CbmTrdDigi>& digiBuffer = fDigiBuffer[pad];
+
+      if (digiBuffer.size() == 0) {  // init pad position in map and build digi for message
+        digiBuffer.emplace_back(pad, lchT, lchR, lTime);
+        digiBuffer.back().SetAddressModule(mod_id);
+        continue;
+      }
+
+      // check if last digi has both R/T message components. Update if not and is within time window
+      auto id = digiBuffer.rbegin();  // Should always be valid here.
+                                      // No need to extra check
+      double r, t;
+      int32_t dt;
+      const int32_t dtime = (*id).GetTimeDAQ() - lTime;
+      bool use(false);
+
+      if (abs(dtime) < 5) {  // test message part of (last) digi
+        r = (*id).GetCharge(t, dt);
+        if (lchR && r < 0.1) {  // set R charge on an empty slot
+          (*id).SetCharge(t, lchR, -dtime);
+          use = true;
+        }
+        else if (lchT && t < 0.1) {  // set T charge on an empty slot
+          (*id).SetCharge(lchT, r, +dtime);
+          (*id).SetTimeDAQ(uint64_t((*id).GetTimeDAQ() - dtime));
+          use = true;
+        }
+      }
+
+      // build digi for message when update failed
+      if (!use) {
+        digiBuffer.emplace_back(pad, lchT, lchR, lTime);
+        digiBuffer.back().SetAddressModule(mod_id);
+        id = digiBuffer.rbegin();
+      }
+
+      // update charge for previously allocated digis to account for FASPRO ADC buffering and read-out feature
+      for (++id; id != digiBuffer.rend(); ++id) {
+        r = (*id).GetCharge(t, dt);
+        if (lchR && int(r)) {  // update R charge and mark on digi
+          (*id).SetCharge(t, lchR, dt);
+          (*id).SetFlag(1);
+          break;
+        }
+        else if (lchT && int(t)) {  // update T charge and mark on digi
+          (*id).SetCharge(lchT, r, dt);
+          (*id).SetFlag(0);
+          break;
+        }
+      }
+    }
+    messes.clear();
+
+    return true;
+  }
+
+  std::vector<CbmTrdDigi> UnpackTrd2d::FinalizeComponent()
+  {
+    std::vector<CbmTrdDigi> outputDigis;
+
+    for (uint16_t ipad(0); ipad < NFASPMOD * NFASPCH; ipad++) {
+      if (!fDigiBuffer[ipad].size()) continue;
+      uint nIncomplete(0);
+      for (auto id = fDigiBuffer[ipad].begin(); id != fDigiBuffer[ipad].end(); id++) {
+        double r, t;
+        int32_t dt;
+        r = (*id).GetCharge(t, dt);
+        // check if digi has all signals CORRECTED
+        if (((t > 0) != (*id).IsFlagged(0)) || ((r > 0) != (*id).IsFlagged(1))) {
+          nIncomplete++;
+          continue;
+        }
+        // reset flags as they were used only to mark the correctly setting of the charge/digi
+        (*id).SetFlag(0, false);
+        (*id).SetFlag(1, false);
+        outputDigis.emplace_back(std::move((*id)));
+      }
+      // clear digi buffer wrt the digi which was forwarded to higher structures
+      fDigiBuffer[ipad].clear();
+      if (nIncomplete > 2) {
+        LOG(warn) << "FinalizeComponent() skip " << nIncomplete << " incomplete digi at pad " << ipad << ".\n";
+      }
+    }
+    return outputDigis;
+  }
+} /* namespace cbm::algo */
diff --git a/algo/detectors/trd2d/UnpackTrd2d.h b/algo/detectors/trd2d/UnpackTrd2d.h
new file mode 100644
index 0000000000000000000000000000000000000000..c1f179cc2ec60831a3c9e2a46a53f7c7a6be9856
--- /dev/null
+++ b/algo/detectors/trd2d/UnpackTrd2d.h
@@ -0,0 +1,173 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pascal Raisig, Dominik Smith [committer] */
+
+#ifndef CBM_ALGO_UNPACKTRD2D_H
+#define CBM_ALGO_UNPACKTRD2D_H 1
+
+
+#include "CbmTrdDigi.h"
+#include "CbmTrdRawMessageSpadic.h"
+
+#include "MicrosliceDescriptor.hpp"
+#include "Timeslice.hpp"
+
+#include <memory>
+
+#include <cmath>
+
+#define NFASPMOD 180
+#define NCROBMOD 5
+#define NFASPCROB NFASPMOD / NCROBMOD
+#define NFASPCH 16
+
+#define FASP_EPOCH_LENGTH 128
+
+namespace cbm::algo
+{
+
+  enum CbmTrdFaspMessageType
+  {
+    kEpoch = 0,
+    kData
+  };
+
+  /** @brief Data structure for unpacking the FASP word */
+  struct CbmTrdFaspMessage {
+    CbmTrdFaspMessage(uint8_t c, uint8_t typ, uint8_t t, uint16_t d, uint8_t rob, uint8_t asic);
+    uint8_t ch     = 0;  ///< ch id in the FASP
+    uint8_t type   = 0;  ///< message type 0 = epoch, 1 = data (not used for the moment)
+    uint8_t tlab   = 0;  ///< time of the digi inside the epoch
+    uint16_t data  = 0;  ///< ADC value
+    uint32_t epoch = 0;  ///< epoch id (not used for the moment)
+    uint32_t mod   = 0;  ///< full module address according to CbmTrdAddress
+    uint8_t crob   = 0;  ///< CROB id in the module
+    uint8_t fasp   = 0;  ///< FASP id in the module
+  };
+
+  /** @struct UnpackTrd2dChannelPar
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023
+   ** @brief TRD Unpacking parameters for one Asic channel
+   **/
+  struct UnpackTrd2dChannelPar {
+    int32_t fPadAddress;      ///< Pad address for channel
+    bool fHasPairingR;        ///< Flag for R or T compoment
+    uint64_t fDaqOffset = 0;  ///< Time calibration parameter
+  };
+
+  /** @struct UnpackTrd2dAsicPar
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023
+   ** @brief TRD Unpacking parameters for one Asic
+   **/
+  struct UnpackTrd2dAsicPar {
+    std::vector<UnpackTrd2dChannelPar> fChanParams;  ///< Parameters for different channels
+  };
+
+  /** @struct UnpackTrd2dPar
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023
+   ** @brief Parameters required for the TRD unpacking (specific to one component)
+   **/
+  struct UnpackTrd2dPar {
+    uint16_t fModId                             = 0;   ///< Module ID of component
+    uint8_t fCrobId                             = 0;   ///< CROB ID of component
+    std::vector<UnpackTrd2dAsicPar> fAsicParams = {};  ///< Parameters for each ASIC
+  };
+
+
+  /** @struct UnpackTrd2dMoni
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023
+   ** @brief Monitoring data for TRD unpacking
+   **/
+  struct UnpackTrd2dMonitorData {
+    uint32_t fNumNonHitOrTsbMessage     = 0;
+    uint32_t fNumErrElinkOutOfRange     = 0;  ///< Elink not contained in parameters
+    uint32_t fNumErrInvalidFirstMessage = 0;  ///< First message is not TS_MSB or second is not EPOCH
+    uint32_t fNumErrInvalidMsSize       = 0;  ///< Microslice size is not multiple of message size
+    uint32_t fNumErrTimestampOverflow   = 0;  ///< Overflow in 64 bit time stampa
+    bool HasErrors()
+    {
+      uint32_t numErrors = fNumNonHitOrTsbMessage + fNumErrElinkOutOfRange + fNumErrInvalidFirstMessage
+                           + fNumErrInvalidMsSize + fNumErrTimestampOverflow;
+      return (numErrors > 0 ? true : false);
+    }
+  };
+
+  /** @class UnpackTrd2d
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023 
+   ** @brief Unpack algorithm for TRD
+   **/
+  class UnpackTrd2d {
+
+  public:
+    typedef std::pair<std::vector<CbmTrdDigi>, UnpackTrd2dMonitorData> resultType;
+
+
+    /** @brief Default constructor **/
+    UnpackTrd2d() {};
+
+
+    /** @brief Destructor **/
+    ~UnpackTrd2d() {};
+
+
+    /** @brief Algorithm execution
+     ** @param  msContent  Microslice payload
+     ** @param  msDescr    Microslice descriptor
+     ** @param  tTimeslice Unix start time of timeslice [ns]
+     ** @return TRD digi data
+     **/
+    resultType operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                          const uint64_t tTimeslice);
+
+    /** @brief Set the parameter container
+     ** @param params Pointer to parameter container
+     **/
+    void SetParams(std::unique_ptr<UnpackTrd2dPar> params) { fParams = *(std::move(params)); }
+
+  private:                        // members
+    UnpackTrd2dPar fParams = {};  ///< Parameter container
+
+    /**
+     ** @brief Time offset for the system
+     ** @todo This should be module and channel dependent and included into the asic parameters
+     **/
+    std::int32_t fSystemTimeOffset = 0;
+
+    bool pushDigis(std::vector<CbmTrdFaspMessage> messages, const uint64_t time);
+
+    /** @brief Finalize component (e.g. copy from temp buffers)  */
+    std::vector<CbmTrdDigi> FinalizeComponent();
+
+    // Constants
+    /** @brief Bytes per FASP frame stored in the microslices (32 bits words)
+   * - DATA WORD -
+   * ffff.ffdd dddd.dddd dddd.tttt ttta.cccc
+   * f - FASP id
+   * d - ADC signal
+   * t - time label inside epoch
+   * a - word type (1)
+   * c - channel id
+   * - EPOCH WORD -
+   * ffff.fftt tttt.tttt tttt.tttt ttta.cccc
+   * f - FASP id
+   * t - epoch index
+   * a - word type (0)
+   * c - channel id
+   */
+    static const std::uint8_t fBytesPerWord = 4;
+
+    std::array<std::vector<CbmTrdDigi>, NFASPMOD* NFASPCH> fDigiBuffer = {
+      {}};  ///> Buffered digi for each pad in CROB component
+
+    std::map<uint32_t, uint8_t[NFASPMOD]>* fFaspMap = nullptr;  ///> FASP mapping update wrt the default setting
+  };
+
+
+} /* namespace cbm::algo */
+
+#endif /* CBM_ALGO_UNPACKTRD2D_H */
diff --git a/core/data/base/CbmDigiData.h b/core/data/base/CbmDigiData.h
index 835fc28493119dbe807edd2c8cc7a4e88c2abea2..d44185d8dedb4b3614f03f576e43dc5e31c1414e 100644
--- a/core/data/base/CbmDigiData.h
+++ b/core/data/base/CbmDigiData.h
@@ -36,6 +36,7 @@ public:
   CbmMuchDigiData fMuch;  ///< MUCH data
   CbmRichDigiData fRich;  ///< RICH data
   CbmTrdDigiData fTrd;    ///< TRD data
+  CbmTrdDigiData fTrd2d;  ///< TRD2D data
   CbmTofDigiData fTof;    ///< TOF data
   CbmPsdDigiData fPsd;    ///< PSD data
 
@@ -49,6 +50,7 @@ public:
     ar& fSts;
     ar& fMuch;
     ar& fTrd;
+    ar& fTrd2d;
     ar& fTof;
     ar& fPsd;
     ar& fRich;
@@ -66,11 +68,12 @@ public:
     fSts.Clear();
     fMuch.Clear();
     fTrd.Clear();
+    fTrd2d.Clear();
     fTof.Clear();
     fPsd.Clear();
     fRich.Clear();
   }
 };
 
-BOOST_CLASS_VERSION(CbmDigiData, 3)
+BOOST_CLASS_VERSION(CbmDigiData, 4)
 #endif /* CBMDIGIDATA_H */
diff --git a/reco/tasks/CbmTaskUnpack.cxx b/reco/tasks/CbmTaskUnpack.cxx
index 7aafa5cbbc0d5815bd02b4a982e83c7965c8b4b4..f709797d6df7e415630beb9fcae56ad401cf2e62 100644
--- a/reco/tasks/CbmTaskUnpack.cxx
+++ b/reco/tasks/CbmTaskUnpack.cxx
@@ -11,7 +11,10 @@
 #include "CbmDigiManager.h"
 #include "CbmDigiTimeslice.h"
 #include "CbmSourceTs.h"
+#include "CbmTrdParFasp.h"
+#include "CbmTrdParModDigi.h"
 #include "CbmTrdParSetAsic.h"
+#include "CbmTrdParSetDigi.h"
 #include "CbmTrdParSpadic.h"
 
 #include "MicrosliceDescriptor.hpp"
@@ -34,9 +37,6 @@
 #include <sstream>
 #include <vector>
 
-#include "UnpackSts.h"
-
-
 using namespace std;
 using cbm::algo::UnpackBmonElinkPar;
 using cbm::algo::UnpackBmonPar;
@@ -46,10 +46,14 @@ using cbm::algo::UnpackStsElinkPar;
 using cbm::algo::UnpackStsPar;
 using cbm::algo::UnpackTofElinkPar;
 using cbm::algo::UnpackTofPar;
+using cbm::algo::UnpackTrd2dAsicPar;
+using cbm::algo::UnpackTrd2dChannelPar;
+using cbm::algo::UnpackTrd2dPar;
 using cbm::algo::UnpackTrdCrobPar;
 using cbm::algo::UnpackTrdElinkPar;
 using cbm::algo::UnpackTrdPar;
 
+
 // -----   Constructor   -----------------------------------------------------
 CbmTaskUnpack::CbmTaskUnpack() : FairTask("Unpack") {}
 // ---------------------------------------------------------------------------
@@ -215,6 +219,26 @@ void CbmTaskUnpack::Exec(Option_t*)
       numCompUsed++;
     }  // system TRD
 
+    // system TRD2D
+    if (systemId == fles::SubsystemIdentifier::TRD2D) {
+      const uint16_t equipmentId = timeslice->descriptor(comp, 0).eq_id;
+      const auto algoIt          = fAlgoTrd2d.find(equipmentId);
+      assert(algoIt != fAlgoTrd2d.end());
+
+      // The current algorithm works for the TRD2D data format version XXX used in 2022.
+      // Other versions are not yet supported.
+      // In the future, different data formats will be supported by instantiating different
+      // algorithms depending on the version.
+      //assert(timeslice->descriptor(comp, 0).sys_ver == XXX);  To do: add something sensible here
+
+      // --- Microslice loop
+      numMsInComp =
+        MsLoop(timeslice, algoIt->second, comp, &fTimeslice->fData.fTrd2d.fDigis, &numBytesInComp, &numDigisInComp);
+
+      numCompUsed++;
+    }  // system TRD2D
+
+
     compTimer.Stop();
     LOG(debug) << GetName() << ": Component " << comp << ", microslices " << numMsInComp << " input size "
                << numBytesInComp << " bytes, "
@@ -238,6 +262,8 @@ void CbmTaskUnpack::Exec(Option_t*)
             [](CbmTofDigi digi1, CbmTofDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
   std::sort(std::execution::par_unseq, fTimeslice->fData.fTrd.fDigis.begin(), fTimeslice->fData.fTrd.fDigis.end(),
             [](CbmTrdDigi digi1, CbmTrdDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
+  std::sort(std::execution::par_unseq, fTimeslice->fData.fTrd2d.fDigis.begin(), fTimeslice->fData.fTrd2d.fDigis.end(),
+            [](CbmTrdDigi digi1, CbmTrdDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
 #else
   std::sort(fTimeslice->fData.fSts.fDigis.begin(), fTimeslice->fData.fSts.fDigis.end(),
             [](CbmStsDigi digi1, CbmStsDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
@@ -249,6 +275,8 @@ void CbmTaskUnpack::Exec(Option_t*)
             [](CbmTofDigi digi1, CbmTofDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
   std::sort(fTimeslice->fData.fTrd.fDigis.begin(), fTimeslice->fData.fTrd.fDigis.end(),
             [](CbmTrdDigi digi1, CbmTrdDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
+  std::sort(fTimeslice->fData.fTrd2d.fDigis.begin(), fTimeslice->fData.fTrd2d.fDigis.end(),
+            [](CbmTrdDigi digi1, CbmTrdDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
 #endif
 
   // --- Timeslice log
@@ -394,12 +422,12 @@ InitStatus CbmTaskUnpack::Init()
 
     std::unique_ptr<UnpackTrdPar> par(new UnpackTrdPar());
     const size_t numCrobs = fTrdConfig.GetNumCrobs(equip);
-    for (size_t crob = 0; crob < numCrobs; crob++) {
 
+    for (size_t crob = 0; crob < numCrobs; crob++) {
       UnpackTrdCrobPar crobPar;
       const size_t numElinks = fTrdConfig.GetNumElinks(equip, crob);
-      for (size_t elink = 0; elink < numElinks; elink++) {
 
+      for (size_t elink = 0; elink < numElinks; elink++) {
         UnpackTrdElinkPar elinkPar;
         auto addresses        = fTrdConfig.Map(equip, crob, elink);
         elinkPar.fAddress     = addresses.first;   // Asic address for this elink
@@ -412,6 +440,34 @@ InitStatus CbmTaskUnpack::Init()
     LOG(info) << "--- Configured equipment " << equip << " with " << numCrobs << " crobs";
   }
 
+  InitTrd2dReadoutConfig();
+  auto equipIdsTrd2d = fTrd2dConfig.GetEquipmentIds();
+  for (auto& equip : equipIdsTrd2d) {
+
+    std::unique_ptr<UnpackTrd2dPar> par(new UnpackTrd2dPar());
+    const size_t numAsics = fTrd2dConfig.GetNumAsics(equip);
+
+    for (size_t asic = 0; asic < numAsics; asic++) {
+      UnpackTrd2dAsicPar asicPar;
+      const size_t numChans = fTrd2dConfig.GetNumChans(equip, asic);
+
+      for (size_t chan = 0; chan < numChans; chan++) {
+        UnpackTrd2dChannelPar chanPar;
+        auto pars            = fTrd2dConfig.ChanMap(equip, asic, chan);
+        chanPar.fPadAddress  = std::get<0>(pars);  // Pad address for channel
+        chanPar.fHasPairingR = std::get<1>(pars);  // Flag for R or T compoment
+        chanPar.fDaqOffset   = std::get<2>(pars);  // Time calibration parameter
+        asicPar.fChanParams.push_back(chanPar);
+      }
+      auto comppars = fTrd2dConfig.CompMap(equip);
+      par->fModId   = comppars.first;
+      par->fCrobId  = comppars.second;
+      par->fAsicParams.push_back(asicPar);
+    }
+    fAlgoTrd2d[equip].SetParams(std::move(par));
+    LOG(info) << "--- Configured equipment " << equip << " with " << numAsics << " asics";
+  }
+
   LOG(info) << "--- Configured " << fAlgoSts.size() << " unpacker algorithms for STS.";
   LOG(debug) << "Readout map:" << fStsConfig.PrintReadoutMap();
   LOG(info) << "--- Configured " << fAlgoMuch.size() << " unpacker algorithms for MUCH.";
@@ -424,6 +480,73 @@ InitStatus CbmTaskUnpack::Init()
 }
 // ----------------------------------------------------------------------------
 
+// -----   Initialisation   ---------------------------------------------------
+void CbmTaskUnpack::InitTrd2dReadoutConfig()
+{
+  // Initialize input files
+  FairParAsciiFileIo asciiInput;
+  std::string digiparfile = Form("%s/parameters/trd/trd_v22h_mcbm.digi.par", std::getenv("VMCWORKDIR"));
+  std::string asicparfile = Form("%s/parameters/trd/trd_v22h_mcbm.asic.par", std::getenv("VMCWORKDIR"));
+
+  // Read the .digi file and store result
+  CbmTrdParSetDigi digiparset;
+  if (asciiInput.open(digiparfile.data())) { digiparset.init(&asciiInput); }
+  asciiInput.close();
+
+  // Read the .asic file and store result
+  CbmTrdParSetAsic asicparset;
+  if (asciiInput.open(asicparfile.data())) { asicparset.init(&asciiInput); }
+  asciiInput.close();
+
+  // Initialize map (moduleId, crobId) -> (equipId) explicitly
+  std::map<uint32_t, uint16_t[NCROBMOD]> crob_map;
+  uint16_t cmap[] = {0xffc2, 0xffc5, 0xffc1, 0, 0};        // "crob map 22" for run Id >= 2335
+  memcpy(crob_map[5], cmap, NCROBMOD * sizeof(uint16_t));  // only module Id 5 is used!
+
+  // Then pass to Trd2dReadoutConfig, will invert to obain map (equipId) -> (module iq, crob id)
+  fTrd2dConfig.InitComponentMap(crob_map);
+
+  // Map (equipId, asicId, chanId) -> (pad address, R pairing flag, daq offset)
+  std::map<size_t, std::map<size_t, std::map<size_t, std::tuple<int32_t, bool, uint64_t>>>> channelMap;
+
+  // Loop through a list of module IDs from the .digi file (can in principle contradict crob_map).
+  for (auto entry : digiparset.GetModuleMap()) {
+
+    const auto moduleId = entry.first;
+    if (crob_map.find(moduleId) == crob_map.end()) { continue; }  //skip if no entry in crob_map
+
+    // Get ASIC parameters for this module
+    const CbmTrdParSetAsic* setDet = static_cast<const CbmTrdParSetAsic*>(asicparset.GetModuleSet(moduleId));
+    if (!setDet) continue;
+    if (setDet->GetAsicType() != int32_t(CbmTrdDigi::eCbmTrdAsicType::kFASP)) continue;
+
+    // Loop through ASICs for this module
+    std::vector<int32_t> addresses;
+    setDet->GetAsicAddresses(&addresses);
+    for (auto add : addresses) {
+
+      //Get local IDs for this component / equipment.
+      const int32_t fasp_in_eq  = ((int) add - 1000 * (int) moduleId) % (NFASPCROB);
+      const int32_t crob_in_mod = ((int) add - 1000 * (int) moduleId) / (NFASPCROB);
+      const uint16_t eq_id      = crob_map[moduleId][crob_in_mod];
+
+      // ASIC parameter set
+      CbmTrdParFasp* fasppar = (CbmTrdParFasp*) setDet->GetModulePar(add);
+
+      // Loop through channels for this ASIC and fill map
+      for (size_t chan = 0; chan < fasppar->GetNchannels(); chan++) {
+        const int32_t pad              = fasppar->GetPadAddress(chan);
+        const bool hasPairingR         = fasppar->GetChannel(chan)->HasPairingR();
+        const CbmTrdParModDigi* modpar = (CbmTrdParModDigi*) digiparset.GetModulePar(moduleId);
+        uint64_t daq_offset            = 0;
+        if (modpar->GetPadRow(fasppar->GetPadAddress(chan)) % 2 == 0) daq_offset = 3;
+        channelMap[eq_id][fasp_in_eq][chan] = std::make_tuple(pad, hasPairingR, daq_offset);
+      }
+    }
+  }
+  fTrd2dConfig.InitChannelMap(channelMap);
+}
+
 // -----   Initialisation   ---------------------------------------------------
 void CbmTaskUnpack::InitTrdReadoutConfig()
 {
diff --git a/reco/tasks/CbmTaskUnpack.h b/reco/tasks/CbmTaskUnpack.h
index 9fd0d0a332b5e2a51814e484659bb57c3ed513ea..70c3fa8999cade09b2b4c98dcb82cbbb14276309 100644
--- a/reco/tasks/CbmTaskUnpack.h
+++ b/reco/tasks/CbmTaskUnpack.h
@@ -12,6 +12,7 @@
 #include <FairTask.h>
 
 #include "TofReadoutConfig.h"
+#include "Trd2dReadoutConfig.h"
 #include "TrdReadoutConfig.h"
 
 #include <sstream>
@@ -26,6 +27,7 @@
 #include "UnpackSts.h"
 #include "UnpackTof.h"
 #include "UnpackTrd.h"
+#include "UnpackTrd2d.h"
 
 class CbmDigiManager;
 class CbmSourceTs;
@@ -77,6 +79,9 @@ private:  // methods
   /** @brief Initialisation of address maps for Trd **/
   virtual void InitTrdReadoutConfig();
 
+  /** @brief Initialisation of address maps for Trd2d **/
+  virtual void InitTrd2dReadoutConfig();
+
 private:  // members
   CbmSourceTs* fSource = nullptr;
 
@@ -95,6 +100,9 @@ private:  // members
   std::map<uint16_t, cbm::algo::UnpackTrd> fAlgoTrd = {};
   cbm::algo::TrdReadoutConfig fTrdConfig {};
 
+  std::map<uint16_t, cbm::algo::UnpackTrd2d> fAlgoTrd2d = {};
+  cbm::algo::Trd2dReadoutConfig fTrd2dConfig {};
+
   size_t fNumTs                = 0;
   size_t fNumMs                = 0;
   size_t fNumBytes             = 0;