components/esp32-owb/owb_rmt.c

changeset 0
88d965579617
child 72
acc1904cd70d
equal deleted inserted replaced
-1:000000000000 0:88d965579617
1 /*
2 Created by Chris Morgan based on the nodemcu project driver.
3 Copyright 2017 Chris Morgan <chmorgan@gmail.com>
4
5 Ported to ESP32 RMT peripheral for low-level signal generation by Arnim Laeuger.
6
7 Permission is hereby granted, free of charge, to any person obtaining
8 a copy of this software and associated documentation files (the
9 "Software"), to deal in the Software without restriction, including
10 without limitation the rights to use, copy, modify, merge, publish,
11 distribute, sublicense, and/or sell copies of the Software, and to
12 permit persons to whom the Software is furnished to do so, subject to
13 the following conditions:
14
15 The above copyright notice and this permission notice shall be
16 included in all copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26 Much of the code was inspired by Derek Yerger's code, though I don't
27 think much of that remains. In any event that was..
28 (copyleft) 2006 by Derek Yerger - Free to distribute freely.
29
30 The CRC code was excerpted and inspired by the Dallas Semiconductor
31 sample code bearing this copyright.
32 //---------------------------------------------------------------------------
33 // Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
34 //
35 // Permission is hereby granted, free of charge, to any person obtaining a
36 // copy of this software and associated documentation files (the "Software"),
37 // to deal in the Software without restriction, including without limitation
38 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
39 // and/or sell copies of the Software, and to permit persons to whom the
40 // Software is furnished to do so, subject to the following conditions:
41 //
42 // The above copyright notice and this permission notice shall be included
43 // in all copies or substantial portions of the Software.
44 //
45 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
46 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
47 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
48 // IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
49 // OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
50 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
51 // OTHER DEALINGS IN THE SOFTWARE.
52 //
53 // Except as contained in this notice, the name of Dallas Semiconductor
54 // shall not be used except as stated in the Dallas Semiconductor
55 // Branding Policy.
56 //--------------------------------------------------------------------------
57 */
58
59 #include "owb.h"
60
61 #include "driver/rmt.h"
62 #include "driver/gpio.h"
63 #include "esp_log.h"
64
65 #undef OW_DEBUG
66
67
68 // bus reset: duration of low phase [us]
69 #define OW_DURATION_RESET 480
70 // overall slot duration
71 #define OW_DURATION_SLOT 75
72 // write 1 slot and read slot durations [us]
73 #define OW_DURATION_1_LOW 2
74 #define OW_DURATION_1_HIGH (OW_DURATION_SLOT - OW_DURATION_1_LOW)
75 // write 0 slot durations [us]
76 #define OW_DURATION_0_LOW 65
77 #define OW_DURATION_0_HIGH (OW_DURATION_SLOT - OW_DURATION_0_LOW)
78 // sample time for read slot
79 #define OW_DURATION_SAMPLE (15-2)
80 // RX idle threshold
81 // needs to be larger than any duration occurring during write slots
82 #define OW_DURATION_RX_IDLE (OW_DURATION_SLOT + 2)
83
84
85 static const char * TAG = "owb_rmt";
86
87 #define info_of_driver(owb) container_of(owb, owb_rmt_driver_info, bus)
88
89 // flush any pending/spurious traces from the RX channel
90 static void onewire_flush_rmt_rx_buf(const OneWireBus * bus)
91 {
92 void *p;
93 size_t s;
94
95 owb_rmt_driver_info *i = info_of_driver(bus);
96
97 while ((p = xRingbufferReceive(i->rb, &s, 0)))
98 {
99 ESP_LOGD(TAG, "flushing entry");
100 vRingbufferReturnItem(i->rb, p);
101 }
102 }
103
104 static owb_status _reset(const OneWireBus *bus, bool *is_present)
105 {
106 rmt_item32_t tx_items[1];
107 bool _is_present = false;
108 int res = OWB_STATUS_OK;
109
110 owb_rmt_driver_info *i = info_of_driver(bus);
111
112 tx_items[0].duration0 = OW_DURATION_RESET;
113 tx_items[0].level0 = 0;
114 tx_items[0].duration1 = 0;
115 tx_items[0].level1 = 1;
116
117 uint16_t old_rx_thresh;
118 rmt_get_rx_idle_thresh(i->rx_channel, &old_rx_thresh);
119 rmt_set_rx_idle_thresh(i->rx_channel, OW_DURATION_RESET+60);
120
121 onewire_flush_rmt_rx_buf(bus);
122 rmt_rx_start(i->rx_channel, true);
123 if (rmt_write_items(i->tx_channel, tx_items, 1, true) == ESP_OK)
124 {
125 size_t rx_size;
126 rmt_item32_t* rx_items = (rmt_item32_t *)xRingbufferReceive(i->rb, &rx_size, 100 / portTICK_PERIOD_MS);
127
128 if (rx_items)
129 {
130 if (rx_size >= (1 * sizeof(rmt_item32_t)))
131 {
132 #ifdef OW_DEBUG
133 ESP_LOGI(TAG, "rx_size: %d", rx_size);
134
135 for (int i = 0; i < (rx_size / sizeof(rmt_item32_t)); i++)
136 {
137 ESP_LOGI(TAG, "i: %d, level0: %d, duration %d", i, rx_items[i].level0, rx_items[i].duration0);
138 ESP_LOGI(TAG, "i: %d, level1: %d, duration %d", i, rx_items[i].level1, rx_items[i].duration1);
139 }
140 #endif
141
142 // parse signal and search for presence pulse
143 if ((rx_items[0].level0 == 0) && (rx_items[0].duration0 >= OW_DURATION_RESET - 2))
144 {
145 if ((rx_items[0].level1 == 1) && (rx_items[0].duration1 > 0))
146 {
147 if (rx_items[1].level0 == 0)
148 {
149 _is_present = true;
150 }
151 }
152 }
153 }
154
155 vRingbufferReturnItem(i->rb, (void *)rx_items);
156 }
157 else
158 {
159 // time out occurred, this indicates an unconnected / misconfigured bus
160 ESP_LOGE(TAG, "rx_items == 0");
161 res = OWB_STATUS_HW_ERROR;
162 }
163 }
164 else
165 {
166 // error in tx channel
167 ESP_LOGE(TAG, "Error tx");
168 res = OWB_STATUS_HW_ERROR;
169 }
170
171 rmt_rx_stop(i->rx_channel);
172 rmt_set_rx_idle_thresh(i->rx_channel, old_rx_thresh);
173
174 *is_present = _is_present;
175
176 ESP_LOGD(TAG, "_is_present %d", _is_present);
177
178 return res;
179 }
180
181 static rmt_item32_t _encode_write_slot(uint8_t val)
182 {
183 rmt_item32_t item;
184
185 item.level0 = 0;
186 item.level1 = 1;
187 if (val)
188 {
189 // write "1" slot
190 item.duration0 = OW_DURATION_1_LOW;
191 item.duration1 = OW_DURATION_1_HIGH;
192 }
193 else
194 {
195 // write "0" slot
196 item.duration0 = OW_DURATION_0_LOW;
197 item.duration1 = OW_DURATION_0_HIGH;
198 }
199
200 return item;
201 }
202
203 /** NOTE: The data is shifted out of the low bits, eg. it is written in the order of lsb to msb */
204 static owb_status _write_bits(const OneWireBus * bus, uint8_t out, int number_of_bits_to_write)
205 {
206 rmt_item32_t tx_items[number_of_bits_to_write + 1];
207 owb_rmt_driver_info *info = info_of_driver(bus);
208
209 if (number_of_bits_to_write > 8)
210 {
211 return OWB_STATUS_TOO_MANY_BITS;
212 }
213
214 // write requested bits as pattern to TX buffer
215 for (int i = 0; i < number_of_bits_to_write; i++)
216 {
217 tx_items[i] = _encode_write_slot(out & 0x01);
218 out >>= 1;
219 }
220
221 // end marker
222 tx_items[number_of_bits_to_write].level0 = 1;
223 tx_items[number_of_bits_to_write].duration0 = 0;
224
225 owb_status status;
226
227 if (rmt_write_items(info->tx_channel, tx_items, number_of_bits_to_write+1, true) == ESP_OK)
228 {
229 status = OWB_STATUS_OK;
230 }
231 else
232 {
233 status = OWB_STATUS_HW_ERROR;
234 ESP_LOGE(TAG, "rmt_write_items() failed");
235 }
236
237 return status;
238 }
239
240 static rmt_item32_t _encode_read_slot(void)
241 {
242 rmt_item32_t item;
243
244 // construct pattern for a single read time slot
245 item.level0 = 0;
246 item.duration0 = OW_DURATION_1_LOW; // shortly force 0
247 item.level1 = 1;
248 item.duration1 = OW_DURATION_1_HIGH; // release high and finish slot
249 return item;
250 }
251
252 /** NOTE: Data is read into the high bits, eg. each bit read is shifted down before the next bit is read */
253 static owb_status _read_bits(const OneWireBus * bus, uint8_t *in, int number_of_bits_to_read)
254 {
255 rmt_item32_t tx_items[number_of_bits_to_read + 1];
256 uint8_t read_data = 0;
257 int res = OWB_STATUS_OK;
258
259 owb_rmt_driver_info *info = info_of_driver(bus);
260
261 if (number_of_bits_to_read > 8)
262 {
263 ESP_LOGE(TAG, "_read_bits() OWB_STATUS_TOO_MANY_BITS");
264 return OWB_STATUS_TOO_MANY_BITS;
265 }
266
267 // generate requested read slots
268 for (int i = 0; i < number_of_bits_to_read; i++)
269 {
270 tx_items[i] = _encode_read_slot();
271 }
272
273 // end marker
274 tx_items[number_of_bits_to_read].level0 = 1;
275 tx_items[number_of_bits_to_read].duration0 = 0;
276
277 onewire_flush_rmt_rx_buf(bus);
278 rmt_rx_start(info->rx_channel, true);
279 if (rmt_write_items(info->tx_channel, tx_items, number_of_bits_to_read+1, true) == ESP_OK)
280 {
281 size_t rx_size;
282 rmt_item32_t* rx_items = (rmt_item32_t *)xRingbufferReceive(info->rb, &rx_size, portMAX_DELAY);
283
284 if (rx_items)
285 {
286 #ifdef OW_DEBUG
287 for (int i = 0; i < rx_size / 4; i++)
288 {
289 ESP_LOGI(TAG, "level: %d, duration %d", rx_items[i].level0, rx_items[i].duration0);
290 ESP_LOGI(TAG, "level: %d, duration %d", rx_items[i].level1, rx_items[i].duration1);
291 }
292 #endif
293
294 if (rx_size >= number_of_bits_to_read * sizeof(rmt_item32_t))
295 {
296 for (int i = 0; i < number_of_bits_to_read; i++)
297 {
298 read_data >>= 1;
299 // parse signal and identify logical bit
300 if (rx_items[i].level1 == 1)
301 {
302 if ((rx_items[i].level0 == 0) && (rx_items[i].duration0 < OW_DURATION_SAMPLE))
303 {
304 // rising edge occured before 15us -> bit 1
305 read_data |= 0x80;
306 }
307 }
308 }
309 read_data >>= 8 - number_of_bits_to_read;
310 }
311
312 vRingbufferReturnItem(info->rb, (void *)rx_items);
313 }
314 else
315 {
316 // time out occurred, this indicates an unconnected / misconfigured bus
317 ESP_LOGE(TAG, "rx_items == 0");
318 res = OWB_STATUS_HW_ERROR;
319 }
320 }
321 else
322 {
323 // error in tx channel
324 ESP_LOGE(TAG, "Error tx");
325 res = OWB_STATUS_HW_ERROR;
326 }
327
328 rmt_rx_stop(info->rx_channel);
329
330 *in = read_data;
331 return res;
332 }
333
334 static owb_status _uninitialize(const OneWireBus *bus)
335 {
336 owb_rmt_driver_info * info = info_of_driver(bus);
337
338 rmt_driver_uninstall(info->tx_channel);
339 rmt_driver_uninstall(info->rx_channel);
340
341 return OWB_STATUS_OK;
342 }
343
344 static struct owb_driver rmt_function_table =
345 {
346 .name = "owb_rmt",
347 .uninitialize = _uninitialize,
348 .reset = _reset,
349 .write_bits = _write_bits,
350 .read_bits = _read_bits
351 };
352
353 static owb_status _init(owb_rmt_driver_info *info, uint8_t gpio_num,
354 rmt_channel_t tx_channel, rmt_channel_t rx_channel)
355 {
356 owb_status status = OWB_STATUS_HW_ERROR;
357
358 // Ensure the RMT peripheral is not already running
359 // Note: if using RMT elsewhere, don't call this here, call it at the start of your prgoram instead.
360 //periph_module_disable(PERIPH_RMT_MODULE);
361 //periph_module_enable(PERIPH_RMT_MODULE);
362
363 info->bus.driver = &rmt_function_table;
364 info->tx_channel = tx_channel;
365 info->rx_channel = rx_channel;
366 info->gpio = gpio_num;
367
368 #ifdef OW_DEBUG
369 ESP_LOGI(TAG, "RMT TX channel: %d", info->tx_channel);
370 ESP_LOGI(TAG, "RMT RX channel: %d", info->rx_channel);
371 #endif
372
373 rmt_config_t rmt_tx;
374 rmt_tx.channel = info->tx_channel;
375 rmt_tx.gpio_num = gpio_num;
376 rmt_tx.mem_block_num = 1;
377 rmt_tx.clk_div = 80;
378 rmt_tx.tx_config.loop_en = false;
379 rmt_tx.tx_config.carrier_en = false;
380 rmt_tx.tx_config.idle_level = 1;
381 rmt_tx.tx_config.idle_output_en = true;
382 rmt_tx.rmt_mode = RMT_MODE_TX;
383 if (rmt_config(&rmt_tx) == ESP_OK)
384 {
385 if (rmt_driver_install(rmt_tx.channel, 0, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) == ESP_OK)
386 {
387 rmt_config_t rmt_rx;
388 rmt_rx.channel = info->rx_channel;
389 rmt_rx.gpio_num = gpio_num;
390 rmt_rx.clk_div = 80;
391 rmt_rx.mem_block_num = 1;
392 rmt_rx.rmt_mode = RMT_MODE_RX;
393 rmt_rx.rx_config.filter_en = true;
394 rmt_rx.rx_config.filter_ticks_thresh = 30;
395 rmt_rx.rx_config.idle_threshold = OW_DURATION_RX_IDLE;
396 if (rmt_config(&rmt_rx) == ESP_OK)
397 {
398 if (rmt_driver_install(rmt_rx.channel, 512, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) == ESP_OK)
399 {
400 rmt_get_ringbuf_handle(info->rx_channel, &info->rb);
401 status = OWB_STATUS_OK;
402 }
403 else
404 {
405 ESP_LOGE(TAG, "failed to install rx driver");
406 }
407 }
408 else
409 {
410 status = OWB_STATUS_HW_ERROR;
411 ESP_LOGE(TAG, "failed to configure rx, uninstalling rmt driver on tx channel");
412 rmt_driver_uninstall(rmt_tx.channel);
413 }
414 }
415 else
416 {
417 ESP_LOGE(TAG, "failed to install tx driver");
418 }
419 }
420 else
421 {
422 ESP_LOGE(TAG, "failed to configure tx");
423 }
424
425 // attach GPIO to previous pin
426 if (gpio_num < 32)
427 {
428 GPIO.enable_w1ts = (0x1 << gpio_num);
429 }
430 else
431 {
432 GPIO.enable1_w1ts.data = (0x1 << (gpio_num - 32));
433 }
434
435 // attach RMT channels to new gpio pin
436 // ATTENTION: set pin for rx first since gpio_output_disable() will
437 // remove rmt output signal in matrix!
438 rmt_set_pin(info->rx_channel, RMT_MODE_RX, gpio_num);
439 rmt_set_pin(info->tx_channel, RMT_MODE_TX, gpio_num);
440
441 // force pin direction to input to enable path to RX channel
442 PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[gpio_num]);
443
444 // enable open drain
445 GPIO.pin[gpio_num].pad_driver = 1;
446
447 return status;
448 }
449
450 OneWireBus * owb_rmt_initialize(owb_rmt_driver_info *info, uint8_t gpio_num,
451 rmt_channel_t tx_channel, rmt_channel_t rx_channel)
452 {
453 ESP_LOGD(TAG, "%s: gpio_num: %d, tx_channel: %d, rx_channel: %d",
454 __func__, gpio_num, tx_channel, rx_channel);
455
456 owb_status status = _init(info, gpio_num, tx_channel, rx_channel);
457 if(status != OWB_STATUS_OK)
458 {
459 ESP_LOGE(TAG, "_init() failed with status %d", status);
460 }
461
462 return &(info->bus);
463 }

mercurial