Programmering STM32

From Chalmers Robotförening

Denna sida innehåller information om vad man bör tänka på när man programmerar STM32 (och delvis generellt ARM). Notera att denna sida endast handlar om att skriva kod, inte om hur man får programmet att bygga osv. Detta tas upp i artiklarna Förberedelse programmering STM32 och Byggprocess ARM. Ifall du stöter på problem eller hittar andra typer av "konstiga" fel när du kodar, skriv gärna upp dom här och fyll på Felsökningslistan

Peripheral-bibliotek

Det finns två huvudsakliga peripheral-bibliotek (kod-bibliotek som innehåller användbara hjälpfunktioner och definitioner för att styra olika delar av processorn) för STM32: STs officiella (kallad stdperiph) och libopencm3. IntroARM är byggd på stdperiph. Nackdelen med stdperiph är att den har en lite tråkig kommersiell licens. Du är fri att använda den i egna projekt, men du får inte använda den kommersiellt hur som helst. Libopencm3 å andra sidan är GNU LGPL3, som är lite trevligare att använda kommersiellt. Det mesta på denna sida är byggt kring stdperiph, men de flesta sakerna är applicerbara för libopencm3 också.

Tillvägagångssätt för programmering

Innan du kan sätta och och börja koda för STM32 behöver du sätta upp mjukvara för att kunna kompilera koden. Ifall du inte har gjort det, läs Förberedelser för programmering av STM32.

Dokumentation för STM32

Här är en länk till STs standardlib (det är svårare att hitta än man kan tro...). dessa dokument vill du ladda hem, och läsa i för att förstå hur just den funktion du vill ha fungerar. reference sheet datasheet disovery board datasheet

Tips om Eclipse

Filstruktur i projekt

i src lägger du koden du skriver, som slutar på .c

i lib finns bibliotek, främst stdlib från ST, men du kan även ta hem andra bibliotek i framtiden

i inc lägger du alla .h filer

makefile är den fil som läses av programmet make varje gång du trycker på kompilera i Eclipse. Det är ungefär som en göralista som styr vad som ska utföras av make, för att alla filer ska kompileras, allt ska länkas etc. Den skriver du antingen sjläv (baserat på en existerande såklart) eller så låter du Eclipse generera den åt dig.

Här behöver du lägga till nya filer du skriver under SRC, annars kommer inte make ta med dem i kompileringen

kompilera i eclipse

skicka över med st-link program

Testprogram för introARM

Debugging

Blinka med LEDs och UART.

Skriva C-kod

I microcontrollers programmerar man nästan uteslutande med C (även om det förekommer lite assembler här och där och ibland mer exotiska). Därför behöver man kunna grunderna i C för att utveckla för sin microcontroller.

Grunderna

Är du ny? Läs lite i C-bok eller läs exempelkod!

Länktips till online-tutorials

Vill du hitta fler tutorials? googla efter c programming tutorial.

Kodstilar

Det finns lika många olika kodstilar som det finns kodare. Många av dessa argument är ett religiöst krig, så var vaksam. Du kan trampa på minerad mark. Här är några vanliga regler och tips:

  • Ska man ha måsvingen ( { ) på samma eller nästa rad?
    • Det är hugget som stucket. Vissa föredrar att ha det på samma rad och vissa föredrar nästa. Du ska dock INTE:
      • Indentera måsvingen.
      • Ha en tom rad innan måsvingen.
      • Definiera BEGIN som { och END som } (ja, detta förekommer och är bara hemskt).
  • Måste man alltid ha måsvingar, även om min kod i en if-sats/loop är bara en rad?
    • JA! Du skall ALLTID ha måsvingar! Någon gång kommer du att komma på att du behöver någon liten extra kodrad i din if-sats/loop och då kommer du glömma måsvingarna och då kommer du bli ledsen.
  • Hur bör man indentera?
    • Man SKALL indentera. Läsbar kod är bra kod och indentering ökar läsbarheten. Det är också belagt med dödsstraff att indentera med mellanslag (se till att ställa in din utvecklingsmiljö på att inte generera mellanslag när du trycker på tab). Man indenterar med tab.
  • Hur namnger man funktioner och variabler?
    • Här finns det ett antal olika stilar. Vad som är viktigt är att skriva tydlige och beskrivande namn. Din utvecklingsmiljö har ofta autokomplettering. Variabler bör ha namn längre än 3 tecken. Nedan följer några vanliga namngivningskonventioner:
      • Camelcase, vilket innebär att du namnger dina variabler/funktioner som GetSomeValue(); och int someValue;. Vanligt är att man börjar med stor bokstav på funktioner och har liten bokstav på variabler.
      • Med understreck, vilket innebär att du namnger dina variable/funktioner som get_some_value(); och int some_value;. Här kan man även använda stora bokstäver, men det är ovanligt.
      • Interna (typ bibliotek) variabler och funktioner börjar ofta med _, t.ex. _delay_ms(); eller _errno.
  • Kommentarer
    • I bästa fall ska kommenterar inte behövas om man skriver bra kod. Men det kan ändå vara bra att dokumentera ungefär vad varje funktion gör och vad varje in-parameter är.
  • Språk (Alltså naturligt språk)
    • Vanligen skriver man kod på engelska, även kommentarer. Gör det. Det ser så dumt ut att skriva kod/kommentarer på andra språk.

Kod i microcontrollers

En microcontroller är väldigt mycket mindre kraftfull än en riktig dator (mindre minne, långsammare processor). Du har inte heller samma möjligheter för I/O eller debugging. Här är några saker att tänka på:

  • Undvik flyttal
    • Din dator har en flyttalsprocessor (FPU) som är snabb på att beräkna flyttal. Din microcontroller har oftast inte det (ARM Cortex-M4 har 32 bit FPU). Därför bör du undvika att använda flyttal om du kan och istället arbeta i mindre enheter (arbeta t.ex. i millisekunder, millimeter etc). Om du måste använda flyttal, fundera ifall du behöver double precision. För de flesta fall räcker det med single precision.
  • Inget (eller väldigt begränsat) OS
    • Din microcontroller har inget stort OS. Ibland kan du använda ett litet OS (t.ex. ChibiOS eller TinyOS). Du har med andra ord ingen som sköter saker åt dig automatiskt, så du får göra allt själv.
  • Undvik standardbibliotek
    • Det finns ett antal standardbibliotek för c (t.ex. stdio, string, math etc). Dessa bibliotek är generellt sett ganska tunga minnesmässigt och exekveringsmässigt. Det är inte heller säkert att du har stöd för det. Om du ska använda dem, gör detta med försiktighet och vara medveten om att dessa inte är designade för microcontrollers. Det finns motsvarande implementationer som är mer lämpade för microcontrollers (t.ex. xprintf eller fatfs av elm-chan).
  • Tänk på strömförbrukningen
    • Detta är viktigt ifall du ska göra någonting som ska gå på batteri och som ska räcka länge utan laddning. Då kan det vara värt att kolla på strömsparningsfunktioner i microcontrollern, t.ex. klocka ner den, sätta den i sleep och att vakna på vissa interrupts etc.

Skriva kod för stm32

processorer med en ARM kärna skiljer sig från microprocessorer från Atmel och PIC i grund och botten i att .... kärna från arm, periferienheter från ST

Structer

Man skriver inte i register, utan man använder ett abstraheringslager med funktioner och structer etc så bör man inte ändra direkt i register

Klockor

Det finns en uppsjö av klockor i STM32, vilket anges i databladet. se "karta"(?)

Exempelkod

Nedan följer en del exempelkod baserat på stdperiph. Dessa exempel är primärt riktade mot IntroARM eller STM32F4Discovery, men kan självklart appliceras generellt. Ytterligare exempelkod finns i stdperiph, men denna är mer generell.

GPIO

Med interrupt

Timer

PWM

UART

Felsökning

Om du setat länge med ett problem finns det några standardmisstag om man brukar göra. Kolla igenom följande checklista så kanske du hittar ditt problem. Ifall du stöter på ett konstigt problem som var svårt att lösa så lägg gärna med det i listan, så att nästa person som stöter på problemet får det lite lättare. Det är även bra som minnesregel till dig själv. Här ges även lite allmänna tips på felsökning:

Bättra på lödningar

Ingen kod är bättre än den hårdvara den körs på. Bättra på dina lödningar och mät så att signalen kommer fram.

Bygg rent!

Många problem kan komma av att det finns gammal kod kvar som kompilatorn inte tycker den ska bygga igen (typiskt om du bara uppdaterat en h-fil). Om du kör clean på ditt projekt och sedan bygger igen kanske detta löser en del problem.

Har du givit klocka till peripheral?

Alla peripherals måste ha klocka. Det första man bör gör när man initar någonting peripheral är att anropa funktionen RCC_APBxPeriphClockCmd(RCC_APBxPeriph_PPPPP,ENABLE) där x är den buss din peripheral sitter på och RCC_APBxPeriph_PPPPP är makrot för den peripheral du ska slå igång. Det finns även för RCC_AHB-bussen också. GPIO sitter på APB2 exempelvis. Notera att alla moduler som är inblandade ska ha klocka. Exempelvis om du ska göra PWM så måste du ge klocka både till GPIO, GPIO_AF och timern.

Är du säker på att det är rätt pinne? Har du skrivit rätt Port-namn i koden?

Du har säkert skrivit fel. Dubbelkolla. Visa för någon annan.

Har du kopplat alternate function till pinnen?

AF till pinnen kopplas med funktionen GPIO_PinAFConfig(GPIO_PORT,GPIO_PinSource,GPIO_AF). Info finns i filen GPIO.c.

Har du kört din init-funktion?

Nej, det har du inte. Kolla igen.

Har du enablat din peripheral?

Detta gör man med Periph_CMD(Enable) (t.ex. TIM_Cmd(TIMx,ENABLE), SPI_Cmd(SPIx,ENABLE) eller USART_Cmd(USARTx,ENABLE)).

Min PWM funkar inte!

Notera att för vissa timers (TIM1, TIM8, TIM15-17 på STM32F1xx och endast TIM1,8 på F4) så måste man anropa en extra funktion som sätter igång PWM. Detta görs med funktionen TIM_CtrlPWMOutputs(TIMx,ENABLE). Denna funktion kan också användas för att stänga av PWM på pinnen (vilket kan vara smidigt ibland)

Kontrollera även feldatabladet, t.ex. på TIM1, utgångskomparator kanal 1 på STM32F100 så krockar timern och UART1 vilket gör att timern inte kan användas om uarten används.

Min pinne funkar inte!

Är din pinne PB3, PB4, PA13, PA14 eller PA15? Detta är JTAG/SWD-pinnarna och dom är som standard denna funktion och INTE GPIO. Dessa måste remappas innan dom kan användas som GPIO. Detta görs med funktionen GPIO_PinRemapConfig(GPIO_Remap_SWJ_xxx,ENABLE), där xxx finns som följande options: NoJRST som låser upp PB4, JTAGDisable som låser upp PB3, PB4 och PA15 samt Disable som låser upp alla pinnar (ja, det ser skumt ut när man anropar GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE), men det är korrekt).

  • Notera 1: Eftersom GPIO på dessa pinnar är tekniskt sett en Alternate Function så måste AFIO-ha klocka innan man utför remapping.
  • Notera 2: På STM32F4xx så finns det ingen remap alls. Där sköts detta enbart via AF-controllern (men man måste fortfarande välja GPIO som AF i STM32F4xx).

Den hänger sig i interrupt!

Det finns många saker som kan bli fel på interrupt. Det vanligaste är:

  • Inte korrekt initad interruptvektor. Du måste ha den funktion som svarar mot din interruptvektor (där du hamnar vid interrupt). Dessa står i startup-filen (.s-filen). Dessa har typiskt formatet void PERIPH_Handler(){ do_interrupt_stuff } där PERIPH är den peripheral du har.
  • Har du kvitterat (clearat) ditt interrupt? Detta bör göras så tidigt som möjligt i interruptet (men kan göras senare vid avancerade applikationer). Det finns inbyggda funktioner för detta. Exempelvis, för external interrupt (pinne som ger interrupt) heter den EXTI_ClearItPendingBit(EXTI_Linex), där x är vilket nummer. Timer-funktionen är TIM_ClearItPendingBit(xxx).
  • För vissa interrupt måste du också läsa av interruptkällan (typiskt interrupts som flera källor kan trigga, exempelvis EXTI_Line som kan triggas av flera pinnar) och cleara rätt interrupt-källa.
  • Att blinka LEDs eller skriva sätta pinnar kan vara bra att göra i olika steg i ditt interrupt (och ditt program generellt sett). Då ser du hur långt den kommer. Använd LED_SET() som "prob" i programmet.

De_init-funktionen

Många peripherals (t.ex. DMA och I2C) har en de_init-funktion (t.ex. DMA_DeInit(DMA1_Channel4)). Den nollställer alla inställningar på peripheralen och bör köras innan man initierar peripheralen, annars finns det risk att den inte fungerar.

Logikanalysator/Oscilloskop

Denna är helt ovärderlig vid felsökning av kommunikationsprotokoll. Om du setat mer än 5 minuter med ditt problem med UART/SPI så hämta logikanalysatorn och koppla upp den. Det tar inte många minuter och löser oftast ditt problem. Den är mycket enkelt att få igång. Mer info finns här. Annars är oscilloskopet din vän. Samma där, det tar inte lång tid att ta fram och sätta upp det.

Min timer dummar sig och sätter sig inte till korrekt idle-värde

P.g.a att ST har gjort fel i sitt bibliotek fungerar inte TIM15 kanal2. De extrafunktioner som har med break att göra (t.ex. idle state) är inte korrekt gjorda. I funktionen TIM_OC2Init i stm32f10x_tim.c ska du lägga till TIMx==TIM15 i if-satsen för att tillåta att de avancerade funktionerna kan användas. Det ska se ut som "if((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM15))"

Jag lyckas inte trigga min peripheral (t.ex. ADC) på en timer

För att man ska kunna trigga en peripheral med en timer (t.ex. compare match) så måste man sätta igång output compare-modulen hela vägen. Du behöver inte koppla till en fysisk pinne, men allt utom det måste göras, annars fungerar det inte. Ett tips kan vara att trigga interrupt på samma capture/compare för att se om timern kör. Se följande exempelkod (OBS; sätter ej upp ADC helt komplett): <syntaxhighlight lang="c"> //Setup timer2 TIM_TimeBaseStruct.TIM_Prescaler = 0; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStruct.TIM_Period = SystemCoreClock/freq; TIM_TimeBaseStruct.TIM_ClockDivision = 0; TIM_TimeBaseStruct.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);

//Init output compare, since ADC trigger cannot be done without it. This is not mapped to any output pin OC_InitStruct.TIM_OCMode = TIM_OCMode_PWM1; OC_InitStruct.TIM_OutputState = TIM_OutputState_Enable; OC_InitStruct.TIM_Pulse=SystemCoreClock/(2*freq); OC_InitStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC2Init(TIM2,&OC_InitStruct);

//Init ADC ADC_DeInit(ADC1); ADC_InitStruct.ADC_ContinuousConvMode=DISABLE; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2; ADC_InitStruct.ADC_NbrOfChannel = ADC_NOF_CHAN; ADC_InitStruct.ADC_ScanConvMode = ENABLE; ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; ADC_Init(ADC1,&ADC_InitStruct);

//Enable external trigger ADC_ExternalTrigConvCmd(ADC1, ENABLE);

</syntaxhighlight>

Mitt DMA-Interrupt fungerar inte

Tänk på att aktivera DMA-kanalen innan interruptet aktiveras. T.ex. <syntaxhighlight lang="c"> DMA_Cmd(DMA1_Channel4, ENABLE); DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE); USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); </syntaxhighlight>

Min I2C fungerar inte

Varför använder du I2C? Det är ju jättedåligt. Vidare är STs I2C väldigt trasig. Den fungerar bäst med DMA. Kolla mer information i Erratan.

Errata sheet - Men jag är säker på att jag har gjort rätt!

Om det inte fungerar och du är helt säker på att du gjort rätt så kan det vara fel på processorn. Kolla därför igenom Erratan för din processor. Man hittar dom på STs hemsida på sidan om din processor (bara sök på artikelnumret) under Design Resources och scrolla ner till errata. Här är några errata för vanligt använda processorer:
STM32F100 Errata
STM32L1 Errata

Parkodning (gristricket)

En vanlig metod för felsökning är s.k. parkodning (eller gristricket som Sternå kallar det efter Askes kollega). Parkodning innebär att man kodar tillsammans med någon eller visar sin kod för någon annan och berättar vad den gör. Många fel upptäcks på detta sätt. Gristricket kommer av att Askes kollega hade en lite plastgris på sin skärm, och när han stötte på ett jobbigt problem så berättade han för grisen hur koden funkade och vad den gjorde, och hittade på så sätt felet. Att berätta om sin kod högt ska inte underskattas.

Modeller

Det finns massa olika modeller.


Länkar

Vart hittar man datablad? Vart hittar man libbet? Vart köper man dom?