STM32SPI接口的简单实现.docx
- 文档编号:20124882
- 上传时间:2023-04-25
- 格式:DOCX
- 页数:17
- 大小:21.77KB
STM32SPI接口的简单实现.docx
《STM32SPI接口的简单实现.docx》由会员分享,可在线阅读,更多相关《STM32SPI接口的简单实现.docx(17页珍藏版)》请在冰豆网上搜索。
STM32SPI接口的简单实现
STM32SPI接口的简单实现
通常SPI通过4个引脚与外部器件相连:
●MISO:
主设备输入/从设备输出引脚。
该引脚在从模式下发送数据,在主模式下接收数据。
●MOSI:
主设备输出/从设备输入引脚。
该引脚在主模式下发送数据,在从模式下接收数据。
●SCK:
串口时钟,作为主设备的输出,从设备的输入
●NSS:
从设备选择.这是一个可选的引脚,用来选择主/从设备。
它的功能是用来作为“片选引脚",让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
从设备的NSS引脚可以由主设备的一个标准I/O引脚来驱动。
一旦被使能(SSOE位),NSS引脚也可以作为输出引脚,并在SPI处于主模式时拉低;此时,所有的SPI设备,如果它们的NSS引脚连接到主设备的NSS引脚,则会检测到低电平,如果它们被设置为NSS硬件模式,就会自动进入从设备状态。
当配置为主设备、NSS配置为输入引脚(MSTR=1,SSOE=0)时,如果NSS被拉低,则这个SPI设备进入主模式失败状态:
即MSTR位被自动清除,此设备进入从模式。
时钟信号的相位和极性
SPI_CR寄存器的CPOL和CPHA位,能够组合成四种可能的时序关系。
CPOL(时钟极性)位控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效。
如果CPOL被清’0’,SCK引脚在空闲状态保持低电平;如果CPOL被置'1',SCK引脚在空闲状态保持高电平。
如果CPHA(时钟相位)位被置'1’,SCK时钟的第二个边沿(CPOL位为0时就是下降沿,CPOL位为’1’时就是上升沿)进行数据位的采样,数据在第二个时钟边沿被锁存.如果CPHA位被清’0’,SCK时钟的第一边沿(CPOL位为'0'时就是下降沿,CPOL位为'1’时就是上升沿)进行数据位采样,数据在第一个时钟边沿被锁存。
CPOL时钟极性和CPHA时钟相位的组合选择数据捕捉的时钟边沿.
图212显示了SPI传输的4种CPHA和CPOL位组合。
此图可以解释为主设备和从设备的SCK脚、MISO脚、MOSI脚直接连接的主或从时序图。
CPOL时钟极性和CPHA时钟相位的组合选择数据捕捉的时钟边沿。
上图显示了SPI传输的4种CPHA和CPOL位组合。
此图可以解释为主设备和从设备的SCK脚、MISO脚、MOSI脚直接连接的主或从时序图。
注意:
1。
在改变CPOL/CPHA位之前,必须清除SPE位将SPI禁止。
2。
主和从必须配置成相同的时序模式。
3.SCK的空闲状态必须和SPI_CR1寄存器指定的极性一致(CPOL为'1’时,空闲时应上拉SCK为高电平;CPOL为’0’时,空闲时应下拉SCK为低电平).
4。
数据帧格式(8位或16位)由SPI_CR1寄存器的DFF位选择,并且决定发送/接收的数据长度。
我只要知道主机和从机的CPOL和CPHA位要一致就够了。
有2种NSS模式:
●软件NSS模式:
可以通过设置SPI_CR1寄存器的SSM位来使能这种模式。
在这种模式下NSS引脚可以用作它用,而内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动
●硬件NSS模式,分两种情况:
─NSS输出被使能:
当STM32F10xxx工作为主SPI,并且NSS输出已经通过SPI_CR2寄存器的SSOE位使能,这时NSS引脚被拉低,所有NSS引脚与这个主SPI的NSS引脚相连并配置为硬件NSS的SPI设备,将自动变成从SPI设备.当一个SPI设备需要发送广播数据,它必须拉低NSS信号,以通知所有其它的设备它是主设备;如果它不能拉低NSS,这意味着总线上有另外一个主设备在通信,这时将产生一个硬件失败错误(HardFault)。
─NSS输出被关闭:
允许操作于多主环境。
//我们用软件NSS主从的转换都可借助库来实现
数据帧格式
根据SPI_CR1寄存器中的LSBFIRST位,输出数据位时可以MSB在先也可以LSB在先。
根据SPI_CR1寄存器的DFF位,每个数据帧可以是8位或是16位。
所选择的数据帧格式对发送和/或接收都有效。
配置一个SPI这里选SPI2
如下:
SPI_InitTypeDefSPI_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
SPI_Cmd(SPI2,DISABLE);//必须先禁能,才能改变MODE
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//两线全双工
SPI_InitStructure。
SPI_Mode=SPI_Mode_Master;//主
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;//8位
SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;//CPOL=1时钟悬空高
SPI_InitStructure。
SPI_CPHA=SPI_CPHA_1Edge;//CPHA=1数据捕获第2个
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;//软件NSS
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;//2分频
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;//高位在前
SPI_InitStructure.SPI_CRCPolynomial=7;//CRC7
SPI_Init(SPI2,&SPI_InitStructure);
SPI_Cmd(SPI2,ENABLE);
//spi的配置结束了可以使用了。
也可用函数SPI_StructInit把SPI_InitStruct中的每一个参数按缺省值填入
_____________________________________________________________________________________
发送缓冲器空闲标志(TXE)【3.0SPI_I2S_FLAG_TXE】
此标志为’1’时表明发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。
当写入SPI_DR时,TXE标志被清除.
接收缓冲器非空(RXNE)【3.0SPI_I2S_FLAG_RXNE】
此标志为’1’时表明在接收缓冲器中包含有效的接收数据。
读SPI数据寄存器可以清除此标志.
注意在2.0的库中函数
SPI_SendDataSPI_ReceiveDataSPI_GetFlagStatus等在3.0的库中变为
SPI_I2S_SendDataSPI_I2S_ReceiveDataSPI_I2S_GetFlagStatus
写一个发送/接受函数
staticu8SPIByte(u8byte)
{
while((SPI2—〉SR&SPI_I2S_FLAG_TXE)==RESET);
//while((SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE))==RESET);
SPI2-〉DR=byte;
//SPI_I2S_SendData(SPI2,byte);
while((SPI2—〉SR&SPI_I2S_FLAG_RXNE)==RESET);
//while((SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE))==RESET);
return(SPI2—>DR);
//returnSPI_I2S_ReceiveData(SPI2);读寄存器用硬件清除标志位。
//SPI_I2S_ClearFlag(SPI2,SPI_I2S_FLAG_RXNE);直接软件清除标志位。
}
这里有两种写法直接操作寄存器与用库函数,相对来说直接操作寄存器应该更直观一些。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
调试总会遇到这样那样的小问题和朋友一起进步总会更快希望对别人能有点帮助节省些时间
万利的板子板载的M25P80PDF写的这一型号有25MHZ和50MHZ两种注意初始化SPI的设置BAUD至少就要PCLK/2=36Mhz
即使配置为主模式发送时钟也不是连产生的接收的时候同样需要发送无用字节以维持数据时钟
STM32的NSS脚需要配置为软件模式(可以通过寄存器控制NSS高低)硬件模式下NSS引脚必须+正的逻辑电平才能将SPEMSTR位时能配置SPI为主模式
先写了一遍然后借鉴一个sst25vf08的应用笔记又写了一遍看这个笔记省了不少时间很感谢
同一地址单元重新写入需先擦除否者二次写入后读出无效
其他按照时序走就可以了
程序如下:
#defineWREN0x06
#defineWRDI0x04
#defineRDSR0x05
#defineWRSR0x01
#defineREAD0x03
#defineFAST_READ0x06
#definePAGE_PROG0x02
#defineSECTOR_ERASER0xd8
#defineBULK_ERASER0xc7
#defineDEEP_SLEEP0xb9
#defineRES0xab
#defineM25P80_SELECT()GPIOB-〉BRR|=GPIO_Pin_2
#defineM25P80_DESELECT()GPIOB—>BSRR|=GPIO_Pin_2
u8SPI_SeRe_Byte(u8data);
voidM25p80_Cmd1b(u8cmd);
u8M25p80_Cmd1b_R1b(u8cmd);
voidM25p80_Cmd1b_S1b(u8cmd,u8data);
voidM25p80_WP_En(void);
voidM25p80_Write_En(void);
u8M25p80_Busy(void);
u8M25p80_Read_1Byte(u32addr);
voidM25p80_Read_Bytes(u32addr,u8*re_buf_p,u16no);
voidM25p80_Write_1Byte(u32addr,u8data);
voidM25p80_Write_Bytes(u32addr,u8*wr_buf_p,u16no);
voidM25p80_Section_Erase(u32addr);
voidM25p80_Bulk_Erase(void);
/********************************************************************************************************************
*op-codeforM25P80SPIEEROM
*8Mbit=1,048,576bytes=16sectors(512Kbits,65536byteseach)=4096pages(256byteseach)
*1sector=256page
*addressrange00000~fffffh
*********************************************************************************************************************/
u8SPI_SeRe_Byte(u8data)
{
u8re_data;
while(!
(SPI1-〉SR&0x0002));/*TXE?
*/
SPI1->DR=data;
while(!
(SPI1—〉SR&0x0001));/*RXNE?
*/
re_data=SPI1—〉DR;
returnre_data;
}
/************************************************************************************************************
*函数名称:
M25p80_cmd1b_r1b/M25p80_Cmd1b_S1bCMD命令发送返回1BYTE/命令发送跟送1BYTE
************************************************************************************************************/
voidM25p80_Cmd1b(u8cmd)
{
M25P80_SELECT();
SPI_SeRe_Byte(cmd);
M25P80_DESELECT();
}
u8M25p80_Cmd1b_R1b(u8cmd)
{
u8data;
M25P80_SELECT();
SPI_SeRe_Byte(cmd);
data=SPI_SeRe_Byte(0xff);
M25P80_DESELECT();
returndata;
}
voidM25p80_Cmd1b_S1b(u8cmd,u8data)
{
M25P80_SELECT();
SPI_SeRe_Byte(cmd);
SPI_SeRe_Byte(data);
M25P80_DESELECT();
}
/****************************************************************************************************************/
/************************************************************************************************************
*函数名称:
M25p80_WP_En/M25p80_Write_EnM25p80写保护使能/写使能status寄存器修改
************************************************************************************************************/
voidM25p80_WP_En(void)
{
u8sta;
sta=M25p80_Cmd1b_R1b(RDSR)|0x1c;
M25p80_Cmd1b_S1b(WRSR,sta);
M25p80_Cmd1b(WRDI);
}
voidM25p80_Write_En(void)
{
u8sta;
sta=M25p80_Cmd1b_R1b(RDSR)&(~0x1c);
M25p80_Cmd1b_S1b(WRSR,sta);
M25p80_Cmd1b(WREN);
}
/****************************************************************************************************************/
u8M25p80_Busy(void)
{
u8sta;
sta=M25p80_Cmd1b_R1b(RDSR);
return(sta&0x01);
}
u8M25p80_Read_1Byte(u32addr)
{
u8ad[3],data;
ad[0]=(addr&0x00ff0000)>〉16;
ad[1]=(addr&0x0000ff00)〉>8;
ad[2]=(addr&0x000000ff);
M25P80_SELECT();
SPI_SeRe_Byte(READ);
SPI_SeRe_Byte(ad[0]);
SPI_SeRe_Byte(ad[1]);
SPI_SeRe_Byte(ad[2]);
data=SPI_SeRe_Byte(0xff);
M25P80_DESELECT();
returndata;
}
voidM25p80_Read_Bytes(u32addr,u8*re_buf_p,u16no)
{
u8ad[3];
ad[0]=(addr&0x00ff0000)>>16;
ad[1]=(addr&0x0000ff00)>〉8;
ad[2]=(addr&0x000000ff);
M25P80_SELECT();
SPI_SeRe_Byte(READ);
SPI_SeRe_Byte(ad[0]);
SPI_SeRe_Byte(ad[1]);
SPI_SeRe_Byte(ad[2]);
for(;no〉0;no—-)
*re_buf_p++=SPI_SeRe_Byte(0xff);
M25P80_DESELECT();
}
/****************************************************************************************************************/
voidM25p80_Write_1Byte(u32addr,u8data)
{
u8ad[3];
ad[0]=(addr&0x00ff0000)>>16;
ad[1]=(addr&0x0000ff00)〉〉8;
ad[2]=(addr&0x000000ff);
M25p80_Write_En();
M25P80_SELECT();
SPI_SeRe_Byte(PAGE_PROG);
SPI_SeRe_Byte(ad[0]);
SPI_SeRe_Byte(ad[1]);
SPI_SeRe_Byte(ad[2]);
SPI_SeRe_Byte(data);
M25P80_DESELECT();
M25p80_WP_En();
while(M25p80_Busy());
}
voidM25p80_Write_Bytes(u32addr,u8*wr_buf_p,u16no)
{
/*
u8ad[3];
ad[0]=(addr&0x00ff0000)〉>16;
ad[1]=(addr&0x0000ff00)>>8;
ad[2]=(addr&0x000000ff);
if(no〈255-ad[2])return0;//PP写操作过本页地址从本页首地址循环写
M25p80_Cmd1b(WREN);
M25P80_SELECT();
SPI_SeRe_Byte(PAGE_PROG);
SPI_SeRe_Byte(ad[0]);
SPI_SeRe_Byte(ad[1]);
SPI_SeRe_Byte(ad[2]);
for(;no>0;no-—)
*wr_buf_p++=SPI_SeRe_Byte(0xff);
M25P80_DESELECT();
M25p80_Cmd1b(WRDI);
return1;
*/
for(;no〉0;no——)
{
M25p80_Write_1Byte(addr,*wr_buf_p);
addr++;wr_buf_p++;
}
M25p80_WP_En();
}
/****************************************************************************************************************/
voidM25p80_Section_Erase(u32addr)
{
u8ad[3];
ad[0]=(addr&0x00ff0000)〉>16;
ad[1]=(addr&0x0000ff00)〉>8;
ad[2]=(addr&0x000000ff);
M25p80_Write_En();
M25P80_SELECT();
SPI_SeRe_Byte(SECTOR_ERASER);
SPI_SeRe_Byte(ad[0]);
SPI_SeRe_Byte(ad[1]);
SPI_SeRe_Byte(ad[2]);
M25P80_DESELECT();
while(M25p80_Busy());
M25p80_WP_En();
}
voidM25p80_Bulk_Erase(void)
{
M25p80_Write_En();
M25p80_Cmd1b(BULK_ERASER);
while(M25p80_Busy());
M25p80_WP_En();
}
这里的多字节写函数voidM25p80_Write_Bytes(u32addr,u8*wr_buf_p,u16no)调用了单字节写函数实际上还是一个一个写主要因为用页写操作的话当数据到达页底会返回本页从头开始写会破坏数据而且没有字节写操作码感觉比sst25vf08麻烦很多。
关于STM32SPINSS问题的探讨。
对于STM32的SPI,ReferenceManual中是给出的schematic如下:
按照标准的SPI协议,当SPI被配置为主机模式后,通过SPI对从设备进行操作时,其NSS应该自动置低,从而选中(使能)从设备;一旦不对从设备进行操作,NSS立刻置为高.
但是,我在实际调试过程中却发现:
STM32SPINSS无法自动实现跳变.一旦SPI初始化完成并使能SPI,NSS立刻置低,然后保持不变。
这
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- STM32SPI 接口 简单 实现