| 本帖最后由 lulugl 于 2024-2-16 13:38 编辑 
 #有奖活动# #申请原创# @21小跑堂
 
 【移植器材】
 1、NUCLEO-G474RE开发板
 2、SH1106显示屏(SPI接口)
 【开发环境】
 1、win11
 2、STM32CubeMAX6.10.0
 3、MDK5.38
 4、TouchGFX4.23.0
 5、Vscode
 【开发板简介】
 板子搭载的是M4内核,速度快,高达170M主频,和数**算加速器CORDIC大大提高运算能力, 输入电压范围1.71~3.6V,512Flash,128K的SRAM,1个用户LED,一个用户按键,32.768khz的外部低速晶振,外接24M的高速晶振,Micro-AB连接器,Arduino™ Uno V3连接器可扩与Arduino™ Uno V3连接,板载STLINK_V3仿真调试器,调试器的主控是STM32F723,下载速度得到的很大的提高。
 实物图:
 
   硬件框图:
 
   开发板硬件布局:
 
   【sh1106OLED屏介绍】
 支持最大 132 X 64 点矩阵面板
 嵌入式 132 X 64 位 SRAM
 工作电压:
 逻辑电源:VDD1 = 1.65V-3.5V
 DC-DC电源:VDD2 = 3.0V-4.2V
 OLED工作电源:
 外部VPP电源 = 6.4V-14.0V
 内部VPP发生器 = 6.4V-9.0V
 最大段输出电流:200mA
 最大公共灌电流:27mA
 8 位 6800 系列并行接口,8 位 8080 系列并行接口,3 线和 4 线串行外设接口,400KHz 快速 I2C 总线接口
 可编程帧频和复用率
 行重映射和列重映射(ADC)
 垂直滚动
 片内振荡器
 可编程内部电荷泵电路输出
 单色无源OLED面板上的256步对比度控制
 低功耗
 睡眠模式:< 5μA
 VDD1 = 0V,VDD2 = 3.0V – 4.2V:< 5μA
 VDD1,2 = 0V,VPP = 6.4V –14.0V:< 5μA
 广泛的工作温度范围:-40至+ 85°C
 可提供COG形式,厚度:300mm
 SH1106 是具有用于有机 / 聚合物发光二极管点矩阵图形显示系统的控制器的单芯片CMOS OLED / PLED 驱动器。 SH1106 由 132 个段,64 个公共端组成,可支持 132 X 64 的最大显示分辨率。它是为公共阴极型 OLED 面板设计的。
 SH1106 嵌入了对比度控制,显示 RAM 振荡器和高效的 DC-DC 转换器,从而减少了外部组件的数量和功耗。 SH1106 适用于各种紧凑型便携式应用,例如手机的子显示屏,计算器和 MP3 播放器等。
 【TouchGFX介绍】
 ouchGFX是一款针对STM32微控制器进行了优化的免费高级图形软件框架。借助STM32图形功能和架构,TouchGFX可通过创建类似于智能手机的图形用户界面,来加快HMI-of-things技术革新。
 
 TouchGFX框架包含易于使用的拖放式图形构建PC工具TouchGFX Designer (TouchGFXDesigner)以及强大的优化图形处理内核TouchGFX引擎。结合WYSIWYG仿真器和自动代码生成功能,TouchGFX大大简化了GUI开发。通过对完成的原型进行快速迭代,它涵盖了从早期设计草图到生成独家最终产品的所有步骤。 TouchGFX Designer可作为独立的软件工具提供,便于快速轻松地进行图形评估和概念验证。TouchGFX框架(包括TouchGFX Designer)包含在STM32Cube MCU软件包中。它完全兼容STM32CubeMX初始化和代码生成工具,便于在统一项目环境中无缝地联合开发图形和主应用程序。 所有功能 结构:轻松创建多屏幕内容和相关转换 小部件:广泛的可定制小部件,如滑动容器和周期进度,便于轻松创建GUI 皮肤: 
 一组即用型图形化皮肤,可实现一致的原型设计,而无需图形化设计工具不限制使用自定义图形交互:动态交互,便于创建用户友好型应用自定义容器:创建用户可重用的应用控件具有统一观感的轻松平台开发
 
 文本处理: 
 
 完全支持多种字母和脚本,如拉丁语、西里尔语、阿拉伯语、汉语和日语 代码生成: 
 TouchGFXDesigner可生成和维护高性能C++代码工具生成的代码与用户代码完全分离各种代码扩展可实现独特的动画与系统互联支持多种集成式开发环境,如IAR Embedded Workbench、Arm Keil和基于GCC的IDE
 
 【实现步骤】
 1、使用STM32CubeMAX生成基于NUCLEO-G474RE开发板的工程。
 
   2、生成工程后,配置SPI2为OLED屏的驱动。spi的配置如下:
 
   这里特别需要提醒一下,sh1106的SPI时序需要配置CPOL为HIGH,CPHA为2 Edge。原因可以查阅sh1106的时序图。
 3、配置SH1106的复位(RST),数据/命令(DC),片选(CS)引脚。具体如下:
 
   配置这些引脚主要是兼顾开发板上的接线,使用开发板上的CN10的如下针脚:
 
   4、配置好后生成MDK工程,添加OLED的驱动。使得可以点亮OLED屏。
 主要的代码如下:
 写一个字节:
 
 写数据与指令:/****************************************************************************
* 名        称:uint8_t OLED_Write_Byte(uint8_t data)
* 功        能:OLED写一个字节
* 入口参数:无
* 出口参数:接收的数据
* 返回值  :0失败,1成功
* 说        明:无
****************************************************************************/
uint8_t OLED_Write_Byte(uint8_t data)
{
        if(!HAL_SPI_Transmit(&hspi2, &data, 1, 10))
                return 0;
        else 
                return 1;
}
 画点:/****************************************************************************
* 名        称:void OLED_Write_Operate(uint8_t mode,uint8_t data)
* 功        能:OLED写操作
* 入口参数:模式,数据
* 出口参数:无
* 返回值  :无
* 说        明:无
****************************************************************************/
void OLED_Write_Operate(uint8_t mode,uint8_t data)
{
        OLED_CS_Low;
        if(mode)
        {
                OLED_Write_Data;
        }
        else
        {
                OLED_Write_Cmd;
        }        
        OLED_Write_Byte(data);        
        OLED_CS_High;
}
 画图片:/*
 * Draw one pixel in the screenbuffer
 * X => X Coordinate
 * Y => Y Coordinate
 * color => Pixel color
 */
void ssd1306_DrawPixel(uint8_t x, uint8_t y, SSD1306_COLOR color) {
    if(x >= MAX_COLUMN || y >= MAX_ROW) {
        // Don't write outside the buffer
        return;
    }
   
    // Draw in the right color
    if(color == White) {
        SSD1306_Buffer[x + (y / 8) * MAX_COLUMN] |= 1 << (y % 8);
    } else { 
        SSD1306_Buffer[x + (y / 8) * MAX_COLUMN] &= ~(1 << (y % 8));
    }
}
 把整个图形缓冲区更新到OLED显存:/* Draw a bitmap */
void ssd1306_DrawBitmap(uint8_t x, uint8_t y, const unsigned char* bitmap, uint8_t w, uint8_t h, SSD1306_COLOR color) {
    int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte
    uint8_t byte = 0;
    if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) {
        return;
    }
    for (uint8_t j = 0; j < h; j++, y++) {
        for (uint8_t i = 0; i < w; i++) {
            if (i & 7) {
                byte <<= 1;
            } else {
                byte = (*(const unsigned char *)(&bitmap[j * byteWidth + i / 8]));
            }
            if (byte & 0x80) {
                ssd1306_DrawPixel(x + i, y, color);
            }
        }
    }
    return;
}
 使用TouchGFX有这几个函数就可以了,因为touchfx是把一整个显示以图片的方式提交给画图与更新函数,这个以后细说。/* Write the screenbuffer with changed to the screen */
void ssd1306_UpdateScreen(void) {
    // Write data to each page of RAM. Number of pages
    // depends on the screen height:
    //
    //  * 32px   ==  4 pages
    //  * 64px   ==  8 pages
    //  * 128px  ==  16 pages
    for(uint8_t i = 0; i < SSD1306_HEIGHT/8; i++) {
                OLED_Write_Operate(OLED_Mode_Cmd,0xB0 + i);// Set the current RAM page address.
        OLED_Write_Operate(OLED_Mode_Cmd,(0x00 + SSD1306_X_OFFSET_LOWER));
        OLED_Write_Operate(OLED_Mode_Cmd,(0x10 + SSD1306_X_OFFSET_UPPER));
                OLED_CS_Low;
                OLED_Write_Data;
                HAL_SPI_Transmit(&hspi2, &SSD1306_Buffer[SSD1306_WIDTH*i], SSD1306_WIDTH, 1000);
                OLED_CS_High;
    }
}
程序到此,我们把程序下载到开发板如果能正常的填满整个OLED屏就成功了。接下面开始配置touchgfx。
 6、下载touchgfx软件包:
 在官方教程:安装 | TouchGFX Documentation里有详细的下载安装toucgfx软件包。大家可以去这里查看,本章节不予介绍。
 7、下载好后,我们开始配置软件包,首先需要打开CRC功能,这是STM32把touchgfx设为免费专用的工具。
 
   我们只需要打开CRC即可,参数不需要特殊配置。
 8、开始TIM7,由于我这次移植没有使用操作系统,所以需要用定时器来定时调用页面刷新。
 
   同时需要开始中断使能。
 7、配置touchGFX
 
   按如下配置好为黑白模式、指定128*64
 
   修改栈空间为0xA000
 
   接着生成代码,并打开在工程的\TouchGFX下面打开TouchGFX Desgner
 
   2、打开设计器后向界面添加一个数据时钟控件:
 
   3、更新一下代码,然后用vscode 打开工程文件夹,添加touchGFX自定义代码:
 1)添加OLED的头文件的引用:
 
 2)修改flushFrameBuffer函数,添加获取图片、更新到OLED的功能,代码如下:#include "oled.h"
#include <touchgfx/hal/OSWrappers.hpp>
 3)添加给C调用的函数如下,获取vSync信号void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
    // Calling parent implementation of flushFrameBuffer(const touchgfx::Rect& rect).
    //
    // To overwrite the generated implementation, omit call to parent function
    // and implemented needed functionality here.
    // Please note, HAL::flushFrameBuffer(const touchgfx::Rect& rect) must
    // be called to notify the touchgfx framework that flush has been performed.
    // To calculate he start adress of rect,
    // use advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect)
    // defined in TouchGFXGeneratedHAL.cpp
    TouchGFXGeneratedHAL::flushFrameBuffer(rect);
        
          const unsigned char* bitmap = (const unsigned char*) getClientFrameBuffer(); //获取图片
    ssd1306_Fill(Black);//清屏
    ssd1306_DrawBitmap(0, 0, bitmap, 128, 64, White); //写入图形
    ssd1306_UpdateScreen(); //更新屏幕
}
 4)在main.c中添回TIM7的回调函数,实现周期调用刷新extern "C"
void touchgfxSignalVSync(void)
{
        /* VSync has occurred, increment TouchGFX engine vsync counter */
        touchgfx::HAL::getInstance()->vSync();
        /* VSync has occurred, signal TouchGFX engine */
        touchgfx::OSWrappers::signalVSync();
}
 5)在主程序中启用TIM7的启动/* USER CODE BEGIN 4 */
extern void touchgfxSignalVSync(void);
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
        if (htim->Instance == TIM7)
        {
                touchgfxSignalVSync();
        }
}
/* USER CODE END 4 */
 到此,用MDK编译好后就可以实现touchGFX来实现点亮屏了。  /* USER CODE BEGIN 2 */
        HAL_TIM_Base_Start_IT(&htim7);
        OLED_Init();
  /* USER CODE END 2 */
为了实现动态的显示时间,我们配置RTC如下:
 
   6)修改ModelListener.hpp
 • 添加Types.h
 • 增加虚函数UpdateTime()
 
   7、修改model.cpp
 • 通过HAL接口获取RTC时间
 
 #include <gui/model/Model.hpp>
#include <gui/model/ModelListener.hpp>
#include "stm32g4xx_hal.h"
extern RTC_HandleTypeDef hrtc;
RTC_TimeTypeDef Time = {0};
RTC_DateTypeDef Date = {0};
Model::Model() : modelListener(0)
{
}
void Model::tick()
{
    HAL_RTC_GetTime(&hrtc, &Time, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &Date, RTC_FORMAT_BIN);
    modelListener->UpdateTime(Time.Hours, Time.Minutes, Time.Seconds);
}
 8、修改Screen1Presenter.hpp
 • 添加UpdateTime接口
 
   9、Screen1Presenter.cpp增加UpdateTime()
 • 通知view更新显示
 
   10、修改Screen1View.hpp
 • 增加UpdateTime()
 
   11、修改screen1View.cpp增加UpdateTime()
 • 设置digitalClock控件时间
 
   到此工程就全部完成了。
 【总结】
 在STM32上使用TouchGFX的图形化设计,简单易用,可支持的字库多,同时移植到其他的芯片也方便快捷。
 编译下载到开发板后效果如下:
 
   
 
 |