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?

NANOPI NEO3 GPIOでDHT11を接続

Posted at

NANOPI NEO3 GPIOにDHT11を接続してみた

はじめに

ここ数か月、空き時間を見ながら調べていたが、やっと動いたので同じことを探している人に役立つかと思い、記録しておきます。
NANOPI NEO3、いくら検索しても情報が少ない。これまでRaspberry Piを参考に解決してきたが、DHT11となると、何も情報がない!
WiringNPも動かない、gpiodも動かない。RPi.GPIO_NPも動かない、どうする?

ChatGTPさんの助けをかりた

次のような基本的な提案がいくつかあった。、

gpiodを使った pythonサンプル, cサンプル...一緒に考えてくれた...そして...解決!

環境

NANOPI NEO3
イメージ: rk3328-sd-friendlycore-lite-focal-5.10-arm64-20210830.img

Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal

必要なパッケージ

gpiodなどは不要。開発環境のみインストール

sudo apt-get update
sudo apt-get install build-essential
sudo apt-get install gdb
sudo apt-get install rt-tests

DHT11の接続方法

接続方法は、以下の様にした。

PIN 2 5V
PIN 6 GND
PIN 7 DATA

NENOPI.jpg

GPIO.jpg

悪戦苦闘

この接続の場合、Linux GPIO66, GPIOチップ名: gpiochip2, GPIOピン番号: 2(gpiochip2内の相対番号)でgpiod で動くはずだがDHT11が全く応答していない。
DHT22やI2C式センサーなら動くよ、との提案もあったが、DHT22が入手困難 (EOL)

gpiodよりmmap

chatGPTさんからの提案で

mmap + C言語で直接GPIO制御する場合
NanoPi NEO3のGPIO2のレジスタベースは 0xFF780000(RK3328の場合)ですが、GPIO2_A2がピン2に対応するなら
GPIO_PIN = 2 (bit 2 を操作)
で問題ありません。

ときたので、gpiodを諦めmmapでアクセスすることにした。サンプルコードを作成してもらう。
コンパイルして、実行したが動かない。

GPIOベースアドレスとピン番号の確認

指示とおりに確認してみた。

pi@NanoPi-NEO3:~/.vscode-server$ sudo cat /proc/iomem | grep gpio
ff210000-ff2100ff : ff210000.gpio gpio@ff210000
ff220000-ff2200ff : ff220000.gpio gpio@ff220000
ff230000-ff2300ff : ff230000.gpio gpio@ff230000
ff240000-ff2400ff : ff240000.gpio gpio@ff240000

サンプルコードの値を正しく修正
修正前

#define RK3328_GPIO2_BASE 0xFF780000 

修正後

#define RK3328_GPIO2_BASE 0xFF230000  // GPIO2 base address corrected

GPIOが動作した

HDT11は、まだ動かない。ただ、初期のGPIO信号の書き込みが成功して、信号がHIGHになったことを確認した。
のでDEBUGの手段として、GPIOのみ確認するよう提案があった。
プログラムは、以下のとおり。

gpio.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <time.h>

#define RK3328_GPIO2_BASE 0xFF230000  

//#define GPIO_MEM_SIZE     0x10000     // 64KB
#define GPIO_MEM_SIZE 0x100
#define GPIO_PIN          2           // GPIO2_A2 = GPIO66
#define DATA_BITS         40
#define DELAY         30
#define TiMEOUT         2500

#define GET_BIT(value, bit) (((value) >> (bit)) & 1)

volatile uint32_t *gpio_base = NULL;

void delay_us(unsigned int us) {
    struct timespec ts_start, ts_now;
    clock_gettime(CLOCK_MONOTONIC_RAW, &ts_start);
    do {
        clock_gettime(CLOCK_MONOTONIC_RAW, &ts_now);
    } while (((ts_now.tv_sec - ts_start.tv_sec) * 1000000 +
             (ts_now.tv_nsec - ts_start.tv_nsec) / 1000) < us);
}

// GPIO register macros (RK3328)
#define GPIO_SWPORTA_DR      0x00
#define GPIO_SWPORTA_DDR     0x04
#define GPIO_EXT_PORTA       0x50

void set_gpio_output() {
    uint32_t val = gpio_base[GPIO_SWPORTA_DDR / 4];
    val |= (1 << GPIO_PIN);
    gpio_base[GPIO_SWPORTA_DDR / 4] = val;
}

void set_gpio_input() {
    uint32_t val = gpio_base[GPIO_SWPORTA_DDR / 4];
    val &= ~(1 << GPIO_PIN);
    gpio_base[GPIO_SWPORTA_DDR / 4] = val;
}

void gpio_write(int val) {
    uint32_t reg = gpio_base[GPIO_SWPORTA_DR / 4];
    if (val)
        reg |= (1 << GPIO_PIN);
    else
        reg &= ~(1 << GPIO_PIN);
    gpio_base[GPIO_SWPORTA_DR / 4] = reg;
}

int gpio_read() {
    return (gpio_base[GPIO_EXT_PORTA / 4] & (1 << GPIO_PIN)) != 0;
}


int main() {
    int fd = open("/dev/mem", O_RDWR | O_SYNC);
    gpio_base = mmap(NULL, GPIO_MEM_SIZE, PROT_READ | PROT_WRITE,
                     MAP_SHARED, fd, RK3328_GPIO2_BASE);

    set_gpio_output();

    while (1) {
        gpio_write(0);  // LOW
        printf("write 0\n");
        usleep(500000);
        gpio_write(1);  // HIGH
        printf("write 1\n");
        usleep(500000);
    }
}

ビルド

$gcc -O2 -o gpio gpio.c

実行

pi@NanoPi-NEO3:~/.vscode-server$ sudo ./gpio
write 0
write 1
write 0
write 1
write 0
write 1
write 0
write 1
write 0
write 1

この間、PIN 7をテスターで確認するとLOW/HIGHが切り替わっていることを確認!
GPIOのLOW/HIGH切り替えが正しく動いているのが確認できた。

DHT11信号タイミングの概要

恐らく、信号のタイミングの問題ではないかとの推測がchatGPTさんからあった。

スタート信号
MCUがDATAラインをLOWにして18ms以上保持 → DHT11が応答を返す準備
DHT11応答
80μs LOW
80μs HIGH
データ送信(40ビット)
1ビットはLOWで始まり、その後のHIGHの長さで0か1か判別
LOWは約50μs固定
HIGHの長さ
約26〜28μs → 0
約70μs → 1

DEBUGメッセージを入れて確認すると、DHT11からの読み取りが40バイト前にタイムアウトしてしまうことが分かった。

mmapサイズを256に小さくする。もとは 0xFF230000

#define GPIO_MEM_SIZE 0x100

完成!

何度かタイミングの修正の提案を反映しながらテストしていると、あれ? 動いた!


pi@NanoPi-NEO3:~/.vscode-server$ sudo ./dht11_mmap 
成功: 湿度 50%, 温度 26°C

だた、何度も実行するとエラー、成功と安定しない。最終的には、リトライを入れて対応
以下が、コードです。

dht11_mmap.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

// GPIO2 Base アドレス(GPIO2_Ax)
#define RK3328_GPIO2_BASE 0xFF230000
#define GPIO_MEM_SIZE     0x100
#define GPIO_PIN          2  // GPIO2_A2 = GPIO66
#define DATA_BITS         40

volatile uint32_t *gpio_base = NULL;

// GPIO レジスタ
#define GPIO_SWPORTA_DR   0x00
#define GPIO_SWPORTA_DDR  0x04
#define GPIO_EXT_PORTA    0x50

void delay_us(unsigned int us) {
    struct timespec ts_start, ts_now;
    clock_gettime(CLOCK_MONOTONIC_RAW, &ts_start);
    do {
        clock_gettime(CLOCK_MONOTONIC_RAW, &ts_now);
    } while (((ts_now.tv_sec - ts_start.tv_sec) * 1000000 +
             (ts_now.tv_nsec - ts_start.tv_nsec) / 1000) < us);
}

void set_gpio_output() {
    uint32_t val = gpio_base[GPIO_SWPORTA_DDR / 4];
    val |= (1 << GPIO_PIN);
    gpio_base[GPIO_SWPORTA_DDR / 4] = val;
}

void set_gpio_input() {
    uint32_t val = gpio_base[GPIO_SWPORTA_DDR / 4];
    val &= ~(1 << GPIO_PIN);
    gpio_base[GPIO_SWPORTA_DDR / 4] = val;
}

void gpio_write(int val) {
    uint32_t reg = gpio_base[GPIO_SWPORTA_DR / 4];
    if (val)
        reg |= (1 << GPIO_PIN);
    else
        reg &= ~(1 << GPIO_PIN);
    gpio_base[GPIO_SWPORTA_DR / 4] = reg;
}

int gpio_read() {
    return (gpio_base[GPIO_EXT_PORTA / 4] & (1 << GPIO_PIN)) != 0;
}

int read_dht11_data(int *humidity, int *temperature) {
    uint8_t data[5] = {0};

    // スタートシーケンス
    set_gpio_output();
    gpio_write(0);
    delay_us(20000);  // 20ms
    gpio_write(1);
    delay_us(40);     // 40us
    set_gpio_input();

    // 応答待ち: LOW → HIGH
    int timeout = 0;
    while (gpio_read()) {
        delay_us(1);
        if (++timeout > 100) {
            printf("Timeout waiting for LOW (start)\n");
            return -1;
        }
    }

    timeout = 0;
    while (!gpio_read()) {
        delay_us(1);
        if (++timeout > 100) {
            printf("Timeout waiting for HIGH (response)\n");
            return -1;
        }
    }

    timeout = 0;
    while (gpio_read()) {
        delay_us(1);
        if (++timeout > 100) {
            printf("Timeout waiting for LOW (data start)\n");
            return -1;
        }
    }

    // ビット読み取り(40ビット = 5バイト)
    for (int i = 0; i < 40; i++) {
        // LOW: 開始ビット (約50us)
        timeout = 0;
        while (!gpio_read()) {
            delay_us(1);
            if (++timeout > 100) {
                printf("Timeout waiting for HIGH at bit %d\n", i);
                return -1;
            }
        }

        // HIGHパルス長で0/1を判別
        delay_us(35);  // 約26〜28us: 0, 70us: 1

        if (gpio_read()) {
            data[i / 8] |= (1 << (7 - (i % 8)));
        }

        // HIGHが続く場合、LOWに戻るまで待つ
        timeout = 0;
        while (gpio_read()) {
            delay_us(1);
            if (++timeout > 100) break;
        }
    }

    // チェックサム確認
    uint8_t sum = data[0] + data[1] + data[2] + data[3];
    if (data[4] != sum) {
        printf("Checksum error: got %d, expected %d\n", data[4], sum);
        return -1;
    }

    *humidity = data[0];
    *temperature = data[2];
    return 0;
}

int main() {
    int fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    gpio_base = (uint32_t *)mmap(NULL, GPIO_MEM_SIZE, PROT_READ | PROT_WRITE,
                                 MAP_SHARED, fd, RK3328_GPIO2_BASE);
    if (gpio_base == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return 1;
    }

    int humidity = 0, temperature = 0;
    int retries = 5;
    while (retries-- > 0) {
        if (read_dht11_data(&humidity, &temperature) == 0) {
            printf("成功: 湿度 %d%%, 温度 %d°C\n", humidity, temperature);
            break;
        }
        printf("再試行中...\n");
        sleep(1); // DHT11の仕様では1秒以上空ける必要あり
    }

    munmap((void *)gpio_base, GPIO_MEM_SIZE);
    close(fd);
    return 0;
}

ビルドと実行

$gcc -O2 -o dht11_mmap dht11_mmap.c
$sudo ./dht11_mmap

終わりに

chatGPTさん、すごいな。もう無理かと思っていたことで解決できたことに感謝!
NANOPI NEO3でDHT11が使えること分かったし、そもそも GPIOが制御できなかったのみ、これで解決した。

フルソースは、GitHubに置きました。お役に立てれば幸いです。

あとがき

そして、これが現在のNANOPI NEO3です。まだまだ拡張できるかな?

USB3.0 aeroTAP 3D USB GSカメラ
USB2.0 WiFi
GPIO DHT11
LAN Gigabit LAN
裏にヒートシンク

SmartSensor.jpg

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?