spark_counter/arduino_nano/lib/spi.c

158 lines
5.4 KiB
C

/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* Copyright (c) 2015 King Kévin <kingkevin@cuvoodoo.info> */
/* This library handles the Serial Peripheral Interface (SPI)
* it uses the SPI port and can but used with or without interrupt (non-block or blocking)
*/
#include <stdint.h> // Standard Integer Types
#include <stdlib.h> // General utilities
#include <stdbool.h> // Boolean
#include <avr/io.h> // AVR device-specific IO definitions
#include <avr/interrupt.h> // Interrupts
#include <avr/sleep.h> // Power Management and Sleep Modes
#include <spi.h> // SPI configuration
volatile uint8_t* spi_b = NULL; // the byte address to transmit/receive
volatile size_t spi_i = 0; // how many remaining bytes to transmit
/* receive SPI byte and transmit next on previous transmit completion when transfer is interrupt based (non-blocking) */
ISR(SPI_STC_vect)
{ /* SPI transfer complete interrupt */
*spi_b = SPDR; // save received byte
spi_i--; // decrement remaining bytes to transmit/receive
if (spi_i==0) { // no remaining bytes to transmit/receive
SPI_PORT |= (1<<SS_IO); // de-select slave
SPCR &= ~(1<<SPIE); // disable SPI interrupt
} else { // bytes to transmit/receive remain
spi_b++; // go to next byte
SPDR = *spi_b; // transmit next byte
}
}
/* initialize SPI
* returns true if initialisation succeeded, else false
*/
void spi_init(void)
{
/* configure IO */
MCUCR &= ~(1<<PUD); // enable global pull-up
SPI_DDR |= (1<<SCK_IO)|(1<<MOSI_IO)|(1<<SS_IO); // set SCK, MOSI, and SS as output
SPI_DDR &= ~(1<<MISO_IO); // set MISO as input
SPI_PORT |= (1<<MISO_IO); // enable pull-up on MISO
SPI_PORT |= (1<<SS_IO); // de-select slave
/* configure SPI */
SPCR &= ~(1<<SPIE); // disable SPI interrupt
SPCR &= ~(1<<DORD); // MSB first
SPCR &= ~(1<<CPOL); // SCK low on idle
SPCR &= ~(1<<CPHA); // sample on leading SCK edge
SPCR |= (1<<MSTR); // set as master
// use nearset lowest clock divider value
if (SCK_DIV>=128) {
SPSR &= ~(1<<SPI2X);
SPCR |= (1<<SPR1);
SPCR |= (1<<SPR0);
} else if (SCK_DIV>=64) {
SPSR |= (1<<SPI2X);
SPCR |= (1<<SPR1);
SPCR |= (1<<SPR0);
} else if (SCK_DIV>=32) {
SPSR |= (1<<SPI2X);
SPCR |= (1<<SPR1);
SPCR &= ~(1<<SPR0);
} else if (SCK_DIV>=16) {
SPSR &= ~(1<<SPI2X);
SPCR &= ~(1<<SPR1);
SPCR |= (1<<SPR0);
} else if (SCK_DIV>=8) {
SPSR |= (1<<SPI2X);
SPCR &= ~(1<<SPR1);
SPCR |= (1<<SPR0);
} else if (SCK_DIV>=4) {
SPSR &= ~(1<<SPI2X);
SPCR &= ~(1<<SPR1);
SPCR &= ~(1<<SPR0);
} else { // use 2 divider
SPSR |= (1<<SPI2X);
SPCR &= ~(1<<SPR1);
SPCR &= ~(1<<SPR0);
}
SPCR |= (1<<SPE); // enable SPI
}
/* transmit and receive data over SPI
* transmits than receives each byte from <data> for <length> bytes
* the read bytes are saved back in <data>
* this function returns only when communication finished
*/
void spi_transfer_blocking(uint8_t* data, size_t length)
{
if ((data==NULL) || (length==0)) { // verify is there is data to transmit
return;
}
SPCR &= ~(1<<SPIE); // disable SPI interrupt
SPI_PORT &= ~(1<<SS_IO); // select slave
for (size_t i=0; i<length; i++) {
SPDR = data[i]; // send byte
while(!(SPSR & (1<<SPIF))); // wait until transmission completed
data[i] = SPDR; // read byte
}
SPI_PORT |= (1<<SS_IO); // de-select slave
}
/* transmit and receive data over SPI
* transmits than receives each byte from <data> for <length> bytes
* the read bytes are saved back in <data>
* global interrupts are required to be enabled
* the function returns immediatly, while data is transfered
* to ensure the transfer is complete, use spi_transfer_wait()
* if data is already being transfered it will idle until the last transfer is completed
*/
void spi_transfer_nonblocking(uint8_t* data, size_t length)
{
if ((data==NULL) || (length==0)) { // verify is there is data to transfer
return;
}
spi_transfer_wait(NULL, 0); // wait for transfer to complete
spi_b = data; // save data pointer
spi_i = length; // save length pointer
SPCR |= (1<<SPIE); // enable SPI interrupt
SPI_PORT &= ~(1<<SS_IO); // select slave
SPDR = *data; // send first byte
}
/* transmit and receive data over SPI
* transmits than receives each byte from <data> for <length> bytes
* the read bytes are saved back in <data>
* global interrupts are required to be enabled
* the function returns when all the data is transfered
* while the data is transfered it goes into idle mode
* if <data> is NULL or <length> is 0 it just ensures the previous transfer is complete
*/
void spi_transfer_wait(uint8_t* data, size_t length)
{
// wait until the previous transfer is finished
while (spi_i>0) {
// go to sleep and wait for next interrupt (SPI will interrupt a one point)
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_mode();
}
if (data != NULL && length!=0) { // verify is there is data to transfer
spi_transfer_nonblocking(data, length); // start new transfer
spi_transfer_wait(NULL, 0); // wait for transfer to complete
}
}