Add initial files for the HH Hacking 101 - code and infos about board will follow soon
This commit is contained in:
10
src/fw-bin/README.md
Normal file
10
src/fw-bin/README.md
Normal file
@@ -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
|
||||
187
src/lib/bmp180.py
Normal file
187
src/lib/bmp180.py
Normal file
@@ -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
|
||||
283
src/lib/sdcard.py
Normal file
283
src/lib/sdcard.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user