关于使用STM32硬件SPI驱动NRF24L01
关于使用STM32硬件SPI驱动NRF24L01+ 今天是大年初一总算有时间做点想做很久的事了,说到NRF2401可能很多电子爱好者都有用过或是软件模拟驱动又或是用单片机自带的硬件SPI来驱动,但不管是用哪种方法来驱动我想都在调试方面耗费了不少的时间(可能那些所谓的电子工程师不会出现这种情况吧!)网上的资料确实很多,但大多数都并没有经过发贴人认真测试过,有的只是理论上可以行的通但上机测试只能说是拿回来给他修改。本文作者也是经过无助的多少天才算是调试成功了(基于STM32硬件SPI,软件模拟的以前用51单片机已经调通了今天就不准备再拿来讲了,当然如果以后有朋友有需要也可以告诉我,我也可以重新写一篇关于51的驱动的只要有时间是没有问题的。)因为我用的是STM32F103C8T6的系统而且是刚接触不知道别的系统和我用的这个系统有多大的差别所以我不会整个代码全贴上来就完事了,我将就着重思路配合代码写出来,这样对于刚接触单片机的朋友会有很好的作用,但是还有一点请大家要原谅,可能会存在一些说的不好的地方,毕竟我没有经过正规渠道系统地学习过电子知识,对于前辈来说存在这样那样的问题不可避免的,在此也希望大家指教!
贴个图先:
NRF2401+的资料大家上网查一下,我输字的速度有点不好说!下面我来说一下整个调试流程吧
1.先把STM32串口一调通(因为我不知道STM32 I/O口不知可不可以像51那样并口输出数据,如果可以那就更方便啰)。
2.与NRF2401建立起通信(这个才是问题的关键);
3.利用读NRF2401的一个状态寄存器(STATUS)的值并通过串口发送到PC后通过51下载软件的串口助手显示出来(如果你用液晶来调试那你太有才了,你液晶和NRF2401存在牵连可能就会给寻找不成功的原因造成困难,而且还有不少硬件工作要做)在这说一下本文只调试发送程序,致于接收只改一个程序参数就行了。
我们先来调试STM32F103C8T6的串口1吧(也就是USART1)!它是STM32F103C8T6的片上外设之一,使用它时相对来说简单了不少。首先我要说一下我们要使用STM32的片上外设那么我们必须先对其进行初始化,实际上就是经过这段初始化代码让外设根据我们的要求来工作:
void USART1_AllInit(void)//意思是USART1的所有初始化工作,我的英文不好所以可能涵数名可能也不怎么规范
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟,它是在APB2这条总线上的
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟,它也是在APB2这条总线上的,因为USART1要用到GPIOA的端口所以也要初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);//到此USART1的IO已经初始化了GPIO_InitStructure要在使用前先通过GPIO_InitTypeDef声明一下。
USART_https://www.360docs.net/doc/0315506253.html,ART_WordLength=USART_WordLength_8b;
USART_https://www.360docs.net/doc/0315506253.html,ART_StopBits=USART_StopBits_1;
USART_https://www.360docs.net/doc/0315506253.html,ART_Parity=USART_Parity_No;
USART_https://www.360docs.net/doc/0315506253.html,ART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_https://www.360docs.net/doc/0315506253.html,ART_Mode=USART_Mode_Tx;
USART_Init(USART1,&USART_InitStructure);
USART_Cmd(USART1,ENABLE);//到此USART1已经初使化完成并使能所以以后就可以用了USART_https://www.360docs.net/doc/0315506253.html,ART_BaudRate=1200;//从这开始初始化串口的工作波特率1200,每帧数据8位长度,停此位1位,不进行较验,硬件流禁止,工作在发送模式,因为要用到USART_InitStructure所以在这之前因该找个合适的地方先使用串口初始化结构体声明一下,说一下我是在主涵数之前声明的。
}
那么我们来发送一个数字到串口然后通过串口助手看下收到的是什么看看串口按要求工作没有;
#include
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//把上面刚写的串口初始化涵数也加到这来!
Int main(void)
{
USART1_AllInit();
While(1)
{
USART_SendData(USART1,0XAA);
}
}
然后打开串口助手设置波特率为1200,不较验,1位停止位。收到数据是0XAA吧!简单吧哈哈!我没有网络不能用QQ剪切屏幕啊要不把效果也贴止来就好了。好了串口就这样被我们调通了兴奋不?不单可以用在我们今天的调试上哦,在主涵数头你想要它在什么时侯发送什么那就看你高兴怎么用了。
好了下面我们继续调试SPI吧,我用的是SPI2,SPI1认不到是我那息不小心整坏啰不的还有5转3.3V 的LM1137不过已经更换好了,还让我调了三四天时间悲哀不?所以没有一个师傅什么都写什么都伤人呀!还好后来用试波器看了下I/O才发现了原因!也是一样先初始化一下SPI2:
void SPI2_AllInit()//SPI2所有初始化乱定义的涵数名不管只要可以用就行你甚至可以用脏话只要你素质达的到就行!
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//SPI2默认I/O在GPIOB上所以RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//这个都不用说了吧
RCC_PCLK2Config(RCC_HCLK_Div2);
SPI_Cmd(SPI2, DISABLE); //这里还是说一下为什么要先失能SPI2,原因是你不关它在工作就不听你的话了,说的好别扭哦,就像电机在高速运转时你不可能不关电源就云调整它的皮带盘吧,那样以我能想到的结果可能你会被搞出血吧!嘻嘻!
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;//这里的GPIO_InitStructure就用上面调USART时的那个声明就好了因为它的值可以变,是个结构变量,包括下面的。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14|GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);//到此SPI2要用到的I/O已初始化完了;速率50MHZ,作为输出的用的是复用推换输出(PIN13-15),输入的I/O用的是GPIO_Mode_IN_FLOATING(浮空输入)。
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//从这点开始开初初始化SPI,SPI_InitStructure要在之前声名。
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_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI2, &SPI_InitStructure);
SPI_NSSInternalSoftwareConfig(SPI2,SPI_NSSInternalSoft_Set);//到此SPI2的初始化进程完成,主机模式,每帧8位,闲时为低电平,第一个上升沿就采样,片选用软件,波特率预分频256调试时尽量让频率低点只要你赖烦,数据高位先发,
SPI_Cmd(SPI2, ENABLE);//然后打开SPI2开始工作了哦
}
到底工作没有我说了不能算(调个SPI1都调3、4天的人),让事实说话,我们先用SPI读数据的涵数来读个我们可以控制的数来看对不;
uchar SPI_RW(uchar value)//这个涵数是写数据的但是同时可以读回一个数
{
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);
SPI_I2S_SendData(SPI2,value);
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);
return SPI_I2S_ReceiveData(SPI2);//这个就是读涵数了是库里面的所以你工程要包涵这些用到的库哟
}
也单独调一下;
Int main(void)
{
SPI2_AllInit();//刚才写的初始化涵数加到这用一下
While(1)
{
USART_SendData(USART1,SPI_RW(0XFF));//这参数0XFF可以是任何8位数据主要是让
SPI_I2S_SendData(SPI2,value);这涵数可以成立也好让SPI产生时钟方便SPI读入数据,SPI_I2S_ReceiveData(SPI2);这个涵数就不会产生时钟用试波器看看吧所以不能单独用它直接收到数据。}
}
编译下载后我们打开串口然后把SPI2的MISO连接到3.3V和GND看看分别收到的是什么数,是不是接3.3V时收到的是0XFF,接GND时是不是0X00呀!用SPI1和SPI2对发数据当然更直观了只是我的SPI1用不了了呀!好!到此我们的准备工作算是全部做完了
好我们开始根据NRF2401+的工作流程写驱动吧,之前初始化时用的是SPI_InitStructure.SPI_NSS = SPI_NSS_Soft所以先写个软件驱动I/O呐!片选不能像用51那样CNS=SBIT P0^*了,所以我用涵数来实现吧!
void CSN(uchar value)//不用反回什么值,用个参数来设置I/O为高或低实际可以为BIT变量的
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//我们用GPIOB的8脚做CSN所以初始化一下该脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);//完成初始化
if(value==1)//如果我们赋的是1就执行下面的置1涵数也是库涵数哦
GPIO_SetBits(GPIOB,GPIO_Pin_8);
else//如果我们赋的是0就执行下面的复位涵数也是库涵数哦
GPIO_ResetBits(GPIOB, GPIO_Pin_8);
}//这个涵数我得夸一下我写的好!
再写NRF2041的CE和IRQ(预计用的时间可能不够了)既然CSN的写法好我们再用这方法来写啰;void CE(uchar value)//表示高低电平的变量,涵数体内部的初始化过程说了好几次了不用说了吧
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
if(value==1)
GPIO_SetBits(GPIOB,GPIO_Pin_7);
else
GPIO_ResetBits(GPIOB, GPIO_Pin_7);
}
uchar Read_IRQ()//这个要说一下IRQ是NRF2401的中断I/O,为低时表示有数据要处理了所以我们可以用中断涵数来做处理,也可以通过查寻方式来处理它这这里我们让它反回一个值方便用查寻方式来处理
{
uchar readdata;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB,&GPIO_InitStructure);
readdata=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9);
return readdata;
}
其它的NRF2401的I/O都可以连接到STM32F103C8T6的硬件上,所以不需再做处理(如果再把其它的I/O都做单独处理那你不要用硬件SPI了用51 I/O模拟吧)。
写单个字节进NRF2401寄存器涵数
uchar SPI_RW(uchar value)//要写的值value
{
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);
SPI_I2S_SendData(SPI2,value);
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);
return SPI_I2S_ReceiveData(SPI2);
}
void SPI_RW_REG(uchar reg,uchar value)//这涵数是往指定的NRF2401(uchar reg)里写设定值(uchar value)
{
CSN(0);
delayms(20);//这里的延时涵数大家自已写个吧目的只要是能让I/O反应过来就行(主要是NRF2401
的I/O STM32的I/O速率到不用那么但心)
SPI_RW(reg);
SPI_RW(value);
CSN(1);
delayms(20);
}
写一个涵数读取NRF2401指定寄存器内的值;
uchar readreg(uchar reg,uchar value)//要反回一个值要不我们怎么会知道读到的是什么呢,寄存器地址reg,最好写个最高位是1的值我们用0XFF赋给形参value
{
CSN(0);
delayms(100);
SPI_RW(reg);
SPI_RW(value);
CSN(1);
return SPI_I2S_ReceiveData(SPI2);
}
我们先用这个刚写的涵数读一下NRF2401的状态寄存器STATUS的值调整一下我们失落的情绪!当然再这之前保证所有I/O连接正确我的NRF2401的I/O标注是以主机I/O为准的也就是说MOSI接的是SPI2的MOSI,NRF2401的MISO接的是SPI2的MISO。
#define STATUS 0X07
Int main(void)
{
SPI2_AllInit();
USART1_AllInit();
while(1)
{
USART_SendData(USART1,readreg(STATUS,0XFF));
}
}
yge ukd 请问读出来的是多少呢?是不是0X0E?如果是恭喜你!你和NRF2401取的了联系(听到你的命令并回应你的命令了,你可以松口气了因为NRF2401的STATUS寄存器复位后的值就是0X0E,看下面这个数据手册就知道了.
那么我们如果让NRF2401发送出数据再读出STATUS的值它应该就会有变化(第五位也就是TX_DS 位将置1)再这之前我们先初始化一下NRF2401;
因写地址和发送多字节时用写单字节涵数太过于麻烦所以我们写一个写多字节的涵数;
void writebuf(uchar reg,uchar *pbuf,uchar width)//我们不要求读回数据,只赋寄存器值reg,数据首地址*pbuf,数据长度width字节
{
uchar i;
CSN(0);
delayms(20);
SPI_RW(reg);
for(i=0;i { SPI_RW(*pbuf++); } CSN(1); } 接下来我们写初始化NRF2401涵数; void init2401(uchar mode) { CE(0); CSN(1); CSN(0); delayms(20); writebuf(Write_REG+TX_ADDR,txaddress,5); writebuf(Write_REG+RX_ADDR_P0,txaddress,5); SPI_RW_REG(Write_REG+EN_AA,0); SPI_RW_REG(Write_REG+EN_RXADDR,1); SPI_RW_REG(Write_REG+SETUP_RETR,0); SPI_RW_REG(Write_REG+RF_CH,0); SPI_RW_REG(Write_REG+RX_PW_P0,1); SPI_RW_REG(Write_REG+RF_SETUP,7); SPI_RW_REG(Write_REG+CONFIG,mode);//把MODE赋给CONFIG这种方法成了我惯用的技俩了} 把NRF2401工作设置成我们的要求后就可以让它发送接收了,我们写一个发关和读取接收的涵数吧void txpackte(uchar *p,uchar num)//要发的数据地址*p,数据长度num { CE(0); CSN(0); delayms(20); SPI_RW(WR_TX_PLOAD); writebuf(WR_TX_PLOAD,p,num); CSN(1); CE(1); delayms(200); } 我们发出倒是解决了但是如果我们改变void init2401(uchar mode)的MODE值设置成接收时怎么读出接收到的数据来呢?看下面的涵数: void readbuf(uchar reg,uchar *pbuf,uchar num)//目标寄存器REG,*pbuf是用来装读取的数据的一个数组变量用前定义一下,数据长度NUM { uchar i; CSN(0); delayms(20); SPI_RW(reg); for(i=0;i pbuf[i]=SPI_RW(0xff); CSN(1); delayms(20); } 我们把所有的NRF2401的寄存器值和指令全部宏定义出来以方便使用: #define TX_ADR_WIDTH 5 #define TX_PLOAD_WIDTH 1 #define RX_PLOAD_WIDTH 1 #define SET_NRF_RX 0x0f #define SET_NRF_TX 0x0e #define Write_REG 0x20 #define RD_RX_PLOAD 0x61 #define WR_TX_PLOAD 0xA0 #define FLUSH_TX 0xE1 #define CONFIG 0x00 #define EN_AA 0x01 #define EN_RXADDR 0x02 #define SETUP_AW 0x03 #define SETUP_RETR 0x04 #define RF_CH 0x05 #define RF_SETUP 0x06 #define STATUS 0x07 #define OBSERVE_TX 0x08 #define CD 0x09 #define RX_ADDR_P0 0x0A #define RX_ADDR_P1 0x0B #define RX_ADDR_P2 0x0C #define RX_ADDR_P3 0x0D #define RX_ADDR_P4 0x0E #define RX_ADDR_P5 0x0F #define TX_ADDR 0x10 #define RX_PW_P1 0x12 #define RX_PW_P2 0x13 #define RX_PW_P3 0x14 #define RX_PW_P4 0x15 #define RX_PW_P5 0x16 #define FIFO_STATUS 0x17 uchar TxBuf[]={1}; uchar txaddress[]={0,1,2,3,4}; 现在我们用上面的所有涵数可以写出一个较完整的主涵数了 int main(void) { SPI2_AllInit(); USART1_AllInit(); init2401(SET_NRF_TX); while(1) { USART_SendData(USART1,readreg(STATUS,0xff));//在发送前看下STATUS的值实际上是0X0E没有发送成功txpackte(TxBuf,1);//把TxBuf发出去只有一个值1 USART_SendData(USART1,readreg(7,0xff));//再读一次看下有没有变化实际上已经变成了0X2E说明已经发送成功 while(1); } } 好了到此已经可以发送了致于发什么样的数据你可以自已定的,致于接收我们初始化时把MODE赋成SET_NRF_RX 就好了然后在主涵数里自已处理接收到的数据就好了,但是如果初始化时CONFIG没有设置成接收完成触发中断的话只能判STATUS值来完成读取了,但是要是你看完本文的话想要它怎么工作都不是问题的,好了谢谢在家的支持,一天的工夫算是没有白费,吃年饭去了!实、祝大家春节快乐!下次再见!