OSCR

IoT-enabled wireless neural implant for chronic, programmable neuropharmacology and optogenetics.

Code ↔ Paper

The paper beside its authors' code: matches between them have not been computed for this paper yet.

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

C · 1,408 lines · 47 KB · CC-BY-4.0

  1. /**
  2. * Copyright (c) 2016 - 2019, Nordic Semiconductor ASA
  3. *
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without modification,
  7. * are permitted provided that the following conditions are met:
  8. *
  9. * 1. Redistributions of source code must retain the above copyright notice, this
  10. * list of conditions and the following disclaimer.
  11. *
  12. * 2. Redistributions in binary form, except as embedded into a Nordic
  13. * Semiconductor ASA integrated circuit in a product or a software update for
  14. * such product, must reproduce the above copyright notice, this list of
  15. * conditions and the following disclaimer in the documentation and/or other
  16. * materials provided with the distribution.
  17. *
  18. * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
  19. * contributors may be used to endorse or promote products derived from this
  20. * software without specific prior written permission.
  21. *
  22. * 4. This software, with or without modification, must only be used with a
  23. * Nordic Semiconductor ASA integrated circuit.
  24. *
  25. * 5. Any software provided in binary form under this license must not be reverse
  26. * engineered, decompiled, modified and/or disassembled.
  27. *
  28. * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
  29. * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  30. * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
  31. * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
  32. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  33. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  34. * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  35. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  36. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  37. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  38. *
  39. */
  40. #include "nordic_common.h"
  41. #include "sdk_config.h"
  42. #include <stdint.h>
  43. #include <stdio.h>
  44. #include <string.h>
  45. #include "app_button.h"
  46. #include "app_error.h"
  47. #include "app_pwm.h"
  48. #include "app_timer.h"
  49. #include "app_uart.h"
  50. #include "app_util_platform.h"
  51. #include "ble.h"
  52. #include "ble_advdata.h"
  53. #include "ble_advertising.h"
  54. #include "ble_conn_params.h"
  55. #include "ble_cus.h"
  56. #include "ble_cus_c.h"
  57. #include "ble_err.h"
  58. #include "ble_fs.h"
  59. #include "ble_hci.h"
  60. #include "ble_srv_common.h"
  61. #include "boards.h"
  62. #include "bsp_btn_ble.h"
  63. #include "dht.h"
  64. #include "nrf.h"
  65. #include "nrf_ble_gatt.h"
  66. #include "nrf_ble_qwr.h"
  67. #include "nrf_ble_scan.h"
  68. #include "nrf_delay.h"
  69. #include "nrf_drv_gpiote.h"
  70. #include "nrf_drv_ppi.h"
  71. #include "nrf_drv_saadc.h"
  72. #include "nrf_drv_timer.h"
  73. #include "nrf_gpio.h"
  74. #include "nrf_gpiote.h"
  75. #include "nrf_log.h"
  76. #include "nrf_log_ctrl.h"
  77. #include "nrf_log_default_backends.h"
  78. #include "nrf_pwr_mgmt.h"
  79. #include "nrf_sdh.h"
  80. #include "nrf_sdh_ble.h"
  81. #include "nrf_uart.h"
  82. #include "peer_manager_handler.h"
  83. #include "pin_mapping.h"
  84. #define DEVICE_NAME "RAPIDO"
  85. /////////////////// Instances of Other Modules ///////////////////////////////
  86. APP_PWM_INSTANCE(BLE_PWM1, 1); // Create the instance "BLE_PWM" using TIMER1.
  87. APP_TIMER_DEF(m_app_timer);
  88. /////////////// static variables //////////////////////////
  89. static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID; /**< Handle of the current connection. */
  90. static uint8_t m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET; /**< Advertising handle used to identify an advertising set. */
  91. static uint8_t m_enc_advdata[BLE_GAP_ADV_SET_DATA_SIZE_MAX]; /**< Buffer for storing an encoded advertising set. */
  92. static uint8_t m_enc_scan_response_data[BLE_GAP_ADV_SET_DATA_SIZE_MAX]; /**< Buffer for storing an encoded scan data. */
  93. static nrf_ppi_channel_t m_ppi_channel, ppi_channel_0, ppi_channel_1, ppi_channel_2, ppi_channel_3,
  94. ppi_channel_4, ppi_channel_5, ppi_channel_6, ppi_channel_7; // ppi channels
  95. static bool ble_pwm1_enable_flag = false;
  96. uint32_t pwm1_frequency = 0;
  97. uint8_t status_ack[8] = {'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'};
  98. #define SAMPLES_IN_BUFFER 1
  99. /////////////// static functions Decleration ////////////////
  100. static void init_ble_pwm1(uint32_t pin, uint32_t freq, float dutyCycle);
  101. static void deinit_ble_pwm1();
  102. #define CONN_INTERVAL_DEFAULT (uint16_t)(MSEC_TO_UNITS(7.5, UNIT_1_25_MS)) /**< Default connection interval used at connection establishment by central side. */
  103. #define MIN_CONN_INTERVAL MSEC_TO_UNITS(7.5, UNIT_1_25_MS) /**< Minimum acceptable connection interval (7.5 mseconds). */
  104. #define MAX_CONN_INTERVAL MSEC_TO_UNITS(500, UNIT_1_25_MS) /**< Maximum acceptable connection interval (0.5 second). */
  105. #define SLAVE_LATENCY 0 /**< Slave latency. */
  106. #define CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) /**< Connection supervisory time-out (4 seconds). */
  107. #define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(20000) /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (15 seconds). */
  108. #define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000) /**< Time between each call to sd_ble_gap_conn_param_update after the first call (5 seconds). */
  109. #define MAX_CONN_PARAMS_UPDATE_COUNT 3 /**< Number of attempts before giving up the connection parameter negotiation. */
  110. #define TIMER_1_TIMEOUT_US_1 200
  111. #define TIMER_1_TIMEOUT_US_2 400
  112. #define TIMER_1_TIMEOUT_US_3 600
  113. #define TIMER_1_TIMEOUT_US_4 800
  114. #define TIMER_ADC_TIMEOUT_US 200
  115. //================================================================
  116. /** GPIO pin setup **/
  117. // GPIO pins for real mode (EYSHSNZWZ)
  118. int OUTPUT_LED = 4;
  119. int OUTPUT_PUMP_LEFT_LV1 = 6; static bool pump_left_lv1_flag = false;
  120. int OUTPUT_PUMP_LEFT_LV2 = 8; static bool pump_left_lv2_flag = false;
  121. int OUTPUT_PUMP_LEFT_LV3 = 7; static bool pump_left_lv3_flag = false;
  122. int OUTPUT_PUMP_RIGHT_LV1 = 5; static bool pump_right_lv1_flag = false;
  123. int OUTPUT_PUMP_RIGHT_LV2 = 18; static bool pump_right_lv2_flag = false;
  124. int OUTPUT_PUMP_RIGHT_LV3 = 20; static bool pump_right_lv3_flag = false;
  125. /*
  126. // GPIO pins for test mode (nRF52840-DK)
  127. int OUTPUT_LED = 27;
  128. int OUTPUT_PUMP_LEFT_LV1 = 26; static bool pump_left_lv1_flag = false;
  129. int OUTPUT_PUMP_LEFT_LV2 = 4; static bool pump_left_lv2_flag = false;
  130. int OUTPUT_PUMP_LEFT_LV3 = 28; static bool pump_left_lv3_flag = false;
  131. int OUTPUT_PUMP_RIGHT_LV1 = 29; static bool pump_right_lv1_flag = false;
  132. int OUTPUT_PUMP_RIGHT_LV2 = 30; static bool pump_right_lv2_flag = false;
  133. int OUTPUT_PUMP_RIGHT_LV3 = 31; static bool pump_right_lv3_flag = false;
  134. */
  135. //================================================================
  136. //================================================================
  137. /** Frequency & Duty cycle **/
  138. #define FREQ1 5
  139. #define FREQ2 10
  140. #define FREQ3 20
  141. #define FREQ4 40
  142. #define DUTY1 5
  143. #define DUTY2 10
  144. #define DUTY3 20
  145. #define DUTY4 40
  146. //================================================================
  147. /** Variables required for pump actuation **/
  148. static bool app_timer_flag = false;
  149. uint16_t electrolysis_timer_sec = 0;
  150. uint32_t pump_actuation_time = 0;
  151. #define PUMP_INTERVAL APP_TIMER_TICKS(1000) // Tick every seconds
  152. //================================================================
  153. #define LESC_MITM_NC 0
  154. /** @brief The maximum number of peripheral and central links combined. */
  155. #define NRF_BLE_LINK_COUNT (NRF_SDH_BLE_PERIPHERAL_LINK_COUNT + NRF_SDH_BLE_CENTRAL_LINK_COUNT)
  156. #define APP_BLE_CONN_CFG_TAG 1 /**< Tag that identifies the SoftDevice BLE configuration. */
  157. #define CENTRAL_SCANNING_LED BSP_BOARD_LED_0
  158. #define CENTRAL_CONNECTED_LED BSP_BOARD_LED_1
  159. #define PERIPHERAL_ADVERTISING_LED BSP_BOARD_LED_2
  160. #define PERIPHERAL_CONNECTED_LED BSP_BOARD_LED_3
  161. #define SCAN_DURATION 0x0000
  162. #define APP_ADV_INTERVAL 64 /**< Duration of the scanning in units of 10 milliseconds. If set to 0x0000, scanning continues until it is explicitly disabled. */
  163. #define APP_ADV_DURATION 18000 /**< The advertising duration (180 seconds) in units of 10 milliseconds. */
  164. #define SEC_PARAMS_BOND 1 /**< Perform bonding. */
  165. #if LESC_MITM_NC
  166. #define SEC_PARAMS_MITM 1 /**< Man In The Middle protection required. */
  167. #define SEC_PARAMS_IO_CAPABILITIES BLE_GAP_IO_CAPS_DISPLAY_YESNO /**< Display Yes/No to force Numeric Comparison. */
  168. #else
  169. #define SEC_PARAMS_MITM 0 /**< Man In The Middle protection required. */
  170. #define SEC_PARAMS_IO_CAPABILITIES BLE_GAP_IO_CAPS_NONE /**< No I/O caps. */
  171. #endif
  172. #define SEC_PARAMS_LESC 0 /**< LE Secure Connections pairing required. */
  173. #define SEC_PARAMS_KEYPRESS 0 /**< Keypress notifications not required. */
  174. #define SEC_PARAMS_OOB 0 /**< Out Of Band data not available. */
  175. #define SEC_PARAMS_MIN_KEY_SIZE 7 /**< Minimum encryption key size in octets. */
  176. #define SEC_PARAMS_MAX_KEY_SIZE 16 /**< Maximum encryption key size in octets. */
  177. // #define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000) /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
  178. // #define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000) /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
  179. #define MAX_CONN_PARAMS_UPDATE_COUNT 3 /**< Number of attempts before giving up the connection parameter negotiation. */
  180. #define UART_INTERVAL APP_TIMER_TICKS(2000) /**< Uart measurement interval (ticks). */
  181. #define UART_TX_BUF_SIZE 64 /**< UART TX buffer size. */
  182. #define UART_RX_BUF_SIZE 32 /**< UART RX buffer size. */
  183. #define APP_BLE_OBSERVER_PRIO 3
  184. typedef struct {
  185. bool is_connected;
  186. ble_gap_addr_t address;
  187. } conn_peer_t;
  188. NRF_BLE_GQ_DEF(m_ble_gatt_queue, /**< BLE GATT Queue instance. */
  189. NRF_SDH_BLE_CENTRAL_LINK_COUNT,
  190. NRF_BLE_GQ_QUEUE_SIZE);
  191. BLE_CUS_DEF(m_cus); /**< Heart Rate Service instance. */
  192. BLE_CUS_C_DEF(m_cus_c); /**< Structure used to identify the Heart Rate client module. */
  193. NRF_BLE_GATT_DEF(m_gatt); /**< GATT module instance. */
  194. NRF_BLE_QWRS_DEF(m_qwr, NRF_SDH_BLE_TOTAL_LINK_COUNT); /**< Context for the Queued Write module.*/
  195. BLE_ADVERTISING_DEF(m_advertising); /**< Advertising module instance. */
  196. BLE_DB_DISCOVERY_DEF(m_db_disc); /**< Database discovery module instance. */
  197. NRF_BLE_SCAN_DEF(m_scan); /**< Scanning Module instance. */
  198. static uint16_t m_conn_handle_cus_c = BLE_CONN_HANDLE_INVALID; /**< Connection handle for the HRS central application. */
  199. static volatile uint16_t m_conn_handle_num_comp_central = BLE_CONN_HANDLE_INVALID; /**< Connection handle for the central that needs a numeric comparison button press. */
  200. static volatile uint16_t m_conn_handle_num_comp_peripheral = BLE_CONN_HANDLE_INVALID; /**< Connection handle for the peripheral that needs a numeric comparison button press. */
  201. static conn_peer_t m_connected_peers[NRF_BLE_LINK_COUNT]; /**< Array of connected peers. */
  202. uint8_t uart_ble_data[2][6]; // 2 arrays of 3 analog values
  203. uint8_t uart_array_check = 0x00;
  204. static char *roles_str[] = {
  205. "INVALID_ROLE",
  206. "PERIPHERAL",
  207. "CENTRAL",
  208. };
  209. static const char m_target_periph_name[] = "Nordic_Template";
  210. static ble_uuid_t m_adv_uuids[] = {{CUSTOM_SERVICE_UUID, BLE_UUID_TYPE_VENDOR_BEGIN}};
  211. // cus_c_error_handler()
  212. static void cus_c_error_handler(uint32_t nrf_error) {
  213. APP_ERROR_HANDLER(nrf_error);
  214. }
  215. // conn_params_error_handler()
  216. static void conn_params_error_handler(uint32_t nrf_error) {
  217. APP_ERROR_HANDLER(nrf_error);
  218. }
  219. // scan_start()
  220. static void scan_start(void) {
  221. ret_code_t err_code;
  222. err_code = nrf_ble_scan_start(&m_scan);
  223. APP_ERROR_CHECK(err_code);
  224. NRF_LOG_INFO("Scanning Starting\n");
  225. }
  226. // scan_stop()
  227. static void scan_stop(void) {
  228. ret_code_t err_code;
  229. nrf_ble_scan_stop();
  230. NRF_LOG_INFO("Scanning Stopped\n");
  231. }
  232. // adv_scan_start()
  233. static void adv_scan_start(void) {
  234. ret_code_t err_code;
  235. // Start advertising.
  236. err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
  237. APP_ERROR_CHECK(err_code);
  238. NRF_LOG_INFO("Advertising");
  239. }
  240. // pm_evt_handler()
  241. static void pm_evt_handler(pm_evt_t const *p_evt) {
  242. pm_handler_on_pm_evt(p_evt);
  243. pm_handler_flash_clean(p_evt);
  244. switch (p_evt->evt_id) {
  245. case PM_EVT_PEERS_DELETE_SUCCEEDED:
  246. adv_scan_start();
  247. break;
  248. default:
  249. break;
  250. }
  251. }
  252. // filter_settings_change()
  253. static void filter_settings_change(void) {
  254. ret_code_t err_code;
  255. err_code = nrf_ble_scan_all_filter_remove(&m_scan);
  256. APP_ERROR_CHECK(err_code);
  257. if (strlen(m_target_periph_name) != 0) {
  258. err_code = nrf_ble_scan_filter_set(&m_scan,
  259. SCAN_NAME_FILTER,
  260. m_target_periph_name);
  261. APP_ERROR_CHECK(err_code);
  262. }
  263. }
  264. // cus_c_evt_handler()
  265. static void cus_c_evt_handler(ble_cus_c_t *p_cus_c, ble_cus_c_evt_t *p_cus_c_evt) {
  266. ret_code_t err_code;
  267. switch (p_cus_c_evt->evt_type) {
  268. case BLE_CUS_C_EVT_DISCOVERY_COMPLETE: {
  269. if (m_conn_handle_cus_c == BLE_CONN_HANDLE_INVALID) {
  270. ret_code_t err_code;
  271. m_conn_handle_cus_c = p_cus_c_evt->conn_handle;
  272. // We do not want to connect to two peripherals offering the same service, so when
  273. // a UUID is matched, we check whether we are not already connected to a peer which
  274. // offers the same service
  275. filter_settings_change();
  276. err_code = ble_cus_c_handles_assign(p_cus_c,
  277. m_conn_handle_cus_c,
  278. &p_cus_c_evt->params.peer_db);
  279. APP_ERROR_CHECK(err_code);
  280. // Heart rate service discovered. Enable notification of Heart Rate Measurement.
  281. err_code = ble_cus_c_data_char_notif_enable(p_cus_c);
  282. APP_ERROR_CHECK(err_code);
  283. }
  284. } break; // BLE_cus_C_EVT_DISCOVERY_COMPLETE
  285. case BLE_CUS_C_EVT_DATA_NOTIFICATION: {
  286. NRF_LOG_INFO("CENTRAL: Received Data len %d. Data is\n", p_cus_c_evt->params.data_char.length);
  287. NRF_LOG_HEXDUMP_INFO(p_cus_c_evt->params.data_char.p_data, p_cus_c_evt->params.data_char.length);
  288. for (uint8_t i = 0; i < p_cus_c_evt->params.data_char.length; i++) {
  289. uart_ble_data[uart_array_check][i] = p_cus_c_evt->params.data_char.p_data[i];
  290. NRF_LOG_INFO("uart_ble_data[%d][%d] = %d\n", uart_array_check, i, uart_ble_data[uart_array_check][i]);
  291. }
  292. uart_array_check ^= 0x01; // choose the altermative arrays next time
  293. } break;
  294. default:
  295. break;
  296. }
  297. }
  298. // is_already_connected()
  299. static bool is_already_connected(ble_gap_addr_t const *p_connected_adr) {
  300. for (uint32_t i = 0; i < NRF_BLE_LINK_COUNT; i++) {
  301. if (m_connected_peers[i].is_connected) {
  302. if (m_connected_peers[i].address.addr_type == p_connected_adr->addr_type) {
  303. if (memcmp(m_connected_peers[i].address.addr,
  304. p_connected_adr->addr,
  305. sizeof(m_connected_peers[i].address.addr)) == 0) {
  306. return true;
  307. }
  308. }
  309. }
  310. }
  311. return false;
  312. }
  313. // on_match_request()
  314. static void on_match_request(uint16_t conn_handle, uint8_t role) {
  315. // Mark the appropriate conn_handle as pending. The rest is handled on button press.
  316. NRF_LOG_INFO("Press Button 1 to confirm, Button 2 to reject");
  317. if (role == BLE_GAP_ROLE_CENTRAL) {
  318. m_conn_handle_num_comp_central = conn_handle;
  319. } else if (role == BLE_GAP_ROLE_PERIPH) {
  320. m_conn_handle_num_comp_peripheral = conn_handle;
  321. }
  322. }
  323. // multi_qwr_conn_handle_assign()
  324. static void multi_qwr_conn_handle_assign(uint16_t conn_handle) {
  325. for (uint32_t i = 0; i < NRF_BLE_LINK_COUNT; i++) {
  326. if (m_qwr[i].conn_handle == BLE_CONN_HANDLE_INVALID) {
  327. ret_code_t err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr[i], conn_handle);
  328. APP_ERROR_CHECK(err_code);
  329. break;
  330. }
  331. }
  332. }
  333. // on_ble_evt()
  334. static void on_ble_evt(uint16_t conn_handle, ble_evt_t const *p_ble_evt) {
  335. char passkey[BLE_GAP_PASSKEY_LEN + 1];
  336. uint16_t role = ble_conn_state_role(conn_handle);
  337. switch (p_ble_evt->header.evt_id) {
  338. case BLE_GAP_EVT_CONNECTED:
  339. m_connected_peers[conn_handle].is_connected = true;
  340. m_connected_peers[conn_handle].address = p_ble_evt->evt.gap_evt.params.connected.peer_addr;
  341. multi_qwr_conn_handle_assign(conn_handle);
  342. break;
  343. case BLE_GAP_EVT_DISCONNECTED:
  344. memset(&m_connected_peers[conn_handle], 0x00, sizeof(m_connected_peers[0]));
  345. break;
  346. case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
  347. NRF_LOG_INFO("%s: BLE_GAP_EVT_SEC_PARAMS_REQUEST", nrf_log_push(roles_str[role]));
  348. break;
  349. case BLE_GAP_EVT_PASSKEY_DISPLAY:
  350. memcpy(passkey, p_ble_evt->evt.gap_evt.params.passkey_display.passkey, BLE_GAP_PASSKEY_LEN);
  351. passkey[BLE_GAP_PASSKEY_LEN] = 0x00;
  352. NRF_LOG_INFO("%s: BLE_GAP_EVT_PASSKEY_DISPLAY: passkey=%s match_req=%d",
  353. nrf_log_push(roles_str[role]),
  354. nrf_log_push(passkey),
  355. p_ble_evt->evt.gap_evt.params.passkey_display.match_request);
  356. if (p_ble_evt->evt.gap_evt.params.passkey_display.match_request) {
  357. on_match_request(conn_handle, role);
  358. }
  359. break;
  360. case BLE_GAP_EVT_AUTH_KEY_REQUEST:
  361. NRF_LOG_INFO("%s: BLE_GAP_EVT_AUTH_KEY_REQUEST", nrf_log_push(roles_str[role]));
  362. break;
  363. case BLE_GAP_EVT_LESC_DHKEY_REQUEST:
  364. NRF_LOG_INFO("%s: BLE_GAP_EVT_LESC_DHKEY_REQUEST", nrf_log_push(roles_str[role]));
  365. break;
  366. case BLE_GAP_EVT_AUTH_STATUS:
  367. NRF_LOG_INFO("%s: BLE_GAP_EVT_AUTH_STATUS: status=0x%x bond=0x%x lv4: %d kdist_own:0x%x kdist_peer:0x%x",
  368. nrf_log_push(roles_str[role]),
  369. p_ble_evt->evt.gap_evt.params.auth_status.auth_status,
  370. p_ble_evt->evt.gap_evt.params.auth_status.bonded,
  371. p_ble_evt->evt.gap_evt.params.auth_status.sm1_levels.lv4,
  372. *((uint8_t *)&p_ble_evt->evt.gap_evt.params.auth_status.kdist_own),
  373. *((uint8_t *)&p_ble_evt->evt.gap_evt.params.auth_status.kdist_peer));
  374. break;
  375. case BLE_GAP_EVT_PHY_UPDATE_REQUEST: {
  376. NRF_LOG_DEBUG("PHY update request.");
  377. ble_gap_phys_t const phys = {
  378. .rx_phys = BLE_GAP_PHY_AUTO,
  379. .tx_phys = BLE_GAP_PHY_AUTO,
  380. };
  381. ret_code_t err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
  382. APP_ERROR_CHECK(err_code);
  383. } break;
  384. default:
  385. break;
  386. }
  387. }
  388. // on_ble_central_evt()
  389. static void on_ble_central_evt(ble_evt_t const *p_ble_evt) {
  390. ble_gap_evt_t const *p_gap_evt = &p_ble_evt->evt.gap_evt;
  391. ret_code_t err_code;
  392. switch (p_ble_evt->header.evt_id) {
  393. // Upon connection, check which peripheral is connected (HR or RSC), initiate DB
  394. // discovery, update LEDs status, and resume scanning, if necessary.
  395. case BLE_GAP_EVT_CONNECTED: {
  396. NRF_LOG_INFO("CENTRAL: Connected, handle: %d.", p_gap_evt->conn_handle);
  397. // If no Heart Rate Sensor is currently connected, try to find them on this peripheral.
  398. if (m_conn_handle_cus_c == BLE_CONN_HANDLE_INVALID) {
  399. NRF_LOG_INFO("CENTRAL: Searching for cus on conn_handle 0x%x", p_gap_evt->conn_handle);
  400. err_code = ble_db_discovery_start(&m_db_disc, p_gap_evt->conn_handle);
  401. APP_ERROR_CHECK(err_code);
  402. }
  403. } break; // BLE_GAP_EVT_CONNECTED
  404. // Upon disconnection, reset the connection handle of the peer that disconnected, update
  405. // the status of LEDs, and start scanning again.
  406. case BLE_GAP_EVT_DISCONNECTED: {
  407. NRF_LOG_INFO("CENTRAL: Disconnected, handle: %d, reason: 0x%x",
  408. p_gap_evt->conn_handle,
  409. p_gap_evt->params.disconnected.reason);
  410. if (p_gap_evt->conn_handle == m_conn_handle_cus_c) {
  411. ble_uuid_t target_uuid = {.uuid = CUSTOM_SERVICE_UUID, .type = BLE_UUID_TYPE_VENDOR_BEGIN};
  412. m_conn_handle_cus_c = BLE_CONN_HANDLE_INVALID;
  413. err_code = nrf_ble_scan_filter_set(&m_scan,
  414. SCAN_UUID_FILTER,
  415. &target_uuid);
  416. APP_ERROR_CHECK(err_code);
  417. }
  418. } break; // BLE_GAP_EVT_DISCONNECTED
  419. case BLE_GAP_EVT_TIMEOUT: {
  420. // Timeout for scanning is not specified, so only connection attemps can time out.
  421. if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN) {
  422. NRF_LOG_DEBUG("CENTRAL: Connection Request timed out.");
  423. }
  424. } break;
  425. case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: {
  426. // Accept parameters requested by peer.
  427. err_code = sd_ble_gap_conn_param_update(p_gap_evt->conn_handle,
  428. &p_gap_evt->params.conn_param_update_request.conn_params);
  429. APP_ERROR_CHECK(err_code);
  430. } break;
  431. case BLE_GATTC_EVT_TIMEOUT:
  432. // Disconnect on GATT Client timeout event.
  433. NRF_LOG_DEBUG("CENTRAL: GATT Client Timeout.");
  434. err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
  435. BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
  436. APP_ERROR_CHECK(err_code);
  437. break;
  438. case BLE_GATTS_EVT_TIMEOUT:
  439. // Disconnect on GATT Server timeout event.
  440. NRF_LOG_DEBUG("CENTRAL: GATT Server Timeout.");
  441. err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
  442. BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
  443. APP_ERROR_CHECK(err_code);
  444. break;
  445. default:
  446. break;
  447. }
  448. }
  449. // on_ble_peripheral_evt()
  450. static void on_ble_peripheral_evt(ble_evt_t const *p_ble_evt) {
  451. ret_code_t err_code;
  452. switch (p_ble_evt->header.evt_id) {
  453. case BLE_GAP_EVT_CONNECTED:
  454. NRF_LOG_INFO("PERIPHERAL: Connected, handle %d.", p_ble_evt->evt.gap_evt.conn_handle);
  455. break;
  456. case BLE_GAP_EVT_DISCONNECTED:
  457. NRF_LOG_INFO("PERIPHERAL: Disconnected, handle %d, reason 0x%x.",
  458. p_ble_evt->evt.gap_evt.conn_handle,
  459. p_ble_evt->evt.gap_evt.params.disconnected.reason);
  460. // LED indication will be changed when advertising starts.
  461. break;
  462. case BLE_GATTC_EVT_TIMEOUT:
  463. // Disconnect on GATT Client timeout event.
  464. NRF_LOG_DEBUG("PERIPHERAL: GATT Client Timeout.");
  465. err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
  466. BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
  467. APP_ERROR_CHECK(err_code);
  468. break;
  469. case BLE_GATTS_EVT_TIMEOUT:
  470. // Disconnect on GATT Server timeout event.
  471. NRF_LOG_DEBUG("PERIPHERAL: GATT Server Timeout.");
  472. err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
  473. BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
  474. APP_ERROR_CHECK(err_code);
  475. break;
  476. default:
  477. break;
  478. }
  479. }
  480. // on_adv_evt()
  481. static void on_adv_evt(ble_adv_evt_t ble_adv_evt) {
  482. switch (ble_adv_evt) {
  483. case BLE_ADV_EVT_FAST:
  484. break;
  485. case BLE_ADV_EVT_IDLE: {
  486. ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
  487. APP_ERROR_CHECK(err_code);
  488. } break;
  489. default:
  490. break;
  491. }
  492. }
  493. // ble_evt_handler()
  494. static void ble_evt_handler(ble_evt_t const *p_ble_evt, void *p_context) {
  495. uint16_t conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
  496. uint16_t role = ble_conn_state_role(conn_handle);
  497. if ((p_ble_evt->header.evt_id == BLE_GAP_EVT_CONNECTED) && (is_already_connected(&p_ble_evt->evt.gap_evt.params.connected.peer_addr))) {
  498. NRF_LOG_INFO("%s: Already connected to this device as %s (handle: %d), disconnecting.",
  499. (role == BLE_GAP_ROLE_PERIPH) ? "PERIPHERAL" : "CENTRAL",
  500. (role == BLE_GAP_ROLE_PERIPH) ? "CENTRAL" : "PERIPHERAL",
  501. conn_handle);
  502. (void)sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
  503. // Do not process the event further.
  504. return;
  505. }
  506. on_ble_evt(conn_handle, p_ble_evt);
  507. if (role == BLE_GAP_ROLE_PERIPH) {
  508. // Manages peripheral LEDs.
  509. on_ble_peripheral_evt(p_ble_evt);
  510. } else if ((role == BLE_GAP_ROLE_CENTRAL) || (p_ble_evt->header.evt_id == BLE_GAP_EVT_ADV_REPORT)) {
  511. on_ble_central_evt(p_ble_evt);
  512. }
  513. }
  514. // cus_c_init()
  515. static void cus_c_init(void) {
  516. ret_code_t err_code;
  517. ble_cus_c_init_t cus_c_init_obj;
  518. cus_c_init_obj.evt_handler = cus_c_evt_handler;
  519. cus_c_init_obj.error_handler = cus_c_error_handler;
  520. cus_c_init_obj.p_gatt_queue = &m_ble_gatt_queue;
  521. err_code = ble_cus_c_init(&m_cus_c, &cus_c_init_obj);
  522. APP_ERROR_CHECK(err_code);
  523. }
  524. // ble_stack_init()
  525. static void ble_stack_init(void) {
  526. ret_code_t err_code;
  527. err_code = nrf_sdh_enable_request();
  528. APP_ERROR_CHECK(err_code);
  529. // Configure the BLE stack by using the default settings.
  530. // Fetch the start address of the application RAM.
  531. uint32_t ram_start = 0;
  532. err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
  533. APP_ERROR_CHECK(err_code);
  534. // Enable BLE stack.
  535. err_code = nrf_sdh_ble_enable(&ram_start);
  536. APP_ERROR_CHECK(err_code);
  537. // Register a handler for BLE events.
  538. NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
  539. }
  540. // peer_manager_init()
  541. static void peer_manager_init(void) {
  542. ble_gap_sec_params_t sec_params;
  543. ret_code_t err_code;
  544. err_code = pm_init();
  545. APP_ERROR_CHECK(err_code);
  546. memset(&sec_params, 0, sizeof(ble_gap_sec_params_t));
  547. // Security parameters to be used for all security procedures.
  548. sec_params.bond = SEC_PARAMS_BOND;
  549. sec_params.mitm = SEC_PARAMS_MITM;
  550. sec_params.lesc = SEC_PARAMS_LESC;
  551. sec_params.keypress = SEC_PARAMS_KEYPRESS;
  552. sec_params.io_caps = SEC_PARAMS_IO_CAPABILITIES;
  553. sec_params.oob = SEC_PARAMS_OOB;
  554. sec_params.min_key_size = SEC_PARAMS_MIN_KEY_SIZE;
  555. sec_params.max_key_size = SEC_PARAMS_MAX_KEY_SIZE;
  556. sec_params.kdist_own.enc = 1;
  557. sec_params.kdist_own.id = 1;
  558. sec_params.kdist_peer.enc = 1;
  559. sec_params.kdist_peer.id = 1;
  560. err_code = pm_sec_params_set(&sec_params);
  561. APP_ERROR_CHECK(err_code);
  562. err_code = pm_register(pm_evt_handler);
  563. APP_ERROR_CHECK(err_code);
  564. }
  565. // delete_bonds()
  566. static void delete_bonds(void) {
  567. ret_code_t err_code;
  568. NRF_LOG_INFO("Erase bonds!");
  569. err_code = pm_peers_delete();
  570. APP_ERROR_CHECK(err_code);
  571. }
  572. // buttons_leds_init()
  573. static void buttons_leds_init(bool *p_erase_bonds) {
  574. ret_code_t err_code;
  575. bsp_event_t startup_event;
  576. *p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);
  577. }
  578. // gap_params_init()
  579. static void gap_params_init(void) {
  580. ret_code_t err_code;
  581. ble_gap_conn_params_t gap_conn_params;
  582. ble_gap_conn_sec_mode_t sec_mode;
  583. BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
  584. err_code = sd_ble_gap_device_name_set(&sec_mode,
  585. (const uint8_t *)DEVICE_NAME,
  586. strlen(DEVICE_NAME));
  587. APP_ERROR_CHECK(err_code);
  588. memset(&gap_conn_params, 0, sizeof(gap_conn_params));
  589. gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
  590. gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
  591. gap_conn_params.slave_latency = SLAVE_LATENCY;
  592. gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
  593. err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
  594. APP_ERROR_CHECK(err_code);
  595. }
  596. // gatt_init()
  597. static void gatt_init(void) {
  598. ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, NULL);
  599. APP_ERROR_CHECK(err_code);
  600. }
  601. // conn_params_init()
  602. static void conn_params_init(void) {
  603. ret_code_t err_code;
  604. ble_conn_params_init_t cp_init;
  605. memset(&cp_init, 0, sizeof(cp_init));
  606. cp_init.p_conn_params = NULL;
  607. cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
  608. cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;
  609. cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;
  610. cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID; // Start upon connection.
  611. cp_init.disconnect_on_fail = true;
  612. cp_init.evt_handler = NULL; // Ignore events.
  613. cp_init.error_handler = conn_params_error_handler;
  614. err_code = ble_conn_params_init(&cp_init);
  615. APP_ERROR_CHECK(err_code);
  616. }
  617. // void_db_disc_handler()
  618. static void db_disc_handler(ble_db_discovery_evt_t *p_evt) {
  619. ble_cus_on_db_disc_evt(&m_cus_c, p_evt);
  620. }
  621. // db_discovery_init()
  622. static void db_discovery_init(void) {
  623. ble_db_discovery_init_t db_init;
  624. memset(&db_init, 0, sizeof(db_init));
  625. db_init.evt_handler = db_disc_handler;
  626. db_init.p_gatt_queue = &m_ble_gatt_queue;
  627. ret_code_t err_code = ble_db_discovery_init(&db_init);
  628. APP_ERROR_CHECK(err_code);
  629. }
  630. // ble_pwm_ready_callback()
  631. static void ble_pwm_ready_callback(uint32_t pwm_id) {
  632. }
  633. // freq_to_period_us()
  634. static uint32_t freq_to_period_us(uint32_t freq) {
  635. return (uint32_t)((1.0 / (float)freq) * 1000000);
  636. }
  637. // init_ble_pwm1()
  638. static void init_ble_pwm1(uint32_t pin, uint32_t freq, float dutyCycle) {
  639. ret_code_t err_code;
  640. app_pwm_config_t out_cfg = APP_PWM_DEFAULT_CONFIG_1CH(freq_to_period_us(freq), pin); // period = 1/freq
  641. // Switch the polarity of the second channel.
  642. out_cfg.pin_polarity[0] = APP_PWM_POLARITY_ACTIVE_HIGH;
  643. // Initialize and enable PWM.
  644. err_code = app_pwm_init(&BLE_PWM1, &out_cfg, ble_pwm_ready_callback);
  645. APP_ERROR_CHECK(err_code);
  646. app_pwm_enable(&BLE_PWM1);
  647. ble_pwm1_enable_flag = true;
  648. pwm1_frequency = freq;
  649. for (uint8_t i = 0; i < out_cfg.num_of_channels; i++) {
  650. APP_ERROR_CHECK(app_pwm_channel_duty_set(&BLE_PWM1, i, dutyCycle));
  651. }
  652. }
  653. // deinit_ble_pwm1()
  654. static void deinit_ble_pwm1() {
  655. app_pwm_disable(&BLE_PWM1);
  656. APP_ERROR_CHECK(app_pwm_uninit(&BLE_PWM1));
  657. ble_pwm1_enable_flag = false;
  658. pwm1_frequency = 0;
  659. }
  660. // pump_all_off()
  661. static void pump_all_off() {
  662. if (app_timer_flag) {
  663. ret_code_t err_code = app_timer_stop(m_app_timer);
  664. app_timer_flag = false;
  665. electrolysis_timer_sec = 0;
  666. pump_actuation_time = 0;
  667. }
  668. if (pump_left_lv1_flag) { // Turn off left pump (Lv.1)
  669. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV1);
  670. nrf_gpio_pin_clear(OUTPUT_PUMP_LEFT_LV1);
  671. pump_left_lv1_flag = false;
  672. }
  673. if (pump_left_lv2_flag) { // Turn off left pump (Lv.2)
  674. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV2);
  675. nrf_gpio_pin_clear(OUTPUT_PUMP_LEFT_LV2);
  676. pump_left_lv2_flag = false;
  677. }
  678. if (pump_left_lv3_flag) { // Turn off left pump (Lv.3)
  679. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV3);
  680. nrf_gpio_pin_clear(OUTPUT_PUMP_LEFT_LV3);
  681. pump_left_lv3_flag = false;
  682. }
  683. if (pump_right_lv1_flag) { // Turn off right pump (Lv.1)
  684. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV1);
  685. nrf_gpio_pin_clear(OUTPUT_PUMP_RIGHT_LV1);
  686. pump_right_lv1_flag = false;
  687. }
  688. if (pump_right_lv2_flag) { // Turn off right pump (Lv.2)
  689. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV2);
  690. nrf_gpio_pin_clear(OUTPUT_PUMP_RIGHT_LV2);
  691. pump_right_lv2_flag = false;
  692. }
  693. if (pump_right_lv3_flag) { // Turn off right pump (Lv.3)
  694. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV3);
  695. nrf_gpio_pin_clear(OUTPUT_PUMP_RIGHT_LV3);
  696. pump_right_lv3_flag = false;
  697. }
  698. // Force full deinitialization of peripheral pins
  699. nrf_gpio_cfg_input(OUTPUT_PUMP_LEFT_LV1, NRF_GPIO_PIN_NOPULL);
  700. nrf_gpio_cfg_input(OUTPUT_PUMP_LEFT_LV2, NRF_GPIO_PIN_NOPULL);
  701. nrf_gpio_cfg_input(OUTPUT_PUMP_LEFT_LV3, NRF_GPIO_PIN_NOPULL);
  702. nrf_gpio_cfg_input(OUTPUT_PUMP_RIGHT_LV1, NRF_GPIO_PIN_NOPULL);
  703. nrf_gpio_cfg_input(OUTPUT_PUMP_RIGHT_LV2, NRF_GPIO_PIN_NOPULL);
  704. nrf_gpio_cfg_input(OUTPUT_PUMP_RIGHT_LV3, NRF_GPIO_PIN_NOPULL);
  705. }
  706. // on_cus_evt()
  707. static void on_cus_evt(ble_cus_t *p_cus_service, ble_cus_evt_t *p_evt, uint8_t *p_data, uint8_t length) {
  708. ret_code_t err_code;
  709. uint8_t data[length];
  710. switch (p_evt->evt_type) {
  711. case BLE_CUS_EVT_NOTIFICATION_ENABLED:
  712. break;
  713. case BLE_CUS_EVT_NOTIFICATION_DISABLED:
  714. break;
  715. case BLE_CUS_EVT_CONNECTED:
  716. break;
  717. case BLE_CUS_EVT_DISCONNECTED:
  718. break;
  719. case BLE_CUS_EVT_WRITE:
  720. memcpy(data, p_data, length);
  721. // 'attr_char_value.max_len' has been modified in "ble_cus.c".
  722. NRF_LOG_INFO("Data is %s and length is %d", (uint32_t)data, length);
  723. if (data[0] == 'a') { // set PWM: LED, 5Hz, 10ms duty
  724. if (ble_pwm1_enable_flag) { // If LED was already operating, turn off the LED.
  725. deinit_ble_pwm1();
  726. NRF_LOG_INFO("LED was already operating. Turning off the LED...");
  727. } else {
  728. init_ble_pwm1(OUTPUT_LED, FREQ1, DUTY1);
  729. NRF_LOG_INFO("Operate LED in 5 Hz...");
  730. }
  731. ble_cus_custom_value_update(&m_cus, status_ack, sizeof(status_ack));
  732. } else if (data[0] == 'b') { // set PWM: LED, 10Hz, 10ms duty
  733. if (ble_pwm1_enable_flag) { // If LED was already operating, turn off the LED.
  734. deinit_ble_pwm1();
  735. NRF_LOG_INFO("LED was already operating. Turning off the LED...");
  736. } else {
  737. init_ble_pwm1(OUTPUT_LED, FREQ2, DUTY2);
  738. NRF_LOG_INFO("Operate LED in 10 Hz...");
  739. }
  740. ble_cus_custom_value_update(&m_cus, status_ack, sizeof(status_ack));
  741. } else if (data[0] == 'c') { // set PWM: LED, 20Hz, 10ms duty
  742. if (ble_pwm1_enable_flag) { // If LED was already operating, turn off the LED.
  743. deinit_ble_pwm1();
  744. NRF_LOG_INFO("LED was already operating. Turning off the LED...");
  745. } else {
  746. init_ble_pwm1(OUTPUT_LED, FREQ3, DUTY3);
  747. NRF_LOG_INFO("Operate LED in 20 Hz...");
  748. }
  749. ble_cus_custom_value_update(&m_cus, status_ack, sizeof(status_ack));
  750. } else if (data[0] == 'd') { // set PWM: LED, 40Hz, 10ms duty
  751. if (ble_pwm1_enable_flag) { // If LED was already operating, turn off the LED.
  752. deinit_ble_pwm1();
  753. NRF_LOG_INFO("LED was already operating. Turning off the LED...");
  754. } else {
  755. init_ble_pwm1(OUTPUT_LED, FREQ4, DUTY4);
  756. NRF_LOG_INFO("Operate LED in 40 Hz...");
  757. }
  758. ble_cus_custom_value_update(&m_cus, status_ack, sizeof(status_ack));
  759. } else if (data[0] == 'e') {
  760. if ((data[1] == 'F') && (data[3] == 'f') && (data[4] == 'P') && (data[6] == 'p') && (data[7] == 'N') && (data[10] == 'n')) {
  761. // Pump actuation: data = "eF_fP_pN__n"
  762. // F: Target brain -> 1(=Left) / 2(=Right) / 3(=Both)
  763. // P: Flow rate level -> 1(=Lv.1) / 2(=Lv.2) / 3(=Lv.3)
  764. // N: Actuation time -> 01-99 sec
  765. if (app_timer_flag) { // If pump was already actuating, turn off the pump.
  766. pump_all_off();
  767. NRF_LOG_INFO("Pump was already actuating. Turning off the pump(s)...");
  768. } else if ((data[2] == '1') && (data[5] == '1')) { // Condition #1. Actuate left pump (Lv.1)
  769. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  770. NRF_LOG_INFO("Left pump (Lv.1) actuation time is %d sec.", pump_actuation_time);
  771. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  772. pump_all_off();
  773. NRF_LOG_INFO("Turning off the pump(s)...");
  774. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  775. electrolysis_timer_sec = 0;
  776. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  777. app_timer_flag = true;
  778. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV1);
  779. nrf_gpio_pin_set(OUTPUT_PUMP_LEFT_LV1);
  780. pump_left_lv1_flag = true;
  781. }
  782. } else if ((data[2] == '1') && (data[5] == '2')) { // Condition #2. Actuate left pump (Lv.2)
  783. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  784. NRF_LOG_INFO("Left pump (Lv.2) actuation time is %d sec.", pump_actuation_time);
  785. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  786. pump_all_off();
  787. NRF_LOG_INFO("Turning off the pump(s)...");
  788. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  789. electrolysis_timer_sec = 0;
  790. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  791. app_timer_flag = true;
  792. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV2);
  793. nrf_gpio_pin_set(OUTPUT_PUMP_LEFT_LV2);
  794. pump_left_lv2_flag = true;
  795. }
  796. } else if ((data[2] == '1') && (data[5] == '3')) { // Condition #3. Actuate left pump (Lv.3)
  797. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  798. NRF_LOG_INFO("Left pump (Lv.3) actuation time is %d sec.", pump_actuation_time);
  799. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  800. pump_all_off();
  801. NRF_LOG_INFO("Turning off the pump(s)...");
  802. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  803. electrolysis_timer_sec = 0;
  804. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  805. app_timer_flag = true;
  806. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV3);
  807. nrf_gpio_pin_set(OUTPUT_PUMP_LEFT_LV3);
  808. pump_left_lv3_flag = true;
  809. }
  810. } else if ((data[2] == '2') && (data[5] == '1')) { // Condition #4. Actuate right pump (Lv.1)
  811. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  812. NRF_LOG_INFO("Right pump (Lv.1) actuation time is %d sec.", pump_actuation_time);
  813. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  814. pump_all_off();
  815. NRF_LOG_INFO("Turning off the pump(s)...");
  816. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  817. electrolysis_timer_sec = 0;
  818. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  819. app_timer_flag = true;
  820. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV1);
  821. nrf_gpio_pin_set(OUTPUT_PUMP_RIGHT_LV1);
  822. pump_right_lv1_flag = true;
  823. }
  824. } else if ((data[2] == '2') && (data[5] == '2')) { // Condition #5. Actuate right pump (Lv.2)
  825. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  826. NRF_LOG_INFO("Right pump (Lv.2) actuation time is %d sec.", pump_actuation_time);
  827. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  828. pump_all_off();
  829. NRF_LOG_INFO("Turning off the pump(s)...");
  830. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  831. electrolysis_timer_sec = 0;
  832. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  833. app_timer_flag = true;
  834. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV2);
  835. nrf_gpio_pin_set(OUTPUT_PUMP_RIGHT_LV2);
  836. pump_right_lv2_flag = true;
  837. }
  838. } else if ((data[2] == '2') && (data[5] == '3')) { // Condition #6. Actuate right pump (Lv.3)
  839. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  840. NRF_LOG_INFO("Right pump (Lv.3) actuation time is %d sec.", pump_actuation_time);
  841. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  842. pump_all_off();
  843. NRF_LOG_INFO("Turning off the pump(s)...");
  844. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  845. electrolysis_timer_sec = 0;
  846. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  847. app_timer_flag = true;
  848. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV3);
  849. nrf_gpio_pin_set(OUTPUT_PUMP_RIGHT_LV3);
  850. pump_right_lv3_flag = true;
  851. }
  852. } else if ((data[2] == '3') && (data[5] == '1')) { // Condition #7. Actuate both pumps (Lv.1)
  853. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  854. NRF_LOG_INFO("Both pumps (Lv.1) actuation time is %d sec.", pump_actuation_time);
  855. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  856. pump_all_off();
  857. NRF_LOG_INFO("Turning off the pump(s)...");
  858. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  859. electrolysis_timer_sec = 0;
  860. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  861. app_timer_flag = true;
  862. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV1);
  863. nrf_gpio_pin_set(OUTPUT_PUMP_LEFT_LV1);
  864. pump_left_lv1_flag = true;
  865. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV1);
  866. nrf_gpio_pin_set(OUTPUT_PUMP_RIGHT_LV1);
  867. pump_right_lv1_flag = true;
  868. }
  869. } else if ((data[2] == '3') && (data[5] == '2')) { // Condition #8. Actuate both pumps (Lv.2)
  870. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  871. NRF_LOG_INFO("Both pumps (Lv.2) actuation time is %d sec.", pump_actuation_time);
  872. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  873. pump_all_off();
  874. NRF_LOG_INFO("Turning off the pump(s)...");
  875. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  876. electrolysis_timer_sec = 0;
  877. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  878. app_timer_flag = true;
  879. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV2);
  880. nrf_gpio_pin_set(OUTPUT_PUMP_LEFT_LV2);
  881. pump_left_lv2_flag = true;
  882. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV2);
  883. nrf_gpio_pin_set(OUTPUT_PUMP_RIGHT_LV2);
  884. pump_right_lv2_flag = true;
  885. }
  886. } else if ((data[2] == '3') && (data[5] == '3')) { // Condition #9. Actuate both pumps (Lv.3)
  887. pump_actuation_time = 10*(data[8] - '0') + 1*(data[9] - '0');
  888. NRF_LOG_INFO("Both pumps (Lv.3) actuation time is %d sec.", pump_actuation_time);
  889. if (pump_actuation_time == 0) { // If pump actuation time input is 0, turn off the pump.
  890. pump_all_off();
  891. NRF_LOG_INFO("Turning off the pump(s)...");
  892. } else if ((pump_actuation_time > 0) && (pump_actuation_time < 100)) { // Actuation time: 01-99 sec
  893. electrolysis_timer_sec = 0;
  894. err_code = app_timer_start(m_app_timer, PUMP_INTERVAL, NULL);
  895. app_timer_flag = true;
  896. nrf_gpio_cfg_output(OUTPUT_PUMP_LEFT_LV3);
  897. nrf_gpio_pin_set(OUTPUT_PUMP_LEFT_LV3);
  898. pump_left_lv3_flag = true;
  899. nrf_gpio_cfg_output(OUTPUT_PUMP_RIGHT_LV3);
  900. nrf_gpio_pin_set(OUTPUT_PUMP_RIGHT_LV3);
  901. pump_right_lv3_flag = true;
  902. }
  903. }
  904. } else { // If pump actuation command is abnormal, turn off the pump.
  905. pump_all_off();
  906. NRF_LOG_INFO("Non-identified command. Turning off the pump(s)...");
  907. }
  908. ble_cus_custom_value_update(&m_cus, status_ack, sizeof(status_ack));
  909. } else if (data[0] == 'q') { // Turn off the LED
  910. if (ble_pwm1_enable_flag) {
  911. deinit_ble_pwm1();
  912. }
  913. NRF_LOG_INFO("Turning off the LED...");
  914. ble_cus_custom_value_update(&m_cus, status_ack, sizeof(status_ack));
  915. } else if (data[0] == 'y') { // Turn off the pumps
  916. pump_all_off();
  917. NRF_LOG_INFO("Turning off the pump(s)...");
  918. ble_cus_custom_value_update(&m_cus, status_ack, sizeof(status_ack));
  919. } else if (data[0] == 'r') { // Reset
  920. sd_nvic_SystemReset();
  921. } else if (data[0] == 'z') { // Restart into bootloader
  922. err_code = sd_power_gpregret_clr(0, 0xffffffff);
  923. VERIFY_SUCCESS(err_code);
  924. err_code = sd_power_gpregret_set(0, 0xB1);
  925. VERIFY_SUCCESS(err_code);
  926. // Signal that DFU mode is to be enter to the power management module
  927. nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_DFU);
  928. }
  929. break;
  930. default:
  931. break;
  932. }
  933. }
  934. // services_init()
  935. static void services_init(void) {
  936. ret_code_t err_code;
  937. ble_cus_init_t cus_init = {0};
  938. // Initialize CUS Service init structure to zero.
  939. cus_init.evt_handler = on_cus_evt;
  940. BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cus_init.custom_value_char_attr_md.cccd_write_perm);
  941. BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cus_init.custom_value_char_attr_md.read_perm);
  942. BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cus_init.custom_value_char_attr_md.write_perm);
  943. err_code = ble_cus_init(&m_cus, &cus_init);
  944. APP_ERROR_CHECK(err_code);
  945. }
  946. // advertising_init()
  947. static void advertising_init(void) {
  948. ret_code_t err_code;
  949. ble_advertising_init_t init;
  950. memset(&init, 0, sizeof(init));
  951. init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
  952. init.advdata.include_appearance = true;
  953. init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
  954. init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
  955. init.srdata.uuids_complete.p_uuids = m_adv_uuids;
  956. init.config.ble_adv_fast_enabled = true;
  957. init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
  958. init.config.ble_adv_fast_timeout = APP_ADV_DURATION;
  959. init.evt_handler = on_adv_evt;
  960. err_code = ble_advertising_init(&m_advertising, &init);
  961. APP_ERROR_CHECK(err_code);
  962. ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
  963. }
  964. // saadc_event_handler()
  965. void saadc_event_handler(nrf_drv_saadc_evt_t const *p_event) {
  966. }
  967. // saadc_init()
  968. int saadc_init1() {
  969. // AIN0 = P0.02 (EYSHSNZWZ)
  970. // AIN1 = P0.03 (nRF52840-DK; A0) = P0.03 (EYSHSNZWZ)
  971. // AIN2 = P0.04 (nRF52840-DK; A1) = P0.04 (EYSHSNZWZ)
  972. // AIN3 = P0.05 (EYSHSNZWZ)
  973. // AIN4 = P0.28 (nRF52840-DK; A2) = P0.28 (EYSHSNZWZ)
  974. // AIN5 = P0.29 (nRF52840-DK; A3)
  975. // AIN6 = P0.30 (nRF52840-DK; A4)
  976. // AIN7 = P0.31 (nRF52840-DK; A5)
  977. int ret = nrf_drv_saadc_init(NULL, saadc_event_handler);
  978. if (ret)
  979. return ret;
  980. nrf_saadc_channel_config_t config =
  981. NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1); // AIN1 = P0.03 (PIN 9 - TAIYOYUDEN)
  982. ret = nrf_drv_saadc_channel_init(0, &config);
  983. return ret;
  984. }
  985. // saadc_measure1()
  986. int saadc_measure1() {
  987. nrf_saadc_value_t value;
  988. nrf_drv_saadc_sample_convert(0, &value);
  989. return value;
  990. }
  991. // timer_handler()
  992. void timer_handler(nrf_timer_event_t event_type, void *p_context) {
  993. electrolysis_timer_sec = electrolysis_timer_sec + 1;
  994. }
  995. // log_init()
  996. static void log_init(void) {
  997. ret_code_t err_code = NRF_LOG_INIT(NULL);
  998. APP_ERROR_CHECK(err_code);
  999. NRF_LOG_DEFAULT_BACKENDS_INIT();
  1000. }
  1001. // timer_init()
  1002. static void timer_init(void) {
  1003. ret_code_t err_code;
  1004. /* timer driver initialization */
  1005. nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
  1006. timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
  1007. }
  1008. // power_management_init()
  1009. static void power_management_init(void) {
  1010. ret_code_t err_code;
  1011. err_code = nrf_pwr_mgmt_init();
  1012. APP_ERROR_CHECK(err_code);
  1013. }
  1014. // application_timers_start()
  1015. static void application_timers_start(void) {
  1016. }
  1017. // advertising_start()
  1018. static void advertising_start(bool erase_bonds) {
  1019. if (erase_bonds == true) {
  1020. delete_bonds();
  1021. // Advertising is started by PM_EVT_PEERS_DELETED_SUCEEDED event
  1022. } else {
  1023. ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
  1024. APP_ERROR_CHECK(err_code);
  1025. }
  1026. }
  1027. // app_timer_handler()
  1028. static void app_timer_handler(void * p_context) {
  1029. electrolysis_timer_sec = electrolysis_timer_sec + 1;
  1030. if (electrolysis_timer_sec == pump_actuation_time) {
  1031. pump_all_off();
  1032. }
  1033. }
  1034. // app_timers_init()
  1035. static void app_timers_init(void) {
  1036. // Initialize timer module, making it use the scheduler
  1037. ret_code_t err_code;
  1038. err_code = app_timer_init();
  1039. APP_ERROR_CHECK(err_code);
  1040. err_code = app_timer_create(&m_app_timer, APP_TIMER_MODE_REPEATED, app_timer_handler);
  1041. APP_ERROR_CHECK(err_code);
  1042. }
  1043. // idle_state_handler()
  1044. static void idle_state_handle(void) {
  1045. ret_code_t err_code;
  1046. if (NRF_LOG_PROCESS() == false) {
  1047. nrf_pwr_mgmt_run();
  1048. }
  1049. }
  1050. // main()
  1051. int main(void) {
  1052. bool erase_bonds;
  1053. // Initialize.
  1054. log_init(); //
  1055. timer_init();
  1056. buttons_leds_init(&erase_bonds);
  1057. app_timers_init();
  1058. power_management_init();
  1059. ble_stack_init();
  1060. gap_params_init();
  1061. gatt_init();
  1062. services_init();
  1063. advertising_init();
  1064. conn_params_init();
  1065. peer_manager_init();
  1066. // Start execution.
  1067. NRF_LOG_INFO("Template example started.");
  1068. application_timers_start();
  1069. db_discovery_init();
  1070. cus_c_init();
  1071. // advertising_start(erase_bonds);
  1072. // saadc_init0();
  1073. saadc_init1();
  1074. // Start execution.
  1075. NRF_LOG_INFO("LE Secure Connections example started.");
  1076. if (erase_bonds == true) {
  1077. delete_bonds();
  1078. // Scanning and advertising is started by PM_EVT_PEERS_DELETE_SUCEEDED.
  1079. } else {
  1080. adv_scan_start(); // "Advertising"
  1081. }
  1082. // Enter main loop.
  1083. for (;;) {
  1084. idle_state_handle();
  1085. }
  1086. }

main.c, under CC-BY-4.0 · at the source

Overview

  1. School of Electrical Engineering, Korea Advanced Institute of Science and Technology (KAIST), Daejeon 34141, Republic of Korea
  2. Department of Medical Sciences, Yonsei University College of Medicine, Seoul 03722, Republic of Korea
  3. Querrey Simpson Institute for Bioelectronics, Northwestern University, Evanston, IL 60208, USA
  4. Department of Physics, Korea Advanced Institute of Science and Technology (KAIST), Daejeon 34141, Republic of Korea
  5. Department of Physiology, Yonsei University College of Medicine, Seoul 03722, Republic of Korea
  6. Department of Brain and Cognitive Sciences, Korea Advanced Institute of Science and Technology (KAIST), Daejeon 34141, Republic of Korea
  7. KAIST Institute for Human Augmentation Convergence, Daejeon 34141, Republic of Korea
Journal: Science advances, volume 12, issue 31, article eaee8648
Dates: received 18 December 2025; accepted 22 June 2026; published online 29 July 2026; in print July 2026
Type: Research article · Language: English
License: CC BY-NC
Identifiers: DOI 10.1126/sciadv.aee8648 · PMID 42525762 · PMCID PMC13418541 · OpenAlex W7171711357
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: mouse (organism), rat (organism), other condition (population), systems (subfield)
Methods: Statistics, Evoked potentials
MeSH: Neuropharmacology*, Optogenetics*, Wireless Technology*, Animals, Cocaine, Drug Delivery Systems, Mice, Rats (* major topic)
Topic: Photoreceptor and optogenetics research (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Funding: National Research Foundation of Korea (RS-2024-00335066, RS-2022-NR070824, RS-2025-00563430)
Citations: not cited yet (Europe PMC); 63 references in the paper

Abstract

Wireless in vivo neuropharmacology and optogenetics offer a powerful way to link molecular signaling, defined neural populations, and behavior in freely moving animals. However, existing implantable wireless systems developed for this purpose often suffer from imprecise dosing, backflow contamination, and nonrefillable reservoirs, and their reliance on experimenter presence can itself alter neural activity and behavior. Here, we introduce a remotely actuated and programmable implant for drug delivery and optical stimulation (RAPIDO), which integrates a refillable, replaceable, and backflow-free fluidic module with a microscale inorganic light-emitting diode probe in a single miniaturized implant. A replaceable cartridge with an integrated unidirectional valve enables rapid refilling with contamination-free dosing, while a programmable electrochemical pump provides multilevel flow-rate control and linear dose modulation. Dual wireless modes combine smartphone control for on-site interactive experiments with internet connectivity for remotely scheduled, observer-free operation. In freely behaving rodents, RAPIDO enabled repeated wireless pharmacological modulation of locomotor behavior and independent optogenetic manipulation of intracellular signaling during cocaine conditioning. This platform establishes a foundation for chronic multimodal neuromodulation, supporting long-term circuit studies across distributed laboratories.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above.

Zenodo 20063531

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Data, code, and materials availability:”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
3 files

jey0920/aee8648_firmware

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 02321cea5b58cd65ce2fbc3f7048b1d4affc699a, 7 May 2026
Languages: C (1)
Size: 3 files, 1 script
Software Heritage: not archived
Found in: the Zenodo archive record
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
3 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 2 scripts, each with its path and the digest of its content;
  • no match between paragraphs and code yet;
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data, code, and materials availability

All data needed to evaluate and reproduce the results in the paper are present in the paper and/or the Supplementary Materials. This study did not generate new materials. The embedded firmware source code for the wireless optofluidic neuromodulation system is publicly available at Zenodo (DOI: 10.5281/zenodo.20063531 (http://dx.doi.org/10.5281/zenodo.20063531)). The IoT webserver architecture and implementation are described in (59).

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 10 authors, 8 MeSH terms, 1 funder, 57 references.

Cite

This paper

Jeong, E. Y., Park, J. W., Cho, S., Han, D., Kim, C. Y., Kim, S. W., Lee, W., Kim, W. Y., Kim, J.-H., & Jeong, J.-W. (2026). IoT-enabled wireless neural implant for chronic, programmable neuropharmacology and optogenetics. Science advances, 12(31), eaee8648. https://doi.org/10.1126/sciadv.aee8648

BibTeX

@article{jeong2026iot,
author = {Jeong, Eun Young and Park, Jong Woo and Cho, Sungwoo and Han, Donggi and Kim, Choong Yeon and Kim, Sung Woo and Lee, Wonhee and Kim, Wha Young and Kim, Jeong-Hoon and Jeong, Jae-Woong},
title = {{IoT-enabled wireless neural implant for chronic, programmable neuropharmacology and optogenetics}},
journal = {Science advances},
year = {2026},
month = jul,
volume = {12},
number = {31},
pages = {eaee8648},
publisher = {American Association for the Advancement of Science},
issn = {2375-2548},
doi = {10.1126/sciadv.aee8648},
url = {https://doi.org/10.1126/sciadv.aee8648},
pmid = {42525762},
pmcid = {PMC13418541}
}

RIS

TY - JOUR
AU - Jeong, Eun Young
AU - Park, Jong Woo
AU - Cho, Sungwoo
AU - Han, Donggi
AU - Kim, Choong Yeon
AU - Kim, Sung Woo
AU - Lee, Wonhee
AU - Kim, Wha Young
AU - Kim, Jeong-Hoon
AU - Jeong, Jae-Woong
TI - IoT-enabled wireless neural implant for chronic, programmable neuropharmacology and optogenetics
T2 - Science advances
J2 - Sci Adv
PY - 2026
DA - 2026/07/29
VL - 12
IS - 31
SP - eaee8648
SN - 2375-2548
PB - American Association for the Advancement of Science
DO - 10.1126/sciadv.aee8648
UR - https://doi.org/10.1126/sciadv.aee8648
LA - en
ER -

CSL-JSON

{
"id": "10.1126/sciadv.aee8648",
"type": "article-journal",
"title": "IoT-enabled wireless neural implant for chronic, programmable neuropharmacology and optogenetics",
"container-title": "Science advances",
"author": [
{
"family": "Jeong",
"given": "Eun Young"
},
{
"family": "Park",
"given": "Jong Woo"
},
{
"family": "Cho",
"given": "Sungwoo"
},
{
"family": "Han",
"given": "Donggi"
},
{
"family": "Kim",
"given": "Choong Yeon"
},
{
"family": "Kim",
"given": "Sung Woo"
},
{
"family": "Lee",
"given": "Wonhee"
},
{
"family": "Kim",
"given": "Wha Young"
},
{
"family": "Kim",
"given": "Jeong-Hoon"
},
{
"family": "Jeong",
"given": "Jae-Woong"
}
],
"container-title-short": "Sci Adv",
"volume": "12",
"issue": "31",
"page": "eaee8648",
"DOI": "10.1126/sciadv.aee8648",
"PMID": "42525762",
"PMCID": "PMC13418541",
"ISSN": "2375-2548",
"publisher": "American Association for the Advancement of Science",
"URL": "https://doi.org/10.1126/sciadv.aee8648",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
29
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41398-026-04015-4
Chemogenetic modulation of the prelimbic cortex to the nucleus accumbens core pathway reduces cocaine-induced increase of risk preference.
Journal: Translational psychiatry
In common: rat, other condition, 1 reference, author Jeong-Hoon Kim
[2] doi:10.1126/sciadv.aeg2327
Top-down corticostriatal control of adaptive restraint during motivational conflict.
Journal: Science advances
In common: rat, systems, 1 reference
[3] doi:10.1038/s41467-026-75244-w [code]
Neural dynamics of temporal interference stimulation monitoring by soft liquid metal interfaces across neural systems.
Journal: Nature communications
In common: systems, mouse, 1 reference
[4] doi:10.1038/s41592-026-03076-z [code]
Neuropixels Opto: combining high-resolution electrophysiology and optogenetics.
Journal: Nature methods
In common: systems, mouse, 1 reference
[5] doi:10.1126/sciadv.aeb5842 [code]
Regulation of autism-related self-injurious behavior by electrical stimulation of corticostriatal circuits in mice and humans.
Journal: Science advances
In common: systems, mouse, 1 reference
[6] doi:10.1126/sciadv.aeb1237
Magnetically actuated nanoantennas for wireless glioblastoma therapy.
Journal: Science advances
In common: other condition, mouse, 1 reference
[7] doi:10.1523/jneurosci.0409-25.2026
The Role of Disulfide Bonds in the GluN1 Subunit in the Early Trafficking and Functional Properties of GluN1/GluN2 and GluN1/GluN3 NMDA Receptors.
Journal: The Journal of neuroscience : the official journal of the Society for Neuroscience
In common: rat, 1 reference
[8] doi:10.1038/s41467-026-72934-3 [code]
Multi-focal ultrasound neuromodulation to the dorsal anterior cingulate cortex disrupts behavioural and neural pain processing.
Journal: Nature communications
In common: systems, 1 reference
[9] doi:10.1016/j.crmeth.2026.101421 [code]
EthoPy provides an accessible platform for reproducible behavioral neuroscience.
Journal: Cell reports methods
In common: mouse, 1 reference
[10] doi:10.7554/elife.108639
The nucleus accumbens shell regulates hedonic feeding via a rostral hotspot.
Journal: eLife
In common: mouse, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.