diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e4a354d..2b64dd7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -448,6 +448,7 @@ list(APPEND SOURCE_FILES
         components/ble/BleController.cpp
         components/ble/NotificationManager.cpp
         components/datetime/DateTimeController.cpp
+        components/statistics/Statistics.cpp
         components/brightness/BrightnessController.cpp
         components/motion/MotionController.cpp
         components/ble/NimbleController.cpp
@@ -519,6 +520,7 @@ list(APPEND RECOVERY_SOURCE_FILES
         components/ble/BleController.cpp
         components/ble/NotificationManager.cpp
         components/datetime/DateTimeController.cpp
+        components/statistics/Statistics.cpp
         components/brightness/BrightnessController.cpp
         components/motion/MotionController.cpp
         components/ble/NimbleController.cpp
diff --git a/src/components/statistics/Statistics.cpp b/src/components/statistics/Statistics.cpp
new file mode 100644
index 0000000..2aafe2c
--- /dev/null
+++ b/src/components/statistics/Statistics.cpp
@@ -0,0 +1,86 @@
+#include "Statistics.h"
+
+using namespace Pinetime::Components;
+
+void Statistics::Start() {
+  // The queue is small: we only keep a few reports pending.
+  queue = xQueueCreate(4, sizeof(Report));
+  if (queue == nullptr) {
+    return;                  // no memory even for the queue
+  }
+
+  // A 512-word stack = 2 KB. The task does not use LVGL and has no
+  // large buffers, so this is enough for it. Check with
+  // uxTaskGetStackHighWaterMark before treating this as the final value.
+  if (xTaskCreate(Task, "statistics", 512, this,
+                  tskIDLE_PRIORITY + 1, &handle) != pdPASS) {
+    handle = nullptr;        // no memory for the stack
+  }
+}
+
+void Statistics::Task(void* param) {
+  static_cast<Statistics*>(param)->Loop();
+}
+
+void Statistics::Loop() {
+  Report r;
+  for (;;) {
+    // The task SLEEPS here until a report arrives. While waiting it costs
+    // nothing, and the idle task can stop the processor's clock.
+    if (xQueueReceive(queue, &r, portMAX_DELAY) != pdTRUE) {
+      continue;
+    }
+
+    // The first report after boot only gives the starting point: we do not
+    // know in which hour the steps taken before the reboot happened.
+    if (!haveBaseline) {
+      lastReceived = r.steps;
+      haveBaseline = true;
+      continue;
+    }
+
+    // The sensor gives the day's total; the difference from the previous
+    // total is the number of steps taken in between.
+    uint32_t added;
+    if (r.steps >= lastReceived) {
+      added = r.steps - lastReceived;
+    } else {
+      // The total went down: midnight has passed and the counter restarted
+      // from zero. We begin a new day of statistics.
+      added = r.steps;
+      taskENTER_CRITICAL();
+      per_hour.fill(0);
+      taskEXIT_CRITICAL();
+    }
+    lastReceived = r.steps;
+
+    if (r.hour < per_hour.size()) {
+      taskENTER_CRITICAL();              // the display may read the table at any time
+      per_hour[r.hour] += added;
+      taskEXIT_CRITICAL();
+    }
+  }
+}
+
+void Statistics::ReportSteps(uint32_t steps) {
+  // Only send when something changed - otherwise we fill the queue for nothing.
+  if (queue == nullptr || steps == lastSent) {
+    return;
+  }
+  Report r {dateTime.Hours(), steps};
+  // No waiting: if the queue is full, the report is lost. No steps are lost,
+  // though - the next report again carries the day's total.
+  if (xQueueSend(queue, &r, 0) == pdTRUE) {
+    lastSent = steps;
+  }
+}
+
+uint32_t Statistics::StepsInHour(uint8_t hour) {
+  if (hour >= per_hour.size()) {
+    return 0;
+  }
+  taskENTER_CRITICAL();
+  uint32_t value = per_hour[hour];
+  taskEXIT_CRITICAL();
+  return value;
+}
diff --git a/src/components/statistics/Statistics.h b/src/components/statistics/Statistics.h
new file mode 100644
index 0000000..238daf0
--- /dev/null
+++ b/src/components/statistics/Statistics.h
@@ -0,0 +1,39 @@
+#pragma once
+#include <FreeRTOS.h>
+#include <task.h>
+#include <queue.h>
+#include <array>
+#include <cstdint>
+#include "components/datetime/DateTimeController.h"
+
+namespace Pinetime {
+  namespace Components {
+
+    class Statistics {
+    public:
+      struct Report {
+        uint8_t  hour;     // 0...23
+        uint32_t steps;    // the day's total, as the sensor gives it
+      };
+
+      explicit Statistics(Controllers::DateTime& dateTime) : dateTime {dateTime} {}
+
+      void Start();                          // creates the queue and the task
+      void ReportSteps(uint32_t steps);      // called from the system task
+      uint32_t StepsInHour(uint8_t hour);    // called from the display task
+
+    private:
+      static void Task(void* param);         // the task function
+      void Loop();
+
+      Controllers::DateTime& dateTime;       // where we read the current hour from
+      TaskHandle_t  handle = nullptr;
+      QueueHandle_t queue = nullptr;
+
+      uint32_t lastSent = 0;                 // used ONLY by the system task
+      uint32_t lastReceived = 0;             // used ONLY by the statistics task
+      bool     haveBaseline = false;         // used ONLY by the statistics task
+      std::array<uint32_t, 24> per_hour {};  // written by statistics, read by display
+    };
+  }
+}
diff --git a/src/displayapp/screens/Steps.cpp b/src/displayapp/screens/Steps.cpp
index 2e73dab..64aeaba 100644
--- a/src/displayapp/screens/Steps.cpp
+++ b/src/displayapp/screens/Steps.cpp
@@ -16,8 +16,11 @@ static void lap_event_handler(lv_obj_t* obj, lv_event_t event) {
   steps->lapBtnEventHandler(event);
 }
 
-Steps::Steps(Controllers::MotionController& motionController, Controllers::Settings& settingsController)
-  : motionController {motionController}, settingsController {settingsController} {
+Steps::Steps(Controllers::MotionController& motionController,
+             Controllers::Settings& settingsController,
+             Components::Statistics& statistics,
+             Controllers::DateTime& dateTime)
+  : motionController {motionController}, settingsController {settingsController}, statistics {statistics}, dateTime {dateTime} {
 
   stepsArc = lv_arc_create(lv_scr_act(), nullptr);
 
@@ -76,6 +79,11 @@ Steps::Steps(Controllers::MotionController& motionController, Controllers::Setti
   lv_label_set_text_fmt(tripLabel, "Trip: %5li", currentTripSteps);
   lv_obj_align(tripLabel, lstepsGoal, LV_ALIGN_IN_LEFT_MID, 0, 20);
 
+  lHour = lv_label_create(lv_scr_act(), nullptr);
+  lv_obj_set_style_local_text_color(lHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange);
+  lv_label_set_text_static(lHour, "");
+  lv_obj_align(lHour, nullptr, LV_ALIGN_IN_TOP_MID, 0, 25);
+
   taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this);
 }
 
@@ -100,6 +108,10 @@ void Steps::Refresh() {
     lv_label_set_text_fmt(tripLabel, "Trip: 99999+");
   }
   lv_arc_set_value(stepsArc, int16_t(500 * stepsCount / settingsController.GetStepsGoal()));
+
+  uint8_t hour = dateTime.Hours();
+  lv_label_set_text_fmt(lHour, "Hour %02d: %lu", int(hour), statistics.StepsInHour(hour));
+  lv_obj_align(lHour, nullptr, LV_ALIGN_IN_TOP_MID, 0, 25);
 }
 
 void Steps::lapBtnEventHandler(lv_event_t event) {
diff --git a/src/displayapp/screens/Steps.h b/src/displayapp/screens/Steps.h
index 4824be6..e8acdd2 100644
--- a/src/displayapp/screens/Steps.h
+++ b/src/displayapp/screens/Steps.h
@@ -7,6 +7,7 @@
 #include "displayapp/apps/Apps.h"
 #include "displayapp/Controllers.h"
 #include "Symbols.h"
+#include "systemtask/SystemTask.h"
 
 namespace Pinetime {
 
@@ -19,7 +20,10 @@ namespace Pinetime {
 
       class Steps : public Screen {
       public:
-        Steps(Controllers::MotionController& motionController, Controllers::Settings& settingsController);
+        Steps(Controllers::MotionController& motionController,
+              Controllers::Settings& settingsController,
+              Components::Statistics& statistics,
+              Controllers::DateTime& dateTime);
         ~Steps() override;
 
         void Refresh() override;
@@ -28,6 +32,9 @@ namespace Pinetime {
       private:
         Controllers::MotionController& motionController;
         Controllers::Settings& settingsController;
+        Components::Statistics& statistics;
+        Controllers::DateTime& dateTime;
+        lv_obj_t* lHour;
 
         uint32_t currentTripSteps = 0;
 
@@ -50,7 +57,10 @@ namespace Pinetime {
       static constexpr const char* icon = Screens::Symbols::shoe;
 
       static Screens::Screen* Create(AppControllers& controllers) {
-        return new Screens::Steps(controllers.motionController, controllers.settingsController);
+        return new Screens::Steps(controllers.motionController,
+                                  controllers.settingsController,
+                                  controllers.systemTask->GetStatistics(),
+                                  controllers.dateTimeController);
       };
 
       static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp
index 56bf927..87729f4 100644
--- a/src/systemtask/SystemTask.cpp
+++ b/src/systemtask/SystemTask.cpp
@@ -138,6 +138,7 @@ void SystemTask::Work() {
   motionSensor.Init();
   motionController.Init(motionSensor.DeviceType());
   settingsController.Init();
+  statistics.Start();
 
   displayApp.Register(this);
   displayApp.Register(&nimbleController.weather());
@@ -454,6 +455,7 @@ void SystemTask::UpdateMotion() {
   auto motionValues = motionSensor.Process();
 
   motionController.Update(motionValues.x, motionValues.y, motionValues.z, motionValues.steps);
+  statistics.ReportSteps(motionValues.steps);
 
   if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep) {
     if ((settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) &&
diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h
index 606ddd3..0d1f6b8 100644
--- a/src/systemtask/SystemTask.h
+++ b/src/systemtask/SystemTask.h
@@ -31,6 +31,7 @@
 
 #include "drivers/Watchdog.h"
 #include "systemtask/Messages.h"
+#include "components/statistics/Statistics.h"
 
 extern std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> NoInit_BackUpTime;
 
@@ -78,6 +79,7 @@ namespace Pinetime {
 
       void Start();
       void PushMessage(Messages msg);
+      Pinetime::Components::Statistics& GetStatistics() { return statistics; }
 
       bool IsSleepDisabled() {
         return wakeLocksHeld > 0;
@@ -110,6 +112,7 @@ namespace Pinetime {
 
       Pinetime::Controllers::Ble& bleController;
       Pinetime::Controllers::DateTime& dateTimeController;
+      Pinetime::Components::Statistics statistics {dateTimeController};
       Pinetime::Controllers::StopWatchController& stopWatchController;
       Pinetime::Controllers::AlarmController& alarmController;
       QueueHandle_t systemTasksMsgQueue;
