123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- #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 comunque un solo canale di interrupt, ma 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) per debug, 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:
- * - con questa implementazione, solo un filo più complessa di "rotary", il risultato è molto più stabile
- * perché si tolgono transizioni non valide.
- *
- *
- * 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(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)
- gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO8);
- // ext. led (red)
- 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 singolo interrupt
- // la puoi riciclare da un progetto ad un altro, cambiando solamente l'if(change < 0)
- // e' importante che questa ISR sia associata all'interrupt del "primo" pin (vedi l'assegnazione di "now")
- void exti9_5_isr(void)
- {
- static const int8_t lookup_table[] = {
- 0,0,0,-1,
- 0,0,1,0,
- 0,1,0,0,
- -1,0,0,0};
- static uint8_t enc_val = 0;
- static int8_t enc_count = 0;
- exti_reset_request(EXTI8);
- 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 < 2 && enc_count > -2) {
- return;
- }
- if(change < 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:
|