STM32 Discovery Print met ARM Cortex M3

Uit RobotMC.be
Ga naar: navigatie, zoeken
Discovery1.jpg

Het STM32 value line Discovery evaluatie bord is bedoeld om de mogelijkheden van de STM32F100RB te demonstreren. Dit is een krachtige 32 bit ARM Cortex M3 processor. Het bordje is te verkrijgen aan 12 Euro bij onder andere Watterott webshop. Verder heb je nog een geschikte compiler nodig en een USB kabeltje. Ik ben gestart met de de compiler van IAR (32 kb code limiet voor de free edition), maar daarna overgeschakeld naar Coocox. Deze IDE heeft geen codelimiet. Coocox is gebaseerd op Eclipse en de GCC compiler. Andere compilers van Keil of Atollic kunnen ook gebruikt worden.

Links :

Overzicht STM32 VL Discovery

STM32L-Eval

The STM32 value line Discovery evaluation board helps you discover the STM32 value line features and to develop and share your applications. It is based on an STM32F100RB and includes ST-Link embedded debug tool interface, LEDs and push buttons.

Wat zijn eigenlijk de voordelen van een 32 bit processor ?

Een belangrijk verschil is de breedte van de databus. Bij een 32 bit processor kan in één clockcyclus een datawoord van 32 bit worden verwerkt. Bij een 8 bitter moeten uiteraard zulke bewerkingen in meerdere cyclussen gedaan worden. Voor rekenwerk is dus een 32 bitter in het voordeel. Ook de maximale klockfrequentie is meestal een stuk hoger bij deze ARM processoren. De discovery kan aan 24 MHz draaien, maar op 48 MHz schijnt ook nog probleemloos te gaan. Andere processoren van de STM32 familie gaan tot 72 MHz. Een belangrijker voordeel vind ik echter de bijzonder krachtige periferie die in deze processor voorzien is :

  • Een debug verbinding genaamd "ST link" die via USB kan verbonden worden met de PC : Langs deze verbinding kan men de processor flashen. Maar ook kan men stap per stap doorheen het C programma gaan en alle registers en variabelen bekijken. Tevens ziet men hier direct het gecompileerde programma in assembler. Er worden maar 3 pinnen van de processor hiervoor gebruikt. Deze link is voorzien op het discovery board met behulp van een andere STM32. De mogelijkheid bestaat om externe STM32 processors te programmeren. Hiertoe moeten een 2 tal jumpers op het discovery bord verzet worden.
  • Een RTC (real time clock) die via een aparte aansluiting kan gevoed worden : Met een extern 32786 Hz kristal kan de juiste tijd en datum bijgehouden worden. Een 20 tal registers + deze klok blijven in werking als men spanning voorziet aan de aansluiting "bat".
  • GPIO 5 Volt tolerant: Alhoewel de processor loopt op 3.3 Volt, zijn alle GPIO 5 Volt tolerant. Voorwaarde is dat men de ingebouwde pull up/pull down weerstanden niet inschakelt. Ook bestaat de mogelijkheid om de schakelsnelheid van de output poorten in te stellen. De snelste instelling is 50 MHz, de langzaamste instelling 2 MHz. Ik vermoed dat de flanksteilheid hier mee beinvloed wordt. Vrijwel alle pinnen zijn als ingang/uitgang te configureren. De initialisatie is vrij omslachtig : pin initalisatie en per poort de klok instellen.
  • 3 onafhankelijke USART poorten : Te gebruiken voor alle soorten communicatie (ook IRda en SPI).
  • 2 onafhankelijke I2C poorten : beide zowel slave als master te gebruiken.
  • Een vrij in te stellen klokfrequentie : Op basis van één enkel extern kristal (8 MHz) via een multiplyer. Ook wordt er altijd opgetart vanuit de interne klok. Daarna wordt gepoogd om de externe klok in te schakelen. Er worden geen "fuses" gebrukt, deze kunnen dus ook niet meer fout gezet worden.
  • Een ruim aantal 16 bit timers die volledig onafhankelijk van mekaar kunnen werken. Een enkele timer kan bij voorbeeld 4 verschillende PWM uitgangen aansturen. Ik heb dit gebruikt om 4 servo-signalen op te wekken.
  • Een 24 bit systemtimer : an gebruikt worden om op vaste tijdstippen een OS op te roepen.
  • Een 12bit/16 kanaals ADC die aan 1.2 µs kan samplen : Ook is er nog een interne spanningref + temperatuursensor die kunnen gemeten worden.
  • Twee 12bit DAC omzetter : Kunnen ook witte ruis genereren. Kan perfect als basis dienen om een frequentiegenerator te bouwen.
  • Verschillende externe interruptkanalen die aan verschillende ingangen kunnen geschakeld worden.

Creating apps with the IAR Workbench

The IAR Workbench can be downloaded and used for free up to 32Kbyte, which will be ample for most of our applications. To use all the memory of the STM32 you need the full version or the workbench and deep pockets ($3395...$6249). So let's stick with the free version for now :-) After downloading and installing the workbench, you can create a new project via the "Project Menu" for the toolchain ARM.

However it is important that you specify following options (Project > Options... or right click the workspace and select Options...)

  • General Options > Target > Device : ST-STM32F100xB
  • General Options > Library Configuration >check the checkbox “Use CMSIS”
  • C/C++ Compiler > Preprocessor > remove the include directory of CM3 ($PROJ_DIR$\..\..\Libraries\CMSIS\CM3\CoreSupport)
  • Linker > Config > Override Default. Click edit and in the "Vector Table" put .intvec.start at 0x08000000. In "Memory Regions" put ROM Start 0x08000000 End 0x08020000 RAM Start 0x20000000 End 0x20002000
  • C/C++ Compiler > Preprocessor > Additional Include directories :
 $PROJ_DIR$\..\inc
 $PROJ_DIR$\Libraries\STM32F10x_StdPeriph_Driver\inc
 $PROJ_DIR$\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
 $PROJ_DIR$
  • C/C++ Compiler > Preprocessor > Additional Defines :
 USE_STDPERIPH_DRIVER
 STM32F10X_MD_VL
 USE_STM3210E_EVAL
  • Debugger > Setup > Driver = "ST-Link"
  • Debuger > Download > check "Use flash loaders"
  • Debugger > ST-Link > choose "SWD"

Save the project settings

Now copy the needed libraries (they can typically be found in one of the sample programs to your project folder) in a folder called "Libraries". In it you should put the CMSIS and STM32F10c_StdPeriph_Driver folders and files. The first is contains your hardware platform specific files, the second the standard libraries which can by used in your applications. They will allow you to use higher level functions for timers, interrupts, USART, DMA, ADC, ....

Once done you can start creating source files. To add them to the project, rightclick the workspace and select "'Add..."

Via Project you can then Rebuild or Make your prokect. The only good outcome here is "0 Errors, 0 Warnings". No guarantee for success, but definitely the first step

Clocks - De hartslag van onze Mikrocontroller

De STM32F103 heeft verschillende interne taktbronnen. Deze moeten absoluut geconfigureerd worden. Daarvoor gebruiken we de RCC (Reset and Clock Control). Hiervoor zijn er functies gedefinieerd in de ST-Library. Welke takten aan welke bussen liggen vindt je in de documentatie. De controller heeft 2 gescheiden Databussen voor de langzamere delen van de periferie : APB1 en APB2. Deze zijn via bruggen aan de systeembus aangesloten, hier AHB1 en AHB2 genoemd. APB1 kan aan maximaal 36 MHz lopen, APB2 kan aan 72 MHz lopen. Bij het Discovery board zijn deze beiden theoretisch begrensd tot 24 MHz. Belangrijk! Alle periferie die we willen gebruiken, moeten met een klok getakt worden alvorens men ze kan gebruiken. De STM32 word normaal gezien met een extern Quarzkristal van 4 tot 16MHz getakt. Hieruit wordt dan via de interne PLL de gewenste frequentie (tot 72 MHz) gegenereerd.Elke benodigde klok wordt van deze hoofdklok afgeleid. De STM32 kontrollers hebben ook een interen RC-oscillator (8MHz, 1% nauwkeurigheid en 40 kHz RC Oscillator) De interne 8MHz oscillator is meestal nauwkeurig genoeg voor lage UART snelheden. De interne 40 kHz is zeer onnauwkeurig (30 - 60 kHz). De library is gebaseerd op een externe takt van 8 MHz. Het Discovery bord heeft zowel een extern 8 MHz kristal en een 32786 Kristal.

Taktbron kiezen

Na een Reset word altijd opgestart vanuit de interne HSI-Takt (interne 8 MHz Oscillator). Hierdoor is opstart klokfreq. vast. Dit is noodzakelijk om, indien gewenst (boot0 en boot1 pin), de interne bootloader te activeren. Deze interne oscillator kan men niet uitschakelen via "fuses". Aldus is het zeker gesteld dat de STM32 altijd een takt heeft na reset.

HSI - Highspeed Internal Oscillator

Na de opstart kan men de interne Oszillator omkonfigureren. Voor het uit/inschakelen van de HSI gebruikt men de functie RCC_HSI(), met als parameter ENABLE/DISABLE.

Takt Resetten

Na een Reset bevindt de Taktkonfiguratie zich in een gedefinieerde toestand. Dit kan men softwarematig ook bereiken door de functie RCC_DeInit()

HSE - Highspeed External Oscillator (Quarz)

De externe oscillator wordt geactiveerd via de functie RCC_HSEConfig(). Hier kan ook een externe takt mee gekozen worden. Opgelet, de Standard_Library gaat van een 8MHz frequentie uit !!

Setting the System frequency

For our discovery board, the adviced max. System frequency is 24 MHz. But how to define that frequency in the software ? Wel, that is done with the function Systeminit(), and a specific name that is defined in the compiler. Off course, you also can define this name in the right c-file from the standard lib (system_stm32f10x.c), but it is simpler too go for the compiler option. So, look for the compiler option for your project, with IAR you get the next window :

Preprocessor.png

In the box below, you see the defined symbols. The one that is important for the system freq. is the "STM32F10_MD_VL" Actually, this stands for STM32F10_Medium Density_Value Line. This symbol is used in the standard lib for different settings. When you look in the system_stm32f10x.c file, you will see that with this symbol, another symbol will be defined, and that will set at the end the right System freq. for us.

#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
#define SYSCLK_FREQ_24MHz  24000000
#else
 /* #define SYSCLK_FREQ_HSE    HSE_VALUE */
#define SYSCLK_FREQ_24MHz  24000000  
/* #define SYSCLK_FREQ_36MHz  36000000 */
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
/*#define SYSCLK_FREQ_72MHz  72000000 */
#endif

Setting the sys clock timer

A very usefull timer is the sys clock. This is a 24 bit timer which can be used to generate a Interrupt at regular intervals. In the next example, the sys clock wil generate a Interrupt every ms.

/* Systick timer-----------------------------------------------------------*/
/* Setup SysTick Timer for 1 msec interrupts  */
 if (SysTick_Config(SystemFrequency / 1000))
 { 
   while (1);/* Capture error */ 
 }

The ISR function is written in the file "stm32f10x_it.c". Here, it will count some variables that are used in my program. Also the Delay function is counting down in this ISR. Dont forgot to define al the variables that are used in a interrupt volatile !

 void SysTick_Handler(void)
 {
  if (Delay != 0x00) Delay--;
  motor_timer++;
  stopwatch0++;
  stopwatch1++;
  stopwatch2++;
  stopwatch3++;
 }   

USART : Serial communication

The STM32 has 3 different USARTs which can be used for different purposes : serial communication, irDA, SPI. Using the serial line on an STM32 can be done in multiple ways : polling, DMA, interrupt driven.

USART by active waiting

In this snippet I describe the most basic - and least efficient - method being polling. We send 1 byte and wait until the line is free for the next write. Reading is similar : check if something is available and if yes, read the byte. I used this in combination with a Bluetooth module of DFRobots which acts as a virtual COM port. This allowed me to build a cheap wireless interface between my robot and my laptop. The following code is to be put somewhere in your program. I typically put everything in subroutines/library which I then call from the main program, but you can opt to copy-paste it in your main program, of course.


At the top of the file : include the standard library routines

#include "stm32f10x_usart.h"

Preparation : define the USART clock and make sure the pins are in the correct mode (Tx = PA9 / Rx = PA10)

/* Enable USART1 */
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
 /* Set USART1 Tx on PA9 */
 GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_9;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_Init(GPIOA, &GPIO_InitStructure);

 /* Set USART1 Rx on PA10 */
 GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_10;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIO_Init(GPIOA, &GPIO_InitStructure);

Initialisation of the USART (115200 baud, 8 bit, 1 stopbit, no parity, no flowcontrol)

  USART_InitTypeDef 	 USART_InitStructure;
  USART_ClockInitTypeDef USART_ClockInitStructure; 
 
  USART_ClockStructInit(&USART_ClockInitStructure);
  USART_ClockInit(USART1, &USART_ClockInitStructure);
  USART_InitStructure.USART_BaudRate = 115200;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_Mode = USART_Mode_Tx;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
 
  //Write USART1 parameters
  USART_Init(USART1, &USART_InitStructure);  

Write one byte and wait until done

 uint8_t outgoing;

 USART_SendData(USART1, outgoing);
 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) ; 

Check if a byte is available and if so, read it

 char incoming ;

 if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){ 
    incoming = (USART_ReceiveData(USART1)); 
 }

USART over DMA channel

There is a more efficient way to send data to the UART, and that is over DMA. The processor don't have to wait until the USART buffer is empty, you just put al your data in a buffer and you start the DMA channel. First off all, how to get your data in the buffer ? Well, there is a well defined sprintf() function in the <stdio.h>, which will change a variable into the corresponding asci char, and in the format you like. This function will fill your buffer, and also will return the exact number of bytes in the string. An that is exactly what we need ! The reason is that the DMA needs the number of data that should be transferred in advance. So, with this sprintf(), you fill the buffer with a string, and you know the length of the string. I tried it out with a string from about 100 characters, and over DMA it took less then 200µs. Active waiting (38800 baud) took more then 20 ms...

Init of the DMA channel :

 char bufferTx[254] = "USART DMA Polling: USARTy -> USARTz using DMA";
 char bufferRx[127] = "USART DMA Polling: USARTz -> USARTy using DMA";
 void DMA_Configuration(void)
 {
  DMA_InitTypeDef DMA_InitStructure;
  /* USARTy TX DMA1 Channel (triggered by USARTy Tx event) Config */
  DMA_DeInit( DMA1_Channel4);  
  DMA_InitStructure.DMA_PeripheralBaseAddr =  0x40013804 ;//USART1_DR_Base
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)bufferTx;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
  DMA_InitStructure.DMA_BufferSize = 10;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init( DMA1_Channel4, &DMA_InitStructure);
  }
 /* Enable USARTy DMA TX request */
 USART_DMACmd(USART1,USART_DMAReq_Tx, ENABLE);
 /* Enable USARTy TX DMA1 Channel */
 DMA_Cmd(DMA1_Channel4, ENABLE);
 USART_Cmd(USART1, ENABLE);

A function to start the transfer from the buffer over DMA

  void Start_DMA(int string_length){
  //Wait until USARTy TX DMA1 Channel Transfer Complete 
       if (DMA_GetFlagStatus(DMA1_FLAG_TC4) == SET){
         DMA1_Channel4->CCR &= ~DMA_CCR_EN;      // disable DMA
         DMA1_Channel4->CMAR  =  (uint32_t)bufferTx;//(unsigned)
         DMA1_Channel4->CNDTR = string_length;//strlen(hallo);
         DMA1_Channel4->CCR |= DMA_CCR_EN; 
       }
  }

And here the sprintf() usage to fill the buffer (several sprintf are used before the DMA starts)

    n=sprintf(bufferTx," PWM1: %d ",soll_speed1);
    a=sprintf(&bufferTx[n]," PWM2: %d ",soll_speed2);n=n+a;
    a=sprintf(&bufferTx[n]," speed1: %d ",speed1);n=n+a;//&buffer[n] = array buffer vanaf element n
    a=sprintf(&bufferTx[n]," speed2: %d ",speed2);n=n+a;
    a=sprintf(&bufferTx[n]," Pos1: %d ",encoder1);n=n+a;
    a=sprintf(&bufferTx[n]," Pos2: %d ",encoder2);n=n+a;
    a=sprintf(&bufferTx[n]," ADC0: %d ",ADC_ConvertedValueTab[4]);n=n+a;
    a=sprintf(&bufferTx[n]," ADC1: %d ",ADC_ConvertedValueTab[5]);n=n+a;
    a=sprintf(&bufferTx[n]," Temp: %d ",temp_ref);n=n+a;
    a=sprintf(&bufferTx[n]," Vref: %d ",vref);n=n+a;
    //Tot hier duurt het 210 µs      
    Start_DMA(n);//heel het zootje naar DMA kanaal en starten

It is better to use the sprintf(), as the printf() because sprintf() is a standard function and don't need the large overhead from printf() !printf() uses a "FILE descriptor") If you want to use printf(), then you need to set a option the IAR compiler use "Full library"

USART Rx over Interrupt

Reveiving bytes from the USART can easily be done with use of a Interrupt. Every time when a byte is received over the USART, it is placed in the USART receive buffer. You have to read out this buffer before the next byte will arrive, otherwise it is lost. Therefore, the use of the USART interrupt is the best way to go. In this interrupt, you can easily place the received byte in a larger string buffer, which you can handle later in the main loop. Next config is suitable for Receiving with Interrupt, and sending over DMA. First the configuration of the USART (dont forgot the GPIO AF config for pin Tx and Rx) :

void USART_Config (void)
{
  /* USARTx configured as follow:
        - BaudRate = 384000 baud  
        - Word Length = 8 Bits
        - One Stop Bit
        - No parity
        - Hardware flow control disabled (RTS and CTS signals)
        - Receive and transmit enabled
  */
  USART_InitTypeDef USART_InitStructure;
  USART_InitStructure.USART_BaudRate = 38400;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx |USART_Mode_Tx; 
  USART_Init(USART1, &USART_InitStructure);//USART configuration
   /* Enable the USART1 Receive Interrupt */
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

You also have to configure the USART interrupt in the NVIC-config :

void NVIC_Configuration(void)
{
   NVIC_InitTypeDef NVIC_InitStructure;
  /* Enable the USART1 Interrupt */
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn  ;
  NVIC_Init(&NVIC_InitStructure);
}

And then we do some string handling in our ISR : The byte in the USART Rx buffer is copied into the string bufferRx[], we check for the '\r' byte (Enter), and if we see this '\r', the string from bufferRx is copied into a other string, and we can again fill the bufferRx[].To handle a buffer overflow (bufferRx[127] is only 127 bytes), the interrupt is blocked if we receive more then 125 bytes without the Enter. To notice the main loop that we have received a string with '\r' at the end, the printRx is set to 1. Because the use of the strcpy(), we have to include <string.h>

void USART1_IRQHandler(void)
{
  if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
  {
    /* Read one byte from the receive data register */
    bufferRx[RxCounter++] = USART_ReceiveData(USART1);
    USART_SendData(USART1, bufferRx[RxCounter-1]);//echo
    if(bufferRx[RxCounter-1]=='\r'){
       strcpy(receiveRx, bufferRx);
       RxCounter=0;printRx=1;
      }
    if(RxCounter > 125)
    {
      /* Disable the USART1 Receive interrupt */
      USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
    }
  }
 }

ADC : scan ADC inputs over DMA

The STM32 has a powerful ADC, which can be connected to 16 analog channels. There is also a internal temperature sensor, and a reference voltage that can be measured by the ADC. A neat feature is the calibrating routine : this routine measures the linearity of the adc and adjust the capacitor bank. With every power up, it is advised to run this routine. A very efficient way to measure different channels is the "regular mode with DMA". In this mode, you can choose the channel and the sample time for each conversion, and the result of every conversion is send over DMA to a choosen memory space (array). If you wish, the end of the complete sequence can be known over a interrupt. The advantage is off course that you dont waste processor time with polling the ADC. One small problem that I noticed with the discovery board is the analog reference. As there is no separat pin for the voltage reference, the normal VCC is used. This VCC comes from a 3.3 voltage regulator, but there is a diode in series with it. That means that the VCC voltage is around 3 Volt, but changing with the temperature. The lower the temperature, the more the voltage drop over the diode and vice versa. To overcome this problem, you can bridge this diode, or you can measure the internal Voltage ref and do a calibration on every measurement. Here you see the config. for the ADC over DMA. Dont forget the GPIO and the Clock config ! The max clockfreq. for the ADC is 6 MHz, so we have to set the prescaler for the ADC clock.

/* ADC1 configuration for regular channel with DMA---------------------------*/
/* 4 ADC channels are configured, but a circular 16 word-DMA Buffer--------- */
/* Conversie = 12 cycles + sample time, hier 239 = 252 cycles--------------*/
/* Enable the right Clocksource for the ADC--------------------------------*/
/* PCLK2 is the APB2 clock */  /* ADCCLK = PCLK2/4 = 24/4 = 6MHz*/ 
  RCC_ADCCLKConfig(RCC_PCLK2_Div4);
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_ADC1, ENABLE);
/* Init the ADC------------------------------------------------------------*/ 
 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
 ADC_InitStructure.ADC_ScanConvMode = ENABLE;
 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
 ADC_InitStructure.ADC_NbrOfChannel = 4;
 ADC_Init(ADC1, &ADC_InitStructure);
 /* ADC1 regular channels configuration */ 
 ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 1, ADC_SampleTime_239Cycles5); //C4
 ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 2, ADC_SampleTime_239Cycles5); //C5
 ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 3, ADC_SampleTime_239Cycles5);//Vref = 1200 mV
 ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 4, ADC_SampleTime_239Cycles5);//Temp Sensor = 1410 mV@25 C°
   /*Init of the DMA Channel---------------------------------------------------------*/
 ADC_DMACmd(ADC1, ENABLE);// Enable ADC1 DMA 
 ADC_Cmd(ADC1, ENABLE); // Enable ADC1 
 ADC_ResetCalibration(ADC1); // Enable ADC1 reset calibaration register  
 while(ADC_GetResetCalibrationStatus(ADC1)); // Check the end of ADC1 reset calibration register 
 ADC_StartCalibration(ADC1);// Start ADC1 calibaration  
 while(ADC_GetCalibrationStatus(ADC1)); // Check the end of ADC1 calibration   
 ADC_TempSensorVrefintCmd(ENABLE);// Enable Vrefint channel 17
 ADC_SoftwareStartConvCmd(ADC1, ENABLE);// Start ADC1 Software Conversion 

/* DMA1 channel1 configuration ----------------------------------------------*/

/* DMA clock enable */
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 
/* In deze config : 16 metingen op 660 µs = 41.25 µs per conversie-----------*/
 DMA_DeInit(DMA1_Channel1);
 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValueTab;
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
 DMA_InitStructure.DMA_BufferSize = 16;
 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
 DMA_Init(DMA1_Channel1, &DMA_InitStructure);
 /* Enable DMA1 Channel1 */
 DMA_Cmd(DMA1_Channel1, ENABLE);

With the ADC config, the regular mode is choosen. 4 channels are configured, every channel with a sample time of 239 ADC cycles. Dont forgot the start the temp. and voltage ref if you use them. The circular DMA buffer is 16 words, which means that you will find the values for channel 1 (ADC 14, pin C4) in the next array positions : ADC_ConvertedValueTab[0],ADC_ConvertedValueTab[3],ADC_ConvertedValueTab[7], ADC_ConvertedValueTab[11]. If you also want a interrupt to know if the complete cycle is done, next config can be used :

 * @brief  Enables or disables the specified DMAy Channelx interrupts.
 *     @arg DMA_IT_TC:  Transfer complete interrupt mask
 *     @arg DMA_IT_HT:  Half transfer interrupt mask
 *     @arg DMA_IT_TE:  Transfer error interrupt mask
 */
 DMA_ITConfig(DMA1_Channel1,DMA_IT_TC, ENABLE);

In the interrupt handler file stm32f10x_it.c we program the next handler :

void DMA1_Channel1_IRQHandler (void){
 GPIOC->BSRR = 1<<8 ; // LEDC8 ON
 DMA_ClearITPendingBit(DMA1_IT_TC1);
 DMA_ClearFlag(DMA1_FLAG_TC1);
 GPIOC->BRR = 1<<8 ; // LEDC8 OFF

}

DAC : Generating analog voltages with a resolution of 12 bit

The DAC on the STM32 can be used in several ways. Standaard, it is possible to generate a "Escalator", a "Triangle Wave", and a noise generator, and with a lookup tabel, you can generate any waveform that wil fit in the memory ! The DAC can be feeded over DMA, with a speed from over 1 MHz. I tried it with a 32 position array of a 12 bit Sine wave numbers. Th highest frequency was a Sinewave from 43 kHz, which means that the DAC was feeded with a frequency from 46*32= 1.472 MHz !! The noise generator is based on a circular shift register, that is "OR" together with specific positions in the same register. This will generate a quasi random number. It is not exactly random, because the pattern is always the same, but it takes a very long time before it will be repeated.

 Noise generation.png

Another feature is the build in "Output buffer" on the DAC. This wil lower the output impedance, so you direct can connect a load the the DAC. Off course, this comes with a small disadvantage : the amplifier will decrease the output range with about 50 bits on upper and lower side. To test this, a fed the DAC with a value from 0 to 4095 bit, will measuring at the same time with the ADC. As both devices are 12 bit, in a ideal world the difference between both values should be zero over the entire range. I was very surprised that in the real world, the difference without the output buffer was less then 3 mV, with output buffer enabled, it was about 50 mV, although the linearity was still very good !

 DAC ADC.png

Generating servo pulses with the timer in PWM-mode

The STM32 has a lot of 16 bit timers, so why not use one of them to generate servo-pulses. A servo puls has a pulswidth from 1000 to 2000 µs, which is repeated every 20 ms. The more sophisticated "digital servo's" use the same width, but the pulses are repeated much faster. With a 16 bit timer, this can be easily done in the PWM-mode. First, we set the Timer frequency to 1 MHz. This gives us a resolution of 1 µs, which is more then sufficient for a analog servo. Then, we set the max counter value to 19.999. This means that after 20 ms, the counting start again of zero. Then, we just have to set the TIM1-CCR1 value to the right servo-pulswidth. The luxury with the STM32, is that TIM1,2, 3 en 4 each have 4 channels connected to a GPIO. With1 Timer, we can generate 4 different servo-pulses ! The 4 channels if TIM1 are connected to GPIO PA8,PA9, PA10 and PA11. It is theoretically possible to remap these channels of TIM1 to another port, but not on this 64-pin package. The remap will connect the channels to port E, which is not available on the Discovery... Off course, it is a 3.3 V output, but my servo's worked fine with it. Do not forgot the GPIO and Clock config. !

 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
 TIM_OCInitTypeDef  TIM_OCInitStructure;
 /* Time base configuration TIM1*/
 TIM_TimeBaseStructure.TIM_Period = 19999;//PWM freq. = 1MHz/20000 = 50Hz
 TIM_TimeBaseStructure.TIM_Prescaler = 23;// Timer loopt aan 24 MHz/24 = 1MHz
 TIM_TimeBaseStructure.TIM_ClockDivision = 0;
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
 /* PWM1 Mode configuration: TIM 1, Channel1 */
 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
 TIM_OC1Init(TIM1, &TIM_OCInitStructure);
 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
 /* PWM1 Mode configuration: TIM 1, Channel4 */
 TIM_OC4Init(TIM1, &TIM_OCInitStructure);
 TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);
 TIM_Cmd(TIM1, ENABLE);
   /* TIM1 Main Output Enable */
 TIM_CtrlPWMOutputs(TIM1, ENABLE);

The servo-pulses are now set with :

TIM1->CCR1=1500; //test 1.5 ms voor servo 1
TIM1->CCR4=1825; //test 1.825 ms voor servo 4

I2C on the STM32

The STM32 on the Discovery board has 2 I2C interfaces. Both have the capability to act as slave or as master. The first interface, SCL is on pin B6, SDA on pin B7. The second has SCL on pin B10, SDA on B11. These pins must be configured as "Open drain". In the GPIO Library, you have to use the "GPIO_Mode_AF_OD" mode. Off couse, also the I2C has its own clock. The library has a lot of functions to handle I2C, but you still have to write some code to get it running. I wrote some simple code to test for a slave : First the init for the second I2C (dont forgot the clock and GPIO init)

 /* I2C2 configuration ------------------------------------------------------*/
 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
 I2C_InitStructure.I2C_OwnAddress1 = I2C2_SLAVE_ADDRESS7;
 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
 I2C_InitStructure.I2C_ClockSpeed = ClockSpeed;
 I2C_Init(I2C2, &I2C_InitStructure);
  /* Enable I2C2  ----------------------------------------------------*/
 I2C_Cmd(I2C2, ENABLE);

Now some very simple code that test a Slave adresses. If the slave answer, the adress is written in a global variable

 void Slave_Read (uint8_t Slave)
 {
 I2C_GenerateSTART(I2C2, ENABLE);// Send START condition 
 while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT));//Test on EV5 and clear it   
 I2C_Send7bitAddress(I2C2,Slave, I2C_Direction_Transmitter);// Send Slave address for write
 delay(2); //wait for ack from slave
 if(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))//Test on EV6 and clear it
 {GPIOC->BSRR = 1<<9 ;Data[6]=(Slave-1);}// Disable Acknowledgement
 else  {GPIOC->BSRR = 1<<25 ; } 
 I2C_GenerateSTOP(I2C2, ENABLE);// Send STOP condition    
 }

RC5 or SIRC infrared remote controle receiver with the STM32

On the site of ST, I found an application note in which a detailed description of the infrared RC5 and SIRC protokol : AN3174. As nowadays, RC5 remote controls are hard to find (it was used in Philips devices), you have a better chance to find a Sony Remote with the SIRC protokol. And even better, you can donwload a library for easy implementing this on your discovery board Library + RC5. A free timer and an external interrupt is needed as resources. As you can initialize almost every pin as interrupt, this is a flexible way to implement RC5 or SIRC to your board. The hardware is a TSOP 38 kHz receiver, a capacitor and a resistor. The 5 V version can be used, as the output from the TSOP is pulled low when active.