コンテンツにスキップ

シリアルバス・制御インターフェース

1.UART

1.1. UARTの概要

1.1.1 シリアルポートリソースの紹介

EASY EAI Nano-TB開発ボードのシリアルポートリソースは、2つのカテゴリに分類されます。1つは特定の機能に占有されている【使用不可のシリアルポート】、もう1つはユーザーが自由に使用できる【使用可能なシリアルポート】です。

【直接使用できないシリアルポート】の分布は以下の通りです。

シリアルポート番号デバイスノード説明
串口0 (Port 0)/dev/ttyFIQ0デバッグ用として占有されています。通常のポートとしては使用できません。
串口1 (Port 1)なし関連ピンは別の機能に多重化(マルチプレックス)されています。
串口35 (Port 35)なし関連ピンは別の機能に多重化(マルチプレックス)されています。

ハードウェア上の配置は図の通りです。

シリアルポートリソースの紹介

シリアルポートリソースの紹介

【使用可能なシリアルポート】の分布は以下の通りです。

シリアルポート番号デバイスノード説明
串口2 (Port 2)/dev/ttyS2TTLレベル
串口6 (Port 6)/dev/ttyS6TTLレベル
串口7 (Port 7)/dev/ttyS7TTLレベル

ハードウェア上の配置は図の通りです。

シリアルポートリソースの紹介

シリアルポートリソースの紹介

1.1.2 ハードウェアの配線

一般的な配線(デバイス間の通信):

ハードウェアの配線

ハードウェアの配線

  • 本サンプルのテスト配線: ジャンパキャップを使用してRxdピンとTxdピンを短絡(ショート)させ、ループバック(自送信・自受信)テストを行います。

ハードウェアの配線

ハードウェアの配線

1.2. クイックスタート

1.2.1 開発環境の準備

初めて本ドキュメントをお読みになる場合は、「入門ガイド/開発環境の準備/Easy-Eaiコンパイル環境の準備と更新」をお読みいただき、関連する手順に従ってコンパイル環境を構築してください。

PC側のUbuntuシステムで run スクリプトを実行し、EASY-EAIコンパイル環境に入ります。詳細は以下の通りです。

Terminal window
cd ~/develop_environment
./run.sh 2204

1.2.2 ソースコードのダウンロードとサンプルのコンパイル

まず、仮想マシンのバックグラウンドターミナルで以下のコマンドを実行し、周辺機器サンプルソースコードの管理ディレクトリを作成します:

Terminal window
cd /opt
mkdir -p EASY-EAI-Nano-TB/demo

サンプルプログラムをダウンロードします:

例えば、サンプルプログラムを「PC\D:」(指定はありません。ユーザーの任意の場所で構いません)にダウンロードします。

その後、ダウンロードしたサンプルを仮想マシンのファイルシステムにコピーします。手順は下図をご参照ください。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

最後に、該当するサンプルのディレクトリに移動してコンパイル操作を実行します。具体的なコマンドは以下の通りです:

Terminal window
cd EASY-EAI-Nano-TB/demo/06_UART
./build.sh

💡 注意 : 依存ライブラリはボード上に配置されているため、クロスコンパイル中は /mntのマウントを維持する必要があります。

注意:

コンパイルが成功すると、Releaseディレクトリに2つの実行可能プログラムが生成され、開発ボードの /userdata/ディレクトリに自動的に配置されます。これらは送信側デモの test-Sendと、受信側デモの test-Recv です。

1.2.3 サンプルの実行

シリアルポートデバッグまたはSSH経由でボードのバックグラウンドにアクセスし、サンプルが配置されているディレクトリに移動します:

Terminal window
cd /userdata

サンプルの実行

サンプルの実行

まず、以下のコマンドを実行して【受信側】のデモを【バックグラウンドで実行】します:

Terminal window
sudo ./test-Recv /dev/ttyS2 &

実行結果は以下の通りです。この時、受信側は送信側からのデータを待機する状態になります。

サンプルの実行

サンプルの実行

次に、以下のコマンドを実行して【送信側】のデモを実行します:

Terminal window
sudo ./test-Send /dev/ttyS2

サンプルの実行

サンプルの実行

1.3. C言語での使用例

シリアルポートのC言語での使用例です。受信側のコードのパスは06_UART/test-uart/Recv.cです。コーディングの参考にしてください。以下のコードは、シリアルポート受信側の操作フローを示しています:

int main(int argc, char **argv){
if(2 != argc){
printf("Usage:\n");
printf(" sudo %s %s\n", argv[0], "/dev/ttyS<2/6/7>");
return -1;
}
int fd = UART_Open(argv[1]);
if(fd < 0){
printf("\033[33m【Open ERROR!】%s\n", DEBUG_COLOR_TAIL);
return -1;
}
if(false == UART_Set(fd, 115200, 0, 8, 1, 'N')){
printf("\033[33m【Init ERROR!】%s\n", DEBUG_COLOR_TAIL);
return -1;
}
const char *strReceiver = "I am uart Receiver";
printf("\033[36m【Init OK \"%s\"%s\n", strReceiver, DEBUG_COLOR_TAIL);
char recvBuf[128]={0};
while(1){
if(UART_Recv(fd, recvBuf, sizeof(recvBuf)) <= 0){
continue;
}else{
printf("\033[36m【Recv Msg from Sender】:%s", DEBUG_COLOR_TAIL);
printf(" %s\n", recvBuf);
break;
}
}
UART_Close(fd);
printf("\033[42m【Recv date OK. BYE BYE!】%s\n", DEBUG_COLOR_TAIL);
return 0;
}

送信側のコードのパスは 06_UART/test-uart/Send.cです。コーディングの参考にしてください。以下のコードは、シリアルポート送信側の操作フローを示しています:

int main(int argc, char **argv){
if(2 != argc){
printf("Usage:\n");
printf(" sudo %s %s\n", argv[0], "/dev/ttyS<2/6/7>");
return -1;
}
int fd = UART_Open(argv[1]);
if(fd < 0){
printf("\033[33m【Open ERROR!】%s\n", DEBUG_COLOR_TAIL);
return -1;
}
if(false == UART_Set(fd, 115200, 0, 8, 1, 'N')){
printf("\033[33m【Init ERROR!】%s\n", DEBUG_COLOR_TAIL);
return -1;
}
char *strSender = "I am uart Sender";
printf("\033[36m【Init OK \"%s\"%s\n", strSender, DEBUG_COLOR_TAIL);
int len = UART_Send(fd, strSender, strlen(strSender));
if(len <= 0){
printf("\033[41m【Send data ERROR!】%s\n", DEBUG_COLOR_TAIL);
return -1;
}
UART_Close(fd);
printf("\033[42m【Send date OK. BYE BYE!】%s\n", DEBUG_COLOR_TAIL);
return 0;
}

コード内の UART_Open()、UART_Set()、UART_Send()、UART_Recv() は、システムコールを使いやすくカプセル化したラッパー関数です。具体的な実装は 06_UART/commonApi/uart.c に記述されています。

2.SPI

2.1. SPIの概要

SPIは、シリアル・ペリフェラル・インターフェース (Serial Peripheral Interface)の略称であり、Motorola社によって開発された同期式シリアルインターフェース技術です。高速、全二重、同期式の通信バスとして機能します。ユーザースペースのアプリケーションにおいて、SPIプロトコルの詳細な規定を気にする必要は全くありません。ドライバ層から提供されるSPI周辺機器の操作インターフェース関数を使用するだけで、Linux内の他の一般的なデバイスファイルと同様に、簡単にSPI周辺機器を操作することができます。

EASY EAI Nano-TBのSPIインターフェースの配置は図の通りです

SPIの概要

SPIの概要

2.1.1 SPIパラメータ設定の解説

  • デバイスファイルフォーマット: /dev/spidev(bus.select)

  • bus: SPIバス番号を表します(すなわち、SCLK、MOSI、MISOの1セット)。

  • select: SPIデバイス番号を表します。同一バス上では、異なるチップセレクト信号(CSN0、CSN1など)を使用して区別します。

Orin-NanoのデフォルトSPIリソースを例に挙げます:SPI機能を有効にすると、以下の4つのデバイスノードが表示されます(つまり、2つのバスと4つのデバイスが存在します)。

  • /dev/spidev0.0

  • /dev/spidev0.1

  • /dev/spidev1.0

  • /dev/spidev1.1

SPI通信には4種類の異なるモードがあります。スレーブデバイスは工場出荷時にモードが固定されており、変更することはできません。しかし、通信する双方のデバイスは同じモードで動作する必要があるため、マスターデバイス側のSPIモードを設定し、CPOL(クロック極性)CPHA(クロック位相)を通じてマスターデバイスの通信モードを制御します。

モードCPOLCPHA
Mode 000
Mode 101
Mode 210
Mode 311
  • クロック極性 (CPOL) は、SCLKの有効レベルを設定するために使用されます。

  • クロック位相 (CPHA) は、データサンプリングがどのエッジで発生するかを設定するために使用されます。

  • CPOL=0 は、SCLK=0の時にアイドル状態であることを示し、SCLKがHighレベルの時に有効となります。

  • CPOL=1 は、SCLK=1の時にアイドル状態であることを示し、SCLKがLowレベルの時に有効となります。

  • CPHA=0 は、データのサンプリングが最初(第1)のエッジで行われ、データの送信が2番目(第2)のエッジで行われることを示します。

  • CPHA=1 は、データのサンプリングが2番目(第2)のエッジで行われ、データの送信が最初(第1)のエッジで行われることを示します。

【*】 SPIマスターモジュールと通信する周辺機器間で、両者のクロック位相と極性は必ず一致させる必要があります。

その他のパラメータについて:

  • speed: 通信のビットレート

  • delay: 通信の遅延時間の設定

  • bits: 通信で占有されるビット数

2.1.2 ハードウェアの接続

本サンプルでは、RFIDカードリーダーモジュールである RC522 を使用して補助的なデモンストレーションを行います。

RC522モジュールとEASY EAI Nano-TBの配線原理図は以下の通りです

ハードウェアの接続

ハードウェアの接続

2.2. クイックスタート

2.2.1 開発環境の準備

初めて本ドキュメントをお読みになる場合は、「入門ガイド/開発環境の準備/Easy-Eaiコンパイル環境の準備と更新」をお読みいただき、関連する手順に従ってコンパイル環境を構築してください。

PC側のUbuntuシステムで run スクリプトを実行し、EASY-EAIコンパイル環境に入ります。詳細は以下の通りです。

Terminal window
cd ~/develop_environment
./run.sh 2204

2.2.2 ソースコードのダウンロードとサンプルのコンパイル

まず、仮想マシンのバックグラウンドターミナルで以下のコマンドを実行し、周辺機器サンプルソースコードの管理ディレクトリを作成します:

Terminal window
cd /opt
mkdir -p EASY-EAI-Nano-TB/demo

サンプルプログラムをダウンロードします:

例えば、サンプルプログラムを「PC\D:」(指定はありません。ユーザーの任意の場所で構いません)にダウンロードします。

その後、ダウンロードしたサンプルを仮想マシンのファイルシステムにコピーします。手順は下図をご参照ください。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

最後に、該当するサンプルのディレクトリに移動してコンパイル操作を実行します。具体的なコマンドは以下の通りです:

Terminal window
cd EASY-EAI-Nano-TB/demo/07_SPI
./build.sh

💡 注意 : 依存ライブラリはボード上に配置されているため、クロスコンパイル中は /mntのマウントを維持する必要があります。

注意

コンパイルが成功すると、ソースコードに基づいてtest-rfid、test-fram、test-spidevの3つのサンプルプログラムが生成され、開発ボードの /userdata/ディレクトリに自動的に配置されます。

本ドキュメントの補助サンプルとして使用するのは test-rfidです。他のサンプルは別のアプリケーションシナリオで使用されるものであり、ここでのコードは参考用です。

2.2.3 サンプルの実行

シリアルポートデバッグまたはSSH経由でボードのバックグラウンドにアクセスし、サンプルが配置されているディレクトリに移動します:

Terminal window
cd /userdata

サンプルの実行

サンプルの実行

以下のコマンドを実行してサンプルを起動します:

Terminal window
sudo ./test-rfid

サンプルの実行

サンプルの実行

実行結果は図の通りです(※元のドキュメント内の図をご参照ください)。

APIの詳細な説明およびAPIの呼び出し(本サンプルのソースコード)については、以下の説明をご参照ください。

2.3. RFID ID読み取りサンプル

RFIDサンプルのソースコードは以下のパスにあります:

  • 07_SPI/rfid.c

  • 07_SPI/dev/rc522.c

  • 07_SPI/include/rc522.h

RC522チップを利用して実装および解説を行っています。操作フローは以下の通りです

RFID ID読み取りサンプル

RFID ID読み取りサンプル

参考となるサンプルコードは以下の通りです。

static unsigned char flag = 0; static unsigned char bits = 8; static unsigned int speed = 100000; static uint16_t delay = 0; unsigned char card_rev_buf[16] = 0;

/*
* セクタパスワード:A、セクタ数:16
* 各セクタのパスワードバイト数:16Byte
*/
unsigned char sector_key_a[16][16];
unsigned char data_buf[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
int main (int argc, char **argv) {
memset(data_buf, 0x00, sizeof data_buf);
int status = MI_ERR;
int numAtempt = 1;
int fd = spi_init(dev_spi_bus, dev_spi_select, mode, bits, speed, delay);
rfid_init(dev_spi_bus, dev_spi_select, fd);
flag = MI_GET_ID;
while(1)
{
while(rfid_request(PICC_REQIDL, &card_rev_buf[0]) != MI_OK && numAtempt-- >= 0) {
usleep(500);
}
if(rfid_anticoll(&card_rev_buf[2]) == MI_OK) {
status = rfid_select(&card_rev_buf[2]);
if(status != MI_ERR) {
if(flag == MI_GET_ID) {
printf("Card ID:%02x%02x%02x%02x\n", card_rev_buf[2], card_rev_buf[3],card_rev_buf[4], card_rev_buf[5]);
} else if (flag == MI_READ) {
memset(sector_key_a, 0xff, 256);
memset(data_buf, 0x00, sizeof data_buf);
status = rfid_auth_state(PICC_AUTHENT1A, addr, sector_key_a[addr/4], &card_rev_buf[2]);
if(status == MI_OK) {
status = rfid_read(addr, data_buf);
if(status == MI_OK) {
print_buff(data_buf, 16);
}
} else {
printf("Error reading");
close(fd);
exit(1);
}
} else if (flag == MI_WRITE) {
memset(sector_key_a, 0xff, 256);
if(addr == 0 || addr % 4 == 3) {
close(fd);
exit(1);
}
status = rfid_auth_state(PICC_AUTHENT1A, addr, sector_key_a[addr/4], &card_rev_buf[2]);
if(status == MI_OK) {
status = rfid_write(addr, data_buf);
if(status != MI_OK) {
printf("rfid write failure!\n");
close(fd);
exit(1);
}
} else {
printf("Error writing");
close(fd);
exit(1);
}
} else {
printf("Not implemented\n");
}
status = rfid_halt();
if(status != MI_OK) {
//printf ("rfid halt failure! [ERROR %d]\n", status);
}
} else {
// printf("None\n");
}
} else {
// printf("None\n");
}
}
spi_exit(dev_spi_bus , dev_spi_select);
return 0;
} /* ----- End of main() ----- */

さらに、SPIインターフェースを使用するFRAM(強誘電体メモリ)の通信ソースコードは以下のパスにあります:

  • 07_SPI/fram.c

  • 07_SPI/mb85rs64.c

  • 07_SPI/mb85rs64.h

SPIインターフェースの読み書き通信ソースコードは以下のパスにあります:

  • 07_SPI/spidev_test.c

2.4. 注意事項

RC522は、主にSPI APIの使用方法を補助的に説明するためのものです。このモジュールの資料や詳細な使用方法については、以下のサイトをご参照ください:

RC522モジュール(※公式サイトのモジュールURL)

3.I2C

3.1. I2Cの概要

IIC(またはI2C)は、マルチマスター・マルチスレーブアーキテクチャを使用するシリアル通信バスです。当初の設計目的は、マザーボード、組み込みシステム、または携帯電話において低速な周辺機器を接続することでした。主にデータ量が少ない場面で使用され、伝送距離が短く、任意の時刻においてマスターは1つしか存在できないなどの特徴があります。Linux組み込みアプリケーション開発のシナリオにおいては、IICプロトコルの詳細な規定を気にする必要は全くありません。ドライバ層から提供されるIIC周辺機器の操作インターフェース関数を使用するだけで、Linux内の他の一般的なデバイスファイルと同様に、簡単にIIC周辺機器を操作することができます。

3.1.1 開発ボード上のI2Cリソースの紹介

EASY EAI Nano-TB開発ボードは、ユーザーがカスタムで呼び出せるように1系統のIICリソース(IIC_5)を予備として引き出しています。正確な配置位置は下図の通りです。

開発ボード上のI2Cリソースの紹介

開発ボード上のI2Cリソースの紹介

3.1.2 ハードウェア配線図

本サンプルでは、ADS1115電圧検出モジュールを使用して補助的なデモンストレーションを行います。このモジュールの機能は、検出した電圧(アナログ信号)をデジタル信号に変換してレジスタに格納し、IIC通信方式を介して外部に電圧情報を提供することです。

ADS1115モジュールとEASY EAI Nano-TBの配線原理図は以下の通りです。

ハードウェア配線図

ハードウェア配線図

3.2. クイックスタート

3.2.1 開発環境の準備

PC側のUbuntuシステムで run スクリプトを実行し、EASY-EAIコンパイル環境に入ります。詳細は以下の通りです。

Terminal window
cd ~/develop_environment
./run.sh 2204

3.2.2 ソースコードのダウンロードとサンプルのコンパイル

まず、仮想マシンのバックグラウンドターミナルで以下のコマンドを実行し、周辺機器サンプルソースコードの管理ディレクトリを作成します:

Terminal window
cd /opt
mkdir -p EASY-EAI-Nano-TB/demo

サンプルプログラムをダウンロードします:

例えば、サンプルプログラムを「PC\D:」(指定はありません。ユーザーの任意の場所で構いません)にダウンロードします。

最後に、該当するサンプルのディレクトリに移動してコンパイル操作を実行します。具体的なコマンドは以下の通りです:

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

Terminal window
cd EASY-EAI-Nano-TB/demo/08_IIC
./build.sh

💡 注意 :依存ライブラリはボード上に配置されているため、クロスコンパイル中は/mnt のマウントを維持する必要があります。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

コンパイルが成功すると、test-ads1115という実行可能プログラムが生成され、開発ボードの /userdata/ディレクトリに自動的に配置されます。

3.2.3 サンプルの実行

シリアルポートデバッグまたはSSH経由でボードのバックグラウンドにアクセスし、サンプルが配置されているディレクトリに移動します:

Terminal window
cd /userdata

サンプルの実行

サンプルの実行

以下のコマンドを実行してサンプルを起動します:

Terminal window
sudo ./test-ads1115

実行結果は以下の通りです。プローブ(探触子)を使用して 3V3、1V8、GNDの3つの端子にそれぞれ触れると、対応する電圧値がターミナルで測定・表示されます。

サンプルの実行

サンプルの実行

3.3. C言語での使用例

ADS1115のC言語での使用例です。コードのパスは 08_IIC/test-ads1115/main.c です。コーディングの参考にしてください。以下のコードは、ADS1115の操作フローを示しています:

int32_t ads1115_config_register(uint32_t fd, uint8_t configH, uint8_t configL){
uint8_t reg_data[3] = {ADS1015_REG_POINTER_CONFIG, configH, configL};
return iic_write(fd, ADS1115_ADDRESS, reg_data, sizeof(reg_data));
}
int16_t ads1115_read_data(uint32_t fd){
bool ret = false;
/* データの読み取り */
uint8_t tx_data[1] = {ADS1015_REG_POINTER_CONVERT};
if(iic_write(fd, ADS1115_ADDRESS, tx_data, sizeof(tx_data)) < sizeof(tx_data)){
printf("iic write faild !\n");
return -1;
}
uint8_t rx_data[3]={0};
if(iic_read(fd, ADS1115_ADDRESS, rx_data, 2) < 0){
printf("iic read faild !\n");
return -1;
}
int16_t data = rx_data[0]*256+rx_data[1];
return data;
}
double ads1115_get_voltage_val(uint32_t fd, uint8_t configH, uint8_t configL){
/* レジスタの設定 */
if(ads1115_config_register(fd, configH, configL) < 0){
printf("ads1115 config register faild\n");
return 0.0;
}
usleep(100 * 1000);
int16_t ad_val = ads1115_read_data(fd);
if((0x7FFF == ad_val)|(0X8000 == ad_val)) { // レンジを超えているか確認
ad_val = 0;
printf("ads1115 over PGA\r\n");
}
double val = 0.0;
switch((0x0E&configH)>>1) // レンジに対応する分解能
{
case(0x00):
val = (double)ad_val*187.5/1000000.0; break;
case(0x01):
val = (double)ad_val*125/1000000.0; break;
case(0x02):
val = (double)ad_val*62.5/1000000.0; break;
case(0x03):
val = (double)ad_val*31.25/1000000.0; break;
case(0x04):
val = (double)ad_val*15.625/1000000.0; break;
case(0x05):
val = (double)ad_val*7.8125/1000000.0; break;
default:
val = 0.0; break;
}
return val;
}
int main(int argc, char const *argv[]){
bool ret = false;
double val;
int fd = iic_init("/dev/i2c-2");
if(fd < 0){
printf("iic init faild \n");
return -1;
}
if(0 != iic_set_addr_len(fd, 7)){
return -1;
}
if(0 != iic_set_addr(fd, ADS1115_ADDRESS)){
return -1;
}
while (1) {
val = ads1115_get_voltage_val(fd, CONFIG_REG_H, CONFIG_REG_L);
printf("val: %f V\r\n",val);
sleep(2);
}
iic_release(fd);
return 0;
}

コード内の iic_init()、iic_set_addr_len()、iic_set_addr()、iic_read()、iic_write()、iic_release()は、システムコールを使いやすくカプセル化したラッパー関数です。具体的な実装は08_IIC/commonApi/iic.c に記述されています。

IICハードウェアリソースを操作するためのインターフェースが必要であることに加え、IICバス上のIICスレーブデバイスのレジスタ操作方法を明確に理解している必要があります。例えば、ADS1115電圧検出チップの場合、そのレジスタに関連する操作定義はすべて08_IIC/test-ads1115/ads1115.h に実装されています。

4.GPIO

4.1. GPIOの概要

4.1.1 ハードウェア配線原理図

💡 注意 : GPIOはホットスワップ(活線挿抜)をサポートしていますが、ベースボードに保護ケースが装着されていない状態で抜き差しを行うと、ベースボード上の部品に触れやすく、ボード付近の金属部品によってショートを引き起こす危険性があります。そのため、周辺機器の抜き差しは、電源を完全に切った状態で行うことをお勧めします。

GPIOの入出力電圧は3.3Vです。 レベル(電位)のマッチングに注意してください。そうしないと、チップのピンや接続されたデバイスが破損する恐れがあります。

4.1.2 GPIOハードウェアリソース分布の紹介

GPIOハードウェアリソース分布の紹介

GPIOハードウェアリソース分布の紹介

  • gpiodライブラリ: 上表の【Chipオブジェクト名】と【Lineオフセット量】を使用する必要があります。

  • sysfsアクセス方式: 上表の【GPIOシステムノードパス】を使用する必要があります。

4.1.3 gpiod の概要

Linux 4.8から libgpiodのサポートが追加され、従来のsysfsベースのアクセス方式は徐々に非推奨となります。そのため、本ドキュメントのデモでは主にgpiod 方式を採用してGPIOを制御しています。gpiod ライブラリは、chipオブジェクトおよび lineオブジェクトを操作することで、GPIOピンの出力レベルを制御したり、GPIOピンのレベルを読み取ったりする目的を達成します。

  • Chipオブジェクト名: gpiod_chip_open_by_name を呼び出してchipオブジェクトを取得する際、引数として使用されます。

  • Lineオフセット量: gpiod_chip_get_line を呼び出してlineオブジェクトを取得する際、引数として使用されます。

GPIO5_C0を例に挙げると、【ピン名】、【Chipオブジェクト名】、【Lineオフセット量】の3つの関係は以下の式のようになります。

gpiod の概要

gpiod の概要

4.1.4 sysfsアクセス方式の概要

sysfsによるGPIO制御方式は、主にカーネルが提供するGPIO制御インターフェースファイルに基づいています。つまり、/sys/class/gpioディレクトリ下のファイルを読み書きすることで、対応するGPIOインターフェースを制御します。

  • ピン番号(pin): sysfsアクセス方式では、すべてピン番号を操作の基準とします。

  • GPIOシステムノードパス: 具体的なGPIOピンに対応するノードのパスです。

【ピン名】と【GPIOシステムノードパス】の関係は以下の式のようになります。

sysfsアクセス方式の概要

sysfsアクセス方式の概要

ピンのエクスポート要求: あるピンを使用する前に、手動でGPIOマネージャーに該当ピンリソースのエクスポートを要求する必要があります。

Terminal window
echo 176 \> /sys/class/gpio/export \## gpio_request 対応するGPIOのエクスポートを要求

該当ピンの動作モード(入力または出力)を設定します。

Terminal window
echo in > /sys/class/gpio/gpio176/direction ## gpio_direction_output 対応するGPIOを入力方向に設定
## または
echo out > /sys/class/gpio/gpio176/direction ## gpio_direction_output 対応するGPIOを出力方向に設定

ピンの動作モードに応じて、対応する制御(レベルの書き込みまたは読み取り)を行います。

Terminal window
cat /sys/class/gpio/gpio176/value ## gpio_get_value GPIOの現在のステータス値を取得
## または
echo 0 > /sys/class/gpio/gpio176/value ## gpio_set_value Lowレベル(低電位)を出力に設定
echo 1 > /sys/class/gpio/gpio176/value ## gpio_set_value Highレベル(高電位)を出力に設定

ピンの解放要求: ピンの使用が完了した後、手動でGPIOマネージャーに該当ピンリソースの解放を要求する必要があります。

Terminal window
echo 176 \> /sys/class/gpio/unexport \## gpio_free 要求したGPIOを解放

4.2. クイックスタート

4.2.1 開発環境の準備

PC側のUbuntuシステムで run スクリプトを実行し、EASY-EAIコンパイル環境に入ります。詳細は以下の通りです。

Terminal window
cd ~/develop_environment
./run.sh 2204

4.2.2 ソースコードのダウンロードとサンプルのコンパイル

まず、仮想マシンのバックグラウンドターミナルで以下のコマンドを実行し、周辺機器サンプルソースコードの管理ディレクトリを作成します:

Terminal window
cd /opt
mkdir -p EASY-EAI-Nano-TB/demo

サンプルプログラムをダウンロードします:

例えば、サンプルプログラムを「PC\D:」(指定はありません。ユーザーの任意の場所で構いません)にダウンロードします。

その後、ダウンロードしたサンプルを仮想マシンのファイルシステムにコピーします。手順は下図をご参照ください。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

最後に、該当するサンプルのディレクトリに移動してコンパイル操作を実行します。具体的なコマンドは以下の通りです:

Terminal window
cd EASY-EAI-Nano-TB/demo/09_GPIO
./build.sh

💡 注意 : 依存ライブラリはボード上に配置されているため、クロスコンパイル中は/mnt のマウントを維持する必要があります。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

コンパイルが成功すると、関連するデモが Releaseディレクトリに生成され、開発ボードの /userdata/ディレクトリに自動的に配置されます。

4.2.3 サンプルの実行

シリアルポートデバッグまたはSSH経由でボードのバックグラウンドにアクセスし、サンプルが配置されているディレクトリに移動します:

Terminal window
cd /userdata

サンプルの実行

サンプルの実行

以下のコマンドを実行してサンプルを起動します:

Terminal window
sudo ./test-gpio

実行結果は以下の通りです。

サンプルの実行

サンプルの実行

さらに、【GPIO5_C0】と【GPIO5_C1】を導線で短絡(ショート)させると、【GPIO5_C1】ピンから【GPIO5_C0】が出力する【Highレベル】を読み取ることができます。詳細は以下の通りです。

サンプルの実行

サンプルの実行

4.3. C言語での使用例

GPIOのC言語での使用例です。コードのパスは 09_GPIO/test-gpio/main.c です。コーディングの参考にしてください。以下のコードは、GPIOの操作フローを示しています:

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
static const GPIOCfg_t gpioCfg_tab[] = {
{
.pinName = "GPIO5_C0",
.direction = DIR_OUTPUT,
.val = 0,
}, {
.pinName = "GPIO5_C1",
.direction = DIR_INPUT,
.val = 0,
/*
}, {
.pinName = "GPIO5_C2",
.direction = DIR_OUTPUT,
.val = 0,
}, {
.pinName = "GPIO5_C6",
.direction = DIR_INPUT,
.val = 0,
*/
}
};
int main(int argc, char **argv){
gpio_init(gpioCfg_tab, ARRAY_SIZE(gpioCfg_tab));
pin_out_val("GPIO5_C0", 1);
// pin_out_val("GPIO5_C2", 0);
int val = read_pin_val("GPIO5_C1");
printf("GPIO5_C1 val : %d\n", val);
// val = read_pin_val("GPIO5_C6");
// printf("GPIO5_C6 val : %d\n", val);
return 0;
}

コード内の gpio_init()、pin_out_val()、read_pin_val() は、libgpiodをベースに使いやすくカプセル化したラッパー関数です。具体的な実装は09_GPIO/commonApi/gpio.c に記述されています。

ユーザーがデモのように libgpiodを参照する必要がある場合、以下の2点に注意してください。

  • ヘッダーファイルをインクルードする必要があります:#include <gpiod.h>

  • コンパイル時に、コンパイルパラメータとして -lgpiod を追加する必要があります。

5.PWM

5.1. PWMの概要

5.1.1 開発ボードのPWMリソース

開発ボードのPWMリソース

開発ボードのPWMリソース

5.1.2 PWMノードの検索

rv1126bのPWMリソース表は以下の通りです:

PWMノードの検索

PWMノードの検索

  • 【PWM1 CH0】は pwm1_4ch_0 に対応し、レジスタアドレスは 20700000 です。

  • 【PWM1 CH1】は pwm1_4ch_1 に対応し、レジスタアドレスは 20710000 です。

PWMドライバが正常にロードされると、ファイルシステムの /sys/class/pwm/ 配下にPWMノード(pwmchip*)が生成されます。以下のコマンドを使用することで、PWMノードとPWMリソースの対応関係を確認できます。

  • 【PWM1 CH0】に対応するノードは【pwmchip1】です。

  • 【PWM1 CH1】に対応するノードは【pwmchip2】です。

5.1.3 PWMノードの操作

以下に【PWM1 CH0】を操作する例を示します:

まず、コマンドで pwmchip1 コントローラ(/sys/class/pwm/pwmchip1)に移動すると、以下の内容が確認できます:

Terminal window
cd /sys/class/pwm/pwmchip1
ls
  • export:PWMタイマーデバイスのエクスポートに使用します。

  • unexport:PWMタイマーデバイスの解放に使用します。

/exportファイルに 0 を書き込むと、PWMタイマーが有効になり、pwm0ディレクトリが生成されます。

Terminal window
echo 0 \> export

PWMノードの操作

PWMノードの操作

pwm0 タイマーディレクトリに入ると、周期やデューティ比など、各種属性を設定できます。

Terminal window
cd pwm0

PWMノードの操作

PWMノードの操作

Terminal window
echo 1000000 > period ## タイマーの1周期内のパルス数(周期時間)を設定
echo 500000 > duty_cycle ## タイマーの1周期内のLowレベルのパルス数(アクティブ時間)を設定
echo 1 > enable ## PWMタイマーを有効化(イネーブル)
echo 0 > enable ## PWMタイマーを無効化(ディスエーブル)

逆に、unexport ファイルに 0 を書き込むとPWMタイマーが閉じられ、同時にpwm0 ディレクトリが削除されます。

Terminal window
cd ..
echo 0 > unexport

PWMノードの操作

PWMノードの操作

5.2. クイックスタート

5.2.1 開発環境の準備

PC側のUbuntuシステムで run スクリプトを実行し、EASY-EAIコンパイル環境に入ります。詳細は以下の通りです。

Terminal window
cd ~/develop_environment
./run.sh 2204

5.2.2 ソースコードのダウンロードとサンプルのコンパイル

まず、仮想マシンのバックグラウンドターミナルで以下のコマンドを実行し、周辺機器サンプルソースコードの管理ディレクトリを作成します:

Terminal window
cd ~/develop_environment
./run.sh 2204

サンプルプログラムをダウンロードします:

例えば、サンプルプログラムを「PC\D:」(指定はありません。ユーザーの任意の場所で構いません)にダウンロードします。

その後、ダウンロードしたサンプルを仮想マシンのファイルシステムにコピーします。手順は下図をご参照ください。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

最後に、該当するサンプルのディレクトリに移動してコンパイル操作を実行します。具体的なコマンドは以下の通りです:

Terminal window
cd EASY-EAI-Nano-TB/demo/10_PWM
./build.sh

💡 注意 :依存ライブラリはボード上に配置されているため、クロスコンパイル中は/mnt のマウントを維持する必要があります。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

コンパイルが成功すると、Release ディレクトリに test-pwmという実行可能プログラムが生成され、開発ボードの /userdata/ディレクトリに自動的に配置されます。

5.2.3 サンプルの実行

シリアルポートデバッグまたはSSH経由でボードのバックグラウンドにアクセスし、サンプルが配置されているディレクトリに移動します:

Terminal window
cd /userdata

サンプルの実行

サンプルの実行

以下のコマンドを実行してPWM出力デモを起動します:

Terminal window
sudo ./test-pwm

実行結果は以下の通りです。

サンプルの実行

サンプルの実行

オシロスコープで取得した波形は以下の図の通りです:

サンプルの実行

サンプルの実行

5.3. C言語での使用例

PWMのC言語での使用例です。コードのパスは 10_PWM/test-pwm/main.c です。コーディングの参考にしてください。以下のコードは、PWMコントローラの初期化、周期の調整、デューティ比の設定、およびリソース解放の操作フローを示しています:

int main(int argc, const char** argv){
int ret;
ret = pwm_init("pwmchip1", "0");
printf("export_ret:%d\n", ret);
ret = pwm_set_attr("pwmchip1", "0", "period", "1000000");
printf("set_period_ret:%d\n", ret);
ret = pwm_set_attr("pwmchip1", "0", "duty_cycle", "500000");
printf("set_duty_cycle_ret:%d\n", ret);
ret = pwm_set_enable("pwmchip1", "0", "1");
printf("set_enable:%d\n", ret);
ret = pwm_release("pwmchip1", "0");
printf("unexport_ret:%d\n", ret);
//======================================================================
ret = pwm_init("pwmchip2", "0");
printf("export_ret:%d\n",ret);
ret = pwm_set_attr("pwmchip2", "0", "period", "1000000");
printf("set_period_ret:%d\n",ret);
ret = pwm_set_attr("pwmchip2", "0", "duty_cycle", "500000");
printf("set_duty_cycle_ret:%d\n",ret);
ret = pwm_set_enable("pwmchip2", "0", "1");
printf("set_enable:%d\n",ret);
ret = pwm_release("pwmchip2", "0");
printf("unexport_ret:%d\n",ret);
//======================================================================
return 0;
}

コード内の pwm_init()、pwm_set_attr()、pwm_set_enable()、pwm_release() は、システムコールを使いやすくカプセル化したラッパー関数です。具体的な実装は 10_PWM/test-pwm/main.c に記述されています。

6.CANインターフェース

6.1. CANの概要

Socket CANを使用する主な目的は、ユーザースペースのアプリケーションにLinuxネットワーク層ベースのソケット(Socket)インターフェースを提供することです。

よく知られているTCP/IPプロトコルやイーサネットとは異なり、CANバスにはイーサネットのようなMAC層のアドレスがなく、ブロードキャストにのみ使用されます。CAN IDはバス上の調停(アービトレーション)にのみ使用されるため、バス上で一意である必要があります。CAN-ECU(Electronic Control Unit:電子制御ユニット)ネットワークを設計する際、CANメッセージのIDを特定のECUにマッピングできます。したがって、CANメッセージIDを送信元のアドレスとして使用することが可能です。

6.1.1 開発ボードのCANリソース

開発ボードのCANリソース

開発ボードのCANリソース

6.1.2 ハードウェアの接続

通常、CPUから出力されるCAN信号はTTL信号であり、差動(ディファレンシャル)信号ではありません。そのため、CAN TTL信号をCAN差動信号に変換するモジュールが必要です。具体的な配線図は以下の通りです。

ハードウェアの接続

ハードウェアの接続

6.2. クイックスタート

6.2.1 開発環境の準備

PC側のUbuntuシステムで run スクリプトを実行し、EASY-EAIコンパイル環境に入ります。詳細は以下の通りです。

Terminal window
cd ~/develop_environment
./run.sh 2204

6.2.2 ソースコードのダウンロードとサンプルのコンパイル

まず、仮想マシンのバックグラウンドターミナルで以下のコマンドを実行し、周辺機器サンプルソースコードの管理ディレクトリを作成します:

Terminal window
cd /opt
mkdir -p EASY-EAI-Nano-TB/demo

サンプルプログラムをダウンロードします:

  • リンク: EASY-EAI-Nano-TB-demo.zip 例えば、サンプルプログラムを「PC\D:」(指定はありません。ユーザーの任意の場所で構いません)にダウンロードします。

その後、ダウンロードしたサンプルを仮想マシンのファイルシステムにコピーします。手順は下図をご参照ください。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

最後に、該当するサンプルのディレクトリに移動してコンパイル操作を実行します。具体的なコマンドは以下の通りです:

Terminal window
cd EASY-EAI-Nano-TB/demo/11_CAN
./build.sh

💡 注意 : 依存ライブラリはボード上に配置されているため、クロスコンパイル中は/mnt のマウントを維持する必要があります。

ソースコードのダウンロードとサンプルのコンパイル

ソースコードのダウンロードとサンプルのコンパイル

コンパイルが成功すると、送信側の test-can_send と受信側のtest-can_reception の2つのデモプログラムが生成され、開発ボードの/userdata/ ディレクトリに自動的に配置されます。

  • このサンプルでは送受信テストに2枚のボードが必要となるため、2枚のボードの両方で上記の手順を繰り返す必要があります。

6.2.3 サンプルの実行

シリアルポートデバッグまたはSSH経由でボードのバックグラウンドにアクセスし、サンプルが配置されているディレクトリに移動します:

Terminal window
cd /userdata

サンプルの実行

サンプルの実行

まず、1枚目のボードで送信側を実行します。コマンドは以下の通りです:

Terminal window
sudo ./test-can-send

次に、【もう1枚の】ボードで受信側を実行します。コマンドは以下の通りです:

Terminal window
cd /userdata
sudo ./test-can-reception

【受信側】の実行結果は以下の通りです。

サンプルの実行

サンプルの実行

APIの詳細な説明およびAPIの呼び出し(本サンプルのソースコード)については、以下の説明をご参照ください。

6.3. CAN操作APIの説明

6.3.1 Socket CANソケットの作成

Socket CANソケットを作成する関数のプロトタイプは以下の通りです。

Terminal window
int socket(int domain, int type, int protocol);

詳細な説明は以下の通りです。

項目内容
関数名socket()
ヘッダーファイル<sys/socket.h>
入力パラメータDomain(ドメイン): AF_CAN (または PF_CAN)Type(タイプ): SOCK_RAWProtocol(プロトコル): CAN_RAW
戻り値成功: ファイルディスクリプタ(fd)失敗: -1
注意事項特になし

6.3.2 ローカルネットワークインターフェースアドレスの指定

ローカルネットワークインターフェースアドレスを指定する関数のプロトタイプは以下の通りです。

Terminal window
int ioctl(int fd, unsigned long request, ...);

詳細な説明は以下の通りです。

ioctl() 関数
項目内容
関数名ioctl()
ヘッダーファイル<sys/ioctl.h>
入力パラメータsockfd: socket() 関数で作成したファイルディスクリプタ(fd)
Request: 制御コマンド
戻り値成功: 0
失敗: -1
注意事項ioctl を使用してローカルネットワークインターフェースのアドレスを取得する際は、ifreq 構造体を使用する必要があります。
struct ifreq 構造体
項目内容
構造体名struct ifreq
ヘッダーファイル<linux/if.h>
注意事項ioctl() 関数呼び出し時に CAN デバイスのインデックス(ifr_ifindex)を取得するために使用します。その他のパラメータについては特に気にする必要はありません。

6.3.3 アドレス構造体のバインド(関連付け)

アドレス構造体をバインドする関数のプロトタイプは以下の通りです。

int bind(int sockfd, const struct sockaddr \*addr, socklen_t addrlen);
項目内容
関数名bind()
ヘッダーファイル<sys/socket.h>
入力パラメータsockfd: socket() 関数で作成したファイルディスクリプタ(fd)
addr: SocketCANアドレス構造体(struct sockaddr_can<linux/can.h> で定義)
addrlen: アドレスの長さ
戻り値成功: 0
失敗: -1
注意事項特になし

6.3.4 CANフィルタの設定

CANフィルタを設定する関数のプロトタイプは以下の通りです。

int setsockopt(int sockfd, int level, int optname, const void \*optval, socklen_t optlen);

詳細な説明は以下の通りです。

setsockopt() 関数
項目内容
関数名setsockopt()
ヘッダーファイル<sys/socket.h>
入力パラメータlevel: オプションが定義されている階層(よく使われるもの:SOL_SOCKET, IPPROTO_TCP, SOL_CAN_RAW
optname: 設定するオプション
optval: オプションに設定する新しい値が格納されたバッファへのポインタ
optlen: optval バッファの長さ
戻り値成功: 0
失敗: -1
注意事項フィルタ構造体 struct can_filter<linux/can.h> で定義)を使用する必要があります。
struct can_filter 構造体
項目内容
構造体名struct can_filter
ヘッダーファイル<linux/can.h>
パラメータcan_id: メッセージID
can_mask: フィルタマスク
注意事項フィルタリングの規則は以下の通りです:
(受信したデータフレームの can_id) & mask == can_id & mask
mask<linux/can.h> で定義されており、受信するフレームタイプに応じて選択します。

6.3.5 CANメッセージフォーマットの定義

CANメッセージフォーマットの定義は以下の通りです。

項目内容
構造体名struct can_frame
ヘッダーファイル<linux/can.h>
パラメータcan_id: メッセージID
can_dlc: データ長
Data[]: データを格納する配列
注意事項デフォルトでは標準フレームが送信されます。リモートフレームやエラーフレームを送信する場合は、メッセージIDを操作する必要があります。
(例:can_id = 0x1

6.4. CAN通信サンプルプログラム

【送信側】のサンプルソースコードは 11_CAN/test-can_send/main.cにあります。操作フローは図の通りです。

CAN通信サンプルプログラム

CAN通信サンプルプログラム

【受信側】のサンプルソースコードは 11_CAN/test-can_reception/main.cにあります。操作フローは図の通りです。

CAN通信サンプルプログラム

CAN通信サンプルプログラム

参考となるサンプルコードは以下の通りです。

送信側サンプル:

/* CAN0のボーレートを500000 bpsに設定 / #define ip_cmd_set_can_params “ip link set can0 type can bitrate 500000 triple-sampling on” / CAN0を起動 / #define ip_cmd_open “ifconfig can0 up” / CAN0を停止 */ #define ip_cmd_close “ifconfig can0 down”

int main()
{
int fd, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame[2] = {{0}};
system(ip_cmd_close);
system(ip_cmd_set_can_params);
system(ip_cmd_open);
fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); // ソケットを作成
strcpy(ifr.ifr_name, "can0");
ioctl(fd, SIOCGIFINDEX, &ifr); // can0 デバイスを指定
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(fd, (struct sockaddr *)&addr, sizeof(addr)); // ソケットを can0 にバインド
// フィルタリングルールを無効化。このプロセスはメッセージを受信せず、送信のみを担当
setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
// 2つのメッセージを生成
frame[0].can_id = 0x11;
frame[0].can_dlc = 1;
frame[0].data[0] = 'Y';
frame[1].can_id = 0x22;
frame[1].can_dlc = 1;
frame[1].data[0] = 'N';
// 2つのメッセージをループで送信
while(1)
{
nbytes = write(fd, &frame[0], sizeof(frame[0])); // frame[0] を送信
printf("write ret:%d\n", nbytes);
if(nbytes != sizeof(frame[0])) {
printf("Send Error frame[0]!\n");
break; // 送信エラー、終了
}
sleep(1);
nbytes = write(fd, &frame[1], sizeof(frame[1])); // frame[1] を送信
if(nbytes != sizeof(frame[1])) {
printf("Send Error frame[1]!\n");
break;
}
sleep(1);
}
close(fd);
return 0;
}

受信側サンプル:

/* CAN0のボーレートを500000 bpsに設定 */
#define ip_cmd_set_can_params "ip link set can0 type can bitrate 500000 triple-sampling on"
/* CAN0を起動 */
#define ip_cmd_open "ifconfig can0 up"
/* CAN0を停止 */
#define ip_cmd_close "ifconfig can0 down"
int main()
{
int fd, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
struct can_filter rfilter[1];
system(ip_cmd_close);
system(ip_cmd_set_can_params);
system(ip_cmd_open);
fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); // ソケットを作成
strcpy(ifr.ifr_name, "can0");
ioctl(fd, SIOCGIFINDEX, &ifr); // can0 デバイスを指定
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(fd, (struct sockaddr *)&addr, sizeof(addr)); // ソケットを can0 にバインド
// 受信ルールを定義。識別子が 0x11 のメッセージのみを受信
rfilter[0].can_id = 0x11;
rfilter[0].can_mask = CAN_SFF_MASK;
// フィルタリングルールを設定
setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
while(1)
{
nbytes = read(fd, &frame, sizeof(frame)); // メッセージを受信
// メッセージを表示
if(nbytes > 0) {
printf("ID=0x%X DLC=%d data[0]=0x%X\n", frame.can_id, frame.can_dlc, frame.data[0]);
}
}
close(fd);
return 0;
}