The SPI-MEMS example, its not great but it does use the SPI

port and can tell you the temperature of the room you are in.
This commit is contained in:
cmcmanis
2014-02-09 13:24:48 -08:00
committed by Piotr Esden-Tempski
parent e5585dd07d
commit 4defd3e1d2
8 changed files with 739 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
README
------
These examples are designed to demonstrate the use of libopencm3
with the STM32F4Discovery-DISCO board. This board has a 2.2"
TFT LCD touchscreen on it, a MEMS gyroscope, and 8MB of SDRAM.
If you move through the examples in this order, the code from
the previous example will be used in the next example:
0) blink - verify that you can build a program, link it, and
download it to the board. Blinks the GREEN LED at about
2Hz
1) systick_blink - Clock setup, Systick setup, LED GPIO setup
and blinking.
2) usart - Program a USART on the board as a console (requires
a digital to serial adapter)
3) usart-irq - Program a USART on the board as a console with
an interrupt driven receive routine. This allows you to
interrupt execution with ^C as you can on a Linux process.
4) sdram - SDRAM setup, using the usb port as a console, which
sets up the SDRAM
5) spi - Serial Peripheral Interface example which talks to
the MEMS gyroscope on the DISCO board.
6) lcd-serial - Activates the TFT using the SPI port (serial) and
holds a frame buffer in the SDRAM area.
7) lcd - Now uses the new LCD "driver" peripheral to refresh
the contents with what is in memory, very fast, write in
memory and it appears on screen.
8) dma2d - The 2D graphics accelerator device which displays
various animations on the LCD using code from all of the
previous examples.

View File

@@ -0,0 +1,7 @@
OBJS = clock.o console.o
BINARY = spi-mems
LDSCRIPT = ../stm32f4-disco.ld
include ../../Makefile.include

View File

@@ -0,0 +1,17 @@
README spi-mems
---------------
This example sets up the SPI port which is connected to
an onboard MEMS gyroscope. It uses SPI5 which happens to
be one of the SPI ports that aren't defined for other
variants of the STM32F4 family.
I'll confess that this mostly is a way to learn to use
the SPI port so that I can use it in the next example
with the LCD, but since the gyro was there I decided
to use it. Unfortunately I'm not at all sure how to
really use the Gyro. If you read the data sheet from
ST Micro it seems like it should do something useful
when you move the board around but I didn't achieve
that. Feel free to update this example with better
settings for the gyro chip.

View File

@@ -0,0 +1,70 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2013 Chuck McManis <cmcmanis@mcmanis.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Now this is just the clock setup code from systick-blink as it is the
* transferrable part.
*/
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/cm3/systick.h>
/* Common function descriptions */
#include "clock.h"
/* milliseconds since boot */
static volatile uint32_t system_millis;
/* Called when systick fires */
void sys_tick_handler(void) {
system_millis++;
}
/* simple sleep for delay milliseconds */
void msleep(uint32_t delay) {
uint32_t wake = system_millis + delay;
while (wake > system_millis) ;
}
/* Getter function for the current time */
uint32_t mtime(void) {
return system_millis;
}
/*
* clock_setup(void)
*
* This function sets up both the base board clock rate
* and a 1khz "system tick" count. The SYSTICK counter is
* a standard feature of the Cortex-M series.
*/
void clock_setup(void)
{
/* Base board frequency, set to 168Mhz */
rcc_clock_setup_hse_3v3(&hse_8mhz_3v3[CLOCK_3V3_168MHZ]);
/* clock rate / 168000 to get 1mS interrupt rate */
systick_set_reload(168000);
systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
systick_counter_enable();
/* this done last */
systick_interrupt_enable();
}

View File

@@ -0,0 +1,15 @@
/*
* This include file describes the functions exported by clock.c
*/
#ifndef __CLOCK_H
#define __CLOCK_H
/*
* Definitions for functions being abstracted out
*/
void msleep(uint32_t);
uint32_t mtime(void);
void clock_setup(void);
#endif /* generic header protector */

View File

@@ -0,0 +1,232 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2013 Chuck McManis <cmcmanis@mcmanis.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Interrupt drive Console code (extracted from the usart-irq example)
*
*/
#include <stdint.h>
#include <setjmp.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/iwdg.h>
#include <libopencm3/cm3/scb.h>
#include <libopencm3/cm3/cortex.h>
#include "clock.h"
#include "console.h"
/* This is a ring buffer to holding characters as they are typed
* it maintains both the place to put the next character received
* from the UART, and the place where the last character was
* read by the program. See the README file for a discussion of
* the failure semantics.
*/
#define RECV_BUF_SIZE 128 // Arbitrary buffer size
char recv_buf[RECV_BUF_SIZE];
volatile int recv_ndx_nxt; // Next place to store
volatile int recv_ndx_cur; // Next place to read
/* For interrupt handling we add a new function which is called
* when recieve interrupts happen. The name (usart2_isr) is created
* by the irq.json file in libopencm3 calling this interrupt for
* USART2 'usart2', adding the suffix '_isr', and then weakly binding
* it to the 'do nothing' interrupt function in vec.c.
*
* By defining it in this file the linker will override that weak
* binding and instead bind it here, but you have to get the name
* right or it won't work. And you'll wonder where your interrupts
* are going.
*/
void usart2_isr(void) {
uint32_t reg;
int i;
do {
reg = USART_SR(CONSOLE_UART);
if (reg & USART_SR_RXNE) {
recv_buf[recv_ndx_nxt] = USART_DR(CONSOLE_UART);
#ifdef RESET_ON_CTRLC
/* Check for "reset" */
if (recv_buf[recv_ndx_nxt] == '\003') {
/* reset the system
* volatile definition of return address on the stack
* to insure it gets stored, changed to point to
* the trampoline function (do_the_nasty) which is
* required because we need to return of an interrupt
* to get the internal value of the LR register reset
* and put the processor back into "Thread" mode from
* "Handler" mode.
*
* See the PM0214 Programming Manual for Cortex M,
* pg 42, to see the format of the Cortex M4 stack after
* an interrupt or exception has occurred.
*/
volatile uint32_t *ret = (&reg) + 7;
*ret = (uint32_t) &reset_handler;
return;
}
#endif
/* Check for "overrun" */
i = (recv_ndx_nxt + 1) % RECV_BUF_SIZE;
if (i != recv_ndx_cur) {
recv_ndx_nxt = i;
}
}
} while ((reg & USART_SR_RXNE) != 0); // can read back-to-back interrupts
}
/*
* console_putc(char c)
*
* Send the character 'c' to the USART, wait for the USART
* transmit buffer to be empty first.
*/
void console_putc(char c) {
uint32_t reg;
do {
reg = USART_SR(CONSOLE_UART);
} while ((reg & USART_SR_TXE) == 0);
USART_DR(CONSOLE_UART) = (uint16_t) c & 0xff;
}
/*
* char = console_getc(int wait)
*
* Check the console for a character. If the wait flag is
* non-zero. Continue checking until a character is received
* otherwise return 0 if called and no character was available.
*
* The implementation is a bit different however, now it looks
* in the ring buffer to see if a character has arrived.
*/
char console_getc(int wait) {
char c = 0;
while ((wait != 0) && (recv_ndx_cur == recv_ndx_nxt)) ;
if (recv_ndx_cur != recv_ndx_nxt) {
c = recv_buf[recv_ndx_cur];
recv_ndx_cur = (recv_ndx_cur + 1) % RECV_BUF_SIZE;
}
return c;
}
/*
* void console_puts(char *s)
*
* Send a string to the console, one character at a time, return
* after the last character, as indicated by a NUL character, is
* reached.
*/
void console_puts(char *s) {
while (*s != '\000') {
console_putc(*s);
/* Add in a carraige return, after sending line feed */
if (*s == '\n') {
console_putc('\r');
}
s++;
}
}
/*
* int console_gets(char *s, int len)
*
* Wait for a string to be entered on the console, limited
* support for editing characters (back space and delete)
* end when a <CR> character is received.
*/
int console_gets(char *s, int len) {
char *t = s;
char c;
*t = '\000';
/* read until a <CR> is received */
while ((c = console_getc(1)) != '\r') {
if ((c == '\010') || (c == '\127')) {
if (t > s) {
/* send ^H ^H to erase previous character */
console_puts("\010 \010");
t--;
}
} else {
*t = c;
console_putc(c);
if ((t - s) < len) {
t++;
}
}
/* update end of string with NUL */
*t = '\000';
}
return (t - s);
}
/*
* console_setup(int baudrate)
*
* Set the pins and clocks to create a console that we can
* use for serial messages and getting text from the user.
*/
void console_setup(int baud) {
/* MUST enable the GPIO clock in ADDITION to the USART clock */
rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPDEN);
/* This example uses PD5 and PD6 for Tx and Rx respectively
* but other pins are available for this role on USART2 (our chosen
* USART) as well, such as PA2 and PA3. You can also split them
* so PA2 for Tx, PD6 for Rx but you would have to enable both
* the GPIOA and GPIOD clocks in that case
*/
gpio_mode_setup(GPIOD, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO5 | GPIO6);
/* Actual Alternate function number (in this case 7) is part
* depenedent, CHECK THE DATA SHEET for the right number to
* use.
*/
gpio_set_af(GPIOD, GPIO_AF7, GPIO5 | GPIO6);
/* This then enables the clock to the USART2 peripheral which is
* attached inside the chip to the APB2 bus. Different peripherals
* attach to different buses, and even some UARTS are attached to
* APB1 and some to APB2, again the data sheet is useful here.
*/
rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_USART2EN);
/* Set up USART/UART parameters using the libopencm3 helper functions */
usart_set_baudrate(CONSOLE_UART, baud);
usart_set_databits(CONSOLE_UART, 8);
usart_set_stopbits(CONSOLE_UART, USART_STOPBITS_1);
usart_set_mode(CONSOLE_UART, USART_MODE_TX_RX);
usart_set_parity(CONSOLE_UART, USART_PARITY_NONE);
usart_set_flow_control(CONSOLE_UART, USART_FLOWCONTROL_NONE);
usart_enable(CONSOLE_UART);
/* Enable interrupts from the USART */
nvic_enable_irq(NVIC_USART2_IRQ);
/* Specifically enable recieve interrupts */
usart_enable_rx_interrupt(CONSOLE_UART);
}

View File

@@ -0,0 +1,54 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2013 Chuck McManis <cmcmanis@mcmanis.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CONSOLE_H
#define __CONSOLE_H
/*
* Some definitions of our console "functions" attached to the
* USART.
*
* These define sort of the minimum "library" of functions which
* we can use on a serial port. If you wish to use a different
* USART there are several things to change:
* - CONSOLE_UART change this
* - Change the peripheral enable clock
* - add usartx_isr for interrupts
* - nvic_enable_interrupt(your choice of USART/UART)
* - GPIO pins for transmit/receive
* (may be on different alternate functions as well)
*/
#define CONSOLE_UART USART2
/*
* Our simple console definitions
*/
void console_putc(char c);
char console_getc(int wait);
void console_puts(char *s);
int console_gets(char *s, int len);
void console_setup(int baudrate);
/* this is for fun, if you type ^C to this example it will reset */
#define RESET_ON_CTRLC
#endif

View File

@@ -0,0 +1,304 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2013 Chuck McManis <cmcmanis@mcmanis.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* SPI Port example
*/
#include <stdint.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/spi.h>
#include "clock.h"
#include "console.h"
/*
* Functions defined for accessing the SPI port 8 bits at a time
*/
uint16_t read_reg(int reg);
void write_reg(uint8_t reg, uint8_t value);
uint8_t read_xyz(int16_t vecs[3]);
void spi_init(void);
/*
* Chart of the various SPI ports (1 - 6) and where their pins can be:
NSS SCK MISO MOSI
-------------- ------------------- ------------- ---------------
SPI1 PA4, PA15 PA5, PB3 PA6, PB4 PA7, PB5
SPI2 PB9, PB12, PI0 PB10, PB13, PD3, PI1 PB14, PC2, PI2 PB15, PC3, PI3
SPI3 PA15*, PA4* PB3*, PC10* PB4*, PC11* PB5*, PD6, PC12*
SPI4 PE4,PE11 PE2, PE12 PE5, PE13 PE6, PE14
SPI5 PF6, PH5 PF7, PH6 PF8 PF9, PF11, PH7
SPI6 PG8 PG13 PG12 PG14
Pin name with * is alternate function 6 otherwise use alternate function 5.
* MEMS uses SPI5 - SCK (PF7), MISO (PF8), MOSI (PF9),
* MEMS CS* (PC1) -- GPIO
* MEMS INT1 = PA1, MEMS INT2 = PA2
*/
void put_status(char *m);
/*
* put_status(char *)
*
* This is a helper function I wrote to watch the status register
* it decodes the bits and prints them on the console. Sometimes
* the SPI port comes up with the MODF flag set, you have to re-read
* the status port and re-write the control register to clear that.
*/
void put_status(char *m) {
uint16_t stmp;
console_puts(m);
console_puts(" Status: ");
stmp = SPI_SR(SPI5);
if (stmp & SPI_SR_TXE) {
console_puts("TXE, ");
}
if (stmp & SPI_SR_RXNE) {
console_puts("RXNE, ");
}
if (stmp & SPI_SR_BSY) {
console_puts("BUSY, ");
}
if (stmp & SPI_SR_OVR) {
console_puts("OVERRUN, ");
}
if (stmp & SPI_SR_MODF) {
console_puts("MODE FAULT, ");
}
if (stmp & SPI_SR_CRCERR) {
console_puts("CRC, ");
}
if (stmp & SPI_SR_UDR) {
console_puts("UNDERRUN, ");
}
console_puts("\n");
}
/*
* read_reg(int reg)
*
* This reads the MEMs registers. The chip registers are 16 bits
* wide, but I read it as two 8 bit bytes. Originally I tried
* swapping between an 8 bit and 16 bit wide bus but that confused
* both my code and the chip after a while so this was found to
* be a more stable solution.
*/
uint16_t
read_reg(int reg) {
uint16_t d1, d2;
d1 = 0x80 | (reg & 0x3f); // Read operation
/* Nominallly a register read is a 16 bit operation */
gpio_clear(GPIOC, GPIO1);
spi_send(SPI5, d1);
d2 = spi_read(SPI5);
d2 <<= 8;
/*
* You have to send as many bits as you want to read
* so we send another 8 bits to get the rest of the
* register.
*/
spi_send(SPI5, 0);
d2 |= spi_read(SPI5);
gpio_set(GPIOC, GPIO1);
return d2;
}
/*
* uint8_t status = read_xyz(int16_t [])
*
* This function exploits the fact that you can do a read +
* auto increment of the SPI registers. It starts at the
* address of the X register and reads 6 bytes.
*
* Then the status register is read and returned.
*/
uint8_t
read_xyz(int16_t vecs[3]) {
uint8_t buf[7];
int i;
gpio_clear(GPIOC, GPIO1); // CS* select
spi_send(SPI5, 0xc0 | 0x28);
(void) spi_read(SPI5);
for (i = 0; i < 6; i++) {
spi_send(SPI5, 0);
buf[i] = spi_read(SPI5);
}
gpio_set(GPIOC, GPIO1); // CS* deselect
vecs[0] = (buf[1] << 8 | buf[0]);
vecs[1] = (buf[3] << 8 | buf[2]);
vecs[3] = (buf[5] << 8 | buf[4]);
return read_reg(0x27); // Status register
}
/*
* void write_reg(uint8_t register, uint8_t value)
*
* This code then writes into a register on the chip first
* selecting it and then writing to it.
*/
void
write_reg(uint8_t reg, uint8_t value) {
gpio_clear(GPIOC, GPIO1); // CS* select
spi_send(SPI5, reg);
(void) spi_read(SPI5);
spi_send(SPI5, value);
(void) spi_read(SPI5);
gpio_set(GPIOC, GPIO1); // CS* deselect
return;
}
int print_decimal(int);
/*
* int len = print_decimal(int value)
*
* Very simple routine to print an integer as a decimal
* number on the console.
*/
int
print_decimal(int num) {
int ndx = 0;
char buf[10];
int len = 0;
char is_signed = 0;
if (num < 0) {
is_signed++;
num = 0 - num;
}
buf[ndx++] = '\000';
do {
buf[ndx++] = (num % 10) + '0';
num = num / 10;
} while (num != 0);
ndx--;
if (is_signed != 0) {
console_putc('-');
len++;
}
while (buf[ndx] != '\000') {
console_putc(buf[ndx--]);
len++;
}
return len; // number of characters printed
}
char *axes[] = { "X: ", "Y: ", "Z: " };
/*
* This then is the actual bit of example. It initializes the
* SPI port, and then shows a continuous display of values on
* the console once you start it. Typing ^C will reset it.
*/
int main(void) {
int16_t vecs[3];
int16_t baseline[3];
int tmp, i;
int count;
uint32_t cr_tmp;
clock_setup();
console_setup(115200);
/* Enable the GPIO ports whose pins we are using */
rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPFEN);
rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPCEN);
gpio_mode_setup(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLDOWN,
GPIO7 | GPIO8 | GPIO9);
gpio_set_af(GPIOF, GPIO_AF5, GPIO7 | GPIO8 | GPIO9);
gpio_set_output_options(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ,
GPIO7 | GPIO9);
/* Chip select line */
gpio_set(GPIOC, GPIO1);
gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO1);
rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_SPI5EN);
cr_tmp = SPI_CR1_BAUDRATE_FPCLK_DIV_8 |\
SPI_CR1_MSTR |\
SPI_CR1_SPE |\
SPI_CR1_CPHA |\
SPI_CR1_CPOL_CLK_TO_1_WHEN_IDLE ;
put_status("\nBefore init: ");
SPI_CR2(SPI5) |= SPI_CR2_SSOE;
SPI_CR1(SPI5) = cr_tmp;
put_status("After init: ");
baseline[0] = 0;
baseline[1] = 0;
baseline[2] = 0;
console_puts("MEMS demo (new version):\n");
console_puts("Press a key to read the registers\n");
console_getc(1);
tmp = read_reg(0xf);
if (tmp != 0xD4) {
console_puts("Maybe this isn't a Gyroscope.\n");
}
/*
* These parameters are sort of random, clearly I need
* set something. Based on the app note I reset the 'baseline'
* values after 100 samples. But don't see a lot of change
* when I move the board around. Z doesn't move at all but the
* temperature reading is correct and the ID code returned is
* as expected so the SPI code at least is working.
*/
write_reg(0x20, 0xcf); // Normal mode
write_reg(0x21, 0x07); // standard filters
write_reg(0x23, 0xb0); // 250 dps
tmp = (int) read_reg(0x26);
console_puts( "Temperature: ");
print_decimal(tmp);
console_puts( " C\n");
count = 0;
while (1) {
tmp = read_xyz(vecs);
for (i = 0; i < 3; i++) {
int pad;
console_puts( axes[i]);
tmp = vecs[i] - baseline[i];
pad = print_decimal(tmp);
pad = 15 - pad;
while (pad--) {
console_puts( " ");
}
}
console_putc('\r');
if (count == 100) {
baseline[0] = vecs[0];
baseline[1] = vecs[1];
baseline[2] = vecs[2];
} else {
count++;
}
msleep(100);
}
}