SD card: establish spi.c/.h and sd.c/.h and get SPI running.

For now this is just a nice demonstration on how to send bytes
over SPI. Add SD_CARD_SELECT_PIN to your configuration board
file manually to see data signals on MOSI dancing on the scope.

The TODO's about SS in arduino*.h were wrong, SS does have a
chip-specific special meaning (used in SPI multi-master or SPI
slave mode). Still, a #define MAX6675_SELECT_PIN is missing.

Squashed in this commit from the SPI development topic branch to
get this first step working:

Author: jbernardis <jeff.bernardis@gmail.com>
2015-02-04 22:35:07
mendel.c: disable SPI in power management only when not needed.

If we want to talk to a SD card connected to SPI, we need SPI
powered, of course.

From Traumflug: nice catch, Jeff!
This commit is contained in:
Markus Hitter 2015-06-10 00:22:20 +02:00
parent b4145e683a
commit 1fb3ece31e
12 changed files with 230 additions and 57 deletions

View File

@ -6,10 +6,6 @@
#define SCK DIO52
#define MISO DIO50
#define MOSI DIO51
// TODO: This depends on the board, so this definition is misplaced here,
// should be more appropriately named and go to config.h. It's used in
// temp.c and simulator.h and defines the Chip Select pin for an eventual
// MAX6675 temperature sensor.
#define SS DIO53
// TWI (I2C)

View File

@ -6,10 +6,6 @@
#define SCK DIO13
#define MISO DIO12
#define MOSI DIO11
// TODO: This depends on the board, so this definition is misplaced here,
// should be more appropriately named and go to config.h. It's used in
// temp.c and simulator.h and defines the Chip Select pin for an eventual
// MAX6675 temperature sensor.
#define SS DIO10
// TWI (I2C)

View File

@ -30,10 +30,6 @@
#define SCK DIO1
#define MISO DIO3
#define MOSI DIO2
// TODO: This depends on the board, so this definition is misplaced here,
// should be more appropriately named and go to config.h. It's used in
// temp.c and simulator.h and defines the Chip Select pin for an eventual
// MAX6675 temperature sensor.
#define SS DIO0
// TWI (I2C)

View File

@ -11,10 +11,6 @@
#define SCK DIO7
#define MISO DIO6
#define MOSI DIO5
// TODO: This depends on the board, so this definition is misplaced here,
// should be more appropriately named and go to config.h. It's used in
// temp.c and simulator.h and defines the Chip Select pin for an eventual
// MAX6675 temperature sensor.
#define SS DIO4
// TWI (I2C)

View File

@ -11,10 +11,6 @@
#define SCK DIO9
#define MISO DIO11
#define MOSI DIO10
// TODO: This depends on the board, so this definition is misplaced here,
// should be more appropriately named and go to config.h. It's used in
// temp.c and simulator.h and defines the Chip Select pin for an eventual
// MAX6675 temperature sensor.
#define SS DIO8

View File

@ -2,10 +2,6 @@
#define SCK DIO9
#define MISO DIO11
#define MOSI DIO10
// TODO: This depends on the board, so this definition is misplaced here,
// should be more appropriately named and go to config.h. It's used in
// temp.c and simulator.h and defines the Chip Select pin for an eventual
// MAX6675 temperature sensor.
#define SS DIO8

View File

@ -49,6 +49,8 @@
#include "clock.h"
#include "intercom.h"
#include "simulator.h"
#include "spi.h"
#include "sd.h"
#ifdef SIMINFO
#include "../simulavr/src/simulavr_info.h"
@ -66,9 +68,15 @@
void io_init(void) {
// disable modules we don't use
#ifdef PRR
PRR = MASK(PRTWI) | MASK(PRADC) | MASK(PRSPI);
PRR = MASK(PRTWI) | MASK(PRADC);
#if ! defined TEMP_MAX6675 && ! defined SD
PRR |= MASK(PRSPI);
#endif
#elif defined PRR0
PRR0 = MASK(PRTWI) | MASK(PRADC) | MASK(PRSPI);
PRR0 = MASK(PRTWI) | MASK(PRADC);
#if ! defined TEMP_MAX6675 && ! defined SD
PRR0 |= MASK(PRSPI);
#endif
#if defined(PRUSART3)
// don't use USART2 or USART3- leave USART1 for GEN3 and derivatives
PRR1 |= MASK(PRUSART3) | MASK(PRUSART2);
@ -179,20 +187,18 @@ void io_init(void) {
power_off();
#endif
#ifdef TEMP_MAX6675
// setup SPI
WRITE(SCK, 0); SET_OUTPUT(SCK);
WRITE(MOSI, 1); SET_OUTPUT(MOSI);
WRITE(MISO, 1); SET_INPUT(MISO);
#endif
#ifdef DEBUG_LED_PIN
WRITE(DEBUG_LED_PIN, 0);
SET_OUTPUT(DEBUG_LED_PIN);
#endif
}
/// Startup code, run when we come out of reset
/** Initialise all the subsystems.
Note that order of appearance is critical here. For example, running
spi_init() before io_init() makes SPI fail (for reasons not exactly
investigated).
*/
void init(void) {
// set up watchdog
wd_init();
@ -206,6 +212,10 @@ void init(void) {
// set up inputs and outputs
io_init();
#if defined TEMP_MAX6675 || defined SD
spi_init();
#endif
// set up timers
timer_init();
@ -222,6 +232,10 @@ void init(void) {
// set up temperature inputs
temp_init();
#ifdef SD
sd_init();
#endif
// enable interrupts
sei();
@ -249,6 +263,23 @@ int main (void)
#endif
init();
#ifdef SD
// This demonstrates SPI. Gives a nice dance on the scope.
// Will go away as soon as we can do something useful with the SD card.
#include "delay.h"
uint8_t c = 0;
while (1) {
// Trigger signal for the scope.
WRITE(STEPPER_ENABLE_PIN, 0);
delay_us(10);
WRITE(STEPPER_ENABLE_PIN, 1);
spi_rw(c);
c++;
delay_ms(50);
}
#endif
// main loop
for (;;)
{

15
sd.c Normal file
View File

@ -0,0 +1,15 @@
/** \file Coordinating reading and writing of SD cards.
*/
#include "sd.h"
#ifdef SD
void sd_init(void) {
WRITE(SD_CARD_SELECT_PIN, 1);
SET_OUTPUT(SD_CARD_SELECT_PIN);
}
#endif /* SD */

17
sd.h Normal file
View File

@ -0,0 +1,17 @@
/** \file Coordinating reading and writing of SD cards.
*/
#ifndef _SD_H
#define _SD_H
#include "config_wrapper.h"
#ifdef SD_CARD_SELECT_PIN
#define SD
void sd_init(void);
#endif /* SD_CARD_SELECT_PIN */
#endif /* _SD_H */

45
spi.c Normal file
View File

@ -0,0 +1,45 @@
/** \file
\brief SPI subsystem
This is much simpler than Teacup's serial subsystem. No ring buffers, no
"write string" functions. Usually using SPI directly is faster than fiddling
with buffers. For example, reading or writing a byte can be done in as few
as 20 clock cycles.
Other than serial, SPI has to deal with multiple devices. Device selection
happens before reading and writing, data exchange its self is the same for
each device, then.
*/
#include "spi.h"
#include "arduino.h"
/** Initialise serial subsystem.
Code copied from ATmega164/324/644/1284 data sheet, section 18.2, page 160,
or moved here from mendel.c.
*/
void spi_init() {
// Set SCK (clock) and MOSI line to output, ie. set USART in master mode.
SET_OUTPUT(SCK);
SET_OUTPUT(MOSI);
SET_INPUT(MISO);
// SS must be set as output to disconnect it from the SPI subsystem.
// Too bad if something else tries to use this pin as digital input.
// See ATmega164/324/644/1284 data sheet, section 18.3.2, page 162.
// Not written there: this must apparently be done before setting the SPRC
// register, else future R/W-operations may hang.
SET_OUTPUT(SS);
#ifdef SPI_2X
SPSR = 0x01;
#else
SPSR = 0x00;
#endif
// This sets the whole SPRC register.
spi_speed_100_400();
}

102
spi.h Normal file
View File

@ -0,0 +1,102 @@
#ifndef _SPI_H
#define _SPI_H
#include "config_wrapper.h"
#include "arduino.h"
// Uncomment this to double SPI frequency from (F_CPU / 4) to (F_CPU / 2).
//#define SPI_2X
/** Initialise SPI subsystem.
*/
void spi_init(void);
/** SPI device selection.
Because out famous WRITE() macro works with constant pins, only, we define
a (de)select function for each of them. In case you add another SPI device,
you also have to define a pair of these functions.
*/
#ifdef SD
static void spi_select_sd(void) __attribute__ ((always_inline));
inline void spi_select_sd(void) {
WRITE(SD_CARD_SELECT_PIN, 0);
}
static void spi_deselect_sd(void) __attribute__ ((always_inline));
inline void spi_deselect_sd(void) {
WRITE(SD_CARD_SELECT_PIN, 1);
}
#endif /* SD */
#ifdef TEMP_MAX6675
// Note: the pin choosen with DEFINE_TEMP_SENSOR() in the board configuration
// should be used here. Currently it's a requirement that this device's
// Chip Select pin is actually SS, while any other pin would work just
// as fine.
static void spi_select_max6675(void) __attribute__ ((always_inline));
inline void spi_select_max6675(void) {
WRITE(SS, 0);
}
static void spi_deselect_max6675(void) __attribute__ ((always_inline));
inline void spi_deselect_max6675(void) {
WRITE(SS, 1);
}
#endif /* TEMP_MAX6675 */
/** Set SPI clock speed to something between 100 and 400 kHz.
This is needed for initialising SD cards. We set the whole SPCR register
in one step, because this is faster than and'ing in bits.
About dividers. We have:
SPCR = 0x50; // normal mode: (F_CPU / 4), 2x mode: (F_CPU / 2)
SPCR = 0x51; // normal mode: (F_CPU / 16), 2x mode: (F_CPU / 8)
SPCR = 0x52; // normal mode: (F_CPU / 64), 2x mode: (F_CPU / 32)
SPCR = 0x53; // normal mode: (F_CPU / 128), 2x mode: (F_CPU / 64)
For now we always choose the /128 divider, because it fits nicely in all
expected situations:
F_CPU 16 MHz 20 MHz
SPI clock normal mode 125 kHz 156 kHz
SPI clock 2x mode 250 kHz 312 kHz
About the other bits:
0x50 = (1 << SPE) | (1 << MSTR);
This is Master SPI mode, SPI enabled, interrupts disabled, polarity mode 0
(right for SD cards).
See ATmega164/324/644/1284 data sheet, section 18.5.1, page 164.
*/
static void spi_speed_100_400(void) __attribute__ ((always_inline));
inline void spi_speed_100_400(void) {
SPCR = 0x53;
}
/** Set SPI clock speed to maximum.
*/
static void spi_speed_max(void) __attribute__ ((always_inline));
inline void spi_speed_max(void) {
SPCR = 0x50; // See list at spi_speed_100_400().
}
/** Exchange a byte over SPI.
Yes, SPI is that simple and you can always only swap bytes. To retrieve
a byte, simply send a dummy value, like: mybyte = spi_rw(0);
As we operate in master mode, we don't have to fear to hang due to
communications errors (e.g. missing a clock beat).
Note: insisting on inlinig (attribute always_inline) costs about 80 bytes
with the current SD card code.
*/
static uint8_t spi_rw(uint8_t) __attribute__ ((always_inline));
inline uint8_t spi_rw(uint8_t byte) {
SPDR = byte;
loop_until_bit_is_set(SPSR, SPIF);
return SPDR;
}
#endif /* _SPI_H */

33
temp.c
View File

@ -27,6 +27,7 @@
#endif
#ifdef TEMP_MAX6675
#include "spi.h"
#endif
#ifdef TEMP_THERMISTOR
@ -83,8 +84,9 @@ void temp_init() {
switch(temp_sensors[i].temp_type) {
#ifdef TEMP_MAX6675
case TT_MAX6675:
WRITE(SS, 1); // Turn sensor off.
SET_OUTPUT(SS);
// Note that MAX6675's Chip Select pin is currently hardcoded to SS.
// This isn't neccessary. See also spi.h.
spi_deselect_max6675();
// Intentionally no break, we might have more than one sensor type.
#endif
@ -132,33 +134,18 @@ void temp_sensor_tick() {
switch(temp_sensors[i].temp_type) {
#ifdef TEMP_MAX6675
case TT_MAX6675:
#ifdef PRR
PRR &= ~MASK(PRSPI);
#elif defined PRR0
PRR0 &= ~MASK(PRSPI);
#endif
SPCR = MASK(MSTR) | MASK(SPE) | MASK(SPR0);
// enable TT_MAX6675
WRITE(SS, 0);
// Note: value reading in this section was rewritten without
// testing when spi.c/.h was introduced. --Traumflug
spi_select_max6675();
// No delay required, see
// https://github.com/triffid/Teacup_Firmware/issues/22
// read MSB
SPDR = 0;
for (;(SPSR & MASK(SPIF)) == 0;);
temp = SPDR;
temp <<= 8;
temp = spi_rw(0) << 8;
// read LSB
SPDR = 0;
for (;(SPSR & MASK(SPIF)) == 0;);
temp |= SPDR;
temp |= spi_rw(0);
// disable TT_MAX6675
WRITE(SS, 1);
spi_deselect_max6675();
temp_sensors_runtime[i].temp_flags = 0;
if ((temp & 0x8002) == 0) {