小小的一个nRF24L01,折腾了好几天
真是捉急
还好,到今天简单的通信功能已经实现了,但是还不是很满意
目前实现了单向的通信,双向通信还需要进一步修改程序流程
其实nRF24L01挺耐玩的,有空再好好研究一下一对多的通信方式
不说了,上测试的代码
接收端配置
/*************************************************************
**函数名: SPI_NRF_MOD_RX()
**描述 : 配置NRF进入 接收模式
**参数 :
**返回值:
**注意 : 仅用于测试 无参无返 可改成带配置参数的模块函数
RX通道0 地址TX_ADDR=0xB3B4B5B605
************************************************************/
void SPI_NRF_MOD_RX(void)
{
u8 TX_Array[5];
u8 _TX_RX_ADDR_[5]={0xB3,0xB4,0xB5,0xB6,0x05};
SPI2_CE_LOW();//CE=0 待机模式
TX_Array[0]=0x01;//允许接收通道0
SPI_NRF_Write(SPI2,W_REGISTER+EN_RXADDR,TX_Array,1);
TX_Array[0]=0x03;//设置地址宽度 11--5字节 10--4字节 01-3字节 00--不合法
SPI_NRF_Write(SPI2,W_REGISTER+SETUP_AW,TX_Array,1);
TX_Array[0]=0x02;//射频通道 X000 0010
SPI_NRF_Write(SPI2,W_REGISTER+RF_CH,TX_Array,1);
TX_Array[0]=0x0f;//射频参数寄存器
SPI_NRF_Write(SPI2,W_REGISTER+RF_SETUP,TX_Array,1);
TX_Array[0]=0x01;//xx11 1111 0通道允许自动应答
SPI_NRF_Write(SPI2,W_REGISTER+EN_AA,TX_Array,1);
TX_Array[0]=0x20;//xx11 1111 数据通道0 32字节通信有效数据宽度
SPI_NRF_Write(SPI2,W_REGISTER+RX_PW_P0,TX_Array,1);
TX_Array[0]=0xfe;//1111 xxxx STATUS寄存器 写‘1’清除所有标志
SPI_NRF_Write(SPI2,W_REGISTER+STATUS,TX_Array,1);
SPI_NRF_Write(SPI2,W_REGISTER+TX_ADDR,_TX_RX_ADDR_,5);//写入接收发送数据的地址,这个地址是接收端收件的凭证
SPI_NRF_Write(SPI2,W_REGISTER+RX_ADDR_P0,_TX_RX_ADDR_,5);//写入接收发送数据的地址,这个地址是接收端收件的凭证
TX_Array[0]=0x0f;//接收模式 PRIM_RX=1 PWR_UP=1 允许接收终端
SPI_NRF_Write(SPI2,W_REGISTER+CONFIG,TX_Array,1);
SPI2_CE_HIGH();//CE=1 使能接收模式
delay_us(200);//CE拉高
发射端配置
/*************************************************************
**函数名: SPI_NRF_MOD_TX()
**描述 : 配置NRF进入 发送模式
**参数 :
**返回值:
**注意 : 仅用于测试 无参无返 可改成带配置参数的模块函数
************************************************************/
void SPI_NRF_MOD_TX(void)
{
u8 TX_Array[5];
u8 _TX_RX_ADDR_[5]={0xB3,0xB4,0xB5,0xB6,0x05};
SPI1_CE_LOW();//CE=0 待机模式
TX_Array[0]=0x03;//设置地址宽度 11--5字节 10--4字节 01-3字节 00--不合法
SPI_NRF_Write(SPI1,W_REGISTER+SETUP_AW,TX_Array,1);
TX_Array[0]=0x1a;//建立自动重发
SPI_NRF_Write(SPI1,W_REGISTER+SETUP_RETR,TX_Array,1);
TX_Array[0]=0x02;//射频通道 X000 0010
SPI_NRF_Write(SPI1,W_REGISTER+RF_CH,TX_Array,1);
TX_Array[0]=0x0f;//射频参数寄存器
SPI_NRF_Write(SPI1,W_REGISTER+RF_SETUP,TX_Array,1);
TX_Array[0]=0x01;//xx11 1111 接收通道0允许
SPI_NRF_Write(SPI1,W_REGISTER+EN_RXADDR,TX_Array,1);
TX_Array[0]=0x01;//xx11 1111 通道0允许自动应答
SPI_NRF_Write(SPI1,W_REGISTER+EN_AA,TX_Array,1);
TX_Array[0]=0x20;//xx11 1111 32字节数据长度
SPI_NRF_Write(SPI1,W_REGISTER+RX_PW_P0,TX_Array,1);
SPI_NRF_Write(SPI1,W_REGISTER+TX_ADDR,_TX_RX_ADDR_,5);//写入接收发送数据的地址,这个地址是接收端收件的凭证
SPI_NRF_Write(SPI1,W_REGISTER+RX_ADDR_P0,_TX_RX_ADDR_,5);//写入接收发送数据的地址,这个地址是接收端收件的凭证
TX_Array[0]=0x0e;//中断全开 发送模式 PRIM_RX=0 PWR_UP=1
SPI_NRF_Write(SPI1,W_REGISTER+CONFIG,TX_Array,1);
TX_Array[0]=0xfe;//1111 xxxx STATUS寄存器 写‘1’清除所有标志
SPI_NRF_Write(SPI1,W_REGISTER+STATUS,TX_Array,1);
SPI1_CE_HIGH();//CE=1 使能发射模式
delay_us(200);//CE拉高需要一定的延时才能进行发送 延时之后 即可通过SPI接口发送TX_PLD
}
我这里没有专门的初始化程序
其实可以将两端相同的配置整合一下,成为一个通用初始化的子函数
看看接收数据包的函数
/*************************************************************
**函数名: SPI_NRF_RX_DATAS()
**描述 : 配置NRF发送 数据
**参数 : RBuff[ByteNUM] ByteNUM
**返回值: ErrorStatus
**注意 :
************************************************************/
ErrorStatus SPI_NRF_RX_DATAS(u8* RBuff)
{
ErrorStatus RX_Status=SUCCESS,flag;
u8 Status[1],fifosta[1],sta[1];
SPI2_CE_HIGH();
delay_ms(1);
/* SPI_NRF_Read(SPI2,R_REGISTER+CD,sta,1);
printf("CD is 0x%02X\r\n",sta[0]);
SPI_NRF_Read(SPI2,R_REGISTER+STATUS,Status,1);//??Status
SPI_NRF_Read(SPI2,R_REGISTER+FIFO_STATUS,fifosta,1);
printf("STATUS is 0x%02X\r\n",Status[0]);
printf("FIFO_STATUS is 0x%02X\r\n",fifosta[0]);*/
//while(NRF_Read_IRQ()!=0);//中断产生时,IRQ引脚低电平
/* SPI_NRF_Read(SPI2,R_REGISTER+STATUS,Status,1);//??Status
SPI_NRF_Read(SPI2,R_REGISTER+FIFO_STATUS,fifosta,1);
printf("STATUS is 0x%02x\r\n",Status[0]);
printf("FIFO_STATUS is 0x%02x\r\n",fifosta[0]);
LED_On;
printf("RX_OK..............................................\r\n");
SPI2_CE_LOW();//拉低待机,才能操作寄存器
SPI_NRF_Read(SPI2,R_REGISTER+STATUS,Status,1);*///读取Status
switch(Status[0]&0x0e)
{
case 0x0e: RX_Status=ERROR; break; //RX_FIFO 空
default : printf("Rx Success!\r\n");//RX_FIFO非空
break;
/*
case 0x00: break; //通道0
case 0x02: break; //通道1
case 0x04: break; //通道2
case 0x06: break; //通道3
case 0x08: break; //通道4
case 0x0A: break; //通道5*/
}
SPI_NRF_Read(SPI2,R_RX_PAYLOAD,RBuff,32);//读RX_FIFO
SPI_NRF_Write(SPI2,W_REGISTER+STATUS,Status,1);//处理状态寄存器标志
return RX_Status;
}
里面有不少debug时用到的语句,可忽略
接收主函数
if(NRF_Read_IRQ()==0)
{
SPI_NRF_RX_DATAS(Rx_buf);
printf("RX_Data is ");
for (i = 0;i<32;i++)
{
USART_SendData(USART1,Rx_buf[i]);
delay_ms(1);
}
delay_ms(1);
printf("\r\n");
SPI_NRF_MOD_RX();
}
必须每次接收结束后配置一次RX模式,要不然就不能接收到下一个数据包
按数据手册理解,设置成完成接收后,不更改配置寄存器的前提下,只有CE的电平才会影响模式状态
一直到现在都不解
看看发送端的发送数据包函数
/*************************************************************
**函数名: SPI_NRF_TX_DATAS()
**描述 : 配置NRF发送 数据
**参数 : TBuff[ByteNUM] ByteNUM
**返回值: ErrorStatus
**注意 :
************************************************************/
ErrorStatus SPI_NRF_TX_DATAS(u8* TBuff,u8 ByteNUM)
{
u8 Status[1],fifosta[1];
do{
SPI1_CE_LOW();//拉低待机
SPI_NRF_Write(SPI1,W_TX_PAYLOAD,TBuff,ByteNUM);//发送TBuff数组
SPI1_CE_HIGH();//拉低待机
delay_us(700);
}
while(NRF_Read_IRQ()!=0);//中断产生时,IRQ引脚低电平
printf("Tx data is %s\r\n",TBuff);
/* SPI_NRF_Read(SPI1,R_REGISTER+STATUS,Status,1);//读取Status
SPI_NRF_Read(SPI1,R_REGISTER+FIFO_STATUS,fifosta,1);
printf("STATUS is 0x%02x\r\n",Status[0]);
printf("FIFO_STATUS is 0x%02x\r\n",fifosta[0]);*/
SPI_NRF_Write(SPI1, FLUSH_TX,TBuff,0);
SPI_NRF_Read(SPI1,R_REGISTER+STATUS,Status,1);
SPI1_CE_LOW();//拉低待机
if(Status[0]&0x10)
{
Status[0]&=0x10;
SPI_NRF_Write(SPI1,W_REGISTER+STATUS,Status,1);//
printf("Tx Error!\r\n");//重发超时 发送失败
return ERROR;
}
else
{
Status[0]&=0x20;
SPI_NRF_Write(SPI1,W_REGISTER+STATUS,Status,1);//
printf("Tx Success\r\n");//发送成功
return SUCCESS;
}
}
接收端需小心调试
有点小心得分享,有兴趣的童鞋可以看看:http://forum.eepw.com.cn/thread/249735/2#12
上视频伺候
发送端从串口助手获取32字节数据,发送给接收端,接收端再通过USART回传PC显示。
That's ALL
晚上玩了一下MPU6050
六轴传感器,集成温度采集
其实这个温度采集
厂家的原意是不是进行温度补偿?
等综合测试的时候,拿这个温度来补偿一下采集到的姿态数据
以前玩加速度计总是读出原始值,再进行运算求得各个分量上的加速度值
谷歌了一下,据说MPU6050内部就集成了DMP
也就是说,DMP模块处理之后,输出直接就是姿态的数据了
这下可省了不少事情
厂家内置的DMP算法,应该会比俺这种新手再进行姿态测量计算要强一些吧
不过要激活MPU6050的DMP,还得先刷新它的固件
其实也不算固件,也就是很多的一段驱动代码,每次上电都需要写进去一次
直接上DMP的驱动程序
回复可见
烈火飞行器IIC使用的硬件IIC2
上相关代码
#include "STM32_I2C.h" #define SCL_H GPIOB->BSRR = GPIO_Pin_10 /* GPIO_SetBits(GPIOB , GPIO_Pin_10) */ #define SCL_L GPIOB->BRR = GPIO_Pin_10 /* GPIO_ResetBits(GPIOB , GPIO_Pin_10) */ #define SDA_H GPIOB->BSRR = GPIO_Pin_11 /* GPIO_SetBits(GPIOB , GPIO_Pin_11) */ #define SDA_L GPIOB->BRR = GPIO_Pin_11 /* GPIO_ResetBits(GPIOB , GPIO_Pin_11) */ #define SCL_read GPIOB->IDR & GPIO_Pin_10 /* GPIO_ReadInputDataBit(GPIOB , GPIO_Pin_10) */ #define SDA_read GPIOB->IDR & GPIO_Pin_11 /* GPIO_ReadInputDataBit(GPIOB , GPIO_Pin_11) */ static void I2C_delay(void) { volatile int i = 7; while (i) i--; } static bool I2C_Start(void) { SDA_H; SCL_H; I2C_delay(); if (!SDA_read) return false; SDA_L; I2C_delay(); if (SDA_read) return false; SDA_L; I2C_delay(); return true; } static void I2C_Stop(void) { SCL_L; I2C_delay(); SDA_L; I2C_delay(); SCL_H; I2C_delay(); SDA_H; I2C_delay(); } static void I2C_Ack(void) { SCL_L; I2C_delay(); SDA_L; I2C_delay(); SCL_H; I2C_delay(); SCL_L; I2C_delay(); } static void I2C_NoAck(void) { SCL_L; I2C_delay(); SDA_H; I2C_delay(); SCL_H; I2C_delay(); SCL_L; I2C_delay(); } static bool I2C_WaitAck(void) { SCL_L; I2C_delay(); SDA_H; I2C_delay(); SCL_H; I2C_delay(); if (SDA_read) { SCL_L; return false; } SCL_L; return true; } static void I2C_SendByte(uint8_t byte) { uint8_t i = 8; while (i--) { SCL_L; I2C_delay(); if (byte & 0x80) SDA_H; else SDA_L; byte <<= 1; I2C_delay(); SCL_H; I2C_delay(); } SCL_L; } static uint8_t I2C_ReceiveByte(void) { uint8_t i = 8; uint8_t byte = 0; SDA_H; while (i--) { byte <<= 1; SCL_L; I2C_delay(); SCL_H; I2C_delay(); if (SDA_read) { byte |= 0x01; } } SCL_L; return byte; } void i2cInit(void) { GPIO_InitTypeDef gpio; //已更改 gpio.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; gpio.GPIO_Speed = GPIO_Speed_2MHz; gpio.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(GPIOB, &gpio); } bool i2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t len, uint8_t * data) { int i; if (!I2C_Start()) return false; I2C_SendByte(addr << 1 | I2C_Direction_Transmitter); if (!I2C_WaitAck()) { I2C_Stop(); return false; } I2C_SendByte(reg); I2C_WaitAck(); for (i = 0; i < len; i++) { I2C_SendByte(data[i]); if (!I2C_WaitAck()) { I2C_Stop(); return false; } } I2C_Stop(); return true; } ///////////////////////////////////////////////////////////////////////////////// int8_t i2cwrite(uint8_t addr, uint8_t reg, uint8_t len, uint8_t * data) { if(i2cWriteBuffer(addr,reg,len,data)) { return TRUE; } else { return FALSE; } //return FALSE; } int8_t i2cread(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf) { if(i2cRead(addr,reg,len,buf)) { return TRUE; } else { return FALSE; } //return FALSE; } ////////////////////////////////////////////////////////////////////////////////// bool i2cWrite(uint8_t addr, uint8_t reg, uint8_t data) { if (!I2C_Start()) return false; I2C_SendByte(addr << 1 | I2C_Direction_Transmitter); if (!I2C_WaitAck()) { I2C_Stop(); return false; } I2C_SendByte(reg); I2C_WaitAck(); I2C_SendByte(data); I2C_WaitAck(); I2C_Stop(); return true; } bool i2cRead(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf) { if (!I2C_Start()) return false; I2C_SendByte(addr << 1 | I2C_Direction_Transmitter); if (!I2C_WaitAck()) { I2C_Stop(); return false; } I2C_SendByte(reg); I2C_WaitAck(); I2C_Start(); I2C_SendByte(addr << 1 | I2C_Direction_Receiver); I2C_WaitAck(); while (len) { *buf = I2C_ReceiveByte(); if (len == 1) I2C_NoAck(); else I2C_Ack(); buf++; len--; } I2C_Stop(); return true; } uint16_t i2cGetErrorCounter(void) { // TODO maybe fix this, but since this is test code, doesn't matter. return 0; }
还好这次IIC调试比较顺利
记得以前在硬件IIC上面栽过不少跟头,汗啊......
来瞧瞧主函数
#define q30 1073741824.0f
float q0=1.0f,q1=0.0f,q2=0.0f,q3=0.0f;
float Pitch,Roll,Yaw;
int main(void)
{
/*!< At this stage the microcontroller clock setting is already configured,
this is done through SystemInit() function which is called from startup
file (startup_stm32f10x_xx.s) before to branch to application main.
To reconfigure the default setting of SystemInit() function, refer to
system_stm32f10x.c file
*/
int result;
/* Configure all unused GPIO port pins in Analog Input mode (floating input
trigger OFF), this will reduce the power consumption and increase the device
immunity against EMI/EMC *************************************************/
Clock_Enable();
GPIO_Configuration();
USART_Configuration();
i2cInit();//IIC总线的初始化,尼玛纠结了这么长时间
result = mpu_init();
if(!result)
{
//mpu_init();
PrintChar("mpu initialization complete......\n ");
//mpu_set_sensor
if(!mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL))
{
PrintChar("mpu_set_sensor complete ......\n");
}
else
{
PrintChar("mpu_set_sensor come across error ......\n");
}
//mpu_configure_fifo
if(!mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL))
{
PrintChar("mpu_configure_fifo complete ......\n");
}
else
{
PrintChar("mpu_configure_fifo come across error ......\n");
}
//mpu_set_sample_rate
if(!mpu_set_sample_rate(DEFAULT_MPU_HZ))
{
PrintChar("mpu_set_sample_rate complete ......\n");
}
else
{
PrintChar("mpu_set_sample_rate error ......\n");
}
//dmp_load_motion_driver_firmvare
if(!dmp_load_motion_driver_firmware())
{
PrintChar("dmp_load_motion_driver_firmware complete ......\n");
}
else
{
PrintChar("dmp_load_motion_driver_firmware come across error ......\n");
}
//dmp_set_orientation
if(!dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation)))
{
PrintChar("dmp_set_orientation complete ......\n");
}
else
{
PrintChar("dmp_set_orientation come across error ......\n");
}
//dmp_enable_feature
if(!dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_TAP |
DMP_FEATURE_ANDROID_ORIENT | DMP_FEATURE_SEND_RAW_ACCEL | DMP_FEATURE_SEND_CAL_GYRO |
DMP_FEATURE_GYRO_CAL))
{
PrintChar("dmp_enable_feature complete ......\n");
}
else
{
PrintChar("dmp_enable_feature come across error ......\n");
}
//dmp_set_fifo_rate
if(!dmp_set_fifo_rate(DEFAULT_MPU_HZ))
{
PrintChar("dmp_set_fifo_rate complete ......\n");
}
else
{
PrintChar("dmp_set_fifo_rate come across error ......\n");
}
run_self_test();
if(!mpu_set_dmp_state(1))
{
PrintChar("mpu_set_dmp_state complete ......\n");
}
else
{
PrintChar("mpu_set_dmp_state come across error ......\n");
}
}
while(1)
{
unsigned long sensor_timestamp;
short gyro[3], accel[3], sensors;
unsigned char more;
long quat[4];
//float Yaw,Roll,Pitch;
dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors,
&more);
/* Gyro and accel data are written to the FIFO by the DMP in chip
* frame and hardware units. This behavior is convenient because it
* keeps the gyro and accel outputs of dmp_read_fifo and
* mpu_read_fifo consistent.
*/
/* if (sensors & INV_XYZ_GYRO )
send_packet(PACKET_TYPE_GYRO, gyro);
if (sensors & INV_XYZ_ACCEL)
send_packet(PACKET_TYPE_ACCEL, accel); */
/* Unlike gyro and accel, quaternions are written to the FIFO in
* the body frame, q30. The orientation is set by the scalar passed
* to dmp_set_orientation during initialization.
*/
if (sensors & INV_WXYZ_QUAT )
{
PrintChar(" In Calculating quaternion steps.....\r\n");
q0=quat[0] / q30;
q1=quat[1] / q30;
q2=quat[2] / q30;
q3=quat[3] / q30;
Pitch = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3; // pitch
Roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3; // roll
Yaw = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) * 57.3;
delay_ms(1);
printf("Pirch = %10f\r\n",Pitch);
delay_ms(1);
printf("Roll = %10f\r\n",Roll);
delay_ms(1);
printf("Yaw = %10f\r\n",Yaw);
delay_ms(1);
delay_ms(300);
}
if(sensors & INV_XYZ_GYRO)
{}
if(sensors & INV_XYZ_ACCEL)
{}
//send_packet(PACKET_TYPE_QUAT, quat);
}
}
这里头要特别注意MUP6050初始化和启用DMP的流程
必须要完成相关操作才能够正常使用DMP。
特别值得的是,定义了一个算子
#定义Q30 1073741824.0f
为什么是这么多,我也不知道,原厂的驱动里面就是这货。
的printf函数真心好用,浮点变量瞬间输出
主函数里边就能够输出偏航,滚动和间距等姿态数据了
没有定标,不知道最终输出的数据是不是准确
上视频:
回复
| 有奖活动 | |
|---|---|
| 硬核工程师专属补给计划——填盲盒 | |
| “我踩过的那些坑”主题活动——第002期 | |
| 【EEPW电子工程师创研计划】技术变现通道已开启~ | |
| 发原创文章 【每月瓜分千元赏金 凭实力攒钱买好礼~】 | |
| 【EEPW在线】E起听工程师的声音! | |
| 高校联络员开始招募啦!有惊喜!! | |
| 【工程师专属福利】每天30秒,积分轻松拿!EEPW宠粉打卡计划启动! | |
| 送您一块开发板,2025年“我要开发板活动”又开始了! | |
我要赚赏金
