OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 24667|回复: 16

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

[复制链接]

1

主题

2

帖子

0

精华

新手入门

积分
26
金钱
26
注册时间
2014-12-2
在线时间
0 小时
发表于 2014-12-3 09:45:52 | 显示全部楼层 |阅读模式
     先废话两句,本人第一次发帖,有什么说的不妥当的地方请各位见谅。学习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("\n");
       }
   
    }

 }


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

#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("\n");

     }
  }


}



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

分析:

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

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

没有傲慢的资格,就要不停的战斗!
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

5

主题

74

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
329
金钱
329
注册时间
2015-9-11
在线时间
46 小时
发表于 2016-3-27 22:12:47 | 显示全部楼层
好东西,有没有寄存器版本的?
迫于生计
回复 支持 1 反对 0

使用道具 举报

7

主题

333

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1838
金钱
1838
注册时间
2012-7-16
在线时间
504 小时
发表于 2014-12-3 10:11:44 | 显示全部楼层
什么样的项目,一块STM32还不够。
回复 支持 反对

使用道具 举报

1

主题

2

帖子

0

精华

新手入门

积分
26
金钱
26
注册时间
2014-12-2
在线时间
0 小时
 楼主| 发表于 2014-12-3 11:08:55 | 显示全部楼层
回复【2楼】hyghyg1234:
---------------------------------
一个机器人,要用到鼠标,中断进入太频繁,很容易死机,所以两块
没有傲慢的资格,就要不停的战斗!
回复 支持 反对

使用道具 举报

58

主题

499

帖子

4

精华

金牌会员

Rank: 6Rank: 6

积分
1920
金钱
1920
注册时间
2013-11-18
在线时间
268 小时
发表于 2014-12-3 13:23:16 | 显示全部楼层
1、如果是STM32从机有数据要发送,只能被动发送,先送数据到Buff,等主机有数据传过来的时候,从机的数据才能发送过去,如果主机一直都没有发送数据过来,从机就不能够发数据到主机上。
2、你的DMA模式还是要死等DMA传输完成或者接收完成,那CPU还是这空出来
已经放下多年的FPGA,要重新再拾起来,却是如此的陌生
回复 支持 反对

使用道具 举报

7

主题

333

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1838
金钱
1838
注册时间
2012-7-16
在线时间
504 小时
发表于 2014-12-3 14:05:07 | 显示全部楼层
回复【3楼】只是戏べ何必认真:
---------------------------------
USB鼠标吗?
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2014-12-4 00:19:31 | 显示全部楼层
不错,谢谢分享。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

4

主题

49

帖子

0

精华

初级会员

Rank: 2

积分
107
金钱
107
注册时间
2015-12-4
在线时间
17 小时
发表于 2015-12-5 15:32:45 | 显示全部楼层
大侠, YOU_CS是什么?
回复 支持 反对

使用道具 举报

2

主题

8

帖子

0

精华

初级会员

Rank: 2

积分
90
金钱
90
注册时间
2013-3-26
在线时间
11 小时
发表于 2016-4-7 13:23:37 | 显示全部楼层
mark即将要用到。哈哈
回复 支持 反对

使用道具 举报

5

主题

20

帖子

0

精华

初级会员

Rank: 2

积分
90
金钱
90
注册时间
2015-10-4
在线时间
15 小时
发表于 2016-7-1 22:04:28 | 显示全部楼层
aa511950071 发表于 2016-3-27 22:12
好东西,有没有寄存器版本的?

我也想要个寄存器版本的
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
10
金钱
10
注册时间
2016-7-25
在线时间
1 小时
发表于 2016-7-26 10:25:53 | 显示全部楼层
好,我想要库函数版的
回复 支持 反对

使用道具 举报

14

主题

57

帖子

0

精华

初级会员

Rank: 2

积分
140
金钱
140
注册时间
2017-7-19
在线时间
26 小时
发表于 2018-6-10 19:24:42 | 显示全部楼层
请问你的 YOU_CS是什么意思啊?
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手上路

积分
49
金钱
49
注册时间
2018-4-27
在线时间
8 小时
发表于 2018-6-28 21:15:00 | 显示全部楼层
为什么要给A3设置高电平?A3和SPI没有什么关系啊?请大佬解答,,
回复 支持 反对

使用道具 举报

1

主题

4

帖子

0

精华

新手入门

积分
16
金钱
16
注册时间
2018-11-24
在线时间
4 小时
发表于 2018-11-26 16:04:18 | 显示全部楼层
这个好
回复 支持 反对

使用道具 举报

4

主题

163

帖子

0

精华

高级会员

Rank: 4

积分
955
金钱
955
注册时间
2018-9-7
在线时间
115 小时
发表于 2018-11-27 08:58:11 | 显示全部楼层
谢谢大佬分享
回复 支持 反对

使用道具 举报

1

主题

4

帖子

0

精华

新手上路

积分
41
金钱
41
注册时间
2019-3-11
在线时间
8 小时
发表于 2020-5-2 08:10:26 | 显示全部楼层
厉害了,大哥,这个问题已经困扰好几天了,必须点赞收藏
回复 支持 反对

使用道具 举报

1

主题

5

帖子

0

精华

初级会员

Rank: 2

积分
53
金钱
53
注册时间
2019-11-14
在线时间
19 小时
发表于 2020-9-21 21:07:45 | 显示全部楼层
艾笑O0 发表于 2018-6-28 21:15
为什么要给A3设置高电平?A3和SPI没有什么关系啊?请大佬解答,,

这个是一个片选信号,只是主机选从机的,与从机连接后,只要拉低从机就知道自己被选中了,所以任意的GPIO引脚都可以的
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-18 16:24

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表