サマリ
前に作ったものはUSB接続した対向のUART機器を用意する必要があったので
PCだけでUARTの動作確認が出来るものを作ってみました。
以下が前に作ったもの
環境
- Fedora 40 + Xfce(*1)
- gcc 14.2.1
- プロセス間通信用キャラクタデバイス(*2)
(*1)
確認にe2studioを使用しているので、ubuntuの方が良いかもしれない。
(*2)
Linux専用です。
実行イメージ
事前準備
プロセス間通信用キャラクタデバイスを作成する
git clone https://github.com/tyano463/cdev_ipc.git
cd cdev_ipc
make
sudo insmod driver/simple_fifo.ko
確認
$ sudo dmesg -T | grep regist | tail -1
[Tue Dec 10 17:49:19 2024] FIFO: Device has been registered
$ sudo ls -l /dev/ttyQEMU*
crw-rw----. 1 root dialout 511, 0 Dec 10 17:49 /dev/ttyQEMU0
crw-rw----. 1 root dialout 511, 1 Dec 10 17:49 /dev/ttyQEMU1
crw-rw----. 1 root dialout 511, 2 Dec 10 17:49 /dev/ttyQEMU2
crw-rw----. 1 root dialout 511, 3 Dec 10 17:49 /dev/ttyQEMU3
crw-rw----. 1 root dialout 511, 4 Dec 10 17:49 /dev/ttyQEMU4
crw-rw----. 1 root dialout 511, 5 Dec 10 17:49 /dev/ttyQEMU5
crw-rw----. 1 root dialout 511, 6 Dec 10 17:49 /dev/ttyQEMU6
crw-rw----. 1 root dialout 511, 7 Dec 10 17:49 /dev/ttyQEMU7
crw-rw----. 1 root dialout 511, 8 Dec 10 17:49 /dev/ttyQEMU8
crw-rw----. 1 root dialout 511, 9 Dec 10 17:49 /dev/ttyQEMU9
要らなくなったら
sudo rmmod simple_fifo
すれば良いが、
起動毎に読み込まれるようにはしていないので
わざわざ削除しなくても再起動するだけで消える
ビルド
これを取得
git clone -b renesas/ra2l1-local-uart --depth=1 --recursive https://github.com/tyano463/qemu.git
mkdir build_qemu
cd build_qemu
../qemu/configure --target-list="arm-softmmu,aarch64-softmmu" --enable-debug --disable-docs
make
※aarch64-softmmuは不要だがついで
実行
ルネサスサンプルコードのダウンロード
確認用にルネサスのサンプルコードを取得する(ルネサスへのユーザ登録が必要)
ここからApplicationsタブを選んでSample Code
をクリックする。
ダウンロード出来たら
unzip r20an0598eu0140-ek-ra2l1-exampleprojects.zip
などで解凍する。
e2studioでサンプルコードのビルド
ダウンロード
e2studioがない人は
e2studioとFSP(RA用)をダウンロードする必要がある。
e2studio
ここからe2studioをダウンロードする。
e2studioのインストールはFedoraだとバグがあるっぽいので
慣れてない人はubuntuのほうが良いかもしれない。
実行権限与えて実行して、あとは雰囲気で
FSP
FSPはここから右側のReleasesをクリック。
ちょっと下にスクロールして、Assetsと書いてあるのをクリックし、
FSP_Packs_v5.6.0.zip
または最新版をダウンロードする。
解凍するとinternal
というディレクトリが出来るので
e2studioのインストール先に上書きする。
Fedoraでe2studioのインストールをデフォルトで行った場合は
${HOME}/.local/share/renesas/e2_studio/internal/
になっていると思う。
ビルド
sci_uart
プロジェクトの読み込み
e2studioを起動し、ワークスペースを適当に決めて
File -> Open Projects from File Systems
ダイアログが開いたらImport source: の右のDirectoryをクリックして
解凍した場所のルネサスサンプルコードのsci_uart
を選択する。
以下のようにsci_uart
ディレクトリではなく
その下のe2studio
が指定対象なので注意
ek_ra2l1/sci_uart/sci_uart_ek_ra2l1_ep/e2studio/
Finish
でプロジェクトが読み込まれる。
configuration.xml
configuration.xmlをダブルクリックしてBSP
タブを選び
FSP versionをダウンロードしたFSPのバージョンに合わせる。
BoardはEK-RA2L1、DeviceはR7FA2L1AB2DFPで良いと思います。
右上のGenerate Project Content
を実行
Properties
ウィンドウを開いてStacks
タブのg_uart UARTを選ぶと
Module g_uart UART(r_sci_uart) -> General -> Channel
で
使用チャンネルがわかる。多分2になっているはず。
というか2以外だと動きません。
ビルド
Project -> Cleanでビルド出来るはず。
その他
e2studioのデバッグは(ルネサス改造gdb用に特殊コマンドを使用するので)使えません。
QEMUと対向UARTデバイス(もどき)の起動
QEMUの起動
qemuを作成したbuild_qemu
ディレクトリへ行ってサンプルコードを指定して実行
cd /path/to/build_qemu
sudo ./qemu-system-arm -machine ra2l1 -cpu cortex-m33 -monitor stdio -kernel /path/to/ek_ra2l1/sci_uart/sci_uart_ek_ra2l1_ep/e2studio/Debug/sci_uart_ek_ra2l1_ep.elf
対向UARTデバイス(もどき)の起動
仮想ターミナルエミュレータならなんでも良いですが、
ここではminicomを使用するものとして説明します。
無ければscreenでも良いし、sudo yum install minicom
でインストール出来ます。
sudo minicom -b 115200 -D /dev/ttyQEMU2
速度は雰囲気で入れてます。もちろん意味はないし指定する必要はありません。
キャラクタデバイスの番号は
e2studioで確認出来る使用チャンネルに合わせる必要があります。
実行イメージ
Welcome to minicom 2.8
OPTIONS: I18n
Compiled on Jan 25 2024, 00:00:00.
Port /dev/ttyQEMU2, 17:49:20
Press CTRL-A Z for help on special keys
123
Invalid input. Input range is from 1 - 100
22
ルネサスのサンプルコードは改行が\r
で判定しているのでうまく動かない場合は
ターミナルエミュレータの改行コードの設定を確認してください。
minicomはCRは必ず送信されるはずなので設定は(多分)不要です。
確認
上の画面にもあるように範囲外の数値を入れると
Invalid input. Input range is from 1 - 100
と表示されます。
範囲内の数値を入れるとqemuがabortするはずです。が
これは正常動作です。
abortの説明
uart_ep_demo
内のコード
if(b_valid_data)
{
/* Change intensity of LED */
err = set_intensity(intensity, TIMER_PIN);
if (FSP_SUCCESS != err)
{
APP_ERR_PRINT ("\r\n** GPT failed while changing intensity ** \r\n");
return err;
}
/* Resetting the temporary buffer */
memset(g_temp_buffer, RESET_VALUE, DATA_LENGTH);
b_valid_data = false;
err = uart_print_user_msg((uint8_t *)"\r\nSet next value\r\n");
if (FSP_SUCCESS != err)
{
return err;
}
}
}
}
}
set_intensity
の引数intensityは入力した数字、TIMER_PINは1です。
fsp_err_t set_intensity(uint32_t raw_count, uint8_t pin)
{
fsp_err_t err = FSP_SUCCESS;
raw_count *= STEP;
#if defined(BOARD_RA4W1_EK) || defined (BOARD_RA6T1_RSSK)
raw_count = (MAX_DUTY_CYCLE - raw_count);
#endif
/* Set GPT timer's DutyCycle as per user input */
err = R_GPT_DutyCycleSet (&g_timer_ctrl, raw_count, pin);
if (FSP_SUCCESS != err)
{
APP_ERR_PRINT ("\r\n ** R_GPT_DutyCycleSet API failed **\r\n");
}
return err;
}
R_GPT_DutyCycleSet
へ行きます。
以下のdefineはデフォルトだとどちらも有効になっています。
fsp_err_t R_GPT_DutyCycleSet (timer_ctrl_t * const p_ctrl, uint32_t const duty_cycle_counts, uint32_t const pin)
{
#if GPT_CFG_OUTPUT_SUPPORT_ENABLE
uint32_t tmp_pin = pin & 3U;
gpt_instance_ctrl_t * p_instance_ctrl = (gpt_instance_ctrl_t *) p_ctrl;
#if GPT_CFG_PARAM_CHECKING_ENABLE
FSP_ASSERT(NULL != p_instance_ctrl);
FSP_ASSERT(tmp_pin <= GPT_IO_PIN_GTIOCA_AND_GTIOCB);
bool pwm_mode3_pin = 0 != (pin & (GPT_IO_PIN_CREST | GPT_IO_PIN_TROUGH));
if (TIMER_MODE_TRIANGLE_WAVE_ASYMMETRIC_PWM_MODE3 == p_instance_ctrl->p_cfg->mode)
{
/* In TIMER_MODE_TRIANGLE_WAVE_ASYMMETRIC_PWM_MODE3, the duty cycle must be for either a trough or crest. */
FSP_ERROR_RETURN(pwm_mode3_pin, FSP_ERR_INVALID_MODE);
}
else
{
FSP_ERROR_RETURN((!pwm_mode3_pin) || (TIMER_MODE_ONE_SHOT_PULSE == p_instance_ctrl->p_cfg->mode),
FSP_ERR_INVALID_MODE);
}
pin
は1で固定なのでpwm_mode3_pin
もfalse固定になります。
p_instance_ctrl->p_cfg->mode
はTIMER_MODE_PWM(3)なので
else文に入ります。
pwm_mode3_pin
がfalseなのでエラーで返ります。
uart_ep_demo
がエラーで返ります。
hal_entry
の中身(抜粋)
err = uart_ep_demo();
if (FSP_SUCCESS != err)
{
APP_PRINT ("\r\n ** UART EP Demo FAILED ** \r\n");
timer_gpt_deinit();
deinit_uart();
APP_ERR_TRAP(err)
}
}
uart_ep_demo
がエラーで返るとAPP_ERR_TRAP
が実行されます。
APP_ERR_TRAP
の中身は以下です。
#define APP_ERR_TRAP(err) if(err) {\
SEGGER_RTT_printf(SEGGER_INDEX, "\r\nReturned Error Code: 0x%x \r\n", err);\
__asm("BKPT #0\n");} /* trap upon the error */
__asm("BKPT #0\n");
でAbortが発生します。
BKPTの説明
アプリケーションデバッグ
qemuの実行を以下に変更して(-s -S
をつけるだけ)
sudo ./qemu-system-arm -machine ra2l1 -cpu cortex-m33 -monitor stdio -kernel /path/to/ek_ra2l1/sci_uart/sci_uart_ek_ra2l1_ep/e2studio/Debug/sci_uart_ek_ra2l1_ep.elf -s -S
別のターミナルからgdbでアタッチしてください。
arm-none-eabi-gdb
がない人はここからダウンロードしてください。
右のDownloadアイコンは罠です。
x86_64 Linux hosted cross toolchains の
arm-gnu-toolchain-13.2.rel1-x86_64-arm-none-eabi.tar.xz
で良いと思います。
tar xf arm-gnu-toolchain-13.2.rel1-x86_64-arm-none-eabi.tar.xz
などで解凍して
中のbinにパスを通してください。
以下はgdbのインストールと設定例(ダウンロードディレクトリに移動してから)
sudo chmod -R 777 /opt/
mv arm-gnu-toolchain-13.2.rel1-x86_64-arm-none-eabi.tar.xz /opt/
cd /opt
tar xf arm-gnu-toolchain-13.2.rel1-x86_64-arm-none-eabi.tar.xz
mv arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-eabi toolchain
echo "export PATH=\$PATH:/opt/toolchain/bin" >> ~/.bashrc
以下はgdbでの接続例(arm-none-eabi-gdbはpython3.8でないと動かないっぽい)
$ cd /path/to/sci_uart/sci_uart_ek_ra2l1_ep/e2studio
$ cat <<EOF > .app-gdbinit
set pagination off
set logging on
target remote localhost:1234
define es
stepi
disas \$pc,+10
end
b hal_entry
command 1
c
end
c
EOF
$
$ PYTHONHOME=/home/tyano/.pyenv/versions/3.8.19 PYTHONPATH=/home/tyano/.pyenv/versions/3.8.19/bin/python3.8 arm-none-eabi-gdb Debug/sci_uart_ek_ra2l1_ep.elf -x .app-gdbinit
疑問点など
- ルネサスがデフォルトでabortするサンプルコードを載せているのは謎
設定変更が必要なのか?実機だとabortしない? -
APP_PRINT
やAPP_ERR_PRINT
は
_SEGGER_RTT
変数にmemcpyするだけになっていて
UARTに接続されておらず、UART用には別にuart_print_user_msg
が用意されている。
エミュレータだと対応してないので表示されないが
これは実機だと表示されるのだろうか? - 一応テスト用にGPIOの入出力をモニタするものをつけてみた
- GPTはエラー回避に枠だけ作ったので動きません
- チャンネルは2固定で実装しています
チャンネルを変えたらターミナルエミュレータ側のキャラクタデバイスの番号を変更するだけではだめで
qemuの割り込み番号をvector_data.cのg_vector_table
に合わせて指定してやる必要があります
124 struct
125 {
126 int txi;
127 int tei;
128 int rxi;
129 // } irqnum[] = {{1, 2, 0}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {5, 6, 4}};
130 } irqnum[] = {{1, 2, 0}, {-1}, {5, 6, 4}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}};
ついでにこの部分も変更が必要です。
hw/arm/ra2l1.cのra2l1_soc_realize
149 ret = local_uart_init(system_memory, s, dev_soc, (intptr_t)R_SCI0, 0);
150 ret |= local_uart_init(system_memory, s, dev_soc, (intptr_t)R_SCI2, 2);
151 ret |= local_uart_init(system_memory, s, dev_soc, (intptr_t)R_SCI9, 9);
- Windowsでは(ドライバが対応していないので)動きません
- 詳細は
hw/arm/renesas_uart.c
を見てください
動作イメージ
対向のdummyプログラムを作成してやるとこんな感じに動かすことも出来ます。
(対向のdummyプログラムはキャラクタデバイスをread
してwrite
するだけです。)
ルネサスのサンプルコード用では無いですが、以下デモで使用した対向dummyのコードです
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "dlog.h"
#define UPPER_UART "/dev/ttyQEMU0"
#define ASCII_CODE_CR '\r'
#define ASCII_CODE_LF '\n'
#define CRLF "\r\n"
#define MSG_DEVICE_FOUND "12:34:56:78:9a:bc -90 WestLK90-1613 11,22,33,44,55,66,77,88,99"
#define MSG_SWPUSH "SWPUSH"
#define MSG_SWREL "SWREL"
typedef enum {
U_CMD_RDDN,
U_CMD_STSC,
U_CMD_SLDP,
U_CMD_SWPUSH,
U_CMD_SWREL,
U_CMD_STAD,
U_CMD_SPAD,
U_CMD_MAX
} U_CMD;
typedef struct {
U_CMD kind;
const char *cmd;
} upper_command_t;
static void sig_handler(int signum);
volatile bool running;
typedef struct {
int fd;
const char *msg;
} send_msg_t;
static void *delay_send_impl(void *arg) {
send_msg_t *p = (send_msg_t *)arg;
sleep(1);
write(p->fd, p->msg, strlen(p->msg));
printf("Tx: %s", p->msg);
free(arg);
return NULL;
}
static uint8_t c2b(char c) {
if ('0' <= c && c <= '9') {
return c - '0';
} else if ('A' <= c && c <= 'F') {
return 0xa + c - 'A';
} else if ('a' <= c && c <= 'f') {
return 0xa + c - 'a';
}
return 0;
}
static bool is_led_on(const char *s, size_t len) {
const char *sldp = "SLDP";
size_t sldppos = 0;
bool sldpchk = false;
uint8_t upper, lower;
uint8_t red = 0, blue = 0;
size_t byte_pos = 0;
for (size_t i = 0; i < len; i++) {
if (!sldpchk) {
if (sldp[sldppos] == s[i]) {
sldppos++;
}
if (sldppos == strlen(sldp)) {
sldpchk = true;
}
} else {
if ((s[i] == ' ') || (s[i] == ','))
continue;
if (byte_pos % 2) {
lower = c2b(s[i]);
if (byte_pos / 2) {
blue = ((upper & 0xf) << 4) | (lower & 0xf);
} else {
red = ((upper & 0xf) << 4) | (lower & 0xf);
}
} else {
upper = c2b(s[i]);
}
byte_pos++;
}
}
return ((red != 0) || (blue != 0));
}
static void delay_send(int fd, U_CMD cmd, const char *s, size_t len) {
send_msg_t *msg;
pthread_t th, th2;
switch (cmd) {
case U_CMD_STSC:
msg = malloc(sizeof(send_msg_t));
msg->fd = fd;
msg->msg = MSG_DEVICE_FOUND CRLF;
pthread_create(&th, NULL, delay_send_impl, msg);
break;
case U_CMD_SLDP:
if (is_led_on(s, len)) {
msg = malloc(sizeof(send_msg_t));
msg->fd = fd;
msg->msg = MSG_SWPUSH CRLF;
sleep(2);
pthread_create(&th, NULL, delay_send_impl, msg);
usleep(500000);
msg = malloc(sizeof(send_msg_t));
msg->fd = fd;
msg->msg = MSG_SWREL CRLF;
pthread_create(&th2, NULL, delay_send_impl, msg);
} else {
// do nothing.
}
break;
}
}
void response(int fd, const char *s, size_t len) {
const char *devname = "IMBLE3-21948576";
const upper_command_t commands[] = {
{U_CMD_RDDN, "RDDN"}, {U_CMD_STSC, "STSC"},
{U_CMD_SLDP, "SLDP"}, {U_CMD_SWPUSH, "SWPUSH"},
{U_CMD_SWREL, "SWREL"}, {U_CMD_SPAD, "RDDN"},
{U_CMD_STAD, "RDDN"}, {U_CMD_MAX, (const char *)NULL},
};
U_CMD cmd = U_CMD_MAX;
for (int i = 0; commands[i].cmd; i++) {
if (strncmp(s, commands[i].cmd, strlen(commands[i].cmd)) == 0) {
cmd = commands[i].kind;
break;
}
}
ERR_RET(cmd == U_CMD_MAX, "command error %s", s);
printf("Rx: %.*s\n", len, s);
switch (cmd) {
case U_CMD_RDDN:
write(fd, devname, strlen(devname));
write(fd, "\r\n", 2);
printf("Tx: %s\n", devname);
break;
case U_CMD_STSC:
case U_CMD_SLDP:
write(fd, "OK\r\n", 4);
printf("Tx: OK\n");
delay_send(fd, cmd, s, len);
break;
default:
write(fd, "OK\r\n", 4);
printf("Tx: OK\n");
break;
}
error_return:
return;
}
int main(void) {
running = true;
char buf[256];
char rx_buf[256];
int rx_pos = 0;
int fd;
fd = open(UPPER_UART, O_RDWR);
ERR_RET(fd < 0, UPPER_UART " file open failed");
signal(SIGINT, sig_handler);
while (running) {
ssize_t len = read(fd, buf, sizeof(buf) - 1);
ERR_RET(len < 0, "read error");
if (!len)
continue;
// buf[len] = '\0';
// dlog("received %s", buf);
memcpy(&rx_buf[rx_pos], buf, len);
rx_pos += len;
rx_buf[rx_pos] = '\0';
size_t start = 0;
for (size_t i = 0; i < rx_pos; i++) {
if ((rx_buf[i] == ASCII_CODE_CR) || (rx_buf[i] == ASCII_CODE_LF)) {
if (i > start) {
response(fd, rx_buf + start, i - start);
}
while ((i + 1 < rx_pos) &&
((rx_buf[i + 1] == ASCII_CODE_CR) || (rx_buf[i + 1] == ASCII_CODE_LF))) {
i++;
}
start = i + 1;
}
}
if (start < rx_pos) {
memmove(rx_buf, rx_buf + start, rx_pos - start);
rx_buf[rx_pos - start] = '\0';
rx_pos -= start;
} else {
rx_buf[0] = '\0';
rx_pos = 0;
}
}
error_return:
if (fd >= 0)
close(fd);
return 0;
}
static void sig_handler(int signum) {
running = false;
}
dlog.h
#ifndef __DLOG_H__
#define __DLOG_H__
#include <string.h>
#define ERR_RETn(c) \
{ \
if (c) \
goto error_return; \
}
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define ERR_RET(c, s, ...) \
{ \
if (c) \
{ \
printf("%s(%d) %s " s "\n", __FILENAME__, __LINE__, __func__, ##__VA_ARGS__); \
goto error_return; \
} \
}
#define dlog(s, ...) \
printf("%s(%d) %s " s "\n", __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#endif
#endif /* dlog_h */