SdFat on GitHub

Status
Not open for further replies.
When using ChibiOS, is there any library that supports multiple threads accessing a SD card?

ChibiOS is distributed with FatFS, the most used library for SD access in open source RTOSs. FatFS does not deal with sharing. You can not properly protect file access by implementing sharing at that level.

One solution is to use an I/O request queue and run file access in a thread. The RTOS queue handles sequentialization. A thread puts an I/O request in the queue and sleeps until it is done. That's how Windows, Linux, and most other OSes work.

For simple apps, you just put the Mutex around file access code in each of the two threads.

Here is a nice page about sharing http://www.chibios.org/dokuwiki/doku.php?id=chibios:guides:mutual_exclusion_guide

From the article :
Another method is to make a single dedicated thread execute the critical code and make it work as a messages server. The other threads can request the service to the server by sending a properly formatted message and then wait for the answer with the result.
This method is very useful when integrating into the system components not designed to be reentrant or to be executed in a multithreaded environment, for example a 3rd part file system or a networking protocol stack.

Giovanni is Italian so his English has an accent.
 
Last edited:
Amazing, I tried the ChibiOS/RT blink example on Teensy 3.1. It compiled and ran with 1.20RC2.

Code:
// Example to demonstrate thread definition, semaphores, and thread sleep.
#include <ChibiOS_ARM.h>

// The LED is attached to pin 13 on Arduino.
const uint8_t LED_PIN = 13;

// Declare a semaphore with an inital counter value of zero.
SEMAPHORE_DECL(sem, 0);
//------------------------------------------------------------------------------
// Thread 1, turn the LED off when signalled by thread 2.

// 64 byte stack beyond task switch and interrupt needs
static WORKING_AREA(waThread1, 64);

static msg_t Thread1(void *arg) {

  while (!chThdShouldTerminate()) {
    // Wait for signal from thread 2.
    chSemWait(&sem);

    // Turn LED off.
    digitalWrite(LED_PIN, LOW);
  }
  return 0;
}
//------------------------------------------------------------------------------
// Thread 2, turn the LED on and signal thread 1 to turn the LED off.

// 64 byte stack beyond task switch and interrupt needs
static WORKING_AREA(waThread2, 64);

static msg_t Thread2(void *arg) {
  pinMode(LED_PIN, OUTPUT);
  while (1) {
    digitalWrite(LED_PIN, HIGH);

    // Sleep for 200 milliseconds.
    chThdSleepMilliseconds(200);

    // Signal thread 1 to turn LED off.
    chSemSignal(&sem);

    // Sleep for 200 milliseconds.
    chThdSleepMilliseconds(200);
  }
  return 0;  
}
//------------------------------------------------------------------------------
void setup() {

  chBegin(chSetup);
  // chBegin never returns, main thread continues with mainThread()
  while(1) {
  }
}
//------------------------------------------------------------------------------
// main thread runs at NORMALPRIO
void chSetup() {

  // start blink thread
  chThdCreateStatic(waThread1, sizeof(waThread1),
    NORMALPRIO + 2, Thread1, NULL);

  chThdCreateStatic(waThread2, sizeof(waThread2),
    NORMALPRIO + 1, Thread2, NULL);

}
//------------------------------------------------------------------------------
void loop() {
  // not used
}

Binary sketch size: 11,804 bytes (of a 262,144 byte maximum)

It's bigger than Nil but it is a fine full featured RTOS.
 
I ran the context switch example. Here is the code and a chart of the LED pin. I expect about 2 usec response time to switching from an ISR to a high priority thread would work fine for a touch screen.

Code:
// Connect a scope to pin 13
// Measure difference in time between first pulse with no context switch
// and second pulse started in thread 2 and ended in thread 1.
// Difference should be about 15-16 usec on a 16 MHz 328 Arduino.
#include <ChibiOS_ARM.h>

const uint8_t LED_PIN = 13;

// Semaphore to trigger context switch
Semaphore sem;
//------------------------------------------------------------------------------
// thread 1 - high priority thread to set pin low
// 64 byte stack beyond task switch and interrupt needs
static WORKING_AREA(waThread1, 64);

static msg_t Thread1(void *arg) {

  while (TRUE) {
    chSemWait(&sem);
    digitalWrite(LED_PIN, LOW);
  }
  return 0;
}
//------------------------------------------------------------------------------
// thread 2 - lower priority thread to toggle LED and trigger thread 1
// 64 byte stack beyond task switch and interrupt needs
static WORKING_AREA(waThread2, 64);

static msg_t Thread2(void *arg) {
  pinMode(LED_PIN, OUTPUT);
  while (TRUE) {
    // first pulse to get time with no context switch
    digitalWrite(LED_PIN, HIGH);
    digitalWrite(LED_PIN, LOW);
    // start second pulse
    digitalWrite(LED_PIN, HIGH);
    // trigger context switch for task that ends pulse
    chSemSignal(&sem);
    // sleep until next tick (1024 microseconds tick on Arduino)
    chThdSleep(1);
  }
  return 0;
}
//------------------------------------------------------------------------------
void setup() {
  chBegin(chSetup);
  while (1) {}
}
//------------------------------------------------------------------------------
void chSetup() {
  // initialize semaphore
  chSemInit(&sem, 0);

  // start high priority thread
  chThdCreateStatic(waThread1, sizeof(waThread1),
    NORMALPRIO+2, Thread1, NULL);

  // start lower priority thread
  chThdCreateStatic(waThread2, sizeof(waThread2),
    NORMALPRIO+1, Thread2, NULL);
}
//------------------------------------------------------------------------------
void loop() {
  // not used
}

Teensy31.png
 
Last edited:
I did a more realistic example with attachInterrupt(). The response time is 4 usec.

Code:
// Example of how a handler task can be triggered from an ISR
// by using a binary semaphore.
#include <ChibiOS_ARM.h>

// pins to generate interrupts - these pins must be connected with a wire
const uint8_t INPUT_PIN = 2;
const uint8_t OUTPUT_PIN = 3;

// initialize semaphore as taken
BSEMAPHORE_DECL(isrSem, 1);

// ISR entry time
volatile uint32_t tIsr = 0;
//------------------------------------------------------------------------------
// Fake ISR, normally
// void isrFcn() {
// would be replaced by somthing like
// CH_IRQ_HANDLER(INT0_vect) {
//
void isrFcn() {

  // on ARM CH_IRQ_PROLOGUE is void
  CH_IRQ_PROLOGUE();
  /* IRQ handling code, preemptable if the architecture supports it.*/

  // Only ISR processing is to save time
  tIsr = micros();

  chSysLockFromIsr();
  /* Invocation of some I-Class system APIs, never preemptable.*/

  // signal handler task
  chBSemSignalI(&isrSem);
  chSysUnlockFromIsr();

  /* More IRQ handling code, again preemptable.*/

  // Perform rescheduling if required.
  CH_IRQ_EPILOGUE();
}
//------------------------------------------------------------------------------
// handler task for interrupt
static WORKING_AREA(waThd1, 200);

msg_t handler(void *arg) {
  while (1) {
    // wait for event
    chBSemWait(&isrSem);
    
    // print elapsed time
    uint32_t t = micros();
    Serial.print("Handler: ");
    Serial.println(t - tIsr);
  }
}
//------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  while (!Serial) {}
  
  // setup and check pins
  pinMode(INPUT_PIN, INPUT);
  pinMode(OUTPUT_PIN, OUTPUT);
  digitalWrite(OUTPUT_PIN, HIGH);
  bool shouldBeTrue = digitalRead(INPUT_PIN);
  digitalWrite(OUTPUT_PIN, LOW);
  if (digitalRead(INPUT_PIN) || !shouldBeTrue) {
    Serial.print("pin ");
    Serial.print(INPUT_PIN);
    Serial.print(" must be connected to pin ");
    Serial.println(OUTPUT_PIN);
    while (1) {}
  }

  // Start ChibiOS
  chBegin(mainThread);
  while (1) {}
}
//------------------------------------------------------------------------------
void mainThread() {
  // start handler task
  chThdCreateStatic(waThd1, sizeof(waThd1), NORMALPRIO + 1, handler, NULL);
  
  // attach interrupt function
  attachInterrupt(INPUT_PIN, isrFcn, RISING);
  while (1) {
    // cause an interrupt - normally done by external event
    Serial.println("High");
    digitalWrite(OUTPUT_PIN, HIGH);
    Serial.println("Low");
    digitalWrite(OUTPUT_PIN, LOW);
    Serial.println();
    chThdSleepMilliseconds(1000);
  }
}
//------------------------------------------------------------------------------
void loop() {
  // not used
}

Output:
High
Handler: 4
Low

Sadly, it's complicated on ARM with priority interrupts. An ISR can be preempted by a higher priority interrupt and the RTOS must work correctly.

The job for Arduino is to select the proper foundation of concurrent processing and package this stuff so libraries and features are user friendly.
 
Last edited:
Bill, what version of ChibiOS would you recommend downloading to try it out? Go to Source Forge (http://sourceforge.net/projects/chibios/) Or do you have you have your own version that I should try? or should I get the stuff up on github? https://github.com/ChibiOS/ChibiOS-RT

FYI - I am involved... But mostly with other stuff particularly with simple robotics in particular mostly Hexapods and some quads. I have been very active with Lynxmotion and Basic Micro and am a moderator on their forums... I am also active up Trossen Robotics.

After I retired from programming due to my hands wearing out, and then not touching a computer for several years, I started having some fun working on these smaller systems. To me it is a lot of fun where you can and at times need to understand what the processor is actually doing. Won't go into details about who all I worked for, but got my MS in CS over 30 years ago and soon after that worked on a distributed OS for 286 and 386 processors, which was canceled, but during that time I did develop a protect mode kernel for the 286... Later worked on some other OS's, but mostly at higher levels...

More on track. While being active with Lynxmotion (before it sold to Robotshop), most of their projects were done in Basic using Basic Atom Pros by Basic Micro. Later on they decided for a variety of reasons that they wanted to move from the BAP to some other processor and finally decided on an Arduino 328 clone. Why? Because they are popular, It is where the market is/was. And while C is a lot less forgiving then Basic, the complexity could be reasonably managed. There are still many of them who are still uncomfortable with C/C++ and Arduino. Also looked at some linux boxes but this completely scared them off. However even while I helped them migrate to the Arduino, I knew that they would soon outgrow the Atmega 328, so I have been dabbling with many different systems, to see what might work out better (Mega, Due, Chipkit, Propeller, RPI, BBBk, ODroid...)

Personally I think the Arduino with the Atmega processors is/was fine for what it was. Problem is that, we are now talking about much higher level of processors, with the Due, Teensy 3x, Chipkit Pic32..., these processors can do so much more and the Arduino stuff has major growing pains. So the project needs to evolve. But the real question to me, is how. Do you really want a full OS, with protected device drivers, that user level programs communicate with through queues... This may solve some of these issues, but you may just end up going to a small Linux...

So hopefully one of these simpler RTOS setups may work well. It looks promising, may try it on one of my Hexapods...

Again sorry if I am a bit off topic here.
 
I ported a version of ChibiOS/RT to Due and Teensy 3. Download ChibiOS20130710.zip here https://code.google.com/p/rtoslibs/downloads/list.

My version, 2.6.0, is a little out of date. The current stable version is 2.6.5.

I will soon port ChibiOS 3.0. It is a major upgrade but still in development.

You might also look at my port of FreeRTOS, FreeRTOS20130714.zip. I have not verified that is still runs on Teensy 3. FreeRTOS is very popular.

But the real question to me, is how. Do you really want a full OS, with protected device drivers, that user level programs communicate with through queues... This may solve some of these issues, but you may just end up going to a small Linux...

I think it is either a RTOS like FreeRTOS, ChibiOS/RT on Cortex M or Linux on a bigger processor.

I do like Nil on AVR Arduino.
 
Last edited:
I first tried your ChibiOS build you mentioned and tried the chBlink program, which compiled and downloaded and then the Teensy 3.1 chip would not respond. No blink, and my system did not show any valid com ports. I could hit the program button and it would reprogram with same result. So loaded up standard blink program, compiled and programmed (hit button) and it worked fine, the comm port came back... Tried ChiBiOS again same result.

So download FreeRTOS build you mentioned, loaded up the frBlink program and it compiled fine and the LED is blinking and valid Com port again... So it appears to be working...

Will have to continue to play...
 
Looks like I removed this code.

It is at about line 72 of ChibiOS_ARM.c
Code:
//------------------------------------------------------------------------------
/** fill heap on Teensy */
/*
void startup_early_hook() {
 uint32_t* end = &_estack - 100;
 uint32_t* p = &_ebss;
 while (p < end) *p++ = MEMORY_FILL_PATTERN;
}
*/

It originally was used for debug of stack problems but is not needed and causes a crash in recent versions of Teensy 3 software.
 
It reason is more general. You don't know why an application uses an interrupt. Maybe the interrupt was caused by a pin change and knowing the time of the event as accurately as possible is important. The SPI part may access a sensor where time is not as important.

I may be a bit lagged on this discussion, but I would just like to contribute a point here.

The situation described by Bill is EXACTLY one of the use cases that I have in my CanSat data logger. Right now, it uses the much slower I2C bus, but this is just because I used the simple GY-80 shield board - all of the sensors involved have an SPI interface as well, so, in principle the argument also applies for SPI.

Whenever one of my sensors (accel, magnet etc.) completes its measurement, it signals an IRQ. The ISR does nothing more than capturing the time of this event (using my own implementation of micros() with an FTM counter, as Teensy's default implementation is quite expensive, involving a lot of math).

As I'm not using any RTOS, my loop() function then queues an I2C transfer, which is handled by a slightly modified i2c_t3 library as soon as the I2C bus becomes available. I'd call this deferred interrupt handling.

And YES, it IS important. I need the accurate timestamp. 12 bit resolution for the measured acceleration is pretty pointless if you don't know exactly WHEN your measurement was taken (acceleration is m/s², so time influences much more than position). It is essential in order to be able to reconstruct trajectory data.

Please consider that typical rocket acceleration gets close to the limits of the sensor (+-16g) and, in order to get a trajectory (position vectors), we need to integrate acceleration data twice, so any error in time or sample data propagates rapidly. Right now, I'm sampling with 800 Hz and I still don't know if that is sufficient for water rockets, which typically have a very short acceleration phase of only 50-100 ms and then coast for like 5-6 seconds.

On the other hand, I've implemented the "deferred handler" without any OS support in the loop() function. That would be possible with the current SPI transaction code as well.

But if this serves as the specific real world example that Paul needs to convince the Arduino folks, here it is.
 
Last edited:
Looks like I removed this code.

It is at about line 72 of ChibiOS_ARM.c
Code:
void startup_early_hook() {
 uint32_t* end = &_estack - 100;
 uint32_t* p = &_ebss;
 while (p < end) *p++ = MEMORY_FILL_PATTERN;
}
*/

It originally was used for debug of stack problems but is not needed and causes a crash in recent versions of Teensy 3 software.

IMHO this is because in recent Teensyduino versions, startup_early_hook disables the Watchdog timer. This needs to be done early, right after unlocking the Watchdog, so Paul put in in startup_early_hook. If you provide your own implementation of startup_early_hook, you must remember to disable the watchdog or set its timeout before doing anything else. Otherwise it will kick in and reset the Teensy.

Actually, instead of putting it into startup_early_hook, it might be an idea to have a specific weak aliased watchdog setup function, but this might break compatibility...
 
IMHO this is because in recent Teensyduino versions, startup_early_hook disables the Watchdog timer.

Paul put startup_early_hook in a very early version of Teensy 3.0 when I thought I needed it. When I ported ChibiOS to Due, I moved most of the functionality to the chBegin() function. I left fill heap in Teensy but then the Watchdog timer change happened. Finally link symbols changed and now my fill heap function clobbers all memory.

I couldn't update the version on Google Code because they no longer allow downloads of zip files.

I will soon post ChibiOS updated to version 2.6.5 on GitHub.
 
I have the work done for updating ChibiOS to 2.6.5 and am now testing.

Here is a shocking comparison of Due with Teensy 3.1. I thought digitalWrite had been fixed on Due. digitalWrite takes as long or longer than a context switch on Due.

The width of the first pulse is the time for digitalWrite. The width of the second pulse is the time for digitalWrite and a context switch. The code is posted above.

Note the scale is 2 usec/div for Due and 1 usec/div for teensy. The Due trace has spikes because of a shield and wires on pin 13.

Due
Due.png

Teensy
Teensy31.png
 
Last edited:
At least a few people wanted to use the watchdog timer, but couldn't because it was being disabled before they could configure it. Freescale has a configure-only-once approach, so the old way was effectively locking people out from using the watchdog.

In hindsight, using 2 early hooks, one for watchdog setup and the other immediately afterward would probably have been a good idea. But at this point, I'm really going to resist any more changes. Unless there's some really serious problem, I'd prefer to avoid any more breakage of existing code.

On the scope traces, wow. I didn't realize Due's digitalWrite is so slow.

I must confess, I _still_ haven't done the const case optimization for digitalWrite and other functions, as I did on Teensy 2.0. It's planned, but less urgent than so many other things. When I get around to that, Teensy's digitalWrite will become dramatically faster for code like this, which uses compile-time constants.
 
At least a few people wanted to use the watchdog timer, but couldn't because it was being disabled before they could configure it. Freescale has a configure-only-once approach, so the old way was effectively locking people out from using the watchdog.

Yeah, I saw why you did it so I didn't post anything. Unfortunately I didn't get a fix on Google Code because of their new policy about zip downloads.

A newer 2.6.5 version is now on GitHub https://github.com/greiman/ChibiOS-Arduino
 
PaulStoffregen
Is ChibiOS GPLv3 licensed?

This is in the newest GitHub files.

Code:
/*
    ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
                 2011,2012,2013,2014 Giovanni Di Sirio.

    This file is part of ChibiOS/RT.

    ChibiOS/RT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    ChibiOS/RT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
 
Status
Not open for further replies.
Back
Top