Skip to content

Commit 6765932

Browse files
committed
nRF52: Add support for sound output over I2S
1 parent 7c6531d commit 6765932

File tree

6 files changed

+618
-1
lines changed

6 files changed

+618
-1
lines changed

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,12 @@ ifeq ($(USE_JIT),1)
679679
SOURCES += src/jsjit.c src/jsjitc.c
680680
endif
681681

682+
ifeq ($(USE_I2S),1)
683+
DEFINES += -DUSE_I2S
684+
WRAPPERSOURCES += src/jswrap_i2s.c
685+
#SOURCES += src/jsi2s.c
686+
endif
687+
682688

683689
endif # BOOTLOADER ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DON'T USE STUFF ABOVE IN BOOTLOADER
684690

make/common/NRF5X.make

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ else # no BOOTLOADER
8181

8282
ifeq ($(FAMILY), NRF52)
8383
# Neopixel support (only NRF52)
84-
SOURCES += targets/nrf5x/i2s_ws2812b_drive.c
84+
SOURCES += targets/nrf5x/i2s_ws2812b_drive.c
85+
endif
86+
ifeq ($(USE_I2S),1)
87+
# I2S support
88+
SOURCES += targets/nrf5x/jsi2s.c
8589
endif
8690
endif
8791

src/jsi2s.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
3+
*
4+
* Copyright (C) 2025 Simon Sievert <[email protected]>
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*
10+
* ----------------------------------------------------------------------------
11+
* I2S support
12+
* ----------------------------------------------------------------------------
13+
*/
14+
15+
#ifndef JSI2S_H
16+
#define JSI2S_H
17+
18+
#include "jspin.h"
19+
20+
typedef void (*jsi2s_data_cb_t)(void);
21+
22+
typedef void (*jsi2s_buffer_release_cb_t)(uint32_t *buf_ptr);
23+
24+
bool jsi2s_init(Pin bclk, Pin lrck, Pin dout, int sample_width_bytes);
25+
26+
void jsi2s_uninit();
27+
28+
bool jsi2s_start(uint32_t *buf_ptr, uint32_t buffer_size_bytes, jsi2s_data_cb_t data_callback,
29+
jsi2s_buffer_release_cb_t buffer_released_callback);
30+
31+
void jsi2s_stop();
32+
33+
bool jsi2s_set_next_buffer(uint32_t *buf_ptr, uint32_t buffer_size_bytes);
34+
35+
bool jsi2s_idle();
36+
37+
#endif // JSI2S_H

src/jswrap_i2s.c

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
/*
2+
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
3+
*
4+
* Copyright (C) 2025 Simon Sievert <[email protected]>
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*
10+
* ----------------------------------------------------------------------------
11+
* JavaScript I2S Functions
12+
* ----------------------------------------------------------------------------
13+
*/
14+
15+
#include "jswrap_i2s.h"
16+
#include "jsinteractive.h"
17+
#include "jsi2s.h"
18+
19+
bool jswrap_i2s_initialized = false;
20+
bool jswrap_i2s_active = false;
21+
JsVar *jswrap_i2s_data_callback = NULL;
22+
JsVar *jswrap_i2s_buffer_released_callback = NULL;
23+
volatile bool jswrap_i2s_needs_more_data_flag = false;
24+
25+
struct jswrap_i2s_buf_t {
26+
JsVar *buf;
27+
volatile uint32_t *buf_ptr;
28+
volatile bool release;
29+
};
30+
31+
#define JSWRAP_I2S_BUFFER_COUNT 2
32+
struct jswrap_i2s_buf_t jswrap_i2s_buffers[JSWRAP_I2S_BUFFER_COUNT];
33+
34+
bool jswrap_i2s_acquire_buf(JsVar *buf, uint32_t *buf_ptr) {
35+
for (size_t i = 0; i < JSWRAP_I2S_BUFFER_COUNT; i++) {
36+
struct jswrap_i2s_buf_t *buf_entry = &jswrap_i2s_buffers[i];
37+
if (buf_entry->buf == NULL) {
38+
jsvLockAgain(buf);
39+
buf_entry->release = false;
40+
buf_entry->buf = buf;
41+
buf_entry->buf_ptr = buf_ptr;
42+
return true;
43+
}
44+
}
45+
return false;
46+
}
47+
48+
bool jswrap_i2s_mark_buf_for_release(const uint32_t *buf_ptr) {
49+
for (size_t i = 0; i < JSWRAP_I2S_BUFFER_COUNT; i++) {
50+
struct jswrap_i2s_buf_t *buf_entry = &jswrap_i2s_buffers[i];
51+
if (buf_entry->buf_ptr == buf_ptr) {
52+
buf_entry->release = true;
53+
return true;
54+
}
55+
}
56+
return false;
57+
}
58+
59+
void jswrap_i2s_on_buffer_released(JsVar *buf) {
60+
if (jswrap_i2s_buffer_released_callback != NULL) {
61+
jspExecuteFunction(jswrap_i2s_buffer_released_callback, NULL, 1, &buf);
62+
}
63+
}
64+
65+
void jswrap_i2s_release_marked_bufs() {
66+
for (size_t i = 0; i < JSWRAP_I2S_BUFFER_COUNT; i++) {
67+
struct jswrap_i2s_buf_t *buf_entry = &jswrap_i2s_buffers[i];
68+
if (buf_entry->release && (buf_entry->buf != NULL)) {
69+
JsVar *buf = buf_entry->buf;
70+
buf_entry->buf = NULL;
71+
buf_entry->buf_ptr = NULL;
72+
buf_entry->release = false;
73+
jswrap_i2s_on_buffer_released(buf);
74+
jsvUnLock(buf);
75+
}
76+
}
77+
}
78+
79+
void jswrap_i2s_release_all_buffers() {
80+
for (size_t i = 0; i < JSWRAP_I2S_BUFFER_COUNT; i++) {
81+
struct jswrap_i2s_buf_t *buf_entry = &jswrap_i2s_buffers[i];
82+
if (buf_entry->buf != NULL) {
83+
JsVar *buf = buf_entry->buf;
84+
buf_entry->buf = NULL;
85+
jswrap_i2s_on_buffer_released(buf);
86+
jsvUnLock(buf);
87+
}
88+
}
89+
memset(jswrap_i2s_buffers, 0, sizeof(struct jswrap_i2s_buf_t) * JSWRAP_I2S_BUFFER_COUNT);
90+
}
91+
92+
bool jswrap_i2s_acquire_callbacks(JsVar *data_callback, JsVar *buffer_released_callback) {
93+
if ((data_callback == NULL) || (buffer_released_callback == NULL)) {
94+
return false;
95+
}
96+
jsvLockAgain(data_callback);
97+
jswrap_i2s_data_callback = data_callback;
98+
jsvLockAgain(buffer_released_callback);
99+
jswrap_i2s_buffer_released_callback = buffer_released_callback;
100+
return true;
101+
}
102+
103+
void jswrap_i2s_release_callbacks() {
104+
if (jswrap_i2s_data_callback != NULL) {
105+
jsvUnLock(jswrap_i2s_data_callback);
106+
jswrap_i2s_data_callback = NULL;
107+
}
108+
if (jswrap_i2s_buffer_released_callback != NULL) {
109+
jsvUnLock(jswrap_i2s_buffer_released_callback);
110+
jswrap_i2s_buffer_released_callback = NULL;
111+
}
112+
}
113+
114+
void jswrap_i2s_on_buffer_released_callback(uint32_t *buf_ptr) {
115+
if (jswrap_i2s_mark_buf_for_release(buf_ptr)) {
116+
jshHadEvent();
117+
}
118+
}
119+
120+
void jswrap_i2s_on_more_data_requested_callback() {
121+
jswrap_i2s_needs_more_data_flag = true;
122+
jshHadEvent();
123+
}
124+
125+
/*JSON{
126+
"type" : "staticmethod",
127+
"class" : "I2S",
128+
"name" : "init",
129+
"generate" : "jswrap_i2s_init",
130+
"params" : [
131+
["bclk","pin","Bit Clock Pin"],
132+
["lrck","pin","Left/Right Clock Pin"],
133+
["dout","pin","Data Out Pin"],
134+
["sample_width_bytes","int","Sample width in Bytes, one of 1, 2 or 3 (so 8 Bits, 16 Bits or 24 Bits)"]
135+
],
136+
"return" : ["bool","true on success, false on error"]
137+
}
138+
Initialize I2S.
139+
*/
140+
bool jswrap_i2s_init(Pin bclk, Pin lrck, Pin dout, int sample_width_bytes) {
141+
if (jswrap_i2s_initialized) {
142+
jswrap_i2s_uninit();
143+
}
144+
memset(jswrap_i2s_buffers, 0, sizeof(struct jswrap_i2s_buf_t) * JSWRAP_I2S_BUFFER_COUNT);
145+
bool init_ok = jsi2s_init(bclk, lrck, dout, sample_width_bytes);
146+
jswrap_i2s_initialized = init_ok;
147+
jswrap_i2s_active = false;
148+
return init_ok;
149+
}
150+
151+
/*JSON{
152+
"type" : "staticmethod",
153+
"class" : "I2S",
154+
"name" : "uninit",
155+
"generate" : "jswrap_i2s_uninit"
156+
}
157+
Uninitialize I2S.
158+
*/
159+
void jswrap_i2s_uninit() {
160+
if (!jswrap_i2s_initialized) {
161+
return;
162+
}
163+
jsi2s_uninit();
164+
jswrap_i2s_release_callbacks();
165+
jswrap_i2s_release_all_buffers();
166+
jswrap_i2s_initialized = false;
167+
jswrap_i2s_active = false;
168+
}
169+
170+
/*JSON{
171+
"type" : "staticmethod",
172+
"class" : "I2S",
173+
"name" : "start",
174+
"generate" : "jswrap_i2s_start",
175+
"params" : [
176+
["buf","JsVar","Data buffer"],
177+
["data_callback","JsVar","Callback for requesting more data"],
178+
["buffer_released_callback","JsVar","Called when a buffer is not used anymore (with that buffer as an argument)"]
179+
],
180+
"return" : ["bool","true on success, false on error"]
181+
}
182+
Start an I2S transfer.
183+
*/
184+
bool jswrap_i2s_start(JsVar *buf, JsVar *data_callback, JsVar *buffer_released_callback) {
185+
if (!jswrap_i2s_initialized) {
186+
jsiConsolePrint("I2S: not initialized\n");
187+
return false;
188+
}
189+
if (jswrap_i2s_active) {
190+
jswrap_i2s_stop();
191+
}
192+
if (!jsvIsArrayBuffer(buf)) {
193+
jsiConsolePrint("I2S start: buffer is not an arraybuffer\n");
194+
return false;
195+
}
196+
size_t buf_len = 0;
197+
uint32_t *buf_ptr = (uint32_t *) jsvGetDataPointer(buf, &buf_len);
198+
if (buf_ptr == NULL) {
199+
jsiConsolePrint("I2S start: failed to get buffer address\n");
200+
return false;
201+
}
202+
if (!jsvIsFunction(data_callback)) {
203+
jsiConsolePrint("I2S: data_callback must be a function\n");
204+
return false;
205+
}
206+
if (!jsvIsFunction(buffer_released_callback)) {
207+
jsiConsolePrint("I2S: buffer_released_callback must be a function\n");
208+
return false;
209+
}
210+
jswrap_i2s_release_callbacks();
211+
jswrap_i2s_release_all_buffers();
212+
if (!jswrap_i2s_acquire_callbacks(data_callback, buffer_released_callback)) {
213+
jsiConsolePrint("I2S: failed to acquire callbacks\n");
214+
return false;
215+
}
216+
if (!jswrap_i2s_acquire_buf(buf, buf_ptr)) {
217+
jsiConsolePrint("I2S: failed to acquire buffer\n");
218+
jswrap_i2s_release_callbacks();
219+
return false;
220+
}
221+
jswrap_i2s_active = true;
222+
bool started = jsi2s_start(buf_ptr, buf_len, jswrap_i2s_on_more_data_requested_callback,
223+
jswrap_i2s_on_buffer_released_callback);
224+
if (!started) {
225+
jswrap_i2s_active = false;
226+
jswrap_i2s_release_callbacks();
227+
jswrap_i2s_release_all_buffers();
228+
}
229+
return started;
230+
}
231+
232+
/*JSON{
233+
"type" : "staticmethod",
234+
"class" : "I2S",
235+
"name" : "stop",
236+
"generate" : "jswrap_i2s_stop"
237+
}
238+
Stop an I2S transfer.
239+
*/
240+
void jswrap_i2s_stop() {
241+
if (!jswrap_i2s_initialized) {
242+
jsiConsolePrint("I2S: not initialized\n");
243+
return;
244+
}
245+
jsi2s_stop();
246+
jswrap_i2s_release_callbacks();
247+
jswrap_i2s_release_all_buffers();
248+
jswrap_i2s_active = false;
249+
}
250+
251+
/*JSON{
252+
"type" : "staticmethod",
253+
"class" : "I2S",
254+
"name" : "setNextBuffer",
255+
"generate" : "jswrap_i2s_set_next_buffer",
256+
"params" : [
257+
["buf","JsVar","Data buffer"]
258+
],
259+
"return" : ["bool","true on success, false on error"]
260+
}
261+
Supply the next buffer used for an active I2S transfer.
262+
*/
263+
bool jswrap_i2s_set_next_buffer(JsVar *buf) {
264+
if (!jswrap_i2s_initialized) {
265+
jsiConsolePrint("I2S: not initialized\n");
266+
return false;
267+
}
268+
if (!jswrap_i2s_active) {
269+
jsiConsolePrint("I2S: not active\n");
270+
return false;
271+
}
272+
if (!jsvIsArrayBuffer(buf)) {
273+
jsiConsolePrint("I2S set next buffer: buffer is not an arraybuffer\n");
274+
return false;
275+
}
276+
size_t buf_len = 0;
277+
uint32_t *buf_ptr = (uint32_t *) jsvGetDataPointer(buf, &buf_len);
278+
if (buf_ptr == NULL) {
279+
jsiConsolePrint("I2S set next buffer: failed to get buffer address\n");
280+
return false;
281+
}
282+
jswrap_i2s_release_marked_bufs();
283+
if (!jswrap_i2s_acquire_buf(buf, buf_ptr)) {
284+
jsiConsolePrint("I2S: failed to acquire buffer\n");
285+
return false;
286+
}
287+
bool ok = jsi2s_set_next_buffer(buf_ptr, buf_len);
288+
if (!ok) {
289+
jswrap_i2s_mark_buf_for_release(buf_ptr);
290+
jswrap_i2s_release_marked_bufs();
291+
return false;
292+
}
293+
return ok;
294+
}
295+
296+
/*JSON{
297+
"type" : "idle",
298+
"generate" : "jswrap_i2s_idle"
299+
}*/
300+
bool jswrap_i2s_idle() {
301+
if (!jswrap_i2s_initialized) {
302+
return false;
303+
}
304+
bool was_busy = jsi2s_idle();
305+
jswrap_i2s_release_marked_bufs();
306+
if (jswrap_i2s_needs_more_data_flag) {
307+
jswrap_i2s_needs_more_data_flag = false;
308+
if (jswrap_i2s_data_callback != NULL) {
309+
jspExecuteFunction(jswrap_i2s_data_callback, NULL, 0, NULL);
310+
}
311+
}
312+
return was_busy;
313+
}
314+
315+
/*JSON{
316+
"type" : "kill",
317+
"generate" : "jswrap_i2s_kill"
318+
}*/
319+
void jswrap_i2s_kill() {
320+
if (jswrap_i2s_active) {
321+
jswrap_i2s_stop();
322+
}
323+
if (jswrap_i2s_initialized) {
324+
jswrap_i2s_uninit();
325+
}
326+
}

0 commit comments

Comments
 (0)