Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add driver for sd-cards (using spi) #6031

Merged
merged 1 commit into from Jan 24, 2017

Conversation

MichelRottleuthner
Copy link
Contributor

This PR adds a driver to read/write sd-cards over spi. Also included is an interactive test application (tests/sdcard_spi) to perform some simple checks with various cards (init card, read/write/copy blocks, show CID and CSR card information).
This driver has been successfully tested with multiple micro-sd cards:
1GB SDSC Kingston
2GB SDSC SanDisk
16GB SDHC SanDisk Ultra
32GB SDHC SanDisk Ultra
32GB SDHC Samsung Evo
128GB SDXC SanDisk Ultra
For now i have only tried this on the samr21-xpro board, but since no board specific features are used it should also work with other platforms.

@MichelRottleuthner MichelRottleuthner changed the title Sdcard spi testing add driver for sd-cards (using spi) Oct 31, 2016
@cgundogan cgundogan added Type: new feature The issue requests / The PR implemements a new feature for RIOT Area: drivers Area: Device drivers labels Oct 31, 2016
@aabadie
Copy link
Contributor

aabadie commented Oct 31, 2016

I guess you tested with a Samr21-xpro using the I/O Xplained extension ?

@MichelRottleuthner
Copy link
Contributor Author

MichelRottleuthner commented Oct 31, 2016

I guess you tested with a Samr21-xpro using the I/O Xplained extension ?

Actually not, never heard of that before. I used a thing you could call "DIY micro-sd to breadboard adapter" which is basically one of those micro-sd to sd adapters with some pins soldered onto it :P
Fits perfectly on a breadboard and can be directly connected to whatever board you want. The only thing that needs to be added is a pull up resistor to the cards DO (MISO) pin.

@cgundogan
Copy link
Member

nice to see this new driver. I look forward to testing this soonish (: @MichelRottleuthner did you try to use the remote with an onboard sd slot?

@MichelRottleuthner
Copy link
Contributor Author

@cgundogan no - but i will try that tomorrow if you hand me one of the remote boards ;) Apart from that, do we have one of those extension boards mentioned by @aabadie lying around?

@aabadie
Copy link
Contributor

aabadie commented Nov 1, 2016

Apart from that, do we have one of those extension boards mentioned by @aabadie lying around?

I have some in my office ;) Otherwise you can access those extension on the IoT-LAB Saclay site on nodes called custom with ids from 1 to 8. Those nodes are samr21 boards with the I/O1 Xplained extension plugged on it. See this page for more information.

Copy link
Member

@miri64 miri64 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a full review, just some stuff I picked up while looking at the code.


/* see sd spec. 5.3.2 CSD Register (CSD Version 1.0) */
struct {
uint8_t CSD_STRUCTURE : 2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we just discuss on the mailing list that bitfields are bad ROM-wise? Also: the distribution of the bits makes no sense (2 byte of uint8_t, then directly after 8 byte of uint8_t => 6 bytes wasted) so this will most likely waste a lot of RAM, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(okay sorry, the spec says the 6 bytes are reserved)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely agree on the point that the ordering doesn't make sence. This just represents the ordering of the values in the sd-spec and comes from monkey-like typing them to the header file ;) I'm not quite sure about the general desicion whether the use of bitfields is bad or good in this case. I have read the discussion (thanks for the hint) and will look at the register stuff again with this in mind. Nonetheless imho trading a few bytes of ROM for better usability may be worth it, especially if the effect causes both more, or less usage of ROM depending on the platform.

struct {
uint8_t MID; /* Manufacturer ID */
char OID[2]; /* OEM/Application ID*/
char PNM[5]; /* Product name */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please adhere to coding conventions

@@ -0,0 +1,359 @@
/*
* Copyright (C) 2016
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyrighted by whom?

#define R1_HAS_CMD_CRC_ERR(X) ((((X) &SD_R1_RESPONSE_CMD_CRC_ERROR) != 0))
#define R1_HAS_ILL_CMD_ERR(X) ((((X) &SD_R1_RESPONSE_ILLEGAL_CMD_ERROR) != 0))
#define IS_R1_IDLE_BIT_SET(X) (((X) &SD_R1_RESPONSE_IN_IDLE_STATE) != 0)
#define R1_HAS_ERROR(X) (R1_HAS_PARAM_ERR(X) || R1_HAS_ADDR_ERR(X) || R1_HAS_ERASE_ERR(X) || R1_HAS_CMD_CRC_ERR(X) || R1_HAS_ILL_CMD_ERR(X))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep the line length to max 80 characters (100 if it absolutely doesn't go any other way)

@OlegHahm OlegHahm added this to the Release 2017.01 milestone Nov 1, 2016
@smlng
Copy link
Member

smlng commented Nov 1, 2016

@MichelRottleuthner: I'm volunteer for helping you getting this work on a remote-revb. Nice work!

@alignan
Copy link
Contributor

alignan commented Nov 3, 2016

@smlng I will update the RE-Mote wiki shortly with instruction on how to enable the Micro-SD for the current revision B batch, as resistors enabling the microSD holder were not mounted at the production line (fiex for the next batch of RE-Motes)

@alignan
Copy link
Contributor

alignan commented Nov 3, 2016

Instruction on enabling the Micro-SD on RE-Motes revision B Here (for the ones that left without the resistors that should been mounted as default)

@MichelRottleuthner
Copy link
Contributor Author

MichelRottleuthner commented Nov 4, 2016

Can confirm this driver as working on the RE-Mote revB.
changes needed to make it work:

  1. Add the both solder bridges as shown in the Instructions above (thanks for that @alignan )
  2. change Makefile to TEST_SDCARD_CS ?= GPIO_PA7 (maybe later this can be added to board.h?)
  3. change spi.c to IOC_PXX_OVER[spi_config[dev].miso_pin] = IOC_OVERRIDE_PUE;
    To enable the internal pull-up on the MISO pin. To avoid changing the SPI implementation it would be nice to do this via a explicit gpio_init call in the driver implementation, but for now it seems there is no platform independent way to get the specific miso-pin of the Makefile-selected SPI-dev(?).
  4. config GPIO_PA6 to output and set it to low before performing the init method of the sd-card
    If you are using the provided driver test
    gpio_init(GPIO_PA6, GPIO_OUT);
    gpio_clear(GPIO_PA6);
    can be added to main before shell_run.
    EDIT: The feature described in the revb schematic for auto-powering the sd-card if the PA6 (SL_MSD) pin is configured as input when a card is inserted did NOT work, and the connected mosfet only provided ~1V to the card if the pin was not explicitly configured as output and driven low. Not sure what's the cause for this.

@alignan
Copy link
Contributor

alignan commented Nov 4, 2016

Nice! I will test ASAP.

To avoid the 1V you need to configure the SPI lines as output/low, we will test and report if this works as expected as well.

Thanks!

@alignan
Copy link
Contributor

alignan commented Nov 4, 2016

What compiler version or toolchain are you using? I had to change the bool types as not being supported in my setup (arm-none-eabi-gcc 5.4.1).

@alignan
Copy link
Contributor

alignan commented Nov 4, 2016

It is working on the RE-Mote revision B:

> size
size
Card size: 7892631552 bytes (7,350 GiB | 7,892 GB)

@MichelRottleuthner
Copy link
Contributor Author

..had to change the bool types..

I'm using 4.9.2 but the missing bool is because I forgot to include stdbool.h. I didn't see that before because compiler didn't complain on samr21x-pro. Will provide a fix for that! thanks for testing!

To avoid the 1V you need to configure the SPI lines as output/low, we will test and report if this works as expected as well.

That's what I wanted to explain by the edit above, driving it low works indeed ;)

Copy link
Member

@smlng smlng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good in general. However some coding style and code improvements possible 😄


for (int i = 0; i < chunk_blocks * SD_HC_BLOCK_SIZE; i++) {

if (((i % SD_HC_BLOCK_SIZE) == 0)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove outer pair of parentheses, one should do


puts("insert SD-card and use 'init' command to set card to spi mode");
puts("WARNING: using 'write' or 'copy' commands WILL overwrite data on your sd-card and\n\
almost for sure corrupt existing filesystems, partitions and contained data!");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add separate puts and indent

printf("read error %d (block %d)\n", rd_state, src_block);
return -1;
}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else not needed, because return -1 above

printf("write error %d (block %d)\n", wr_state, dst_block);
return -1;
}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again else not needed here

int src_block;
int dst_block;

if (argc == 3) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert statement into:

if (argc != 3) {
    printf("usage: %s src_block dst_block\n", argv[0]);
    return -1;
}

src_block = (uint32_t)atoi(argv[1]);
dst_block = (uint32_t)atoi(argv[2]);

xtimer_init();
return SD_INIT_SPI_POWER_SEQ;
}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else not needed due to return above

gpio_set(card->cs_pin); /* unselect sdcard for power up sequence */

/* powersequence: perform at least 74 clockcycles with mosi_pin being high
(same as sending dummy bytes with 0xFF) */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

second line of comment should start with aligned *, too

_dyn_spi_transfer_byte = &_hw_spi_transfer_byte;
return SD_INIT_ENABLE_CRC;
}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else not needed, again


char sdcard_spi_send_cmd(sd_card_t *card, char sd_cmd_idx, uint32_t argument, int max_retry)
{

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove empty line


}
else {
DEBUG("_read_data_packet: _transfer_bytes [RX_TX_ERROR] (while transmitting crc)\n");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same error message as below, remove here completely and remove else below, so it will hit the message in both cases?

@haukepetersen
Copy link
Contributor

@MichelRottleuthner nice work! IMHO for the clarity of the interface, I think it would make much sense to split the header file, one (i.e. drivers/include/sdcard_spi.h) containing the actual 'user interface', and one (i.e. drivers/sdcard_spi/include/sdcar_spi_internal.h containing the functions and data structures that are used only internally.

@haukepetersen
Copy link
Contributor

Just tried to run this using the pba-d-01-kw2x and a custom shield, and a 32GB Samsung sd-card: Init fails after some timeout time. When enabling debug output, I get
_wait_for_r1: r1=0xff about every 2ms, and in some intervals (~5s?) I get:

2016-12-19 15:59:21,636 - INFO # _wait_for_r1: [TIMEOUT]
2016-12-19 15:59:21,639 - INFO # sdcard_spi_send_cmd: R1_TIMEOUT (0xff)
2016-12-19 15:59:21,643 - INFO # sdcard_spi_send_cmd: CMD59 (0x00000001) (retry 2)
2016-12-19 15:59:21,645 - INFO # _wait_for_not_busy: [OK]
2016-12-19 15:59:21,650 - INFO # CMD59 echo: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 
2016-12-19 15:59:21,652 - INFO # _wait_for_r1: r1=0xff

before getting back to the _wait_for_r1: r1=0xff.

@MichelRottleuthner: Have you any hint, what this could be about?

@haukepetersen
Copy link
Contributor

Connected the same shield to an samr21-xpro, now I get

2016-12-19 16:10:24,656 - INFO # > init
2016-12-19 16:10:24,660 - INFO # Initializing SD-card at SPI_1...SD_INIT_START
2016-12-19 16:10:24,661 - INFO # gpio_init(): [OK]
2016-12-19 16:10:24,663 - INFO # SD_INIT_SPI_POWER_SEQ
2016-12-19 16:10:24,668 - INFO # SD_INIT_SEND_CMD0
2016-12-19 16:10:24,672 - INFO # sdcard_spi_send_cmd: CMD00 (0x00000000) (retry 0)
2016-12-19 16:10:24,675 - INFO # _wait_for_not_busy: [OK]
2016-12-19 16:10:24,681 - INFO # CMD00 echo: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 
2016-12-19 16:10:24,683 - INFO # _wait_for_r1: r1=0xff
2016-12-19 16:10:24,685 - INFO # _wait_for_r1: r1=0x01
2016-12-19 16:10:24,687 - INFO # _wait_for_r1: R1_VALID
2016-12-19 16:10:24,688 - INFO # CMD0: [OK]
2016-12-19 16:10:24,690 - INFO # spi_init_master(): [OK]
2016-12-19 16:10:24,692 - INFO # SD_INIT_ENABLE_CRC
2016-12-19 16:10:24,696 - INFO # sdcard_spi_send_cmd: CMD59 (0x00000001) (retry 0)
2016-12-19 16:10:24,699 - INFO # _wait_for_not_busy: [BUSY]
2016-12-19 16:10:24,701 - INFO # _wait_for_not_busy: [OK]
2016-12-19 16:10:24,705 - INFO # CMD59 echo: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 
2016-12-19 16:10:24,707 - INFO # _wait_for_r1: r1=0xff
2016-12-19 16:10:24,709 - INFO # _wait_for_r1: r1=0x01
2016-12-19 16:10:24,711 - INFO # _wait_for_r1: R1_VALID
2016-12-19 16:10:24,712 - INFO # CMD59: [OK]
2016-12-19 16:10:24,714 - INFO # SD_INIT_SEND_CMD8
2016-12-19 16:10:24,718 - INFO # sdcard_spi_send_cmd: CMD08 (0x000001b5) (retry 0)
2016-12-19 16:10:24,720 - INFO # _wait_for_not_busy: [OK]
2016-12-19 16:10:24,724 - INFO # CMD08 echo: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 
2016-12-19 16:10:24,726 - INFO # _wait_for_r1: r1=0xff
2016-12-19 16:10:24,728 - INFO # _wait_for_r1: r1=0x00
2016-12-19 16:10:24,730 - INFO # _wait_for_r1: R1_VALID
2016-12-19 16:10:24,734 - INFO # CMD8: [OK] --> reading remaining bytes for R7
2016-12-19 16:10:24,737 - INFO # R7 response: 0x00 0x00 0x02 0x9f
2016-12-19 16:10:24,739 - INFO # CMD8: [R7 MISMATCH]
2016-12-19 16:10:24,741 - INFO # SD_INIT_CARD_UNKNOWN
2016-12-19 16:10:24,742 - INFO # [FAILED]
2016-12-19 16:10:24,747 - INFO # enable debugging in sdcard_spi.c for further information!

@MichelRottleuthner
Copy link
Contributor Author

MichelRottleuthner commented Dec 19, 2016

@haukepetersen
Thanks for trying it out :)
The first thing about the pba-d-01-kw2x looks like the card hasn't enough time to complete the request to enable CRC. Try playing with the retry counts in sdcard_spi.h, for this case the R1_POLLING_RETRY_CNT should be the right value to alter.
Also you might want to try disabling the debug output for the actual values of R1 (ie. DEBUG("_wait_for_r1: r1=0x%02x\n", r1); in _wait_for_r1) to speed things up a bit while debugging.

The second log looks suspicious to me. The lower 12 Bits of R7 response should match the argument that is sent with CMD8, which clearly isn't the case here (01b5 vs. 029f). This could indicate there is a problem with the specified voltage, or a communication error - which is more likely imho because the last byte of R7 doesn't even match the b5 that should simply be echoed.
The first thing I would try to ensure is sufficient power supply. Some cards I tested were way more power hungry than others and sometimes that lead to behaviour like this.

Can you give me more details about the card used?

@MichelRottleuthner
Copy link
Contributor Author

@haukepetersen I just tested init on pba-d-01-kw2x with a few cards I have lying around here (one of them is a 32GB microSDHC Samsung EVO MB-MP32D) but I could not reproduce this behaviour. Look at the newly added SDCARD_SPI_CONFIGS in the tests Makefile to see which pins I used.

@haukepetersen
Copy link
Contributor

Thanks for the replies. My card here is a Samsung 32 Evo+ - MB-MC32D. Will try out your suggestions and see where I end up.

@MichelRottleuthner
Copy link
Contributor Author

@haukepetersen I finally got my hands on one of the pba-d-01-kw2x shields with msd slot and can confirm that it doesn't work. It looks like the MISO and MOSI pins are swapped (compared to the boards periph_conf.h). I double checked it and the shield works fine when connected with jumper wires that swap both pins.

@haukepetersen
Copy link
Contributor

now finally: using an arduino-due with my Ethernet shield (which also contains a micro-SD card slot), I was able to successfully interact with the SD-card. I used this config (so I don't forget...):

  SDCARD_SPI_CONFIGS ?= { { .spi_dev = SPI_0, \
                            .cs_pin = GPIO_PIN(0,29), \
                            .clk_pin = GPIO_PIN(0,27), \
                            .mosi_pin = GPIO_PIN(0,26), \
                            .miso_pin = GPIO_PIN(0,25), \
                            .power_pin = GPIO_UNDEF } }

@haukepetersen
Copy link
Contributor

now I don't know what the problems with my other setup's were, I would say let's get this PR in a mergable condition and get it into master.

So my main issue is still the separation of user interface and internal functionality: Could you give it a try to improve this?! I will give some suggestions as comments in the code below.


#ifndef NUM_OF_SD_CARDS
#define NUM_OF_SD_CARDS 1
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather have this driver following the same paradigm as our other device drivers for defining parameters and deducting the the number of defined devices:

  • have a default parameter configuration in drivers/sdcard_spi/include/sdcard_spi_params.h
  • use auto_init (as e.g. sys/auto_init/netif/auto_init_w5100.c, of course ommiting the gnrc specific parts)
  • define the device descriptor sdcard_spi_t and a params struct sdcard_spi_params_t in this header file
  • have an init function, that takes exactly those two prameters: int sdcard_init(sdcard_spi_t *dev, sdcard_spi_params_t *params);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haukepetersen
Copy link
Contributor

Oh, and forget about the comment above about the interface saparation -> didn't see that you already addressed that... sorry.

#define ASCII_UNPRINTABLE_REPLACEMENT "."

/* this is provided by the sdcard_spi driver
* see /home/michel/Projekte/fatfs/RIOT/sys/auto_init/storage/auto_init_sdcard_spi.c */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this path might be hard to follow for people that don't have access to your computer :-)

int csd_structure;
cid_t cid;
csd_t csd;
} typedef sdcard_spi_t;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here and above: you need to comment every struct member, otherwise Murdock will complain...

* @name Onboard micro-sd slot pin definitions
* @{
*/
#define SDCARD_SPI_PARAM_SPI SPI_1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be SPI_DEV(1)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ups, never mind, SPI_1 is just fine here, the SPI_DEV() define is not yet merged...

* @{
*/
#define SDCARD_SPI_PARAM_SPI SPI_1
#define SDCARD_SPI_PARAM_CS GPIO_PA7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here and below: better use the RIOT global GPIO defines -> GPIO_PIN(0, 7)

#define SDCARD_SPI_PARAM_MISO (GPIO_PIN(2,7))
#endif
#ifndef SDCARD_SPI_PARAM_POWER
#define SDCARD_SPI_PARAM_POWER (GPIO_UNDEF)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pickey, but the indention is off. Its somehow unaligned to 4-space boundaries..

@haukepetersen
Copy link
Contributor

re-confirmed working on the arduino-due. Please address the (minor) comments above and we are IMHO ready to go! Nice job!

@haukepetersen
Copy link
Contributor

Oh, and feel free to squash!

@MichelRottleuthner
Copy link
Contributor Author

rebased and squashed

@haukepetersen haukepetersen added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Jan 18, 2017
@haukepetersen
Copy link
Contributor

perfect, then let's see what Murdock has to say about this.

@haukepetersen
Copy link
Contributor

ERROR: The following new files generate doxygen warnings: drivers/include/sdcard_spi.h drivers/sdcard_spi/include/sdcard_spi_internal.h

Murdock complains about missing doc in the file stated above, and there is some trouble with printf formatting on msp and avr8 platforms...

@cgundogan
Copy link
Member

requires another rebase

#define SD_CARD_WAIT_AFTER_POWER_UP_US 1000

/* R1 response bits (see sd spec. 7.3.2.1 Format R1) */
#define SD_R1_RESPONSE_PARAM_ERROR 0b01000000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate to be nitty (: but binary literals are GCC Extensions. I would vote for replacing them with hex numbers or with shifts+ors

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@haukepetersen
Copy link
Contributor

@MichelRottleuthner you think you can get the rebase and Murdock fixes done today? Would be nice to merge this before feature freeze...

@MichelRottleuthner
Copy link
Contributor Author

Should be possible :)

@PeterKietzmann
Copy link
Member

@MichelRottleuthner please squash so everything is ready for merging (once CI has passed)

@MichelRottleuthner
Copy link
Contributor Author

squashed

@PeterKietzmann
Copy link
Member

@smlng, @haukepetersen Murdock and Jenkins are finally successful. Please remove your dismisses and approve this PR ASAP!

@haukepetersen
Copy link
Contributor

ACK and go

@haukepetersen haukepetersen merged commit 5d8f686 into RIOT-OS:master Jan 24, 2017
@kYc0o
Copy link
Contributor

kYc0o commented Jan 24, 2017

@aabadie it would be possible to include this to the Xplained extension for samr21?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: drivers Area: Device drivers CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants