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.
Serial Setup
Section titled “Serial Setup”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.
Initiating a Data Stream
Section titled “Initiating a Data Stream”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.
Common EEG Block
Section titled “Common EEG Block”Every frame contains this 20-byte block:
| Byte(s) | Field | Description |
|---|---|---|
[0] | Start byte | Packet delimiter (0xA0) |
[1] | Sample number | Rolls over 0 → 255 → 0 |
[2–3] | EXG channel 1 | 16-bit signed, MSB first |
[4–5] | EXG channel 2 | 16-bit signed, MSB first |
[6–7] | EXG channel 3 | 16-bit signed, MSB first |
[8–9] | EXG channel 4 | 16-bit signed, MSB first |
[10–11] | EXG channel 5 | 16-bit signed, MSB first |
[12–13] | EXG channel 6 | 16-bit signed, MSB first |
[14–15] | EXG channel 7 | 16-bit signed, MSB first |
[16–17] | EXG channel 8 | 16-bit signed, MSB first |
[18] | P contact | Positive-input status; bit 0 = channel 1 |
[19] | N contact | Negative-input status; bit 0 = channel 1 |
The frame counter and contact bytes are unsigned. EEG values are big-endian signed 16-bit integers.
EEG-Only Frame (NP_DEFAULT)
Section titled “EEG-Only Frame (NP_DEFAULT)”The default frame is 21 bytes:
| Byte(s) | Size | Description |
|---|---|---|
[0-19] | 20 | Common EEG block |
[20] | 1 | End-of-frame marker (0xC0) |
No IMU placeholder is sent in this mode.
EEG + IMU Frame (NP_IMU)
Section titled “EEG + IMU Frame (NP_IMU)”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) | Size | Description |
|---|---|---|
[0-19] | 20 | Common EEG block |
[20-31] | 12 | Accelerometer X, Y, Z in m/s2 |
[32-43] | 12 | Gyroscope X, Y, Z in rad/s |
[44-55] | 12 | Magnetometer X, Y, Z in microtesla |
[56] | 1 | End-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.
Converting Raw Counts to Microvolts
Section titled “Converting Raw Counts to Microvolts”EEG channel values are 16-bit signed integers in two’s-complement format. To convert a channel’s two bytes to a signed integer:
def interpret_16bit(b0, b1): val = (b0 << 8) | b1 if val & 0x8000: val |= ~0xFFFF # sign-extend return valThen multiply by the scale factor, which is derived from the ADC’s full-scale reference, its 16-bit signed resolution, and the selected gain:
GAIN = 12 # default; 1, 2, 3, 4, 6, 8, 12scale = 4 / (2**15 - 1) / GAIN * 1_000_000 # µV per count
microvolts = interpret_16bit(b0, b1) * scaleAt the default gain of 12 this gives approximately 10.2 µV per count.
Auxiliary Channels (IMU Board)
Section titled “Auxiliary Channels (IMU Board)”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 index | Signal |
|---|---|
11 | Accelerometer X |
12 | Accelerometer Y |
13 | Accelerometer Z |
14 | Gyroscope X |
15 | Gyroscope Y |
16 | Gyroscope Z |
17 | Magnetometer X |
18 | Magnetometer Y |
19 | Magnetometer 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.
Using BrainFlow
Section titled “Using BrainFlow”If you are using BrainFlow, packet parsing and unit conversion are handled for you.
get_current_board_data vs get_board_data
Section titled “get_current_board_data vs get_board_data”| Method | Flushes buffer? | Use case |
|---|---|---|
get_current_board_data(N) | No — data stays in the ring buffer | Real-time processing loop |
get_board_data() | Yes — removes data from the ring buffer | One-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.
import timefrom brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
BoardShim.enable_board_logger()
params = BrainFlowInputParams()params.serial_port = 'COM3' # '/dev/ttyUSB0' on Linux, '/dev/cu.*' on macOSparams.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) # 125eeg_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()