Exemple #1
0
def run():
    # some SD cards won't work in 4-bit mode unless freq() is explicitely set
    freq(240 * 1000 * 1000)  # 80/160/240 MHz, faster CPU = faster SD card
    #sd=SDCard(slot=3) # 1-bit mode
    sd = SDCard()  # 4-bit mode
    mount(sd, "/sd")
    print(listdir("/sd"))
    f = open("/sd/long_file.bin", "rb")  # any 1-10 MB long file
    b = bytearray(16 * 1024)
    i = 0
    t1 = ticks_ms()
    while f.readinto(b):
        i += 1
    t2 = ticks_ms()
    print("%d KB in %d ms => %d KB/s file read" %
          (i * len(b) // 1024, t2 - t1, 1000 * i * len(b) // 1024 //
           (t2 - t1)))
    f.close()
    umount("/sd")
    i = 0
    t1 = ticks_ms()
    while i < 256:
        sd.readblocks(i, b)
        i += 1
    t2 = ticks_ms()
    print("%d KB in %d ms => %d KB/s raw sector read" %
          (i * len(b) // 1024, t2 - t1, 1000 * i * len(b) // 1024 //
           (t2 - t1)))
    sd.deinit()
async def play_wav():
    bck_pin = Pin(21) 
    ws_pin = Pin(22)  
    sdout_pin = Pin(27)
    
    # channelformat settings:
    #     mono WAV:  channelformat=I2S.ONLY_LEFT
    audio_out = I2S(
        I2S.NUM0, 
        bck=bck_pin, ws=ws_pin, sdout=sdout_pin, 
        standard=I2S.PHILIPS, 
        mode=I2S.MASTER_TX,
        dataformat=I2S.B16, 
        channelformat=I2S.ONLY_LEFT,
        samplerate=SAMPLE_RATE_IN_HZ,
        dmacount=10, dmalen=512)
    
    # configure SD card
    #   slot=2 configures SD card to use the SPI3 controller (VSPI), DMA channel = 2
    #   slot=3 configures SD card to use the SPI2 controller (HSPI), DMA channel = 1
    sd = SDCard(slot=3, sck=Pin(18), mosi=Pin(23), miso=Pin(19), cs=Pin(4))
    uos.mount(sd, "/sd")
    wav_file = '/sd/{}'.format(WAV_FILE)
    wav = open(wav_file,'rb')
    
    # advance to first byte of Data section in WAV file
    pos = wav.seek(44) 
    
    # allocate sample arrays
    #   memoryview used to reduce heap allocation in while loop
    wav_samples = bytearray(1024)
    wav_samples_mv = memoryview(wav_samples)
    
    print('Starting ... ')
    # continuously read audio samples from the WAV file 
    # and write them to an I2S DAC
    
    try:
        while True:
            num_read = wav.readinto(wav_samples_mv)
            num_written = 0
            # end of WAV file?
            if num_read == 0:
                # advance to first byte of Data section
                pos = wav.seek(44) 
            else:
                # loop until all samples are written to the I2S peripheral
                while num_written < num_read:
                    num_written += audio_out.write(wav_samples_mv[num_written:num_read], timeout=0)
            await asyncio.sleep_ms(10)
    except (KeyboardInterrupt, Exception) as e:
        print('caught exception {} {}'.format(type(e).__name__, e))
        raise
    finally:
        wav.close()
        uos.umount("/sd")
        sd.deinit()
        audio_out.deinit()
        print('Done')
Exemple #3
0
class SD:
    def __init__(self,
                 miso=PIN_MISO,
                 mosi=PIN_MOSI,
                 sck=PIN_SCK,
                 cs=PIN_CS,
                 sd_path=SD_PATH):
        self.miso = miso
        self.mosi = mosi
        self.cs = cs
        self.sck = sck
        self.sdcard = None
        self.sd_path = sd_path
        self.inited = False
        self.mounted = False

    def start(self):
        try:
            self.sdcard = SDCard(slot=2,
                                 miso=Pin(self.miso),
                                 mosi=Pin(self.mosi),
                                 sck=Pin(self.sck),
                                 cs=Pin(self.cs))
            self.inited = True
            self.mount()
        except Exception as e:
            sys.print_exception(e)
            print('sd init failure')

    def mount(self):
        try:
            os.mount(self.sdcard, self.sd_path)
            self.mounted = True
        except Exception as e:
            sys.print_exception(e)
            print('sd mount failure')

    def stop(self):
        if self.mounted:
            self.mounted = False
            try:
                os.umount(self.sd_path)
            except Exception as e:
                sys.print_exception(e)
                print('sd umount failure')
        if self.inited:
            try:
                self.sdcard.deinit()
            except Exception as e:
                sys.print_exception(e)
                print('sd deinit failure')
Exemple #4
0
class SD:
    def __init__(self, slot=2, pin=4) -> None:
        self.sd = SDCard(slot=slot, cs=Pin(pin))

    def __del__(self):
        self.sd.__del__()

    def deinit(self):
        self.sd.deinit()

    def mount(self, path="/sd"):
        mount(self.sd, path)

    def umount(self, path="/sd"):
        umount(path)
Exemple #5
0
class FTP_client:

  def __init__(self, ftpsocket):
    global AP_addr, STA_addr
    self.command_client, self.remote_addr = ftpsocket.accept()
    self.remote_addr = self.remote_addr[0]
    self.command_client.settimeout(_COMMAND_TIMEOUT)
    log_msg(1, "FTP Command connection from:", self.remote_addr)
    self.command_client.setsockopt(socket.SOL_SOCKET,
                                   _SO_REGISTER_HANDLER,
                                   self.exec_ftp_command)
    self.command_client.sendall("220 Hello, this is the ULX3S.\r\n")
    self.cwd = '/'
    self.fromname = None
    # self.logged_in = False
    self.act_data_addr = self.remote_addr
    self.DATA_PORT = 20
    self.active = True
    # check which interface was used by comparing the caller's ip
    # adress with the ip adresses of STA and AP; consider netmask;
    # select IP address for passive mode
    if ((AP_addr[1] & AP_addr[2]) ==
       (num_ip(self.remote_addr) & AP_addr[2])):
        self.pasv_data_addr = AP_addr[0]
    elif ((STA_addr[1] & STA_addr[2]) ==
          (num_ip(self.remote_addr) & STA_addr[2])):
        self.pasv_data_addr = STA_addr[0]
    elif ((AP_addr[1] == 0) and (STA_addr[1] != 0)):
        self.pasv_data_addr = STA_addr[0]
    elif ((AP_addr[1] != 0) and (STA_addr[1] == 0)):
        self.pasv_data_addr = AP_addr[0]
    else:
        self.pasv_data_addr = "0.0.0.0"  # Invalid value

  def send_list_data(self, path, data_client, full):
    try:
      for fname in uos.listdir(path):
        data_client.sendall(self.make_description(path, fname, full))
    except:  # path may be a file name or pattern
      path, pattern = self.split_path(path)
      try:
        for fname in uos.listdir(path):
          if self.fncmp(fname, pattern):
            data_client.sendall(
              self.make_description(path, fname, full))
      except:
          pass

  def make_description(self, path, fname, full):
    global _month_name
    if full:
      stat = uos.stat(self.get_absolute_path(path, fname))
      file_permissions = ("drwxr-xr-x"
                          if (stat[0] & 0o170000 == 0o040000)
                          else "-rw-r--r--")
      file_size = stat[6]
      tm = localtime(stat[7])
      if tm[0] != localtime()[0]:
        description = "{} 1 owner group {:>10} {} {:2} {:>5} {}\r\n".\
          format(file_permissions, file_size,
                 _month_name[tm[1]], tm[2], tm[0], fname)
      else:
        description = "{} 1 owner group {:>10} {} {:2} {:02}:{:02} {}\r\n".\
          format(file_permissions, file_size,
                 _month_name[tm[1]], tm[2], tm[3], tm[4], fname)
    else:
      description = fname + "\r\n"
    return description

  def send_file_data(self, path, data_client):
    with open(path,"rb") as file:
      chunk = file.read(_CHUNK_SIZE)
      while len(chunk) > 0:
        data_client.sendall(chunk)
        chunk = file.read(_CHUNK_SIZE)
      data_client.close()

  def save_file_data(self, path, data_client, mode):
    with open(path, mode) as file:
      chunk = data_client.recv(_CHUNK_SIZE)
      while len(chunk) > 0:
        file.write(chunk)
        chunk = data_client.recv(_CHUNK_SIZE)
      data_client.close()

  def get_absolute_path(self, cwd, payload):
    # Just a few special cases "..", "." and ""
    # If payload start's with /, set cwd to /
    # and consider the remainder a relative path
    if payload.startswith('/'):
      cwd = "/"
    for token in payload.split("/"):
      if token == '..':
        cwd = self.split_path(cwd)[0]
      elif token != '.' and token != '':
        if cwd == '/':
          cwd += token
        else:
          cwd = cwd + '/' + token
    return cwd

  def split_path(self, path):  # instead of path.rpartition('/')
    tail = path.split('/')[-1]
    head = path[:-(len(tail) + 1)]
    return ('/' if head == '' else head, tail)

  # compare fname against pattern. Pattern may contain
  # the wildcards ? and *.
  def fncmp(self, fname, pattern):
    pi = 0
    si = 0
    while pi < len(pattern) and si < len(fname):
      if (fname[si] == pattern[pi]) or (pattern[pi] == '?'):
        si += 1
        pi += 1
      else:
        if pattern[pi] == '*':  # recurse
          if pi == len(pattern.rstrip("*?")):  # only wildcards left
            return True
          while si < len(fname):
            if self.fncmp(fname[si:], pattern[pi + 1:]):
              return True
            else:
              si += 1
          return False
        else:
          return False
    if pi == len(pattern.rstrip("*")) and si == len(fname):
      return True
    else:
      return False

  def open_dataclient(self):
    if self.active:  # active mode
      data_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      data_client.settimeout(_DATA_TIMEOUT)
      data_client.connect((self.act_data_addr, self.DATA_PORT))
      log_msg(1, "FTP Data connection with:", self.act_data_addr)
    else:  # passive mode
      data_client, data_addr = datasocket.accept()
      log_msg(1, "FTP Data connection with:", data_addr[0])
    return data_client

  def mount(self):
    try:
      self.sd = SDCard(slot=3)
      uos.mount(self.sd,"/sd")
      return True
    except:
      return False

  def umount(self):
    try:
      uos.umount("/sd")
      try:
        self.sd.deinit()
        del self.sd
      except:
        pass
      # let all SD pins be inputs
      for i in bytearray([2,4,12,13,14,15]):
        p = Pin(i,Pin.IN)
        a = p.value()
        del p, a
      return True
    except:
      return False

  def exec_ftp_command(self, cl):
    global datasocket
    global client_busy
    global my_ip_addr

    try:
      collect()

      data = cl.readline().decode("utf-8").rstrip("\r\n")

      if len(data) <= 0:
          # No data, close
          # This part is NOT CLEAN; there is still a chance that a
          # closing data connection will be signalled as closing
          # command connection
          log_msg(1, "*** No data, assume QUIT")
          close_client(cl)
          return

      if client_busy:  # check if another client is busy
          cl.sendall("400 Device busy.\r\n")  # tell so the remote client
          return  # and quit
      client_busy = True  # now it's my turn

      # check for log-in state may done here, like
      # if self.logged_in == False and not command in\
      #    ("USER", "PASS", "QUIT"):
      #    cl.sendall("530 Not logged in.\r\n")
      #    return

      command = data.split()[0].upper()
      payload = data[len(command):].lstrip()  # partition is missing
      path = self.get_absolute_path(self.cwd, payload)
      log_msg(1, "Command={}, Payload={}".format(command, payload))

      if command == "USER":
        # self.logged_in = True
        cl.sendall("230 Logged in.\r\n")
        # If you want to see a password,return
        #   "331 Need password.\r\n" instead
        # If you want to reject an user, return
        #   "530 Not logged in.\r\n"
      elif command == "PASS":
        # you may check here for a valid password and return
        # "530 Not logged in.\r\n" in case it's wrong
        # self.logged_in = True
        cl.sendall("230 Logged in.\r\n")
      elif command == "SYST":
        cl.sendall("215 UNIX Type: L8\r\n")
      elif command in ("TYPE", "NOOP", "ABOR"):  # just accept & ignore
        cl.sendall('200 OK\r\n')
      elif command == "QUIT":
        cl.sendall('221 Bye.\r\n')
        close_client(cl)
      elif command == "PWD" or command == "XPWD":
        cl.sendall('257 "{}"\r\n'.format(self.cwd))
      elif command == "CWD" or command == "XCWD":
        try:
          if (uos.stat(path)[0] & 0o170000) == 0o040000:
            self.cwd = path
            cl.sendall('250 OK\r\n')
          else:
            cl.sendall('550 Fail\r\n')
        except:
          cl.sendall('550 Fail\r\n')
      elif command == "PASV":
        cl.sendall('227 Entering Passive Mode ({},{},{}).\r\n'.format(
          self.pasv_data_addr.replace('.', ','),
          _DATA_PORT >> 8, _DATA_PORT % 256))
        self.active = False
      elif command == "PORT":
        items = payload.split(",")
        if len(items) >= 6:
          self.act_data_addr = '.'.join(items[:4])
          if self.act_data_addr == "127.0.1.1":
            # replace by command session addr
            self.act_data_addr = self.remote_addr
          self.DATA_PORT = int(items[4]) * 256 + int(items[5])
          cl.sendall('200 OK\r\n')
          self.active = True
        else:
            cl.sendall('504 Fail\r\n')
      elif command == "LIST" or command == "NLST":
        if payload.startswith("-"):
          option = payload.split()[0].lower()
          path = self.get_absolute_path(
                 self.cwd, payload[len(option):].lstrip())
        else:
          option = ""
        try:
          data_client = self.open_dataclient()
          cl.sendall("150 Directory listing:\r\n")
          self.send_list_data(path, data_client,
                              command == "LIST" or 'l' in option)
          cl.sendall("226 Done.\r\n")
          data_client.close()
        except:
          cl.sendall('550 Fail\r\n')
          if data_client is not None:
            data_client.close()
      elif command == "RETR":
        try:
          data_client = self.open_dataclient()
          cl.sendall("150 Opened data connection.\r\n")
          self.send_file_data(path, data_client)
          # if the next statement is reached,
          # the data_client was closed.
          data_client = None
          cl.sendall("226 Done.\r\n")
        except:
          cl.sendall('550 Fail\r\n')
          if data_client is not None:
            data_client.close()
      elif command == "STOR" or command == "APPE":
        result = False
        try:
          data_client = self.open_dataclient()
          cl.sendall("150 Opened data connection.\r\n")
          if path == "/fpga":
            import ecp5
            ecp5.prog_stream(data_client,_CHUNK_SIZE)
            result = ecp5.prog_close()
            data_client.close()
          elif path.startswith("/flash@"):
            import ecp5
            dummy, addr = path.split("@")
            addr = int(addr)
            result = ecp5.flash_stream(data_client,addr)
            ecp5.flash_close()
            del addr, dummy
            data_client.close()
          elif path.startswith("/sd@"):
            import sdraw
            dummy, addr = path.split("@")
            addr = int(addr)
            sd_raw = sdraw.sdraw()
            result = sd_raw.sd_write_stream(data_client,addr)
            del sd_raw, addr, dummy
            data_client.close()
          else:
            self.save_file_data(path, data_client,
                                "w" if command == "STOR" else "a")
            result = True
          # if the next statement is reached,
          # the data_client was closed.
          data_client = None
        except:
          if data_client is not None:
            data_client.close()
        if result:
          cl.sendall("226 Done.\r\n")
        else:
          cl.sendall('550 Fail\r\n')
        del result
      elif command == "SIZE":
        try:
          cl.sendall('213 {}\r\n'.format(uos.stat(path)[6]))
        except:
          cl.sendall('550 Fail\r\n')
      elif command == "STAT":
        if payload == "":
          cl.sendall("211-Connected to ({})\r\n"
                     "    Data address ({})\r\n"
                     "    TYPE: Binary STRU: File MODE: Stream\r\n"
                     "    Session timeout {}\r\n"
                     "211 Client count is {}\r\n".format(
                      self.remote_addr, self.pasv_data_addr,
                      _COMMAND_TIMEOUT, len(client_list)))
        else:
          cl.sendall("213-Directory listing:\r\n")
          self.send_list_data(path, cl, True)
          cl.sendall("213 Done.\r\n")
      elif command == "DELE":
        try:
          uos.remove(path)
          cl.sendall('250 OK\r\n')
        except:
          cl.sendall('550 Fail\r\n')
      elif command == "RNFR":
        try:
          # just test if the name exists, exception if not
          uos.stat(path)
          self.fromname = path
          cl.sendall("350 Rename from\r\n")
        except:
          cl.sendall('550 Fail\r\n')
      elif command == "RNTO":
        try:
          uos.rename(self.fromname, path)
          cl.sendall('250 OK\r\n')
        except:
          cl.sendall('550 Fail\r\n')
        self.fromname = None
      elif command == "CDUP" or command == "XCUP":
        self.cwd = self.get_absolute_path(self.cwd, "..")
        cl.sendall('250 OK\r\n')
      elif command == "RMD" or command == "XRMD":
        try:
          uos.rmdir(path)
          cl.sendall('250 OK\r\n')
        except:
          cl.sendall('550 Fail\r\n')
      elif command == "MKD" or command == "XMKD":
        try:
          uos.mkdir(path)
          cl.sendall('250 OK\r\n')
        except:
          cl.sendall('550 Fail\r\n')
      elif command == "SITE":
        if path == "/mount":
          if self.mount():
            cl.sendall('250 OK\r\n')
          else:
            cl.sendall('550 Fail\r\n')
        elif path == "/umount":
          if self.umount():
            cl.sendall('250 OK\r\n')
          else:
            cl.sendall('550 Fail\r\n')
        elif path == "/passthru":
          import ecp5
          ecp5.passthru()
          cl.sendall('250 OK passthru\r\n')
        elif path.endswith(".bit") or path.endswith(".bit.gz"):
          try:
            import ecp5
            if ecp5.prog(path, close=False):
              if path.startswith("/sd/"):
                try:
                  self.umount()
                  cl.sendall('111 umount /sd OK\r\n')
                except:
                  cl.sendall('411 umount /sd Fail\r\n')
              if ecp5.prog_close():
                cl.sendall('250 OK\r\n')
              else:
                cl.sendall('550 Fail\r\n')
            else:
              cl.sendall('550 Fail\r\n')
          except:
            cl.sendall('550 Fail\r\n')
        else:
          if path.startswith("/"):
            exe=path[1:]
          else:
            exe=path
          try:
            exec(exe)
            cl.sendall('250 OK '+exe+'\r\n')
          except:
            cl.sendall('550 Fail '+exe+'\r\n')
          del exe
      else:
        cl.sendall("502 Unsupported command.\r\n")
        # log_msg(2,
        #  "Unsupported command {} with payload {}".format(command,
        #  payload))
    # handle unexpected errors
    except Exception as err:
      log_msg(1, "Exception in exec_ftp_command: {}".format(err))
    # tidy up before leaving
    client_busy = False
Exemple #6
0
# allocate sample arrays
#   memoryview used to reduce heap allocation in while loop
wav_samples = bytearray(1024)
wav_samples_mv = memoryview(wav_samples)

print('Starting')
# continuously read audio samples from the WAV file
# and write them to an I2S DAC
while True:
    try:
        num_read = wav.readinto(wav_samples_mv)
        num_written = 0
        # end of WAV file?
        if num_read == 0:
            # advance to first byte of Data section
            pos = wav.seek(44)
        else:
            # loop until all samples are written to the I2S peripheral
            while num_written < num_read:
                num_written += audio_out.write(
                    wav_samples_mv[num_written:num_read], timeout=0)
    except (KeyboardInterrupt, Exception) as e:
        print('caught exception {} {}'.format(type(e).__name__, e))
        break

wav.close()
uos.umount("/sd")
sd.deinit()
audio_out.deinit()
print('Done')
Exemple #7
0
class CardManager:
    """
    High level auto-mounting SD Card manager
    """
    def __init__(self, directory="/sd"):
        self._card = None
        self._directory = directory

        if Helpers.exists(self._directory) and \
                not Helpers.is_dir(self._directory):
            raise Exception("Cannot mount {}".format(self._directory))

        if not Helpers.exists(self._directory):
            uos.mkdir(self._directory)

        # Setup and IRQ on the CD line to auto-mount/dismount the sdcard
        pin = Pin(PIN_CD, Pin.IN, Pin.PULL_UP)
        pin.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING,
                handler=self._card_state_change)
        # These are for debouncing the card detect signal
        self._debounce_timer = None
        self._debounce = False

        self._card_state_change(pin)

    def is_card_present(self):
        return bool(self._card is not None)

    def _remove_debounce(self, test):
        self._debounce = False

    def _init_card(self):
        try:
            if self._card is None:
                self._card = SDCard(slot=SLOT,
                                    mosi=PIN_MOSI,
                                    miso=PIN_MISO,
                                    sck=PIN_SCK,
                                    cs=PIN_CS)
        except Exception:
            raise Exception("Card reader not present")

        return self._card

    def _deinit_card(self):
        self._card.deinit()
        self._card = None

    def _card_state_change(self, pin):
        """
        Need to debounce this
        :param pin:
        :return:
        """
        if self._debounce:
            return

        self._debounce = True
        self._debounce_timer = Timer(-1)
        self._debounce_timer.init(period=DEBOUNCE_TIME,
                                  mode=Timer.ONE_SHOT,
                                  callback=self._remove_debounce)

        irq_state = disable_irq()

        if pin.value():  # No card present
            if self._card:  # Card may not be present on boot
                enable_irq(irq_state)
                uos.umount(self._directory)
                irq_state = disable_irq()
                self._deinit_card()
        else:
            try:
                card = self._init_card()
                enable_irq(irq_state)
                uos.mount(card, self._directory)
                irq_state = disable_irq()
            except OSError:  # Mount issue, probably EPERM
                pass

        enable_irq(irq_state)
        continue  # keine '.py' datei, nächste datei in liste nehemen

    a = open(file, 'r')  # file open in quelle read A
    if TTGO:
        b = open('/sd/' + file, 'w')  # file open in zieldir write B
    else:
        b = open('subdir/' + file, 'w')  # file open in zieldir write B

    kopf = "# ************ {} ****************\n".format(file)
    b.write(kopf)  # titel
    # for zeilen in A
    while True:
        # read zeile n in A
        y = a.readline()  # achtung : geht nur bei ASCII, zb. "xxx.py"
        if y == '':  # nichts mehr gelesen ?
            break
        print(y, end='')  # zeigen wie gelesen
        b.write(y)  # write zeile nach B
        pass  # next zeile
    # eine datei fertig
    # close A,B
    a.close()
    b.close()
    pass  # nächste datei

# unmount sd karte
if TTGO:
    os.umount('/sd')
    filesystem.deinit()
    del filesystem
Exemple #9
0
class sdraw:
    #def __init__(self):
    #print("SD RAW writer")
    #self.init_pinout_sd()

    def stopwatch_start(self):
        self.stopwatch_ms = ticks_ms()

    def stopwatch_stop(self, bytes_uploaded):
        elapsed_ms = ticks_ms() - self.stopwatch_ms
        transfer_rate_MBps = 0
        if elapsed_ms > 0:
            transfer_rate_kBps = bytes_uploaded // elapsed_ms
        print("%d bytes uploaded in %d ms (%d kB/s)" %
              (bytes_uploaded, elapsed_ms, transfer_rate_kBps))

    def open_file(self, filename, gz=False):
        filedata = open(filename, "rb")
        if gz:
            import uzlib
            return uzlib.DecompIO(filedata, 31)
        return filedata

    def open_web(self, url, gz=False):
        import socket
        _, _, host, path = url.split('/', 3)
        port = 80
        if (len(host.split(':')) == 2):
            host, port = host.split(':', 2)
        print("host = ", host, " port = ", port, " path = ", path)
        addr = socket.getaddrinfo(host, port)[0][-1]
        s = socket.socket()
        s.connect(addr)
        s.send(
            bytes(
                'GET /%s HTTP/1.0\r\nHost: %s\r\nAccept:  image/*\r\n\r\n' %
                (path, host), 'utf8'))
        for i in range(100):  # read first 100 lines searching for
            if len(s.readline()) < 3:  # first empty line (contains "\r\n")
                break
        if gz:
            import uzlib
            return uzlib.DecompIO(s, 31)
        return s

    def sd_open(self):
        self.sd = SDCard(slot=3)

    def sd_close(self):
        self.sd.deinit()
        for i in bytearray([2, 4, 12, 13, 14, 15]):
            p = Pin(i, Pin.IN)
            a = p.value()
            del p, a
        del self.sd

    def sd_check_param(self, addr):
        if addr & 0x1FF:
            print("parameter must be rounded to block_size = 512 bytes")
            return False
        return True

    # negative addr means reference from end of the card
    def sd_wrapaddr(self, addr):
        if addr >= 0:
            return addr
        cardsize = self.sd.ioctl(4, 0) * 0x200
        return cardsize + addr

    def sd_read(self, data, addr=0):
        if not self.sd_check_param(addr) or not self.sd_check_param(len(data)):
            return False
        self.sd_open()
        self.sd.readblocks(self.sd_wrapaddr(addr) // 0x200, data)
        self.sd_close()
        return True

    def sd_write_stream(self, filedata, addr=0, blocksize=16384):
        if not self.sd_check_param(addr):
            return False
        bytes_uploaded = 0
        self.sd_open()
        addr = self.sd_wrapaddr(addr)
        nearend = self.sd_wrapaddr(-blocksize)
        self.stopwatch_start()
        block = bytearray(blocksize)
        while True:
            waddr = addr + bytes_uploaded
            if waddr >= nearend and len(block) > 0x200:
                block = bytearray(0x200)
            if filedata.readinto(block):
                self.sd.writeblocks(waddr // 0x200, block)
                bytes_uploaded += len(block)
            else:
                break
        self.stopwatch_stop(bytes_uploaded)
        self.sd_close()
        return True