穷孩子不用Lens Stations照样更新SAMYANG镜头固件
m24h2022/08/04原创 软件综合 IP:上海
关键词
三阳森养固件
SAMYANGFirmwareLens Station

我有个特别喜欢的镜头森养(这不是保健品品牌)AF45/1.8 Sony FE接口的 原固件版本V5

最近发现森养这个镜头更新固件了 新版本是V6 虽然没有什么大的改进 但是不更新就不舒服斯基

我不想买那个更新底座 一个底座就要三五百 这个镜头本身就便宜 我也没打算买其他更贵的镜头

lens-station.jpg

所以我就研究了镜头的固件和他用来更新镜头的Lens Manager 感谢IDA 感谢dnSpy 感谢PulseView 反正基本搞清楚它们的协议和流程 

本来想用个小MCU实现中间的协议转换和转发 换句话说就是自己做个Lens Station (而且Lens Station也肯定就是这么干的)

然后发现不用那么麻烦 感谢ch340g 感谢COM0COM 我编写了一个脚本就OK了

框图如下

block-diagram.png

镜头和CH340G连接如下

lens-connect-ch340g.png

实际工作现场如下(现实总是这么凌乱)(我舍弃了一个微距转接环来做镜头座 因为没有大用 而且弹簧触点零买也得几十了)

work.jpg

终于成功连上了 实现了固件更新 对焦微调(结果发现不调最好) 现在舒服斯基了

connected.jpg

fw-updating.jpg

af-adjust.jpg

脚本贴在最后头 我先吐槽一下Sony的协议是又臭又长 SAMYANG的协议更是凌乱不堪毫无统一 

然后提醒一下 除了给镜头提供3.1V(其实3.3V也可以 但是Sony标准是3.1V)逻辑电压 还要提供5V的镜头驱动电压 不然有些功能需要镜头机械复位才能继续(我认为没有必要 但是固件是这样流程了 而且还有很多Lens Manager披露之外的微调和Debug功能需要镜头进行机械动作) 电压要稳 不然ch340G可能收到干扰失联

再提醒一下 由于协议是脚本转换/转发 还串过了三个串口 速度不太快 有时候会导致超时 尤其是先擦除镜头flash的那段 我的做法是第一次擦除 等超时Lens Manager断掉后 改脚本SKIP_FLASH_CLEAN=True 之后Lens Manager重新再来进行第二次固件更新 因为假装马上擦除成功(其实第一次已经擦好了) 就不会超时了

最后的提醒 我只有这么一个镜头 所以它需要的功能我实现了 至于其他镜头 比如某些高级镜头支持个性化了 有些功能我随便写了没法检验 有些功能无法保证(比如我只有一个镜头 bootloader版本如果比较低 回应我没有办法确定是什么) 有些我就没写下去了 谁想拿来用 风险自负 功能自加

import serial
import sys, threading, time, struct
import signal

# just emulate am AF45/1.8, no need to connect to a real lens
EMULATE_AF4518=False
# don't do firmware downloading/updating, just want to adjust some parameters
DONT_TOUCH_FW=True
# if last firmware updating failed, lens stays in bootloading
FIXFW=False
# when downloding, flash cleaning will result in timeout of Lens Manager
# this can be set only when flash cleaning is done before at least once 
SKIP_FLASH_CLEAN=False
# when downloding, packets printing will result in timeout of Lens Manager
PRINT_FW_DATA=True  

PORT_LENS="COM4"
PORT_BROKER="COM5"

'''
	to use this script, a back-to-back virtual serial port pair is needed. 
	for example, a virtual serial port pair like COM5<->COM6 can be setup by "COM0COM" tool,
	then let "Sanyang Lens Manager" open COM6, 
	and this script should open COM5 and listen requests, 
	translate/transfer to lens, translate/transfer response back to COM5.
	
	to connect to a real lens, a USB-serial hardware is needed, and it should support 750kHz baudrate, like ch340G.
	face to the tail of lens (on which side electronic pins exist),
	rotate the lens keeping the electronic pins on bottom side,
	then the pins from left to right are (excluding 2 fake pins on the most left side):
		Gnd, Vcc-Drive, Gnd, body_vd_lens, Vcc, lens_cs_body, lens_data_body, body_data_lens, body_cs_lens, lens_detect
	connections from USB-serial hardware to lens:
		Gnd <-> Gnd
		5V   <-> Vcc-Drive
		Gnd <-> Gnd
		DTR <-> body_vd_lens
		3.1V <-> Vcc
		RXD <-> lens_data_body
		TXD <-> body_data_lens
		RTS <-> body_cs_lens

	a stable 5V Vcc-Drive is needed, some functions need to reset focus/aperture of lens before going on.

'''


ser_lens=None
ser_broker=None
vd_sync=False
prn_pkt=True

def vd():
	while True:
		if (not EMULATE_AF4518) and vd_sync:
			ser_lens.dtr=1
			ser_lens.dtr=0
		time.sleep(0.02)
				
def bsend(b):
	s=b'\x02'+b+b'\x0D\x0A'
	ser_broker.write(s)
	if prn_pkt:
		print ('B S:', s.hex(' '))
	
def brecv():
	global prn_pkt
	ser_broker.read_until(b'\x02')
	s=ser_broker.read_until(b'\x0D\x0A')
	if s[2:3]==b'\xF0':
		size=struct.unpack('<H',s[3:5])[0]
		if len(s)<size+2+2:
			s+=ser_broker.read(size+2+2-len(s))
	if s[0:2]==b'B\x03':
		prn_pkt=PRINT_FW_DATA 
	else:
		prn_pkt=True
	if prn_pkt:
		print('============================')
		print ('B R: 02', s.hex(' '))	
	return s		

def lsend(b, size=0, type=2, seq=0):
	if size>0:
		b+=b'\x00'*(size-len(b))
	else:
		size=len(b)
	size+=1+4+3
	cksum=(size>>8)+(size & 0xff)+type+seq
	for x in b:
		cksum+=x
	s=struct.pack('<BHBB', 0xF0, size, type, seq)+b+struct.pack('<HB',cksum,0x55)
	ser_lens.rts=0
	ser_lens.write(s)
	ser_lens.rts=1
	if prn_pkt:
		print ('L S:', s.hex(' '))

def lsendB(cmd1, s):
	if s[0:1]==b'\xF0':
		size=struct.unpack('<H', s[1:3])[0]
		s=s[0:size]
		ser_lens.rts=0
		ser_lens.write(s)
		ser_lens.rts=1
		if prn_pkt:
			print ('L S:', s.hex(' '))
	else:	
		lsend(b'\x40B'+cmd1, 11)

def lrecv(wait=b'', timeout=None):
	while True:
		ser_lens.timeout=timeout
		som=ser_lens.read_until(b'\xF0')
		ser_lens.timeout=None
		if len(som)<1:
			return None
		head=ser_lens.read(4)
		(size, type, seq)=struct.unpack("<HBB", head)
		if size<9:
			continue
		s=ser_lens.read(size-8)
		tail=ser_lens.read(2)
		cksum=struct.unpack("<H", tail)[0]
		eom=ser_lens.read(1)
		if eom!=b'\x55':
			continue
		if prn_pkt:
			print ('L R: F0', head.hex(' '), s.hex(' '), tail.hex(' '), eom.hex())
		if s.startswith(wait):
			return s

def b2l2b():
	global vd_sync

	while True:
		s=brecv()
		cmd=s[0:1]
		s=s[1:len(s)-2]
		
		if cmd==b'M':  #20,1,35,3
			print ("--- lens model get ---")
			if EMULATE_AF4518:
				bsend(b'M45')
				continue
			lsend(b'\x40M', 19)
			s=lrecv(b'\x40M')
			bsend(b'M%d' % s[3])
			
		elif cmd==b'K': #20,1,39,5
			print ("--- product ID get ---")
			if EMULATE_AF4518:
				bsend(b's12345678\x00')
				continue			
			lsend(b'\x40K\xFA', 19)
			s=lrecv(b'\x40K\xFA')
			bsend(s[3:13])
			
		elif cmd==b'V': #20,1,37,4
			print ("--- lens firmware version get ---")
			if EMULATE_AF4518:
				bsend(b'V0101')
				continue			
			lsend(b'\x40V', 19)
			s=lrecv(b'\x40V')
			bsend(b'V%02d%02d' % (s[3],s[4]))
						
		elif cmd==b'G':  # 20,12,32,15
			print ("--- dock firmware version get ---") 
			bsend(b'G100')
			
		elif cmd==b'X':
			cmd1=s[0:1]
			s=s[1:]
			if cmd1==b'4': #20,13,32,64,F1
				print ("--- enter bootloader ---")
				if EMULATE_AF4518 or DONT_TOUCH_FW:
					bsend(b'A')
					continue				
				lsend(b'\x40X4'+struct.pack('B', int(s[1:])), 19)
				bsend(b'A')
			elif cmd1==b'2':  #20,1,32,2   20,2,32,2  20,22,,2
				print ("--- lens reset 2---")
				if EMULATE_AF4518:
					bsend(b'A')
					continue
				lsend(b'\x40X2', 19)
				bsend(b'A')
			elif cmd1==b'B':  #20,1,33,1
				print ("--- lens reset B ---")
				if EMULATE_AF4518:
					bsend(b'A')
					continue
				lsend(b'\x40XB', 19)
				bsend(b'A')
			else:
				print ("!!! X unknown %02x !!!" % cmd1[0])
				bsend(b'F')
			
		elif cmd==b'F':
			cmd1=s[0:1]
			s=s[1:]
			vd_sync=True
			if cmd1==b'5':
				if len(s)>0:  #20,4,32,7 20,17,34,17 20,17,36,18  20,17,38,19  20,17,40,20
					print ("--- AF Punt set ---")
					if EMULATE_AF4518:
						bsend(b'A')
						continue
					lsend(b'\x40F\xCB'+struct.pack('B', int(s[1:])), 19)
					t=lrecv(b'\x40F\xCB', timeout=1)
					bsend(b'A')
				else:    #20,1,41,6   20,3,32,6  20,16,34,6
					print ("--- AF punt get ---") 
					if EMULATE_AF4518:
						bsend(b'1')
						continue
					lsend(b'\x40F\xCA', 19)
					t=lrecv(b'\x40F\xCA', timeout=1)
					bsend(b'%d' % t[3])
			elif cmd1==b'6':
				if len(s)>0: #20,6,32,9
					print ("--- MF sense set ---")
					if EMULATE_AF4518:
						bsend(b'A')
						continue					
					lsend(b'\x40F\xBB'+struct.pack('B', int(s[1:])), 19)
					t=lrecv(b'\x40F\xBB', timeout=1)
					bsend(b'A')
				else:  #20,1,45,8 20,5,32,8
					print ("--- MF sense get ---")
					if EMULATE_AF4518:
						bsend(b'0')
						continue					
					lsend(b'\x40F\xBA', 19)
					t=lrecv(b'\x40F\xBA', timeout=1)
					bsend(b'%d' % t[3])
			elif cmd1==b'\x21':
				if len(s)>0:  #20,16,32,23  20,17,32,23
					print ("!!! Zoom Pos Punt set -- not implemented !!!")
					bsend(b'F')
				else:    #
					print ("!!! Zoom punt get -- not implemented !!!") 
					bsend(b'0')
			else:
				print ("!!! F unknown %02x !!!" % cmd1[0])
			vd_sync=False
	
		elif cmd==b'B':
			cmd1=s[0:1]
			s=s[1:]			
			if cmd1==b'\x0B':  #20,13,32,66,F2
				print ("--- firmware reset ---")
				if (not FIXFW) and (EMULATE_AF4518 or DONT_TOUCH_FW):
					bsend(b'\x0B\x04')
					continue					
				lsendB(cmd1, s) 
				bsend(b'\x0B\x04')
			elif cmd1==b'\x0A':  #20,13,32,68,F3
				print ("--- firmware update prepare ---")
				if (not FIXFW) and (EMULATE_AF4518 or DONT_TOUCH_FW):
					bsend(b'\x0A\x04')
					continue					
				lsendB(cmd1, s) 
				bsend(b'\x0A\x04')
			elif cmd1==b'\x01':  #20,13,32,70,F4  a F0..40 'B' 01 ..55  get bootloader version
				print ("--- bootloader version get ---")
				if (not FIXFW) and (EMULATE_AF4518 or DONT_TOUCH_FW):
					bsend(b'\x10\x00\x10\x02\x04')
					continue					
				lsendB(cmd1, s) 
				s=lrecv(b'\x40X\x01')
				bsend(b'\x10\x00\x10'+s[3:4]+b'\x04')
			elif cmd1==b'\x02':  #20,13,33,64,F5  a F0..40..55
				print ("--- firmware flash cleaning ---")
				if SKIP_FLASH_CLEAN or (not FIXFW) and (EMULATE_AF4518 or DONT_TOUCH_FW):
					bsend(b'\x02\x04')
					continue	
				lsendB(cmd1, s)
				s=lrecv(b'\x40X\x02')
				bsend(b'\x02\x04')
			elif cmd1==b'\x03':  #20,13,34,64,F6  a F0..15..55 with firmware data
				print ("--- firmware hex data downloading ---")
				if (not FIXFW) and (EMULATE_AF4518 or DONT_TOUCH_FW):
					bsend(b'\x03\x04')
					continue
				lsendB(cmd1, s)
				s=lrecv(b'\x15')
				bsend(b'\x03\x04')
			elif cmd1==b'\x05':  #20,13,35,64,F7  a F0..40..55
				print ("--- exit bootloader ---")
				if (not FIXFW) and (EMULATE_AF4518 or DONT_TOUCH_FW):
					bsend(b'\x05\x04')
					continue	
				lsendB(cmd1, s) 
				bsend(b'\x05\x04')
			else:
				print ("!!! B unknown %02x !!!" % cmd1[0])
				
		elif cmd==b'P':
			if len(s)==0:  #20,15,32,21
				print ("--- custom mode get ---") 
				if EMULATE_AF4518:
					bsend(b'\x00\x00\x00\x00\x00\x00\x00\x01')
					continue					
				lsend(b'\x40P\xFA')
				s=lrecv(b'\x40P\xFA', timeout=1)
				bsend(t[3:11])
			else:
				cmd1=s[0:1]
				s=s[1:]
				if  cmd1==b'8':  #20,18,32,22
					print ("--- custom mode set ---")
					if EMULATE_AF4518:
						bsend(b'A')
						continue					
					lsend(b'\x40P\x38'+struct.pack('B', int(s[1:])+0x30), 19)
					t=lrecv(b'\x40F\x38', timeout=1)
					bsend(b'A')
				else:
					print ("!!! P unknown %02x !!!" % cmd1[0])
					bsend(b'F')
								
		elif cmd==b'I':
			cmd1=s[0:1]
			s=s[1:]
			if cmd1==b' ': #20,8,32,10
				print ("!!! IRIS offset reset -- not implemented !!!")
				bsend(b'F')
			elif cmd1==b'!': #20,10,32,11
				print ("!!! IRIS offset +1 -- not implemented !!!")
				bsend(b'F')
			elif cmd1==b'"': #20,11,32,12
				print ("!!! IRIS offset -1 -- not implemented !!!")
				bsend(b'F')
			elif cmd1==b'#': #20,9,32,13
				print ("!!! IRIS offset save to user config -- not implemented !!!")
				bsend(b'F')
			elif cmd1==b'$':   # 20,1,43,14  #20,7,34,14
				print ("!!! IRIS offset get -- not implemented !!!")
				bsend(b'0')
			elif cmd1==b'2':   # 20,7,32,16
				print ("!!! IRIS offset get -- not implemented !!!")
				bsend(b'0')
			else:
				print ("!!! I unknown %02x !!!" % cmd1[0])
				bsend(b'F')
				
		else:
			print ("!!! Cmd unknown %02x !!!" % cmd[0])
			bsend(b'F')

if __name__ == '__main__':
	try:
		#open serial port
		ser_broker=serial.Serial(
			port=PORT_BROKER,
	    	baudrate=115200,
			bytesize=serial.EIGHTBITS,
			parity=serial.PARITY_NONE,
			stopbits=serial.STOPBITS_ONE
		)

		if FIXFW or not EMULATE_AF4518:
			ser_lens=serial.Serial(
				port=PORT_LENS,
		    	baudrate=750000,
				bytesize=serial.EIGHTBITS,
				parity=serial.PARITY_NONE,
				stopbits=serial.STOPBITS_ONE
			)
			time.sleep(0.5)
			#send a start sequence to RTS
			ser_lens.rts=1
			time.sleep(0.1)
			ser_lens.rts=0
			time.sleep(0.1)
			ser_lens.rts=1
			ser_lens.dtr=0
	except Exception as e:
		print("failed to open serial ports:",e)
		exit(1)
  
   #make thread
	thr_b2l2b=threading.Thread(target=b2l2b, daemon=True)
	thr_vd=threading.Thread(target=vd, daemon=True)
	thr_b2l2b.start()
	thr_vd.start()
	
	#waitr for CTRL-C
	print ("Press CTRL-C to quit.")
	try:
		while True:
			time.sleep(1)
	except KeyboardInterrupt as e:
		pass

	sys.exit(0)


[修改于 2年4个月前 - 2022/08/04 15:10:09]

来自:计算机科学 / 软件综合
3
4
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
机器懒猫
2年4个月前 IP:四川
906549

我觉得写这么多行,值350了

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
m24h作者
2年4个月前 IP:上海
906622
引用机器懒猫发表于1楼的内容
我觉得写这么多行,值350了

哎 我原本以为是简单的桥接。。。

后来发现 实在是每个命令响应都那么独特

有些命令是字符 有些是二进制 响应也一样 甚至有些结果从字节0开始 有些从字节1开始

如果以为命令行是\r\n结尾就能按行处理 里面还会有二进制的固件内容

一般程序里的参数吧 获取和设置是一致的 但是居然这里就有获取一个参数 想设置回去得先加48

甚至还有些参数 读4-5次 结果是轮换的 都不一样 读几次才读完

有些命令要等响应 有些没有 有些命令正常那么长 进入bootloader后又是一个长度 正常情况镜头会响应相同的命令字 但是有时候B命令的响应是X 而且长度还是两种规则

至于镜头类型 设置方式更完全不同 比如AF微调 有些镜头1表明原点 有些是4 如果不是lens manager自己承担了许多混沌的逻辑 我就甩开它直接用命令行微调和加载固件了

这是我见过最混乱的程序了

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
gtf216
2年3个月前 IP:山东
906685

程序可能是故意加密的

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
上级专业
同级专业
m24h
进士 学者 机友
文章
52
回复
896
学术分
1
2020/01/22注册,1时21分前活动

个人开源项目: XXXXXXXXXXXXXX

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:上海
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}