RISC-V技术论坛
直播中

cszzlsw

9年用户 202经验值
擅长:嵌入式技术
私信 关注
[经验]

【昉·星光 2 高性能RISC-V单板计算机体验】添加触摸驱动并添加图形界面

1、前面的话
既上一篇【昉·星光 2 高性能RISC-V单板计算机体验】用自制的转接板学习lcd驱动点亮屏幕之后,其实在鼓捣两件事,加上图形界面和触摸驱动,实现一个小小的智能夹具中控系统,不过自从10月之后,忙的不行,虽然触摸的实验做出来了,但是感觉这玩意当不了一篇帖子,索性就想等把图形界面QT一起整出来再一起发出来,结果奇怪的是在VF2上交叉编译Qt一直跑不起来,而同样的Qt版本,在算能华山派上没有问题,所以很费解,确实也花了不少时间在上面,发邮件求救官方也没得到解决方案,
image.png

最后无奈换了LVGL,好在LVGL比较顺利,没有太多曲折,以下简单记录一下折腾的过程吧。

二、编译QT
1。下载Qt源码,网址:https://download.qt.io/,找到离线安装包或文件,之前下载的5.12.12版本,现在找不到了,用5.15代替:
image.png

2。下载之后拖到开发环境里,解压出来,在qtbase/mkspecs/文件夹创建linux-riscv64-starfive2-g++文件夹,从别处复制两个文件qmake.conf和qplatfirmdefs.h文件:
image.png
image.png

此处为定义交叉编译链,
3.然后在根目录创建compile.sh文件,填入内容:

#!/bin/sh
./configure
-prefix $PWD/qtbase
-confirm-license
-opensource
-release
-make libs
-xplatform linux-riscv64-starfive2-g++
-pch
-widgets
-dbus-runtime
--pcre=qt
--zlib=qt
--freetype=qt
--harfbuzz=qt
-linuxfb
-qt-libjpeg
-qt-libpng
-qt-zlib
-no-sse2
-no-openssl
-no-opengl
-no-cups
-no-glib
-no-dbus
-no-xcb
-no-separate-debug-info
-c++std c++11
-L/usr/riscv64-linux-gnu/lib/

添加执行权限chmod a+x ./compile.sh然后可以执行./compile.sh启动配置,出现下图所示:image.png
然后可以执行gmake -j 12,-j后面表示使用多少线程参与编译,越大越快.
编译过程举例:
image.png

在编译的过程中可能会出现一些问题:
1)没有atomic库,如下:
image.png

参考解决方案:
image.png

2)limits错误:
image.png
参考解决方案如下:
image.png

其他的问题可能没碰到也无无从知晓了

三.编译完成

编译完成之后如图:
image.png

会在qtbase/lib出现库:
image.png
把这些库拷贝到开发板上的/home/user文件夹内,我用的scp方式:
image.png

image.png

然后是plugins的动态库:
image.png
image.png

image.png

然后是声明一些与qt相关的变量,可以直接写在~/.bashrc里:

export LD_LIBRARY_PATH=/usr/local/lib
export GST_PLUGIN_PATH=/usr/local/lib/gstreamer-1.0


export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0:size=320x480:mmSize=320x480:offset=0x0:tty=/dev/ttyS0
export QT_QPA_FONTDIR=/home/user/lib/fonts
export QT_QPA_FB_FORCE_FULLSCREEN=1 

export QT_ONSCREEN_PAINT=1

image.png
接着编译一个案例,比如calculate:
image.png

直接make:
image.png

image.png

然后同样使用scp传输到开发板:
image.png

然后在开发板上运行就会出问题:
image.png

就是这个问题 抓破脑袋也想不出来原因,最后无奈放弃了.

四.改用lvgl实现图形界面
到底qt放弃了,了解到就图形界面来说LVGL也可以到达效果,所以最后采用LVGL实现了,整体参考的韦老师的帖子,很顺利,而且这次是在板子上编译的,因为使用libinput的话会需要几个依赖库,拷起来太费劲了,需要在板子上安装libinput-dev:apt-get install libinput-dev
然后大体步骤就和韦老师一致了,帖子地址:https://mp.weixin.qq.com/s?__biz=MzAxNTAyOTczMw==&mid=2649344034&idx=1&sn=6782bcb407e8dad790b1931176c9f4b6&chksm=83972ca8b4e0a5bef0ba61cd4370a2471b4b3d794ef2dd144b3692d32c9f78d7a6b7c5c24c5b&scene=27,大家直接参考就行,此处不就赘述了,改了几个地方而已,把libevdev改成了libinput,然后源码里面也有小小改动:
image.png

然后编译运行就可以显示界面了:
image.png

b2ff059cf3b512ffd6ffd9d15421abd.jpg

五.添加触摸驱动
到上步为止图形界面是搞定了,但是缺少触摸驱动,我用的芯片的ft6236,需要在设备树添加对应描述:
image.png

使用了一个中断引脚和reset引脚,同时x,y轴需要反转一下,
然后就是修改驱动文件,这里比较友好的是已经有现成的驱动文件了
image.png
但是由于linux内核版本升级,原先的驱动并不适用了,典型的就是中断的写法变了,主要体现在这里,因为不清楚内核的机制,这里卡住了2天左右吧,难受:

static int edt_ft5x06_ts_probe(struct i2c_client *client,
					 const struct i2c_device_id *id)
{
	const struct edt_i2c_chip_data *chip_data;
	struct edt_ft5x06_ts_data *tsdata;
	u8 buf[2] = { 0xfc, 0x00 };
	struct input_dev *input;
	unsigned long irq_flags;
	int error;
	char fw_version[EDT_NAME_LEN];

	dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n");

	tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
	if (!tsdata) {
		dev_err(&client->dev, "failed to allocate driver data.\n");
		return -ENOMEM;
	}

	chip_data = device_get_match_data(&client->dev);
	if (!chip_data)
		chip_data = (const struct edt_i2c_chip_data *)id->driver_data;
	if (!chip_data || !chip_data->max_support_points) {
		dev_err(&client->dev, "invalid or missing chip data\n");
		return -EINVAL;
	}

	tsdata->max_support_points = chip_data->max_support_points;

	tsdata->vcc = devm_regulator_get(&client->dev, "vcc");
	
	if (IS_ERR(tsdata->vcc)) {
		error = PTR_ERR(tsdata->vcc);
		if (error != -EPROBE_DEFER)
			dev_err(&client->dev,
				"failed to request regulator: %d\n", error);
		return error;
	}

	tsdata->iovcc = devm_regulator_get(&client->dev, "iovcc");
	if (IS_ERR(tsdata->iovcc)) {
		error = PTR_ERR(tsdata->iovcc);
		if (error != -EPROBE_DEFER)
			dev_err(&client->dev,
				"failed to request iovcc regulator: %d\n", error);
		return error;
	}

	error = regulator_enable(tsdata->iovcc);

	if (error < 0) {
		dev_err(&client->dev, "failed to enable iovcc: %d\n", error);
		return error;
	}

	/* Delay enabling VCC for > 10us (T_ivd) after IOVCC */
	usleep_range(10, 100);

	error = regulator_enable(tsdata->vcc);

	if (error < 0) {
		dev_err(&client->dev, "failed to enable vcc: %d\n", error);
		regulator_disable(tsdata->iovcc);
		return error;
	}

	error = devm_add_action_or_reset(&client->dev,
					 edt_ft5x06_disable_regulators,
					 tsdata);
	printk(">>%s:%d _ %s()\n",__FILE__,__LINE__, __func__);
	if (error)
		return error;

	error = request_one_gpio(&client->dev, "reset-gpios", 0, &tsdata->reset_gpio);

	// tsdata->reset_gpio = of_get_named_gpio_flags(&client->dev.of_node,"reset-gpios", 0);
	
	if (IS_ERR(tsdata->reset_gpio)) {
		error = PTR_ERR(tsdata->reset_gpio);
		dev_err(&client->dev,
			"Failed to request GPIO reset pin, error %d\n", error);
		return error;
	}
	error = request_one_gpio(&client->dev, "wakeup-gpios", 0, &tsdata->wake_gpio);
	// tsdata->wake_gpio = of_get_named_gpio_flags(&client->dev.of_node,"wakeup-gpios", 0);
							
	if (IS_ERR(tsdata->wake_gpio)) {
		error = PTR_ERR(tsdata->wake_gpio);
		dev_err(&client->dev,
			"Failed to request GPIO wake pin, error %d\n", error);
		// return error;
	}

	error = request_one_gpio(&client->dev, "interrupt-gpios", 0, &tsdata->irq_pin);
	// tsdata->irq_pin = of_get_named_gpio_flags(&client->dev.of_node,"interrupt-gpios", 0);
	
	if (IS_ERR(tsdata->irq_pin)) {
		error = PTR_ERR(tsdata->irq_pin);
		dev_err(&client->dev,
			"Failed to request GPIO irq pin, error %d\n", error);
		return error;
	}
	
	gpiod_direction_input(tsdata->irq_pin);

	/*
	 * Check which sleep modes we can support. Power-off requieres the
	 * reset-pin to ensure correct power-down/power-up behaviour. Start with
	 * the EDT_PMODE_POWEROFF test since this is the deepest possible sleep
	 * mode.
	 */
	if (tsdata->reset_gpio)
		tsdata->suspend_mode = EDT_PMODE_POWEROFF;
	else if (tsdata->wake_gpio)
		tsdata->suspend_mode = EDT_PMODE_HIBERNATE;
	else
		tsdata->suspend_mode = EDT_PMODE_NOT_SUPPORTED;

	if (tsdata->wake_gpio) {
		usleep_range(5000, 6000);
		gpiod_set_value_cansleep(tsdata->wake_gpio, 1);
	}
	printk(">>%s:%d _ %s() %p %d \n",__FILE__,__LINE__, __func__,tsdata->reset_gpio ,tsdata->suspend_mode );
	if (tsdata->reset_gpio) {
		usleep_range(5000, 6000);
		printk(">>%s:%d _ %s() reset gpio set sleep\n",__FILE__,__LINE__, __func__);
		gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
		msleep(300);
	}

	input = devm_input_allocate_device(&client->dev);

	if (!input) {
		dev_err(&client->dev, "failed to allocate input device.\n");
		return -ENOMEM;
	}

	mutex_init(&tsdata->mutex);
	tsdata->client = client;
	tsdata->input = input;
	tsdata->factory_mode = false;

	error = edt_ft5x06_ts_identify(client, tsdata, fw_version);
	printk(">>%s:%d _ %s() %d\n",__FILE__,__LINE__, __func__,error);
	if (error) {
		dev_err(&client->dev, "touchscreen probe failed\n");
		return error;
	}

	/*
	 * Dummy read access. EP0700MLP1 returns bogus data on the first
	 * register read access and ignores writes.
	 */
	edt_ft5x06_ts_readwrite(tsdata->client, 2, buf, 2, buf);

	edt_ft5x06_ts_set_regs(tsdata);
	edt_ft5x06_ts_get_defaults(&client->dev, tsdata);
	edt_ft5x06_ts_get_parameters(tsdata);

	printk(">>%s:%d _ %s() x:%d y:%d\n",__FILE__,__LINE__, __func__,tsdata->num_x, tsdata->num_y);

	dev_dbg(&client->dev,
		"Model \"%s\", Rev. \"%s\", %dx%d sensors\n",
		tsdata->name, fw_version, tsdata->num_x, tsdata->num_y);

	input->name = tsdata->name;
	input->id.bustype = BUS_I2C;
	input->dev.parent = &client->dev;

	__set_bit(EV_ABS,input->evbit);
	__set_bit(BTN_TOUCH,input->keybit);

	input_set_abs_params(input, ABS_X,
			     0, 320, 0, 0);
	input_set_abs_params(input, ABS_Y,
			     0, 480, 0, 0);
	input_set_abs_params(input, ABS_MT_POSITION_X,
			     0, 320, 0, 0);
	input_set_abs_params(input, ABS_MT_POSITION_Y,
			     0, 480, 0, 0);
				 
		edt_ft5x06_ts_get_parameters(tsdata);

	printk(">>2%s:%d _ %s() x:%d y:%d\n",__FILE__,__LINE__, __func__,tsdata->num_x, tsdata->num_y);
	touchscreen_parse_properties(input, true, &tsdata->prop);

	printk(">>%s:%d _ %s() ypoints:%d\n",__FILE__,__LINE__, 
		__func__,tsdata->max_support_points );
	error = input_mt_init_slots(input, tsdata->max_support_points,
				INPUT_MT_DIRECT);

	if (error) {
		dev_err(&client->dev, "Unable to init MT slots.\n");
		return error;
	}

	input_set_drvdata(input , tsdata);
	i2c_set_clientdata(client, tsdata);


	error = devm_request_threaded_irq(&client->dev,gpiod_to_irq(tsdata->irq_pin),// client->irq,
					NULL, edt_ft5x06_ts_isr, IRQF_TRIGGER_FALLING |IRQF_ONESHOT,
					client->name, tsdata);
	
	if (error) {
		dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
		return error;
	}

	error = devm_device_add_group(&client->dev, &edt_ft5x06_attr_group);
					
	if (error)
		return error;

	error = input_register_device(input);
	
	if (error)
		return error;

	edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev));
	printk(">>%s:%d _ %s() %d\n",__FILE__,__LINE__, __func__,error);
	device_init_wakeup(&client->dev , 1);

	dev_dbg(&client->dev,
		"EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d. IRQ_Pin %d\n",
		client->irq,
		tsdata->wake_gpio ? desc_to_gpio(tsdata->wake_gpio) : -1,
		tsdata->reset_gpio ? desc_to_gpio(tsdata->reset_gpio) : -1,
		tsdata->irq_pin ? desc_to_gpio(tsdata->irq_pin) : -1);

		// printk(">>%s:%d _ %s() %d %d %d %d\n",__FILE__,__LINE__, __func__,
		// client->irq,
		// desc_to_gpio(tsdata->wake_gpio),
		// desc_to_gpio(tsdata->reset_gpio),
		// desc_to_gpio(tsdata->irq_pin));

	error = edt_ft5x06_register_write(tsdata, FT_DEVIDE_MODE, 0x00);
	if (error) {
		dev_dbg(&client->dev,
			"failed to write FT_DEVIDE_MODE register, error %d\n", error);
		return error;
	}

	error = edt_ft5x06_register_write(tsdata, FT_ID_G_THGROUP,12);
	if (error) {
		dev_dbg(&client->dev,
			"failed to write FT_ID_G_THGROUP register, error %d\n", error);
		return error;
	}

	error = edt_ft5x06_register_write(tsdata, FT_ID_G_PERIODACTIVE, 12);
	if (error) {
		dev_dbg(&client->dev,
			"failed to write FT_ID_G_PERIODACTIVE register, error %d\n", error);
		return error;
	}

	return 0;
}

主要为以下两个大坑:

image.png

image.png

解决完之后,更新设备树可以看到出现/dev/input/event0设备:
image.png

使用cat /dev/input/event0指令然后可以读到有输入
在网上找一个测试event0的代码也能测试:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <linux/input.h>	// 输入子系统头文件

int ts_fd;	//  触摸屏文件描述符全局变量

// 获取当前点击坐标
void get_xy(int *x, int *y);

int main()
{
	// 1.打开触摸屏文件
	ts_fd = open("/dev/input/event0", O_RDONLY);
	if(ts_fd == -1)
	{
		perror("open ts failed");
		return -1;
	}

	// 2.读取坐标(产生阻塞,等待用户点击)
	int pos_x, pos_y;
	while(1)
	{
		// 黑色底板屏幕,触摸屏坐标范围是(0~1024,  0~600),可通过计算进行缩小
		get_xy(&pos_x, &pos_y);
		printf("(%d, %d)\n", pos_x, pos_y);
	}
	
	// 3.关闭触摸屏
	close(ts_fd);

	return 0;
}

// 获取当前点击坐标
void get_xy(int *x, int *y)
{
	int x_ready=0, y_ready=0;
	struct input_event ts_buf;
	while(1)
	{
		read(ts_fd, &ts_buf, sizeof(ts_buf));
		// printf("type:0x%x code:0x%x value:%d\n", ts_buf.type, ts_buf.code, ts_buf.value);
		// if(ts_buf.type==0x3 && ts_buf.code==0x0)
		if(ts_buf.type==EV_ABS && ts_buf.code==ABS_X)
		{
			*x = ts_buf.value;
			x_ready = 1;
			y_ready = 0;	// 确保x坐标获取在前
		}
		else if(ts_buf.type==EV_ABS && ts_buf.code==ABS_Y)
		{
			*y = ts_buf.value;
			y_ready = 1;
		}

		if(x_ready==1 && y_ready==1)
			break;
	}
}

,现象如下:
image.png
然后就可以使用lvgl的demo实现触摸的读取并播放歌曲了,可以看到歌曲进度条动了:
3be7985301a6ee11dbd1403d43097c3.jpg

六.总结
虽然不甘心qt不能用只能用lvgl,但是好歹图形界面加触摸是出来了,这其中也学到了不少的东西,还是比较感谢这次经历的,下一篇收官篇,就用图形界面搞点事情吧,敬请期待.

回帖(3)

jf_44860206

2023-11-20 18:24:12
感谢大佬分享,不容易呀
举报

大懒猫54

2023-11-24 08:45:11
是linux下的开发吗?能否在windows下获得同样的功能?
1 举报

大懒猫54

2023-11-27 12:22:24
我看你开发的环境,应该是在linux做的吧?在windows下,需要配置什么样的环境(开发IDE和驱动之类的),才能进这样的开发?
1 举报
  • cszzlsw: 使用win10及以上系统,安装WSL2即可获得一个可以使用命令行的linux系统,如果喜欢图形界面也可以安装VMware或者VirtualBox然后安装Ubuntu系统来进行开发的

更多回帖

发帖
×
20
完善资料,
赚取积分