STM32H743 驱动 0.96彩屏OLED (一)

 

数据通讯方式 4-SPI
屏幕尺寸 0.96寸
分辨率 160*80*3 
色彩模式 RGB888/565
显示IC SP5210
模块制造商 台湾YEEBO
产地 苏州
物理接口形式 25PIN0.3FPC
主要引脚 VCC VSS RES A0 CS SCL MOSI VPP FRM
显示类型 OLED

 单片机      STM32H743

工作中的任务,给产品增加一个状态指示屏,由于初期SPEC要求的页面不多,复杂度也小,就决定从驱动到应用层都自己写了。最开始使用单个数据的硬件SPI发送,软件触发传输,之后改为DMA触发SPI,最后增加垂直同步外部中断。

从IC手册和设备手册中查好接口定义,网购了那个0.3间距25PIN的FPC排线插座(各种各样的,我喜欢前插口后锁定板的,有同事觉得前插口前锁定的更好)和转2.54的转接PCB板,按照推荐电路接好电阻电容,用直流电源给VPP外接16V供电,虽然容易误碰电压旋钮烧屏,但是这个屏幕不是那种正负4.5V的供电方式,没办法。。。

调试用的H743 最小开发板,上面的晶振12M,与产品样机上用的25M晶振不一样,所以在给不同板下载测试时,要把system_stm32h7xx.c里面的

#define HSE_VALUE                  ((uint32_t)12000000)

#define HSE_VALUE                  ((uint32_t)25000000)

修改成实际使用的晶振频率,不改的话,很大概率也能运行起来,但时不时看门狗定期重启或者SPI传输不正确等现象。

而且后续的PLLM、PLLN、PLLP,以及选用的SPI所在PLL分段也要对应修改一下。

----------此处路段掉坑很多,请小心驾驶。------------

(怕踢电源,先commit一下)

配置完时钟,继续配置引脚。为了尽量降低CPU负载,选用硬件SPI4进行通讯。

#define IO_PE2             GPIOE, GPIO_PIN_2 /* OLED SCK (HARD) SPI4_SCL */
#define IO_PE15_O_OLED_RESET    GPIOE, GPIO_PIN_3 /* OLED RESET (SOFT) */
#define IO_PE11_O_OLED_CS      GPIOE, GPIO_PIN_4 /* OLED CS (SOFT) SPI4_NSS */
#define IO_PE13_O_OLED_A0      GPIOE, GPIO_PIN_5 /* OLED DATA OR PARAM SELECT (SOFT) SPI4_MISO */
#define IO_PE6             GPIOE,  GPIO_PIN_6 /* OLED SDA_IN (HARD) SPI4_MOSI*/

为方便整体修改,做宏定义

#define SPI_CH2_SPI SPI4
#define SPI_CH2_AF GPIO_AF5_SPI4
#define SPI_CH2_GRP_CLK LL_AHB4_GRP1_PERIPH_GPIOE
#define SPI_CH2_PORT GPIOE
#define SPI_CH2_PINS LL_GPIO_PIN_2 | LL_GPIO_PIN_6
#define SPI_CH2_SCK_PORT GPIOE
#define SPI_CH2_SCK_PIN LL_GPIO_PIN_2
#define SPI_CH2_MOSI_PORT GPIOE
#define SPI_CH2_MOSI_PIN LL_GPIO_PIN_6
#define SPI_CH2_CLK_ENABLE LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI4)

(不知道发布后是否可以追加编辑,试试)

配置SPI外设

/* Enable GPIO Clock */

LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_GPIOE);

/* Enable SPI Clock */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI4);


/* SPI SCK GPIO pin configuration*/
{LL_GPIO_InitTypeDef io_initStructure_s;

io_initStructure_s.Pin = SPI_CH2_PINS ;
io_initStructure_s.Mode = LL_GPIO_MODE_ALTERNATE;
io_initStructure_s.Speed = LL_GPIO_SPEED_FREQ_MEDIUM;
io_initStructure_s.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
io_initStructure_s.Pull = LL_GPIO_PULL_NO;
io_initStructure_s.Alternate = LL_GPIO_AF_5;
LL_GPIO_Init(SPI_CH2_PORT , &io_initStructure_s);}


LL_SPI_Disable(SPI4);
/* Configure the SPI parameters */

{LL_SPI_InitTypeDef spi_ch_s;
spi_ch_s.TransferDirection = LL_SPI_SIMPLEX_TX;
spi_ch_s.Mode = LL_SPI_MODE_MASTER;
spi_ch_s.DataWidth = LL_SPI_DATAWIDTH_8BIT;
spi_ch_s.ClockPolarity = LL_SPI_POLARITY_HIGH;
spi_ch_s.ClockPhase = LL_SPI_PHASE_2EDGE;
spi_ch_s.NSS = LL_SPI_NSS_SOFT;

spi_ch_s.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV4;    /* PLL SET 40MHz clock DIV2 for 20MHz /DIV4 for 10MHz*/
spi_ch_s.BitOrder = LL_SPI_MSB_FIRST;
spi_ch_s.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
LL_SPI_Init(SPI_CH2_SPI , &spi_ch_s);}

LL_SPI_SetTransferSize(SPI_CH2_SPI , 0);
LL_SPI_Enable(SPI_CH2_SPI );
LL_SPI_IsActiveFlag_TXC(SPI_CH2_SPI );
LL_SPI_StartMasterTransfer(SPI_CH2_SPI );

使能IO时钟,SPI时钟,IO初始化,SPI初始化。

主要注意 DataWidth = LL_SPI_DATAWIDTH_8BIT; 

OLED的芯片手册上没有限定8BIT还是16BIT,想提高传输速度,自然想用16BIT宽度,但是发现有些指令仅8BIT,有些是7*8BIT,所以为了不增加麻烦,退而求其次,选了8

还有时钟空闲极性ClockPolarity和数据沿位置ClockPhase 也要按照OLED的要求。虽然设置错了可能也能驱动,但是在速度变化或者某些特殊数据时就会造成数据不完整,所以一定要按照芯片手册的要求配置。

因为4线SPI的OLED一般不提供读功能,所以SPI方式为仅发送的主机模式,也可以选双向,区别不大。

 

这样,一个由软件触发传输一次8BIT的SPI就配置好了,可以进一步调试屏幕了。

(commit)

屏幕初始化:(部分IO配置简化表述)

io_cfgOutput(IO_PE11_O_OLED_CS);

io_cfgOutput_pull(IO_PE13_O_OLED_A0,LL_GPIO_PULL_DOWN);

io_cfgOutput(IO_PE15_O_OLED_RESET);

#define LCD_CS_CLR() io_clrOutput(IO_PE11_O_OLED_CS)
#define LCD_CS_SET() io_setOutput(IO_PE11_O_OLED_CS)

#define LCD_RST_CLR() io_clrOutput(IO_PE15_O_OLED_RESET)
#define LCD_RST_SET() io_setOutput(IO_PE15_O_OLED_RESET)

#define LCD_RS_CLR() io_clrOutput(IO_PE13_O_OLED_A0)
#define LCD_RS_SET() io_setOutput(IO_PE13_O_OLED_A0)

配置除SPI SCK、MOSI以外3个控制IO,其中A0给下拉的原因是由于它在使用中负责数据/指令选择,瞬间的高低变化可能会引起显示错误,由此给他一个下拉防止积累电荷。

 

按照屏幕的说明,写数据和写指令都是使用SPI发数据,区分点就是那个A0的高低,所以把写指令 单独 写个函数:

void disp_WriteIndex(BYTE Index)
{
  while(!LL_SPI_IsActiveFlag_TXC(SPI_CH2_SPI )){}
  LCD_RS_CLR();

  LL_SPI_TransmitData8(SPI_CH2_SPI,Index);
  LL_SPI_StartMasterTransfer(SPI_CH2_SPI);

}

这个函数先检查SPI的发送完成标记,等待其发送完成。再拉低A0线,再向SPI数据寄存器写入要发送的数据,最后启动传输。因为在传输后没有继续等待发送完成,因此再次调用SPI发送任何数据、指令之前必须先检查发送状态,再调整A0线高低。

 

BYTE device_display_initialize(BYTE channel_)

{

LCD_CS_CLR();
LCD_RST_SET();
OS_Delay(5);
LCD_RST_CLR();
OS_Delay(5);
LCD_RST_SET();
OS_Delay(10);
disp_WriteIndex(0xAEu); /* DISPLAY OFF */
disp_WriteIndex(0xACu); /* Color/Gray Mode 0-ColorMode */
disp_WriteIndex(0x00u); /* --0--for ColorMode,1 for GrayMode */

disp_WriteIndex(0xFDu); /*removed function MLA--FD ADPS--D6*/
disp_WriteIndex(0x5Bu); /* ---- */
disp_WriteIndex(0x00u); /* ---- */
disp_WriteIndex(0xD6u); /* ---- */

disp_WriteIndex(0x20u); /* Set refresh direction 2lines command*/
disp_WriteIndex(0x00u); /* Set horiz refresh*/
disp_WriteIndex(0xD4u); /* Set color format 2lines command*/
disp_WriteIndex(0x10u); /* Set 65K RGB */

disp_WriteIndex(0xA1u);     /* Horizontal Mirror */

disp_WriteIndex(0xD5u); /* Set Display Clock Divide Ratio/Oscillator Frequency */
disp_WriteIndex(0x13u); /* --12--V_sync limit from 10MHzSPI 20.8ms trans_time ,0x13 the min speed */

disp_WriteIndex(0x80u); /* Set Contrast */
disp_WriteIndex(0xFFu); /* --255--Contrast Max from 0-255*/

disp_WriteIndex(0xA2u); /* Set Display Start Line */
disp_WriteIndex(0x00u); /* --00--default*/
disp_WriteIndex(0x00u); /* --00--default*/

disp_WriteIndex(0xA8u); /* Set Multiplex Ratio: */
disp_WriteIndex(0x4Fu); /* --4F--default*/

disp_WriteIndex(0xADu); /* Set iRef resist */
disp_WriteIndex(0x00u); /* --04--internal 300uA */

disp_WriteIndex(0xDDu); /* VSEGH control no use for user */
disp_WriteIndex(0x1Fu); /* ----VSEGH control no use for user */

disp_WriteIndex(0xD9u); /* Set Discharge/Pre-charge Period */
disp_WriteIndex(0x07u); /* ---- */
disp_WriteIndex(0x0Au); /* ---- */
disp_WriteIndex(0x0Du); /* ---- */
disp_WriteIndex(0x0Du); /* ---- */
disp_WriteIndex(0x0Du); /* ---- */
disp_WriteIndex(0x0Du); /* ---- */
disp_WriteIndex(0x29u); /* ---- */

disp_WriteIndex(0xA4u); /* Set Entire Display hold A5 or normal A4 */

Lcd_SetRegion(0u,0u,159u,79u);

disp_WriteIndex(0xAFu); /* DISPLAY ON */
_Delay(20);

return 1u;

}

其中设置显示区域由于 假设 要经常调用,

所以单独写了一个函数:

void Lcd_SetRegion(WORD x_start,WORD y_start,WORD x_end,WORD y_end)
{
  while(!LL_SPI_IsActiveFlag_TXC(SPI_CH2_SPI)){}
  LCD_RS_CLR();
  disp_WriteIndex(0x22u);
  disp_WriteIndex(0x02u);
  disp_WriteIndex((BYTE)y_start);
  disp_WriteIndex((BYTE)y_end);
  disp_WriteIndex(0x21u);
  disp_WriteIndex(0x01u);
  disp_WriteIndex((BYTE)x_start);
  disp_WriteIndex((BYTE)x_end);
  while(!LL_SPI_IsActiveFlag_TXC(SPI_CH2_SPI)){}
}

如果一切正常,执行到这一步,屏幕就能看到点杂乱的颜色了,之后就可以刷新显示了。

由于不能从屏幕读数据,即便能读也比读内存慢,所以先开辟一块显存,读写先到显存,再整体刷新到屏幕,此思路来自于emWin图形框架。

WORD display_ram_wa[80*160]@ "SRAM1_section"={0};

void Refresh_Gram_Async(void)
{
  WORD i;
  BYTE mid_b;
  SCB_CleanInvalidateDCache();
  LCD_RS_SET();
  for(i=0u;i<12800u;i++)
  {
    mid_b=(BYTE)(display_ram_wa[i]>>8);
    disp_WriteData((BYTE)(display_ram_wa[i]));
    disp_WriteData(mid_b);
  }
}

函数中,SCB_CleanInvalidateDCache();是由于H7的芯片带有Cache,由于显存的数据量较大,写入后立即使用其他方式刷新可能会读不到正确的内容,所以强制刷新一下Cache。由于屏幕像素160*80*RGB565,所以定义了160*80大小的WORD数组;

先解释下RGB565,常规的Windows绘图软件可供用户选择的颜色其红绿蓝色彩分量各有256档,能组成256*256*256=16,777,216种色彩,即RGB888格式,每一个8代表8位,可存储0x00~0xFF共256档。所以存储一个RGB888数据需要24位空间,当然也可以用更多的存储空间存储颜色,进行更细致的划分,但是这样就会增加所需要的内存空间或者调色板尺寸,由于一般的显示设备,并不需要专业级的色彩细节,所以一般彩色显示屏提供RGB888,但是对于常规的单片机,24位并不是一个常用的数据类型,16位或者32位才是更常用的,所以用32位存储RGB888数据当然可以,浪费8位空间,这也是计算机上常规颜色会出现ARGB的原因,富裕出来的8位用来记录透明度。但是选用32位存储颜色不仅仅在存储时增大了空间,还在传输时增加了传输时间。因此简单的显示设备中还提供一种低位宽的颜色格式,RGB565,加和后5+6+5=16,刚好一个WORD,而这种数据仅仅是忽略了各颜色数据的最后两、三位,还原RGB888颜色时将不够的位数尾部补0凑够8位。

刷新显存时只需要将每个像素点共12800个WORD发送出去,由于使用的8BIT宽度配置SPI,所以要将WORD拆分成两块发送。

且发送时最好按照先高位后低位的MSB顺序,至于为什么后面再说,MSB就导致了显存中的颜色数据与实际的RGB565有个区别,如下:

#define Color_RED_MSB565        (0x00F8u)        /* 0xF800-MSB>0x00F8 */

实际的红色RGB565格式 应该是0xF800,但是发送时会先发送内存中的地址较低的一位,而屏幕要求传入的16位颜色数据顺序还是RGB,所以要将真实数据前后位交换后再存入显存,即存入的内容为WORD格式的0x00F8,这样通过SPI逐byte发送,屏幕收到的顺序是0xF8,0x00,才能正确表达位0xF800.

(commit)

 

 

void tskDisplay_tsk(void)
{
    tskDisplay_stack_ba[TASK_DISPALY_STACKSIZE - 1u] = TASK_STACKPROTECT;
    callCount_w                                      = 0u;

    OS_EVENT_CreateEx(&vsyncDisplay_event, OS_EVENT_RESET_MODE_MANUAL); /* globle EVENT */
    initStaticParam();
    while(TRUE)
    {
        if(!display_isConfigured())
        {
            OS_Delay(1);
            OS_EVENT_Set(&vsyncDisplay_event);
            device_close(&displayDevice_s);
            tskDisplay_on();
        }
        else
        {
            display_refresh();
            OS_Delay(tskDisplay_skipFrame_sdw);
        }

        /* Flowcontrol */
        callCount_w++;
        /* Stack analysis */
        if(tskDisplay_stack_ba[TASK_DISPALY_STACKSIZE - 1u] != TASK_STACKPROTECT)
        {
            tuv_systemReset(TUV_ERROR_CPU_FLOWRTOS, TUV_EMERGENCY_CPU_FLOWRTOS);
        }
    }
}
 
后续补充功能,比如运行中更改显示配置,如亮度、显示方向,刷新率等,需要重新初始化,就设置了一个 display_isConfigured() 状态检测,同时用tskDisplay_skipFrame_sdw来控制刷新率。
 
由于后续启用了DMA-SPI传输,所以在更改设置之前,要等待当前DMA发送完毕。
void display_waitForRefresh(void)
{
    DWORD DMA_TC0_b, DMA_EN_b;

#if (SPI_DMA_VSYNC_OFF_0_ON_1 == 1)
    /* while(spi_dma_allow){} */
    OS_EVENT_WaitTimed(&vsyncDisplay_event, 100);
#endif
    WORD timeout_w = 100u;
    do
    {
        DMA_TC0_b = LL_DMA_IsActiveFlag_TC0(SPI_CH2_DMA);
        DMA_EN_b  = LL_DMA_IsEnabledStream(SPI_CH2_DMA, SPI_CH2_DMA_TX_STREAM);
        if((!DMA_TC0_b)  && (DMA_EN_b))
        {
            OS_Delay(1);
        }

        if(timeout_w) { timeout_w--; }
    }while((!DMA_TC0_b)  && (DMA_EN_b) && timeout_w);
}
并把这段放在常规的 display_refresh(); 开头或结尾。放开头是由于没而有预留双显存,必须要等前一帧发送结束,才能再在显存上绘图。预留双缓存后就可以放结尾,下一次启动刷新前等待发送完毕。
 
void Refresh_Gram(void)
{
#if (RTIG_SPI__SOFT_0_DMA_1 == 1)
    SCB_CleanInvalidateDCache();
    LCD_RS_SET();
    #if (SPI_DMA_VSYNC_OFF_0_ON_1 == 1)
    OS_EVENT_Reset(&vsyncDisplay_event);
    /* spi_dma_allow=1u; */
    #elif (SPI_DMA_VSYNC_OFF_0_ON_1 == 0)
    LL_DMA_ClearFlag_TC0(SPI_CH2_DMA);
    LL_DMA_EnableStream(SPI_CH2_DMA, SPI_CH2_DMA_TX_STREAM);
    #endif
#else
    WORD i;

    BYTE mid_b;

    SCB_CleanInvalidateDCache();
    LCD_RS_SET();
    for(i = 0u; i < 12800u; i++)
    {
        mid_b = (BYTE)(display_ram_wa[i] >> 8);
        disp_WriteData((BYTE)(display_ram_wa[i]));
        disp_WriteData(mid_b);
    }
#endif
}

void Refresh_Gram_ISRHandler(void)
{
#if (RTIG_SPI__SOFT_0_DMA_1 == 1)
    SCB_CleanInvalidateDCache();
    LCD_RS_SET();
    LL_DMA_ClearFlag_TC0(SPI_CH2_DMA);
    LL_DMA_EnableStream(SPI_CH2_DMA, SPI_CH2_DMA_TX_STREAM);
#endif
}
单独这个启动DMA传输,就写了三种:
一个同步模式,就是垂直同步,只做标记,等垂直同步信号收到后启动传输。
一个非同步模式,忽略垂直同步,直接启动,在刷新较慢的屏幕上会看到图像撕裂。
另一个就是垂直同步信号IO外部中断函数里的启动传输。
 
写入缓存的函数,包含清空(统一设置一个颜色),设置、读取
void clear_mem_color16(WORD color)
{
    WORD i;

    for(i = 0u; i < 12800u; i++)
    {
        display_ram_wa[i] = color;
    }
}

/** @brief set color to a point in Gram
  * @param WORD color
  * @return void
  */
void set_mem_16bit(WORD u16color, BYTE x, BYTE y)
{
#if    (REFRESH__HORIZ_0_VERTI_1 == 1)
    display_ram_wa[x * 80u + (79u - y)] = u16color;
#elif  (REFRESH__HORIZ_0_VERTI_1 == 0)
    display_ram_wa[y * 160u + x]        = u16color;
#endif
}

/** @brief get color from a point in Gram
  * @param BYTE x
  * @param BYTE y
  * @return WORD
  */
WORD get_mem_16bit(BYTE x, BYTE y)
{
#if    (REFRESH__HORIZ_0_VERTI_1 == 1)
    return (display_ram_wa[x * 80u + (79u - y)]);
#elif  (REFRESH__HORIZ_0_VERTI_1 == 0)
    return (display_ram_wa[y * 160u + x]);
#endif
}
 
简单应用不涉及读取,读取是为了后面做透明叠加时运算重叠后颜色用的。
REFRESH__HORIZ_0_VERTI_1 用来定义屏幕的刷新方向。
屏幕参数可以配置,是由起始点 --第一行首位 开始横向刷新到第一行末位 再到 第二行 首位再至 第二行末位 这种横向刷新
或者是  第一列首位 刷新至 第一列末位,再换至 第二列首位 刷新至 第二列末位。
两种方式看似没有区别,但实际,在非垂直同步模式下(垂直同步掉帧切需要多硬件配合,简单功能或静止图像不用考虑),如果图像 沿 行方向移动频繁,刷新与移动在同一直线,
图像撕裂现象明显。此时如果采用 列模式刷新,图像移动和刷新方向垂直,非同步引起的撕裂只会将图像压缩变紧凑,在动态中不明显,图像撕裂现象很不容易被察觉。
 
下面是常规16位彩色 RGB565颜色转8位灰度函数,和RGB565前景色背景色按比例透明混合的计算函数。
BYTE color565ToGray(WORD frontcolor565)
{
    WORD colo_ = (WORD)((((DWORD)frontcolor565 & 0xC000u) >> 8 | ((DWORD)frontcolor565 & 0x0007u) << 8));

    colo_ = (WORD)(((((DWORD)frontcolor565 & 0x1F00u) >> 7u) * 30u +
                    ((DWORD)colo_ >> 5u) * 150u +

                    (((DWORD)frontcolor565 & 0x00F8u) >> 2u) * 76u
                    ) >> 8u);

    return (BYTE)colo_;
}

WORD color565mulitply(WORD cololr565, BYTE mult_, BYTE divi_)
{
    WORD colo_ = ((((cololr565 & 0xC000u) >> 8 | (cololr565 & 0x0007u) << 8) * mult_ / divi_) & 0x07E0u);

    return (
        (((cololr565 & 0x1F00u) / divi_ * mult_) & 0x1F00u) |
        (colo_ >> 8) | ((colo_ & 0xE0u) << 8) |
        /* (((cololr565&0x07E0u)*mult_/divi_)&0x07E0u) |  */
        (((cololr565 & 0x00F8u) * mult_ / divi_) & 0x00F8u)
        );
} /* 0xE007,0x1F00 */

WORD color565mulitplyTransp(WORD frontColor565, WORD backColor565, BYTE mult_, BYTE divi_)
{
    WORD colo_  = (WORD)(((((DWORD)frontColor565 & 0xC000u) >> 8 | ((DWORD)frontColor565 & 0x0007u) << 8) * mult_ / divi_) & 0x07E0u);
    WORD colob_ = (WORD)(((((DWORD)backColor565 & 0xC000u) >> 8 | ((DWORD)backColor565 & 0x0007u) << 8) * ((DWORD)divi_ - (DWORD)mult_) / divi_) & 0x07E0u);

    colo_ = ((WORD)(((((DWORD)frontColor565 & 0x1F00u) * (WORD)mult_ / (WORD)divi_) & 0x1F00u))
             | (WORD)(colo_ >> 8u) |
             ((colo_ & 0xE0u) << 8u) |
             ((WORD)((((DWORD)frontColor565 & 0x00F8u) * (WORD)mult_ / (WORD)divi_) & 0x00F8u))
             );

    colob_ = (
        (WORD)((((DWORD)backColor565 & 0x1F00u) * ((DWORD)divi_ - (DWORD)mult_) / divi_) & 0x1F00u) |
        (WORD)(colob_ >> 8) | ((colob_ & 0xE0u) << 8) |
        (WORD)((((DWORD)backColor565 & 0x00F8u) * ((DWORD)divi_ - (DWORD)mult_) / divi_) & 0x00F8u)
        );

    return (colo_ + colob_);
}

WORD newcolor565mulitplyTransp(WORD frontColor565, WORD backColor565, BYTE mult_, BYTE divi_)
{
    DWORD R_dw, G_dw, B_dw;
    DWORD R2_dw, G2_dw, B2_dw;

    R_dw = (((DWORD)frontColor565 & 0x00F8u) * (DWORD)mult_ / (DWORD)divi_) & 0x00F8u;
    if(R_dw > 0x00F8u) { R_dw = 0x00F8u; }

    R2_dw = (((DWORD)backColor565 & 0x00F8u) * ((DWORD)divi_ - (DWORD)mult_) / (DWORD)divi_) & 0x00F8u;
    if(R2_dw > 0x00F8u) { R2_dw = 0x00F8u; }

    R2_dw += R_dw;
    if(R2_dw > 0x00F8u) { R2_dw = 0x00F8u; }

    G_dw = ((((DWORD)frontColor565 & 0xE000u) >> 8 | ((DWORD)frontColor565 & 0x0007u) << 8) * mult_ / divi_) & 0x07E0u;
    if(G_dw > 0x07E0u) { G_dw = 0x07E0u; }

    /* G_dw=(G_dw>>8u) | ((G_dw&0xE0u)<<8u); */
    G2_dw = ((((DWORD)backColor565 & 0xE000u) >> 8 | ((DWORD)backColor565 & 0x0007u) << 8) * ((DWORD)divi_ - (DWORD)mult_) / divi_) & 0x07E0u;
    if(G2_dw > 0x07E0u) { G2_dw = 0x07E0u; }

    G2_dw += G_dw;
    if(G2_dw > 0x07E0u) { G2_dw = 0x07E0u; }

    G2_dw = (G2_dw >> 8u) | ((G2_dw & 0xE0u) << 8u);

    B_dw  = ((((DWORD)frontColor565 & 0x1F00u) * (DWORD)mult_ / (DWORD)divi_) & 0x1F00u);
    if(B_dw > 0x1F00u) { B_dw = 0x1F00u; }

    B2_dw = ((((DWORD)backColor565 & 0x1F00u) * ((DWORD)divi_ - (DWORD)mult_) / divi_) & 0x1F00u);
    if(B2_dw > 0x1F00u) { B2_dw = 0x1F00u; }

    B2_dw += B_dw;
    if(B2_dw > 0x1F00u) { B2_dw = 0x1F00u; }

    return ((WORD)(R2_dw + G2_dw + B2_dw));
}

 

 

将颜色或图片数据绘制到显存,并进行镜像翻转旋转90°等操作的函数示例,再加入蒙版数据还可以进行透明叠加等动作,函数种类太多,并未来得及整理,就先放一个。

/** @brief draw PIC at GRAM
  * set color with get original color data
  * Draw a 16 bit grayscale image in the memory buffer, fill the foreground color
  * with (grayscale/16) mask transparency to the background color, and then draw the
  * entire image to the memory according to the specified transparency. (Draw watermark with background color)
  * @param
  * const WORD frontColor565,   ForeColor
  * const WORD backColor565,    --no-used--BackColor
  * const BYTE msk_16gray[],    Brightness mask pointer
  * BYTE max16Gray,             Max brightness
  * SWORD xpos,SWORD ypos,      top left position
  * SWORD width, SWORD height,  size
  * SWORD starwidth, SWORD starheight,  start width when draw,start height when draw
  * SWORD endWidth, SWORD endHeight,    finish width when draw,finish height when draw
  * enum PIC_ROUTE_ANGLE route_0_90_180_270,    rotate
  * enum PIC_MIRROR_ANGLE mirror                mirror
  * @return void
  */
void drawcolor_mask16grayTrFoTrBaRM(const WORD frontColor565, const WORD backColor565, const BYTE msk_16gray[], BYTE max16Gray, BYTE transparent, SWORD xpos, SWORD ypos, SWORD width, SWORD height, SWORD starwidth, SWORD starheight, SWORD endWidth, SWORD endHeight, enum PIC_ROUTE_ANGLE route_0_90_180_270, enum PIC_MIRROR_ANGLE mirror)
{
    SWORD j, k, mid_k, mid_j, mid_sw, mid_x, mid_y;
    SWORD org_width = width, org_height = height;
    SWORD mid1_sw = (width % 2);
    BYTE gap_w    = (BYTE)mid1_sw;
    BYTE gray;

    if((X_MAX_PIXEL < xpos) || (Y_MAX_PIXEL < ypos) || (xpos + width < 1) || (ypos + height < 1))
    {}
    else
    {
        if(width + xpos > X_MAX_PIXEL)
        {
            width = X_MAX_PIXEL - xpos;
        }

        if(height + ypos > Y_MAX_PIXEL)
        {
            height = Y_MAX_PIXEL - ypos;
        }

        for(j = starheight; j < endHeight; j++)
        {
            mid_j = j;
            if(mirror & 0x01u) { mid_j = org_height - 1 - mid_j; } /* 0 1 2 3 4 5 6 7 8 9 ~ drawHeight */

            if((j >= height) || (j + ypos - 1 < 0))
            {}
            else
            {
                for(k = starwidth; k < endWidth; k++) /* 0 1 2 3 4 5 6 7 8 9 ~ 29 */
                {
                    mid_k = k;
                    if((k >= width) || (k + xpos < 0)) {}
                    else
                    {
                        if(mirror & 0x02u) { mid_k = org_width - 1 - mid_k; }

                        /* MIRROR_HORIZ */
                        /* 79 78 77 76 75 74 73 ~ 0 */

                        mid_sw = ((j * (org_width + (SWORD)gap_w) + k));
                        gray   = (msk_16gray[mid_sw / 2] >> ((1 - mid_sw % 2) * 4)) & 0x0fu;
                        if(gray)
                        {
                            switch(route_0_90_180_270)
                            {
                            case ROUTE_0:
                                mid_x = (xpos + mid_k);
                                mid_y = (ypos + mid_j);

                                break;
                            case ROUTE_90:
                                mid_x = xpos + (org_height - 1 - (mid_j));
                                mid_y = ypos + ((mid_k));

                                break;
                            case ROUTE_180:
                                mid_x = xpos + (org_width - 1 - (mid_k));
                                mid_y = ypos + (org_height - 1 - (mid_j));

                                break;
                            case ROUTE_270:
                                mid_x = xpos + ((mid_j));
                                mid_y = ypos + (org_width - 1 - (mid_k));

                                break;
                            default:
                                break;
                            }

                            if(((WORD)mid_x < MAX_X_PIXELS) && ((WORD)mid_y < MAX_Y_PIXELS))
                            {
                                set_mem_16bit(color565mulitplyTransp(
                                                  color565mulitplyTransp(frontColor565, backColor565, gray, max16Gray),
                                                  get_mem_16bit((BYTE)mid_x, (BYTE)mid_y) /* backColor565 */,
                                                  transparent, 0xfu),
                                              (BYTE)mid_x,
                                              (BYTE)mid_y
                                              );
                            }
                        }
                    }
                }
            }
        }
    }
}

(commit -- 2024 08 26)

posted @ 2022-07-07 10:59  华斯基  阅读(884)  评论(0编辑  收藏  举报