2008年11月26日 星期三

機械手臂控制篇(4) -- 程式基本功能PWM

下面這個程式是目前剛完成PWM基本測試的內容

使用的MCU大家應該不陌生-- C8051F300

大致上就是輪替送出PWM的功能





//-----------------------------------------------------------------------------

// ROBO_ARM.c

//-----------------------------------------------------------------------------

// AUTH: Nichal

// DATE: 2008/11/27

//

// Target: C8051F300

// Tool chain: KEIL C51 6.03 / KEIL EVAL C51

//



//-----------------------------------------------------------------------------

// Includes

//-----------------------------------------------------------------------------



#include // SFR declarations



//-----------------------------------------------------------------------------

// 16-bit SFR Definitions for 'F30x

//-----------------------------------------------------------------------------



sfr16 DP = 0x82; // data pointer

sfr16 TMR2RL = 0xca; // Timer2 reload value

sfr16 TMR2 = 0xcc; // Timer2 counter



//-----------------------------------------------------------------------------

// Global CONSTANTS

//-----------------------------------------------------------------------------



#define SYSCLK 24500000 // SYSCLK frequency in Hz

#define BAUDRATE 9600 // Baud rate of UART in bps

#define SAMPLE_RATE 50 // Sample frequency in Hz

#define SERVO1_PWM SERVO1=1

#define SERVO2_PWM SERVO2=1

#define SERVO3_PWM SERVO3=1

#define SERVO4_PWM SERVO4=1




#define TMH (65536-(SYSCLK/12/SAMPLE_RATE))/256

#define TML (65536-(SYSCLK/12/SAMPLE_RATE))%256



#define MAX_ANGLE 62200

#define MIN_ANGLE 12400

#define MID_ANGLE 37300



sbit SERVO1 = P0^0;

sbit SERVO2 = P0^1;

sbit SERVO3 = P0^2;

sbit SERVO4 = P0^3;




//-----------------------------------------------------------------------------

// Function PROTOTYPES

//-----------------------------------------------------------------------------



void SYSCLK_Init (void);

void PORT_Init (void);

void Timer0_Init (void);

void Timer0_ISR (void);

void Timer2_Init (void);

void Timer2_Switch (unsigned int counts);

void Timer2_ISR (void);



//-----------------------------------------------------------------------------

// Global VARIABLES

//-----------------------------------------------------------------------------



bit start_flag;

bit change_flag;

unsigned char servo_counts;



//-----------------------------------------------------------------------------

// MAIN Routine

//-----------------------------------------------------------------------------



void main (void)

{

// Disable Watchdog timer

PCA0MD &= ~0x40; // WDTE = 0 (clear watchdog timer enable)

SYSCLK_Init (); // initialize oscillator

PORT_Init (); // initialize crossbar and GPIO

Timer2_Init ();

Timer0_Init ();

start_flag=0;

change_flag=0;

servo_counts=0;

EA=1;



while (1)

{

if (start_flag)

{

start_flag=0;

SERVO1_PWM;

Timer2_Switch(MID_ANGLE);



while (change_flag==0);

change_flag=0;

SERVO2_PWM;

Timer2_Switch(MID_ANGLE);



while (change_flag==0);

change_flag=0;

SERVO3_PWM;

Timer2_Switch(MID_ANGLE);



while (change_flag==0);

change_flag=0;

SERVO4_PWM;

Timer2_Switch(MID_ANGLE);



while (change_flag==0);

change_flag=0;

// add new servo here



}

}

}



//-----------------------------------------------------------------------------

// Initialization Subroutines

//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------

// SYSCLK_Init

//-----------------------------------------------------------------------------

//

// This routine initializes the system clock to use the internal 24.5MHz

// oscillator as its clock source. Also enables missing clock detector reset.

//

void SYSCLK_Init (void)

{

OSCICN |= 0x03; // configure internal oscillator for

// its maximum frequency}

RSTSRC = 0x04; // enable missing clock detector

}



//-----------------------------------------------------------------------------

// PORT_Init

//-----------------------------------------------------------------------------

//

void PORT_Init (void)

{

XBR0 = 0xff;

XBR1 = 0x00;

XBR2 = 0x40;

P0MDOUT = 0xff;

P0MDIN = 0xff;

P0 = 0;

}



//-----------------------------------------------------------------------------

// Timer0_Init

//-----------------------------------------------------------------------------

// 20mS Timer

//

void Timer0_Init (void)

{

TCON &= 0xcf; // Stop Timer0; Clear TF0

CKCON &= ~0x08; // Timer0 uses SYSCLK/12

TMOD |= 0x01; // Timer0 in 16-bit mode

TH0 = TMH; // 20mS interrupt

TL0 = TML;

ET0 = 1; // enable Timer0 interrupts

TR0 = 1; // Timer0 enabled

}



//-----------------------------------------------------------------------------

// Timer0_ISR

//-----------------------------------------------------------------------------

//

//

void Timer0_ISR (void) interrupt 1 // wheel signal off

{

TF0 = 0;

TH0 = TMH;

TL0 = TML;



start_flag=1;

servo_counts=0;

}

//-----------------------------------------------------------------------------

// Timer2_Init

//-----------------------------------------------------------------------------

// PWM Timer

//



void Timer2_Init (void)

{

TMR2CN = 0x00;

CKCON |= 0x20;

ET2 = 0;

TR2 = 0;

}



void Timer2_Switch (unsigned int counts)

{

TMR2 = -counts; // set to reload immediately

ET2 = 1; // disable Timer2 interrupts

TR2 = 1;

}

//-----------------------------------------------------------------------------

// Timer2_ISR

//-----------------------------------------------------------------------------

//



void Timer2_ISR (void) interrupt 5

{

TF2H = 0; // clear Timer2 interrupt flag

TR2 = 0;

P0 = 0;

change_flag=1;

}





看完一大片程式碼

有沒有眼花撩亂

沒關係

改看看輸出的波形測試



1977649456

程式中的Timer0是用來產生歸零動作的

也就是所謂的20mS信號

不過因為內部振盪器的誤差

所以實際值只有19.7mS

這個值是可以調整的

只是因為我的需求到這樣就夠了

所以就沒有再修過



1977649457

看看這一張

非常準確的1.5mS

代表信號是真的可以修得非常準的

只是需不需要修到這麼準罷了!!

這個信號是用MID_ANGLE的參數所產生的波形



1977649458

而這一張0.5mS(500uS)

則是用MIN_ANGLE所產生的波形



1977649459

再來看一張2.5mS

是用MAX_ANGLE所產生的波形





1977649460

為了看看輪替輸出的樣子

把所有的測棒通通都接上控制板



1977649461

這就是最後輸出的樣子

因為只有四根測棒

所以先開4CH的PWM給大家看看就好



其實這一類的程式

很多網頁或書籍都有說明跟範例

不過絕大多數會用同一個埠來輪替輸出

以運算的方式強制從該埠的第0腳到第7腳輸出

然而對於腳位較少

而且腳位分布不固定的MCU

(例如一個埠只有0,3,4,6,7這幾隻腳)

這樣的寫法會比較有彈性

只要每增加一個 SERVO

在程式的最前方宣告一下

#define SERVOX_PWM SERVO1=1

sbit SERVOX=腳位;


然後再到主程式裡

標記// add new servo here的地方

再加上一段程式碼

************************************

SERVOX_PWM;

Timer2_Switch(MID_ANGLE);



while (change_flag==0);

change_flag=0;

************************************


這樣就可以再新增一組控制項(多一組PWM出來)

其中MID_ANGLE的參數是可以修正的

對應到的是實際需求的角度信號



有了這個基本功能後

接下來就要加入通訊功能

這樣才有辦法透過PC來控制多變的機械手臂動作

敬請期待~~~



ps.

最近工作比較忙

沒辦法天天更新

請大家多多包涵

20 則留言:

  1. 大大,請問一下,這可以移植到2051或8051嗎?

    回覆刪除
    回覆
    1. 2051有兩個Timer
      如果要移植的話
      串列的Timer1就先犧牲了
      用剩下的拆成一個8bit 與一個16bit
      理論上還是可行
      只是解析度就會變差很多
      而且就沒辦法擴充其它功能了

      8051跟2051差不多
      只是IO多了一點
      建議是用8052會比較好
      那就應該可以整個調整過去
      因為有Timer0,1,2三組可用
      但一樣還是會有解析度的問題

      刪除
  2. keil c跟普通的c是一樣的嗎?

    不太懂XD

    回覆刪除
    回覆
    1. C有分寫給低階韌體用的
      也有寫給高階應用程式用的

      Keil C是針對MCU開發專用的工具
      而VC或BCB是一般PC介面用的工具
      大概就這樣囉

      差別只在於編譯器不同
      語法是一樣的

      刪除
  3. 請問一下 我們是XX科技大學的學生



    我們想知道你是如何製作你的手臂(尤其是軟體的部分)



    我們與您一樣是使用keil C 的程式撰寫 但我們對程式一竅不通



    方便留一下你的E-MAIL或是電話 我們想要依依地請教你

    回覆刪除
    回覆
    1. 在這裡留言就可以了 ~~~
      不方便公開的話就用悄悄話

      對程式不懂的話
      建議先看一下Keil C的說明文件 (C51.pdf)
      軟體的部分其實分兩大部分
      一個是手臂本體 (就是你要問的部分)
      另一個是PC端的控制程式 (就是最後要用電腦控制的部分)
      看你的完成度要到哪~~~

      如果想學快一點
      右邊有個 "小鯨魚的機器人" 的連結
      裡面的小鯨魚是專職的程式教師
      也是專門在做機器人與機械手臂的
      直接與他聯繫會更快得到你想知道的答案唷~~~~

      不過有任何問題都還是隨時歡迎來這裡與我交流
      祝你順利~~~

      刪除
  4. 板大 我想請問一下 因為我完全沒有單晶片基礎 有學過c 市面上有哪本書 有詳細解說並有範例

    由淺入深 完全用c去撰寫的呢?? 找了好幾本 裡面大部分都適用組合語言去寫的 c的部分提的很

    少...看完也沒有範例可以練習..

    回覆刪除
    回覆
    1. PIC真的比較多人寫組語
      因為指令比較少
      如果用C寫
      大概不能只看PIC的書
      而是要上網找範例

      一般來說
      單晶片的C跟電腦上的C其實差不多
      最大的差異是在Head檔的引用與暫存器的用法
      假如你已經有C的基礎
      那麽你只要熟悉暫存器的功能就可以了
      這個看組語的書會更好
      不須要去看指令
      只要看他對暫存器如何設定
      用C來寫的話
      就是把暫存器當成變數
      指定一個值給它的話就是用"="就可以了
      比方說 ACC=1;
      就是把累加器的值設定為1

      之所以很少有全部用C來寫的書
      是因為再怎麼少
      有一些動作還是要用組語來替代
      單純用C轉不出某些碼
      你就慢慢習慣吧~~~
      剛開始都會比較不好懂
      多看幾遍就會了

      刪除
  5. 上一篇忘記打name.. 也忘記提到 用的pic是18f8720

    回覆刪除
    回覆
    1. 原廠其實蠻多用C寫的範例
      你不妨去下載看看
      針對每個功能應該都有對應的C code寫法
      拿那個來改看看
      應該會比較容易進入狀況
      祝你順利囉~~~

      看C的範例不要局限在特定的IC編號上
      因為用C寫的差異並不大
      不一樣的只有暫存器的名稱跟用法而已
      語法跟運算看起來應該都是一樣的

      刪除
  6. 你好~我在網路有找到 先生好像有



    全華 機器人 單晶片微電腦控制 書的東西



    可不可以麻煩跟你拿一下 光碟裡面的程式

    回覆刪除
    回覆
    1. 印象中我的好像只有附一片PCB
      程式都自己Keyin
      所以也不記得是不是有光碟
      我再找看看好了
      說不定是忘了丟在哪裡了
      找到再跟你說

      BTW
      我的書是綠色面的
      跟現在的藍色面不太一樣
      因為我這系列文章是四年前寫的了
      所以可能東西已經變化不少了~~~
      你是因為光碟丟了?
      還是沒有買書呀?

      刪除
  7. 板大您好,我想請問一下有關於伺服馬達的問題

    在書上說明的週期為20~30ms,脈波寬度為0.8~2.2ms

    如果用us為單位的話,會動作嗎? 會對馬達造成傷害嗎?

    2者的差異在哪裡呢?

    回覆刪除
    回覆
    1. servo的種類很多
      常見的RC servo必須送連續信號給他
      如果沒有連續送就會自動放棄鎖角度
      這個週期就是你說的20~30mS
      控制角度就是0.8~2.2mS
      對應的角度一般是-60~+60度
      中高階的會到-90~+90度
      特別一點的有-110~+110度及360度連續運轉
      不過控制信號大多落在0.5mS~2.5mS之間

      如果你用的是uS為單位
      其實就是800uS~2200uS
      這樣的條件是相等的
      但是如果你送0.8uS~2.2uS
      那馬達收到這樣的指令是看不懂的
      所以會怎麼動作並不曉得

      有些馬達會利用<0.5mS的時間當做設定參數 (很高階的馬達)
      如果你的信號剛好落在這個範圍
      可能會不小心把出廠設定改掉
      大概是這樣囉~~~

      刪除
  8. 板大 伺服機訊號我送進週期25ms 脈波寬度1ms

    訊號從pic經過741放大 從5v-- 7v

    供給電壓6v

    週期從0.8~2.2ms都調整過 都沒動靜..

    用手去碰轉軸它會前後轉動 轉動幅度沒有很大 不碰就甚麼動作都沒有..

    伺服機的地與741共用 這樣會有影響嗎?

    還是說這顆已經陣亡了呢??

    回覆刪除
    回覆
    1. 週期如果是0.8~2.2mS
      那一定動不了
      如果你的週期25mS, 脈寬1mS連續送40次(約一秒)
      再送週期25mS, 脈寬2mS連續送40次(約一秒)
      這樣看看能不能動

      再來為什麼要放大信號?
      servo的輸入信號通常是TTL準位
      所以2V以上就算High
      0.8V以下就算Low
      信號應該不用經過放大
      放大信號準位反而會讓servo死掉
      (一般IC最多承受到6V就不行了)
      電源的電壓在servo內部會經過降壓才到IC
      可是信號應該是直接接到IC上的
      你對servo的認識可能有點錯誤唷~~~
      再試試看吧
      如果不能動應該是控制板被過高的電壓打壞了
      那就得換一個servo了......

      刪除
  9. 板大 想請問一下 如果要讓馬達連續動作 左轉到底--中立--右轉到底 每個動作都隔一秒 這樣子循環的話

    那我要給馬達的信號是 0.8mS的1秒--1.5mS的1秒--2.2mS的1秒--1.5mS的1秒--0.8mS的1秒

    還是 0.8mS1個--delay1秒--1.5mS1個--delay1秒--2.2mS1個--delay1秒--1.5mS1個

    不太了解書上的意思

    回覆刪除
    回覆
    1. servo每20mS就一定要有一個信號給他
      如果delay了1秒才送
      那剩下的980mS都沒有信號
      servo應該是完全不會動才對

      正確的作法是
      0.8mS的重覆送50次 (20x50=1000mS=1S)
      1.5mS的再重覆送50次
      2.2mS的接著送50次
      以此類推

      這樣才是正確的servo信號

      不好意思回覆得較晚
      希望這樣說明有解決您的問題

      刪除
  10. 板大 目前手臂動作都有了可是會有奇怪的抖動 該如何解決馬達抖動問題呢?

    是硬體組裝上的誤差?? 還是軟體有問題呢??

    另外 一顆馬達配一個訊號嗎? 可以兩顆馬達一個訊號嗎?

    回覆刪除
    回覆
    1. 會抖動有很多原因
      如果是馬達本體的問題
      靜止時就會抖動
      那要克服就很難
      只能換馬達

      如果是運動時才會抖動
      那就要看看哪個關節的結構需要加強
      或是運動的速度需要調整
      很可能是運動慣量超過馬達的負載所造成的抖動

      一顆馬達原則上只能一個控制信號
      即便是同步的信號在同廠牌的馬達上
      還是會有些許的角度誤差
      這個誤差會讓本來應該同步互相分擔負載的馬達
      變成互相消力加重負載
      如果不是同軸且負載不重就沒差

      我的demo影片其中一個就是一個控制信號控制4~5個servo
      不過每次的移動量都很小

      刪除