I had tested reading Mifare RFID 1K cards with an RFID-RC522 module via UART using a Pyhton script on my PC. Now I am using STM8S103F3 to interface with the same RFID-RC522 (MFRC522) via SPI, but I have the problem of not getting the correct values/addresses returned by MFRC522 when sending the same commands as I did with the Python script/Terminal. It's the 1st time I use SPI so I suspect myself of not having properly configured SPI or doing something wrong with the write/read sequence, but I could not manage to troubleshoot it myself.
Here is my main() function:
int main ( void ) {
initClockHSI();
initGPIO();
initUART1();
initMasterSPI();
initMFRC522();
return 0;
}
The init functions are as follows:
void initClockHSI ( void ) {
CLK_DeInit(); // Deinitializes the CLK peripheral registers to their default reset
CLK_SYSCLKConfig ( CLK_PRESCALER_CPUDIV1 ); // (uint8_t)0x80 CPU clock division factors 1
CLK_SYSCLKConfig ( CLK_PRESCALER_HSIDIV1 ); // (uint8_t)0x00 High speed internal clock prescaler: 1
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, // (uint8_t)0x01 Enable the automatic clock switching mode
CLK_SOURCE_HSI, // (uint8_t)0xE1 Clock Source HSI
DISABLE, // DISABLE = 0
CLK_CURRENTCLOCKSTATE_DISABLE); // CLK_CURRENTCLOCKSTATE_DISABLE = (uint8_t)0x00 Current clock disable
}
void initGPIO ( void ) {
// UART1:
GPIO_DeInit(GPIOD);
GPIO_Init(GPIOD, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_SLOW); // (uint8_t)0xD0 Output push-pull, high level, 2MHz
GPIO_Init(GPIOD, GPIO_PIN_6, GPIO_MODE_IN_PU_NO_IT); // UART1 Rx (uint8_t)0x40 Input pull-up, no external interrupt
// MFRC522:
GPIO_DeInit(GPIOC);
// MOSI:
GPIO_Init (GPIOC, GPIO_PIN_6, GPIO_MODE_OUT_PP_HIGH_SLOW);// (uint8_t)0xD0 Output push-pull, high level, 2MHz
// SCK:
GPIO_Init (GPIOC, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_SLOW); // (uint8_t)0xC0 Output push-pull, low level, 2MHz
// MISO (Input):
GPIO_Init (GPIOC, GPIO_PIN_7, GPIO_MODE_IN_PU_NO_IT); // (uint8_t)0x40 Input pull-up, no external interrupt
GPIO_DeInit(GPIOB);
// NRSTPD:
GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_OUT_PP_HIGH_SLOW); // (uint8_t)0xD0 Output push-pull, high level, 2MHz
// NSS (pulled-up externally via 10k R):
GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_SLOW); // (uint8_t)0xD0 Output push-pull, high level, 2MHz
}
void initMasterSPI ( void ) {
SPI_DeInit(); // Deinitializes the SPI peripheral registers to their default reset values
SPI_Init(SPI_FIRSTBIT_MSB, // (uint8_t)0x00 MSB bit will be transmitted first
SPI_BAUDRATEPRESCALER_16, // (uint8_t)0x18 SPI frequency = frequency(CPU)/16 == 1 MHz baud
SPI_MODE_MASTER, // (uint8_t)0x04 SPI Master configuration
SPI_CLOCKPOLARITY_LOW, // (uint8_t)0x00 Clock to 0 when idle
SPI_CLOCKPHASE_1EDGE, // (uint8_t)0x00 The first clock transition is the first data capture edge
SPI_DATADIRECTION_2LINES_FULLDUPLEX, // (uint8_t)0x00 2-line uni-directional data mode enable
SPI_NSS_SOFT, // (uint8_t)0x02 Software slave management disabled ?! isn't it enabled, when it's called SOFT ?!
0x00); // CRCPolynomial
SPI_Cmd(ENABLE); // Enables or disables the SPI peripheral; parameter can be: ENABLE or DISABLE
}
void initMFRC522 ( void ) {
char result;
// 1. hard reset MFRC522:
result = resetHardMFRC522 ();
// 3. set timer to start automatically at the end of the TRANSMISSION:
result = writeMFRC522 ( TModeReg, 0x80 ); // TModeReg = (0x2A == 42):defines settings for the internal timer; 0x80 == 1000 0000 --> MSB = 1 => timer starts automatically at the end of the TRANSMISSION in all communication modes at all speeds
}
char resetHardMFRC522 () {
unsigned int i;
// 1. Pull reset line LOW:
GPIO_WriteLow(NRSTPD_PORT, NRSTPD_PIN);
// 2. keep reset line LOW for some time; reset timing requirements: min 100ns (page 34)
for ( i = 0; i < 1000; i++) // @ 16 Mhz: 1 clock cycle == 62,5 ns;
nop();
//// 1000 == 340 - 370 us, rectangular
// 3. Pull reset line HIGH:
GPIO_WriteHigh(NRSTPD_PORT, NRSTPD_PIN);
// 4. wait for a stable oscillator; oscillator start-up time is the start up time of the crystal + 37,74µs;
for ( i = 0; i < 60000; i++) // @ 16 Mhz: 1 clock cycle == 62,5 ns; 60000 == 22,4 ms
nop(); // ;
// 5. check for wake-up procedure end (hard Power-Down mode): bit 'PowerDown' 1 --> 0:
if ( ( readMFRC522( CommandReg ) & 0x10 ) ) // if 1 returned ; 0x10 == 0001 0000 (BIt 4: PowerDown)
return ERROR; // 1
return OK; // 0 => Bit 4 (PoweDown) of CommandReg is cleared => chip should be ready to work
}
char readMFRC522 ( unsigned char address ) { // 0x01
char dataMFRC522;
// 2. Before sending data, the user must pull low an SS signal to let the slave device know it is the recipient of the message:
GPIO_WriteLow(NSS_PORT, NSS_PIN); // pull NSS line LOW before start of SPI communication
// 3. wait while SPI is busy communicating, exit when any ongoing SPI transfer has finished
while(SPI_GetFlagStatus(SPI_FLAG_BSY)) // RESET(0) or SET(1)
;
// 4. send serially the address to MFRC522 with MSB set (R mode) & LSB cleared:
SPI_SendData( ( ( address << 1 ) & 0x7E ) | 0x80 ); // highest MFRC522 address is 0x3f == 0011 1111; 0x82 == 1000 0010
// 5. check that the address is indeed sent:
while( ! SPI_GetFlagStatus(SPI_FLAG_TXE) )
;
// 6. check that data has been received:
while( ! SPI_GetFlagStatus(SPI_FLAG_RXNE) ) // data received ?
;
// 7. read the value returned serially:
dataMFRC522 = SPI_ReceiveData(); // Returns the most recent received data by the SPI peripheral; resets SPI_FLAG_RXNE
// 8. wait while SPI is busy communicating, exit when SPI transfer has finished
while(SPI_GetFlagStatus(SPI_FLAG_BSY)) // RESET(0) or SET(1)
;
// 9. raise NSS line HIGH afer end of SPI communication:
GPIO_WriteHigh(NSS_PORT, NSS_PIN);
return dataMFRC522;
}
char writeMFRC522 ( unsigned char address, unsigned char value ) {
char addressMFRC522;
// 2. Before sending data, the user must pull low an SS signal to let the slave device know it is the recipient of the message:
GPIO_WriteLow(NSS_PORT, NSS_PIN); // pull NSS line LOW before start of SPI communication
// 3. wait while SPI is busy communicating, exit when any ongoing SPI transfer has finished
while(SPI_GetFlagStatus(SPI_FLAG_BSY)) // RESET(0) or SET(1)
;
// 4. send serially the address to MFRC522 with MSB cleared (W mode) & LSB cleared:
SPI_SendData( ( address << 1 ) & 0x7E ); // highest MFRC522 address is 0x3f == 0011 1111;
// 5. check that the address is indeed sent:
while( ! SPI_GetFlagStatus(SPI_FLAG_TXE) )
;
// 6. send serially the data to MFRC522:
SPI_SendData( value );
// 7. check that the data is indeed sent:
while(!SPI_GetFlagStatus(SPI_FLAG_TXE))
;
// 8. check that data has been received as reposnse to sent address:
while( ! SPI_GetFlagStatus(SPI_FLAG_RXNE) ) // data received ?
;
// 9. read the address returned serially:
addressMFRC522 = SPI_ReceiveData(); // Returns the most recent received data by the SPI peripheral; resets SPI_FLAG_RXNE
// 10. wait while SPI is busy communicating, exit when SPI transfer has finished:
while(SPI_GetFlagStatus(SPI_FLAG_BSY)) // RESET(0) or SET(1)
;
// 11. raise NSS line HIGH afer end of SPI communication
GPIO_WriteHigh(NSS_PORT, NSS_PIN);
if ( addressMFRC522 == address )
return OK; // 0
else
return ERROR; // 1
}
My problem arises within the call of the last function initMFRC522()
within my main()
function:
- when I only execute the first part,
resetHardMFRC522 ();
, then I get the expected reset value 0x20 for the CommandRegister of the MFRC522 after the hard reset; - however, when I execute also the second part, the function
result = writeMFRC522 ( TModeReg, 0x80 );
, then I don't get the expected reset value 0x20 for the CommandRegister of the MFRC522 after the hard reset, plus I don't receive the same address returned as the one I write to (as per MFRC522 protocol). I cannot figure out where my problem is ? I checked the SPI settings, but the setting of the clock polarity and phase seem to be the correct ones. The pin input/output directions also seem to be correct. I guess I am misusing / overusing / not using correctly the SPI flags or I don't have the correct command sequence while reading/writing, but I could not figure out where my mistake is from reading the datasheet and looking at other people's code on the net ?
the issue was that, when trying to read a register value, I need to send a 2nd dummy byte via MOSI after the register address itself because the 1st byte returned is a non-sense byte and the real register content is returned as a 2nd byte which requires a CLK signal (which itself is generated by the 2nd dummy byte sent). That was different in comparison to using UART but I got some help interpreting the datasheet :-)