diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index eedb1a9ebaf2f427bb7856986325da8a48b0092b..f4d65d357bbe06dc709c443248334153fda68b6c 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -15,7 +15,9 @@ set(SRCS
   detectors/tof/UnpackTof.cxx
   detectors/bmon/BmonReadoutConfig.cxx
   detectors/bmon/UnpackBmon.cxx
- )
+  detectors/trd/TrdReadoutConfig.cxx
+  detectors/trd/UnpackTrd.cxx
+)
 
 add_library(Algo SHARED ${SRCS})
 
@@ -28,6 +30,7 @@ target_include_directories(Algo
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors/much
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors/tof
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors/bmon
+         ${CMAKE_CURRENT_SOURCE_DIR}/detectors/trd
  )
 
 target_link_libraries(Algo PUBLIC OnlineData ROOT::GenVector INTERFACE FairLogger::FairLogger external::fles_ipc)
diff --git a/algo/data/CMakeLists.txt b/algo/data/CMakeLists.txt
index f2b9167a41df2cb1d8c375e9c2e06f5da396e857..62acd74b15849fe57c27354bbab6ec4502328096 100644
--- a/algo/data/CMakeLists.txt
+++ b/algo/data/CMakeLists.txt
@@ -17,6 +17,7 @@ set(SRCS
   ${CMAKE_SOURCE_DIR}/core/data/much/CbmMuchAddress.cxx
 
   ${CMAKE_SOURCE_DIR}/core/data/trd/CbmTrdDigi.cxx
+  ${CMAKE_SOURCE_DIR}/core/data/trd/CbmTrdRawMessageSpadic.cxx
 
   ${CMAKE_SOURCE_DIR}/core/data/tof/CbmTofDigi.cxx
   ${CMAKE_SOURCE_DIR}/core/data/tof/CbmTofAddress.cxx
diff --git a/algo/detectors/trd/TrdReadoutConfig.cxx b/algo/detectors/trd/TrdReadoutConfig.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f3d54987e8b6509f4ea96a9a5a4b876c3c71d7e9
--- /dev/null
+++ b/algo/detectors/trd/TrdReadoutConfig.cxx
@@ -0,0 +1,168 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#include "TrdReadoutConfig.h"
+
+//#include "CbmTrdAddress.h"
+
+#include <cassert>
+#include <iomanip>
+#include <iostream>
+#include <iterator>
+
+using std::pair;
+using std::setw;
+
+namespace cbm::algo
+{
+
+  // ---  Constructor  ------------------------------------------------------------------
+  TrdReadoutConfig::TrdReadoutConfig() {}
+
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Destructor   -----------------------------------------------------------------
+  TrdReadoutConfig::~TrdReadoutConfig() {}
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Equipment IDs   --------------------------------------------------------------
+  std::vector<uint16_t> TrdReadoutConfig::GetEquipmentIds()
+  {
+    std::vector<uint16_t> result;
+    for (auto& entry : fReadoutMap)
+      result.push_back(entry.first);
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Number of Crobs for a component / equipment   -------------------------------
+  size_t TrdReadoutConfig::GetNumCrobs(uint16_t equipmentId)
+  {
+    size_t result = 0;
+    auto it       = fReadoutMap.find(equipmentId);
+    if (it != fReadoutMap.end()) result = fReadoutMap[equipmentId].size();
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Number of Elinks for a component / equipment, crob pair  ---------------------
+  size_t TrdReadoutConfig::GetNumElinks(uint16_t equipmentId, uint16_t crobId)
+  {
+    size_t result = 0;
+    if (crobId < GetNumCrobs(equipmentId)) result = fReadoutMap[equipmentId][crobId].size();
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Initialise the mapping structure   --------------------------------------------
+  void
+  TrdReadoutConfig::Init(const std::map<size_t, std::map<size_t, std::map<size_t, size_t>>>& addressMap,
+                         std::map<size_t, std::map<size_t, std::map<size_t, std::map<size_t, size_t>>>>& channelMap)
+  {
+    // Constructing the map (equipmentId, crobId, eLink) -> (ASIC address)
+    for (auto compMap : addressMap) {
+      uint16_t equipmentId = compMap.first;
+      uint16_t numCrobs    = compMap.second.size();
+      fReadoutMap[equipmentId].resize(numCrobs);
+
+      for (auto crobMap : compMap.second) {
+        uint16_t crobId    = crobMap.first;
+        uint16_t numElinks = crobMap.second.size();
+        fReadoutMap[equipmentId][crobId].resize(numElinks);
+
+        for (auto elinkMap : crobMap.second) {
+          uint16_t elinkId                          = elinkMap.first;
+          uint16_t address                          = elinkMap.second;
+          fReadoutMap[equipmentId][crobId][elinkId] = address;
+        }
+      }
+    }
+
+    // Constructing the map (equipmentId, crobId, eLink, chan) -> (chan address)
+    for (auto compMap : channelMap) {
+      uint16_t equipmentId = compMap.first;
+      uint16_t numCrobs    = compMap.second.size();
+      fChannelMap[equipmentId].resize(numCrobs);
+
+      for (auto crobMap : compMap.second) {
+        uint16_t crobId    = crobMap.first;
+        uint16_t numElinks = crobMap.second.size();
+        fChannelMap[equipmentId][crobId].resize(numElinks);
+
+        for (auto elinkMap : crobMap.second) {
+          uint16_t elinkId  = elinkMap.first;
+          uint16_t numChans = elinkMap.second.size();
+          fChannelMap[equipmentId][crobId][elinkId].resize(numChans);
+
+          for (auto chanMap : elinkMap.second) {
+            uint16_t chanId                                   = chanMap.first;
+            uint32_t address                                  = chanMap.second;
+            fChannelMap[equipmentId][crobId][elinkId][chanId] = address;
+          }
+        }
+      }
+    }
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Mapping (equimentId, crobId, elink) -> (ASIC address, channel addresses)  -----
+  std::pair<int32_t, std::vector<uint32_t>> TrdReadoutConfig::Map(uint16_t equipmentId, uint16_t crobId,
+                                                                  uint16_t elinkId)
+  {
+    std::pair<int32_t, std::vector<uint32_t>> result;
+    result.first = -1;
+    auto it      = fChannelMap.find(equipmentId);
+    if (it != fChannelMap.end()) {
+      if (crobId < fChannelMap[equipmentId].size()) {
+        if (elinkId < fChannelMap[equipmentId][crobId].size()) {
+          result.first  = fReadoutMap[equipmentId][crobId][elinkId];
+          result.second = fChannelMap[equipmentId][crobId][elinkId];
+        }
+      }
+    }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // -----   Print readout map   ------------------------------------------------
+  std::string TrdReadoutConfig::PrintReadoutMap()
+  {
+    std::stringstream ss;
+    for (auto compMap : fReadoutMap) {
+
+      uint16_t equipmentId = compMap.first;
+      uint16_t numCrobs    = compMap.second.size();
+      ss << "\n Equipment " << equipmentId << " nCrobs " << numCrobs;
+      for (size_t crobId = 0; crobId < numCrobs; crobId++) {
+
+        uint16_t numElinks = compMap.second.at(crobId).size();
+        ss << "\n Equipment " << equipmentId << " CrobId " << crobId << " nElinks " << numElinks;
+        for (size_t elinkId = 0; elinkId < numElinks; elinkId++) {
+
+          uint16_t address = compMap.second.at(crobId).at(elinkId);
+          ss << "\n Equipment " << equipmentId << " CrobId " << crobId << " elinkID " << elinkId << " address "
+             << address;
+
+          //Now output channel addresses
+          ss << "\n Channels ";
+          auto vec = fChannelMap[equipmentId][crobId][elinkId];
+          std::copy(vec.begin(), vec.end() - 1, std::ostream_iterator<uint32_t>(ss, " "));
+          ss << vec.back();
+        }
+      }
+    }
+    ss << "\n";
+    return ss.str();
+  }
+  // ----------------------------------------------------------------------------
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/trd/TrdReadoutConfig.h b/algo/detectors/trd/TrdReadoutConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..62d6ec558b4f6b2ed56f254756cd7ad526aba559
--- /dev/null
+++ b/algo/detectors/trd/TrdReadoutConfig.h
@@ -0,0 +1,90 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#ifndef ALGO_DETECTORS_TRD_TRDREADOUTCONFIG_H
+#define ALGO_DETECTORS_TRD_TRDREADOUTCONFIG_H
+
+#include <map>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+namespace cbm::algo
+{
+
+
+  /** @class TrdReadoutConfig
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 3 March 2022
+   ** @brief Provides the hardware-to-software address mapping for the CBM-TRD
+   **
+   ** 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 TrdReadoutConfig {
+
+  public:
+    /** @brief Constructor **/
+    TrdReadoutConfig();
+
+
+    /** @brief Destructor **/
+    virtual ~TrdReadoutConfig();
+
+
+    /** @brief Equipment in the configuration
+     ** @return Vector of equipment IDs
+     **/
+    std::vector<uint16_t> GetEquipmentIds();
+
+
+    /** @brief Number of CROBS of a component
+     ** @param Equipment ID
+     ** @return Number of CROBS
+     **/
+    size_t GetNumCrobs(uint16_t equipmentId);
+
+
+    /** @brief Number of elinks of a component - CROB pair
+     ** @param Equipment ID
+     ** @param CROB ID
+     ** @return Number of elinks
+     **/
+    size_t GetNumElinks(uint16_t equipmentId, uint16_t crobId);
+
+
+    /** @brief API: Mapping from component, crob and elink to pair (ASIC address, channel addresses)
+     ** @param equipId     Equipment identifier (component)
+     ** @param crob        CROB number within component
+     ** @param elink       Elink number within CROB
+     ** @return pair (ASIC address, channel addresses )
+     */
+    std::pair<int32_t, std::vector<uint32_t>> Map(uint16_t equipId, uint16_t crob, uint16_t elink);
+
+
+    /** @brief Debug output of readout map **/
+    std::string PrintReadoutMap();
+
+
+    /** @brief Initialisation of readout map **/
+    void Init(const std::map<size_t, std::map<size_t, std::map<size_t, size_t>>>& addressMap,
+              std::map<size_t, std::map<size_t, std::map<size_t, std::map<size_t, size_t>>>>& channelMap);
+
+  private:
+    // --- TRD readout map
+    // --- Map index: (equipment, crob, elink), map value: (ASIC address)
+    std::map<uint16_t, std::vector<std::vector<uint16_t>>> fReadoutMap = {};  //!
+
+    // --- TRD channel map
+    // --- Map index: (equipment, crob, elink, chan), map value: (channel address)
+    std::map<uint16_t, std::vector<std::vector<std::vector<uint32_t>>>> fChannelMap = {};  //!
+  };
+
+} /* namespace cbm::algo */
+
+#endif /* ALGO_DETECTORS_TRD_TRDREADOUTCONFIG_H_ */
diff --git a/algo/detectors/trd/UnpackTrd.cxx b/algo/detectors/trd/UnpackTrd.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..854cb8e202096b2e6c5449aa8b2082ffdd06c5e8
--- /dev/null
+++ b/algo/detectors/trd/UnpackTrd.cxx
@@ -0,0 +1,516 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pascal Raisig, Dominik Smith [committer] */
+
+#include "UnpackTrd.h"
+
+#include <algorithm>
+#include <cassert>
+#include <vector>
+
+using std::unique_ptr;
+
+namespace cbm::algo
+{
+
+  // ----   Algorithm execution   ---------------------------------------------
+  UnpackTrd::resultType UnpackTrd::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                                              const uint64_t tTimeslice)
+  {
+    // --- Output data
+    resultType result = {};
+
+    // Get the µSlice starttime relative to the timeslice starttime (constant is clock length of Spadic in ns)
+    fMsStartTimeRelCC = (msDescr.idx - tTimeslice) / fAsicClockCycle;
+
+    // We only want to count on TS_MSB per Stream per TS_MSB package (each eLink sends its own TS_MSB frame)
+    // so we store the current TS_MSB and compare it to the incoming.
+    std::int8_t currTsMsb = 0;
+
+    // Reset the TS_MSB counter for the new µSlice we unpack
+    fNrTsMsbVec.clear();
+    fNrTsMsbVec.resize(fStreamsPerWord);
+
+    // Get the µslice size in bytes to calculate the number of completed words
+    auto mssize = msDescr.size;
+
+    // Get the hardware ids from which the current µSlice is coming
+    std::uint8_t crobId = 0;
+    auto criId          = msDescr.eq_id;
+
+    // Digest the flags from the µSlice
+    digestMsFlags(msDescr.flags, result.second);
+
+    // Get the number of complete words in the input MS buffer.
+    std::uint32_t nwords = mssize / fBytesPerWord;
+
+    // We have 32 bit spadic frames in this readout version
+    const auto mscontent = reinterpret_cast<const size_t*>(msContent);
+
+    // Loop over all 64bit-Spadic-Words in the current µslice
+    for (std::uint32_t istream = 0; istream < fStreamsPerWord; istream++) {
+      currTsMsb = -1;
+      for (std::uint32_t iword = 0; iword < nwords; ++iword) {
+        // Access the actual word from the pointer
+        size_t word = static_cast<size_t>(mscontent[iword]);
+
+        // Access the actual frame[iframe] from the word. (see fStreamsPerWord)
+        std::uint32_t frame = (word >> (32 * istream)) & 0xffffffff;
+
+        // Get the type of the frame
+        auto kWordtype = getMessageType(frame);
+
+        // In case we saw any other word than an EPO(TS_MSB) reset the flag,
+        // such that we again increase by one if an EPO frame arrives
+        auto elinkId = (frame >> 24) & 0x3f;
+
+        switch (kWordtype) {
+          case Spadic::MsMessageType::kEPO: {
+            auto tsmsb = getTsMsb(frame, result.second);
+            if (((tsmsb - currTsMsb) & 0x3f) == 1 || currTsMsb == -1) fNrTsMsbVec.at(istream)++;
+            currTsMsb = tsmsb;
+            result.second.fNumEpochMsgs++;
+            break;
+            // FIXME in the kEPO msg we also have further flags that should be extracted
+          }
+          case Spadic::MsMessageType::kSOM: {
+            // Create the raw message and fill it with all information we can get from the SOM msg
+            CbmTrdRawMessageSpadic raw = makeRaw(frame, criId, crobId, elinkId, istream);
+
+            // FIXME since we can not deduce the sample position from the messages we need in
+            // future some parameter handling here to place the samples at the correct position
+            // 6 adc bits are stored in the som message
+            size_t nadcbits      = 6;
+            size_t nadcbitstotal = 6;
+            // Get the first bits from the adc signal
+            size_t adcbuffer = frame & 0x3f;
+            size_t isample   = 0;
+            size_t irda      = 0;
+
+            // Now lets check if we have rda words following our som
+            iword++;
+            word  = static_cast<size_t>(mscontent[iword]);
+            frame = (word >> (32 * istream)) & 0xffffffff;
+
+            // The maximum amount of samples (32) equals to 12 RDA messages
+            while (getMessageType(frame) == Spadic::MsMessageType::kRDA && irda < 12) {
+              // We have to count the number of rda frames for sample reconstruction in eom
+              irda++;
+
+              // Ensure that we are on the correct eLink
+              elinkId = (frame >> 24) & 0x3f;
+              if (elinkId != raw.GetElinkId()) { result.second.fNumElinkMis++; }
+
+              // We have 22 adc bits per RDA word lets add them to the buffer...
+              adcbuffer <<= 22;
+              adcbuffer |= static_cast<size_t>((frame & 0x3fffff));
+              // and increase the adcbit counter by 22 bits
+              nadcbits += 22;
+              nadcbitstotal += 22;
+              // If we have 9 or more samples stored we can extract n samples
+              while (nadcbits >= 9) {
+                raw.IncNrSamples();
+                // In case the avg baseline feature was used we need to take special care of sample 0
+                if (isample == 0 && fParams.fUseBaselineAvg)
+                  raw.SetSample(extractAvgSample(&adcbuffer, &nadcbits), isample);
+                else
+                  raw.SetSample(extractSample(&adcbuffer, &nadcbits), isample);
+                isample++;
+              }
+              iword++;
+              word  = static_cast<size_t>(mscontent[iword]);
+              frame = (word >> (32 * istream)) & 0xffffffff;
+            }
+
+            if (getMessageType(frame) == Spadic::MsMessageType::kEOM) {
+              // Ensure that we are on the correct eLink
+              elinkId = (frame >> 24) & 0x3f;
+              if (elinkId != raw.GetElinkId()) { result.second.fNumElinkMis++; }
+
+              // Number of samples indicator = nsamples % 4
+              std::uint8_t nsamplesindicator = (frame >> 18) & 0x3;
+              // Number of required samples as indicated
+              std::uint64_t nreqsamples = (nadcbitstotal + 18) / 9;
+              std::uint8_t nn           = nreqsamples % 4;
+              for (std::uint8_t itest = 0; itest < 3; itest++) {
+                if (nn == nsamplesindicator || nreqsamples == 0) break;
+                nreqsamples--;
+                nn = nreqsamples % 4;
+              }
+
+              // There is a chance that the nsamplesindicator bits are corrupted,
+              // here we check that we do not try to extract more adcbits than actually are streamed
+              if (nreqsamples >= isample) {
+                // Now extract from the above values the number of required adc bits from the eom
+                std::int8_t nrequiredbits = (nreqsamples - isample) * 9 - nadcbits;
+                adcbuffer <<= nrequiredbits;
+
+                // The eom carries at maximum 18 adcbits
+                adcbuffer |= static_cast<size_t>((frame & 0x3ffff) >> (18 - nrequiredbits));
+                nadcbits += nrequiredbits;
+
+                while (nadcbits >= 9) {
+                  raw.IncNrSamples();
+                  raw.SetSample(extractSample(&adcbuffer, &nadcbits), isample);
+                  isample++;
+                }
+              }
+              else {
+                result.second.fNumCorruptEom++;
+              }
+              result.second.fNumCreatedRawMsgs++;
+
+              // the message is done and the raw message container should contain everything we need.
+              // So now we can call makeDigi(). Nevertheless there is a chance for a corrupted message,
+              // which ends up with 0 samples so we have to check for it.
+              if (isample > 0) result.first.push_back(makeDigi(raw));
+            }
+            else {
+              // We move the word counter backwards by one, such that the unexpected message can correctly be digested
+              iword--;
+              result.second.fNumMissingEom++;
+            }
+            break;
+          }
+          case Spadic::MsMessageType::kRDA: {
+            result.second.fNumWildRda++;
+            break;
+          }
+          case Spadic::MsMessageType::kEOM: {
+            result.second.fNumWildEom++;
+            break;
+          }
+          case Spadic::MsMessageType::kINF: {
+            result.second.fNumCreatedInfoMsgs++;
+            digestInfoMsg(frame);
+            break;
+          }
+          case Spadic::MsMessageType::kNUL: {
+            // last word in Microslice is 0.
+            if (iword != (nwords - 1) || (istream != (fStreamsPerWord - 1))) { result.second.fNumWildNul++; }
+            break;
+          }
+          case Spadic::MsMessageType::kUNK: {
+            result.second.fNumUnknownWords++;
+            return result;
+            break;
+          }
+          default:
+            // We have varying msg types for different versions of the message format.
+            // Hence, to not produce compiler warnings we have a "default break;" here.
+            break;
+        }
+      }
+    }
+    return result;
+  }
+  // --------------------------------------------------------------------------
+
+  // ---- digestBufInfoFlags ----
+  Spadic::MsInfoType UnpackTrd::digestBufInfoFlags(const std::uint32_t frame)
+  {
+    auto flag = (frame >> 15) & 0x3;
+    Spadic::MsInfoType infotype;
+    if (flag == 1) infotype = Spadic::MsInfoType::kChannelBuf;
+    if (flag == 2) infotype = Spadic::MsInfoType::kOrdFifoBuf;
+    if (flag == 3) infotype = Spadic::MsInfoType::kChannelBufM;
+    return infotype;
+  }
+
+  // ---- digestInfoMsg ----
+  void UnpackTrd::digestInfoMsg(const std::uint32_t frame)
+  {
+    /// Save info message if needed.
+    //if (fOptOutBVec) { fOptOutBVec->emplace_back(std::make_pair(fLastFulltime, frame)); }
+    Spadic::MsInfoType infotype = getInfoType(frame);
+    // "Spadic_Info_Types";
+  }
+
+  // ---- digestInfoMsg ----
+  void UnpackTrd::digestMsFlags(const std::uint16_t flags, UnpackTrdMonitorData& monitor)
+  {
+    if (flags & static_cast<std::uint16_t>(fles::MicrosliceFlags::CrcValid)) { monitor.fNumCrcValidFlags++; }
+    if (flags & static_cast<std::uint16_t>(fles::MicrosliceFlags::OverflowFlim)) { monitor.fNumOverflowFlimFlags++; }
+    if (flags & static_cast<std::uint16_t>(fles::MicrosliceFlags::OverflowUser)) { monitor.fNumOverflowUserFlags++; }
+    if (flags & static_cast<std::uint16_t>(fles::MicrosliceFlags::DataError)) { monitor.fNumDataErrorFlags++; }
+  }
+
+  // ---- extractSample ----
+  std::float_t UnpackTrd::extractAvgSample(size_t* adcbuffer, size_t* nadcbits)
+  {
+    // can not extract samples from a buffer with less than 9 bits
+    assert(*nadcbits >= 9);
+    *nadcbits -= 9;
+
+    // The decoding of the average sample is kind of interesting:
+    // We get 9 bits in total iiiiiiiff. The 7 "i" bits refer to std integer bits, hence,
+    // covering a range of 0..128.
+    // The 2 "f" bits refer to values after a fix point refering to [0,0.25,0.5,0.75].
+    // The sign we have to assume to be negative (bit-9 = 1) and also bit 8 of our std
+    // interger range we have to assume to be 0, such that the returned number is in
+    // between -256..-128.
+
+    // Activate the 7 "i" bits
+    std::int16_t sample = 0x07f;
+
+    // Write the content of the 7 "i" bits to temp
+    sample &= (*adcbuffer >> (*nadcbits + 2));
+
+    // Switch on the negative sign
+    sample |= 0xff00;
+
+    return sample;
+  }
+
+  // ---- extractSample ----
+  std::int16_t UnpackTrd::extractSample(size_t* adcbuffer, size_t* nadcbits)
+  {
+    // can not extract samples from a buffer with less than 9 bits
+    assert(*nadcbits >= 9);
+
+    // We want to access the bits stored at the positions between nadcbits and nadcbits - 9, so we can already here
+    // reduce nadcbits by 9 and than shift the adcbuffer by this value to the right and compare it with temp which has the 9 lsbs set to 1
+    *nadcbits -= 9;
+
+    std::int16_t temp = 0x1ff;
+    temp &= (*adcbuffer >> (*nadcbits));
+
+    // Now we have our 9 bits stored in temp, but for a std::int16_t this does not match in terms of the sign handling.
+    // So we check on bit 9 for the sign (temp & 0x0100) and if we have a negative value we manipulate bit 16-10 to 1
+    // to get the correct negative number
+    std::int16_t sample = (temp & 0x0100) ? (temp | 0xff00) : temp;
+
+    return sample;
+  }
+
+  // ---- getInfoType ----
+  Spadic::MsInfoType UnpackTrd::getInfoType(const std::uint32_t frame)
+  {
+    // Set first 20 bits to 1 for the mask
+    size_t mask = 0x000FFFFF;
+
+    // 000011.................. : BOM word
+    // 0000010................. : MSB word
+    // 0000011................. : BUF word
+    // 0000100................. : UNU word
+    // 0000101................. : MIS word
+
+    if (((frame & mask) >> 18) == 3)  // BOM
+    {
+      return Spadic::MsInfoType::kBOM;
+    }
+    if (((frame & mask) >> 17) == 2)  // MSB
+    {
+      return Spadic::MsInfoType::kMSB;
+    }
+    if (((frame & mask) >> 17) == 3)  // BUF
+    {
+      digestBufInfoFlags(frame);
+      return Spadic::MsInfoType::kBUF;
+    }
+    if (((frame & mask) >> 17) == 4)  // UNU
+    {
+      return Spadic::MsInfoType::kUNU;
+    }
+    if (((frame & mask) >> 17) == 5)  // MIS
+    {
+      return Spadic::MsInfoType::kMIS;
+    }
+    else {
+      std::cout << "UnpackTrd::GetInfoType] unknown type!" << std::endl;
+      exit(1);
+      return Spadic::MsInfoType::kMSB;
+    }
+  }
+
+  // ---- getMessageType ----
+  Spadic::MsMessageType UnpackTrd::getMessageType(const std::uint32_t frame)
+  {
+    std::uint32_t checkframe = frame;
+    checkframe &= 0xffffff;
+    if ((checkframe >> 21) == 1)  // SOM  001. ....
+    {
+      return Spadic::MsMessageType::kSOM;
+    }
+    else if ((checkframe >> 22) == 1)  // RDA  01.. ....
+    {
+      return Spadic::MsMessageType::kRDA;
+    }
+    else if ((checkframe >> 20) == 1)  // EOM  0001 ....
+    {
+      return Spadic::MsMessageType::kEOM;
+    }
+    else if ((checkframe >> 22) == 3)  // TS_MSB 11.. ....
+    {
+      return Spadic::MsMessageType::kEPO;
+    }
+    else if (0 < (checkframe >> 18) && (checkframe >> 18) <= 3) {
+      return Spadic::MsMessageType::kINF;
+    }
+    else if (checkframe == 0)  // Last Word in a Microslice is 0
+    {
+      return Spadic::MsMessageType::kNUL;
+    }
+    else  // not a spadic message
+    {
+      return Spadic::MsMessageType::kUNK;
+    }
+  }
+
+  // ---- getTsMsb ----
+  std::uint8_t UnpackTrd::getTsMsb(const std::uint32_t frame, UnpackTrdMonitorData& monitor)
+  {
+    if ((frame & 0xf) > 0)
+      return -2;  // if a 'error' ts_msb is received the tsmsb value is not correct. To not mess up the counting -2 is returned.
+    // The epoch in form of the TS_MSBs is written 3 times into the frame to allow to check for bit flips and catch errors.
+    // It has the length of 6 bits and an offset of 4
+    std::uint8_t tsmsb[3];
+    for (uint iepoch = 0; iepoch < 3; ++iepoch) {
+      tsmsb[iepoch] = static_cast<std::uint8_t>((frame >> (4 + 6 * iepoch) & 0x3f));
+    }
+
+    // Check if the epoch at position 0 is at least compatible with one of the others.
+    // Since, we only have 3 that value is automatically the majority value.
+    if (tsmsb[0] == tsmsb[1] || tsmsb[0] == tsmsb[2]) return tsmsb[0];
+
+    // If we arrive here the epoch at position 0 is not compatible with the other two.
+    // So let's check if they are compatible with each other. If so we have again a majority epoch
+    if (tsmsb[1] == tsmsb[2]) return tsmsb[1];
+
+    monitor.fNumNonMajorTsMsb++;
+
+    return tsmsb[0];
+  }
+
+  // ---- makeDigi ----
+  CbmTrdDigi UnpackTrd::makeDigi(CbmTrdRawMessageSpadic raw)
+  {
+    // Extract the trigger type and translate it to the digi enum
+    auto rawTriggerType = static_cast<Spadic::eTriggerType>(raw.GetHitType());
+    auto triggerType    = GetDigiTriggerType(rawTriggerType);
+
+    // Get the digi error class (dummy for the time being)
+    int32_t errClass = 0;
+
+    // Get the address of the originating spadic
+    const UnpackTrdCrobPar& crobPar   = fParams.fCrobParams.at(raw.GetCrobId());
+    const UnpackTrdElinkPar& elinkPar = crobPar.fElinkParams.at(raw.GetElinkId());
+    const uint32_t asicAddress        = elinkPar.fAddress;
+
+    // Get the channel id on the module
+    int32_t padChNr = elinkPar.fChanAddress.at(raw.GetChannelId());
+
+    // Store the full time information to last full-time member for error message handling
+    fLastFulltime = raw.GetFullTime();
+
+    // Get the time information and apply the necessary correction
+    uint64_t time = raw.GetTime() - fSystemTimeOffset;
+
+    // Get the timeshift and set the member, which is required for some of the rtd methods
+    uint64_t currentTimeshift = GetBinTimeShift(raw.GetSamples());
+
+    // In simulation since we often start at time = 0 there is a non negligible chance that a time < 0 is extracted.
+    //Since, this is not allowed in the code we set it to 0 for these cases
+    time = time > currentTimeshift ? time - currentTimeshift : 0;
+
+    // In this case of CbmTrdRawToDigi GetCharge calls GetBinTimeshift, since the information is needed.
+    // The shift is stored in fCurrentTimeshift
+    // Hence, the order of charge and time assignement here makes a difference!
+    auto maxadc = GetMaxAdcValue(raw.GetSamples());
+
+    // Get energy from maxadc value
+    auto energy = maxadc * fParams.fMaxAdcToEnergyCal;
+
+    // Get the unique module id from the asic address
+    int32_t uniqueModuleId = asicAddress / 1000;
+
+    CbmTrdDigi digi = CbmTrdDigi(padChNr, uniqueModuleId, energy, time, triggerType, errClass);
+
+    // If the raw message was flagged as multi hit, forward this info to the digi
+    if (raw.GetMultiHit()) digi.SetTriggerType(CbmTrdDigi::eTriggerType::kMulti);
+
+    return digi;
+  }
+
+  // ---- makeRaw ----
+  CbmTrdRawMessageSpadic UnpackTrd::makeRaw(const std::uint32_t frame, std::uint16_t criId, std::uint8_t crobId,
+                                            std::uint16_t elinkId, std::uint8_t istream)
+  {
+    auto chId             = static_cast<std::uint8_t>(((frame >> 17) & 0xf));
+    auto timestamp        = static_cast<std::uint8_t>((frame >> 9) & 0xff);
+    bool multihit         = ((frame >> 8) & 0x1);
+    auto hitType          = static_cast<std::uint8_t>((frame >> 6) & 0x3);
+    std::uint8_t nsamples = 0;
+    // We directly start with the largest possible samples vector to only init it once
+    std::vector<std::int16_t> samples = std::vector<std::int16_t>(0);
+
+    uint64_t fulltime = fMsStartTimeRelCC + (fNrTsMsbVec.at(istream) * fTsMsbLengthCC) + timestamp;
+
+    // Create message
+    return CbmTrdRawMessageSpadic(chId, elinkId, crobId, criId, hitType, nsamples, multihit, fulltime, samples);
+  }
+
+
+  // ---- GetDigiTriggerType ----
+  CbmTrdDigi::eTriggerType UnpackTrd::GetDigiTriggerType(Spadic::eTriggerType tt)
+  {
+    // Shift self trigger to digi selftrigger
+    // Shift neighbour trigger to digi neighbour
+    // Hide spadic kSandN in Self
+    switch (tt) {
+      case Spadic::eTriggerType::kGlobal: return CbmTrdDigi::eTriggerType::kNTrg;
+      case Spadic::eTriggerType::kSelf: return CbmTrdDigi::eTriggerType::kSelf;
+      case Spadic::eTriggerType::kNeigh: return CbmTrdDigi::eTriggerType::kNeighbor;
+      case Spadic::eTriggerType::kSandN: return CbmTrdDigi::eTriggerType::kSelf;
+      default: return CbmTrdDigi::eTriggerType::kNTrg;
+    }
+  }
+
+  // --- GetCharge ----
+  float_t UnpackTrd::GetMaxAdcValue(const std::vector<std::int16_t>* samples)
+  {
+    // Safety for corrupted input samples
+    assert(samples->size() >= fPeakingBinMin);
+
+    // The signal should peak at the shaping time.
+    // The corresponding sample is the peaking time divided by the sample length.
+    auto itbegin = std::next(samples->begin(), fPeakingBinMin);
+
+    // Check if the expected maximum position of the peaking bin exceeds the size of the vector
+    auto nsamples      = samples->size();
+    auto peakingBinMax = (nsamples - 1) > fPeakingBinMax ? fPeakingBinMax : nsamples;
+    auto itend         = std::next(samples->begin(), peakingBinMax);
+
+    // Get the maximum element
+    auto itmax = std::max_element(itbegin, itend);
+
+    // Get charge and correct for the baseline
+    float_t charge = static_cast<float_t>(*itmax) - GetBaseline(samples);
+
+    // Remark: Due to the fact, that we store the charge UInt_t in the Digi values below 0 are not allowed.
+    // In this case the above only appears if the baseline fluctuated above all values in the applied peaking range.
+    // This can only happen for forced neighbor triggers with a deposited charged that can not be separated from the baseline.
+    return charge > 0 ? charge : 0;
+  }
+
+  // ---- GetBaseline ----
+  float_t UnpackTrd::GetBaseline(const std::vector<std::int16_t>* samples)
+  {
+    // The spadic 2.2 has a functionality that an average baseline can be written to the first sample.
+    // So first we have to check if this is active.
+    if (fParams.fUseBaselineAvg) return samples->at(0);
+    else {
+      float_t baseline = 0.0;
+      auto itend       = samples->begin() + fNrOfPresamples;
+      if (itend > samples->end()) itend = samples->end();
+      for (auto isample = samples->begin(); isample < itend; isample++) {
+        baseline += *isample;
+      }
+      baseline /= fNrOfPresamples;
+
+      return baseline;
+    }
+  }
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/trd/UnpackTrd.h b/algo/detectors/trd/UnpackTrd.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d43d737cf34af34c44bb5fd64e8e18748c482af
--- /dev/null
+++ b/algo/detectors/trd/UnpackTrd.h
@@ -0,0 +1,284 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pascal Raisig, Dominik Smith [committer] */
+
+#ifndef CBM_ALGO_UNPACKTRD_H
+#define CBM_ALGO_UNPACKTRD_H 1
+
+
+#include "CbmTrdDigi.h"
+#include "CbmTrdRawMessageSpadic.h"
+
+#include "MicrosliceDescriptor.hpp"
+#include "Timeslice.hpp"
+
+#include <memory>
+
+#include <cmath>
+
+namespace cbm::algo
+{
+  /** @struct UnpackTrdElinkPar
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023
+   ** @brief TRD Unpacking parameters for one eLink 
+   **/
+  struct UnpackTrdElinkPar {
+    std::vector<uint32_t> fChanAddress;  ///< CbmTrdAddress for different channels
+    uint32_t fAddress;                   ///< Asic address
+    uint64_t fTimeOffset = 0.;           ///< Time calibration parameter
+  };
+
+  /** @struct UnpackTrdCrobPar
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023
+   ** @brief TRD Unpacking parameters for one CROB
+   **/
+  struct UnpackTrdCrobPar {
+    std::vector<UnpackTrdElinkPar> fElinkParams = {};  ///< Parameters for each eLink
+  };
+
+  /** @struct UnpackTrdPar
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023
+   ** @brief Parameters required for the TRD unpacking (specific to one component)
+   **/
+  struct UnpackTrdPar {
+    bool fUseBaselineAvg                      = true;  ///< Is baseline average function of Spadic activated
+    float_t fMaxAdcToEnergyCal                = 1.0;   ///< max adc to energy in keV
+    uint32_t fNumChansPerAsic                 = 0;     ///< Number of channels per ASIC
+    uint32_t fNumAsicsPerModule               = 0;     ///< Number of ASICS per module
+    std::vector<UnpackTrdCrobPar> fCrobParams = {};    ///< Parameters for each CROB
+  };
+
+
+  /** @struct UnpackTrdMoni
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023
+   ** @brief Monitoring data for TRD unpacking
+   **/
+  struct UnpackTrdMonitorData {
+    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
+    size_t fNumNonMajorTsMsb            = 0;  ///< Counter for the ts_msb used to reconstruct the time
+    size_t fNumElinkMis                 = 0;  ///< Number of SOM to RDA/EOM mismatches
+    size_t fNumCorruptEom               = 0;  ///< Number of corrupted EOM frames
+    size_t fNumWildRda                  = 0;  ///< Number of rda frames outside of a SOM frame range
+    size_t fNumWildEom                  = 0;  ///< Number of eom frames outside of a SOM frame range
+    size_t fNumUnknownWords             = 0;  ///< Number of unknown words
+    size_t fNumMissingEom               = 0;  ///< Number of missing EOM frames to finish a SOM frame
+    size_t fNumWildNul                  = 0;  ///< Number of wild null words, should only appear at the end of a µSlice
+    size_t fNumCreatedRawMsgs           = 0;  ///< counter of created raw messages
+    size_t fNumEpochMsgs                = 0;  ///< counter of created raw messages
+    size_t fNumCrcValidFlags            = 0;  ///< counter for inf/error flags from the µSlices
+    size_t fNumOverflowFlimFlags        = 0;  ///< counter for inf/error flags from the µSlices
+    size_t fNumOverflowUserFlags        = 0;  ///< counter for inf/error flags from the µSlices
+    size_t fNumDataErrorFlags           = 0;  ///< counter for inf/error flags from the µSlices
+    size_t fNumCreatedInfoMsgs          = 0;  ///< counter of created info messages
+    bool HasErrors()
+    {
+      uint32_t numErrors = fNumNonHitOrTsbMessage + fNumErrElinkOutOfRange + fNumErrInvalidFirstMessage
+                           + fNumErrInvalidMsSize + fNumErrTimestampOverflow;
+      return (numErrors > 0 ? true : false);
+    }
+  };
+
+
+  /** @class UnpackTrd
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 31 January 2023 
+   ** @brief Unpack algorithm for TRD
+   **/
+  class UnpackTrd {
+
+  public:
+    typedef std::pair<std::vector<CbmTrdDigi>, UnpackTrdMonitorData> resultType;
+
+
+    /** @brief Default constructor **/
+    UnpackTrd() {};
+
+
+    /** @brief Destructor **/
+    ~UnpackTrd() {};
+
+
+    /** @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<UnpackTrdPar> params) { fParams = *(std::move(params)); }
+
+  private:                      // members
+    UnpackTrdPar fParams = {};  ///< Parameter container
+
+    /**
+     ** @brief Handle the output created by the explicit algorithms. E.g. write to output vectors.
+     ** @param digi 
+     ** @param raw 
+     **/
+    void digestOutput(std::unique_ptr<CbmTrdDigi> digi, CbmTrdRawMessageSpadic raw);
+
+    /**
+     ** @brief Digest the aditional flags stored in the 4 "cccc" bits of the EPO messages.
+     ** @param frame 
+     ** @return Spadic::MsInfoType
+     **/
+    Spadic::MsInfoType digestBufInfoFlags(const std::uint32_t frame);
+
+    /**
+     ** @brief Digest the flags of the currently unpacked µSlice.
+     ** @param flags flags stored in the µSlice descriptor 
+     ** @param storage of monitoring data
+     **/
+    void digestMsFlags(const std::uint16_t flags, UnpackTrdMonitorData& monitor);
+
+    /**
+     ** @brief Digest a info message run all default information forwarding from the msg.
+     ** @param frame 
+     **/
+    void digestInfoMsg(const std::uint32_t frame);
+
+    /**
+     ** @brief Extract one adc sample from a given adcbuffer
+     ** @param[in] adcbuffer 
+     ** @param[in,out] nadcbits 
+     ** @return std::int16_t 
+     **/
+    std::int16_t extractSample(size_t* adcbuffer, size_t* nadcbits);
+
+    /**
+     ** @brief Extract the baseline average sample from a given adcbuffer.
+     ** Depending on the Spadic settings sample-0 is a plain sample or the averaged 
+     ** baseline calculation. The latter is not a 9 bit signed integer, but a 9 bit 
+     ** floating point number 7 digits before the point and 2 afterwards.
+     ** @param[in] adcbuffer 
+     ** @param[in,out] nadcbits 
+     ** @return std::float_t
+     **/
+    std::float_t extractAvgSample(size_t* adcbuffer, size_t* nadcbits);
+
+    /** @brief Identify the InfoType of a 64bit InfoMessage word inside a Microslice */
+    Spadic::MsInfoType getInfoType(const std::uint32_t frame);
+
+    /**
+     ** @brief Get the ts_msb information from the TS_MSB(kEPO) frame. We take the first of the 3
+     ** The 3 redundant TS_MSB sets are already compared at the FPGA level.
+     ** @param frame
+     ** @param storage of monitoring data
+     ** @return ts_msb value
+     **/
+    std::uint8_t getTsMsb(const std::uint32_t frame, UnpackTrdMonitorData& monitor);
+
+    /**
+     ** @brief Create a CbmTrdRawMessageSpadic from the hit message input. 
+     ** @param word 
+     ** @param criId id of the cri that send the µSlice
+     ** @param criobId id of the crob that send the µSlice (currently not used set to 0 062021 PR)
+     ** @param istream
+     ** @return CbmTrdRawMessageSpadic 
+     ** @todo Check if we can get rid of the future obsolete microslice stuff.
+     **/
+    CbmTrdRawMessageSpadic makeRaw(const std::uint32_t frame, std::uint16_t criId, std::uint8_t crobId,
+                                   std::uint16_t elinkId, std::uint8_t istream);
+
+    /**
+     ** @brief Get the Digi Trigger Type from the raw message triggertype
+     ** @param tt 
+     ** @return CbmTrdDigi::eTriggerType 
+     **/
+    static CbmTrdDigi::eTriggerType GetDigiTriggerType(Spadic::eTriggerType tt);
+
+    /**
+     ** @brief Create an actual digi from the raw message
+     ** @param raw 
+     **/
+    CbmTrdDigi makeDigi(CbmTrdRawMessageSpadic raw);
+
+    /**
+     ** @brief Get the Bin Time Shift value 
+     ** @param samples 
+     ** @return ULong64_t 
+     **/
+    uint64_t GetBinTimeShift(const std::vector<std::int16_t>* samples) { return 0.; };
+
+    /**
+     ** @brief Get the MaxAdc value
+     ** @param samples 
+     ** @return Float_t 
+     **/
+    std::float_t GetMaxAdcValue(const std::vector<std::int16_t>* samples);
+
+    /**
+     ** @brief Get the Baseline value
+     ** The digi charge is an unsigned. Hence, we need to get the baseline to 0
+     ** @param samples 
+     ** @return float_t 
+     **/
+    float_t GetBaseline(const std::vector<std::int16_t>* samples);
+
+    /** @brief Identify the message type of a given 32bit frame inside a Microslice */
+    Spadic::MsMessageType getMessageType(const std::uint32_t frame);
+
+    /** @brief Counter for the ts_msb used to reconstruct the time */
+    std::vector<std::uint8_t> fNrTsMsbVec = {};
+
+    /** @brief Bytes per spadic frame stored in the microslices */
+    static const std::uint8_t fBytesPerWord = 8;
+
+    /** For the msg format used from 2021 ongoing we have 2 parallel streams per word. *
+     ** All data from eLinks 0..20 go to one stream and 21..41 to the other            */
+    /** @brief Number of streams per word **/
+    static const std::uint8_t fStreamsPerWord = 2;
+
+    /**
+     ** @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;
+
+    /** @brief Start time of the current µSlice relative to the Timeslice start time in Spadic CC. */
+    size_t fMsStartTimeRelCC = 0;
+
+    /** @brief Time of the last succesful digest hit message */
+    size_t fLastFulltime = 0;
+
+    /** @brief Number of samples not considered for max adc */
+    static constexpr size_t fNrOfPresamples = 1;
+
+    /** @brief Clock length of Spadic in ns. */
+    static constexpr float_t fAsicClockCycle = 62.5;
+
+    /** @brief length of one ts_msb in [ns] */
+    static constexpr std::uint16_t fTsMsbLength = 16000;
+
+    /** @brief length of one ts_msb in [cc] */
+    static constexpr size_t fTsMsbLengthCC = fTsMsbLength / fAsicClockCycle;
+
+    /** @brief First sample to look for the max adc */
+    static constexpr size_t fPeakingBinMin = fNrOfPresamples;
+
+    /**
+     ** @brief Last sample to look for the max adc
+     ** Default value is set based on the Shaping time + 5 samples safety margin.
+     ** @remark the peaking time strongly depends on the input signal. Effective range of 
+     ** the shaping time is between 120 and 240 ns.
+     **/
+    static constexpr size_t fPeakingBinMax = static_cast<size_t>(120.0 / fAsicClockCycle + fNrOfPresamples + 5);
+  };
+
+
+} /* namespace cbm::algo */
+
+#endif /* CBM_ALGO_UNPACKTRD_H */
diff --git a/core/data/trd/CbmTrdRawMessageSpadic.cxx b/core/data/trd/CbmTrdRawMessageSpadic.cxx
index 66bf8e03b53e6e4caa80e8a69569605cfa67357b..43388b0af6072bacd5d1eaa4ff76d417fbb1dda2 100644
--- a/core/data/trd/CbmTrdRawMessageSpadic.cxx
+++ b/core/data/trd/CbmTrdRawMessageSpadic.cxx
@@ -82,5 +82,3 @@ void CbmTrdRawMessageSpadic::SetSample(std::int16_t value, std::uint8_t pos)
 
   return;
 }
-
-ClassImp(CbmTrdRawMessageSpadic)
diff --git a/core/data/trd/CbmTrdRawMessageSpadic.h b/core/data/trd/CbmTrdRawMessageSpadic.h
index fca0d2f3f86e1d8a6aaf6a07f2ac3052a76c6bfd..abf4698f252a9ca102b65cfc7a2b1e7af637d605 100644
--- a/core/data/trd/CbmTrdRawMessageSpadic.h
+++ b/core/data/trd/CbmTrdRawMessageSpadic.h
@@ -11,14 +11,12 @@
 #ifndef CbmTrdRawMessageSpadic_H
 #define CbmTrdRawMessageSpadic_H
 
-#include <Rtypes.h>      // for ClassDef
-
 #include <boost/serialization/access.hpp>
 #include <boost/serialization/base_object.hpp>
 #include <boost/serialization/vector.hpp>
 
 #include <cstdint>
-#include <vector>   // for vector
+#include <vector>  // for vector
 
 namespace Spadic
 {
@@ -125,7 +123,7 @@ public:
   void IncNrSamples() { fNrSamples++; }
 
   /** Set the full time in nanoseconds */
-  void SetTime(Double_t setvalue) { fFullTime = static_cast<std::uint64_t>(setvalue / 62.5); }
+  void SetTime(double setvalue) { fFullTime = static_cast<std::uint64_t>(setvalue / 62.5); }
 
   /** Returns the value of the sample with the highest value. */
   int16_t GetMaxAdc();
@@ -153,9 +151,6 @@ public:
     ar& fFullTime;
     ar& fSamples;
   }
-
-  // Root Class Def Macro
-  ClassDef(CbmTrdRawMessageSpadic, 2);
 };
 
 #endif
diff --git a/reco/tasks/CMakeLists.txt b/reco/tasks/CMakeLists.txt
index 6c7302a21cc694f3fe6713dff4e69a2af41d8f03..8e4478fdd84d1336ac03a9ccdaaac562de1f4d08 100644
--- a/reco/tasks/CMakeLists.txt
+++ b/reco/tasks/CMakeLists.txt
@@ -35,6 +35,7 @@ set(PUBLIC_DEPENDENCIES
 
 set(PRIVATE_DEPENDENCIES
   CbmBase
+  CbmTrdBase
   FairLogger::FairLogger
   FairRoot::Online
   external::yaml-cpp
diff --git a/reco/tasks/CbmTaskUnpack.cxx b/reco/tasks/CbmTaskUnpack.cxx
index a736afb4f29a1a182421fd0536b590ceae1cc9d8..a5b8926b8886121f353fc195638a8616c74b476d 100644
--- a/reco/tasks/CbmTaskUnpack.cxx
+++ b/reco/tasks/CbmTaskUnpack.cxx
@@ -11,9 +11,13 @@
 #include "CbmDigiManager.h"
 #include "CbmDigiTimeslice.h"
 #include "CbmSourceTs.h"
+#include "CbmTrdParSetAsic.h"
+#include "CbmTrdParSpadic.h"
 
 #include "MicrosliceDescriptor.hpp"
 
+#include <FairParAsciiFileIo.h>
+#include <FairParamList.h>
 #include <FairRunOnline.h>
 #include <Logger.h>
 
@@ -42,6 +46,9 @@ using cbm::algo::UnpackStsElinkPar;
 using cbm::algo::UnpackStsPar;
 using cbm::algo::UnpackTofElinkPar;
 using cbm::algo::UnpackTofPar;
+using cbm::algo::UnpackTrdCrobPar;
+using cbm::algo::UnpackTrdElinkPar;
+using cbm::algo::UnpackTrdPar;
 
 // -----   Constructor   -----------------------------------------------------
 CbmTaskUnpack::CbmTaskUnpack() : FairTask("Unpack") {}
@@ -191,6 +198,25 @@ void CbmTaskUnpack::Exec(Option_t*)
       numCompUsed++;
     }  // system T0
 
+    // system TRD
+    if (systemId == fles::SubsystemIdentifier::TRD) {
+      const uint16_t equipmentId = timeslice->descriptor(comp, 0).eq_id;
+      const auto algoIt          = fAlgoTrd.find(equipmentId);
+      assert(algoIt != fAlgoTrd.end());
+
+      // The current algorithm works for the TRD 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.fTrd.fDigis, &numBytesInComp, &numDigisInComp);
+
+      numCompUsed++;
+    }  // system TRD
+
     compTimer.Stop();
     LOG(debug) << GetName() << ": Component " << comp << ", microslices " << numMsInComp << " input size "
                << numBytesInComp << " bytes, "
@@ -212,6 +238,8 @@ void CbmTaskUnpack::Exec(Option_t*)
             [](CbmTofDigi digi1, CbmTofDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
   std::sort(std::execution::par_unseq, fTimeslice->fData.fT0.fDigis.begin(), fTimeslice->fData.fT0.fDigis.end(),
             [](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(); });
 #else
   std::sort(fTimeslice->fData.fSts.fDigis.begin(), fTimeslice->fData.fSts.fDigis.end(),
             [](CbmStsDigi digi1, CbmStsDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
@@ -221,6 +249,8 @@ void CbmTaskUnpack::Exec(Option_t*)
             [](CbmTofDigi digi1, CbmTofDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
   std::sort(fTimeslice->fData.fT0.fDigis.begin(), fTimeslice->fData.fT0.fDigis.end(),
             [](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(); });
 #endif
 
   // --- Timeslice log
@@ -360,6 +390,29 @@ InitStatus CbmTaskUnpack::Init()
     LOG(info) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
   }
 
+  InitTrdReadoutConfig();
+  auto equipIdsTrd = fTrdConfig.GetEquipmentIds();
+  for (auto& equip : equipIdsTrd) {
+
+    std::unique_ptr<UnpackTrdPar> par(new UnpackTrdPar());
+    const size_t numCrobs = fTrdConfig.GetNumCrobs(equip);
+    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++) {
+
+        UnpackTrdElinkPar elinkPar;
+        auto addresses        = fTrdConfig.Map(equip, crob, elink);
+        elinkPar.fAddress     = addresses.first;   // Asic address for this elink
+        elinkPar.fChanAddress = addresses.second;  // Channel addresses for this elink
+        crobPar.fElinkParams.push_back(elinkPar);
+      }
+      par->fCrobParams.push_back(crobPar);
+    }
+    fAlgoTrd[equip].SetParams(std::move(par));
+    LOG(info) << "--- Configured equipment " << equip << " with " << numCrobs << " crobs";
+  }
 
   LOG(info) << "--- Configured " << fAlgoSts.size() << " unpacker algorithms for STS.";
   LOG(debug) << "Readout map:" << fStsConfig.PrintReadoutMap();
@@ -373,5 +426,62 @@ InitStatus CbmTaskUnpack::Init()
 }
 // ----------------------------------------------------------------------------
 
+// -----   Initialisation   ---------------------------------------------------
+void CbmTaskUnpack::InitTrdReadoutConfig()
+{
+  std::string trdparfile = Form("%s/parameters/trd/trd_v21d_mcbm.asic.par", std::getenv("VMCWORKDIR"));
+
+  CbmTrdParSetAsic trdpar;
+
+  FairParAsciiFileIo asciiInput;
+  if (asciiInput.open(trdparfile.data())) { trdpar.init(&asciiInput); }
+
+  FairParamList parlist;
+  trdpar.putParams(&parlist);
+
+  std::vector<int> moduleIds(trdpar.GetNrOfModules());
+  parlist.fill("ModuleId", moduleIds.data(), moduleIds.size());
+
+  std::map<size_t, std::map<size_t, std::map<size_t, size_t>>> addressMap;  //[criId][crobId][elinkId] -> asicAddress
+  std::map<size_t, std::map<size_t, std::map<size_t, std::map<size_t, size_t>>>>
+    channelMap;  //[criId][crobId][elinkId][chanId] -> chanAddress
+
+  for (auto module : moduleIds) {
+    CbmTrdParSetAsic* moduleSet = (CbmTrdParSetAsic*) trdpar.GetModuleSet(module);
+
+    // Skip entries for "Fasp" modules in .asic.par file
+    if (moduleSet->GetAsicType() != static_cast<int32_t>(CbmTrdDigi::eCbmTrdAsicType::kSPADIC)) continue;
+
+    std::vector<int> asicAddresses;
+    moduleSet->GetAsicAddresses(&asicAddresses);
+
+    for (auto address : asicAddresses) {
+      CbmTrdParSpadic* asicPar = (CbmTrdParSpadic*) moduleSet->GetAsicPar(address);
+      const uint16_t criId     = asicPar->GetCriId();
+      const uint8_t crobId     = asicPar->GetCrobId();
+      const uint8_t elinkId    = asicPar->GetElinkId(0);
+      if (elinkId >= 98) { continue; }  // Don't add not connected asics to the map
+      addressMap[criId][crobId][elinkId]     = address;
+      addressMap[criId][crobId][elinkId + 1] = address;
+
+      const uint8_t numChans = 16;
+      for (uint8_t chan = 0; chan < numChans; chan++) {
+        auto asicChannelId                       = (elinkId % 2) == 0 ? chan : chan + numChans;
+        auto chanAddr                            = asicPar->GetChannelAddresses().at(asicChannelId);
+        channelMap[criId][crobId][elinkId][chan] = chanAddr;
+      }
+      for (uint8_t chan = 0; chan < numChans; chan++) {
+        auto asicChannelId                           = (elinkId + 1 % 2) == 0 ? chan : chan + numChans;
+        auto chanAddr                                = asicPar->GetChannelAddresses().at(asicChannelId);
+        channelMap[criId][crobId][elinkId + 1][chan] = chanAddr;
+      }
+      LOG(debug) << "componentID " << asicPar->GetComponentId() << " "
+                 << "address " << address << " key " << criId << " " << unsigned(crobId) << " " << unsigned(elinkId);
+    }
+  }
+  fTrdConfig.Init(addressMap, channelMap);
+  LOG(debug) << fTrdConfig.PrintReadoutMap();
+}
+
 
 ClassImp(CbmTaskUnpack)
diff --git a/reco/tasks/CbmTaskUnpack.h b/reco/tasks/CbmTaskUnpack.h
index 040dbc0175e24fdaf32789cea63ee4bcadb530fd..9fd0d0a332b5e2a51814e484659bb57c3ed513ea 100644
--- a/reco/tasks/CbmTaskUnpack.h
+++ b/reco/tasks/CbmTaskUnpack.h
@@ -12,6 +12,7 @@
 #include <FairTask.h>
 
 #include "TofReadoutConfig.h"
+#include "TrdReadoutConfig.h"
 
 #include <sstream>
 #include <vector>
@@ -24,6 +25,7 @@
 #include "UnpackMuch.h"
 #include "UnpackSts.h"
 #include "UnpackTof.h"
+#include "UnpackTrd.h"
 
 class CbmDigiManager;
 class CbmSourceTs;
@@ -72,6 +74,8 @@ private:  // methods
   /** @brief Task initialisation **/
   virtual InitStatus Init();
 
+  /** @brief Initialisation of address maps for Trd **/
+  virtual void InitTrdReadoutConfig();
 
 private:  // members
   CbmSourceTs* fSource = nullptr;
@@ -88,6 +92,9 @@ private:  // members
   std::map<uint16_t, cbm::algo::UnpackBmon> fAlgoBmon = {};
   cbm::algo::BmonReadoutConfig fBmonConfig {};
 
+  std::map<uint16_t, cbm::algo::UnpackTrd> fAlgoTrd = {};
+  cbm::algo::TrdReadoutConfig fTrdConfig {};
+
   size_t fNumTs                = 0;
   size_t fNumMs                = 0;
   size_t fNumBytes             = 0;