-
Notifications
You must be signed in to change notification settings - Fork 0
/
nrf24l01.ceu
477 lines (419 loc) · 16.5 KB
/
nrf24l01.ceu
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
///////////////////////////////////////////////////////////////////////////////
// EXTERNAL INTERFACE
///////////////////////////////////////////////////////////////////////////////
#ifndef NRF24L01_IRQ
#error Missing interrupt pin `NRF24L01_IRQ`.
#endif
data NRF24L01_Data with
var& Lock spi;
var int ce;
var int csn;
var u8 payload_size = 32;
var byte config;
var u64 pipe0;
event none ok;
event none rx;
event none tx;
event none max;
end
code/await NRF24L01_Init (var& NRF24L01_Data nrf) -> NEVER;
code/await NRF24L01_Rx (var& NRF24L01_Data nrf, var&[] byte buf) -> u8;
code/await NRF24L01_Tx (var& NRF24L01_Data nrf, var&[] byte buf) -> bool;
///////////////////////////////////////////////////////////////////////////////
// DOCUMENTATION
///////////////////////////////////////////////////////////////////////////////
#if 0
Tutorial 0: Everything You Need to Know about the nRF24L01 and MiRF-v2.
Written by Brennen Ball, 2007
The SPI interface uses four pins, CSN, SCK, MISO, and MOSI for data
transmission and reception.
The CSN (chip select not) pin is active-low, and is normally kept high.
When this pin goes low, the 24L01 begins listening on its SPI port for data
and processes it accordingly.
The remaining two pins are CE and IRQ.
CE is used to control data transmission and reception when in TX and RX modes,
respectively.
IRQ is the interrupt pin, and is active-low.
There are three internal interrupts that can cause this pin to go low when they
are active.
Each of these bits can be masked out such that when the bit’s respective
interrupt becomes active, the status of the IRQ pin is not changed.
The second pin down is the CE pin.
This pin is always an input with respect to the 24L01.
This pin has different meanings depending on what mode the 24L01 is in at a
given time.
First we assume that the device is powered-up internally (by setting the PWR_UP
bit in the CONFIG register).
If the device is a receiver, having CE high allows the 24L01 to monitor the air
and receive packets.
CE being low puts the chip in standby and it no longer monitors the air.
If the device is a transmitter, CE is always held low except when the user
wants to transmit a packet.
This is done by loading the TX FIFO and then toggling the CE pin (low-to-high
transition, leave high for at least 10 uS, then back to low).
The third pin is CSN, which stands for chip select not.
This is the enable pin for the SPI bus, and it is active low.
You always want to keep this pin high except when you are sending the device an
SPI command or getting data on the SPI bus from the chip
You can set interrupts for any combination of the following events: data
received, data transmitted, and maximum number of transmit retries reached.
NOTE: the IRQ pin is ACTIVE-LOW. It is normally high, but when an interrupt is
asserted, the pin will go low.
In order to send data to or receive data from the SPI port on the 24L01, you
have to do a few things.
The CSN pin on the 24L01 must be high to start out with.
Then, you bring the CSN pin low to alert the 24L01 that it is about to receive
SPI data.
(Note: this pin will stay low throughout the entire transaction.)
Then, you will transmit the command byte of the instruction you wish to send.
If you are receiving data bytes for this instruction, you must then send one
byte to the 24L01 for every one byte that you wish to get out of the 24L01.
If you are just sending the 24L01 data, you simply send your data bytes and
generally don’t worry about what it sends back to you.
When receiving data from the 24L01, it makes absolutely no difference what is
contained in the data bytes you send after the command byte, just so long as
you send the correct number of them.
Once you have transmitted and/or read all of the bytes that you need, you bring
CSN back high.
Finally, you can process this data in your micro at will.
The first of the "big-money" operation is R_RX_PAYLOAD.
When you are receiving packets, CE is held high.
Once you have received a packet you MUST bring CE low to disable the receiver,
and then you execute the R_RX_PAYLOAD operation.
You send the command byte (0x61), and then send the same amount of dummy bytes
as your payload width is for the pipe you received data on.
The 24L01 will automatically delete the payload that you read out from the top
of the FIFO.
If there are more payloads in the FIFO (the device can hold 3 payloads in the
RX FIFO), then you should continue reading them out until they are all read.
At this point, you would clear the RX_DR interrupt, bring CE high again to
monitor for packets, and then process the data you received.
The second of the "big-money" operation is W_TX_PAYLOAD.
This guy is used when you are in TX mode and wish to send a packet.
In normal TX operation, the CE pin is held low.
You first send the command byte (0xA0), and then the payload.
The number of payload bytes you send MUST match the payload length of the
receiver you are sending the payload to.
Once you load the packet, you toggle the CE pin to send the packet on its way
(keeping it high for at least 10 us).
If the packet was sent, the TX_DS interrupt will occur.
This is to signal to you that the 24L01 has successfully sent your packet.
If you had auto-ack enabled on this pipe, the TX_DS flag will only be set if
the packet actually gets through.
If the maximum amount of retries is hit, then the MAX_RT interrupt will become
active.
At this point, you should clear the interrupts and continue based on
which interrupt was asserted.
Also remember that, like the RX FIFO, the TX FIFO is three levels deep.
This means that you can load up to three packets into the 24L01’s TX
FIFO before you do the CE toggle to send them on their way.
#endif
///////////////////////////////////////////////////////////////////////////////
// DECLARATIONS
///////////////////////////////////////////////////////////////////////////////
#define CONFIG 0x00
#define EN_AA 0x01
#define EN_RXADDR 0x02
#define SETUP_AW 0x03
#define SETUP_RETR 0x04
#define RF_CH 0x05
#define RF_SETUP 0x06
#define STATUS 0x07
#define OBSERVE_TX 0x08
#define CD 0x09
#define RX_ADDR_P0 0x0A
#define RX_ADDR_P1 0x0B
#define RX_ADDR_P2 0x0C
#define RX_ADDR_P3 0x0D
#define RX_ADDR_P4 0x0E
#define RX_ADDR_P5 0x0F
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
#define FIFO_STATUS 0x17
#define DYNPD 0x1C
#define FEATURE 0x1D
/* Bit Mnemonics */
#define MASK_RX_DR 6
#define MASK_TX_DS 5
#define MASK_MAX_RT 4
#define EN_CRC 3
#define CRCO 2
#define PWR_UP 1
#define PRIM_RX 0
#define ENAA_P5 5
#define ENAA_P4 4
#define ENAA_P3 3
#define ENAA_P2 2
#define ENAA_P1 1
#define ENAA_P0 0
#define ERX_P5 5
#define ERX_P4 4
#define ERX_P3 3
#define ERX_P2 2
#define ERX_P1 1
#define ERX_P0 0
#define AW 0
#define ARD 4
#define ARC 0
#define PLL_LOCK 4
#define RF_DR 3
#define RF_PWR 6
#define RX_DR 6
#define TX_DS 5
#define MAX_RT 4
#define RX_P_NO 1
#define TX_FULL 0
#define PLOS_CNT 4
#define ARC_CNT 0
#define TX_REUSE 6
#define FIFO_FULL 5
#define TX_EMPTY 4
#define RX_FULL 1
#define RX_EMPTY 0
#define DPL_P5 5
#define DPL_P4 4
#define DPL_P3 3
#define DPL_P2 2
#define DPL_P1 1
#define DPL_P0 0
#define EN_DPL 2
#define EN_ACK_PAY 1
#define EN_DYN_ACK 0
/* Instruction Mnemonics */
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define REGISTER_MASK 0x1F
#define ACTIVATE 0x50
#define R_RX_PL_WID 0x60
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define W_ACK_PAYLOAD 0xA8
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define NOP 0xFF
/* Non-P omissions */
#define LNA_HCURR 0
/* P model memory Map */
#define RPD 0x09
/* P model bit Mnemonics */
#define RF_DR_LOW 5
#define RF_DR_HIGH 3
#define RF_PWR_HIGH 2
#define RF_PWR_LOW 1
native/pre do
static const uint8_t child_pipe_enable[] PROGMEM = {
ERX_P0, ERX_P1, ERX_P2, ERX_P3, ERX_P4, ERX_P5
};
static const uint8_t child_pipe[] PROGMEM = {
RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5
};
static const uint8_t child_payload_size[] PROGMEM = {
RX_PW_P0, RX_PW_P1, RX_PW_P2, RX_PW_P3, RX_PW_P4, RX_PW_P5
};
end
///////////////////////////////////////////////////////////////////////////////
// ABSTRACTIONS
///////////////////////////////////////////////////////////////////////////////
#include "wclock.ceu"
code/await NRF24L01_Write (var& NRF24L01_Data nrf, var byte reg, var byte value) -> byte do
lock nrf.spi do
watching SPI_Transaction(4000000, SPI_MSBFIRST, SPI_MODE0, _, nrf.csn) do
var[2] byte vs = [ (W_REGISTER | (REGISTER_MASK & reg)), value ];
await SPI_Transfer(&vs);
escape vs[0];
end
end
end
code/await NRF24L01_Writes (var& NRF24L01_Data nrf, var byte reg, var&[] byte values) -> byte do
lock nrf.spi do
watching SPI_Transaction(4000000, SPI_MSBFIRST, SPI_MODE0, _, nrf.csn) do
var byte status = await SPI_Transfer_8(W_REGISTER | (REGISTER_MASK & reg));
await SPI_Transfer(&values);
escape status;
end
end
end
code/await NRF24L01_Read (var& NRF24L01_Data nrf, var byte reg) -> byte do
lock nrf.spi do
watching SPI_Transaction(4000000, SPI_MSBFIRST, SPI_MODE0, _, nrf.csn) do
var[2] byte vs = [ (R_REGISTER | (REGISTER_MASK & reg)), 0xFF ];
await SPI_Transfer(&vs);
escape vs[1];
end
end
end
code/await NRF24L01_Init (var& NRF24L01_Data nrf) -> NEVER
do
_pinMode(nrf.ce, _OUTPUT);
_pinMode(nrf.csn, _OUTPUT);
_digitalWrite(nrf.ce, _LOW);
_digitalWrite(nrf.csn, _HIGH);
await 5ms; // see RF24 library
// set RF channel (76???)
await NRF24L01_Write(&nrf, RF_CH, 76);
// RF24_2MBPS, 0dBm
//await NRF24L01_Write(&nrf, RF_SETUP, _bit(RF_DR_HIGH)|_bit(RF_PWR_LOW)|_bit(RF_PWR_HIGH));
// 1MBPS
await NRF24L01_Write(&nrf, RF_SETUP, _bit(RF_DR_LOW)|_bit(RF_DR_HIGH)|_bit(RF_PWR_LOW)|_bit(RF_PWR_HIGH));
// set pipe addresses, lengths
await NRF24L01_Write(&nrf, EN_RXADDR, _bit(ERX_P0)); // enables RX0 pipe
do
var[5] byte addr = [0xEE, 0xDD, 0xCC, 0xBB, 0xAA];
await NRF24L01_Writes(&nrf, RX_ADDR_P0, &addr); // RX0 address
end
await NRF24L01_Write(&nrf, RX_PW_P0, 1); // RX0 payload length
do
var[5] byte addr = [0xEE, 0xDD, 0xCC, 0xBB, 0xAA];
await NRF24L01_Writes(&nrf, TX_ADDR, &addr); // TX address
end
// ACK=no
await NRF24L01_Write(&nrf, EN_AA, 0);
// clears ISRs
await NRF24L01_Write(&nrf, STATUS, _bit(RX_DR)|_bit(TX_DS)|_bit(MAX_RT));
// flush buffers
lock nrf.spi do
watching SPI_Transaction(4000000, SPI_MSBFIRST, SPI_MODE0, _, nrf.csn) do
await SPI_Transfer_8(FLUSH_RX);
end
watching SPI_Transaction(4000000, SPI_MSBFIRST, SPI_MODE0, _, nrf.csn) do
await SPI_Transfer_8(FLUSH_TX);
end
end
// CRC = 2 bytes
// POWER UP (TODO: finalize power down)
nrf.config = (_bit(PWR_UP) | _bit(EN_CRC) | _bit(CRCO));
await NRF24L01_Write(&nrf, CONFIG, nrf.config);
spawn do
await 150ms; // power up
emit nrf.ok;
end
await FOREVER;
end
code/await NRF24L01_Rx (var& NRF24L01_Data nrf, var&[] byte buf) -> u8
do
await NRF24L01_Write(&nrf, RX_PW_P0, $$buf as byte);
await NRF24L01_Write(&nrf, STATUS, _bit(RX_DR) | _bit(TX_DS) | _bit(MAX_RT));
await NRF24L01_Write(&nrf, CONFIG, nrf.config | _bit(PRIM_RX) | _bit(MASK_TX_DS) | _bit(MASK_MAX_RT));
_digitalWrite(nrf.ce, 1);
do finalize with
_digitalWrite(nrf.ce, 0);
end
await NRF24L01_IRQ;
// read status for receiving pipe
var u8 pipe = do
lock nrf.spi do
watching SPI_Transaction(4000000, SPI_MSBFIRST, SPI_MODE0, _, nrf.csn) do
var byte v = await SPI_Transfer_8(0xFF);
escape v as u8;
end
end
end;
pipe = (pipe >> RX_P_NO) & 0x07;
lock nrf.spi do
watching SPI_Transaction(4000000, SPI_MSBFIRST, SPI_MODE0, _, nrf.csn) do
await SPI_Transfer_8(R_RX_PAYLOAD);
var usize i;
loop i in [0 -> $$buf[ do
buf = buf .. [0xFF];
end
await SPI_Transfer(&buf);
end
end
await NRF24L01_Write(&nrf, STATUS, _bit(RX_DR));
escape pipe;
end
code/await NRF24L01_Tx (var& NRF24L01_Data nrf, var&[] byte buf) -> bool
do
await NRF24L01_Write(&nrf, STATUS, _bit(RX_DR) | _bit(TX_DS) | _bit(MAX_RT));
watching NRF24L01_IRQ do
await NRF24L01_Write(&nrf, CONFIG, nrf.config | _bit(MASK_RX_DR));
lock nrf.spi do
watching SPI_Transaction(4000000, SPI_MSBFIRST, SPI_MODE0, _, nrf.csn) do
await SPI_Transfer_8(W_TX_PAYLOAD);
var usize i;
#if 1
// needed for non-dynamic payloads
loop i in [$buf -> $$buf[ do
buf = buf .. [0];
end
#endif
await SPI_Transfer(&buf);
end
end
_digitalWrite(nrf.ce, 1);
_delayMicroseconds(10);
_digitalWrite(nrf.ce, 0);
//await 1ms;
_delayMicroseconds(125); // TODO: this line is required, why?
// - needed before MCU sleep?
// - just luck?
// - 100us is not enough
// - await 1ms is enough
// - but puts MCU to sleep
// - so is it just luck?
await FOREVER;
end
// ISR -> event
var bool ret = do
var byte status = await NRF24L01_Read(&nrf, STATUS);
if _bitRead(status, {TX_DS}) as bool then
escape true;
else/if _bitRead(status, {MAX_RT}) as bool then
escape false;
else
_ceu_arduino_assert(0, 4);
_ceu_assert(0, "unexpected interrupt");
end
end;
await NRF24L01_Write(&nrf, STATUS, _bit(TX_DS) | _bit(MAX_RT));
escape ret;
end
code/await NRF24L01_Set_Channel (var& NRF24L01_Data nrf, var u8 channel) -> none do
_ceu_assert(channel <= 125, "invalid channel");
await NRF24L01_Write(&nrf, RF_CH, channel as byte);
end
#define NRF24L01_IDX(i) {(((byte*) &@(address))[i])}
code/await NRF24L01_Open_Reading_Pipe (var& NRF24L01_Data nrf, var u8 pipe, var u64 address) -> none do
_ceu_assert(pipe < 6, "invalid pipe");
// If this is pipe 0, cache the address. This is needed because
// openWritingPipe() will overwrite the pipe 0 address, so
// startListening() will have to restore it.
if pipe == 0 then
nrf.pipe0 = address;
end
// For pipes 2-5, only write the LSB
if pipe < 2 then
var[5] byte address_ = [NRF24L01_IDX(0), NRF24L01_IDX(1), NRF24L01_IDX(2), NRF24L01_IDX(3), NRF24L01_IDX(4)];
await NRF24L01_Writes(&nrf, {pgm_read_byte(&child_pipe[@pipe])}, &address_);
else
await NRF24L01_Write(&nrf, {pgm_read_byte(&child_pipe[@pipe])}, NRF24L01_IDX(0));
end
await NRF24L01_Write(&nrf, {pgm_read_byte(&child_payload_size[@pipe])}, nrf.payload_size as byte);
// Note it would be more efficient to set all of the bits for all open
// pipes at once. However, I thought it would make the calling code
// more simple to do it this way.
var byte en_rxaddr = await NRF24L01_Read(&nrf, EN_RXADDR);
await NRF24L01_Write(&nrf, EN_RXADDR, en_rxaddr | {_BV(pgm_read_byte(&child_pipe_enable[@pipe]))});
end
code/await NRF24L01_Open_Writing_Pipe (var& NRF24L01_Data nrf, var u64 address) -> none do
var[5] byte address_ = [NRF24L01_IDX(0), NRF24L01_IDX(1), NRF24L01_IDX(2), NRF24L01_IDX(3), NRF24L01_IDX(4)];
await NRF24L01_Writes(&nrf, RX_ADDR_P0, &address_);
await NRF24L01_Writes(&nrf, TX_ADDR, &address_);
await NRF24L01_Write(&nrf, RX_PW_P0, nrf.payload_size as byte);
end
code/await NRF24L01_Set_Auto_Ack (var& NRF24L01_Data nrf, var u8 pipe, var bool enable) -> none do
_ceu_assert(pipe < 6, "invalid pipe");
var byte en_aa = await NRF24L01_Read(&nrf, EN_AA);
if enable then
en_aa = en_aa | _bit(pipe);
else
en_aa = en_aa & ~_bit(pipe);
end
await NRF24L01_Write(&nrf, EN_AA, en_aa);
end