Wed, 28 Jul 2021 22:04:54 +0200
Removed the 1024 - again.
0 | 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 | } |