2008年11月30日 星期日

機械手臂控制篇(6) -- 通訊測試MCU程式篇

終於要開始寫通訊了

相信這一段是很多人心中的痛

並不是通訊連不上

而是連上了卻不正確動作



其中有很多設定的部分是要重覆確認的(Double Check)

程式的中斷也是很重要的部分

更重要的是程式的編碼組成



為了簡化程式困難度

所以我採用四個Byte 的通訊

每個Byte以1~255來決定SERVO的角度

而0是一個特別指令

用來將信號歸零用

這個功能的優點

大家稍後就會知道了~~~



先來看看程式吧!!

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

// ROBO_ARM.c

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

// AUTH: Nichal

// DATE: 2008/11/30

//

// Target: C8051F30x

// 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 UART0_Init (void);

void UART0_ISR (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;

unsigned int S[4]={MID_ANGLE,MID_ANGLE,MID_ANGLE,MID_ANGLE};

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

// 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;

UART0_Init ();

EA=1;



while (1)

{

if (start_flag)

{

start_flag=0;

SERVO1_PWM;

Timer2_Switch(S[0]);



while (change_flag==0);

change_flag=0;

SERVO2_PWM;

Timer2_Switch(S[1]);



while (change_flag==0);

change_flag=0;

SERVO3_PWM;

Timer2_Switch(S[2]);



while (change_flag==0);

change_flag=0;

SERVO4_PWM;

Timer2_Switch(S[3]);



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

}



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

// UART0_Init

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

//

// Configure the UART0 using Timer1, for and 8-N-1.

//

void UART0_Init (void)

{

SCON0 = 0x10; // SCON0: 8-bit variable bit rate

// level of STOP bit is ignored

// RX enabled

// ninth bits are zeros

// clear RI0 and TI0 bits

if (SYSCLK/BAUDRATE/2/256 < 1) {

TH1 = -(SYSCLK/BAUDRATE/2);

CKCON |= 0x10; // T1M = 1; SCA1:0 = xx

} else if (SYSCLK/BAUDRATE/2/256 < 4) {

TH1 = -(SYSCLK/BAUDRATE/2/4);

CKCON |= 0x01; // T1M = 0; SCA1:0 = 01

CKCON &= ~0x12;

} else if (SYSCLK/BAUDRATE/2/256 < 12) {

TH1 = -(SYSCLK/BAUDRATE/2/12);

CKCON &= ~0x13; // T1M = 0; SCA1:0 = 00

} else {

TH1 = -(SYSCLK/BAUDRATE/2/48);

CKCON |= 0x02; // T1M = 0; SCA1:0 = 10

CKCON &= ~0x11;

}



TL1 = 0xff; // set Timer1 to overflow immediately

TMOD |= 0x20; // TMOD: timer 1 in 8-bit autoreload

TMOD &= ~0xD0; // mode

TR1 = 1; // START Timer1

IP |= 0x10; // Make UART high priority

ES0 = 1; // Enable UART0 interrupts

}

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

// UART0_ISR

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

//

//

void UART0_ISR (void) interrupt 4

{

unsigned char x;



if(RI0)

{

RI0=0;

x=SBUF0;

if (x)

{

S[servo_counts]= ((unsigned int)(x)*190) + 12980;

servo_counts++;

if (servo_counts>3) servo_counts=0;

}

else servo_counts=0;

}



if (TI0) TI0=0;

}



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

// PORT_Init

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

//

void PORT_Init (void)

{

XBR0 = 0xdf;

XBR1 = 0x02;

XBR2 = 0x40;

P0MDOUT = 0xdf;

P0MDIN = 0xff;

P0 &= 0xf0;

}




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

// 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;

}

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

// 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 &= 0xf0;

change_flag=1;

}





顏色與眾不同的

就是新加入或修改的部分

接下來談一談為啥要這樣規劃程式





1. 中位點的運算



大家都知道

所謂的一個Byte

指的是8 bit

由2的八次方來看

它可以代表1~256

然而實際上在MCU裡

並沒有256這個值

它所對應的是0~255



以中位點來說

(0+255)/2=127.5

如果程式碼要決定中位點

必須送出一個127.5的值

請問大家這個值要怎麼送???



答案是

我也不會!!! ^+++++++^



因為MCU的每一個值都只能定義整數

小數點是我們自己幫它加上去的!!!

如果要提高到127.5

就必須多一個Byte讓它變成65535之後

以1275去加小數點



為了讓中位點更中位

所以我用1~255來表示

又最大與最小值的間隔是62200-12400=49800

以49800/256會得到 194.xxxxxx

所以我直接取190做為每一個count的時間間隔

在UART0_ISR的程式中可以看到





2. 預防指令誤傳的法寶



同樣在UART0_ISR的程式中

有一個if (x)..... else servo_counts=0;

為什麼要加這個動作呢?



我們先分析一下這個UART的中斷裡

SERVO的值是如何被改變的:



首先是servo_counts=0

所以當你收到第一個Byte的資料後

你會把資料填在S[0]裡面

接著把servo_counts加1

再接收下一個byte

放在S[1]....

依序放完4個Byte後

servo_counts會自己歸零

完美的寫法!!!

????



如果不小心多送一個byte

會怎樣???

那災難就會發生了!!!!

因為往後所有的信號都會錯開一個Byte

送給第一個servo的信號會被第二個servo用

.......



你或許會問

怎麼可能會多送一個Byte?



我也覺得不可能

可是有時候因為信號線被誤觸

多一個或少一個信號是常有的事

這時候系統就要夠聰明

多一個防呆動作

利用0來將信號歸零後再繼續動作

確保就算這一次不小心送錯了

下一次也絕對不要一直錯下去





3. 其它部分



原則上多了一個數據陣列

用來儲存SERVO的角度

預設是停在中位點

大家看一下宣告就知道了



IO的設定要小心

F300在這方面很講究

設錯了就不會動作



大致上就這樣囉~~~

下一篇再來看看如何動作的

6 則留言:

  1. hi nichal,

    有個問題,大大的程式是用在晶片上,而且用通訊程式就可以連線控制伺服機的動作嗎?

    還有,可不可下載程式來看看啦。因為不能按右鍵來複製啦。

    這個部落格上的程式都是這樣囉。

    回覆刪除
    回覆
    1. 是呀~~~
      只要這樣就可以動作了
      夠簡單吧
      原來我所用的PWM測試儀
      就是這樣做的呀!!!

      程式我可以直接丟給您
      請問一下您的MSN是?

      對了
      我的程式只能用在F300唷
      跟8051的原腳位不相容
      所以要移植的話需要重新寫過

      ps.
      剛剛把程式寄到您學校的信箱了
      開看看吧

      刪除
  2. 你好



    我現在想開發一個產品

    我現在需要找位會寫程式,有MCU經驗和有打版能力的EE

    如果有興趣配合的話麻煩你和我連絡

    我的email是hello@amideas.com





    謝謝

    Helen Chen

    回覆刪除
    回覆
    1. Helen 您好

      請問您想開發哪一類的產品呢?
      只要有幫得上忙的地方
      基本上我都很樂意啦!!

      刪除
  3. 請問

    SERVO1_PWM;

    的用意是甚麼? 因為我沒學過keil C所以看不太懂

    回覆刪除
    回覆
    1. 只是一個定義
      在最上面有#define的設定
      意思是第一組SERVO的PWM開始輸出

      刪除