目录
前言
环境:
芯片:STM32F103C8T6
Keil:V5.24.2.0
模块:NRF24L01
一、IO口初始化
根据:NRF24L01模块STM32通信-调试前言-CSDN博客
需要初始6个IO:4个输出,2个输入
我初始对应的IO口如下;
//输出
CSN ->PA3
CE ->PA4
MOSI ->PA7
SCK ->PA5
//输入
IRQ ->PB1
MISO ->PA6
IO口初始化,包含了OLED和其他的初始化代码.
void Gpio_Init(void)
{
/* 输出
CSN ->PA3//
CE ->PA4
MOSI ->PA7
SCK ->PA5
输入
IRQ ->PB1
MISO ->PA6
*/
//output
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//input
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
二、模拟SPI的基础代码
1.一些代码的宏定义
#define SPI_CS_PROT GPIOA //CS接线引脚通道, CSN
#define SPI_CS_PIN GPIO_Pin_3
#define SPI_DO_PROT GPIOA //D0接线引脚通道, MOSI
#define SPI_DO_PIN GPIO_Pin_7
#define SPI_SLK_PROT GPIOA //CL接线引脚通道, SCK
#define SPI_SLK_PIN GPIO_Pin_5
#define SPI_DI_PROT GPIOA //DI接线引脚通道, MISO
#define SPI_DI_PIN GPIO_Pin_6
#define SPI_IRQ_PROT GPIOB //DI接线引脚通道, MISO
#define SPI_IRQ_PIN GPIO_Pin_1
#define MYSPI_W_CS(x) GPIO_WriteBit(SPI_CS_PROT,SPI_CS_PIN,(BitAction)(x))//对CS线进行操作
#define MYSPI_W_DI(x) GPIO_WriteBit(SPI_DI_PROT,SPI_DI_PIN,(BitAction)(x))//对DI线进行操作
#define MYSPI_W_DO(x) GPIO_WriteBit(SPI_DO_PROT,SPI_DO_PIN,(BitAction)(x))//对DO线进行操作
#define MYSPI_W_SLK(x) GPIO_WriteBit(SPI_SLK_PROT,SPI_SLK_PIN,(BitAction)(x))//对SLK线进行操作
#define NRF24L01_CE(x) GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)(x))//对CE线进行操作
2.起始信号
void MySPI_Start(void)
{
MYSPI_W_CS(0); //拉低为开始信号
}
void MySPI_Stop(void)
{
MYSPI_W_CS(1); //拉高为结束信号
}
3.CS,SCK,MOSI操作
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, SPI_CS_PIN, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, SPI_SLK_PIN, (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, SPI_DO_PIN, (BitAction)BitValue);
}
4.MISO,IRQ操作
uint8_t MySPI_Read_MISO(void)
{
return GPIO_ReadInputDataBit(SPI_DI_PROT, SPI_DI_PIN);
}
uint8_t MySPI_Read_IRQ(void)
{
return GPIO_ReadInputDataBit(SPI_IRQ_PROT, SPI_IRQ_PIN);
}
三.中间层代码
1.字节的输入和读取
uint8_t MySPI_SwapByte(uint8_t ByteSend) //字节读取和交换
{
uint8_t i,ByteReceive = 0x00;
for(i = 0;i < 8;i ++)
{
MySPI_W_MOSI(ByteSend & (0x80 >> i));
MySPI_W_SCK(1);
if (MySPI_Read_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}
关于这段代码更详细的解说可以观看江科大的视频SPI部分.
2.写操作
uint8_t NRF24l01_write_buf(uint8_t reg, uint8_t *pbuf, uint8_t len)
{
uint8_t status, i;
MySPI_Start(); /* 使能SPI传输 */
status = MySPI_SwapByte(reg); /* 发送寄存器值(位置),并读取状态值 */
for (i = 0; i < len; i++)
{
MySPI_SwapByte(*pbuf++); /* 写入数据 */
}
MySPI_Stop(); /* 关闭SPI传输 */
return status; /* 返回读到的状态值 */
}
3.读操作
uint8_t NRF24l01_read_buf(uint8_t reg, uint8_t *pbuf, uint8_t len)
{
uint8_t status, i;
MySPI_Start(); /* 使能SPI传输 */
status = MySPI_SwapByte(reg); /* 发送寄存器值(位置),并读取状态值 */
for (i = 0; i < len; i++)
{
pbuf[i] = MySPI_SwapByte(0X55); /* 读出数据 */
}
return status; /* 返回读到的状态值 */
}
四.应用层代码
1.24L01的检测
/**
* @brief 检测24L01是否存在
* @param 无
* @retval 0, 成功; 1, 失败;
*/
uint8_t NRF24l01_check(void)
{
uint8_t buf[5] = {0XA5, 0XA5, 0XA5, 0XA5, 0XA5};
uint8_t i;
NRF24l01_write_buf(NRF_WRITE_REG + TX_ADDR, buf, 5); /* 写入5个字节的地址. */
NRF24l01_read_buf(TX_ADDR, buf, 5); /* 读出写入的地址 */
for (i = 0; i < 5; i++)
{
if (buf[i] != 0XA5) break;
}
if (i != 5) return 1; /* 检测24L01错误 */
return 0; /* 检测到24L01 */
}
关于其中的一些宏定义,代码放在本文末.
2.在main函数进行简单验证
void MY24L01_Init(void)//对模块和通信线的前期操作
{
NRF24L01_CE(0);
MYSPI_W_CS(1);
MYSPI_W_SLK(0);
}
while(1)
{
while (NRF24l01_check()) /* 检查NRF24L01是否在线 */
{
OLED_ShowString(16, 18, "NRF24l01 NGNGNG", OLED_6X8);
OLED_Update();
}
OLED_ShowString(32, 18, "GOOD", OLED_6X8);
OLED_Update();
}
可以使用OLED进行显示,当然因为结果只有0或1,所以也可以采用其他方式进行验证,如LED的亮灭
如果代码正确则可以进行后面的代码书写.
3.24L01宏定义的代码
/******************************************************************************************/
/* NRF24L01寄存器操作命令 */
#define NRF_READ_REG 0x00 /* 读配置寄存器,低5位为寄存器地址 */
#define NRF_WRITE_REG 0x20 /* 写配置寄存器,低5位为寄存器地址 */
#define RD_RX_PLOAD 0x61 /* 读RX有效数据,1~32字节 */
#define WR_TX_PLOAD 0xA0 /* 写TX有效数据,1~32字节 */
#define FLUSH_TX 0xE1 /* 清除TX FIFO寄存器.发射模式下用 */
#define FLUSH_RX 0xE2 /* 清除RX FIFO寄存器.接收模式下用 */
#define REUSE_TX_PL 0xE3 /* 重新使用上一包数据,CE为高,数据包被不断发送. */
#define NOP 0xFF /* 空操作,可以用来读状态寄存器 */
/* SPI(NRF24L01)寄存器地址 */
#define CONFIG 0x00 /* 配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选择;bit2:CRC模式;bit3:CRC使能; */
/* bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使能;bit6:中断RX_DR使能 */
#define EN_AA 0x01 /* 使能自动应答功能 bit0~5,对应通道0~5 */
#define EN_RXADDR 0x02 /* 接收地址允许,bit0~5,对应通道0~5 */
#define SETUP_AW 0x03 /* 设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字节; */
#define SETUP_RETR 0x04 /* 建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时 250*x+86us */
#define RF_CH 0x05 /* RF通道,bit6:0,工作通道频率; */
#define RF_SETUP 0x06 /* RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功率;bit0:低噪声放大器增益 */
#define STATUS 0x07 /* 状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,达到最多次重发 */
/* bit5:数据发送完成中断;bit6:接收数据中断; */
#define MAX_TX 0x10 /* 达到最大发送次数中断 */
#define TX_OK 0x20 /* TX发送完成中断 */
#define RX_OK 0x40 /* 接收到数据中断 */
#define OBSERVE_TX 0x08 /* 发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器 */
#define CD 0x09 /* 载波检测寄存器,bit0,载波检测; */
#define RX_ADDR_P0 0x0A /* 数据通道0接收地址,最大长度5个字节,低字节在前 */
#define RX_ADDR_P1 0x0B /* 数据通道1接收地址,最大长度5个字节,低字节在前 */
#define RX_ADDR_P2 0x0C /* 数据通道2接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等; */
#define RX_ADDR_P3 0x0D /* 数据通道3接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等; */
#define RX_ADDR_P4 0x0E /* 数据通道4接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等; */
#define RX_ADDR_P5 0x0F /* 数据通道5接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等; */
#define TX_ADDR 0x10 /* 发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址相等 */
#define RX_PW_P0 0x11 /* 接收数据通道0有效数据宽度(1~32字节),设置为0则非法 */
#define RX_PW_P1 0x12 /* 接收数据通道1有效数据宽度(1~32字节),设置为0则非法 */
#define RX_PW_P2 0x13 /* 接收数据通道2有效数据宽度(1~32字节),设置为0则非法 */
#define RX_PW_P3 0x14 /* 接收数据通道3有效数据宽度(1~32字节),设置为0则非法 */
#define RX_PW_P4 0x15 /* 接收数据通道4有效数据宽度(1~32字节),设置为0则非法 */
#define RX_PW_P5 0x16 /* 接收数据通道5有效数据宽度(1~32字节),设置为0则非法 */
#define NRF_FIFO_STATUS 0x17 /* FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留 */
/* bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环; */
/******************************************************************************************/
总结
本文代码并未涉及很多的交互动作,只是验证基础代码和IO口的连接正确.操作相对简单,方便及时对代码进行验证,查缺补漏,方便后面代码的调试