嵌入式Linux C应用编程——LCD横竖屏切换

LCD 横屏切换为竖屏

横屏/竖屏的切换与驱动程序无关,是应用层需要解决的一个问题。

背景知识:横屏下的显存寻址

LCD 硬件上像素点的排列顺序是 从左到右、从上到下。以 800×480 分辨率为例,物理坐标分布如下:

(0, 0)            (799, 0)
    ┌──────────────────┐
    │                  │
    │                  │
    │                  │
    └──────────────────┘
(0, 479)          (799, 479)

对于任意物理坐标 (x, y),其对应的显存地址为:

地址 = base + (y × width + x) × pix_bytes

其中:

  • base — 显存基地址(mmap 返回值)
  • width — LCD 水平分辨率(如 800)
  • pix_bytes — 每个像素占用的字节数(RGB565 为 2 字节)

竖屏显示的坐标变换

竖屏坐标定义

将横屏屏幕 顺时针旋转 90°,得到竖屏坐标系:

  • 竖屏起点 (0, 0) 对应物理坐标 (0, height-1)(左下角)
  • 竖屏 X 轴向上增长,对应物理 Y 轴的反向
  • 竖屏 Y 轴向右增长,对应物理 X 轴的正向

两个坐标系的对应关系如下:

横屏物理坐标(800×480)           竖屏应用坐标(480×800)
                                   
(0, 0)  ┌──────────┐ (799, 0)      (479, 0) ┌──────────┐ (479, 799)
        │  ──→ Y   │                        │  ←── Y   │
        │  ↓ X     │                        │  ↑ X     │
        │          │                        │          │
(0, 479)└──────────┘ (799, 479)    (0, 0)   └──────────┘ (0, 799)

对应关系对照表:

竖屏坐标 物理坐标 说明
(0, 0) (0, height-1) = (0, 479) 左下角
(0, width-1) = (0, 799) (799, 479) 右下角
(height-1, 0) = (479, 0) (0, 0) 左上角
(height-1, width-1) = (479, 799) (799, 0) 右上角

核心公式推导

目标:给定竖屏坐标 (x, y),找到它对应的显存地址。

步骤 1:竖屏 X → 物理 Y

竖屏 X 坐标从底部向上增长,而物理 Y 坐标从顶部向下增长。两者是反向线性关系

物理 Y = (height - 1) - 竖屏_X

注意这里的 竖屏_X 是一个变量(取值 0 ~ height-1),不是固定值:

  • 竖屏 X = 0(最底部)→ 物理 y = (height-1) - 0 = height - 1(最底行)
  • 竖屏 X = 1(向上移一行)→ 物理 y = (height-1) - 1(向上一行)
  • 竖屏 X = height - 2 → 物理 y = 1
  • 竖屏 X = height - 1(最顶部)→ 物理 y = (height-1) - (height-1) = 0(最顶行)

不难看出,竖屏 X 每增加 1,物理 Y 就减少 1(向上移动一行),这就是 (height - 1) - x 中减去 x 的原因。

步骤 2:竖屏 Y → 物理 X

竖屏 Y 坐标从左向右增长,物理 X 坐标也是从左向右增长。两者是正向线性关系

物理 X = 竖屏 Y
  • 竖屏 y = 0 → 物理 x = 0(最左列)
  • 竖屏 y = width - 1 → 物理 x = width - 1(最右列)

步骤 3:代入显存寻址公式

将上述映射关系代入物理显存地址公式:

地址 = base + (物理_y × width + 物理_x) × pix_bytes
     = base + (((height - 1) - x) × width + y) × pix_bytes

公式的直观理解

竖屏坐标 物理坐标 显存地址计算
(0, 0) 左下角 (0, height-1) base + ((height-1) × width + 0) × pix_bytes
(0, width-1) 右下角 (width-1, height-1) base + ((height-1) × width + (width-1)) × pix_bytes
(height-1, 0) 左上角 (0, 0) base + 0
(height-1, width-1) 右上角 (width-1, 0) base + (width-1) × pix_bytes

注意:这个公式仅适用于"顺时针旋转 90°,起点在左下角"这一种竖屏方式。如果旋转 180° 或其他角度,公式需要相应调整。

应用编程实战

完整源码


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

#define argb8888_to_rgb565(color)   ({ \
            unsigned int temp = (color); \
            ((temp & 0xF80000UL) >> 8) | \
            ((temp & 0xFC00UL) >> 5) | \
            ((temp & 0xF8UL) >> 3); \
            })

static int lcd_width;                   // LCD X 分辨率
static int lcd_height;                  // LCD Y 分辨率
static int lcd_max_y;                   // LCD Y 坐标最大值
static int user_width;                  // 竖屏模式下 X 分辨率
static int user_height;                 // 竖屏模式下 Y 分辨率
static unsigned short *screen_base = NULL;  // 映射后的显存基地址

/********************************************************************
 * 函数名称: lcd_draw_point
 * 功能描述: 打点
 * 输入参数: x, y, color
 * 返 回 值: 无
 ********************************************************************/
static void lcd_draw_point(unsigned int x, unsigned int y,
                           unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);

    if (x >= user_width)
        x = user_width - 1;
    if (y >= user_height)
        y = user_height - 1;

    /* 核心:竖屏坐标转物理显存地址 */
    screen_base[(lcd_max_y - x) * lcd_width + y] = rgb565_color;
}

/********************************************************************
 * 函数名称: lcd_draw_line
 * 功能描述: 画线(水平或垂直线)
 ********************************************************************/
static void lcd_draw_line(unsigned int x, unsigned int y, int dir,
            unsigned int length, unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);
    unsigned int end;
    unsigned long temp;

    if (x >= user_width)
        x = user_width - 1;
    if (y >= user_height)
        y = user_height - 1;

    temp = (lcd_max_y - x) * lcd_width + y;
    if (dir) {  // 水平线(竖屏X方向)
        end = x + length - 1;
        if (end >= user_width)
            end = user_width - 1;
        for ( ; x <= end; x++, temp -= lcd_width)
            screen_base[temp] = rgb565_color;
    } else {    // 垂直线(竖屏Y方向)
        end = y + length - 1;
        if (end >= user_height)
            end = user_height - 1;
        for ( ; y <= end; y++, temp++)
            screen_base[temp] = rgb565_color;
    }
}

/********************************************************************
 * 函数名称: lcd_draw_rectangle
 * 功能描述: 画矩形
 ********************************************************************/
static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x,
            unsigned int start_y, unsigned int end_y,
            unsigned int color)
{
    int x_len = end_x - start_x + 1;
    int y_len = end_y - start_y - 1;

    lcd_draw_line(start_x, start_y, 1, x_len, color);       // 上边
    lcd_draw_line(start_x, end_y, 1, x_len, color);         // 下边
    lcd_draw_line(start_x, start_y + 1, 0, y_len, color);   // 左边
    lcd_draw_line(end_x, start_y + 1, 0, y_len, color);     // 右边
}

/********************************************************************
 * 函数名称: lcd_fill
 * 功能描述: 将一个矩形区域填充指定颜色
 ********************************************************************/
static void lcd_fill(unsigned int start_x, unsigned int end_x,
            unsigned int start_y, unsigned int end_y,
            unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);
    unsigned long temp;
    unsigned long step_size_count;
    int x;

    if (end_x >= user_width)
        end_x = user_width - 1;
    if (end_y >= user_height)
        end_y = user_height - 1;

    temp = (lcd_max_y - start_x) * lcd_width + start_y;
    for ( ; start_y <= end_y; start_y++, temp++) {
        step_size_count = 0;
        for (x = start_x; x <= end_x; x++, step_size_count += lcd_width)
            screen_base[temp - step_size_count] = rgb565_color;
    }
}

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    /* 打开 framebuffer 设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    screen_size = fb_fix.line_length * fb_var.yres;
    lcd_width = fb_var.xres;
    lcd_height = fb_var.yres;
    lcd_max_y = lcd_height - 1;
    user_width = fb_var.yres;   // 竖屏宽 = 物理高
    user_height = fb_var.xres;  // 竖屏高 = 物理宽

    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE,
                       MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 画正方形方块 */
    int w = user_height * 0.25;
    lcd_fill(0, user_width-1, 0, user_height-1, 0x0);       // 清屏黑色
    lcd_fill(0, w, 0, w, 0xFF0000);                         // 红色方块
    lcd_fill(user_width-w, user_width-1, 0, w, 0xFF00);     // 绿色方块
    lcd_fill(0, w, user_height-w, user_height-1, 0xFF);     // 蓝色方块
    lcd_fill(user_width-w, user_width-1,
             user_height-w, user_height-1, 0xFFFF00);        // 黄色方块

    /* 画十字交叉线 */
    lcd_draw_line(0, user_height * 0.5, 1,
                  user_width, 0xFFFFFF);                     // 白色水平线
    lcd_draw_line(user_width * 0.5, 0, 0,
                  user_height, 0xFFFFFF);                    // 白色垂直线

    /* 画嵌套矩形 */
    unsigned int s_x, s_y, e_x, e_y;
    s_x = 0.25 * user_width;
    s_y = w;
    e_x = user_width - s_x;
    e_y = user_height - s_y;

    for ( ; (s_x <= e_x) && (s_y <= e_y);
            s_x+=5, s_y+=5, e_x-=5, e_y-=5)
        lcd_draw_rectangle(s_x, e_x, s_y, e_y, 0xFFFFFF);

    /* 退出 */
    munmap(screen_base, screen_size);
    close(fd);
    exit(EXIT_SUCCESS);
}

与横屏代码的主要区别

以下是竖屏代码相比之前横屏代码的关键改动点:

对比项 横屏代码 竖屏代码(本程序)
用户坐标范围 user_width = lcd_width(800)
user_height = lcd_height(480)
user_width = lcd_height(480)
user_height = lcd_width(800)—— 横竖互换
打点公式 screen_base[y * lcd_width + x]
用户坐标 = 物理坐标,直接映射
screen_base[(lcd_max_y - x) * lcd_width + y]
竖屏坐标 (x, y) 经过变换后写入物理显存
画水平线
(dir=1)
沿物理 X 方向步进:
temp++(向右)
沿竖屏 X 方向步进(视觉上为竖屏的水平方向):
temp -= lcd_width向上,因为竖屏X映射到物理Y的反向)
画垂直线
(dir=0)
沿物理 Y 方向步进:
temp += lcd_width(向下)
沿竖屏 Y 方向步进(视觉上为竖屏的垂直方向):
temp++向右,因为竖屏Y映射到物理X的正向)
参数校验 校验 x 是否超出 lcd_width 校验 x 是否超出 user_width(即物理 lcd_height

竖屏代码没有改变 LCD 硬件,只是改变了应用层坐标到显存地址的映射方式,让应用程序可以用竖屏坐标系来操作横屏的物理屏幕。

编译 & 测试

编译:

${CC} -o lcd_vertical_display lcd_vertical_display.c

测试:

  1. 将可执行文件拷贝到开发板。
  2. 关闭 Qt GUI 应用程序。
  3. 运行 ./lcd_vertical_display,LCD 上将显示竖屏效果:四个角有红/绿/蓝/黄方块,中间有十字交叉线和嵌套矩形。
posted @ 2026-07-20 14:58  Javenwww  阅读(2)  评论(0)    收藏  举报