to colorize a colorful pixel with a simple touch using nfc technology
Dependencies: Chainable_RGB_LED mbed
use Arch, NFC Shield and Grove - Chainable RGB LED to DIY a touch pixel. Then use an Android with NFC support to colorize it.
The project is on https://github.com/Seeed-Studio/TouchPixel
nfc/PN532.cpp@0:88960f3eeb2c, 2013-12-27 (annotated)
- Committer:
- yihui
- Date:
- Fri Dec 27 01:46:32 2013 +0000
- Revision:
- 0:88960f3eeb2c
initial
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
yihui | 0:88960f3eeb2c | 1 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 2 | /*! |
yihui | 0:88960f3eeb2c | 3 | @file PN532.cpp |
yihui | 0:88960f3eeb2c | 4 | @author Adafruit Industries & Seeed Studio |
yihui | 0:88960f3eeb2c | 5 | @license BSD |
yihui | 0:88960f3eeb2c | 6 | */ |
yihui | 0:88960f3eeb2c | 7 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 8 | |
yihui | 0:88960f3eeb2c | 9 | #include "PN532.h" |
yihui | 0:88960f3eeb2c | 10 | #include "PN532_debug.h" |
yihui | 0:88960f3eeb2c | 11 | #include <string.h> |
yihui | 0:88960f3eeb2c | 12 | |
yihui | 0:88960f3eeb2c | 13 | #ifdef ARDUINO |
yihui | 0:88960f3eeb2c | 14 | #include "Arduino.h" |
yihui | 0:88960f3eeb2c | 15 | #else |
yihui | 0:88960f3eeb2c | 16 | #include <stdio.h> |
yihui | 0:88960f3eeb2c | 17 | #endif |
yihui | 0:88960f3eeb2c | 18 | |
yihui | 0:88960f3eeb2c | 19 | #define HAL(func) (_interface->func) |
yihui | 0:88960f3eeb2c | 20 | |
yihui | 0:88960f3eeb2c | 21 | PN532::PN532(PN532Interface &interface) |
yihui | 0:88960f3eeb2c | 22 | { |
yihui | 0:88960f3eeb2c | 23 | _interface = &interface; |
yihui | 0:88960f3eeb2c | 24 | } |
yihui | 0:88960f3eeb2c | 25 | |
yihui | 0:88960f3eeb2c | 26 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 27 | /*! |
yihui | 0:88960f3eeb2c | 28 | @brief Setups the HW |
yihui | 0:88960f3eeb2c | 29 | */ |
yihui | 0:88960f3eeb2c | 30 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 31 | void PN532::begin() |
yihui | 0:88960f3eeb2c | 32 | { |
yihui | 0:88960f3eeb2c | 33 | HAL(begin)(); |
yihui | 0:88960f3eeb2c | 34 | HAL(wakeup)(); |
yihui | 0:88960f3eeb2c | 35 | } |
yihui | 0:88960f3eeb2c | 36 | |
yihui | 0:88960f3eeb2c | 37 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 38 | /*! |
yihui | 0:88960f3eeb2c | 39 | @brief Prints a hexadecimal value in plain characters |
yihui | 0:88960f3eeb2c | 40 | |
yihui | 0:88960f3eeb2c | 41 | @param data Pointer to the uint8_t data |
yihui | 0:88960f3eeb2c | 42 | @param numBytes Data length in bytes |
yihui | 0:88960f3eeb2c | 43 | */ |
yihui | 0:88960f3eeb2c | 44 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 45 | void PN532::PrintHex(const uint8_t *data, const uint32_t numBytes) |
yihui | 0:88960f3eeb2c | 46 | { |
yihui | 0:88960f3eeb2c | 47 | #ifdef ARDUINO |
yihui | 0:88960f3eeb2c | 48 | for (uint8_t i = 0; i < numBytes; i++) { |
yihui | 0:88960f3eeb2c | 49 | if (data[i] < 0x10) { |
yihui | 0:88960f3eeb2c | 50 | Serial.print(" 0"); |
yihui | 0:88960f3eeb2c | 51 | } else { |
yihui | 0:88960f3eeb2c | 52 | Serial.print(' '); |
yihui | 0:88960f3eeb2c | 53 | } |
yihui | 0:88960f3eeb2c | 54 | Serial.print(data[i], HEX); |
yihui | 0:88960f3eeb2c | 55 | } |
yihui | 0:88960f3eeb2c | 56 | Serial.println(""); |
yihui | 0:88960f3eeb2c | 57 | #else |
yihui | 0:88960f3eeb2c | 58 | for (uint8_t i = 0; i < numBytes; i++) { |
yihui | 0:88960f3eeb2c | 59 | printf(" %2X", data[i]); |
yihui | 0:88960f3eeb2c | 60 | } |
yihui | 0:88960f3eeb2c | 61 | printf("\n"); |
yihui | 0:88960f3eeb2c | 62 | #endif |
yihui | 0:88960f3eeb2c | 63 | } |
yihui | 0:88960f3eeb2c | 64 | |
yihui | 0:88960f3eeb2c | 65 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 66 | /*! |
yihui | 0:88960f3eeb2c | 67 | @brief Prints a hexadecimal value in plain characters, along with |
yihui | 0:88960f3eeb2c | 68 | the char equivalents in the following format |
yihui | 0:88960f3eeb2c | 69 | |
yihui | 0:88960f3eeb2c | 70 | 00 00 00 00 00 00 ...... |
yihui | 0:88960f3eeb2c | 71 | |
yihui | 0:88960f3eeb2c | 72 | @param data Pointer to the data |
yihui | 0:88960f3eeb2c | 73 | @param numBytes Data length in bytes |
yihui | 0:88960f3eeb2c | 74 | */ |
yihui | 0:88960f3eeb2c | 75 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 76 | void PN532::PrintHexChar(const uint8_t *data, const uint32_t numBytes) |
yihui | 0:88960f3eeb2c | 77 | { |
yihui | 0:88960f3eeb2c | 78 | #ifdef ARDUINO |
yihui | 0:88960f3eeb2c | 79 | for (uint8_t i = 0; i < numBytes; i++) { |
yihui | 0:88960f3eeb2c | 80 | if (data[i] < 0x10) { |
yihui | 0:88960f3eeb2c | 81 | Serial.print(" 0"); |
yihui | 0:88960f3eeb2c | 82 | } else { |
yihui | 0:88960f3eeb2c | 83 | Serial.print(' '); |
yihui | 0:88960f3eeb2c | 84 | } |
yihui | 0:88960f3eeb2c | 85 | Serial.print(data[i], HEX); |
yihui | 0:88960f3eeb2c | 86 | } |
yihui | 0:88960f3eeb2c | 87 | Serial.print(" "); |
yihui | 0:88960f3eeb2c | 88 | for (uint8_t i = 0; i < numBytes; i++) { |
yihui | 0:88960f3eeb2c | 89 | char c = data[i]; |
yihui | 0:88960f3eeb2c | 90 | if (c <= 0x1f || c > 0x7f) { |
yihui | 0:88960f3eeb2c | 91 | Serial.print('.'); |
yihui | 0:88960f3eeb2c | 92 | } else { |
yihui | 0:88960f3eeb2c | 93 | Serial.print(c); |
yihui | 0:88960f3eeb2c | 94 | } |
yihui | 0:88960f3eeb2c | 95 | } |
yihui | 0:88960f3eeb2c | 96 | Serial.println(""); |
yihui | 0:88960f3eeb2c | 97 | #else |
yihui | 0:88960f3eeb2c | 98 | for (uint8_t i = 0; i < numBytes; i++) { |
yihui | 0:88960f3eeb2c | 99 | printf(" %2X", data[i]); |
yihui | 0:88960f3eeb2c | 100 | } |
yihui | 0:88960f3eeb2c | 101 | printf(" "); |
yihui | 0:88960f3eeb2c | 102 | for (uint8_t i = 0; i < numBytes; i++) { |
yihui | 0:88960f3eeb2c | 103 | char c = data[i]; |
yihui | 0:88960f3eeb2c | 104 | if (c <= 0x1f || c > 0x7f) { |
yihui | 0:88960f3eeb2c | 105 | printf("."); |
yihui | 0:88960f3eeb2c | 106 | } else { |
yihui | 0:88960f3eeb2c | 107 | printf("%c", c); |
yihui | 0:88960f3eeb2c | 108 | } |
yihui | 0:88960f3eeb2c | 109 | printf("\n"); |
yihui | 0:88960f3eeb2c | 110 | } |
yihui | 0:88960f3eeb2c | 111 | #endif |
yihui | 0:88960f3eeb2c | 112 | } |
yihui | 0:88960f3eeb2c | 113 | |
yihui | 0:88960f3eeb2c | 114 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 115 | /*! |
yihui | 0:88960f3eeb2c | 116 | @brief Checks the firmware version of the PN5xx chip |
yihui | 0:88960f3eeb2c | 117 | |
yihui | 0:88960f3eeb2c | 118 | @returns The chip's firmware version and ID |
yihui | 0:88960f3eeb2c | 119 | */ |
yihui | 0:88960f3eeb2c | 120 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 121 | uint32_t PN532::getFirmwareVersion(void) |
yihui | 0:88960f3eeb2c | 122 | { |
yihui | 0:88960f3eeb2c | 123 | uint32_t response; |
yihui | 0:88960f3eeb2c | 124 | |
yihui | 0:88960f3eeb2c | 125 | pn532_packetbuffer[0] = PN532_COMMAND_GETFIRMWAREVERSION; |
yihui | 0:88960f3eeb2c | 126 | |
yihui | 0:88960f3eeb2c | 127 | if (HAL(writeCommand)(pn532_packetbuffer, 1)) { |
yihui | 0:88960f3eeb2c | 128 | return 0; |
yihui | 0:88960f3eeb2c | 129 | } |
yihui | 0:88960f3eeb2c | 130 | |
yihui | 0:88960f3eeb2c | 131 | // read data packet |
yihui | 0:88960f3eeb2c | 132 | int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); |
yihui | 0:88960f3eeb2c | 133 | if (0 > status) { |
yihui | 0:88960f3eeb2c | 134 | return 0; |
yihui | 0:88960f3eeb2c | 135 | } |
yihui | 0:88960f3eeb2c | 136 | |
yihui | 0:88960f3eeb2c | 137 | response = pn532_packetbuffer[0]; |
yihui | 0:88960f3eeb2c | 138 | response <<= 8; |
yihui | 0:88960f3eeb2c | 139 | response |= pn532_packetbuffer[1]; |
yihui | 0:88960f3eeb2c | 140 | response <<= 8; |
yihui | 0:88960f3eeb2c | 141 | response |= pn532_packetbuffer[2]; |
yihui | 0:88960f3eeb2c | 142 | response <<= 8; |
yihui | 0:88960f3eeb2c | 143 | response |= pn532_packetbuffer[3]; |
yihui | 0:88960f3eeb2c | 144 | |
yihui | 0:88960f3eeb2c | 145 | return response; |
yihui | 0:88960f3eeb2c | 146 | } |
yihui | 0:88960f3eeb2c | 147 | |
yihui | 0:88960f3eeb2c | 148 | |
yihui | 0:88960f3eeb2c | 149 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 150 | /*! |
yihui | 0:88960f3eeb2c | 151 | Writes an 8-bit value that sets the state of the PN532's GPIO pins |
yihui | 0:88960f3eeb2c | 152 | |
yihui | 0:88960f3eeb2c | 153 | @warning This function is provided exclusively for board testing and |
yihui | 0:88960f3eeb2c | 154 | is dangerous since it will throw an error if any pin other |
yihui | 0:88960f3eeb2c | 155 | than the ones marked "Can be used as GPIO" are modified! All |
yihui | 0:88960f3eeb2c | 156 | pins that can not be used as GPIO should ALWAYS be left high |
yihui | 0:88960f3eeb2c | 157 | (value = 1) or the system will become unstable and a HW reset |
yihui | 0:88960f3eeb2c | 158 | will be required to recover the PN532. |
yihui | 0:88960f3eeb2c | 159 | |
yihui | 0:88960f3eeb2c | 160 | pinState[0] = P30 Can be used as GPIO |
yihui | 0:88960f3eeb2c | 161 | pinState[1] = P31 Can be used as GPIO |
yihui | 0:88960f3eeb2c | 162 | pinState[2] = P32 *** RESERVED (Must be 1!) *** |
yihui | 0:88960f3eeb2c | 163 | pinState[3] = P33 Can be used as GPIO |
yihui | 0:88960f3eeb2c | 164 | pinState[4] = P34 *** RESERVED (Must be 1!) *** |
yihui | 0:88960f3eeb2c | 165 | pinState[5] = P35 Can be used as GPIO |
yihui | 0:88960f3eeb2c | 166 | |
yihui | 0:88960f3eeb2c | 167 | @returns 1 if everything executed properly, 0 for an error |
yihui | 0:88960f3eeb2c | 168 | */ |
yihui | 0:88960f3eeb2c | 169 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 170 | bool PN532::writeGPIO(uint8_t pinstate) |
yihui | 0:88960f3eeb2c | 171 | { |
yihui | 0:88960f3eeb2c | 172 | // Make sure pinstate does not try to toggle P32 or P34 |
yihui | 0:88960f3eeb2c | 173 | pinstate |= (1 << PN532_GPIO_P32) | (1 << PN532_GPIO_P34); |
yihui | 0:88960f3eeb2c | 174 | |
yihui | 0:88960f3eeb2c | 175 | // Fill command buffer |
yihui | 0:88960f3eeb2c | 176 | pn532_packetbuffer[0] = PN532_COMMAND_WRITEGPIO; |
yihui | 0:88960f3eeb2c | 177 | pn532_packetbuffer[1] = PN532_GPIO_VALIDATIONBIT | pinstate; // P3 Pins |
yihui | 0:88960f3eeb2c | 178 | pn532_packetbuffer[2] = 0x00; // P7 GPIO Pins (not used ... taken by I2C) |
yihui | 0:88960f3eeb2c | 179 | |
yihui | 0:88960f3eeb2c | 180 | DMSG("Writing P3 GPIO: "); |
yihui | 0:88960f3eeb2c | 181 | DMSG_HEX(pn532_packetbuffer[1]); |
yihui | 0:88960f3eeb2c | 182 | DMSG("\n"); |
yihui | 0:88960f3eeb2c | 183 | |
yihui | 0:88960f3eeb2c | 184 | // Send the WRITEGPIO command (0x0E) |
yihui | 0:88960f3eeb2c | 185 | if (HAL(writeCommand)(pn532_packetbuffer, 3)) |
yihui | 0:88960f3eeb2c | 186 | return 0; |
yihui | 0:88960f3eeb2c | 187 | |
yihui | 0:88960f3eeb2c | 188 | return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); |
yihui | 0:88960f3eeb2c | 189 | } |
yihui | 0:88960f3eeb2c | 190 | |
yihui | 0:88960f3eeb2c | 191 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 192 | /*! |
yihui | 0:88960f3eeb2c | 193 | Reads the state of the PN532's GPIO pins |
yihui | 0:88960f3eeb2c | 194 | |
yihui | 0:88960f3eeb2c | 195 | @returns An 8-bit value containing the pin state where: |
yihui | 0:88960f3eeb2c | 196 | |
yihui | 0:88960f3eeb2c | 197 | pinState[0] = P30 |
yihui | 0:88960f3eeb2c | 198 | pinState[1] = P31 |
yihui | 0:88960f3eeb2c | 199 | pinState[2] = P32 |
yihui | 0:88960f3eeb2c | 200 | pinState[3] = P33 |
yihui | 0:88960f3eeb2c | 201 | pinState[4] = P34 |
yihui | 0:88960f3eeb2c | 202 | pinState[5] = P35 |
yihui | 0:88960f3eeb2c | 203 | */ |
yihui | 0:88960f3eeb2c | 204 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 205 | uint8_t PN532::readGPIO(void) |
yihui | 0:88960f3eeb2c | 206 | { |
yihui | 0:88960f3eeb2c | 207 | pn532_packetbuffer[0] = PN532_COMMAND_READGPIO; |
yihui | 0:88960f3eeb2c | 208 | |
yihui | 0:88960f3eeb2c | 209 | // Send the READGPIO command (0x0C) |
yihui | 0:88960f3eeb2c | 210 | if (HAL(writeCommand)(pn532_packetbuffer, 1)) |
yihui | 0:88960f3eeb2c | 211 | return 0x0; |
yihui | 0:88960f3eeb2c | 212 | |
yihui | 0:88960f3eeb2c | 213 | HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); |
yihui | 0:88960f3eeb2c | 214 | |
yihui | 0:88960f3eeb2c | 215 | /* READGPIO response without prefix and suffix should be in the following format: |
yihui | 0:88960f3eeb2c | 216 | |
yihui | 0:88960f3eeb2c | 217 | byte Description |
yihui | 0:88960f3eeb2c | 218 | ------------- ------------------------------------------ |
yihui | 0:88960f3eeb2c | 219 | b0 P3 GPIO Pins |
yihui | 0:88960f3eeb2c | 220 | b1 P7 GPIO Pins (not used ... taken by I2C) |
yihui | 0:88960f3eeb2c | 221 | b2 Interface Mode Pins (not used ... bus select pins) |
yihui | 0:88960f3eeb2c | 222 | */ |
yihui | 0:88960f3eeb2c | 223 | |
yihui | 0:88960f3eeb2c | 224 | |
yihui | 0:88960f3eeb2c | 225 | DMSG("P3 GPIO: "); DMSG_HEX(pn532_packetbuffer[7]); |
yihui | 0:88960f3eeb2c | 226 | DMSG("P7 GPIO: "); DMSG_HEX(pn532_packetbuffer[8]); |
yihui | 0:88960f3eeb2c | 227 | DMSG("I0I1 GPIO: "); DMSG_HEX(pn532_packetbuffer[9]); |
yihui | 0:88960f3eeb2c | 228 | DMSG("\n"); |
yihui | 0:88960f3eeb2c | 229 | |
yihui | 0:88960f3eeb2c | 230 | return pn532_packetbuffer[0]; |
yihui | 0:88960f3eeb2c | 231 | } |
yihui | 0:88960f3eeb2c | 232 | |
yihui | 0:88960f3eeb2c | 233 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 234 | /*! |
yihui | 0:88960f3eeb2c | 235 | @brief Configures the SAM (Secure Access Module) |
yihui | 0:88960f3eeb2c | 236 | */ |
yihui | 0:88960f3eeb2c | 237 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 238 | bool PN532::SAMConfig(void) |
yihui | 0:88960f3eeb2c | 239 | { |
yihui | 0:88960f3eeb2c | 240 | pn532_packetbuffer[0] = PN532_COMMAND_SAMCONFIGURATION; |
yihui | 0:88960f3eeb2c | 241 | pn532_packetbuffer[1] = 0x01; // normal mode; |
yihui | 0:88960f3eeb2c | 242 | pn532_packetbuffer[2] = 0x14; // timeout 50ms * 20 = 1 second |
yihui | 0:88960f3eeb2c | 243 | pn532_packetbuffer[3] = 0x01; // use IRQ pin! |
yihui | 0:88960f3eeb2c | 244 | |
yihui | 0:88960f3eeb2c | 245 | DMSG("SAMConfig\n"); |
yihui | 0:88960f3eeb2c | 246 | |
yihui | 0:88960f3eeb2c | 247 | if (HAL(writeCommand)(pn532_packetbuffer, 4)) |
yihui | 0:88960f3eeb2c | 248 | return false; |
yihui | 0:88960f3eeb2c | 249 | |
yihui | 0:88960f3eeb2c | 250 | return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); |
yihui | 0:88960f3eeb2c | 251 | } |
yihui | 0:88960f3eeb2c | 252 | |
yihui | 0:88960f3eeb2c | 253 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 254 | /*! |
yihui | 0:88960f3eeb2c | 255 | Sets the MxRtyPassiveActivation uint8_t of the RFConfiguration register |
yihui | 0:88960f3eeb2c | 256 | |
yihui | 0:88960f3eeb2c | 257 | @param maxRetries 0xFF to wait forever, 0x00..0xFE to timeout |
yihui | 0:88960f3eeb2c | 258 | after mxRetries |
yihui | 0:88960f3eeb2c | 259 | |
yihui | 0:88960f3eeb2c | 260 | @returns 1 if everything executed properly, 0 for an error |
yihui | 0:88960f3eeb2c | 261 | */ |
yihui | 0:88960f3eeb2c | 262 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 263 | bool PN532::setPassiveActivationRetries(uint8_t maxRetries) |
yihui | 0:88960f3eeb2c | 264 | { |
yihui | 0:88960f3eeb2c | 265 | pn532_packetbuffer[0] = PN532_COMMAND_RFCONFIGURATION; |
yihui | 0:88960f3eeb2c | 266 | pn532_packetbuffer[1] = 5; // Config item 5 (MaxRetries) |
yihui | 0:88960f3eeb2c | 267 | pn532_packetbuffer[2] = 0xFF; // MxRtyATR (default = 0xFF) |
yihui | 0:88960f3eeb2c | 268 | pn532_packetbuffer[3] = 0x01; // MxRtyPSL (default = 0x01) |
yihui | 0:88960f3eeb2c | 269 | pn532_packetbuffer[4] = maxRetries; |
yihui | 0:88960f3eeb2c | 270 | |
yihui | 0:88960f3eeb2c | 271 | if (HAL(writeCommand)(pn532_packetbuffer, 5)) |
yihui | 0:88960f3eeb2c | 272 | return 0x0; // no ACK |
yihui | 0:88960f3eeb2c | 273 | |
yihui | 0:88960f3eeb2c | 274 | return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); |
yihui | 0:88960f3eeb2c | 275 | } |
yihui | 0:88960f3eeb2c | 276 | |
yihui | 0:88960f3eeb2c | 277 | /***** ISO14443A Commands ******/ |
yihui | 0:88960f3eeb2c | 278 | |
yihui | 0:88960f3eeb2c | 279 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 280 | /*! |
yihui | 0:88960f3eeb2c | 281 | Waits for an ISO14443A target to enter the field |
yihui | 0:88960f3eeb2c | 282 | |
yihui | 0:88960f3eeb2c | 283 | @param cardBaudRate Baud rate of the card |
yihui | 0:88960f3eeb2c | 284 | @param uid Pointer to the array that will be populated |
yihui | 0:88960f3eeb2c | 285 | with the card's UID (up to 7 bytes) |
yihui | 0:88960f3eeb2c | 286 | @param uidLength Pointer to the variable that will hold the |
yihui | 0:88960f3eeb2c | 287 | length of the card's UID. |
yihui | 0:88960f3eeb2c | 288 | |
yihui | 0:88960f3eeb2c | 289 | @returns 1 if everything executed properly, 0 for an error |
yihui | 0:88960f3eeb2c | 290 | */ |
yihui | 0:88960f3eeb2c | 291 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 292 | bool PN532::readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout) |
yihui | 0:88960f3eeb2c | 293 | { |
yihui | 0:88960f3eeb2c | 294 | pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; |
yihui | 0:88960f3eeb2c | 295 | pn532_packetbuffer[1] = 1; // max 1 cards at once (we can set this to 2 later) |
yihui | 0:88960f3eeb2c | 296 | pn532_packetbuffer[2] = cardbaudrate; |
yihui | 0:88960f3eeb2c | 297 | |
yihui | 0:88960f3eeb2c | 298 | if (HAL(writeCommand)(pn532_packetbuffer, 3)) { |
yihui | 0:88960f3eeb2c | 299 | return 0x0; // command failed |
yihui | 0:88960f3eeb2c | 300 | } |
yihui | 0:88960f3eeb2c | 301 | |
yihui | 0:88960f3eeb2c | 302 | // read data packet |
yihui | 0:88960f3eeb2c | 303 | if (HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout) < 0) { |
yihui | 0:88960f3eeb2c | 304 | return 0x0; |
yihui | 0:88960f3eeb2c | 305 | } |
yihui | 0:88960f3eeb2c | 306 | |
yihui | 0:88960f3eeb2c | 307 | // check some basic stuff |
yihui | 0:88960f3eeb2c | 308 | /* ISO14443A card response should be in the following format: |
yihui | 0:88960f3eeb2c | 309 | |
yihui | 0:88960f3eeb2c | 310 | byte Description |
yihui | 0:88960f3eeb2c | 311 | ------------- ------------------------------------------ |
yihui | 0:88960f3eeb2c | 312 | b0 Tags Found |
yihui | 0:88960f3eeb2c | 313 | b1 Tag Number (only one used in this example) |
yihui | 0:88960f3eeb2c | 314 | b2..3 SENS_RES |
yihui | 0:88960f3eeb2c | 315 | b4 SEL_RES |
yihui | 0:88960f3eeb2c | 316 | b5 NFCID Length |
yihui | 0:88960f3eeb2c | 317 | b6..NFCIDLen NFCID |
yihui | 0:88960f3eeb2c | 318 | */ |
yihui | 0:88960f3eeb2c | 319 | |
yihui | 0:88960f3eeb2c | 320 | if (pn532_packetbuffer[0] != 1) |
yihui | 0:88960f3eeb2c | 321 | return 0; |
yihui | 0:88960f3eeb2c | 322 | |
yihui | 0:88960f3eeb2c | 323 | uint16_t sens_res = pn532_packetbuffer[2]; |
yihui | 0:88960f3eeb2c | 324 | sens_res <<= 8; |
yihui | 0:88960f3eeb2c | 325 | sens_res |= pn532_packetbuffer[3]; |
yihui | 0:88960f3eeb2c | 326 | |
yihui | 0:88960f3eeb2c | 327 | DMSG("ATQA: 0x"); DMSG_HEX(sens_res); |
yihui | 0:88960f3eeb2c | 328 | DMSG("SAK: 0x"); DMSG_HEX(pn532_packetbuffer[4]); |
yihui | 0:88960f3eeb2c | 329 | DMSG("\n"); |
yihui | 0:88960f3eeb2c | 330 | |
yihui | 0:88960f3eeb2c | 331 | /* Card appears to be Mifare Classic */ |
yihui | 0:88960f3eeb2c | 332 | *uidLength = pn532_packetbuffer[5]; |
yihui | 0:88960f3eeb2c | 333 | |
yihui | 0:88960f3eeb2c | 334 | for (uint8_t i = 0; i < pn532_packetbuffer[5]; i++) { |
yihui | 0:88960f3eeb2c | 335 | uid[i] = pn532_packetbuffer[6 + i]; |
yihui | 0:88960f3eeb2c | 336 | } |
yihui | 0:88960f3eeb2c | 337 | |
yihui | 0:88960f3eeb2c | 338 | return 1; |
yihui | 0:88960f3eeb2c | 339 | } |
yihui | 0:88960f3eeb2c | 340 | |
yihui | 0:88960f3eeb2c | 341 | |
yihui | 0:88960f3eeb2c | 342 | /***** Mifare Classic Functions ******/ |
yihui | 0:88960f3eeb2c | 343 | |
yihui | 0:88960f3eeb2c | 344 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 345 | /*! |
yihui | 0:88960f3eeb2c | 346 | Indicates whether the specified block number is the first block |
yihui | 0:88960f3eeb2c | 347 | in the sector (block 0 relative to the current sector) |
yihui | 0:88960f3eeb2c | 348 | */ |
yihui | 0:88960f3eeb2c | 349 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 350 | bool PN532::mifareclassic_IsFirstBlock (uint32_t uiBlock) |
yihui | 0:88960f3eeb2c | 351 | { |
yihui | 0:88960f3eeb2c | 352 | // Test if we are in the small or big sectors |
yihui | 0:88960f3eeb2c | 353 | if (uiBlock < 128) |
yihui | 0:88960f3eeb2c | 354 | return ((uiBlock) % 4 == 0); |
yihui | 0:88960f3eeb2c | 355 | else |
yihui | 0:88960f3eeb2c | 356 | return ((uiBlock) % 16 == 0); |
yihui | 0:88960f3eeb2c | 357 | } |
yihui | 0:88960f3eeb2c | 358 | |
yihui | 0:88960f3eeb2c | 359 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 360 | /*! |
yihui | 0:88960f3eeb2c | 361 | Indicates whether the specified block number is the sector trailer |
yihui | 0:88960f3eeb2c | 362 | */ |
yihui | 0:88960f3eeb2c | 363 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 364 | bool PN532::mifareclassic_IsTrailerBlock (uint32_t uiBlock) |
yihui | 0:88960f3eeb2c | 365 | { |
yihui | 0:88960f3eeb2c | 366 | // Test if we are in the small or big sectors |
yihui | 0:88960f3eeb2c | 367 | if (uiBlock < 128) |
yihui | 0:88960f3eeb2c | 368 | return ((uiBlock + 1) % 4 == 0); |
yihui | 0:88960f3eeb2c | 369 | else |
yihui | 0:88960f3eeb2c | 370 | return ((uiBlock + 1) % 16 == 0); |
yihui | 0:88960f3eeb2c | 371 | } |
yihui | 0:88960f3eeb2c | 372 | |
yihui | 0:88960f3eeb2c | 373 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 374 | /*! |
yihui | 0:88960f3eeb2c | 375 | Tries to authenticate a block of memory on a MIFARE card using the |
yihui | 0:88960f3eeb2c | 376 | INDATAEXCHANGE command. See section 7.3.8 of the PN532 User Manual |
yihui | 0:88960f3eeb2c | 377 | for more information on sending MIFARE and other commands. |
yihui | 0:88960f3eeb2c | 378 | |
yihui | 0:88960f3eeb2c | 379 | @param uid Pointer to a byte array containing the card UID |
yihui | 0:88960f3eeb2c | 380 | @param uidLen The length (in bytes) of the card's UID (Should |
yihui | 0:88960f3eeb2c | 381 | be 4 for MIFARE Classic) |
yihui | 0:88960f3eeb2c | 382 | @param blockNumber The block number to authenticate. (0..63 for |
yihui | 0:88960f3eeb2c | 383 | 1KB cards, and 0..255 for 4KB cards). |
yihui | 0:88960f3eeb2c | 384 | @param keyNumber Which key type to use during authentication |
yihui | 0:88960f3eeb2c | 385 | (0 = MIFARE_CMD_AUTH_A, 1 = MIFARE_CMD_AUTH_B) |
yihui | 0:88960f3eeb2c | 386 | @param keyData Pointer to a byte array containing the 6 bytes |
yihui | 0:88960f3eeb2c | 387 | key value |
yihui | 0:88960f3eeb2c | 388 | |
yihui | 0:88960f3eeb2c | 389 | @returns 1 if everything executed properly, 0 for an error |
yihui | 0:88960f3eeb2c | 390 | */ |
yihui | 0:88960f3eeb2c | 391 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 392 | uint8_t PN532::mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData) |
yihui | 0:88960f3eeb2c | 393 | { |
yihui | 0:88960f3eeb2c | 394 | uint8_t i; |
yihui | 0:88960f3eeb2c | 395 | |
yihui | 0:88960f3eeb2c | 396 | // Hang on to the key and uid data |
yihui | 0:88960f3eeb2c | 397 | memcpy (_key, keyData, 6); |
yihui | 0:88960f3eeb2c | 398 | memcpy (_uid, uid, uidLen); |
yihui | 0:88960f3eeb2c | 399 | _uidLen = uidLen; |
yihui | 0:88960f3eeb2c | 400 | |
yihui | 0:88960f3eeb2c | 401 | // Prepare the authentication command // |
yihui | 0:88960f3eeb2c | 402 | pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; /* Data Exchange Header */ |
yihui | 0:88960f3eeb2c | 403 | pn532_packetbuffer[1] = 1; /* Max card numbers */ |
yihui | 0:88960f3eeb2c | 404 | pn532_packetbuffer[2] = (keyNumber) ? MIFARE_CMD_AUTH_B : MIFARE_CMD_AUTH_A; |
yihui | 0:88960f3eeb2c | 405 | pn532_packetbuffer[3] = blockNumber; /* Block Number (1K = 0..63, 4K = 0..255 */ |
yihui | 0:88960f3eeb2c | 406 | memcpy (pn532_packetbuffer + 4, _key, 6); |
yihui | 0:88960f3eeb2c | 407 | for (i = 0; i < _uidLen; i++) { |
yihui | 0:88960f3eeb2c | 408 | pn532_packetbuffer[10 + i] = _uid[i]; /* 4 bytes card ID */ |
yihui | 0:88960f3eeb2c | 409 | } |
yihui | 0:88960f3eeb2c | 410 | |
yihui | 0:88960f3eeb2c | 411 | if (HAL(writeCommand)(pn532_packetbuffer, 10 + _uidLen)) |
yihui | 0:88960f3eeb2c | 412 | return 0; |
yihui | 0:88960f3eeb2c | 413 | |
yihui | 0:88960f3eeb2c | 414 | // Read the response packet |
yihui | 0:88960f3eeb2c | 415 | HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); |
yihui | 0:88960f3eeb2c | 416 | |
yihui | 0:88960f3eeb2c | 417 | // Check if the response is valid and we are authenticated??? |
yihui | 0:88960f3eeb2c | 418 | // for an auth success it should be bytes 5-7: 0xD5 0x41 0x00 |
yihui | 0:88960f3eeb2c | 419 | // Mifare auth error is technically byte 7: 0x14 but anything other and 0x00 is not good |
yihui | 0:88960f3eeb2c | 420 | if (pn532_packetbuffer[0] != 0x00) { |
yihui | 0:88960f3eeb2c | 421 | DMSG("Authentification failed\n"); |
yihui | 0:88960f3eeb2c | 422 | return 0; |
yihui | 0:88960f3eeb2c | 423 | } |
yihui | 0:88960f3eeb2c | 424 | |
yihui | 0:88960f3eeb2c | 425 | return 1; |
yihui | 0:88960f3eeb2c | 426 | } |
yihui | 0:88960f3eeb2c | 427 | |
yihui | 0:88960f3eeb2c | 428 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 429 | /*! |
yihui | 0:88960f3eeb2c | 430 | Tries to read an entire 16-bytes data block at the specified block |
yihui | 0:88960f3eeb2c | 431 | address. |
yihui | 0:88960f3eeb2c | 432 | |
yihui | 0:88960f3eeb2c | 433 | @param blockNumber The block number to authenticate. (0..63 for |
yihui | 0:88960f3eeb2c | 434 | 1KB cards, and 0..255 for 4KB cards). |
yihui | 0:88960f3eeb2c | 435 | @param data Pointer to the byte array that will hold the |
yihui | 0:88960f3eeb2c | 436 | retrieved data (if any) |
yihui | 0:88960f3eeb2c | 437 | |
yihui | 0:88960f3eeb2c | 438 | @returns 1 if everything executed properly, 0 for an error |
yihui | 0:88960f3eeb2c | 439 | */ |
yihui | 0:88960f3eeb2c | 440 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 441 | uint8_t PN532::mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data) |
yihui | 0:88960f3eeb2c | 442 | { |
yihui | 0:88960f3eeb2c | 443 | DMSG("Trying to read 16 bytes from block "); |
yihui | 0:88960f3eeb2c | 444 | DMSG_INT(blockNumber); |
yihui | 0:88960f3eeb2c | 445 | |
yihui | 0:88960f3eeb2c | 446 | /* Prepare the command */ |
yihui | 0:88960f3eeb2c | 447 | pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; |
yihui | 0:88960f3eeb2c | 448 | pn532_packetbuffer[1] = 1; /* Card number */ |
yihui | 0:88960f3eeb2c | 449 | pn532_packetbuffer[2] = MIFARE_CMD_READ; /* Mifare Read command = 0x30 */ |
yihui | 0:88960f3eeb2c | 450 | pn532_packetbuffer[3] = blockNumber; /* Block Number (0..63 for 1K, 0..255 for 4K) */ |
yihui | 0:88960f3eeb2c | 451 | |
yihui | 0:88960f3eeb2c | 452 | /* Send the command */ |
yihui | 0:88960f3eeb2c | 453 | if (HAL(writeCommand)(pn532_packetbuffer, 4)) { |
yihui | 0:88960f3eeb2c | 454 | return 0; |
yihui | 0:88960f3eeb2c | 455 | } |
yihui | 0:88960f3eeb2c | 456 | |
yihui | 0:88960f3eeb2c | 457 | /* Read the response packet */ |
yihui | 0:88960f3eeb2c | 458 | HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); |
yihui | 0:88960f3eeb2c | 459 | |
yihui | 0:88960f3eeb2c | 460 | /* If byte 8 isn't 0x00 we probably have an error */ |
yihui | 0:88960f3eeb2c | 461 | if (pn532_packetbuffer[0] != 0x00) { |
yihui | 0:88960f3eeb2c | 462 | return 0; |
yihui | 0:88960f3eeb2c | 463 | } |
yihui | 0:88960f3eeb2c | 464 | |
yihui | 0:88960f3eeb2c | 465 | /* Copy the 16 data bytes to the output buffer */ |
yihui | 0:88960f3eeb2c | 466 | /* Block content starts at byte 9 of a valid response */ |
yihui | 0:88960f3eeb2c | 467 | memcpy (data, pn532_packetbuffer + 1, 16); |
yihui | 0:88960f3eeb2c | 468 | |
yihui | 0:88960f3eeb2c | 469 | return 1; |
yihui | 0:88960f3eeb2c | 470 | } |
yihui | 0:88960f3eeb2c | 471 | |
yihui | 0:88960f3eeb2c | 472 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 473 | /*! |
yihui | 0:88960f3eeb2c | 474 | Tries to write an entire 16-bytes data block at the specified block |
yihui | 0:88960f3eeb2c | 475 | address. |
yihui | 0:88960f3eeb2c | 476 | |
yihui | 0:88960f3eeb2c | 477 | @param blockNumber The block number to authenticate. (0..63 for |
yihui | 0:88960f3eeb2c | 478 | 1KB cards, and 0..255 for 4KB cards). |
yihui | 0:88960f3eeb2c | 479 | @param data The byte array that contains the data to write. |
yihui | 0:88960f3eeb2c | 480 | |
yihui | 0:88960f3eeb2c | 481 | @returns 1 if everything executed properly, 0 for an error |
yihui | 0:88960f3eeb2c | 482 | */ |
yihui | 0:88960f3eeb2c | 483 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 484 | uint8_t PN532::mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data) |
yihui | 0:88960f3eeb2c | 485 | { |
yihui | 0:88960f3eeb2c | 486 | /* Prepare the first command */ |
yihui | 0:88960f3eeb2c | 487 | pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; |
yihui | 0:88960f3eeb2c | 488 | pn532_packetbuffer[1] = 1; /* Card number */ |
yihui | 0:88960f3eeb2c | 489 | pn532_packetbuffer[2] = MIFARE_CMD_WRITE; /* Mifare Write command = 0xA0 */ |
yihui | 0:88960f3eeb2c | 490 | pn532_packetbuffer[3] = blockNumber; /* Block Number (0..63 for 1K, 0..255 for 4K) */ |
yihui | 0:88960f3eeb2c | 491 | memcpy (pn532_packetbuffer + 4, data, 16); /* Data Payload */ |
yihui | 0:88960f3eeb2c | 492 | |
yihui | 0:88960f3eeb2c | 493 | /* Send the command */ |
yihui | 0:88960f3eeb2c | 494 | if (HAL(writeCommand)(pn532_packetbuffer, 20)) { |
yihui | 0:88960f3eeb2c | 495 | return 0; |
yihui | 0:88960f3eeb2c | 496 | } |
yihui | 0:88960f3eeb2c | 497 | |
yihui | 0:88960f3eeb2c | 498 | /* Read the response packet */ |
yihui | 0:88960f3eeb2c | 499 | return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); |
yihui | 0:88960f3eeb2c | 500 | } |
yihui | 0:88960f3eeb2c | 501 | |
yihui | 0:88960f3eeb2c | 502 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 503 | /*! |
yihui | 0:88960f3eeb2c | 504 | Formats a Mifare Classic card to store NDEF Records |
yihui | 0:88960f3eeb2c | 505 | |
yihui | 0:88960f3eeb2c | 506 | @returns 1 if everything executed properly, 0 for an error |
yihui | 0:88960f3eeb2c | 507 | */ |
yihui | 0:88960f3eeb2c | 508 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 509 | uint8_t PN532::mifareclassic_FormatNDEF (void) |
yihui | 0:88960f3eeb2c | 510 | { |
yihui | 0:88960f3eeb2c | 511 | uint8_t sectorbuffer1[16] = {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; |
yihui | 0:88960f3eeb2c | 512 | uint8_t sectorbuffer2[16] = {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; |
yihui | 0:88960f3eeb2c | 513 | uint8_t sectorbuffer3[16] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; |
yihui | 0:88960f3eeb2c | 514 | |
yihui | 0:88960f3eeb2c | 515 | // Note 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 must be used for key A |
yihui | 0:88960f3eeb2c | 516 | // for the MAD sector in NDEF records (sector 0) |
yihui | 0:88960f3eeb2c | 517 | |
yihui | 0:88960f3eeb2c | 518 | // Write block 1 and 2 to the card |
yihui | 0:88960f3eeb2c | 519 | if (!(mifareclassic_WriteDataBlock (1, sectorbuffer1))) |
yihui | 0:88960f3eeb2c | 520 | return 0; |
yihui | 0:88960f3eeb2c | 521 | if (!(mifareclassic_WriteDataBlock (2, sectorbuffer2))) |
yihui | 0:88960f3eeb2c | 522 | return 0; |
yihui | 0:88960f3eeb2c | 523 | // Write key A and access rights card |
yihui | 0:88960f3eeb2c | 524 | if (!(mifareclassic_WriteDataBlock (3, sectorbuffer3))) |
yihui | 0:88960f3eeb2c | 525 | return 0; |
yihui | 0:88960f3eeb2c | 526 | |
yihui | 0:88960f3eeb2c | 527 | // Seems that everything was OK (?!) |
yihui | 0:88960f3eeb2c | 528 | return 1; |
yihui | 0:88960f3eeb2c | 529 | } |
yihui | 0:88960f3eeb2c | 530 | |
yihui | 0:88960f3eeb2c | 531 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 532 | /*! |
yihui | 0:88960f3eeb2c | 533 | Writes an NDEF URI Record to the specified sector (1..15) |
yihui | 0:88960f3eeb2c | 534 | |
yihui | 0:88960f3eeb2c | 535 | Note that this function assumes that the Mifare Classic card is |
yihui | 0:88960f3eeb2c | 536 | already formatted to work as an "NFC Forum Tag" and uses a MAD1 |
yihui | 0:88960f3eeb2c | 537 | file system. You can use the NXP TagWriter app on Android to |
yihui | 0:88960f3eeb2c | 538 | properly format cards for this. |
yihui | 0:88960f3eeb2c | 539 | |
yihui | 0:88960f3eeb2c | 540 | @param sectorNumber The sector that the URI record should be written |
yihui | 0:88960f3eeb2c | 541 | to (can be 1..15 for a 1K card) |
yihui | 0:88960f3eeb2c | 542 | @param uriIdentifier The uri identifier code (0 = none, 0x01 = |
yihui | 0:88960f3eeb2c | 543 | "http://www.", etc.) |
yihui | 0:88960f3eeb2c | 544 | @param url The uri text to write (max 38 characters). |
yihui | 0:88960f3eeb2c | 545 | |
yihui | 0:88960f3eeb2c | 546 | @returns 1 if everything executed properly, 0 for an error |
yihui | 0:88960f3eeb2c | 547 | */ |
yihui | 0:88960f3eeb2c | 548 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 549 | uint8_t PN532::mifareclassic_WriteNDEFURI (uint8_t sectorNumber, uint8_t uriIdentifier, const char *url) |
yihui | 0:88960f3eeb2c | 550 | { |
yihui | 0:88960f3eeb2c | 551 | // Figure out how long the string is |
yihui | 0:88960f3eeb2c | 552 | uint8_t len = strlen(url); |
yihui | 0:88960f3eeb2c | 553 | |
yihui | 0:88960f3eeb2c | 554 | // Make sure we're within a 1K limit for the sector number |
yihui | 0:88960f3eeb2c | 555 | if ((sectorNumber < 1) || (sectorNumber > 15)) |
yihui | 0:88960f3eeb2c | 556 | return 0; |
yihui | 0:88960f3eeb2c | 557 | |
yihui | 0:88960f3eeb2c | 558 | // Make sure the URI payload is between 1 and 38 chars |
yihui | 0:88960f3eeb2c | 559 | if ((len < 1) || (len > 38)) |
yihui | 0:88960f3eeb2c | 560 | return 0; |
yihui | 0:88960f3eeb2c | 561 | |
yihui | 0:88960f3eeb2c | 562 | // Note 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 must be used for key A |
yihui | 0:88960f3eeb2c | 563 | // in NDEF records |
yihui | 0:88960f3eeb2c | 564 | |
yihui | 0:88960f3eeb2c | 565 | // Setup the sector buffer (w/pre-formatted TLV wrapper and NDEF message) |
yihui | 0:88960f3eeb2c | 566 | uint8_t sectorbuffer1[16] = {0x00, 0x00, 0x03, len + 5, 0xD1, 0x01, len + 1, 0x55, uriIdentifier, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
yihui | 0:88960f3eeb2c | 567 | uint8_t sectorbuffer2[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
yihui | 0:88960f3eeb2c | 568 | uint8_t sectorbuffer3[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
yihui | 0:88960f3eeb2c | 569 | uint8_t sectorbuffer4[16] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; |
yihui | 0:88960f3eeb2c | 570 | if (len <= 6) { |
yihui | 0:88960f3eeb2c | 571 | // Unlikely we'll get a url this short, but why not ... |
yihui | 0:88960f3eeb2c | 572 | memcpy (sectorbuffer1 + 9, url, len); |
yihui | 0:88960f3eeb2c | 573 | sectorbuffer1[len + 9] = 0xFE; |
yihui | 0:88960f3eeb2c | 574 | } else if (len == 7) { |
yihui | 0:88960f3eeb2c | 575 | // 0xFE needs to be wrapped around to next block |
yihui | 0:88960f3eeb2c | 576 | memcpy (sectorbuffer1 + 9, url, len); |
yihui | 0:88960f3eeb2c | 577 | sectorbuffer2[0] = 0xFE; |
yihui | 0:88960f3eeb2c | 578 | } else if ((len > 7) || (len <= 22)) { |
yihui | 0:88960f3eeb2c | 579 | // Url fits in two blocks |
yihui | 0:88960f3eeb2c | 580 | memcpy (sectorbuffer1 + 9, url, 7); |
yihui | 0:88960f3eeb2c | 581 | memcpy (sectorbuffer2, url + 7, len - 7); |
yihui | 0:88960f3eeb2c | 582 | sectorbuffer2[len - 7] = 0xFE; |
yihui | 0:88960f3eeb2c | 583 | } else if (len == 23) { |
yihui | 0:88960f3eeb2c | 584 | // 0xFE needs to be wrapped around to final block |
yihui | 0:88960f3eeb2c | 585 | memcpy (sectorbuffer1 + 9, url, 7); |
yihui | 0:88960f3eeb2c | 586 | memcpy (sectorbuffer2, url + 7, len - 7); |
yihui | 0:88960f3eeb2c | 587 | sectorbuffer3[0] = 0xFE; |
yihui | 0:88960f3eeb2c | 588 | } else { |
yihui | 0:88960f3eeb2c | 589 | // Url fits in three blocks |
yihui | 0:88960f3eeb2c | 590 | memcpy (sectorbuffer1 + 9, url, 7); |
yihui | 0:88960f3eeb2c | 591 | memcpy (sectorbuffer2, url + 7, 16); |
yihui | 0:88960f3eeb2c | 592 | memcpy (sectorbuffer3, url + 23, len - 24); |
yihui | 0:88960f3eeb2c | 593 | sectorbuffer3[len - 22] = 0xFE; |
yihui | 0:88960f3eeb2c | 594 | } |
yihui | 0:88960f3eeb2c | 595 | |
yihui | 0:88960f3eeb2c | 596 | // Now write all three blocks back to the card |
yihui | 0:88960f3eeb2c | 597 | if (!(mifareclassic_WriteDataBlock (sectorNumber * 4, sectorbuffer1))) |
yihui | 0:88960f3eeb2c | 598 | return 0; |
yihui | 0:88960f3eeb2c | 599 | if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 1, sectorbuffer2))) |
yihui | 0:88960f3eeb2c | 600 | return 0; |
yihui | 0:88960f3eeb2c | 601 | if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 2, sectorbuffer3))) |
yihui | 0:88960f3eeb2c | 602 | return 0; |
yihui | 0:88960f3eeb2c | 603 | if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 3, sectorbuffer4))) |
yihui | 0:88960f3eeb2c | 604 | return 0; |
yihui | 0:88960f3eeb2c | 605 | |
yihui | 0:88960f3eeb2c | 606 | // Seems that everything was OK (?!) |
yihui | 0:88960f3eeb2c | 607 | return 1; |
yihui | 0:88960f3eeb2c | 608 | } |
yihui | 0:88960f3eeb2c | 609 | |
yihui | 0:88960f3eeb2c | 610 | /***** Mifare Ultralight Functions ******/ |
yihui | 0:88960f3eeb2c | 611 | |
yihui | 0:88960f3eeb2c | 612 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 613 | /*! |
yihui | 0:88960f3eeb2c | 614 | Tries to read an entire 4-bytes page at the specified address. |
yihui | 0:88960f3eeb2c | 615 | |
yihui | 0:88960f3eeb2c | 616 | @param page The page number (0..63 in most cases) |
yihui | 0:88960f3eeb2c | 617 | @param buffer Pointer to the byte array that will hold the |
yihui | 0:88960f3eeb2c | 618 | retrieved data (if any) |
yihui | 0:88960f3eeb2c | 619 | */ |
yihui | 0:88960f3eeb2c | 620 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 621 | uint8_t PN532::mifareultralight_ReadPage (uint8_t page, uint8_t *buffer) |
yihui | 0:88960f3eeb2c | 622 | { |
yihui | 0:88960f3eeb2c | 623 | if (page >= 64) { |
yihui | 0:88960f3eeb2c | 624 | DMSG("Page value out of range\n"); |
yihui | 0:88960f3eeb2c | 625 | return 0; |
yihui | 0:88960f3eeb2c | 626 | } |
yihui | 0:88960f3eeb2c | 627 | |
yihui | 0:88960f3eeb2c | 628 | /* Prepare the command */ |
yihui | 0:88960f3eeb2c | 629 | pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; |
yihui | 0:88960f3eeb2c | 630 | pn532_packetbuffer[1] = 1; /* Card number */ |
yihui | 0:88960f3eeb2c | 631 | pn532_packetbuffer[2] = MIFARE_CMD_READ; /* Mifare Read command = 0x30 */ |
yihui | 0:88960f3eeb2c | 632 | pn532_packetbuffer[3] = page; /* Page Number (0..63 in most cases) */ |
yihui | 0:88960f3eeb2c | 633 | |
yihui | 0:88960f3eeb2c | 634 | /* Send the command */ |
yihui | 0:88960f3eeb2c | 635 | if (HAL(writeCommand)(pn532_packetbuffer, 4)) { |
yihui | 0:88960f3eeb2c | 636 | return 0; |
yihui | 0:88960f3eeb2c | 637 | } |
yihui | 0:88960f3eeb2c | 638 | |
yihui | 0:88960f3eeb2c | 639 | /* Read the response packet */ |
yihui | 0:88960f3eeb2c | 640 | HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); |
yihui | 0:88960f3eeb2c | 641 | |
yihui | 0:88960f3eeb2c | 642 | /* If byte 8 isn't 0x00 we probably have an error */ |
yihui | 0:88960f3eeb2c | 643 | if (pn532_packetbuffer[0] == 0x00) { |
yihui | 0:88960f3eeb2c | 644 | /* Copy the 4 data bytes to the output buffer */ |
yihui | 0:88960f3eeb2c | 645 | /* Block content starts at byte 9 of a valid response */ |
yihui | 0:88960f3eeb2c | 646 | /* Note that the command actually reads 16 bytes or 4 */ |
yihui | 0:88960f3eeb2c | 647 | /* pages at a time ... we simply discard the last 12 */ |
yihui | 0:88960f3eeb2c | 648 | /* bytes */ |
yihui | 0:88960f3eeb2c | 649 | memcpy (buffer, pn532_packetbuffer + 1, 4); |
yihui | 0:88960f3eeb2c | 650 | } else { |
yihui | 0:88960f3eeb2c | 651 | return 0; |
yihui | 0:88960f3eeb2c | 652 | } |
yihui | 0:88960f3eeb2c | 653 | |
yihui | 0:88960f3eeb2c | 654 | // Return OK signal |
yihui | 0:88960f3eeb2c | 655 | return 1; |
yihui | 0:88960f3eeb2c | 656 | } |
yihui | 0:88960f3eeb2c | 657 | |
yihui | 0:88960f3eeb2c | 658 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 659 | /*! |
yihui | 0:88960f3eeb2c | 660 | @brief Exchanges an APDU with the currently inlisted peer |
yihui | 0:88960f3eeb2c | 661 | |
yihui | 0:88960f3eeb2c | 662 | @param send Pointer to data to send |
yihui | 0:88960f3eeb2c | 663 | @param sendLength Length of the data to send |
yihui | 0:88960f3eeb2c | 664 | @param response Pointer to response data |
yihui | 0:88960f3eeb2c | 665 | @param responseLength Pointer to the response data length |
yihui | 0:88960f3eeb2c | 666 | */ |
yihui | 0:88960f3eeb2c | 667 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 668 | bool PN532::inDataExchange(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength) |
yihui | 0:88960f3eeb2c | 669 | { |
yihui | 0:88960f3eeb2c | 670 | uint8_t i; |
yihui | 0:88960f3eeb2c | 671 | |
yihui | 0:88960f3eeb2c | 672 | pn532_packetbuffer[0] = 0x40; // PN532_COMMAND_INDATAEXCHANGE; |
yihui | 0:88960f3eeb2c | 673 | pn532_packetbuffer[1] = inListedTag; |
yihui | 0:88960f3eeb2c | 674 | |
yihui | 0:88960f3eeb2c | 675 | if (HAL(writeCommand)(pn532_packetbuffer, 2, send, sendLength)) { |
yihui | 0:88960f3eeb2c | 676 | return false; |
yihui | 0:88960f3eeb2c | 677 | } |
yihui | 0:88960f3eeb2c | 678 | |
yihui | 0:88960f3eeb2c | 679 | int16_t status = HAL(readResponse)(response, *responseLength, 1000); |
yihui | 0:88960f3eeb2c | 680 | if (status < 0) { |
yihui | 0:88960f3eeb2c | 681 | return false; |
yihui | 0:88960f3eeb2c | 682 | } |
yihui | 0:88960f3eeb2c | 683 | |
yihui | 0:88960f3eeb2c | 684 | if ((response[0] & 0x3f) != 0) { |
yihui | 0:88960f3eeb2c | 685 | DMSG("Status code indicates an error\n"); |
yihui | 0:88960f3eeb2c | 686 | return false; |
yihui | 0:88960f3eeb2c | 687 | } |
yihui | 0:88960f3eeb2c | 688 | |
yihui | 0:88960f3eeb2c | 689 | uint8_t length = status; |
yihui | 0:88960f3eeb2c | 690 | length -= 1; |
yihui | 0:88960f3eeb2c | 691 | |
yihui | 0:88960f3eeb2c | 692 | if (length > *responseLength) { |
yihui | 0:88960f3eeb2c | 693 | length = *responseLength; // silent truncation... |
yihui | 0:88960f3eeb2c | 694 | } |
yihui | 0:88960f3eeb2c | 695 | |
yihui | 0:88960f3eeb2c | 696 | for (uint8_t i = 0; i < length; i++) { |
yihui | 0:88960f3eeb2c | 697 | response[i] = response[i + 1]; |
yihui | 0:88960f3eeb2c | 698 | } |
yihui | 0:88960f3eeb2c | 699 | *responseLength = length; |
yihui | 0:88960f3eeb2c | 700 | |
yihui | 0:88960f3eeb2c | 701 | return true; |
yihui | 0:88960f3eeb2c | 702 | } |
yihui | 0:88960f3eeb2c | 703 | |
yihui | 0:88960f3eeb2c | 704 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 705 | /*! |
yihui | 0:88960f3eeb2c | 706 | @brief 'InLists' a passive target. PN532 acting as reader/initiator, |
yihui | 0:88960f3eeb2c | 707 | peer acting as card/responder. |
yihui | 0:88960f3eeb2c | 708 | */ |
yihui | 0:88960f3eeb2c | 709 | /**************************************************************************/ |
yihui | 0:88960f3eeb2c | 710 | bool PN532::inListPassiveTarget() |
yihui | 0:88960f3eeb2c | 711 | { |
yihui | 0:88960f3eeb2c | 712 | pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; |
yihui | 0:88960f3eeb2c | 713 | pn532_packetbuffer[1] = 1; |
yihui | 0:88960f3eeb2c | 714 | pn532_packetbuffer[2] = 0; |
yihui | 0:88960f3eeb2c | 715 | |
yihui | 0:88960f3eeb2c | 716 | DMSG("inList passive target\n"); |
yihui | 0:88960f3eeb2c | 717 | |
yihui | 0:88960f3eeb2c | 718 | if (HAL(writeCommand)(pn532_packetbuffer, 3)) { |
yihui | 0:88960f3eeb2c | 719 | return false; |
yihui | 0:88960f3eeb2c | 720 | } |
yihui | 0:88960f3eeb2c | 721 | |
yihui | 0:88960f3eeb2c | 722 | int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 30000); |
yihui | 0:88960f3eeb2c | 723 | if (status < 0) { |
yihui | 0:88960f3eeb2c | 724 | return false; |
yihui | 0:88960f3eeb2c | 725 | } |
yihui | 0:88960f3eeb2c | 726 | |
yihui | 0:88960f3eeb2c | 727 | if (pn532_packetbuffer[0] != 1) { |
yihui | 0:88960f3eeb2c | 728 | return false; |
yihui | 0:88960f3eeb2c | 729 | } |
yihui | 0:88960f3eeb2c | 730 | |
yihui | 0:88960f3eeb2c | 731 | inListedTag = pn532_packetbuffer[1]; |
yihui | 0:88960f3eeb2c | 732 | |
yihui | 0:88960f3eeb2c | 733 | return true; |
yihui | 0:88960f3eeb2c | 734 | } |
yihui | 0:88960f3eeb2c | 735 | |
yihui | 0:88960f3eeb2c | 736 | int8_t PN532::tgInitAsTarget(const uint8_t* command, const uint8_t len, const uint16_t timeout){ |
yihui | 0:88960f3eeb2c | 737 | |
yihui | 0:88960f3eeb2c | 738 | int8_t status = HAL(writeCommand)(command, len); |
yihui | 0:88960f3eeb2c | 739 | if (status < 0) { |
yihui | 0:88960f3eeb2c | 740 | return -1; |
yihui | 0:88960f3eeb2c | 741 | } |
yihui | 0:88960f3eeb2c | 742 | |
yihui | 0:88960f3eeb2c | 743 | status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout); |
yihui | 0:88960f3eeb2c | 744 | if (status > 0) { |
yihui | 0:88960f3eeb2c | 745 | return 1; |
yihui | 0:88960f3eeb2c | 746 | } else if (PN532_TIMEOUT == status) { |
yihui | 0:88960f3eeb2c | 747 | return 0; |
yihui | 0:88960f3eeb2c | 748 | } else { |
yihui | 0:88960f3eeb2c | 749 | return -2; |
yihui | 0:88960f3eeb2c | 750 | } |
yihui | 0:88960f3eeb2c | 751 | } |
yihui | 0:88960f3eeb2c | 752 | |
yihui | 0:88960f3eeb2c | 753 | /** |
yihui | 0:88960f3eeb2c | 754 | * Peer to Peer |
yihui | 0:88960f3eeb2c | 755 | */ |
yihui | 0:88960f3eeb2c | 756 | int8_t PN532::tgInitAsTarget(uint16_t timeout) |
yihui | 0:88960f3eeb2c | 757 | { |
yihui | 0:88960f3eeb2c | 758 | const uint8_t command[] = { |
yihui | 0:88960f3eeb2c | 759 | PN532_COMMAND_TGINITASTARGET, |
yihui | 0:88960f3eeb2c | 760 | 0, |
yihui | 0:88960f3eeb2c | 761 | 0x00, 0x00, //SENS_RES |
yihui | 0:88960f3eeb2c | 762 | 0x00, 0x00, 0x00, //NFCID1 |
yihui | 0:88960f3eeb2c | 763 | 0x40, //SEL_RES |
yihui | 0:88960f3eeb2c | 764 | |
yihui | 0:88960f3eeb2c | 765 | 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, // POL_RES |
yihui | 0:88960f3eeb2c | 766 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
yihui | 0:88960f3eeb2c | 767 | 0xFF, 0xFF, |
yihui | 0:88960f3eeb2c | 768 | |
yihui | 0:88960f3eeb2c | 769 | 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, 0x00, 0x00, //NFCID3t: Change this to desired value |
yihui | 0:88960f3eeb2c | 770 | |
yihui | 0:88960f3eeb2c | 771 | 0x06, 0x46, 0x66, 0x6D, 0x01, 0x01, 0x10, 0x00// LLCP magic number and version parameter |
yihui | 0:88960f3eeb2c | 772 | }; |
yihui | 0:88960f3eeb2c | 773 | return tgInitAsTarget(command, sizeof(command), timeout); |
yihui | 0:88960f3eeb2c | 774 | } |
yihui | 0:88960f3eeb2c | 775 | |
yihui | 0:88960f3eeb2c | 776 | int16_t PN532::tgGetData(uint8_t *buf, uint8_t len) |
yihui | 0:88960f3eeb2c | 777 | { |
yihui | 0:88960f3eeb2c | 778 | buf[0] = PN532_COMMAND_TGGETDATA; |
yihui | 0:88960f3eeb2c | 779 | |
yihui | 0:88960f3eeb2c | 780 | if (HAL(writeCommand)(buf, 1)) { |
yihui | 0:88960f3eeb2c | 781 | return -1; |
yihui | 0:88960f3eeb2c | 782 | } |
yihui | 0:88960f3eeb2c | 783 | |
yihui | 0:88960f3eeb2c | 784 | int16_t status = HAL(readResponse)(buf, len, 3000); |
yihui | 0:88960f3eeb2c | 785 | if (0 >= status) { |
yihui | 0:88960f3eeb2c | 786 | return status; |
yihui | 0:88960f3eeb2c | 787 | } |
yihui | 0:88960f3eeb2c | 788 | |
yihui | 0:88960f3eeb2c | 789 | uint16_t length = status - 1; |
yihui | 0:88960f3eeb2c | 790 | |
yihui | 0:88960f3eeb2c | 791 | |
yihui | 0:88960f3eeb2c | 792 | if (buf[0] != 0) { |
yihui | 0:88960f3eeb2c | 793 | DMSG("status is not ok\n"); |
yihui | 0:88960f3eeb2c | 794 | return -5; |
yihui | 0:88960f3eeb2c | 795 | } |
yihui | 0:88960f3eeb2c | 796 | |
yihui | 0:88960f3eeb2c | 797 | for (uint8_t i = 0; i < length; i++) { |
yihui | 0:88960f3eeb2c | 798 | buf[i] = buf[i + 1]; |
yihui | 0:88960f3eeb2c | 799 | } |
yihui | 0:88960f3eeb2c | 800 | |
yihui | 0:88960f3eeb2c | 801 | return length; |
yihui | 0:88960f3eeb2c | 802 | } |
yihui | 0:88960f3eeb2c | 803 | |
yihui | 0:88960f3eeb2c | 804 | bool PN532::tgSetData(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) |
yihui | 0:88960f3eeb2c | 805 | { |
yihui | 0:88960f3eeb2c | 806 | if (hlen > (sizeof(pn532_packetbuffer) - 1)) { |
yihui | 0:88960f3eeb2c | 807 | if ((body != 0) || (header == pn532_packetbuffer)) { |
yihui | 0:88960f3eeb2c | 808 | DMSG("tgSetData:buffer too small\n"); |
yihui | 0:88960f3eeb2c | 809 | return false; |
yihui | 0:88960f3eeb2c | 810 | } |
yihui | 0:88960f3eeb2c | 811 | |
yihui | 0:88960f3eeb2c | 812 | pn532_packetbuffer[0] = PN532_COMMAND_TGSETDATA; |
yihui | 0:88960f3eeb2c | 813 | if (HAL(writeCommand)(pn532_packetbuffer, 1, header, hlen)) { |
yihui | 0:88960f3eeb2c | 814 | return false; |
yihui | 0:88960f3eeb2c | 815 | } |
yihui | 0:88960f3eeb2c | 816 | } else { |
yihui | 0:88960f3eeb2c | 817 | for (int8_t i = hlen - 1; i >= 0; i--){ |
yihui | 0:88960f3eeb2c | 818 | pn532_packetbuffer[i + 1] = header[i]; |
yihui | 0:88960f3eeb2c | 819 | } |
yihui | 0:88960f3eeb2c | 820 | pn532_packetbuffer[0] = PN532_COMMAND_TGSETDATA; |
yihui | 0:88960f3eeb2c | 821 | |
yihui | 0:88960f3eeb2c | 822 | if (HAL(writeCommand)(pn532_packetbuffer, hlen + 1, body, blen)) { |
yihui | 0:88960f3eeb2c | 823 | return false; |
yihui | 0:88960f3eeb2c | 824 | } |
yihui | 0:88960f3eeb2c | 825 | } |
yihui | 0:88960f3eeb2c | 826 | |
yihui | 0:88960f3eeb2c | 827 | if (0 > HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 3000)) { |
yihui | 0:88960f3eeb2c | 828 | return false; |
yihui | 0:88960f3eeb2c | 829 | } |
yihui | 0:88960f3eeb2c | 830 | |
yihui | 0:88960f3eeb2c | 831 | if (0 != pn532_packetbuffer[0]) { |
yihui | 0:88960f3eeb2c | 832 | return false; |
yihui | 0:88960f3eeb2c | 833 | } |
yihui | 0:88960f3eeb2c | 834 | |
yihui | 0:88960f3eeb2c | 835 | return true; |
yihui | 0:88960f3eeb2c | 836 | } |
yihui | 0:88960f3eeb2c | 837 | |
yihui | 0:88960f3eeb2c | 838 | int16_t PN532::inRelease(const uint8_t relevantTarget){ |
yihui | 0:88960f3eeb2c | 839 | |
yihui | 0:88960f3eeb2c | 840 | pn532_packetbuffer[0] = PN532_COMMAND_INRELEASE; |
yihui | 0:88960f3eeb2c | 841 | pn532_packetbuffer[1] = relevantTarget; |
yihui | 0:88960f3eeb2c | 842 | |
yihui | 0:88960f3eeb2c | 843 | if (HAL(writeCommand)(pn532_packetbuffer, 2)) { |
yihui | 0:88960f3eeb2c | 844 | return 0; |
yihui | 0:88960f3eeb2c | 845 | } |
yihui | 0:88960f3eeb2c | 846 | |
yihui | 0:88960f3eeb2c | 847 | // read data packet |
yihui | 0:88960f3eeb2c | 848 | return HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); |
yihui | 0:88960f3eeb2c | 849 | } |
yihui | 0:88960f3eeb2c | 850 | |
yihui | 0:88960f3eeb2c | 851 |