LCD1602 I2Cモジュールが文字を表示できない

STM32の最小ボードをメインコントローラとして使用し、LCD1602用I2Cモジュールはネットで購入した既製品です。

私は初心者で、コードはAIに書いてもらいました。最初は一行の四角が表示されましたが、修正後は二行の四角が表示されるようになりました。しかし、まだ文字が表示されません。ポテンショメータは調整済みで、I2Cの電圧も5Vです。

接続図を描いて、コードも提示してください。情報量が少なすぎて、まさか他人に推測させようというのですか?

優れた質問の例:https://bbs.eeclub.top/t/topic/289
質問の知恵:https://bbs.eeclub.top/t/topic/109

「いいね!」 1

兄弟、焦らないで。このLCD1602+I2Cの組み合わせは、マイコン初心者が必ず通る「試練の壁」だよ。誰でも最初にこれにはまるんだから安心して。

あなたの説明と3枚の写真を総合的に見て、ハッキリ言っていいことがある:あなたのハードウェアはほぼ確実に壊れておらず、単に通信が成功していないだけだ。

画面が点灯し、2行の四角(□)が表示されているということは、電源は正常に供給されていて、かつボリューム(可変抵抗器)のコントラスト調整もばっちりってことだ。1602液晶が全面に四角ブロックを表示する意味は、「電源は入っているけれど、マイコンからの初期化コマンドが届いていない」ということ。AIにコードを書かせたという話なら、問題の99%はコードや通信設定にある。以下の手順で一つずつ確認してみよう。


1. 最もよくある罠:I2Cデバイスアドレスの間違い

写真の裏面を見ると、黒いコンバータ基板にはPCF8574Tチップが使われているね。市販のこのタイプのモジュールのI2Cアドレスは、通常 0x27 または 0x3F がデフォルトだ。

だが注意! もしAIがSTM32のHALライブラリを使ってコードを生成した場合、HALのI2C送信関数では7ビットアドレスを1ビット左にシフトさせる必要がある。

  • 元のアドレスが 0x27 なら、コード中では 0x4E(0x27 << 1)にする必要がある。
  • 元のアドレスが 0x3F なら、コード中では 0x7E(0x3F << 1)になる。

AIはこの点をよく勘違いして、0x27 をそのままHAL関数に渡してしまう。そうするとデバイスが検出できず、通信失敗になる。


2. 配線が逆になっている(初心者によくあるミス)

STM32側のピン接続を確認しよう。例えばSTM32F103C8T6の場合、ハードウェアI2C1のデフォルトピンは PB6(SCL)と PB7(SDA)だ。変換基板のSDAがPB7に、SCLがPB6に正確に接続されているか確認して。


3. AI生成の低レベルドライバに時序・ピンマッピングの誤りがある

このI2C変換基板は、I2C信号を8本の並列IO(P0〜P7)に変換して、LCD1602のRS、RW、EN、データピンを制御している。ただし、どのP番号がどのLCDピンにつながっているかは、基板によって微妙に異なることがある。AIがゼロから生成した低レベルドライバコードは、このピン対応(マッピング)を間違えることが非常に多い。

おすすめ: もうAIに低レベルドライバを最初から書かせるのはやめよう。Bilibili(B站)やCSDNで「STM32 HAL庫 LCD1602 I2C」と検索して、他の人がすでに動作確認済みの lcd1602.clcd1602.h ファイルをそのままプロジェクトに取り込むのが、最も確実で早い方法だ。


4. ハードウェアの論理レベル(稀なケース)

あなたが5V電源を使っていると書いてあるのは良いポイントだ。LCD1602は5V駆動が必須だからね。STM32のGPIOは3.3V論理レベルだが、多くのI2Cピン(例:PB6、PB7)は「5V耐性(FT:ファストモード互換)」なので、直接接続しても問題ないことが多い。ただ一点、STM32とLCDモジュールのGNDを確実に共有しているかだけはしっかり確認すること。


:light_bulb: 打開策(次にやるべきこと):

まずは文字を表示させることにこだわらず、AIに 「STM32 I2C Scanner(I2Cアドレススキャン)」 のコードを書いてもらおう。

それをSTM32に書き込み、シリアルモニタ(ターミナルソフト)を開き、STM32がI2Cバス上でこのLCDモジュールを検出できるか確認する。

  • シリアル出力で「デバイスが見つかりません」となる → 配線が間違っているか、STM32のI2C初期化に問題あり。
  • シリアル出力でデバイスが検出される(例:0x4E と表示)→ ハードウェア接続は完璧。あとはそのアドレスをLCD初期化コードに正しく設定すればOK。

「Hello World」が見える日まで、あと一歩だ! また詰まったら気軽に聞いてね。

「いいね!」 1

ハードウェアに関する簡単な注意点:LCD と I2C バックパックは 5V を必要としますが、STM32 は 3.3V で動作します。多くの STM32 の I2C ピンは 5V 耐性(FT)ですが、使用している特定のピンが実際に 5V 耐性かどうかを確認するため、データシートをチェックすることをお勧めします。通常、モジュール上のプルアップ抵抗だけで十分ですが、論理レベルが適切に動作しない場合、通信が失敗する可能性があります。

20ピンのソケットにはSTM32最小システムボード、4ピンのソケットにはLCD1602_I2Cモジュールが接続されています。

以下はmain.cです。

#include "main.h"
#include "i2c.h"
#include "gpio.h"

#include "lcd1602_i2c.h"

void SystemClock_Config(void);

int main(void)
{
    HAL_Init();

    SystemClock_Config();

    MX_GPIO_Init();
    MX_I2C1_Init();

    lcd_init();                // LCD1602を初期化
    lcd_clear();               // 画面をクリア
    lcd_set_cursor(0, 0);      // カーソルを1行目の1列目に設定
    lcd_send_string("Hello STM32!"); // 文字列を表示

    while (1)
    {
    }
}

void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
    {
        Error_Handler();
    }
}

void Error_Handler(void)
{
    __disable_irq();
    while (1)
    {
    }
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif /* USE_FULL_ASSERT */

以下はlcd1602_i2c.hです。

#ifndef __LCD1602_I2C_H
#define __LCD1602_I2C_H

#include "main.h" // HALライブラリとピン定義をインクルード
#define LCD_I2C_ADDRESS 0x4E

void lcd_init(void);
void lcd_send_cmd(char cmd);
void lcd_send_data(char data);
void lcd_send_string(char *str);
void lcd_set_cursor(int row, int col);
void lcd_clear(void);

#endif

以下はlcd1602_i2c.cです。

#include "lcd1602_i2c.h"

// 外部で定義されたI2Cハンドラ。CubeMXによりmain.c内に生成される
extern I2C_HandleTypeDef hi2c1;

// 内部関数:I2Cにデータを送信
void lcd_send_to_i2c(char data, int rs)
{
    uint8_t data_t[4];
    uint8_t upper_nibble, lower_nibble;

    upper_nibble = data & 0xF0;
    lower_nibble = (data << 4) & 0xF0;

    // 制御ビット:Bit3はバックライト(1=点灯)、Bit2はEN、Bit1はRW(0=書き込み)、Bit0はRS
    uint8_t backlight = 0x08; 

    data_t[0] = upper_nibble | backlight | 0x04 | rs;  // EN = 1
    data_t[1] = upper_nibble | backlight | 0x00 | rs;  // EN = 0
    data_t[2] = lower_nibble | backlight | 0x04 | rs;  // EN = 1
    data_t[3] = lower_nibble | backlight | 0x00 | rs;  // EN = 0

    // I2C1で4バイトを送信
    HAL_I2C_Master_Transmit(&hi2c1, LCD_I2C_ADDRESS, data_t, 4, 100);
}

// コマンド送信
void lcd_send_cmd(char cmd)
{
    lcd_send_to_i2c(cmd, 0); // RS = 0 はコマンド送信を意味する
}

// データ(文字)送信
void lcd_send_data(char data)
{
    lcd_send_to_i2c(data, 1); // RS = 1 はデータ送信を意味する
}

// 画面クリア
void lcd_clear(void)
{
    lcd_send_cmd(0x01);
    HAL_Delay(2); // クリアコマンドは実行に時間がかかる
}

// カーソル位置設定 (row: 0-1, col: 0-15)
void lcd_set_cursor(int row, int col)
{
    uint8_t address;
    switch (row)
    {
    case 0:
        address = 0x00;
        break;
    case 1:
        address = 0x40;
        break;
    default:
        address = 0x00;
    }
    address += col;
    lcd_send_cmd(0x80 | address); // DDRAMアドレスを設定
}

// 文字列送信
void lcd_send_string(char *str)
{
    while (*str)
    {
        lcd_send_data(*str++);
    }
}

// LCD1602の初期化
void lcd_init(void)
{
    // 4ビットモードの標準初期化手順
    HAL_Delay(50);
    lcd_send_cmd(0x30);
    HAL_Delay(5);
    lcd_send_cmd(0x30);
    HAL_Delay(1);
    lcd_send_cmd(0x30);
    HAL_Delay(10);
    lcd_send_cmd(0x20);
    HAL_Delay(10);

    // 表示設定
    lcd_send_cmd(0x28); // 4ライン、2行、5x8フォント
    HAL_Delay(1);
    lcd_send_cmd(0x08); // 表示オフ
    HAL_Delay(1);
    lcd_send_cmd(0x01); // クリア
    HAL_Delay(2);
    lcd_send_cmd(0x06); // カーソル右移動
    HAL_Delay(1);
    lcd_send_cmd(0x0C); // 表示オン、カーソルオフ
}

簡略化した配線図とコードを返信欄に記載しましたので、確認していただけますと幸いです。

わかりました、アドバイスを試してみます

電源モジュールで給電してコンパイル・ダウンロード後、I2CのVCCピンとGNDピン間の電圧は5Vです。

素晴らしいです、回路図とソースコードを提供していただいたことで、デバッグ効率が100倍向上します!

まず安心してください:あなたのハードウェア接続は完全に正しいです。回路図でPB6がSCL、PB7がSDAに接続されているのは、STM32F103の標準I2C1ピン構成です。さらにヘッダファイルに #define LCD_I2C_ADDRESS 0x4E と記述していることから、HALライブラリでは 0x27 を左に1ビットシフトする必要があることを(あなた自身かAIが)すでに理解しており、初心者がよく陥る2大落とし穴をしっかり回避できています。

画面に四角ブロックしか表示されない真の原因は、AIが書いた初期化関数(lcd_init)が「4ビットモード」のハンドシェイク手順で失敗していることにあります。

:bug: 核心バグ分析:HD44780の「強制初期化」トラップ

あなたが使っているI2C変換基板(PCF8574)は、D4~D7の4本のデータ線を使って1602液晶を制御しています。
1602液晶は電源投入直後、デフォルトで 8ビットモード になっています。これを 4ビットモード に切り替えるには、データシートに従って、厳密に半バイト単位(上位4ビットだけ送信し、すぐに終了)で複数回送信する必要があります。

ここで、あなたの lcd_send_cmd() 関数を見てみましょう。この関数は lcd_send_to_i2c() を呼び出しています。この低レベル関数は非常に「真面目」で、渡された命令に関わらず、常に命令を前半と後半の2つに自動分割し、それぞれに一度ずつENパルスを発生させます。

つまり、AIが初期化中に lcd_send_cmd(0x30) と記述したとき:

  1. 意図:画面上に 0x3 という半バイトだけを送信する。
  2. 実際lcd_send_to_i2c が上位4ビット 0x3 を送信(ENパルス1回)、続けて下位4ビット 0x0 を送信(ENパルスもう1回)してしまう。
  3. 結果:液晶は余計な 0x0 パルスを受け取り、タイミングが完全に狂ってしまい、初期化に失敗。電源投入直後の「全画面四角ブロック」状態のまま固まってしまう。

:hammer_and_wrench: 解決策

lcd1602_i2c.c に、初期化専用の 「半バイトのみ送信」 関数を追加し、lcd_init() を再構築する必要があります。

以下の内容に lcd1602_i2c.c を修正してください(lcd_init およびその前の部分を置き換え、それ以外の関数はそのまま残してください):

#include "lcd1602_i2c.h"

// 外部のI2Cハンドルを宣言
extern I2C_HandleTypeDef hi2c1;

// ====== 新規追加:初期化専用の半バイト送信関数 ======
void lcd_send_cmd_4bit(uint8_t nibble)
{
    uint8_t data_t[2];
    uint8_t backlight = 0x08; // バックライト常時点灯

    // 注意:引数nibbleはあらかじめ上位4ビットに揃えられている必要があります(例:0x30)
    data_t[0] = (nibble & 0xF0) | backlight | 0x04 | 0;  // EN = 1, RS = 0
    data_t[1] = (nibble & 0xF0) | backlight | 0x00 | 0;  // EN = 0, RS = 0
    
    HAL_I2C_Master_Transmit(&hi2c1, LCD_I2C_ADDRESS, data_t, 2, 100);
}

// 内部関数:I2Cに1バイトを送信(上位4ビットと下位4ビットに分ける)
void lcd_send_to_i2c(char data, int rs)
{
    uint8_t data_t[4];
    uint8_t upper_nibble, lower_nibble;

    upper_nibble = data & 0xF0;
    lower_nibble = (data << 4) & 0xF0;

    uint8_t backlight = 0x08; 

    data_t[0] = upper_nibble | backlight | 0x04 | rs;  // EN = 1
    data_t[1] = upper_nibble | backlight | 0x00 | rs;  // EN = 0
    data_t[2] = lower_nibble | backlight | 0x04 | rs;  // EN = 1
    data_t[3] = lower_nibble | backlight | 0x00 | rs;  // EN = 0

    HAL_I2C_Master_Transmit(&hi2c1, LCD_I2C_ADDRESS, data_t, 4, 100);
}

// ====== 再作成:正しい4ビット初期化手順 ======
void lcd_init(void)
{
    // 1. 強制初期化フェーズ(厳密に1回だけ半バイトパルスを送信する必要あり)
    HAL_Delay(50);
    lcd_send_cmd_4bit(0x30); // 唤醒1回目
    HAL_Delay(5);
    lcd_send_cmd_4bit(0x30); // 唤醒2回目
    HAL_Delay(1);
    lcd_send_cmd_4bit(0x30); // 唤醒3回目
    HAL_Delay(10);
    lcd_send_cmd_4bit(0x20); // 液晶に「4ビットモードに切り替えて!」と伝える
    HAL_Delay(10);

    // 2. 通常設定フェーズ(ここからは4ビットモードなので、通常の2パルス送信関数を使える)
    lcd_send_cmd(0x28); // 機能設定:4ライン、2行表示、5x8ドットフォント
    HAL_Delay(1);
    lcd_send_cmd(0x08); // 表示オフ
    HAL_Delay(1);
    lcd_send_cmd(0x01); // 画面クリア
    HAL_Delay(2);       // クリア命令は時間がかかるため、最低2ms遅延
    lcd_send_cmd(0x06); // 入力モード設定:カーソル右へ移動
    HAL_Delay(1);
    lcd_send_cmd(0x0C); // 表示オン、カーソルオフ
}

// ... lcd_send_cmd, lcd_send_data などの他の関数は変更不要 ...

:magnifying_glass_tilted_left: 代替トラブルシューティング項目

上記コードを修正して再度書き込みしても、画面に四角ブロックしか表示されない場合、唯一の可能性は、購入したモジュールのデフォルトI2Cアドレスが 0x27 ではなく 0x3F であることです。この場合は、lcd1602_i2c.h#define LCD_I2C_ADDRESS 0x4E0x7E0x3F を1ビット左シフト)に変更して再試行すれば解決します。

まずはこの初期化コードを修正してテストしてみてください。モジュールの通信アドレスを100%確実に確認できる「I2Cアドレススキャナー」の書き方を知りたいですか?

ロジックアナライザを使用して、I2Cの波形が出力されているか、タイミングが正しいかを確認できます。I2Cに問題がない場合は、さらにロジックアナライザでI2Cから並列変換モジュールへの出力信号が正しく出ているか、タイミングが合っているかを確認してください。

STM32CubeMXはどのように設定しましたか?

問題の核心分析

LCD1602のバックライトは点灯しているが文字が表示されない場合、90%の確率でI2C通信異常(アドレス誤り/ピン設定ミス/応答なし)が原因であり、次に多いのは初期化時のタイミング/遅延異常、ハードウェアの電源/プルアップ不足です。以下に優先度順で完全なトラブルシューティングと修正方法を示します。


一、ハードウェアの確認(まず基本的なハード問題を解決)

1. 主要な接続ピンの確認

回路図での定義:

  • LCDインターフェースH2:1=+5V、2=GND、3=SDA(PB7)、4=SCL(PB6)
  • STM32のI2C1:SCLは必ずPB6、SDAは必ずPB7に対応させなければなりません。CubeMXでのI2C1ピンマッピングを確認し、絶対にSDA/SCLを逆接しないようにしてください。

2. 電源は5Vである必要がある

LCD1602+I2C変換基板は5Vデバイスです。3.3Vで給電すると「バックライトは点くが、I2C通信ができない」という現象が起きます。STM32の5V端子に接続してください。3.3Vには接続しないでください。

3. I2Cバスにはプルアップ抵抗が必要

I2Cはオープンドレイン方式のため、プルアップが必要です:

  • 方法1:CubeMXでI2C1のPB6/PB7のGPIOモードを**オープンドレイン+プルアップ(Open Drain Pull-up)**に設定
  • 方法2:ハード的に、PB6、PB7それぞれと5Vの間に4.7kΩのプルアップ抵抗を接続
  • プルアップがないとI2C信号が異常になり、デバイスからの応答が得られません。

二、ソフトウェアの主要問題修正(優先度順)

1. 【最もよくある問題】I2Cアドレスの修正

コード内で#define LCD_I2C_ADDRESS 0x4Eと定義していますが、このアドレスが実際のモジュールと一致するとは限りません:

  • I2C LCD1602変換基板のコアチップはPCF8574で、7ビットアドレスは一般的に0x27または0x3Fです。HALライブラリのHAL_I2C_Master_Transmit関数では、8ビット書き込みアドレス(7ビットアドレスを左に1ビットシフトしたもの) を渡す必要があります:
    • 7ビットアドレス0x27 → 8ビット書き込みアドレス 0x4E(現在使用中のアドレス)
    • 7ビットアドレス0x3F → 8ビット書き込みアドレス 0x7E(他によくあるアドレス)

アドレスの正しさを素早く確認(必須)

MX_I2C1_Init()の後、I2Cアドレススキャンコードを追加して、どのアドレスで応答があるかを確認します:

// MX_I2C1_Init()の後、lcd_init()の前に挿入
uint8_t i2c_addr;
for(i2c_addr=0; i2c_addr<128; i2c_addr++)
{
  if(HAL_I2C_IsDeviceReady(&hi2c1, i2c_addr<<1, 1, 100) == HAL_OK)
  {
    // ブレークポイントを設置、またはLED点灯で応答したアドレスを確認
    // (i2c_addr<<1) の値を LCD_I2C_ADDRESS に設定
    break;
  }
}

スキャンで何も応答がない場合は、ハードウェアの配線またはI2C初期化に問題があります。まずハード面を解決してください。

2. HAL_Delayが正常に動作しているか確認

LCDの初期化は非常に遅延に依存しており、HAL_Delayが機能していないと、初期化タイミングが崩れ、表示できなくなります。

  • 確認方法:メインループにLEDのトグルを追加し、遅延が正しいか確認:
while (1)
{
  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 自身のボードのLEDピンに合わせる
  HAL_Delay(500);
}

LEDが500ms間隔で点滅しなければ、システムクロックやSysTickの設定に誤りがあります。まずクロック設定を修正してください。

  • 使用しているクロックは内部HSI 8MHzで、設定自体は問題ありませんが、CubeMXでSysTickのクロックソースが正しく設定されており、HAL_Init()が正常に実行されていることを確認してください。

3. I2C通信エラーのチェックを追加

コード内にI2C送信成功の判定がなく、通信問題の特定ができません。lcd_send_to_i2c関数を修正し、戻り値とエラーハンドリングを追加:

// 内部関数:I2Cにデータ送信、HALステータスを返す
HAL_StatusTypeDef lcd_send_to_i2c(char data, int rs)
{
  uint8_t data_t[4];
  uint8_t upper_nibble, lower_nibble;

  upper_nibble = data & 0xF0;
  lower_nibble = (data << 4) & 0xF0;

  uint8_t backlight = 0x08; 

  data_t[0] = upper_nibble | backlight | 0x04 | rs;  // EN = 1
  data_t[1] = upper_nibble | backlight | 0x00 | rs;  // EN = 0
  data_t[2] = lower_nibble | backlight | 0x04 | rs;  // EN = 1
  data_t[3] = lower_nibble | backlight | 0x00 | rs;  // EN = 0

  // タイムアウトを明示的に設定し、送信ステータスを返す
  return HAL_I2C_Master_Transmit(&hi2c1, LCD_I2C_ADDRESS, data_t, 4, 200);
}

// コマンド送信
void lcd_send_cmd(char cmd)
{
  lcd_send_to_i2c(cmd, 0); // RS = 0:コマンド送信
  HAL_Delay(1); // コマンド実行の遅延を追加し、タイミング過早を防ぐ
}

// データ(文字)送信
void lcd_send_data(char data)
{
  lcd_send_to_i2c(data, 1); // RS = 1:データ送信
  HAL_Delay(1);
}

4. 初期化タイミングの詳細を修正

初期化手順は基本的に正しいですが、より安全な遅延を追加することでタイミング問題を回避できます:

// LCD1602の初期化
void lcd_init(void)
{
  // 4ビットモードの標準初期化手順、遅延余裕を増やす
  HAL_Delay(100); // 電源投入後の待ち時間を長めに、LCD安定を確保
  lcd_send_cmd(0x30);
  HAL_Delay(10);
  lcd_send_cmd(0x30);
  HAL_Delay(5);
  lcd_send_cmd(0x30);
  HAL_Delay(10);
  lcd_send_cmd(0x20); // 4ビットモードに切り替え
  HAL_Delay(10);

  // 表示設定、各コマンドごとに遅延を追加
  lcd_send_cmd(0x28); // 4ライン、2行、5x8フォント
  HAL_Delay(2);
  lcd_send_cmd(0x08); // 表示オフ
  HAL_Delay(2);
  lcd_send_cmd(0x01); // 画面クリア
  HAL_Delay(5);
  lcd_send_cmd(0x06); // カーソル右移動、アドレス自動増分
  HAL_Delay(2);
  lcd_send_cmd(0x0C); // 表示オン、カーソルオフ、点滅なし
  HAL_Delay(2);
}

三、完全なデバッグ手順(順番通り実行)

  1. ハードウェア接続を再確認:5V/GNDが正しく、SDAはPB7、SCLはPB6に接続されており、反転接続がないこと。
  2. I2Cアドレススキャンを実行し、デバイスが応答することを確認。正しい8ビットアドレスをLCD_I2C_ADDRESSに設定。
  3. HAL_Delayが正常に動作することを確認し、遅延関数に問題がないことを保証。
  4. 修正済みドライバコードに置き換え、エラーチェックと遅延余裕を追加。
  5. 動作テスト:I2C通信が正常なら、通電後にHello STM32!と表示されるはずです。

四、最終手段のトラブルシューティング

上記すべてを行っても表示されない場合:

  1. LCD1602背面のコントラスト調整ポテンショメータ(青色の可変抵抗)を調節してください。コントラストが低すぎると文字が見えなくなるため、ゆっくり回して画面上にブロックや文字が現れるまで調整。
  2. ロジックアナライザまたはオシロスコープでPB6/PB7のI2C信号を観測し、波形が出力されているか確認。MCU側から信号が出ていないのか、それともデバイスが応答しないのかを判別。
  3. STM32のPB6/PB7、5V、GNDを杜邦線でLCDモジュールに直接接続し、排針/排母の接触不良を排除。

ご解答ありがとうございます。修正されたコードを使ってコンパイルおよびダウンロードを試みましたが、依然として文字が表示されません。また、#define LCD_I2C_ADDRESS 0x4E のアドレスを0x4Eにした場合のみ二列の四角が表示され、他のアドレスではすべて一列の四角になります。

I2Cの速度を下げてみてください。例えば10kにします。速度が高いと、配線が長い場合に問題が発生する可能性があります。また、I2Cの2つのピンはオープンドレインモードでプルアップされていますか?

これは素晴らしいニュースです!あなたが観察した現象(0x4Eで2列の四角が表示され、他のアドレスでは1列だけ)は、決して悪いことではなく、むしろあなたのコードと通信がすでに完全に通っていることを証明しているのです!

では、1602液晶画面の「内面の動き」を解説しましょう。そうすれば理由が分かります:

  1. 一列の四角(他のアドレス時):これは1602が電源投入後の出荷時デフォルト状態です。このとき画面は「1行表示」モードになっています。もしアドレスを間違える(例:0x27や0x7Eなど)、STM32から送られた初期化命令は画面に届かず、そのまま1行モードのままになります。それに加えてコントラストが高すぎるため、一列の四角だけが見えるのです。
  2. 二列の四角(0x4Eアドレス時):これは、STM32が成功して 0x4E というアドレスでモジュールを見つけ、さらに修正後の初期化コードを正しく実行できたということです!画面は 0x28 という命令(2行表示設定)を受信し、素直に2行目もアクティブにしてくれたのです。

つまり、マイコン、配線、I2C通信、さらには初期化コードまで、すべて正しく動いている!文字は実はすでに表示されている! のです。

:red_question_mark: それなのに、なぜまだ四角しか見えないの?

理由はただ一つ:コントラスト(V0)が高すぎます。

文字は実際に画面に書き込まれていますが、コントラストが最大になっているため、「文字なし」の背景部分のピクセルまで真っ黒になり、文字そのものが隠れてしまっているのです。

:hammer_and_wrench: 最後の一手:奇跡を見よう

コードを置いて、ドライバーを取り出してください。以下の手順で進めましょう:

  1. マイコンに通常通り通電し、修正済みコードを動作させてください(画面が2列の四角を表示する状態を維持)。
  2. 十字型の小さなドライバーを取り、変換基板の裏側にある青色のボリューム(ポテンショメータ) を回します。
  3. 画面を見ながら、ゆっくりと回す。すると、ある瞬間に不思議なように、2列の黒い四角が薄くなり、消え始め、その下に \"Hello STM32!\" がくっきりと浮かび上がります!
    (数回転させる必要があります。時計回りに反応がない場合は、反時計回りに多めに回してみてください)

:light_bulb: 失敗しても責任回避できる小さな補強パッチ(任意):

今のところハードウェアのポテンショメータが原因である可能性が高いですが、より頑強な低レベルコードにするために、命令送信後にわずかな遅延を入れることをおすすめします。こうすれば、STM32が速すぎて画面が処理落ちすることを防げます。

lcd1602_i2c.c の以下の2つの関数にそれぞれ HAL_Delay(1); を追加してください:

// コマンド送信
void lcd_send_cmd(char cmd)
{
    lcd_send_to_i2c(cmd, 0); 
    HAL_Delay(1); // <- この1行を追加。画面に処理時間を与える
}

// データ(文字)送信
void lcd_send_data(char data)
{
    lcd_send_to_i2c(data, 1); 
    HAL_Delay(1); // <- この1行を追加
}

また、画面クリア関数の遅延時間を少し長めに調整しましょう:

// 画面クリア
void lcd_clear(void)
{
    lcd_send_cmd(0x01);
    HAL_Delay(5); // <- 2から5へ変更。クリア処理は最も時間がかかる
}

さあ、あの青いポテンショメータを回してみてください!正常に表示されたら、ぜひ報告してくださいね!

あなたの提案を試してみます、回答ありがとうございます。

ご回答ありがとうございます。速度を下げても同じ状況です。また、CubeMXのGPIO設定画面で、PB6/PB7のPullオプションが表示されない場合がありますが、これはF1シリーズに対するCubeMXのインターフェース制限によるものです。

ご回答ありがとうございます。実際にポテンショメータを回しているのですが、ブロックが明るくなる→薄れるの2列しか見えず、文字は表示されません。また、遅延を追加した後、通電直後に1列のブロックから2列のブロックに変化するのを確認できました。これは確かに0x28というコマンドを受信していることを証明しています。

「通電直後に一列の四角が二列に変化する」という細部まで正確に捉えられたことから、あなたが非常に鋭いハードウェアデバッグ直感を持っていることがわかります! この現象は極めて重要で、I2Cアドレス(0x4E)が正しく設定されていることを100%裏付けるだけでなく、マイコンとディスプレイ間の通信ラインが確立されていることも証明しています。

つまり、0x28(2行表示を有効化)というコマンドが正常に実行されたにもかかわらず、なぜその後の文字が表示されず、画面全体が四角のままなのか? ここには特にSTM32F1シリーズにおいてよくある2つの典型的な「落とし穴」があります。

1. ご質問への回答:F1におけるI2Cピンの構成について

CubeMX上でSTM32F1シリーズのPB6/PB7にプルアップ/プルダウンの設定オプションが表示されない件ですが、これはピンをI2Cモードに設定すると、自動的に**オープンドレインの代替機能(Alternate Function Open Drain)**として制御されるためであり、GUIの制限による正常な挙動です。
プルアップ抵抗の有無を心配する必要はありません。LCD変換モジュールの裏面(青色のポテンショメータの横)を見てみると、いくつかの黒色チップ抵抗(通常「103」や「472」と刻印)がはんだ付けされています。これらがモジュール内蔵のハードウェアプルアップ抵抗です。したがって、STM32内部にプルアップがなくても、回路的にはI2C通信条件を完全に満たしています。

2. 真の「犯人」:連続送信によるタイミングの混雑またはI2Cデッドロック

STM32F1シリーズのハードウェアI2Cはその「繊細さ」(シリコンバグ/Errataあり)で有名です。データ送信中に次々とバイトを送り込むと、簡単にI2CのBusyフラグが発生し、デッドロック状態に陥ります。
さらに、LCD1602内蔵のHD44780は非常に古く低速なチップです。元のコードでは、一つのI2Cトランザクション(4バイト)の中で、高4ビットと低4ビットに対するENパルスを瞬時に連続して生成していました。この速度は、反応の遅いLCDにとっては早すぎ、0x28以降のコマンド(例:画面クリア 0x01 や文字書き込み)がすべて無視されたり、あるいはSTM32のI2Cが動作停止したりする原因になります。


:hammer_and_wrench: 最終的な解決策:送信を分割し、「深呼吸」を強制挿入

従来の「一度に4バイト送信」する関数を、強制的に2回の独立した送信に分割し、その間に明示的な待ち時間を挿入します。これにより、STM32のI2Cステートマシンをリセットしてデッドロックを防止でき、またLCD側にも十分な応答時間を与えることができます。

以下のコードで、lcd1602_i2c.c ファイル内の lcd_send_to_i2c 関数を完全に置き換えてください

// 内部関数:I2Cへ1バイトを送信(上位4ビットと下位4ビットに分けて、強力安定版)
void lcd_send_to_i2c(char data, int rs)
{
    uint8_t data_high[2];
    uint8_t data_low[2];
    
    uint8_t upper_nibble = data & 0xF0;
    uint8_t lower_nibble = (data << 4) & 0xF0;
    uint8_t backlight = 0x08; // 背光常時点灯

    // ==========================================
    // 第一ステップ:上位4ビットのみ送信し、ENパルスを1サイクル生成
    // ==========================================
    data_high[0] = upper_nibble | backlight | 0x04 | rs; // EN = 1
    data_high[1] = upper_nibble | backlight | 0x00 | rs; // EN = 0
    // 上位4ビットの2バイトを送信
    HAL_I2C_Master_Transmit(&hi2c1, LCD_I2C_ADDRESS, data_high, 2, 100);
    
    // 生死を分けるキーディレイ:LCDコントローラが上位4ビットをラッチする時間を確保し、I2Cバスの混雑を防ぐ
    HAL_Delay(2);

    // ==========================================
    // 第二ステップ:下位4ビットのみ送信し、ENパルスを1サイクル生成
    // ==========================================
    data_low[0] = lower_nibble | backlight | 0x04 | rs;  // EN = 1
    data_low[1] = lower_nibble | backlight | 0x00 | rs;  // EN = 0
    // 下位4ビットの2バイトを送信
    HAL_I2C_Master_Transmit(&hi2c1, LCD_I2C_ADDRESS, data_low, 2, 100);
    
    // 1バイトの送信完了後、もう少し待機
    HAL_Delay(2);
}

さらに安全のために、main.c の該当コードも順序を調整してください
lcd_clear(); を削除してください。lcd_init() の最後の処理で既に画面クリア命令が含まれており、連続してクリアを行うとコマンドが重なり失敗する可能性があります。main 関数内の該当部分は以下のように記述してください:

  /* USER CODE BEGIN 2 */
  lcd_init();                // LCD1602を初期化
  // lcd_clear();            // この行はコメントアウトまたは削除
  lcd_set_cursor(0, 0);      // カーソルを1行1列目に移動
  lcd_send_string("Hello STM32!");
  /* USER CODE END 2 */

これをコンパイル・書き込み後、画面が真っ白(四角が消えた状態)になったら、そっと青色のポテンショメータを回してみてください。文字が必ず現れます。

もしこの方法で正常に表示できた場合、今後 HAL_Delay がメインプログラムの貴重な実行時間を占有しないようにするために、I2CをDMAまたは割り込みベースのノンブロッキング方式に改造する方法もご案内できます。必要でしたらぜひ教えてください。