Skip to content

Knight Board Data Format

The Knight Board streams data as a continuous sequence of fixed-length binary frames over its USB-C serial connection. This page describes both the 21-byte EEG-only format and the 57-byte EEG plus IMU format, along with raw-count conversion.

The Knight Board enumerates as a USB CDC serial device (virtual COM port). Connect at 115200 baud, 8 data bits, no parity, and 1 stop bit. No external adapter or dongle is required; the USB-C cable provides the data link directly. A USB-connected host such as a Raspberry Pi can forward the data over a network.

Each frame begins with 0xA0 and ends with 0xC0. Firmware may emit ASCII status lines before the first binary frame. Host parsers should search for a start marker and accept it only when the expected end marker appears at the mode-specific frame length.

The Knight Board begins streaming data automatically once it powers up — you do not need to send any command to start the stream. However, the channels themselves are off by default, so you will not see signal data until you enable each channel you want. Send channel-on commands after prepare_session() / start_stream() to begin receiving EEG data.

See the Command Set page for the full list of commands used to turn channels on and off, set gain, and configure RLD.

Every frame contains this 20-byte block:

Byte(s)FieldDescription
[0]Start bytePacket delimiter (0xA0)
[1]Sample numberRolls over 0 → 255 → 0
[2–3]EXG channel 116-bit signed, MSB first
[4–5]EXG channel 216-bit signed, MSB first
[6–7]EXG channel 316-bit signed, MSB first
[8–9]EXG channel 416-bit signed, MSB first
[10–11]EXG channel 516-bit signed, MSB first
[12–13]EXG channel 616-bit signed, MSB first
[14–15]EXG channel 716-bit signed, MSB first
[16–17]EXG channel 816-bit signed, MSB first
[18]P contactPositive-input status; bit 0 = channel 1
[19]N contactNegative-input status; bit 0 = channel 1

The frame counter and contact bytes are unsigned. EEG values are big-endian signed 16-bit integers.

The default frame is 21 bytes:

Byte(s)SizeDescription
[0-19]20Common EEG block
[20]1End-of-frame marker (0xC0)

No IMU placeholder is sent in this mode.

The IMU frame is 57 bytes. A 36-byte payload of nine little-endian IEEE-754 float32 values is inserted before the end marker:

Byte(s)SizeDescription
[0-19]20Common EEG block
[20-31]12Accelerometer X, Y, Z in m/s2
[32-43]12Gyroscope X, Y, Z in rad/s
[44-55]12Magnetometer X, Y, Z in microtesla
[56]1End-of-frame marker (0xC0)

If no IMU responds, an NP_IMU firmware build sends nine zero values so that the frame length does not change while streaming. IMU support must be enabled at build time; otherwise requesting NP_IMU falls back to the 21-byte stream.

EEG channel values are 16-bit signed integers in two’s-complement format. To convert a channel’s two bytes to a signed integer:

Decode a 16-bit signed sample
def interpret_16bit(b0, b1):
val = (b0 << 8) | b1
if val & 0x8000:
val |= ~0xFFFF # sign-extend
return val

Then multiply by the scale factor, which is derived from the ADC’s full-scale reference, its 16-bit signed resolution, and the selected gain:

Scale Factor  (μV/count)=42151×1gain×106\text{Scale Factor}\;(\mu V/\text{count}) = \frac{4}{2^{15}-1} \times \frac{1}{\text{gain}} \times 10^6
Counts to microvolts
GAIN = 12 # default; 1, 2, 3, 4, 6, 8, 12
scale = 4 / (2**15 - 1) / GAIN * 1_000_000 # µV per count
microvolts = interpret_16bit(b0, b1) * scale

At the default gain of 12 this gives approximately 10.2 µV per count.

The standard Knight Board streams the 8 EXG channels shown above. The Knight IMU Board variant adds onboard 9-DOF motion data — use the NEUROPAWN_KNIGHT_BOARD_IMU board ID in BrainFlow to access it.

The IMU data occupies the following BrainFlow channel indices:

BrainFlow Channel indexSignal
11Accelerometer X
12Accelerometer Y
13Accelerometer Z
14Gyroscope X
15Gyroscope Y
16Gyroscope Z
17Magnetometer X
18Magnetometer Y
19Magnetometer Z

On the serial wire these channels are the nine float32 values at bytes 20 through 55 of an NP_IMU frame. BrainFlow maps them into the rows above and handles packet parsing automatically.

For a standalone serial implementation, see Creating a Custom Data Parser.

If you are using BrainFlow, packet parsing and unit conversion are handled for you.

MethodFlushes buffer?Use case
get_current_board_data(N)No — data stays in the ring bufferReal-time processing loop
get_board_data()Yes — removes data from the ring bufferOne-shot data capture

For live applications, call get_current_board_data(sampling_rate * seconds) in a loop. Pass the number of samples you want — typically the sampling rate multiplied by a time window in seconds. BrainFlow returns up to that many of the most recent samples without removing them from its internal ring buffer.

Streaming with get_current_board_data
import time
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
BoardShim.enable_board_logger()
params = BrainFlowInputParams()
params.serial_port = 'COM3' # '/dev/ttyUSB0' on Linux, '/dev/cu.*' on macOS
params.other_info = '{"gain": 12}' # optional; values: 1, 2, 3, 4, 6, 8, 12 (default)
board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD, params)
board.prepare_session()
board.start_stream()
sampling_rate = BoardShim.get_sampling_rate(BoardIds.NEUROPAWN_KNIGHT_BOARD) # 125
eeg_channels = BoardShim.get_eeg_channels(BoardIds.NEUROPAWN_KNIGHT_BOARD)
try:
while True:
time.sleep(1) # wait 1 second before each read
# Get the latest 1 second of data without flushing the buffer
data = board.get_current_board_data(sampling_rate * 1)
# data shape: (num_rows, num_samples)
# rows = channels (EEG, timestamp, package_num, …)
# columns = samples ordered oldest → newest
eeg_data = data[eeg_channels, :] # shape: (8, num_samples), values in µV
print(f"Received {eeg_data.shape[1]} samples")
finally:
board.stop_stream()
board.release_session()