0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RAのUART動作確認エミュレータ(ローカル版)

Last updated at Posted at 2024-12-11

サマリ

前に作ったものはUSB接続した対向のUART機器を用意する必要があったので
PCだけでUARTの動作確認が出来るものを作ってみました。

以下が前に作ったもの

環境

(*1)
確認にe2studioを使用しているので、ubuntuの方が良いかもしれない。

(*2)
Linux専用です。

実行イメージ

kroki

事前準備

プロセス間通信用キャラクタデバイスを作成する

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をクリックする。

image.png

ダウンロード出来たら

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_PRINTAPP_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 */

Peek 2024-12-16 12-57.gif

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?