参考
BD
注意事项:
Vivado 自动生成了两个未使用的片选信号。请在 system_wrapper.v 文件中删除与这两个片选相关的代码,以避免综合报错。
未使用的片选信号包括:
bash
output SPI_0_0_ss1_o;
output SPI_0_0_ss2_o;
PIN.xdc
bash
## SPI SCLK
set_property PACKAGE_PIN M15 [get_ports SPI_0_0_sck_io]
set_property IOSTANDARD LVCMOS33 [get_ports SPI_0_0_sck_io]
## SPI MOSI
set_property PACKAGE_PIN L16 [get_ports SPI_0_0_io0_io]
set_property IOSTANDARD LVCMOS33 [get_ports SPI_0_0_io0_io]
## SPI MISO
set_property PACKAGE_PIN K14 [get_ports SPI_0_0_io1_io]
set_property IOSTANDARD LVCMOS33 [get_ports SPI_0_0_io1_io]
## SPI CS
set_property PACKAGE_PIN J14 [get_ports SPI_0_0_ss_io]
set_property IOSTANDARD LVCMOS33 [get_ports SPI_0_0_ss_io]
## DC
set_property PACKAGE_PIN N20 [get_ports {GPIO_EMIO_tri_io[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_EMIO_tri_io[0]}]
## RES
set_property PACKAGE_PIN U19 [get_ports {GPIO_EMIO_tri_io[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_EMIO_tri_io[1]}]
裸机驱动
main.c
c
#include "xparameters.h"
#include "xspips.h"
#include "xgpiops.h"
#include "sleep.h"
#include "xil_printf.h"
/*=========================================================
* 硬件配置区域
*---------------------------------------------------------
* 这里定义的是 ZYNQ PS 侧外设资源:
* - SPI 使用 PS SPI0
* - GPIO 使用 EMIO(从 PL 引出来的 IO)
*========================================================*/
#define SPI_DEVICE_ID XPAR_XSPIPS_0_DEVICE_ID
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
/*=========================================================
* EMIO 引脚映射
*---------------------------------------------------------
* PIN_DC :数据/命令选择信号(DC)
* PIN_RES :LCD 复位信号(RESET)
*
* 注意:
* 这两个引脚来自 PL → PS EMIO 映射
* 对应 Vivado 里 GPIO_EMIO_tri_io[x]
*========================================================*/
#define PIN_DC 54 // EMIO[0]
#define PIN_RES 55 // EMIO[1]
/*=========================================================
* LCD 逻辑分辨率与偏移
*---------------------------------------------------------
* LCD_W / LCD_H :逻辑可用显示区域
* LCD_XOFF / YOFF:ST7735 内部显存偏移(非常关键)
*
* 原因:
* ST7735 实际 GRAM 比显示区域大,需要 offset 对齐
*========================================================*/
#define LCD_W 80
#define LCD_H 160
#define LCD_XOFF 26
#define LCD_YOFF 1
/*=========================================================
* 全局外设句柄
*---------------------------------------------------------
* Spi :SPI 控制器实例
* Gpio :EMIO GPIO 控制器实例
*========================================================*/
static XSpiPs Spi;
static XGpioPs Gpio;
/*=========================================================
* GPIO 控制函数(DC / RES)
*---------------------------------------------------------
* DC = 0 -> 命令模式
* DC = 1 -> 数据模式
*
* RES = 0 -> LCD 复位
* RES = 1 -> LCD 正常工作
*========================================================*/
static inline void dc_cmd() { XGpioPs_WritePin(&Gpio, PIN_DC, 0); }
static inline void dc_data() { XGpioPs_WritePin(&Gpio, PIN_DC, 1); }
static inline void res_low() { XGpioPs_WritePin(&Gpio, PIN_RES, 0); }
static inline void res_high() { XGpioPs_WritePin(&Gpio, PIN_RES, 1); }
/*=========================================================
* SPI 底层发送函数(阻塞模式)
*---------------------------------------------------------
* 使用 PolledTransfer:
* - CPU 阻塞等待发送完成
* - 适合 LCD 这种低速外设
*========================================================*/
static inline void spi_write(u8 *buf, int len)
{
XSpiPs_PolledTransfer(&Spi, buf, NULL, len);
}
/*=========================================================
* LCD 写命令
*---------------------------------------------------------
* 流程:
* 1. DC = 0(命令)
* 2. SPI 发送 1 byte
*========================================================*/
static void lcd_cmd(u8 cmd)
{
dc_cmd();
spi_write(&cmd, 1);
}
/*=========================================================
* LCD 写数据
*---------------------------------------------------------
* 流程:
* 1. DC = 1(数据)
* 2. SPI 发送 1 byte
*========================================================*/
static void lcd_data(u8 data)
{
dc_data();
spi_write(&data, 1);
}
/*=========================================================
* LCD 硬件复位
*---------------------------------------------------------
* 时序要求:
* RES 拉低 ≥10ms
* RES 拉高后等待 ≥120ms
*
* 这是 ST7735 启动稳定关键步骤
*========================================================*/
static void lcd_reset()
{
res_low();
usleep(20000); // 保证复位时间充足
res_high();
usleep(120000); // 等待内部振荡器稳定
}
/*=========================================================
* 设置显示窗口(核心函数)
*---------------------------------------------------------
* 功能:
* 指定 LCD GRAM 写入区域
*
* ST7735 使用 3 个命令:
* 0x2A -> 列地址(X)
* 0x2B -> 行地址(Y)
* 0x2C -> 写显存开始
*
* 注意:
* 必须加 LCD_XOFF / LCD_YOFF,否则图像错位
*========================================================*/
static void lcd_set_addr(u16 x0, u16 y0, u16 x1, u16 y1)
{
/* 加偏移(硬件坐标修正) */
x0 += LCD_XOFF;
x1 += LCD_XOFF;
y0 += LCD_YOFF;
y1 += LCD_YOFF;
/*========================
* 设置列地址
*========================*/
lcd_cmd(0x2A);
dc_data();
u8 col[] = {0, x0, 0, x1};
spi_write(col, 4);
/*========================
* 设置行地址
*========================*/
lcd_cmd(0x2B);
dc_data();
u8 row[] = {0, y0, 0, y1};
spi_write(row, 4);
/*========================
* 写入 GRAM
*========================*/
lcd_cmd(0x2C);
}
/*=========================================================
* LCD 初始化流程
*---------------------------------------------------------
* 初始化步骤(必须严格顺序):
* 1. 复位
* 2. 退出睡眠
* 3. 开启 IPS 模式
* 4. 帧率配置
* 5. 电源控制
* 6. 像素格式
* 7. 显示方向
* 8. 开显示
*========================================================*/
static void lcd_init()
{
lcd_reset();
lcd_cmd(0x11); // Sleep Out(退出睡眠)
usleep(120000); // 必须等待内部稳定
lcd_cmd(0x21); // IPS 显示必须开启(反色控制)
/*========================
* 帧率控制
*========================*/
lcd_cmd(0xB1); lcd_data(0x05); lcd_data(0x3A); lcd_data(0x3A);
lcd_cmd(0xB2); lcd_data(0x05); lcd_data(0x3A); lcd_data(0x3A);
lcd_cmd(0xB4); lcd_data(0x03); // 反转控制
/*========================
* 电源控制
*========================*/
lcd_cmd(0xC0); lcd_data(0x62); lcd_data(0x02); lcd_data(0x04);
lcd_cmd(0xC1); lcd_data(0xC0);
lcd_cmd(0xC2); lcd_data(0x0D); lcd_data(0x00);
lcd_cmd(0xC5); lcd_data(0x0E);
/*========================
* 像素格式 + 显示方向
*========================*/
lcd_cmd(0x3A); lcd_data(0x05); // RGB565
lcd_cmd(0x36); lcd_data(0xC8); // 扫描方向控制
lcd_cmd(0x29); // Display ON(开启显示)
}
/*=========================================================
* 全屏填充颜色
*---------------------------------------------------------
* 本质:
* - 设置整个窗口
* - 连续写 LCD_W * LCD_H 个像素
*
* 注意:
* RGB565 = 2 byte / pixel
*========================================================*/
static void lcd_clear(u16 color)
{
lcd_set_addr(0, 0, LCD_W-1, LCD_H-1);
dc_data(); // 进入数据模式
u8 hi = color >> 8;
u8 lo = color & 0xFF;
for (int i = 0; i < LCD_W * LCD_H; i++) {
u8 d[2] = {hi, lo};
spi_write(d, 2);
}
}
/*=========================================================
* 画 8x8 字符 A
*---------------------------------------------------------
* A 使用点阵:
* 每一行 8bit 表示像素开关
*
* fc = 前景色(字体颜色)
* bc = 背景色
*========================================================*/
static void lcd_draw_A(u16 x, u16 y, u16 fc, u16 bc)
{
u8 A[8] = {
0x18,
0x24,
0x42,
0x7E,
0x42,
0x42,
0x42,
0x00
};
lcd_set_addr(x, y, x + 7, y + 7);
dc_data(); // 进入数据模式
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
u16 color;
/* 判断当前像素是否属于字体笔画 */
if (A[row] & (1 << (7 - col))) {
color = fc; // 字体颜色
} else {
color = bc; // 背景颜色
}
u8 d[2] = { color >> 8, color & 0xFF };
spi_write(d, 2);
}
}
}
/*=========================================================
* 主函数
*========================================================*/
int main()
{
xil_printf("ST7735 ZYNQ OK\r\n");
/*========================
* SPI 初始化
*========================*/
XSpiPs_Config *cfg;
cfg = XSpiPs_LookupConfig(SPI_DEVICE_ID);
XSpiPs_CfgInitialize(&Spi, cfg, cfg->BaseAddress);
XSpiPs_SetOptions(&Spi,
XSPIPS_MASTER_OPTION | // 主机模式
XSPIPS_FORCE_SSELECT_OPTION // 强制片选控制
);
XSpiPs_SetClkPrescaler(&Spi, XSPIPS_CLK_PRESCALE_16);
/*========================
* EMIO GPIO 初始化
*========================*/
XGpioPs_Config *gcfg;
gcfg = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
XGpioPs_CfgInitialize(&Gpio, gcfg, gcfg->BaseAddr);
/* DC 输出配置 */
XGpioPs_SetDirectionPin(&Gpio, PIN_DC, 1);
XGpioPs_SetOutputEnablePin(&Gpio, PIN_DC, 1);
/* RES 输出配置 */
XGpioPs_SetDirectionPin(&Gpio, PIN_RES, 1);
XGpioPs_SetOutputEnablePin(&Gpio, PIN_RES, 1);
/*========================
* LCD 初始化
*========================*/
lcd_init();
/*========================
* 主循环演示
*========================*/
while (1) {
lcd_clear(0xF800); // 红色
usleep(500000);
lcd_clear(0x07E0); // 绿色
usleep(500000);
lcd_clear(0x001F); // 蓝色
usleep(500000);
/* 显示字母 A */
lcd_draw_A(10, 20, 0xFFFF, 0x0000);
usleep(500000);
}
}
测试
全红,全黄, 全蓝, 字母A 之间循环播放
bash
[10:22:25.989]收←◆=== ST7735 OK TEST ===
[10:22:26.304]收←◆DONE
[10:23:46.980]收←◆ST7735 ZYNQ OK
linux驱动
BD
与裸机共用,但把sd,emmc ,enet0 打开 linux_spi_oled_system.tcl
内核配置确认
bash
$:grep -i SPI .config | grep -v "^#"
CONFIG_UNINLINE_SPIN_UNLOCK=y # ❌ 非必须(内核锁优化)
CONFIG_MUTEX_SPIN_ON_OWNER=y # ❌ 非必须(锁优化)
CONFIG_RWSEM_SPIN_ON_OWNER=y # ❌ 非必须(锁优化)
CONFIG_LOCK_SPIN_ON_OWNER=y # ❌ 非必须(锁优化)
CONFIG_REGMAP_SPI=y # ❌ 非必须(SPI寄存器抽象框架,驱动用)
CONFIG_MTD_SPI_NOR=y # ❌ 非必须(SPI Flash / W25Qxx)
CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=y # ❌ 非必须(Flash性能优化)
CONFIG_SPI=y # ✔ 必须(SPI核心框架)
CONFIG_SPI_MASTER=y # ✔ 必须(SPI主机模式)
CONFIG_SPI_MEM=y # ❌ 非必须(SPI Flash/QSPI memory接口)
CONFIG_SPI_BITBANG=y # ❌ 非必须(GPIO模拟SPI,不用)
CONFIG_SPI_CADENCE=y # ✔ 必须(Zynq PS SPI底层驱动)
CONFIG_SPI_XILINX=y # ✔ 必须(Xilinx SPI控制器支持)
CONFIG_SPI_ZYNQ_QSPI=y # ❌ 非必须(QSPI Flash启动用)
CONFIG_RTC_I2C_AND_SPI=y # ❌ 非必须(RTC相关)
bash
$:grep -i FB .config | grep -v "^#"
CONFIG_DRM_KMS_FB_HELPER=y # ❌ 非必须(DRM转fb兼容层,用于老程序/X11)
CONFIG_DRM_FBDEV_EMULATION=y # ❌ 非必须(DRM自动生成 /dev/fb0)
CONFIG_DRM_FBDEV_OVERALLOC=100 # ❌ 非必须(fb buffer额外预分配,性能参数)
CONFIG_FB_CMDLINE=y # ❌ 非必须(允许bootargs配置fb参数)
CONFIG_FB_NOTIFY=y # ❌ 非必须(fb事件通知机制)
CONFIG_FB=y # ✔ 必须(Framebuffer核心框架)
CONFIG_FB_CFB_FILLRECT=y # ✔ 推荐(矩形填充加速)
CONFIG_FB_CFB_COPYAREA=y # ✔ 推荐(显存区域拷贝)
CONFIG_FB_CFB_IMAGEBLIT=y # ✔ 推荐(图像/字符绘制)
CONFIG_FB_SYS_FILLRECT=y # ✔ 建议(软件fb基础实现)
CONFIG_FB_SYS_COPYAREA=y # ✔ 建议
CONFIG_FB_SYS_IMAGEBLIT=y # ✔ 建议
CONFIG_FB_SYS_FOPS=y # ✔ 必须(/dev/fb0 用户接口)
CONFIG_FB_DEFERRED_IO=y # ⭐ 强烈推荐(SPI LCD刷屏优化,减少卡顿)
设备树 system-user.dtsi
bash
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/media/xilinx-vip.h>
#include <dt-bindings/phy/phy.h>
/ {
model = "ant Board111";
compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000";
/* =========================================================
* LED
* ========================================================= */
leds {
compatible = "gpio-leds";
gpio-led1 {
label = "led2";
gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
gpio-led5 {
label = "ps_led0";
gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
gpio-led6 {
label = "ps_led1";
gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "timer";
};
};
/* =========================================================
* KEY
* ========================================================= */
keys {
compatible = "gpio-keys";
autorepeat;
gpio-key3 {
label = "ps_key1";
gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
linux,code = <KEY_UP>;
};
gpio-key4 {
label = "ps_key2";
gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
linux,code = <KEY_DOWN>;
};
};
};
/* =========================================================
* UART / SD / QSPI
* ========================================================= */
&uart0 {
u-boot,dm-pre-reloc;
status = "okay";
};
&sdhci0 {
u-boot,dm-pre-reloc;
status = "okay";
};
&qspi {
u-boot,dm-pre-reloc;
flash@0 {
compatible = "w25q256", "jedec,spi-nor";
reg = <0x0>;
spi-max-frequency = <50000000>;
partition@0 {
label = "boot";
reg = <0x00000000 0x00100000>;
};
partition@1 {
label = "bootenv";
reg = <0x00100000 0x00020000>;
};
partition@2 {
label = "kernel";
reg = <0x00540000 0x00500000>;
};
};
};
&gem0 {
local-mac-address = [00 0a 35 00 8b 87];
ethernet_phy: ethernet-phy@7 {
reg = <0x7>;
};
};
/* =========================================================
* SPI 控制器
* ========================================================= */
&spi1 {
status = "disabled";
};
&spi0 {
status = "okay";
num-cs = <1>;
is-decoded-cs;
st7735@0 {
compatible = "my,st7735s";
reg = <0>;
spi-max-frequency = <20000000>;
/* SPI mode 0 */
spi-cpol = <0>;
spi-cpha = <0>;
/* GPIO控制 */
reset-gpios = <&gpio0 55 GPIO_ACTIVE_LOW>;
dc-gpios = <&gpio0 54 GPIO_ACTIVE_HIGH>;
/* LCD参数 */
width = <80>;
height = <160>;
rotate = <90>;
invert = <1>;
status = "okay";
};
};
ST7735S LCD 驱动 (未使用设备树)
my_st7735.c
c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/of.h>
#define LCD_W 80
#define LCD_H 160
#define LCD_XOFF 26
#define LCD_YOFF 1
struct st7735 {
struct spi_device *spi;
struct gpio_desc *dc;
struct gpio_desc *reset;
struct delayed_work dwork; // 定时切换
};
static int lcd_spi(struct st7735 *ctx, u8 *buf, int len)
{
return spi_write_then_read(ctx->spi, buf, len, NULL, 0);
}
static void dc_cmd(struct st7735 *ctx) { gpiod_set_value(ctx->dc, 0); }
static void dc_data(struct st7735 *ctx) { gpiod_set_value(ctx->dc, 1); }
static void lcd_reset(struct st7735 *ctx)
{
gpiod_set_raw_value(ctx->reset, 0);
msleep(20);
gpiod_set_raw_value(ctx->reset, 1);
msleep(120);
}
static void lcd_cmd(struct st7735 *ctx, u8 cmd)
{
dc_cmd(ctx);
lcd_spi(ctx, &cmd, 1);
}
static void lcd_data(struct st7735 *ctx, u8 data)
{
dc_data(ctx);
lcd_spi(ctx, &data, 1);
}
static void lcd_set_addr(struct st7735 *ctx, u16 x0, u16 y0, u16 x1, u16 y1)
{
x0 += LCD_XOFF;
x1 += LCD_XOFF;
y0 += LCD_YOFF;
y1 += LCD_YOFF;
lcd_cmd(ctx, 0x2A);
dc_data(ctx);
u8 col[4] = {0, x0, 0, x1};
lcd_spi(ctx, col, 4);
lcd_cmd(ctx, 0x2B);
dc_data(ctx);
u8 row[4] = {0, y0, 0, y1};
lcd_spi(ctx, row, 4);
lcd_cmd(ctx, 0x2C);
}
static void lcd_init(struct st7735 *ctx)
{
lcd_reset(ctx);
lcd_cmd(ctx, 0x11); msleep(120);
lcd_cmd(ctx, 0x21);
lcd_cmd(ctx, 0xB1); lcd_data(ctx, 0x05); lcd_data(ctx, 0x3A); lcd_data(ctx, 0x3A);
lcd_cmd(ctx, 0xB2); lcd_data(ctx, 0x05); lcd_data(ctx, 0x3A); lcd_data(ctx, 0x3A);
lcd_cmd(ctx, 0xB4); lcd_data(ctx, 0x03);
lcd_cmd(ctx, 0xC0); lcd_data(ctx, 0x62); lcd_data(ctx, 0x02); lcd_data(ctx, 0x04);
lcd_cmd(ctx, 0xC1); lcd_data(ctx, 0xC0);
lcd_cmd(ctx, 0xC2); lcd_data(ctx, 0x0D); lcd_data(ctx, 0x00);
lcd_cmd(ctx, 0xC5); lcd_data(ctx, 0x0E);
lcd_cmd(ctx, 0x3A); lcd_data(ctx, 0x05);
lcd_cmd(ctx, 0x36); lcd_data(ctx, 0xC8);
lcd_cmd(ctx, 0x29);
}
// ==========================
// 颜色填充函数
// ==========================
static void lcd_fill(struct st7735 *ctx, u16 color)
{
lcd_set_addr(ctx, 0, 0, LCD_W-1, LCD_H-1);
dc_data(ctx);
u8 c[2] = {color >> 8, color & 0xFF};
int i;
for (i = 0; i < LCD_W * LCD_H; i++)
lcd_spi(ctx, c, 2);
}
// 颜色定义
#define RED 0xF800
#define BLUE 0x001F
#define YELLOW 0xFFE0
static int color_idx = 0;
// 定时切换颜色
static void lcd_color_work(struct work_struct *work)
{
struct st7735 *ctx = container_of(to_delayed_work(work), struct st7735, dwork);
u16 color;
if (color_idx == 0) color = RED;
else if (color_idx == 1) color = BLUE;
else color = YELLOW;
lcd_fill(ctx, color);
color_idx = (color_idx + 1) % 3;
// 500ms 切换一次
schedule_delayed_work(&ctx->dwork, msecs_to_jiffies(500));
}
static int st7735_probe(struct spi_device *spi)
{
struct st7735 *ctx;
// 完全使用你稳定的配置
spi->mode = SPI_MODE_0 | SPI_CS_HIGH;
spi->max_speed_hz = 4000000;
spi_setup(spi);
ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
ctx->spi = spi;
ctx->dc = devm_gpiod_get(&spi->dev, "dc", GPIOD_OUT_LOW);
ctx->reset = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
spi_set_drvdata(spi, ctx);
lcd_init(ctx);
// 启动颜色切换
INIT_DELAYED_WORK(&ctx->dwork, lcd_color_work);
schedule_delayed_work(&ctx->dwork, 0);
dev_info(&spi->dev, "红/蓝/黄自动切换\n");
return 0;
}
static int st7735_remove(struct spi_device *spi)
{
struct st7735 *ctx = spi_get_drvdata(spi);
cancel_delayed_work_sync(&ctx->dwork);
return 0;
}
static const struct of_device_id st7735_dt_ids[] = {
{ .compatible = "my,st7735s" },
{},
};
MODULE_DEVICE_TABLE(of, st7735_dt_ids);
static struct spi_driver st7735_driver = {
.driver = {
.name = "my_st7735",
.of_match_table = st7735_dt_ids,
},
.probe = st7735_probe,
.remove = st7735_remove,
};
module_spi_driver(st7735_driver);
MODULE_LICENSE("GPL");
测试
bash
# 红蓝黄交替
root@ant:/lib/modules/5.4.0-xilinx-v2020.2# insmod my_st7735.ko
ST7735S LCD 驱动 (绑定/dev/fb0)
bash
# 查看分辨率
cat /sys/class/graphics/fb0/virtual_size
my_st7735_fb.c
c
