Featured image of post RL78/G10ファミリR5F10Y47ASPでマイクロサーボSG92Rを動かしてみた。

RL78/G10ファミリR5F10Y47ASPでマイクロサーボSG92Rを動かしてみた。

以前にSTM32L010F4P6マイコンでSG92Rを動かしたが、同じことをR5F10Y47ASPマイコンでやってみる。

使うもの

マイクロサーボのデータシートを確認する

マイクロサーボSG92Rのデータシートそのものが見つからなかったので,
マイクロサーボ9g SG-90 のデータシートを参照する。

SG-90_ds

Specifications

  • Operating voltage: 3.3V(~6V)
  • 20ms(50Hz) PWM Period
  • 0.5 ~ 2.4ms Duty Cycle
  • 4.8V(~5V) Power and Signal

とのこと。

つまりサーボコネクタの赤色線を電源の+に, 茶色線をGNDに接続して橙色線に図に書かれている信号を入力すると動く。

e2 studioで新規プロジェクトを作る。

回路を作るのは後回しにして、e2 studioを起動して新規プロジェクトを作る。

新規 -> Renesas C/C++ Project -> Renesas RL78 から
Renesas CC-RL C/C++ Executable Project を選択して
ターゲット・デバイス: R5F10Y47 を選択して
Use 周辺コード作成にチェックを入れてプロジェクトを作る。

これはそのまま確定する。 e2studio01

クロックは基盤に20MHzセラロックが載っているが、今回はオンチップオシレータ20MHzを選択する。 e2studio02

周辺機能のシリアル・インターフェースIICAの転送モードはシングルマスタ e2studio03

設定はそのままでいい e2studio04

PWMのためにタイマーチャネル0をPWM出力(マスタ)、タイマーチャネル1をPWM出力(スレーブ)にする。
ロータリーエンコーダ読み取りとディレイのためにタイマーチャネル2をインターバル・タイマにする。 e2studio05

チャネル0の周期は20ms e2studio06

サーボの位置0度はデーターシートに書いてある通りオンデューティ1.45msで周期は20msだから

$$ \frac{1.45}{20} \times 100 = 7.25 \% $$

チャネル1のデューティは7.25% e2studio07

チャネル2の周期は125us(この値は適当に) e2studio08

ロータリーエンコーダを接続するポートP00, P01を入力に設定して内蔵プルアップにチェックを入れる。 e2studio09

保存して「コードを生成する」をクリックする。

端子配置図を確認する。 e2studio10

VSS(7番ピン)をGND
VDD(8番ピン)を5V
TOOL0(2番ピン)を書き込み器
RESET#(3番ピン)を書き込み器
SCLA0(15番ピン)を液晶モジュールのSCL
SDAA0(16番ピン)を液晶モジュールのSDA
TO01(13番ピン)をサーボのPWM入力(橙色)
P00(9番ピン)をロータリーエンコーダのB相出力
P01(10番ピン)をロータリーエンコーダのA相出力
に接続する。
(P00/P01はマイコン内臓プルアップをSCL/SDAは液晶モジュールの変換基盤に載っているプルアップを利用した)

あと
サーボのVCC入力(赤色)は5V
サーボのGND入力(茶色)はGND
ロータリーエンコーダのC相(common)はGND

image01

プロジェクト設定

プロジェクト設定は 前前回と同じ。

GitHubリポジトリ

https://github.com/ak1211/R5F10Y47_servo

コード

詳細はGitHubリポジトリ で確認してください。

/src/cg_src/r_cg_userdefine.h

1
2
3
4
5
6
/* Start user code for function. Do not edit comment generated here */
#include <stdint.h>
#include <stdbool.h>
extern volatile bool gMILLISECONDS_TIMER_EVENT_OCCURRED_FLAG;
extern volatile int8_t gROTATION_COUNTER;
/* End user code. Do not edit comment generated here */

タイマユニット /src/cg_src/r_cg_tau_user.c

1
2
3
/* Start user code for include. Do not edit comment generated here */
#include <stdbool.h>
/* End user code. Do not edit comment generated here */

ロータリーエンコーダ読み取り部分はchanさんのサイトを参考にした

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/* Start user code for global. Do not edit comment generated here */
volatile bool gMILLISECONDS_TIMER_EVENT_OCCURRED_FLAG = false;
//
#define INITIAL_TIMER_EVENT_FLAGS (0xFFU)
volatile static uint8_t gTIMER_EVENT_FLAGS = INITIAL_TIMER_EVENT_FLAGS;
//
#define MIN_ROTATION_COUNT  (-90)
#define MAX_ROTATION_COUNT  (90)
volatile int8_t gROTATION_COUNTER = 0;
//
static inline void countup_rotation_counter(void) {
    if (gROTATION_COUNTER < MAX_ROTATION_COUNT) {
        gROTATION_COUNTER++;
    }
}
//
static inline void countdown_rotation_counter(void) {
    if (gROTATION_COUNTER > MIN_ROTATION_COUNT) {
        gROTATION_COUNTER--;
    }
}
#define ENCORDER_B_BIT  (P0_bit.no0)
#define ENCORDER_A_BIT  (P0_bit.no1)
#define ENCORDER_AB_BIT  (P0 & 3)
volatile static uint8_t gCAPTURED_ENCORDER = 0U;
//
static void sample_encoder(void) {
    static const int8_t dir[] = { 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1,
            1, 0 };
    static int8_t i;
    int8_t n;

    i = (i << 2) + ENCORDER_AB_BIT;
    n = dir[i & 15];
    if (n < 0) {
        countdown_rotation_counter();
    } else if (n > 0) {
        countup_rotation_counter();
    }
}

static void capture_encorder(void) {
    gCAPTURED_ENCORDER = (gCAPTURED_ENCORDER << 2) | ENCORDER_AB_BIT;
    gCAPTURED_ENCORDER &= 15;
    switch (gCAPTURED_ENCORDER) {
    case 0x7:
        countup_rotation_counter();
        break;
    case 0xd:
        countdown_rotation_counter();
        break;
    default:
        break;
    }
}
/* End user code. Do not edit comment generated here */
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void __near r_tau0_channel2_interrupt(void) {
    /* Start user code. Do not edit comment generated here */
    sample_encoder();
    // タイマー割り込みを8分周する
    gTIMER_EVENT_FLAGS <<= 1;
    if (gTIMER_EVENT_FLAGS == 0U) {
        gMILLISECONDS_TIMER_EVENT_OCCURRED_FLAG = true;
        gTIMER_EVENT_FLAGS = INITIAL_TIMER_EVENT_FLAGS;
    }
    /* End user code. Do not edit comment generated here */
}

/src/cg_src/r_cg_main.c

1
2
3
4
5
/* Start user code for include. Do not edit comment generated here */
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
/* End user code. Do not edit comment generated here */
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* Start user code for global. Do not edit comment generated here */
// I2C LCD (AE-AQM1602A) のターゲット(スレーブ)アドレス
#define AQM1602A_I2C_TARGET_ADDRESS (0x3e)

// I2C LCD (AE-AQM1602A) 用送信バッファ
#define I2C_SEND_BUFFER_SIZE (2U)
static uint8_t gI2C_SEND_BUFFER[I2C_SEND_BUFFER_SIZE];

// 1ミリ秒 × multiply 遅延
void delay_milliseconds(uint16_t multiply) {
	while (multiply-- > 0U) {
		gMILLISECONDS_TIMER_EVENT_OCCURRED_FLAG = false;
		while (!gMILLISECONDS_TIMER_EVENT_OCCURRED_FLAG) {
			HALT();
		}
		R_WDT_Restart();
	}
}

// ミリ秒delay
static inline void delay(uint16_t milliseconds) {
	delay_milliseconds(milliseconds);
}

// I2C LCD (AE-AQM1602A)にコマンドを送信する
void AQM1602A_send_command(const uint8_t command_byte) {
	gI2C_SEND_BUFFER[0] = 0x00;
	gI2C_SEND_BUFFER[1] = command_byte;
	R_IICA0_Master_Send(AQM1602A_I2C_TARGET_ADDRESS << 1, gI2C_SEND_BUFFER,
	I2C_SEND_BUFFER_SIZE, 128U);
	delay(10);
}

// I2C LCD (AE-AQM1602A)に1文字送信する
void AQM1602A_send_data(const uint8_t byte) {
	gI2C_SEND_BUFFER[0] = 0x40;
	gI2C_SEND_BUFFER[1] = byte;
	R_IICA0_Master_Send(AQM1602A_I2C_TARGET_ADDRESS << 1, gI2C_SEND_BUFFER,
	I2C_SEND_BUFFER_SIZE, 128U);
	delay(1);
}

// I2C LCD (AE-AQM1602A)に文字列を送信する
void AQM1602A_puts(const char *p) {
	while (*p) {
		AQM1602A_send_data(*p++);
	}
}

// I2C LCD (AE-AQM1602A) クリアディスプレイ
void AQM1602A_clear_display(void) {
	AQM1602A_send_command(0x01);    // clear display
	delay(10);
}

// I2C LCD (AE-AQM1602A) ホーム(左上)に移動する
void AQM1602A_return_home(void) {
	AQM1602A_send_command(0x02);    // return home
	delay(10);
}

// I2C LCD (AE-AQM1602A)の初期化
void AQM1602A_init(void) {
	AQM1602A_send_command(0x38);    // function set
	delay(20);
	AQM1602A_send_command(0x39);    // function set
	delay(20);
	AQM1602A_send_command(0x14);    // internal osc frequency
	delay(20);
	AQM1602A_send_command(0x7A);    // contrast lower
	delay(20);
	AQM1602A_send_command(0x54);    // contrast higher / icon / power
	delay(20);
	AQM1602A_send_command(0x6C);    // follower control
	delay(20);
	//
	AQM1602A_send_command(0x38);    // function set
	delay(20);
	AQM1602A_send_command(0x01);    // clear display
	delay(20);
	AQM1602A_send_command(0x0C);    // display on
	delay(20);
}

// 値を文字列にする
void to_string(char *dst, int16_t value) {
	if (value < 0) {
		*dst++ = '-';
		value = -value;
	} else {
		*dst++ = ' ';
	}
	uint8_t xs[5];
	int8_t idx = 0;
	do {
		xs[idx++] = '0' + value % 10;
		value = value / 10;
	} while (idx < sizeof(xs) && value != 0);
	while (--idx >= 0) {
		*dst++ = xs[idx];
	}
	*dst = '\0';
}

static void set_servo_angle(int8_t degrees) {
	if (degrees < -90 || 90 < degrees) {
		return;
	}
	// PWM出力の周期
	uint16_t pwm_period = (TDR00H << 8 | TDR00L) + 1;
	//
	// SG-90 / SG-92R
	// center: 1.45ms (on duty) / 20ms(period) * 100 = 7.25%
	// -90 deg: 0.5ms (on duty) / 20ms(period) * 100 = 2.5%
	// 90 deg: 2.4ms (on duty) / 20ms(period) * 100 = 12.0%
	//
	const float center = 1.45f / 20.0f;
	float on_duty;
	if (degrees == 0) {
		on_duty = center;
	} else {
		on_duty = center + ((2.4f - 0.5f) / 20.0f) * (float) degrees / 180.0f;
	}
	//
	uint16_t tdr = pwm_period * on_duty;
	TDR01H = tdr >> 8;
	TDR01L = tdr & 0xFF;
}
/* End user code. Do not edit comment generated here */
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void main(void) {
	R_MAIN_UserInit();
	/* Start user code. Do not edit comment generated here */
	// 100ms待つ
	delay(100);
	// LCDの初期化
	AQM1602A_init();
	// 1行目
	AQM1602A_puts("RL78 R5F10Y47ASP");
	//
	gROTATION_COUNTER = 0;
	while (1U) {
		// 2行目
		char message[17];
		strcpy(&message[0], "Angle = ");
		to_string(&message[8], gROTATION_COUNTER);
		strncat(message, " deg ", 5);
		//
		AQM1602A_send_command(0x80 | 0x40); // アドレス設定
		AQM1602A_puts(message);
		//
		set_servo_angle(gROTATION_COUNTER);
		delay(100);
	}
	/* End user code. Do not edit comment generated here */
}
1
2
3
4
5
6
7
static void R_MAIN_UserInit(void) {
	/* Start user code. Do not edit comment generated here */
	R_TAU0_Channel0_Start();
	R_TAU0_Channel2_Start();
	EI();
	/* End user code. Do not edit comment generated here */
}

Renesas Flash Programmerで書き込む

rfp01

実行

当たり前だけどデータシート通りですね。

サーボ角度-90度

サーボ角度-90度

  • PWM信号周期: 20ms
  • Duty Cycle: 0.500ms サーボ角度-90度

サーボ角度-45度

サーボ角度-45度

  • PWM信号周期: 20ms
  • Duty Cycle: 0.976ms サーボ角度-45度

サーボ角度0度(center)

サーボ角度0度

  • PWM信号周期: 20ms
  • Duty Cycle: 1.452ms サーボ角度0度

サーボ角度45度

サーボ角度45度

  • PWM信号周期: 20ms
  • Duty Cycle: 1.927ms サーボ角度45度

サーボ角度90度

サーボ角度90度

  • PWM信号周期: 20ms
  • Duty Cycle: 2.403ms サーボ角度90度
comments powered by Disqus

This website uses cookies to improve your experience.
このサイトは「Googleアナリティクス」を使用しています。
Googleアナリティクスはデータの収集のためにCookieを使用しています。


Built with Hugo
テーマ StackJimmy によって設計されています。