diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index eabd850ac8d6239138405068a08ce7106a6a535f..bcaa1a9ecd426e022d4b0ec02606fb9cad2dd070 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -14,6 +14,7 @@ set(SRCS
   base/Options.cxx
   base/util/TimingsFormat.cxx
   data/sts/LandauTable.cxx
+  data/sts/ReadoutSetup.cxx
   evbuild/EventBuilder.cxx
   trigger/TimeClusterTrigger.cxx
   evselector/DigiEventSelector.cxx
@@ -105,6 +106,7 @@ install(
     ca/simd/CaSimdPseudo.h
     detectors/sts/StsHitfinder.h
     detectors/sts/StsHitfinderChain.h
+    global/Reco.h
   DESTINATION
     include/
 )
diff --git a/algo/data/sts/ReadoutSetup.cxx b/algo/data/sts/ReadoutSetup.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..a436569489f94cd865b39927338e2972cfc5b182
--- /dev/null
+++ b/algo/data/sts/ReadoutSetup.cxx
@@ -0,0 +1,140 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer], Felix Weiglhofer */
+#include "ReadoutSetup.h"
+
+#include "CbmStsAddress.h"
+
+#include <cassert>
+#include <iomanip>
+
+using std::pair;
+using std::setw;
+
+using namespace cbm::algo;
+
+// ---  Constructor  ------------------------------------------------------------------
+sts::ReadoutMapping::ReadoutMapping(const ReadoutSetup& config) { Init(config); }
+// ------------------------------------------------------------------------------------
+
+// ---   Equipment IDs   --------------------------------------------------------------
+std::vector<u16> sts::ReadoutMapping::GetEquipmentIds()
+{
+  std::vector<uint16_t> result;
+  for (auto& entry : fReadoutMapping)
+    result.push_back(entry.first);
+  return result;
+}
+// ------------------------------------------------------------------------------------
+
+// ---   Number of elinks for a component / equipment   -------------------------------
+size_t sts::ReadoutMapping::GetNumElinks(u16 equipmentId)
+{
+  size_t result = 0;
+  auto it       = fReadoutMapping.find(equipmentId);
+  if (it != fReadoutMapping.end()) result = fReadoutMapping[equipmentId].size();
+  return result;
+}
+// ------------------------------------------------------------------------------------
+
+size_t sts::ReadoutMapping::GetNumElinks()
+{
+  size_t result = 0;
+  for (auto& entry : fReadoutMapping) {
+    result += entry.second.size();
+  }
+  return result;
+}
+
+// ---  Initialise the mapping structure   --------------------------------------------
+void sts::ReadoutMapping::Init(const ReadoutSetup& config)
+{
+  // The readout hierarchy is: component - CROB - FEB - ASIC (elink). Each elink
+  // connects one ASIC. One FEB comprises 8 ASICs and reads out one side of a module (sensor).
+  // In this setup, there is only one CROB per component. The code below is formulate such
+  // as to support also multiple CROBs per component. In that case, the elinks are numbered
+  // consecutively within one component.
+
+  // Constants
+  const uint16_t numModules     = config.modules.size();                      // Number of modules in the setup
+  const uint16_t numComp        = config.components.size();                   // Number of components
+  const uint16_t numCrobPerComp = config.components.at(0).feb2module.size();  // Number of CROBs per component
+  // const uint16_t numFebsPerCrob   = config.components.at(0).feb2module.at(0).size();  // Number of FEBs per CROB
+  const uint16_t numAsicsPerFeb   = config.numAsicsPerFeb;  // Number of ASICs per FEB
+  const uint16_t numElinksPerCrob = config.elinks.size();   // Number of elinks per CROB
+
+  // Constructing the map (equipmentId, eLink) -> (module, ASIC within module)
+  uint16_t numElinksPerComp = numCrobPerComp * numElinksPerCrob;
+  for (uint16_t comp = 0; comp < numComp; comp++) {
+    uint16_t equipment = config.components.at(comp).equipmentId;
+    fReadoutMapping[equipment].resize(numElinksPerComp);
+    for (uint16_t crob = 0; crob < numCrobPerComp; crob++) {
+      for (uint16_t elink = 0; elink < numElinksPerCrob; elink++) {
+
+        int32_t moduleAddress = -1;
+        uint16_t asicInModule = 0;
+        bool isPulser         = false;
+
+        uint16_t elinkId = numElinksPerCrob * crob + elink;  // elink within component
+        int16_t feb      = config.elinks.at(elinkId).toFeb;  // FEB within CROB
+        if (feb != -1) {
+          int16_t module = config.components[comp].feb2module[crob][feb];         // Module index
+          isPulser       = config.components[comp].febIsPulser.at(crob).at(feb);  // Pulser flag
+
+          if (module != -1) {
+            assert(module < numModules);
+            moduleAddress      = config.modules.at(module).address;                     // Module address
+            bool moduleType    = config.modules.at(module).type;                        // 0 or 1
+            int16_t moduleSide = config.components.at(comp).feb2moduleSide[crob][feb];  // 0 or 1, -1 is inactive
+            int16_t febType    = (moduleType == 0 ? moduleSide : !moduleSide);          // 0 = FEB A, 1 = FEB B
+            uint32_t asicIndex = (febType == 0 ? config.elinks[elinkId].toAsicFebA : config.elinks[elinkId].toAsicFebB);
+            uint32_t asicInFeb = asicIndex % numAsicsPerFeb;  // ASIC number within FEB
+
+            asicInModule = (moduleSide == 1 ? asicInFeb : asicInFeb + numAsicsPerFeb);
+          }
+        }
+        fReadoutMapping[equipment][elink] = {moduleAddress, asicInModule, isPulser};
+
+      }  //# elink
+    }    //# CROB
+  }      //# component
+}
+// ------------------------------------------------------------------------------------
+
+// ---  Mapping (equimentId, elink) -> (address, ASIC)  --------------------------------
+sts::ReadoutMapping::Entry sts::ReadoutMapping::Map(uint16_t equipmentId, uint16_t elinkId)
+{
+  Entry result {-1, 0, false};
+  auto equipIter = fReadoutMapping.find(equipmentId);
+  if (equipIter != fReadoutMapping.end()) {
+    if (elinkId < equipIter->second.size()) { result = equipIter->second.at(elinkId); }
+  }
+  return result;
+}
+// ------------------------------------------------------------------------------------
+
+// -----   Print readout map   ------------------------------------------------
+std::string sts::ReadoutMapping::PrintReadoutMap()
+{
+
+  std::stringstream ss;
+  for (auto& equipment : fReadoutMapping) {
+    auto eqId = equipment.first;
+    for (size_t elink = 0; elink < equipment.second.size(); elink++) {
+      auto address = equipment.second.at(elink).moduleAddress;
+      auto asicNr  = equipment.second.at(elink).asicNumber;
+      ss << "\n Equipment " << eqId << "  elink " << setw(2) << elink;
+      ss << "  ASIC " << setw(2) << asicNr << "  module " << address;
+      if (address != -1) {
+        ss << "  Unit " << setw(2) << CbmStsAddress::GetElementId(address, kStsUnit);
+        ss << "  Ladd " << setw(2) << CbmStsAddress::GetElementId(address, kStsLadder);
+        ss << "  Hlad " << setw(2) << CbmStsAddress::GetElementId(address, kStsHalfLadder);
+        ss << "  Modu " << setw(2) << CbmStsAddress::GetElementId(address, kStsModule);
+      }
+      else
+        ss << "  Inactive";
+    }  //# elink
+  }    //# component
+  return ss.str();
+}
+// ----------------------------------------------------------------------------
diff --git a/algo/data/sts/ReadoutSetup.h b/algo/data/sts/ReadoutSetup.h
new file mode 100644
index 0000000000000000000000000000000000000000..ee09adf1311b7bb2dfff7eca8197aa72a29180b6
--- /dev/null
+++ b/algo/data/sts/ReadoutSetup.h
@@ -0,0 +1,135 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer], Felix Weiglhofer */
+#ifndef CBM_ALGO_DATA_STS_READOUT_H
+#define CBM_ALGO_DATA_STS_READOUT_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "Prelude.h"
+#include "config/Property.h"
+
+namespace cbm::algo::sts
+{
+
+  /**
+   * @brief Readout setup / Hardware cabling for STS
+   * Used to create the hardware mapping for the STS unpacker.
+   */
+  struct ReadoutSetup {
+
+    struct Module {
+      i32 address;
+      i32 type;
+
+      static constexpr auto Properties = std::make_tuple(
+        config::Property(&Module::address, "address", "HW address of module", YAML::Hex),
+        config::Property(&Module::type, "type",
+                         "Type 0 had the connector at the right side, type 1 at the left side. For type 0, the mapping "
+                         "of FEB to module side as above applies, for type 1, it has to be inverted."));
+    };
+
+    struct Component {
+      u16 equipmentId;
+      std::vector<std::vector<i16>> feb2module;
+      std::vector<std::vector<i16>> feb2moduleSide;
+      std::vector<std::vector<bool>> febIsPulser;
+
+      static constexpr auto Properties = std::make_tuple(
+        config::Property(&Component::equipmentId, "equipmentId",
+                         "Equipment ID of component. Written to the data stream (MicrosliceDescriptor).", YAML::Hex),
+        config::Property(&Component::feb2module, "feb2module",
+                         "Mapping of FEB within CROB to module index (-1 = inactive)"),
+        config::Property(&Component::feb2moduleSide, "feb2moduleSide",
+                         "Mapping of FEB within CROB to module side (0 = left, 1 = right)"),
+        config::Property(&Component::febIsPulser, "febIsPulser", "Flag if FEB is pulser (true) or not (false)"));
+    };
+
+    struct Elink {
+      i16 toFeb;
+      u32 toAsicFebA;
+      u32 toAsicFebB;
+
+      static constexpr auto Properties = std::make_tuple(
+        config::Property(&Elink::toFeb, "toFeb", "Mapping of elink to FEB within CROB (-1 = inactive)"),
+        config::Property(&Elink::toAsicFebA, "toAsicFebA", "Mapping of eLink to ASIC for FEB Type A", YAML::Hex),
+        config::Property(&Elink::toAsicFebB, "toAsicFebB", "Mapping of eLink to ASIC for FEB Type B", YAML::Hex));
+    };
+
+    u16 numAsicsPerFeb;
+    std::vector<Module> modules;
+    std::vector<Component> components;
+    std::vector<Elink> elinks;
+
+    static constexpr auto Properties =
+      std::make_tuple(config::Property(&ReadoutSetup::numAsicsPerFeb, "numAsicsPerFeb", "Number of ASICs per FEB"),
+                      config::Property(&ReadoutSetup::modules, "modules", "Modules", {}, YAML::Flow),
+                      config::Property(&ReadoutSetup::components, "components", "Components", {}, YAML::Flow),
+                      config::Property(&ReadoutSetup::elinks, "elinks", "Elinks", {}, YAML::Flow));
+  };
+
+  /** @class ReadoutMapping
+  ** @author Volker Friese <v.friese@gsi.de>
+  ** @since 3 March 2022
+  ** @brief Provides the hardware-to-software address mapping for the CBM-STS
+  **
+  ** 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.
+  **/
+  class ReadoutMapping {
+
+  public:
+    struct Entry {
+      i32 moduleAddress;
+      u16 asicNumber;
+      bool pulser;
+    };
+
+    /** @brief Empty mapping **/
+    ReadoutMapping() = default;
+
+    /** @brief Constructor **/
+    ReadoutMapping(const ReadoutSetup&);
+
+    /** @brief Equipment in the configuration
+   ** @return Vector of equipment IDs
+      **/
+    std::vector<u16> GetEquipmentIds();
+
+    /** @brief Number of elinks of a component
+   ** @param Equipment ID
+      ** @return Number of elinks
+      **/
+    size_t GetNumElinks(u16 equipmentId);
+
+    /** @brief Total number of elinks for STS
+     ** @return Number of elinks
+     **/
+    size_t GetNumElinks();
+
+    /** @brief API: Mapping from component and elink to address / ASIC number + pulser flag
+      ** @param equipId     Equipment identifier (component)
+      ** @param elink       Elink number within component
+      ** @return (module address, ASIC number within module)
+      */
+    Entry Map(u16 equipId, u16 elink);
+
+    /** @brief Debug output of readout map **/
+    std::string PrintReadoutMap();
+
+  private:
+    // --- STS readout map
+    // --- Map index: (equipment, elink)
+    std::map<u16, std::vector<Entry>> fReadoutMapping = {};  //!
+
+    /** @brief Initialisation of readout map **/
+    void Init(const ReadoutSetup&);
+  };
+
+}  // namespace cbm::algo::sts
+
+#endif  // CBM_ALGO_DATA_STS_READOUT_H
diff --git a/algo/detectors/sts/StsReadoutConfig.h b/algo/detectors/sts/StsReadoutConfig.h
index 76dd79efb5d2c9a1fb9bc1593cef86ccf230de96..b6480d5c87edf183bd66320fcb942880b5def04d 100644
--- a/algo/detectors/sts/StsReadoutConfig.h
+++ b/algo/detectors/sts/StsReadoutConfig.h
@@ -24,6 +24,8 @@ namespace cbm::algo
    ** 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.
+   **
+   ** NOTE: This class is deprecated. It's replaced by ReadoutMapping in algo/data/sts. The new class no longer hardcodes the mapping. It can be read from a configuration file instead.
    **/
 
   class StsReadoutConfig {
diff --git a/algo/detectors/sts/StsUnpackChain.cxx b/algo/detectors/sts/StsUnpackChain.cxx
index 506813298585ec326a44fb0bad19e864ce718a1f..4dfe0add409b3bfc9abdbf7c47401c71b54e3911 100644
--- a/algo/detectors/sts/StsUnpackChain.cxx
+++ b/algo/detectors/sts/StsUnpackChain.cxx
@@ -13,7 +13,7 @@
 
 using namespace cbm::algo;
 
-void sts::UnpackChain::Init(StsReadoutConfig config)
+void sts::UnpackChain::Init(sts::ReadoutMapping config)
 {
   fConfig = config;
 
@@ -33,8 +33,8 @@ void sts::UnpackChain::Init(StsReadoutConfig config)
     for (size_t elink = 0; elink < numElinks; elink++) {
       UnpackStsElinkPar elinkPar;
       auto mapEntry        = fConfig->Map(equip, elink);
-      elinkPar.fAddress    = mapEntry.first;   // Module address for this elink
-      elinkPar.fAsicNr     = mapEntry.second;  // ASIC number within module
+      elinkPar.fAddress    = mapEntry.moduleAddress;  // Module address for this elink
+      elinkPar.fAsicNr     = mapEntry.asicNumber;     // ASIC number within module
       elinkPar.fTimeOffset = 0.;
       elinkPar.fAdcOffset  = 1.;
       elinkPar.fAdcGain    = 1.;
diff --git a/algo/detectors/sts/StsUnpackChain.h b/algo/detectors/sts/StsUnpackChain.h
index e5d7a8375197566290766294d88fb2dc4964a83c..eacba3cb4d9dd614edcd14cf431ee1046725e959 100644
--- a/algo/detectors/sts/StsUnpackChain.h
+++ b/algo/detectors/sts/StsUnpackChain.h
@@ -12,9 +12,9 @@
 #include <yaml-cpp/yaml.h>
 
 #include "Prelude.h"
-#include "StsReadoutConfig.h"
 #include "SubChain.h"
 #include "UnpackSts.h"
+#include "sts/ReadoutSetup.h"
 
 class CbmStsDigi;
 namespace fles
@@ -28,12 +28,12 @@ namespace cbm::algo::sts
   class UnpackChain : public SubChain {
 
   public:
-    void Init(StsReadoutConfig config);
+    void Init(sts::ReadoutMapping config);
     std::vector<CbmStsDigi> Run(const fles::Timeslice& ts);
 
   private:
     std::unordered_map<u16, UnpackSts> fAlgoSts;
-    std::optional<StsReadoutConfig> fConfig;
+    std::optional<sts::ReadoutMapping> fConfig;
   };
 
 }  // namespace cbm::algo::sts
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index bc42367af62f71803f7cc66639e2615759869a0b..1b71db50f1f58e9e48c33205ede3d4b895827b89 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -32,10 +32,11 @@ void Reco::Init(const Options& opts)
   fContext.recoParams     = config::Read<RecoParams>(yaml);
 
   // STS Unpacker
-  // yaml = YAML::LoadFile(opts.ParamsDir() / "StsReadout.yaml");
-  // sts::ReadoutPars readoutConfig = config::Read<sts::ReadoutPars>(yaml);
-  StsReadoutConfig readoutConfig;
-  fUnpack.Init(readoutConfig);
+  fs::path stsUnpackParamsPath    = opts.ParamsDir() / "StsReadout.yaml";
+  yaml                            = YAML::LoadFile(stsUnpackParamsPath.string());
+  sts::ReadoutSetup readoutConfig = config::Read<sts::ReadoutSetup>(yaml);
+  sts::ReadoutMapping mapping(readoutConfig);
+  fUnpack.Init(mapping);
 
   // STS Hitfinder
   fs::path stsHitfinderParamsPath    = opts.ParamsDir() / "StsHitfinder.yaml";
diff --git a/algo/params/StsReadout.yaml b/algo/params/StsReadout.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0e5dc63e60fcab36b4a845d286c1767fb1d6a2de
--- /dev/null
+++ b/algo/params/StsReadout.yaml
@@ -0,0 +1,67 @@
+---
+numAsicsPerFeb: 8
+modules:
+  - {address: 0x10008002, type: 0}
+  - {address: 0x10018002, type: 0}
+  - {address: 0x10008402, type: 1}
+  - {address: 0x10018402, type: 1}
+  - {address: 0x10107c02, type: 1}
+  - {address: 0x10008412, type: 0}
+  - {address: 0x10018412, type: 0}
+  - {address: 0x101ffc02, type: 0}
+  - {address: 0x10008812, type: 1}
+  - {address: 0x10018812, type: 1}
+  - {address: 0x10028812, type: 1}
+  - {address: 0x10008012, type: 1}
+  - {address: 0x10018012, type: 1}
+components:
+  - {equipmentId: 0x1003, feb2module: [[-1, 1, 1, 0, 0]], feb2moduleSide: [[-1, 1, 0, 1, 0]], febIsPulser: [[n, n, n, n, n]]}
+  - {equipmentId: 0x1002, feb2module: [[4, 3, 3, 2, 2]], feb2moduleSide: [[1, 1, 0, 1, 0]], febIsPulser: [[y, n, n, n, n]]}
+  - {equipmentId: 0x1006, feb2module: [[7, 6, 6, 5, 5]], feb2moduleSide: [[1, 1, 0, 1, 0]], febIsPulser: [[y, n, n, n, n]]}
+  - {equipmentId: 0x1005, feb2module: [[10, 9, 9, 8, 8]], feb2moduleSide: [[0, 1, 0, 1, 0]], febIsPulser: [[n, n, n, n, n]]}
+  - {equipmentId: 0x1004, feb2module: [[12, 12, 11, 11, 10]], feb2moduleSide: [[1, 0, 1, 0, 1]], febIsPulser: [[n, n, n, n, n]]}
+elinks:
+  - {toFeb: 4, toAsicFebA: 0x21, toAsicFebB: 0x27}
+  - {toFeb: 4, toAsicFebA: 0x23, toAsicFebB: 0x25}
+  - {toFeb: 4, toAsicFebA: 0x25, toAsicFebB: 0x23}
+  - {toFeb: 4, toAsicFebA: 0x20, toAsicFebB: 0x26}
+  - {toFeb: 4, toAsicFebA: 0x22, toAsicFebB: 0x24}
+  - {toFeb: 3, toAsicFebA: 0x18, toAsicFebB: 0x1e}
+  - {toFeb: 3, toAsicFebA: 0x1a, toAsicFebB: 0x1c}
+  - {toFeb: 3, toAsicFebA: 0x1c, toAsicFebB: 0x1a}
+  - {toFeb: 4, toAsicFebA: 0x24, toAsicFebB: 0x22}
+  - {toFeb: 4, toAsicFebA: 0x27, toAsicFebB: 0x21}
+  - {toFeb: -1, toAsicFebA: 0xffff, toAsicFebB: 0xffff}
+  - {toFeb: -1, toAsicFebA: 0xffff, toAsicFebB: 0xffff}
+  - {toFeb: 4, toAsicFebA: 0x26, toAsicFebB: 0x20}
+  - {toFeb: 3, toAsicFebA: 0x1e, toAsicFebB: 0x18}
+  - {toFeb: 2, toAsicFebA: 0x10, toAsicFebB: 0x16}
+  - {toFeb: 2, toAsicFebA: 0x12, toAsicFebB: 0x14}
+  - {toFeb: 3, toAsicFebA: 0x19, toAsicFebB: 0x1f}
+  - {toFeb: 3, toAsicFebA: 0x1b, toAsicFebB: 0x1d}
+  - {toFeb: 3, toAsicFebA: 0x1f, toAsicFebB: 0x19}
+  - {toFeb: 1, toAsicFebA: 0xe, toAsicFebB: 0x8}
+  - {toFeb: 2, toAsicFebA: 0x11, toAsicFebB: 0x17}
+  - {toFeb: 2, toAsicFebA: 0x13, toAsicFebB: 0x15}
+  - {toFeb: 1, toAsicFebA: 0xc, toAsicFebB: 0xa}
+  - {toFeb: 2, toAsicFebA: 0x15, toAsicFebB: 0x13}
+  - {toFeb: 2, toAsicFebA: 0x17, toAsicFebB: 0x11}
+  - {toFeb: 2, toAsicFebA: 0x16, toAsicFebB: 0x10}
+  - {toFeb: 3, toAsicFebA: 0x1d, toAsicFebB: 0x1b}
+  - {toFeb: 2, toAsicFebA: 0x14, toAsicFebB: 0x12}
+  - {toFeb: 1, toAsicFebA: 0x9, toAsicFebB: 0xf}
+  - {toFeb: 1, toAsicFebA: 0xd, toAsicFebB: 0xb}
+  - {toFeb: 1, toAsicFebA: 0xf, toAsicFebB: 0x9}
+  - {toFeb: 1, toAsicFebA: 0x8, toAsicFebB: 0xe}
+  - {toFeb: 1, toAsicFebA: 0xa, toAsicFebB: 0xc}
+  - {toFeb: 0, toAsicFebA: 0x2, toAsicFebB: 0x4}
+  - {toFeb: 0, toAsicFebA: 0x4, toAsicFebB: 0x2}
+  - {toFeb: 0, toAsicFebA: 0x6, toAsicFebB: 0x0}
+  - {toFeb: 1, toAsicFebA: 0xb, toAsicFebB: 0xd}
+  - {toFeb: 0, toAsicFebA: 0x5, toAsicFebB: 0x3}
+  - {toFeb: 0, toAsicFebA: 0x0, toAsicFebB: 0x6}
+  - {toFeb: 0, toAsicFebA: 0x3, toAsicFebB: 0x5}
+  - {toFeb: 0, toAsicFebA: 0x7, toAsicFebB: 0x1}
+  - {toFeb: 0, toAsicFebA: 0x1, toAsicFebB: 0x7}
+...
+