在这一节中我们将完成NRF24L01模块的驱动程序。NRF2401是一个无线通信模块,这里为了简化我们的电路与程序并没有直接采用把芯片集成到我们的电路当中,而是使用了一个现成的模块来完成的这一功能。我们采用的NRF24L01是经过一个信号放大改造版本的通信模块,如下:
关于这款无线通信模块读者可以在其它网站上找到很多资料,我们不对它做过多的介绍,只是给出示例代码,讲解如何使用它。它使用的是SPI接口,这在我们设计电路之初就已经完成了,因此我们需要在程序中配置SPI接口的相关程序:
void MX_SPI1_Init(void)
{
/* SPI1 parameter configuration*/
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
此外,在stm32f1xx_hal_msp.c文件中也有关于SPI的配置内容:
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
if (hspi->Instance == SPI1)
{
/* Peripheral clock enable */
__HAL_RCC_SPI1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
这些代码也同样是由CubeIDE工具自动生成的,不过我们需要注意的是:我们使用的是SPI1接口,使用的是PA5、PA6、PA7作为SPI1_SCK、SPI1_MISO、SPI1_MOSI。除此之外我们还需要3个脚来做一些NRF24L01的特殊功能操作:PB0作为模块使能脚;PB1作为SPI1的片选脚,PB12作为模块的中断触发脚。因此,我们需要首先编写几个函数来处理模块的使能、片选和中断:
#define RF24L01_SET_CE_HIGH() spi_nrf_ce_high()
#define RF24L01_SET_CE_LOW() spi_nrf_ce_low()
#define RF24L01_SET_CS_HIGH() spi_nrf_cs_high()
#define RF24L01_SET_CS_LOW() spi_nrf_cs_low()
#define RF24L01_GET_IRQ_STATUS() spi_nrf_irq_st()
void spi_nrf_ce_high()
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
}
void spi_nrf_ce_low()
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
}
void spi_nrf_cs_high()
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
}
void spi_nrf_cs_low()
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
uint8_t spi_nrf_irq_st()
{
return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12);
}
与上一节中MPU6050一样,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 MAX_TX 0x10 //达到最大发送次数中断
#define TX_OK 0x20 // TX发送完成中断
#define RX_OK 0x40 //接收到数据中断
#define OBSERVE_TX 0x08 //发送检测寄存器,bit7~4:数据包丢失计数器;bit3~0:重发计数器
#define CD 0x09 //载波检测寄存器,bit0:载波检测
关于通信模块的初始化配置和数据通信部分,就是对其寄存器的读写操作,我们这里给出它的一些配置:
void RF24L01_Init(void)
{
uint8_t addr[5] = {INIT_ADDR};
RF24L01_SET_CE_HIGH();
NRF24L01_Clear_IRQ_Flag(IRQ_ALL);
#if DYNAMIC_PACKET == 1
NRF24L01_Write_Reg(DYNPD, (1 << 0)); //使能通道1动态数据长度
NRF24L01_Write_Reg(FEATRUE, 0x07);
NRF24L01_Read_Reg(DYNPD);
NRF24L01_Read_Reg(FEATRUE);
#elif DYNAMIC_PACKET == 0
NRF24L01_Write_Reg(RX_PW_P0, FIXED_PACKET_LEN); //固定数据长度
NRF24L01_Write_Reg(RX_PW_P1, FIXED_PACKET_LEN); //固定数据长度
NRF24L01_Write_Reg(RX_PW_P2, FIXED_PACKET_LEN); //固定数据长度
NRF24L01_Write_Reg(RX_PW_P3, FIXED_PACKET_LEN); //固定数据长度
NRF24L01_Write_Reg(RX_PW_P4, FIXED_PACKET_LEN); //固定数据长度
NRF24L01_Write_Reg(RX_PW_P5, FIXED_PACKET_LEN); //固定数据长度
#endif //DYNAMIC_PACKET
NRF24L01_Write_Reg(CONFIG, 1); //开启设备
NRF24L01_Write_Reg(EN_AA, (1 << ENAA_P0)); //通道0自动应答
NRF24L01_Write_Reg(EN_RXADDR, (1 << ERX_P0)); //通道0接收
NRF24L01_Write_Reg(SETUP_AW, AW_5BYTES); //地址宽度 5个字节
NRF24L01_Write_Reg(SETUP_RETR, ARD_4000US | (REPEAT_CNT & 0x0F)); //重复等待时间 250us
NRF24L01_Write_Reg(RF_CH, 60); //初始化通道
NRF24L01_Write_Reg(RF_SETUP, 0x26);
NRF24L01_Set_TxAddr(&addr[0], 5); //设置TX地址
NRF24L01_Set_RxAddr(0, &addr[0], 5); //设置RX地址
}
void RF24L01_Set_Mode(nRf24l01ModeType Mode)
{
uint8_t controlreg = 0;
controlreg = NRF24L01_Read_Reg(CONFIG);
if (Mode == MODE_TX)
{
controlreg &= ~(1 << PRIM_RX);
}
else
{
if (Mode == MODE_RX)
{
controlreg |= (1 << PRIM_RX);
}
}
NRF24L01_Write_Reg(CONFIG, controlreg);
}
需要说明的是,这里我们采用了RF24L01_Set_Mode()函数来对NRF24L01模块进行配置,使其可以在接收模式或发送模式下工作。实际上我们使用遥控器电路上的NRF24L01发送信号,并通过小四轴上的NRF24L01来接收控制信号,这样我们就可以完成遥控器来控制小四轴的功能了。最后我们编写一个nrf2401_task.c文件,来创建一个线程读取遥控器的信号。遥控器程序部分我们将在后续章节中来讲述。
void* nrf2401_pthread(void* arg)
{
protocol_init();
sem_init(&sem_sig, 0);
uint16_t ctl[5] = { 0 };
uint8_t rx_buff[32] = { 0 };
uint8_t rx_len = 0;
NRF24L01_check();
RF24L01_Init();
RF24LL01_Write_Hopping_Point(64);
NRF24L01_Set_Power(POWER_0DBM);
NRF24L01_Set_Speed(SPEED_250K);
float filter = 1.0f; //不使用滤波(在发射端已经滤波了)
float ctl_yaw_last = 0;
float ctl_thro_last = 0;
float ctl_roll_last = 0;
float ctl_pitch_last = 0;
RF24L01_Set_Mode(MODE_RX);
while (1)
{
sem_wait(&sem_sig);
rx_len = NRF24L01_Read_Reg(R_RX_PL_WID); //读取接收到的数据个数
NRF24L01_Read_Buf(RD_RX_PLOAD, rx_buff, rx_len); //接收到数据
if (rx_len > 0)
{
// for (int i = 0; i < rx_len; i++)
// {
// printf("%02x ", rx_buff[i]);
// }
// printf("\n");
protocol_append(rx_buff, rx_len);
}
NRF24L01_Write_Reg(FLUSH_RX, 0xff); //清除RX FIFO
NRF24L01_Clear_IRQ_Flag(IRQ_ALL);
RF24L01_Set_Mode(MODE_RX);
int ret = protocol_parse(ctl);
if (ret == 0)
{
tk_recv = HAL_GetTick();
ctl_switch(ctl);
float roll = ((float)(ctl[0] - CTL_PWM_MIN)) / CTL_PWM_SCALE;
float pitch = ((float)(ctl[1] - CTL_PWM_MIN)) / CTL_PWM_SCALE;
float yaw = ((float)(ctl[2] - CTL_PWM_MIN)) / CTL_PWM_SCALE;
float thro = ((float)(ctl[3] - CTL_PWM_MIN)) / CTL_PWM_SCALE;
pitch = 1.0f - pitch * 2.0f;
roll = 1.0f - roll * 2.0f;
yaw = 1.0f - yaw * 2.0f;
if (fabs(pitch) < 0.05)
{
pitch = 0;
}
if (fabs(roll) < 0.05)
{
roll = 0;
}
if (fabs(yaw) < 0.05)
{
yaw = 0;
}
ctl_thro = thro * filter + ctl_thro_last * (1.0f - filter);
ctl_roll = roll * filter + ctl_roll_last * (1.0f - filter);
ctl_pitch = pitch * filter + ctl_pitch_last * (1.0f - filter);
ctl_yaw = yaw * filter + ctl_yaw_last * (1.0f - filter);
ctl_thro_last = ctl_thro;
ctl_roll_last = ctl_roll;
ctl_pitch_last = ctl_pitch;
ctl_yaw_last = ctl_yaw;
}
// sleep_ticks(1);
}
return NULL;
}
这里我们就读取到了遥控器发送的控制信号,最终我们会得到油门(ctl_thro)、滚转(ctl_roll)、俯仰(ctl_pitch)、航向(ctl_yaw)和一些其它开关控制信号。
有一点需要补充说明,如果我们采用主动轮询方式来读取NRF24L01的数据,将会占用处理器的大量时间,这是非常不高效的,因此我们采用了中断方式来读取数据,当有新的数据到来时,NRF24L01将触发数据中断,我们在中断中post一个信号量,而在读取线程中则等待此信号量。这样就可以在最大程度上降低处理的工作时间。中断内容如下:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_12)
{
sem_post(&sem_sig);
}
}
最后我们给出完整的程序代码:下载地址
Copyright © 2015-2023 问渠网 辽ICP备15013245号