@tigger 在 D1 LicheeRV Dock 移植RTL8723DS驱动 中说:
ifconfig -a
是不是有显示 wlan0 和 wlan1?
wlan1 是不是可以作为热点用?
大佬,研究一下airkiss或者smartconfig,我在8723ds测试 不太稳定,有时可以配网成功 有时不行,不知道怎么修改
@tigger 在 D1 LicheeRV Dock 移植RTL8723DS驱动 中说:
ifconfig -a
是不是有显示 wlan0 和 wlan1?
wlan1 是不是可以作为热点用?
大佬,研究一下airkiss或者smartconfig,我在8723ds测试 不太稳定,有时可以配网成功 有时不行,不知道怎么修改
@whycan lvgl本身就支持吧,只要在lv_config.h配置一下就可以用了
*---------------------
/*File system interfaces for common APIs
To enable set a driver letter for that API/
#define LV_USE_FS_STDIO '\0' /Uses fopen, fread, etc/
//#define LV_FS_STDIO_PATH "/home/john/" /*Set the working directory. If commented it will be "./" */
#define LV_USE_FS_POSIX 1 /Uses open, read, etc/
#define CONFIG_LV_FS_POSIX_LETTER 'S' /Uses open, read, etc/
//#define LV_FS_POSIX_PATH "/" /*Set the working directory. If commented it will be "./" */
#define LV_USE_FS_WIN32 '\0' /Uses CreateFile, ReadFile, etc/
//#define LV_FS_WIN32_PATH "C:\Users\john\" /*Set the working directory. If commented it will be ".\" */
#define LV_USE_FS_FATFS '\0' /Uses f_open, f_read, etc/
if (pid == 0)
{
LOG_D("child pid:%d\n", getpid());
char cmd[32];
prctl(PR_SET_PDEATHSIG, SIGKILL);
close(0);
dup2(pip[1], 1); //标准输出重定向到管道输出
close(pip[0]);
sprintf(buf, "./music/%s", _lv_demo_music_get_title(track_id));
sprintf(cmd, "--start=%d", _time, _lv_demo_music_get_track_length(track_id));
// execlp("ls", "ls", "./music", NULL);
// execlp("play", "play", "-p", buf, "trim", cmd, NULL);
// execlp("sox", "sox", buf, "-p", "|", "play", "-", "trim", cmd, NULL);
//最后知道怎么使用管道了,但是这是2个进程
// sox ./music/云非非\ -\ 邂逅.flac -t flac - | play -t flac - &
// sox ./music/云非非\ -\ 邂逅.flac -t flac - | play - &
// sox ./music/云非非\ -\ 邂逅.flac -p | play - &
return 0;
}
MPV是著名开源播放器mplayer和mplayer2的一个分支。
mplayer则是这个地球上最强的播放器(没有之一),跨平台的特性使得windows、mac、linux上都可以见到它的身影,电脑、手机上很多播放器也是基于它开发的,由于mplayer不带界面,所以很多时候你都不知道是它在默默为你工作。
并且mplayer播放视频时对于资源的消耗往往最少,所以你会发现在一台配置极差的电脑上播放高清电影,mplayer通常是最流畅的,使用快进时最能体现出差距,其他播放器已经画面卡死时,mplayer的画面可能只是感觉到掉帧而已。
MPV播放器继承这些众多优良特性的同时,添加了内置于窗口的播放控制界面(OSC),对硬解的良好支持,以及其他额外特性。由于口碑很好,使得著名的mplayer前端:smplayer在不久前也添加了对MPV的支持,现在的smplayer你可以在mplayer和MPV2个核心之间切换使用。
mpv官网的开发文档比较好,参考使用手册都可以使用起来
方法1:
int scan_music_list(char *_path)
{
char path[128];
sprintf(path, "ls %s", _path);
FILE *fp = popen(path, "r");
if (ferror(fp))
{
LOG_D("error\n");
}
char buf[128];
while (!feof(fp))
{
fgets(buf, sizeof(buf), fp);
strncpy(title_list[idx], buf, strlen(buf) - 1); //去掉文件名后的\n
get_music_info(title_list[idx], idx);
idx++;
usleep(1);
}
return idx;
}
方法2:
int list_dir(char *path, int depth)
{
DIR *dir;
struct dirent *file;
struct stat st;
dir = opendir(path);
if (!dir)
{
LOG_D("open dir %s failed!", path);
return -1;
}
LOG_D("open dir %s ok!", path);
while ((file = readdir(dir)) != NULL)
{
if (strncmp(file->d_name, ".", 1) == 0 || strncmp(file->d_name, "..", 2) == 0)
{
continue;
}
strcpy(title_list[idx++], file->d_name);
if (stat(file->d_name, &st) >= 0 && S_ISDIR(st.st_mode) && depth <= 5)
{
list_dir(file->d_name, depth + 1);
}
}
closedir(dir);
return 0;
} ```
b.程序启动时创建一个子进程,一个线程获取播放进度,一个线程获取各种输入 void lv_demo_music(void)
{
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x343247), 0);
music_num = scan_music_list(MUSIC_PATH);
list = _lv_demo_music_list_create(lv_scr_act());
ctrl = _lv_demo_music_main_create(lv_scr_act());
pid = vfork();
if (pid == 0)
{
LOG_D("child pid:%d\n", getpid());
prctl(PR_SET_PDEATHSIG, SIGKILL);
execlp("mpv", "mpv", "--quiet", "--no-terminal", "--no-video", "--idle=yes", "--term-status-msg=", "--input-ipc-server=/tmp/mpvsocket", NULL);
LOG_D("child exit!\n");
return 0;
}
else if (pid > 0)
{
LOG_D("parent pid:%d\n", getpid());
sleep(1);
close(0);
act.sa_handler = sigusr1;
sigfillset(&act.sa_mask);
act.sa_flags = SA_RESTART; /* don't fiddle with EINTR */
sigaction(SIGUSR1, &act, NULL);
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/mpvsocket");
fd_mpv = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd_mpv == -1)
{
LOG_D("Create socket failed\n");
}
if (connect(fd_mpv, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
LOG_D("Cannot connect to socket %s\n", addr.sun_path);
}
if (pthread_create(&mthread, NULL, get_music_percent_pos, NULL) != 0)
{
LOG_D("pthread create error!\n");
return 0;
}
// LOG_D("get music pos pthread create ok!\n");
}
else
{
LOG_D("fork error:\n");
}
#if LV_DEMO_MUSIC_AUTO_PLAY
lv_timer_create(auto_step_cb, 1000, NULL);
#endif
}
这里说明一下mpv里的参数
"--quiet", //输出尽量少的信息
"--no-terminal", //不接受终端输入
"--no-video", //不需要视频
"--idle=yes", //播放完不能出进程
"--term-status-msg=", //状态信息不打印
"--input-ipc-server=/tmp/mpvsocket"//使用sock方式与mpv通信
这里使用的是mpv推荐的基于socket的JSON-based IPC protocol通信方式
c. 一个线程启动时向mpv发送命令,然后监听事件就可以知道当前进度及状态 void *get_music_percent_pos(void *arg)
{
// char cmd[] = "{\"command\": [ \"get_property\", \"playback-time\"] }\n";
// char cmd[] = "{\"command\": [ \"get_property\", \"percent-pos\"] ,\"request_id\":2}\n";
char cmd2[] = "{\"command\": [ \"observe_property\",2, \"percent-pos\"]}\n";
char cmd1[] = "{\"command\": [ \"observe_property\",1,\"time-pos\"]}\n";
char buf[512];
cJSON *root;
cJSON *event;
cJSON *percent;
write(fd_mpv, cmd1, strlen(cmd1));
_msleep(100);
write(fd_mpv, cmd2, strlen(cmd2));
memset(buf, 0, sizeof(buf));
while (1)
{
if (read(fd_mpv, buf, sizeof(buf)) > 0)
{
//LOG_D("--->:%s\n", buf);
root = cJSON_Parse(buf);
if (root == NULL)
{
LOG_D("cJSON parse error!\n");
return;
}
// LOG_D("cJSON parse ok!\n");
if (cJSON_HasObjectItem(root, "event"))
{
event = cJSON_GetObjectItem(root, "event");
if (event != NULL)
{
if (strcmp(event->valuestring, "start-file") == 0)
{
}
else if (strcmp(event->valuestring, "metadata-update") == 0)
{
}
else if (strcmp(event->valuestring, "file-loaded") == 0)
{
}
else if (strcmp(event->valuestring, "property-change") == 0 && (cJSON_GetObjectItem(root, "id")->valueint == 1))
{
percent = cJSON_GetObjectItem(root, "data");
lv_label_set_text_fmt(time_obj, "%02d:%02d", (int)percent->valuedouble / 60, (int)percent->valuedouble % 60);
LOG_D("time pos:%d\n", (int)percent->valuedouble);
}
else if (strcmp(event->valuestring, "property-change") == 0 && (cJSON_GetObjectItem(root, "id")->valueint == 2))
{
percent = cJSON_GetObjectItem(root, "data");
lv_slider_set_value(slider_obj, (int)percent->valuedouble, LV_ANIM_ON);
LOG_D("percent pos:%d\n", (int)percent->valuedouble);
}
else if (strcmp(event->valuestring, "end-file") == 0)
{
LOG_D("end-file\n");
is_loaded = true;
_lv_demo_music_album_next(true);
}
else if (strcmp(event->valuestring, "playback-restart") == 0)
{
}
else if (strcmp(event->valuestring, "idle") == 0)
{
LOG_D("idle\n");
}
}
}
free(root);
}
//_msleep(250);
}
pthread_exit(NULL);
LOG_D("pthread exit:\n");
}
这里通信都是采用JSON格式,使用了cJSON来解析送来的数据
``````
扩展板上资源,除了外挂的声卡没调试好外,其它都OK了,既然不能注册声卡,那就应用层操作,反正D1上I2S配置好了,PCM5121通过I2C操作也很简单
补上板子工程源文件 Kicad 5.99
我发现Neza-D1开发板的这个引脚跟树莓派引脚兼容
今天才收到板子,焊接好后再补一张实物图片
lcd_ili9341@0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "ilitek,ili9341";
reg = <0>;
spi-max-frequency = <32000000>;
rotation = <0>;
spi-cpol;
spi-cpha;
rgb;
fps = <30>;
buswidth = <8>;
txbuflen = <32768>;
reset-gpios = <&pcf8574 6 GPIO_ACTIVE_LOW>;
dc-gpios = <&pcf8574 7 GPIO_ACTIVE_LOW>;
/*backlight = <&backlight>;*/
led-gpios = <&pcf8574 5 GPIO_ACTIVE_HIGH>;
status = "okay";
};
在内核打开small lcd support选项
[] Staging drivers --->
<> Support for small TFT LCD display modules --->3
<*> FB driver for the ILI9341 LCD Controller
系统启动log
[ 2.670067] fbtft_of_value: buswidth = 8
[ 2.674512] fbtft_of_value: backlight = 39
[ 2.679097] fbtft_of_value: fps = 30
[ 2.683098] fbtft_of_value: txbuflen = 32768
[ 2.691259] GPT:Primary header thinks Alt. header is not at the end of the disk.
[ 2.699560] GPT:625184 != 7774207
[ 2.703265] GPT:Alternate GPT header not at the end of the disk.
[ 2.709981] GPT:625184 != 7774207
[ 2.713688] GPT: Use GNU Parted to correct GPT errors.
[ 2.719480] mmcblk0: p1 p2 p3 p4
[ 2.987837] random: fast init done
[ 3.036258] graphics fb1: fb_ili9341 frame buffer, 240x320, 150 KiB video memory, 32 KiB buffer memory, fps=31, spi1.0 at 32 MHz
这里有一点需要注意,我们使用的GPIO是通过PCF8574扩展出来的,对IO读写操作时可能会导致休眠,因此不能在中断函数里使用,我们需要简单的修改一下驱动文件,用一个宏定义即可,如果不修改操作一次IO,就会出现一堆警告
#define gpio_set_value gpio_set_value_cansleep
进系统里可以查看是否有对应设备
# ls /dev/fb*
/dev/fb0 /dev/fb1
测试屏幕
# cat /dev/urandom > /dev/fb1
cat: write error: No space left on device
清屏
# cat /dev/zero > /dev/fb1
cat: write error: No space left on device
显示图片
# fbv image1.jpg
Neza-D1开发板扩展IO上引出了I2C2,从原理图上看,已经接了PCF8574@38了,总线速度400K,我们的OLED也是接在这上边的,由于这屏幕比较小,显示不了多少东西,就不用FB方式了,直接在应用层操作OLED,所以设备树也不需要配置
在应用层操作就跟单片机操作是一样的了,这里为了方便操作屏幕,我分辨率移植u8g2这个库,我手里的哦了的是1.3"的,驱动IC是SHT1106,用1306也可以,只能显示不满屏,可能是基地址定义的不一样
我们不需要从OLED读取数据,所以只需要实现I2C写函数就可以,注意OLED模块上标的地址是0x78,这个是8位地址,这里使用的是7位地址,右移一位所以是0x3C
unsigned char i2c_init()
{
const char *i2c_dev = "/dev/i2c-2";
fd = open(i2c_dev, O_RDWR);
if (fd < 0)
{
printf("not have /dev/i2c-2 t\r\n");
fflush(stdout);
return -1;
}
return 0;
}
// I2C Addr 7bit
unsigned char base_i2c_write(unsigned char device_addr, unsigned char *buff, int num)
{
struct i2c_rdwr_ioctl_data data;
struct i2c_msg msgs_str[1];
data.msgs = msgs_str;
data.nmsgs = 1;
data.msgs[0].len = num;
data.msgs[0].addr = device_addr;
data.msgs[0].flags = 0;
data.msgs[0].buf = buff;
ioctl(fd, I2C_RDWR, (unsigned long)&data);
}
移植u8g2 非常简单,只需修改这下边这个函数里初始化位置和写数据位置就行,另外一个是延时,可以不用修改
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
/* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
static uint8_t buffer[128];
static uint8_t buf_idx;
uint8_t *data;
switch (msg)
{
case U8X8_MSG_BYTE_INIT:
{
/* add your custom code to init i2c subsystem */
i2c_init; // I2C初始化
}
break;
case U8X8_MSG_BYTE_START_TRANSFER:
{
buf_idx = 0;
}
break;
case U8X8_MSG_BYTE_SEND:
{
data = (uint8_t *)arg_ptr;
while (arg_int > 0)
{
buffer[buf_idx++] = *data;
data++;
arg_int--;
}
}
break;
case U8X8_MSG_BYTE_END_TRANSFER:
{
//i2c写函数
if (base_i2c_write(OLED_ADDRESS, buffer, buf_idx) != 0)
return 0;
}
break;
case U8X8_MSG_BYTE_SET_DC:
break;
default:
return 0;
}
return 1;
}
uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
switch (msg)
{
case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
break;
case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
break;
case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
break;
case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
break; // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
break; // arg_int=1: Input dir with pullup high for I2C clock pin
case U8X8_MSG_GPIO_I2C_DATA: // arg_int=0: Output low at I2C data pin
break; // arg_int=1: Input dir with pullup high for I2C data pin
case U8X8_MSG_GPIO_MENU_SELECT:
u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_NEXT:
u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_PREV:
u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_HOME:
u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
break;
default:
u8x8_SetGPIOResult(u8x8, 1); // default return value
break;
}
return 1;
}
这就移植完了,带中文字库,可以直接显示中文,非常方便,做一个显示信息的小玩意儿还是非常方便的,下边编译一下传到开发板里,可以看到显示成功(图片)
最后附上完整工程供参考
neza_d1_u8g2_oled.7z
ir_send {
compatible = "gpio-ir-tx";
gpios = <&pio 3 22 GPIO_ACTIVE_HIGH>; /* PD22 */
status = "okay";
};
接收引脚
&s_cir0 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&s_cir0_pins_a>;
pinctrl-1 = <&s_cir0_pins_b>;
linux,rc-map-name = "rc-tbs-nec";
status = "okay";
};
下边是系统log
[ 1.719737] i2c /dev entries driver
[ 1.723748] IR NEC protocol handler initialized
[ 1.728825] IR RC5(x/sz) protocol handler initialized
[ 1.735217] rc rc0: GPIO IR Bit Banging Transmitter as /devices/platform/soc@3000000/soc@3000000:ir_send/rc/rc0
[ 1.746722] rc rc0: lirc_dev: driver gpio-ir-tx registered at minor = 0, no receiver, raw IR transmitter
[ 1.758445] sunxi_ir_startup: get ir protocol failed
[ 1.758467] (NULL device *): deviceless supply not found, using dummy regulator
[ 1.772394] Registered IR keymap rc_map_sunxi
[ 1.777404] rc rc1: sunxi-ir as /devices/platform/soc@3000000/7040000.s_cir/rc/rc1
[ 1.786130] rc rc1: lirc_dev: driver sunxi-rc-recv registered at minor = 1, raw IR receiver, no transmitter
[ 1.797263] input: sunxi-ir as /devices/platform/soc@3000000/7040000.s_cir/rc/rc1/s_cir_rx
查看系统 /dev下的设备
# ls /dev/lirc*
/dev/lirc1 /dev/lirc1 /dev/lircd
此时查看/dev/input目录下,会有如下显示:
# evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: sunxi-keyboard
/dev/input/event1: soc@3000000:rotary
/dev/input/event2: sunxi-ir
/dev/input/event3: audiocodec sunxi Audio Jack
/dev/input/event4: soc@3000000:gpio_keys
/dev/input/event5: ns2009_ts
此时如果用evtest测试按键是没有任何反应的,因为keymap我们没有修改成自己用的遥控器,当然就不上报事件了,但不影响后续操作
我们可以用lirc里的工具去测试硬件是否OK,测试之前要先修改默认配置文件,不修改的话,同样没反应
# vi /etc/lirc/lirc_options.conf
driver = default
device = /dev/lric1
修改这2行就可以,里边driver默认是uinput,device默认是auto
再次测试就有反应了
# mode2 -m -d /dev/lirc1
Using driver default on device /dev/lirc1
Trying device: /dev/lirc1
Using device: /dev/lirc1
Warning: Running as root.
9043 4402 626 497 656 499
624 499 656 499 624 499
656 499 624 499 624 531
624 1630 619 1624 655 1624
624 1624 624 1634 646 1624
624 1624 655 1624 593 1656
624 501 654 1624 593 534
652 1624 624 499 625 1624
656 504 619 499 655 1625
624 499 656 1625 593 531
624 1633 648 498 624 1624
624 40035
9060 2184 624 143451-pulse 483450-space
从这结果上可以明显的看出这些数值代表脉冲宽度,单位是us
使用irrecod -f -d /dev/lirc1 --disable-namespace来录制配置文件
录制完成后放到 /etc/lric/liricd.conf.d/下边
!注意,这里录制的结果不准确,需要借助上边的命令来获取每一个按键的值,这里只是借用一下配置文件的格式,里边的数字是右对齐
示例:
# Please take the time to finish this file as described in
# https://sourceforge.net/p/lirc-remotes/wiki/Checklist/
# and make it available to others by sending it to
# <lirc@bartelmus.de>
#
# This config file was automatically generated
# using lirc-0.10.1(default) on Thu Jan 1 01:37:06 1970
# Command line used: -f -d /dev/lirc1 --disable-namespace
# Kernel version (uname -r): 5.10.19
#
# Remote name (as of config file): elac
# Brand of remote device, the thing you hold in your hand:
# Remote device model nr:
# Remote device info url:
# Does remote device has a bundled capture device e. g., a
# usb dongle? :
# For bundled USB devices: usb vendor id, product id
# and device string (use dmesg or lsusb):
# Type of device controlled
# (TV, VCR, Audio, DVD, Satellite, Cable, HTPC, ...) :
# Device(s) controlled by this remote:
begin remote
name elac
flags RAW_CODES|CONST_LENGTH
eps 30
aeps 100
gap 108533
begin raw_codes
name Power
9043 4403 623 531 593 531
624 531 593 531 624 531
593 531 624 531 593 536
594 1680 592 1656 593 1656
625 1655 593 1666 582 1656
624 1656 593 1655 625 530
593 534 621 530 593 1656
624 1624 624 531 593 530
624 531 595 1655 623 1655
593 1656 593 530 624 531
593 1664 616 1656 593 1656
593
name BT
9041 4407 622 531 593 531
593 562 593 531 593 531
625 533 596 525 624 530
593 1656 624 1656 593 1655
593 1664 616 1655 593 1656
593 1656 624 1658 591 530
624 1656 593 531 624 531
593 531 593 531 624 1661
587 530 624 1656 593 531
593 1687 593 1656 601 1648
624 1656 593 531 593 1656
624
name Vol+
9013 4437 592 530 593 562
593 531 624 531 593 531
594 565 589 530 593 531
624 1656 593 1656 624 1665
590 1651 591 1656 624 1655
593 1656 593 1667 613 531
593 1656 624 1656 593 531
593 562 595 528 593 1656
624 531 593 1656 624 531
593 530 593 1659 621 1655
593 1656 593 562 593 1656
593
name Vol-
9044 4436 592 531 624 531
593 533 622 530 593 531
593 531 624 531 593 530
624 1656 593 1661 595 1679
593 1655 593 1656 624 1656
593 1667 582 1659 620 530
593 1656 624 1656 600 524
624 1656 593 531 593 531
624 531 593 1656 624 531
598 530 620 1654 593 531
624 1625 624 1656 593 1666
614
name Pre
9048 4403 592 562 593 530
593 562 593 530 593 531
624 531 593 531 624 531
593 1661 593 1680 592 1656
593 1656 624 1656 603 1650
588 1656 623 1656 593 1656
624 1662 587 1655 593 531
624 531 593 531 624 1656
593 531 631 524 593 530
593 562 593 1656 593 1656
624 1663 586 530 624 1624
624
.
.
.
.
.
end raw_codes
end remote
接下来使用irw来验证刚才录制的是否正确
# irw
lircd-0.10.1[432]: Notice: accepted new client on /var/run/lirc/lircd
lircd-0.10.1[432]: Info: [lirc] protocol is enabled
0000000000000001 00 Power elac
0000000000000002 00 BT elac
0000000000000003 00 Vol+ elac
0000000000000004 00 Vol- elac
0000000000000008 00 Ana1 elac
000000000000000a 00 Opt1 elac
000000000000000b 00 Opt2 elac
然后在/etc/lirc/目录下创建lircrc配置文件或~/.lircrc
格式如下
begin
prog = irexec
button = Power
repeat = 0
config = echo "power"
end
begin
prog = irexec
button = Vol+
repeat = 1
config = amixer -M -c 0 sset 'Headphone',0 1%+ > /dev/null
end
begin
prog = irexec
button = Vol-
repeat = 1
config = amixer -M -c 0 sset 'Headphone',0 1%- > /dev/null
end
保存
最后就是使用irexec来进行各种操作了,无需编程,到这里可以自由发挥了,想要集成到代码里也很简单,看下图
# irexec
lircd-0.10.1[448]: Notice: accepted new client on /var/run/lirc/lircd
lircd-0.10.1[448]: Info: [lirc] protocol is enabled
volume up click
Simple mixer control 'Headphone',0
Capabilities: pvolume pvolume-joined pswitch
Playback channels: Front Left - Front Right
Limits: Playback 0 - 63
Mono:
Front Left: Playback 47 [54%] [-16.00dB] [on]
Front Right: Playback 47 [54%] [-16.00dB] [on]
volume down click
Simple mixer control 'Headphone',0
Capabilities: pvolume pvolume-joined pswitch
Playback channels: Front Left - Front Right
Limits: Playback 0 - 63
Mono:
Front Left: Playback 45 [50%] [-18.00dB] [on]
Front Right: Playback 45 [50%] [-18.00dB] [on]
LIRC处理流程
+--------+ +-------------+ +--------+
| | | Linux input | | Appli- |
--->---| kernel |---->----| layer |---------->----------| cation |
| | | | /dev/input/eventX | |
+--------+ +-------------+ +--------+
+--------+ +-------------+
| | | Linux input |
--->---| kernel |---->----| layer |
| | | |
+--------+ +-------------+
|
v
|
| +--------+
+-------------+ | Appli- |
| lirc |---------->----------| cation |-+
| | lirc socket | | |
+-------------+ +--------+ |-+
| | |
+--------+ |
| |
+--------+
+--------+ +-------------+ +--------+
| | | | | Appli- |
--->---| kernel |---->----| lirc |---------->----------| cation |-+
| | | | lirc socket | | |
+--------+ +-------------+ +--------+ |-+
| | |
+--------+ |
| |
+--------+
通过socket使用lirc,不需要引入任何文件,可以方便的集成到代码里,就是上述第三种方式
struct sigaction act;
char buf[128];
struct sockaddr_un addr;
typedef struct
{
char addr[32];
char code[8];
char type[16];
char name[16];
} IRW_DATA;
IRW_DATA irw_data;
act.sa_handler = sigusr1;
sigfillset(&act.sa_mask);
act.sa_flags = SA_RESTART; /* don't fiddle with EINTR */
sigaction(SIGUSR1, &act, NULL);
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/var/run/lirc/lircd");
fd_lircd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (fd_lircd == -1)
{
printf("Create socket failed\n");
}
if (connect(fd_lircd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
printf("Cannot connect to socket %s\n", addr.sun_path);
}
while (1)
{
// lircd
if (read(fd_lircd, buf, 128) > 0)
{
LOG_D("%s\n", buf);
char *p = strtok(buf, " ");
int i = 0;
while (p)
{
// LOG_D("%s\n", p);
if (i == 0)
strcpy(irw_data.addr, p);
else if (i == 1)
strcpy(irw_data.code, p);
else if (i == 2)
strcpy(irw_data.type, p);
else if (i == 3)
strcpy(irw_data.name, p);
p = strtok(NULL, " ");
i++;
}
LOG_D("%s\n", irw_data.type);
switch ((uint8_t)strtol(irw_data.addr, NULL, 10))
{
case Vol_up:
rotary_encoder_handler(&u8g2, -1);
break;
case Vol_down:
rotary_encoder_handler(&u8g2, 1);
break;
case Play:
rotary_encoder_button_handler(&u8g2);
break;
case Bt:
printf("BT button\n");
break;
}
}
}
下边开始向外发射IR信号,发射就使用之前录制的配置文件
irsend SEND_ONCE elac power
Neza-D1开发板上只引出一个按键,是使用的LRADC检检测按键的,想使用更多按键,可以使用板子上通过PCF8574扩展出来的IO,我画的扩展板使用了四个按键,分别为PP0,PP1,PP2,PP4
设备树配置如下 内核需要勾选 Polled input device skeleton
gpio_keys {
compatible = "gpio-keys";
pinctrl-names = "default";
/*pinctrl-0 = <&key_pins>;*/
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
button@0 {
label = "Key volume up";
linux,code = <KEY_VOLUMEUP>;
gpios = <&pcf8574 0 GPIO_ACTIVE_LOW>; /* PP0 */
};
button@1 {
label = "Key volume down";
linux,code = <KEY_VOLUMEDOWN>;
gpios = <&pcf8574 1 GPIO_ACTIVE_LOW>; /* PP1 */
};
button@2 {
label = "Key back";
linux,code = <KEY_BACK>;
gpios = <&pcf8574 2 GPIO_ACTIVE_LOW>; /* PP2 */
};
button@3 {
label = "Key enter";
linux,code = <KEY_ENTER>;
gpios = <&pcf8574 4 GPIO_ACTIVE_LOW>; /* PP4 */
};
};
编码器接在D1的PD14 PD15 设备树如下
注意需要内核勾选<*> Polled GPIO Decoder Input driver
rotary {
compatible = "rotary-encoder";
pinctrl-names = "default";
/*pinctrl-0 = <&rotary_pins>;*/
gpios = <&pio 3 14 GPIO_ACTIVE_LOW>, <&pio 3 15 GPIO_ACTIVE_LOW>; /* PD14, PD15 */
linux,axis = <0>; /* REL_X */
rotary-encoder,encoding = "gray";
rotary-encoder,relative-axis;
};
然后在内核勾选
系统启动会有如下log
[ 0.111735] input: sunxi-keyboard as /devices/virtual/input/input0 这是板载的那个LRADC 按键
[ 1.668976] rotary-encoder soc@3000000:rotary: gray 这是旋转编码器
[ 1.675145] input: soc@3000000:rotary as /devices/platform/soc@3000000/soc@3000000:rotary/input/input1
[ 3.178668] input: soc@3000000:gpio_keys as /devices/platform/soc@3000000/soc@3000000:gpio_keys/input/input5 这是扩展出来的那4个按键
查看系统里已注册的输入设备,用这个比较清楚,ls /dev/input也可以
# evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: sunxi-keyboard
/dev/input/event1: soc@3000000:rotary
/dev/input/event2: sunxi-ir
/dev/input/event3: audiocodec sunxi Audio Jack
/dev/input/event4: soc@3000000:gpio_keys
/dev/input/event5: ns2009_ts
可以使用evtest或hexdump来测试按键是否正常了
在代码使用就比较简单了
#include <errno.h>
#include <global.h>
#include <linux/fcntl.h>
#include <linux/input.h>
#include <signal.h>
#include <stdio.h> //-std=c99 -std=gnu99
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define msleep(t) usleep((t)*1000)
FILE *fp;
int32_t fd_key;
int32_t fd_encoder;
struct input_event t;
int main(int argc, char const *argv[])
{
fd_key = open("/dev/input/event1", O_RDONLY | O_NONBLOCK);
if (fd_key < 0)
{
fprintf(stderr, "error:can not open /dev/input/event1\n");
return -1;
}
fd_encoder = open("/dev/input/event4", O_RDONLY | O_NONBLOCK);
if (fd_encoder < 0)
{
fprintf(stderr, "error:can not open /dev/input/event4\n");
return -1;
}
while (1)
{
// rotary encoder
if (read(fd_encoder, &t, sizeof(t)) == sizeof(t))
{
if (t.type == EV_REL)
{
LOG_D("ROTARY DIR: %d->%s\n", t.value, t.value == 1 ? "CC" : "CW");
rotary_encoder_handler(&u8g2, t.value);
}
}
// key
if (read(fd_key, &t, sizeof(t)) == sizeof(t))
{
if (t.type == EV_KEY)
{
switch (t.code)
{
case KEY_OK:
LOG_D("KEY_ENTER %s \n", t.value ? "Pressed" : "Released");
if (t.value == 0)
rotary_encoder_button_handler(&u8g2);
break;
case KEY_SELECT:
LOG_D("KEY_SELECT %s \n", t.value ? "Pressed" : "Released");
break;
case KEY_VOLUMEUP:
LOG_D("KEY_VOLUMEUP %s \n", t.value ? "Pressed" : "Released");
if (t.value == 0)
{
rotary_encoder_handler(&u8g2, -1);
}
break;
case KEY_VOLUMEDOWN:
LOG_D("KEY_VOLUMEDOWN %s \n", t.value ? "Pressed" : "Released");
if (t.value == 0)
{
rotary_encoder_handler(&u8g2, 1);
}
break;
default:
break;
}
}
}
usleep(1);
}
return 0;
}
板子到手先来点个灯那是少不了的,先确定LED接在哪个引脚上,查看原理图,发现LED的座位号是U13,原来不是普通的LED,是一颗单总线的WS2812灯珠,这颗LED是接在PC0引脚上的,引脚标有LEDC-DO,是接在了LED控制器上,驱动官方已经做好了,我们直接用就可以
下边废话不多说,来看一下系统启动过程,系统已经把驱动给加载好了
[ 2.100847] sunxi_led_probe()1715 - start
[ 2.105375] sunxi_get_str_of_property()1560 - failed to get the string of propname led_regulator!
[ 2.115306] sunxi_register_led_classdev()1448 - led_classdev start
[ 2.126681] sunxi_led_probe()1820 - finish
来查看一下LED子系统下的灯
# ls /sys/class/leds/
blink sunxi_led11g sunxi_led3b sunxi_led5r sunxi_led8g
sunxi_led0b sunxi_led11r sunxi_led3g sunxi_led6b sunxi_led8r
sunxi_led0g sunxi_led1b sunxi_led3r sunxi_led6g sunxi_led9b
sunxi_led0r sunxi_led1g sunxi_led4b sunxi_led6r sunxi_led9g
sunxi_led10b sunxi_led1r sunxi_led4g sunxi_led7b sunxi_led9r
sunxi_led10g sunxi_led2b sunxi_led4r sunxi_led7g
sunxi_led10r sunxi_led2g sunxi_led5b sunxi_led7r
sunxi_led11b sunxi_led2r sunxi_led5g sunxi_led8b
这里出现了很多LED,设备树默认是12个,这样子是不是很熟悉了,我们板子上的LED就是led0
# ls /sys/class/leds/sunxi_led0b/
brightness power uevent
device subsystem waiting_for_supplier
max_brightness trigger
先来个红色的呼吸灯
# echo heartbeat > /sys/class/leds/sunxi_led0r/trigger
再来个蓝色1秒闪烁一次
# echo timer > /sys/class/leds/sunxi_led0b/trigger
这灯真是亮瞎眼啊,查看亮度
# cat /sys/class/leds/sunxi_led0b/brightness
255
调到50,这下好了
# echo 50 > /sys/class/leds/sunxi_led0b/brightness
把灯关掉
# echo none > /sys/class/leds/sunxi_led0b/trigger
查看支持哪些触发方式
# cat /sys/class/leds/sunxi_led0b/trigger
none rc-feedback rfkill-any rfkill-none kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock mmc0 mmc1 timer oneshot [mtd] nand-disk heartbeat backlight gpio cpu cpu0 activity default-on transient netdev pattern audio-mute audio-micmute
当然以上的操作不是操作的GPIO,而是使用的LED子系统,下边是用GPIO操作LED,这里的LED是在扩展板上引出的,引脚是通过PCF8574扩展出来的,具体可以看看D1原理图
使用SYSFS方式操作GPIO,在操作前先查看一下系统里未使用的GPIO,这里以PD18 BL—PWM为例
# ls /sys/class/gpio/
export gpiochip0 gpiochip2020 unexport
# PD18 = 32*3+18=114
# echo 114 > export
执行成功 目录下会出现gpio114,查看一下内容,可以看到里边有direction,value
# ls gpio114
active_low edge uevent
device power value
direction subsystem waiting_for_supplier
接下来设置方向
# echo out > gpio114/direction
输出高电平
# echo 1 > gpio114/value
输出低电平
# echo 0 > gpio114/value
不用的话执行
# echo 114 > unexport
查看一下,已经没有那个引脚导出的目录了
# ls
export gpiochip0 gpiochip2020 unexport
使用GPIOD操作GPIO,先查询一下系统GPIO情况
# gpiodetect
gpiochip0 [2000000.pinctrl] (224 lines)
gpiochip1 [pcf8574] (8 lines)
# gpioinfo gpiochip0 | grep -v unused | grep -v kernel
gpiochip0 - 224 lines:
line 0: unnamed "sysfs" input active-high [used]
line 1: unnamed "sysfs" input active-high [used]
line 5: unnamed "sysfs" input active-high [used]
line 34: unnamed "interrupt" input active-high [used]
line 110: unnamed "soc@3000000:rotary" input active-low [used]
line 111: unnamed "soc@3000000:rotary" input active-low [used]
line 115: unnamed "usb1-vbus" output active-high [used]
line 116: unnamed "otg_det" input active-high [used]
line 117: unnamed "otg_id" input active-high [used]
line 118: unnamed "soc@3000000:ir_send" output active-high [used]
line 144: unnamed "phy-rst" output active-high [used]
line 166: unnamed "cd" input active-high [used]
# gpioinfo gpiochip1
gpiochip1 - 8 lines:
line 0: unnamed "Key volume up" input active-low [used]
line 1: unnamed "Key volume down" input active-low [used]
line 2: unnamed "Key back" input active-low [used]
line 3: unnamed "blink" output active-low [used]
line 4: unnamed "Key enter" input active-low [used]
line 5: unnamed "led" output active-high [used]
line 6: unnamed "reset" output active-low [used]
line 7: unnamed "dc" output active-low [used]
同样使用上边那个未使用的引脚为例 PD18 BL-PWM
输出高电平
# gpioset gpiochip0 114=1
输出低电平
# gpioset gpiochip0 114=0
读取引脚电平
# gpioget gpiochip0 114
1
就这么简单
以上操作还都未涉及到代码,下边开始用代码操作GPIO,代码仅供参考
SYSFS方式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
FILE *p = NULL;
int i = 0;
p = fopen("/sys/class/gpio/export", "w");
fprintf(p, "%d", 38);
fclose(p);
p = fopen("/sys/class/gpio/gpio38/direction", "w");
fprintf(p, "out");
fclose(p);
for (i = 0; i < 100; i++)
{
p = fopen("/sys/class/gpio/gpio38/value", "w");
fprintf(p, "%d", 1);
sleep(1);
fclose(p);
p = fopen("/sys/class/gpio/gpio38/value", "w");
fprintf(p, "%d", 0);
sleep(1);
fclose(p);
}
p = fopen("/sys/class/gpio/unexport", "w");
fprintf(p, "%d", 38);
fclose(p);
return 0;
}
GPIOD方式
#include "gpiod/gpiod.h"
#include <linux/fcntl.h>
#include <signal.h>
#include <stdio.h> -std=c99 -std=gnu99
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#define LED_PIN 148 PD8 32*3+18
#define msleep(t) usleep((t)*1000)
struct gpiod_chip *chip;
struct gpiod_line *line;
void _timer_handler(int n)
{
num++;
gpiod_line_set_value(line, num % 2);
}
int main(int argc, char const *argv[])
{
struct gpiod_line_request_config config;
int req;
/* 1.打开 GPIO 控制器 */
chip = gpiod_chip_open("/dev/gpiochip0");
if (!chip)
return -1;
/* 2.获取指定引脚 */
line = gpiod_chip_get_line(chip, LED_PIN);
if (!line)
{
gpiod_chip_close(chip);
return -1;
}
/* 3.配置引脚输出 */
req = gpiod_line_request_output(line, "blink", 0);
if (req)
{
fprintf(stderr, "gpio line request error.\n");
return -1;
}
signal(SIGALRM, _timer_handler);
struct itimerval itv;
itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 1000;
itv.it_interval.tv_sec = 1;
itv.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, NULL);
while (1)
{
}
return 0;
}
到此点灯结束,感谢观看!
# modprobe xr829.ko
[ 100.391586] ======== XRADIO WIFI OPEN ========
[ 100.398517] [XRADIO] Driver Label:XR_V02.16.84_P2P_HT40_01.31
[ 100.405334] [XRADIO] Allocated hw_priv @ 000000009b3877eb
[ 100.413152] sunxi-rfkill soc@3000000:rfkill@0: bus_index: 1
[ 100.429430] sunxi-rfkill soc@3000000:rfkill@0: wlan power on success
[ 100.636545] [XRADIO] Detect SDIO card 1
[ 100.651420] sunxi-mmc 4021000.sdmmc: sdc set ios:clk 0Hz bm PP pm UP vdd 21 width 1 timing LEGACY(SDR12) dt B
[ 100.673356] sunxi-mmc 4021000.sdmmc: no vqmmc,Check if there is regulator
[ 100.693300] sunxi-mmc 4021000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B
[ 100.717916] sunxi-mmc 4021000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B
[ 100.737273] sunxi-mmc 4021000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B
[ 100.762531] sunxi-mmc 4021000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing SD-HS(SDR25) dt B
[ 100.774088] sunxi-mmc 4021000.sdmmc: sdc set ios:clk 50000000Hz bm PP pm ON vdd 21 width 1 timing SD-HS(SDR25) dt B
[ 100.786258] sunxi-mmc 4021000.sdmmc: sdc set ios:clk 50000000Hz bm PP pm ON vdd 21 width 4 timing SD-HS(SDR25) dt B
[ 100.798775] mmc1: new high speed SDIO card at address 0001
[ 100.806454] [SBUS] XRadio Device:sdio clk=50000000
[ 100.872826] [XRADIO] XRADIO_HW_REV 1.0 detected.
[ 100.881593] xradio_wlan mmc1:0001:1: Direct firmware load for sdd_xr829.bin failed with error -2
[ 100.891437] [XRADIO_ERR] xradio_parse_sdd: can't load sdd file sdd_xr829.bin.
[ 100.899420] [XRADIO_ERR] xradio_load_firmware failed(-2).
[ 100.906159] sunxi-rfkill soc@3000000:rfkill@0: wlan power off success
使用的是韦老师的【极简操作】使用builroot 2021一键编译生成D1 nezha 系统镜像!
目前编译完驱动,加载时找不到sdd_xr829.bin buildroot里好像没有,谁能提供一份XR829的固件 sdd_xr829.bin,谢谢!
不外接codec 使用I2S2 我测试是正常的 虽然pcm512x有硬件模式 但这样不太方便 如果外挂codec 启动时会出错 但从图片上看pcm512x配置应该是正常的 也能通信 但是I2S却没输出了
扩展板资源:
1.一路IR-RX(DHT22)
2.一路IR-TX
3.一路IIC OLED 1.3“
4.一路RotaryRncoder
5.一路SPI LCD 240x320 2.4”
6.一路触摸IC TS2009
7.一路I2S + PCM5121 384KHz DAC
8.四路按键
Neza-D1_expand_board.pdf
板子已经JLC打样,调试完成后全部放上来
@haoxuan-cui 我的编译出来也是这么大 系统可以正常使用 可能是编译了qt进去了才比较大
@whycan 这么一捋 第一条就不满足了whycan.com贴算不?
@whycan 想报名音乐播放器 可惜看到时已经错过日期了 不知道还能报名不?