From f723aecc9d83b6ab9b4b77ebe7eb51402c0a51d1 Mon Sep 17 00:00:00 2001 From: Christopher Scheuring Date: Tue, 17 Jan 2023 01:51:29 +0100 Subject: [PATCH] Add initial files for the HH Hacking 101 - code and infos about board will follow soon --- README.md | 4 +- code-snippets/ADC_Photo_Resistor.py | 9 + code-snippets/Button_LED_Test.py | 55 ++++++ code-snippets/KeyPad_4x4_esp32.py | 83 ++++++++ code-snippets/SD_Card_ESP32.py | 51 +++++ scripts/init_flash.sh | 31 +++ src/fw-bin/README.md | 10 + src/lib/bmp180.py | 187 ++++++++++++++++++ src/lib/sdcard.py | 283 ++++++++++++++++++++++++++++ 9 files changed, 711 insertions(+), 2 deletions(-) create mode 100644 code-snippets/ADC_Photo_Resistor.py create mode 100644 code-snippets/Button_LED_Test.py create mode 100644 code-snippets/KeyPad_4x4_esp32.py create mode 100644 code-snippets/SD_Card_ESP32.py create mode 100755 scripts/init_flash.sh create mode 100644 src/fw-bin/README.md create mode 100644 src/lib/bmp180.py create mode 100644 src/lib/sdcard.py diff --git a/README.md b/README.md index 1d5becc..e75ed07 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# hw-hacking-101 +# Hardware-Hacking-101 -This repo contains the tools, infos, hardware details for the hardware hacking 101 sessions. \ No newline at end of file +Hardware Hacking Learning Platform for Educational Usage like Schools, University etc. diff --git a/code-snippets/ADC_Photo_Resistor.py b/code-snippets/ADC_Photo_Resistor.py new file mode 100644 index 0000000..27ac59a --- /dev/null +++ b/code-snippets/ADC_Photo_Resistor.py @@ -0,0 +1,9 @@ +from machine import ADC, Pin +from time import sleep + +adc = ADC(Pin(34)) # create an ADC object acting on a pin +adc.atten(ADC.ATTN_11DB) # 3.3V max + +while True: + print("Analog Value: " , adc.read()) + sleep(1) \ No newline at end of file diff --git a/code-snippets/Button_LED_Test.py b/code-snippets/Button_LED_Test.py new file mode 100644 index 0000000..fb4f121 --- /dev/null +++ b/code-snippets/Button_LED_Test.py @@ -0,0 +1,55 @@ +## Simple Button and LED test +# yellow led will blink +# red + green will be off button is pressed + +from machine import Pin +import time + +## define led pins +led_gr_p = 21 +led_re_p = 22 +led_ye_p = 17 + +## define switch pins +sw1_p = 32 +sw2_p = 33 + + +## init leds +led_gr = Pin(led_gr_p, Pin.OUT) +led_gr.off() # switch on led - inverted logic! + +led_re = Pin(led_re_p, Pin.OUT) +led_re.off() # switch on led - inverted logic! + +led_ye = Pin(led_ye_p, Pin.OUT) +led_ye.off() # switch on led - inverted logic! + + +## int buttons (pull-up) +sw1 = Pin(sw1_p, Pin.IN) +sw2 = Pin(sw2_p, Pin.IN) + +while True: + if sw1.value(): + print("sw1 true") + led_re.on() + #led_ye.on() + else: + print("sw1 false") + led_re.off() + #led_ye.off() + if sw2.value(): + print("sw2 true") + led_gr.on() + else: + print("sw2 false") + led_gr.off() + + # toggle led + if led_ye.value(): + led_ye.off(); + else: + led_ye.on(); + time.sleep(0.5) + \ No newline at end of file diff --git a/code-snippets/KeyPad_4x4_esp32.py b/code-snippets/KeyPad_4x4_esp32.py new file mode 100644 index 0000000..b1419cf --- /dev/null +++ b/code-snippets/KeyPad_4x4_esp32.py @@ -0,0 +1,83 @@ +# KeyPad Vers #2 +# Tony Goodhew 9th May 2020 +# https://www.instructables.com/Using-a-4x4-KeyPad-With-CircuitPython/ +# +# Changes by Christopher Scheuring 2022-05-13 for ESP32 support +# +# Connections left to right on KeyPad +# 0 1 2 3 4 5 6 7 +# 13 12 14 27 15 02 04 16 + +from machine import Pin +import gc +import time + +gc.collect() # make some room + +# Set up LED on pin 17 +led_pin = 17 + +led = Pin(led_pin, Pin.OUT) +led.on() # switch off led - inverted logic! + +# Set up Rows +rows = [] +for p in [13, 12, 14, 27]: + row = Pin(p, Pin.OUT) + rows.append(row) +# anodes OFF +for i in range(4): + rows[i].off() + +# Set up columns +cols = [] +for p in [15, 02, 04, 16]: + col = Pin(p, Pin.IN, Pin.PULL_DOWN) + cols.append(col) + +def getkey(): # Returns -999 or key value + values = [1,2,3,10, 4,5,6,11, 7,8,9,12, 14,0,15,13] + val = -999 # Error value for no key press + for count in range(10): # Try to get key press 10 times + for r in range(4): # Rows, one at a time + rows[r].on() # row HIGH + for c in range(4): # Test columns, one at a time + if cols[c].value() == 1: # Is column HIGH? + p = r * 4 + c # Pointer to values list + val = values[p] + count = 11 # This stops looping + led.off() # Flash LED ON if key pressed + rows[r].off() # row LOW + time.sleep(0.2) # Debounce + led.on() # LED OFF + return val + +def getvalue(digits): # Number of digits + result = 0 + count = 0 + while True: + x = getkey() + if x != -999 and x < 10: # Check if numeric key pressed + result = result * 10 + x + print(result) + count = count + 1 + if count == digits: + return result + +# +++++ Main +++++ +print("Press 4 numeric (BLUE) keys") +y = getvalue(4) +print('\n',y) + +print("\nPress any of the keys\n # halts the program") + +characters =["1","2","3","4","5","6","7","8","9","A","B","C","D","*","#","0"] +running = True +while running: + x = getkey() + if x != -999: # A key has been pressed! + print(characters[x-1]) + if x == 15: + running = False + +led.on() diff --git a/code-snippets/SD_Card_ESP32.py b/code-snippets/SD_Card_ESP32.py new file mode 100644 index 0000000..e91e33e --- /dev/null +++ b/code-snippets/SD_Card_ESP32.py @@ -0,0 +1,51 @@ +from machine import Pin, SPI +import machine, sdcard, os, esp32 + +spi = SPI(2, baudrate=10000000, polarity=0, phase=0, sck=machine.Pin(18), mosi=machine.Pin(23), miso=machine.Pin(19)) +sd = sdcard.SDCard(spi, machine.Pin(5)) + +os.mount(sd, '/sd') + +def print_directory(path, tabs = 0): + for file in os.listdir(path): + stats = os.stat(path+"/"+file) + filesize = stats[6] + isdir = stats[0] & 0x4000 + + if filesize < 1000: + sizestr = str(filesize) + " byte" + elif filesize < 1000000: + sizestr = "%0.1f KB" % (filesize/1000) + else: + sizestr = "%0.1f MB" % (filesize/1000000) + + prettyprintname = "" + for i in range(tabs): + prettyprintname += " " + prettyprintname += file + if isdir: + prettyprintname += "/" + print('{0:<40} Size: {1:>10}'.format(prettyprintname, sizestr)) + + # recursively print directory contents + if isdir: + print_directory(path+"/"+file, tabs+1) + +#with open("/sd/test1.txt", "w") as f: +# f.write("Hello world!\r\n") + +print("Files on filesystem:") +print("====================") +print_directory("/sd") + +# +#print("Files on filesystem:") +#print("====================") +#print_directory("/sd") +# +#with open("/sd/test/test1.txt", "w") as f: +# f.write("Hello world!\r\n") +# +#print("Files on filesystem:") +#print("====================") +#print_directory("/sd/test") diff --git a/scripts/init_flash.sh b/scripts/init_flash.sh new file mode 100755 index 0000000..6cd9177 --- /dev/null +++ b/scripts/init_flash.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +############################################################################## +# This script inits the esp with the miropython image +# end some needed dependencies like the sdcard and +# bmp180 libs +# +# This scripts has following dependencies: +# - esptool.py (https://github.com/espressif/esptool) +# - ampy (https://github.com/scientifichackers/ampy) +# +# The paths, files etc. may need to be configured +# to your own environment +# +# Script is provided by Christopher Scheuring +# as it is. Use by your own risks. +# +############################################################################## + +echo "Flush the esp32 etc. using the esptool and ampy for uploading the micropyhton libs." +python3 /usr/local/bin/esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash +echo "Flashing starts in 1 second..." +sleep 1 +python3 /usr/local/bin/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 230400 write_flash -z 0x1000 ../src/fw-bin/esp32-20220117-v1.18.bin +echo "Flash micropython finished" +sleep 1 +echo "Upload micropython dependency..." +ampy -p /dev/ttyUSB0 put ../src/lib/sdcard.py +ampy -p /dev/ttyUSB0 put ../src/lib/bmp180.py +ampy -p /dev/ttyUSB0 ls +echo "Flashing finished" diff --git a/src/fw-bin/README.md b/src/fw-bin/README.md new file mode 100644 index 0000000..9bd630f --- /dev/null +++ b/src/fw-bin/README.md @@ -0,0 +1,10 @@ +Put your ESP firmware for flashing e.g. inside this dir + +ESP32 firmware is available from the Espressif site: +https://docs.espressif.com/projects/esp-at/en/latest/esp32/AT_Binary_Lists/ESP32_AT_binaries.html + +ESP32 micropython firmware: +https://micropython.org/download/esp32/ + +Infos about flashing etc: +https://docs.espressif.com/projects/esp-at/en/latest/esp32/Get_Started/Downloading_guide.html diff --git a/src/lib/bmp180.py b/src/lib/bmp180.py new file mode 100644 index 0000000..ff3e899 --- /dev/null +++ b/src/lib/bmp180.py @@ -0,0 +1,187 @@ +''' +bmp180 is a micropython module for the Bosch BMP180 sensor. It measures +temperature as well as pressure, with a high enough resolution to calculate +altitude. +Breakoutboard: http://www.adafruit.com/products/1603 +data-sheet: http://ae-bst.resource.bosch.com/media/products/dokumente/ +bmp180/BST-BMP180-DS000-09.pdf + +The MIT License (MIT) +Copyright (c) 2014 Sebastian Plamauer, oeplse@gmail.com +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +''' + +from ustruct import unpack as unp +from machine import I2C, Pin +import math +import time + +# BMP180 class +class BMP180(): + ''' + Module for the BMP180 pressure sensor. + ''' + + _bmp_addr = 119 # adress of BMP180 is hardcoded on the sensor + + # init + def __init__(self, i2c_bus): + + # create i2c obect + _bmp_addr = self._bmp_addr + self._bmp_i2c = i2c_bus + #self._bmp_i2c.start() # uncomment for SoftI2C ESP usage + self.chip_id = self._bmp_i2c.readfrom_mem(_bmp_addr, 0xD0, 2) + # read calibration data from EEPROM + self._AC1 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xAA, 2))[0] + self._AC2 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xAC, 2))[0] + self._AC3 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xAE, 2))[0] + self._AC4 = unp('>H', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB0, 2))[0] + self._AC5 = unp('>H', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB2, 2))[0] + self._AC6 = unp('>H', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB4, 2))[0] + self._B1 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB6, 2))[0] + self._B2 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB8, 2))[0] + self._MB = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xBA, 2))[0] + self._MC = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xBC, 2))[0] + self._MD = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xBE, 2))[0] + + # settings to be adjusted by user + self.oversample_setting = 3 + self.baseline = 101325.0 + + # output raw + self.UT_raw = None + self.B5_raw = None + self.MSB_raw = None + self.LSB_raw = None + self.XLSB_raw = None + self.gauge = self.makegauge() # Generator instance + for _ in range(128): + next(self.gauge) + time.sleep_ms(1) + + def compvaldump(self): + ''' + Returns a list of all compensation values + ''' + return [self._AC1, self._AC2, self._AC3, self._AC4, self._AC5, self._AC6, + self._B1, self._B2, self._MB, self._MC, self._MD, self.oversample_setting] + + # gauge raw + def makegauge(self): + ''' + Generator refreshing the raw measurments. + ''' + delays = (5, 8, 14, 25) + while True: + self._bmp_i2c.writeto_mem(self._bmp_addr, 0xF4, bytearray([0x2E])) + t_start = time.ticks_ms() + while (time.ticks_ms() - t_start) <= 5: # 5mS delay + yield None + try: + self.UT_raw = self._bmp_i2c.readfrom_mem(self._bmp_addr, 0xF6, 2) + except: + yield None + self._bmp_i2c.writeto_mem(self._bmp_addr, 0xF4, bytearray([0x34+(self.oversample_setting << 6)])) + t_pressure_ready = delays[self.oversample_setting] + t_start = time.ticks_ms() + while (time.ticks_ms() - t_start) <= t_pressure_ready: + yield None + try: + self.MSB_raw = self._bmp_i2c.readfrom_mem(self._bmp_addr, 0xF6, 1) + self.LSB_raw = self._bmp_i2c.readfrom_mem(self._bmp_addr, 0xF7, 1) + self.XLSB_raw = self._bmp_i2c.readfrom_mem(self._bmp_addr, 0xF8, 1) + except: + yield None + yield True + + def blocking_read(self): + if next(self.gauge) is not None: # Discard old data + pass + while next(self.gauge) is None: + pass + + @property + def oversample_sett(self): + return self.oversample_setting + + @oversample_sett.setter + def oversample_sett(self, value): + if value in range(4): + self.oversample_setting = value + else: + print('oversample_sett can only be 0, 1, 2 or 3, using 3 instead') + self.oversample_setting = 3 + + @property + def temperature(self): + ''' + Temperature in degree C. + ''' + next(self.gauge) + try: + UT = unp('>H', self.UT_raw)[0] + except: + return 0.0 + X1 = (UT-self._AC6)*self._AC5/2**15 + X2 = self._MC*2**11/(X1+self._MD) + self.B5_raw = X1+X2 + return (((X1+X2)+8)/2**4)/10 + + @property + def pressure(self): + ''' + Pressure in mbar. + ''' + next(self.gauge) + self.temperature # Populate self.B5_raw + try: + MSB = unp('B', self.MSB_raw)[0] + LSB = unp('B', self.LSB_raw)[0] + XLSB = unp('B', self.XLSB_raw)[0] + except: + return 0.0 + UP = ((MSB << 16)+(LSB << 8)+XLSB) >> (8-self.oversample_setting) + B6 = self.B5_raw-4000 + X1 = (self._B2*(B6**2/2**12))/2**11 + X2 = self._AC2*B6/2**11 + X3 = X1+X2 + B3 = ((int((self._AC1*4+X3)) << self.oversample_setting)+2)/4 + X1 = self._AC3*B6/2**13 + X2 = (self._B1*(B6**2/2**12))/2**16 + X3 = ((X1+X2)+2)/2**2 + B4 = abs(self._AC4)*(X3+32768)/2**15 + B7 = (abs(UP)-B3) * (50000 >> self.oversample_setting) + if B7 < 0x80000000: + pressure = (B7*2)/B4 + else: + pressure = (B7/B4)*2 + X1 = (pressure/2**8)**2 + X1 = (X1*3038)/2**16 + X2 = (-7357*pressure)/2**16 + return pressure+(X1+X2+3791)/2**4 + + @property + def altitude(self): + ''' + Altitude in m. + ''' + try: + p = -7990.0*math.log(self.pressure/self.baseline) + except: + p = 0.0 + return p diff --git a/src/lib/sdcard.py b/src/lib/sdcard.py new file mode 100644 index 0000000..2c3e99d --- /dev/null +++ b/src/lib/sdcard.py @@ -0,0 +1,283 @@ +""" +MicroPython driver for SD cards using SPI bus. + +Requires an SPI bus and a CS pin. Provides readblocks and writeblocks +methods so the device can be mounted as a filesystem. + +Example usage on pyboard: + + import pyb, sdcard, os + sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5) + pyb.mount(sd, '/sd2') + os.listdir('/') + +Example usage on ESP8266: + + import machine, sdcard, os + sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15)) + os.mount(sd, '/sd') + os.listdir('/') + +""" + +from micropython import const +import time + + +_CMD_TIMEOUT = const(100) + +_R1_IDLE_STATE = const(1 << 0) +# R1_ERASE_RESET = const(1 << 1) +_R1_ILLEGAL_COMMAND = const(1 << 2) +# R1_COM_CRC_ERROR = const(1 << 3) +# R1_ERASE_SEQUENCE_ERROR = const(1 << 4) +# R1_ADDRESS_ERROR = const(1 << 5) +# R1_PARAMETER_ERROR = const(1 << 6) +_TOKEN_CMD25 = const(0xFC) +_TOKEN_STOP_TRAN = const(0xFD) +_TOKEN_DATA = const(0xFE) + + +class SDCard: + def __init__(self, spi, cs, baudrate=1320000): + self.spi = spi + self.cs = cs + + self.cmdbuf = bytearray(6) + self.dummybuf = bytearray(512) + self.tokenbuf = bytearray(1) + for i in range(512): + self.dummybuf[i] = 0xFF + self.dummybuf_memoryview = memoryview(self.dummybuf) + + # initialise the card + self.init_card(baudrate) + + def init_spi(self, baudrate): + try: + master = self.spi.MASTER + except AttributeError: + # on ESP8266 + self.spi.init(baudrate=baudrate, phase=0, polarity=0) + else: + # on pyboard + self.spi.init(master, baudrate=baudrate, phase=0, polarity=0) + + def init_card(self, baudrate): + + # init CS pin + self.cs.init(self.cs.OUT, value=1) + + # init SPI bus; use low data rate for initialisation + self.init_spi(100000) + + # clock card at least 100 cycles with cs high + for i in range(16): + self.spi.write(b"\xff") + + # CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts) + for _ in range(5): + if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE: + break + else: + raise OSError("no SD card") + + # CMD8: determine card version + r = self.cmd(8, 0x01AA, 0x87, 4) + if r == _R1_IDLE_STATE: + self.init_card_v2() + elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND): + self.init_card_v1() + else: + raise OSError("couldn't determine SD card version") + + # get the number of sectors + # CMD9: response R2 (R1 byte + 16-byte block read) + if self.cmd(9, 0, 0, 0, False) != 0: + raise OSError("no response from SD card") + csd = bytearray(16) + self.readinto(csd) + if csd[0] & 0xC0 == 0x40: # CSD version 2.0 + self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024 + elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB) + c_size = csd[6] & 0b11 | csd[7] << 2 | (csd[8] & 0b11000000) << 4 + c_size_mult = ((csd[9] & 0b11) << 1) | csd[10] >> 7 + self.sectors = (c_size + 1) * (2 ** (c_size_mult + 2)) + else: + raise OSError("SD card CSD format not supported") + # print('sectors', self.sectors) + + # CMD16: set block length to 512 bytes + if self.cmd(16, 512, 0) != 0: + raise OSError("can't set 512 block size") + + # set to high data rate now that it's initialised + self.init_spi(baudrate) + + def init_card_v1(self): + for i in range(_CMD_TIMEOUT): + self.cmd(55, 0, 0) + if self.cmd(41, 0, 0) == 0: + self.cdv = 512 + # print("[SDCard] v1 card") + return + raise OSError("timeout waiting for v1 card") + + def init_card_v2(self): + for i in range(_CMD_TIMEOUT): + time.sleep_ms(50) + self.cmd(58, 0, 0, 4) + self.cmd(55, 0, 0) + if self.cmd(41, 0x40000000, 0) == 0: + self.cmd(58, 0, 0, 4) + self.cdv = 1 + # print("[SDCard] v2 card") + return + raise OSError("timeout waiting for v2 card") + + def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False): + self.cs(0) + + # create and send the command + buf = self.cmdbuf + buf[0] = 0x40 | cmd + buf[1] = arg >> 24 + buf[2] = arg >> 16 + buf[3] = arg >> 8 + buf[4] = arg + buf[5] = crc + self.spi.write(buf) + + if skip1: + self.spi.readinto(self.tokenbuf, 0xFF) + + # wait for the response (response[7] == 0) + for i in range(_CMD_TIMEOUT): + self.spi.readinto(self.tokenbuf, 0xFF) + response = self.tokenbuf[0] + if not (response & 0x80): + # this could be a big-endian integer that we are getting here + for j in range(final): + self.spi.write(b"\xff") + if release: + self.cs(1) + self.spi.write(b"\xff") + return response + + # timeout + self.cs(1) + self.spi.write(b"\xff") + return -1 + + def readinto(self, buf): + self.cs(0) + + # read until start byte (0xff) + for i in range(_CMD_TIMEOUT): + self.spi.readinto(self.tokenbuf, 0xFF) + if self.tokenbuf[0] == _TOKEN_DATA: + break + time.sleep_ms(1) + else: + self.cs(1) + raise OSError("timeout waiting for response") + + # read data + mv = self.dummybuf_memoryview + if len(buf) != len(mv): + mv = mv[: len(buf)] + self.spi.write_readinto(mv, buf) + + # read checksum + self.spi.write(b"\xff") + self.spi.write(b"\xff") + + self.cs(1) + self.spi.write(b"\xff") + + def write(self, token, buf): + self.cs(0) + + # send: start of block, data, checksum + self.spi.read(1, token) + self.spi.write(buf) + self.spi.write(b"\xff") + self.spi.write(b"\xff") + + # check the response + if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05: + self.cs(1) + self.spi.write(b"\xff") + return + + # wait for write to finish + while self.spi.read(1, 0xFF)[0] == 0: + pass + + self.cs(1) + self.spi.write(b"\xff") + + def write_token(self, token): + self.cs(0) + self.spi.read(1, token) + self.spi.write(b"\xff") + # wait for write to finish + while self.spi.read(1, 0xFF)[0] == 0x00: + pass + + self.cs(1) + self.spi.write(b"\xff") + + def readblocks(self, block_num, buf): + nblocks = len(buf) // 512 + assert nblocks and not len(buf) % 512, "Buffer length is invalid" + if nblocks == 1: + # CMD17: set read address for single block + if self.cmd(17, block_num * self.cdv, 0, release=False) != 0: + # release the card + self.cs(1) + raise OSError(5) # EIO + # receive the data and release card + self.readinto(buf) + else: + # CMD18: set read address for multiple blocks + if self.cmd(18, block_num * self.cdv, 0, release=False) != 0: + # release the card + self.cs(1) + raise OSError(5) # EIO + offset = 0 + mv = memoryview(buf) + while nblocks: + # receive the data and release card + self.readinto(mv[offset : offset + 512]) + offset += 512 + nblocks -= 1 + if self.cmd(12, 0, 0xFF, skip1=True): + raise OSError(5) # EIO + + def writeblocks(self, block_num, buf): + nblocks, err = divmod(len(buf), 512) + assert nblocks and not err, "Buffer length is invalid" + if nblocks == 1: + # CMD24: set write address for single block + if self.cmd(24, block_num * self.cdv, 0) != 0: + raise OSError(5) # EIO + + # send the data + self.write(_TOKEN_DATA, buf) + else: + # CMD25: set write address for first block + if self.cmd(25, block_num * self.cdv, 0) != 0: + raise OSError(5) # EIO + # send the data + offset = 0 + mv = memoryview(buf) + while nblocks: + self.write(_TOKEN_CMD25, mv[offset : offset + 512]) + offset += 512 + nblocks -= 1 + self.write_token(_TOKEN_STOP_TRAN) + + def ioctl(self, op, arg): + if op == 4: # get number of blocks + return self.sectors