Embedded System 개발자라면 생산과정이든, 펌웨어 업그에이드로 인해 생산된 제품을 다시 writing해야하는 경우든, 제품에 프로그램 writing해야하는 일은 종종 경험하게 된다.

문제는 수량이 많은 경우에 시간과 비용이 만만치 않다는 데 있다. 또, 각 단계별로 사람이 개입해서 제어를 해줘야하니 여간 귀찮은 일이 아니다.

이 글에서는 펌웨어 writing 업무를 반자동화(프로그램 된 것을 빼내고 새 모듈을 연결하는 등 작업자의 관여가 필요함.) 하는 과정을 설명하고 그 과정에서 작성한 소프트웨어 모듈을 다른 사람들이 참고할 수 있도록 공유하고자 한다.

 

프로그래밍 흐름

다중 프로그래밍이 필요한 제품은 위즈네트의 WizFi630A 모듈이다(물론 다른 제품도 동작 시나리오에 따라 코드를 수정한다면 동일한 결과를 얻을 수 있을 것이다).

이 모듈에 펌웨어를 writing 하는 과정을 살펴보면 다음과 같다.

  1. 전원이 인가되면 UART로 메뉴가 출력되고, 일정시간동안 (약 2초간) 작업자의 메뉴 선택을 기다린다.
  2. 작업자가 메뉴를 선택하면 UART를 통해서 그 값이 모듈에 전달된다.
  3. Flash에 프로그램 Write하기 메뉴가 선택되었으면 모듈은 모듈의 IP 주소, 접속할 TFTP 서버의 IP 주소, 다운로드할 바이너리 파일의 이름에 대한 입력을 기다린다.
  4. 각 단계별로 사용자가 값을 입력하고 나면, 모듈은 Ethernet을 통해서 TFTP 서버에 접속하고 해당 서버에서 업데이트에 사용할 펌웨어를 다운로드한다.
  5. 다운로드가 완료되면 Flash menory에 write한후, 해당 image를 이용해서 booting 과정을 수행한다.
  6. 정상적으로 booting이 완료되면 command line prompt를 표시하는 것으로 writing 작업은 완료된다.

위와 같은 과정에 따라서 프로그래밍을 하기 위해서는 UART 포트와 Ethernet 포트, 두가지 물리적인 인터페이스가 필요하다.

 

Target system 구성

위의 프로그래밍 흐름에서 본 것 처럼, 모듈 하나당 UART 1 port, Ethernet 1 port를 필요하다. 여기에 WizFi630A 모듈은 miniPCB I/F를 가지므로 miniPCB socket 통해서 다른 장치들과 연결될 수 있다.

다중 프로그래밍 툴을 PC에서 실행할 예정인데 요즈음 PC는 RS232 포트를 기본으로 제공하지도 않을뿐더러 USB-to-Serial 컨버터를 사용하다고 하더라도 4개, 8개 등 다수의 모듈을 지원하도록 확장하기에는 효과적인 수단이라고 할 수 없다.

Serial-to-Ethernet 모듈

그래서 Serial-to-Ethernet 모듈(이하 S2E 모듈)을 이용해서 PC에서는 Ethernet을 통해서 논리적 socket 통신을 하고, S2E 모듈이 Ethernet과 UART 간의 데이터를 교환하는 역할을 하도록 구성하는 것이 확장성 측면에서 훨씬 효과적이다.

WIZnet에는 1 port S2E, 2 ports S2E, 4 ports S2E 등 다양한 S2E 모듈이 있는데, 한번에 최대한 많은 모듈을 다루기 위해서 4 ports S2E, 그 중에서도 RJ45 jack이 내장된, WIZ145SR 모듈을 선정하였다.

Digital In/Out 모듈

다수 모듈을 동시에 프로그래밍 할 때, 작업자가 최소한의 개입만 하도록 하려면 현재 상태 표시 또는 시작 명령을 내리기 위한 장치가 필요하다. 또한, 전체 시스템에 상시 전원이 들어와 있는 상태에서 타겟 모듈을 연결, 제거하기 위해서는 모듈에만 전원 인가, 분리를 프로그램이 제어할 수 있어야 한다.

이런 작업을 위해서 각각의 타겟 모듈당 4개의 GPIO를 할당하였다. 3개는 출력, 1개는 입력.

Digital Out

  • 전원 제어 (모듈 전원 인가  Relay – 또는 Transistor – 제어용)
  • Red LED 제어 (프로그래밍 Fail indicator)
  • Blue LED 제어 (프로그래밍 Success indicator)

Digital In

  • 프로그래밍 시작 버튼 (작업자가 버튼을 누르면 프로그래밍 작업 시작)

WIZnet에는 16개의 Digital In/Out 포트를 가진 제품으로 WIZ550WEB 이라는 제품이 있다. 16개의 GPIOs는 사용자가 손쉽고 Digital In 또는 Digital Out으로 변경할 수 있으며, HTTP GET을 통해서 각 포트의 현재 값을 확인하고 HTTP POST를 이용해서 특정 포트의 값을 변경할 수 있다.

본 과제를 위해서 새로운 개발을 최소화 하는 것이 필요하므로 WIZ550WEB을 그대로 사용하기로 하였다.

Easy release latch

프로그래밍 툴은 짧은 작업 시간후에 완료된 모듈을 제거하고 새로운 모듈을 장착하는 작업이 빈번히 일어난다. 따라서 모듈을 고정시키기 위한 latch의 선택도 모듈의 탈, 부착이 쉬운 것을 선택하는 것이 중요하다.

 

위에서 언급한 WIZ145SR, WIZ550WEB 그리고 miniPCI sockets 들로 아래와 같이 patten 연결만 하는 것으로 하드웨어 구성을 쉽게 완성할 수 있었다. 한가지 아쉬운 것은 miniPCI socket과 WizFi630A용 Ethernet connector를 손쉽게 장착할 방법이 없어서 어쩔 수 없이 PCB를 새롭게 만들 수 밖에 없었다.

아무튼 손쉽게 4의 배수의 모듈에 대한 반자동화 프로그래밍 툴에 대한 개념 설계를 아래 그림과 같이 하였다.

system_handwriting

동작 Flow

  1. 보드에 전원 공급
  2. 각 Slot별로 프로그래밍을 수행할 Python 기반의 프로그램을 수행한다.
  3. 각 miniPCI Slot에 프로그래밍할 WizFi630A 모듈을 삽입.
  4. 각 Slot별 SW를 누른다.
  5. 프로그래밍 소프트웨어는 프로그래밍 단계에 따라서 WIZ145SR의 해당 채널을 통해서 관련 WizFi630A 모듈과 통신하면서 프로그래밍을 수행한다.
  6. 프로그래밍이 성공하거나 실패하면 해당하는 LED를 켜서 결과를 표시하고 WizFi630A 모듈로의 전원을 차단하고 사용자의 SW 입력을 기다린다.  4 ~ 6번의 과정을 작업자의 SW 입력에 따라서 무한 반복한다.

아래의 링크는 Python 기반의 프로그래밍 SW의 전체 Flowchart 이다. 위의 동작 Flow를 state machine 으로 세분화하고 각 단계에서 할 일을 구체적으로 명시하였다.

flowchart

소프트웨어 모듈

Python 기반 프로그래밍 SW는 두개의 자체 작성한 Python 모듈을 사용한다.

  • TCPClient.py
  • WIZ550WebClient.py

TCPClient.py

WIZ145SR S2E 모듈과 Ethernet 통신하는 소프트웨어 모듈이다. 아래와 같이 7개의 member function을 지원한다.

시리얼 객체의 member function 이름과 맞추기위해서 readline(), read(), write() 의 함수명을 사용하였다.

  • TCPClient(timeout, ipaddr, portnum)
    • 객체 생성자
    • 접속할 서버 IP, 서버 포트 넘버를 parameter로 지정해야한다.
  • open()
    • 소켓 생성
  • connect()
    • 지정된 서버로 접속 시도
  • readline()
    • ‘\r’이 올때까지는 모든 문자열을 return 한다.
  • read()
    • 한 문자를 return한다.
    • timeout이 발생하면 버퍼에 있는 모든 문자열을 return 한다.
  • write(data)
    • data 를 해당 소켓을 통해서 send 한다.
  • close()
    • 소켓을 닫는다.

WIZ550WebClient.py

WIZ550WEB 모듈과 통신하는 소프트웨어 모듈이다. 아래와 같이 3개의 member function을 지원한다.

  • WIZ550WebClient(host)
    • 객체 생성자
    • WIZ550WEB 모듈의 IP 주소를 parameter로 지정한다.
  • getGINstate(portnum)
    • 16개의 GPIO port 중 지정된 port의 입력값을 읽어 온다.
    • Digital In으로 설정된 경우에만 제대로된 값을 읽어오게 된다.
  • setGOUTvalue(portnum, value)
    • 지정된 GPIO port에 지정된 값을 출력시킨다.
    • Digital Out으로 설정된 경우에만 제대로된 값을 출력할 수 있다.

 

testWizFi630A.py

main 프로그래밍 SW이다.

state machine에 따라서 무한 반복하면서 해당 모듈에 새로운 펌웨어를 writing하는 과정을 수행한다.

#!/usr/bin/python

import time
import socket
import sys
import getopt
import threading

from WIZ550WebClient import WIZ550WebClient

from TCPClient import TCPClient

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger()

idle_state = 1
ready_state = 2
init_state = 3
menuselect_state = 4
flasherase_state = 5
localIP_state = 6
localIP2_state = 7
localIPDone_state = 8
serverIP_state = 9
serverIP2_state = 10
serverIPDone_state = 11
filename_state = 12
filenameDone_state = 13
done_state = 14
fail_state = 15
check_sw_1_state = 16
check_sw_2_state = 17

SOCK_CLOSE_STATE = 1
SOCK_OPENTRY_STATE = 2
SOCK_OPEN_STATE = 3
SOCK_CONNECTTRY_STATE = 4
SOCK_CONNECT_STATE = 5

dst_ip = ”
ch_num = 0
dst_port = 0
webserver_ip = ”

power_port = 0
switch_port = 4
redled_port = 8
blueled_port = 9

POWER_ON = 1
POWER_OFF = 0
SWITCH_DOWN = ‘0’
SWITCH_RELEASE = ‘1’
LED_ON = 1
LED_OFF = 0

IsTimeout = 0

def timeoutfunc():
global IsTimeout
print ‘timer1 timeout’
IsTimeout = 1

timer1 = threading.Timer(10.0, timeoutfunc)

try:
opts, args = getopt.getopt(sys.argv[1:], “h:c:s:w:”)
except getopt.GetoptError:
print ‘testWizFi630A.py -s <WIZ145SR ip address> -c <WIZ145SR channel num :[0/1/2/3]> -w <webserver ip address>’
sys.exit()

for opt, arg in opts:
if opt == ‘-h’:
print ‘testWizFi630A.py -s <WIZ145SR ip address> -c <WIZ145SR channel num :[0/1/2/3]> -w <webserver ip address>’
sys.exit()
elif opt in (“-c”, “–cnum”) :
ch_num = int(arg)
elif opt in (“-s”, “–sip”) :
dst_ip = arg
elif opt in (“-w”, “–wip”) :
webserver_ip = arg

if ch_num is 0:
dst_port = 5001
power_port = 0
switch_port = 4
redled_port = 8
blueled_port = 9
elif ch_num is 1:
dst_port = 5002
power_port = 1
switch_port = 5
redled_port = 10
blueled_port = 11
elif ch_num is 2:
dst_port = 5003
power_port = 3
switch_port = 7
redled_port = 14
blueled_port = 15
elif ch_num is 3:
dst_port = 5004
power_port = 2
switch_port = 6
redled_port = 12
blueled_port = 13

print ‘dst_ip : ‘, dst_ip
print ‘ch_num : ‘, ch_num
print ‘dst_port : ‘, dst_port
print ‘webserver ip: ‘, webserver_ip
print ‘power port: ‘, power_port
print ‘switch port: ‘, switch_port
print ‘red led port: ‘, redled_port
print ‘blue led port: ‘, blueled_port

if ch_num is 0:
module_ip = “192.168.10.2\r”
elif ch_num is 1:
module_ip = “192.168.10.3\r”
elif ch_num is 2:
module_ip = “192.168.10.4\r”
else:
module_ip = “192.168.10.5\r”

client = TCPClient(2, dst_ip, dst_port)
webclient = WIZ550WebClient(webserver_ip)

value = 1

timeout = 0

while True:
#    sys.stdout.write(‘.’)
if client.state is SOCK_CLOSE_STATE:
cur_state = client.state
client.state = client.open()
if client.state != cur_state:
print(client.state)
#        client.state = OPENTRY_STATE

elif client.state is SOCK_OPEN_STATE:
cur_state = client.state
client.state = client.connect()
if client.state != cur_state:
print(client.state)

elif client.state is SOCK_CONNECT_STATE:
if client.working_state == idle_state:
try:
print(‘power off\r\n’)
webclient.setGOUTvalue(power_port, POWER_OFF)
#                sys.stdout.write(‘>’)
time.sleep(0.1)
# check input switch value
client.working_state = check_sw_1_state
logger.debug(“Waiting SW%r input\r\n” % (switch_port -4))
except :
time.sleep(1)

elif client.working_state == check_sw_1_state:
while True:
try:
value = webclient.getGINstate(switch_port)
time.sleep(0.1)
if value == SWITCH_DOWN:
logger.debug(“SW%r pressed down\r\n” % (switch_port – 4))
logger.debug(“Waiting SW%r release\r\n” % (switch_port -4))
client.working_state = check_sw_2_state
break
except:
time.sleep(1)

elif client.working_state == check_sw_2_state:
while True:
try:
value = webclient.getGINstate(switch_port)
time.sleep(0.1)
if value == SWITCH_RELEASE:
logger.debug(“SW%r released up\r\n” % (switch_port – 4))
client.working_state = ready_state
break
except:
time.sleep(1)

elif client.working_state == ready_state:
try:
print(‘Red led off’)
webclient.setGOUTvalue(redled_port, LED_OFF)
#                sys.stdout.write(‘>’)
time.sleep(0.5)
print(‘Blue led off’)
webclient.setGOUTvalue(blueled_port, LED_OFF)
#                sys.stdout.write(‘>’)
time.sleep(0.5)
# POWER ON
print(‘Power on’)
webclient.setGOUTvalue(power_port, POWER_ON)
#                sys.stdout.write(‘>’)
# 1 second sleep
time.sleep(0.5)
# RED LED OFF (LOW)
print(‘Red led on’)
webclient.setGOUTvalue(redled_port, LED_ON)
#                sys.stdout.write(‘>’)
time.sleep(0.1)
# RED LED OFF (HIGH)
print(‘Red led off’)
webclient.setGOUTvalue(redled_port, LED_OFF)
time.sleep(0.1)
client.working_state = init_state
print ‘timer1 start’
timer1.start()
except Exception as e:
print e
print ‘timer1 stop’
timer1.cancel()
time.sleep(1)

elif client.working_state == init_state:
response = client.readline()
logger.debug(“[init_state] [%r] %r” % (len(response), response))
if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()
if (“9: Load Boot Loader code then write to Flash via TFTP.” in response) :
client.write(“2”)
print ‘timer1 stop’
timer1.cancel()
client.working_state = menuselect_state
response = “”

if IsTimeout is 1:
print ‘timer1 stop’
timer1.cancel()
client.working_state = fail_state

elif client.working_state == menuselect_state:
response = client.readline()
if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()
if (“Warning!! Erase Linux in Flash then burn new one. Are you sure?(Y/N)” in response) :
client.write(“Y”)
client.working_state = flasherase_state
elif (“3: System Boot system code via Flash” in response):
client.working_state = fail_state
response = “”

elif client.working_state == flasherase_state:
response = client.readline()
if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()
if (“Input device IP (10.10.10.123)” in response) :
for i in range(12):
client.write(“\b \b”)
time.sleep(0.1)
client.working_state = localIP_state
response = “”

elif client.working_state == localIP_state:

response = client.readline()
if (response != “”) :
sys.stdout.write(response)
sys.stdout.flush()
response = “”
client.write(module_ip)
client.working_state = localIP2_state

response = “”

elif client.working_state == localIP2_state:

response = client.readline()
if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()

client.working_state = localIPDone_state

response = “”

elif client.working_state == localIPDone_state:
response = client.readline()
if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()
if (“Input server IP (10.10.10.3)” in response) :
for i in range(10):
client.write(“\b \b”)
time.sleep(0.1)
client.working_state = serverIP_state

response = “”

elif client.working_state == serverIP_state:

response = client.readline()
if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()

client.write(“192.168.10.212\r”)
client.working_state = serverIP2_state
response = “”

elif client.working_state == serverIP2_state:

response = client.readline()

if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()
client.working_state = serverIPDone_state

response = “”

elif client.working_state == serverIPDone_state:
response = client.readline()
if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()
if (“Input Linux Kernel filename ()” in response) :
client.write(“a.bin\r”)
client.working_state = filename_state
response = “”

elif client.working_state == filename_state:
response = client.readline()
if(response != “”):
sys.stdout.write(response)
sys.stdout.flush()
client.working_state = filenameDone_state
response = “”

elif client.working_state == filenameDone_state:
ch = client.read()
if(ch != ”):
client.str_list.append(ch)
sys.stdout.write(“%c” % ch)
sys.stdout.flush()

if(ch == ‘\r’):
response = ”.join(client.str_list)
del client.str_list[:]
if (“Please press Enter to activate this console.” in response) :
client.write(“\n”)
client.working_state = done_state
elif (“Retry count exceeded; starting again” in response) :
client.retrycount += 1
logger.debug(“retrycount: %r\r\n” % client.retrycount)
if(client.retrycount >= 20):
client.retrycount = 0
client.working_state = fail_state
elif (“failsafe button BTN_1 was pressed” in response) :
client.working_state = done_state

response = “”

elif client.working_state == done_state:
response = client.readline()
if(response != “”):
if (“root@” in response) :
sys.stdout.write(response)
sys.stdout.write(“\r\n”)
sys.stdout.flush()
client.write(“\r”)
sys.stdout.write(“=================================\n”)
sys.stdout.write(” Firmware Update Succeeded!!!\n”)
sys.stdout.write(“=================================\n”)
sys.stdout.flush()
# BLUELED ON (HIGH)
try:
webclient.setGOUTvalue(blueled_port, LED_ON)
sys.stdout.write(‘<‘)
time.sleep(0.1)
# POWER OFF (LOW)
webclient.setGOUTvalue(power_port, POWER_OFF)
sys.stdout.write(‘<‘)
time.sleep(0.1)
client.working_state = idle_state
logger.debug(“You can remove a module on bank%r now\r\n” % ch_num)
time.sleep(0.1)
except:
time.sleep(1)
response = “”

elif client.working_state == fail_state:
try:
webclient.setGOUTvalue(redled_port, LED_ON)
sys.stdout.write(‘*’)
time.sleep(0.1)
webclient.setGOUTvalue(power_port, POWER_OFF)
sys.stdout.write(‘*’)
time.sleep(0.1)
client.working_state = idle_state
logger.debug(“You can remove a module on bank%r now\r\n” % ch_num)
time.sleep(1)
except:
time.sleep(1)

#    time.sleep(0.01)

시연

아래의 사진은 실제로 구현한 프로토타입의 Programming Tool이다.

기대한대로 잘 동작한다.

WizFi630A_programming_tool

Source Code

https://github.com/javakys/WizFi630A-Multi-Programming-Tool

Advertisements