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 件のコメント:
コメントを投稿