环境
# linux操作系统
uname -a
Linux incipe-virtual-machine 5.4.0-31-generic #35-Ubuntu SMP Thu May 7 20:20:34 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
# 交叉编译器
arm-linux-gcc -v
Using built-in specs.
Target: arm-none-linux-gnueabi
Configured with: /opt/FriendlyARM/mini2440/build-toolschain/working/src/gcc-4.4.3/configure --build=i386-build_redhat-linux-gnu --host=i386-build_redhat-linux-gnu --target=arm-none-linux-gnueabi --prefix=/opt/FriendlyARM/toolschain/4.4.3 --with-sysroot=/opt/FriendlyARM/toolschain/4.4.3/arm-none-linux-gnueabi//sys-root --enable-languages=c,c++ --disable-multilib --with-arch=armv4t --with-cpu=arm920t --with-tune=arm920t --with-float=soft --with-pkgversion=ctng-1.6.1 --disable-sjlj-exceptions --enable-__cxa_atexit --with-gmp=/opt/FriendlyARM/toolschain/4.4.3 --with-mpfr=/opt/FriendlyARM/toolschain/4.4.3 --with-ppl=/opt/FriendlyARM/toolschain/4.4.3 --with-cloog=/opt/FriendlyARM/toolschain/4.4.3 --with-mpc=/opt/FriendlyARM/toolschain/4.4.3 --with-local-prefix=/opt/FriendlyARM/toolschain/4.4.3/arm-none-linux-gnueabi//sys-root --disable-nls --enable-threads=posix --enable-symvers=gnu --enable-c99 --enable-long-long --enable-target-optspace
Thread model: posix
gcc version 4.4.3 (ctng-1.6.1)
开发板 GEC6818 ,详情介绍
思路
Ⅰ. 总体框架
Ⅱ. 目录结构
.
├── include
│ ├── get_touch.h
│ ├── lcd.h
│ ├── play_music.h
│ ├── show_bmp.h
│ └── ts_init.h
├── Makefile
├── README.md
└── sources
├── lcd.c
├── main.c
├── play_music.c
├── show_bmp.c
└── ts_init.c
Ⅲ. 流程图
Ⅳ. 需要用的知识
- Linux系统API,可以参考此文,你会linux系统API吗?
- Makefile文件编写,可以参考此文,简简单单学会写makefile
- 交叉编译开发
- 如果没有安装 madplay ,还要源码编译安装 madplay ,这个后面有空补上。
- markdown使用语法,可以参考,Markdown: 语法
代码
接下来详细讲解每个模块的具体功能与作用。
Ⅰ. LCD模块
LCD是GEC6818的显示屏,要想展示图片就必须打开LCD显示屏。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// lcd 文件描述符
int fd = 0;
// 共享映射区首地址
unsigned int *plcd = NULL;
/**
* 打开lcd屏幕和共享映射区
* */
void lcd_init() {
fd = open("/dev/fb0", O_RDWR);
if (fd == -1) {
perror("lcd open error: ");
exit(-1);
}
// 打开共享映射区
plcd = (unsigned int *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (plcd == MAP_FAILED) {
perror("mmap error: ");
exit(-1);
}
}
/**
* 关闭lcd屏幕和映射区
* */
void lcd_uninit() {
// 关闭共享映射区
munmap(plcd, 800 * 480 * 4);
// 关闭文件描述符
close(fd);
}
打开映射区的目的是为了加快显示图片的速度,直接使用 write
函数也是可以的。
Ⅱ. 显示图片模块
在这之前先简单介绍下 bmp 图片的存储格式。
BMP文件通常是不压缩的,通常比同一幅图像的压缩图像文件格式要大很多。可以参考百度百科 ,这里只介绍存储格式。
BMP文件组成
- BMP文件头,14字节,BMP文件的类型、文件大小和位图起始位置等信息。
- 位头信息,40字节。
- 颜色表,可用索引来表示图像。
- 位图数据,即图像数据。
例如,偏移量从 0x02 ~ 0x05
表示图片大小,0x12 ~ 0x15
表示图片宽, 0x16 ~ 0x19
表示图片高, 0x1c ~ 0x1d
表示图片的位深。
把这按照小端拼接起来就可以得到图片大小,宽高和位深信息。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
extern int fd;
extern unsigned int* plcd;
/**
* 绘制图片
* */
void lcd_drawpoint(int w, int h, unsigned int color) {
//(w,h)显示了color色
*(plcd + w + h * 800) = color;
}
/**
* 读取bmp图片数据
* 从x0,y0处开始显示一张宽w高h的图片
* */
void show_bmp(int x0, int y0, int w, int h, const char* bmp_file) {
int bmp = 0;
bmp = open(bmp_file, O_RDONLY);
if (-1 == bmp) {
perror("open bmp error");
exit(-1);
}
//读取BMP和DIB数据
int ret = 0;
// BMP头和DIB数据
unsigned char ch[64] = {0};
ret = read(bmp, ch, 54);
if (-1 == ret) {
perror("read bmp error");
exit(-1);
} else if (0 == ret) {
printf("no read data or file end\n");
exit(-1);
}
// 3.处理数据
int file_size = 0;
int width = 0, hight = 0, pix_bit = 0;
unsigned int color = 0;
// rgba位图
unsigned char a, r, g, b;
//存储图像的位图数据(各个像素点颜色值分量)
unsigned char pix[800 * 480 * 4] = {0};
file_size = ch[2] | ch[3] << 8 | ch[4] << 16 | ch[5] << 24;
width = ch[0x12] | ch[0x13] << 8 | ch[0x14] << 16 | ch[0x15] << 24;
hight = ch[0x16] | ch[0x17] << 8 | ch[0x18] << 16 | ch[0x19] << 24;
pix_bit = ch[0x1c] | ch[0x1d] << 8;
//读取位图数据
read(bmp, pix, w * h * pix_bit / 8);
int i = 0;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
b = pix[i++];
g = pix[i++];
r = pix[i++];
a = (pix_bit == 32) ? pix[i++] : 0;
color = a << 24 | r << 16 | g << 8 | b;
lcd_drawpoint(x0 + x, y0 + ((h - 1) - y), color);
}
}
close(bmp);
}
因为前54个字节,我们是不需要的,所以,文件要偏移54个字节,或者把这54个字节读取出来。
注:bmp 图片是没有透明度选项的,即 rgb 颜色标准。
Ⅲ. 打开触屏文件
打开触屏的主要目的是实现上一首下一首,播放暂停功能的实现。
#include <fcntl.h>
#include <linux/input.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int ts = 0;
extern bool flag;
/**
* 获取触摸的x,y坐标
* */
void get_touch(int *x, int *y) {
int ret;
struct input_event ev; //输入事件结构体变量,用来保存读取的输入事件
// 1) 打开触摸屏文件
ts = open("/dev/input/event0", O_RDWR);
if (-1 == ts) {
perror("open input error");
exit(-1);
}
// 2) 读取触摸屏事件
while (1) {
if (flag) {
break;
}
ret = read(ts, &ev, sizeof(ev)); //读取输入事件保存到结构体ev中
if (ret == sizeof(ev)) {
if (ev.type == EV_ABS && ev.code == ABS_X) {
*x = ev.value * 0.8; //此时的value是触摸点X轴的坐标
}
if (ev.type == EV_ABS && ev.code == ABS_Y) {
*y = ev.value * 0.8; //此时的value是触摸点Y轴的坐标
}
if (ev.type == EV_KEY && ev.code == BTN_TOUCH && ev.value == 0) {
//手指从触摸屏 离开
printf("(x = %d, y = %d)\n", *x, *y);
break;
}
}
}
}
/**
* 关闭触屏板
* */
void close_ts() { close(ts); }
Ⅳ. 实现音乐播放器功能
介绍下 madplay
的使用。
管理madplay的主程序,包括播放,暂停播放,恢复播放,停止播放
system("madplay 1.mp3 &"); // 利用system函数调用madplay播放器播放*.mp3音乐
system("madplay 1.mp3 -r &"); // 循环播放:参数-r
system("killall -9 madplay"); // 利用system函数调用killall命令将madplay终止掉
system("killall -STOP madplay &"); // 利用system函数调用killall命令将madplay暂停
system("killall -CONT madplay &"); // 利用system函数调用killall命令恢复madplay的播放
system("madplay 1.mp3 -a volume &");// 初始化播放音量,volume表示音量大小,范围是 -175 to +18 dB
// 更多可以使用man命令查看
// man madplay
再介绍下信号:
kill -
-l -- list signal names or numbers of specified signals
-n -- specify signal number
-s -- specify signal name
-ABRT -BUS -CONT -HUP -INT -PIPE -PROF -QUIT -STKFLT -SYS -TRAP -TTIN -URG -USR2 -WINCH -XFSZ
-ALRM -CHLD -FPE -ILL -KILL -POLL -PWR -SEGV -STOP -TERM -TSTP -TTOU -USR1 -VTALRM -XCPU
代码:
#include <fcntl.h>
#include <linux/input.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
char music[7][6] = {"1.mp3", "2.mp3", "3.mp3", "4.mp3",
"5.mp3", "6.mp3", "7.mp3"};
int order = 0;
extern bool isFirst;
extern bool isPlay;
extern int vol;
/**
* 播放音乐
* 如果是第一次播放就开始播放
* 如果不是,就继续播放
* */
void play_music() {
if (isFirst) {
char command[100] = {0};
sprintf(command, "madplay %s -a %d &", music[order], vol);
printf("%s\n", command);
system(command);
} else {
system("killall -CONT madplay &");
}
}
/**
* 暂停音乐
* */
void stop_music() { system("killall -STOP madplay &"); }
/**
* 下一首
* */
void next_music() {
system("killall -9 madplay");
if (order == 6) {
order = -1;
}
char command[100] = {0};
sprintf(command, "madplay %s -a %d &", music[++order], vol);
printf("%s\n", command);
system(command);
}
/**
* 上一首
* */
void pre_music() {
system("killall -9 madplay");
if (order == 0) {
order = 7;
}
char command[100] = {0};
sprintf(command, "madplay %s -a %d &", music[--order], vol);
printf("%s\n", command);
system(command);
}
这里播放音乐有个逻辑,就是如果是第一次播放的话,就要开始播放音乐,如果不是的话,就要继续播放音乐。
另外,上一首下一首功能,要防止数组越界,更简单的直接取模也是可以的。
Ⅴ. 主函数逻辑功能
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
extern void lcd_init();
extern void lcd_uninit();
extern void get_touch(int *, int *);
extern void close_ts();
extern void show_bmp(int, int, int, int, const char *);
extern void play_music();
extern void stop_music();
extern void next_music();
extern void pre_music();
bool isFirst = true;
bool isPlay = false;
bool flag = false;
int vol = 0;
/**
* 处理信号函数
* */
void my_handler(int sig) {
// 最好不加
printf("End of program, end of code: %d\n", sig);
flag = true;
}
int main(int argc, char *argv[]) {
// argv[0] 文件名 argv[1] 音量大小
if (argc != 2) {
vol = 0;
} else {
switch (atoi(argv[1])) {
case 0:
vol = -175;
break;
case 1:
vol = -15;
break;
case 2:
vol = 0;
break;
case 3:
vol = 10;
break;
}
}
signal(SIGINT, my_handler);
const char *background_bmp = "./bmp/background.bmp";
const char *next_bmp = "./bmp/next.bmp";
const char *pre_bmp = "./bmp/pre.bmp";
const char *pause_bmp = "./bmp/pause.bmp";
const char *play_bmp = "./bmp/play.bmp";
lcd_init();
show_bmp(0, 0, 800, 480, background_bmp);
show_bmp(44, 340, 100, 100, pre_bmp);
show_bmp(375, 340, 100, 100, pause_bmp);
show_bmp(639, 340, 100, 100, next_bmp);
// 触屏得到的坐标
int x = 0, y = 0;
while (1) {
if (flag) {
// 不让程序自动处理ctrl + z/c
system("killall -9 madplay");
show_bmp(375, 340, 100, 100, pause_bmp);
flag = false;
break;
}
get_touch(&x, &y);
if (!flag) {
if (375 < x && x < 475 && 340 < y && y < 440) {
// 如果正在播放音乐,就停止播放音乐
// 如果音乐没有播放,就开始播放音乐
if (isPlay) {
stop_music();
show_bmp(375, 340, 100, 100, pause_bmp);
isPlay = false;
} else {
play_music();
show_bmp(375, 340, 100, 100, play_bmp);
isPlay = true;
isFirst = false;
}
}
// 上一首音乐
if (45 < x && x < 145 && 340 < y && y < 440) {
pre_music();
show_bmp(375, 340, 100, 100, play_bmp);
isFirst = false;
isPlay = true;
}
// 下一首音乐
if (639 < x && x < 739 && 340 < y && y < 440) {
next_music();
show_bmp(375, 340, 100, 100, play_bmp);
isFirst = false;
isPlay = true;
}
}
}
close_ts();
lcd_uninit();
return 0;
}
主函数增加了一个 ctrl + c/z
信号处理,不想让程序帮我处理这个信号,我要自己处理,目的是为了解决直接使用 ctrl + c
结束程序,madplay
还在播放音乐的情况。
其次就是通过触摸屏得到的触摸点,进行相应的操作逻辑。
总结
代码经过编译,可以成功移植到 GEC1818 开发板上,具体操作,可见 README.md 文件。
源码地址,github
代码很大一部分是教课设的粤嵌老师造的轮子,自己的代码实际工作量不大。
不足之处
- 使用了较多的全局变量,这样会导致代码的耦合性降低,后期维护难度大;
- 功能还是有所欠缺;
- 代码还是有点冗余,不太精简。
- ……