-
Notifications
You must be signed in to change notification settings - Fork 3
ADC in ChibiOS
For those interested in how analog to digital conversions work in ChibiOS I'm writing this little page to explain it. ChibiOS has very neat and powerful tools for doing ADC but they're a bit tricky to understand at first glance.
The ADC procedure has more or less four parts:
- The high level init
- The ADC driver start and pin modes
- The ADC conversion group configuration
- And the actual call that triggers the conversion
The hardest part to understand is the conversion group configuration and we'll look into this in detail.
But first things first, before you can start using the ADC functionality you must do the high level initialization by calling these in the main function:
halInit();
chSysInit();
(These functions are found in "ch.h"
and "hal.h"
which are probably always needed for any ChibiOS code)
This call initializes the ADC drivers (note that there are multiple drivers that deal with different pin groups, see the manual to find which group your pins belong too), the one we will be using is ADC1
and it's made available after this call.
With that knowledge we can take a look at starting the driver and also setting up our I/O pins, it might look something like this(example from the IR sensor code):
void ADCinit(void){
palSetGroupMode(GPIOC, PAL_PORT_BIT(1), 0, PAL_MODE_INPUT_ANALOG); //Pin PC1
palSetGroupMode(GPIOC, PAL_PORT_BIT(4), 0, PAL_MODE_INPUT_ANALOG); //Pin PC4
palSetGroupMode(GPIOC, PAL_PORT_BIT(5), 0, PAL_MODE_INPUT_ANALOG); //Pin PC5
adcStart(&ADCD1, NULL);
}
This snippet starts by setting the mode of pins in group C (to find which group your pin belongs to you should refer to the DiscoveryBoard manual, PC1, PC4 and PC5 all belong to group C) to be input pins.
The palSetGroupMode
functions takes four arguments. The first is the group of pins to alter, the second is which pin in the group is being affected by this call, the third is a mask offset which we don't need to change (and I don't know what it does in truth) and the last is the mode we wish the pin to assume.
Lastly this function starts ADC driver ADC1
with a NULL config (which is sufficient for this example) by calling adcStart
.
This function should be called once before starting any conversions.
Before we take a look at the conversion group configuration I'll jump ahead and show the ADC convert call but before that we must define some things we will need for both this and for the configuration.
First we must define how many channels our conversion will cover, usually you'll have one channel per sensor, so in our example we set it to three because we use three IR sensors.
#define IR_ADC_GRP1_NUM_CHANNELS 3
Secondly we must define how many samples of each channel we want to perform with each conversion.
#define IR_ADC_GRP1_BUF_DEPTH 32
Lastly we must create a buffer to store our samples, this is a array of the type adcsample_t
with the size M*N where M is the number of channels and N is the number of samples per channel.
static adcsample_t irSamples[IR_ADC_GRP1_NUM_CHANNELS * IR_ADC_GRP1_BUF_DEPTH];
With this and our conversion group configuration (coming further down) we are ready to make the call to start a conversion. Conversions can be either synchronous or asynchronous and you'll need to use different functions to start each version, I'll show you a synchronous call (which you can embed in a thread).
adcConvert(&ADCD1, &adcgrpcfg1, irSamples, IR_ADC_GRP1_BUF_DEPTH);
The adcConvert
function takes four parameters, the first is the ADC driver that we initialized and started earlier, the second is the conversion group configuration, the third is the buffer where the samples will be stored and the fourth is the depth of the buffer (the number of samples per channel). The amount of channels is defined in the conversion group so they do not need to be included here. Once this call has finished you'll find your readings in the sample buffer. When doing conversions with multiple channels the buffer is filled like this:
Ch1 Ch2 Ch3 Ch1 Ch2 Ch3
With three channels, so you'll need to step through the buffer in steps of three. The sample buffer will be filled with voltages that have been read from the different pins.
Now let's take a look at the thing that probably causes most confusion, the conversion group configuration. In our example it'll look something like this:
static const ADCConversionGroup adcgrpcfg1 = {
FALSE, // circular buffer mode
IR_ADC_GRP1_NUM_CHANNELS, // number of the analog channels
NULL, // callback function
NULL, // error callback
0, // CR1
ADC_CR2_SWSTART, // CR2
ADC_SMPR1_SMP_AN11(ADC_SAMPLE_3) | ADC_SMPR1_SMP_AN14(ADC_SAMPLE_3) | ADC_SMPR1_SMP_AN15(ADC_SAMPLE_3),// sample times for channel 10-18
0,// sample times for channel 0-9
ADC_SQR1_NUM_CH(IR_ADC_GRP1_NUM_CHANNELS),// ADC SQR1 Conversion group sequence 13-16 + sequence length.
0, // ADC SQR2 Conversion group sequence 7-12
ADC_SQR3_SQ1_N(ADC_CHANNEL_IN11) | ADC_SQR3_SQ2_N(ADC_CHANNEL_IN14) | ADC_SQR3_SQ3_N(ADC_CHANNEL_IN15) // ADC SQR3 Conversion group sequence 1-6
};
Simple, right? Let's take a look at what each of these things mean (at least the relevant parts).
- Circular buffer mode true/false, should the buffer start over if it becomes full before the conversion is over.
- Number of analog channels, this one is clear enough and we simply use the macro we defined before
- Callback function for when the buffer is full, only used if we're doing asynchronous calls
- Error callback function, called if an error occurs during the conversion (in this case we don't care)
- Configuration for the CR1 register which is a low level driver registry
- Configuration for the CR2 register (unsure what role the SWTRIGGER flag plays but it's needed)
- Sample times for channel 10-18
- Sample times for channel 0-9
- ADC SQR1 Conversion group sequence 13-16 + sequence length.
- ADC SQR2 Conversion group sequence 7-12
- ADC SQR3 Conversion group sequence 1-6
7 and 8 require a bit more explanation. These two fields control the sample rate to be used during the conversion for a set channel, meaning you can have different rates for different channels in the same conversion(useful if you're using different types of sensors but want to do the conversion in one group). A higher sample rate will give a better sample. To find which channel your connected pin belongs to see the DiscoveryBoard manual, our pins (PC1, PC4 and PC5) map to the channels 11, 14 and 15 which belong to the group SMPR1
, hence we use the macro ADC_SMPR1_SMP_AN11
to set the sample rate of channel 11. If we used pins that belong group SMPR2
(channel 0-9) we'd use ADC_SMPR2_SMP_AN8
for example to set the sample rate on channel 8. SMPR1 goes into field number 7, and SMPR2 goes into field number 8, since we're not using any channels in the SMPR2 group we set it to 0. We simply use binary or to mask more sample rates into one field.
Points 9, 10 and 11 also require more explanation. SQR1, 2 and 3 are registries that deal with binding channels to a sequence in a conversion (to know which order to read the pins in). Each registry deals with a set of pins and it's a bit strange the way they are split up. The pins get paired based on their number, so PC1 belong to SQR3 which deals with pins from 1 to 6, but PA1 also belongs in this group, so does every other pin that has the number 1 to 6, while pins like PC15 belong to SQR1 that deals with pins 13 through 16 and SQR2 deals with pins 7 through 12 so simply check the pins you're using and find what group they belong in. Our pins, PC1, PC4 and PC5 all fit in SQR3. One thing that is always the same however is that we must put the sequence length into SQR1 by using the ADC_SQR1_NUM_CH
macro, this tells how many pins are used in the full sequence (even pins that belong to SQR2 and SQR3), along with any bindings for pins. Our example does not use any pins in SQR2 so we set that to 0. When binding channels to a sequence we start with the first item in the sequence, the first item in SQR3 is SQ1 (SQR2 starts with SQ7, SQR1 start with SQ13). ADC_SQR3_SQ1_N(ADC_CHANNEL_IN11)
binds the channel ADC_CHANNEL_IN11
to SQ1 in SQR3. To find what channel your pin has see again the DiscoveryBoard manual.
ChibiOS is a powerful RTOS and with this ADC architecture you can attach a load of sensors, configure their sample times, bind them to different channels and get conversions from all of them in one call, assuming you do your configuration correctly.
ChibiOS STM32F4 LLD source Here you can find definitions of all the macros to use when binding channels and setting sample rates. DiscoveryBoard manual Refer to this to find information about your pins.