cyhal_gpio.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2024 Evlers
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in all
  14. * copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. * SOFTWARE.
  23. *
  24. * Change Logs:
  25. * Date Author Notes
  26. * 2023-12-21 Evlers first implementation
  27. * 2024-05-18 Evlers add __builtin_clz to support the gcc compiler
  28. * 2024-05-22 Evlers fix oob interrupt loss issue
  29. * 2024-05-28 Evlers add assert to gpio external interrupt
  30. * 2024-07-09 Evlers modify the gpio interrupt callback parameter to provide an absolute event type
  31. * 2024-08-14 Evlers add __CLZ to support the iar compiler
  32. */
  33. #include "cyhal_gpio.h"
  34. #include <rtdevice.h>
  35. #include "rthw.h"
  36. #define CYHAL_INT_TIMEOUT 10
  37. #define CYHAL_MAX_EXTI_NUMBER (16U)
  38. /* Return pin number by counts the number of leading zeros of a data value.
  39. * NOTE: __CLZ returns 32 if no bits are set in the source register, and zero
  40. * if bit 31 is set. Parameter 'pin' should be in range GPIO_PIN0..GPIO_PIN15. */
  41. #if defined(__ARMCC_VERSION)
  42. #define CYHAL_GET_PIN_NUMBER(pin) (uint32_t)(31u - __clz(pin))
  43. #elif defined(__GNUC__)
  44. #define CYHAL_GET_PIN_NUMBER(pin) (uint32_t)(31u - __builtin_clz(pin))
  45. #elif defined(__ICCARM__)
  46. #include <intrinsics.h>
  47. #define CYHAL_GET_PIN_NUMBER(pin) (uint32_t)(31u - __CLZ(pin))
  48. #endif
  49. typedef struct
  50. {
  51. cyhal_gpio_irq_handler_t callback;
  52. void* callback_args;
  53. bool enable;
  54. cyhal_gpio_t pin;
  55. /* fix oob interrupt loss issue */
  56. cyhal_gpio_irq_event_t event;
  57. rt_tick_t tick;
  58. rt_bool_t last_level;
  59. rt_uint32_t soft_int_count;
  60. } cyhal_gpio_event_callback_info_t;
  61. static cyhal_gpio_event_callback_info_t _exti_callbacks_info[CYHAL_MAX_EXTI_NUMBER] = { 0u };
  62. /** Initialize the GPIO pin
  63. *
  64. * @param[in] pin The GPIO pin to initialize
  65. * @param[in] direction The pin direction (input/output)
  66. * @param[in] drvMode The pin drive mode
  67. * @param[in] initVal Initial value on the pin
  68. *
  69. * @return The status of the init request
  70. */
  71. cy_rslt_t cyhal_gpio_init(cyhal_gpio_t pin, cyhal_gpio_direction_t direction, cyhal_gpio_drive_mode_t drvMode,
  72. bool initVal)
  73. {
  74. rt_uint8_t mode = PIN_MODE_INPUT;
  75. switch (drvMode)
  76. {
  77. case CYHAL_GPIO_DRIVE_NONE:
  78. mode = PIN_MODE_INPUT;
  79. break;
  80. case CYHAL_GPIO_DRIVE_PULLUP:
  81. mode = (direction == CYHAL_GPIO_DIR_INPUT) ? PIN_MODE_INPUT_PULLUP : PIN_MODE_OUTPUT;
  82. break;
  83. case CYHAL_GPIO_DRIVE_PULLDOWN:
  84. mode = (direction == CYHAL_GPIO_DIR_INPUT) ? PIN_MODE_INPUT_PULLDOWN : PIN_MODE_OUTPUT;
  85. break;
  86. case CYHAL_GPIO_DRIVE_OPENDRAINDRIVESLOW:
  87. mode = (direction == CYHAL_GPIO_DIR_INPUT) ? PIN_MODE_INPUT_PULLDOWN : PIN_MODE_OUTPUT_OD;
  88. break;
  89. case CYHAL_GPIO_DRIVE_OPENDRAINDRIVESHIGH:
  90. mode = (direction == CYHAL_GPIO_DIR_INPUT) ? PIN_MODE_INPUT_PULLUP : PIN_MODE_OUTPUT_OD;
  91. break;
  92. case CYHAL_GPIO_DRIVE_STRONG:
  93. mode = PIN_MODE_OUTPUT;
  94. break;
  95. case CYHAL_GPIO_DRIVE_PULLUPDOWN:
  96. if ((direction == CYHAL_GPIO_DIR_OUTPUT) || (direction == CYHAL_GPIO_DIR_BIDIRECTIONAL))
  97. {
  98. mode = PIN_MODE_OUTPUT;
  99. }
  100. else
  101. {
  102. mode = PIN_MODE_INPUT_PULLUP;
  103. }
  104. break;
  105. }
  106. rt_pin_mode(pin, mode);
  107. rt_pin_write(pin, initVal);
  108. return CY_RSLT_SUCCESS;
  109. }
  110. /** Uninitialize the gpio peripheral and the cyhal_gpio_t object
  111. *
  112. * @param[in] pin Pin number
  113. */
  114. void cyhal_gpio_free(cyhal_gpio_t pin)
  115. {
  116. rt_pin_detach_irq(pin);
  117. rt_pin_mode(pin, PIN_MODE_INPUT);
  118. }
  119. /** Set the output value for the pin. This only works for output & in_out pins.
  120. *
  121. * @param[in] pin The GPIO object
  122. * @param[in] value The value to be set (high = true, low = false)
  123. */
  124. void cyhal_gpio_write(cyhal_gpio_t pin, bool value)
  125. {
  126. rt_pin_write(pin, value);
  127. }
  128. /** Read the input value. This only works for input & in_out pins.
  129. *
  130. * @param[in] pin The GPIO object
  131. * @return The value of the IO (true = high, false = low)
  132. */
  133. bool cyhal_gpio_read(cyhal_gpio_t pin)
  134. {
  135. return rt_pin_read(pin) ? true : false;
  136. }
  137. static void gpio_interrupt(void *args)
  138. {
  139. cyhal_gpio_event_callback_info_t *info = (cyhal_gpio_event_callback_info_t *)args;
  140. uint32_t pin_number = CYHAL_GET_PIN_NUMBER(info->pin);
  141. /* Check the parameters */
  142. RT_ASSERT(pin_number < CYHAL_MAX_EXTI_NUMBER);
  143. if ((pin_number < CYHAL_MAX_EXTI_NUMBER) && (info->enable))
  144. {
  145. /* Call user's callback */
  146. info->callback(info->callback_args, info->event);
  147. info->tick = rt_tick_get();
  148. }
  149. }
  150. /** Register/clear an interrupt handler for the pin toggle pin IRQ event
  151. *
  152. * @param[in] pin The pin number
  153. * @param[in] intrPriority The NVIC interrupt channel priority
  154. * @param[in] handler The function to call when the specified event happens. Pass NULL to unregister the handler.
  155. * @param[in] handler_arg Generic argument that will be provided to the handler when called, can be NULL
  156. */
  157. void cyhal_gpio_register_irq(cyhal_gpio_t pin, uint8_t intrPriority, cyhal_gpio_irq_handler_t handler,
  158. void *handler_arg)
  159. {
  160. /* Get pin number */
  161. uint32_t pin_number = CYHAL_GET_PIN_NUMBER(pin);
  162. /* Check the parameters */
  163. RT_ASSERT(pin_number < CYHAL_MAX_EXTI_NUMBER);
  164. if (pin_number < CYHAL_MAX_EXTI_NUMBER && handler != NULL)
  165. {
  166. rt_base_t level = rt_hw_interrupt_disable();
  167. _exti_callbacks_info[pin_number].callback = handler;
  168. _exti_callbacks_info[pin_number].callback_args = handler_arg;
  169. _exti_callbacks_info[pin_number].pin = pin;
  170. rt_hw_interrupt_enable(level);
  171. }
  172. else if (pin_number >= CYHAL_MAX_EXTI_NUMBER)
  173. {
  174. /* Wrong param */
  175. RT_ASSERT(false);
  176. }
  177. else
  178. {
  179. /* Clean callback information */
  180. uint32_t level = rt_hw_interrupt_disable();
  181. _exti_callbacks_info[pin_number].callback = NULL;
  182. _exti_callbacks_info[pin_number].callback_args = NULL;
  183. _exti_callbacks_info[pin_number].enable = false;
  184. _exti_callbacks_info[pin_number].pin = 0xFFFFFFFF;
  185. rt_hw_interrupt_enable(level);
  186. }
  187. }
  188. /* Notes:
  189. * Normally, we need to pull the pin up and then give the falling edge to trigger the interrupt,
  190. * but the WiFi module sometimes will continue to be low, without raising the pin first,
  191. * or sometimes only raised for less than 1us time, resulting in failure to trigger external interruptions.
  192. * In order to solve the problem that WiFi does not give a falling edge,
  193. * we need to regularly check whether the edge times out.
  194. */
  195. static void timer_callback (void *parameter)
  196. {
  197. rt_tick_t tick = rt_tick_get();
  198. for (rt_uint8_t i = 0; i < CYHAL_MAX_EXTI_NUMBER; i ++)
  199. {
  200. cyhal_gpio_event_callback_info_t *info = &_exti_callbacks_info[i];
  201. if (info->enable)
  202. {
  203. bool level = rt_pin_read(info->pin) ? true : false;
  204. bool continuous_valid = false;
  205. /* Check whether pin are valid continuously */
  206. switch(info->event)
  207. {
  208. case CYHAL_GPIO_IRQ_RISE:
  209. if (info->last_level && level)
  210. {
  211. continuous_valid = true;
  212. }
  213. break;
  214. case CYHAL_GPIO_IRQ_FALL:
  215. if (!info->last_level && !level)
  216. {
  217. continuous_valid = true;
  218. }
  219. break;
  220. case CYHAL_GPIO_IRQ_BOTH:
  221. /* This double edge mode should not have this problem */
  222. break;
  223. default:
  224. break;
  225. }
  226. /* Save the last level to see if it times out */
  227. info->last_level = level;
  228. if (continuous_valid)
  229. {
  230. rt_tick_t elapsed_time = tick - info->tick;
  231. /* If the pin does not trigger an interrupt continuous active, the interrupt is missed */
  232. if (elapsed_time >= rt_tick_from_millisecond(CYHAL_INT_TIMEOUT))
  233. {
  234. /* Here the software triggers an interrupt */
  235. info->soft_int_count ++;
  236. gpio_interrupt(info);
  237. }
  238. }
  239. }
  240. }
  241. }
  242. /** Enable or Disable the GPIO IRQ
  243. *
  244. * @param[in] pin The GPIO object
  245. * @param[in] event The GPIO IRQ event
  246. * @param[in] enable True to turn on interrupts, False to turn off
  247. */
  248. void cyhal_gpio_irq_enable(cyhal_gpio_t pin, cyhal_gpio_irq_event_t event, bool enable)
  249. {
  250. /* Get pin number */
  251. uint32_t pin_number = CYHAL_GET_PIN_NUMBER(pin);
  252. /* Check the parameters */
  253. RT_ASSERT(pin_number < CYHAL_MAX_EXTI_NUMBER);
  254. if (enable)
  255. {
  256. rt_uint8_t mode = PIN_IRQ_MODE_FALLING;
  257. switch(event)
  258. {
  259. case CYHAL_GPIO_IRQ_RISE:
  260. mode = PIN_IRQ_MODE_RISING;
  261. break;
  262. case CYHAL_GPIO_IRQ_FALL:
  263. mode = PIN_IRQ_MODE_FALLING;
  264. break;
  265. case CYHAL_GPIO_IRQ_BOTH:
  266. mode = PIN_IRQ_MODE_RISING_FALLING;
  267. break;
  268. default:
  269. break;
  270. }
  271. rt_pin_attach_irq(pin, mode, gpio_interrupt, &_exti_callbacks_info[pin_number]);
  272. _exti_callbacks_info[pin_number].enable = true;
  273. rt_pin_irq_enable(pin, RT_TRUE);
  274. /* fix oob interrupt loss issue */
  275. _exti_callbacks_info[pin_number].event = event;
  276. _exti_callbacks_info[pin_number].last_level = rt_pin_read(pin);
  277. static rt_timer_t timer = NULL;
  278. if (timer == NULL)
  279. {
  280. timer = rt_timer_create("whd_oob", timer_callback, NULL,
  281. rt_tick_from_millisecond(CYHAL_INT_TIMEOUT),
  282. RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
  283. rt_timer_start(timer);
  284. }
  285. }
  286. else
  287. {
  288. rt_pin_irq_enable(pin, RT_FALSE);
  289. rt_pin_detach_irq(pin);
  290. }
  291. }