ref_clock_impl_rmt_pcnt.c 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /*
  2. * SPDX-FileCopyrightText: 2017-2022 Espressif Systems (Shanghai) CO LTD
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. /**
  7. * Some unit test cases need to have access to reliable timestamps even when CPU and APB clock frequencies change over time.
  8. * This reference clock is built upon two peripherals: one RMT channel and one PCNT channel (hopefully we can have these two peripherals in all ESP chips).
  9. *
  10. * +---------------------+ 500KHz Square Wave +--------------------------+
  11. * | RMT (channel 0, TX) +----------------------------------->+ PCNT (unit 0, channel 0) |
  12. * +---------------------+ +--------------------------+
  13. *
  14. * RMT TX channel is configured to use a fixed clock (e.g. REF_TICK, XTAL) as clock source, so that our ref clock won't be affected during APB/CPU clock switch.
  15. * Configure RMT channel to generate a 500KHz square wave (using carrier feature) to one GPIO.
  16. * PCNT takes the input signal from the GPIO and counts the edges (which occur at 1MHz frequency).
  17. * PCNT counter is only 16 bit wide, an interrupt is configured to trigger when the counter reaches 30000,
  18. * incrementing a 32-bit millisecond counter maintained by software.
  19. */
  20. #include "sdkconfig.h"
  21. #include "unity.h"
  22. #include "test_utils.h"
  23. #include "freertos/FreeRTOS.h"
  24. #include "esp_intr_alloc.h"
  25. #include "esp_private/periph_ctrl.h"
  26. #include "driver/rmt.h"
  27. #include "driver/pulse_cnt.h"
  28. #include "soc/gpio_sig_map.h"
  29. #include "soc/gpio_periph.h"
  30. #include "soc/soc_caps.h"
  31. #include "hal/rmt_types.h"
  32. #include "hal/rmt_hal.h"
  33. #include "hal/rmt_ll.h"
  34. #include "esp_rom_gpio.h"
  35. #include "esp_rom_sys.h"
  36. #if !CONFIG_IDF_TARGET_ESP32
  37. #error "RMT+PCNT timestamp workaround is only for ESP32"
  38. #endif
  39. #define REF_CLOCK_RMT_CHANNEL 0 // RMT channel 0
  40. #define REF_CLOCK_GPIO 21 // GPIO used to combine RMT out signal with PCNT input signal
  41. #define REF_CLOCK_PRESCALER_MS 30 // PCNT high threshold interrupt fired every 30ms
  42. static rmt_hal_context_t s_rmt_hal;
  43. static pcnt_unit_handle_t s_pcnt_unit;
  44. static pcnt_channel_handle_t s_pcnt_chan;
  45. static volatile uint32_t s_milliseconds;
  46. // RMTMEM address is declared in <target>.peripherals.ld
  47. extern rmt_mem_t RMTMEM;
  48. static bool on_reach_watch_point(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
  49. {
  50. s_milliseconds += REF_CLOCK_PRESCALER_MS;
  51. return false;
  52. }
  53. void ref_clock_init(void)
  54. {
  55. // Initialize PCNT
  56. pcnt_unit_config_t unit_config = {
  57. .high_limit = REF_CLOCK_PRESCALER_MS * 1000,
  58. .low_limit = -100, // any minus value is OK, in this case, we don't count down
  59. };
  60. TEST_ESP_OK(pcnt_new_unit(&unit_config, &s_pcnt_unit));
  61. pcnt_chan_config_t chan_config = {
  62. .edge_gpio_num = REF_CLOCK_GPIO,
  63. .level_gpio_num = -1,
  64. .flags.io_loop_back = true,
  65. };
  66. TEST_ESP_OK(pcnt_new_channel(s_pcnt_unit, &chan_config, &s_pcnt_chan));
  67. // increase count on both edges
  68. TEST_ESP_OK(pcnt_channel_set_edge_action(s_pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
  69. // don't care level change
  70. TEST_ESP_OK(pcnt_channel_set_level_action(s_pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
  71. // add watch point
  72. TEST_ESP_OK(pcnt_unit_add_watch_point(s_pcnt_unit, REF_CLOCK_PRESCALER_MS * 1000));
  73. // register watch event
  74. pcnt_event_callbacks_t cbs = {
  75. .on_reach = on_reach_watch_point,
  76. };
  77. TEST_ESP_OK(pcnt_unit_register_event_callbacks(s_pcnt_unit, &cbs, NULL));
  78. // start pcnt
  79. TEST_ESP_OK(pcnt_unit_start(s_pcnt_unit));
  80. // Route RMT output to GPIO matrix
  81. esp_rom_gpio_connect_out_signal(REF_CLOCK_GPIO, RMT_SIG_OUT0_IDX, false, false);
  82. // Initialize RMT
  83. periph_module_enable(PERIPH_RMT_MODULE);
  84. rmt_hal_init(&s_rmt_hal);
  85. rmt_item32_t data = {
  86. .duration0 = 1,
  87. .level0 = 1,
  88. .duration1 = 0,
  89. .level1 = 0
  90. };
  91. rmt_ll_enable_periph_clock(s_rmt_hal.regs, true);
  92. rmt_ll_set_group_clock_src(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, RMT_CLK_SRC_APB_F1M, 1, 1, 0); // select REF_TICK (1MHz)
  93. rmt_ll_tx_set_channel_clock_div(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1); // channel clock = REF_TICK / 1 = 1MHz
  94. rmt_ll_tx_fix_idle_level(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1, true); // enable idle output, idle level: 1
  95. rmt_ll_tx_enable_carrier_modulation(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, true);
  96. rmt_ll_tx_set_carrier_high_low_ticks(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1, 1); // set carrier to 1MHz / (1+1) = 500KHz, 50% duty cycle
  97. rmt_ll_tx_set_carrier_level(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1);
  98. rmt_ll_enable_mem_access_nonfifo(s_rmt_hal.regs, true);
  99. rmt_ll_tx_reset_pointer(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL);
  100. rmt_ll_tx_set_mem_blocks(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1);
  101. RMTMEM.chan[REF_CLOCK_RMT_CHANNEL].data32[0] = data;
  102. rmt_ll_tx_enable_loop(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, false);
  103. rmt_ll_tx_start(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL);
  104. s_milliseconds = 0;
  105. }
  106. void ref_clock_deinit(void)
  107. {
  108. // Deinitialize PCNT
  109. TEST_ESP_OK(pcnt_unit_stop(s_pcnt_unit));
  110. TEST_ESP_OK(pcnt_unit_remove_watch_point(s_pcnt_unit, REF_CLOCK_PRESCALER_MS * 1000));
  111. TEST_ESP_OK(pcnt_del_channel(s_pcnt_chan));
  112. TEST_ESP_OK(pcnt_del_unit(s_pcnt_unit));
  113. // Deinitialize RMT
  114. rmt_ll_tx_enable_carrier_modulation(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, false);
  115. periph_module_disable(PERIPH_RMT_MODULE);
  116. }
  117. uint64_t ref_clock_get(void)
  118. {
  119. int microseconds = 0;
  120. TEST_ESP_OK(pcnt_unit_get_count(s_pcnt_unit, &microseconds));
  121. return 1000 * (uint64_t)s_milliseconds + (uint64_t)microseconds;
  122. }