U8g2グラフィックスライブラリをSTM32F407標準ライブラリプロジェクトテンプレートに移植しました。0.96インチOLEDディスプレイ(SSD1306)を使用し、ハードウェアIICで駆動しています。
移植に一晩かかりました。開発ボードのメインMCUはSTM32F407VET6で、I2CインターフェースはI2C1を使用し、SCLはPB6、SDAはPB7に接続しています。
組み込み関連記事:https://blog.zeruns.com/category/IOT/
電子・回路関連記事:https://blog.zeruns.com/category/electrical/
電子・マイコン技術交流グループ:2169025065
U8g2について
U8g2グラフィックスライブラリは、組み込み機器向けのモノクログラフィックスライブラリで、SSD1306やST7920など、複数のモノクロOLEDおよびLCD表示コントローラをサポートしています。U8g2ライブラリはArduino IDEのライブラリマネージャからインストールすることもでき、STM32などのプラットフォームに移植することもできます。U8g2ライブラリは、フルスクリーンバッファモード、ページバッファモード、U8x8文字モードの3つの描画モードをサポートしています。U8g2ライブラリを使用するには、適切なコンストラクタを選択し、ディスプレイを初期化し、ピン番号を設定し、コールバック関数と描画命令を記述する必要があります。
U8g2グラフィックスライブラリの利点は、複数のフォントを使用でき、中国語表示をサポートし、線、ボックス、円、ビットマップなど、豊富なグラフィックプログラムを提供することです。欠点は、一定のメモリスペースを占有し、速度が遅く、コントローラのないディスプレイをサポートしていないことです。U8g2グラフィックスライブラリの使用例としては、センサーデータの表示、時計の作成、メニューの表示、アニメーションの表示などがあります。U8g2グラフィックスライブラリは、機能が強く、互換性が高く、使いやすいモノクログラフィックスライブラリです。
効果図
デモビデオ:https://www.bilibili.com/video/BV17W4y197WW/
水晶振動子についての問題
水晶振動子は8MHzを使用しており、クロックツリーの設定でPLLパラメータを変更し、MCUの動作周波数を168MHzにしています。他の周波数の水晶振動子を使用する場合はパラメータを変更する必要があります。具体的な変更方法は各自で検索してください。
変更箇所は以下の通りです:
stm32f4xx.h ファイルの137行目。
#define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
// ここの8000000をあなたの水晶振動子の周波数に変更、単位はHz
system_stm32f4xx.c ファイルの364行目と394行目。
#if defined(STM32F40_41xxx) || defined(STM32F427_437xx) || defined(STM32F429_439xx) || defined(STM32F401xx) || defined(STM32F469_479xx)
/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
#define PLL_M 4
// ここの4は下のクロックツリー画像の /M の /4 に対応
#if defined (STM32F40_41xxx)
#define PLL_N 168
// ここの168は下のクロックツリー画像の *N の x168 に対応
```
## 部品購入先
- 0.96インチOLEDディスプレイ:[https://u.jd.com/1zjpDNC](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BARAJK1olXwQCXVheC0IUBV8IGloTXw4AU1hVDkweBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksWAmkKE1kSWw4EU1dbFxJSXzI4WzB2K1NjPSw9VzFBQBpJTg5iPBhWElJROEonA24JGloSWgAAXG5tCEwnQgEIGV4SXwcFVW5cOEsQCmgNHV0UWg4EUVttD0seM20IGFgQWgQLUUJUDEoUAGY4K2sWbQECXUpbegpFF2l6K2sVbQUyVF9dAEgXA2cOGFMJXQMGXF9cFEsQCmgNHV0UXAEFVlttCkoWB2Y4K2tVGQViISotXEpUR2xIeSNWKGJREh4DXxF5AS1dSD0SFQNXVQoHfghLSm8LKw)
- STM32F407開発ボード:[https://s.click.taobao.com/7AtnHFu](https://s.click.taobao.com/t?e=m%3D2%26s%3DczQqv026Zjtw4vFB6t2Z2ueEDrYVVa64LKpWJ%2Bin0XLjf2vlNIV67lRwohxEizvIxa9spvDO8Cl%2FmzaS0s2vXCsmIF0UHtujBkxkgN7jKg%2BiJaQjFxYBevWfPAv4x3d%2FOlGkMkr%2BSUUYmUGuVZn0uRDBbf95h6seTxpmwkkouzqi1jMNxDhLMnotgd7NXRy%2F3OppJwt5ethtXyEYK9MgsI1cD97yFV3TmSpANgNSVXSPM7MeivsNdkAXdMlWzMNyHk3i9xXmKdKMJi1gYBJvNFSsXCNd9EoxE59iYTGkDbWFi4V5KBpmMoBZLSyIfgF7gR2l5BVRVVbGDmntuH4VtA%3D%3D&union_lens=lensId%3APUB%401686477136%40210562f5_0a55_188a9dee240_346f%4001%40eyJmbG9vcklkIjo2MTM1NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIn0ie)
- ジャンパワイヤ:[https://u.jd.com/1zjFP0Z](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAQcJK1olVQAKVlZZCUoXM2oJElkcXwUHXVtYOA9IWzFXKwJQGEdAX0BDUA5DX3BTTkRHA1ocUV9UCkIVAGoBHl4KBENeCW4NDA9CAGcLXjlHWnBqEycFACNOCxZoF1clXDYCVV9cCUwQBW0AK2sVWjZDOllYAE0XAF8JK1sSVAEHUldZCU4fBWo4HFscbQQCV11YD0keBnMBH1oWXg8yZG5eOEwXCnsOaRpHSQBwZG5dOEgnA24IE1gQXwYHV1lBCEkWAW8IB1sSVAEHUlhdAUweCmY4GVoUWQ8yZG4YXDFkd2tXEjhHCXxLB1wAXzsVAmxfRAN7X19ZVgg8VhZMYB94fChoFEVmZA)
## コード
完全なプロジェクトファイルのダウンロード:[https://url.zeruns.com/JUoKJ](https://url.zeruns.com/JUoKJ) 抽出コード:t6wt
コードの一部:
**main.c**
```C
#include "stm32f4xx.h"
// #include "Timer.h"
#include "Delay.h"
#include "u8g2.h"
#include "OLED.h"
#include "IWDG.h"
uint8_t u8x8_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr);
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
void u8g2_Init(u8g2_t *u8g2);
void draw(u8g2_t *u8g2);
int main(void)
{
uint8_t t = 0;
IWDG_Configuration(); // ウォッチドッグ初期化
OLED_I2C_Init(); // OLED初期化
u8g2_t u8g2;
u8g2_Init(&u8g2); // U8g2初期化
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // GPIOAのペリフェラルクロックを有効化
GPIO_InitTypeDef GPIO_InitStructure; // 構造体定義
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // GPIOモードを出力に設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // GPIOピン6を設定
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed; // GPIO速度を100MHzに設定
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // プッシュプル出力に設定
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // プルアップ/プルダウンなし
GPIO_Init(GPIOA, &GPIO_InitStructure); // GPIO初期化
Delay_ms(100);
u8g2_DrawLine(&u8g2, 0, 0, 127, 63); // 線を描画、開始座標(0,0)、終点座標(127,63)
u8g2_SendBuffer(&u8g2); // バッファデータを送信
u8g2_DrawLine(&u8g2, 127, 0, 0, 63);
u8g2_SendBuffer(&u8g2);
Delay_ms(300);
u8g2_ClearBuffer(&u8g2); // バッファデータをクリア
draw(&u8g2);
u8g2_SendBuffer(&u8g2);
Delay_ms(1000);
u8g2_ClearBuffer(&u8g2);
IWDG_FeedDog(); // ウォッチドッグに餌をやり、CPUリセットを防ぐ
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr); // フォント選択
u8g2_DrawStr(&u8g2, 0, 15, "Hello World!");
u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese2);
u8g2_DrawUTF8(&u8g2, 0, 30, "H你好世界");
u8g2_SetFont(&u8g2, u8g2_font_wqy12_t_chinese2);
u8g2_DrawUTF8(&u8g2, 0, 43, "H你好世界");
u8g2_SetFont(&u8g2, u8g2_font_fur11_tr);
u8g2_DrawUTF8(&u8g2, 0, 59, "blog.zeruns.com");
u8g2_SendBuffer(&u8g2);
Delay_ms(1300);
while (1)
{
Delay_ms(100);
u8g2_ClearBuffer(&u8g2);// バッファデータをクリア
if (++t >= 32)
t = 1;
u8g2_DrawCircle(&u8g2, 64, 32, t, U8G2_DRAW_ALL); // 円を描画
u8g2_DrawCircle(&u8g2, 32, 32, t, U8G2_DRAW_ALL);
u8g2_DrawCircle(&u8g2, 96, 32, t, U8G2_DRAW_ALL);
u8g2_SendBuffer(&u8g2); // バッファデータを送信
GPIO_ToggleBits(GPIOA, GPIO_Pin_6);
IWDG_FeedDog(); // ウォッチドッグに餌をやり、CPUリセットを防ぐ
}
}
// https://blog.zeruns.com
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
static uint8_t buffer[32]; /* u8g2/u8x8はSTART_TRANSFERとEND_TRANSFERの間に32バイト以上送信しません */
static uint8_t buf_idx;
uint8_t *data;
switch (msg)
{
case U8X8_MSG_BYTE_SEND:
data = (uint8_t *)arg_ptr;
while (arg_int > 0)
{
buffer[buf_idx++] = *data;
data++;
arg_int--;
}
break;
case U8X8_MSG_BYTE_INIT:
/* I2Cサブシステムを初期化するためのカスタムコードを追加 */
break;
case U8X8_MSG_BYTE_START_TRANSFER:
buf_idx = 0;
break;
case U8X8_MSG_BYTE_END_TRANSFER:
HW_I2cWrite(buffer, buf_idx); // ハードウェアI2Cでバイトを書き込み
break;
default:
return 0;
}
return 1;
}
// https://blog.zeruns.com
uint8_t u8g2_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr)
{
switch (msg)
{
case U8X8_MSG_GPIO_AND_DELAY_INIT:
OLED_I2C_Init(); // 初期化
break;
case U8X8_MSG_DELAY_MILLI:
Delay_ms(arg_int); // 遅延
break;
case U8X8_MSG_GPIO_I2C_CLOCK:
break;
case U8X8_MSG_GPIO_I2C_DATA:
break;
default:
return 0;
}
return 1; // コマンドが正常に処理されました
}
void u8g2_Init(u8g2_t *u8g2)
{
// u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay); // u8g2を初期化、ソフトI2C
u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8g2_gpio_and_delay); // u8g2を初期化、ハードウェアI2C
u8g2_InitDisplay(u8g2); // 選択したチップに応じて初期化を行い、初期化完了後ディスプレイはオフ状態
u8g2_SetPowerSave(u8g2, 0); // ディスプレイをオン
u8g2_SetContrast(u8g2, 88); // 画面の輝度を設定
u8g2_ClearBuffer(u8g2); // バッファをクリア
}
// https://blog.vpszj.cn
void draw(u8g2_t *u8g2)
{
u8g2_SetFontMode(u8g2, 1); /* フォントモード選択 */
u8g2_SetFontDirection(u8g2, 0); /* フォント方向選択 */
u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /* フォント選択 */
u8g2_DrawStr(u8g2, 0, 20, "U");
u8g2_SetFontDirection(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_inb30_mn);
u8g2_DrawStr(u8g2, 21, 8, "8");
u8g2_SetFontDirection(u8g2, 0);
u8g2_SetFont(u8g2, u8g2_font_inb24_mf);
u8g2_DrawStr(u8g2, 51, 30, "g");
u8g2_DrawStr(u8g2, 67, 30, "\xb2");
u8g2_DrawHLine(u8g2, 2, 35, 47);
u8g2_DrawHLine(u8g2, 3, 36, 47);
u8g2_DrawVLine(u8g2, 45, 32, 12);
u8g2_DrawVLine(u8g2, 46, 33, 12);
u8g2_SetFont(u8g2, u8g2_font_4x6_tr);
u8g2_DrawStr(u8g2, 1, 54, "github.com/olikraus/u8g2");
}
```OLED.c
```C
#include "stm32f4xx.h"
#include "OLED_Font.h"
#include "Delay.h"
/*OLED屏地址*/
#define OLED_ADDRESS 0x78
//I2C等待超时时间
#define I2C_TIMEOUT 5000
/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE); //使能I2C1时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟
GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_I2C1); //开启PB6的复用功能连接至I2C1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);
/*STM32F407芯片的硬件I2C: PB6 -- SCL; PB7 -- SDA */
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体配置GPIO
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //设置GPIO口模式为复用IO模式
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed; //设置GPIO口速度100Mhz
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //设置GPIO口为开漏输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //设置GPIO上拉模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //设置GPIO口
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_DeInit(I2C1); //将外设I2C1寄存器重设为缺省值
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //工作模式
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比,Tlow/Thigh = 2
I2C_InitStructure.I2C_OwnAddress1 = 0x30; //主机的I2C地址,用不到则随便写,无影响
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //使能应答位
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//设置地址长度7位
I2C_InitStructure.I2C_ClockSpeed = 400000; //I2C传输速度,400K,根据自己所用芯片手册查看支持的速度。
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
}
// https://blog.zeruns.com
void HW_I2cWrite(uint8_t *buf,uint8_t len)
{
if(len<=0)
return ;
uint32_t wait_time=0;
/* wait for the busy falg to be reset */
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
{
wait_time++;
if(wait_time>=I2C_TIMEOUT){
wait_time=0;
break;
}
}
I2C_GenerateSTART(I2C1, ENABLE);//开启I2C1
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))/*EV5,主模式*/
{
wait_time++;
if(wait_time>=I2C_TIMEOUT){
wait_time=0;
break;
}
}
I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter); //器件地址 -- 默认0x78
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
wait_time++;
if(wait_time>=I2C_TIMEOUT){
wait_time=0;
break;
}
}
for(uint8_t i=0;i<len;i++)
{
I2C_SendData(I2C1, buf[i]);//发送数据
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
wait_time++;
if(wait_time>=I2C_TIMEOUT){
wait_time=0;
break;
}
}
}
I2C_GenerateSTOP(I2C1, ENABLE);//关闭I2C1总线
}
オープンソースプロジェクトおすすめ
- MSP430F149 最小システムボードを描いてオープンソース化しました:https://blog.zeruns.com/archives/713.html
- STM32F030C8T6 最小システムボードと流水灯(回路図とPCB):https://blog.zeruns.com/archives/715.html
- SY8205 同期降圧可調DCDC電源モジュール(回路図とPCB):https://blog.zeruns.com/archives/717.html
- 2011年全国電子設計コンテスト課題:スイッチング電源モジュール並列給電システム:https://blog.zeruns.com/archives/718.html
- 2007年電源課題:30~36V可調昇圧DCDCモジュール(UC3843):https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842
おすすめ記事
- 高コスパで安価なVPS/クラウドサーバーおすすめ: https://blog.vpszj.cn/archives/41.html
- 個人ブログの構築方法:https://blog.zeruns.com/archives/218.html
- マインクラフトサーバー構築チュートリアル:https://blog.zeruns.com/tag/mc/
- STM32でSHT3xシリーズ温湿度センサーを読み取る:https://blog.zeruns.com/archives/700.html
- Keilの代わりにVSCodeを使ってSTM32・51マイコン開発を実現:https://blog.zeruns.com/archives/690.html
- 野草云 香港BGP クラウドサーバー 性能レビュー、1Core 2GB 100Mbps 年額138元:https://blog.vpszj.cn/archives/1474.html
- 雨云 宿遷5900X高防護クラウドサーバー 性能レビュー、2Core 4GB 5Mbps 150G防御 月額50元:https://blog.vpszj.cn/archives/1125.html

