2015年5月16日土曜日

STM32でモータ制御1

STM32F4でコアレスモータmk06を回してみた。


PB4、PB5にロータリーエンコーダを接続して、TIM3の位相計数でカウントしています。
モータはPE9、PE11に接続したモータドライバBD6211を介して行ないます。PE9、PE11はTIM1のPWM出力で動作します。

すんなりとはいかなかったところは以下。


1.符号なし変数から符号あり変数へのキャスト。

ロータリーエンコーダを使って、カウント動作させてみたのだけれど、STM32のレジスタは符号なしで定義されているので、負の値がそのまま使えない。
そこで、何も考えずにC言語の型のキャストを使ってみたのだけれど、うまくはいかなかった。

int16_t counter = (int16_t)  TIM3->CNT;

これで、符号なし16bitデータを符号付16bitデータに出来たと思って、printfデバッグしてみた。

printf("counter : %d " , counter);

この値が、常に正の数で、デクリメントしても負の数にならない。0の下が65535になってしまう。
仕方ないので、最上位bitが1の時は、2の補数を取ることで、負の数にしている。

本当はもっときれいな方法があるのだろうけれど・・・。



2.TIM1(高機能タイマ)の動作

 汎用タイマのTIM3などを使うときは必要ないのだけれど、高機能タイマのTIM1を出力で使用するときは、以下のコマンドが必要。

TIM_CtrlPWMOutputs(TIM1,ENABLE);    // 高機能タイマ専用

詳しく追いかけていないけれど、必要だということで書いておく。



3.BD6211の制御論理

説明の前に、まずはモータドライバICのBD6211のデータシートを紹介(秋月リンク)

このドライバを使ってPWM制御する際には、PWM制御モードAとPWM制御モードBというのがある。詳しいことはデータシートを見てほしいのだけれど、真理値表を見るとPWMで制御しないほうのピンをH固定するか、Lこてするかの違いに見えた。
その程度の違いなら、PWMモードAでいいかなと思い、ソフトのコーディングをした。
コンパイルエラーもなくしたところで、もう一度データシートを見ていたら、PWM制御モードAだと、PWM周期が20kHz以下だと途中でスタンバイモードになるため、理想どおりの制御にならないことがあると書かれている。
今回は、84kHz周期で使う予定なので、問題ないのだけれど、やはり気になるので、PWM制御モードBに変更した。
といっても、この段階では使っていないほうのピンをH固定からL固定にしただけ。

そして通電。

リセット直後は、信号を出していないのでモータは空転状態。そして、ロータリーエンコーダを一回まわすと、勢いよく回り始めた。なんだか全力で。これがコアレスモータの性能なのかと思いながらもロータリーエンコーダをまわし続けたら、次第にモータの元気がなくなってきた。

・・・あれ?

熱か電流でモータにダメージでも与えてしまったのかと思い、リセット。
気を取り直してエンコーダを回すと勢いよく回る。

ここで一呼吸おいて、よく考えてみたら当然のことだった。PWM制御モードをAからBに変更したときに、PWM制御してないほうの端子の論理を反転したのに、PWM信号自体はそのままだったので、最小幅のPWMで制御しているつもりが、最大幅になっていたのだ。すぐにTIM1のPWM出力の論理も反転することで、思ったとおりにゆっくりスタートのモータ制御が出来た。




ソースコードの表示は、以下をクリック。(書きなぐりでまとめてないので、汚いです。)



#include "stm32f4xx.h"
#include \"stm32f4xx.h\"
#include \"mcp23s17.h\"
#include \"uart.h\"
#include \"stm32f4dis.h\"

#define PWM_HIGH  249

/*
  time : 約1uSec,1000000=1sec
*/
void delay(uint32_t time){
  uint32_t t;
  volatile uint8_t i;
  
  for(i=0;i<24;i++){
    t = time;
    while(t--);
  }
} 

//
void spi2_init(void){
  // structure to initialize GPIO
  GPIO_InitTypeDef GPIO_InitStructure;
  
  // GPIOB , SPI2 のクロックを有効化
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;  // PB13(SCK),PB14(MISO),PB15(MOSI)
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_Init(GPIOB,&GPIO_InitStructure);

 //GPIOBのPIN13-15をオルタネィテブファンクションのSPI2に割り当て
  GPIO_PinAFConfig(GPIOB , GPIO_PinSource13 , GPIO_AF_SPI2);
  GPIO_PinAFConfig(GPIOB , GPIO_PinSource14 , GPIO_AF_SPI2);
  GPIO_PinAFConfig(GPIOB , GPIO_PinSource15 , GPIO_AF_SPI2);

  // PB12 はSPI2のCSとして使用
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;        // PB12(NSS)
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_Init(GPIOB,&GPIO_InitStructure);

  // SPI2 設定
  SPI_InitTypeDef SPI_InitStructure;
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  SPI_InitStructure.SPI_Mode      = SPI_Mode_Master;
  SPI_InitStructure.SPI_DataSize  = SPI_DataSize_8b;
  SPI_InitStructure.SPI_CPOL      = SPI_CPOL_High;
  SPI_InitStructure.SPI_CPHA      = SPI_CPHA_2Edge;
  SPI_InitStructure.SPI_NSS       = SPI_NSS_Soft;
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
  SPI_InitStructure.SPI_FirstBit  = SPI_FirstBit_MSB;

  SPI_Init(SPI2 , &SPI_InitStructure);
  SPI_Cmd(SPI2,ENABLE);
}


// PB4 : encoder input
// PB5 : encoder input
void tim3_encoder_init(void){
    /* GPIOA Periph clock enable */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  
    GPIO_PinAFConfig(GPIOB , GPIO_PinSource4 , GPIO_AF_TIM3);
    GPIO_PinAFConfig(GPIOB , GPIO_PinSource5 , GPIO_AF_TIM3);

    /* Configure PA0 PA1*/
    GPIO_InitTypeDef  GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* TIM3 clock enable */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    /* TIM3 encoder mode enable*/
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI2
         ,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
    TIM_Cmd(TIM3, ENABLE);  


}

int32_t enc1_Read(void)
{
  uint16_t cnt;
  int32_t ret;
  
  cnt = TIM3->CNT;
  
  if(cnt & 0x8000){
    cnt = ~cnt + 1;
 ret = (int32_t)(cnt * (-1));
  }else{
    ret = (int32_t)cnt;
  }

  return ret;
}

void enc1_Clear(void)
{
    TIM3->CNT = 0;
}


void tim1_pwm_init(void){

  // --------------------------------------------------
  // Port E 設定(PE9,PE10 PWM 出力) ここから
  
  // GPIOE クロックの有効化
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);

  // PE9,PE10 : 
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9|GPIO_Pin_11;
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
  GPIO_Init(GPIOE,&GPIO_InitStructure);
  
  //GPIOEのPIN9,PE10をオルタネィテブファンクションのTIM1に割り当て
  GPIO_PinAFConfig(GPIOE , GPIO_PinSource9  , GPIO_AF_TIM1);
  GPIO_PinAFConfig(GPIOE , GPIO_PinSource11 , GPIO_AF_TIM1);
  // Port E 設定(PE9,PE10 PWM 出力) ここまで
  // --------------------------------------------------
  

  // --------------------------------------------------
  // TimeBase 設定 ここから
  // TIM1 クロックの有効化
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

  //   TIM1 clock = SystemCoreClock / 2 = 168 MHz /2 = 84 MHz
  TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;
  TIM_TimeBaseStructure.TIM_Prescaler     = 4;               // 84 MHz / 4 = 21,000 kHz
  TIM_TimeBaseStructure.TIM_Period        = PWM_HIGH;              // 周期設定
  TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;  // カウントアップモード
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;        // インプットキャプチャ用設定(未使用)
  TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure);
  // TimeBase 設定 ここまで
  // --------------------------------------------------
  
  
  // --------------------------------------------------
  // アウトプット・キャプチャ設定 ここから
  //PWM1 Modeの設定・チャネル1,2
  TIM_OCInitTypeDef TIM_OCInitStructure;
  TIM_OCStructInit(&TIM_OCInitStructure);
  
  TIM_OCInitStructure.TIM_OCMode      = TIM_OCMode_PWM1;         // PWM1 モード
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // タイマ出力を有効
  TIM_OCInitStructure.TIM_Pulse       = 0;                     // Duty 設定
//  TIM_OCInitStructure.TIM_Pulse       = 0;                     // Duty 設定
  TIM_OCInitStructure.TIM_OCPolarity  = TIM_OCPolarity_Low;     // アクティブ時 出力High
  // ch1
  TIM_OC1Init(TIM1, &TIM_OCInitStructure);
  TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);             // プリロード
  // ch2
  TIM_OC2Init(TIM1, &TIM_OCInitStructure);
  TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);             // プリロード
  // アウトプット・キャプチャ設定 ここまで
  // --------------------------------------------------

  TIM_CtrlPWMOutputs(TIM1,ENABLE);    // 高機能タイマ専用
  TIM_Cmd(TIM1,ENABLE);               // TIM1 有効
}

void set_motor1(int16_t pwm_cw, int16_t pwm_ccw){
  TIM1->CCR1 = pwm_cw;
  TIM1->CCR2 = pwm_ccw;
}

int main(void)
{
  /* structure to initialize GPIO */
//  GPIO_InitTypeDef GPIO_InitStructure;
  int32_t enc1_data = 0;

  SystemInit();
  stm32f4_init();

  spi2_init();
  uart2_init(9600);
  tim3_encoder_init();
  enc1_Clear();
  
  tim1_pwm_init();
  set_motor1(0,0);
  set_motor1(0,0);

  delay(10000);
  printf2(\"\\r\\n\");
  printf2(\"motor PWM test %d \\r\\n\",-100);

  mcp23s17_init(SPI2);
  mcp23s17_wr(SPI2,MCP23S17_IODIRA,0x00);  // GPA output 
  mcp23s17_wr(SPI2,MCP23S17_GPIOA,~0x00);    delay(100000);
  while(1){
   enc1_data = enc1_Read();
      printf2(\"enc : %8d \\r\\n\",enc1_data);

   if(enc1_data>0){
     if(enc1_data>PWM_HIGH) enc1_data = PWM_HIGH;
     set_motor1(enc1_data , 0);
        mcp23s17_wr(SPI2,MCP23S17_GPIOA,(uint16_t)~enc1_data);
   }else if(enc1_data<0){
     enc1_data = enc1_data * (-1);
     if(enc1_data>PWM_HIGH) enc1_data = PWM_HIGH;
     set_motor1(0,enc1_data);
        mcp23s17_wr(SPI2,MCP23S17_GPIOA,(uint16_t)~enc1_data);
   }else{
     set_motor1(PWM_HIGH,PWM_HIGH);
        mcp23s17_wr(SPI2,MCP23S17_GPIOA,(uint16_t)~enc1_data);
   }
   
    if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)){
      //ボタンが押されて1になっていたら以下を実行
      GPIO_SetBits(GPIOD,GPIO_Pin_13);       // 点灯
   while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0));  // ボタンを放したら以下を実行
   
    }else{
      GPIO_ResetBits(GPIOD,GPIO_Pin_13);     // 消灯
    }
  }
  return 0;
}


0 件のコメント:

コメントを投稿