有没有办法在 Nodemcu ESP8266 上的 LUA 中提高 SD 卡写入速度?

Posted

技术标签:

【中文标题】有没有办法在 Nodemcu ESP8266 上的 LUA 中提高 SD 卡写入速度?【英文标题】:Is there a way to increase SD Card Write speed in LUA on Nodemcu ESP8266? 【发布时间】:2021-07-28 04:00:46 【问题描述】:

我能达到的最大写入速度是 2.4 KB/s。有没有办法增加这个?

在 NodeMCU ESP8266 上使用 LUA 和 User_Modules.h 中的 SPI 模块。 #define BUILD_FATFS 也在 user_config.h 中启用。

我有一个数据记录器,它一次采样 920SPS 或 ~1.1ms/Sample 10 小时。 1.1 ms 应该是很多时间将两个字节写入 SD 卡或样本之间的 xxx 字节缓冲区,但是我看到的最大写入速度是 498 ms 写入 1200 字节或 7ms 写入 3 字节。这与 12.5MB/s 的 SD 0 类标准相去甚远。当我将 1200 B 转储到卡上时,记录器最终会丢失约 450 个样本。


local adc1 = nil
local t_tbl=
local n=1

function adcReady(_,_,c)
    
    _,_, adctbl[n], _ = adc1:read()
    n=n+1
    if n>400 then
    
        t_tbl[1]=tmr.now()
        
        file.open("/SD0/sddata.txt","a")
        for k,v in ipairs(adctbl) do 
            file.write(v..",")
            adctbl[k]=nil
        end
        file.close()
        
        t_tbl[2]=tmr.now()
        
        print(t_tbl[2] - t_tbl[1])
        n=1
        
    end
end

do
    local adc = 
        ADC1_ID             =   0,
        ADC1_ADDRESS        =   ads1115.ADDR_GND, 
        GAIN                =   ads1115.GAIN_4_096V, 
        SAMPLES             =   ads1115.DR_920SPS, 
        CHANNEL             =   ads1115.SINGLE_0,       
        MODE                =   ads1115.CONTINUOUS, 
        CONV_READY          =   ads1115.CONV_RDY_1, 
    
    i2c.setup(i2c0.id, i2c0.sda, i2c0.scl, i2c0.speed)
    ads1115.reset()
    adc1 = ads1115.ads1015(adc.ADC1_ID, adc.ADC1_ADDRESS)   
    adc1:setting(adc.GAIN, adc.SAMPLES, adc.CHANNEL, adc.MODE, adc.CONV_READY)  
    
    spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 2, spi.HALFDUPLEX)
    vol = file.mount("/SD0", 8)   -- 2nd parameter is optional for non-standard SS/CS pin
    file.open("/SD0/sddata.txt","w+")
    file.close()
    
    tmr.create():alarm(1000,tmr.ALARM_SINGLE,function()
        gpio.mode(i2c0.conv_rdy,gpio.INT) 
        gpio.trig(i2c0.conv_rdy,'up', adcReady) --enable interrupt, active low rising edge==conv ready  
    end)
end

【问题讨论】:

选择最便宜的解决方案并尝试从中获得高端品质是很有趣的。将您的工具更改为为初学者编写的 C 语言。您使用的是儿童版,没有任何严肃的项目可以考虑在 ESP8266 上使用 Lua 来完成 RT 更接近的任务。 哦,我不知道 LUA 是为孩子们准备的。我刚开始使用 esp8266,我不喜欢 arduino 的 c 和 c++ 混搭。认为 LUA 会给我一个新的挑战,让我在业余时间学习一门新语言。 Lua 是一种有趣的语言,问题是你在平台上运行它,其他任务超载。 @0andriy 我怀疑这会说服你,但 Lua 肯定不(只是)为孩子们准备的。 en.wikipedia.org/wiki/List_of_applications_using_Lua。如果 NodeMCU Lua 足以运行商业 ESP8266/ESP32 家庭安全系统(而不是家庭自动化),它可能足以满足许多其他用例。话虽如此,看到 NodeMCU Lua 和使用例如本地 C/C++ 解决方案之间的这个特定任务的速度/吞吐量比较会很有趣。 Arduino 核心。 和@MarcelStör,你似乎完全错过了我的观点,我建议重新阅读我上面的cmets。 【参考方案1】:

您可以通过准备 2Kbyte 对齐的文本块来加快文件写入速度。 将您的 adcReady 替换为:

local log_text = ""
local chunk_size = 2*1024

function adcReady(_,_,c)
   _, _, adctbl[n], _ = adc1:read()
   n = n + 1
   if n > 400 then
   
      t_tbl[1] = tmr.now()
      
      log_text = log_text..table.concat(adctbl, ",", 1, n-1)..","
      local size = #log_text - #log_text % chunk_size
      local log_text_to_save = log_text:sub(1, size)
      log_text = log_text:sub(size + 1)
      
      t_tbl[2] = tmr.now()
      
      if size ~= 0 then 
         file.open("/SD0/sddata.txt","a")
         file.write(log_text_to_save)
         file.close()
      end
      
      t_tbl[3] = tmr.now()
      
      print(t_tbl[2] - t_tbl[1], t_tbl[3] - t_tbl[2])  -- for strings and GC, for File operations
      n = 1
      
   end
end

比 498 毫秒快吗?


更新:

带有缓存 tostring() 的新版本

local num2str = 

function adcReady(_,_,c)
   _, _, adctbl[n], _ = adc1:read()
   n = n + 1
   if n > 400 then

      t_tbl[1] = tmr.now()

      for i = 1, n - 1 do
         local v = adctbl[i]
         local s = num2str[v]
         if not s then
            s = v..","
            num2str[v] = s
         end
         adctbl[i] = s
      end
      local log_text_to_save = table.concat(adctbl, "", 1, n-1)

      t_tbl[2] = tmr.now()

      file.open("/SD0/sddata.txt","a")
      file.write(log_text_to_save)
      file.close()

      t_tbl[3] = tmr.now()

      print(t_tbl[2] - t_tbl[1], t_tbl[3] - t_tbl[2])  -- for strings and GC, for File operations
      n = 1

   end
end

比以前的版本快吗?


更新 2:

local chr = string.char

function adcReady(_,_,c)
   _, _, adctbl[n], _ = adc1:read()
   n = n + 1
   if n > 400 then

      t_tbl[1] = tmr.now()

      for i = 1, n - 1 do
         local v = adctbl[i]
         -- 0<=v<=4095
         local s
         if v < 10 then
            s = chr(v + 48, 44)
         else
            local m10 = v % 10
            if v < 100 then
               s = chr((v - m10)/10 + 48, m10 + 48, 44)
            else
               local m100 = v % 100
               if v < 1000 then
                  s = chr((v - m100)/10 + 48, (m100 - m10)/10 + 48, m10 + 48, 44)
               else
                  local m1000 = v % 1000
                  s = chr((v - m1000)/1000 + 48, (m1000 - m100)/100 + 48, (m100 - m10)/10 + 48, m10 + 48, 44)
               end
            end
         end
         adctbl[i] = s
      end
      local log_text_to_save = table.concat(adctbl, "", 1, n-1)

      t_tbl[2] = tmr.now()

      file.open("/SD0/sddata.txt","a")
      file.write(log_text_to_save)
      file.close()

      t_tbl[3] = tmr.now()

      print(t_tbl[2] - t_tbl[1], t_tbl[3] - t_tbl[2])  -- for strings and GC, for File operations
      n = 1

   end
end

更新3:

对于 Lua 5.3 和日志中的十六进制数字:

-- log output is in hex
local high =  -- [1] = "", [2] = "1", ..., [256] = "FF"
local low =   -- [1] = "0,", [2] = "1,", ..., [16] = "F,"
for x = 0, 255 do  -- replace 255 with 127 (to save memory) if ADC generates only positive values 0x0000-0x7FF0
   high[x+1] = string.format("%X", x*16):sub(1, -2)
   if x < 16 then
      low[x+1] = string.format("%X,", x)
   end
end

-- in case of out-of-memory error reduce measures count (400) to 256
local measures = 400   -- recommended values are powers of 2
local measures_2 = measures*2

-- adctbl[] is not used anymore, text_buffer[] is used instead
local text_buffer =   -- array of (2*measures) elements
for x = 1, measures_2 do
   text_buffer[x] = ""
end

function adcReady(_,_,c)
   local _, _, v = adc1:read()
   -- 0x0000<=v<=0xFFF0
   text_buffer[n] = high[(v>>8)+1]
   text_buffer[n+1] = low[((v>>4)&15)+1]
   n = n + 2
   if n > measures_2 then

      t_tbl[1] = tmr.now()

      local log_text_to_save = table.concat(text_buffer, "", 1, n-1)

      t_tbl[2] = tmr.now()

      file.open("/SD0/sddata.txt","a")
      file.write(log_text_to_save)
      file.close()

      t_tbl[3] = tmr.now()

      print(t_tbl[2] - t_tbl[1], t_tbl[3] - t_tbl[2])  -- for strings and GC, for File operations
      n = 1

   end
end

【讨论】:

从测量中可以看出,脚本在字符串操作(将数字转换为字符串并连接)上花费了 250 毫秒,在保存到文件上花费了 6 毫秒。您的 SD 卡速度很快! 6 毫秒 = 2 KB,0.3 兆字节/秒。 Lua 字符串很慢。 250 毫秒是创建人类可读日志文件所必须付出的代价。 检查新版本(请参阅答案中的更新)。它可能会更快。 这很聪明,使用 num2str 作为动态创建的查找表。你是对的,当它实际上是LUA中的数据类型转换时,我错误地认为SD spi写入速度很慢。对于 400 个样本,更新后的代码要快得多(13194 3264, 13183 4359, 13192 3116, 13180 3228, 13172 4203, 13166 4214, 13155 3598, 13140 6142, 13203 3022, 13159 3366, 13197 3223 如果值为 0..4095,则尝试 update2 中的代码。 你有什么 Lua:5.1 还是 5.3?

以上是关于有没有办法在 Nodemcu ESP8266 上的 LUA 中提高 SD 卡写入速度?的主要内容,如果未能解决你的问题,请参考以下文章

基于ESP8266 NodeMCU + DHT22 记录室内温湿度

基于ESP8266 NodeMCU + DHT22 记录室内温湿度

ESP8266(nodemcu)引脚和I2C的坑!!!

ESP-NOW ESP8266 NodeMCU单板双向通信

ESP8266 nodemcu

无法在 NodeMCU/ESP8266 中使用 math.pow 函数