Loading... 移植好[U8g2](https://blog.zeruns.tech/tag/U8g2/)图形库的[STM32F407](https://blog.zeruns.tech/tag/STM32F407/)标准库工程模板,用的0.96寸[OLED](https://blog.zeruns.tech/tag/OLED/)屏(SSD1306),用硬件[IIC](https://blog.zeruns.tech/tag/IIC/)驱动。 花了一晚上时间去移植。开发板主控MCU用的是[STM32F407VET6](https://blog.zeruns.tech/tag/STM32F407VET6/),I2C接口用I2C1,SCL接PB6,SDA接PB7。 嵌入式相关文章:[https://blog.zeruns.tech/category/IOT/](https://blog.zeruns.tech/category/IOT/) 电子/电路相关文章:[https://blog.zeruns.tech/category/electrical/](https://blog.zeruns.tech/category/electrical/) ## U8g2简介 U8g2图形库是一个用于嵌入式设备的单色图形库,支持多种单色OLED和LCD显示控制器,如SSD1306,ST7920等。U8g2库可以从Arduino IDE的库管理器安装,也可以移植到[STM32](https://blog.zeruns.tech/tag/stm32/)等平台。U8g2库支持三种绘图模式:全屏缓存模式,页面缓存模式和U8x8字符模式。U8g2库的使用需要选择合适的构造函数,初始化显示器,设置引脚号,编写回调函数和绘图指令。 U8g2图形库的优点是可以使用多种字体,支持中文显示,提供丰富的图形程序,如线条,框,圆,位图等。U8g2图形库的缺点是需要占用一定的内存空间,速度较慢,不支持无控制器的显示屏。U8g2图形库的应用场景有:显示传感器数据,制作时钟,显示菜单,显示动画等。U8g2图形库是一个功能强大,兼容性好,易于使用的单色图形库。 ## 效果图    演示视频:https://www.bilibili.com/video/BV17W4y197WW/ ## 关于晶振的问题 我晶振用的8Mhz的,时钟树配置那PLL参数我修改过,使MCU工作主频168Mhz,如果换用其他频率的晶振需要修改参数,具体怎么改自行百度。 **修改的地方如下:** **stm32f4xx.h** 文件的137行。 ```C #define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */ // 这里的8000000改成你的晶振频率,单位Hz ``` **system_stm32f4xx.c** 文件的364行和394行。 ```C #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.tech/JUoKJ](https://url.zeruns.tech/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口为推挽输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 设置GPIO上下拉模式 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.tech"); 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.tech 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 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */ 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: /* add your custom code to init i2c subsystem */ 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.tech 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; // command processed successfully. } 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.tech 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.tech/archives/713.html](https://blog.zeruns.tech/archives/713.html) - STM32F030C8T6最小系统板和流水灯(原理图和PCB):[https://blog.zeruns.tech/archives/715.html](https://blog.zeruns.tech/archives/715.html) - SY8205同步降压可调DCDC电源模块(原理图和PCB):[https://blog.zeruns.tech/archives/717.html](https://blog.zeruns.tech/archives/717.html) - 2011年全国电赛题-开关电源模块并联供电系统:[https://blog.zeruns.tech/archives/718.html](https://blog.zeruns.tech/archives/718.html) - 2007年电赛电源题:30到36V可调升压DCDC模块(UC3843):[https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842](https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842) ## 推荐阅读 - **高性价比和便宜的VPS/云服务器推荐:** [https://blog.vpszj.cn/archives/41.html](https://blog.vpszj.cn/archives/41.html) - 怎样搭建个人博客:[https://blog.zeruns.tech/archives/218.html](https://blog.zeruns.tech/archives/218.html) - 我的世界服务器搭建教程:[https://blog.zeruns.tech/tag/mc/](https://blog.zeruns.tech/tag/mc/) - STM32读取SHT3x系列温湿度传感器:[https://blog.zeruns.tech/archives/700.html](https://blog.zeruns.tech/archives/700.html) - 使用VSCode代替Keil实现STM32和51单片机的开发:[https://blog.zeruns.tech/archives/690.html](https://blog.zeruns.tech/archives/690.html) - 野草云 香港BGP 云服务器 性能测评,1核2G 100兆 仅需138元/年:[https://blog.vpszj.cn/archives/1474.html](https://blog.vpszj.cn/archives/1474.html) - 雨云 宿迁5900X高防云服务器 性能测评,2核4G 5兆 150G防御 仅需50元/月:[https://blog.vpszj.cn/archives/1125.html](https://blog.vpszj.cn/archives/1125.html) 最后修改:2023 年 06 月 15 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 1 如果您觉得我的文章有帮助,请随意赞赏,赞赏有助于激发博主的热情,感谢!