These are the basic functions to communicate serially with a small memory card. Higher level capabilities as e.g. a file system are build upon those functions.
Files | |
file | smc.h |
weAutSys' (low level) system calls, services and types for communication with a small memory card | |
Data Structures | |
struct | smcThr_data_t |
The organisational data for a small memory card (SMC) handling thread. More... | |
Defines | |
#define | APP_CMD 55 |
SMC command: next command is application specific. | |
#define | GO_IDLE_STATE 0 |
SMC command: soft reset. | |
#define | LOCK_UNLOCK 42 |
SMC command: lock / unlock the card. | |
#define | NCR_EXTR_WAIT 8 |
Maximum extra waits for command response. | |
#define | othersAskPrio() |
Other devices asks for priority. | |
#define | READ_OCR 58 |
SMC command: read the card's OCR register (R3) | |
#define | READ_SINGLE_BLOCK 17 |
SMC command: read one block. | |
#define | SEND_CID |
SMC command: get CID. | |
#define | SEND_CID |
SMC command: get CID. | |
#define | SEND_CSD 9 |
SMC command: send card specific data. | |
#define | SEND_IF_COND 8 |
SMC command: send interface condition. | |
#define | SEND_OP_COND 1 |
SMC command: init. process. | |
#define | SEND_OP_COND_APP |
SMC application specific command: initialisation process. | |
#define | SEND_STATUS 13 |
SMC command: get status (R2) | |
#define | SET_BLOCKLEN 16 |
SMC command: set block length for LOCK_UNLOCK. | |
#define | smcInsPow() |
Inserted card is powered up. | |
#define | smcReceive() |
Receive a single byte from the small memory card. | |
#define | smcReceiveN(receiveB, skip, n) |
Receive n bytes from the SMC to a buffer with optional skip. | |
#define | smcTypeD() |
The SMC's type is determined and OCR is known. | |
#define | smcXmit(datByte) |
Transmit a single byte to the small memory card. | |
#define | smcXmit2(sendB1, sendB2) |
Send two bytes and receive one byte to/from the small memory card. | |
#define | WRITE_BLOCK 24 |
SMC command: write one block. | |
Functions | |
uint8_t | checkBusy (uint8_t tries) |
Check if card is busy. | |
void | clk80 (void) |
Have 80 dummy SPI clocks. | |
uint8_t | crc7stp (uint8_t crcIn, uint8_t datByte) __attribute__((always_inline)) |
Calculate CRC7 (one step) | |
void | deSelectSMC (void) __attribute__((always_inline)) |
De-select the small memory card. | |
uint8_t | doSectorRead (uint32_t sector) |
Order sector read (as background task) | |
uint8_t | doSectorSync (void) |
Order sector synchronisation (as background task) | |
uint8_t | getSMCtype (void) |
Determine the card type. | |
void | initSMCthreadState (uint8_t actionFlag) |
Initialise the small memory card (smc) handling thread. | |
uint8_t | readDataBlock (uint32_t sector, uint8_t *buff) |
Read single data block (512 byte) | |
uint8_t | sendAppCmd (uint8_t appCmdNum, uint32_t cmdArg) |
Send an application specific command to the small memory card. | |
uint8_t | sendCmd (uint8_t cmdNum, uint32_t cmdArg) |
Send a command to the small memory card. | |
uint8_t | sendCmd0arg (uint8_t cmdNum) |
Send a command with 0 argument to the small memory card. | |
void | setSectorModified (uint32_t sector) |
Mark sector buffer data as modified (start) | |
uint32_t | smcGetSectCount (void) |
Get the sector count. | |
uint8_t | smcInsertSwitch (void) |
Hardware card detect. | |
uint8_t | smcReadCID (uint8_t *buff) |
Read the SMC's CID. | |
uint8_t | smcReadCSD (void) |
Read the SMC's CSD. | |
uint8_t | smcReadOCR (void) |
Read the SMC's ORC. | |
void | smcSetFrq (uint8_t frqUse) __attribute__((always_inline)) |
Set the SPI clock frequency for the small memory card. | |
uint8_t | smcSetIdle (uint8_t mode) |
Put card to idle state. | |
ptfnct_t | smcThreadF (void) |
The small memory card (smc) handling thread. | |
uint8_t | writeDataBlock (uint32_t sector, const uint8_t *buff) |
Write single data block (512 byte) | |
Variables | |
struct smcThr_data_t | smcState |
The (one) small memory card (SMC) state. |
#define othersAskPrio | ( | ) |
Other devices asks for priority.
This expression is true if other software respectively devices need resources used by SMC operations in critical situations.
As some SMC operations need SPI communication or thread time uninterrupted for quite a long time they should refrain from doing so as long as this request is pending.
For SMC processing controlled by this module's functions this time may be up to 2ms as documented in each case. File system implementations implemented upon these (driver) functions would increase this value quite considerably if programmed unaware of the (Protothread) threading model.
As of Revision 422++ this request comes from (seldom) NTP synchronisation actions only.
#define NCR_EXTR_WAIT 8 |
Maximum extra waits for command response.
The maximum number of (8 SPI clock) cycles to receive a valid command response is this value + 1. The specification says the number (NCR) of those waiting cycles is in the range of 1 .. 8 (depending on card type and command). Hence 7 would be the minimum for NCR_EXTR_WAIT; anything larger than 10 would just waste time in case of a (command) failure.
#define SEND_CID |
SMC command: get CID.
SMC command: send card idendification.
#define SEND_CID |
SMC command: get CID.
SMC command: send card idendification.
#define LOCK_UNLOCK 42 |
SMC command: lock / unlock the card.
#define SEND_OP_COND_APP |
SMC application specific command: initialisation process.
Like SEND_OP_COND this command activates the card's initialisation process. But as an application specific command it has to be prepended by APP_CMD() or simply used in sendAppCmd instead of sendCmd().
Note: In SPI mode SEND_OP_COND_APP and SEND_OP_COND should have the same behaviour, though most implementors prefer the application specific variant. This tradition is respected here.
#define smcXmit | ( | datByte | ) |
Transmit a single byte to the small memory card.
datByte | the byte to be sent to the SMC |
#define smcXmit2 | ( | sendB1, | |
sendB2 | |||
) |
Send two bytes and receive one byte to/from the small memory card.
sendB1 | the byte to be sent first |
sendB2 | the second byte to be sent |
sendB2
(0 may indicate an error) #define smcReceive | ( | ) |
#define smcReceiveN | ( | receiveB, | |
skip, | |||
n | |||
) |
Receive n bytes from the SMC to a buffer with optional skip.
This function does skip
+ n
receptions. The (last) n
bytes received are put into the buffer receiveB
. It returns nothing (void).
Compared to doing this by skip
+ n
times smcReceive() this function is at least 20 % faster.
receiveB | pointer to the buffer (type uint8_t *) to receive n bytes to (must not be NULL if n != 0) |
skip | if > 0 skip bytes are received and forgotten before receiveB will be filled |
n | number of bytes to be received and filled into receiveB |
#define smcInsPow | ( | ) |
Inserted card is powered up.
This expression is true if a SMC is inserted and powered up according to smcState.
#define smcTypeD | ( | ) |
The SMC's type is determined and OCR is known.
This expression is true if a (inserted and powered up) SMC's type has been determined. In the process also the OCR has been read to smcState.ocr.
uint8_t smcInsertSwitch | ( | void | ) |
Hardware card detect.
On the weAut_01 module the card switch must be "jumpered" to port D6 for this function to work properly.
Remark: It might well be considered a misconception in weAut_01 not to tie the respective port (D5) directly to the card insert switch and have a lot of jumper options instead. This can be mended by an (1K) resistor soldered in 2 via holes near the jumper.
void clk80 | ( | void | ) |
Have 80 dummy SPI clocks.
Some small memory cards need at least 74 (dummy) SPI clocks with their chip select inactive (high) and data in (DI, MoSi) all ones after power up.
This function sends 80 clocks (sClk) on the SPI (2) interface used for small memory cards with all (SPI2) devices de-selected. The stream of 80 clocks delivered by this function has no gaps.
At SPI clock frequency of 2 MHz the whole thing would take 40,4 µs. At the 400 kHz usually required for power up it will take 200 µs.
void smcSetFrq | ( | uint8_t | frqUse | ) |
Set the SPI clock frequency for the small memory card.
Some (older) small memory cards require SPI clock frequencies of 400 kHz (or lower) during initialisation. Afterwards higher SPI baud-rates are to be used. This function sets the latter / normal mode frequency.
A 20 MHz clocked micro-controller (like ATmega1284P) has a maximum 10 MHz SPI baud rate. There are few SMC types with a maximum SPI clock frequency of 6.5 MHz around. Most other types can handle much higher clock rates. So the principally possible 10 MHz would hardly give them a wet shirt.
The next possible lower frequency is 5 MHz. It would fit all card types, anyway. Additionally in case of resistor divide level shifters — from ATmega's 5V to SMC's 3.3V — the waveforms provided for some card types dictate 5 MHz as maximum SPI clock frequency.
frqUse | control value to set SPI 2 clock for the memory card (use the predefined values or the UBRR1 formula) |
uint8_t crc7stp | ( | uint8_t | crcIn, |
uint8_t | datByte | ||
) |
Calculate CRC7 (one step)
This function calculates the CRC7 (used in some small memory cards for command transmission). It is a 7 bit CRC with polynomial x7 + x3 + 1. This function implements just the step for next 8 bits given by datByte
. The result returned would have to be fed as crcIn
for the next step. Use 0 for crcIn
in the first step.
To make the final 7 bit CRC sendable to the small memory card via SPI it must be put in the upper seven bit of the CRC byte padded with an one in bit 0 by:
crc = (crc<<1) | 1; // make so sendable after the last step
The function uses 10 CPU cycles if inlined as intended or 18 including call and return. Hence the algorithm is "faster than one byte" for all SPI clocks not faster than 1/2 CPU clock. Sending one byte at 5 MHz takes 48 CPU clocks with a 20 MHz clocked ATmega.
crcIn | the crc before; use 0 in first step |
datByte | the next 8 bits for the CRC |
void deSelectSMC | ( | void | ) |
De-select the small memory card.
This function (chip-) de-selects the SMC. It has to be used when all commands and write / reads are done. An explicit select is not needed as all command functions do so automatically.
Hint: Most operations in most SMCs (probably all) fail if de-selected and re-selected. Keep in mind that small memory cards are not very friendly to sharing a SPI bus with other devices.
uint8_t sendCmd | ( | uint8_t | cmdNum, |
uint32_t | cmdArg | ||
) |
Send a command to the small memory card.
This function sends the command, the arguments and the CRC7 to the memory card. Returned is the card's answer if any. Several (i.e. NCR_EXTR_WAIT + 1) attempts are made to get the cards command response recognised by bit 7 zero. If the response returned is 0xFF the card may be gone.
In this (worst) case this function takes 74 µs at a SPI clock frequency of 2 MHz compared to 32.5 µs for a card answering fast.
Hint: To send an application specific command use the function sendAppCmd().
Hint2: If the response is 0 (all OK) and if the command's specified answer is not of type R1 (or if it is a block read command), its up to the caller of this or similar functions to receive all coming response bytes, using e.g. smcReceive() or smcReceiveN().
Hint3: The returned response is also stored in smcState.lastCmdResp[0].
cmdNum | command number, only the lower 6 bits are relevant |
cmdArg | the 4 byte (32 bits) command arguments |
uint8_t sendCmd0arg | ( | uint8_t | cmdNum | ) |
Send a command with 0 argument to the small memory card.
This function is equivalent to
sendCmd(cmdNr, 0);
with some improvements for this frequent case.
See also: The hints at sendCmd().
cmdNum | command number, only the lower 6 bits are relevant |
uint8_t sendAppCmd | ( | uint8_t | appCmdNum, |
uint32_t | cmdArg | ||
) |
Send an application specific command to the small memory card.
This function is equivalent to
sendCmd0arg(APP_CMD);
sendCmd(cmdNr, cmdArg);
besides some slight improvements.
See also: The hints at sendCmd() and SEND_OP_COND_APP.
appCmdNum | application specific command number (lower 6 bits) |
cmdArg | the 4 byte (32 bits) command arguments |
uint8_t smcSetIdle | ( | uint8_t | mode | ) |
Put card to idle state.
This function should run once a small memory card is inserted (or is believed to have been). Returned is the "R1" answer to the soft reset command. Only the value 1 is OK meaning card is present and (now) in idle state. All other values are faults. 0xFF (no valid R1 response) usually means no card inserted or card not responding at all.
Most specifications require 400 kHz for the initial ref clk80 "dummy clocks" and the command. Though this seems out-dated for most modern cards it can for compatibility reasons be done by setting bit 0 in mode
. The whole thing takes 370µs then.
In smcState.cardType this function sets bits CT_POWERD_UP and CT_INSERTED on success; all other bits are cleared.
mode | Bit 0 set (1): force 400 kHz and restore spi2MemCardBaud to previous value afterwards Bit 1 set (2): omit the 80 dummy clocks before the go idle command Bit 2 set (4): omit the go idle command (0 is returned in that case) |
uint8_t checkBusy | ( | uint8_t | tries | ) |
Check if card is busy.
This function returns 0 (false not busy) if the SMC is inserted and responds 0xFF at least twice to two dummy reads. The two consecutive not busy answers are looked for 3 .. (3 + tries
) times.
If the outcome is not busy the card is not de-selected.
tries | 2..255 maximum number of dummy check reads (byte clock cycles) used |
ptfnct_t smcThreadF | ( | void | ) |
The small memory card (smc) handling thread.
If some basic handling, like initialising, has to be done with a SMC this protothread thread will do it reasonable steps, dividing long running procedures by yielding.
Hint: As this is for small (petit) systems with just one SMC slot this thread uses the one smcState (without any choice by parameter).
void initSMCthreadState | ( | uint8_t | actionFlag | ) |
Initialise the small memory card (smc) handling thread.
This function resets all SMC state. It should be called on reset / restart and on SMC removal, failures and insertion.
actionFlag | start value for the SMC thread's flag |
uint8_t smcReadOCR | ( | void | ) |
Read the SMC's ORC.
This function reads the operation condition register (OCR, 4 byte) of an inserted and powered up small memory card. In case of success 0 is returned and the result is stored in smcState.ocr.
It is usually not necessary to call this function if smcTypeD() is true as the OCR and the CSD have been read during the card type determination process.
uint8_t smcReadCSD | ( | void | ) |
Read the SMC's CSD.
This function reads the card specific data register (CSD, 16 byte) of an inserted and powered up small memory card. In case of success 0 is returned and the result is stored in smcState.csd.
It is usually not necessary to call this function if smcTypeD() is true as the OCR and CSD have been read during the card type determination process.
uint32_t smcGetSectCount | ( | void | ) |
Get the sector count.
If the card type is determined this function returns the number of (512 byte) sectors on the card and 0 otherwise.
uint8_t smcReadCID | ( | uint8_t * | buff | ) |
Read the SMC's CID.
This function reads the card identification register (CID, 16 byte) of an inserted and powered up small memory card. In case of success 0 is returned and the result is stored in buff
.
0: Manufacturer ID
1,2: OEM/Application ID
3..7: Product name
8: Product revision
9..12: Serial number
13 15: Manufaturing date
buff | 16 byte to write the CID to |
uint8_t readDataBlock | ( | uint32_t | sector, |
uint8_t * | buff | ||
) |
Read single data block (512 byte)
This function does one single block respectively sector read — with block size being fixed to 512 byte here. In case of success 0 is returned. If the given sector
number has already been cached the SMC is not accessed.
In case of failure non 0 is returned. More information can be found in smcState.
In the normal case of SCC access the time needed by this function heavily depends on the SMC make. The (astonishing) differences are mainly due to different waits for the so called data token. The time needed for this With ~31 waits for data token (512MByte KData card) this takes about 1 ms. With 181 checks for the data token (8GByte Verbatim) we need 1,35 ms but this varies up to 250 waits and about 2 ms.
weAutSys is a non pre-emptive system — and besides that SMC operations (the /CS) could not be interrupted anyway. Hence it is clear and was said at other places this block read operation is the most critical for an automation modul / system with an 1ms cycle. No two such operations shall follow each other without yielding — and multi-block SMC operations are, of course, out of the question.
Hint: This function as well as writeDataBlock() handle the sector to byte address transformation for commands READ_SINGLE_BLOCK resp. WRITE_BLOCK needed with low capacity SMC types internally. Parameters sector
(and smcState.rwSector) always reflect the sector number (0, 1 ...).
sector | the sector number |
buff | pointer to where to write the received data (NULL not allowed) |
uint8_t writeDataBlock | ( | uint32_t | sector, |
const uint8_t * | buff | ||
) |
Write single data block (512 byte)
sector | the sector number |
buff | pointer to where to get the send data from (NULL not allowed) |
uint8_t getSMCtype | ( | void | ) |
Determine the card type.
If the an SMC is inserted and its type has been determined this function returns the type bits set in smcState.cardType.
Otherwise 0 is returned and the card type determination is triggered in the SMC handling thread.
uint8_t doSectorRead | ( | uint32_t | sector | ) |
Order sector read (as background task)
This function sets smcState.rwSector by sector
and initialises smcState.sectorBuff to be read from that sector.
sector | the (new) sector number for smcState.rwSector/.sectorBuff |
void setSectorModified | ( | uint32_t | sector | ) |
Mark sector buffer data as modified (start)
This function sets smcState.rwSector by sector
and marks as smcState.sectorBuff as modified (new) data for the respective sector. This neither performs any read or write operations nor orders such as background task.
Hint: If this functions changes the current buffer's (smcState.sectorBuff) sector number and the buffer contained modified data for that previous sector those modifications will be lost. Call doSectorSync() before if that would not be intended.
sector | the (new) sector number for smcState.rwSector/.sectorBuff |
uint8_t doSectorSync | ( | void | ) |
Order sector synchronisation (as background task)
This function checks if smcState.sectorBuff contains modified data (for sector smcState.rwSector) and if so initialises smcState.sectorBuff to be written to that sector.
Example sector copy:
PT_WAIT_ASYIELD_WHILE(&myThread, doSectorRead(sourceSect) == 1); if (doSectorSync()) { optional error handling / exit } setSectorModified(destinationSect); PT_WAIT_ASYIELD_WHILE(&myThread, doSectorSync() == 1); if (doSectorSync()) { optional error handling / exit }