Showing posts with label embedded. Show all posts
Showing posts with label embedded. Show all posts

Saturday, December 12, 2015

Microcontroller Wars - The Ultra-Low End Part 2


This is the second article from a series of articles that shows the differences in selected ultra-low end microcontrollers. The idea of ultra-low end here is to find MCUs that costs less than $0.40 at volume and have 8 or less pins on the package. That spectrum seems to be the last bastion of various 8-bit architectures and has not been overrun by ARM-clones.
After part one that provided some background to this series. It is time to get down to business and put the parts that I sampled to use. Before we look at the RS08, let us start with the PIC10F322.

PIC10F3xx Overview

The PIC10F3xx is the newest chip of Microchips ultra-low end series. It is primarily marketed ASIC fixes, very simple analog control, and disposable medical applications. I wonder how they came up with ASIC fixes and how big the market size for that problem actually is, but the other two target applications are clearly filling a need. The 8-PIN DFN (2x3mm) and 6-PIN SOT-23 (3x3 mm) packages make it a very small solution. The good news is that Microchip also provides free samples for 8-pin PDIP variant that can be bread-boarded easily. The core is an 8-bit architecture with 4 cycles per instruction (more explained below in PICuliarities). The peripherals include enough options to make it versatile enough to satisfy many different applications. Because it has an ADC it actually can satisfy applications that their previous generation cannot satisfy. That said if you are not attached to analog, you can potentially save another $0.08 by going to a PIC10F200.

If this line existed 50 years ago it would be a worthy contender as guidance system for ballistic missiles; now this is more of a contender for disposable pregnancy tests.

PICuliarities (TM) - The Good, Bad, and Ugly Facts of the 8-Bit Architecture

While being one of the oldest Microcontroller vendors the 8-bit PIC architecture has earned Microchip a lot of haters for mid- and high-end applications. The cause of this is probably its age. The history goes all the way back to 1975 when it was first developed as I/O controller for the CP1600. The RISC instruction set varies between 33 and about 80 instructions. 
Except for the high-end variant of that core the instruction set does not include conditional branches instead it has conditional skip conditions. Rather than testing for a positive argument the compiler tests for the negative and skips over an unconditional branch. Most people will use C to program the core and not notice that, but it is a neat thing to be aware of if you ever dive into assembly on that core.

The core itself has a hardware stack that only stores the return address of calls. If you attended my or any other operating systems course in Waterloo, you know that this is a problem. You cannot implement a multi-tasking OS on that core ever. Also the core itself will silently overrun the stack and not indicate in any way what stack level you are on or if you overran it. As a result, the compiler designers have to worry a lot where to function parameters and how to handle function calls. Since Microchip is comprised of smart people, they figured out that instead of maintaining a growing parameter stack they would stick all parameters in fixed locations and call it a compiled stack. That compiler also tracks how many stack levels you go from your main function. If you avoid function calls from interrupts you can avoid nasty surprises there. The good news is that you can statically predict what you stack usage will be at run-time, but that also means you can never use recursion or function pointers. More of the PICuliarities are in the compiler reference manual. If you are bored and want to know more than is described here then it is definitely worth a read and you are not in for nasty surprises. Lastly for fun, there are PICs out there that do not have an interrupt controller. I picked the PIC10F3xx series over the PIC10F2xx because the PIC10F3xx actually has them. 8-bit PICs come in three flavours: Baseline devices, mid-range (extended mid-range), and high-end. They essentially boils down to what the addressable space is, if you have an interrupt vector or interrupts at all, how many stack-levels the hardware stack has, and if this chip has an on-board debug unit. PIC18s actually have options to access the stack, can run a software parameter stack and run an OS. They are not the focus of this comparison; personally I'd always pick a cheaper 32-bit part that does not have the other PICuliarities for mid or high-end microcontroller applications.

I suspect that a lot of hatred against PICs is a result of expecting something that you know from your "other" architectures. That said, I learned those lessons the hard way 10 years ago by trying to be agile and did not turning to the compiler reference manual first. Aside from turning a blind eye to execution performance, RTFM is probably the best remedy not to become a PIC (8-bit) hater in the first place.

In terms of core the PIC10F32x series is what Microchip calls a mid-range device. It has 35 instructions, an 8-level hardware stack, and an interrupt. It comes with 64 bytes of data memory and up to 896 bytes of program memory, and a 128 byte EEPROM (well it is actually emulated using flash). Again read the CPU Overview section in the datasheet to avoid any nasty surprises afterwards.

Minimal Circuit and Programming Interface

The following picture shows the minimal circuit to get the chip running. The diagram next to it highlights the connections. 



In a nutshell:
  • Weak pull-up resistor on MCLR, 10K or 100K should do
  • Small decoupling capacitor (100nF) between Vdd and Vcc
  • The ICSP needs two signal lines, provides the power, and pulls MCLR low
  • Optional Hello World LED with resistor as shown
It should be noted here that this chip has no debugger circuit. In order to get debugging you need to buy a special version of this chip ($$$) that plugs into the PDIP header of your end-product. Since you would not pay extra cost for the debug version in production, Microchip sells you a board that has just that chip and a PDIP header that you can plug into your target board for debugging. This is called "debug header" in Microchips terms. In production you'd put the regular chip that does not have the debug unit. Because these are cumbersome design steps and this requires you to reserve enough space for a separate PDIP header (or a mess of wires to hook it up to your DFN pads) on your target application, I suspect that a lot of Microchip users develop software for that chip the "burn and pray" way (i.e. flash the firmware and hope it is working). If you come from the Arduino world then this is not a problem, but the normal modus operandi. If you worked on safety critical systems like me and like to inspect every register during run-time this causes some grief and needs some time to get used to.



Our friendly Microchip FAE left a "Low Pin Count Demo" board and PICKit-3 behind during his last visit. Unfortunately the PIC10Fxxx series is not pin-compatible with the PIC12Fxxx 8-pin variants. So I had to breadboard it instead of plugging it into the demo board.

Programming Environment


I usually live and breathe Linux for all our other projects. Since we do a lot of BSPs for various client applications, I normally use Linux to develop everything. So having development tools that run on Linux is a big plus. Microchips newest IDE is MPLAB-X which is loosely based on IntelliJ and it comes with full Linux support. The same goes for the XC8 compiler that is needed to build software for the PIC10F32x series. Both are free and not code-size limited. There is a paid version of the compiler that allows you to do some performance optimizations. Honestly, I would pick a different core if I were in for performance so for the context of low-end applications the free XC8 compiler is more than enough.

I start the development by creating a new project and picking the PIC10F template as shown below.


The next step is to configure build environment to only include the XC8 configuration for the PIC10F322 chip.

As part of that configuration, I would make the compiler and linker as verbose as possible. Because space is very limited I would like to dive in to the memory usage for every linker section (or what Microchip calls psect). 

My next thought was to use Microchips highly advertised MCC configuration tool to graphically configure all the peripherals and generate all required code. Unfortunately, it turns out that this chip is not supported by MCC. That means we have to go back to the normal PIC usage mode: RTFM. 


Luckily at least the configuration bits configurator for mid-range PICs worked on that chip. So we could at least generate that file. Those bits are fuses that are set during flashing and persist during resets. Again, RTFM for all options needed.

Clocking

The chip has internal 16 MHz and 32 KHz oscillators. For the purpose of this article we will use the undivided 16MHz clock. 
Since I cannot dive into the clock configuration registers with a debugger during run-time, I hardcode all values with very verbose comments in my code. By using C preprocessor macros you can build maintainable program blocks that do not result in an increase of code size. Even though my commenting style looks obscure, it guarantees that I will see proper bit-field documentation when I run Doxygen.

GPIOs and Delay Cycles

In order to blink an LED we have to configure the LED. I decided to make the PIC the current source in my breadboard. This means we have to write a logical zero to an output pin to switch the LED on. There are three registers involved to configure the PIN functions in that PIC.


The TRISx register configures the I/O direction of the pin. Setting TRIS to zero for a particular pin makes it an output pin. The ANSELx register and WPUx registers configure the analog function and pull-ups respectively. On the PIC10F32x you also have to clear a bit in the options register to get pull-ups to work.


The output is toggled by writing to a latch register. The bit fields of that register are defined macros making them very easy to use in my main loop. The delay macro that the compiler provides hardcodes delay cycles based on the defined clock-rate in the header file.

Usage Profiling and Chip Programming

After we wrote our piece of firmware for the chip, it is probably worthwhile to check how much of the chip we are actually using. Because we configured the compiler as very verbose the compiler will output the required information. Because we only have 64 bytes of SRAM (parameter) stack overflows would be toxic. The compiled stack option actually comes handy now because it predicts the stack depth at compile time. It can be seen that this Hello World demo only consumes a fraction of the resources of that chip. Even though we selected an ultra-low-end part, we still have quite a bit of wiggle room in terms of program and data memory for more complex operations.


After we are happy with the performance figures, we need to flash the firmware. This can be done straight from the IDE. Remember to set the PICKit-3 configuration to provide target power. In order not to incinerate the target device it is probably advised to pick 3.25V. That covers both the normal line as well as the XLP variant of the chip. If everything goes well the LED should blink in one second intervals.



The source-code can be downloaded from here.

In order to get to know the chip, I advise the reader to try interrupt processing, and the wakeup timer. The peripherals included in the chip are fairly similar to what other PIC microcontrollers offer. You will find ample of tutorials for that elsewhere.

Summary

The PIC10F322 is probably a worthy contender for ultra-low end applications. At the time of writing the chip retails for $0.35-$0.37 @ 5k volumes. That pricing is very hard to beat with any ARM-based solution. If you call your Microchip sales person you can probably bring that down further at larger projected volumes. It has ample of peripherals to satisfy many low-end applications. The DFN and SOT-23 packages are small enough for disposable applications. Anyone starting PIC after having learned a different architecture has to relearn, the PICuliarities can be a real cause of grief. On a positive note the whole idea of the compiled stack may not be a problem but a handy feature to avoid stack overflows, if you know what programming patterns to strictly avoid.

I am going to keep the PIC10F322 on the list for low-end applications. Despite my ARM-bias, I may keep templates for low-end, mid-range, and PIC18 microcontrollers on the shelf if a customer insists on using them or needs legacy support.

Next up I will write a piece about the Freescale/NXP RS08.

References

Microcontroller Wars -The (super) Low-End (Part 1/3)


This is the start of a little blog series reviewing various microcontroller architectures. We have an integrated consulting service division in the business. On the embedded side, we get approached a lot for prototyping, product demonstrators, board support packages, and finished solutions. Many designs we churned out over the years went into high-volume production. In that process we built good relationships with suppliers, semi-conductor vendors, and contract manufacturers around the world. We recently got approached by Microchip. Many know me as an ARM-(and Freescale in particular)-fan-boy.

When dealing with firmware and embedded engineers there is almost a religious adherence to various microcontroller architectures. Because of efficiencies in the silicon process an argument for and against particular microcontroller architecture can seldom be made purely on a cost-basis. In most cases the die-space spent on peripherals, SRAM, and flash outweighs by a fair margin the die-space spend on the core itself.  An additional cost-driver seems to be various design licenses that the manufacturer pays to various organizations (e.g. ARM-licenses or MIPS-licenses). In most cases the argument for or against a particular microcontroller architecture is made on experience, the existence of development tools for that microcontroller in the business, vendor support, or lastly customer requirements.

While being very happy about other Microchip parts (operational amplifiers, power solutions …), I have not touched a PIC in more than 10 years. Back in the day I had no support, very mixed impressions about the development tools and the architecture. For 8-bit solutions 10-years ago, I would have always chosen an Atmel AVR over a PIC purely based on technology and development tools. 10 years on the support for Atmel seems to be waning, we have much better relationships with other vendors (such as Freescale and others), and I have a friendly Microchip sales-person and a field-application engineer (FAE) walking to our office.

The availability of an FAE on a short notice can be the single biggest factor in choosing architecture to de-risk the design process, something we really learned to value with Freescale over the years. If you ever get stuck on a deadline, you can pick up the phone and get help from an experienced person rather than pulling an iron-man yourself. A lot of people will pride themselves to solve every problem, but when it really comes to crunch time nobody solves it all alone. So I decided to bury old misconceptions, prejudice and have a look at Microchips product line again and put the free development tools to use that they left behind.

Glancing over their Microcontroller product line and comparing it to what our other vendors offer, I have a hard time to choose a PIC over known parts from other vendors. From my perspective anything that PIC24s, and PIC32s offer can easily be covered by a Freescale Kinetis part. The dsPIC product line seems to have an edge over other motor controllers; but that area is too specialized not to count for general purpose comparison (someone should look into TIs C2000 line for comparison). Looking at the 8-bit microcontrollers, many of Microchips microcontrollers can be easily outrun in terms of performance by the KL-Kinetis series from Freescale, LPC8xx series from NXP, and the STM32F0xx series.

The good argument here for Microchip is that they seem to be more willing to hold inventory for volume production than other vendors. They will give you volume pricing for 100k parts, even if you only order them in smaller batches over the lifetime of your product. This is something we have a hard time negotiating with other vendors that sell through suppliers on first contact. That said budgetary prices at these volumes for PIC24s, many 8-bit PICs still are higher than lower-volumes of some Kinetis or LPC parts.

Although the 8-bit and 16-bit microcontrollers seem to be dying species, Microchip seems to be the Highlander of the super-low end, the PIC12Fxxx and PIC10Fxxx product lines. These products have been around for decades and retail at prices, where it does make little sense to pay a design-license to ARM for a Cortex-M0 or a Cortex-M0+ core, even though NXP attempts this with their LPC810-series. Those pieces are meant for simple control systems or reconfigurable switches in your design. The aim here is to bring volume pricing down to less than $0.40 per chip, minimise PCB-space usage by super-low pin-count devices (less or equal than 8-Pins). Other contenders in this segment are the Freescale RS08, and the Atmel AtTiny series. Since Atmel never came to our office with an FAE, I will limit this evaluation to the RS08KA-series and the PIC10F3xx.

In Microchips favour, they seem to have free samples for everything on their website. While writing the first piece of this blog I already ordered samples for some PICs including the PIC10F322, which promptly arrived in Canada from Thailand within five days. Because of the Freescale/NXP merger the availability for free samples seems to be limited at the moment. A quick phone call to our Freescale/NXP sales person and FAE would have solved that problem in a heart-beat. Unfortunately, this may not be an option for someone who does not have any existing relationships with Freescale and just browses through their website.

In the next article I will discuss the breadboard bring up of a PIC10F322. In a later article I will show how to do that with the RS08KA. In the last article I will show how a Cortex-M0 can solve similar problems in a little less margin sensitive environment. 

Other references

Monday, March 4, 2013

STM32F4 and “Most” of What You Ever Wanted to Learn about Its Analog Digital Converter (ADC)



When developing a lab for an upcoming course, I finally got the opportunity to work with the STM32F4 ADC. The capabilities of the MCU and its ADC almost qualify it as digital signal processor (DSP). Not surprisingly the ADC ended up a highly-configurable but arguably complex beast. The novelty of the F4 series makes documented sample code on the web sparse. 
In this post, I summarize and exemplify the different operating modes of a single ADC that comes with the STM32F4 microcontroller (MCU) from ST microelectronics. Dual ADC modes are not discussed (therefore "most" and not everything).
This post is targeted at engineers, hobbyists, students, and engineers with working knowledge of see and some basic exposure to the STM32 peripheral libraries (any of their STM32Fxxx series). I am explaining and exemplifying the different operating modes of the ADC and show where to look for the right information at each step.

Overview of Operating Modes

STMs Application Note AN3116 explains the different operating modes quite nicely but fails to address any practical considerations of their implementation. For an overview, I am just reviewing the properties shortly. The ADC can perform a single conversion, or continuously convert values. The conversion can be performed on a single channel or multiple channels. The latter is referred to as scan configuration. The permutation of these modes gives the different operating modes.
  1. Single-channel & single-conversion
  2. Multi-channel (scan) & multiple-conversion
  3. Single-channel & continuous operation
  4. Multi-channel (scan) & continuous operation
Each of the four modes can be triggered by modifying a memory-mapped register or an external trigger such as a timer. In any mode the sample time per channel can be specified as number of ADC clock steps. Furthermore, the STM32F4 contains an analog watchdog that triggers an interrupt when values fall outside a specified range. In any case interrupts can be used to signal the completion of a conversion.
The first mode performs a single conversion on a single channel when triggered and then stops. This can be used to measure a calibration value or perform checks between different program states.
The second mode performs a scan conversion, reading one channel after the other (of up to 16 channels in a single scan). This is very useful to capture sensor data at discrete points in time such as positions.
The third mode converts a single channel continuously. This can be useful to get data on a critical analog input.
The last mode is just scan conversion performed continuously.

Setup Description

Being an ARM processor, peripherals need to be clocked to be switched on. This applies to all components that have anything to do with the clock configuration. Again the STM32 being high-end MCU from STM makes the clock three a bit of a Maze (at the time of writing to be found in Reference Manual 90, Figure 9).
Use their clock configuration utility that actually generates you the initialisation code; or, be brave, print the page and determines the register value yourself. Once the clock itself is set up, the individual peripherals can be enabled with the RCC_xxx functions, depending on the bus they are connected to.
In this example we use ADC1, which is connected to Advanced Peripheral Bus 2 (APB2), which is a subdivision of the ARM AMBA high-performance bus (AHB). In this example it is assumed that the main clock is configured at 168 MHz and that APB2 runs at 84 MHz. Furthermore to constrain the scope just to the ADC we use the internal reference channels VREF_int and Temperature as input for our experiments.

Single-Conversion Mode


Based on the setup-description we describe in this chapter how to perform a single-channel conversion.
The initialisation looks as follows:

/* Define ADC init structures */
 ADC_InitTypeDef       ADC_InitStructure;
 ADC_CommonInitTypeDef ADC_CommonInitStructure;

 /* IMPORTANT: populates structures with reset values */
 ADC_StructInit(&ADC_InitStructure);
 ADC_CommonStructInit(&ADC_CommonInitStructure);

 /* enable ADC clock */
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

 /* init ADCs in independent mode, div clock by two */
 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
 ADC_CommonInit(&ADC_CommonInitStructure);

 /* init ADC1: 12bit, single-conversion */
 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
 ADC_InitStructure.ADC_ScanConvMode = DISABLE;
 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
 ADC_InitStructure.ADC_ExternalTrigConvEdge = 0;
 ADC_InitStructure.ADC_ExternalTrigConv = 0;
 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
 ADC_InitStructure.ADC_NbrOfConversion = 1;
 ADC_Init(ADC1, &ADC_InitStructure);

 /* Enable VREF_INT & Temperature channel */
 ADC_TempSensorVrefintCmd(ENABLE);

 /* Enable ADC1 **************************************************************/
 ADC_Cmd(ADC1, ENABLE);
The most important take home is to always initialise the structures of the STM32 peripheral libraries. Since those structures are allocated from the stack the values are unpredictable. Using the InitStruct methods of the peripheral library, the structures will always reflect the reset state of the peripheral.
In addition, even though we are just using a single ADC channel on a single ADC, we have to initialise the entire ADC block. The ADC block can operate in interleaved or independent mode. To use a single channel only the ADC block has to be configured in independent mode.
Furthermore, for a single conversion the modes Scan and Continuous are disabled. The internal VREF_INT and TEMP channels have to be explicitly enabled. They are disabled by default to conserve power. Finally the ADC has to be enabled for further usage.

The read function is relatively uneventful. After the ADC has been initialised, all we have to specify is the ADC channel, the sampling time, and wait for the conversion to complete.

uint16_t adc_read(ADC_TypeDef* ADCx, uint8_t channel, uint8_t ADC_SampleTime) {
 /* Configure Channel */
 ADC_RegularChannelConfig(ADCx, channel, 1, ADC_SampleTime);

 /* check if conversion was started, if not start */
 ADC_SoftwareStartConv(ADCx);

 /* wait for end of conversion */
 while((ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC) == RESET));

 return ADC_GetConversionValue(ADCx);
}
The temperature channel and the voltage can then be read individually as follows:
uint16_t temp = adc_read(ADC1, ADC_Channel_16, ADC_SampleTime_480Cycles);
uint16_t vrefint = adc_read(ADC1, ADC_Channel_17, ADC_SampleTime_480Cycles);
The temperature sensor is wired to ADC channel 16 and VREF_INT is wired to ADC channel 17. In both cases a sample time of 480 ADC clock cycles (approx 11.4uS) and 12 cycles (0.3uS) ramp up time. The respective STM32F4 datasheet gives the conversion values for the temperature and the voltage. Some examples for the STM32F4Discovery are shown below.
#define ADC_TEMPERATURE_V25       760  /* mV */
#define ADC_TEMPERATURE_AVG_SLOPE 2500 /* mV/C */

int32_t adc_value_to_temp(const uint16_t value, const uint16_t steps_per_volt) {
 /* convert reading to millivolts */
 int32_t mv = ((uint32_t)value * 1000)/steps_per_volt;
 return (mv - ADC_TEMPERATURE_V25) / 25 + 25;
}

uint16_t adc_steps_per_volt(const uint16_t vref_value) {
 return (vref_value * 10) / 12; /* assume 1.2V internal voltage */
}
The example shown so far can be used for simple ADC experiments, in which one has to programmatically read an analog channel non-periodically. This mode can be useful for the calibration of sensors or for testing environmental conditions, when entering a different program state.

ADC Scan-Mode with Single-Conversion Interrupt


After having demonstrated how to read individual channels programmatically, we will describe how the scan-mode of the ADC works.
Since the ADC only has a single register that stores the information of the last conversion, there are only two ways to retrieve the values that are sampled through the scan.

  • Configure an interrupt to trigger after each channel is sampled,
  • Configure the DMA controller to copy the data of the channels into a defined memory location.
In this section we will illustrate the former and describe the latter mode for continuous-mode operation later on. Since this example uses interrupts, the nested vectored interrupt controller (NVIC) needs to be initialised as well. The code for the initialisation is shown below:
/* Unchanged: Define ADC init structures */
        ADC_InitTypeDef       ADC_InitStructure;
        ADC_CommonInitTypeDef ADC_CommonInitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;

        /* Unchanged: populate default values before use */
        ADC_StructInit(&ADC_InitStructure);
        ADC_CommonStructInit(&ADC_CommonInitStructure);

        /* Unchanged: enable ADC peripheral */
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

        /* Unchanged: init ADC */
        ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
        ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
        ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
        ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
        ADC_CommonInit(&ADC_CommonInitStructure);

        /* Changed: Enabled scan mode conversion*/
        ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
        ADC_InitStructure.ADC_ScanConvMode = ENABLE; 
        ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; 
        ADC_InitStructure.ADC_DataAlign= ADC_DataAlign_Right; 
        ADC_InitStructure.ADC_ExternalTrigConv= 0; 
        ADC_InitStructure.ADC_ExternalTrigConvEdge= 0; 
        ADC_InitStructure.ADC_NbrOfConversion= 2; 

        ADC_Init(ADC1, &ADC_InitStructure);

        /* Enable Vref & Temperature channel */
        ADC_TempSensorVrefintCmd(ENABLE);

        /* Configure channels */
        /* Temp sensor */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_480Cycles);
        /* VREF_int (2nd) */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_480Cycles);



        ADC_EOCOnEachRegularChannelCmd(ADC1, ENABLE);

        /* Enable ADC interrupts */
        ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);

        /* Configure NVIC */
        NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
        NVIC_Init(&NVIC_InitStructure);

        /* Enable ADC1 **************************************************************/
        ADC_Cmd(ADC1, ENABLE);
In essence what changed is that now the ADC_ScanConvMode is set enabled, the channel initialisation has been moved into the initialisation routine, the NVIC configuration has been added.
The order of the channel conversion is controlled by assigning priorities (1...16) as third parameter to ADC_RegularChannelConfig. The total number of channels to be converted in sequence is specified by  ADC_InitStructure.ADC_NbrOfConversion.
As said before since the data register of the ADC only holds the last converted value, we have to trigger the interrupt on every conversion. This is done by calling ADC_EOCOnEachRegularChannelCmd(ADC1, ENABLE). This call enables the end-of-conversion flag after each channel, which triggers the end-of-conversion interrupt every time this flag is set.
In the interrupt service routine (ISR) the converted value can be copied to a global buffer and the ISR has to acknowledge the interrupt once the value is received. A global counter can be used to identify the channel that was converted.

uint16_t temp = 0;
uint16_t vref = 0;
uint16_t counter = 0;

void ADC_IRQHandler() {
        /* acknowledge interrupt */
        uint16_t value;
        ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);

        value = ADC_GetConversionValue(ADC1);
        if(counter % 2 == 0) {
                temp = value;
        } else {
                vref = value;
        }
        counter++;
}
It should be strongly noted that in the main program the scan is started by ADC_SoftwareStartConv(ADC1). The counter value can be checked against to determine when to read out the values.
        ADC_SoftwareStartConv(ADC1);
        while(counter <= 2);
        if(counter>= 2) {
                uint16_t v1, v2;
                v1 = temp;
                v2 = vref;
                /* … */
        }
This example shows how to read out multiple channels of an ADC in a single-scan. Interrupts are needed to identify the converted values of the individual channels. This mode is useful, when data points from multiple sources need to be taken at the same time.

ADC Continous-Scan Mode with DMA


After having discussed how to sample multiple values in a single sweep, we modify the example to execute the ADC continuously. Furthermore, because the interrupt after each channel conversion created substantial overhead, we use DMA in this example to copy the channel values to a cyclic buffer and just interrupt after the conversion of all channels is complete.
volatile uint16_t ADCBuffer[] = {0xAAAA, 0xAAAA, 0xAAAA};

...

        /* Define ADC init structures */
        ADC_InitTypeDef       ADC_InitStructure;
        ADC_CommonInitTypeDef ADC_CommonInitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        DMA_InitTypeDef DMA_InitStructure;

        /* Initialise DMA */
        DMA_StructInit(&DMA_InitStructure);

        /* Enable clock on DMA1 */
        /* Enable DMA2, thats where ADC is hooked on -> see Tab 20 (RM00090) */
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);               
        /* config of DMAC */
        DMA_InitStructure.DMA_Channel = DMA_Channel_0;                     
        DMA_InitStructure.DMA_BufferSize = 2;                       /* 2 * memsize */
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;     /* direction */
        DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;      /* no FIFO */
        DMA_InitStructure.DMA_FIFOThreshold = 0;
        DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
        DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;            /* circular buffer */
        DMA_InitStructure.DMA_Priority = DMA_Priority_High;        /* high priority */
        /* config of memory */
        DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADCBuffer; /* target address */
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; /* 16 bit */
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /* increment after wrt */
        /* config of peripheral */
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_Init(DMA2_Stream0, &DMA_InitStructure); /* See Table 20 for mapping */
        DMA_Cmd(DMA2_Stream0, ENABLE);

        ADC_StructInit(&ADC_InitStructure);
        ADC_CommonStructInit(&ADC_CommonInitStructure);

        /* init ADC clock */
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

        /* init ADC */
        ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
        ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
        ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
        ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
        ADC_CommonInit(&ADC_CommonInitStructure);

        ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
        ADC_InitStructure.ADC_ScanConvMode = ENABLE;
        ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
        ADC_InitStructure.ADC_ExternalTrigConvEdge = 0;
        ADC_InitStructure.ADC_ExternalTrigConv = 0;
        ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
        ADC_InitStructure.ADC_NbrOfConversion = 2; /* 2 channels in total */
        ADC_Init(ADC1, &ADC_InitStructure);

        /* Enable Vref & Temperature channel */
        ADC_TempSensorVrefintCmd(ENABLE);

        /* Configure channels */
        /* Temp sensor */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_480Cycles);
        /* VREF_int (2nd) */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_480Cycles);

        /* Enable ADC interrupts */
        ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);

        /* Enable DMA request after last transfer (Single-ADC mode) */
        ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);

        /* Enable ADC3 DMA */
        ADC_DMACmd(ADC1, ENABLE);
        /* Configure NVIC */
        NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
        NVIC_Init(&NVIC_InitStructure);

        /* Enable ADC1 **************************************************************/
        ADC_Cmd(ADC1, ENABLE);
The major change to the previous configurations is that now scan mode and continuous modes are enabled. Furthermore the DMA is configured to copy the buffer off the ADC register into the target location. It should be strongly noted that individual DMA peripherals and their channels are multiplexed among multiple components. The mapping is explained in Table 20 in RM00090. Furthermore, it should be noted that the DMA performs after the EOC interrupt triggers. A reliable exchange protocol would be to copy the previous buffer value of the DMA buffer in the scan conversion for the main control program or to configure DMA interrupts (which is beyond the scope of this article).
void ADC_IRQHandler() {
        /* acknowledge interrupt */
        ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);

        /* nothing to do here except for possibly copying DMA buffers */
}

This example shows how to convert ADC channels continuously and store them in a global buffer using DMA. The ADC interrupt can be used to implement a synchronization scheme with the main program. This mode is useful for applications, in which a stream of continuous data is needed. An alternative to the cyclic buffer described in the example would be to use a linear buffer and use a counter in the ADC to abort the sampling after a fixed number of samples have been converted.

ADC Continous-Scan Mode with DMA and Additional GPIO Channels


So far we have just discussed examples that read off the on-chip channels. We modify the previous example to add three additional external pins as input to the ADC. We have chosen ADC_IN 10 ... 12, which are mapped to PORTC 0 ... 2. Note the pin mapping can be obtained from the datasheet of your STM32F4 chip. For the STM32F4Discovery the mapping is highlighted in the above picture.
The modified initialisation looks as follows:

/* Define ADC init structures */
        ADC_InitTypeDef       ADC_InitStructure;
        ADC_CommonInitTypeDef ADC_CommonInitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        DMA_InitTypeDef DMA_InitStructure;
        GPIO_InitTypeDef GPIO_InitStructure;

        /* Enable clock on DMA1 & GPIOC */
        /* Enable DMA2, thats where ADC is hooked on -> see Tab 20 (RM00090) */
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_DMA2, ENABLE);

        /* Initialise GPIOs C0 (ADC123_IN10), C1 (ADC123_IN11), C2 (ADC123_IN12)*/
        GPIO_StructInit(&GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
        GPIO_Init(GPIOC, &GPIO_InitStructure);

        /* Initialise DMA */
        DMA_StructInit(&DMA_InitStructure);

        /* config of DMAC */
        DMA_InitStructure.DMA_Channel = DMA_Channel_0; /* See Tab 20 */
        DMA_InitStructure.DMA_BufferSize = 5; /* 5 * memsize */
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;/* direction */
        DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;/* no FIFO */
        DMA_InitStructure.DMA_FIFOThreshold = 0;
        DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
        DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; /* circular buffer */
        DMA_InitStructure.DMA_Priority = DMA_Priority_High;
        /* config of memory */
        DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADCBuffer;/* target addr.  */
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; /* 16 bit */
       /* config of peripheral */
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_Init(DMA2_Stream0, &DMA_InitStructure); /* See Table 20 for mapping */
        DMA_Cmd(DMA2_Stream0, ENABLE);

        ADC_StructInit(&ADC_InitStructure);
        ADC_CommonStructInit(&ADC_CommonInitStructure);

        /* init ADC clock */
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

        /* init ADC */
        ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
        ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
        ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
        ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
        ADC_CommonInit(&ADC_CommonInitStructure);

        /* ADC1 Init: this is mostly done with ADC1->CR */
        ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
        ADC_InitStructure.ADC_ScanConvMode = ENABLE;
        ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
        ADC_InitStructure.ADC_ExternalTrigConvEdge = 0;
        ADC_InitStructure.ADC_ExternalTrigConv = 0;
        ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
        ADC_InitStructure.ADC_NbrOfConversion = 5; /* 5 channels in total */
        ADC_Init(ADC1, &ADC_InitStructure);

        /* Enable Vref & Temperature channel */
        ADC_TempSensorVrefintCmd(ENABLE);

        /* Configure channels */
        /* Temp sensor */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_480Cycles);        
        /* VREF_int (2nd) */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_480Cycles);         
        /* PC0 */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 3, ADC_SampleTime_480Cycles);
        /* PC1 */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 4, ADC_SampleTime_480Cycles);
        /* PC2 */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 5, ADC_SampleTime_480Cycles);

        /* Enable ADC interrupts */
        ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);

        /* Enable DMA request after last transfer (Single-ADC mode) */
        ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);

        /* Enable ADC3 DMA */
        ADC_DMACmd(ADC1, ENABLE);

        /* Configure NVIC */
        NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
        NVIC_Init(&NVIC_InitStructure);

        /* Enable ADC1 **************************************************************/
        ADC_Cmd(ADC1, ENABLE);

The main difference to the previous example is that now a GPIO initialization has been added to the initialization, that the total number of conversions changed, and that the number and priorities of the input channels changed.
The GPIOs used as inputs need to be configured as analog inputs (AIN) and not be pulled up or down. The configured port speed is not relevant for the AIN mode. Furthermore, the clock (RCC) for the GPIO also needs to be enabled for the block to function properly.
The outcomes of this experiment are similar to the previous example, except that now three external pins are added to the buffer that contains the analog conversions.

Time-Triggered ADC-Scan Mode with DMA and Additional GPIO Channels

So far we have discussed the different operating modes of the ADC that are triggered by software. In addition to those modes the ADC can also be triggered by an external pin or a timer source. In this example, we will demonstrate how to modify the code of the previous example to be triggered from a timer.
In this case we enable Timer2 to provide the clock for the ADC. To synchronize the transfer we enable the interrupt of timer two. Note any timer is likely connected to a different part of the AHB bus structure than the ADC; it is always wise to look at the clock tree and validate the clock configuration.
The code is shown below:

 /* Define ADC init structures */
        ADC_InitTypeDef       ADC_InitStructure;
        ADC_CommonInitTypeDef ADC_CommonInitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        DMA_InitTypeDef DMA_InitStructure;
        GPIO_InitTypeDef GPIO_InitStructure;
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

        /* Enable timer (timer runs at 21 MHz)*/
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
        TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
        TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseStructure.TIM_Period = 1999;
        TIM_TimeBaseStructure.TIM_Prescaler = 17999;
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
        TIM_SelectOutputTrigger(TIM2,TIM_TRGOSource_Update);
        TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

        NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
        NVIC_Init(&NVIC_InitStructure);

        /* Enable clock on DMA1 & GPIOC */
        /* Enable DMA2, thats where ADC is hooked on -> see Tab 20 (RM00090) */
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_DMA2, ENABLE);

        /* Initialise GPIOs C0 (ADC123_IN10), C1 (ADC123_IN11), C2 (ADC123_IN12)*/
        GPIO_StructInit(&GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
        GPIO_Init(GPIOC, &GPIO_InitStructure);

        /* Initialise DMA */
        DMA_StructInit(&DMA_InitStructure);

        /* config of DMAC */
        DMA_InitStructure.DMA_Channel = DMA_Channel_0; /* See Tab 20 */
        DMA_InitStructure.DMA_BufferSize = 5; /* 5 * memsize */
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; /* direction */
        DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; /* no FIFO */
        DMA_InitStructure.DMA_FIFOThreshold = 0;
        DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
        DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; /* circular buffer */
        DMA_InitStructure.DMA_Priority = DMA_Priority_High; /* high priority */
        /* config of memory */
        DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADCBuffer; /* target addr */
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; /* 16 bit */
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_Init(DMA2_Stream0, &DMA_InitStructure); /* See Table 20 for mapping */
        DMA_Cmd(DMA2_Stream0, ENABLE);

        /* IMPORTANT: populate default values before use */
        ADC_StructInit(&ADC_InitStructure);
        ADC_CommonStructInit(&ADC_CommonInitStructure);

        /* reset configuration if needed, could be used for previous init */
        ADC_Cmd(ADC1, DISABLE);
        ADC_DeInit();

        /* init ADC clock */
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

        /* init ADC */
        ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
        ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
        ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
        ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
        ADC_CommonInit(&ADC_CommonInitStructure);

        /* ADC1 Init: this is mostly done with ADC1->CR */
        ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
        ADC_InitStructure.ADC_ScanConvMode = ENABLE;
        ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
        ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
        ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
        ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
        ADC_InitStructure.ADC_NbrOfConversion = 5; /* 5 channels in total */
        ADC_Init(ADC1, &ADC_InitStructure);

        /* Enable Vref & Temperature channel */
        ADC_TempSensorVrefintCmd(ENABLE);

        /* Configure channels */
        ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_480Cycles);         
        ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_480Cycles); 
        ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 3, ADC_SampleTime_480Cycles);
        ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 4, ADC_SampleTime_480Cycles);
        ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 5, ADC_SampleTime_480Cycles);

        /* Enable DMA request after last transfer (Single-ADC mode) */
        ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);

        /* Enable ADC3 DMA */
        ADC_DMACmd(ADC1, ENABLE);

        /* Enable ADC1 **************************************************************/
        ADC_Cmd(ADC1, ENABLE);

        … /* in main */ 
        TIM_Cmd(TIM2, ENABLE);
As you can see the timer is enabled to feed a signal into the ADC. Upon expiry of the timer period an interrupt is triggered that initiates the ADC. During the conversion the DMA copies the samples into the cyclic buffer. The timer ISR can be used to synchronize the transfer to the main program, by copying the “previous” sample into a global buffer.
void TIM2_IRQHandler() {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        
        /* nothing to do here except for possibly copying DMA buffers of previous conversion */
}
This example shows how to use a timer as clock source to pace the ADC. This configuration is useful for applications that require a data points at periodic intervals. In this post we demonstrated the four basic operating modes of the ADC. We furthermore demonstrated practical modifications of these scenarios that involve an external trigger and data coming from an external pin. We have left out specific concepts such as injected channels or the interleaved operation of channels, which will be discussed in different posts.

References

Sunday, January 27, 2013

Turning a STM32 Evaluation Board into a Programmer and Debugger


The STM32F4Discovery evaluation board is allegedly build to program remote targets. It features a header that that has a debug-out Serial Wire Debug (SWD) configuration. The documentation and examples that exemplify this technique are sparse. The header features the following signals (see UM1472 for further info):
  • Pin1 Voltage from target application (do not use),
  • Pin2 SWD Clock: Connect to SWCLK of the target (PA14 on STM32s),
  • Pin3 GND: Connect to GND of the target (needed to share the signal ground),
  • Pin4 SWD I/O: Connect to SWDIO of the target (PA13 on STM32s),
  • Pin5 NRST: Connect to the target reset pin (RST on STM32s),
  • Pin6 SWO: SWD trace data line (optional and therefore not subject to this post).
If it would be that easy to just connect the signals then this tutorial could stop here. Unfortunately, to get things setup the board has to be modified further. The SWD data lines are obviously used by the STM32F407 that sits on the board. It is easy to have exclusive access to these lines as follows:
·         Remove the jumpers on CN3.
That way thedebugger portion of the discovery board will only share the SWD signals with the SWD connector. 

Furthermore, the debugger shares the reset line of the target with the on-board chip as well. If this line is not cut, the internal circuit of the on-board chip will prevent the signal to be properly used for remote applications. Instead of adding a jumper that would make it easy for people to fix this, the STM guys decided to add a solder bride.
·         Replace the solder bridge SB11 with a jumper as shown in the following.


In order to be safe and avoid adverse affects, one can remove the power to the on-board chip. The STM people added a jumper that can be replaced with an ampere-meter to measure the power consumption of the chip.
·         Remove JP1/PPI to cut the power to the on-board chip.

In order to be sure that I can program other chips of the STM32-family, I used a STM32VLDiscovery board as my guinea pig. As seen in the picture, I shared the signal ground (GND) and provided 3V to the 3.3V pin of the target board from the STM32F4Discovery. The reset line and SWD signals were connected as described above.

All people who have no real operating system (i.e., M$ Windows), can download and use the STLink utility from STM. It provides crude access to the chip that is connected to the SWD port.
·         Select “Target” -> “Connect…”
Watch the status line and confirm that the connected chip matches the outcome you expect. In my case I use the debugger to connect to a STM32VLDiscovery that has a STM32F100 chip.
·         Select “Target” -> “Program”
This allows you to upload an ELF or HEX file to the target board. The programming process should not take longer than a few milliseconds.

For all the advanced people there is OpenOCD for Linux. I am using a development version (v0.7.x) from their git repository. When you build OpenOCD from source (not covered in this article) make sure you enable the STLink support. Shown below is my example file to flash the ELF file into the STM32VLDiscovery.

OpenOCD can be invoked by just calling “openocd” in the directory where the ELF- and the configuration files reside.

## openocd.cfg file for STLINK/v2 to program
# an STM32F1xx chip.
#
# @author Thomas Reidemeister

# Use STLink/V2 protocol
source [find interface/stlink-v2.cfg]

# Use STM32F1x
source [find target/stm32f1x_stlink.cfg]

# Connect to target
init

# Reset target into halt
# Hint: _reset_ clears breakpoint buffer and puts the 
#  system in defined state.
#  The target must be _halted_ to write to flash.
reset halt

# Write the image to the target
flash write_image erase main.elf
# uncomment the line if you want to have things verified
#verify_image main.elf

# execute the target
reset run

# close down OpenOCD
shutdown 

In conclusion, I have shown how to use the debugger portion of the STM32F4Discovery in a debug-out configuration to debug an external target. To make it hard, I picked a different chip from their value line series.
Although I am not a big fan of Windows, the STLink software seems to be a great way to upgrade the firmware of the debugger itself. The reader should note that the STM engineers already use internal pull-ups for the debug circuit of the STM. As a result it is possible to just connect the power supply, ground pins, PA13, and PA14 on a remote target on most STM32 processors. Be sure to check out the “Getting started with XXX hardware development” application notes of your STM32 target to confirm this.

(And a couple more toys to play with :D)