Skip to content

Commit 35e3f88

Browse files
authored
Merge pull request #3 from jaindinkar/dinkar/sd-card-integration
Enable SD card logs
2 parents 6d77d11 + aa7e605 commit 35e3f88

19 files changed

+557
-115
lines changed

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
# micropython_datalogger_esp8266
22

3-
A simple MicroPython script for data logging environmental sensors on ESP8266 board with OLED data display. This project follows SemVer-v2.0.0 guidelines for it's new releases/tags.
3+
A simple MicroPython script for data logging environmental sensors on ESP8266 board with OLED data display. Libs used in this project are freezed(.mpy), using [mpy-cross](https://github.com/micropython/micropython/tree/master/mpy-cross). Original source file is also included, checkout <kbd>driver-libs</kbd> directory. Libs under this directory can be changed and recompiled to suit your needs but it's seldom required unless you know what you are doing. Happy Hacking!!
4+
5+
** This project follows SemVer-v2.0.0 guidelines for it's new releases/tags.
46

57
## Project Setup:
68
Wiring Diagram:
7-
![Wiring Diagram for this project](docs/images/wiring-diagram-v0.2.0.png)
9+
![Wiring Diagram for this project](docs/images/wiring-diagram-v0.3.0.png)
810

911
Project Setup:
10-
![Setup for this project](docs/images/project-setup.jpg)
12+
![Setup for this project](docs/images/project-setup-v0.3.0.jpg)
1113

12-
OLED display showing data:
13-
![Working OLED display](docs/images/oled-working.jpg)
14+
Top View:
15+
![Working OLED display](docs/images/project-setup-top-v0.3.0.jpg)
1416

1517
### Sensor Inputs:
1618
- Bosch's BME280 in I2C mode.
@@ -20,6 +22,7 @@ OLED display showing data:
2022
### Outputs:
2123
- Serial terminal debugging logs.
2224
- SSD1306 White OLED Display in I2C mode.
25+
- SD card logs using SD card module in SPI mode.
2326
- Logging using Zapier workflow (zap) Integrating zapier webhook with Google sheets.
2427

2528
### Dev console config:
@@ -32,11 +35,11 @@ OLED display showing data:
3235
- MicroPython Firmware v1.18
3336

3437
### Expected New Features:
35-
- SD Card Logging. (Using web agents/hooks from 3rd party is not very economic for an individual looking to log a large dataset.)
3638
- Setting up Huginn a hackable and opensource alternative to IFTTT and Zapier. (For those who want their own service running.)
3739
- Hotswappable sensors.
3840
- Key-pad -> Adding ssid and password on the go.
3941
- Key-pad -> Screen scrolling and menu navigation.
4042

4143
### References:
42-
- More about editing Pymakr config file: https://github.com/pycom/pymakr-vsc/blob/HEAD/settings.md
44+
- More about editing Pymakr config file:[Pymaker config](https://github.com/pycom/pymakr-vsc/blob/HEAD/settings.md)
45+
- Python cross compilation to bytecode: [mpy-cross](https://github.com/micropython/micropython/tree/master/mpy-cross)
117 KB
Loading
File renamed without changes.

docs/images/project-setup-v0.3.0.jpg

120 KB
Loading

docs/images/wiring-diagram-v0.3.0.png

193 KB
Loading
File renamed without changes.

src/libs/scd30_sm.py renamed to driver-libs/scd30_sm.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from machine import I2C
21
import utime
32
import struct
43

driver-libs/sdcard.py

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
"""
2+
MicroPython driver for SD cards using SPI bus.
3+
4+
Requires an SPI bus and a CS pin. Provides readblocks and writeblocks
5+
methods so the device can be mounted as a filesystem.
6+
7+
Example usage on pyboard:
8+
9+
import pyb, sdcard, os
10+
sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)
11+
pyb.mount(sd, '/sd2')
12+
os.listdir('/')
13+
14+
Example usage on ESP8266:
15+
16+
import machine, sdcard, os
17+
sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))
18+
os.mount(sd, '/sd')
19+
os.listdir('/')
20+
21+
"""
22+
23+
from micropython import const
24+
import time
25+
26+
27+
_CMD_TIMEOUT = const(100)
28+
29+
_R1_IDLE_STATE = const(1 << 0)
30+
# R1_ERASE_RESET = const(1 << 1)
31+
_R1_ILLEGAL_COMMAND = const(1 << 2)
32+
# R1_COM_CRC_ERROR = const(1 << 3)
33+
# R1_ERASE_SEQUENCE_ERROR = const(1 << 4)
34+
# R1_ADDRESS_ERROR = const(1 << 5)
35+
# R1_PARAMETER_ERROR = const(1 << 6)
36+
_TOKEN_CMD25 = const(0xFC)
37+
_TOKEN_STOP_TRAN = const(0xFD)
38+
_TOKEN_DATA = const(0xFE)
39+
40+
41+
class SDCard:
42+
def __init__(self, spi, cs):
43+
self.spi = spi
44+
self.cs = cs
45+
46+
self.cmdbuf = bytearray(6)
47+
self.dummybuf = bytearray(512)
48+
self.tokenbuf = bytearray(1)
49+
for i in range(512):
50+
self.dummybuf[i] = 0xFF
51+
self.dummybuf_memoryview = memoryview(self.dummybuf)
52+
53+
# initialise the card
54+
self.init_card()
55+
56+
def init_spi(self, baudrate):
57+
try:
58+
master = self.spi.MASTER
59+
except AttributeError:
60+
# on ESP8266
61+
self.spi.init(baudrate=baudrate, phase=0, polarity=0)
62+
else:
63+
# on pyboard
64+
self.spi.init(master, baudrate=baudrate, phase=0, polarity=0)
65+
66+
def init_card(self):
67+
# init CS pin
68+
self.cs.init(self.cs.OUT, value=1)
69+
70+
# init SPI bus; use low data rate for initialisation
71+
self.init_spi(100000)
72+
73+
# clock card at least 100 cycles with cs high
74+
for i in range(16):
75+
self.spi.write(b"\xff")
76+
77+
# CMD0: init card; should return _R1_IDLE_STATE (allow 10 attempts)
78+
for _ in range(10):
79+
if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE:
80+
break
81+
else:
82+
raise OSError("no SD card")
83+
84+
# CMD8: determine card version
85+
for j in range(5):
86+
r = self.cmd(8, 0x01AA, 0x87, 4)
87+
if r == _R1_IDLE_STATE:
88+
self.init_card_v2()
89+
break
90+
elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND):
91+
self.init_card_v1()
92+
break
93+
else:
94+
raise OSError("couldn't determine SD card version")
95+
96+
# get the number of sectors
97+
# CMD9: response R2 (R1 byte + 16-byte block read)
98+
if self.cmd(9, 0, 0, 0, False) != 0:
99+
raise OSError("no response from SD card")
100+
csd = bytearray(16)
101+
self.readinto(csd)
102+
if csd[0] & 0xC0 == 0x40: # CSD version 2.0
103+
self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024
104+
elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB)
105+
c_size = csd[6] & 0b11 | csd[7] << 2 | (csd[8] & 0b11000000) << 4
106+
c_size_mult = ((csd[9] & 0b11) << 1) | csd[10] >> 7
107+
self.sectors = (c_size + 1) * (2 ** (c_size_mult + 2))
108+
else:
109+
raise OSError("SD card CSD format not supported")
110+
# print('sectors', self.sectors)
111+
112+
# CMD16: set block length to 512 bytes
113+
if self.cmd(16, 512, 0) != 0:
114+
raise OSError("can't set 512 block size")
115+
116+
# set to high data rate now that it's initialised
117+
self.init_spi(1320000)
118+
119+
def init_card_v1(self):
120+
for i in range(_CMD_TIMEOUT):
121+
self.cmd(55, 0, 0)
122+
if self.cmd(41, 0, 0) == 0:
123+
self.cdv = 512
124+
# print("[SDCard] v1 card")
125+
return
126+
raise OSError("timeout waiting for v1 card")
127+
128+
def init_card_v2(self):
129+
for i in range(_CMD_TIMEOUT):
130+
time.sleep_ms(50)
131+
self.cmd(58, 0, 0, 4)
132+
self.cmd(55, 0, 0)
133+
if self.cmd(41, 0x40000000, 0) == 0:
134+
self.cmd(58, 0, 0, 4)
135+
self.cdv = 1
136+
# print("[SDCard] v2 card")
137+
return
138+
raise OSError("timeout waiting for v2 card")
139+
140+
def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):
141+
self.cs(0)
142+
143+
# create and send the command
144+
buf = self.cmdbuf
145+
buf[0] = 0x40 | cmd
146+
buf[1] = arg >> 24
147+
buf[2] = arg >> 16
148+
buf[3] = arg >> 8
149+
buf[4] = arg
150+
buf[5] = crc
151+
self.spi.write(buf)
152+
153+
if skip1:
154+
self.spi.readinto(self.tokenbuf, 0xFF)
155+
156+
# wait for the response (response[7] == 0)
157+
for i in range(_CMD_TIMEOUT):
158+
self.spi.readinto(self.tokenbuf, 0xFF)
159+
response = self.tokenbuf[0]
160+
if not (response & 0x80):
161+
# this could be a big-endian integer that we are getting here
162+
for j in range(final):
163+
self.spi.write(b"\xff")
164+
if release:
165+
self.cs(1)
166+
self.spi.write(b"\xff")
167+
return response
168+
169+
# timeout
170+
self.cs(1)
171+
self.spi.write(b"\xff")
172+
return -1
173+
174+
def readinto(self, buf):
175+
self.cs(0)
176+
177+
# read until start byte (0xff)
178+
for i in range(_CMD_TIMEOUT):
179+
self.spi.readinto(self.tokenbuf, 0xFF)
180+
if self.tokenbuf[0] == _TOKEN_DATA:
181+
break
182+
time.sleep_ms(1)
183+
else:
184+
self.cs(1)
185+
raise OSError("timeout waiting for response")
186+
187+
# read data
188+
mv = self.dummybuf_memoryview
189+
if len(buf) != len(mv):
190+
mv = mv[: len(buf)]
191+
self.spi.write_readinto(mv, buf)
192+
193+
# read checksum
194+
self.spi.write(b"\xff")
195+
self.spi.write(b"\xff")
196+
197+
self.cs(1)
198+
self.spi.write(b"\xff")
199+
200+
def write(self, token, buf):
201+
self.cs(0)
202+
203+
# send: start of block, data, checksum
204+
self.spi.read(1, token)
205+
self.spi.write(buf)
206+
self.spi.write(b"\xff")
207+
self.spi.write(b"\xff")
208+
209+
# check the response
210+
if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05:
211+
self.cs(1)
212+
self.spi.write(b"\xff")
213+
return
214+
215+
# wait for write to finish
216+
while self.spi.read(1, 0xFF)[0] == 0:
217+
pass
218+
219+
self.cs(1)
220+
self.spi.write(b"\xff")
221+
222+
def write_token(self, token):
223+
self.cs(0)
224+
self.spi.read(1, token)
225+
self.spi.write(b"\xff")
226+
# wait for write to finish
227+
while self.spi.read(1, 0xFF)[0] == 0x00:
228+
pass
229+
230+
self.cs(1)
231+
self.spi.write(b"\xff")
232+
233+
def readblocks(self, block_num, buf):
234+
nblocks = len(buf) // 512
235+
assert nblocks and not len(buf) % 512, "Buffer length is invalid"
236+
if nblocks == 1:
237+
# CMD17: set read address for single block
238+
if self.cmd(17, block_num * self.cdv, 0, release=False) != 0:
239+
# release the card
240+
self.cs(1)
241+
raise OSError(5) # EIO
242+
# receive the data and release card
243+
self.readinto(buf)
244+
else:
245+
# CMD18: set read address for multiple blocks
246+
if self.cmd(18, block_num * self.cdv, 0, release=False) != 0:
247+
# release the card
248+
self.cs(1)
249+
raise OSError(5) # EIO
250+
offset = 0
251+
mv = memoryview(buf)
252+
while nblocks:
253+
# receive the data and release card
254+
self.readinto(mv[offset : offset + 512])
255+
offset += 512
256+
nblocks -= 1
257+
if self.cmd(12, 0, 0xFF, skip1=True):
258+
raise OSError(5) # EIO
259+
260+
def writeblocks(self, block_num, buf):
261+
nblocks, err = divmod(len(buf), 512)
262+
assert nblocks and not err, "Buffer length is invalid"
263+
if nblocks == 1:
264+
# CMD24: set write address for single block
265+
if self.cmd(24, block_num * self.cdv, 0) != 0:
266+
raise OSError(5) # EIO
267+
268+
# send the data
269+
self.write(_TOKEN_DATA, buf)
270+
else:
271+
# CMD25: set write address for first block
272+
if self.cmd(25, block_num * self.cdv, 0) != 0:
273+
raise OSError(5) # EIO
274+
# send the data
275+
offset = 0
276+
mv = memoryview(buf)
277+
while nblocks:
278+
self.write(_TOKEN_CMD25, mv[offset : offset + 512])
279+
offset += 512
280+
nblocks -= 1
281+
self.write_token(_TOKEN_STOP_TRAN)
282+
283+
def ioctl(self, op, arg):
284+
if op == 4: # get number of blocks
285+
return self.sectors

0 commit comments

Comments
 (0)