以前にSTM32L010F4P6マイコンでSG92Rを動かしたが、同じことをR5F10Y47ASPマイコンでやってみる。
使うもの
マイクロサーボのデータシートを確認する
マイクロサーボSG92Rのデータシートそのものが見つからなかったので,
マイクロサーボ9g SG-90 のデータシートを参照する。
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 周辺コード作成にチェックを入れてプロジェクトを作る。
これはそのまま確定する。
クロックは基盤に20MHzセラロックが載っているが、今回はオンチップオシレータ20MHzを選択する。
周辺機能のシリアル・インターフェースIICAの転送モードはシングルマスタ
設定はそのままでいい
PWMのためにタイマーチャネル0をPWM出力(マスタ)、タイマーチャネル1をPWM出力(スレーブ)にする。
ロータリーエンコーダ読み取りとディレイのためにタイマーチャネル2をインターバル・タイマにする。
チャネル0の周期は20ms
サーボの位置0度はデーターシートに書いてある通りオンデューティ1.45msで周期は20msだから
$$
\frac{1.45}{20} \times 100 = 7.25 \%
$$
チャネル1のデューティは7.25%
チャネル2の周期は125us(この値は適当に)
ロータリーエンコーダを接続するポートP00, P01を入力に設定して内蔵プルアップにチェックを入れる。
保存して「コードを生成する」をクリックする。
端子配置図を確認する。
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
プロジェクト設定
プロジェクト設定は 前前回と同じ。
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で書き込む
実行
当たり前だけどデータシート通りですね。
サーボ角度-90度
- PWM信号周期: 20ms
- Duty Cycle: 0.500ms
サーボ角度-45度
- PWM信号周期: 20ms
- Duty Cycle: 0.976ms
サーボ角度0度(center)
- PWM信号周期: 20ms
- Duty Cycle: 1.452ms
サーボ角度45度
- PWM信号周期: 20ms
- Duty Cycle: 1.927ms
サーボ角度90度
- PWM信号周期: 20ms
- Duty Cycle: 2.403ms