360 lines
12 KiB
C
360 lines
12 KiB
C
// ######## necessities
|
|
|
|
// include guards
|
|
#ifndef CH32V003_SPI_H
|
|
#define CH32V003_SPI_H
|
|
|
|
// includes
|
|
#include "ch32fun.h"
|
|
#include <stdint.h> //uintN_t support
|
|
|
|
#ifndef APB_CLOCK
|
|
#define APB_CLOCK FUNCONF_SYSTEM_CORE_CLOCK
|
|
#endif
|
|
|
|
/*######## library usage and configuration
|
|
|
|
in the .c files that use this library, you'll need to #define some configuration options _before_ the #include "ch32v003_SPI.h"
|
|
|
|
SYSTEM_CORE_CLOCK and APB_CLOCK should be defined already as APB_CLOCK is used by this library
|
|
|
|
|
|
#ifndef APB_CLOCK
|
|
#define APB_CLOCK FUNCONF_SYSTEM_CORE_CLOCK
|
|
#endif
|
|
|
|
to enable using the functions of this library:
|
|
#define CH32V003_SPI_IMPLEMENTATION
|
|
|
|
to configure the settings of the SPI bus, first, declare the desired bus speed
|
|
|
|
#define CH32V003_SPI_SPEED_HZ 1000000
|
|
|
|
then pick the desired setting of each group:
|
|
|
|
#define CH32V003_SPI_DIRECTION_2LINE_TXRX
|
|
#define CH32V003_SPI_DIRECTION_1LINE_TX
|
|
|
|
#define CH32V003_SPI_CLK_MODE_POL0_PHA0 //leading = rising trailing = falling sample on leading default if you're unsure
|
|
#define CH32V003_SPI_CLK_MODE_POL0_PHA1 //leading = rising trailing = falling sample on trailing
|
|
#define CH32V003_SPI_CLK_MODE_POL1_PHA0 //leading = falling trailing = rising sample on leading
|
|
#define CH32V003_SPI_CLK_MODE_POL1_PHA1 //leading = falling trailing = rising sample on trailing
|
|
|
|
#define CH32V003_SPI_NSS_HARDWARE_PC0 // _NSS toggled by hardware, automatic
|
|
#define CH32V003_SPI_NSS_HARDWARE_PC1 // NSS toggled by hardware, automatic
|
|
#define CH32V003_SPI_NSS_SOFTWARE_PC3 // PC3 toggled by software, automatic, manual setters available
|
|
#define CH32V003_SPI_NSS_SOFTWARE_PC4 // PC4 toggled by software, automatic, manual setters available
|
|
#define CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL // toggle manually!
|
|
*/
|
|
|
|
// ######## function overview (declarations): use these!
|
|
// initialize and configure the SPI peripheral
|
|
static inline void SPI_init();
|
|
|
|
// establish / end a connection to the SPI device
|
|
static inline void SPI_begin_8();
|
|
static inline void SPI_begin_16();
|
|
static inline void SPI_end();
|
|
|
|
// manually set the NSS (chip select) pin high / low
|
|
// "SPI_NSS_HIGH_FN" and "SPI_NSS_LOW_FN" only become available functions if the selected NSS is software PC3 or PC4
|
|
#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
|
|
static inline void SPI_NSS_software_low();
|
|
static inline void SPI_NSS_software_high();
|
|
#endif
|
|
|
|
// read / write the SPI device
|
|
// these commands are raw, you'll have to consider all other steps in SPI_transfer!
|
|
static inline uint8_t SPI_read_8();
|
|
static inline uint16_t SPI_read_16();
|
|
static inline void SPI_write_8(uint8_t data);
|
|
static inline void SPI_write_16(uint16_t data);
|
|
|
|
// send a command and get a response from the SPI device
|
|
// you'll use this for most devices
|
|
static inline uint8_t SPI_transfer_8(uint8_t data);
|
|
static inline uint16_t SPI_transfer_16(uint16_t data);
|
|
|
|
// SPI peripheral power enable / disable (default off, init() automatically enables)
|
|
// send SPI peripheral to sleep
|
|
static inline void SPI_poweroff();
|
|
// wake SPI peripheral from sleep
|
|
static inline void SPI_poweron();
|
|
|
|
// helper: kill / restore all interrupts on the CH32V003
|
|
static inline void kill_interrrupts();
|
|
static inline void restore_interrupts();
|
|
|
|
// ######## internal function declarations
|
|
static inline void SPI_wait_TX_complete();
|
|
static inline uint8_t SPI_is_RX_empty();
|
|
static inline void SPI_wait_RX_available();
|
|
|
|
// ######## internal variables
|
|
static uint16_t EXT1_INTENR_backup;
|
|
|
|
// ######## preprocessor macros
|
|
// min and max helper macros
|
|
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
|
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
|
|
|
// stringify for displaying what #defines evaluated to at preprocessor stage
|
|
#define VALUE_TO_STRING(x) #x
|
|
#define VALUE(x) VALUE_TO_STRING(x)
|
|
#define VAR_NAME_VALUE(var) #var "=" VALUE(var)
|
|
|
|
// compile-time log2
|
|
#define LOG2(x) ((x) == 0 ? -1 : __builtin_ctz(x))
|
|
|
|
// compile-time clock prescaler calculation: log2(APB_CLOCK/SPEED_BUS)
|
|
#define SPI_CLK_RATIO (APB_CLOCK / CH32V003_SPI_SPEED_HZ)
|
|
#define SPI_CLK_PRESCALER LOG2(SPI_CLK_RATIO)
|
|
|
|
// ensure that CLOCK_PRESCALER_VALUE is within the range of 0..7
|
|
_Static_assert(SPI_CLK_PRESCALER >= 0 && SPI_CLK_PRESCALER <= 7, "SPI_CLK_PRESCALER is out of range (0..7). Please set a different SPI bus speed. prescaler = log2(f_CPU/f_SPI)");
|
|
// #pragma message(VAR_NAME_VALUE(SPI_CLK_PRESCALER))
|
|
|
|
// ######## preprocessor #define requirements
|
|
|
|
#if !defined(CH32V003_SPI_DIRECTION_2LINE_TXRX) && !defined(CH32V003_SPI_DIRECTION_1LINE_TX)
|
|
#warning "none of the CH32V003_SPI_DIRECTION_ options were defined!"
|
|
#endif
|
|
#if defined(CH32V003_SPI_DIRECTION_2LINE_TXRX) && defined(CH32V003_SPI_DIRECTION_1LINE_TX)
|
|
#warning "both CH32V003_SPI_DIRECTION_ options were defined!"
|
|
#endif
|
|
|
|
#if ((defined(CH32V003_SPI_CLK_MODE_POL0_PHA0) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_CLK_MODE_POL0_PHA1) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_CLK_MODE_POL1_PHA0) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_CLK_MODE_POL1_PHA1) ? 1 : 0)) > 1
|
|
#warning "more than one of the CH32V003_SPI_CLK_MODE_ options were defined!"
|
|
#endif
|
|
#if ((defined(CH32V003_SPI_CLK_MODE_POL0_PHA0) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_CLK_MODE_POL0_PHA1) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_CLK_MODE_POL1_PHA0) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_CLK_MODE_POL1_PHA1) ? 1 : 0)) == 0
|
|
#warning "none of the CH32V003_SPI_CLK_MODE_ options were defined!"
|
|
#endif
|
|
|
|
#if ((defined(CH32V003_SPI_NSS_HARDWARE_PC0) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_NSS_HARDWARE_PC1) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_NSS_SOFTWARE_PC3) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_NSS_SOFTWARE_PC4) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL) ? 1 : 0)) > 1
|
|
#warning "more than one of the CH32V003_SPI_NSS_ options were defined!"
|
|
#endif
|
|
#if ((defined(CH32V003_SPI_NSS_HARDWARE_PC0) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_NSS_HARDWARE_PC1) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_NSS_SOFTWARE_PC3) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_NSS_SOFTWARE_PC4) ? 1 : 0) + \
|
|
(defined(CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL) ? 1 : 0)) == 0
|
|
#warning "none of the CH32V003_SPI_NSS_ options were defined!"
|
|
#endif
|
|
|
|
// ######## small function definitions, static inline
|
|
static inline void SPI_init()
|
|
{
|
|
SPI_poweron();
|
|
|
|
// reset control register
|
|
SPI1->CTLR1 = 0;
|
|
|
|
// set prescaler
|
|
SPI1->CTLR1 |= SPI_CTLR1_BR & (SPI_CLK_PRESCALER << 3);
|
|
|
|
// set clock polarity and phase
|
|
#if defined(CH32V003_SPI_CLK_MODE_POL0_PHA0)
|
|
SPI1->CTLR1 |= (SPI_CPOL_Low | SPI_CPHA_1Edge);
|
|
#elif defined(CH32V003_SPI_CLK_MODE_POL0_PHA1)
|
|
SPI1->CTLR1 |= (SPI_CPOL_Low | SPI_CPHA_2Edge);
|
|
#elif defined(CH32V003_SPI_CLK_MODE_POL1_PHA0)
|
|
SPI1->CTLR1 |= (SPI_CPOL_High | SPI_CPHA_1Edge);
|
|
#elif defined(CH32V003_SPI_CLK_MODE_POL1_PHA1)
|
|
SPI1->CTLR1 |= (SPI_CPOL_High | SPI_CPHA_2Edge);
|
|
#endif
|
|
|
|
// configure NSS pin, master mode
|
|
#if defined(CH32V003_SPI_NSS_HARDWARE_PC0)
|
|
// _NSS (negative slave select) on PC0, 10MHz Output, alt func, push-pull1
|
|
SPI1->CTLR1 |= SPI_NSS_Hard; // NSS hardware control mode
|
|
GPIOC->CFGLR &= ~(0xf << (4 * 0));
|
|
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * 0);
|
|
AFIO->PCFR1 |= GPIO_Remap_SPI1; // remap NSS (C1) to _NSS (C0)
|
|
SPI1->CTLR2 |= SPI_CTLR2_SSOE; // pull _NSS high
|
|
#elif defined(CH32V003_SPI_NSS_HARDWARE_PC1)
|
|
// NSS (negative slave select) on PC1, 10MHz Output, alt func, push-pull1
|
|
SPI1->CTLR1 |= SPI_NSS_Hard; // NSS hardware control mode
|
|
GPIOC->CFGLR &= ~(0xf << (4 * 1));
|
|
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * 1);
|
|
SPI1->CTLR2 |= SPI_CTLR2_SSOE; // pull _NSS high
|
|
#elif defined(CH32V003_SPI_NSS_SOFTWARE_PC3)
|
|
SPI1->CTLR1 |= SPI_NSS_Soft; // SSM NSS software control mode
|
|
GPIOC->CFGLR &= ~(0xf << (4 * 3));
|
|
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * 3);
|
|
#elif defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
|
|
SPI1->CTLR1 |= SPI_NSS_Soft; // SSM NSS software control mode
|
|
GPIOC->CFGLR &= ~(0xf << (4 * 4));
|
|
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * 4);
|
|
#elif defined(CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL)
|
|
SPI1->CTLR1 |= SPI_NSS_Soft; // SSM NSS software control mode
|
|
#endif
|
|
|
|
// SCK on PC5, 10MHz Output, alt func, push-pull
|
|
GPIOC->CFGLR &= ~(0xf << (4 * 5));
|
|
GPIOC->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 5);
|
|
|
|
// CH32V003 is master
|
|
SPI1->CTLR1 |= SPI_Mode_Master;
|
|
|
|
// set data direction and configure data pins
|
|
#if defined(CH32V003_SPI_DIRECTION_2LINE_TXRX)
|
|
SPI1->CTLR1 |= SPI_Direction_2Lines_FullDuplex;
|
|
|
|
// MOSI on PC6, 10MHz Output, alt func, push-pull
|
|
GPIOC->CFGLR &= ~(0xf << (4 * 6));
|
|
GPIOC->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 6);
|
|
|
|
// MISO on PC7, 10MHz input, floating
|
|
GPIOC->CFGLR &= ~(0xf << (4 * 7));
|
|
GPIOC->CFGLR |= GPIO_CNF_IN_FLOATING << (4 * 7);
|
|
#elif defined(CH32V003_SPI_DIRECTION_1LINE_TX)
|
|
SPI1->CTLR1 |= SPI_Direction_1Line_Tx;
|
|
|
|
// MOSI on PC6, 10MHz Output, alt func, push-pull
|
|
GPIOC->CFGLR &= ~(0xf << (4 * 6));
|
|
GPIOC->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 6);
|
|
#endif
|
|
}
|
|
|
|
static inline void SPI_begin_8()
|
|
{
|
|
SPI1->CTLR1 &= ~(SPI_CTLR1_DFF); // DFF 16bit data-length enable, writable only when SPE is 0
|
|
SPI1->CTLR1 |= SPI_CTLR1_SPE;
|
|
}
|
|
static inline void SPI_begin_16()
|
|
{
|
|
SPI1->CTLR1 |= SPI_CTLR1_DFF; // DFF 16bit data-length enable, writable only when SPE is 0
|
|
SPI1->CTLR1 |= SPI_CTLR1_SPE;
|
|
}
|
|
static inline void SPI_end()
|
|
{
|
|
SPI1->CTLR1 &= ~(SPI_CTLR1_SPE);
|
|
}
|
|
|
|
#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3)
|
|
static inline void SPI_NSS_software_high()
|
|
{
|
|
GPIOC->BSHR = (1 << 3);
|
|
}
|
|
static inline void SPI_NSS_software_low()
|
|
{
|
|
GPIOC->BSHR = (1 << (16 + 3));
|
|
}
|
|
#elif defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
|
|
static inline void SPI_NSS_software_high()
|
|
{
|
|
GPIOC->BSHR = (1 << 4);
|
|
}
|
|
static inline void SPI_NSS_software_low()
|
|
{
|
|
GPIOC->BSHR = (1 << (16 + 4));
|
|
}
|
|
#endif
|
|
|
|
static inline uint8_t SPI_read_8()
|
|
{
|
|
return SPI1->DATAR;
|
|
}
|
|
static inline uint16_t SPI_read_16()
|
|
{
|
|
return SPI1->DATAR;
|
|
}
|
|
static inline void SPI_write_8(uint8_t data)
|
|
{
|
|
SPI1->DATAR = data;
|
|
}
|
|
static inline void SPI_write_16(uint16_t data)
|
|
{
|
|
SPI1->DATAR = data;
|
|
}
|
|
static inline uint8_t SPI_transfer_8(uint8_t data)
|
|
{
|
|
#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
|
|
SPI_NSS_software_high();
|
|
#endif
|
|
SPI_write_8(data);
|
|
SPI_wait_TX_complete();
|
|
asm volatile("nop");
|
|
SPI_wait_RX_available();
|
|
#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
|
|
SPI_NSS_software_low();
|
|
#endif
|
|
return SPI_read_8();
|
|
}
|
|
static inline uint16_t SPI_transfer_16(uint16_t data)
|
|
{
|
|
#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
|
|
SPI_NSS_software_high();
|
|
#endif
|
|
SPI_write_16(data);
|
|
SPI_wait_TX_complete();
|
|
asm volatile("nop");
|
|
SPI_wait_RX_available();
|
|
#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
|
|
SPI_NSS_software_low();
|
|
#endif
|
|
return SPI_read_16();
|
|
}
|
|
|
|
static inline void SPI_poweroff()
|
|
{
|
|
SPI_end();
|
|
RCC->APB2PCENR &= ~RCC_APB2Periph_SPI1;
|
|
}
|
|
static inline void SPI_poweron()
|
|
{
|
|
RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1;
|
|
}
|
|
|
|
static inline void kill_interrrupts()
|
|
{
|
|
EXT1_INTENR_backup = EXTI->INTENR;
|
|
// zero the interrupt enable register to disable all interrupts
|
|
EXTI->INTENR = 0;
|
|
}
|
|
static inline void restore_interrupts()
|
|
{
|
|
EXTI->INTENR = EXT1_INTENR_backup;
|
|
}
|
|
|
|
// ######## small internal function definitions, static inline
|
|
static inline void SPI_wait_TX_complete()
|
|
{
|
|
while (!(SPI1->STATR & SPI_STATR_TXE)) {}
|
|
}
|
|
static inline uint8_t SPI_is_RX_empty()
|
|
{
|
|
return SPI1->STATR & SPI_STATR_RXNE;
|
|
}
|
|
static inline void SPI_wait_RX_available()
|
|
{
|
|
while (!(SPI1->STATR & SPI_STATR_RXNE)) {}
|
|
}
|
|
static inline void SPI_wait_not_busy()
|
|
{
|
|
while ((SPI1->STATR & SPI_STATR_BSY) != 0) {}
|
|
}
|
|
static inline void SPI_wait_transmit_finished()
|
|
{
|
|
SPI_wait_TX_complete();
|
|
SPI_wait_not_busy();
|
|
}
|
|
|
|
// ######## implementation block
|
|
// #define CH32V003_SPI_IMPLEMENTATION //enable so LSP can give you text colors while working on the implementation block, disable for normal use of the library
|
|
#if defined(CH32V003_SPI_IMPLEMENTATION)
|
|
|
|
// no functions here because I think all of the functions are small enough to static inline
|
|
|
|
#endif // CH32V003_SPI_IMPLEMENTATION
|
|
#endif // CH32V003_SPI_H
|