1. 前言
s5pv210之路(3) — 编译环境文章中我们能够编译一个固件烧写到板子中运行,本章我们解锁一个更便捷的启动方式。
2. DNW
2.1 下载编译
s5pv210的ROM支持UART/USB下载,需要在虚拟机上编译一个DNW驱动。GitHub上有一份USB的DNW的驱动源码。下载后解压,进入dnw-linux-master
执行make
,发现编译错误:
经过和这份Qunero/dnw4linux驱动对比,修改dnw-linux-master/src/driver/Makefile
文件,注释掉secbulk-m := secbulk.o
这一行,再次编译即正常。
2.2 查看USB ID
硬件配置:
- 选择USB启动模式
- 连接OTG到电脑
- 长按POWER开机,此时电脑会检测到有USB接入,松开POWER键USB设备会消失
一直按着POWER键,将设备连接至虚拟机。
使用dmesg
查看系统日志,其中末尾的地方有我们刚刚连接USB的日志。可以看到USB的厂商ID为0x04E8
,产品ID为0x1234
。
2.3 修改驱动
- USB ID数据定义在
src/driver/secbulk.c
文件中,打开这个文件发现它已经定义了{ USB_DEVICE(0x04e8, 0x1234) }, /* EZ6410 */
我们就不用添加了。 - 修改
dnw.rules
文件,参考Linux udev规则编写,在文件中每行末尾增加, GROUP="dialout"
,再次安装sudo make install
覆盖dnw.rules
,这样设备加载时/dev/secbulk0
的用户组就是dialout
了。
SUBSYSTEMS=="usb", ATTRS{idVendor}=="5345", ATTRS{idProduct}=="1234", RUN+="/sbin/modprobe secbulk", GROUP="dialout"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="04e8", ATTRS{idProduct}=="1234", RUN+="/sbin/modprobe secbulk", GROUP="dialout"
2.4 修改dnw
参考[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)可知S5PV210的默认启动位置是0xD002_0000
,所以修改src/dnw/dnw.c
,将0x57e00000
修改为0xd0020000
。
2.5 编译安装
修改完毕后编译,然后执行安装即可,涉及到驱动安装需要管理员权限。
make
sudo make install
安装完毕后,再次使用虚拟机连接设备,使用dmesg
查看日志,可看到系统已经识别设备为secbulk
。
使用ls -l /dev/sec*
可以看到设备文件。
2.6 修改用户组
查看本地用户的用户组,并添加本地用户到dialout
用户组。
$ groups
xflm adm cdrom sudo dip plugdev lpadmin lxd sambashare
$ sudo usermod -a -G dialout xflm
$ sudo gpasswd -a xflm dialout # 这条命令和上面一条作用一致
$ groups # groups的结果并没有更新,即没有生效
xflm adm cdrom sudo dip plugdev lpadmin lxd sambashare
$ groups xflm # 已经加入了
xflm : xflm adm dialout cdrom sudo dip plugdev lpadmin lxd sambashare
# 注销桌面重新登录后发现groups仍未生效,在终端使用su命令重新登录
$ su xflm # su命令重新登录
Password:
$ groups # 已生效
xflm adm dialout cdrom sudo dip plugdev lpadmin lxd sambashare
# ubuntu桌面注销可能注销不完全,还是重启下吧
# 将用户移除用户组,也需要重新登录后生效
$ sudo gpasswd -d xflm dialout
至此本地用户可以直接访问/dev/secbulk0
设备了,也即dnw
不需要使用sudo
提权了。
2.5 修改工程
x210v3裸机开发教程\src\buzzer
工程的链接脚本需要修改rom
ram
的地址以及STACK_SRV_SIZE
的大小,如下。
/*STACK_SRV_SIZE = 0x8000;*/
STACK_SRV_SIZE = 0x1000;
MEMORY
{
/*rom (rx) : org = 0x34000000, len = 0x02000000*/ /* 32 MB */
/*ram (rwx) : org = 0x36000000, len = 0x0a000000*/ /* 160 MB */
rom (rx) : org = 0xd0020000, len = 0x00002000
ram (rwx) : org = 0xd0022000, len = 0x00002000
}
链接脚本修改后重新编译,编译的bin文件可直接使用dnw
程序下载,也即DNW下载需要16字节文件头,但不关心文件头的数据。将bin使用mkv210
程序处理,得到的镜像仍可用于SD卡烧录。
2.6 DNW下载
虚拟机连上设备后,执行dnw output/buzzer.bin
即可完成下载,下载过程中需要按着电源键,下载完成后程序会运行起来,此时可以松开电源键,若需要重新下载则需要复位板卡重来。
3. 串口自动控制电路
DNW下载很方便但是每次下载需要先复位,然后按住POWER按键,下载的程序运行后才能松开POWER按键,考虑做个自动下载控制电路。开发板使用的是RS232 9针接口,实际通信只用到Tx/Rx两根线,我们可以考虑使用DTR
RTS
两个信号线控制板卡复位以及POWER保持。
3.1 原理分析
- RS232是±12V电平通信,市面上USB转RS232线多为±5V或±9V电平,都能正常通信。
- 板卡的复位键是低电平复位,该复位键一边接地,另一边直接连接核心板,核心板电路如下,并通过R198(0欧)连接到U17网口PHY的复位引脚上。
- 下面是主板电源电路,途中Q5是P沟道增强型MOSFET,1脚低电平场管导通,MP1482使能,主板上电,1脚高电平场管截至,主板掉电。
3.2 电路设计
参考ESP8266/ESP32自动下载电路原理分析实现自动控制电路,真值表如下。
DTR | RTS | 复位 | 电源 |
---|---|---|---|
0 | 0 | 高阻 | 高阻 |
0 | 1 | 0(复位) | 高阻 |
1 | 0 | 高阻 | 0(上电) |
1 | 1 | 高阻 | 高阻 |
主板的串口电路如下,DTS和RTS没有进行电平变换,也即DTS/RTS的低电平是-5V/-9V
,直接使用这个电平会有问题,又不想增加个电平转换芯片,就只能使用电阻分压。
由于事情过去一段时间了,部分参数记不清了,这里给出一个大致的图,部分地方的电阻可以更大,可以在±9V和±5V上寻找一个平衡,两者都可以使用,图中电源不是电源按键,是Q5场管的1号脚,复位是连接复位按键。
3.3 实物图
我这里选择了UART2,ROM也使用UART2进行DNW下载,实物图如下。
4. 下载程序
经测试这个电路能够满足自动下载电路的需求了,但是距离自动下载还差一步,即编写个程序自动完成串口操作。
4.1 串口控制
参考Linux串口调试详解编写串口控制代码。开发板选择USB启动时,按下电源键UART2会打印如下图。Ubuntu下安装puttysudo apt install putty
,打开串口/dev/ttyUSB0
,波特率115200。
4.2 源码
可以通过串口日志判断设备进入DNW模式,Linux下串口设备文件默认可以共享,若有多个程序读串口数据,串口数据会随机传输到任一接收者,其他读取者将丢失数据,考虑到串口复用问题,故采用采用判断/dev/secbulk0
文件是否存在,然后调用dnw
程序进行下载,此处串口控制程序仅控制DTR/RTS的电平,不读数据。另外,下载的程序运行后/dev/secbulk0
设备文件还存在,直至OTG重新注册才会消失,由于虚拟机挂载设备比较慢,再次下载时会存在问题,故程序执行复位后先判断/dev/secbulk0
是否存在,若存在则等待它消失,然后再等待它可用,依此判断设备重新连接了。
adnw.c
#include <stdio.h> #include <stddef.h> #include <stdint.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> #include <getopt.h> #include <termios.h> #include <sys/ioctl.h> /* /usr/include/asm-generic/ioctls.h */ #define TIOCMGET 0x5415 #define TIOCMSET 0x5418 /* /usr/include/asm-generic/termios.h */ #define TIOCM_LE 0x001 #define TIOCM_DTR 0x002 #define TIOCM_RTS 0x004 #define TIOCM_ST 0x008 #define TIOCM_SR 0x010 #define TIOCM_CTS 0x020 #define TIOCM_CAR 0x040 #define TIOCM_RNG 0x080 #define TIOCM_DSR 0x100 #define TIOCM_CD TIOCM_CAR #define TIOCM_RI TIOCM_RNG #define TIOCM_OUT1 0x2000 #define TIOCM_OUT2 0x4000 #define TIOCM_LOOP 0x8000 /* 1(DCD) 2(RXD) 3(TXD) 4(DTR) 5(GND) * 6(DSR) 7(RTS) 8(CTS) 9(RI) */ char buf[1024]; const char *selfname; const char *device = NULL; const char *find_device(void) { static char devicename[32]; DIR *dir; struct dirent *p; const char *ret = NULL; strcpy(devicename, "/dev/"); dir = opendir(devicename); if(dir == NULL) { perror(devicename); goto RETURN; } while((p = readdir(dir)) != NULL) { if((p->d_type == DT_CHR) && (memcmp(p->d_name, "ttyUSB", 6) == 0) && (strlen(p->d_name) <= (sizeof(devicename)-strlen(devicename)-1))) { strcat(devicename, p->d_name); ret = devicename; break; } } RETURN: closedir(dir); return ret; } void print_modem(int fd) { int status, ret; ret = ioctl(fd, TIOCMGET, &status); if(ret < 0) { perror(device); return; } printf("DSR:%08x\n", status & TIOCM_LE); printf("DTR:%08x\n", status & TIOCM_DTR); printf("RTS:%08x\n", status & TIOCM_RTS); printf("TXD:%08x\n", status & TIOCM_ST); printf("RXD:%08x\n", status & TIOCM_SR); printf("CTS:%08x\n", status & TIOCM_CTS); printf("DCD:%08x\n", status & TIOCM_CAR); printf("RI: %08x\n", status & TIOCM_RI); printf("DSR:%08x\n", status & TIOCM_DSR); } int set_dtr_rts(int fd, int dtr, int rts) { int status; int ret; ret = ioctl(fd, TIOCMGET, &status); if(ret < 0) { perror(device); return -1; } if(dtr == 0) status &= ~TIOCM_DTR; else if(dtr > 0) status |= TIOCM_DTR; if(rts == 0) status &= ~TIOCM_RTS; else if(rts > 0) status |= TIOCM_RTS; ret = ioctl(fd, TIOCMSET, &status); if(ret < 0) { perror(device); return -1; } return 0; } typedef enum { OPT_BINFILE = 0x62, /* 'b' */ OPT_DEVICE = 0x64, /* 'd' */ OPT_HELP = 0x68, /* 'h' */ } opt_e; const struct option longopts[] = { {"binfile", required_argument, NULL, OPT_BINFILE}, {"device", required_argument, NULL, OPT_DEVICE}, {"help", no_argument, NULL, OPT_HELP}, {0}, }; const char optstring[] = "b:d:h"; void help(FILE *fp) { fprintf(fp, "Usage: %s [option]\n" " -b the binary of s5pv210 has a 16-type header, and the " "program ectry address is 0xD0020010\n" " --binfile\n" " -d the device node of serial. If it doesn't exist, " "the program will automatically determine\n" " --device\n" " -h show this message\n" " --help\n" , selfname); } int main(int argc, char *argv[]) { int fd; // int ret; int opt; ssize_t retlen; // struct termios tm; const char *binfile = NULL; FILE *fpp; selfname = argv[0]; while((opt = getopt_long(argc, argv, optstring, longopts, NULL)) > 0) { switch(opt) { case OPT_BINFILE: binfile = optarg; break; case OPT_DEVICE: device = optarg; break; case OPT_HELP: help(stdout); return 0; case '?': help(stderr); return -1; } } if(device == NULL) { device = find_device(); if(device == NULL) { fprintf(stderr, "not find device device\n"); return -1; } } if(binfile == NULL) { fprintf(stderr, "no binfile\n"); return -1; } fd = open(device, O_RDWR | O_NOCTTY); if(fd < 0) { perror(device); return -1; } // retlen = tcgetattr(fd, &tm); // if(retlen < 0) // { // perror(device); // return -1; // } // // cfsetispeed(&tm, B115200); // cfsetospeed(&tm, B115200); // tm.c_cflag &= ~CSIZE; // tm.c_cflag |= CS8; // tm.c_cflag |= CREAD; // tm.c_cflag &= ~CSTOPB; // tm.c_cflag &= ~PARENB; // tm.c_cflag &= ~HUPCL; // tm.c_cflag &= ~CRTSCTS; // tm.c_iflag &= ~INPCK; // tm.c_iflag &= ~IXON; // // tcflush(fd, TCIFLUSH); // // ret = tcsetattr(fd, TCSANOW, &tm); // if(ret < 0) // { // perror(device); // return -1; // } set_dtr_rts(fd, 0, 1); /* reset */ usleep(10000); set_dtr_rts(fd, 1, 0); /* power on */ if(access("/dev/secbulk0", W_OK) != -1) { while(access("/dev/secbulk0", W_OK) != -1); } while(access("/dev/secbulk0", W_OK) == -1); usleep(100000); strcpy(buf, "dnw "); if(strlen(binfile) > (sizeof(buf) - strlen(buf) -1)) { fprintf(stderr, "binfile name is too long\n"); return -1; } strcat(buf, binfile); fpp = popen(buf, "r"); if(fpp == NULL) { perror(buf); return -1; } retlen = fread(buf, 1, sizeof(buf)-1, fpp); buf[retlen] = '\0'; if(strstr(buf, "speed") == NULL) { fprintf(stderr, buf); return -1; } set_dtr_rts(fd, 1, 1); /* release */ pclose(fpp); close(fd); return 0; }
编译运行
gcc adnw.c -o adnw ./adwn -b output/buzzer.bin ./adwn -b output/buzzer.bin # 可重复下载
5. 插曲
5.1 sudo命令找不到
我将dnw
的安装路径改为了/opt/bin
,因为/dev/secbulk0
是root权限,需要使用sudo
提权,但是出现以下错误。
$ sudo dnw output/buzzer.bin
sudo: dnw: command not found
经查找是因为/opt/bin
路径不在sudo
的查找范围内。参考执行sudo命令时command not found的解决办法解决。
- 在/etc/sudoers文件内增加这么一行:Defaults secure_path=”/bin:/usr/bin:/usr/local/bin:…”, 把要用的命令path包括进去。
- 用命令的绝对路径。3. 使用sudo的env选项,像这样sudo env PATH=$PATH cmd.sh。
- 把脚本拷贝或链接到系统$PATH中。
5.2 修改/dev/secbulk0
的权限
修改dnw.rules
文件,在文件中每行末尾增加, MODE="0666"
,再次安装sudo make install
覆盖dnw.rules
,这样设备加载时任意用户均可访问该设备。
SUBSYSTEMS=="usb", ATTRS{idVendor}=="5345", ATTRS{idProduct}=="1234", RUN+="/sbin/modprobe secbulk", MODE="0666"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="04e8", ATTRS{idProduct}=="1234", RUN+="/sbin/modprobe secbulk", MODE="0666"
6. 参考
在Linux中安装dnw
[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)
Linux串口编程
Linux串口调试详解
ESP8266/ESP32自动下载电路原理分析
执行sudo命令时command not found的解决办法