使用SPI子系统去点亮ST7789(1)

使用SPI子系统去点亮ST7789(1)

最近手头有一块ST7789 1.54寸的SPI屏幕,准备在泰山派(RK3566)上进行点亮,并且运行LVGL

操作流程

  • 编写正确的SPI设备树
  • 编写驱动
  • 移植LVGL相关

以上就是运行的思路,先完成设备树和驱动相关的部分

SPI设备树

rk3566.dtsi->rk3568.dtsi,从而确定最终根设备树来自rk3568.dtsi,而 rk3566.dtsi是关闭一些在rk3566上没有的东西

我们可以看看rockchip对SPI设备树的写法

spi3: spi@fe640000 {
		compatible = "rockchip,rk3066-spi";
		reg = <0x0 0xfe640000 0x0 0x1000>;
		interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
		#address-cells = <1>;
		#size-cells = <0>;
		clocks = <&cru CLK_SPI3>, <&cru PCLK_SPI3>;
		clock-names = "spiclk", "apb_pclk";
		dmas = <&dmac0 26>, <&dmac0 27>;
		dma-names = "tx", "rx";
		pinctrl-names = "default", "high_speed";
		pinctrl-0 = <&spi3m0_cs0 &spi3m0_cs1 &spi3m0_pins>;
		pinctrl-1 = <&spi3m0_cs0 &spi3m0_cs1 &spi3m0_pins_hs>;
		num-cs = <2>;
		status = "disabled";
};

官方的dts是一家确定好了对spi的io映射和cs脚的数目选择,所以我们只需要在自己的设备树文件下开启spi和使用我们所需要的io就可以了

这是我写的设备树

&spi3 {
	status = "okay";
	max-freq = <80000000>;
	dma-names = "tx","rx";
	pinctrl-names = "default", "high_speed";
	pinctrl-0 = <&spi3m1_cs0 &spi3m1_pins>;
	pinctrl-1 = <&spi3m1_cs0 &spi3m1_pins_hs>;
	spi_lcd@10 {
			device_type = "spi_lcd";
			compatible = "lcd-st7789";
			spi-max-frequency = <80000000>;
			reg = <0x00>;//cs single
			//spi-cpha;
			// spi-cpol;
			status = "okay";
	};
};

首先是开启spi的设备树节点,其次再追加时钟频率和我们所需要的io映射

然后再添加spi_lcd的子节点,用于驱动去匹配该节点

其中需要说明的是,reg属性是spi的cs信号,不同于单片机,soc上的spi往往会有多个cs来去控制多个spi设备,reg里的参数是该子节点使用哪个cs的io,我使用的是cs0,所以reg里的参数写0即可

设备树完成了,编译好后上传板子,确定在spi3的节点下有spi_lcd节点即可

SPI驱动编写

Makefile的编写参考我关于gpio子系统的章节,修改编译的文件和源码目录即可

接下来就是关于spi驱动的注册

首先是要使用 spi_register_driver,需要填入一个 struct spi_driver的结构体

然后我们再去填入 spi_driver所有需要的内容,分别是

  • probe函数
  • remove函数
  • driver结构体
  • id_table

其中非常重要的是driver当中的 of_match_table结构体,这个结构中的 compatible属性就是我们来匹配设备树节点的属性

这里不再赘述,贴上代码

驱动的init函数

/* 4.注册驱动函数 */
static int __init st7789_init(void)
{
	int ret;
	int err;
	ret = spi_register_driver(&spi_st7789);
	printk("start spi regist driver\n");
	if(ret < 0){
		printk("st7789 set up fail , ret = %d\n",ret);
		return ret;
	}
	else{
		ret = gpio_request(GPIO_ST7789_RST, "lcd-rst");
		printk("start GPIO_ST7789_RST regist driver\n");
		if(ret < 0){
			//gpio get faile
			spi_unregister_driver(&spi_st7789);
			printk("gpio get faile , ret = %d",ret);
		}
		else{
			gpio_direction_output(GPIO_ST7789_RST, 0);
			ret = gpio_request(GPIO_ST7789_DC, "lcd-dc");
			printk("start GPIO_ST7789_DC regist driver\n");
			if(ret < 0){
				//gpio get faile
				spi_unregister_driver(&spi_st7789);
				gpio_free(GPIO_ST7789_RST);
				printk("gpio get faile , ret = %d",ret);
			}
			else{
				gpio_direction_output(GPIO_ST7789_DC, 1);
				/* 创建驱动类 */
				st7789_class = class_create(THIS_MODULE, "st7789_class");
				err = PTR_ERR(st7789_class);
				if (IS_ERR(st7789_class)){
					printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
					return -1;
				}
				/* 注册驱动,led是驱动文件名称 */
				major = register_chrdev(0, "st7789", &st7789_file_operations);
				/* 设备创建 */
				device_create(st7789_class,NULL,MKDEV(major, 0), NULL, "st7789_class");
				printk("start LCD_Init\n");
				LCD_Init();
				printk("end LCD_Init\n");
			}
		}
	}
	
	
	printk("kfifo_avail=%d,kfifo_len=%d\n",kfifo_avail(&st7789_fifo),kfifo_len(&st7789_fifo));




	th = kthread_run(st7789_flush_thread , 0 , "st7789_flush_thread");
	if (IS_ERR(th)) {
		printk("setup thread fail\r\n");
		return -1;
	}
	return ret;
}

其中和kthread相关的可以先不看,是负责刷新屏幕的线程

spi_driver结构体

struct spi_driver spi_st7789 = {
	.probe = st7789_probe,
	.remove = st7789_remove,
	.driver = {
		.name = "st7789",//随便起
		.owner = THIS_MODULE,
		.of_match_table = st7789_of_match_table,
	},
	.id_table = st7789_id_table
};

probe函数

int	st7789_probe(struct spi_device *spi){
	spi_dev = spi;
	printk("st7789_probe\n");
	printk("st7789 cs = %d,max_speed_hz = %d,mode=%d\n",spi_dev->cs_gpio,spi_dev->max_speed_hz,spi_dev->mode);
	return 0;
}

remove函数

int st7789_remove(struct spi_device *spi){
	return 0;
}

spi_device_id结构体


const struct spi_device_id st7789_id_table[] = {
	{"st7789"},
	{}
};

of_device_id结构体

const struct of_device_id st7789_of_match_table[] = {
	{.compatible = "lcd-st7789"},//需要和设备节点中的名字一样
	{}//表示match_table结束
};

LCD屏幕的初始化

和spi相关的内容改好了,接下来是修改和屏幕相关的驱动,驱动的源码可以从相关的屏幕厂家得到,但是大多数都是正对单片机的,但是我们完成和spi相关的内容后,其实只需要修改和屏幕相关的spi发送接口,即可完成对应的操作

spi发送需要几个步骤

  • 1.写入发送内容
  • 2.将发送内容写入spi_message
  • 3.同步发送message

这里直接贴上我修改的驱动内容

#define LCD_W 240
#define LCD_H 240
#define USE_HORIZONTAL 0


#define DC_Set()  gpio_set_value(GPIO_ST7789_DC,1)
#define DC_Clr()  gpio_set_value(GPIO_ST7789_DC,0)
#define RST_Clr() gpio_set_value(GPIO_ST7789_RST,0)
#define RST_Set() gpio_set_value(GPIO_ST7789_RST,1)


void LCD_Writ_Bus(uint8_t dat)
{
	spi_write(spi_dev , &dat , 1);
}

void LCD_WR_DATA8(uint8_t dat)
{
    DC_Set();
    LCD_Writ_Bus(dat);
}

static inline void LCD_WR_DATA32(uint8_t* dat)
{
	int ret; 
    struct spi_message m; 
    struct spi_transfer t = { 
        .tx_buf = dat, 
        .len = 4, 
    }; 
    DC_Set();
    spi_message_init(&m);/* 初始化 spi_message */ 
    spi_message_add_tail(&t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ 
    ret = spi_sync(spi_dev, &m);/* 同步传输 */ 
}


static inline void LCD_WR_REG(uint8_t dat)
{
    int ret; 
    struct spi_message m; 
    struct spi_transfer t = { 
        .tx_buf = &dat, 
        .len = 1, 
    }; 
	printk("dc clr\n");
	DC_Clr();
    spi_message_init(&m);/* 初始化 spi_message */ 
    spi_message_add_tail(&t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ 
    ret = spi_sync(spi_dev, &m);/* 同步传输 */
	printk("sync result = %d\n",ret);
}


static inline void LCD_Address_Set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
	uint8_t x_value[4];
	uint8_t y_value[4];
	x_value[0]  = x1>>8;
	x_value[1]  = x1;
	x_value[2]  = x2>>8;
	x_value[3]  = x2;

	y_value[0]  = y1>>8;
	y_value[1]  = y1;
	y_value[2]  = y2>>8;
	y_value[3]  = y2;

	//x cmd
	LCD_WR_REG(0x2a);
	//x value
	LCD_WR_DATA32(x_value);
	//y cmd
	LCD_WR_REG(0x2b);
	//y value
	LCD_WR_DATA32(y_value);
	//end cmd
	LCD_WR_REG(0x2c);
}

到这一步,ST7789的初始化已经结束了,接下来就是修改对刷屏部分进行优化,为移植lvgl打下基础

linux中spi的发送耗费的时间大多数都是组帧和打包,写入硬件fifo,这些过程相当的花费时间,但是spi发送的过程占用的时间却很少,所以刷新屏幕的时候,我们直接写入一整个屏幕的数据,或者缓存多帧的屏幕数据一起发出去,这样效率就比较高

直接贴代码

static void LCD_Fill(uint8_t* buf , int size)
{
	unsigned long old_ns,now_ns; 
	int ret; 
	struct spi_message m;
	int queue_length;
	int i;
	int ava_len;
	struct spi_transfer* t = (struct spi_transfer*)vzalloc(sizeof(struct spi_transfer)*10);
	#define one_buf_size 0xffff
	queue_length = size/one_buf_size + 1;
	printk("queue_length=%d\n",queue_length);
	ava_len = size;
	old_ns= ktime_get();
	DC_Set();
    spi_message_init(&m);/* 初始化 spi_message */ 
	for(i = 0 ; i < queue_length ; i++)
	{
		t[i].tx_buf = &buf[i*one_buf_size];
		if(ava_len > one_buf_size) t[i].len = one_buf_size;
		else t[i].len = ava_len;
		ava_len = ava_len - one_buf_size;
		spi_message_add_tail(&t[i], &m);/* 将 spi_transfer 添加到 spi_message 队列 */
	}
	ret = spi_sync(spi_dev, &m);/* 同步传输 */ 
	now_ns = ktime_get();
	vfree(t);
	printk("tranmit size = %d , tranmit tim = %d , ret=%d\n", size , (int)(now_ns - old_ns),ret);
}

驱动部分相关的代码完成,下章将关于LVGL的移植

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值