两块STM32通过SPI完美实现通信(可添加DMA)

2019-07-21 07:27发布

     先废话两句,本人第一次发帖,有什么说的不妥当的地方请各位见谅。学习32有半年了,寄存器走了两遍,本以为学的差不多了。于是做了几个小项目。可是现在再做一个稍微大一点的项目,发现一块芯片根本没办法实现,于是只能用两块,这就牵扯当通信的问题,头疼几天才弄好。所以借着这股兴奋劲,发出来和大家分享分享!
欢迎大牛指点。

/*==============================================================================*/

                                          两块stm32,通过spi通信(寄存器版)


连线方式:(我主从机都用的SPI1,DMA用的DMA1)
==========================
    主机:                           从机:
 SPI_SCK  ------------------SPI_SCK
 SPI_MISO------------------SPI_MISO
SPI_MOSI------------------SPI_MOSI
 NSS不用连接

注意CS,  最好是加上片选信号线(原因见代码讲解的地方 !!很坑)
我用的是:主机PA3(推挽输出)   从机PA3(上拉输入)  (当然可以不同,不过必须要连接起来)?
/*=========================*/
先上传代码:
一些宏定义:
#define SPI1_DR_Addr ( (u32)0x4001300C )?
  ******************* *********************公用: **************************************************
/*==================================
SPI初始化函数
参数:
mode(模式)   1 配置为主机      0 配置为从机
dma (dma开关) ?1 代开dma   0  关闭DMA
/*===================================*/

void  SPI1_Init(u8 mode,u8 dma)
{  
RCC->APB2ENR|=1<<2;        //GPIOA时钟使能  
RCC->APB2ENR|=1<<12;      //SPI1时钟使能 
  
//IO口初始化  
GPIOA->CRL&=0X000FFFFF; 
GPIOA->CRL|=0XBBB00000;//GPIOA 5.6.7复用    
GPIOA->ODR|=0X7<<5;      //GPIOA 5.6.7上拉

//SPI1配置
SPI1->CR1|=0<<10; //全双工模式 
SPI1->CR1|=1<<9;   //软件nss管理

SPI1->CR1|=mode<<8;  //*******************ssi低电平**************************(如果是主机mode=1)?
/*这里需要注意::
主机:SSM(SPI1->CR1 位9)= 1      SSI (SPI1->CR1 位9)= 1

从机:SSM(SPI1->CR1 位9) = 1      SSI (SPI1->CR1 位9)=  0
*/


SPI1->CR1|=mode<<2; //SPI 主机模式    (如果是主机mode=1)
SPI1->CR1|=0<<11;      //  8bit数据格式   (可以配置为更高的位数,但是如果用了    DMA 数据只能是8位!!!)
SPI1->CR1|=1<<1;        //空闲模式下SCK为1 CPOL=1
SPI1->CR1|=1<<0;        //数据采样从第二个时间边沿开始,CPHA=1  
SPI1->CR1|=7<<3;        //Fsck=Fcpu/256  (默认低速模式)
SPI1->CR1|=0<<7;        //MSB 高位在前  
   if(dma)   //如果使能了DMA
    {
       SPI1->CR2 |= 1<<1  ;        //发送缓冲区DMA使能
       SPI1->CR2 |= 1<<0  ;        //接收缓冲区DMA使能
    } 

/***这里  如果你想用中断 可以直接将  从机片选  线设置为外部中断。或者spi中断但是我试了很多次spi中断的话 数据会丢失
  //  SPI1->CR2|=1<<6;      //接收缓冲区非空中断使能
  //   MY_NVIC_Init(1,0,SPI1_IRQn,4);       
       SPI1->CR1|=1<<6; //SPI设备使能 

//SPI1_ReadWriteByte(0xff);//启动传输(主要作用:维持MOSI为高) 
//这里要注意 千万不要加这条语句,和其他flash芯片等通信是可以加上,但是两个32通信千万不要加,在这块坑了我一下午!!!后面解释
}




DMA :优点:1、传输不占用芯片cpu,配合ucosII可以做大量数据的传输(很美,很强大)、其他的想不起来了 
           缺点:2、传输有些死板,如果是小量数据传输实在没必要!!(我没用,如果有人喜欢,可以加上。改几句就行) 
/*===================================
 DMA初始化函数   (不想使用DMA的直接跳过)
参数:*tbuff 和*rbuff:数据缓存器指针     
====================================*/

void SPI_DMA_Init(u8 *tbuff,u8 *rbuff)
{
      RCC->AHBENR |= 1<<0 ;                     //DMA1时钟使能

       /*------------------配置SPI1_RX_DMA通道1---------------------*/
    DMA1_Channel1->CCR&=~(1<<14);        //非存储器到存储器模式
    DMA1_Channel1->CCR|=2<<12;              //通道优先级高
    DMA1_Channel1->CCR&=~(3<<10);        //存储器数据宽度8bit
    DMA1_Channel1->CCR&=~(3<<8);          //外设数据宽度8bit
    DMA1_Channel1->CCR|=1<<7;               //存储器地址增量模式
    DMA1_Channel1->CCR&=~(1<<6);        //不执行外设地址增量模式
    DMA1_Channel1->CCR&=~(1<<5);        //不执行循环操作
    DMA1_Channel1->CCR&=~(1<<4);        //从外设读

  DMA1_Channel1->CNDTR &= 0x0000   ;        //传输数量寄存器清零
  //DMA1_Channel1->CNDTR = buffersize ;       //传输数量设置为buffersize个,每传输一个8bit数据会减1      先不配置

  DMA1_Channel2->CPAR = SPI1_DR_Addr ;    //设置外设地
  DMA1_Channel2->CMAR = (u32)&rbuff;         //设置DMA存储器地址

/*------------------配置SPI1_TX_DMA通道Channel3---------------------*/

DMA1_Channel3->CCR&=~(1<<14);     //非存储器到存储器模式
DMA1_Channel3->CCR|=(3<<12);        //通道优先级最低
DMA1_Channel3->CCR&=~(3<<10);     //存储器数据宽度8bit
DMA1_Channel3->CCR&=~(3<<8);       //外设数据宽度8bit
DMA1_Channel3->CCR|=(1<<7);          //存储器地址增量模式
DMA1_Channel3->CCR&=~(1<<6);       //不执行外设地址增量模式
DMA1_Channel3->CCR&=~(1<<5);       //不执行循环操作
DMA1_Channel3->CCR|=(1<<4);           //从存储器读

DMA1_Channel3->CNDTR &= 0x0000   ;        //传输数量寄存器清零
//DMA1_Channel3->CNDTR = buffersize ;       //传输数量设置为buffersize个,每传输一个8bit数据会减1

DMA1_Channel3->CPAR = SPI1_DR_Addr ;      //设置外设地址
DMA1_Channel3->CMAR =(u32)&tbuff;            //设置DMA存储器地址
}


/************************下面两个代码  一个是用DMA  一个是不用DMA***************************


/*=============使用DMA的代码==============*/
参数:*pdata 要发送的数据指针    
           *buff   数据缓存器地址??(即 主机从从机获取的数据,或者  从机从主机获取的数据 )
             leng  要传输的数据个数?
/*======================================*/
void SPI_DMA_Runing(u8 *pdata,u8 *buff,u32 leng)
{

DMA1_Channel3->CCR &= ~( 1 << 0 );               //关闭DMA通道3
DMA1_Channel2->CCR &= ~ (1 << 0 );               //关闭DMA通道2

//设置传输数量  (从SPI->DR 读取)
DMA1_Channel2->CNDTR &=0x0000   ;  //传输数量寄存器清零
DMA1_Channel2->CNDTR =leng;           //传输数  每传输一个8bit数据会减1
 
//设置传输数量  (向SPI->DR 发送)
DMA1_Channel3->CNDTR &= 0x0000   ;        //传输数量寄存器清零
DMA1_Channel3->CNDTR =leng;       //传输数量设置为leng个,每传输一个8bit数据会减1

//要把*pdata的内容发送出去 所以pdata为发送的源地址 
  
DMA1_Channel3->CMAR =(u32)pdata ; //设置DMA存储器地址
DMA1_Channel2->CMAR =(u32)buff;   //设置DMA存储器地址

//开始传输

  DMA1_Channel3->CCR |= 1 << 0 ;               //开启DMA通道3
  DMA1_Channel2->CCR |= 1 << 0 ;               //开启DMA通道2


while(((DMA1->ISR & (1<<5))==0)   &&  ((DMA1->ISR & (1<<9))==0) ); //等待通道2传输完成

//while(); //等待通道3传输完成

DMA1_Channel3->CCR &= ~( 1 << 0 );               //关闭DMA通道2
DMA1_Channel2->CCR &= ~ (1 << 0 );               //开启DMA通道2

DMA1->IFCR = 0xff ; //清除DMA传输完成标志

}
/*=========================================*/
//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)     //这个代码是抄的原子大哥的,我就不BB了  
{
u16 retry=0;
  u8 data;
while((SPI1->SR&1<<1)==0)//等待发送区空
{
retry++;
if(retry>0XFFFE)return 0;
}  
SPI1->DR=TxData;  //发送一个byte 
retry=0;
while((SPI1->SR&1<<0)==0) //等待接收完一个byte  
{
retry++;
if(retry>0XFFFE)return 0;
}  

   data=SPI1->DR;
return SPI1->DR;          //返回收到的数据    
}




/*=========================================*/
/*=================不使用DMA=================*/
参数:*pdata 要发送的数据指针    
           *buff   数据缓存器地址(即 主机从从机获取的数据,或者  从机从主机获取的数据 )
             leng  要传输的数据个数
/*======================================*/

void SPI_32(u8 *pdata,u8 *buff,u32 leng)
{
 while(leng--)
                     *buff++=SPI1_ReadWriteByte(*pdata++);
}






******************************************************************************************************



  *****   主机:*****
#define STM32_CS  Aout(3)
void main()
{
......
  u8 tbuff[]="0123456789";
  u8 rbuff[10];


   GPIOA->CRL&=0XFFFF0FFF;     //片选
   GPIOA->CRL|=0X00003000;  
   GPIOA->ODR|=1<<3;   //默认为高 关片选
   
   SPI1_Init(1,0);  //spi初始化  主机模式  不使能DMA
   //SPI1_SetSpeed(SPI_SPEED_256);  //越低越好  可以适当调整
   
while(1)
  {
    if(KEY_Scan()==0)   //按下key0键  发送10个字节 接受从机发回的10个字节  并打印
     {  
     SPI_32(tbuff,rbuff,10);     
       for(i=0;i<10;i++) 
           printf("%c",rbuff); 
           printf(" ");
       }
   
    }

 }


  *****   从机:*****

#define STM32_CS  Ain(3)   //从机片选
  当主机选择了从机 即拉低了主机的片选信号,那么主机的片选信号线会拉低,因为我们主从机的片选线是用杜邦线连接的,所以可以用从机的片选端检测,主机是否开始发送数据了。这样做有一个好处,主机在发送数据前
先拉低片选线,提前告知从机。  如果通过检测SPI->SR的第0位(数据接收缓存器非空) 有可能导致数据第一位接受错误。

void mian()
{
u8 tbuff[]="0123456789";
u8 rbuff[20];

...

   GPIOA->CRL&=0XFFFF0FFF;     //片选
   GPIOA->CRL|=0X00008000;    //输入模式
   GPIOA->ODR|=1<<3;   //上拉
   
   SPI1_Init(0,0);  //spi初始化  从机模式  不使能DMA
//从机spi的速度 不用设置,因为通信时速度(时钟)由主机决定
while(1)
{
   if(!YOU_CS)    //这里我用的是查询的方式,大家可以用外部中断。
      {
       SPI_32(&tbuff[0],&rbuff[0],10);
       //Write_Char(0,0,16,&rbuff[0]);
        for(i=0;i<10;i++)
       printf("%c",rbuff);    //可以开两个串口助手,分别接主从机  也可以都用TFT 显示
       printf(" ");

     }
  }


}



/*=============================================================================*/

分析:

SPI全双工通信的特点:一边发送一边接收,硬件上只有一个SPI->DR寄存器和两个缓冲器(发送缓冲器和接收缓冲器),主模式(从模式类似):SPI->DR会先读发送缓冲器,并通过MOSI管脚(Master output Slave Input)一位一位地发送出去,在发送的过程中,SPI->DR的数据会左移(如果是高位先发送),并且会从MISOMaster input Slave output)读入数据填补SPI->DR左移后的空缺。传完8比特后,SPI->DR再把数据并行写入接收缓冲寄存器。所以,主机SPI1与从机SPI1的通信过程如下:



图是从网上档的...我从机也会用的spi1  大家在这里只需用关注主从机的区别就行

还有一点要注意:两个stm32通信 必须保证 两块芯片共地 否则 呵呵...




还有就是这个图   想学好 spi 必须要看的懂这个图  太重要

然后把我学习 SPI时候的笔记上传了,有兴趣的可以看下
http://note.youdao.com/share/?id=9bc24f851f2de191f81fc0f369d811fa&type=note

敲完  收工 大牛转身, 不喜勿喷

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。