Skip to content

Commit b0a8e19

Browse files
neuropawn-adminKevin-Xue01aa217
authored
Imu (#5)
Co-authored-by: Kevin <kevinx1698@gmail.com> Co-authored-by: Kevin-Xue01 <59587075+Kevin-Xue01@users.noreply.github.com> Co-authored-by: aa217 <125326241+aa217@users.noreply.github.com> Co-authored-by: abdallah <abdallah.alwan2001@gmail.com>
1 parent c3a28e2 commit b0a8e19

File tree

11 files changed

+602
-3
lines changed

11 files changed

+602
-3
lines changed

README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,107 @@ It provides a uniform SDK to work with biosensors with a primary focus on neuroi
3131
* Powerful CI/CD system which runs integrations tests for each commit automatically using BrainFlow's Emulator
3232
* Simplified process to add new boards and methods
3333

34+
## NeuroPawn Knight Board Docs
35+
36+
### [Python] Brainflow Simple Setup
37+
```
38+
import brainflow as bf
39+
import time
40+
41+
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
42+
43+
class KnightBoard:
44+
def __init__(self, serial_port: str, num_channels: int):
45+
"""Initialize and configure the Knight Board."""
46+
self.params = BrainFlowInputParams()
47+
self.params.serial_port = serial_port
48+
self.num_channels = num_channels
49+
50+
# Initialize board
51+
self.board_shim = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD.value, self.params)
52+
self.board_id = self.board_shim.get_board_id()
53+
self.eeg_channels = self.board_shim.get_exg_channels(self.board_id)
54+
self.sampling_rate = self.board_shim.get_sampling_rate(self.board_id)
55+
56+
def start_stream(self, buffer_size: int = 450000):
57+
"""Start the data stream from the board."""
58+
self.board_shim.prepare_session()
59+
self.board_shim.start_stream(buffer_size)
60+
print("Stream started.")
61+
time.sleep(2)
62+
for x in range(1, self.num_channels + 1):
63+
time.sleep(0.5)
64+
cmd = f"chon_{x}_12"
65+
self.board_shim.config_board(cmd)
66+
print(f"sending {cmd}")
67+
time.sleep(1)
68+
rld = f"rldadd_{x}"
69+
self.board_shim.config_board(rld)
70+
print(f"sending {rld}")
71+
time.sleep(0.5)
72+
73+
def stop_stream(self):
74+
"""Stop the data stream and release resources."""
75+
self.board_shim.stop_stream()
76+
self.board_shim.release_session()
77+
print("Stream stopped and session released.")
78+
79+
Knight_board = KnightBoard("COM3", 8)
80+
Knight_board.start_stream()
81+
82+
while True:
83+
data = Knight_board.board_shim.get_board_data()
84+
# do stuff with data
85+
86+
if keyboard.is_pressed('q'):
87+
Knight_board.stop_stream()
88+
break
89+
```
90+
### BrainFlow Configuration Commands
91+
92+
To configure the NeuroPawn Knight board with BrainFlow, pass the commands as strings into:
93+
94+
board_shim.config_board(<command>)
95+
96+
#### Command 1: Enable EEG Channel / Set Gain
97+
**Purpose**: Enables a specified channel with a specified gain, starting data acquisition on that channel. If the channel is already enabled, it will remain enabled, but will still update its gain.
98+
99+
f"chon_{channel}_{gain_value}"
100+
101+
##### Parameters:
102+
103+
- channel: The channel number to start the data acquisition. Replace this with the actual number of the channel you want to configure. One-indexed.
104+
105+
- gain: Specifies the gain value for the channel to be enabled. Allowable gain values are: [1, 2, 3, 4, 6, 8, 12 (recommended)]. The gain value controls the amplification level of the EEG signal on the specified channel.
106+
107+
#### Command 2: Disable EEG Channel
108+
**Purpose**: Disables a specified channel, stopping data acquisition on that channel.
109+
110+
f"choff_{channel_number}"
111+
112+
##### Parameters:
113+
114+
- channel_number: The channel number to **stop** the data acquisition. This is appended to *'choff'* to construct the configuration command. One-indexed.
115+
116+
#### Command 3: Toggle on RLD
117+
**Purpose**: Toogle **on** right leg drive for the specified channel.
118+
119+
f"rldadd_{channel_number}"
120+
121+
##### Parameters:
122+
123+
- channel_number: The channel number to toggle **on** the right leg drive. This number is converted to a string and appended to *'rldadd'* to create the configuration command. One-indexed.
124+
125+
126+
### Command 4: Toggle off RLD
127+
**Purpose**: Toogle **off** right leg drive for the specified channel.
128+
129+
f"rldremove_{channel}"
130+
131+
#### Parameters:
132+
133+
- channel_number: The channel number to toggle **off** the right leg drive. This number is converted to a string and appended to *'rldremove'* to create the configuration command. One-indexed.
134+
34135
## Resources
35136

36137
* [***BrainFlow Docs, Dev and User guides and other information***](https://brainflow.readthedocs.io)

docs/SupportedBoards.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,7 +1425,7 @@ Knight Board
14251425
14261426
To create such board you need to specify the following board ID and fields of BrainFlowInputParams object:
14271427
1428-
- :code:`BoardIds.NEUROPAWN_KNIGHT_BOARD`
1428+
- :code:`BoardIds.NEUROPAWN_KNIGHT_BOARD` or :code:`BoardIds.NEUROPAWN_KNIGHT_BOARD_IMU`
14291429
- :code:`serial_port`, e.g. COM3, /dev/tty.*
14301430

14311431
Initialization Example:
@@ -1434,7 +1434,10 @@ Initialization Example:
14341434
14351435
params = BrainFlowInputParams()
14361436
params.serial_port = "COM3"
1437-
board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD, params)
1437+
params.other_info = '{"gain": 6}' # optional: set gain to allowed values: 1, 2, 3, 4, 6, 8, 12 (default)
1438+
1439+
board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD, params) # standard Knight Board
1440+
board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD_IMU, params) # Knight Board IMU
14381441
14391442
**On Unix-like systems you may need to configure permissions for serial port or run with sudo.**
14401443

python_package/brainflow/board_shim.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class BoardIds(enum.IntEnum):
8080
OB3000_24_CHANNELS_BOARD = 63 #:
8181
BIOLISTENER_BOARD = 64 #:
8282
IRONBCI_32_BOARD = 65 #:
83+
NEUROPAWN_KNIGHT_BOARD_IMU = 66 #:
8384

8485

8586
class IpProtocolTypes(enum.IntEnum):

src/board_controller/board_controller.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "gforce_pro.h"
4646
#include "json.hpp"
4747
#include "knight.h"
48+
#include "knightimu.h"
4849
#include "muse.h"
4950
#include "muse_bled.h"
5051
#include "notion_osc.h"
@@ -299,6 +300,10 @@ int prepare_session (int board_id, const char *json_brainflow_input_params)
299300
case BoardIds::BIOLISTENER_BOARD:
300301
board = std::shared_ptr<Board> (new BioListener<8> (board_id, params));
301302
break;
303+
case BoardIds::NEUROPAWN_KNIGHT_BOARD_IMU:
304+
board = std::shared_ptr<Board> (
305+
new KnightIMU ((int)BoardIds::NEUROPAWN_KNIGHT_BOARD_IMU, params));
306+
break;
302307
default:
303308
return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR;
304309
}

src/board_controller/brainflow_boards.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ BrainFlowBoards::BrainFlowBoards()
8484
{"63", json::object()},
8585
{"64", json::object()},
8686
{"65", json::object()},
87+
{"66", json::object()}
8788
}
8889
}};
8990

@@ -1147,6 +1148,17 @@ BrainFlowBoards::BrainFlowBoards()
11471148
{"emg_channels", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}},
11481149
{"ecg_channels", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}}
11491150
};
1151+
brainflow_boards_json["boards"]["66"]["default"] =
1152+
{
1153+
{"name", "KnightIMU"},
1154+
{"sampling_rate", 125},
1155+
{"timestamp_channel", 20},
1156+
{"marker_channel", 21},
1157+
{"package_num_channel", 0},
1158+
{"num_rows", 22},
1159+
{"eeg_channels", {1, 2, 3, 4, 5, 6, 7, 8}},
1160+
{"other_channels", {9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}}
1161+
};
11501162
}
11511163

11521164
BrainFlowBoards boards_struct;

src/board_controller/build.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ SET (BOARD_CONTROLLER_SRC
8484
${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/pieeg/pieeg_board.cpp
8585
${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/synchroni/synchroni_board.cpp
8686
${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/neuropawn/knight.cpp
87+
${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/neuropawn/knightimu.cpp
8788
${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/biolistener/biolistener.cpp
8889
)
8990

src/board_controller/neuropawn/inc/knight.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <set>
34
#include <thread>
45

56
#include "board.h"
@@ -17,6 +18,7 @@ class Knight : public Board
1718
Serial *serial;
1819

1920
int min_package_size;
21+
int gain;
2022

2123
virtual int send_to_board (const char *msg);
2224
virtual int send_to_board (const char *msg, std::string &response);
@@ -25,6 +27,9 @@ class Knight : public Board
2527
int set_port_settings ();
2628
void read_thread ();
2729

30+
private:
31+
static const std::set<int> allowed_gains;
32+
2833
public:
2934
Knight (int board_id, struct BrainFlowInputParams params);
3035
~Knight ();
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#include <set>
4+
#include <thread>
5+
6+
#include "board.h"
7+
#include "board_controller.h"
8+
#include "serial.h"
9+
10+
class KnightIMU : public Board
11+
{
12+
13+
protected:
14+
volatile bool keep_alive;
15+
bool initialized;
16+
bool is_streaming;
17+
std::thread streaming_thread;
18+
Serial *serial;
19+
20+
int min_package_size;
21+
int gain;
22+
23+
virtual int send_to_board (const char *msg);
24+
virtual int send_to_board (const char *msg, std::string &response);
25+
virtual std::string read_serial_response ();
26+
int open_port ();
27+
int set_port_settings ();
28+
void read_thread ();
29+
30+
private:
31+
static const std::set<int> allowed_gains;
32+
33+
public:
34+
KnightIMU (int board_id, struct BrainFlowInputParams params);
35+
~KnightIMU ();
36+
37+
int prepare_session ();
38+
int start_stream (int buffer_size, const char *streamer_params);
39+
int stop_stream ();
40+
int release_session ();
41+
int config_board (std::string config, std::string &response);
42+
43+
static constexpr int start_byte = 0xA0;
44+
static constexpr int end_byte = 0xC0;
45+
};

src/board_controller/neuropawn/knight.cpp

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,62 @@
33
#include <vector>
44

55
#include "custom_cast.h"
6+
#include "json.hpp"
67
#include "knight.h"
78
#include "serial.h"
89
#include "timestamp.h"
910

11+
using json = nlohmann::json;
12+
1013
constexpr int Knight::start_byte;
1114
constexpr int Knight::end_byte;
15+
const std::set<int> Knight::allowed_gains = {1, 2, 3, 4, 6, 8, 12};
1216

1317
Knight::Knight (int board_id, struct BrainFlowInputParams params) : Board (board_id, params)
1418
{
1519
serial = NULL;
1620
is_streaming = false;
1721
keep_alive = false;
1822
initialized = false;
23+
gain = 12; // default gain value
24+
25+
// Parse gain from other_info if provided
26+
if (!params.other_info.empty ())
27+
{
28+
try
29+
{
30+
json j = json::parse (params.other_info);
31+
if (j.contains ("gain"))
32+
{
33+
int parsed_gain = j["gain"];
34+
// Validate gain is one of allowed values
35+
if (allowed_gains.count (parsed_gain))
36+
{
37+
gain = parsed_gain;
38+
safe_logger (spdlog::level::info, "Knight board gain set to {}", gain);
39+
}
40+
else
41+
{
42+
safe_logger (spdlog::level::warn,
43+
"Invalid gain value {} in other_info, using default 12", parsed_gain);
44+
}
45+
}
46+
else
47+
{
48+
safe_logger (spdlog::level::info, "No gain field in other_info, using default 12");
49+
}
50+
}
51+
catch (json::parse_error &e)
52+
{
53+
safe_logger (spdlog::level::warn,
54+
"Failed to parse JSON from other_info: {}, using default gain 12", e.what ());
55+
}
56+
catch (json::exception &e)
57+
{
58+
safe_logger (spdlog::level::warn,
59+
"JSON exception while parsing other_info: {}, using default gain 12", e.what ());
60+
}
61+
}
1962
}
2063

2164
Knight::~Knight ()
@@ -136,7 +179,7 @@ void Knight::read_thread ()
136179

137180
int res;
138181
unsigned char b[20] = {0};
139-
float eeg_scale = 4 / float ((pow (2, 23) - 1)) / 12 * 1000000.;
182+
float eeg_scale = 4 / float ((pow (2, 15) - 1)) / gain * 1000000.;
140183
int num_rows = board_descr["default"]["num_rows"];
141184
double *package = new double[num_rows];
142185
for (int i = 0; i < num_rows; i++)

0 commit comments

Comments
 (0)