diff --git a/src/framedreader.cpp b/src/framedreader.cpp
new file mode 100644
--- /dev/null
+++ b/src/framedreader.cpp
@@ -0,0 +1,208 @@
+/*
+  Copyright © 2016 Hasan Yavuz Özderya
+
+  This file is part of serialplot.
+
+  serialplot is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  serialplot is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with serialplot.  If not, see .
+*/
+
+#include 
+#include 
+#include "floatswap.h"
+
+#include "framedreader.h"
+
+FramedReader::FramedReader(QIODevice* device, ChannelManager* channelMan, QObject *parent) :
+    AbstractReader(device, channelMan, parent)
+{
+    paused = false;
+
+    // initial settings
+    settingsInvalid = 0;
+    _numOfChannels = _settingsWidget.numOfChannels();
+    hasFrameByte = _settingsWidget.frameSize() == 0;
+    frameSize = _settingsWidget.frameSize();
+    syncWord = _settingsWidget.syncWord();
+    checksumEnabled = _settingsWidget.checksumEnabled();
+    onNumberFormatChanged(_settingsWidget.numberFormat());
+    checkSettings();
+
+    connect(&_settingsWidget, &FramedReaderSettings::numberFormatChanged,
+            this, &FramedReader::onNumberFormatChanged);
+
+    connect(&_settingsWidget, &FramedReaderSettings::numOfChannelsChanged,
+            this, &FramedReader::onNumOfChannelsChanged);
+
+    connect(&_settingsWidget, &FramedReaderSettings::syncWordChanged,
+            this, &FramedReader::onSyncWordChanged);
+
+    connect(&_settingsWidget, &FramedReaderSettings::frameSizeChanged,
+            this, &FramedReader::onFrameSizeChanged);
+
+    connect(&_settingsWidget, &FramedReaderSettings::checksumChanged,
+            [this](bool enabled){checksumEnabled = enabled;});
+}
+
+void FramedReader::enable(bool enabled)
+{
+    if (enabled)
+    {
+        // TODO
+        // QObject::connect(_device, &QIODevice::readyRead,
+                         // this, &BinaryStreamReader::onDataReady);
+    }
+    else
+    {
+        QObject::disconnect(_device, 0, this, 0);
+    }
+}
+
+QWidget* FramedReader::settingsWidget()
+{
+    return &_settingsWidget;
+}
+
+unsigned FramedReader::numOfChannels()
+{
+    return _numOfChannels;
+}
+
+void FramedReader::pause(bool enabled)
+{
+    paused = enabled;
+}
+
+void FramedReader::onNumberFormatChanged(NumberFormat numberFormat)
+{
+    switch(numberFormat)
+    {
+        case NumberFormat_uint8:
+            sampleSize = 1;
+            readSample = &FramedReader::readSampleAs;
+            break;
+        case NumberFormat_int8:
+            sampleSize = 1;
+            readSample = &FramedReader::readSampleAs;
+            break;
+        case NumberFormat_uint16:
+            sampleSize = 2;
+            readSample = &FramedReader::readSampleAs;
+            break;
+        case NumberFormat_int16:
+            sampleSize = 2;
+            readSample = &FramedReader::readSampleAs;
+            break;
+        case NumberFormat_uint32:
+            sampleSize = 4;
+            readSample = &FramedReader::readSampleAs;
+            break;
+        case NumberFormat_int32:
+            sampleSize = 4;
+            readSample = &FramedReader::readSampleAs;
+            break;
+        case NumberFormat_float:
+            sampleSize = 4;
+            readSample = &FramedReader::readSampleAs;
+            break;
+    }
+
+    checkSettings();
+}
+
+template double FramedReader::readSampleAs()
+{
+    T data;
+
+    _device->read((char*) &data, sizeof(data));
+
+    // TODO: checksum
+
+    if (_settingsWidget.endianness() == LittleEndian)
+    {
+        data = qFromLittleEndian(data);
+    }
+    else
+    {
+        data = qFromBigEndian(data);
+    }
+
+    return double(data);
+}
+
+void FramedReader::checkSettings()
+{
+    // sync word is invalid (empty or missing a nibble at the end)
+    if (!syncWord.size())
+    {
+        settingsInvalid |= SYNCWORD_INVALID;
+    }
+    else // sync word is valid
+    {
+        settingsInvalid &= ~SYNCWORD_INVALID;
+    }
+
+    // check if fixed frame size is multiple of a sample set size
+    if (!hasFrameByte && frameSize % (_numOfChannels * sampleSize) != 0)
+    {
+        settingsInvalid |= FRAMESIZE_INVALID;
+    }
+    else
+    {
+        settingsInvalid &= ~FRAMESIZE_INVALID;
+    }
+
+    // show an error message
+    if (settingsInvalid & SYNCWORD_INVALID)
+    {
+        _settingsWidget.showMessage("Sync word is invalid!", true);
+    }
+    else if (settingsInvalid & FRAMESIZE_INVALID)
+    {
+        QString errorMessage =
+            QString("Frame size must be multiple of %1 (#channels * sample size)!")\
+            .arg(_numOfChannels * sampleSize);
+
+        _settingsWidget.showMessage(errorMessage, true);
+    }
+    else
+    {
+        _settingsWidget.showMessage("All is well!");
+    }
+}
+
+void FramedReader::onNumOfChannelsChanged(unsigned value)
+{
+    _numOfChannels = value;
+    checkSettings();
+}
+
+void FramedReader::onSyncWordChanged(QByteArray word)
+{
+    syncWord = word;
+    checkSettings();
+}
+
+void FramedReader::onFrameSizeChanged(unsigned value)
+{
+    if (value == 0)
+    {
+        hasFrameByte = true;
+    }
+    else
+    {
+        hasFrameByte = false;
+        frameSize = value;
+    }
+    checkSettings();
+}