环境
# 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 &"); // 循环播放:参数-rsystem("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/csystem("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
代码很大一部分是教课设的粤嵌老师造的轮子,自己的代码实际工作量不大。
不足之处
- 使用了较多的全局变量,这样会导致代码的耦合性降低,后期维护难度大;
- 功能还是有所欠缺;
- 代码还是有点冗余,不太精简。
- …