├── img ├── board.png ├── schematic.png └── screenshot.jpg ├── .gitmodules ├── src ├── Makefile ├── funconfig.h └── nfc.c ├── LICENSE └── README.md /img/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinEssink/CH32V003-NFC/HEAD/img/board.png -------------------------------------------------------------------------------- /img/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinEssink/CH32V003-NFC/HEAD/img/schematic.png -------------------------------------------------------------------------------- /img/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinEssink/CH32V003-NFC/HEAD/img/screenshot.jpg -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ch32v003fun"] 2 | path = ch32v003fun 3 | url = https://github.com/cnlohr/ch32v003fun.git 4 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | all : flash 2 | 3 | TARGET:=nfc 4 | 5 | CH32V003FUN?=../ch32v003fun/ch32v003fun 6 | 7 | include $(CH32V003FUN)/ch32v003fun.mk 8 | 9 | flash : cv_flash 10 | clean : cv_clean 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/funconfig.h: -------------------------------------------------------------------------------- 1 | #ifndef _FUNCONFIG_H 2 | #define _FUNCONFIG_H 3 | 4 | #define FUNCONF_USE_HSE 1 // Use External Oscillator 5 | #define FUNCONF_USE_PLL 1 // Use built-in 2x PLL 6 | #define FUNCONF_SYSTICK_USE_HCLK 1 // Should systick be at 48 MHz or 6MHz? 7 | #define CH32V003 1 8 | 9 | 10 | #endif 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MartinEssink 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CH32V003 NFC 2 | Emulating a NFC tag directly on the CH32V003, inspired by [SimpleNFC](https://www.nonan.net/nkruse/simplenfc) using 3 | [ch32v003fun](https://github.com/cnlohr/ch32v003fun). This repository allows this 10-cent microcontroller to act as a ncf tag, requiring only a few passive components. The implementation here emulates a ISO/IEC 14443-3A, NFC Forum Type 2 Tag containg Ndef data. Though, any other tag supporting the 14443-3A standard should work as well. Currently supported commands reading and writing to the tag storage (including the UID), as well as the commands required for establishing communication. 4 | 5 | ![Image](img/screenshot.jpg) 6 | 7 | The code in this repository relies on the built-in OPA in the CH32V003, including the corresponding pins D0, D4 and D7. Further TIM2 is needed for transmission through load modulation. Finally, a 13.56MHz oscillator provides the clock signal that matches the NFC signal. 8 | 9 | The current implementation is very basic, and does not handle anticollision and state changes according to specifications. Instead, this tag responds to every query it receives. Luckily, this is no problem when no other tags are present in front of the reader. 10 | 11 | ## Hardware 12 | ![Image](img/schematic.png) 13 | The antenna is a square planar coil of 36.83mm outer length, consisting of 6 turns of 0.254mm track spaced 0.254mm apart. This, with a capacitor `C5` of 47pF, has a resonance frequency of 13.56MHz. 14 | 15 | - `D1` Rectifies the incoming AC-signal 16 | - `C8` and `R1` act as a low-pass filter, rejecting the 13.56MHz carrier. 17 | - `C6`, `R5` and `R4` are a further low-pass filter that acts as a reference for the OPA to detect incoming pulses. 18 | - `R3` is the input resistor, as well as the modulated load for transmission. 19 | - `R2` weakly pulls up the signal to avoid false triggers. 20 | 21 | `⚠️` The schematic above relies on the protection diodes in the IC. To better protect the microcontroller it may be advisable to add protection diodes. 22 | 23 | ## Applications 24 | There is a lot that can be done over nfc, such as: 25 | - Emulating a tag 26 | - Reading sensors 27 | - Controlling peripherals 28 | - Programming the microcontroller 29 | - So much more... 30 | -------------------------------------------------------------------------------- /src/nfc.c: -------------------------------------------------------------------------------- 1 | #include "ch32v003fun.h" 2 | #include 3 | #include 4 | 5 | #define NFC_CMD_WUPA 0x52 6 | #define NFC_CMD_REQA 0x26 7 | #define NFC_CMD_CL1 0x93 8 | #define NFC_CMD_CL2 0x95 9 | #define NFC_CMD_READ 0x30 10 | #define NFC_CMD_WRITE 0xA2 11 | 12 | #define NFC_CASCADE_TAG 0x88 13 | #define NFC_NVB_NONE 0x20 14 | #define NFC_NVB_ALL 0x70 15 | #define NFC_SAK_NC 0x04 //Select acknowledge uid not complete 16 | #define NFC_SAK_C 0x00 //Select acknowledge uid complete, Type 2 (PICC not compliant to ISO/IEC 14443-4) 17 | 18 | #define NFC_ACK 0xA 19 | #define NFC_NAK_CRC 0x1 20 | #define NFC_NAK 0x0 21 | 22 | #define NFC_CLOCK_MULTIPLIER 2 // Multiple of 13.56MHz 23 | #define NFC_RX_BIT_DELAY (64*NFC_CLOCK_MULTIPLIER) 24 | #define NFC_TX_DELAY_HIGH (916*NFC_CLOCK_MULTIPLIER) // 1236-5*64 25 | #define NFC_TX_DELAY_LOW (852*NFC_CLOCK_MULTIPLIER) // 1172-5*64 26 | 27 | #define NFC_BUFFER_LENGTH 32 // Must be a multiple of 2 28 | 29 | uint32_t nfc_bit_time; 30 | uint8_t buffer[NFC_BUFFER_LENGTH]; 31 | 32 | struct nfc_data_t { 33 | uint8_t ATQA[2]; 34 | // We store the cascase tag right before the tag storage. 35 | // This way we don't need to copy it to the buffer for transmission. 36 | uint8_t CT; 37 | uint8_t storage[128]; 38 | }; 39 | 40 | struct nfc_data_t nfc_data = { 41 | {0x44, 0x00}, 42 | NFC_CASCADE_TAG, 43 | { 44 | 0x11, 0x22, 0x33, 0x88, // UID0-2, BCC0 (XOR CT, UID0-2) 45 | 0x44, 0x55, 0x66, 0x77, // UID3-7 46 | 0x00, 0x00, 0x00, 0x00, // BCC1 (XOR UID3-7), INT, Lock0, Lock1 47 | 0xE1, 0x10, 0x0F, 0x00, // CC: Magic number, Version, Size, RW Access. (NFCForum-TS-Type-2-Tag_1.1) 48 | 49 | 0x03,//Type NDEF 50 | 0x19,//Length 51 | 52 | 0xD1, 0x01, 0x15, 0x54, 53 | 0x02, 0x65, 0x6E, 54 | 55 | 0x48, 0x69, 0x2C, 0x20, 0x49, 0x20, 0x61, 0x6D, 0x20, 0x43, 0x48, 0x33, 0x32, 0x56, 0x30, 0x30, 0x33, 0x21, 56 | 57 | 0xFE //Terminate 58 | } 59 | }; 60 | 61 | void nfc_crc16(uint8_t *data, uint8_t len){ 62 | uint8_t ch; 63 | uint16_t wCrc = 0x6363; // ITU-V.41 64 | 65 | do { 66 | ch = *data++; 67 | ch = (ch^(uint8_t)((wCrc) & 0x00FF)); 68 | ch = (ch^(ch<<4)); 69 | wCrc = (wCrc >> 8)^((uint16_t)ch << 8)^((uint16_t)ch<<3)^((uint16_t)ch>>4); 70 | } while (--len); 71 | 72 | *data++ = (uint8_t) (wCrc & 0xFF); 73 | *data = (uint8_t) ((wCrc >> 8) & 0xFF); 74 | } 75 | 76 | void nfc_tx_bit(uint8_t bit){ 77 | while( ((int32_t)( SysTick->CNT - nfc_bit_time )) < 0 ); 78 | 79 | if(bit){ 80 | GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(7*4); 81 | }else{ 82 | GPIOD->CFGLR &= ~(0xf<<(7*4)); 83 | } 84 | 85 | nfc_bit_time += NFC_RX_BIT_DELAY; 86 | while( ((int32_t)( SysTick->CNT - nfc_bit_time )) < 0 ); 87 | 88 | if(bit){ 89 | GPIOD->CFGLR &= ~(0xf<<(7*4)); 90 | }else{ 91 | GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(7*4); 92 | } 93 | 94 | nfc_bit_time += NFC_RX_BIT_DELAY; 95 | } 96 | 97 | void nfc_tx_bytes(uint8_t *data, uint8_t len){ 98 | uint8_t tx_parity; 99 | 100 | //Start bit 101 | nfc_tx_bit(1); 102 | 103 | for(uint8_t tx_byte = 0; tx_byteCNT - nfc_bit_time )) < 0 ); 118 | GPIOD->CFGLR &= ~(0xf<<(7*4)); 119 | } 120 | 121 | void nfc_tx_ack(uint8_t data){ 122 | //Start bit 123 | nfc_tx_bit(1); 124 | 125 | for(uint8_t tx_bit = 0; tx_bit<4; tx_bit++) nfc_tx_bit( data & (1U << tx_bit) ); 126 | 127 | // Stop 'bit' 128 | while( ((int32_t)( SysTick->CNT - nfc_bit_time )) < 0 ); 129 | GPIOD->CFGLR &= ~(0xf<<(7*4)); 130 | } 131 | 132 | void nfc_read(uint8_t block){ 133 | uint16_t pos = (uint16_t)block * 4; 134 | 135 | for(uint8_t i=0; i < 16; i++){ 136 | buffer[i] = (pos < sizeof(nfc_data.storage)) ? nfc_data.storage[pos] : 0; 137 | pos++; 138 | } 139 | 140 | nfc_crc16(buffer, 16); 141 | nfc_tx_bytes(buffer, 18); 142 | } 143 | 144 | void nfc_write(uint8_t block){ 145 | uint16_t pos = (uint16_t)block * 4; 146 | 147 | nfc_crc16(buffer, 8); 148 | if ((buffer[8] | buffer[9]) == 0){ 149 | for(uint8_t i=2; i < 6; i++){ //byte 2-5 contains Data 150 | if (pos < sizeof(nfc_data.storage)) nfc_data.storage[pos] = buffer[i]; 151 | pos++; 152 | } 153 | nfc_tx_ack(NFC_ACK); // ACK 154 | }else{ 155 | nfc_tx_ack(NFC_NAK_CRC); // NAK (CRC) 156 | } 157 | } 158 | 159 | void EXTI7_0_IRQHandler( void ) __attribute__((interrupt)); 160 | void EXTI7_0_IRQHandler( void ){ 161 | // The interrupt triggers somewhere in the middle of the first pulse, 162 | // so we can use this directly as the starting point. Offset if needed. 163 | nfc_bit_time = SysTick->CNT; 164 | 165 | // We do not have enough cycles to initiate the entire buffer, we it on the fly. 166 | buffer[0] = 0; 167 | uint8_t rx_bit = 0; 168 | uint8_t rx_byte = 0; 169 | uint8_t i; 170 | 171 | rx_loop: 172 | i = 3; 173 | do{ 174 | nfc_bit_time += NFC_RX_BIT_DELAY; 175 | while( ((int32_t)( SysTick->CNT - nfc_bit_time )) < 0 ); 176 | if((GPIOD->INDR & (1<<4))==0){ 177 | switch(i){ 178 | // There is no break here on purpose, we slide to the next goto. 179 | case 0: rx_bit++; // Increment by 2, write 180 | case 1: rx_bit++; // Increment by 1, write 181 | case 2: goto rx_write; // Write 182 | case 3: goto rx_reset; // Consecutive pulses, reset 183 | } 184 | } 185 | } while(i--); 186 | goto rx_done; 187 | 188 | rx_write: 189 | if(rx_bit>17){ 190 | if(++rx_byte >= NFC_BUFFER_LENGTH) goto rx_reset; 191 | buffer[rx_byte] = 0; 192 | rx_bit -= 18; 193 | } 194 | 195 | if(rx_bit & 0x01) buffer[rx_byte] |= (1 << (rx_bit>>1)); 196 | 197 | rx_bit += 2; 198 | goto rx_loop; 199 | 200 | rx_done: 201 | if (rx_bit > 7) rx_byte++; 202 | if( rx_byte == 0 ) goto rx_reset; 203 | 204 | if (rx_bit & 0x01){ // Odd rx bit, we wrote a 1 205 | nfc_bit_time += NFC_TX_DELAY_HIGH; 206 | }else{ 207 | nfc_bit_time += NFC_TX_DELAY_LOW; 208 | } 209 | 210 | switch(buffer[0]){ 211 | // Wakeup commands 212 | case NFC_CMD_REQA: //REQuest (type A) 213 | case NFC_CMD_WUPA: //Wake-UP (type A) 214 | // nfc_tx_bytes(ATQA, sizeof(ATQA)); // Answer To reQuest (type A) 215 | nfc_tx_bytes(nfc_data.ATQA, sizeof(nfc_data.ATQA)); // Answer To reQuest (type A) 216 | break; 217 | 218 | case NFC_CMD_CL1: // Cascade Level 1 (anticollision) 219 | if(buffer[1] == NFC_NVB_NONE){ 220 | nfc_tx_bytes(&nfc_data.CT, 5); 221 | }else if(buffer[1] == NFC_NVB_ALL){ 222 | buffer[0] = NFC_SAK_NC; 223 | nfc_crc16(buffer,1); 224 | nfc_tx_bytes(buffer, 3); 225 | } 226 | break; 227 | 228 | case NFC_CMD_CL2: // Cascade Level 2 (anticollision) 229 | if(buffer[1] == NFC_NVB_NONE){ 230 | nfc_tx_bytes(&nfc_data.storage[4], 5); 231 | }else if(buffer[1] == NFC_NVB_ALL){ 232 | buffer[0] = NFC_SAK_C; 233 | nfc_crc16(buffer,1); 234 | nfc_tx_bytes(buffer, 3); 235 | } 236 | break; 237 | 238 | case NFC_CMD_READ: 239 | nfc_read(buffer[1]); 240 | break; 241 | 242 | case NFC_CMD_WRITE: 243 | nfc_write(buffer[1]); 244 | break; 245 | } 246 | 247 | rx_reset: 248 | EXTI->INTFR = 1<<4; 249 | } 250 | 251 | void nfc_init(){ 252 | // Enable required peripherals 253 | RCC->APB1PCENR |= RCC_APB1Periph_TIM2; 254 | RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO; 255 | 256 | // Enable OPA, use PD7 and PD0 as input 257 | EXTEN->EXTEN_CTR |= EXTEN_OPA_EN | EXTEN_OPA_PSEL | EXTEN_OPA_NSEL; 258 | 259 | // Setup GPIOD 260 | GPIOD->CFGLR &= ~( (0xF<<(4*0)) | (0xF<<(4*4)) | (0xF<<(4*7)) ); 261 | GPIOD->CFGLR |= (GPIO_Speed_In | GPIO_CNF_IN_PUPD)<<(4*4); 262 | 263 | // External interupt triggers on the first pulse of the signal 264 | AFIO->EXTICR = 3<<(4*2); // EXT4 on PORTD 265 | EXTI->INTENR = 1<<4; // Enable EXT4 266 | EXTI->FTENR = 1<<4; // Falling edge trigger 267 | 268 | // Transmission is by subcarrier at 1/16 of 13.56MHz 269 | // Setup timer for transmit, we modulate SIG (PD7) 270 | TIM2->PSC = 0x0000; // Prescaler 271 | TIM2->ATRLR = 31; // Auto Reload - sets period 272 | TIM2->SWEVGR |= TIM_UG; // Reload immediately 273 | TIM2->CCER |= TIM_CC4E | TIM_CC4P; // Enable CH4 output, positive pol 274 | TIM2->CHCTLR2 |= TIM_OC4M_2 | TIM_OC4M_1; // CH4 Mode is output, PWM1 (CC1S = 00, OC1M = 110) 275 | TIM2->CH4CVR = 16; // Set the Capture Compare Register value to 50% 276 | TIM2->BDTR |= TIM_MOE; // Enable TIM2 outputs 277 | TIM2->CTLR1 |= TIM_CEN; // Enable TIM2 278 | 279 | // Enable the interrupt 280 | NVIC_EnableIRQ( EXTI7_0_IRQn ); 281 | } 282 | 283 | int main(){ 284 | SystemInit(); 285 | nfc_init(); 286 | 287 | while(1){ 288 | // Delay_Ms(1000); 289 | } 290 | } --------------------------------------------------------------------------------