Application design

ChibiOS public support forum for all topics not covered by a specific support forum.

Moderators: RoccoMarco, lbednarz, tfAteba

hazelnusse
Posts: 77
Joined: Thu May 24, 2012 8:01 am

Application design

Post by hazelnusse »

I am implementing a controller for a robotic bicycle on an STM32F107. I have designed the control law to stabilize the bicycle but need some help with the OS / timing side of things.

I would like my control loop to run at 200Hz and do the following:
    1) Gather sensor data (i2c sensors and onboard timers connected to encoders)
    2) Store the collected data (32 bytes) in a buffer or buffer(s).
    3) Compute the appropriate PWM for my two motors.

Additionally, I have a microSD card I would like to use to log the sensor data. I was thinking I would put the sample data in a 1024 or 2048 byte buffer and then when the buffer is full, wake up a thread that initiates a SD card write. Will SD card writes of buffers this size automatically use DMA? Should I use a raw byte array, or does ChibiOS provide a data structure designed for this purpose with some extra functionality? In addition to the sensor data, I might also want to have a log file to store when events are occurring. What sort of locking or resource management do I need to be aware of if I have two files open and they are both only being written to from one thread? The overall data collection rate isn't too high -- about 6400 bytes / second.

I have TIM2 available (TIM1, TIM3, TIM4, TIM5 are tied up with PWM and reading encoders) to use obtain a 5ms period. Should I be using this timer for the 5ms tick or is there a better/different timer? I was thinking that the interrupt handler for TIM2 could wake up a thread which would then initiate the data collection and computation of the PWM commands. The slowest part of this operation is the i2c collection of data (18 bytes @ 100kHz, around 1.4ms excluding bus/CPU overhead, though I can run the I2C clock a bit faster than 100kHz).

Also, I've already got code that configures the I2C sensors how I want them, should I just call this once I get to main but before I start any threads, or is it possible to put this code in the boardInit() function or somewhere else before main()? I'm not too clear exactly when the HAL initialization takes place, I presume my sensor configuration code needs to run after HAL has configured the I2C peripheral.

I know this is a ton of questions, but any thoughts or suggestions would be greatly appreciated.

Thanks,
Luke
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Application design

Post by Giovanni »

Hi,

There are several possible solutions for each of those questions.

Assuming that the I2C bus fast enough to collect data within the 200Hz timing the whole thing can be done in a single loop:

0) Initial PWM values.
1) Wait the next 200Hz timeline
2) Change PWM parameters using calculated PWM values.
3) Read I2C data.
4) Process data and change PWM values.
5) Repeat from one.

No need for threads nor buffering. The function chThdSleepUntil() can be useful in this scenario, see the article: http://www.chibios.org/dokuwiki/doku.ph ... :kb:timing

The above loop ensures a correct timing even if I2C access and processing do not always have the same execution time.

About timers, you don't need an HW timer for delays of 5mS, you can use a virtual timer for this.

Both the SDC and MMC_SPI drivers use DMA for transfers, probably you need to decouple the SD write with the data generation, this can be done using mailboxes (two of them, one to send buffers to the SD writer the other to return buffers to senders) and a dedicated thread as SD writer.

About the sequence of operations, boardInit() is meant for board related initializations so application code should not go there. In the main function use the following sequence:

1) Your initializations (no calls to HAL or Kernel except xxxInit() functions), interrupts are still disabled here, it is the place where HW and custom device drivers should be initialized.
2) halInit().
3) More initializations for code that depends on the HAL, still no calls to the kernel, interrupts still disabled.
4) chSysInit().
5) Everything else, the system is active, interrupts enabled.

Giovanni
hazelnusse
Posts: 77
Joined: Thu May 24, 2012 8:01 am

Re: Application design

Post by hazelnusse »

Giovanni,
Thank you for your detailed reply. I've tested the I2C bus and it is indeed fast enough to run within the 200Hz system timing that I'm shooting for. I read the timing article, that was very helpful. I will read about mailboxes next to get a handle on them.

Also, I'm using TIM3, TIM4, TIM5 configured to use encoders. I currently have code to configure the peripheral registers directly to get them set up properly. Looking at the GPTConfig documentation, it wasn't clear to me how I would use the HAL to make the equivalent configuration. Is there an example this type of thing somewhere? Should I even be using the HAL for this? Once the timer is configured and on, all my application needs is to read TIMx->CNT within my 5ms loop, there are no interrupts needed. It would be nice to have a uniform configuration though, so if it is possible to do with the HAL, I think I would like to.

Thanks,
Luke
User avatar
barthess
Posts: 861
Joined: Wed Dec 08, 2010 7:55 pm
Been thanked: 7 times

Re: Application design

Post by barthess »

hi hazelnusse
hazelnusse wrote:Will SD card writes of buffers this size automatically use DMA?

Yes it will. You just need to call writing function. Look at documentation for FAT driver here http://elm-chan.org/fsw/ff/00index_e.html
hazelnusse wrote: Should I use a raw byte array, or does ChibiOS provide a data structure designed for this purpose with some extra functionality?

As Giovanni says, there is no need of buffer, but I strongly recommend you to use buffer at least 512 bytes. Because writing of 1 byte and writing of couple of pages takes almost the same amount of time. Also I recommend you to use dedicated thread for serving SD.
hazelnusse wrote:What sort of locking or resource management do I need to be aware of if I have two files open and they are both only being written to from one thread?

You do not need locking if writing performs from a single thread. Do not forget to tweak ffconf.h for you needs.
hazelnusse
Posts: 77
Joined: Thu May 24, 2012 8:01 am

Re: Application design

Post by hazelnusse »

Barthess,
Thank you for your reply. I am thinking that what I can do is have one thread that collects the data and copies it to a buffer and also computes the PWM command. After the buffer fills up (my data is 32 bytes / sample, so 32 samples would be 1024 bytes and would be filled in 160ms, and should be easily written to an SD card in < 10ms), switch my buffer pointer to a second buffer, and then initiate a SD write of the first buffer by waking the SD writing thread.

When my buffer fills up, how should I wake up the SD card writing thread? And I presume that in thread, it should basically just have a single call to f_write() followed immediately by a call to put the thread to sleep?

Thanks
Luke
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Application design

Post by Giovanni »

Hi Luke,

Keep in mind that looping while reading a counter keeps the CPU active and drawing maximum power, using kernel primitives like chThdSleepUntil() puts the CPU in a sleep state when it is not needed freeing it for other threads and saving power when there are no threads to execute. Creating a system without interrupts is pointless if you plan to use a RTOS.

About the HAL, in general it is better to avoid doing direct HW accesses when an equivalent functionality is available in the HAL. There are examples of all HAL drivers usage under ./testhal/STM32F1xx.

About reading a counter, the HAL offers an abstraction of a realtime counter without having to use a TIM unit, see the functions/macros halGetCounterValue(), halGetCounterFrequency(), xx2RTT(), RTT2xx(). The RT counter allow to have cycle-accurate timings. I still recommend using kernel primitives for mS-class timings.

Giovanni
kgysmits
Posts: 20
Joined: Tue May 03, 2011 10:16 am

Re: Application design

Post by kgysmits »

Giovanni, it seems hazelnusse is using the quadrature encoder input function of the timers.
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Application design

Post by Giovanni »

I noticed, we discussed it here: viewtopic.php?f=2&t=247&start=40

Giovanni
User avatar
barthess
Posts: 861
Joined: Wed Dec 08, 2010 7:55 pm
Been thanked: 7 times

Re: Application design

Post by barthess »

hazelnusse wrote:When my buffer fills up, how should I wake up the SD card writing thread? And I presume that in thread, it should basically just have a single call to f_write() followed immediately by a call to put the thread to sleep?

You can do that without explicit calling of sleeping function. Use synchronous message mechanism for it, you can found documentation on the official site.

As I do in my project:
1) define global message box (do not forget to switch on this feature in halconf.h).
2) put pointer to buffer from application thread into that message box
3) in SD writing thread use infinite loop like following:

Code: Select all

  
while TRUE{
  chMBFetch(&mbox, &buf_p, TIME_INFINITE);
  f_write();
  }
hazelnusse
Posts: 77
Joined: Thu May 24, 2012 8:01 am

Re: Application design

Post by hazelnusse »

Giovanni wrote:I noticed, we discussed it here: viewtopic.php?f=2&t=247&start=40

Giovanni


So it seems that to use the timers for counting quadrature encoders I should:
    1) Enable the STM32_GPT_USE_TIMx in mcuconf.h
    2) configure SMCR and CCMR1 timer registers as needed to put them in encoder input mode
    3) halInit()
    4) chSysInit()

I am not using anything HAL specific in step 2, but am I guaranteed that halInit() won't change the timer register settings?

If not, it seems I should do my configuration after the call to halInit() to make sure my settings don't get wiped by halInit().

Thanks,
Luke
Post Reply