|
@@ -0,0 +1,211 @@
|
|
|
+#include <FreeRTOS.h>
|
|
|
+#include <task.h>
|
|
|
+#include <queue.h>
|
|
|
+#include <libopencm3/stm32/gpio.h>
|
|
|
+#include <libopencm3/stm32/rcc.h>
|
|
|
+#include <libopencm3/stm32/exti.h>
|
|
|
+#include <libopencm3/cm3/nvic.h>
|
|
|
+
|
|
|
+#ifdef __GNUC__
|
|
|
+#define UNUSED(x) UNUSED_##x __attribute__((__unused__))
|
|
|
+#else
|
|
|
+#define UNUSED(x) UNUSED_##x
|
|
|
+#endif
|
|
|
+
|
|
|
+//add stack-overflow hook
|
|
|
+extern void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName);
|
|
|
+
|
|
|
+// our tasks
|
|
|
+void task_onboardled(void *args);
|
|
|
+void task_buttonled(void *args);
|
|
|
+static QueueHandle_t btn_q; // coda dall'ISR del pulsante a task_buttonled
|
|
|
+
|
|
|
+/*
|
|
|
+ * versione più evoluta del controllo di un rotary encoder
|
|
|
+ * Questa versione usa due canali di interrupt, prendendo sia il rising sia il falling.
|
|
|
+ * L'implementazione, relativamente semplice, della ISR è spiegata in
|
|
|
+ * http://makeatronics.blogspot.com/2013/02/efficiently-reading-quadrature-with.html
|
|
|
+ * sostanzialmente invece di fare molte variabili e molti if, come spesso si fa per gestire i rotary encoder,
|
|
|
+ * viene usata una lookup table, modo compatto e veloce di fare una specie di macchina a stati finiti.
|
|
|
+ *
|
|
|
+ * sugli interrupt va segnalato che non sono riuscito a far andare gli interrupt sulle porte GPIOB, o almeno
|
|
|
+ * sulle B0, B1, B4, B5
|
|
|
+ *
|
|
|
+ * un led (quello built-in) fa on/off con duty cycle variabile
|
|
|
+ *
|
|
|
+ * altri due led (porte B8 e C15) si accendono seguendo il rotary encoder (porte A8 e A9) è premuto (mandato a
|
|
|
+ * VCC): quando si ruota in un verso, il led rosso è fermo e quello verde cambia; nella direzione opposta
|
|
|
+ * avviene l'inverso.
|
|
|
+ * Il rotary controlla inoltre il duty cycle del led on-board. È possibile quindi testare il tutto anche senza
|
|
|
+ * montare i LED, ma sarà tendenzialmente più difficile debuggare.
|
|
|
+ *
|
|
|
+ * OSSERVAZIONI
|
|
|
+ * - il codice è praticamente uguale a rotary_tbl1 (cambia solo la lookup table), ma vengono usati 2
|
|
|
+ * interrupt. Inoltre per come è scritta serve che siano sullo stesso gruppo di interrupt (5-9 oppure 10-15,
|
|
|
+ * anche di linee diverse in teoria)
|
|
|
+ *
|
|
|
+ * MONTAGGIO
|
|
|
+ * - un led va messo tra la porta A8, una resistenza (330ohm) e la massa
|
|
|
+ * - un led va messo tra la porta B11, una resistenza (330ohm) e la massa
|
|
|
+ * - il rotary encoder vuole il VCC sul pin centrale, porte B5 e B4 ai lati.
|
|
|
+ * Per fare "debouncing" si usano dei condensatori da 0.1uF tra ciascun pin "laterale" e il VCC. Ovvero ci
|
|
|
+ * vuole un condensatore tra B5 e VCC e uno tra B4 e VCC. Il tutto è dunque in parallelo rispetto al rotary.
|
|
|
+ */
|
|
|
+
|
|
|
+static void gpio_setup(void)
|
|
|
+{
|
|
|
+
|
|
|
+ // built-in LED
|
|
|
+ rcc_periph_clock_enable(RCC_GPIOC);
|
|
|
+ gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
|
|
|
+ GPIO13);
|
|
|
+
|
|
|
+ rcc_periph_clock_enable(RCC_GPIOA);
|
|
|
+ rcc_periph_clock_enable(RCC_GPIOB);
|
|
|
+ // ext. rotary button: due pin per la direzione (B4 e B5). Metto l'interrupt solo su uno dei due (B1)
|
|
|
+ gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO8|GPIO9);
|
|
|
+
|
|
|
+ exti_select_source(EXTI9, GPIOA);
|
|
|
+ exti_set_trigger(EXTI9, EXTI_TRIGGER_BOTH);
|
|
|
+ exti_enable_request(EXTI9);
|
|
|
+ exti_select_source(EXTI8, GPIOA);
|
|
|
+ exti_set_trigger(EXTI8, EXTI_TRIGGER_BOTH);
|
|
|
+ exti_enable_request(EXTI8);
|
|
|
+ nvic_enable_irq(NVIC_EXTI9_5_IRQ); // occhio! guarda tabella 11-2 a pag. 209 o ti perdi su quale NVIC_EXTI usare
|
|
|
+
|
|
|
+ // ext. led (green): increasing
|
|
|
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO8);
|
|
|
+ // ext. led (red): decreasing
|
|
|
+ gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO15);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int main(void)
|
|
|
+{
|
|
|
+ rcc_clock_setup_in_hse_8mhz_out_72mhz();
|
|
|
+ gpio_setup();
|
|
|
+ gpio_set(GPIOB, GPIO8);
|
|
|
+ gpio_set(GPIOC, GPIO15);
|
|
|
+ // gpio_clear(GPIOB, GPIO11);
|
|
|
+
|
|
|
+ btn_q = xQueueCreate(10, sizeof(char));
|
|
|
+ xTaskCreate(task_buttonled, "BUTTONLED", 50, NULL, configMAX_PRIORITIES - 1,
|
|
|
+ NULL);
|
|
|
+ xTaskCreate(task_onboardled, "ONBOARD", 50, NULL, configMAX_PRIORITIES - 1,
|
|
|
+ NULL);
|
|
|
+ vTaskStartScheduler();
|
|
|
+ for (;;) {
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int boardLedDuty = 1000;
|
|
|
+void task_onboardled(void *UNUSED(args))
|
|
|
+{
|
|
|
+ while (1) {
|
|
|
+ gpio_clear(GPIOC, GPIO13); /* LED on */
|
|
|
+ vTaskDelay(pdMS_TO_TICKS(boardLedDuty));
|
|
|
+
|
|
|
+ gpio_set(GPIOC, GPIO13); /* LED off */
|
|
|
+ vTaskDelay(pdMS_TO_TICKS(300));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void task_buttonled(void *UNUSED(args))
|
|
|
+{
|
|
|
+ while (1) {
|
|
|
+ char increasing;
|
|
|
+ if(xQueueReceive(btn_q, &increasing, pdMS_TO_TICKS(5000)) == pdPASS)
|
|
|
+ {
|
|
|
+ if(increasing) {
|
|
|
+ gpio_toggle(GPIOB, GPIO8);
|
|
|
+ } else {
|
|
|
+ gpio_toggle(GPIOC, GPIO15);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// ISR per controllo dei rotary a doppio interrupt
|
|
|
+// la puoi riciclare da un progetto ad un altro, cambiando solamente l'if(change < 0)
|
|
|
+// per avere 2 interrupt su 1 funzione, usiamo il blocco 5-9 che ha proprio questa caratteristica.
|
|
|
+// quindi i pin 8 e 9 finiscono entrambi qui
|
|
|
+// se dovevamo per forza fare due funzioni diverse, dovevamo
|
|
|
+void exti9_5_isr(void)
|
|
|
+{
|
|
|
+ static const int8_t lookup_table[] = {
|
|
|
+ 0, -1, 1, 0,
|
|
|
+ 1, 0, 0, -1,
|
|
|
+ -1, 0, 0, 1,
|
|
|
+ 0, 1, -1, 0};
|
|
|
+ static uint8_t enc_val = 0;
|
|
|
+ static int8_t enc_count = 0;
|
|
|
+
|
|
|
+ exti_reset_request(EXTI8|EXTI9);
|
|
|
+ enc_val = enc_val << 2;
|
|
|
+ // now is in the range [0,3]; lsb is A9, then there is A8
|
|
|
+ uint8_t now = ((!!gpio_get(GPIOA, GPIO8)) * 2) + !!gpio_get(GPIOA, GPIO9);
|
|
|
+ enc_val |= now;
|
|
|
+
|
|
|
+ int8_t change = lookup_table[enc_val & 0b1111];
|
|
|
+ enc_count += change;
|
|
|
+
|
|
|
+ if(enc_count < 4 && enc_count > -4) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(enc_count < 0) { // decrease
|
|
|
+ if(boardLedDuty < 220)
|
|
|
+ boardLedDuty = 30;
|
|
|
+ else
|
|
|
+ boardLedDuty -= 200;
|
|
|
+ xQueueSend(btn_q, (void*) 0, 0);
|
|
|
+ } else {
|
|
|
+ if(boardLedDuty >= 2300)
|
|
|
+ boardLedDuty = 2500;
|
|
|
+ else
|
|
|
+ boardLedDuty += 200;
|
|
|
+ xQueueSend(btn_q, (void*) 1, 0);
|
|
|
+ }
|
|
|
+ enc_count = 0;
|
|
|
+}
|
|
|
+
|
|
|
+// three "small flashes" (an S in Morse code) on onboard led
|
|
|
+//
|
|
|
+// questa funzione viene chiamata in caso di stackoverflow su uno dei task; e'
|
|
|
+// quindi utile metterci un "segnale" riconoscibile per debug. In questo caso 3
|
|
|
+// flash rapidi.
|
|
|
+//
|
|
|
+// siccome "tarare" la dimensione dello stack necessario e' difficile, con
|
|
|
+// questa funzione si puo' andare "a tentoni" e ridurre la dimensione finche'
|
|
|
+// non si ottengono crash
|
|
|
+//
|
|
|
+// in questa funzione, a quanto pare (ma non ho trovato doc) non si possono
|
|
|
+// usare cose come vTaskDelay. Siamo quindi privi di RTOS e per fare gli sleep
|
|
|
+// dobbiamo fare i for "all'antica"
|
|
|
+
|
|
|
+void vApplicationStackOverflowHook(xTaskHandle *UNUSED(pxTask),
|
|
|
+ signed portCHAR *UNUSED(pcTaskName))
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ for (;;) {
|
|
|
+ for (char c = 0; c < 3; c++) {
|
|
|
+ gpio_clear(GPIOC, GPIO13); /* LED on */
|
|
|
+ for (i = 0; i < 1000000;
|
|
|
+ i++) /* small delay */
|
|
|
+ __asm__("nop");
|
|
|
+
|
|
|
+ gpio_set(GPIOC, GPIO13); /* LED off */
|
|
|
+ for (i = 0; i < 3000000; i++) { /* a bit more delay */
|
|
|
+ __asm__("nop");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (i = 0; i < 10000000; i++) { /* long delay */
|
|
|
+ __asm__("nop");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// vim: set fdm=marker:
|