STM32の最小ボードをメインコントローラとして使用し、LCD1602用I2Cモジュールはネットで購入した既製品です。
私は初心者で、コードはAIに書いてもらいました。最初は一行の四角が表示されましたが、修正後は二行の四角が表示されるようになりました。しかし、まだ文字が表示されません。ポテンショメータは調整済みで、I2Cの電圧も5Vです。
STM32の最小ボードをメインコントローラとして使用し、LCD1602用I2Cモジュールはネットで購入した既製品です。
私は初心者で、コードはAIに書いてもらいました。最初は一行の四角が表示されましたが、修正後は二行の四角が表示されるようになりました。しかし、まだ文字が表示されません。ポテンショメータは調整済みで、I2Cの電圧も5Vです。
接続図を描いて、コードも提示してください。情報量が少なすぎて、まさか他人に推測させようというのですか?
優れた質問の例:https://bbs.eeclub.top/t/topic/289
質問の知恵:https://bbs.eeclub.top/t/topic/109
兄弟、焦らないで。この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.c と lcd1602.h ファイルをそのままプロジェクトに取り込むのが、最も確実で早い方法だ。
4. ハードウェアの論理レベル(稀なケース)
あなたが5V電源を使っていると書いてあるのは良いポイントだ。LCD1602は5V駆動が必須だからね。STM32のGPIOは3.3V論理レベルだが、多くのI2Cピン(例:PB6、PB7)は「5V耐性(FT:ファストモード互換)」なので、直接接続しても問題ないことが多い。ただ一点、STM32とLCDモジュールのGNDを確実に共有しているかだけはしっかり確認すること。
打開策(次にやるべきこと):
まずは文字を表示させることにこだわらず、AIに 「STM32 I2C Scanner(I2Cアドレススキャン)」 のコードを書いてもらおう。
それをSTM32に書き込み、シリアルモニタ(ターミナルソフト)を開き、STM32がI2Cバス上でこのLCDモジュールを検出できるか確認する。
0x4E と表示)→ ハードウェア接続は完璧。あとはそのアドレスをLCD初期化コードに正しく設定すればOK。「Hello World」が見える日まで、あと一歩だ! また詰まったら気軽に聞いてね。
ハードウェアに関する簡単な注意点: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ビットモード」のハンドシェイク手順で失敗していることにあります。
あなたが使っているI2C変換基板(PCF8574)は、D4~D7の4本のデータ線を使って1602液晶を制御しています。
1602液晶は電源投入直後、デフォルトで 8ビットモード になっています。これを 4ビットモード に切り替えるには、データシートに従って、厳密に半バイト単位(上位4ビットだけ送信し、すぐに終了)で複数回送信する必要があります。
ここで、あなたの lcd_send_cmd() 関数を見てみましょう。この関数は lcd_send_to_i2c() を呼び出しています。この低レベル関数は非常に「真面目」で、渡された命令に関わらず、常に命令を前半と後半の2つに自動分割し、それぞれに一度ずつENパルスを発生させます。
つまり、AIが初期化中に lcd_send_cmd(0x30) と記述したとき:
0x3 という半バイトだけを送信する。lcd_send_to_i2c が上位4ビット 0x3 を送信(ENパルス1回)、続けて下位4ビット 0x0 を送信(ENパルスもう1回)してしまう。0x0 パルスを受け取り、タイミングが完全に狂ってしまい、初期化に失敗。電源投入直後の「全画面四角ブロック」状態のまま固まってしまう。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 などの他の関数は変更不要 ...
上記コードを修正して再度書き込みしても、画面に四角ブロックしか表示されない場合、唯一の可能性は、購入したモジュールのデフォルトI2Cアドレスが 0x27 ではなく 0x3F であることです。この場合は、lcd1602_i2c.h の #define LCD_I2C_ADDRESS 0x4E を 0x7E(0x3F を1ビット左シフト)に変更して再試行すれば解決します。
まずはこの初期化コードを修正してテストしてみてください。モジュールの通信アドレスを100%確実に確認できる「I2Cアドレススキャナー」の書き方を知りたいですか?
ロジックアナライザを使用して、I2Cの波形が出力されているか、タイミングが正しいかを確認できます。I2Cに問題がない場合は、さらにロジックアナライザでI2Cから並列変換モジュールへの出力信号が正しく出ているか、タイミングが合っているかを確認してください。
STM32CubeMXはどのように設定しましたか?
LCD1602のバックライトは点灯しているが文字が表示されない場合、90%の確率でI2C通信異常(アドレス誤り/ピン設定ミス/応答なし)が原因であり、次に多いのは初期化時のタイミング/遅延異常、ハードウェアの電源/プルアップ不足です。以下に優先度順で完全なトラブルシューティングと修正方法を示します。
回路図での定義:
LCD1602+I2C変換基板は5Vデバイスです。3.3Vで給電すると「バックライトは点くが、I2C通信ができない」という現象が起きます。STM32の5V端子に接続してください。3.3Vには接続しないでください。
I2Cはオープンドレイン方式のため、プルアップが必要です:
コード内で#define LCD_I2C_ADDRESS 0x4Eと定義していますが、このアドレスが実際のモジュールと一致するとは限りません:
HAL_I2C_Master_Transmit関数では、8ビット書き込みアドレス(7ビットアドレスを左に1ビットシフトしたもの) を渡す必要があります:
0x4E(現在使用中のアドレス)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初期化に問題があります。まずハード面を解決してください。
HAL_Delayが正常に動作しているか確認LCDの初期化は非常に遅延に依存しており、HAL_Delayが機能していないと、初期化タイミングが崩れ、表示できなくなります。
while (1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 自身のボードのLEDピンに合わせる
HAL_Delay(500);
}
LEDが500ms間隔で点滅しなければ、システムクロックやSysTickの設定に誤りがあります。まずクロック設定を修正してください。
HAL_Init()が正常に実行されていることを確認してください。コード内に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);
}
初期化手順は基本的に正しいですが、より安全な遅延を追加することでタイミング問題を回避できます:
// 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);
}
LCD_I2C_ADDRESSに設定。HAL_Delayが正常に動作することを確認し、遅延関数に問題がないことを保証。Hello STM32!と表示されるはずです。上記すべてを行っても表示されない場合:
ご解答ありがとうございます。修正されたコードを使ってコンパイルおよびダウンロードを試みましたが、依然として文字が表示されません。また、#define LCD_I2C_ADDRESS 0x4E のアドレスを0x4Eにした場合のみ二列の四角が表示され、他のアドレスではすべて一列の四角になります。
I2Cの速度を下げてみてください。例えば10kにします。速度が高いと、配線が長い場合に問題が発生する可能性があります。また、I2Cの2つのピンはオープンドレインモードでプルアップされていますか?
これは素晴らしいニュースです!あなたが観察した現象(0x4Eで2列の四角が表示され、他のアドレスでは1列だけ)は、決して悪いことではなく、むしろあなたのコードと通信がすでに完全に通っていることを証明しているのです!
では、1602液晶画面の「内面の動き」を解説しましょう。そうすれば理由が分かります:
0x4E というアドレスでモジュールを見つけ、さらに修正後の初期化コードを正しく実行できたということです!画面は 0x28 という命令(2行表示設定)を受信し、素直に2行目もアクティブにしてくれたのです。つまり、マイコン、配線、I2C通信、さらには初期化コードまで、すべて正しく動いている!文字は実はすでに表示されている! のです。
理由はただ一つ:コントラスト(V0)が高すぎます。
文字は実際に画面に書き込まれていますが、コントラストが最大になっているため、「文字なし」の背景部分のピクセルまで真っ黒になり、文字そのものが隠れてしまっているのです。
コードを置いて、ドライバーを取り出してください。以下の手順で進めましょう:
\"Hello STM32!\" がくっきりと浮かび上がります!
失敗しても責任回避できる小さな補強パッチ(任意):
今のところハードウェアのポテンショメータが原因である可能性が高いですが、より頑強な低レベルコードにするために、命令送信後にわずかな遅延を入れることをおすすめします。こうすれば、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つの典型的な「落とし穴」があります。
CubeMX上でSTM32F1シリーズのPB6/PB7にプルアップ/プルダウンの設定オプションが表示されない件ですが、これはピンをI2Cモードに設定すると、自動的に**オープンドレインの代替機能(Alternate Function Open Drain)**として制御されるためであり、GUIの制限による正常な挙動です。
プルアップ抵抗の有無を心配する必要はありません。LCD変換モジュールの裏面(青色のポテンショメータの横)を見てみると、いくつかの黒色チップ抵抗(通常「103」や「472」と刻印)がはんだ付けされています。これらがモジュール内蔵のハードウェアプルアップ抵抗です。したがって、STM32内部にプルアップがなくても、回路的にはI2C通信条件を完全に満たしています。
STM32F1シリーズのハードウェアI2Cはその「繊細さ」(シリコンバグ/Errataあり)で有名です。データ送信中に次々とバイトを送り込むと、簡単にI2CのBusyフラグが発生し、デッドロック状態に陥ります。
さらに、LCD1602内蔵のHD44780は非常に古く低速なチップです。元のコードでは、一つのI2Cトランザクション(4バイト)の中で、高4ビットと低4ビットに対するENパルスを瞬時に連続して生成していました。この速度は、反応の遅いLCDにとっては早すぎ、0x28以降のコマンド(例:画面クリア 0x01 や文字書き込み)がすべて無視されたり、あるいはSTM32のI2Cが動作停止したりする原因になります。
従来の「一度に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または割り込みベースのノンブロッキング方式に改造する方法もご案内できます。必要でしたらぜひ教えてください。