How to correctly read data returned by MFRC522 via SPI using STM8S103F3? - rfid

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 :-)

Related

Mbed SPI Communication with Digital Sensor

I have a Mbed and a digital gyroscope sensor. I want to make sure two of them communications through SPI where Mbed (master) able to read the data from the gyroscope sensor (slave). Can anyone help out?
#include "mbed.h"
SPI spi(p5, p6, p7); // mosi, miso, sclk
DigitalOut cs(p8);
Serial pc(USBTX, USBRX);
int main() {
// Slave select disabled //
cs = 1;
// Setup the spi for 8 bit data, high steady state clock,
// Second edge capture, with a 1MHz clock rate
spi.format(8,3);
spi.frequency(500000);
// Slave select enabled, it is active low //
cs = 0;
// Send 0x8F, the command to read the WHO_AM_I register //
// 0x8F 1000 1111, MSB = 0 means write and MSB = 1 means read //
spi.write(0x8F);
// Send a dummy byte to receive the contents of the WHO_AM_I register //
// The default response 1101 0011 //
int whoami = spi.write(0x00);
pc.printf("WHOAMI register = 0x%X\n", whoami);
// Slave select disabled //
cs = 1;
while(1) {
cs = 0 ;
// 0x28 is the register for OUT_X_L of the gyroscope //
spi.write(0x28);
int data = spi.write(0x00);
pc.printf("data output 0x%X\n", data);
wait(0.5);
cs = 1;
}
}
A longshot answer. The gyro spec may specify a time between CS goes low and you can start sending data. Hence, try with a delay after cs = 0;
Secondly, why are you setting MSB when you intend to read WHO_AM_I register. MSB=1 is for writing and not reading.

why repeated start based i2c operation are not supported in linux?

I want to read from i2c slave which need multi start operation to read its register values.
As up-to some extent I have traced I2C driver in Linux kernel 3.18.21, I found it do not support multi start operation and I have no way to read from this I2C slave (Power Over Ethernet manager PD69104B1).
I am still finding the way I can extended driver if needed for this i2c slave or anything else needed.
I use i2c-tools 3.2.1.
I try to
$ i2cdump -y 0 0x20
but I can see same values which means it read first register every time.
$ i2cget -y 0 0x20 0x12
or any other register address returns the same value as a first register.
This slave support two read operation:
byte read - write address get its value but this need multi start
block read - start reading and i2c slave will give register values in sequence like 0x00 0x01.... (first register, second , third, fourth....etc)
I try all possible ways:
i2c_smbus_access()
i2c_smbus_write_byte()
i2c_smbus_read_block_data()
write()
read()
but then most of the time i2c bus goes into timeout error and hang situations.
Anyone has idea how to achieve this in Linux?
Update0:
This I2C slaves need unique Read cycles:
Change of Direction: S Addr Wr [A] RegAddress [A] S Addr Rd [A] [RegValue] P
Short Read: S Addr Rd [A] [RegValue] P
here last value returned from i2c slave do not expect ACK.
I tried to use I2C_M_NO_RD_ACK but with not much help. I read some value and then get FF.
This POE I2C slave have i2c time out of 14ms on SCL which is bit of doubt. This looks like i2c non standard as i2c can work on 0HZ i.e. SCL can be stretched by master as long as it want. Linux is definitely not real time OS so achieving this time out can not be guaranteed and i2c slave SCL timeout reset may happen. This is what my current conclusion is!
I2C Message notation used is from:
https://www.kernel.org/doc/Documentation/i2c/i2c-protocol
why repeated start based i2c operation are not supported in linux?
As a matter of fact, they are supported.
If you are looking for a way to perform repeated start condition in user-space, you probably need to do ioctl() with I2C_RDWR request, like it's described here (see last code snippet in original question) and here (code in question).
Below described the way to perform repeated start in kernel-space.
In Linux kernel I2C read operations with repeated start condition are performed by default for combined (write/read) messages.
Here is an example how to perform combined I2C transfer:
/**
* Read set of registers via I2C using "repeated start" condition.
*
* Two I2C messages are being sent by this function:
* 1. I2C write operation (write register address) with no STOP bit in the end
* 2. I2C read operation
*
* #client: I2C client structure
* #reg: register address (subaddress)
* #len: bytes count to read
* #buf: buffer which will contain read data
*
* Returns 0 on success or negative value on error.
*/
static int i2c_read_regs(struct i2c_client *client, u8 reg, u8 len, u8 *buf)
{
int ret;
struct i2c_msg msg[2] = {
{
.addr = client->addr,
.len = 1,
.buf = &reg,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = buf,
}
};
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0) {
dev_err(&client->dev, "I2C read failed\n");
return ret;
}
return 0;
}
To read just 1 byte (single register value) you can use next helper function:
/**
* Read one register via I2C using "repeated start" condition.
*
* #client: I2C client structure
* #reg: register address (subaddress)
* #val: variable to store read value
*
* Returns 0 on success or negative value on error.
*/
static int i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
return i2c_read_regs(client, reg, 1, val);
}
Below is the illustration for i2c_read_regs(client, reg, 1, val) call:
device address is client->addr
register address is reg
1 means that we want to read 1 byte of data (pink rectangle on picture)
read data will reside at val
NOTE: If your I2C controller (or its driver) doesn't support repeated starts in combined messages, you still can use bit-bang implementation of I2C, which is i2c-gpio driver.
If nothing works, you can try next as a last resort. For some reason I can't quite remember, in order to make repeated start work I was needed to add I2C_M_NOSTART to .flags of first message, like this:
struct i2c_msg msg[2] = {
{
.addr = client->addr,
.flags = I2C_M_NOSTART,
.len = 1,
.buf = &reg,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = buf,
}
};
As noted in Documentation/i2c/i2c-protocol:
If you set the I2C_M_NOSTART variable for the first partial message,
we do not generate Addr, but we do generate the startbit S.
References:
[1] I2C on STLinux

How to convert arduino's digital output in terms of frequency

I am using a analog-output sound sensor module where the output of the sensor module is connected to the arduino and can see arduino is doing Ato D conversion and displaying integers from range 0 to 1023.
But I need to calculate the frequency of the sound getting measure from the sensor.
so could you help me, hwo to calculate the frequecy from this Ato D converted values from arduino.
You don't really need to to ADC conversions do you? All you need to do is detect a rising edge on the input and then count those. Since your sensor will output low-high-low sequences, and since the Arduino will register HIGH as over a certain voltage, that should be adequate.
This code will measure up to around 200 kHz, from an input connected to digital pin 8 on the board:
// Input: Pin D8
volatile boolean first;
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;
// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect)
{
overflowCount++;
} // end of TIMER1_OVF_vect
ISR (TIMER1_CAPT_vect)
{
// grab counter value before it changes any more
unsigned int timer1CounterValue;
timer1CounterValue = ICR1; // see datasheet, page 117 (accessing 16-bit registers)
unsigned long overflowCopy = overflowCount;
// if just missed an overflow
if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
overflowCopy++;
// wait until we noticed last one
if (triggered)
return;
if (first)
{
startTime = (overflowCopy << 16) + timer1CounterValue;
first = false;
return;
}
finishTime = (overflowCopy << 16) + timer1CounterValue;
triggered = true;
TIMSK1 = 0; // no more interrupts for now
} // end of TIMER1_CAPT_vect
void prepareForInterrupts ()
{
noInterrupts (); // protected code
first = true;
triggered = false; // re-arm for next time
// reset Timer 1
TCCR1A = 0;
TCCR1B = 0;
TIFR1 = bit (ICF1) | bit (TOV1); // clear flags so we don't get a bogus interrupt
TCNT1 = 0; // Counter to zero
overflowCount = 0; // Therefore no overflows yet
// Timer 1 - counts clock pulses
TIMSK1 = bit (TOIE1) | bit (ICIE1); // interrupt on Timer 1 overflow and input capture
// start Timer 1, no prescaler
TCCR1B = bit (CS10) | bit (ICES1); // plus Input Capture Edge Select (rising on D8)
interrupts ();
} // end of prepareForInterrupts
void setup ()
{
Serial.begin(115200);
Serial.println("Frequency Counter");
// set up for interrupts
prepareForInterrupts ();
} // end of setup
void loop ()
{
// wait till we have a reading
if (!triggered)
return;
// period is elapsed time
unsigned long elapsedTime = finishTime - startTime;
// frequency is inverse of period, adjusted for clock period
float freq = F_CPU / float (elapsedTime); // each tick is 62.5 ns at 16 MHz
Serial.print ("Took: ");
Serial.print (elapsedTime);
Serial.print (" counts. ");
Serial.print ("Frequency: ");
Serial.print (freq);
Serial.println (" Hz. ");
// so we can read it
delay (500);
prepareForInterrupts ();
} // end of loop
More discussion and information at Timers and counters.
As I suggested you in the other thread, the best is to
filter
amplify
apply a threshold
measure the time between edges
Steps 1, 2, 3 can be performed in software but it is MUCH better to perform them in hardware. The fourth step is what Nick Gammon solution is about... But you have to first make steps 1,2,3 in HW otherwise you will receive a lot of "noisy" readings

Atmega8 microcontroller reading from bluetooth

I want to read a byte sent by Bluetooth to the Atmega8 to process it. I found this function online to receive a byte
uint8_t receiveByte()
{
// Wait until a byte has been received
while((UCSRA&(1<<RXC)) == 0);
// Return received data
return UDR;
}
but it doesn't work by say turning a led on if 'a' was sent, so when I changed it and enabled port c to HIGH just before the while loop, and turned it low after it, but port c never gone low - which means this loop is infinite.
so my question is how to fix it or how can I read a byte from Bluetooth module
its atmega8-16pu and I configured it as follows:
/** define the cpu clock frequency*/
#define F_CPU 8000000UL
and fuse = 0xD9C4, from this site http://www.engbedded.com/fusecalc/

Bluetooth data shown as 0X80 instead of 0X00

I have been using Bluetooth module (HC-05) with Atmega8(both A and L) microcontroller to transmit data to my Android device. In following code an 8-bit signed(or unsigned doesn't made any change) value is sent over bluetooth to be displayed on device, this value starts at 0X00 and is incremented in every iteration:
#define F_CPU 1000000
#define BAUD 9600
#define MYUBRR (F_CPU/16/BAUD-1)
#include <avr/io.h>
#include <util/delay.h>
int main (void)
{
uint8_t data = 0;
UBRRH = (MYUBRR >> 8); // setting higher bits of UBRR
UBRRL = MYUBRR; // setting lower bit of UBRR
UCSRB = (1 << TXEN); // transmit enable
UCSRC = ((1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0)); //URSEL=USART reg selection (R/W), UCSZ1 & UCSZ0 set equal to 011 that is 8 bit data size
while (1)
{
UDR=data; // loading data in USART Data register (8-bit) and it will be transmitted immidiately
while(!(UCSRA&(1<<UDRE))); // waiting till complete data sent and UDRE flag set
_delay_ms(200); // after some time
data++; // incrementing data
}
return 0;
}
On the android device end there is "Bluetooth spp Pro" app to display the recieved data on screen.
Following is the configuration of recieve mode (Data is displayed as Hex values):
The data recieved here should start at 0X00 and go up to 0XFF instead it starts at 0X80 and increments upto 0XFF in a very unfamiliar manner.
Referring above image. The pattern I observed here is that the tens place digit starts at 8 and units place change from 0 to F then in next loop again it becomes 9 and unit place change from 0 to F after that instead of incrementing (expected) tens place again goes to 8 and then in next cycle it again becomes 9, after these four cycles of two repetetive words tens place increments to A and units place change from 0 to F and later the strange tens place pattern reappears for A and B then for C and D and later for E and F at tens place.
So my concern is:
Why is the device showing 80 for 00, as it is correctly working for ones place why is it not working for tens place as expected???
Thanks!!!
Edit:
This problem is neither Android version nor device manufacturer specific.
Problem was with voltage levels. Operating the microcontroller circuit on 3.2V and Bluetooth module on 3.8V solved the problem and data is transmitted as expected. However I am unable to predict an explanation for this.
Please help.
It is observed clearly on varying the potentiometer of voltage regulator, when I keep it below 3.20V data is transmitted smooth, and as the voltage level crosses 3.20V the tens place of data starts getting corrupted up to the point of complete data corruption and output becomes constant data 0XFE at 3.8V.

Resources