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