Commit be047ff7 authored by Marcel Stör's avatar Marcel Stör
Browse files

Merge branch 'dev', 1.5.4.1 master drop

parents b580bfe7 a8d984ae
亐亐亐亐亐亐亐亐亐亐囼黩腙噼疪g[h^j_面刭酉细ISKXN_I屙噤儋M9?CBK@x褛湓菔躢(<5?=@Q葳垩浊讌9.=5C9类杂惺效#3+:1C/涚巫宋屏4*.52>1r嫖偕依蠷!1/588P垩质依觲4)92>9蜜椅闻蠜3)8/A.握仕乒,,+5/?.{缣兹狭蘃"0/395Y咝咨欣襪3*73<=挛膳萍菐6+;2B3ω撬拿庐,2.94C1勣蒲萌寄C*367>8c廴颐嘶蚫#70:9?G滔吻式藞 8.=5D7柏仕敲浓)5/;4D3庍切蒙纠=,377A7k萸衙嘶薥$72;:=M型衅私蛝 :/=5D:钢趟攘洽&7.<5E2樲刃那阑70287A6p泳然麓罶*96<?>U棠山亩舥&=4@:FA何琶潞脰)=4A9H9炚氯懒奖676?:G9}亓私鸥翺/::=C?]遗炭品莕(>6A>EH了嚷幕艔)>4B:I:υ徘量精386@:I8呚绿颗箍H19<<D<e漳叹欧莊*=7A?DK剖闪乒茋(>5B;I>θ冀苟笩2>8C=K<娤好菇档E8<A@H@m谓鸥辣繾1A=DDGT怕暮来羱.D;F@KD笔晾靖綔2A:F?M=斝灸航覆A:<CAK?v呀乒脸繷3@?DFE[柿呕脸聎.C<EBKH啡亮几細'n酱吵g&;4>=?澤簿昂0A7H8b棵缎羞(;9<D7烄衍行蚔$=/A5Q提呜抛*.61A.逖倚且h8-97=躲首囊&7,@,f壅我轮3/4<2濆稍翘肏8*>/Q哇收琳%-2/>+呬鸵似蝋6*:4@横芍卵&5+?,l芤涡略w4-493ュ芍仆緾 8*>.W胸嗽琳".00=-嗁蒙晾肶 9-<5E鹤挛凰,81A/r姿扇刻q"81:<:к眯廊礎'</B2^弦俏较&557A3戄仆哪肬#<0@6L磷男轿/83C1z谏巳克k":2=;?男渴=*;0C2e有忍轿%658?5椶畔闷罰$=/A5Q胖畔嘉-184B2|欣每沽c%>3@=C饺该<0>5G5j吻缕溉{)<8=B<涀渴祭稭,A5F8X猛可干8<:F8喼燎两耡)A6C=I从克骨:2>7H6q亚呐喝w)=8@B>≈克济碕.A6G8]忍辽故.:<;E9嵶寥揽繺*A6E<N寡克干4>8G7s燃蓟簿o-A:CCC⑼孤淳狦3D9J;d妹剂疵?>@H>愊幻购竃1F:I@U故耗趁:<B>K<~慰两防l/E<FEI┪耗独〧6E;L<j饶玖的1A?BI@栄荒辜礥1F;K>Z际耗撑<B?K<傃计放G;L<x霞欧膼3D<J=l途酶脷5D=I>i檑嘤艿2:7@:X拶傺约436;9J屹匝蚊:-55:@熔幸适C'62<8晦逃峭M$7.=2苏囊Y!7,>.b烧抡e8+>+曚芍轮r8*?*囧芍伦7*>+y闶置諑7+=,l崴峙詺!4+;._萃悠学%2-90S匦由尾+./73J延亚来3,247@牢破澜>*53<;芬剖颗J(82?7盘渴V&:2@3Y奈就a$<0B1樮孟较n#<0C/嬢醚狙{#=/B/~菽丫袌#;0A0r芘欣蠒%:0@2f厍狭虪)82?5Z允蚊诗/44;8P瓮膛谴6169:G切嗜沤>/76=@接人旅H+85@9惩幻防Q+<5B7⑿角放]*?5E5樤救干j)@4F5幷绞干v)A4F5冎臼甘*@5F6w挚珊蓭+@6E7k恿苫葰.=7D8a心冉皮2<8A;W是瓶铆88:@>N纳怕炼@5;=AF继媚炯H2>;CA聪缕悸R/?9E<┮寥浩\-A7F9熢可股g+A6F7幪独傲p,B8H9勎该裁|.C9J9z喂么脠0D:I;p突亩脫3B<H=f示梅罎6A>G?]评潞喀<>?EBU侣阑籍A<@CEN黄烤憾I:B@GH踩究够R6C?HC涣防[5E=J@犖荒得e2E=K=曅盖〧<J;嬓古疜:J:佄郝礡7K8w私久a0N4x幺刳e+I1h挹臆l&C0Z折誊t#?0O梯绍~"<0E罗擒$82<膏能&546嫩)070⑩刨-:,椼抛);)嬧圃=&;'嗌崖F#=&s菟紊P!='h傧涛Z=']砸视e=)S握侵p;+K琴刨| 9.A拒内!70:缔纶%335ふ合+161涁幌/;.捹疚->-堎劳礏+@+}倜私J(A,r峙擅S&A+h由剖]%B,_翁梦g$@.U刃卵r$?0M掠烙}%=4F赫吭';6>弊驹+899ㄚ驹5;4澺恳1=0撣佬:/?.囓挛稡,A,~谀丝K)B,s智赡T'B+i暇嚼]'A0\拍寂h(C2V廊簧r*B5N惶固}+@8G聪和.>;B刮=><ぴ雇9?8浾禾:7B5懺皇珸3D3喸饺碐1E2}揖藕O/F2s新美X-G2i膛僚a,F3`惹咳k+E5X檬妓u,D8P纪纪~-B9H葱何.@<B何<>=び核:@:撌泵<9C9屗绰–7F7勌妨甁6I6{斯康R4I7s苫竞[3K8i瓶豢d2K8a谅孤m2J:Z寄概v2H<S非非2F>M笆肚DAF┨渡BCA犖肚<?D>椣镀<H;幬概狥:H9喭姑盠8J9{毯婪T5J8r示窘]3K8j评焕d3J:_饭焙l4J<Y醇苯u6J@T袄袄~8ICO袄:HEJD傲=GGG溒袄桞CJC斍笨烢ALA屓部@M?吳臣璔>N>{诺翰W;O>s酶阜`:O>j炕痘h9O?d冀唇p8N@\防蠢x9LCV甭猜:KDP甭;IFKて奥=GIF溓甭橞EJC捊ǘ欸CNC埦燦BPB偪BQCz景串\ASBs疾潮b@SDl沟钡i>REe捣r>SF_焙y?QGY丂PJT圔NLP牽怐LNL毩桯IOH懧濴GRG娐ERD兝胺猇DSC{勘诞]ATEt匠吵cASEl沟捕j?SEe贩北nARI]ūケwCRKZΤΤ~DRMV》Т嘒QPR浌У岼PSP暪У擬NTM徆ǔ歈MUL埡┏燰JVK伖IWJ{弓蔼aGXIt动gEXJn窗nEXKg安tFWLb┑{FVN\ǘ┒丟UPX7ǖ塇RRT湽ǘ嶫PRP柣ǖ朜NTN徎┐決LUM偘々橶LXM~埃]MZMy唉─cL[OtЖhL\OoΚnK[QjΝuL[RdМく{LZS`ぐ侻YU[灢ぐ圢WVX櫝け峇VXV挸く揟TYS嵉く榃R[Q嚦キ漑QZQ伌ЙP[Pz触瘷櫒、JQMVMw阿獮﹊JUQUYかē☉JWN]J滹蓍c;IGH[玎阏銧,D7K6榆弦E28=8^阋凵輩!=0B2扁嫌土411;0i涮啬賝 9.=5口型尾(3-<,y渖致誠!6/9<嗽疑眩 6*>)嬨烧眯J%425H招云詰7*>+濃视迫<*061W芴致8+>.绦示0/-9.f馍茁譵7-<4截咸捅(2,<*x闵至訹"5/8<试焉癣 6*>)嬨日男K%416I韵云詰7*>*溹廊娇=,272W幽图蝲!9.@1ㄕ湃霉322<2h诿辖衛#;1?9秆善黔,61?0z苊薪蝄':4=A盼嗣虩':0B.娷孟渴N+88:L鲜卫螏$<0B0氌磐撩?/5:7Z智峡褈#=0A3鞘墓543>4h勰薪裯%<1@9挂是拳-81@1z菽丫蝄':5=B磐嗣虩':0B/嬢奈克M+88:M鲜侠蠋$</C0涆盘撩A06<6Z浊峡衺$=1B3鞘墓543>4f鸦谴莐(=4A<词量璃0;4C4y战雀荿-=8BE寥啪艣,?5F5娬旧耗P1=<>P逝燃蓫*@5F6氂狼季D6;@:]蚜屎蕕*A6F9ㄐ履来::9B8k绽使薾+A7D?端怕茅2=6E6z卓屎蒧/?9AG氯蔷茰-@5G5娭渴慌P2==>R逝杉葘+A5G5氂狼浇D6:@;^蚜珊蘿)B6F9┬媚赖9:8C9l岳撕蘮+A8D?短怕茅3=8E7w硕腊繻1@<DI估糠緱2C:I9壩孤淳R6AABT木露聤/E:K;樚豪父H;?D@_式牡舼0F;J>ι季话??=H>m虾糯舘2E=ID财拷茎7C<I<{虾诺腵4E@GK铰梁翙3D;K:嬒荒独S7BBCU趴姆膵0F;K<櫷宦父H<@E@a思诺膤0F<J>祭话>?>G=n魏拼苙2E>HD称兰睛8B<J;|泻糯胈5D?GK韭凉聵4D;J;嬒汗R;BCDV欢话粓4H>M@暸春辈K@CIDb姆桨緗7J@OD⒚饭东CDCLCo嵌堪縪8JBNI狗埂=GAN@}纱颈綽;IDJP辜嫉紩9JAPA娙稻埠W>HGHX抗境綁7K@PB椙都炒MBFJEd欧堪縸7KBOEC腹番DEBLCp鹊腊纎8IBMI悍浮=HBNA}纱勘絙;HELP菇嫉紬:JAP@娚稻补W?HGHY亢匠緤7KAPB樒痘创LAEJGb划旦秠:LDPG灮安GHGNGp喇釜竜=NGPM巢碀CLFRF}炉宫積AMJPT撤栋稉@OFTF娐案ZELMN]捍腑箞>PFTG柨捕PGJOKg坎弓箎>PGTJ〖泊钵JKHQHr帘宫筽?OHRN当礉DMHSH茂宫竐AMKQU撤钒窊?NGTF嬃案ZDLMM^勾府箞>OFTG柪卜PGJOKg砍宫簗>PGTJ⒔炒宝IJIQIn法痭BQKTQけ瑱GPKVK}憨钵癴FROTY鲍皯FSKXK埡ó\JPQR`疮畅硣DULXM敼VMPTPi弓揣磡EULXO灧NPOWNt滑揣磓FSMWTТ瘹JRMWL~滑敞瞗GRQUY鲍睉FSLXM壔┋]JRRRa蛋唱矆DUMYM敽UNPTPk弓川硔DTMXO煼OPNVMt棘川硄ETNVTТ瘹ISLXL棘矠ISQUZΘá▽JUOZO嚦かⅷ^NUVUd瓍JYQ\R懖ЙイXRUYUl钵畕KYS[U洷īSUTZSu揣璻LYS\XぎPXS\R郸琲NWVZ_瑥MYR\R壍КぉaPVWXe瓎JZR\S挸ЙΕZSTYTm敞畖KZS]T洷┅SUTZTv川畄LYT[Yく獥OWR\S郸琲NXUY^珟LYS]R壍Лエ`QWXWdO[U_V崻牐灋\VX\XmP]W`Y槳¥XZX^Xw╮R^Y`]煣ぃU\X`XU]Z_bウΑR]WaW壆ⅷ。eV\\]i▍Q^YaX懎%^XZ^[o﹠R^X`[槴いY[Y_Zx﹖S]Y`^煩ィU\XaWT][^cΖАT^XaW壆ⅶ牑dV[\\i━▌R^WaY懎う^Y[^[n灄焬U_[b^摙湝洈\^]b\wXa^ca櫋灊煈Z`]d]爉[a`bf煛牅爥Yc]e]嚙湣洘g\`bakXc^f^彟灎湜b__b`rXc^e`暏灎潠^a^d^yYc`dc洟牉煈[b^d^爊Zb`ch牋Xc]f]嚙潰洘h\`b`lWc]e^彟潫潥b^`d`rYb^e`暏煙潠]`^d_y榪[b`dd摍棖棆^daga~煐檽歰_ddfj櫃殩殗_gbib厾棝枡kbeffo潥湒潅_gcic崰槢棔gcegeu牁潠潁^hdie挐殮檽dedicz牁湒漹_heih棟洐泿afdic牁潠沺afegk殰湕泩`gcjc啞槣棛kcffgp灇潡渻_hcid崰殯棖hedieu牂湒潁^hdie挒殮殥dechdz潡渧`hdih枩洐殠agcjd仭槣棝qafehk憫拸拑cifkf儤憯悞mgijiq枔枒晛clgmh姍摃拺kijljw檽枒杴elinj彉晹搷jjjlmnnoppqqrssstuuvvwwxyyyyyyzzz{{{{|{{||{}|}}}~~}}}}~~~~}~~~亐亐
\ No newline at end of file
-- ****************************************************************************
-- Play file with pcm module.
--
-- Upload jump_8k.u8 to spiffs before running this script.
--
-- ****************************************************************************
function cb_drained(d)
print("drained "..node.heap())
file.seek("set", 0)
-- uncomment the following line for continuous playback
--d:play(pcm.RATE_8K)
end
function cb_stopped(d)
print("playback stopped")
file.seek("set", 0)
end
function cb_paused(d)
print("playback paused")
end
file.open("jump_8k.u8", "r")
drv = pcm.new(pcm.SD, 1)
-- fetch data in chunks of LUA_BUFFERSIZE (1024) from file
drv:on("data", file.read)
-- get called back when all samples were read from the file
drv:on("drained", cb_drained)
drv:on("stopped", cb_stopped)
drv:on("paused", cb_paused)
-- start playback
drv:play(pcm.RATE_8K)
-- ****************************************************************************
-- Network streaming example
--
-- stream = require("play_network")
-- stream.init(pin)
-- stream.play(pcm.RATE_8K, ip, port, "/jump_8k.u8", function () print("stream finished") end)
--
-- Playback can be stopped with stream.stop().
-- And resources are free'd with stream.close().
--
local M, module = {}, ...
_G[module] = M
local _conn
local _drv
local _buf
local _lower_thresh = 2
local _upper_thresh = 5
local _play_cb
-- ****************************************************************************
-- PCM
local function stop_stream(cb)
_drv:stop()
if _conn then
_conn:close()
_conn = nil
end
_buf = nil
if cb then cb()
elseif _play_cb then _play_cb()
end
_play_cb = nil
end
local function cb_drained()
print("drained "..node.heap())
stop_stream()
end
local function cb_data()
if #_buf > 0 then
local data = table.remove(_buf, 1)
if #_buf <= _lower_thresh then
-- unthrottle server to get further data into the buffer
_conn:unhold()
end
return data
end
end
local _rate
local function start_play()
print("starting playback")
-- start playback
_drv:play(_rate)
end
-- ****************************************************************************
-- Networking functions
--
local _skip_headers
local _chunk
local _buffering
local function data_received(c, data)
if _skip_headers then
-- simple logic to filter the HTTP headers
_chunk = _chunk..data
local i, j = string.find(_chunk, '\r\n\r\n')
if i then
_skip_headers = false
data = string.sub(_chunk, j+1, -1)
_chunk = nil
end
end
if not _skip_headers then
_buf[#_buf+1] = data
if #_buf > _upper_thresh then
-- throttle server to avoid buffer overrun
c:hold()
if _buffering then
-- buffer got filled, start playback
start_play()
_buffering = false
end
end
end
end
local function cb_disconnected()
if _buffering then
-- trigger playback when disconnected but we're still buffering
start_play()
_buffering = false
end
end
local _path
local function cb_connected(c)
c:send("GET ".._path.." HTTP/1.0\r\nHost: iot.nix.nix\r\n".."Connection: close\r\nAccept: /\r\n\r\n")
_path = nil
end
function M.play(rate, ip, port, path, cb)
_skip_headers = true
_chunk = ""
_buffering = true
_buf = {}
_rate = rate
_path = path
_play_cb = cb
_conn = net.createConnection(net.TCP, false)
_conn:on("receive", data_received)
_conn:on("disconnection", cb_disconnected)
_conn:connect(port, ip, cb_connected)
end
function M.stop(cb)
stop_stream(cb)
end
function M.init(pin)
_drv = pcm.new(pcm.SD, pin)
-- get called back when all samples were read from stream
_drv:on("drained", cb_drained)
_drv:on("data", cb_data)
--_drv:on("stopped", cb_stopped)
end
function M.vu(cb, freq)
_drv:on("vu", cb, freq)
end
function M.close()
stop_stream()
_drv:close()
_drv = nil
end
return M
...@@ -3,12 +3,15 @@ function init_spi_display() ...@@ -3,12 +3,15 @@ function init_spi_display()
-- Hardware SPI CLK = GPIO14 -- Hardware SPI CLK = GPIO14
-- Hardware SPI MOSI = GPIO13 -- Hardware SPI MOSI = GPIO13
-- Hardware SPI MISO = GPIO12 (not used) -- Hardware SPI MISO = GPIO12 (not used)
-- Hardware SPI /CS = GPIO15 (not used)
-- CS, D/C, and RES can be assigned freely to available GPIOs -- CS, D/C, and RES can be assigned freely to available GPIOs
local cs = 8 -- GPIO15, pull-down 10k to GND local cs = 8 -- GPIO15, pull-down 10k to GND
local dc = 4 -- GPIO2 local dc = 4 -- GPIO2
local res = 0 -- GPIO16 local res = 0 -- GPIO16
spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8) spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8)
-- we won't be using the HSPI /CS line, so disable it again
gpio.mode(8, gpio.INPUT, gpio.PULLUP)
-- initialize the matching driver for your display -- initialize the matching driver for your display
-- see app/include/ucg_config.h -- see app/include/ucg_config.h
......
...@@ -3,13 +3,20 @@ function init_spi_display() ...@@ -3,13 +3,20 @@ function init_spi_display()
-- Hardware SPI CLK = GPIO14 -- Hardware SPI CLK = GPIO14
-- Hardware SPI MOSI = GPIO13 -- Hardware SPI MOSI = GPIO13
-- Hardware SPI MISO = GPIO12 (not used) -- Hardware SPI MISO = GPIO12 (not used)
-- Hardware SPI /CS = GPIO15 (not used)
-- CS, D/C, and RES can be assigned freely to available GPIOs -- CS, D/C, and RES can be assigned freely to available GPIOs
local cs = 8 -- GPIO15, pull-down 10k to GND local cs = 8 -- GPIO15, pull-down 10k to GND
local dc = 4 -- GPIO2 local dc = 4 -- GPIO2
local res = 0 -- GPIO16 local res = 0 -- GPIO16
spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8) spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8)
disp = ucg.ili9341_18x240x320_hw_spi(cs, dc, res) -- we won't be using the HSPI /CS line, so disable it again
gpio.mode(8, gpio.INPUT, gpio.PULLUP)
-- initialize the matching driver for your display
-- see app/include/ucg_config.h
--disp = ucg.ili9341_18x240x320_hw_spi(cs, dc, res)
disp = ucg.st7735_18x128x160_hw_spi(cs, dc, res)
end end
......
...@@ -3,12 +3,19 @@ function init_spi_display() ...@@ -3,12 +3,19 @@ function init_spi_display()
-- Hardware SPI CLK = GPIO14 -- Hardware SPI CLK = GPIO14
-- Hardware SPI MOSI = GPIO13 -- Hardware SPI MOSI = GPIO13
-- Hardware SPI MISO = GPIO12 (not used) -- Hardware SPI MISO = GPIO12 (not used)
-- Hardware SPI /CS = GPIO15 (not used)
-- CS, D/C, and RES can be assigned freely to available GPIOs -- CS, D/C, and RES can be assigned freely to available GPIOs
local cs = 8 -- GPIO15, pull-down 10k to GND local cs = 8 -- GPIO15, pull-down 10k to GND
local dc = 4 -- GPIO2 local dc = 4 -- GPIO2
local res = 0 -- GPIO16 local res = 0 -- GPIO16
spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8) spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8)
-- we won't be using the HSPI /CS line, so disable it again
gpio.mode(8, gpio.INPUT, gpio.PULLUP)
-- initialize the matching driver for your display
-- see app/include/ucg_config.h
--disp = ucg.ili9341_18x240x320_hw_spi(cs, dc, res)
disp = ucg.st7735_18x128x160_hw_spi(cs, dc, res) disp = ucg.st7735_18x128x160_hw_spi(cs, dc, res)
end end
......
...@@ -26,6 +26,7 @@ pages: ...@@ -26,6 +26,7 @@ pages:
- Home: 'en/index.md' - Home: 'en/index.md'
- Building the firmware: 'en/build.md' - Building the firmware: 'en/build.md'
- Flashing the firmware: 'en/flash.md' - Flashing the firmware: 'en/flash.md'
- Filesystem notes: 'en/spiffs.md'
- Uploading code: 'en/upload.md' - Uploading code: 'en/upload.md'
- FAQs: - FAQs:
- Lua Developer FAQ: 'en/lua-developer-faq.md' - Lua Developer FAQ: 'en/lua-developer-faq.md'
...@@ -34,6 +35,7 @@ pages: ...@@ -34,6 +35,7 @@ pages:
- Support: 'en/support.md' - Support: 'en/support.md'
- Modules: - Modules:
- 'adc': 'en/modules/adc.md' - 'adc': 'en/modules/adc.md'
- 'adxl345': 'en/modules/adxl345.md'
- 'am2320': 'en/modules/am2320.md' - 'am2320': 'en/modules/am2320.md'
- 'apa102': 'en/modules/apa102.md' - 'apa102': 'en/modules/apa102.md'
- 'bit': 'en/modules/bit.md' - 'bit': 'en/modules/bit.md'
...@@ -47,14 +49,17 @@ pages: ...@@ -47,14 +49,17 @@ pages:
- 'enduser setup': 'en/modules/enduser-setup.md' - 'enduser setup': 'en/modules/enduser-setup.md'
- 'file': 'en/modules/file.md' - 'file': 'en/modules/file.md'
- 'gpio': 'en/modules/gpio.md' - 'gpio': 'en/modules/gpio.md'
- 'hmc5883l': 'en/modules/hmc5883l.md'
- 'http': 'en/modules/http.md' - 'http': 'en/modules/http.md'
- 'hx711' : 'en/modules/hx711.md' - 'hx711' : 'en/modules/hx711.md'
- 'i2c' : 'en/modules/i2c.md' - 'i2c' : 'en/modules/i2c.md'
- 'l3g4200d' : 'en/modules/l3g4200d.md'
- 'mdns': 'en/modules/mdns.md' - 'mdns': 'en/modules/mdns.md'
- 'mqtt': 'en/modules/mqtt.md' - 'mqtt': 'en/modules/mqtt.md'
- 'net': 'en/modules/net.md' - 'net': 'en/modules/net.md'
- 'node': 'en/modules/node.md' - 'node': 'en/modules/node.md'
- 'ow (1-Wire)': 'en/modules/ow.md' - 'ow (1-Wire)': 'en/modules/ow.md'
- 'pcm' : 'en/modules/pcm.md'
- 'perf': 'en/modules/perf.md' - 'perf': 'en/modules/perf.md'
- 'pwm' : 'en/modules/pwm.md' - 'pwm' : 'en/modules/pwm.md'
- 'rc' : 'en/modules/rc.md' - 'rc' : 'en/modules/rc.md'
...@@ -66,6 +71,8 @@ pages: ...@@ -66,6 +71,8 @@ pages:
- 'sntp': 'en/modules/sntp.md' - 'sntp': 'en/modules/sntp.md'
- 'spi': 'en/modules/spi.md' - 'spi': 'en/modules/spi.md'
- 'struct': 'en/modules/struct.md' - 'struct': 'en/modules/struct.md'
- 'switec': 'en/modules/switec.md'
- 'tm1829': 'en/modules/tm1829.md'
- 'tmr': 'en/modules/tmr.md' - 'tmr': 'en/modules/tmr.md'
- 'tsl2561': 'en/modules/tsl2561.md' - 'tsl2561': 'en/modules/tsl2561.md'
- 'u8g': 'en/modules/u8g.md' - 'u8g': 'en/modules/u8g.md'
......
#############################################################
# Options
#
FSSOURCE ?= ../local/fs/
FLASHSIZE ?= 4mb 32mb 8mb
SUBDIRS =
OBJDUMP = $(or $(shell which objdump),xtensa-lx106-elf-objdump)
#############################################################
# Get the files to pack into the spiffs image
#
SPIFFSFILES ?= $(patsubst $(FSSOURCE)%,%,$(shell find $(FSSOURCE) -name '*' '!' -name .gitignore ))
#################################################################
# Get the filesize of /bin/0x10000.bin
#
FLASH_USED_END = $$((0x`$(OBJDUMP) -t ../app/.output/eagle/debug/image/eagle.app.v6.out |grep _flash_used_end |cut -f1 -d" "` - 0x40200000))
#############################################################
# Rules base
#
#
all: spiffsscript
spiffsscript: remove-image
$(MAKE) -C spiffsimg CC=gcc
rm -f ./spiffsimg/spiffs.lst
echo "" >> ./spiffsimg/spiffs.lst
@$(foreach f, $(SPIFFSFILES), echo "import $(FSSOURCE)$(f) $(f)" >> ./spiffsimg/spiffs.lst ;)
@$(foreach sz, $(FLASHSIZE), spiffsimg/spiffsimg -U $(FLASH_USED_END) -o ../bin/spiffs-$(sz).dat -f ../bin/0x%x-$(sz).bin -S $(sz) -r ./spiffsimg/spiffs.lst -d; )
@$(foreach sz, $(FLASHSIZE), if [ -r ../bin/spiffs-$(sz).dat ]; then echo Built $$(cat ../bin/spiffs-$(sz).dat)-$(sz).bin; fi; )
remove-image:
$(foreach sz, $(FLASHSIZE), if [ -r ../bin/spiffs-$(sz).dat ]; then rm -f ../bin/$$(cat ../bin/spiffs-$(sz).dat)-$(sz).bin; fi; )
rm -f ../bin/spiffs*.dat
spiffsclean: remove-image
rm -f ./spiffsimg/spiffsimg
rm -f ./spiffsimg/spiffs.lst
#!/usr/bin/env python #!/usr/bin/env python
# NB: Before sending a PR to change the above line to '#!/usr/bin/env python2', please read https://github.com/themadinventor/esptool/issues/21
# #
# ESP8266 ROM Bootloader Utility # ESP8266 ROM Bootloader Utility
# https://github.com/themadinventor/esptool # https://github.com/themadinventor/esptool
# #
# Copyright (C) 2014 Fredrik Ahlberg # Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, other contributors as noted.
# #
# This program is free software; you can redistribute it and/or modify it under # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software # the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later version. # Foundation; either version 2 of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, but WITHOUT # This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# #
...@@ -17,17 +18,23 @@ ...@@ -17,17 +18,23 @@
# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
# Street, Fifth Floor, Boston, MA 02110-1301 USA. # Street, Fifth Floor, Boston, MA 02110-1301 USA.
import sys
import struct
import serial
import math
import time
import argparse import argparse
import hashlib
import inspect
import json
import os import os
import serial
import struct
import subprocess import subprocess
import sys
import tempfile
import time
__version__ = "1.2-dev"
class ESPROM:
class ESPROM(object):
# These are the currently known commands supported by the ROM # These are the currently known commands supported by the ROM
ESP_FLASH_BEGIN = 0x02 ESP_FLASH_BEGIN = 0x02
ESP_FLASH_DATA = 0x03 ESP_FLASH_DATA = 0x03
...@@ -55,72 +62,64 @@ class ESPROM: ...@@ -55,72 +62,64 @@ class ESPROM:
# OTP ROM addresses # OTP ROM addresses
ESP_OTP_MAC0 = 0x3ff00050 ESP_OTP_MAC0 = 0x3ff00050
ESP_OTP_MAC1 = 0x3ff00054 ESP_OTP_MAC1 = 0x3ff00054
ESP_OTP_MAC3 = 0x3ff0005c
# Sflash stub: an assembly routine to read from spi flash and send to host # Flash sector size, minimum unit of erase.
SFLASH_STUB = "\x80\x3c\x00\x40\x1c\x4b\x00\x40\x21\x11\x00\x40\x00\x80" \ ESP_FLASH_SECTOR = 0x1000
"\xfe\x3f\xc1\xfb\xff\xd1\xf8\xff\x2d\x0d\x31\xfd\xff\x41\xf7\xff\x4a" \
"\xdd\x51\xf9\xff\xc0\x05\x00\x21\xf9\xff\x31\xf3\xff\x41\xf5\xff\xc0" \ def __init__(self, port=0, baud=ESP_ROM_BAUD):
"\x04\x00\x0b\xcc\x56\xec\xfd\x06\xff\xff\x00\x00" self._port = serial.Serial(port)
self._slip_reader = slip_reader(port)
def __init__(self, port = 0, baud = ESP_ROM_BAUD): # setting baud rate in a separate step is a workaround for
self._port = serial.Serial(port, baud) # CH341 driver on some Linux versions (this opens at 9600 then
# sets), shouldn't matter for other platforms/drivers. See
""" Read bytes from the serial port while performing SLIP unescaping """ # https://github.com/themadinventor/esptool/issues/44#issuecomment-107094446
def read(self, length = 1): self._port.baudrate = baud
b = ''
while len(b) < length: """ Read a SLIP packet from the serial port """
c = self._port.read(1) def read(self):
if c == '\xdb': return self._slip_reader.next()
c = self._port.read(1)
if c == '\xdc':
b = b + '\xc0'
elif c == '\xdd':
b = b + '\xdb'
else:
raise Exception('Invalid SLIP escape')
else:
b = b + c
return b
""" Write bytes to the serial port while performing SLIP escaping """ """ Write bytes to the serial port while performing SLIP escaping """
def write(self, packet): def write(self, packet):
buf = '\xc0'+(packet.replace('\xdb','\xdb\xdd').replace('\xc0','\xdb\xdc'))+'\xc0' buf = '\xc0' \
+ (packet.replace('\xdb','\xdb\xdd').replace('\xc0','\xdb\xdc')) \
+ '\xc0'
self._port.write(buf) self._port.write(buf)
""" Calculate checksum of a blob, as it is defined by the ROM """ """ Calculate checksum of a blob, as it is defined by the ROM """
@staticmethod @staticmethod
def checksum(data, state = ESP_CHECKSUM_MAGIC): def checksum(data, state=ESP_CHECKSUM_MAGIC):
for b in data: for b in data:
state ^= ord(b) state ^= ord(b)
return state return state
""" Send a request and read the response """ """ Send a request and read the response """
def command(self, op = None, data = None, chk = 0): def command(self, op=None, data=None, chk=0):
if op: if op is not None:
# Construct and send request
pkt = struct.pack('<BBHI', 0x00, op, len(data), chk) + data pkt = struct.pack('<BBHI', 0x00, op, len(data), chk) + data
self.write(pkt) self.write(pkt)
# Read header of response and parse # tries to get a response until that response has the
if self._port.read(1) != '\xc0': # same operation as the request or a retries limit has
raise Exception('Invalid head of packet') # exceeded. This is needed for some esp8266s that
hdr = self.read(8) # reply with more sync responses than expected.
(resp, op_ret, len_ret, val) = struct.unpack('<BBHI', hdr) for retry in xrange(100):
if resp != 0x01 or (op and op_ret != op): p = self.read()
raise Exception('Invalid response') if len(p) < 8:
continue
# The variable-length body (resp, op_ret, len_ret, val) = struct.unpack('<BBHI', p[:8])
body = self.read(len_ret) if resp != 1:
continue
# Terminating byte body = p[8:]
if self._port.read(1) != chr(0xc0): if op is None or op_ret == op:
raise Exception('Invalid end of packet') return val, body # valid response received
return val, body raise FatalError("Response doesn't match request")
""" Perform a connection test """ """ Perform a connection test """
def sync(self): def sync(self):
self.command(ESPROM.ESP_SYNC, '\x07\x07\x12\x20'+32*'\x55') self.command(ESPROM.ESP_SYNC, '\x07\x07\x12\x20' + 32 * '\x55')
for i in xrange(7): for i in xrange(7):
self.command() self.command()
...@@ -140,128 +139,131 @@ class ESPROM: ...@@ -140,128 +139,131 @@ class ESPROM:
time.sleep(0.05) time.sleep(0.05)
self._port.setDTR(False) self._port.setDTR(False)
self._port.timeout = 0.3 # worst-case latency timer should be 255ms (probably <20ms) # worst-case latency timer should be 255ms (probably <20ms)
self._port.timeout = 0.3
for _ in xrange(4): for _ in xrange(4):
try: try:
self._port.flushInput() self._port.flushInput()
self._slip_reader = slip_reader(self._port)
self._port.flushOutput() self._port.flushOutput()
self.sync() self.sync()
self._port.timeout = 5 self._port.timeout = 5
return return
except: except:
time.sleep(0.05) time.sleep(0.05)
# this is a workaround for the CH340 serial driver on current versions of Linux, raise FatalError('Failed to connect to ESP8266')
# which seems to sometimes set the serial port up with wrong parameters
self._port.close()
self._port.open()
raise Exception('Failed to connect')
""" Read memory address in target """ """ Read memory address in target """
def read_reg(self, addr): def read_reg(self, addr):
res = self.command(ESPROM.ESP_READ_REG, struct.pack('<I', addr)) res = self.command(ESPROM.ESP_READ_REG, struct.pack('<I', addr))
if res[1] != "\0\0": if res[1] != "\0\0":
raise Exception('Failed to read target memory') raise FatalError('Failed to read target memory')
return res[0] return res[0]
""" Write to memory address in target """ """ Write to memory address in target """
def write_reg(self, addr, value, mask, delay_us = 0): def write_reg(self, addr, value, mask, delay_us=0):
if self.command(ESPROM.ESP_WRITE_REG, if self.command(ESPROM.ESP_WRITE_REG,
struct.pack('<IIII', addr, value, mask, delay_us))[1] != "\0\0": struct.pack('<IIII', addr, value, mask, delay_us))[1] != "\0\0":
raise Exception('Failed to write target memory') raise FatalError('Failed to write target memory')
""" Start downloading an application image to RAM """ """ Start downloading an application image to RAM """
def mem_begin(self, size, blocks, blocksize, offset): def mem_begin(self, size, blocks, blocksize, offset):
if self.command(ESPROM.ESP_MEM_BEGIN, if self.command(ESPROM.ESP_MEM_BEGIN,
struct.pack('<IIII', size, blocks, blocksize, offset))[1] != "\0\0": struct.pack('<IIII', size, blocks, blocksize, offset))[1] != "\0\0":
raise Exception('Failed to enter RAM download mode') raise FatalError('Failed to enter RAM download mode')
""" Send a block of an image to RAM """ """ Send a block of an image to RAM """
def mem_block(self, data, seq): def mem_block(self, data, seq):
if self.command(ESPROM.ESP_MEM_DATA, if self.command(ESPROM.ESP_MEM_DATA,
struct.pack('<IIII', len(data), seq, 0, 0)+data, ESPROM.checksum(data))[1] != "\0\0": struct.pack('<IIII', len(data), seq, 0, 0) + data,
raise Exception('Failed to write to target RAM') ESPROM.checksum(data))[1] != "\0\0":
raise FatalError('Failed to write to target RAM')
""" Leave download mode and run the application """ """ Leave download mode and run the application """
def mem_finish(self, entrypoint = 0): def mem_finish(self, entrypoint=0):
if self.command(ESPROM.ESP_MEM_END, if self.command(ESPROM.ESP_MEM_END,
struct.pack('<II', int(entrypoint == 0), entrypoint))[1] != "\0\0": struct.pack('<II', int(entrypoint == 0), entrypoint))[1] != "\0\0":
raise Exception('Failed to leave RAM download mode') raise FatalError('Failed to leave RAM download mode')
""" Start downloading to Flash (performs an erase) """ """ Start downloading to Flash (performs an erase) """
def flash_begin(self, size, offset): def flash_begin(self, size, offset):
old_tmo = self._port.timeout old_tmo = self._port.timeout
num_blocks = (size + ESPROM.ESP_FLASH_BLOCK - 1) / ESPROM.ESP_FLASH_BLOCK num_blocks = (size + ESPROM.ESP_FLASH_BLOCK - 1) / ESPROM.ESP_FLASH_BLOCK
self._port.timeout = 10
if self.command(ESPROM.ESP_FLASH_BEGIN, sectors_per_block = 16
struct.pack('<IIII', size, num_blocks, ESPROM.ESP_FLASH_BLOCK, offset))[1] != "\0\0": sector_size = self.ESP_FLASH_SECTOR
raise Exception('Failed to enter Flash download mode') num_sectors = (size + sector_size - 1) / sector_size
start_sector = offset / sector_size
head_sectors = sectors_per_block - (start_sector % sectors_per_block)
if num_sectors < head_sectors:
head_sectors = num_sectors
if num_sectors < 2 * head_sectors:
erase_size = (num_sectors + 1) / 2 * sector_size
else:
erase_size = (num_sectors - head_sectors) * sector_size
self._port.timeout = 20
t = time.time()
result = self.command(ESPROM.ESP_FLASH_BEGIN,
struct.pack('<IIII', erase_size, num_blocks, ESPROM.ESP_FLASH_BLOCK, offset))[1]
if size != 0:
print "Took %.2fs to erase flash block" % (time.time() - t)
if result != "\0\0":
raise FatalError.WithResult('Failed to enter Flash download mode (result "%s")', result)
self._port.timeout = old_tmo self._port.timeout = old_tmo
""" Write block to flash """ """ Write block to flash """
def flash_block(self, data, seq): def flash_block(self, data, seq):
if self.command(ESPROM.ESP_FLASH_DATA, result = self.command(ESPROM.ESP_FLASH_DATA,
struct.pack('<IIII', len(data), seq, 0, 0)+data, ESPROM.checksum(data))[1] != "\0\0": struct.pack('<IIII', len(data), seq, 0, 0) + data,
raise Exception('Failed to write to target Flash') ESPROM.checksum(data))[1]
if result != "\0\0":
raise FatalError.WithResult('Failed to write to target Flash after seq %d (got result %%s)' % seq, result)
""" Leave flash mode and run/reboot """ """ Leave flash mode and run/reboot """
def flash_finish(self, reboot = False): def flash_finish(self, reboot=False):
pkt = struct.pack('<I', int(not reboot)) pkt = struct.pack('<I', int(not reboot))
if self.command(ESPROM.ESP_FLASH_END, pkt)[1] != "\0\0": if self.command(ESPROM.ESP_FLASH_END, pkt)[1] != "\0\0":
raise Exception('Failed to leave Flash mode') raise FatalError('Failed to leave Flash mode')
""" Run application code in flash """ """ Run application code in flash """
def run(self, reboot = False): def run(self, reboot=False):
# Fake flash begin immediately followed by flash end # Fake flash begin immediately followed by flash end
self.flash_begin(0, 0) self.flash_begin(0, 0)
self.flash_finish(reboot) self.flash_finish(reboot)
""" Read MAC from OTP ROM """ """ Read MAC from OTP ROM """
def read_mac(self): def read_mac(self):
mac0 = esp.read_reg(esp.ESP_OTP_MAC0) mac0 = self.read_reg(self.ESP_OTP_MAC0)
mac1 = esp.read_reg(esp.ESP_OTP_MAC1) mac1 = self.read_reg(self.ESP_OTP_MAC1)
if ((mac1 >> 16) & 0xff) == 0: mac3 = self.read_reg(self.ESP_OTP_MAC3)
if (mac3 != 0):
oui = ((mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff)
elif ((mac1 >> 16) & 0xff) == 0:
oui = (0x18, 0xfe, 0x34) oui = (0x18, 0xfe, 0x34)
elif ((mac1 >> 16) & 0xff) == 1: elif ((mac1 >> 16) & 0xff) == 1:
oui = (0xac, 0xd0, 0x74) oui = (0xac, 0xd0, 0x74)
else: else:
raise Exception("Unknown OUI") raise FatalError("Unknown OUI")
return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff) return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff)
""" Read Chip ID from OTP ROM - see http://esp8266-re.foogod.com/wiki/System_get_chip_id_%28IoT_RTOS_SDK_0.9.9%29 """
def chip_id(self):
id0 = self.read_reg(self.ESP_OTP_MAC0)
id1 = self.read_reg(self.ESP_OTP_MAC1)
return (id0 >> 24) | ((id1 & 0xffffff) << 8)
""" Read SPI flash manufacturer and device id """ """ Read SPI flash manufacturer and device id """
def flash_id(self): def flash_id(self):
self.flash_begin(0, 0) self.flash_begin(0, 0)
self.write_reg(0x60000240, 0x0, 0xffffffff) self.write_reg(0x60000240, 0x0, 0xffffffff)
self.write_reg(0x60000200, 0x10000000, 0xffffffff) self.write_reg(0x60000200, 0x10000000, 0xffffffff)
flash_id = esp.read_reg(0x60000240) flash_id = self.read_reg(0x60000240)
self.flash_finish(False) self.flash_finish(False)
return flash_id return flash_id
""" Read SPI flash """
def flash_read(self, offset, size, count = 1):
# Create a custom stub
stub = struct.pack('<III', offset, size, count) + self.SFLASH_STUB
# Trick ROM to initialize SFlash
self.flash_begin(0, 0)
# Download stub
self.mem_begin(len(stub), 1, len(stub), 0x40100000)
self.mem_block(stub, 0)
self.mem_finish(0x4010001c)
# Fetch the data
data = ''
for _ in xrange(count):
if self._port.read(1) != '\xc0':
raise Exception('Invalid head of packet (sflash read)')
data += self.read(size)
if self._port.read(1) != chr(0xc0):
raise Exception('Invalid end of packet (sflash read)')
return data
""" Abuse the loader protocol to force flash to be left in write mode """ """ Abuse the loader protocol to force flash to be left in write mode """
def flash_unlock_dio(self): def flash_unlock_dio(self):
# Enable flash write mode # Enable flash write mode
...@@ -285,62 +287,210 @@ class ESPROM: ...@@ -285,62 +287,210 @@ class ESPROM:
# Yup - there's no good way to detect if we succeeded. # Yup - there's no good way to detect if we succeeded.
# It it on the other hand unlikely to fail. # It it on the other hand unlikely to fail.
class ESPFirmwareImage: def run_stub(self, stub, params, read_output=True):
stub = dict(stub)
def __init__(self, filename = None): stub['code'] = unhexify(stub['code'])
if 'data' in stub:
stub['data'] = unhexify(stub['data'])
if stub['num_params'] != len(params):
raise FatalError('Stub requires %d params, %d provided'
% (stub['num_params'], len(params)))
params = struct.pack('<' + ('I' * stub['num_params']), *params)
pc = params + stub['code']
# Upload
self.mem_begin(len(pc), 1, len(pc), stub['params_start'])
self.mem_block(pc, 0)
if 'data' in stub:
self.mem_begin(len(stub['data']), 1, len(stub['data']), stub['data_start'])
self.mem_block(stub['data'], 0)
self.mem_finish(stub['entry'])
if read_output:
print 'Stub executed, reading response:'
while True:
p = self.read()
print hexify(p)
if p == '':
return
class ESPBOOTLOADER(object):
""" These are constants related to software ESP bootloader, working with 'v2' image files """
# First byte of the "v2" application image
IMAGE_V2_MAGIC = 0xea
# First 'segment' value in a "v2" application image, appears to be a constant version value?
IMAGE_V2_SEGMENT = 4
def LoadFirmwareImage(filename):
""" Load a firmware image, without knowing what kind of file (v1 or v2) it is.
Returns a BaseFirmwareImage subclass, either ESPFirmwareImage (v1) or OTAFirmwareImage (v2).
"""
with open(filename, 'rb') as f:
magic = ord(f.read(1))
f.seek(0)
if magic == ESPROM.ESP_IMAGE_MAGIC:
return ESPFirmwareImage(f)
elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC:
return OTAFirmwareImage(f)
else:
raise FatalError("Invalid image magic number: %d" % magic)
class BaseFirmwareImage(object):
""" Base class with common firmware image functions """
def __init__(self):
self.segments = [] self.segments = []
self.entrypoint = 0 self.entrypoint = 0
def add_segment(self, addr, data, pad_to=4):
""" Add a segment to the image, with specified address & data
(padded to a boundary of pad_to size) """
# Data should be aligned on word boundary
l = len(data)
if l % pad_to:
data += b"\x00" * (pad_to - l % pad_to)
if l > 0:
self.segments.append((addr, len(data), data))
def load_segment(self, f, is_irom_segment=False):
""" Load the next segment from the image file """
(offset, size) = struct.unpack('<II', f.read(8))
if not is_irom_segment:
if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536:
raise FatalError('Suspicious segment 0x%x, length %d' % (offset, size))
segment_data = f.read(size)
if len(segment_data) < size:
raise FatalError('End of file reading segment 0x%x, length %d (actual length %d)' % (offset, size, len(segment_data)))
segment = (offset, size, segment_data)
self.segments.append(segment)
return segment
def save_segment(self, f, segment, checksum=None):
""" Save the next segment to the image file, return next checksum value if provided """
(offset, size, data) = segment
f.write(struct.pack('<II', offset, size))
f.write(data)
if checksum is not None:
return ESPROM.checksum(data, checksum)
def read_checksum(self, f):
""" Return ESPROM checksum from end of just-read image """
# Skip the padding. The checksum is stored in the last byte so that the
# file is a multiple of 16 bytes.
align_file_position(f, 16)
return ord(f.read(1))
def append_checksum(self, f, checksum):
""" Append ESPROM checksum to the just-written image """
align_file_position(f, 16)
f.write(struct.pack('B', checksum))
def write_v1_header(self, f, segments):
f.write(struct.pack('<BBBBI', ESPROM.ESP_IMAGE_MAGIC, len(segments),
self.flash_mode, self.flash_size_freq, self.entrypoint))
class ESPFirmwareImage(BaseFirmwareImage):
""" 'Version 1' firmware image, segments loaded directly by the ROM bootloader. """
def __init__(self, load_file=None):
super(ESPFirmwareImage, self).__init__()
self.flash_mode = 0 self.flash_mode = 0
self.flash_size_freq = 0 self.flash_size_freq = 0
self.version = 1
if load_file is not None:
(magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', load_file.read(8))
if filename is not None:
f = file(filename, 'rb')
(magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', f.read(8))
# some sanity check # some sanity check
if magic != ESPROM.ESP_IMAGE_MAGIC or segments > 16: if magic != ESPROM.ESP_IMAGE_MAGIC or segments > 16:
raise Exception('Invalid firmware image') raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments))
for i in xrange(segments):
(offset, size) = struct.unpack('<II', f.read(8))
if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536:
raise Exception('Suspicious segment %x,%d' % (offset, size))
self.segments.append((offset, size, f.read(size)))
# Skip the padding. The checksum is stored in the last byte so that the
# file is a multiple of 16 bytes.
align = 15-(f.tell() % 16)
f.seek(align, 1)
self.checksum = ord(f.read(1)) for i in xrange(segments):
self.load_segment(load_file)
def add_segment(self, addr, data): self.checksum = self.read_checksum(load_file)
# Data should be aligned on word boundary
l = len(data)
if l % 4:
data += b"\x00" * (4 - l % 4)
self.segments.append((addr, len(data), data))
def save(self, filename): def save(self, filename):
f = file(filename, 'wb') with open(filename, 'wb') as f:
f.write(struct.pack('<BBBBI', ESPROM.ESP_IMAGE_MAGIC, len(self.segments), self.write_v1_header(f, self.segments)
self.flash_mode, self.flash_size_freq, self.entrypoint)) checksum = ESPROM.ESP_CHECKSUM_MAGIC
for segment in self.segments:
checksum = ESPROM.ESP_CHECKSUM_MAGIC checksum = self.save_segment(f, segment, checksum)
for (offset, size, data) in self.segments: self.append_checksum(f, checksum)
f.write(struct.pack('<II', offset, size))
f.write(data)
checksum = ESPROM.checksum(data, checksum) class OTAFirmwareImage(BaseFirmwareImage):
""" 'Version 2' firmware image, segments loaded by software bootloader stub
(ie Espressif bootloader or rboot)
"""
def __init__(self, load_file=None):
super(OTAFirmwareImage, self).__init__()
self.version = 2
if load_file is not None:
(magic, segments, first_flash_mode, first_flash_size_freq, first_entrypoint) = struct.unpack('<BBBBI', load_file.read(8))
align = 15-(f.tell() % 16) # some sanity check
f.seek(align, 1) if magic != ESPBOOTLOADER.IMAGE_V2_MAGIC:
f.write(struct.pack('B', checksum)) raise FatalError('Invalid V2 image magic=%d' % (magic))
if segments != 4:
# segment count is not really segment count here, but we expect to see '4'
print 'Warning: V2 header has unexpected "segment" count %d (usually 4)' % segments
# irom segment comes before the second header
self.load_segment(load_file, True)
(magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', load_file.read(8))
if first_flash_mode != self.flash_mode:
print('WARNING: Flash mode value in first header (0x%02x) disagrees with second (0x%02x). Using second value.'
% (first_flash_mode, self.flash_mode))
if first_flash_size_freq != self.flash_size_freq:
print('WARNING: Flash size/freq value in first header (0x%02x) disagrees with second (0x%02x). Using second value.'
% (first_flash_size_freq, self.flash_size_freq))
if first_entrypoint != self.entrypoint:
print('WARNING: Enterypoint address in first header (0x%08x) disagrees with second header (0x%08x). Using second value.'
% (first_entrypoint, self.entrypoint))
if magic != ESPROM.ESP_IMAGE_MAGIC or segments > 16:
raise FatalError('Invalid V2 second header magic=%d segments=%d' % (magic, segments))
class ELFFile: # load all the usual segments
for _ in xrange(segments):
self.load_segment(load_file)
self.checksum = self.read_checksum(load_file)
def save(self, filename):
with open(filename, 'wb') as f:
# Save first header for irom0 segment
f.write(struct.pack('<BBBBI', ESPBOOTLOADER.IMAGE_V2_MAGIC, ESPBOOTLOADER.IMAGE_V2_SEGMENT,
self.flash_mode, self.flash_size_freq, self.entrypoint))
# irom0 segment identified by load address zero
irom_segments = [segment for segment in self.segments if segment[0] == 0]
if len(irom_segments) != 1:
raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments))
# save irom0 segment
irom_segment = irom_segments[0]
self.save_segment(f, irom_segment)
# second header, matches V1 header and contains loadable segments
normal_segments = [s for s in self.segments if s != irom_segment]
self.write_v1_header(f, normal_segments)
checksum = ESPROM.ESP_CHECKSUM_MAGIC
for segment in normal_segments:
checksum = self.save_segment(f, segment, checksum)
self.append_checksum(f, checksum)
class ELFFile(object):
def __init__(self, name): def __init__(self, name):
self.name = name self.name = binutils_safe_path(name)
self.symbols = None self.symbols = None
def _fetch_symbols(self): def _fetch_symbols(self):
...@@ -349,15 +499,23 @@ class ELFFile: ...@@ -349,15 +499,23 @@ class ELFFile:
self.symbols = {} self.symbols = {}
try: try:
tool_nm = "xtensa-lx106-elf-nm" tool_nm = "xtensa-lx106-elf-nm"
if os.getenv('XTENSA_CORE')=='lx106': if os.getenv('XTENSA_CORE') == 'lx106':
tool_nm = "xt-nm" tool_nm = "xt-nm"
proc = subprocess.Popen([tool_nm, self.name], stdout=subprocess.PIPE) proc = subprocess.Popen([tool_nm, self.name], stdout=subprocess.PIPE)
except OSError: except OSError:
print "Error calling "+tool_nm+", do you have Xtensa toolchain in PATH?" print "Error calling %s, do you have Xtensa toolchain in PATH?" % tool_nm
sys.exit(1) sys.exit(1)
for l in proc.stdout: for l in proc.stdout:
fields = l.strip().split() fields = l.strip().split()
self.symbols[fields[2]] = int(fields[0], 16) try:
if fields[0] == "U":
print "Warning: ELF binary has undefined symbol %s" % fields[1]
continue
if fields[0] == "w":
continue # can skip weak symbols
self.symbols[fields[2]] = int(fields[0], 16)
except ValueError:
raise FatalError("Failed to strip symbol output from nm: %s" % fields)
def get_symbol_addr(self, sym): def get_symbol_addr(self, sym):
self._fetch_symbols() self._fetch_symbols()
...@@ -365,276 +523,714 @@ class ELFFile: ...@@ -365,276 +523,714 @@ class ELFFile:
def get_entry_point(self): def get_entry_point(self):
tool_readelf = "xtensa-lx106-elf-readelf" tool_readelf = "xtensa-lx106-elf-readelf"
if os.getenv('XTENSA_CORE')=='lx106': if os.getenv('XTENSA_CORE') == 'lx106':
tool_objcopy = "xt-readelf" tool_readelf = "xt-readelf"
try: try:
proc = subprocess.Popen([tool_readelf, "-h", self.name], stdout=subprocess.PIPE) proc = subprocess.Popen([tool_readelf, "-h", self.name], stdout=subprocess.PIPE)
except OSError: except OSError:
print "Error calling "+tool_nm+", do you have Xtensa toolchain in PATH?" print "Error calling %s, do you have Xtensa toolchain in PATH?" % tool_readelf
sys.exit(1) sys.exit(1)
for l in proc.stdout: for l in proc.stdout:
fields = l.strip().split() fields = l.strip().split()
if fields[0] == "Entry": if fields[0] == "Entry":
return int(fields[3], 0); return int(fields[3], 0)
def load_section(self, section): def load_section(self, section):
tool_objcopy = "xtensa-lx106-elf-objcopy" tool_objcopy = "xtensa-lx106-elf-objcopy"
if os.getenv('XTENSA_CORE')=='lx106': if os.getenv('XTENSA_CORE') == 'lx106':
tool_objcopy = "xt-objcopy" tool_objcopy = "xt-objcopy"
subprocess.check_call([tool_objcopy, "--only-section", section, "-Obinary", self.name, ".tmp.section"]) tmpsection = binutils_safe_path(tempfile.mktemp(suffix=".section"))
f = open(".tmp.section", "rb") try:
data = f.read() subprocess.check_call([tool_objcopy, "--only-section", section, "-Obinary", self.name, tmpsection])
f.close() with open(tmpsection, "rb") as f:
os.remove(".tmp.section") data = f.read()
finally:
os.remove(tmpsection)
return data
class CesantaFlasher(object):
# From stub_flasher.h
CMD_FLASH_WRITE = 1
CMD_FLASH_READ = 2
CMD_FLASH_DIGEST = 3
CMD_BOOT_FW = 6
def __init__(self, esp, baud_rate=0):
print 'Running Cesanta flasher stub...'
if baud_rate <= ESPROM.ESP_ROM_BAUD: # don't change baud rates if we already synced at that rate
baud_rate = 0
self._esp = esp
esp.run_stub(json.loads(_CESANTA_FLASHER_STUB), [baud_rate], read_output=False)
if baud_rate > 0:
esp._port.baudrate = baud_rate
# Read the greeting.
p = esp.read()
if p != 'OHAI':
raise FatalError('Failed to connect to the flasher (got %s)' % hexify(p))
def flash_write(self, addr, data, show_progress=False):
assert addr % self._esp.ESP_FLASH_SECTOR == 0, 'Address must be sector-aligned'
assert len(data) % self._esp.ESP_FLASH_SECTOR == 0, 'Length must be sector-aligned'
sys.stdout.write('Writing %d @ 0x%x... ' % (len(data), addr))
sys.stdout.flush()
self._esp.write(struct.pack('<B', self.CMD_FLASH_WRITE))
self._esp.write(struct.pack('<III', addr, len(data), 1))
num_sent, num_written = 0, 0
while num_written < len(data):
p = self._esp.read()
if len(p) == 4:
num_written = struct.unpack('<I', p)[0]
elif len(p) == 1:
status_code = struct.unpack('<B', p)[0]
raise FatalError('Write failure, status: %x' % status_code)
else:
raise FatalError('Unexpected packet while writing: %s' % hexify(p))
if show_progress:
progress = '%d (%d %%)' % (num_written, num_written * 100.0 / len(data))
sys.stdout.write(progress + '\b' * len(progress))
sys.stdout.flush()
while num_sent - num_written < 5120:
self._esp._port.write(data[num_sent:num_sent + 1024])
num_sent += 1024
p = self._esp.read()
if len(p) != 16:
raise FatalError('Expected digest, got: %s' % hexify(p))
digest = hexify(p).upper()
expected_digest = hashlib.md5(data).hexdigest().upper()
print
if digest != expected_digest:
raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest))
p = self._esp.read()
if len(p) != 1:
raise FatalError('Expected status, got: %s' % hexify(p))
status_code = struct.unpack('<B', p)[0]
if status_code != 0:
raise FatalError('Write failure, status: %x' % status_code)
def flash_read(self, addr, length, show_progress=False):
sys.stdout.write('Reading %d @ 0x%x... ' % (length, addr))
sys.stdout.flush()
self._esp.write(struct.pack('<B', self.CMD_FLASH_READ))
# USB may not be able to keep up with the read rate, especially at
# higher speeds. Since we don't have flow control, this will result in
# data loss. Hence, we use small packet size and only allow small
# number of bytes in flight, which we can reasonably expect to fit in
# the on-chip FIFO. max_in_flight = 64 works for CH340G, other chips may
# have longer FIFOs and could benefit from increasing max_in_flight.
self._esp.write(struct.pack('<IIII', addr, length, 32, 64))
data = ''
while True:
p = self._esp.read()
data += p
self._esp.write(struct.pack('<I', len(data)))
if show_progress and (len(data) % 1024 == 0 or len(data) == length):
progress = '%d (%d %%)' % (len(data), len(data) * 100.0 / length)
sys.stdout.write(progress + '\b' * len(progress))
sys.stdout.flush()
if len(data) == length:
break
if len(data) > length:
raise FatalError('Read more than expected')
p = self._esp.read()
if len(p) != 16:
raise FatalError('Expected digest, got: %s' % hexify(p))
expected_digest = hexify(p).upper()
digest = hashlib.md5(data).hexdigest().upper()
print
if digest != expected_digest:
raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest))
p = self._esp.read()
if len(p) != 1:
raise FatalError('Expected status, got: %s' % hexify(p))
status_code = struct.unpack('<B', p)[0]
if status_code != 0:
raise FatalError('Write failure, status: %x' % status_code)
return data return data
def flash_digest(self, addr, length, digest_block_size=0):
self._esp.write(struct.pack('<B', self.CMD_FLASH_DIGEST))
self._esp.write(struct.pack('<III', addr, length, digest_block_size))
digests = []
while True:
p = self._esp.read()
if len(p) == 16:
digests.append(p)
elif len(p) == 1:
status_code = struct.unpack('<B', p)[0]
if status_code != 0:
raise FatalError('Write failure, status: %x' % status_code)
break
else:
raise FatalError('Unexpected packet: %s' % hexify(p))
return digests[-1], digests[:-1]
def boot_fw(self):
self._esp.write(struct.pack('<B', self.CMD_BOOT_FW))
p = self._esp.read()
if len(p) != 1:
raise FatalError('Expected status, got: %s' % hexify(p))
status_code = struct.unpack('<B', p)[0]
if status_code != 0:
raise FatalError('Boot failure, status: %x' % status_code)
def slip_reader(port):
"""Generator to read SLIP packets from a serial port.
Yields one full SLIP packet at a time, raises exception on timeout or invalid data.
Designed to avoid too many calls to serial.read(1), which can bog
down on slow systems.
"""
partial_packet = None
in_escape = False
while True:
waiting = port.inWaiting()
read_bytes = port.read(1 if waiting == 0 else waiting)
if read_bytes == '':
raise FatalError("Timed out waiting for packet %s" % ("header" if partial_packet is None else "content"))
for b in read_bytes:
if partial_packet is None: # waiting for packet header
if b == '\xc0':
partial_packet = ""
else:
raise FatalError('Invalid head of packet (%r)' % b)
elif in_escape: # part-way through escape sequence
in_escape = False
if b == '\xdc':
partial_packet += '\xc0'
elif b == '\xdd':
partial_packet += '\xdb'
else:
raise FatalError('Invalid SLIP escape (%r%r)' % ('\xdb', b))
elif b == '\xdb': # start of escape sequence
in_escape = True
elif b == '\xc0': # end of packet
yield partial_packet
partial_packet = None
else: # normal byte in packet
partial_packet += b
def arg_auto_int(x): def arg_auto_int(x):
return int(x, 0) return int(x, 0)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description = 'ESP8266 ROM Bootloader Utility', prog = 'esptool') def div_roundup(a, b):
""" Return a/b rounded up to nearest integer,
equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only
without possible floating point accuracy errors.
"""
return (int(a) + int(b) - 1) / int(b)
def binutils_safe_path(p):
"""Returns a 'safe' version of path 'p' to pass to binutils
Only does anything under Cygwin Python, where cygwin paths need to
be translated to Windows paths if the binutils wasn't compiled
using Cygwin (should also work with binutils compiled using
Cygwin, see #73.)
"""
if sys.platform == "cygwin":
try:
return subprocess.check_output(["cygpath", "-w", p]).rstrip('\n')
except subprocess.CalledProcessError:
print "WARNING: Failed to call cygpath to sanitise Cygwin path."
return p
def align_file_position(f, size):
""" Align the position in the file to the next block of specified size """
align = (size - 1) - (f.tell() % size)
f.seek(align, 1)
def hexify(s):
return ''.join('%02X' % ord(c) for c in s)
def unhexify(hs):
s = ''
for i in range(0, len(hs) - 1, 2):
s += chr(int(hs[i] + hs[i + 1], 16))
return s
class FatalError(RuntimeError):
"""
Wrapper class for runtime errors that aren't caused by internal bugs, but by
ESP8266 responses or input content.
"""
def __init__(self, message):
RuntimeError.__init__(self, message)
@staticmethod
def WithResult(message, result):
"""
Return a fatal error object that includes the hex values of
'result' as a string formatted argument.
"""
return FatalError(message % ", ".join(hex(ord(x)) for x in result))
# "Operation" commands, executable at command line. One function each
#
# Each function takes either two args (<ESPROM instance>, <args>) or a single <args>
# argument.
def load_ram(esp, args):
image = LoadFirmwareImage(args.filename)
print 'RAM boot...'
for (offset, size, data) in image.segments:
print 'Downloading %d bytes at %08x...' % (size, offset),
sys.stdout.flush()
esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, offset)
seq = 0
while len(data) > 0:
esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq)
data = data[esp.ESP_RAM_BLOCK:]
seq += 1
print 'done!'
print 'All segments done, executing at %08x' % image.entrypoint
esp.mem_finish(image.entrypoint)
def read_mem(esp, args):
print '0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address))
def write_mem(esp, args):
esp.write_reg(args.address, args.value, args.mask, 0)
print 'Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address)
def dump_mem(esp, args):
f = file(args.filename, 'wb')
for i in xrange(args.size / 4):
d = esp.read_reg(args.address + (i * 4))
f.write(struct.pack('<I', d))
if f.tell() % 1024 == 0:
print '\r%d bytes read... (%d %%)' % (f.tell(),
f.tell() * 100 / args.size),
sys.stdout.flush()
print 'Done!'
def write_flash(esp, args):
flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40, '16m-c1': 0x50, '32m-c1':0x60, '32m-c2':0x70, '64m':0x80, '128m':0x90}[args.flash_size]
flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
flash_params = struct.pack('BB', flash_mode, flash_size_freq)
flasher = CesantaFlasher(esp, args.baud)
for address, argfile in args.addr_filename:
image = argfile.read()
argfile.seek(0) # rewind in case we need it again
# Fix sflash config data.
if address == 0 and image[0] == '\xe9':
print 'Flash params set to 0x%02x%02x' % (flash_mode, flash_size_freq)
image = image[0:2] + flash_params + image[4:]
# Pad to sector size, which is the minimum unit of writing (erasing really).
if len(image) % esp.ESP_FLASH_SECTOR != 0:
image += '\xff' * (esp.ESP_FLASH_SECTOR - (len(image) % esp.ESP_FLASH_SECTOR))
t = time.time()
flasher.flash_write(address, image, not args.no_progress)
t = time.time() - t
print ('\rWrote %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...'
% (len(image), address, t, len(image) / t * 8 / 1000))
print 'Leaving...'
if args.verify:
print 'Verifying just-written flash...'
_verify_flash(flasher, args, flash_params)
flasher.boot_fw()
def image_info(args):
image = LoadFirmwareImage(args.filename)
print('Image version: %d' % image.version)
print('Entry point: %08x' % image.entrypoint) if image.entrypoint != 0 else 'Entry point not set'
print '%d segments' % len(image.segments)
print
checksum = ESPROM.ESP_CHECKSUM_MAGIC
for (idx, (offset, size, data)) in enumerate(image.segments):
if image.version == 2 and idx == 0:
print 'Segment 1: %d bytes IROM0 (no load address)' % size
else:
print 'Segment %d: %5d bytes at %08x' % (idx + 1, size, offset)
checksum = ESPROM.checksum(data, checksum)
print
print 'Checksum: %02x (%s)' % (image.checksum, 'valid' if image.checksum == checksum else 'invalid!')
def make_image(args):
image = ESPFirmwareImage()
if len(args.segfile) == 0:
raise FatalError('No segments specified')
if len(args.segfile) != len(args.segaddr):
raise FatalError('Number of specified files does not match number of specified addresses')
for (seg, addr) in zip(args.segfile, args.segaddr):
data = file(seg, 'rb').read()
image.add_segment(addr, data)
image.entrypoint = args.entrypoint
image.save(args.output)
def elf2image(args):
e = ELFFile(args.input)
if args.version == '1':
image = ESPFirmwareImage()
else:
image = OTAFirmwareImage()
irom_data = e.load_section('.irom0.text')
if len(irom_data) == 0:
raise FatalError(".irom0.text section not found in ELF file - can't create V2 image.")
image.add_segment(0, irom_data, 16)
image.entrypoint = e.get_entry_point()
for section, start in ((".text", "_text_start"), (".data", "_data_start"), (".rodata", "_rodata_start")):
data = e.load_section(section)
image.add_segment(e.get_symbol_addr(start), data)
image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
image.flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40, '16m-c1': 0x50, '32m-c1':0x60, '32m-c2':0x70, '64m':0x80, '128m':0x90}[args.flash_size]
image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
irom_offs = e.get_symbol_addr("_irom0_text_start") - 0x40200000
if args.version == '1':
if args.output is None:
args.output = args.input + '-'
image.save(args.output + "0x00000.bin")
data = e.load_section(".irom0.text")
if irom_offs < 0:
raise FatalError('Address of symbol _irom0_text_start in ELF is located before flash mapping address. Bad linker script?')
if (irom_offs & 0xFFF) != 0: # irom0 isn't flash sector aligned
print "WARNING: irom0 section offset is 0x%08x. ELF is probably linked for 'elf2image --version=2'" % irom_offs
with open(args.output + "0x%05x.bin" % irom_offs, "wb") as f:
f.write(data)
f.close()
else: # V2 OTA image
if args.output is None:
args.output = "%s-0x%05x.bin" % (os.path.splitext(args.input)[0], irom_offs & ~(ESPROM.ESP_FLASH_SECTOR - 1))
image.save(args.output)
def read_mac(esp, args):
mac = esp.read_mac()
print 'MAC: %s' % ':'.join(map(lambda x: '%02x' % x, mac))
def chip_id(esp, args):
chipid = esp.chip_id()
print 'Chip ID: 0x%08x' % chipid
def erase_flash(esp, args):
print 'Erasing flash (this may take a while)...'
esp.flash_erase()
def run(esp, args):
esp.run()
def flash_id(esp, args):
flash_id = esp.flash_id()
print 'Manufacturer: %02x' % (flash_id & 0xff)
print 'Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff)
def read_flash(esp, args):
flasher = CesantaFlasher(esp, args.baud)
t = time.time()
data = flasher.flash_read(args.address, args.size, not args.no_progress)
t = time.time() - t
print ('\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...'
% (len(data), args.address, t, len(data) / t * 8 / 1000))
file(args.filename, 'wb').write(data)
def _verify_flash(flasher, args, flash_params=None):
differences = False
for address, argfile in args.addr_filename:
image = argfile.read()
argfile.seek(0) # rewind in case we need it again
if address == 0 and image[0] == '\xe9' and flash_params is not None:
image = image[0:2] + flash_params + image[4:]
image_size = len(image)
print 'Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name)
# Try digest first, only read if there are differences.
digest, _ = flasher.flash_digest(address, image_size)
digest = hexify(digest).upper()
expected_digest = hashlib.md5(image).hexdigest().upper()
if digest == expected_digest:
print '-- verify OK (digest matched)'
continue
else:
differences = True
if getattr(args, 'diff', 'no') != 'yes':
print '-- verify FAILED (digest mismatch)'
continue
flash = flasher.flash_read(address, image_size)
assert flash != image
diff = [i for i in xrange(image_size) if flash[i] != image[i]]
print '-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0])
for d in diff:
print ' %08x %02x %02x' % (address + d, ord(flash[d]), ord(image[d]))
if differences:
raise FatalError("Verify failed.")
def verify_flash(esp, args, flash_params=None):
flasher = CesantaFlasher(esp)
_verify_flash(flasher, args, flash_params)
def version(args):
print __version__
#
# End of operations functions
#
def main():
parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool')
parser.add_argument( parser.add_argument(
'--port', '-p', '--port', '-p',
help = 'Serial port device', help='Serial port device',
default = '/dev/ttyUSB0') default=os.environ.get('ESPTOOL_PORT', '/dev/ttyUSB0'))
parser.add_argument( parser.add_argument(
'--baud', '-b', '--baud', '-b',
help = 'Serial port baud rate', help='Serial port baud rate used when flashing/reading',
type = arg_auto_int, type=arg_auto_int,
default = ESPROM.ESP_ROM_BAUD) default=os.environ.get('ESPTOOL_BAUD', ESPROM.ESP_ROM_BAUD))
subparsers = parser.add_subparsers( subparsers = parser.add_subparsers(
dest = 'operation', dest='operation',
help = 'Run esptool {command} -h for additional help') help='Run esptool {command} -h for additional help')
parser_load_ram = subparsers.add_parser( parser_load_ram = subparsers.add_parser(
'load_ram', 'load_ram',
help = 'Download an image to RAM and execute') help='Download an image to RAM and execute')
parser_load_ram.add_argument('filename', help = 'Firmware image') parser_load_ram.add_argument('filename', help='Firmware image')
parser_dump_mem = subparsers.add_parser( parser_dump_mem = subparsers.add_parser(
'dump_mem', 'dump_mem',
help = 'Dump arbitrary memory to disk') help='Dump arbitrary memory to disk')
parser_dump_mem.add_argument('address', help = 'Base address', type = arg_auto_int) parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int)
parser_dump_mem.add_argument('size', help = 'Size of region to dump', type = arg_auto_int) parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int)
parser_dump_mem.add_argument('filename', help = 'Name of binary dump') parser_dump_mem.add_argument('filename', help='Name of binary dump')
parser_read_mem = subparsers.add_parser( parser_read_mem = subparsers.add_parser(
'read_mem', 'read_mem',
help = 'Read arbitrary memory location') help='Read arbitrary memory location')
parser_read_mem.add_argument('address', help = 'Address to read', type = arg_auto_int) parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int)
parser_write_mem = subparsers.add_parser( parser_write_mem = subparsers.add_parser(
'write_mem', 'write_mem',
help = 'Read-modify-write to arbitrary memory location') help='Read-modify-write to arbitrary memory location')
parser_write_mem.add_argument('address', help = 'Address to write', type = arg_auto_int) parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int)
parser_write_mem.add_argument('value', help = 'Value', type = arg_auto_int) parser_write_mem.add_argument('value', help='Value', type=arg_auto_int)
parser_write_mem.add_argument('mask', help = 'Mask of bits to write', type = arg_auto_int) parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int)
def add_spi_flash_subparsers(parent):
""" Add common parser arguments for SPI flash properties """
parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency',
choices=['40m', '26m', '20m', '80m'],
default=os.environ.get('ESPTOOL_FF', '40m'))
parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode',
choices=['qio', 'qout', 'dio', 'dout'],
default=os.environ.get('ESPTOOL_FM', 'qio'))
parent.add_argument('--flash_size', '-fs', help='SPI Flash size in Mbit', type=str.lower,
choices=['4m', '2m', '8m', '16m', '32m', '16m-c1', '32m-c1', '32m-c2', '64m', '128m'],
default=os.environ.get('ESPTOOL_FS', '4m'))
parser_write_flash = subparsers.add_parser( parser_write_flash = subparsers.add_parser(
'write_flash', 'write_flash',
help = 'Write a binary blob to flash') help='Write a binary blob to flash')
parser_write_flash.add_argument('addr_filename', nargs = '+', help = 'Address and binary file to write there, separated by space') parser_write_flash.add_argument('addr_filename', metavar='<address> <filename>', help='Address followed by binary filename, separated by space',
parser_write_flash.add_argument('--flash_freq', '-ff', help = 'SPI Flash frequency', action=AddrFilenamePairAction)
choices = ['40m', '26m', '20m', '80m'], default = '40m') add_spi_flash_subparsers(parser_write_flash)
parser_write_flash.add_argument('--flash_mode', '-fm', help = 'SPI Flash mode', parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true")
choices = ['qio', 'qout', 'dio', 'dout'], default = 'qio') parser_write_flash.add_argument('--verify', help='Verify just-written data (only necessary if very cautious, data is already CRCed', action='store_true')
parser_write_flash.add_argument('--flash_size', '-fs', help = 'SPI Flash size in Mbit',
choices = ['4m', '2m', '8m', '16m', '32m'], default = '4m') subparsers.add_parser(
'run',
parser_run = subparsers.add_parser( help='Run application code in flash')
'run',
help = 'Run application code in flash')
parser_image_info = subparsers.add_parser( parser_image_info = subparsers.add_parser(
'image_info', 'image_info',
help = 'Dump headers from an application image') help='Dump headers from an application image')
parser_image_info.add_argument('filename', help = 'Image file to parse') parser_image_info.add_argument('filename', help='Image file to parse')
parser_make_image = subparsers.add_parser( parser_make_image = subparsers.add_parser(
'make_image', 'make_image',
help = 'Create an application image from binary files') help='Create an application image from binary files')
parser_make_image.add_argument('output', help = 'Output image file') parser_make_image.add_argument('output', help='Output image file')
parser_make_image.add_argument('--segfile', '-f', action = 'append', help = 'Segment input file') parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file')
parser_make_image.add_argument('--segaddr', '-a', action = 'append', help = 'Segment base address', type = arg_auto_int) parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int)
parser_make_image.add_argument('--entrypoint', '-e', help = 'Address of entry point', type = arg_auto_int, default = 0) parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0)
parser_elf2image = subparsers.add_parser( parser_elf2image = subparsers.add_parser(
'elf2image', 'elf2image',
help = 'Create an application image from ELF file') help='Create an application image from ELF file')
parser_elf2image.add_argument('input', help = 'Input ELF file') parser_elf2image.add_argument('input', help='Input ELF file')
parser_elf2image.add_argument('--output', '-o', help = 'Output filename prefix', type = str) parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str)
parser_elf2image.add_argument('--flash_freq', '-ff', help = 'SPI Flash frequency', parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1','2'], default='1')
choices = ['40m', '26m', '20m', '80m'], default = '40m') add_spi_flash_subparsers(parser_elf2image)
parser_elf2image.add_argument('--flash_mode', '-fm', help = 'SPI Flash mode',
choices = ['qio', 'qout', 'dio', 'dout'], default = 'qio')
parser_elf2image.add_argument('--flash_size', '-fs', help = 'SPI Flash size in Mbit',
choices = ['4m', '2m', '8m', '16m', '32m'], default = '4m')
parser_read_mac = subparsers.add_parser(
'read_mac',
help = 'Read MAC address from OTP ROM')
parser_flash_id = subparsers.add_parser(
'flash_id',
help = 'Read SPI flash manufacturer and device ID')
parser_read_flash = subparsers.add_parser(
'read_flash',
help = 'Read SPI flash content')
parser_read_flash.add_argument('address', help = 'Start address', type = arg_auto_int)
parser_read_flash.add_argument('size', help = 'Size of region to dump', type = arg_auto_int)
parser_read_flash.add_argument('filename', help = 'Name of binary dump')
parser_erase_flash = subparsers.add_parser(
'erase_flash',
help = 'Perform Chip Erase on SPI flash')
args = parser.parse_args()
# Create the ESPROM connection object, if needed
esp = None
if args.operation not in ('image_info','make_image','elf2image'):
esp = ESPROM(args.port, args.baud)
esp.connect()
# Do the actual work. Should probably be split into separate functions. subparsers.add_parser(
if args.operation == 'load_ram': 'read_mac',
image = ESPFirmwareImage(args.filename) help='Read MAC address from OTP ROM')
print 'RAM boot...'
for (offset, size, data) in image.segments:
print 'Downloading %d bytes at %08x...' % (size, offset),
sys.stdout.flush()
esp.mem_begin(size, math.ceil(size / float(esp.ESP_RAM_BLOCK)), esp.ESP_RAM_BLOCK, offset)
seq = 0
while len(data) > 0:
esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq)
data = data[esp.ESP_RAM_BLOCK:]
seq += 1
print 'done!'
print 'All segments done, executing at %08x' % image.entrypoint
esp.mem_finish(image.entrypoint)
elif args.operation == 'read_mem':
print '0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address))
elif args.operation == 'write_mem':
esp.write_reg(args.address, args.value, args.mask, 0)
print 'Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address)
elif args.operation == 'dump_mem':
f = file(args.filename, 'wb')
for i in xrange(args.size/4):
d = esp.read_reg(args.address+(i*4))
f.write(struct.pack('<I', d))
if f.tell() % 1024 == 0:
print '\r%d bytes read... (%d %%)' % (f.tell(), f.tell()*100/args.size),
sys.stdout.flush()
print 'Done!'
elif args.operation == 'write_flash':
assert len(args.addr_filename) % 2 == 0
flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40}[args.flash_size]
flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
flash_info = struct.pack('BB', flash_mode, flash_size_freq)
while args.addr_filename:
address = int(args.addr_filename[0], 0)
filename = args.addr_filename[1]
args.addr_filename = args.addr_filename[2:]
image = file(filename, 'rb').read()
print 'Erasing flash...'
blocks = math.ceil(len(image)/float(esp.ESP_FLASH_BLOCK))
esp.flash_begin(blocks*esp.ESP_FLASH_BLOCK, address)
seq = 0
while len(image) > 0:
print '\rWriting at 0x%08x... (%d %%)' % (address + seq*esp.ESP_FLASH_BLOCK, 100*(seq+1)/blocks),
sys.stdout.flush()
block = image[0:esp.ESP_FLASH_BLOCK]
# Fix sflash config data
if address == 0 and seq == 0 and block[0] == '\xe9':
block = block[0:2] + flash_info + block[4:]
# Pad the last block
block = block + '\xff' * (esp.ESP_FLASH_BLOCK-len(block))
esp.flash_block(block, seq)
image = image[esp.ESP_FLASH_BLOCK:]
seq += 1
print
print '\nLeaving...'
if args.flash_mode == 'dio':
esp.flash_unlock_dio()
else:
esp.flash_finish(False)
elif args.operation == 'run':
esp.run()
elif args.operation == 'image_info':
image = ESPFirmwareImage(args.filename)
print ('Entry point: %08x' % image.entrypoint) if image.entrypoint != 0 else 'Entry point not set'
print '%d segments' % len(image.segments)
print
checksum = ESPROM.ESP_CHECKSUM_MAGIC
for (idx, (offset, size, data)) in enumerate(image.segments):
print 'Segment %d: %5d bytes at %08x' % (idx+1, size, offset)
checksum = ESPROM.checksum(data, checksum)
print
print 'Checksum: %02x (%s)' % (image.checksum, 'valid' if image.checksum == checksum else 'invalid!')
elif args.operation == 'make_image': subparsers.add_parser(
image = ESPFirmwareImage() 'chip_id',
if len(args.segfile) == 0: help='Read Chip ID from OTP ROM')
raise Exception('No segments specified')
if len(args.segfile) != len(args.segaddr):
raise Exception('Number of specified files does not match number of specified addresses')
for (seg, addr) in zip(args.segfile, args.segaddr):
data = file(seg, 'rb').read()
image.add_segment(addr, data)
image.entrypoint = args.entrypoint
image.save(args.output)
elif args.operation == 'elf2image': subparsers.add_parser(
if args.output is None: 'flash_id',
args.output = args.input + '-' help='Read SPI flash manufacturer and device ID')
e = ELFFile(args.input)
image = ESPFirmwareImage()
image.entrypoint = e.get_entry_point()
for section, start in ((".text", "_text_start"), (".data", "_data_start"), (".rodata", "_rodata_start")):
data = e.load_section(section)
image.add_segment(e.get_symbol_addr(start), data)
image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] parser_read_flash = subparsers.add_parser(
image.flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40}[args.flash_size] 'read_flash',
image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] help='Read SPI flash content')
parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int)
parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int)
parser_read_flash.add_argument('filename', help='Name of binary dump')
parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true")
parser_verify_flash = subparsers.add_parser(
'verify_flash',
help='Verify a binary blob against flash')
parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space',
action=AddrFilenamePairAction)
parser_verify_flash.add_argument('--diff', '-d', help='Show differences',
choices=['no', 'yes'], default='no')
subparsers.add_parser(
'erase_flash',
help='Perform Chip Erase on SPI flash')
subparsers.add_parser(
'version', help='Print esptool version')
# internal sanity check - every operation matches a module function of the same name
for operation in subparsers.choices.keys():
assert operation in globals(), "%s should be a module function" % operation
image.save(args.output + "0x00000.bin") args = parser.parse_args()
data = e.load_section(".irom0.text")
off = e.get_symbol_addr("_irom0_text_start") - 0x40200000
assert off >= 0
f = open(args.output + "0x%05x.bin" % off, "wb")
f.write(data)
f.close()
elif args.operation == 'read_mac': print 'esptool.py v%s' % __version__
mac = esp.read_mac()
print 'MAC: %s' % ':'.join(map(lambda x: '%02x'%x, mac))
elif args.operation == 'flash_id': # operation function can take 1 arg (args), 2 args (esp, arg)
flash_id = esp.flash_id() # or be a member function of the ESPROM class.
print 'Manufacturer: %02x' % (flash_id & 0xff)
print 'Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff)
elif args.operation == 'read_flash': operation_func = globals()[args.operation]
print 'Please wait...' operation_args,_,_,_ = inspect.getargspec(operation_func)
file(args.filename, 'wb').write(esp.flash_read(args.address, 1024, int(math.ceil(args.size / 1024.)))[:args.size]) if operation_args[0] == 'esp': # operation function takes an ESPROM connection object
initial_baud = min(ESPROM.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate
esp = ESPROM(args.port, initial_baud)
esp.connect()
operation_func(esp, args)
else:
operation_func(args)
class AddrFilenamePairAction(argparse.Action):
""" Custom parser class for the address/filename pairs passed as arguments """
def __init__(self, option_strings, dest, nargs='+', **kwargs):
super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
# validate pair arguments
pairs = []
for i in range(0,len(values),2):
try:
address = int(values[i],0)
except ValueError as e:
raise argparse.ArgumentError(self,'Address "%s" must be a number' % values[i])
try:
argfile = open(values[i + 1], 'rb')
except IOError as e:
raise argparse.ArgumentError(self, e)
except IndexError:
raise argparse.ArgumentError(self,'Must be pairs of an address and the binary filename to write there')
pairs.append((address, argfile))
setattr(namespace, self.dest, pairs)
# This is "wrapped" stub_flasher.c, to be loaded using run_stub.
_CESANTA_FLASHER_STUB = """\
{"code_start": 1074790404, "code": "080000601C000060000000601000006031FCFF71FCFF\
81FCFFC02000680332D218C020004807404074DCC48608005823C0200098081BA5A9239245005803\
1B555903582337350129230B446604DFC6F3FF21EEFFC0200069020DF0000000010078480040004A\
0040B449004012C1F0C921D911E901DD0209312020B4ED033C2C56C2073020B43C3C56420701F5FF\
C000003C4C569206CD0EEADD860300202C4101F1FFC0000056A204C2DCF0C02DC0CC6CCAE2D1EAFF\
0606002030F456D3FD86FBFF00002020F501E8FFC00000EC82D0CCC0C02EC0C73DEB2ADC46030020\
2C4101E1FFC00000DC42C2DCF0C02DC056BCFEC602003C5C8601003C6C4600003C7C08312D0CD811\
C821E80112C1100DF0000C180000140010400C0000607418000064180000801800008C1800008418\
0000881800009018000018980040880F0040A80F0040349800404C4A0040740F0040800F0040980F\
00400099004012C1E091F5FFC961CD0221EFFFE941F9310971D9519011C01A223902E2D1180C0222\
6E1D21E4FF31E9FF2AF11A332D0F42630001EAFFC00000C030B43C2256A31621E1FF1A2228022030\
B43C3256B31501ADFFC00000DD023C4256ED1431D6FF4D010C52D90E192E126E0101DDFFC0000021\
D2FF32A101C020004802303420C0200039022C0201D7FFC00000463300000031CDFF1A333803D023\
C03199FF27B31ADC7F31CBFF1A3328030198FFC0000056C20E2193FF2ADD060E000031C6FF1A3328\
030191FFC0000056820DD2DD10460800000021BEFF1A2228029CE231BCFFC020F51A33290331BBFF\
C02C411A332903C0F0F4222E1D22D204273D9332A3FFC02000280E27B3F721ABFF381E1A2242A400\
01B5FFC00000381E2D0C42A40001B3FFC0000056120801B2FFC00000C02000280EC2DC0422D2FCC0\
2000290E01ADFFC00000222E1D22D204226E1D281E22D204E7B204291E860000126E012198FF32A0\
042A21C54C003198FF222E1D1A33380337B202C6D6FF2C02019FFFC000002191FF318CFF1A223A31\
019CFFC00000218DFF1C031A22C549000C02060300003C528601003C624600003C72918BFF9A1108\
71C861D851E841F83112C1200DF00010000068100000581000007010000074100000781000007C10\
0000801000001C4B0040803C004091FDFF12C1E061F7FFC961E941F9310971D9519011C01A662906\
21F3FFC2D1101A22390231F2FF0C0F1A33590331EAFFF26C1AED045C2247B3028636002D0C016DFF\
C0000021E5FF41EAFF2A611A4469040622000021E4FF1A222802F0D2C0D7BE01DD0E31E0FF4D0D1A\
3328033D0101E2FFC00000561209D03D2010212001DFFFC000004D0D2D0C3D01015DFFC0000041D5\
FFDAFF1A444804D0648041D2FF1A4462640061D1FF106680622600673F1331D0FF10338028030C43\
853A002642164613000041CAFF222C1A1A444804202FC047328006F6FF222C1A273F3861C2FF222C\
1A1A6668066732B921BDFF3D0C1022800148FFC0000021BAFF1C031A2201BFFFC000000C02460300\
5C3206020000005C424600005C5291B7FF9A110871C861D851E841F83112C1200DF0B0100000C010\
0000D010000012C1E091FEFFC961D951E9410971F931CD039011C0ED02DD0431A1FF9C1422A06247\
B302062D0021F4FF1A22490286010021F1FF1A223902219CFF2AF12D0F011FFFC00000461C0022D1\
10011CFFC0000021E9FFFD0C1A222802C7B20621E6FF1A22F8022D0E3D014D0F0195FFC000008C52\
22A063C6180000218BFF3D01102280F04F200111FFC00000AC7D22D1103D014D0F010DFFC0000021\
D6FF32D110102280010EFFC0000021D3FF1C031A220185FFC00000FAEEF0CCC056ACF821CDFF317A\
FF1A223A310105FFC0000021C9FF1C031A22017CFFC000002D0C91C8FF9A110871C861D851E841F8\
3112C1200DF0000200600000001040020060FFFFFF0012C1E00C02290131FAFF21FAFF026107C961\
C02000226300C02000C80320CC10564CFF21F5FFC02000380221F4FF20231029010C432D010163FF\
C0000008712D0CC86112C1200DF00080FE3F8449004012C1D0C9A109B17CFC22C1110C13C51C0026\
1202463000220111C24110B68202462B0031F5FF3022A02802A002002D011C03851A0066820A2801\
32210105A6FF0607003C12C60500000010212032A01085180066A20F2221003811482105B3FF2241\
10861A004C1206FDFF2D011C03C5160066B20E280138114821583185CFFF06F7FF005C1286F5FF00\
10212032A01085140066A20D2221003811482105E1FF06EFFF0022A06146EDFF45F0FFC6EBFF0000\
01D2FFC0000006E9FF000C022241100C1322C110C50F00220111060600000022C1100C13C50E0022\
011132C2FA303074B6230206C8FF08B1C8A112C1300DF0000000000010404F484149007519031027\
000000110040A8100040BC0F0040583F0040CC2E00401CE20040D83900408000004021F4FF12C1E0\
C961C80221F2FF097129010C02D951C91101F4FFC0000001F3FFC00000AC2C22A3E801F2FFC00000\
21EAFFC031412A233D0C01EFFFC000003D0222A00001EDFFC00000C1E4FF2D0C01E8FFC000002D01\
32A004450400C5E7FFDD022D0C01E3FFC00000666D1F4B2131DCFF4600004B22C0200048023794F5\
31D9FFC0200039023DF08601000001DCFFC000000871C861D85112C1200DF000000012C1F0026103\
01EAFEC00000083112C1100DF000643B004012C1D0E98109B1C9A1D991F97129013911E2A0C001FA\
FFC00000CD02E792F40C0DE2A0C0F2A0DB860D00000001F4FFC00000204220E71240F7921C226102\
01EFFFC0000052A0DC482157120952A0DD571205460500004D0C3801DA234242001BDD3811379DC5\
C6000000000C0DC2A0C001E3FFC00000C792F608B12D0DC8A1D891E881F87112C1300DF00000", "\
entry": 1074792180, "num_params": 1, "params_start": 1074790400, "data": "FE0510\
401A0610403B0610405A0610407A061040820610408C0610408C061040", "data_start": 10736\
43520}
"""
elif args.operation == 'erase_flash': if __name__ == '__main__':
esp.flash_erase() try:
main()
except FatalError as e:
print '\nA fatal error occurred: %s' % e
sys.exit(2)
...@@ -9,13 +9,13 @@ cd "$TRAVIS_BUILD_DIR"/app/include || exit ...@@ -9,13 +9,13 @@ cd "$TRAVIS_BUILD_DIR"/app/include || exit
sed -E -i.bak 's@(//.*)(#define *LUA_USE_MODULES_.*)@\2@g' user_modules.h sed -E -i.bak 's@(//.*)(#define *LUA_USE_MODULES_.*)@\2@g' user_modules.h
cat user_modules.h cat user_modules.h
# disable SSL # enable SSL
sed -i.bak 's@#define CLIENT_SSL_ENABLE@//#define CLIENT_SSL_ENABLE@' user_config.h sed -i.bak 's@//#define CLIENT_SSL_ENABLE@#define CLIENT_SSL_ENABLE@' user_config.h
cat user_config.h cat user_config.h
cd "$TRAVIS_BUILD_DIR"/ld || exit cd "$TRAVIS_BUILD_DIR"/ld || exit
# increase irom0_0_seg size for all modules build # increase irom0_0_seg size for all modules build
sed -E -i.bak 's@(.*irom0_0_seg *:.*len *=) *[^,]*(.*)@\1 0x90000\2@' nodemcu.ld sed -E -i.bak 's@(.*irom0_0_seg *:.*len *=) *[^,]*(.*)@\1 0xA0000\2@' nodemcu.ld
cat nodemcu.ld cat nodemcu.ld
# change to "root" directory no matter where the script was started from # change to "root" directory no matter where the script was started from
......
spiffs.lst
spiffsimg
SRCS=\
main.c \
../../app/spiffs/spiffs_cache.c ../../app/spiffs/spiffs_check.c ../../app/spiffs/spiffs_gc.c ../../app/spiffs/spiffs_hydrogen.c ../../app/spiffs/spiffs_nucleus.c
CFLAGS=-g -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -I. -I../../app/spiffs -DNODEMCU_SPIFFS_NO_INCLUDE --include spiffs_typedefs.h
spiffsimg: $(SRCS)
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
clean:
rm -f spiffsimg
# spiffsimg - Manipulate SPI Flash File System disk images
Ever wished you could prepare a SPIFFS image offline and flash the whole
thing onto your microprocessor's storage instead of painstakingly upload
file-by-file through your app on the micro? With spiffsimg you can!
For the full gory details see [spiffs.md](../../docs/en/spiffs.md)
/*
* Copyright 2015 Dius Computing Pty Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
* - Neither the name of the copyright holders nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Johny Mattsson <jmattsson@dius.com.au>
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/mman.h>
#include <errno.h>
#include "spiffs.h"
#define NO_CPU_ESP8266_INCLUDE
#include "../platform/cpu_esp8266.h"
static spiffs fs;
static uint8_t *flash;
static int retcode = 0;
#define LOG_PAGE_SIZE 256
// If the caclulated size is this or less, then use smaller blocks
// (1 page) rather than the normal 2 pages. This gives a little bit
// more flexibility for the file system, at the expense of a bit of
// efficiency
#define SMALL_FILESYSTEM (128 * 1024)
static int delete_on_die = 0;
static const char *delete_list[10];
static int delete_list_index = 0;
static u8_t spiffs_work_buf[LOG_PAGE_SIZE*2];
static u8_t spiffs_fds[32*4];
static s32_t flash_read (u32_t addr, u32_t size, u8_t *dst) {
memcpy (dst, flash + addr, size);
return SPIFFS_OK;
}
static s32_t flash_write (u32_t addr, u32_t size, u8_t *src) {
memcpy (flash + addr, src, size);
return SPIFFS_OK;
}
static s32_t flash_erase (u32_t addr, u32_t size) {
memset (flash + addr, 0xff, size);
return SPIFFS_OK;
}
static void die (const char *what)
{
if (errno == 0) {
fprintf(stderr, "%s: fatal error\n", what);
} else {
perror (what);
}
if (delete_on_die) {
const char **p = delete_list;
while (*p) {
unlink(*p);
p++;
}
}
exit (1);
}
static void list (void)
{
spiffs_DIR dir;
if (!SPIFFS_opendir (&fs, "/", &dir))
die ("spiffs_opendir");
struct spiffs_dirent de;
while (SPIFFS_readdir (&dir, &de))
{
static const char types[] = "?fdhs"; // file, dir, hardlink, softlink
char name[sizeof(de.name)+1] = { 0 };
memcpy (name, de.name, sizeof(de.name));
printf("%c %6u %s\n", types[de.type], de.size, name);
}
SPIFFS_closedir (&dir);
}
static void cat (char *fname)
{
spiffs_file fh = SPIFFS_open (&fs, fname, SPIFFS_RDONLY, 0);
char buff[512];
s32_t n;
while ((n = SPIFFS_read (&fs, fh, buff, sizeof (buff))) > 0)
write (STDOUT_FILENO, buff, n);
SPIFFS_close (&fs, fh);
}
static void import (char *src, char *dst)
{
int fd = open (src, O_RDONLY);
if (fd < 0)
die (src);
spiffs_file fh = SPIFFS_open (&fs, dst, SPIFFS_CREAT | SPIFFS_TRUNC | SPIFFS_WRONLY, 0);
if (fh < 0)
die ("spiffs_open");
char buff[16384];
s32_t n;
while ((n = read (fd, buff, sizeof (buff))) > 0)
if (SPIFFS_write (&fs, fh, buff, n) < 0)
die ("spiffs_write");
if (SPIFFS_close (&fs, fh) < 0)
die("spiffs_close");
close (fd);
}
static void export (char *src, char *dst)
{
spiffs_file fh = SPIFFS_open (&fs, src, SPIFFS_RDONLY, 0);
if (fh < 0)
die ("spiffs_open");
int fd = open (dst, O_CREAT | O_TRUNC | O_WRONLY, 0664);
if (fd < 0)
die (dst);
char buff[512];
s32_t n;
while ((n = SPIFFS_read (&fs, fh, buff, sizeof (buff))) > 0)
if (write (fd, buff, n) < 0)
die ("write");
SPIFFS_close (&fs, fh);
close (fd);
}
char *trim (char *in)
{
if (!in)
return "";
char *out = 0;
while (*in)
{
if (!out && !isspace (*in))
out = in;
++in;
}
if (!out)
return "";
while (--in > out && isspace (*in))
;
in[1] = 0;
return out;
}
void syntax (void)
{
fprintf (stderr,
"Syntax: spiffsimg -f <filename> [-d] [-o <locationfilename>] [-c size] [-S flashsize] [-U usedsize] [-l | -i | -r <scriptname> ]\n\n"
);
exit (1);
}
static size_t getsize(const char *s)
{
char *end = 0;
size_t val = strtoul(s, &end, 0);
if (end) {
int factor;
if (*end == 'k' || *end == 'K') {
factor = 1 << 10;
end++;
} else if (*end == 'm' || *end == 'M') {
factor = 1 << 20;
end++;
} else {
factor = 1;
}
// Capital B is bytes
if (*end == 'B') {
factor = factor << 3;
}
// we want bytes
val = (val * factor) / 8;
}
return val;
}
int main (int argc, char *argv[])
{
if (argc == 1)
syntax ();
int opt;
const char *fname = 0;
bool create = false;
enum { CMD_NONE, CMD_LIST, CMD_INTERACTIVE, CMD_SCRIPT } command = CMD_NONE;
int sz = 0;
const char *script_name = 0;
const char *resolved = 0;
int flashsize = 0;
int used = 0;
while ((opt = getopt (argc, argv, "do:f:c:lir:S:U:")) != -1)
{
switch (opt)
{
case 'f': fname = optarg; break;
case 'o': resolved = optarg; break;
case 'c': create = true; sz = strtol(optarg, 0, 0); break;
case 'S': create = true; flashsize = getsize(optarg); break;
case 'U': create = true; used = strtol(optarg, 0, 0); break;
case 'd': delete_on_die = 1; break;
case 'l': command = CMD_LIST; break;
case 'i': command = CMD_INTERACTIVE; break;
case 'r': command = CMD_SCRIPT; script_name = optarg; break;
default: die ("unknown option");
}
}
if (!fname) {
die("Need a filename");
}
int fd;
if (create)
{
if (!sz) {
if (!flashsize || !used) {
die("Missing flashSize or Used");
}
sz = flashsize - used - SYS_PARAM_SEC_NUM * 4096; // This leaves space for the parameter area
if (sz < 0x4000) {
die("Not enough space");
}
if (sz > SMALL_FILESYSTEM) {
used = (used + 0xffff) & ~0xffff;
} else {
used = (used + 0x1fff) & ~0x1fff;
}
sz = flashsize - used - SYS_PARAM_SEC_NUM * 4096;
}
sz &= ~(0x1fff);
char fnamebuff[1024];
sprintf(fnamebuff, fname, used);
delete_list[delete_list_index++] = strdup(fnamebuff);;
fd = open (fnamebuff, (create ? (O_CREAT | O_TRUNC) : 0) | O_RDWR, 0664);
if (fd == -1)
die ("open");
if (resolved) {
delete_list[delete_list_index++] = resolved;
FILE *f = fopen(resolved, "w");
fprintf(f, "0x%x", used);
fclose(f);
}
if (lseek (fd, sz -1, SEEK_SET) == -1)
die ("lseek");
if (write (fd, "", 1) != 1)
die ("write");
}
else {
fd = open (fname, O_RDWR, 0664);
if (fd == -1)
die ("open");
if (!sz)
{
off_t offs = lseek (fd, 0, SEEK_END);
if (offs == -1)
die ("lseek");
sz = offs;
}
}
if (sz & (0x1000 -1))
die ("file size not multiple of erase block size");
flash = mmap (0, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!flash)
die ("mmap");
if (create)
memset (flash, 0xff, sz);
spiffs_config cfg;
cfg.phys_size = sz;
cfg.phys_addr = 0;
cfg.phys_erase_block = 0x1000;
cfg.log_block_size = 0x1000 * (sz > SMALL_FILESYSTEM ? 2 : 1);
cfg.log_page_size = LOG_PAGE_SIZE;
cfg.hal_read_f = flash_read;
cfg.hal_write_f = flash_write;
cfg.hal_erase_f = flash_erase;
if (cfg.phys_size < 4 * cfg.log_block_size) {
die("disk not large enough for four blocks");
}
if (SPIFFS_mount (&fs, &cfg,
spiffs_work_buf,
spiffs_fds,
sizeof(spiffs_fds),
malloc(65536), 65536, 0) != 0) {
if (create) {
if (SPIFFS_format(&fs) != 0) {
die("spiffs_format");
}
if (SPIFFS_mount (&fs, &cfg,
spiffs_work_buf,
spiffs_fds,
sizeof(spiffs_fds),
malloc(65536), 65536, 0) != 0) {
die ("spiffs_mount");
}
if (command == CMD_INTERACTIVE) {
printf("Created filesystem -- size 0x%x, block_size=%d\n", cfg.phys_size, cfg.log_block_size);
}
} else {
die ("spiffs_mount");
}
}
if (command == CMD_NONE)
; // maybe just wanted to create an empty image?
else if (command == CMD_LIST)
list ();
else
{
FILE *in = (command == CMD_INTERACTIVE) ? stdin : fopen (script_name, "r");
if (!in)
die ("fopen");
char buff[128] = { 0 };
if (in == stdin)
printf("> ");
while (fgets (buff, sizeof (buff) -1, in))
{
char *line = trim (buff);
if (!line[0] || line[0] == '#')
continue;
if (strcmp (line, "ls") == 0)
list ();
else if (strncmp (line, "import ", 7) == 0)
{
char *src = 0, *dst = 0;
if (sscanf (line +7, " %ms %ms", &src, &dst) != 2)
{
fprintf (stderr, "SYNTAX ERROR: %s\n", line);
retcode = 1;
}
else
import (src, dst);
free (src);
free (dst);
}
else if (strncmp (line, "export ", 7) == 0)
{
char *src = 0, *dst = 0;
if (sscanf (line + 7, " %ms %ms", &src, &dst) != 2)
{
fprintf (stderr, "SYNTAX ERROR: %s\n", line);
retcode = 1;
}
else
export (src, dst);
free (src);
free (dst);
}
else if (strncmp (line, "rm ", 3) == 0)
{
if (SPIFFS_remove (&fs, trim (line + 3)) < 0)
{
fprintf (stderr, "FAILED: %s\n", line);
retcode = 1;
}
}
else if (strncmp (line, "cat ", 4) == 0)
cat (trim (line + 4));
else if (strncmp (line, "info", 4) == 0)
{
u32_t total, used;
if (SPIFFS_info (&fs, &total, &used) < 0)
{
fprintf (stderr, "FAILED: %s\n", line);
retcode = 1;
}
else
printf ("Total: %u, Used: %u\n", total, used);
}
else
{
printf ("SYNTAX ERROR: %s\n", line);
retcode = 1;
}
if (in == stdin)
printf ("> ");
}
if (in == stdin)
printf ("\n");
}
SPIFFS_unmount (&fs);
munmap (flash, sz);
close (fd);
return retcode;
}
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef int32_t s32_t;
typedef uint32_t u32_t;
typedef int16_t s16_t;
typedef uint16_t u16_t;
typedef int8_t s8_t;
typedef uint8_t u8_t;
typedef long long ptrdiff_t;
#define offsetof(type, member) __builtin_offsetof (type, member)
#!/bin/sh
# This script updates the SPIFFS code from the master repository
if [ ! -e ../tools/update_spiffs.sh ]; then
echo Must run from the tools directory
exit 1
fi
git clone https://github.com/pellepl/spiffs
cp spiffs/src/*.[ch] ../app/spiffs
cp spiffs/LICENSE ../app/spiffs
rm -fr spiffs
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment