截止到3月1号的四轴飞行器代码
由 动力老男孩 发表于 2012/03/14 22:19:05应SSS_SXS建议,把目前的代码贴出来供大家分析。其中注释有点乱,有英文有中文,凑合看吧。
【特别感谢】有一位非常热心的朋友wnq送了我几套木质机架,还有一套CACM的飞控+GPS模块。所以最近正在计划重新用成品飞控搭一套四轴找找感觉。原来的四轴暂时不打算拆,正在淘宝各种散件和遥控器。另外电机轴也摔歪了,所以准备按wnq的攻略把几根轴都换一换。
下面这段代码是3月1号最后一次修改,很遗憾最近这段时间一直没啥更新。
大致思路是先计算出飞行姿态角AngleX和AngleY,然后根据角度和角速度,计算出blanceX和blanceY。
这两个参数的含义是,blanceX表示x轴两个电机的参数分配比例,例如balanceX = 1,表示x1和x2两个电机转速相同,
balanceX = 0.5,表示x1的转速是0.5,x2转速是1.5。
Y轴的概念是类似的,之所以这样处理,是期望x轴两个电机的转速和大致和y轴两个电机相同,这样飞行器不至于打转。
另外,还有个balanceXY,用于在电机转速不均匀的时候,微调x轴电机和y轴电机的转速比,这个本来是打算在电子罗盘的应用中加上的,现在暂时保持等于1。
- #include <wire.h>
- #include <eeprom.h>
- /**
- * 记录:目前较为稳定的实验参数为33, 66,挂绳貌似能垂直起降,但是真正试飞时依然侧飞
- * 试飞时,在3秒内飞到10米高,测距周期是100ms,对应的距离是33cm
- * 起飞时:
- * 1. 遥控在0~directCtrlRange时,按n*5计算转速,方便调试
- * 2. 遥控>directCtrlRange时,按n-directCtrlRange+10计算高度,油门按一定速度缓慢上升,直到离地指定的高度
- * 3. 超声波测距量程是2~180cm,不知道为什么超出量程时会读出7cm的数值,必须注意纠错
- */
- boolean connectToPc = false;
- long stableTime = 1500000;
- int directCtrlRange = 40;
- double fastStep = 1.8;
- double maxThrottle = 650;
- /* Gyro sensor */
- int GYRO_ADDR = 0x68; // 7-bit address: 1101000, change to 0x69 if SDO is High
- byte GYRO_READ_START = 0xA8; //Start register address, total 6 bytes: (XL,XH,YL,YH,ZL,ZH)
- int gyroXYZ[3];
- int gyroOffset[3];
- int gyroCS_Pin = 4;
- int gyroSDO_Pin = 5;
- /* Accessory sensor */
- int ACC_ADDR = 0x53; // 7-bit address, change to 0x53 if SDO is High
- byte ACC_READ_START = 0x32; //Start register address, total 6 bytes: (XL,XH,YL,YH,ZL,ZH)
- int accXYZ[3];
- int accOffset[3];
- /* Compass sensor */
- int COMP_ADDR = 0x3C >> 1;
- byte COMP_READ_START = 0x03;
- int compXYZ[3]; //Notice that it is XZY
- /* Motor settings */
- int motorPins[4];
- int minPWM = 900; //电机控制的PWM最低幅值为0.9ms,最高幅值为2.1ms
- int maxPWM_Delay = 1100; //电机控制的PWM范围,这里记录它们的差并留一点裕度
- int ctrlPower = 0; //从遥控器发来的控制指令
- double throttle = 0; //油门范围,从0到1200,对应从minPWM到2100
- int motorPowers[4];
- int motorCtrl[8]; //电机控制的顺序和时间,值表示从0.9ms开始,(delayMs-PinIdx)*4
- /* Ultrasonic sensor */
- int trigPin = 3;
- int echoPin = 2; //对应的中断号是0
- volatile double ultraT1 = 0;
- volatile double ultraT2 = 0;
- double distance = 0; //测量的离地距离,单位是厘米
- double preDistance = 0;
- double stablePower = 0; //正好可以离地而起的马力(假设保持这个马力,可以匀速上升或下降)
- int maxDistance = 250; //离地可测量的最大高度
- int targetDistance = 10; //目标离地高度
- int ultraCount = 0;
- /* For testing */
- int reportType = 0;
- int reportArr[3];
- int reportT[3];
- /* Read from control */
- int buffer[6]; //0,1: 0xFF, 2: Power, 3: Log Type, 4: P_Value, 5: I_Value
- int bufferIndex = 0;
- /* PID console */
- double P_Control, I_Control;
- double balanceGyro = 0.99; //用于融合陀螺仪和加速度计的角度
- double balanceAcc = 0.9; // 加速度传感器读数平均,用于消除抖动
- double balanceSpeed = 0.05; // 用于陀螺仪读数平均(陀螺仪比较精确)
- double balanceXY = 1, balanceX = 1, balanceY = 1;
- double angleX, angleY, angleZ, speedX, speedY, accAngleX, accAngleY;
- double testAngleX;
- double compAngle;
- boolean hasReadCompAngle = false;
- /* Main control start */
- void setup()
- {
- motorPins[0] = 38; //x1
- motorPins[1] = 43; //x2
- motorPins[2] = 42; //y1
- motorPins[3] = 39; //y2
- for(int i = 0; i < 4; i++) pinMode(motorPins[i], OUTPUT);
- Serial.begin(9600);
- Wire.begin();
- pinMode(gyroCS_Pin, OUTPUT);
- pinMode(gyroSDO_Pin, OUTPUT);
- digitalWrite(gyroCS_Pin, 1); //enable I2C for GYRO
- digitalWrite(gyroSDO_Pin, 0); //set I2C address last bit
- setupGyroSensor(250);
- setupAccSensor();
- setupCompSensor();
- pinMode(trigPin, OUTPUT);
- attachInterrupt(0, blink, CHANGE);
- digitalWrite(trigPin, 0);
- // 从EEPROM中读取零点的校准值
- for(int i = 0; i < 3; i++) {
- gyroOffset[i] = EEPROM.read(2 * i + 1) << 8;
- gyroOffset[i] |= EEPROM.read(2 * i);
- accOffset[i] = EEPROM.read(7 + 2 * i) << 8;
- accOffset[i] |= EEPROM.read(6 + 2 * i);
- }
- }
- // 用于处理超声波传感器产生的中断,尽量减少在中断函数中处理逻辑
- void blink()
- {
- if(ultraT1 == 0) ultraT1 = micros();
- else ultraT2 = micros();
- }
- int loopIndex = 0;
- void loop()
- {
- if(reportType == 103) {
- /* 校准模式,在静止的平地上测量N次读数,记录零点误差
- 校准隔一段时间做一次即可,例如季节变化,地点变化等 */
- gyroOffset[0] = gyroOffset[1] = gyroOffset[2] = 0;
- accOffset[0] = accOffset[1] = accOffset[2] = 0;
- int repeatTime = 100;
- for(int i = 0; i < repeatTime; i++) {
- // 单次读数大约6ms,校准读数总耗时在0.3秒左右
- readGyroSensor();
- readAccSensor();
- for(int j = 0; j < 3; j++) {
- gyroOffset[j] += gyroXYZ[j];
- accOffset[j] += accXYZ[j];
- }
- delay(10);
- }
- // 读数完毕写入EEPROM,每个字节需要3.3ms写入
- for(int i = 0; i < 3; i++) {
- gyroOffset[i] /= repeatTime;
- EEPROM.write(2 * i, (byte) (gyroOffset[i] & 0xFF));
- EEPROM.write(2 * i + 1, (byte) ((gyroOffset[i] >> <img src="http://www.diy-robots.com/wp-includes/images/smilies/icon_cool.gif" alt="8)" class="wp-smiley"> & 0xFF));
- accOffset[i] /= repeatTime;
- EEPROM.write(6 + 2 * i, (byte) (accOffset[i] & 0xFF));
- EEPROM.write(7 + 2 * i, (byte) (accOffset[i] >> 8 & 0xFF));
- }
- // 校准完成,恢复成普通模式
- reportType = 0;
- }
- /*
- * 采样率为350Hz,所以每个周期2.857ms。每个周期中都需要读一次陀螺仪和加速度计,
- * 用时大约0.7ms;然后剩余的时间分别用来做不同的事情,例如控制电机,计算角度等。
- * 然后进行角度姿态的融合,大约需要0.7ms。这部分放在控制电机的后面,因为0.9ms开始
- * 就需要控制电机,时间不够用,放在后面则刚好够用。
- * 电机的控制周期是20ms,也就是每7次循环修改一次电机功率
- */
- long t0 = micros(); //记录进入loop的时间
- if(loopIndex == 1) {
- for(int i = 0; i < 4; i++) digitalWrite(motorPins[i], HIGH);
- } else {
- // digitalWrite需要20微秒,为了读数时间均匀,其他情况统一做相应延时
- delayMicroseconds(20);
- }
- // 读取陀螺仪和加速度计的读数,前1.5秒等待传感器稳定
- if(t0 > stableTime) {
- readGyroSensor();
- readAccSensor();
- }
- if(connectToPc && loopIndex == ctrlPower % 7) reportT[0] = (int) (micros() - t0);
- double newSpeedX, newSpeedY, newAngleX, newAngleY, power, powerX, powerY, adjust;
- int loopCountLimit;
- switch(loopIndex) {
- case 0:
- // 这时候角度数值已就绪:angleX, angleY, angleZ, speedX, speedY
- // 但是需要修正45度安装的换算
- newSpeedX = (speedX + speedY) * 0.707;
- newSpeedY = (speedY - speedX) * 0.707;
- // 假设四轴翻转不会超过90度
- newAngleX = asin((sin(angleX) + sin(angleY)) * 0.707);
- newAngleY = asin((sin(angleY) - sin(angleX)) * 0.707);
- // 计算平衡参数
- adjust = 1;
- if(throttle > 350) adjust = 500.0 / throttle;
- balanceX = 1 - (newSpeedX * P_Control + newAngleX * I_Control) * adjust;
- balanceX = min(max(balanceX, 0), 2);
- balanceY = 1 - (newSpeedY * P_Control + newAngleY * I_Control) * adjust;
- balanceY = min(max(balanceY, 0), 2);
- // 计算油门值
- if(ctrlPower <= directCtrlRange) {
- if(throttle > ctrlPower * 5) {
- // 缓冲下降20ms下降0.8,1秒钟下降40
- throttle -= fastStep;
- } else {
- // 上升之前可以不考虑缓冲问题
- throttle = ctrlPower * 5;
- }
- } else {
- // 目标离地高度为ctrlPower - directCtrlRange + 10;
- targetDistance = ctrlPower - directCtrlRange + 10;
- if(stablePower == 0 && distance > preDistance + 2) {
- // 100ms内爬升了2cm以上,设置2主要是为了避免读数跳动误差
- stablePower = throttle;
- }
- if(stablePower == 0) {
- // 处于起飞状态
- throttle += fastStep;
- } else {
- // 可以认为已经离地
- double newThrottle = stablePower + (targetDistance - distance) * 0.11- (distance - preDistance) * 0.13;
- if(newThrottle > throttle + fastStep) throttle += fastStep;
- else if(newThrottle < throttle - fastStep) throttle -= fastStep;
- else throttle = newThrottle;
- }
- // 油门保护,不超过某个最大值(避免飞太快超出控制范围)
- if(throttle > maxThrottle) throttle = maxThrottle;
- // directCtrlRange优先控制权
- if(throttle < directCtrlRange * 5) throttle = directCtrlRange * 5;
- }
- // 计算电机功率
- power = throttle;
- powerX = power * balanceXY;
- powerY = power * 2 - powerX;
- motorPowers[0] = (int) (powerX * balanceX);
- motorPowers[1] = (int) (powerX * 2) - motorPowers[0];
- motorPowers[2] = (int) (powerY * balanceY);
- motorPowers[3] = (int) (powerY * 2) - motorPowers[2];
- motorCtrl[0] = 9999;
- for(int i = 0; i < 4; i++) {
- motorPowers[i] += 5 * i;
- if(motorPowers[i] > maxPWM_Delay) motorPowers[i] = maxPWM_Delay;
- if(motorPowers[i] < motorCtrl[0]) {
- motorCtrl[0] = motorPowers[i];
- motorCtrl[1] = i;
- }
- }
- // 排序, 设置motorCtrl
- for(int i = 1; i < 4; i++) {
- int pre = motorCtrl[i * 2 - 2];
- int preIdx = motorCtrl[i * 2 - 1];
- motorCtrl[i * 2] = 9999;
- for(int j = 0; j < 4; j++) {
- if(motorPowers[j] > pre || (motorPowers[j] == pre && j > preIdx)) {
- if(motorPowers[j] < motorCtrl[i * 2]) {
- motorCtrl[i * 2] = motorPowers[j];
- motorCtrl[i * 2 + 1] = j;
- }
- }
- }
- }
- // 计算delayMs
- for(int i = 0; i < 3; i++) {
- motorCtrl[6 - i * 2] -= (motorCtrl[4 - i * 2] + 5);
- }
- break;
- case 1:
- // 控制电机转速,首先运行到0.9ms
- delayMicroseconds(minPWM + t0 - micros());
- for(int i = 0; i < 4; i++) {
- // 根据间隔时间设置低电平
- if(motorCtrl[i * 2] > 0) delayMicroseconds(motorCtrl[i * 2]);
- digitalWrite(motorPins[motorCtrl[i * 2 + 1]], LOW);
- }
- break;
- case 2:
- // 电子指南针读数,这个不需要太精确,所以20ms读一次
- if(t0 > stableTime) {
- readCompSensor();
- }
- // 从串口获取遥控信息
- loopCountLimit = 18;
- while(Serial.available() && loopCountLimit--) {
- pushData(Serial.read());
- }
- break;
- case 3:
- // 发送各种报告
- if(connectToPc) {
- if(reportType == 1) writeBuffer(gyroOffset, 3);
- if(reportType == 2) writeBuffer(accOffset, 3);
- if(reportType == 3) writeBuffer(compXYZ, 3);
- //if(reportType == 1) writeNums(motorCtrl[0],motorCtrl[1],motorCtrl[2]);
- //if(reportType == 2) writeNums(motorCtrl[3],motorCtrl[4],motorCtrl[5]);
- //if(reportType == 3) writeNums(motorCtrl[6],motorPowers[2],motorPowers[3]);
- if(reportType == 4) writeBuffer(reportArr, 3);
- if(reportType == 5) writeNums((int)distance, (int)(speedX * 1000), motorPowers[0]);
- //if(reportType == 5) writeNums(reportArr[1], motorPowers[2], motorPowers[3]);
- if(reportType == 6) writeBuffer(reportT, 3);
- }
- break;
- case 4:
- // 测量离地距离,音速340m/s = 34cm/ms = 0.034cm/μs,测量的是来回距离,所以需要除以2
- if(ultraT1 > 0 && ultraT2 > ultraT1) {
- double newDistance = (ultraT2 - ultraT1) * 0.017;
- if(newDistance < 250) {
- preDistance = distance;
- if(distance > 100 && newDistance < 10) {
- // 高度突然变小的情况(从100以上变到小于10,可以认为是超出量程)
- distance = maxDistance;
- } else {
- distance = newDistance;
- }
- }
- }
- // 考虑到回音等问题,把测量的时间间隔增加到100ms,相当于17米的反射
- ultraCount++;
- if(ultraCount >= 5) {
- ultraT1 = 0;
- ultraT2 = 0;
- // 因为每次执行到这个循环是20ms,所以计数器增长到5时进行一次测量
- ultraCount = 0;
- // 用10μs的高电平发出触发信号
- digitalWrite(trigPin, 1);
- delayMicroseconds(10);
- digitalWrite(trigPin, 0);
- }
- break;
- default:
- // 其他扩展功能
- break;
- }
- if(connectToPc && loopIndex == ctrlPower % 7) reportT[1] = (int) (micros() - t0);
- // 计算角度angleX, angleY, speedX, speedY, accAngleX, accAngleY
- if(t0 > stableTime) {
- // 量程为250时,测量值单位是8.75 mdps/digi;
- // 正确:180/3.1415926*1000/8.75 = 6548.09
- // 错误:3754.94 = (65536/1000)*(180/3.1415926)
- double readSpeedX = (gyroXYZ[0] - gyroOffset[0]) / 6548.09; //单位:弧度/秒
- speedX = balanceSpeed * speedX + (1 - balanceSpeed) * readSpeedX;
- accAngleX = accAngleX * balanceAcc + atan2(accXYZ[0] - accOffset[0], accXYZ[2]) * (1 - balanceAcc);
- angleX = (angleX + readSpeedX * 0.002857) * balanceGyro + accAngleX * (1 - balanceGyro);
- testAngleX = testAngleX + readSpeedX * 0.002857;
- reportArr[0] = (int) (angleX * 1000);
- reportArr[1] = gyroXYZ[0] ;
- reportArr[2] = (int) (testAngleX * 1000);
- double readSpeedY = (gyroXYZ[1] - gyroOffset[1]) / 6548.09; //单位:弧度/秒
- speedY = balanceSpeed * speedY + (1 - balanceSpeed) * readSpeedY;
- accAngleY = accAngleY * balanceAcc + atan2(accXYZ[1] - accOffset[1], accXYZ[2]) * (1 - balanceAcc);
- angleY = (angleY + readSpeedY * 0.002857) * balanceGyro + accAngleY * (1 - balanceGyro);
- }
- if(loopIndex == ctrlPower % 7) reportT[2] = (int) (micros() - t0);
- // 更新loopIndex
- loopIndex++;
- if(loopIndex >= 7) loopIndex = 0;
- // 截止到2.857ms
- t0 = micros() % 2857;
- delayMicroseconds(2817 - t0);
- }
- /* get data from bluetooth devices */
- void pushData(byte data)
- {
- //验证是否数据的开始
- if (bufferIndex < 1 && data != (byte)0xFF) {
- bufferIndex = 0;
- } else {
- buffer[bufferIndex] = data;
- if (bufferIndex == 5) {
- setParams();
- }
- bufferIndex = (bufferIndex + 1) % 6;
- }
- }
- void setParams()
- {
- // 因为超声波中断可能导致数据丢失,所以需要额外的一个字节用于校验
- if(buffer[1] ^ buffer[2] ^ buffer[3] ^ buffer[4] == buffer[5]) {
- ctrlPower = buffer[1];
- reportType = buffer[2];
- //P_Control = (20.0 + buffer[3] / 4.0) / 200.0;
- //I_Control = (20.0 + buffer[4] / 4.0) / 200.0;
- P_Control = buffer[3] / 100.0;
- I_Control = buffer[4] / 100.0;
- if(reportType == 100 && !connectToPc) {
- Serial.end();
- connectToPc = true;
- Serial.begin(115200);
- }
- if(reportType == 101) {
- // 设置为上拉绳调试模式
- directCtrlRange = maxThrottle / 5;
- }
- if(reportType == 102) {
- // 设置为下拉绳调试模式
- directCtrlRange = 40;
- }
- //if(reportType == 103) {} //校准模式
- }
- }
- /*****************************************************************************/
- void writeBuffer(int arr[], int len) {
- byte buff[len * 2 + 2];
- buff[0] = 0xFF;
- buff[1] = 0xFF;
- for(int i = 0; i < len; i++) {
- buff[i * 2 + 2] = (byte) (arr[i] >> 8 & 0xFF);
- buff[i * 2 + 3] = (byte) (arr[i] & 0xFF);
- }
- Serial.write(buff, len * 2 + 2);
- }
- void writeNums(int n1, int n2, int n3) {
- byte buff[8];
- buff[0] = 0xFF;
- buff[1] = 0xFF;
- buff[2] = (byte) (n1 >> 8 & 0xFF);
- buff[3] = (byte) (n1 & 0xFF);
- buff[4] = (byte) (n2 >> 8 & 0xFF);
- buff[5] = (byte) (n2 & 0xFF);
- buff[6] = (byte) (n3 >> 8 & 0xFF);
- buff[7] = (byte) (n3 & 0xFF);
- Serial.write(buff, 8);
- }
- /*****************************************************************************/
- void writeRegister(byte deviceAddress, byte address, byte val) {
- Wire.beginTransmission(deviceAddress); // start transmission to device
- Wire.send(address); // send register address
- Wire.send(val); // send value to write
- Wire.endTransmission(); // end transmission
- }
- byte readRegister(int deviceAddress, byte address){
- Wire.beginTransmission(deviceAddress);
- Wire.send(address);
- Wire.endTransmission();
- Wire.requestFrom(deviceAddress, 1);
- while(!Wire.available()) {}
- return Wire.receive();
- }
- /*****************************************************************************/
- int setupGyroSensor(int scale){
- byte GYRO_CTRL_REG1 = 0x20;
- byte GYRO_CTRL_REG2 = 0x21;
- byte GYRO_CTRL_REG3 = 0x22;
- byte GYRO_CTRL_REG4 = 0x23;
- byte GYRO_CTRL_REG5 = 0x24;
- writeRegister(GYRO_ADDR, GYRO_CTRL_REG1, 0x0f);
- writeRegister(GYRO_ADDR, GYRO_CTRL_REG2, 0x00);
- writeRegister(GYRO_ADDR, GYRO_CTRL_REG3, 0x08);
- if(scale == 250){
- writeRegister(GYRO_ADDR, GYRO_CTRL_REG4, 0x00);
- }else if(scale == 500){
- writeRegister(GYRO_ADDR, GYRO_CTRL_REG4, 0x10);
- }else{
- writeRegister(GYRO_ADDR, GYRO_CTRL_REG4, 0x30);
- }
- writeRegister(GYRO_ADDR, GYRO_CTRL_REG5, 0x00);
- }
- void readGyroSensor(){
- Wire.beginTransmission(GYRO_ADDR);
- Wire.send(GYRO_READ_START);
- Wire.endTransmission();
- Wire.requestFrom(GYRO_ADDR, 6);
- for(int i = 0; i < 3; i++) {
- while(!Wire.available()) {}
- gyroXYZ[i] = Wire.receive(); //Low byte
- while(!Wire.available()) {}
- gyroXYZ[i] |= (Wire.receive() << 8); //High byte
- }
- }
- /*****************************************************************************/
- void readAccSensor(){
- Wire.beginTransmission(ACC_ADDR);
- Wire.send(ACC_READ_START);
- Wire.endTransmission();
- Wire.requestFrom(ACC_ADDR, 6);
- for(int i = 0; i < 3; i++) {
- while(!Wire.available()) {}
- accXYZ[i] = Wire.receive(); //Low byte
- while(!Wire.available()) {}
- accXYZ[i] |= (Wire.receive() << 8); //High byte
- }
- }
- int setupAccSensor(){
- writeRegister(ACC_ADDR, 0x2D, 0x28); //Power control
- }
- /*****************************************************************************/
- void readCompSensor(){
- Wire.beginTransmission(COMP_ADDR);
- Wire.send(COMP_READ_START);
- Wire.endTransmission();
- Wire.requestFrom(COMP_ADDR, 6);
- for(int i = 0; i < 3; i++) {
- while(!Wire.available()) {}
- compXYZ[i] = Wire.receive(); //High byte
- while(!Wire.available()) {}
- compXYZ[i] = Wire.receive() | (compXYZ[i] << 8); //Low byte
- }
- // Switch xzy to xyz
- int temp = compXYZ[1];
- compXYZ[1] = compXYZ[2];
- compXYZ[2] = temp;
- }
- int setupCompSensor() {
- writeRegister(COMP_ADDR, 0x00, 0x70); //Mod setting
- writeRegister(COMP_ADDR, 0x02, 0x00); //Mod setting
- }
- /*****************************************************************************/
- </eeprom.h></wire.h>
终于开放源代码了…………
我的源代码一直就准备要开放的,之所以没贴出来,是因为调试不成功,怕误导别人,呵呵
动力兄,能采纳我的建议真的很高兴,但你这一堆代码,我还真一时半会很难看懂,准备慢慢消化了,吃透了准备搞个自平衡小车试试。
虽然,代码不怎么明白,但有一点很明确,动力兄没有调用ARDUINO自带的PID库,所以涉及到的一些PID的问题没有彻底解决,http://arduino.cc/playground/Code/PIDLibrary,pid库文件应该可以下载到,结合上次给你的官网对PID描述,再试一下,加油啊!(我纸上谈兵了:))
这里顺便提一下,arduino的库文件真是个好东西,我前段时间做的短信控制ARDUINO也是用到了#include ,问题一下迎刃而解了,
实现了短信控制电源开关,当然在进一步的话,可以控制带红外遥控器的设备,
比如空调,电视,再进一步的话,接上汽车发动机就是“安吉星”啦,创意无极限!
好的,多谢提醒,我也试试这个PID库看看,不过有个叫ENIAC的小朋友提到商品电调有可能0.2秒的减速延时,这个很致命啊,我先查查这个事情。
是这样的,很多电调都有自带的滤波,会使电调反应迟缓,最好自己做带I2C总线的电调,比如MK电调
这里顺便提一下,arduino的库文件真是个好东西,我前段时间做的短信控制ARDUINO也是用到了#include 库文件,多串口并用问题一下迎刃而解了,实现了短信控制电源开关,当然再进一步的话,可以控制带红外遥控器的设备,比如空调,电视,再进一步的话,接上汽车发动机就是“安吉星”啦(上海这里这广告特多,不知道首都人民知道吗?呵呵)
奇怪了,一串库文件名字打不上来,看来涉及敏感字符了
安吉星的广告在这里也放,呵呵
好多程序呀,看着就晕,学习还没学到啊。。
哈哈哈,被表扬了,嘿嘿。
您的代码好多啊,啊啊,可能是我水平太差才觉得,无从入手。希望lz照顾下像我这样笨的,新的四轴制作的话,能不能采用循序渐进的方式,如:
第一阶段:只用陀螺仪做四轴(最开始,用最简单最少的内容,估计我这种菜鸟有希望入门,啊啊)
第二阶段:陀螺仪+加速度 做四轴(慢慢加入更多的内容)
第三阶段:陀螺仪+加速度+超声波 做四轴
第三阶段:陀螺仪+加速度+超声波+gps 做四轴
。。。。。。
啊啊,可能太麻烦了,不过真想要这样的教程,我只好厚脸皮的向lz要啦,哈哈
我本来就是计划这样一步步发攻略的,呵呵,不过后来担心不成功的攻略会误导别人。
这次也是应邀把代码发出来让大家帮忙找找错。最终实验成功的话,我就会把这些代码分解成小块介绍。
确实要想把你这一堆代码弄明白不容易,周末买了陀螺仪和加速度计,准备参照你的代码做一个自平衡小车练练手,顺便验证一下PID库是否实用
本来我周末也想试试,结果发烧了两天,悲催啊
兄弟,身体要紧啊,ARDUINO这件事情对我来说,足够折磨才够意思,哈哈,是不是很BT,接下来等东西齐了,慢下心来慢慢消化了
动力哥,有一个很厉害的朋友把Arduino的库函数简化了很多,做了一个叫做arduino-lite库,http://www.csksoft.net/blog/catalog.asp?cate=4
生成代码长度要短很多,可能对你的制作有帮助
多谢老薛,我看看
动力兄,看你好久没发帖了,不知道你还在关注这里吗?我也在做四轴,现在问个问题,四轴在飞行过程中,如果如果突然打剁,四个桨提供的动力在垂直方向的分力会立即减小,四轴岂不是会立即下落,你们是怎么处理这个问题的?谢谢!
打剁是什么意思?如果撞到东西的话,确实是没有办法的
呵!老薛,看上去他们不仅是基于现有的Arduino库二次开发,而是从编译系统上改进了。
还没有开始试用,不过从他们这个例子上看,貌似对性能提高很大!
代码类型 代码 所需的AVR时钟周期
Arduino analogWrite(9, pwm_val); ~80
Arduino-Lite ANALOG_WRITE(9, pwm_val); 2
美中不足的是库有点少了,比如I2C的库,自己写的话代码有点多
是啊,这个库大大的简化了原有的库,但是原有的库比较健壮,考虑的情况比较周全,所以产生代码多。但是我想只要按照Arduino-lite里面的说明和约定来连接硬件,应该就可以了
那位朋友很厉害,对计算机底层的东西和网络都懂得很深入。我也很想学习这些技术,但不知道从何入手。动力哥可有建议?
隔行如隔山啊,老薛你的编程能力已经相当不错了。但是对咱们这些玩家来说,这些东西应该是用不上的。
你说的计算机底层应该是指编译原理和操作系统原理,关于网络有一门课叫计算机网络原理,另外计算机专业比较推崇的还有算法导论。
这几门课很多计算机专业的人学完以后也抱怨没有什么意义,不过高手们一般都学的很好。
我属于野路子出身的程序员,上面这些东西都是自己瞎看的,理解个皮毛,只有算法研究的多一点,主要是面试总考这些 :)
这些方面我也不算擅长,所以不敢瞎提建议,老薛如果有兴趣不妨先看看相关教材吧
两位,在我眼里都算是高手了,虽然我是计算机网络专业背景,但实在是懂个皮毛,没有深入理解过,ARDUINO算是对自己所学知识运用的一个开始,涉及到编程这块完全是摸石头过河,走一步看一步。
计算机网络原理我上过,都是些原理性的东西,没有实践真是很难理解
是啊!高手们总是学的很好。因为他们总会自己找机会去实践书本上教的那些东西。我学过一门叫做微机原理的课,不知从何实践,所以也早忘了。可能现在重新捡起来看看会有深一些的体会
动力哥职业上的华丽转身还是很值得佩服的,哈哈!
像你这样的物理学专业人士,简直就是人类的未来啊,计算机只需要当工具来用就好啦
还有强力的基础课 离散数学
以及为了实现强大数据处理算法的 数据结构等等
编译原理和操作系统原理目前暂时用不上,毕竟AVR上OS还是很辛苦的(不像ARM都可以上LINUX内核了)
动哥…想问问为什么你不用arduino的servo库要自己写PWM呢?是更精准和好操作一下吗?
最近终于把架子,电源电路那些都弄好了..
但发现把马达调到转速很高都没法起飞..只会原地旋转..
你觉得是怎么一回事呢?是不是桨的问题呢?
对,我是觉得自己的PWM会更精确和容易控制。
不过这只是我的个人看法,你知道程序员有时候会固执的自己写代码:)
你的高电平脉宽调到多少了呢?我估计差不多要到1.5ms才能飞起来。
最好注意一点,带上护目镜,不要在家里飞,或者栓上绳子,因为一飞起来可能就飞老高
哈哈..都是执着的程序员啊..
我也是像你一样..大部分代码都自己重写那样..其实都有现成的轮子可以用了..
不过我看了看servo库的代码..它是设置AVR的定时器做PWM方波的输出..直接配置寄存器就能很好的生成PWM..这样来得更精准..不会占用CPU的时间..不受中断程序的影响..
同时也不用自己考虑太多..动哥也可以参考一下这样..
对了..你之前的四轴支架..轴距大概是多少呢??我的是60cm..不知道是不是太重太沉了..我现在脉宽到1.3ms左右吧..感觉马达已经转得非常快了..转动的声音尤其吓人..都有点怕了..呵呵..
好的..我下次去外面再开大一点保护好试试看..谢谢你的建议啦~
好的,我回头也看下源代码,印象中系统默认的PWM是50Hz,而且PWM的值只有0~255的范围。不过可能真的实现方式不一样,先去看看再说。
轴距我也差不多是60cm,称重量接近2kg。脉宽1.3ms太小啦,0.9~2.1是从0到满速,1.3刚开个头呢。
不过声音是够吓人的,一定要做好保护啊,切记!
刚才看了下servo库,里面主要控制时间的方法是:myservo.writeMicroseconds(1500);
从这点看来,servo库默认的PWM频率还是50Hz,而且貌似没有修改的接口。
如果这样的话,只能先用自己的代码来处理了
记得,以前试过,用自带的servo不能驱动所有的舵机,可能就是这个原因。
动哥..我用你的传感器代码来测试了一下ADXL+D3G的模块
发现只有一开始的读值正确..只要一移动传感器..
串口传回来的值就变成全1和全255了..
很奇怪你有遇过这样的问题吗?
我没有遇到这样的问题,你的接线检查过了吗?
用I2C总线
都检查过了..好像没有什么问题啊..
我也是用I2C总线..我换了两个传感器,两个Arduino MEGA的板子好像都是类似的情况
动哥你用的也是CJMCU-345+4200的模块吗?
我用的是 L3G4200D + ADXL345
可以看这个链接:http://www.diy-robots.com/?p=1025
好的~我有看過和使用你的代碼,但就很奇怪還是出現全1或全255。
鬱悶死了,卡在這個問題上太長時間了
動哥可以把你單獨控製adxl+d3g的代碼發到我郵箱里嗎?謝謝啦~
已发出
啊~~~枉我还是计算机科班出身的..
原来犯了一个非常低级的错误..
忘了每次读数传感器值时清零~才会出现全-1或全255..
谢谢你的帮助啦动哥:]
哈哈,找到原因就好!
问一下动哥。我们可以自己做一个用您的代码?
用代码没问题,悲催的是我这个代码还不能平稳的飞行
你还需要自己调试下PID
我下载了MWC的源码,里面全是宏定义,怎么看啊?
有些文件你不需要动,你可以看作是驱动程序, 只要动定义的那个文件啊
好像是CONFIG.H这个头文件 比如传感器,不能同时开太多,比如 三轴陀螺你不能开好几个三轴陀螺仪,按照你实际用的传感器来,很多常见配置都已经被罗列 比如:
//#define INNOVWORKS_10DOF // with ITG3200, BMA180, HMC5883, BMP085
也就是说INNOVWORKS_10DOF 这个10轴传感器就包含了ITG3200, BMA180, HMC5883, BMP085 4个 ,很多很多常见组合都有了,实在没有就手动的开下面的单个的传感器
我的10轴在5月份的版本已经测试通过了 当时的标注 翻译过来就是 中国山寨10轴模块 (三轴陀螺+3轴加速度+磁定向+气压)
darkorgin同学太强大了
不带这样吧。。这样做四轴还有什么意思呢?可能什么都没有学会啊?我是想全面理解四轴的控制机理。做出来只是为了验证我完全懂了。我说的做四轴实质上是做飞控。。
你好,
我想问下你,关于,电调初始化的程序。
我用的是 hoobywing skywalker 20A*4 的集成电调
在开始飞之前,要初始化电调range。(说是不同的发射机,有不同的range)
但是在程序中,输出是PWM波形,拨到最大值,保持两秒。(那说明保持PWM duty cycle 最大,但是电调怎么知道这是最大的啊?)
我看电调的manual,里面没有说道电调是怎么工作的,要怎么能查到,电调的
电路信息,还有工作原理啊?
谢谢了。
taorui
个人理解电调其实主要作用在于放大电流和电压(很明显,单片机的管脚肯定无法直接提供驱动电机的足够电流和电压)
SO
个人认为,可以忽略它的设计(编程或者电路设计里面叫做 黑盒子 就是说忽略它的内在,只操作的接口和使用的方式)
电调的说明书主要是针对飞行板而非单片机,自然是不会公开这些技术参数。
真的需要研究,可以参考MWC 一个开源的飞行控制系统(包含自稳,定向等功能,当然也包含相应周边电路的初始化等工作)
我猜想电调内置了一些参数,其中2ms左右就可以认为是大值
谢谢你的回答。
我现在不太明白的是,单片机输出的是PWM信号(490HZ),但是我看网上的资料,信号输出应该是50HZ,在这里有点疑惑。
我看动力哥的电调属性那里(PPW)20ms的模型-(0.5-2ms)的dutycycle来控制转速。我要做的就是让单片机输出50HZ的PPM信号就OK了?
周一去学校机房用PWM信号试下能驱动电机不。。
还有好长距离要做啊。
在补充一下:
如果动力哥能把程序的运行步骤(模块)分开写上去就好了。
这样直接看所有代码。从起飞,检测,离地,PID,油门反应。。混在一起。。
初学者 还是蛮难看懂的
taorui
我也想这么写啊,不过我自己还没调好了,先不误人子弟了
这几个月很忙,很久没更新了,对不起大家
对50Hz就ok了,我用的是arduino单片机,跟淳朴的单片机还不一样,它是已经封装了一些功能,所以输出的PWM已经是50Hz了
另外能问个很简单的问题(MWC):
#if defined(BI) || defined(TRI) || defined(SERVO_TILT) || defined(GIMBAL) || defined(FLYING_WING) || defined(CAMTRIG)
#define SERVO
#endif
#if defined(GIMBAL) || defined(FLYING_WING)
#define NUMBER_MOTOR 0
#elif defined(BI)
#define NUMBER_MOTOR 2
#elif defined(TRI)
#define NUMBER_MOTOR 3
#elif defined(QUADP) || defined(QUADX) || defined(Y4)
#define NUMBER_MOTOR 4
#elif defined(Y6) || defined(HEX6) || defined(HEX6X)
#define NUMBER_MOTOR 6
#elif defined(OCTOX8) || defined(OCTOFLATP) || defined(OCTOFLATX)
#define NUMBER_MOTOR 8
#endif
这是MWC源码中OUTPUT 控制电机输出代码,但是看不懂之前的define是用来判断什么的啊?
这些语法是这样么?动力哥能帮忙看下么。
这种开源程序,不仅仅是对四轴的,还可以用来飞直升机、六轴等等,可能是用来设置这些参数的。
动哥,请教个问题
accAngleX = atan2(accXYZ[0] – accOffset[0], accXYZ[2])
X指的是pitch吗? 这个公式有理论依据吗?
我发现MWC 解算Pitch roll 的公式跟上面的差不多。
但是根据资料《freescale-tilt sensing using linear accelerometer AN3461.pdf》以及国外的一些做平衡车的资料,
pitch 角度 = atan2( accXYZ[0], accXYZ[2])。
迷惑中。。。。
上面第二个公式打错了
应该是
pitch 角度 =
atan2( accXYZ[0], sqrt(accXYZ[2]^2 + accXYZ[1]^2))。
这个我没有看过你说的资料,但是你写的这个公式很奇怪啊。我想算的是倾角,根据直角三角形的正切定义,tan(alpha) = Z / X (正切等于对边比邻边)
之所以用atan2,是因为atan的范围是180度,而atan2的范围是0到360度。
而这个公式里的是对边/斜边,看上去像是asin啊
感谢动哥的回复
我还是有疑惑
当物体只做pitch运动时,tan(alpha) = Z / X 来求pitch角度,这很好理解;但是当物体同时做pitch和roll运动时,能保证tan(pithc) = Z / X,tan(roll) = Z / X吗?
第一个参数是除以Y吧?确实不能保证,这样计算从几何学来说是不行的。
但是我们本来就是估算吧,而且角度比较小的时候,可以认为近似相等。
我也有点纠结这个问题,所以一直觉得没有飞起来,不敢乱写攻略,怕误导别人了
最近在自己打算用一种不同的方式来实现X4. 没有APM,没有MWC,Arduino仅用来控制电机和检测电压~
赞,欢迎分享经验
目前用了一块i9300的主板.板载了气压计等传感器.去掉了android-jni以上的东西,保留了linux内核.自己驱动3G以及各传感器.
用usb和arduino通信.
arduino则用了sero库来驱动,频率上到了600hz以上了.esc用的rctimer 40a
目前还在做i9300层面的开发,已经完成了3G信号中断重连.VPN断线重连,传感器数据以及控制协议开发.
关于控制的话用了一台公网服务器,通过VPN转发了主板上的端口.达到可直接tcp/ip通信的目的.
目前最大的问题有:
1. 通信延迟, 服务器是电信的,主板用了联通3G.ping延迟基本上到了200ms左右
2. GPS精度问题. 目前只能达到6m左右. 打算通过天线增益,看能不能有所改善,本来用了陶瓷无源天线,25x25mm的那种.结果发现还没有使用 800mhz-2500mhz全频的3g天线增益大. 自制了陶瓷有源天线,在馈电电路上貌似一直不成功. 不知道老男孩在这方面能否提供一些帮助?
3. 传感器及pid算法,这个最蛋腾. 唯一的打算是剥离mwc的代码.转换为c代码嵌入
关于地面站的现实打算使用baidu maps api.地面站支持手机控制
我想做的,其实并不是实时控制,而是无人机方向.
关于gps的误差,我打算使用”相对GPS定位”来解决, 就是当x4和控制设备在起飞前各记录一次各自的GPS位置,之后所有路径以及位置信息不依赖GPS坐标。而是通过加速度和磁力计以及陀螺仪来完成路径和位置计算。
总之一切都是从0开始。主板上的2颗摄像头,后置1080p用于航拍。前面那个打算用来进行障碍计算。。 路太漫长。
如果对这种方向有兴趣的朋友 也可以与我一起交流,期待。。。
感觉不依赖GPS坐标。而是通过加速度和磁力计以及陀螺仪来完成路径和位置计算,难度相当的大,而且传感器的误差积分,。。。。。。。
仰慕
太赞了,你这可是一个大工程!
1. 关于通信延时的问题,现在的机房一般都支持联通电信双线吧,看能不能给调一下。如果实在不行,也可以看有没有朋友在双线机房给配个socket转发代理。我的主机是放在美国的,刚才ping了下,也超过200ms了
2. GPS精度其实还没有这么高,十几米到50米的误差都是正常的。但这个事情不用太纠结,它的误差基本是偏移量,而不是随机误差。市面上的导航设备,也是通过相对位置差分来修正的。但是不建议用加速度和陀螺仪来计算路径,不是准不准的问题,而是根本无法计算。陀螺仪只能测角速度,加速度传感器在不知道准确姿态和当地准确重力加速度的情况下,没法计算路径的。
GPS虽然有误差,但是在相同的气候条件下,误差基本比较稳定。所以你在起飞的时候记录GPS位置,只要对应这个位置回来,误差应该不会有6米这么大。
其他的事情过于高端,已经超出我的能力范围啦。我的博客里其实有好多玩四轴的朋友,看来评论这种方式很难交流啊。之前搭过一个论坛,discuz屏蔽垃圾功能做的不行,虽然设置了验证码和验证问题,但是很快就被海量广告淹没了。最近计划自己做一个交流用的小站,到时候欢迎提意见
又来汇报进度了..
目前已经完成了陀螺仪\加速度\磁力计\重力加速度\气压计的校准
周末在家终于把主板固定到了中心板. 加了根GPS天线.
我刚好也打算搞个论坛,要不一起?