initial commit

This commit is contained in:
boyska 2018-10-19 11:58:19 +02:00
commit c5e6e28e6f
7 changed files with 607 additions and 0 deletions

2
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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