STM32-SPI使用 SPI知识:
1)
高速同步串行口。3~4线接口(CS ,CLK ,MOSI,MISO),收发独立、可同步进行。
2)SPI分为主从模式,主模式提供时钟和片选选择信号.
3) 模式控制:CPOL用来控制时钟信号(clk)在空闲时候的状态;CPHA用来控制采样时刻时CLK的边缘动作。
CPOL CPHA 模式
0 0 CLK空闲时为低电平,CLK上升沿采样数据。
0 1 CLK空闲为低电平,CLK下降沿采样数据。
1 0 CLK空闲时为高电平,CLK上升沿采样数据。
1 1 CLK空闲时为高电平,CLK下降沿采样数据。
1)SPI配置(3.01库):
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双工模式
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //SPI主模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8bit数据
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //CLK空闲时为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //CLK上升沿采样,因为上升沿是第二个边沿动作,所以也可以理解为第二个边沿采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //片选用软件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; //SPI频率
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前
SPI_InitStructure.SPI_CRCPolynomial = 7; //crc7,stm32spi带硬件ecc
SPI_Init(SPI1, &SPI_InitStructure);
2)CS信号:
主模式下要为从设备提供片选信号,值得注意的是STM32的主频相当较高,要提防数据没有完全发送前拉高CS信号。
3)SPI读写:(非中断模式)
a)写一个字节:
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
//确保发生前Buffer为空,也就是说上一次已经发生完成
SPI_I2S_SendData(SPI1, Data); //往寄存器中写入一个字节
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
//等待接受到一个字节数据,为什么要这么做?加这一句的原因是为了确保这个字节已经发送出去,因为发生和接受是并行同步进行,那就是说你发生出去一个字节意味着你收到一个字节。所以这样判断完全没有问题,再说必要性,如果你不加这句你就会容易犯过早拉高CS信号的错误,你想想如果在SPI_I2S_SendData(SPI1,
Data)后面立即拉高CS是什么后果。
SPI_I2S_ReceiveData(SPI1); //都会接收到的数据,看起来没什么必要,但以用stm32的经验推荐这样做,也许会有意想不到的收获。
SPI_Writebyte(u8 data)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, Data);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
SPI_I2S_ReceiveData(SPI1);
}
b)读一个字节:
读的时候要注意一个问题,因为从模式是没法提供时钟的,所以主模式下必须要在接收的同时提供时钟。办法就是发送一个字节来实现,因为还是上面说的,发送一个字节就意味着收到一个字节,代码和写完全一样,只要把读出来的字节保存即可。
u8 SPI_Readbyte(u8 data)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, Data);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
总结:上面的程序是最求稳定而设定的,如果你对速度有要求,你可以做相应的精简,比如读写直接对寄存器进行操作,另外配置SPI前要对从模式的模式了解清楚,包括从设备支持的时钟范围和模式(CPOL,CPHA状态)。
/*****************************************************************************************************
* @brief: LDC1000应用程序
* _____________ _______________
* |PB4(SSI2CLK)
----> SCLK|
* |PB5(SSI2FSS)
----> CSB |
* |PB6(SSI2RX) <---- SDO
|
* Tiva M4 |PB7(SSI2TX)
----> SDI
| LDC1000
* |PA4(INT/GPIO)
<---- INTB|
* |PB0(Timer CLK)
----> TBCLK|
* _____________| |______________
*****************************************************************************************************/
#include <stdint.h>
#include <stdbool.h>
#include "driverlib/rom.h"
#include "driverlib/adc.h"
#include "driverlib/sysctl.h"
#include "driverlib/pwm.h"
#include "driverlib/timer.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/interrupt.h"
#include "driverlib/ssi.h"
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_gpio.h"
#include "LDC1000_cmd.h"
#include "inc/hw_timer.h"
#include "inc/hw_types.h"
#include "inc/hw_ssi.h"
#ifndef TARGET_IS_BLIZZARD_RA1
#define TARGET_IS_BLIZZARD_RA1
#endif
#ifndef PART_TM4C123GH6PM
#define PART_TM4C123GH6PM
#endif
#define SPI_RWBIT 0x80
//LDC1000,SPI时序读写位,1=读,0=写
unsigned long ProximityData; //LDC上Proximity Data
unsigned long FrequencyData; //LDC1000上的Frequency Data
volatile unsigned char DataRdy ; //LDC1000中断标志
uint32_t DataRcv[5] ; //存储SPI读取的数据
/********************************************************************
* @brief: SPI写数据
* @param: unsigned int,SPIdata:待写的数据
* @return: none
*********************************************************************/
void SPIDataSend(unsigned int SPIdata)
{
SSIDataPut(SSI2_BASE,SPIdata);
//SPI发送(写)数据
while(SSIBusy(SSI2_BASE)) ;
//等待SPI发送(写)完成
}
/********************************************************************
* @brief: LDC1000初始化配置,ps:在SPI中配置了数据位16个数据长度,故
* 在发送数据时可以将地址和值进行或运算一起发送出去
* @param: none
* @return: none
*********************************************************************/
void LDC1000_init()
{
SPIDataSend(LDC1000_CMD_RPMAX<<8|TEST_RPMAX_INIT);
//配置Rp_MAX(0x01)寄存器
SPIDataSend(LDC1000_CMD_RPMIN<<8|TEST_RPMIN_INIT);
//配置Rp_MIN(0x02)寄存器
SPIDataSend(LDC1000_CMD_SENSORFREQ<<8|0x94);
//配置Sensor Frequency(0x03)寄存器
SPIDataSend(LDC1000_CMD_LDCCONFIG<<8|0x17);
//配置LDC Configuration(0x04)寄存器
SPIDataSend(LDC1000_CMD_CLKCONFIG<<8|0x00);
//配置Clock Configuration(0x05)寄存器,
//使用TBCLK作为时钟源
SPIDataSend(LDC1000_CMD_INTCONFIG<<8|0x02);
//配置INTB Pin Configuration(0x0A),
//配置INTB为比较输出标志位(status of Comparator output)
SPIDataSend(LDC1000_CMD_THRESHILSB<<8|0x50);
//配置Comparator Threshold High(0x06)寄存器低8位
SPIDataSend(LDC1000_CMD_THRESHIMSB<<8|0x14);
//配置Comparator Threshold High(0x07)寄存器高8位
SPIDataSend(LDC1000_CMD_THRESLOLSB<<8|0xC0);
//配置Comparator Threshold Low(0x08)寄存器低8位
SPIDataSend(LDC1000_CMD_THRESLOMSB<<8|0x12);
//配置Comparator Threshold Low(0x09)寄存器高8位
SPIDataSend(LDC1000_CMD_PWRCONFIG<<8|0x01);
//配置Power Configuration(0x0B)寄存器,
//为Active Mode,使能转化
}
/********************************************************************
* @brief: 使用SPI读取LDC1000中的数据
* @param: none
* @return: none
*********************************************************************/
void LDCRead()
{
SPIDataSend((LDC1000_CMD_PROXLSB|SPI_RWBIT)<<8);
//写入将要读取的Proximity Data LSB寄存器地址(0x21)
SSIDataGet(SSI2_BASE,&DataRcv[0]);
//读取上述寄存器中的值,并存入DataRcv[0]
ProximityData|= DataRcv[0] ;
SPIDataSend((LDC1000_CMD_PROXMSB|SPI_RWBIT)<<8);
//写入将要读取的Proximity Data MSB寄存器地址(0x22)
SSIDataGet(SSI2_BASE,&DataRcv[1]);
//读取上述寄存器中的值,并存入DataRcv[1]
ProximityData|= (DataRcv[1]<<8) ;
//组合成ProximityData
SPIDataSend((LDC1000_CMD_FREQCTRLSB|SPI_RWBIT)<<8);
//写入将要读取的Frequency Counter Data LSB寄存器地址(0x23)
SSIDataGet(SSI2_BASE,&DataRcv[2]);
//读取上述寄存器中的值,并存入DataRcv[2]
FrequencyData|= DataRcv[2] ;
SPIDataSend((LDC1000_CMD_FREQCTRMID|SPI_RWBIT)<<8);
//写入将要读取的Frequency Counter Data Mid-Byte寄存器地址(0x24)
SSIDataGet(SSI2_BASE,&DataRcv[3]);
//读取上述寄存器中的值,并存入DataRcv[3]
FrequencyData|= (DataRcv[3]<<8) ;
SPIDataSend((LDC1000_CMD_FREQCTRMSB|SPI_RWBIT)<<8);
//写入将要读取的Frequency Counter Data MSB寄存器地址(0x25)
SSIDataGet(SSI2_BASE,&DataRcv[4]);
//读取上述寄存器中的值,并存入DataRcv[4]
FrequencyData|= (DataRcv[4]<<16) ;
//组合成FrequencyData
GPIOIntEnable(GPIO_PORTA_BASE,GPIO_INT_PIN_4);
//使能PA4中断
}
/********************************************************************
* @brief: Timer初始化
* @param: none
* @return: none
* _____________ _______________
* | |
* Tiva M4 |PB0(Timer CLK)
----> TBCLK|
LDC1000
* _____________| |______________
*********************************************************************/
void TimerInit()
{
TimerDisable(TIMER2_BASE,TIMER_A);
GPIOPinTypeTimer(GPIO_PORTB_BASE,GPIO_PIN_0);
GPIOPinConfigure(GPIO_PB0_T2CCP0);
//配置PB0为CCP模式
HWREG(TIMER2_BASE + TIMER_O_CFG) = 0x04;
//选择16-bit timer
//配置TimerA周期计数(Periodic Timer mode)
HWREG(TIMER2_BASE + TIMER_O_TAMR)|=(TIMER_TAMR_TAAMS|TIMER_TAMR_TAMR_PERIOD) ;
//加载Timer计数值:40,并且设置Match值:20(Timer默认为减计数)
HWREG(TIMER2_BASE + TIMER_O_TAMATCHR) = 20;
TimerLoadSet(TIMER2_BASE,TIMER_A,40);
TimerEnable(TIMER2_BASE,TIMER_A);
//使能Timer
}
/********************************************************************
* @brief: PA4初始化
* @param: none
* @return: none
* _________ ___________
* | |
* | |
* Tiva M4
| PA4 <--- INTB| LDC1000
* | |
* —————————| |__________
*********************************************************************/
void GPIOInit()
{
HWREG(GPIO_PORTA_BASE + GPIO_O_DEN) |= 1<<4 ;
GPIOIntTypeSet(GPIO_PORTA_BASE,GPIO_PIN_4,GPIO_RISING_EDGE); //配置为上升沿中断
GPIOIntEnable(GPIO_PORTA_BASE,GPIO_INT_PIN_4);
//使能PA4中断
IntEnable(INT_GPIOA);
//使能GPIOA中断
}
/********************************************************************
* @brief:
SPI通信初始化
* @param:
none
* @return:
none
* _____________ _______________
* |PB4(SSI2CLK)
----> SCLK|
* |PB5(SSI2FSS)
----> CSB |
* |PB6(SSI2RX) <---- SDO
|
* Tiva M4 |PB7(SSI2TX)
----> SDI
| LDC1000
* |PB0(Timer CLK)
----> TBCLK|
* _____________| |______________
*********************************************************************/
void SPIInit()
{
//配置PB6为SSI2RX,即对Tiva M4而言的SPI数据接收线
GPIOPinTypeSSI(GPIO_PORTB_BASE,GPIO_PIN_6) ;
GPIOPinConfigure(GPIO_PB6_SSI2RX);
//配置PB6为SSI2TX,即对Tiva M4而言的SPI数据发送线
GPIOPinTypeSSI(GPIO_PORTB_BASE,GPIO_PIN_7) ;
GPIOPinConfigure(GPIO_PB7_SSI2TX);
//配置PB4为SSI2CLK线,作为时钟线
GPIOPinTypeSSI(GPIO_PORTB_BASE,GPIO_PIN_4) ;
GPIOPinConfigure(GPIO_PB4_SSI2CLK);
//配置PB5为SSI2FFS线,作为片选线
GPIOPinTypeSSI(GPIO_PORTB_BASE,GPIO_PIN_5) ;
GPIOPinConfigure(GPIO_PB5_SSI2FSS);
SSIDisable(SSI2_BASE); //禁能SSI2
//配置SSI2为SSI_FRF_MOTO_MODE_0协议格式,SPI主模式,时钟源为5K,16位数据长度
SSIConfigSetExpClk(SSI2_BASE,SysCtlClockGet(),SSI_FRF_MOTO_MODE_0,SSI_MODE_MASTER,5000,16);
SSIEnable(SSI2_BASE) ;
//使能SSI2
}
/********************************************************************
* @brief:
PA4中断服务函数,该函数在startup_ccs.c的中断向量表中进行
* 了注册
* @param:
none
* @return:
none
* _________ ___________
* | |
* | |
* Tiva M4
| PA4 <--- INTB| LDC1000
* | |
* —————————| |__________
*********************************************************************/
void GPIOAIntHandler()
{
GPIOIntClear(GPIO_PORTA_BASE,GPIO_INT_PIN_4) ;
//清除PA4中断标志
DataRdy = 1 ;
//LDC1000中断标志置位
GPIOIntDisable(GPIO_PORTA_BASE,GPIO_INT_PIN_4) ;
//禁能PA4中断,将在SPI数据读取完成后重新使能PA4中断
}
//---------------------------------------main函数----------------------------------------------
int main(void)
{
SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ |
//配置主时钟为50MHz
SYSCTL_OSC_MAIN);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA) ;
//使能GPIOA外设模块
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB) ;
//使能GPIOB外设模块
SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER2);
//使能Timer2外设模块
SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);
//使能SSI2外设模块
DataRdy = 0 ; //LDC1000中断标志清零
TimerInit() ; //TBCLK所在时钟初始化
GPIOInit() ; //GPIO初始化(PA4)
SPIInit() ; //SPI初始化
LDC1000_init(); //LDC1000初始配置
ROM_IntMasterEnable();
//使能总中断
while(HWREG(SSI2_BASE + SSI_O_SR)& SSI_SR_RNE)
//首先清除SPI上的接收缓存,排除干扰
{
DataRcv[0] = HWREG(SSI2_BASE + SSI_O_DR) ;
}
while(1)
{
//转化结束后读取ProximityData和FrequencyData
if(DataRdy)
{
DataRdy = 0 ;
//LDC1000中断标志清零(在PA4中断服务程序中置位)
LDCRead() ;
//SPI读取数据操作
}
}
}