ZYNQ7000 SDK部署LVGL

前言

很久没静下心来写博客了,学了一些新的东西但也没什么能写的出来的。对很多事情还是要一些热情的,以后会常更新博客,地址https://sunlee.top,也算是对自己小小的督促。
前两天在ZYNQ上安装了个ST7789v的LCD,顺便写一下过程。

LVGL

LVGL官网https://lvgl.io/
一个开源的图形库,主要用于MCU上屏幕UI的部署,功能完善,封装合理,可裁切性强,当然也可以实现Linux上fbx的部署。以前在很多MCU上都实现过,比较熟悉,这次主要是要试一下ZYNQ的SPI EMIO以及LCD的驱动。

Vivado

这里用Vivado v2018.3,主要是因为跟着正点原子的教程走了,ZYNQ的使用离不开Xilinx某一套的全家桶。
ZYNQ系统上分PL以及PS两部分,手上的开发板居然PS一个IO都没留出来,全留的PL段接口,给我一开始不是很熟悉FPGA的人来说可是让我走了不少弯路。
既然PS端IO没留出来还要用PS端的SPI模块只能用EMIO引出了,ZYNQ上PS端的IO叫MIO,但是也可以用内部的FPGA来引出它的IO,叫EMIO,也勉强算是功能强大。
不过查了一下UG585手册,EMIO引出的SPI接口速度只能到25M,而PS端的MIO可以达到50M,问题不大。

Vidado上主要步骤有新建工程、在Block Design里添加ZYNQ7 Processing System(PS)、配置PS端内的DDR型号,添加使能SPI EMIO。

顺便添加一下串口,使能几个普通的EMIO来驱动LED。
退出来。
因为FPGA要设置IO管脚的,叫做管脚约束,所以要在GPIO以及SPI的接口上右键Make External,引出到顶层模块上。
完事之后在source的block design上右键,先generate output products再create HDL wapper,这样整个PS端就变成了一个.v文件,这里没有别的功能的话直接进Elaborated Design,在I/O Ports里直接配置IO管脚。

最后直接Generate Bitstream,生成FPGA端的文件。
最后File>Export>Export Hardware,记得勾上include bitstream,完成PL端配置。

SDK

初始化外设

在vidado中file>launch SDK。直接打开的话应该只会有一个design_1_wapper_hw_platform,在这个基础上要新建一个BSP工程,BSP板级支持包里面包含了要用到的一些库函数,最后新建一个应用工程。后面就是裸机C语言编程的内容了。
GPIO以及LCD初始化主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
void Lcd_Gpio_Init(void){
XGpioPs_Config *ConfigPtr;

ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr);

XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_CD, 1);
XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_CD, 1);

XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_RES, 1);
XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_RES, 1);

XGpioPs_SetDirectionPin(&Gpio, LED_1, 1);
XGpioPs_SetOutputEnablePin(&Gpio, LED_1, 1);

XGpioPs_SetDirectionPin(&Gpio, LED_2, 1);
XGpioPs_SetOutputEnablePin(&Gpio, LED_2, 1);

XGpioPs_SetDirectionPin(&Gpio, LED_3, 1);
XGpioPs_SetOutputEnablePin(&Gpio, LED_3, 1);

XGpioPs_SetDirectionPin(&Gpio, LED_4, 1);
XGpioPs_SetOutputEnablePin(&Gpio, LED_4, 1);

XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 0);
XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0);

XGpioPs_WritePin(&Gpio, LED_1, 1);
XGpioPs_WritePin(&Gpio, LED_2, 1);
XGpioPs_WritePin(&Gpio, LED_3, 0);
XGpioPs_WritePin(&Gpio, LED_4, 0);
}


void Lcd_Spi_Init(){
XSpiPs_Config *SpiConfig;

SpiConfig = XSpiPs_LookupConfig(SPI_DEVICE_ID);
XSpiPs_CfgInitialize(&SpiInstance, SpiConfig,
SpiConfig->BaseAddress);

XSpiPs_SetOptions(&SpiInstance, XSPIPS_MASTER_OPTION |
XSPIPS_FORCE_SSELECT_OPTION);
XSpiPs_SetClkPrescaler(&SpiInstance, XSPIPS_CLK_PRESCALE_4);
}


void delay(unsigned char i){
volatile int Delay;
volatile int k;
for(k=0;k<i;k++)
for (Delay = 0; Delay < 10000; Delay++);
}

void LCD_WR_DATA8(u8 dat){
XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 1);
XSpiPs_PolledTransfer(&SpiInstance, &dat, NULL, 1);
}

void LCD_WR_REG(u8 dat){
XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 0);
XSpiPs_PolledTransfer(&SpiInstance, &dat, NULL, 1);
}

void Lcd_Init(void){
XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0);
delay(10);
XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 1);
delay(10);
LCD_WR_REG(0x36);
LCD_WR_DATA8(0x00);

LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);

LCD_WR_REG(0xB2);
LCD_WR_DATA8(0x0C);
LCD_WR_DATA8(0x0C);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x33);

LCD_WR_REG(0xB7);
LCD_WR_DATA8(0x35);

LCD_WR_REG(0xBB);
LCD_WR_DATA8(0x19);

LCD_WR_REG(0xC0);
LCD_WR_DATA8(0x2C);

LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x01);

LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x12);

LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x20);

LCD_WR_REG(0xC6);
LCD_WR_DATA8(0x0F);

LCD_WR_REG(0xD0);
LCD_WR_DATA8(0xA4);
LCD_WR_DATA8(0xA1);

LCD_WR_REG(0xE0);
LCD_WR_DATA8(0xD0);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x0D);
LCD_WR_DATA8(0x11);
LCD_WR_DATA8(0x13);
LCD_WR_DATA8(0x2B);
LCD_WR_DATA8(0x3F);
LCD_WR_DATA8(0x54);
LCD_WR_DATA8(0x4C);
LCD_WR_DATA8(0x18);
LCD_WR_DATA8(0x0D);
LCD_WR_DATA8(0x0B);
LCD_WR_DATA8(0x1F);
LCD_WR_DATA8(0x23);

LCD_WR_REG(0xE1);
LCD_WR_DATA8(0xD0);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x0C);
LCD_WR_DATA8(0x11);
LCD_WR_DATA8(0x13);
LCD_WR_DATA8(0x2C);
LCD_WR_DATA8(0x3F);
LCD_WR_DATA8(0x44);
LCD_WR_DATA8(0x51);
LCD_WR_DATA8(0x2F);
LCD_WR_DATA8(0x1F);
LCD_WR_DATA8(0x1F);
LCD_WR_DATA8(0x20);
LCD_WR_DATA8(0x23);
// LCD_WR_REG(0x21); // 注释掉 不然颜色反的
LCD_WR_REG(0x11);
LCD_WR_REG(0x29);
Address_set(0,0,240-1,320-1);
for(int i = 0; i < 240 * 320; i++)
LCD_WR_DATA(BLACK);
}
void LCD_WR_DATA(u16 dat)
{
u8 spi_dat[2];
XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 1);
spi_dat[0]=dat>>8;
spi_dat[1]=dat;
XSpiPs_PolledTransfer(&SpiInstance, spi_dat, NULL, 2);
}
void Address_set(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{
LCD_WR_REG(0x2a);
LCD_WR_DATA8(x1>>8);
LCD_WR_DATA8(x1);
LCD_WR_DATA8(x2>>8);
LCD_WR_DATA8(x2);
LCD_WR_REG(0x2b);
LCD_WR_DATA8(y1>>8);
LCD_WR_DATA8(y1);
LCD_WR_DATA8(y2>>8);
LCD_WR_DATA8(y2);
LCD_WR_REG(0x2C);
}

LVGL

从仓库克隆代码到应用工程的文件夹里。

1
git clone -b v8.3.3  https://github.com/lvgl/lvgl.git

将文件夹里面的lv_conf_template.h拷出到外面一级文件夹改名为lv_conf.h,然后进去把第一行宏定义#if 0改为#if 1。
可能需要添加一些include目录,酌情处理。
最后目录结构如下:

LVGL Porting

这部分内容lvgl做起来很简单,只要图形库与LCD驱动的部分衔接好就行。

LCD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void my_lvgl_init(void)
{
lv_init();

/*A static or global variable to store the buffers*/
static lv_disp_draw_buf_t disp_buf;

/*Static or global buffer(s). The second buffer is optional*/
static lv_color_t buf_1[MY_DISP_HOR_RES * 10];
static lv_color_t buf_2[MY_DISP_HOR_RES * 10];

/*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
lv_disp_draw_buf_init(&disp_buf, buf_1, buf_2, MY_DISP_HOR_RES*10);

lv_disp_drv_init(&disp_drv); /*Basic initialization*/
disp_drv.draw_buf = &disp_buf; /*Set an initialized buffer*/
disp_drv.flush_cb = my_flush_cb; /*Set a flush callback to draw to the display*/
disp_drv.hor_res = 240; /*Set the horizontal resolution in pixels*/
disp_drv.ver_res = 320; /*Set the vertical resolution in pixels*/

lv_disp_t * disp;
disp = lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/

// 简单初始化个例程
lv_example_spinner_1();
}

void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one
*`put_px` is just an example, it needs to be implemented by you.*/
// 画个窗
Address_set(area->x1, area->y1, area->x2, area->y2);
// 向窗子里推像素颜色
for(int i = 0; i < (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); i++)
{
LCD_WR_DATA(color_p->full);
color_p++;
}
/* IMPORTANT!!!
* Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}

while循环

在主循环里面循环调用lv_timer_handler();,zynq裸机的延时5ms,这个延时准不准不重要usleep(5000);

定时器中断

定时器初始化以及服务函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#define TIMER_DEVICE_ID     XPAR_XSCUTIMER_0_DEVICE_ID   //定时器ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //中断ID
#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR //定时器中断ID

//私有定时器的时钟频率 = CPU时钟频率/2 = 333MHz
//0.2s闪烁一次,0.2*1000_000_000/(1000/333) - 1 = 3F83C3F
#define TIMER_LOAD_VALUE 333000 - 1//0x3F83C3F

XScuGic Intc; //中断控制器驱动程序实例
XScuTimer Timer; //定时器驱动程序实例

void TimerIntrHandler(void *CallBackRef);

//定时器中断初始化
void timer_intr_init(XScuGic *intc_ptr,XScuTimer *timer_ptr)
{
//初始化中断控制器
XScuGic_Config *intc_cfg_ptr;
intc_cfg_ptr = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(intc_ptr, intc_cfg_ptr,
intc_cfg_ptr->CpuBaseAddress);
//设置并打开中断异常处理功能
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, intc_ptr);
Xil_ExceptionEnable();

//设置定时器中断
XScuGic_Connect(intc_ptr, TIMER_IRPT_INTR,
(Xil_ExceptionHandler)TimerIntrHandler, (void *)timer_ptr);

XScuGic_Enable(intc_ptr, TIMER_IRPT_INTR); //使能GIC中的定时器中断
XScuTimer_EnableInterrupt(timer_ptr); //使能定时器中断
}
int time_isr_init(void)
{
int status;
//私有定时器初始化
XScuTimer_Config *timer_cfg_ptr;
XScuTimer *timer_ptr = &Timer;
timer_cfg_ptr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
if (NULL == timer_cfg_ptr)
return XST_FAILURE;
status = XScuTimer_CfgInitialize(timer_ptr, timer_cfg_ptr,
timer_cfg_ptr->BaseAddr);
if (status != XST_SUCCESS)
return XST_FAILURE;

XScuTimer_LoadTimer(timer_ptr, TIMER_LOAD_VALUE); // 加载计数周期
XScuTimer_EnableAutoReload(timer_ptr); // 设置自动装载模式

timer_intr_init(&Intc,&Timer);
XScuTimer_Start(&Timer); //启动定时器
return XST_SUCCESS;
}

void TimerIntrHandler(void *CallBackRef)
{
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
// 让灯闪 确保中断初始化无误
if(XGpioPs_ReadPin(&Gpio, LED_2))
XGpioPs_WritePin(&Gpio, LED_2, 0);
else
XGpioPs_WritePin(&Gpio, LED_2, 1);

lv_tick_inc(1);

XScuTimer_ClearInterruptStatus(TimerInstancePtr);
}

结束

至此LVGL部署就完成了,主要要注意的是先把SPI以及LCD的驱动调通了,然后再部署LVGL,LVGL整个库功能很多,可以代替一部分操作系统的功能,可以多看看LVGL文档。