emWin图形库移植—-stm32F103

本次emWin移植根据开发板自带的教程《普中STM32F1xx-STemWin开发指南》进行 ,stemWin的源码也使用教程资源自带的5.26版本的源码。

源码目录结构如下

253xEV.png

首先可以看下emWin的框架图

2fa34K.png

从中可以看出,连接emWin与硬件的是驱动层,所以我们移植中需要修改和重点关注的是与显示外设的驱动有关的部分

  • 移植思路如下
  1. 修改emWIn需要分配内存部分的代码来兼容不同芯片与外设配置(外部RAM)下的内存分配接口,主要在GUIConf.c 中修改。
  2. 使用LCD的接口函数来替代emWin的底层画图函数(读点、画点、画线、填充),主要在GUIDRV_Template.c 中修改
  3. 修改emWin驱动底层硬件的接口代码,来适配显示外设

根据移植思路,首先创建好移植的工程模板,并在工程模板文件夹下创建EMWIN文件夹,并将emWin源码放入文件夹中

25GQiT.png

接下来在工程中建立好管理组,并添加包含路径

25wcIs.png

其中需要注意的是在OS文件夹中,由于本次只是移植到裸机中,所以使用GUI_X.c,如果要移植到实时操作系统中则需要添加相应操作系统版本的OS文件,同理在LIB中我们也要选择相应的链接库

  • 准备好源码后就可以开始修改移植emWIn了,首先修改GUIConf.c文件,修改emWIn中的内存分配代码来适配硬件

      在·GUIConf.c·中我们需要修改GUI_X_Config()函数,来完成内存初始化的分配任务,使用我们自己的管理内存接口来为其分配内存

    源代码如下:

    void GUI_X_Config(void) 
    {
    //
    // 32 bit aligned memory area
    //
    static U32 aMemory[GUI_NUMBYTES / 4];
    //
    // Assign memory to emWin
    //
    GUI_ALLOC_AssignMemory(aMemory, GUI_NUMBYTES);
    //
    // Set default font
    //
    GUI_SetDefaultFont(GUI_FONT_6X8);
    }

    其中我们需要修改的是使aMemory指针所分配到的内存由我们自己分配来适配我们的硬件,使其能正确分配到合适且足够的内存空间。

    修改代码如下:

    #include "malloc.h" 
    //设置 EMWIN 内存大小 
    #define GUI_NUMBYTES (500*1024) 
    #define GUI_BLOCKSIZE 0X80 //块大小
    
    void GUI_X_Config(void) 
    {
    //
    // 32 bit aligned memory area
    //
    //static U32 aMemory[GUI_NUMBYTES / 4];
    
    U32 *aMemory = mymalloc(SRAMEX,GUI_NUMBYTES); //从外部 SRAM 中分配 GUI_NUMBYTES 字节的内存
    
    //
    // Assign memory to emWin
    //
    GUI_ALLOC_AssignMemory(aMemory, GUI_NUMBYTES);
    
    GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSIZE); //设置存储快的平均尺寸,该区越大,可用的存 储快数量越少
    
    //
    // Set default font
    //
    GUI_SetDefaultFont(GUI_FONT_6X8);
    }
  • 接下来就是修改emWIn的底层画图函数,使其使用LCD屏的底层接口函数来完成底层的画图函数接口

    打点函数

    static void _SetPixelIndex(GUI_DEVICE * pDevice, int x, int y, int >PixelIndex) 
    {
       LCD_DrawFRONT_COLOR(x,y,PixelIndex); //调用 tftlcd.c 文件中的打点函数 
    }

读点函数

static unsigned int _GetPixelIndex(GUI_DEVICE * pDevice, int x, int y) 
{
  unsigned int PixelIndex;
    //
    // Convert logical into physical coordinates (Dep. on LCDConf.h)
    //
    #if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
      int xPhys, yPhys;

      xPhys = LOG2PHYS_X(x, y);
      yPhys = LOG2PHYS_Y(x, y);
    #else
      #define xPhys x
      #define yPhys y
    #endif
    GUI_USE_PARA(pDevice);
    GUI_USE_PARA(x);
    GUI_USE_PARA(y);
    {
      PixelIndex = LCD_ReadPoint(x,y);//这个是彩屏底层的读点函数
    }
    #if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
      #undef xPhys
      #undef yPhys
    #endif
  return PixelIndex;
}

填充函数

static void _FillRect(GUI_DEVICE * pDevice, int x0, int y0, int x1, int y1) 
{ 
    LCD_Fill(x0,y0,x1,y1,LCD_COLORINDEX);//这个是彩屏底层的填充函数 
}

画图函数

static void _DrawBitLine16BPP(GUI_DEVICE * pDevice, int x, int y, U16 const GUI_UNI_PTR * p, int xsize) 
{
    LCD_PIXELINDEX pixel; 
    for (;xsize > 0; xsize--, x++, p++) 
    { 
        if(xsize>tftlcd_data.width)
            y++; 
        LCD_Set_Window(x,y,tftlcd_data.width-xsize,y); 
        pixel = *p; 
        LCD_WriteData_Color(pixel); 
    } 
}
  • 然后修改`LCDConf_FlexColor_Template.c中的驱动函数,使其适配我们的LCD屏

    其中LcdWriteReg()、LcdWriteData()、LcdWriteDataMultiple()、 LcdReadDataMultiple()、LCD_X_Config()和 LCD_X_DisplayDriver()这 6 个函数中的前4个函数是直接使用STemWin的LCD驱动程序,与我们的LCD不适配,所以我们不需要,将其删除并修改LCD_X_Config()来适配LCD

    修改代码如下:

    void LCD_X_Config(void) 
    { 
    GUI_DEVICE_CreateAndLink(&GUIDRV_Template_API, GUICC_M565, 0, 0); //创建显示驱动器件 
    LCD_SetSizeEx (0, tftlcd_data.width, tftlcd_data.height); 
    LCD_SetVSizeEx (0, tftlcd_data.width, tftlcd_data.height);
    }

    完成上述操作后,就完成了最基本移植功能,触摸功能移植等下次有时间再来添加

    接下来编写一段测试代码,检测是否移植成功

    #include "stm32f10x.h"
    #include "system.h"
    #include "SysTick.h"
    #include "tftlcd.h"
    #include "24cxx.h"
    #include "malloc.h" 
    #include "time.h"
    #include "flash.h"
    #include "sram.h"
    #include "GUI.h"
    
    int main()
    {
    SysTick_Init(72);
    
    TFTLCD_Init();          //LCD初始化
    
    FSMC_SRAM_Init(); 
    
    my_mem_init(SRAMIN);        //初始化内部内存池
    my_mem_init(SRAMEX);        //初始化外部SRAM内存池
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC,ENABLE);//使能CRC时钟,否则STemWin不能使用
    
    GUI_Init();
    
    GUI_SetBkColor(GUI_BLUE); //设置背景颜色 
    GUI_SetColor(GUI_RED); //设置颜色 
    GUI_Clear(); //清屏 
    GUI_SetFont(&GUI_Font24_ASCII); //设置字体 
    GUI_DispStringAt("Hello Word!",0,0); //在指定位置显示字符串
    
    while(1)
    {
    
    }
    }
    

MFC使用Opencv接口记录

  这次在写一个使用Opencv做图像处理,并使用MFC做窗口界面程序的Demo。在MFC中使用GDI来显示图片需要使用bitmap格式的图片,但Opencv里是使用Mat对象来作为图像接口的,因此需要自己转换一下。为避免重复造轮(在之前确实没有记录笔记的习惯,重复写了好几次),所以这次记录一下,方便下次查阅。


正文开始


  既然为了避免重复造轮所以,自己写了一个类来完成opencv与MFC的接口工作

类声明如下

#pragma once
//导入opencv的库
#include <opencv2/opencv.hpp>
//导入字符串类型所需要的库
#include <string>

using namespace cv;

class MyPicture
{
//析构与构造函数
private:
    MyPicture();
    ~MyPicture();

//成员
public:
    Mat* mat;//opencv接口对象指针
    bool mat_flag;//标志mat是否已加载图像
    MyBitmap* bmp;//MFC显示图像所需Bmp数据指针

//方法
public:
    void ReadPicture(LPCTSTR adr);//读取图片接口函数
    void DeletePicture();//清除bitmap图片缓存,为读取新图片做准备
    void MatToBmp();//将Mat转换为Bitmap图片数据
    void ShowBmp(CDC * dc);//MFC显示图片接口
};
  • 由于MFC提供的显示图片接口StretchDIBits(),需要位图的信息头指针LPBITMAPINFO以及位图数据指针byte*,在保存图片时还需要向图片文件写入位图文件头BITMAPFILEHEADER。所以自己定义了一个结构体来方便管理。
    上面说的两个数据类型MFC已经为我们声明好了,所以导入MFC的库即可

//导入BITMAP类所必要的头文件
#include <afxwin.h>

//定义GDI使用的Bmp位图数据结构体
struct MyBitmap
{
    BITMAPFILEHEADER* pFileHead;//位图文件头数据
    LPBITMAPINFO pDibInfo;//位图信息结构体
    unsigned char* pBmp;//图像位数据缓冲区

    //图片加载标记
    bool flag;
};

声明完之后开始定义类中的方法

  • 首先是构造和析构函数
//构造函数
MyPicture:MyPicture()
{
    mat = NULL;
    mat_flag = false;
    bmp = NULL;
}

//析构函数
MyPicture:~MyPicture()
{
    if(mat != NULL)
    {
        delete mat;
    }
    if(bmp != NULL)
    {
        delete bmp;
    }
}
  • 读取图片的接口函数
void MyPicture::ReadPicture(LPCTSTR adr)
{
    CString cfilename(adr);//将LPCTSTR转为CString
    std::string filename = CStringA(cfilename);//将CString转为string

    //若上一次已加载过图片,则清空内存
    if (mat_flag)
    {
        mat_flag = false;
        delete mat;
    }

    //调用CV读取图片接口
    mat = new Mat(imread(filename));
    //标记图片已加载
    mat_flag = true;

}
  • Mat转bitmap格式函数
void MyPicture::MatToBmp()
{
    //若未加载图像则直接退出
    if (!mat_flag)
        return;

    //清除bmp成员对象之前占用的内存空间
    DeletePicture();

    bmp->pDibInfo = (LPBITMAPINFO)(new BYTE[40]);

    //将图片宽度变换为4的整数倍
    int MatWidth = WIDTHBYTES(mat->cols);

    //创建位图信息结构体
    bmp->pDibInfo->bmiHeader.biSize = 40;//结构体大小
    bmp->pDibInfo->bmiHeader.biWidth = MatWidth;//图像宽度
    bmp->pDibInfo->bmiHeader.biHeight = mat->rows;//图像高度
    bmp->pDibInfo->bmiHeader.biPlanes = 1;
    bmp->pDibInfo->bmiHeader.biBitCount = 24;//位深度
    bmp->pDibInfo->bmiHeader.biCompression = 0;
    bmp->pDibInfo->bmiHeader.biSizeImage = MatWidth * 3 * mat->rows;//图片数据大小
    bmp->pDibInfo->bmiHeader.biXPelsPerMeter = 0;     // 水平方向像素/米,分辨率
    bmp->pDibInfo->bmiHeader.biYPelsPerMeter = 0;     // 垂直方向像素/米,分辨率
    bmp->pDibInfo->bmiHeader.biClrUsed = 0;           // BMP图像使用的颜色,0:表示使用全部颜色
    bmp->pDibInfo->bmiHeader.biClrImportant = 0;      // 重要的颜色数,0:所有的颜色都重要,当显卡不能够显示所有颜色时,辅助驱动程序显示颜色

    //创建位图数据指针
    bmp->pBmp = new unsigned char[mat->rows * MatWidth * 3];
    //存储位图数据
    int LineBytes = MatWidth * 3;//计算图片每行的字节宽度
    for (int h = 1; h <= mat->rows; h++)
    {
        for (int w = 0; w < LineBytes; w += 3)
        {
            bmp->pBmp[LineBytes * (mat->rows - h) + w] = mat->at<cv::Vec3b>(h - 1, (int)(w / 3))[0];
            bmp->pBmp[LineBytes * (mat->rows - h) + w + 1] = mat->at<cv::Vec3b>(h - 1, (int)(w / 3))[1];
            bmp->pBmp[LineBytes * (mat->rows - h) + w + 2] = mat->at<cv::Vec3b>(h - 1, (int)(w / 3))[2];
        }
    }

    //更新文件信息头
    BITMAPFILEHEADER* matHead = new BITMAPFILEHEADER;

    matHead->bfType = (WORD)(('M' << 8) | 'B');//填入文件格式
    matHead->bfOffBits = 14 + 40;//填入位图数据偏移量
    matHead->bfSize = 14 + 40 + bmp->pDibInfo->bmiHeader.biSizeImage;//填入文件大小
    matHead->bfReserved1 = 0;//保留用
    matHead->bfReserved2 = 0;//保留用
    bmp->pFileHead = matHead;//更新文件头
    bmp->flag = true;
}
  • 为了防止第二次转换图片时,没有清空上一次的内存空间,所以定义一个清空MyPicture的内存空间的函数
void MyPicture::DeletePicture()
{
    if (bmp->pFileHead != NULL)
    {
        delete bmp->pFileHead;
    }
    if (bmp->pDibInfo != NULL)
    {
        delete bmp->pDibInfo;
    }
    if (bmp->pBmp != NULL)
    {
        delete[] bmp->pBmp;
    }
    bmp->flag = false;
}
  • 有了bitmap图片的数据后,就可以开始显示图片了
void MyBmp::ShowBmp(CDC * dc)
{
    //若已加载好图像,则开始转换为bitmap格式数据
    if (mat_flag)
        MatToBmp();

    //转换完成则显示图片
    if(bmp->flag)//若已加载图片
    //显示缓存区位图
    StretchDIBits
    (
        dc->GetSafeHdc(),//显示的设备上下文句柄
        0,//图像显示位置 X
        0,//图像显示位置 Y
        bmp->pDibInfo->bmiHeader.biWidth,//图像显示宽度
        bmp->pDibInfo->bmiHeader.biHeight,//图像显示高度
        0,//图像缓冲区位置 X
        0,//图像缓冲区位置 Y
        bmp->pDibInfo->bmiHeader.biWidth,//图像缓冲区宽度
        bmp->pDibInfo->bmiHeader.biHeight,//图像缓冲区高度
        bmp->pBmp,//图像缓冲区指针
        bmp->pDibInfo,//图像信息结构体指针
        DIB_RGB_COLORS,//图像显示类型
        SRCCOPY//图像显示方式
    );
}

接下来使用MFC单文档来使用上述接口

  • 首先在文档类中定义自己写好的图片类接口
//导入自己的图像类
#include "MyPicture.h"

class **<em>Doc : public CDocument
{
.....
// 特性
public:
    MyPicture</em> picture;
.....
}
  • 在构造和析构函数中分配图像类内存
//构造函数
****Doc::C****Doc() noexcept
{
    // TODO: 在此添加一次性构造代码
    picture = new MyPicture();
}

//析构函数
*****Doc::~C****Doc()
{
    if (picture != NULL)
    {
        delete picture;
    }
}
  • 重写文档类的OnOpenDocument函数
    2t7K5F.png
BOOL C****Doc::OnOpenDocument(LPCTSTR lpszPathName)
{
    if (!CDocument::OnOpenDocument(lpszPathName))
        return FALSE;

    // TODO:  在此添加您专用的创建代码

    picture->ReadPicture(lpszPathName);

    //刷新视图
    CMiniFrameWnd <em>pMainFrame = (CMiniFrameWnd</em>)AfxGetMainWnd();
    pMainFrame->Invalidate();

    return TRUE;

}
  • 搞定读取图片接口后就可以开始在OnDrow函数中显示图片了
void C****View::OnDraw(CDC* pDC)
{
    C****Doc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;

    // TODO: 在此处为本机数据添加绘制代码
    pDoc->picture->ShowBmp(pDC);
}
  • 最后就是显示结果啦
    2tHSMR.png
    由于显示图片尺寸不固定,所以没显示完,后续可以修改图片尺寸,或修改显示图片接口StretchDIBits中的尺寸来使图片与窗口适配

UCOSIII系统移植_STM32F103

由于时间线问题,此次先记录下最近在STM32F103上移植UCOSIII的过程。

  在移植前需要UCOSIII的源码,由于官网需要使用Goole邮箱注册,暂时没上外网,所以没有从官网获取,直接去网上找到的UCOSIII(3.02)源码。

UCOSIII的源码文件如下
2uu0nH.png

文件夹 存放文件描述
EvalBoards UCOSIII官网给的评估板的工程文件
uC-CPU 与CPU相关文件
uC-LIB Micrium 公司提供的官方库,诸如字符串操作、内存操作等接口,可用可不用。
uCOS-III UCOSIII与硬件的接口文件(Ports文件夹中),UCOSIII的源代码

移植思路如下:

  • 在UCOSIII中的源代码文件为操作系统的核心文件,属于软件层的底层接口,是不需要修改的。
  • 在UCOSIII中我们的任务切换依赖于CPU的Pendsv异常中断,所以在启动任务中我们还需要修改Pendsv的中断处理函数。
  • 同理,操作系统的延时处理是在滴答定时器的中断函数中完成的,所以也要将滴答定时器的中断处理函数改为UCSOIII为我们编写的处理函数
  • 除此之外我们还要把评估板的相关文件修改一下除时钟配置相关之外的外设代码来适配我们的板子。
  • 最后就是修改UCOS的配置文件来裁剪我们的UCOS。

因此,移植步骤如下:

  1. 修改M3内核的启动文件,将Pendsv的中断处理函数改为UCOSIII为我们写好的中断处理函数。
  2. 将评估板的相关代码中的与我们板子不兼容的外设配置代码删除。
  3. 修改UCOS配置宏,裁剪UCOS功能。
  4. 开始编写任务函数。

至此记录下本次的移植过程:

  1. 首先创建待移植的工程文件,其文件结构如下:
graph TD

style 工程文件夹 fill:   #5F9EA0,stroke:#333,stroke-width:4px,color: #DC143C

    subgraph 工程文件夹
        User(User)
        UCOSIII(UCOSIII)
        OutPut(OutPut)
        Libraies(Libraies)
        App(App)
    end

    User(User)-->存放用户文件
    UCOSIII(UCOSIII)-->存放UCOSIII的移植文件
    OutPut(OutPut)-->存放编译后的输出文件
    Libraies(Libraies)-->存放库文件
    App(App)-->存放驱动与业务逻辑文件

  在此顺便连工程模板文件的创建步骤也记录一下,方便日后查阅

  1. 打开Keil5,并创建好上述工程文件夹
    20vB5Q.png
  • 选择之前创建好的工程文件夹

  • 在工程文件夹中建立好上述的文件夹(UCOSIII在不使用时,UCOSIII文件夹可以不要)

20zWh4.png

  • 选定芯片型号
    20xSGd.png
  1. 设置工程

    20xYi4.png
    (还要记得查看更改芯片的时钟频率,避免仿真时频率不对)

    20zO4e.png

    2BSiE8.png

    2BSZgs.png

    2BSQET.png

    添加的宏如下:USE_STDPERIPH_DRIVER,STM32F10X_HD

    2BSfVf.png

    2Bp9z9.png

  2. 移植库函数文件
    官方库函数文件如下
    2B9atO.png

其中我们要移植的有

graph TB

subgraph 内核文件
    core_cm3.c
    core_cm3.h
end

subgraph 启动文件
    startup_stm32f10x_hd.s
end

subgraph 片上外设接入层系统文件
    system_stm32f10x.c
    system_stm32f10x.h
end

subgraph 外设驱动文件
    inc中的文件
    src中的文件
end

subgraph 库函数头文件
    stm32f10x.h
end

Libraries-->CMSIS-->CM3-->CoreSupport
                          CoreSupport-->core_cm3.c
                          CoreSupport-->core_cm3.h
                    CM3-->DeviceSupport-->ST-->STM32F10X-->stm32f10x.h
                                               STM32F10X-->system_stm32f10x.c
                                               STM32F10X-->system_stm32f10x.h
                                               STM32F10X-->startup-->arm-->startup_stm32f10x_hd.s

Libraries-->STM32F10x_StdPeriph_Driver-->inc中的文件
            STM32F10x_StdPeriph_Driver-->src中的文件
graph LR

subgraph 配置文件
    stm32f10x_conf.h
end

subgraph 中断向量设置文件
    stm32f10x_it.c
    stm32f10x_it.h
end

project-->STM32F10x_StdPeriph_Template-->stm32f10x_conf.h
            STM32F10x_StdPeriph_Template-->stm32f10x_it.c
            STM32F10x_StdPeriph_Template-->stm32f10x_it.h

移植存放路径如下
2BEpKe.png

将上述文件加入管理组后就创建完一个基础的工程模板了
2BVAeJ.png


那么接着回归正题, 开始移植UCOSIII源码

  • 在工程文件夹中的UCOSIII文件夹中创建以下几个目录

2DQJ39.png

同样的在管理组中创建一下几个文件管理组

2DB9u8.png

  • 接下来就是按照之前的思路来移植配置UCOSIII

    1. 修改启动文件中的中断服务函数为OS为我们编写的中断处理函数(Pendsv与Systick中断)

      2DB4aQ.png

      2DBqMV.png

    2. 修改UCOSIII源码中的板载外设文件(bsp.h、bsp.c)

      删除里面的所有外设配置函数,只留下初始化和时钟配置函数

    3. 按需修改UCOS的配置文件

      os_cfg.h 配置UCOSIII中的内核对象与功能

      cpu_cfg.h 配置CPU相关的宏定义(对CPU不熟悉的话可以先忽略)

      os_cfg_app.h 配置系统任务的一些设置(如优先级大小、堆栈大小、空闲与统计任务开启等)


到此操作系统就 移植完成了。