使用STM32F030F4P6的SPI协议和NRF24L01模块进行通讯 实现无线数据的收发
单片机这块纯属个人业余爱好, 有很多不足的地方还请大家多多指教, 代码中有些命名不规范的地方还请大家多多包涵.
本文只实现无线模块的简单的点亮(能收发一个字节数据), 一直想diy一个无线遥控的小车, 就要使用到无线模块, 找了好久发现NRF24L01(下面简称NRF)是最便宜的一款无线模块(除过WiFi和蓝牙模块), 就买了几个, 由于stm32f103涨价, 就选择了便宜的stm32f030, 网上找了很多资料对于stm32f030的资料很少, 他和stm32f103代码大同小异, 就试着在stm32f103代码的基础上修改一下, 就是不能通讯, 只能发送成功, 不能接收到数据, 搁置了好久最后从新选择了一块 HC-12 的无线通讯模块, 这个模块比较贵首次购买一套(收发两个模块)比较便宜, 空旷视野最远通讯距离1公里(没有实测量过), 他使用的是串口通讯, 写好代码烧录进去后可以通讯, 最后成功diy了无线遥控小车, 利用HC-12感觉大材小用了, 最后闲来无事就又琢磨一下这个NRF模块, 终于可以相互通讯了, 也不知道哪里出问题了, 唯一不同的是, 之前的是在网上找的资料上修修改改, 没有使用中断, 只使用了while循环进行检测, 这次重头开始编写的时候使用了中断, 在调试了一下就可以通讯.
遇到的一些问题:
1.原理图上PA4 是SPI1的片选spi1_nss的复用, 配置的时候把PA4也配置成了复用模式, 发现不能成功, 需要配置成输出模式解决了问题
2.NRF的IRQ脚配置中断的时候需要配置为下降沿触发
3.stm32板子和NRF模块进行连接的时候数据输出和输入线不能交叉连接(MCU 的MISO 和 NRF的 MISO 相连, MOSI同理)
以下是代码 , 适用于stm32f030
1. spi配置
#ifndef __bsp_spi_h #define __bsp_spi_h #include "stm32f0xx_gpio.h" #define SPIx SPI1 //SPI_1 #define SPI1_PORT GPIOA //PA 端口 #define PORTA_LCK RCC_AHBPeriph_GPIOA //GPIO 时钟 #define SPI_LCK RCC_APB2Periph_SPI1//spi 时钟 #define SPI1_CSN GPIO_Pin_1 //PA1 NSS #define SPI1_SCK GPIO_Pin_5 //PA5 SCK #define SPI1_MISO GPIO_Pin_6 //PA6 MISO #define SPI1_MOSI GPIO_Pin_7 //PA7 MOSI void SPI_Config(void); u8 SPI_SendByte(u8 byte); void Pin_CSN(u8 u); #endif
#include "bsp_spi.h" #include "stm32f0xx_gpio.h" //初始化 void SPI_Config() { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStruct; //端口初始化 RCC_AHBPeriphClockCmd(PORTA_LCK , ENABLE);//开启GPIO时钟 RCC_APB2PeriphClockCmd(SPI_LCK, ENABLE);//开启SPI_1时钟 //复用模式 GPIO_PinAFConfig(SPI1_PORT,GPIO_PinSource5,GPIO_AF_0);//SCK GPIO_PinAFConfig(SPI1_PORT,GPIO_PinSource6,GPIO_AF_0);//MISO GPIO_PinAFConfig(SPI1_PORT,GPIO_PinSource7,GPIO_AF_0);//MOSI GPIO_InitStruct.GPIO_Pin = SPI1_SCK | SPI1_MISO | SPI1_MOSI; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; GPIO_Init(SPI1_PORT, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = SPI1_CSN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_Init(SPI1_PORT , &GPIO_InitStruct); //spi初始化 //SPI_I2S_DeInit(SPIx); //将寄存器重设为缺省值 //SPI_Cmd(SPIx, DISABLE); //SPI_Direction_2Lines_FullDuplex SPI_Direction_1Line_Rx SPI_Direction_1Line_Tx SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI_Mode_Master 主机 SPI_Mode_Slave 从机 SPI_InitStruct.SPI_Mode = SPI_Mode_Master; SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;//SPI_CPOL_Low SPI_CPOL_High SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;//SPI_CPHA_1Edge SPI_CPHA_2Edge SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //SPI_FirstBit_MSB SPI_FirstBit_LSB SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStruct.SPI_CRCPolynomial = 7; SPI_Init(SPIx, &SPI_InitStruct); //SPI_I2S_IT_TXE SPI_I2S_IT_RXNE SPI_I2S_IT_ERR SPI_I2S_ITConfig(SPIx, SPI_I2S_IT_TXE | SPI_I2S_IT_RXNE, ENABLE);//中断 SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF); //重要,把应答数据位设置为 8 位 SPI_Cmd(SPIx, ENABLE);//使能 } //SPI 收发一个字节 u8 SPI_SendByte(u8 byte) { //设置时间溢出 u32 SPITimeout = 0xffff; /* 等待发送缓冲区为空,TXE 事件 */ while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET) { if ((SPITimeout--) == 0) return 0; } /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */ SPI_SendData8(SPIx, byte);//SPI_I2S_SendData16 //设置时间溢出 SPITimeout = 0xfffff; /* 等待接收缓冲区非空,RXNE 事件 */ while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET) { if ((SPITimeout--) == 0) return 0; } /* 读取数据寄存器,获取接收缓冲区数据 */ return SPI_ReceiveData8(SPIx); } //设置片选高低电平 void Pin_CSN(u8 u) { if(u==0) { SPI1_PORT->BRR = SPI1_CSN; } else { SPI1_PORT->BSRR = SPI1_CSN; } }
2.nrf配置
#ifndef __bsp_nrf0241_h #define __bsp_nrf0241_h #include "stm32f0xx_gpio.h" #define NRF_PORT GPIOA //PA 端口 #define KEY0 GPIO_Pin_0 //KEY0 #define LED0 GPIO_Pin_4 //LED0 #define NRF_CE GPIO_Pin_2 //PA2 CE #define NRF_IRQ GPIO_Pin_3 //PA3 IRQ #define NOP 0xFF // 空操作。可以用来读 状态寄存器 //设置ce置高和拉低 void Pin_CE(u8 u); //获取IRQ中断的电平,(没有用到) u8 Get_IRQ(void); //写入数据 u8 SPI_WriteBuf(u8 reg, u8 *pBuf, u8 len); //读取数据 u8 SPI_ReadBuf(u8 reg, u8 *pBuf, u8 len); //读写一条指令 u8 SPI_RWReg(u8 reg, u8 value); //配置 void NRF_Config(void); //获取按键的电平(用于判断按键是否按下) u8 Get_KEY0(void); //配置测试按键和LED灯 void KEY0_LED0_Config(void); //设置LED灯(低电平点亮) void Pin_LED0(u8 u); //发送数据buff为数据,len为个数 void send_data(u8 *buff,u8 len); //检测NRF模块是否存在 存在返回0 u8 nrf24l0_check(void); //读取状态寄存器, 用于判断发送成功, 接收成功, 发送达到最大值 u8 Get_Status(void); //接收数据 void receive_data(void); //获取接收到的数据 buf 存放接收的数据, len数据个数 void Get_Data(u8 *buf,u8 len); //清除所有(发送, 接收, 最大发送次数)的中断 void ClearStatus(void); #endif
#include "bsp_nrf0241.h" #include "stm32f0xx_gpio.h" #include "bsp_spi.h" #include "delay.h" void KEY0_LED0_Config() { GPIO_InitTypeDef GPIO_InitStruct; NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; RCC_AHBPeriphClockCmd(PORTA_LCK , ENABLE); //用户测试的按键和LED灯 GPIO_InitStruct.GPIO_Pin = KEY0;//按键 GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; GPIO_Init(NRF_PORT, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = LED0;//LED灯 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_Init(NRF_PORT , &GPIO_InitStruct); Pin_LED0(1);//低电平 熄灭LED灯 //添加按键中断 EXTI_InitStructure.EXTI_Line = KEY0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //EXTI_Trigger_Rising , EXTI_Trigger_Falling , EXTI_Trigger_Rising_Falling EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); //中断 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPriority = 1; //子优先级1 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 } u8 Get_KEY0(void) { return GPIO_ReadInputDataBit(NRF_PORT, KEY0); } void Pin_LED0(u8 u) { if(u==0) { NRF_PORT->BRR = LED0; } else { NRF_PORT->BSRR = LED0; } } //地址 u8 TX_ADDRESS1[5]= {0x34,0x43,0x10,0x10,0x01}; //本地地址 u8 RX_ADDRESS1[5]= {0x34,0x43,0x10,0x10,0x01}; //接收地址 //配置 void NRF_Config() { GPIO_InitTypeDef GPIO_InitStruct; NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; //端口初始化 RCC_AHBPeriphClockCmd(PORTA_LCK , ENABLE); GPIO_InitStruct.GPIO_Pin = NRF_CE;//CE 输出 GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; GPIO_Init(NRF_PORT, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = NRF_IRQ;//IRQ 中断输入 GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; GPIO_Init(NRF_PORT, &GPIO_InitStruct); //中断 EXTI_InitStructure.EXTI_Line = NRF_IRQ; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //EXTI_Trigger_Rising , EXTI_Trigger_Falling , EXTI_Trigger_Rising_Falling EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); //中断 NVIC_InitStructure.NVIC_IRQChannel = EXTI2_3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPriority = 1; //子优先级1 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //配置RNF Pin_CSN(1); Pin_CE(0); //0x02 发射, 0x03 接收- crc不使能 crc模式:8位效验 //0x0e 发射, 0x0f 接收- crc使能 crc模式:16位效验 SPI_RWReg(0x20+0x00,0x02);//0x02 发射, 0x03 接收 SPI_RWReg(0x20+0x01,0x00);//自动应答 若通道0应答:0x01 禁止自动应答:0x00 SPI_RWReg(0x20+0x02,0x01);//接收地址允许 通道0允许 SPI_RWReg(0x20+0x03,0x03);//设置地址宽度 5个地址宽度 SPI_RWReg(0x20+0x04,0x00);//建立自动重发 禁止自动重发 SPI_RWReg(0x20+0x05,40);//射频通道 SPI_RWReg(0x20+0x06,0x07);//射频寄存器 SPI_RWReg(0x20+0x07,0x70);//状态寄存器 SPI_WriteBuf(0x20+0x0A,RX_ADDRESS1,5);//通道0接收地址 SPI_WriteBuf(0x20+0x10,TX_ADDRESS1,5);//发送地址 SPI_RWReg(0x20+0x11,32);//接收数据通道0有效数据宽度 1-32 字节 } //发送 void TX_MODE() { //配置RNF Pin_CSN(1); Pin_CE(0); //0x02 发射, 0x03 接收- crc不使能 crc模式:8位效验 //0x0e 发射, 0x0f 接收- crc使能 crc模式:16位效验 SPI_RWReg(0x20+0x00,0x02); SPI_RWReg(0x20+0x01,0x00);//自动应答 若通道0应答:0x01 禁止自动应答:0x00 SPI_RWReg(0x20+0x02,0x01);//接收地址允许 通道0允许 SPI_RWReg(0x20+0x03,0x03);//设置地址宽度 5个地址宽度 SPI_RWReg(0x20+0x04,0x00);//建立自动重发 禁止自动重发 SPI_RWReg(0x20+0x05,40);//射频通道 SPI_RWReg(0x20+0x06,0x07);//射频寄存器 SPI_RWReg(0x20+0x07,0x70);//状态寄存器 SPI_WriteBuf(0x20+0x0A,RX_ADDRESS1,5);//通道0接收地址 SPI_WriteBuf(0x20+0x10,TX_ADDRESS1,5);//发送地址 SPI_RWReg(0x20+0x11,32);//接收数据通道0有效数据宽度 1-32 字节 } //接收 void RX_MODE() { //配置RNF Pin_CSN(1); Pin_CE(0); //0x02 发射, 0x03 接收- crc不使能 crc模式:8位效验 //0x0e 发射, 0x0f 接收- crc使能 crc模式:16位效验 SPI_RWReg(0x20+0x00,0x03);//0x02 发射, 0x03 接收 SPI_RWReg(0x20+0x01,0x00);//自动应答, 若通道0应答:0x01 禁止自动应答:0x00 SPI_RWReg(0x20+0x02,0x01);//接收地址允许 通道0允许 SPI_RWReg(0x20+0x03,0x03);//设置地址宽度 5个地址宽度 SPI_RWReg(0x20+0x04,0x00);//建立自动重发 禁止自动重发 SPI_RWReg(0x20+0x05,40);//射频通道 SPI_RWReg(0x20+0x06,0x07);//射频寄存器 SPI_RWReg(0x20+0x07,0x70);//状态寄存器 SPI_WriteBuf(0x20+0x0A,RX_ADDRESS1,5);//通道0接收地址 SPI_WriteBuf(0x20+0x10,TX_ADDRESS1,5);//发送地址 SPI_RWReg(0x20+0x11,32);//接收数据通道0有效数据宽度 1-32 字节 Pin_CE(1); delay_us(120); } //发送数据 void send_data(u8 *buff,u8 len) { TX_MODE(); SPI_RWReg(0xE1,0xFF);//清除发送寄存器 SPI_WriteBuf(0xA0,buff,len);//写入发送寄存器 Pin_CE(1); delay_us(20); Pin_CE(0); } //接收数据 void receive_data() { RX_MODE(); SPI_RWReg(0xE2,0xFF);//清除接收寄存器 Pin_CE(1); delay_us(120); } //获取数据 void Get_Data(u8 *buf,u8 len) { SPI_ReadBuf(0x61,buf,len); } //清除中断 void ClearStatus() { Pin_CE(0); SPI_RWReg(0x20+0x07,0x70); Pin_CE(1); delay_us(20); } void Pin_CE(u8 u) { if(u==0) { NRF_PORT->BRR = NRF_CE; } else { NRF_PORT->BSRR = NRF_CE; } } u8 Get_IRQ(void) { return GPIO_ReadInputDataBit(NRF_PORT, NRF_IRQ); } //用于写数据:为寄存器地址,pBuf:为待写入数据地址,uchars:写入数据的个数 u8 SPI_WriteBuf(u8 reg, u8 *pBuf, u8 len) { u8 s,ctr; Pin_CSN(0); delay_us(100); s=SPI_SendByte(reg); for(ctr=0; ctr<len; ctr++) SPI_SendByte(*pBuf++); Pin_CSN(1); delay_us(100); return s; } //功能: 用于读数据,reg:为寄存器地址,pBuf:为待读出数据地址,uchars:读出数据的个数 u8 SPI_ReadBuf(u8 reg, u8 *pBuf, u8 len) { u8 s,ctr; Pin_CSN(0); delay_us(100); s=SPI_SendByte(reg); for(ctr=0; ctr<len; ctr++) pBuf[ctr]=SPI_SendByte(NOP); Pin_CSN(1); delay_us(100); return s; } //功能:NRF24L01 读写寄存器函数 u8 SPI_RWReg(u8 reg, u8 value) { u8 status = 0x00; Pin_CSN(0); delay_us(100); status = SPI_SendByte(reg); //发送寄存器地址, SPI_SendByte(value);//发送寄存器值 delay_us(100); Pin_CSN(1); return (status); } //功能:NRF24L01 读写寄存器函数 u8 Get_Status() { return SPI_RWReg(0x07,0xFF); } //检测是否存在 u8 nrf24l0_check(void) { u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5}; u8 i; SPI_WriteBuf(0x20+0x10,buf,5); SPI_ReadBuf(0x10,buf,5); for(i=0; i<5; i++)if(buf[i]!=0xA5)break; if(i!=5)return 1; return 0; }
3.stm32f0xx_it.c 中断内的函数
#include "delay.h" #include "stm32f0xx.h" #include "bsp_nrf0241.h" //按键中断 void EXTI0_1_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0)!= RESET) { delay_ms(20); if(Get_KEY0()!= RESET)//消除按键抖动 { u8 tx_buf[32]={0x11};//用户测试的数据 send_data(tx_buf,32);//发送 } EXTI_ClearITPendingBit(EXTI_Line0);//清除中断标志 } } //nrf2401收发中断 void EXTI2_3_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line3)!= RESET) { u8 gstatus = Get_Status();//获取状态 //发送 if((gstatus & 0x20) != 0) { //发送成功 LED灯闪烁一次 Pin_LED0(0); delay_ms(1000); Pin_LED0(1); } //发送达到最大次数 if((gstatus & 0x10) != 0) { //发送最大次数 Pin_LED0(0); delay_ms(1000); Pin_LED0(1); } //接收 if((gstatus & 0x40) != 0) { //接收到数据 u8 tx_bufr[32]; Get_Data(tx_bufr,32); if(tx_bufr[0] == 0x11)//和发送数据进行对比 { Pin_LED0(0); delay_ms(1000); Pin_LED0(1); } } //清除中断标志 EXTI_ClearITPendingBit(EXTI_Line3); } ClearStatus();//清除中断 receive_data();//继续接收 }
4.delay.h 延时函数(摘自网络)
#include "stm32f0xx.h" #include "delay.h" static u8 fac_us=0;//us延时倍乘数 static u16 fac_ms=0;//ms延时倍乘数 //初始化延迟函数 //SYSTICK的时钟固定为HCLK时钟的1/8 //SYSCLK:系统时钟 void delay_init(u8 SYSCLK) { SysTick->CTRL&=0xfffffffb;//bit2清空,选择外部时钟 HCLK/8 fac_us=SYSCLK/8; fac_ms=(u16)fac_us*1000; } //延时nms //注意nms的范围 //SysTick->LOAD为24位寄存器,所以,最大延时为: //nms<=0xffffff*8*1000/SYSCLK //SYSCLK单位为Hz,nms单位为ms //对72M条件下,nms<=1864 void delay_ms(u16 nms) { u32 temp; SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit) SysTick->VAL =0x00; //清空计数器 SysTick->CTRL=0x01 ; //开始倒数 do { temp=SysTick->CTRL; } while(temp&0x01&&!(temp&(1<<16)));//等待时间到达 SysTick->CTRL=0x00; //关闭计数器 SysTick->VAL =0X00; //清空计数器 } //延时nus //nus为要延时的us数. void delay_us(u32 nus) { u32 temp; SysTick->LOAD=nus*fac_us; SysTick->VAL=0x00; SysTick->CTRL=0x01 ; do { temp=SysTick->CTRL; } while(temp&0x01&&!(temp&(1<<16))); SysTick->CTRL=0x00; SysTick->VAL =0X00; }
5.main.c 入口测试函数
#include "stm32f0xx.h" #include "delay.h" #include "bsp_spi.h" #include "bsp_nrf0241.h" void daly(uint32_t a) { for(;a>0;a--); } int main(void) { //初始化延时函数 stm32f030为48 delay_init(48); /* *KEY0_LED0_Config();配置一个按键和一个LED灯 * PA0 按键 * PA4 LED灯(系统自带的用户LED灯 低电平点亮) * *SPI_Config();配置SPI * PA1 NSS 片选 * PA5 SCK 时钟 * PA6 MISO 主机输入从机输出 * PA7 MOSI 主机输出从机输入 * *NRF_Config();配置NRF24L01 * PA2 CE 控制收发 * PA3 IRQ 收发中断 */ KEY0_LED0_Config(); SPI_Config(); NRF_Config(); delay_us(20); if(nrf24l0_check()==0) { //存在nrf2401模块 Pin_LED0(0); delay_ms(1000); Pin_LED0(1); receive_data();//接收 } while(1){ } }
准备两块用于收发的stm32f030单片机和NRF无线模块 用杜邦线连接起来(注意 MISO 和 MOSI 要对应连接, 不要交叉连接), 有必要还要引出用于测试的key0按钮, LED是板子默认的PA4, 连接好后分别给两块板子插上烧录器烧录编译好的代码, 就可以按KEY0进行测试了, 如果另外一个单片机的LED灯亮说明成功了.
如果有不足的地方和经验欢迎在评论区交流.