s5pv210之路(4) --- 一键自动烧录

1. 前言

s5pv210之路(3) — 编译环境文章中我们能够编译一个固件烧写到板子中运行,本章我们解锁一个更便捷的启动方式。

2. DNW

2.1 下载编译

s5pv210的ROM支持UART/USB下载,需要在虚拟机上编译一个DNW驱动。GitHub上有一份USB的DNW的驱动源码。下载后解压,进入dnw-linux-master执行make,发现编译错误:
DNW编译错误
经过和这份Qunero/dnw4linux驱动对比,修改dnw-linux-master/src/driver/Makefile文件,注释掉secbulk-m := secbulk.o这一行,再次编译即正常。

2.2 查看USB ID

硬件配置:

  1. 选择USB启动模式
  2. 连接OTG到电脑
  3. 长按POWER开机,此时电脑会检测到有USB接入,松开POWER键USB设备会消失
    硬件配置步骤

一直按着POWER键,将设备连接至虚拟机。
虚拟机连接设备
使用dmesg查看系统日志,其中末尾的地方有我们刚刚连接USB的日志。可以看到USB的厂商ID为0x04E8,产品ID为0x1234
dmesg日志

2.3 修改驱动

  1. USB ID数据定义在src/driver/secbulk.c文件中,打开这个文件发现它已经定义了{ USB_DEVICE(0x04e8, 0x1234) }, /* EZ6410 */ 我们就不用添加了。
  2. 修改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*可以看到设备文件。
设备文件diaout用户组

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 原理分析

  1. RS232是±12V电平通信,市面上USB转RS232线多为±5V或±9V电平,都能正常通信。
  2. 板卡的复位键是低电平复位,该复位键一边接地,另一边直接连接核心板,核心板电路如下,并通过R198(0欧)连接到U17网口PHY的复位引脚上。
    核心板复位电路
  3. 下面是主板电源电路,途中Q5是P沟道增强型MOSFET,1脚低电平场管导通,MP1482使能,主板上电,1脚高电平场管截至,主板掉电。

电源电路

3.2 电路设计

参考ESP8266/ESP32自动下载电路原理分析实现自动控制电路,真值表如下。

DTRRTS复位电源
00高阻高阻
010(复位)高阻
10高阻0(上电)
11高阻高阻

主板的串口电路如下,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。
UART打印

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的解决办法解决。

  1. 在/etc/sudoers文件内增加这么一行:Defaults secure_path=”/bin:/usr/bin:/usr/local/bin:…”, 把要用的命令path包括进去。
  2. 用命令的绝对路径。3. 使用sudo的env选项,像这样sudo env PATH=$PATH cmd.sh。
  3. 把脚本拷贝或链接到系统$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的解决办法

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值