先废话两句,本人第一次发帖,有什么说的不妥当的地方请各位见谅。学习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的数据会左移(如果是高位先发送),并且会从MISO(Master input Slave output)读入数据填补SPI->DR左移后的空缺。传完8比特后,SPI->DR再把数据并行写入接收缓冲寄存器。所以,主机SPI1与从机SPI1的通信过程如下:
图是从网上档的...我从机也会用的spi1 大家在这里只需用关注主从机的区别就行
还有一点要注意:两个stm32通信 必须保证 两块芯片共地 否则 呵呵...
还有就是这个图 想学好 spi 必须要看的懂这个图 太重要
然后把我学习 SPI时候的笔记上传了,有兴趣的可以看下
http://note.youdao.com/share/?id=9bc24f851f2de191f81fc0f369d811fa&type=note
敲完 收工 大牛转身, 不喜勿喷
一周热门 更多>