Line data Source code
1 : #include "LR_common.h"
2 :
3 : #include <string.h>
4 :
5 17 : LRxxxx::LRxxxx(Module* mod) : PhysicalLayer() {
6 17 : this->mod = mod;
7 17 : this->XTAL = false;
8 17 : this->mod->spiConfig.stream = true;
9 17 : this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_16;
10 17 : this->mod->spiConfig.statusPos = 0;
11 17 : this->mod->spiConfig.parseStatusCb = SPIparseStatus;
12 17 : this->mod->spiConfig.checkStatusCb = SPIcheckStatus;
13 17 : }
14 :
15 0 : void LRxxxx::setIrqAction(void (*func)(void)) {
16 0 : this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, this->mod->hal->GpioInterruptRising);
17 0 : }
18 :
19 0 : void LRxxxx::clearIrqAction() {
20 0 : this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()));
21 0 : }
22 :
23 0 : void LRxxxx::setPacketReceivedAction(void (*func)(void)) {
24 0 : this->setIrqAction(func);
25 0 : }
26 :
27 0 : void LRxxxx::clearPacketReceivedAction() {
28 0 : this->clearIrqAction();
29 0 : }
30 :
31 0 : void LRxxxx::setPacketSentAction(void (*func)(void)) {
32 0 : this->setIrqAction(func);
33 0 : }
34 :
35 0 : void LRxxxx::clearPacketSentAction() {
36 0 : this->clearIrqAction();
37 0 : }
38 :
39 4 : uint32_t LRxxxx::getIrqStatus() {
40 : // there is no dedicated "get IRQ" command, the IRQ bits are sent after the status bytes
41 4 : uint8_t buff[6] = { 0 };
42 4 : Module::BitWidth_t statusWidth = mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS];
43 4 : this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_0;
44 4 : mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true);
45 4 : this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = statusWidth;
46 4 : uint32_t irq = ((uint32_t)(buff[2]) << 24) | ((uint32_t)(buff[3]) << 16) | ((uint32_t)(buff[4]) << 8) | (uint32_t)buff[5];
47 4 : return(irq);
48 : }
49 :
50 3 : RadioLibTime_t LRxxxx::getTimeOnAir(size_t len, ModemType_t modem) {
51 3 : DataRate_t dr = {};
52 3 : PacketConfig_t pc = {};
53 3 : switch(modem) {
54 0 : case ModemType_t::RADIOLIB_MODEM_LORA: {
55 0 : uint8_t cr = this->codingRate;
56 : // We assume same calculation for short and long interleaving, so map CR values 0-4 and 5-7 to the same values
57 0 : if (cr < 5) {
58 0 : cr = cr + 4;
59 0 : } else if (cr == 7) {
60 0 : cr = cr + 1;
61 : }
62 :
63 0 : dr.lora.spreadingFactor = this->spreadingFactor;
64 0 : dr.lora.bandwidth = this->bandwidthKhz;
65 0 : dr.lora.codingRate = cr;
66 :
67 0 : pc.lora.preambleLength = this->preambleLengthLoRa;
68 0 : pc.lora.implicitHeader = (this->headerType == RADIOLIB_LRXXXX_LORA_HEADER_IMPLICIT);
69 0 : pc.lora.crcEnabled = (this->crcTypeLoRa == RADIOLIB_LRXXXX_LORA_CRC_ENABLED);
70 0 : pc.lora.ldrOptimize = (bool)this->ldrOptimize;
71 0 : break;
72 : }
73 :
74 0 : case ModemType_t::RADIOLIB_MODEM_FSK: {
75 0 : dr.fsk.bitRate = (float)this->bitRate / 1000.0f;
76 0 : dr.fsk.freqDev = (float)this->frequencyDev;
77 0 : pc.fsk.preambleLength = this->preambleLengthGFSK;
78 0 : pc.fsk.syncWordLength = this->syncWordLength;
79 0 : pc.fsk.crcLength = this->crcLenGFSK;
80 0 : break;
81 : }
82 :
83 0 : case ModemType_t::RADIOLIB_MODEM_LRFHSS: {
84 0 : dr.lrFhss.bw = this->lrFhssBw;
85 0 : dr.lrFhss.cr = this->lrFhssCr;
86 0 : dr.lrFhss.narrowGrid = (this->lrFhssGrid == RADIOLIB_LRXXXX_LR_FHSS_GRID_STEP_NON_FCC) ? true : false;
87 :
88 0 : pc.lrFhss.hdrCount = this->lrFhssHdrCount;
89 0 : break;
90 : }
91 :
92 3 : default:
93 3 : return(RADIOLIB_ERR_WRONG_MODEM);
94 : }
95 :
96 0 : return(this->calculateTimeOnAir(modem, dr, pc, len));
97 : }
98 :
99 17 : RadioLibTime_t LRxxxx::calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len) {
100 : // check active modem
101 17 : if (modem == ModemType_t::RADIOLIB_MODEM_LORA) {
102 6 : uint32_t symbolLength_us = ((uint32_t)(1000 * 10) << dr.lora.spreadingFactor) / (dr.lora.bandwidth * 10) ;
103 6 : uint8_t sfCoeff1_x4 = 17; // (4.25 * 4)
104 6 : uint8_t sfCoeff2 = 8;
105 6 : if(dr.lora.spreadingFactor == 5 || dr.lora.spreadingFactor == 6) {
106 0 : sfCoeff1_x4 = 25; // 6.25 * 4
107 0 : sfCoeff2 = 0;
108 : }
109 6 : uint8_t sfDivisor = 4*dr.lora.spreadingFactor;
110 6 : if(pc.lora.ldrOptimize) {
111 3 : sfDivisor = 4*(dr.lora.spreadingFactor - 2);
112 : }
113 6 : const int8_t bitsPerCrc = 16;
114 6 : const int8_t N_symbol_header = pc.lora.implicitHeader ? 0 : 20;
115 :
116 : // numerator of equation in section 6.1.4 of SX1268 datasheet v1.1 (might not actually be bitcount, but it has len * 8)
117 6 : int16_t bitCount = (int16_t) 8 * len + pc.lora.crcEnabled * bitsPerCrc - 4 * dr.lora.spreadingFactor + sfCoeff2 + N_symbol_header;
118 6 : if(bitCount < 0) {
119 0 : bitCount = 0;
120 : }
121 : // add (sfDivisor) - 1 to the numerator to give integer CEIL(...)
122 6 : uint16_t nPreCodedSymbols = (bitCount + (sfDivisor - 1)) / (sfDivisor);
123 :
124 : // preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit
125 6 : uint32_t nSymbol_x4 = (pc.lora.preambleLength + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * dr.lora.codingRate * 4;
126 :
127 : // get time-on-air in us
128 6 : return((symbolLength_us * nSymbol_x4) / 4);
129 :
130 11 : } else if(modem == ModemType_t::RADIOLIB_MODEM_FSK) {
131 4 : return((((float)(pc.fsk.crcLength * 8) + pc.fsk.syncWordLength + pc.fsk.preambleLength + (uint32_t)len * 8) / (dr.fsk.bitRate / 1000.0f)));
132 :
133 7 : } else if(modem == ModemType_t::RADIOLIB_MODEM_LRFHSS) {
134 : // calculate the number of bits based on coding rate
135 : uint16_t N_bits;
136 3 : switch(dr.lrFhss.cr) {
137 0 : case RADIOLIB_LRXXXX_LR_FHSS_CR_5_6:
138 0 : N_bits = ((len * 6) + 4) / 5; // this is from the official LR11xx driver, but why the extra +4?
139 0 : break;
140 0 : case RADIOLIB_LRXXXX_LR_FHSS_CR_2_3:
141 0 : N_bits = (len * 3) / 2;
142 0 : break;
143 0 : case RADIOLIB_LRXXXX_LR_FHSS_CR_1_2:
144 0 : N_bits = len * 2;
145 0 : break;
146 3 : case RADIOLIB_LRXXXX_LR_FHSS_CR_1_3:
147 3 : N_bits = len * 3;
148 3 : break;
149 0 : default:
150 0 : return(RADIOLIB_ERR_INVALID_CODING_RATE);
151 : }
152 :
153 : // calculate number of bits when accounting for unaligned last block
154 3 : uint16_t N_payBits = (N_bits / RADIOLIB_LRXXXX_LR_FHSS_FRAG_BITS) * RADIOLIB_LRXXXX_LR_FHSS_BLOCK_BITS;
155 3 : uint16_t N_lastBlockBits = N_bits % RADIOLIB_LRXXXX_LR_FHSS_FRAG_BITS;
156 3 : if(N_lastBlockBits) {
157 3 : N_payBits += N_lastBlockBits + 2;
158 : }
159 :
160 : // add header bits
161 3 : uint16_t N_totalBits = (RADIOLIB_LRXXXX_LR_FHSS_HEADER_BITS * pc.lrFhss.hdrCount) + N_payBits;
162 3 : return(((uint32_t)N_totalBits * 8 * 1000000UL) / RADIOLIB_LRXXXX_LR_FHSS_BIT_RATE);
163 :
164 : } else {
165 4 : return(RADIOLIB_ERR_WRONG_MODEM);
166 : }
167 :
168 : return(0);
169 : }
170 :
171 4 : RadioLibTime_t LRxxxx::calculateRxTimeout(RadioLibTime_t timeoutUs) {
172 : // the timeout value is given in units of 30.52 microseconds
173 : // the calling function should provide some extra width, as this number of units is truncated to integer
174 4 : RadioLibTime_t timeout = timeoutUs / 30.52;
175 4 : return(timeout);
176 : }
177 :
178 0 : int16_t LRxxxx::reset() {
179 : // run the reset sequence
180 0 : this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput);
181 0 : this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow);
182 0 : this->mod->hal->delay(10);
183 0 : this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh);
184 :
185 : // the typical transition duration should be 273 ms
186 0 : this->mod->hal->delay(300);
187 :
188 : // wait for BUSY to go low
189 0 : RadioLibTime_t start = this->mod->hal->millis();
190 0 : while(this->mod->hal->digitalRead(this->mod->getGpio())) {
191 0 : this->mod->hal->yield();
192 0 : if(this->mod->hal->millis() - start >= 3000) {
193 : RADIOLIB_DEBUG_BASIC_PRINTLN("BUSY pin timeout after reset!");
194 0 : return(RADIOLIB_ERR_SPI_CMD_TIMEOUT);
195 : }
196 : }
197 :
198 0 : return(RADIOLIB_ERR_NONE);
199 : }
200 :
201 0 : int16_t LRxxxx::getStatus(uint8_t* stat1, uint8_t* stat2, uint32_t* irq) {
202 0 : uint8_t buff[6] = { 0 };
203 :
204 : // the status check command doesn't return status in the same place as other read commands
205 : // but only as the first byte (as with any other command), hence LRxxxx::SPIcommand can't be used
206 : // it also seems to ignore the actual command, and just sending in bunch of NOPs will work
207 0 : int16_t state = this->mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true);
208 :
209 : // pass the replies
210 0 : if(stat1) { *stat1 = buff[0]; }
211 0 : if(stat2) { *stat2 = buff[1]; }
212 0 : if(irq) { *irq = ((uint32_t)(buff[2]) << 24) | ((uint32_t)(buff[3]) << 16) | ((uint32_t)(buff[4]) << 8) | (uint32_t)buff[5]; }
213 :
214 0 : return(state);
215 : }
216 :
217 0 : int16_t LRxxxx::lrFhssBuildFrame(uint16_t cmd, uint8_t hdrCount, uint8_t cr, uint8_t grid, uint8_t hop, uint8_t bw, uint16_t hopSeq, int8_t devOffset, const uint8_t* payload, size_t len) {
218 : // check maximum size
219 0 : const uint8_t maxLen[4][4] = {
220 : { 189, 178, 167, 155, },
221 : { 151, 142, 133, 123, },
222 : { 112, 105, 99, 92, },
223 : { 74, 69, 65, 60, },
224 : };
225 0 : if((cr > RADIOLIB_LRXXXX_LR_FHSS_CR_1_3) || ((hdrCount - 1) > (int)sizeof(maxLen[0])) || (len > maxLen[cr][hdrCount - 1])) {
226 0 : return(RADIOLIB_ERR_SPI_CMD_INVALID);
227 : }
228 :
229 : // build buffers
230 0 : size_t buffLen = 9 + len;
231 : #if RADIOLIB_STATIC_ONLY
232 : uint8_t dataBuff[9 + 190];
233 : #else
234 0 : uint8_t* dataBuff = new uint8_t[buffLen];
235 : #endif
236 :
237 : // set properties of the packet
238 0 : dataBuff[0] = hdrCount;
239 0 : dataBuff[1] = cr;
240 0 : dataBuff[2] = RADIOLIB_LRXXXX_LR_FHSS_MOD_TYPE_GMSK;
241 0 : dataBuff[3] = grid;
242 0 : dataBuff[4] = hop;
243 0 : dataBuff[5] = bw;
244 0 : dataBuff[6] = (uint8_t)((hopSeq >> 8) & 0x01);
245 0 : dataBuff[7] = (uint8_t)(hopSeq & 0xFF);
246 0 : dataBuff[8] = devOffset;
247 0 : memcpy(&dataBuff[9], payload, len);
248 :
249 0 : int16_t state = this->SPIcommand(cmd, true, dataBuff, buffLen);
250 : #if !RADIOLIB_STATIC_ONLY
251 0 : delete[] dataBuff;
252 : #endif
253 0 : return(state);
254 : }
255 :
256 3 : uint8_t LRxxxx::roundRampTime(uint32_t rampTimeUs) {
257 : uint8_t regVal;
258 :
259 : // Round up the ramp time to nearest discrete register value
260 3 : if(rampTimeUs <= 2) {
261 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_2U;
262 3 : } else if(rampTimeUs <= 4) {
263 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_4U;
264 3 : } else if(rampTimeUs <= 8) {
265 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_8U;
266 3 : } else if(rampTimeUs <= 16) {
267 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_16U;
268 3 : } else if(rampTimeUs <= 32) {
269 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_32U;
270 3 : } else if(rampTimeUs <= 48) {
271 3 : regVal = RADIOLIB_LRXXXX_PA_RAMP_48U;
272 0 : } else if(rampTimeUs <= 64) {
273 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_64U;
274 0 : } else if(rampTimeUs <= 80) {
275 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_80U;
276 0 : } else if(rampTimeUs <= 96) {
277 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_96U;
278 0 : } else if(rampTimeUs <= 112) {
279 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_112U;
280 0 : } else if(rampTimeUs <= 128) {
281 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_128U;
282 0 : } else if(rampTimeUs <= 144) {
283 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_144U;
284 0 : } else if(rampTimeUs <= 160) {
285 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_160U;
286 0 : } else if(rampTimeUs <= 176) {
287 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_176U;
288 0 : } else if(rampTimeUs <= 192) {
289 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_192U;
290 0 : } else if(rampTimeUs <= 208) {
291 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_208U;
292 0 : } else if(rampTimeUs <= 240) {
293 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_240U;
294 0 : } else if(rampTimeUs <= 272) {
295 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_272U;
296 : } else { // 304
297 0 : regVal = RADIOLIB_LRXXXX_PA_RAMP_304U;
298 : }
299 :
300 3 : return regVal;
301 : }
302 :
303 0 : int16_t LRxxxx::findRxBw(float rxBw, const uint8_t* lut, size_t lutSize, float rxBwMax, uint8_t* val) {
304 : // lookup tables to avoid comparing a whole bunch of floats
305 0 : const uint16_t rxBwAvg[] = {
306 : 53, 66, 85, 108, 134, 170, 211, 264,
307 : 341, 424, 529, 682, 847, 1058, 1364,
308 : 1695, 2116, 2729, 3390, 4233, 5159,
309 : 6111, 7179, 9401, 16665, 24440, 28710,
310 : };
311 :
312 : // iterate through the table and find whether the user-provided value
313 : // is lower than the pre-computed average of the adjacent bandwidth values
314 : // if it is, we consider that to be a match even though the actual value is not precise
315 0 : uint16_t rxBwInt = rxBw*10.0f;
316 0 : for(size_t i = 0; i < lutSize; i++) {
317 0 : if(rxBwInt < rxBwAvg[i]) {
318 0 : *val = lut[i];
319 0 : return(RADIOLIB_ERR_NONE);
320 : }
321 : }
322 :
323 : // if nothing matched up to here, match with the last value
324 0 : if(rxBwInt <= rxBwMax*10) {
325 0 : *val = lut[lutSize - 1];
326 0 : return(RADIOLIB_ERR_NONE);
327 : }
328 :
329 0 : return(RADIOLIB_ERR_INVALID_RX_BANDWIDTH);
330 : }
331 :
332 8 : int16_t LRxxxx::setU32(uint16_t cmd, uint32_t u32) {
333 : uint8_t buff[] = {
334 8 : (uint8_t)((u32 >> 24) & 0xFF), (uint8_t)((u32 >> 16) & 0xFF),
335 8 : (uint8_t)((u32 >> 8) & 0xFF), (uint8_t)(u32 & 0xFF),
336 8 : };
337 16 : return(this->SPIcommand(cmd, true, buff, sizeof(buff)));
338 : }
339 :
340 3059 : int16_t LRxxxx::SPIparseStatus(uint8_t in) {
341 3059 : if((in & 0b00001110) == RADIOLIB_LRXXXX_STAT_1_CMD_PERR) {
342 0 : return(RADIOLIB_ERR_SPI_CMD_INVALID);
343 3059 : } else if((in & 0b00001110) == RADIOLIB_LRXXXX_STAT_1_CMD_FAIL) {
344 0 : return(RADIOLIB_ERR_SPI_CMD_FAILED);
345 3059 : } else if((in == 0x00) || (in == 0xFF)) {
346 3059 : return(RADIOLIB_ERR_CHIP_NOT_FOUND);
347 : }
348 0 : return(RADIOLIB_ERR_NONE);
349 : }
350 :
351 9 : int16_t LRxxxx::SPIcheckStatus(Module* mod) {
352 : // the status check command doesn't return status in the same place as other read commands,
353 : // but only as the first byte (as with any other command), hence LR11x0::SPIcommand can't be used
354 : // it also seems to ignore the actual command, and just sending in bunch of NOPs will work
355 9 : uint8_t buff[6] = { 0 };
356 9 : Module::BitWidth_t statusWidth = mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS];
357 9 : mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_0;
358 9 : int16_t state = mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true);
359 9 : mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = statusWidth;
360 9 : RADIOLIB_ASSERT(state);
361 0 : return(LRxxxx::SPIparseStatus(buff[0]));
362 : }
363 :
364 0 : int16_t LRxxxx::writeCommon(uint16_t cmd, uint32_t addrOffset, const uint32_t* data, size_t len, bool nonvolatile) {
365 : // build buffers - later we need to ensure endians are correct,
366 : // so there is probably no way to do this without copying buffers and iterating
367 0 : size_t buffLen = sizeof(uint32_t) + len*sizeof(uint32_t);
368 : #if RADIOLIB_STATIC_ONLY
369 : uint8_t dataBuff[sizeof(uint32_t) + RADIOLIB_LRXXXX_SPI_MAX_READ_WRITE_LEN];
370 : #else
371 0 : uint8_t* dataBuff = new uint8_t[buffLen];
372 : #endif
373 :
374 : // set the address or offset
375 0 : uint8_t* dataBuffPtr = reinterpret_cast<uint8_t*>(dataBuff);
376 0 : if(this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] >= Module::BITS_32) {
377 : // LR2021 has 24-bit address, whereas LR11x0 has 32-bit
378 0 : *(dataBuffPtr++) = (uint8_t)((addrOffset >> 24) & 0xFF);
379 : }
380 :
381 0 : *(dataBuffPtr++) = (uint8_t)((addrOffset >> 16) & 0xFF);
382 0 : *(dataBuffPtr++) = (uint8_t)((addrOffset >> 8) & 0xFF);
383 0 : *(dataBuffPtr++) = (uint8_t)(addrOffset & 0xFF);
384 :
385 : // convert endians
386 0 : for(size_t i = 0; i < len; i++) {
387 0 : uint32_t bin = 0;
388 0 : if(nonvolatile) {
389 0 : uint32_t* ptr = const_cast<uint32_t*>(data) + i;
390 0 : bin = RADIOLIB_NONVOLATILE_READ_DWORD(ptr);
391 : } else {
392 0 : bin = data[i];
393 : }
394 0 : *(dataBuffPtr++) = (uint8_t)((bin >> 24) & 0xFF);
395 0 : *(dataBuffPtr++) = (uint8_t)((bin >> 16) & 0xFF);
396 0 : *(dataBuffPtr++) = (uint8_t)((bin >> 8) & 0xFF);
397 0 : *(dataBuffPtr++) = (uint8_t)(bin & 0xFF);
398 : }
399 :
400 0 : int16_t state = this->mod->SPIwriteStream(cmd, dataBuff, buffLen, true, false);
401 : #if !RADIOLIB_STATIC_ONLY
402 0 : delete[] dataBuff;
403 : #endif
404 0 : return(state);
405 : }
406 :
407 149 : int16_t LRxxxx::SPIcommand(uint16_t cmd, bool write, uint8_t* data, size_t len, const uint8_t* out, size_t outLen) {
408 149 : int16_t state = RADIOLIB_ERR_UNKNOWN;
409 149 : if(!write) {
410 : // the SPI interface of LR11x0 requires two separate transactions for reading
411 : // send the 16-bit command
412 101 : state = this->mod->SPIwriteStream(cmd, out, outLen, true, false);
413 101 : RADIOLIB_ASSERT(state);
414 :
415 : // read the result without command
416 101 : this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_0;
417 101 : state = this->mod->SPIreadStream(RADIOLIB_LRXXXX_CMD_NOP, data, len, true, false);
418 101 : this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_16;
419 :
420 : } else {
421 : // write is just a single transaction
422 48 : state = this->mod->SPIwriteStream(cmd, data, len, true, true);
423 :
424 : }
425 :
426 149 : return(state);
427 : }
|