main.c 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #include <FreeRTOS.h>
  2. #include <task.h>
  3. #include <queue.h>
  4. #include <libopencm3/stm32/gpio.h>
  5. #include <libopencm3/stm32/rcc.h>
  6. #include <libopencm3/stm32/exti.h>
  7. #include <libopencm3/cm3/nvic.h>
  8. #ifdef __GNUC__
  9. #define UNUSED(x) UNUSED_##x __attribute__((__unused__))
  10. #else
  11. #define UNUSED(x) UNUSED_##x
  12. #endif
  13. //add stack-overflow hook
  14. extern void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName);
  15. // our tasks
  16. void task_onboardled(void *args);
  17. void task_buttonled(void *args);
  18. static QueueHandle_t btn_q; // coda dall'ISR del pulsante a task_buttonled
  19. /*
  20. * versione più evoluta del controllo di un rotary encoder
  21. * Questa versione usa comunque un solo canale di interrupt, ma prendendo sia il rising sia il falling.
  22. * L'implementazione, relativamente semplice, della ISR è spiegata in
  23. * http://makeatronics.blogspot.com/2013/02/efficiently-reading-quadrature-with.html
  24. * sostanzialmente invece di fare molte variabili e molti if, come spesso si fa per gestire i rotary encoder,
  25. * viene usata una lookup table, modo compatto e veloce di fare una specie di macchina a stati finiti.
  26. *
  27. * sugli interrupt va segnalato che non sono riuscito a far andare gli interrupt sulle porte GPIOB, o almeno
  28. * sulle B0, B1, B4, B5
  29. *
  30. * un led (quello built-in) fa on/off con duty cycle variabile
  31. *
  32. * altri due led (porte B8 e C15) per debug, si accendono seguendo il rotary encoder (porte A8 e A9) è premuto
  33. * (mandato a VCC): quando si ruota in un verso, il led rosso è fermo e quello verde cambia; nella direzione
  34. * opposta avviene l'inverso.
  35. * Il rotary controlla inoltre il duty cycle del led on-board. È possibile quindi testare il tutto anche senza
  36. * montare i LED, ma sarà tendenzialmente più difficile debuggare.
  37. *
  38. * OSSERVAZIONI:
  39. * - con questa implementazione, solo un filo più complessa di "rotary", il risultato è molto più stabile
  40. * perché si tolgono transizioni non valide.
  41. *
  42. *
  43. * MONTAGGIO
  44. * - un led va messo tra la porta A8, una resistenza (330ohm) e la massa
  45. * - un led va messo tra la porta B11, una resistenza (330ohm) e la massa
  46. * - il rotary encoder vuole il VCC sul pin centrale, porte B5 e B4 ai lati.
  47. * Per fare "debouncing" si usano dei condensatori da 0.1uF tra ciascun pin "laterale" e il VCC. Ovvero ci
  48. * vuole un condensatore tra B5 e VCC e uno tra B4 e VCC. Il tutto è dunque in parallelo rispetto al rotary.
  49. */
  50. static void gpio_setup(void)
  51. {
  52. // built-in LED
  53. rcc_periph_clock_enable(RCC_GPIOC);
  54. gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
  55. GPIO13);
  56. rcc_periph_clock_enable(RCC_GPIOA);
  57. rcc_periph_clock_enable(RCC_GPIOB);
  58. // ext. rotary button: due pin per la direzione (B4 e B5). Metto l'interrupt solo su uno dei due (B1)
  59. gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO8|GPIO9);
  60. exti_select_source(EXTI8, GPIOA);
  61. exti_set_trigger(EXTI8, EXTI_TRIGGER_BOTH);
  62. exti_enable_request(EXTI8);
  63. nvic_enable_irq(NVIC_EXTI9_5_IRQ); // occhio! guarda tabella 11-2 a pag. 209 o ti perdi su quale NVIC_EXTI usare
  64. // ext. led (green)
  65. gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO8);
  66. // ext. led (red)
  67. gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO15);
  68. }
  69. int main(void)
  70. {
  71. rcc_clock_setup_in_hse_8mhz_out_72mhz();
  72. gpio_setup();
  73. gpio_set(GPIOB, GPIO8);
  74. gpio_set(GPIOC, GPIO15);
  75. // gpio_clear(GPIOB, GPIO11);
  76. btn_q = xQueueCreate(10, sizeof(char));
  77. xTaskCreate(task_buttonled, "BUTTONLED", 50, NULL, configMAX_PRIORITIES - 1,
  78. NULL);
  79. xTaskCreate(task_onboardled, "ONBOARD", 50, NULL, configMAX_PRIORITIES - 1,
  80. NULL);
  81. vTaskStartScheduler();
  82. for (;;) {
  83. }
  84. return 0;
  85. }
  86. int boardLedDuty = 1000;
  87. void task_onboardled(void *UNUSED(args))
  88. {
  89. while (1) {
  90. gpio_clear(GPIOC, GPIO13); /* LED on */
  91. vTaskDelay(pdMS_TO_TICKS(boardLedDuty));
  92. gpio_set(GPIOC, GPIO13); /* LED off */
  93. vTaskDelay(pdMS_TO_TICKS(300));
  94. }
  95. }
  96. void task_buttonled(void *UNUSED(args))
  97. {
  98. while (1) {
  99. char increasing;
  100. if(xQueueReceive(btn_q, &increasing, pdMS_TO_TICKS(5000)) == pdPASS)
  101. {
  102. if(increasing) {
  103. gpio_toggle(GPIOB, GPIO8);
  104. } else {
  105. gpio_toggle(GPIOC, GPIO15);
  106. }
  107. }
  108. }
  109. }
  110. // ISR per controllo dei rotary a singolo interrupt
  111. // la puoi riciclare da un progetto ad un altro, cambiando solamente l'if(change < 0)
  112. // e' importante che questa ISR sia associata all'interrupt del "primo" pin (vedi l'assegnazione di "now")
  113. void exti9_5_isr(void)
  114. {
  115. static const int8_t lookup_table[] = {
  116. 0,0,0,-1,
  117. 0,0,1,0,
  118. 0,1,0,0,
  119. -1,0,0,0};
  120. static uint8_t enc_val = 0;
  121. static int8_t enc_count = 0;
  122. exti_reset_request(EXTI8);
  123. enc_val = enc_val << 2;
  124. // now is in the range [0,3]; lsb is A9, then there is A8
  125. uint8_t now = ((!!gpio_get(GPIOA, GPIO8)) * 2) + !!gpio_get(GPIOA, GPIO9);
  126. enc_val |= now;
  127. int8_t change = lookup_table[enc_val & 0b1111];
  128. enc_count += change;
  129. if(enc_count < 2 && enc_count > -2) {
  130. return;
  131. }
  132. if(change < 0) { // decrease
  133. if(boardLedDuty < 220)
  134. boardLedDuty = 30;
  135. else
  136. boardLedDuty -= 200;
  137. xQueueSend(btn_q, (void*) 0, 0);
  138. } else {
  139. if(boardLedDuty >= 2300)
  140. boardLedDuty = 2500;
  141. else
  142. boardLedDuty += 200;
  143. xQueueSend(btn_q, (void*) 1, 0);
  144. }
  145. enc_count = 0;
  146. }
  147. // three "small flashes" (an S in Morse code) on onboard led
  148. //
  149. // questa funzione viene chiamata in caso di stackoverflow su uno dei task; e'
  150. // quindi utile metterci un "segnale" riconoscibile per debug. In questo caso 3
  151. // flash rapidi.
  152. //
  153. // siccome "tarare" la dimensione dello stack necessario e' difficile, con
  154. // questa funzione si puo' andare "a tentoni" e ridurre la dimensione finche'
  155. // non si ottengono crash
  156. //
  157. // in questa funzione, a quanto pare (ma non ho trovato doc) non si possono
  158. // usare cose come vTaskDelay. Siamo quindi privi di RTOS e per fare gli sleep
  159. // dobbiamo fare i for "all'antica"
  160. void vApplicationStackOverflowHook(xTaskHandle *UNUSED(pxTask),
  161. signed portCHAR *UNUSED(pcTaskName))
  162. {
  163. int i;
  164. for (;;) {
  165. for (char c = 0; c < 3; c++) {
  166. gpio_clear(GPIOC, GPIO13); /* LED on */
  167. for (i = 0; i < 1000000;
  168. i++) /* small delay */
  169. __asm__("nop");
  170. gpio_set(GPIOC, GPIO13); /* LED off */
  171. for (i = 0; i < 3000000; i++) { /* a bit more delay */
  172. __asm__("nop");
  173. }
  174. }
  175. for (i = 0; i < 10000000; i++) { /* long delay */
  176. __asm__("nop");
  177. }
  178. }
  179. }
  180. // vim: set fdm=marker: