initial commit
This commit is contained in:
commit
c5e6e28e6f
7 changed files with 607 additions and 0 deletions
2
README.md
Normal file
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
This is a collection of misc libraries, examples, etc. for stm32f103 (aka blue pill) + libopencm3
|
||||
(+ FreeRTOS, sometimes)
|
48
ad9850/ad98xx.c
Normal file
48
ad9850/ad98xx.c
Normal file
|
@ -0,0 +1,48 @@
|
|||
// This wants to be a "generic" library for ad9833, ad9850 and ad9851.
|
||||
// currently, only ad9851 is supported
|
||||
#include <stdint.h>
|
||||
#include <libopencm3/stm32/gpio.h>
|
||||
#include "lib/ad98xx.h"
|
||||
#include "lib/gpio_utils.h"
|
||||
|
||||
|
||||
/* ad98_init will initialize pins, not the whole port.
|
||||
* That is, you'll need to
|
||||
* rcc_periph_clock_enable(RCC_${dds.gpioport})
|
||||
* */
|
||||
void ad98_init(ad98_dds dds) {
|
||||
gpio_set_mode(dds.gpioport, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
|
||||
dds.reset | dds.clock | dds.load | dds.data);
|
||||
gpio_pulseHigh(dds.gpioport, dds.reset);
|
||||
gpio_pulseHigh(dds.gpioport, dds.clock);
|
||||
gpio_pulseHigh(dds.gpioport, dds.load);
|
||||
}
|
||||
|
||||
static void _send_byte(ad98_dds dds, unsigned char byte) {
|
||||
for(int i=0; i<8; i++) { // every bit
|
||||
char bit = ((byte >> i) & 1);
|
||||
if(bit) {
|
||||
gpio_set(dds.gpioport, dds.data);
|
||||
} else {
|
||||
gpio_clear(dds.gpioport, dds.data);
|
||||
}
|
||||
gpio_pulseHigh(dds.gpioport, dds.clock);
|
||||
}
|
||||
}
|
||||
|
||||
const float tunings[AD98_MODELS_NUM] = {AD98_AD9833_TUNING,
|
||||
AD98_AD9850_TUNING,
|
||||
AD98_AD9851_TUNING};
|
||||
|
||||
void ad98_set_frequency(ad98_dds dds, double frequency) { // requires dds_init to be called before it
|
||||
// support AD98_MODEL_AD9850
|
||||
uint32_t freqWord;
|
||||
freqWord = frequency * tunings[dds.model];
|
||||
for(char i=0; i<4; i++) {
|
||||
_send_byte(dds, freqWord & 0xFF);
|
||||
freqWord >>= 8;
|
||||
}
|
||||
_send_byte(dds, 0x01);
|
||||
gpio_pulseHigh(dds.gpioport, dds.load);
|
||||
}
|
||||
|
33
ad9850/ad98xx.h
Normal file
33
ad9850/ad98xx.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
// This wants to be a "generic" library for ad9833, ad9850 and ad9851.
|
||||
// currently, only ad9851 is supported and tested.
|
||||
// ad9850 is supported but not tested
|
||||
// ad9833 might just not work at all
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define AD98_MODEL_AD9833 (unsigned char) 0
|
||||
#define AD98_MODEL_AD9850 (unsigned char) 1
|
||||
#define AD98_MODEL_AD9851 (unsigned char) 2
|
||||
#define AD98_MODELS_NUM 3
|
||||
|
||||
#define AD98_AD9833_CLOCK 25000000
|
||||
#define AD98_AD9850_CLOCK 125000000
|
||||
#define AD98_AD9851_CLOCK 180000000
|
||||
#define AD98_AD9833_TUNING 171.8 // (2**32)/AD98_AD9850_CLOCK
|
||||
#define AD98_AD9850_TUNING 34.36 // (2**32)/AD98_AD9850_CLOCK
|
||||
#define AD98_AD9851_TUNING 23.86 // (2**32)/AD98_AD9851_CLOCK
|
||||
|
||||
typedef struct {
|
||||
unsigned char model; // 0 for ad9833, 1 for ad9850 and 2 for ad9851
|
||||
// same port for all pins!
|
||||
uint32_t gpioport;
|
||||
// pins
|
||||
uint16_t clock;
|
||||
uint16_t load; // aka FQ_UD aka FSYNC (on 9833)
|
||||
uint16_t data;
|
||||
uint16_t reset; // puoi anche metterlo a massa, in tal caso segnalo come 0 così non viene usato un pin inutile
|
||||
} ad98_dds;
|
||||
|
||||
void ad98_init(ad98_dds);
|
||||
void ad98_set_frequency(ad98_dds, double);
|
205
rotary_tbl/main.c
Normal file
205
rotary_tbl/main.c
Normal file
|
@ -0,0 +1,205 @@
|
|||
#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:
|
211
rotary_tbl2/main.c
Normal file
211
rotary_tbl2/main.c
Normal file
|
@ -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:
|
85
ssd1306/ssd1306.c
Normal file
85
ssd1306/ssd1306.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include <libopencm3/stm32/gpio.h>
|
||||
#include <libopencm3/stm32/spi.h>
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
#include "ssd1306.h"
|
||||
|
||||
|
||||
static void oled_command(struct ssd1306 *dev, uint8_t byte) {
|
||||
gpio_clear(dev->port,dev->dc);
|
||||
spi_enable(dev->spi);
|
||||
spi_xfer(dev->spi,byte);
|
||||
spi_disable(dev->spi);
|
||||
}
|
||||
|
||||
static void oled_command2(struct ssd1306 *dev, uint8_t byte,uint8_t byte2) {
|
||||
gpio_clear(dev->port,dev->dc);
|
||||
spi_enable(dev->spi);
|
||||
spi_xfer(dev->spi,byte);
|
||||
spi_xfer(dev->spi,byte2);
|
||||
spi_disable(dev->spi);
|
||||
}
|
||||
|
||||
static void oled_data(struct ssd1306 *dev, uint8_t byte) {
|
||||
gpio_set(dev->port,dev->dc);
|
||||
spi_enable(dev->spi);
|
||||
spi_xfer(dev->spi,byte);
|
||||
spi_disable(dev->spi);
|
||||
}
|
||||
|
||||
void oled_reset(struct ssd1306 *dev) {
|
||||
gpio_clear(dev->port,dev->rst);
|
||||
// TODO: that's ugly because it depends on 1) freertos 2) being inside a task
|
||||
vTaskDelay(1);
|
||||
gpio_set(dev->port,dev->rst);
|
||||
}
|
||||
|
||||
void oled_init(struct ssd1306 *dev) {
|
||||
static uint8_t cmds[] = {
|
||||
0xAE, 0x00, 0x10, 0x40, 0x81, 0xCF, 0xA1, 0xA6,
|
||||
0xA8, 0x3F, 0xD3, 0x00, 0xD5, 0x80, 0xD9, 0xF1,
|
||||
0xDA, 0x12, 0xDB, 0x40, 0x8D, 0x14, 0xAF, 0xFF };
|
||||
|
||||
oled_reset(dev);
|
||||
for ( unsigned ux=0; cmds[ux] != 0xFF; ++ux )
|
||||
oled_command(dev, cmds[ux]);
|
||||
}
|
||||
|
||||
void oled_setup(struct ssd1306 *dev) {
|
||||
gpio_set_mode(dev->port, GPIO_MODE_OUTPUT_2_MHZ,
|
||||
GPIO_CNF_OUTPUT_PUSHPULL, dev->dc | dev->rst);
|
||||
gpio_clear(dev->port, dev->rst);
|
||||
gpio_set_mode(dev->port, GPIO_MODE_OUTPUT_50_MHZ,
|
||||
GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
|
||||
dev->clock | dev->mosi | dev->cs);
|
||||
spi_reset(dev->spi);
|
||||
spi_init_master( dev->spi,
|
||||
SPI_CR1_BAUDRATE_FPCLK_DIV_256,
|
||||
SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE,
|
||||
SPI_CR1_CPHA_CLK_TRANSITION_1,
|
||||
SPI_CR1_DFF_8BIT,
|
||||
SPI_CR1_MSBFIRST
|
||||
);
|
||||
spi_disable_software_slave_management(dev->spi);
|
||||
spi_enable_ss_output(dev->spi);
|
||||
}
|
||||
|
||||
// buf must be exactly 128*64/8 bytes
|
||||
void oled_draw(struct ssd1306 *dev, uint8_t *buf) {
|
||||
oled_command2(dev, 0x20,0x02);// Page mode
|
||||
oled_command(dev, 0x40);
|
||||
oled_command2(dev, 0xD3,0x00);
|
||||
for ( uint8_t px=0; px<8; ++px ) {
|
||||
oled_command(dev, 0xB0|px);
|
||||
oled_command(dev, 0x00); // Lo col
|
||||
oled_command(dev, 0x10); // Hi col
|
||||
for ( unsigned bx=0; bx<128; ++bx ) {
|
||||
oled_data(dev, *buf++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
23
ssd1306/ssd1306.h
Normal file
23
ssd1306/ssd1306.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#ifndef SSD1306_H
|
||||
#define SSD1306_H
|
||||
struct ssd1306 {
|
||||
uint32_t spi;
|
||||
uint32_t port;
|
||||
|
||||
// those are actually determined by "spi"
|
||||
uint16_t clock;
|
||||
uint16_t mosi;
|
||||
uint16_t cs; // aka NSS
|
||||
|
||||
uint16_t dc;
|
||||
uint16_t rst;
|
||||
};
|
||||
|
||||
void oled_reset(struct ssd1306*);
|
||||
void oled_init(struct ssd1306*);
|
||||
void oled_setup(struct ssd1306*);
|
||||
void oled_draw(struct ssd1306*, uint8_t *buf);
|
||||
|
||||
#endif // SSD1306_H
|
Loading…
Reference in a new issue