單片機這塊純屬個人業余愛好, 有很多不足的地方還請大家多多指教, 代碼中有些命名不規范的地方還請大家多多包涵.
本文只實現無線模塊的簡單的點亮(能收發一個字節數據), 一直想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燈亮說明成功了.
如果有不足的地方和經驗歡迎在評論區交流.
