johnsonzzd 发表于 2012-5-20 21:08:39

Arduino上拉磁悬浮

本帖最后由 johnsonzzd 于 2012-12-9 16:59 编辑

折腾了几天终于把这个东西搞定了。看似简单,还是有一些小技巧的。
主要材料:

[*]Arduino UNO主控
[*]SS495线性霍尔采集位置信息
[*]L298N驱动
[*]直径12.7mm强磁球
[*]电磁铁12V,线圈电阻10欧

主要参数:PID控制,采样周期1ms,PWM频率 3921Hz,悬浮距离30mm,电流300mA
筷子版的,下面是一个磁体,上面是一个小铁块,中间是一截筷子。悬浮距离比较小,但是方便进行建模和控制理论分析。

磁球版,悬浮距离很大,不容易进行理论分析。

http://v.youku.com/v_show/id_XMzk5NjMwMTI4.htmlconst int SS495_PIN = A0;// Analog input pin that the SS495 is attached to
const int PWM_PIN=3;// Pins 3 and 11 are connected to Timer 2.

const int SampleTime=600;
const int PWM_BIAS=128;

int Setpoint=850;
double Kp=1, Ki=0.001, Kd=50;

void setupCoilPWM()
{
   // Setup the timer 2 as Phase Correct PWM, 3921 Hz.
   pinMode(3, OUTPUT);
   // Timer 2 register: WGM20 sets PWM phase correct mode, COM2x1 sets the PWM out to channels A and B.
   TCCR2A = 0;
   TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
   // Set the prescaler to 8, the PWM freq is 16MHz/255/2/<prescaler>
   TCCR2B = 0;
   TCCR2B = _BV(CS21);
}

void writeCoilPWM(uint8_t pin, int val)
{
      OCR2B = val;
}

void PID_Compute( )
{
static int lastError=0;
static unsigned long int lastTime=0;
static double iiterm=0;

unsigned long now,timeChange;
int input,output,error,piterm,diterm;

now = micros();
timeChange = (now - lastTime);
if(timeChange>=SampleTime)
{
    lastTime = now;

    input=read_input();

    error = -(Setpoint - input);

    iiterm+= (Ki * error);
    iiterm=constrain(iiterm,-255,255);

    piterm=Kp * error;
    diterm=Kd * (error - lastError)*1000/timeChange;
    output = PWM_BIAS+(piterm + diterm);
    output=constrain(output,0,255);
    writeCoilPWM(PWM_PIN, output);

    // print the results to the serial monitor:
    //sprintf(str,"%04d,%+04d,%+04d,%+04d,%04d",error,piterm,diterm,(int)iiterm,output);
    //Serial.println(str);   

    /*Remember some variables for next time*/
    lastError = error;
   }
}

int read_input()
{
static int last=0;
int r;
r=analogRead(SS495_PIN);
if(abs(r-last)<4)
    r=last;
else
    last=r;
return r;
}

void setup() {
setupCoilPWM();
}

void loop() {
// read the analog in value:
PID_Compute();
}
下一步准备加上串口通讯,用matlab做上位机界面。已经做好了Arduino的自平衡车和两旋翼模型,有空也放上来。

自平衡车就在这里:
http://www.geek-workshop.com/thread-2398-1-1.html

单旋翼实验装置,已经发到amobbs了,有详细资料:
http://www.amobbs.com/thread-5505843-1-1.html

wing 发表于 2012-5-20 22:44:13

非常有趣的东西 , 代码先收下了,有空慢慢看

darkorigin 发表于 2012-5-21 00:54:01

很久以来一直在泡一个blog,跟大家分享下动力老男孩的博客 (为免广告嫌疑,请自行百度。)
他用了多个电磁线圈实现了 陀螺(自制磁铁陀螺)的悬浮自稳(当然,Arduino的PID平衡算法和霍尔元件也功不可没)
蛮有意思。

一直以来也学了不少。

darkorigin 发表于 2012-5-21 00:56:45

第一幅图用皮筋捆着 上面亮灯的板子能透露细节吗?谢谢!

arduino-tinker 发表于 2012-5-21 07:13:08

darkorigin 发表于 2012-5-21 00:56 static/image/common/back.gif
第一幅图用皮筋捆着 上面亮灯的板子能透露细节吗?谢谢!

那应该是L298N电极驱动板吧~~看楼主的电磁铁是12V的,Arduino提供不了那么大的电压。Arduino先连L298N,外部供电给L298N,再连电磁铁。Arduino通过PWM控制L298N,间接控制电磁铁。
以上纯属猜测,望指点。

黑马 发表于 2012-5-21 08:34:05

调参数的话悬浮高度范围有多大?

Amuro_zhou 发表于 2012-5-21 16:10:27

我刚要说,是不是仿照动力老男孩做的,就看到他的名字了……
话说他的磁悬浮鸡蛋还在我们这里

johnsonzzd 发表于 2012-5-21 23:25:59

那个带个皮筋的板子是L298驱动板,在这里有点浪费,用个功率三极管或者MOS管就可以了。
这个东西的高度调整范围主要受限于霍尔元件。SS495从0到饱和,磁球的运动范围只有2、3毫米。所以有用图像识别的手段测量高度的,调整范围就很大了。
我看过动力老男孩的东西,受到启发,确实不错,但是有一个缺点是理论分析不够。磁悬浮不是什么创新的东西,国内的项目和国外的比起来还有很大差距。有兴趣的可以参考下面这个,从实践到理论非常详细。
http://www.coilgun.info/levitation/home.htm

johnsonzzd 发表于 2012-5-21 23:45:36

完整版,用串口向PC机发送信息:const int SS495_PIN = A0;// Analog input pin that the SS495 is attached to
const int PWM_PIN=3;// Pins 3 and 11 are connected to Timer 2.

const double SampleTime=0.5;//ms
const int PWM_BIAS=128;

int Setpoint=850;
double Kp=1, Ki=0.01, Kd=90;
double piterm,diterm, iiterm=0;
int input,output,error;
unsigned long now,timeChange;

void setupCoilPWM()
{
// Setup the timer 2 as Phase Correct PWM, 3921 Hz.
pinMode(3, OUTPUT);
// Timer 2 register: WGM20 sets PWM phase correct mode, COM2x1 sets the PWM out to channels A and B.
TCCR2A = 0;
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
// Set the prescaler to 8, the PWM freq is 16MHz/255/2/<prescaler>
TCCR2B = 0;
TCCR2B = _BV(CS21);
}

void setupControlTimer()
{
//set timer1 in CTC mode.
//"ATmega4_88_168_328_DataSheet" , section 16.9.2 Clear Timer on Compare Match (CTC) Mode
cli();          // disable global interrupts
TCCR1A = 0;   // set entire TCCR1A register to 0
TCCR1B = 0;   // same for TCCR1B

// turn on CTC mode:
TCCR1B |= (1 << WGM12);
// Set CS12 bits for 256 prescaler:
TCCR1B |= (1 << CS12);
// set compare match register to desired timer count:
OCR1A = (SampleTime/1000)*(16000000/256);
// enable timer compare interrupt:
TIMSK1 |= (1 << OCIE1A);
sei();          // enable global interrupts
}

void writeCoilPWM(uint8_t pin, int val)
{
    OCR2B = val;
}

ISR(TIMER1_COMPA_vect)
{
PID_Compute();
}

void PID_Compute( )
{
static int lastError=0;
static unsigned long int lastTime=0;

now = micros();
timeChange = (now - lastTime);
if(timeChange>=SampleTime)
{
    lastTime = now;

    input=read_input();

    error = -(Setpoint - input);

    iiterm+= (Ki * error);
    iiterm=constrain(iiterm,-255,255);

    piterm=Kp * error;
    diterm=Kd * (error - lastError)/SampleTime;
    output = PWM_BIAS+(piterm + diterm);
    output=constrain(output,0,255);
    writeCoilPWM(PWM_PIN, output);

   /*Remember some variables for next time*/
    lastError = error;
}
}

int read_input()
{
static int last=0;
int r;
r=analogRead(SS495_PIN);
if(abs(r-last)<4)
    r=last;
else
    last=r;
return r;
}

void setup() {
setupCoilPWM();
setupControlTimer();
// initialize serial communications at 9600 bps:
Serial.begin(115200);
}

void loop() {
// read the analog in value:
// User commands.
//   if( Serial.available() )
//   {
//      processCommand( Serial.read() );
//   }
   // print the results to the serial monitor:
char str;
sprintf(str,"%ld, %03d, %04d, %+04d, %+04d, %+04d, %04d",now/1000, input,error,piterm,diterm,(int)iiterm,output);
Serial.println(str);   

delay(20);
}


PC机监控程序。Matlab,能够自动滚屏
s1=serial('COM4','Baudrate',115200);
fopen(s1);
x=0;time=0;input=0;error=0;piterm=0;diterm=0;iiterm=0;pwm=0;
str='';
sen=0;


try                           % use try catch to ensure fclose
figure(1)
hErr=plot(x,error);
%title('Dados de LDR Aquisitados');
hold all
hP=plot(x,piterm);
hD=plot(x,diterm);
hPWM=plot(x,pwm);

j=1;
while(j<2000)
    str=fscanf(s1);
    sen=str2num(str);
    if length(sen)<7
      continue
    end
   
    x(j)=j
   time(j)=sen(1);
   input(j)=sen(2);
   error(j)=sen(3);
   piterm(j)=sen(4);
   diterm(j)=sen(5);
   iiterm(j)=sen(6);
   pwm(j)=sen(7);
   
   low=j-200;
   if(low<1)
       low=1;
   end
   up=j;
   
   x_1=x(low:up);
   error_1=error(low:up);
   piterm_1=piterm(low:up);
   diterm_1=diterm(low:up);
   pwm_1=pwm(low:up);

   set(hErr,'XData',x_1,'YData',error_1)
   set(hPWM,'XData',x_1,'YData',pwm_1)
   if(up<200)
       up=200;
   end
   axis();
   drawnow;
   j=j+1;

end;

figure(2)
plot(time,input);
hold all
plot(time,error);
plot(time,piterm);
plot(time,diterm);
plot(time,pwm);
legend('input','error','pIterm','dIterm','pwm');

fclose(s1);
delete(s1);
clear s1;

catch exception
    fclose(s1);               % always, always want to close s1
    throw (exception);
end                  

gantree 发表于 2012-7-3 20:36:31

有动手能力的实践者都是牛人。。。赞

ysit1990 发表于 2012-7-27 13:23:31

johnsonzzd 发表于 2012-5-21 23:45 static/image/common/back.gif
完整版,用串口向PC机发送信息:const int SS495_PIN = A0;// Analog input pin that the SS495 is attac ...

用matlab 来图形显示串口数据 这个满好的
楼主能不能    把这部分内容展开一下

johnsonzzd 发表于 2012-8-16 08:53:34

ysit1990 发表于 2012-7-27 13:23 static/image/common/back.gif
用matlab 来图形显示串口数据 这个满好的
楼主能不能    把这部分内容展开一下

大部分是常规内容。关键的显示部分:
   设置数据: set(hErr,'XData',x_1,'YData',error_1)
   强制刷新: drawnow;

pww999 发表于 2012-8-16 10:02:00

楼住什么时候放 Arduino的自平衡车和两旋翼模型 上来啊,好其待哦{:soso_e154:}

麽麽茶㊣ 发表于 2012-8-16 13:44:51

求详细教程~ 好想做啊~
动力老男孩博客里面怎么只要一端做线圈呢?

johnsonzzd 发表于 2012-8-16 17:10:28

详细资料已经上传到百度文库里了,正在审核中。感兴趣的朋友可以搜索:
杨麟 - 磁悬浮下位机软件毕业设计
来俊鹏-磁悬浮系统下位机硬件设计

自平衡车和两旋翼模型已经完成,但还不很完美,需要修正。正在整理中。
页: [1] 2 3
查看完整版本: Arduino上拉磁悬浮