打印
[STM32F7]

STM32之SPI浅析

[复制链接]
5623|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
okyouwin|  楼主 | 2015-12-30 14:52 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
对于STM32的SPI ,Reference Manual中是给出的schematic如下:

按照标准的SPI协议,当SPI被配置为主机模式后,通过SPI对从设备进行操作时,其NSS应该自动置低,从而选中(使能)从设备;一旦不对从设备进行操作,NSS立刻置为高。

但是,我在实际调试过程中却发现:STM32 SPI NSS无法自动实现跳变。 一旦SPI初始化完成并使能SPI,NSS立刻置低,然后保持不变。

这个问题一直无法解决,直到我在ST官方论坛上看到国外有些技术人员也在讨论这个问题,他们得出的结论是:STM32 SPI NSS无法自动跳变。


沙发
okyouwin|  楼主 | 2015-12-30 14:52 | 只看该作者
对于STM32的SPI ,Reference Manual中是给出的schematic如下:

按照标准的SPI协议,当SPI被配置为主机模式后,通过SPI对从设备进行操作时,其NSS应该自动置低,从而选中(使能)从设备;一旦不对从设备进行操作,NSS立刻置为高。

但是,我在实际调试过程中却发现:STM32 SPI NSS无法自动实现跳变。 一旦SPI初始化完成并使能SPI,NSS立刻置低,然后保持不变。

这个问题一直无法解决,直到我在ST官方论坛上看到国外有些技术人员也在讨论这个问题,他们得出的结论是:STM32 SPI NSS无法自动跳变。
ST官方技术人员也证实:STM32 SPI NSS是不会自动置位和复位的。按照官方说法,ST已经将其列入了改进计划。

对于这个问题,可以采用下面的方法解决:

在SPI初始化时,采用NSS soft模式,然后使能NSS输出功能。从而将NSS当做GPIO使用,通过软件set和reset来实现NSS的置位和复位。

具体代码如下:

/* SPI1 configuration ------------------------------------------------------*/

SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;

SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//SPI_FirstBit_MSB;

SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStructure);

/*Enable SPI1.NSS as a GPIO*/

SPI_SSOutputCmd(SPI1, ENABLE);

/*Configure PA.4(NSS)--------------------------------------------*/

GPIO_InitStructure.GPIO_Pin =GPIO_Pin_4;

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;

GPIO_Init(GPIOA, &GPIO_InitStructure);

通过将NSS配置为GPIO,在通过SPI操作从设备时,就可以通过软件来选中和释放从设备了。虽然比起硬件自动置位要麻烦,但问题毕竟解决了。

进一步阅读长达900页的Manual,我发现,文中对于SPI hard模式的描述并非大多数人所认为的硬件SPI,原文如下:

Slave select (NSS) pin management

There are two NSS modes:

● Software NSS mode: this mode is enabled by setting the SSM bit in the SPI_CR1

register (see Figure 209). In this mode, the external NSS pin is free for other

application uses and the internal NSS signal level is driven by writing to the SSI bit in

the SPI_CR1 register.

● Hardware NSS mode: there are two cases:

– NSS output is enabled: when the STM32F20xxx is operating as a Master and the

NSS output is enabled through the SSOE bit in the SPI_CR2 register, the NSS pin

is driven low and all the NSS pins of devices connected to the Master NSS pin see

a low level and become slaves when they are configured in NSS hardware mode.

When an SPI wants to broadcast a message, it has to pull NSS low to inform all

others that there is now a master for the bus. If it fails to pull NSS low, this means

that there is another master communicating, and a Hard Fault error occurs.

– NSS output is disabled: the multimaster capability is allowed.

当SPI配置为hard模式后,通过检测NSS可以实现的是自身主机和从机模式的切换,而不是大多数人所认为的自动NSS。。。也就是说:在一个多SPI系统中,STM32 SPI通过NSS检测,一旦发现系统中无NSS低信号,自己就输出低,从而成为主机;当系统中有NSS低信号时(及已经有其它SPI宣布为主机),自己就配置为从机。 所谓的hard模式的NSS,实际就是为了实现多机间通信的。

小结:

望文生义很可怕,Manual要仔细研读。

STM32的SPI NSS不论是配置为soft还是hard都是无法自动置位的,但这却是大多数应用所需要的。正如ST 论坛上RichardE所说:“Everything would be done by the peripheral. Fire and forget.”


使用特权

评论回复
板凳
beyond696| | 2015-12-30 15:56 | 只看该作者
一直都是手动控制片选信号

使用特权

评论回复
地板
捉虫天师| | 2015-12-30 20:52 | 只看该作者
mbed SPI通讯

Uart串口通讯只能满足点对点的通讯,无法满足一对多的通讯要求,另外,在传输速度上也比较受限,不能满足高速传输的要求,于是人们又设计出了新的通讯方式,即SPI(Serial Peripheral Interface,串行外围设备接口)。

SPI通讯需要使用4条数据线:主机输出/从机输入(MOSI)、主机输入/主机输出(MISO)、串行时钟SCK和外设芯片选择(CS)。对于SPI通讯来说,提供时钟信号的一方为主机,另外一方为从机,主机还需要负责提供芯片选择信号以确认主机和谁通讯,而从机需要有SPI接口专用的芯片选择管脚,称为从机选择(SS)管脚,其目的是使MCU处于SPI从机模式。下图显示了SPI通讯的连接示意:

从图中可以更清楚地看出,SPI通讯可以实现一对多的连接和数据传输,但在一确定的时刻,它的通讯方式还是1对1的,也就是一个主机对应一个从机,而具体的从机是谁,是有片选信号决定的,主机负责提供片选信号,从机负责提供片选引脚。其中SPI的四个引脚意义具体描述如下:

(1)   MOSI – 主器件数据输出,从器件数据输入

(2)   MISO – 主器件数据输入,从器件数据输出

(3)   SCK – 时钟信号,由主器件产生

(4)   /SS – 从器件使能信号,由主器件控制

对于SPI来说,我们还要理解以下它的不同工作模式,下图是一个典型的SPI时序:

这里列出了两种时钟信号,一个在周期内由低向高跳变,另外一个则是由高向低跳变,这就是工作模式的决定因素之一,另外一个因素则是数据信号是在上升沿采集还是下降沿采集,则是,SPI就有四种工作模式了,具体SPI的工作原理可以参考产品的Datasheet。

Mbed支持SPI主机和从机的相关操作,它使用SPI完成SPI主机的相关功能,使用SPISlave完成SPI从机的相关功能,具体列表如下:

类名
方法
用途
SPI
SPI(PinName mosi, PinName miso, PinName sclk, PinName _unused=NC);
构造函数,用mosi,miso,sclk三个管脚用作SPI主机通讯管脚,同样需要注意的是,只有特定的几个管脚能用作SPI通讯管脚,具体定义在spi_api.c中
void format(int bits, int mode = 0);
设置SPI工作的模式,bits表示SPI帧的bit数,取值为4-16,mode取值为0-3
void frequency(int hz = 1000000);
设置SPI时钟的频率
virtual int write(int value);
主机通过SPI发送数据,返回值为从机德返回值
SPISlave
SPISlave(PinName mosi, PinName miso, PinName sclk, PinName ssel);
构造函数,用mosi,miso,sclk三个管脚用作SPI从机通讯管脚,ssel用作片选管脚
void format(int bits, int mode = 0);
设置SPI工作的模式,bits表示SPI帧的bit数,取值为4-16,mode取值为0-3
void frequency(int hz = 1000000);
设置SPI时钟的频率
int receive(void);
判断SPI从机是否接收到数据,0表示没有,1表示有
int read(void);
读取SPI从机接收缓冲区中的数据
void reply(int value);
SPI从机向SPI主机发送数据value




使用特权

评论回复
5
捉虫天师| | 2015-12-30 20:53 | 只看该作者
mbed SPI串行通讯主机应用

对于单片机来说,大多数时候它是当SPI主机使用,而当做SPI从机使用的一般是各种各样的传感器或执行器,xbed LPC1768上的at86rf231射频芯片采用的就是SPI通讯方式。SPI通讯主机应用的基本过程包括两步,首先是SPI的初始化,包括工作模式的确定,工作频率的确定等;然后再是SPI主机的收发,下面是一个读取xbed LPC1768附带射频芯片版本的示例代码:

#define RF231_CSPIN P0_20

SPI RF231_spi(P0_18, P0_17, P0_15);

DigitalOut RF231_cs(RF231_CSPIN);

#define RG_PART_NUM (0x1c)

#define RG_VERSION_NUM (0x1d)

#define TRX_CMD_RADDR_MASK   (0x3f)

#define TRX_CMD_RR           (1<<7)

#define TRX_CMD_RW           (1<<6|1<<5)

Serial pc(USBTX,USBRX);

void trx_reg_write(uint8_t addr, uint8_t val)

{

    addr = TRX_CMD_RW | (TRX_CMD_RADDR_MASK & addr);

    RF231_cs = 0;

    RF231_spi.write(addr);

    RF231_spi.write(val);

    RF231_cs = 1;

}

uint8_t trx_reg_read(uint8_t addr)

{

    uint8_t val;

    addr=TRX_CMD_RR | (TRX_CMD_RADDR_MASK & addr);

    RF231_cs = 0;

    RF231_spi.write(addr);

    val=RF231_spi.write(addr);

    RF231_cs=1;

    return (uint8_t)val;

}

int main() {

    RF231_cs = 1;

    RF231_spi.format(8, 0);

    RF231_spi.frequency(1000000);

    while (1)

    {

        pc.printf("RFIC part number and version number is:%d.%d \n",trx_reg_read(RG_PART_NUM),trx_reg_read(RG_VERSION_NUM));

        wait(1);

    }

}这里需要理解的就是SPI从机的寄存器地址的概念,其实大量的SPI通讯,尤其是和SPI外设之间的通讯其主要目的就是寄存器的读写,如上面例子的RG_PART_NUM和RG_VERSION_NUM就是保存射频芯片版本的寄存器地址, 在SPI读写时的如下操作:

addr=TRX_CMD_RR | (TRX_CMD_RADDR_MASK & addr)

是根据AT86RF231的Datasheet编写的,其目的是为了区分是读寄存器还是写寄存器,以及限制各自可操作的寄存器地址范围,从上面的代码可以看出,对于一般的SPI通讯来说,读写是类似的,不管是读写,首先是拉低片选信号,然后发送需要操作的寄存器地址,最后再进行后续读写操作,如果是写,直接发送写入值即可,如果是读,可以再发送一次地址数据然后读取返回值。上面的代码中还给出了SPI写寄存器的函数,用户需要时可以操作。


使用特权

评论回复
6
捉虫天师| | 2015-12-30 20:53 | 只看该作者
借楼用用--------------------

mbed SPI双向串行通讯

虽然微处理器主要用作SPI从机,但在某些微处理器之间需要高速双向通讯的场合,它也可以当SPI从机使用,为了完成SPI之间的双向主从通讯,我们需要两块xbed LPC1768,配合示例代码使用的连接方式如下:

SPI主机的代码:

SPI spi_master(p11, p12, p13); // mosi, miso, sclk

#define PIN_CS p14

DigitalOut cs(PIN_CS);

DigitalOut led(LED1);

Serial pc(USBTX,USBRX);

int main()

{

    spi_master.format(8,3);

    spi_master.frequency(1000000);

    while (1)

    {

        cs=0;

        led=1;

        pc.putc(spi_master.write('H'));

        cs=1;

        led=0;

        wait(1);

    }

}

SPI从机的代码:

SPISlave spi_slave(p11, p12, p13, p14); // mosi, miso, sclk, ssel

Serial pc(USBTX,USBRX);

int main() {

    spi_slave.format(8,3);

    while(1) {

        while(spi_slave.receive())

        {

            pc.putc( spi_slave.read());

            spi_slave.reply('K');

        }

    }


}



使用特权

评论回复
7
捉虫天师| | 2015-12-30 20:56 | 只看该作者
由于在SPI通讯总是由SPI主机发起的,所以以上的代码就够用了,那么有没有SPI从机主动发送数据的时候呢,答案是肯定的,如射频芯片接收到一个数据包后就应该主动发给微处理器,但我们必须明确,在任何时刻,SPI从机都是不能主动发送数据的,为此,我们需要先发送一个中断信号给SPI从机,然后再由SPI主机来读取,下面是相应的演示代码,另外,我们还需要把两块xbed的p15相连用于处理中断信息,主机端代码如下:
SPI spi_master(p11, p12, p13); // mosi, miso, sclk
#define PIN_CS p14
DigitalOut cs(PIN_CS);
DigitalOut led1(LED1);
DigitalOut led2(LED2);
Serial pc(USBTX,USBRX);
InterruptIn spi_in(p15);
void getdata()
{
    led1=!led1;
    wait_ms(1);
    cs=0;
    spi_master.write('A');
    pc.putc(spi_master.write(0));
    pc.putc('\n');
    cs=1;
}
int main()
{
    spi_master.format(8,3);
    spi_master.frequency(100000);
    spi_in.fall(&getdata);
    while (1)
        ;

}
从机端代码如下:
SPISlave spi_slave(p11, p12, p13, p14); // mosi, miso, sclk, ssel
Serial pc(USBTX,USBRX);
DigitalOut spi_signal(p15);
DigitalOut led1(LED1);
DigitalOut led2(LED2);
int value;
void senddata()
{
    led1=!led1;
    value=pc.getc();
    spi_signal=0;
    spi_signal=1;
}
int main() {
    spi_slave.format(8,3);
    spi_slave.frequency(100000);
    spi_signal=1;
    pc.attach(&senddata);
    while(1)
    {
        if(spi_slave.receive())
        {
            spi_slave.reply(0);
            int addr=spi_slave.read();
            if (addr=='A')
                spi_slave.reply(value);
            led2=!led2;
        }
    }

}
     其中的led主要用来指示通讯的过程,上述代码的工作过程如下:当从机的串口收到一个字符以后,主动发送中断信号给SPI主机,主机响应后发起SPI读取过程,该过程实际上就是SPI主机读取SPI外设的模拟。


使用特权

评论回复
8
dongnanxibei| | 2015-12-30 21:40 | 只看该作者
SPI接口是通过CS进行地质选择的吗?也就是只有通过CS进行片选操作,

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:把每天当做世界末日、。、

56

主题

765

帖子

3

粉丝