https://www.tme.eu/cz/katalog/?mapped_params=2%3A39%3B1752%3A1593880%2C1593875%2C1593879%3B&utm_source=MCU&utm_medium=banner&utm_campaign=2019-12_IXYS_MTM-2120_936x60_CZ
Jak pájet? Tipy a triky, technika pájení, vše ukázané názorně. Užitečné tipy a nápady pro dobré pájení pomocí jednoduchých nástrojů.



Raspberry Pi 4 bola vypustená len pred niekoľkými týždňami s novými možnosťami do 4 GB pamäte RAM, podporou pre 4K displeje a pripojením cez USB-C. Používatelia potrebovali len pár týždňov na to, aby objavili vážny problém s USB-C. Je to pomerne jednoduchý problém, ktorý sa dá ľahko vyriešiť v nových verziách Ri4.

Niektorí používatelia zistili, že ich existujúce USB-C káble nenapájajú novo zakúpené Raspberry Pi 4. Ako Tyler Ward podrobne vysvetlil, nová doska Pi má konektor USB-C, ktorý nepodporuje konkrétne nabíjačky, ako napríklad pre MacBook.

Nabíjací port USB-C Pi 4 je ukončený jediným odporom na dvoch pinoch, namiesto toho, aby každý pin mal svoj vlastný odpor.

Výsledkom je, že inteligentné USB-C nabíjačky, známe aj ako elektronicky označené alebo e-marked USB-C nabíjačky, nebudú poskytovať do dosky žiadne napájanie.

Spoluzakladateľ Raspberry Pi Eben Upton túto otázku potvrdil spoločnosti TechRepublic, pričom dodal, že oprava je zahrnutá do revízie budúcej verzie.

Inteligentná nabíjačka s káblom označená ako e-marked nesprávne identifikuje Raspberry Pi 4 ako audio príslušenstvo a odmietne poskytnúť napájanie.

Očakávam, že oprava bude v budúcej revízii dosky, ale teraz budú musieť používatelia použiť jedno z navrhovaných riešení. Je to prekvapujúce, že sa to nevyskytlo v našom (dosť rozsiahlom) testovacom programe.


Ak ste si zakúpili Pi 4, budete musieť použiž inú USB-C nabíjačku, lacnejšiu a nie e-marked, ktoré sa dodávajú so smartfónmi. Oficiálne napájanie Raspberry Pi 4 funguje správne.

Ak ste si Pi 4 nekúpili, môžete počkať na novú revíziu dosky, ktorá by mala prísť v „najbližších mesiacoch“.

Spravne zapojenie pinov CC1 a CC2 na USB-C:
Významová tabulka pre CC1 a CC2 konfuguračné piny:


Podrobnejšia dokumentácia:
A primer on USB Type-C and Power Delivery applications and requirements
Introduction to USB Type-CTM
Introduction to USB Type-C and Power Delivery (video)

Poznámka mcu:
Čo viac dodať. Ušetrili sme jeden odpor. Ale nie to katastrofa, iba nepríjemnosť. Kto by si kupoval USB-C nabíjačku k MacBook pre Ri 4?


Nová generace Raspberry Pi 4 přináší vyšší procesorový i grafický výkon, ale to vše může být. k ničemu, pokud bude provozovaný systém na pomalé microSD kartě. Jakmile by jakákoli aplikace pracovala se soubory, může dojít k výraznému zpomalení celého systému. Proto je vhodné vybrat tu správnou microSD kartu.

I když byly karty v mnoha oblastech vyrovnané, s přehledem zvítězily dva modely - Samsung Evo+ a SanDisk Extreme, které rovněž patří mezi velmi levné karty. Konkrétně Samsung Evo+ dosahuje i přes velmi nízkou cenu výrazně vyšší rychlosti náhodného zápisu než jakýkoli konkurenční model.

Celý článek a další detaily včetně grafického srovnání naleznete na zive.cz

Zdroj: zive.cz


Malá poznámka o načítání dat do MCU rovnou z multimetru a pokus ověřit jak je na tom vestavěný DAC s přesností.

Multimetr jako ADC pro STM32 (+ přesnost DAC)

Motivace

V některém z mých předchozích příspěvků se rozvinula debata na téma přesnosti DAC na STM32. Nějakou dobu vrtalo mi hlavou jak pokud možno bezpracně ověřit přesnost v celém rozsahu. Nejsem majitelem žádné přesné ani nepřesné měřicí karty, takže mě pořád dokola napadalo jen připojit multimetr k PC a nějak z něj tahat data. To je docela pracné řešení když uvážíte, že musíte napsat dva programy, jeden pro STM a jeden pro PC. Což je jistě pro někoho hračka, ale já na PC programuji jen velmi zřídka, takže jsem našel následující alternativní variantu. Multimetry posílají data do PC skrze optočleny tvořené infradiodou v mutlimetru a fototranzistorem/fotodiodou v přijímacím modulu ("USB káblíku"). Není tedy vůbec složité přijímat data pomocí STM a zbavit se tak potřeby psát nějaký program pro PC. Což oceníte zvlášť v situaci kdy si s mikrokontroléry rozumíte víc jak s počítačem.

Příprava

Minimálně dva multimetry (Axiomet a UNI-T) co jsem v labu našel posílají skrze infraport data "protokolem" UART. Navíc v ASCII podobě takže jejich rozklíčování je triviální záležitostí. Nemůžu zaručit, že nenarazíte na multimetr který bude data posílat v nějakém nečitelném binárním protokolu, ale očekávám, že UART-ASCII kombinace bude docela standard. K testu jsem si vybral UT71A, neboť se mi zdálo, že jeho přesnost (disp 19999, 0.1%+8dig) bude stačit. Než se pustíme do samotného měření musíme překonat tři vcelku jednoduché problémy. Prvním z nich je jak z optického signálu udělat konvenční UART čitelný STMkem. Díky tomu že je "infraport" velice slušně krytý před okolním osvětlením lze k tomu využít celou řadu metod s pomocí fotodiody nebo fototranzistoru. Namátkou například tyto:

  • Fotodiodu můžete zapojit jako "solární článek", tedy anodou ke GND a napětí na katodě porovnávat s nastavenou úrovní pomocí komparátoru. Výstup z komparátoru bude signál pro UART. Díky tomu, že komparátor máte přímo na STMku, nebudete krom fotodiody potřebovat žádné další součástky. Tento postup má dvě nevýhody. K vnitřnímu nastavení komparační hladiny spotřebujete jeden DAC kanál (signál z fotodiody může mít tak nízké napětí že nelze využít ani hladinu 1/4 Vrefint). Na některých STM (jmenovitě STM32F303K) nemá komparátor volitelnou hysterezi (nejspíš nemá žádnou když ji datasheet neuvádí), takže na jeho výstupu budou vznikat zákmity, které ale díky svému krátkému trvání UARTu nevadí. Oba problémy lze snadno odstranit doplněním externích součástek, ale ztrácí se ta elegance.

  • Můžete využít vnitřní operační zesilovač a zapojit ho jako "transimpedance amplifier". Jinak řečeno připojit ho jako na schématu níže. Zpětnovazební odpor zvolit tak velký aby OZ pracoval až do saturace a výstup opět přivést na Rx UARTu. Krom fotodiody vám k tomu postačí jediný rezistor. Nevýhodou, z principu věci, je fakt že k tomu spotřebujete OZ, který umí spolupracovat s ADC a může se tedy stát objektem kalibrace.

  • Můžete udělat totéž jako v předchozím bodě ale s pomocí externího OZ. Nevýhodou je že budete muset osadit víc součástek, výhodou je že pak takové řešení můžete připlácnout k libovolnému MCU. Já zvolil právě tuto variantu a zapojil "převodník světlo-napětí" dle následujícího schematu. Jako OZ posloužil MCP6002 jakožto laciný zástupce Rail-to-Rail operáků.

Schema zapojení

Druhým problémem je navázat fotodiodu na infradiodu v multimetru. Což se naštěstí ukázalo jako velice jednoduché a postačil k tomu kus kartonu. Nejlépe to popisuje následující fotografie.

Kousek kartonu vymezuje polohu fotodiody proti IR diodě v multimetru
Celá sestava před měřením bez bufferu. Na pomocné DPS je zesilovač ze schematu.

Třetím a posledním problémem je zjistit komunikační protokol. V datasheetu k mutlimetru jsem ho nenašel (a ani jsem nečekal že tam bude, ale možná jsem jen špatně hledal). Na první pohled se signál jevil jako UART s 2400b/s v klasické kombinaci 8n1 (8bit, žádná parita, 1 stop bit). Což se jevilo jako správný tip dokud multimetr neposlal jiný znak než "0". Po velmi nepříjemné půlhodince jsem pochopil, že jde ve skutečnosti o 7o1 (7bit, lichá parita, 1 stop bit). Zpráva se stává z 11ti bytů. První pětice je obsah displeje multimetru, pak zvolený rozsah (multimetr je typu auto-range), pak nějaké detaily a celá zprava je zakončena kombinací \r\n viz odkaz.

Oscilogram zprávy z multimetru. Vpravo si můžete všimnou konfigurace 7bitů, lichá parita, 1 stopbit (2400 Baud)

Program

Program pro STM32F303K8 je naprosto přímočarý. Postupně inkrementuje výstup DAC od nuly do maxima, čeká na výsledek z multimetru a posílá ho UARTem na terminál v PC. Protože multimetr odesílá svá data až po skončení měření (a mezi tím nejspíš měří další) tak program první výsledek po změně DAC vždy vyřadí. Multimetr posílá výsledky měření přibližně 3x-4x za sekundu a MCU nemá žádnou kontrolu nad tím kdy přijdou. Proto probíhá příjem pomocí přerušení. Signál z operačního zesilovače (na obrázku výše) je nutné invertovat pomocí funkce USART_InvPinCmd(). Program přečte prvních 5 znaků zprávy (obsah displeje) a převede je na celé číslo. Podle šestého znaku zjistí nastavený rozsah multimetru, z tabulky restable[] vyčte jednotku a složí výsledek měření do typu float. Nakonec napětí odešle spolu s hodnotou DAC na terminál v PC. Použití float by se dalo na tomto místě vyhnout, ale pokud by měl přístroj provádět nějakou "samokalibraci", určitě se bude dělat snáze ve floatech.

// Ověřování přesnosti DAC pomocí multimetru s "optovýstupem"
// Multimetr UT71A měří výstupní napětí DAC a vysílá infradiodou "do světa"
// opt. signál z mutlimetru vede do fotodiody a zpracován triviálně pomocí OZ (current to voltage converter)
// a je následně přijímán UARTem 1, výsledky kalibrace jsou odesílány UARTem 2 do PC
// AVDD = 3.318V, výstup DAC s bufferem veden na střed děliče 27k(GND)-27k(VDD), nucleo32 kit (F303K8)

// UT71 A protocol (7o1 2400 baud/s), message 11 byte in format:
// Dig1,Dig2,Dig3,Dig4,Dig5,Range,Function, State1, State2, \r, \n
// PA10 Usart1 Rx - Opamp output (from multimeter)
// PA2 Usart2 Tx - to PC
// PA4 DAC1_OUT1

#include "stm32f30x.h"
#include "stdio.h"
#include "string.h"
#include "stdlib.h"

void dac_init(void);
void usart1_init(void);
void usart2_init(void);
void USART_puts(volatile char *s);
void calibrate(void);
void dac_init(void);

// pro příjem UARTu 1
#define MAX_STRLEN 32
volatile char message[MAX_STRLEN]; // zpráva od multimetru
volatile uint8_t new_message=0; // příznak příchodu zprávy (flag)
const float restable[5]={0.1e-3,1e-3,10e-3,0.1,0.1}; // tabulka rozlišení multimetru [V]

int main(void){
 SystemCoreClockUpdate(); // 8MHz
 usart1_init(); // USART1 (příjem dat z multimetru pomocí IRQ)
 usart2_init(); // USART2 (odesílání výsledku měření do PC)
 dac_init(); // Výstup DAC který testujeme
 calibrate(); // jednorázové měření celého rozsahu DAC

  while (1){
    // konec programu
  }
return 0;
}

void calibrate(void){
uint16_t dac_raw = 0; // hodnota v DAC
char txt[20]; // pracovní řetězec
volatile uint32_t value; // přijatá hodnota z multimetru v integer formátu
float meas, base; // přijatá hodnota napětí z multimetru + jednotka napětí z multimetru
uint8_t res; // přijaté informace o aktivovaném rozsahu multimetru

// procházej celý rozsah DAC
while(dac_raw<4096){
 DAC_SetChannel1Data(DAC1,DAC_Align_12b_R,dac_raw);  // nastav hodnotu na výstup DAC   
 while(!new_message); // počkej na první zprávu z multimetru...
 new_message=0; // ... a ignoruj ji
 while(!new_message); // počkej na druhou (už relevantní) zprávu z multimetru...
 strncpy(txt,message,5); // vyčti prvních 5 znaků (číselné vyjádření změřené hodnoty)
 txt[5]=0; // korektní zakončení řetězce
 value = atoi(txt); // převeď řetězec na číslo
 res = message[5]-48; // zjisti zvolený rozsah
 base = restable[res-1]; // zjisti si jednotku z tabulky rozlišení
 meas = base*(float)value; // spočti výsledek měření jako desetinné číslo
 snprintf(txt,sizeof(txt),"%u %1.4f\n",dac_raw, meas); // pošli zprávu o výsledku měření...
 USART_puts(txt); // ... UARTem do PC (v praxi lze nahradit například zápisem do flash,eeprom a pod.)
 new_message=0; // zprávu od multimetru jsme zpracovali ...
 dac_raw++; // přejdeme na další hodnotu DAC
 }
}

// inicializace DAC a jeho výstupu
void dac_init(void){
GPIO_InitTypeDef gp;
DAC_InitTypeDef dac;

// zapnout GPIOA a DAC1
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC1, ENABLE);

// PA4 (DAC1_OUT1) - analog, výstup DAC
gp.GPIO_Pin = GPIO_Pin_4;
gp.GPIO_Mode = GPIO_Mode_AN;
gp.GPIO_OType = GPIO_OType_PP;
gp.GPIO_PuPd = GPIO_PuPd_NOPULL;
gp.GPIO_Speed = GPIO_Speed_Level_1;
GPIO_Init(GPIOA, &gp);

// konfigurace DAC
dac.DAC_Buffer_Switch = DAC_BufferSwitch_Disable; // nevypínat buffer
dac.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;  // nezájem
dac.DAC_Trigger = DAC_Trigger_None; // žádný trigger
dac.DAC_WaveGeneration = DAC_WaveGeneration_None; // wave generation nepoužívat
DAC_Init(DAC1,DAC_Channel_1,&dac); // aplikovat nastavení na kanál1 DAC1
DAC_Cmd(DAC1,DAC_Channel_1,ENABLE); // spustit DAC1
}

// inicializace USART2 (odesílání dat do PC)
void usart2_init(void){
USART_InitTypeDef usart_is;
GPIO_InitTypeDef gp;
// zapnout GPIOA a USART2
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 , ENABLE);

// PA2 RX USART2
gp.GPIO_Pin = GPIO_Pin_2;
gp.GPIO_Mode = GPIO_Mode_AF;
gp.GPIO_OType = GPIO_OType_PP;
gp.GPIO_PuPd = GPIO_PuPd_NOPULL;
gp.GPIO_Speed = GPIO_Speed_Level_1;
GPIO_Init(GPIOA, &gp);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_7);

// konfigurace (38400 Baud, 8n1)
usart_is.USART_BaudRate = 38400;
usart_is.USART_WordLength = USART_WordLength_8b;
usart_is.USART_StopBits = USART_StopBits_1;
usart_is.USART_Parity = USART_Parity_No ;
usart_is.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart_is.USART_Mode = USART_Mode_Tx;
USART_Init(USART2, &usart_is);
USART_Cmd(USART2, ENABLE);
}

// Odesílání řetězce do PC na terminál
void USART_puts(volatile char *s){
 while(*s){
  while (!USART_GetFlagStatus(USART2,USART_FLAG_TXE)){}
  USART_SendData(USART2, *s);
  s++;
 }
}

// inicializace USART1 (příjem zpráv z multimetru)
void usart1_init(void){
GPIO_InitTypeDef gp;
USART_InitTypeDef usart_is;
NVIC_InitTypeDef nvic;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// PA10 (Rx))
gp.GPIO_Pin = GPIO_Pin_10;
gp.GPIO_Mode = GPIO_Mode_AF;
gp.GPIO_OType = GPIO_OType_PP;
gp.GPIO_PuPd = GPIO_PuPd_NOPULL;
gp.GPIO_Speed = GPIO_Speed_Level_1;
GPIO_Init(GPIOA, &gp);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_7);

// Baudrate 2400, 8n1 (v reálu 7o1), invertovat Rx pin
usart_is.USART_BaudRate = 2400;
usart_is.USART_WordLength = USART_WordLength_8b;
usart_is.USART_StopBits = USART_StopBits_1;
usart_is.USART_Parity = USART_Parity_No ;
usart_is.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart_is.USART_Mode = USART_Mode_Rx;
USART_Init(USART1, &usart_is);
USART_InvPinCmd(USART1,USART_InvPin_Rx, ENABLE);
// deaktivuji overrun (zjednodušuje debug, neboť tok dat z multimetru nejde regulovat)
USART_OverrunDetectionConfig(USART1,USART_OVRDetection_Disable);
// přerušení od příjmu
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
nvic.NVIC_IRQChannel = USART1_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 1;
nvic.NVIC_IRQChannelSubPriority = 1;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
// Spustit USART
USART_Cmd(USART1, ENABLE);
}

// rutina přijmu znaků z multimetru
// Řetězec se přijímá do rx_string a po skončení se kopíruje do "message"
void USART1_EXTI25_IRQHandler(void){
 static char rx_string[MAX_STRLEN];
 static uint16_t cnt = 0;
 char t;
 if (USART_GetFlagStatus(USART1,USART_FLAG_RXNE)){
  t=USART_ReceiveData(USART1); // vyčíst přijatý znak
  t = t & 0x7f; // odstranění paritního bitu
  // testovat konec zprávy
  if((t!='\r') && (t!='\n') && (cnt < (MAX_STRLEN-1))){
   rx_string[cnt] = t; // uložit přijatý znak
   cnt++;
  }
  else if(cnt>0){ // jakmile je zpráva celá...
   strncpy (message,rx_string, cnt); // zkopírovat ji...
   message[cnt]= '\0'; // zakončit ji tak aby z ní byl plnohodnotný řetězec...
   cnt = 0;
   new_message++; // ... a informovat program že přišla
  }
 }
}




Výsledky

Multimetr je relativně pomalé měřidlo, takže projít celý rozsah trvalo přibližně tři čtvrtě hodiny. Pro zajímavost jsem proměřil výstup DAC se zapnutým i vypnutým bufferem. Výstup s bufferem jsem zatížil děličem 27k/27k (VCC/GND). Pro snadnější orientaci jsem čárkovanou čarou vyznačil úroveň referenčního napětí (strop DAC). V grafu odchylky si můžete všimnou že od určité hodnoty jsou výsledky z multimetru "zašuměné". To je dáno změnou rozsahu z 2V na 20V.

Výsledky měření s bufferem pracujícím do děliče 27k/27k (VCC/GND).
Zelená stopa vyjadřuje "ideální" závislost výstupního napětí na kódu DAC
Červená stopa je změřená závislost výstupního napětí na kódu DAC
Černá stopa je odchylka od ideální hodnoty (mV), růžově vyznačeno 1LSB
Výsledky měření s vypnutým bufferem bez zátěže.
Zelená stopa vyjadřuje "ideální" závislost výstupního napětí na kódu DAC
Červená stopa je změřená závislost výstupního napětí na kódu DAC
Černá stopa je odchylka od ideální hodnoty (mV), růžově vyznačeno 1LSB

Odkazy

Další tutoriály lze nalézt na www.elektromys.cz
| V1.00 4.7.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /


Informácie a úžasnom Raspberry P4 sú takmer všade vrátane veľkého nadšenia. Ale je to opodstatnené nadšenie? Na čo je vhodné a stojí za to si ho teraz kúpiť? Pozrime sa bližšie.

Je ťažké hodnotiť Raspberry Pi 4 lebo tento SBC je používaný na rôzne účely a názory sa budú asi rôzniť. Priznám sa, že som očakával viacej, hlavne s pohľadu grafiky a jej výkonu. Je úžasné že Raspberry Pi 4 má dva 4K výstupy na Micro HDMI, ale reálne použitie bude mať ďaleko k údajom z technickej špecifikácie. Ale takéto použitie bude asi veľmi zriedkavé.
Čo sa ostatných parametrov týka tak ide skôr o štandardný update, ktorý vzhľadom na vývoj ARM CPU bol asi predvídateľný.

Raspberry Pi 4 je spätne plne kompatibilný, čo sa rozmerov, rozmiestnenia konektorov a veľkosti týka. Preto je možné ho zameniť za predošlé verzie. Veľmi vhodným krokom je update na USB3 a napájanie z USB-C. Čo je stále nepochopiteľné, je bootovanie z SD karty.

Pozitívne je, že číňania budú sekať SLA 3D tlačiarne s vyšším rozlišním, t.j. 4K, rýchlejšie a lačnejšie.

Cena základnej konfigurácie s 1GB RAM je 35,- USD, čo je prijateľné. Cena pre 2GB RAM je 45,- USD a pre 4GB RAM je 55,- USD.



Dnes 23.07.2019 sme dostali infomáciu ohladne údajnej podpory 2x4k60p:
"We don't support dual 4KP60. Max is one 4Kp60 one 4k30."



Technické Parametre Raspberry Pi 4:
* Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
* 1GB, 2GB or 4GB LPDDR4-2400 SDRAM (depending on model)
* 2.4 GHz and 5.0 GHz IEEE 802.11ac wireless, Bluetooth 5.0, BLE
* Gigabit Ethernet
* 2 USB 3.0 ports; 2 USB 2.0 ports.
* Raspberry Pi standard 40 pin GPIO header (fully backwards compatible with previous boards)
* 2× micro-HDMI ports (up to 4kp60 supported)
* 2-lane MIPI DSI display port
* 2-lane MIPI CSI camera port
* 4-pole stereo audio and composite video port
* H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
* OpenGL ES 3.0 graphics
* Micro-SD card slot for loading operating system and data storage
* 5V DC via USB-C connector (minimum 3A*)
* 5V DC via GPIO header (minimum 3A*)
* Power over Ethernet (PoE) enabled (requires separate PoE HAT)
* Operating temperature: 0 – 50 degrees C ambient

Zatiaľ predbežné skúsenosti iných používateľov:

Raspberry Pi 4 - My First Look - Benchmarks - initial impressions Review
Raspberry Pi 4: Test and Weaknesses

Nová DDS kmitočtová základna vznikla při myšlence návrhu nové verze řídící desky do transceiveru JUMA-TRX2. Návrh vychází z originálního zapojení testovací desky, kterou navrhnul původní autor transceiveru už v roce 2008. Jako referenční zdroj kmitočtu byl na doporučení Richarda HB9TQU zvolen vysoce kvalitní XO, s výbornou stabilitou a čistotou signálu.


Napájecí napětí 5V je regulováno trojicí LDO regulátorů U2, U3, U4 na tři samostatná napájecí napětí. IO U1, DDS (Direct Digital Synthesizer) AD9952 fy. Analog devices může generovat použitelný kmitočet o frekvenci max. 160MHz. Je to právě 40% vstupního referenčního kmitočtu (400MHz). Vyšší procento se nedoporučuje používat. Toto obecně platí při používání DDS. Referenční kmitočet je zaveden z Q1 symetrickým propojením LVDS. Použití XO na přímé pracovní frekvenci DDS má výhodu hlavně ve stabilitě. Stabilita XO je pak výsledná stabilita generovaného kmitočtu. Kmitočet se nemusí v DDS násobit a tím tak násobit i nestabilitu. Vygenerovaný kmitočet je přiveden na symetrickou dolní propust, jejíž vrchol propustnosti je nastaven právě na maximální generovaný kmitočet tj. 160MHz. Poté je zaveden symetricky zpět do obvodu DDS na vstup komparátoru. Z výstupu komparátoru je signál přivedený na logická hradla (U5) a klopné obvody D (U6), kde se kmitočet dělí a fázově posouvá. Výsledkem jsou dva signály I-LO a Q-LO, která jsou vůči sobě fázově posunuté. U5 a U6 lze alternativně osadit rychlejšími obvody fy. Potato Semiconductor. DDS základna je navrhnutá jako malý modul o rozměrech 40 x 21 mm, který je na kratší hraně zakončen kolíkovým hřebínkem 2 x 8 pinů (rozteč pinů je 2,54 mm). Použití je tak univerzálnější a navíc se dá zvolit velikost napětí výstupních signálů I-LO a Q-LO.

Schéma zapojení:


Více na ...
Jak kopírovat v mikrokontroléru data bez použití CPU? S pomocí periferie DMA. V dnešním díle poodhalíme k čemu je dobrá a pro jaké triky ji lze využít.



Začínáme s STM32 VL Discovery 21
mcu.cz/news2210.html.3

ARM Cortex M4 Advanced Tips - Bit-Banding - single bit manipulation
www.youtube.com/watch?v=h78DyF1NOio

Using Direct Memory Access (DMA) in STM32 projects
embedds.com/using-direct-memory…-in-stm23-projects/

Robin Obůrka nám představí základní koncept a použití verzovacího systému Git. Ať už vyvíjíte sám nebo v týmu, verzovací systém se vám v embedded vývoji určitě vyplatí a zachrání spoustu času při debugu a porovnávání, co se ve zdrojáku od posledních úprav změnilo.

Odkazy:

akademie.nic.cz
git-scm.com/
git-scm.com/downloads/guis
meldmerge.org/



Krátká demonstrace appnote od ST o tom jak s pomocí externího OZ zrychlit DAC převodník na libovolném STM32

Rychlejší DAC na STM32


Motivace


V řadě projektů s STM32 využívám vestavěný DAC. Jeho parametry nejsou nijak oslnivé a na "precizní" práci se nehodí. Ale pro některé pomocné úlohy jako třeba nastavování komparačních úrovní pro rychlé externí komparátory jsou přesné víc než dost. Dokonce úspěšně slouží jako generátory signálu pro buzení elektromagnetů v měřicích aplikacích. Dá se tedy říct, že DAC není úplně k zahození a možná by nebylo od věci vyvinout trochu úsilí a jeho parametry mírně zlepšit.

Appnote


Nebudu ztrácet čas a rozkecávat se o DAC obecně, neboť to už udělali jiní a lépe. Taktéž nebudu zabředávat do specifik DAC na STM32F3, neboť to jsem již udělal v dřívějším příspěvku. Jediné co si vyzkoušíme bude návod z appnote AN4566 - Extending the DAC performance of STM32 microcontrollers. Když to zestručním tak se v ní tvrdí, že úzké hrdlo DA převodníku je výstupní buffer. Limituje minimální a maximální výstupní napětí (tedy okrádá vás o část rozsahu) a rychlost přeběhu. Samozřejmě použitím bufferu něco získáte. A to relativně nízkou výstupní impedanci (dle datasheetu je výstup při zachování dané přesnosti ochoten dodávat proud až 0.6mA, případně odebírat proud něco přes 0.1mA). Tím se ale zmiňovaná appnote nezabývá. Vysvětluje jak lze pomocí externího operáku citelně zvětšit rychlost přeběhu a spolu s tím se ještě zbavit omezování min. a max. výstupního napětí. Protože mám bláhový dojem, že by se mi to mohlo někdy hodit, rozhodl jsem se appnote vyzkoušet a při té příležitosti vás s výsledky seznámit. Na tomto místě bych zkušenějším uživatelům doporučil neplýtvat svým časem, prolétnou očima appnote a věnovat se něčemu zábavnějšímu. Nic převratného totiž předvádět nebudu.

Hardware


Kvalitní zpracování tohoto tématu by si zasloužilo vyrobit pro obvod vlastní DPS a ideálně zvolit i čip ve větším pouzdře se samostatně vyvedeným VREF+. To mi ale přišlo jako příliš mnoho práce, takže celý test provedeme na Nucleo kitu a v nepájivém poli. A myslím že i přes to vše bude pozitivní výsledek dobře patrný. Test jsem provedl na Nucleo kitu STM32F303K8 protože jich mám prostě hodně. O něco zajímavější by to asi bylo na některé STM32F4 a úplně nejzajímavější asi na STM32H7, protože tam bychom mohli překročit 10Msps (tedy 10x víc než uvádí datasheet jako maximum). Přejděme ale k věci. Appnote nás nabádá abychom na výstupu DAC vypnuli buffer a s využitím vnitřního odporu a externího OZ sestavili v podstatě diferenciální zesilovač. Ten totiž umožní udržet výstup DAC z čipu na konstantním napětí a eliminuje tím nabíjení a vybíjení parazitních kapacit. Ty totiž spolu s výstupním odporem (až 15k) nebufferovaného DAC tvoří filtr, který imituje rychlost přeběhu. V této konfiguraci ho limitovat nebudou. Drobné úskalí je ale volba zpětnovazebního odporu. Pokud neznáme výstupní odpor DAC, nemůžeme ho přímo spočítat. Nabízí se tedy buď výstupní odpor změřit a nebo aplikaci vybavit trimrem a nastavit jeho hodnotu podle potřeby. Já sáhl po trimrech. Navíc jsem se rozhodl napájet OZ symetricky a vytvořit rovnou bipolární výstup (protože to v podstatě nevyžaduje žádnou úpravu schematu z appnote - až na změny hodnot odporů). Schema tedy vypadá následovně.


Schema zapojení. DAC je výstup z DA převodníku (PA5), VREF je referenční napětí pro DAC (na Nucleo kitu vlastně VDD).


Na malém čipu F303K je referenční napětí pro DAC/ADC (VREF) spojeno s VDDA a je v našem případě 3.3V. Signál DAC přivedeme z druhého kanálu DAC1 (DAC1_OUT2). Díky tomu nám zůstane první kanál (vybavený bufferem) volný abychom mohli porovnávat vnitřní a vnější buffer. Pokud se ztrácíte v tom který kanál DAC je jak vybaven, projděte si poznámku v předchozím dílu. Volba operačního zesilovače byla limitovaná domácí zásobou. Evergreeny jako LM358, LM324 nejsou dost rychlé. Jediný další kus co jsem doma našel je LMH6612. Jeho volba tedy není promyšleným krokem a na celou práci by jistě stačil "operák" o řád pomalejší. Naopak jeho použití přináší další nadbytečné komplikace. Jednou z nich může být nutnost vypořádat se s velkým vstupním proudem (typ. 6uA), který by zaváděl značný offset. Což jsem si pro účely demonstrace dovolil ignorovat. Zpětnovazební kondenzátor CFB jsem zapojil kvůli obavám z rozkmitání. Nakonec se ukázalo že byl OZ stabilní i bez něj. Výsledky naměřené bez něj budou mít tuto informaci v komentáři. Na Nucleo kitu využívám jako zdroj clocku 8MHz signál z ST-linku (spojený jumpr SB4). Clock vede na pin PF0 a pokud nerozpojíte jumper SB6 tak vede i na vnější konektor (pin D7) a může nepříjemně zarušit signál z DAC. Pro tyto účely jsem tedy SB4 rozpojil.


Foto zapojení aneb jak to nedělat


Na softwaru nebude nic převratného a protože o něj tu teď nejde, dovolím si zdrojový kód zveřejnit jen jako odkaz na konci článku. Program obdobný jako v již zmiňovaném předchozím díle. DAC1 pracuje v duálním 12bit režimu, tedy převádí oba kanály zároveň. K tomu potřebuje krmit 32bit daty o což se stará jeden DMA kanál. Okamžik převodu je řízen pomocí TIM7. Kanál 1 má zapnutý buffer a slouží pro srovnávání. Kanál 2 buffer vůbec nemá a jeho výstup ženeme do našeho zapojení. Protože naše zapojení invertuje, posílám do obou kanálů odpovídajícím způsobem "převrácená" data. Díky tomu můžeme sledovat "stejný" výstup. Srovnávací průběh má jen dvě hodnoty, maximum (4095) a minimum (0). Z takové skokové se funkce bude nejlépe odečítat rychlost přeběhu. Ve druhé ukázce pak průběh upravím na schodovitou funkci (17 schodů). Změnou parametrů TIM7 je možné ladit tempo převodů. Dle appnote lze dosáhnout maximálně 4.5Msps, víc sběrnice běžící na 36MHz nedovolí, což jak brzy uvidíte je pravda (v tom by byla zajímavější právě volba čipu F4 nebo H7). Pojďme se tedy podívat jak vypadá výstup.


Celkový pohled na výstupy při 100ksps s CFB=3pF.
Žlutý průběh je výstup našeho OZ. Modrý průběh je z kanálu1 vybaveného vnitřním bufferem. Červená stopa je výstup z nebufferovaného kanálu. Na průběhu je několik zajímavých jevů, které si zaslouží komentář v textu.


První co vás praští do očí je znatelné zpoždění (1.5us) modrého signálu. Vnitřní buffer jak vidno reaguje opožděně. Nejprve jsem se domníval, že dělám nějakou chybu při nahrávání dat do DAC. Zkusil jsem tedy buffer na kanálu 1 vypnout a zpoždění zmizelo. Je to tedy vlastnost bufferu. Další nepříjemností je hrb na žlutém průběhu. Jakmile se dá výstup 1.kanálu (s vnitřním bufferem) do pohybu, vzniká nám, nejspíše skrze parazitní kapacity, značný přeslech. Tajně doufám, že to je pouze chybou layoutu a že nějaký přeslech nevzniká i uvnitř čipu. Následující dva oscilogramy ukazují značný rozdíl rychlosti přeběhu. Vnitřní buffer zvládá přibližně 2V/us, výstup operačního zesilovače okolo 30V/us. Podotýkám že toto měření mělo zapojený CFB (3pF).


Detail vzestupných hran



Detail vzestupných hran


Na dalších oscilogramech si můžete prohlédnout upravenou situaci. Vypnul jsem "rušivý" první kanál a odpojil kondenzátor CFB. Přeslech v podobě nepříjemného hrbu zmizel a doba přeběhu se ještě citelně zkrátila.


Detail vzestupné hrany. CFB odpojený, rušivý kanál s bufferem vypnutý.




Detail sestupné hrany. CFB odpojený, rušivý kanál s bufferem vypnutý.


Na závěr si dovolím ještě prezentovat výsledek s DAC běžícím na "plný" výkon. DAC generuje schodovitý průběh s taktem 4.5Msps. Na prvním oscilogramu s vypnutým 1.kanálem. Na druhém oscilogramu je 1.kanál pro srovnání zapnutý (a kazí žlutý průběh). Je vidět, že vnitřní buffer vůbec nestíhá. Vlnky patrné na červeném kanále vznikají zpožděnou reakcí OZ a trvají do té doby než OZ nastaví na svém výstupu odpovídající úroveň. Jejich přítomnost naznačuje že by bylo možné pracovat ještě rychleji.


Schodovitá funkce přes celý rozsah při 4.5Msps.



Schodovitá funkce přes celý rozsah při 4.5Msps. Srovnání interního (modrá) a externího bufferu (žlutá).
Funkce se skládá ze 17ti schodů. Její frekvence 264.7kHz prozrazuje že DAC musí pracovat na 264.7*17 = 4.5Msps.


Závěr

Z ukázky je zřejmé, že DAC má slušný potenciál pro zlepšení. Zřejmé je ale i to, že to nebude bez práce. Přeji příjemné bastlení.

Odkazy
Appnote - Extending the DAC performance
STM32F303x6/8 Reference manual
STM32F303x6/8 Datasheet
Nucleo32 Datasheet
zdrojak.zip

Rozcestník na další díly seriálu naleznete na www.elektromys.eu/stm32.php V1.00 8.5.2019
By Michal Dudka (m.dudka@seznam.cz)


Ve volném pokračování tutoriálu o low-power technikách na STM32F0 si vyzkoušíme periodické buzení ze STOP režimu pomocí RTC.

STOP režim s periodickým buzením


V tomto tutoriálu volně navážeme na předchozí díl o low-power technikách. V něm jsme otestovali všechny tři režimy spánku a čip jsme budili stiskem tlačítka (nebo nějakým vnějším signálem). Mnohem užitečnější by bylo kdyby se čip uměl nějak vzbudit sám. To je samozřejmě možné. Například z režimu SLEEP (mělký spánek) lze čip budit libovolným timerem. My ale míříme k tomu jak čip budit z hlubších spánků - z režimů STOP a STANDBY. Tady se možnosti štěpí podle toho jaký čip používáte. Příslušníci rodiny STM32L0 mají v tomto ohledu vcelku slušný výběr periferií. My se ale budeme držet STM32F0. I tam jsou rozdíly značné. Například dobře vybavený STM32F051 lze budit pomocí RTC (kalendáře), I2C, UARTem, komparátorem, nebo rozhraním CEC. O poznání chudší STM32F030 s nímž pracujeme si musí vystačit pouze s RTC. A přesně to si vyzkoušíme. Naše ukázka bude mít jednoduchý úkol. Probudit se ze spánku každou sekundu (úplně na konci si ukážeme i probouzení s vyšší frekvencí). Aby to nebyla úplná nuda, tak si po probuzení přečteme stav RTC a pošleme ho UARTem do světa. Při odesílání zkusíme využít SLEEP režim a uděláme tak jakousi optimalizaci na spotřebu. Kdo bude chtít, může si odesílaný řetězec sbírat nějakým USB->UART převodníkem.


Motivační fotografie. Spotřeba i s běžícím RTC je okolo 7.2uA


LSI

Jak už jsme řekli, chceme náš čip budit v pravidelných intervalech a k tomu potřebujeme nějaký zdroj časování. Tedy nějaký oscilátor. Chceme také udržet spotřebu v rámci jednotek až desítek uA. Nabízí se tedy dva adepti. Vnitřní 40kHz LSI RC oscilátor a LSE (oscilátor s externím hodinovým krystalem). Druhá varianta je určitě tou lepší neboť tolerance frekvence LSI je značně široká (datasheet uvádí 30-50kHz). Nominální frekvence je 40kHz, a můj konkrétní kus měl při pokojové teplotě 41.5kHz. Záměrně uvádím že při pokojové teplotě, neboť teplotní závislost jeho frekvence je značná. Tak značná, že se datasheet ani neobtěžuje ji uvádět, neboť jeho autoři zcela oprávněně nepočítají s tím, že by to vůbec někdo mohl potřebovat. Zato s externím krystalem bychom hravě dosáhli přesnosti o několik řádů vyšší... pokud bychom ho měli na našem miniaturním STM32F030F4 kam připojit Hrátky s LSE a s ním uzce spjatým RTC si tedy ponecháme jako téma jiného dílu a smíříme se s nenáročným LSI.

RTC

RTC (Real Time Clock) je bohatá periferie a příklady použití by vystačily na několik samostatných článků. Proto si uděláme jen zběžnou prohlídku a povíme si jen to nezbytně nutné k našemu příkladu. Jistě jste se už dovtípili, že do RTC lze připojit clock z LSI. Clock prochází dvojicí děliček, jejichž smyslem je snížit frekvenci na 1Hz. Od níž se pak odvíjí činnost plnohodnotného kalendáře, který udržuje informaci o sekundách, minutách, hodinách, dnech v týdnu a v měsíci, měsíci a roku. Aktuální datum a čas se neustále porovnává s obsahem alarmu jehož položky lze různě maskovat (vyřadit z porovnávání). Díky tomu lze vyvolávat alarm za různých podmínek (například každý den ve stejnou hodinu). Stejný způsobem lze porovnávat i obsah druhé děličky a realizovat tak časy nebo alarmy s intervalem nebo rozlišením kratším jak sekunda. Této funkci se budeme ještě věnovat. Další (velmi zajímavé) funkce jen letmo naznačím. Patří mezi ně například "time stamp" (zaznamená čas a datum příchodu vnější události), jemná kalibrace frekvence (1ppm), přímé ovládání výstupu nebo automatická kalibrace proti referenčnímu signálu.

Příklad

Pojďme tedy rovnou na věc. Zde je základní kostra našeho programu:


int main(void){
 clock_8();
 pull_unused_gpio(); // pulldown pro nepoužité piny
 gpio_init(); // PA4 (TEST výtup)
 init_uart(); // nastavíme UART na 115200 s clockem přímo z HSI
 init_rtc(); // nastaví RTC (nezkoumám zda už běží)

 while (1){
  RTC_ClearFlag(RTC_FLAG_ALRAF); // před usnutím smažeme vlajku Alarmu RTC
  PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFE); // uspíme čip
  TEST_H; // pro informaci jak dlouho je čip vzhůru si nastavíme pomocný výstup
  clock_8(); // taktujeme čip na požadovanou frekvenci (není nutné - po probuzení je 8MHz HSI automaticky)
  print_time(); // uděláme nějakou zajímavou / užitečnou činnost
  TEST_L; // ... informuje o konci bdělého stavu
 }
}
 


Inicializace je typická. Nejprve si nastavíme clock (každý jak potřebuje, já na 8MHz). Ošetříme nepoužité piny (buď jako analog function, nebo jako vstupy s pull-up nebo pull-down). Poté inicializujeme USART, ale nezapneme ho. UART budeme zapínat až před samotným vysíláním. Pak přijde jádro věci. Konfigurace RTC. Nejprve si spustíme LSI oscilátor a počkáme až se rozběhne. Pak pustíme clock do PWR (Power Control). Protože celé RTC je v "backup doméně" a do té je přístup běžně "uzamčen", musíme si ho odemčít funkcí PWR_BackupAccessCmd() (kvůli ní jsme potřebovali rozběhnout PWR). Od této chvíle můžeme přistupovat k registrům RTC a nastavit ho. Nejprve zvolíme zdroj clocku (LSI) a pak clock do RTC pustíme. Následně se dáme do konfigurace děliček. Tady nás čeká myšlenková práce. První (asynchronní) děličce lze nastavit dělicí poměr až 128, druhé (synchronní) až 8192. Nastavení pro hodinový krystal je triviální 32768/(128*256)=1Hz (Asynchronní dělička 128, synchronní dělička 256). Stejně jednoduchá práce by to byla kdyby náš vnitřní oscilátor měl nominálních 40kHz. Jenže jak už víme, on nemá. Takže kdo chce sekundu alespoň s 1% přesností bude si muset dosadit individuální hodnoty. Fajn, ale jak zjistit frekvenci LSI ? Jasně vyvedeme si ji na MCO ! Ten se nachází na pinu PA8... no a ten na našem miniaturním "F030F" není Takže smůla. Měřit budeme nepřímo. Jedna za variant je předpokládat, že LSI=40kHz, nastavit děličky na (100*400) a změřit si frekvenci s jakou se naše aplikace probouzí (viz oscilogram na konci). Z ní pak lze snadno dopočítat frekvenci LSI pouhým násobením 40k. Já se dopočítal k 41.525kHz a proto jsem děličku nastavil na (11*3775). Nezapomeňte, že nastavení děliček se počítá "od nuly", takže do nich zapisujete hodnotu o jedno nižší. Inicializaci clocku provedeme funkcí RTC_Init(). Všimněte si, že inicializační funkce u RTC vrací "ErrorStatus". RTC jde totiž konfigurovat pouze v "init módu" a tudíž se může stát, že se konfigurace nepovede. Funkce z SPL, mají tuhle komplikaci ošetřenou, takže to můžete pustit z hlavy.


void init_rtc(void){
 RTC_InitTypeDef rtc;
 RTC_AlarmTypeDef alarm;
 EXTI_InitTypeDef EXTI_InitStructure;
 ErrorStatus err;

 // PWR ovládá přístup do backup domény
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
 // spustit LSI oscilátor (~40kHz)
 RCC_LSICmd(ENABLE);
 // počkat na rozběh LSI
 while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);
 // povolit přístup do backu domény
 PWR_BackupAccessCmd(ENABLE);
 // LSI jako clock pro RTC
 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
 // Povolit clock do RTC
 RCC_RTCCLKCmd(ENABLE);

 // nastavit dělení clocku pro RTC (jen z legrace zkusíme velmi nepřesné LSI trochu umravnit)
 // moje LSI mělo při pokojové teplotě (důležitá informace) 41525Hz, takže nejlepší způsob jak udělat 1s je:
 // 41525/11/3775 = 1Hz
 // pokud bychom chtěli periodické buzení s kratším intervalem než 1s, museli bychom SynchPrediv volit chytře !
 rtc.RTC_AsynchPrediv = 10; // 11-1
 rtc.RTC_SynchPrediv = 3774; // 3775-1
 rtc.RTC_HourFormat = RTC_HourFormat_24;
 err=RTC_Init(&rtc);

 // nastavit datum (pro pouhé periodické buzení není třeba)
 date.RTC_Date = 5;
 date.RTC_Month = RTC_Month_May;
 date.RTC_WeekDay = RTC_Weekday_Sunday;
 date.RTC_Year = 19;
 err=RTC_SetDate(RTC_Format_BIN,&date);

 // nastavit čas (pro pouhé periodické buzení není třeba)
 time.RTC_H12 = RTC_H12_AM;
 time.RTC_Hours = 9;
 time.RTC_Minutes = 48;
 time.RTC_Seconds = 0;
 RTC_SetTime(RTC_Format_BIN,&time);

 // nastavujeme alarm na buzení každou sekundu
 // předplníme si nastavení sekund,minut,hodin... protože nehrají roli
 RTC_AlarmStructInit(&alarm);
 // klasické položky (rok, měsíc, den, hodina, minuta, sekunda) alarmu maskujeme
 alarm.RTC_AlarmMask = RTC_AlarmMask_All;
 // alarm lze přepisovat jen když je vypnutý (takže ho pro jistotu vypneme !)
 err=RTC_AlarmCmd(RTC_Alarm_A, DISABLE);
 // Aplkikujeme naše nastavení Alarmu
 RTC_SetAlarm(RTC_Format_BIN,RTC_Alarm_A,&alarm);
 // nastavíme sub-second alarm na periodické buzení každou sekundu
 RTC_AlarmSubSecondConfig(RTC_Alarm_A,0,RTC_AlarmSubSecondMask_None);
 // povolíme Alarm
 err=RTC_AlarmCmd(RTC_Alarm_A, ENABLE);
 // povolíme přerušení od Alarmu
 RTC_ITConfig(RTC_IT_ALRA,ENABLE);

 // Přerušení od RTC vedou do EXTI (pustíme EXTI clock)
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
 // povolíme Event od RTC (ten nás bude budit)
 EXTI_InitStructure.EXTI_Line = EXTI_Line17; // To je IRQ od Alarmu
 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;  // Event (!)
 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
 EXTI_Init(&EXTI_InitStructure);
}
 


Nastavení času a data je pro naši aplikaci spíš zbytečné a slouží jen k tomu abychom měli něco zajímavého k posílání na uart. Všimněte si, že čas i datum lze nastavovat (a jak uvidíte i číst) buď v binárním formátu nebo jako BCD. V registrech RTC se čas skladuje jako BCD. BCD formát zjednodušuje výpis času a data na "displej" a my toho později také využijeme. Další zamyšlení nás čeká u alarmu. Ten není koncipován pro periodické buzení s intervalem menším jak minuta. Obsah RTC se porovnává s obsahem Alarmu a když nastane shoda, alarm se aktivuje. Z porovnání můžeme pomocí maskování vyřadit jednotlivé položky data a času (dny, měsíce, hodiny, minuty atd.). Vyřadíme-li všechno krom sekund, bude se porovnávat jen obsah sekund a ten se zopakuje vždy jednou za minutu. To je ale pro nás příliš dlouhá doba. My tedy vymaskujeme všechny položky alarmu (pomocí RTC_AlarmMask_All). Díky tomu nám příliš nezáleží na tom jaké konkrétní hodnoty jsou v alarmu nastavené (proto jsem si dovolil využít RTC_AlarmStructInit() k "předvyplnění"). Jenže jak přesvědčit alarm aby nás budil každou sekundu ?

Řešení se nachází v "sub second" registrech. Synchronní dělička (o níž už byla řeč) je jen pouhý čítače (s nastavitelným stropem) a jeho obsah je přístupný. A hlavně účastní se též porovnávání s alarmem. Alarm tedy lze vyvolávat s jemnějším rozlišením než 1s. Rozlišení a časy jichž lze dosahovat, ale závisí na konfiguraci synchronní děličky. Jestliže dělíme 400, můžeme čas specifikovat až na 1/400s. Stejně jako alarm, lze jednotlivé bity tohoto "sub second" čítače maskovat a vyřadit je tak z porovnávání. Ti optimističtější z vás se teď zaradují, že alarmem půjdou dělat i desetiny a setiny sekundy a že čip půjde budit třeba 5x za sekundu. Ti realističtější z vás tuší, že v tom bude nějaký háček když o tom píšu. A taky že je. Uvedu to na příkladu. Do synchronní děličky přichází 400Hz. Náš čítač/dělička tedy čítá od 0 do 399 (celkem 400 kroků) než uplyne jedna sekunda. Maskování jednotlivých bitů mi umožní generovat alarm každý druhý, čtvrtý, osmý, šestnáctý... tik. Oněch 400 tedy můžu dělit 1,2,4,8,16,32,64,128,256. Můžu vytvořit 1/200s, 1/100s, 1/50s a 1/25s. Ale 1/12.5s už je blbost ... a hlavně nelze získat 1/10 ani 1/5 a ani 1/4s a žádný další hezký zlomek sekundy. A to jsme měli ještě štěstí, že jsme zvolili 400Hz, protože je slušně dělitelné dvěma. Zkusme teď zvolit realističtější hodnotu 3775 jako v mém příkladě. To číslo vůbec dělitelné dvěma není ! Takže z něj nepůjde udělat žádné buzení v pravidelných intervalech ! Práce by byla výrazně jednodušší kdyby synchronní dělička udržovala dělící poměr jako mocninu dvou (což je splněno například u 32.768kHz clocku). Vraťme se ale k původnímu plánu - budit aplikaci každou sekundu.

Zavoláním funkce RTC_AlarmSubSecondConfig(RTC_Alarm_A,0,RTC_AlarmSubSecondMask_All) vymaskujeme všechny bity i v "sub second" části alarmu (a máme tak vymaskované naprosto všechny položky Alarmu) a díky tomu se alarm zavolá každou sekundu. Stejného efektu bychom dosáhli i voláním RTC_AlarmSubSecondConfig(RTC_Alarm_A,0,RTC_AlarmSubSecondMask_None). V tomto případě by se obsah "sub second" registru porovnával s nulovou hodnotou a shoda (kdy je v čítači/děličce přesně 0) by nastala opět každou sekundu (dělička/čítač by přetekl). Tím máme tu nejobtížnější práci za sebou. Zapomněl jsem poznamenat, že alarm lze konfigurovat jen když je vypnutý, takže je dobré si ho předem vypnout. Po nastavení je potřeba alarm povolit, pak povolit přerušení od alarmu (RTC umí obecně volat vícero přerušení). Přerušení od Alarmu vede do EXTI na linku 17, takže ji musíme také nastavit. Nastavíme ji jako event a čip budeme uspávat pomocí WFE (viz předchozí díl). Od teď už můžeme čip uspávat a měl by se probudit. Přirozeně před uspáním musíme vymazat vlajku Alarmu, jinak se čip neuspí.

Teď ještě pár poznámek k výpisu na UART. Čas a datum vyčteme z RTC v BCD formátu, nemusíme pak složitě konvertovat binární hodnotu na text (např pomocí sprintf a pod.). O sestavení zprávy se stará funkce print_time(). Odeslání UARTem provedeme trochu netradičně. Protože chceme čip hned po odeslání uspat, musíme si počkat než se zpráva celá odešle ! Jak jistě víte v UARTu je buffer, do kterého nacpeme data a ze kterého si je UART sám přebírá a posílá. Je tedy na nás abychom si hlídali kolik dat jsme do UARTu nacpali a kolik z nich se kompletně odeslalo. To nám umožní vlajka TC (Transfer Complete). Takže vysílání provedeme netradičně kontrolou této vlajky namísto běžného postupu s TXE (Transmit buffer empty). Během odesílání nemá čip nic na práci a jen čeká na nastavení vlajky. To je situace jako stvořená pro využití SLEEP režimu. Takže prostoje během odesílání jednotlivých znaků prospíme. Povolíme si v UARTu přerušení od TC a využijeme informace z předchozího dílu o buzení libovolným přerušením (SEVONPEND).


void print_time(void){
 // vyčteme čas a datum z RTC
 RTC_GetTime(RTC_Format_BCD, &time);
 RTC_GetDate(RTC_Format_BCD, &date);
 // využijeme BCD formátu a vyhneme se použití sprintf ...
 // ... a připravíme si řetězec
 txt[0]=(time.RTC_Hours >> 4) + '0'; // desítky hodin
 txt[1]=(time.RTC_Hours & 0x0f)  + '0'; // jednotky hodin
 txt[2]=':';
 txt[3]=(time.RTC_Minutes >> 4) + '0'; // desítky minut
 txt[4]=(time.RTC_Minutes & 0x0f) + '0'; // jednotky minut
 txt[5]=':';
 txt[6]=(time.RTC_Seconds >> 4) + '0'; // desítky sekund
 txt[7]=(time.RTC_Seconds & 0x0f) + '0'; // jednotky sekund
 txt[8] = ' ';
 // vypíšeme den v týdnu
 if(date.RTC_WeekDay == RTC_Weekday_Monday){txt[9]='P';txt[10]='o';}
 if(date.RTC_WeekDay == RTC_Weekday_Tuesday){txt[9]='U';txt[10]='t';}
 if(date.RTC_WeekDay == RTC_Weekday_Wednesday){txt[9]='S';txt[10]='t';}
 if(date.RTC_WeekDay == RTC_Weekday_Thursday){txt[9]='C';txt[10]='t';}
 if(date.RTC_WeekDay == RTC_Weekday_Friday){txt[9]='P';txt[10]='a';}
 if(date.RTC_WeekDay == RTC_Weekday_Saturday){txt[9]='S';txt[10]='o';}
 if(date.RTC_WeekDay == RTC_Weekday_Sunday){txt[9]='N';txt[10]='e';}
 // vypíšeme datum
 txt[11] = ' ';
 txt[12]=(date.RTC_Date >> 4) + '0'; // den (desítky)
 txt[13]=(date.RTC_Date & 0x0f)  + '0'; // den (jednotky)
 txt[14]='.';
 txt[15]=(date.RTC_Month >> 4) + '0'; // měsíc (desítky)
 txt[16]=(date.RTC_Month & 0x0f)  + '0'; // měsíc (jednotky)
 txt[17]=' ';
 // a rok (2019)
 txt[18]='2';
 txt[19]='0';
 txt[20]=(date.RTC_Year >> 4) + '0';
 txt[21]=(date.RTC_Year & 0x0f)  + '0';
 txt[22]=0; // ukončení řetězce
 // vše pošleme na trochu optimalizovaný UART
 USART_puts(txt); // vypíšeme připravený řetězec
}
 



// funkce k odeslání řetězce, využívající režim spánku
void USART_puts(volatile char *s){
 // chceme během vysílání spát (jádro nemá co na práci)
 // povolíme buzení libovolným přerušením
 NVIC_SystemLPConfig(NVIC_LP_SEVONPEND, ENABLE);
 USART_Cmd(USART1,ENABLE); // zapneme UART
 // vyčistíme vlajku "Transfer Complete" (dokončené vysílání)
 USART_ClearFlag(USART1,USART_FLAG_TC);
 // povolíme přerušení od "Transfer Complete"
 USART_ITConfig(USART1,USART_IT_TC,ENABLE);
 while(*s){ // dokud je co vysílat
  USART_SendData(USART1, *s); // naložíme znak k odeslání
  s++; // posuneme se na další znak
  // než se vysílání dokončí tak si zdřímneme
  PWR_EnterSleepMode(PWR_SLEEPEntry_WFE); // mělkým spánkem (SLEEP)
  // vysílání dokončeno
  // tady by asi bylo na místě si pro jistotu zkontrolovat zda nás nevzbudilo něco jiného
  USART_ClearFlag(USART1,USART_FLAG_TC); // vyčistíme vlajku (jinak znovu neusneme)
  NVIC_ClearPendingIRQ(USART1_IRQn); // vyčistíme "vlajku" i v NVIC (jinak znovu neusneme)
 }
 // po odeslání celé zprávy vypneme přerušení od USARTu
 USART_ITConfig(USART1,USART_IT_TC,DISABLE);
 USART_Cmd(USART1,DISABLE); // vypneme UART
 // a deaktivujeme buzení libovolným přerušením
 NVIC_SystemLPConfig(NVIC_LP_SEVONPEND, DISABLE);
}
 


Nadešel čas, podívat se jak náš příklad pracuje. Dobře to ilustrují následující dva oscilogramy.



Na prvním oscilogramu ověřujeme 1Hz frekvenci. Je patrné, že i přes precizní ruční ladění jeho přesnost pro plnohodnotné RTC stačit nebude.


V druhém oscilogramu si prohlédneme detail na aktivity našeho programu v bdělé fázi (červená stopa). Modrá stopa je signál z Tx pinu a můžeme si na něm přečíst zprávu. Žlutá stopa je orientační měření spotřeby na 10Ohm rezistoru zapojeném v serii s napájením čipu. Další informace v textu. Náš program probdí přibližně 2.2ms z každé sekundy.


Opět jsem si dovolil neprezentovat celý zdrojový kód, ale jen jeho klíčové fragmenty, takže si ho stáhněte. Z prvního oscilogramu si můžete všimnout, že odchylku frekvence jsme zkrotili někde k 200ppm. Ale on je to spíš takový optický klam, protože teplotní závislost je v řádu desítek až stovek ppm na stupeň Celsia. Takže stačí na čip položit prst a frekvence se začne viditelně hýbat. Tedy LSI je použitelné jen tam kde přesné časování nevyžadujeme. Z druhého oscilogramu je vidět, že naše aplikace pracuje podle očekávání. Po probuzení nastaví svůj testovací výstup do log.1 (červená stopa) a my díky tomu můžeme vidět, že v bdělém stavu stráví přibližně 2.2ms. Žlutá stopa představuje odběr. Hned po probuzení aplikace zahájí "výpočty" (sestavuje zprávu pro UART). Během této fáze je čip taktován 8MHz, jeho odběr je vyšší a na žluté stopě se to projeví jako pík. Až začne odesílání dat tak se odběr díky uspávání trochu sníží. Podle osciloskopu je plocha pod grafem rovna 2.83uA*s. To by znamenalo, že při našem 1Hz buzení se zvedne průměrný odběr právě o těchto 2.8uA. Jak asi tušíte tohle se těžko ověřuje (viz metoda jak měřit odběr z tutoriálu o AVR). Spotřeba během spánku je přibližně 7.2uA, což jsem prozradil už na titulní fotografii. S vypnutím VDDA monitoru v option bytes pak klesne na 6.1uA.

Úprava

Kdo potřebuje pravidelné buzení v kratších intervalech, může si změnit hodnoty děliček. Nastavíme-li synchronní děličku na 512 a asynchronní děličku na 81, pokazíme si sice frekvenci (f=1.001Hz), ale otevře se nám možnost realizovat různé zlomky sekundy. A to 1/2, 1/4, 1/8, 1/16 (a další zlomky mocnin dvou). Což nejspíš bude užitečná funkce, takže si dovolím uvést jednoduchý postup úpravy kódu. Přirozeně ale existují i jiné postupy, třeba alarm při každém probuzení rekonfigurovat. Pak si můžete dělat intervaly libovolně dlouhé podle aktuální potřeby. Já tuto variantu vyřadil, protože jsem se pokoušel sestavit nějakou šablonu, kterou budu kopírovat do svých low-power aplikacích jako kostru programu.


// LSI = 41525, F1Hz = 41525/81/512 = 1.00128Hz
rtc.RTC_AsynchPrediv = 80; // 81-1
rtc.RTC_SynchPrediv = 511; // 512-1
// ...
RTC_AlarmSubSecondConfig(RTC_Alarm_A,0,RTC_AlarmSubSecondMask_SS14_8); // maskovat bity 14 až 8 a budit čip každou 1/2s
RTC_AlarmSubSecondConfig(RTC_Alarm_A,0,RTC_AlarmSubSecondMask_SS14_7); // maskovat bity 14 až 7 a budit čip každou 1/4s
RTC_AlarmSubSecondConfig(RTC_Alarm_A,0,RTC_AlarmSubSecondMask_SS14_6); // maskovat bity 14 až 6 a budit čip každou 1/8s
// ... atd...
 


Závěr

Závěrem bych chtěl říct, že jsem to čekal o něco snazší. Ale tak to chodí. Ti z vás, kteří chtějí si mohou upravit příklad s využitím režimu STANDBY. Já jsem se do toho nepouštěl, protože většina aplikací co dělám, potřebuje udržet obsah RAM a STANDBY režim tedy neplánuji využívat. Doufám že příklad poslouží i vám a budu rád když se na mě obrátíte s náměty jak ho vyladit.

Odkazy


Reference manual STM32F030
Datasheet STM32F030
STM32F Low-power RTC appnote<
STM32F RTC appnote
Vybrané Low-Power postupy pro STM32F0
Low Power techniky 2 (AVR)

Zdrojový kód ke stažení: zdrojaky.zip

Rozcestník na další díly seriálu naleznete na www.elektromys.eu/stm32.php V1.00 8.5.2019
By Michal Dudka (m.dudka@seznam.cz)


Tutoriál o základních low-power technikách na STM32F0. V krátkosti v něm vyzkoušíme závislost spotřeby na taktovací frekvenci a pak projdeme tři režimy spánku - SLEEP, STOP a STANDBY.

Vybrané Low-Power postupy pro STM32F0

Úvod

Snad se na mě nebudete zlobit když si dovolím vypustit motivaci. Předpokládám totiž, že důvody proč snižovat spotřebu mikrokontrolérům nebo elektronice obecně jsou vám známé. Místo motivace vás tedy raději seznámím o čem tento tutoriál bude a o čem ne. Nebude o hardwaru. To je kapitola sama pro sebe. Bude o různých možnostech jak pomocí SW snižovat spotřebu STMka. Stručně se mrkneme jak lze manipulovat taktovací frekvencí a jaký to má vliv na spotřebu a poté se vrhneme na režimy spánku. Vyzkoušíme si SLEEP, STOP i STANDBY režim. Vyzkoušíme si různé formy uspání a probouzení. U toho všeho budeme sledovat spotřebu abychom získali hrubou představu. K pokusům nám poslouží jeden z nejmenších a nejlevnějších čipů - STM32F030F4 v TSSOP24 pouzdře osazený na bastldesku a napájený 3.3V zdrojem. Předpokládám, ale že většina technik bude přenositelná i na jiné čipy než jen řadu F0. Protože kódy budou někdy rozsáhlé a budou se často opakovat, najdete je celé ke stažení na konci článku.

Než se pustíme do práce, stálo by za to se ve stručnosti seznámit se základními technikami které lze na STMkách používat ke kontrole spotřeby.

  • Všem periferiím na STMku lze vypnout clock (tzv. "Clock gating"). Všichni to běžně děláte, protože po startu má většina z nich clock vypnutý a vy si ho musíte zapnout. Není na ní nic složitého a jen pro představu třeba takové GPIOB spolkne při 48MHz přibližně 0.7mA (a to aniž by piny PB vykonávaly nějakou činnost).


  • Odběr závisí na frekvenci. Je tedy vhodné taktovat jádro nebo periferie vysokými frekvencemi jen když je to opravdu potřeba. Systém prescalerů (děliček/předděliček chcete-li) vám dává široké možnosti. Umožňuje vám měnit si clock dle okamžité potřeby. Například můžete před výpočtem jádro taktovat na 48MHz a po výpočtu snížit frekvenci kam vám aplikace dovolí (klidně na desítky kHz). Podobné je to i u periferií, timeru tikajícím na několika kHz nebo UARTu s baudrate 9600B/s bohatě postačí clock pod 1MHz.


  • Režimy spánku umožňují snížit spotřebu až o několik řádů. Čím hlubší režim spánku zvolíte, tím menší bude spotřeba a tím méně událostí bude schopných mikrokontrolér probudit.


  • Nezanedbatelný vliv má také konfigurace nevyužitých pinů. Jsou-li nastaveny jako vstupy a není na ně přivedena jasná logická hodnota, mohou zvyšovat odběr v řádu desítek až stovek uA. Je tedy vhodné je správně ošetřit a nevyužité GPIO dát do analogového módu, nebo jim připnout pullup nebo pulldown rezistor.


Vliv taktovací frekvence

Velkou částí tutoriálu nás bude provázet jeden modelový příklad. Necháme STMko reagovat na stisk tlačítka půlsekundovým bliknutím LEDkou na PA5. Tlačítko budeme mít připojené na PA0 netradičně proti VCC. V čipu zapneme vnitřní pull-down rezistor a stisk rozpoznáme jako vzestupnou hranu. K detekci využijeme externí přerušení. To není běžná a ani vhodná metoda jak hlídat stisk tlačítka, ale nám teď nejde o tlačítko. To je zde jen v roli "generátoru" signálu. První příklady v nichž budeme zkoušet redukci clocku, ale můžete postavit i jinak. Klidně můžete blikat LEDkou pomocí "delay" nebo timeru, případně stisk hlídat libovolnou metodou co se vám líbí. Já se ale pro přehlednost budu držet zmíněného postupu s využitím externího přerušení. Zkusme tedy jaký bude odběr naší aplikace s různým clockem. Pro testy jsem připravil následující funkce:

  • clock_48() Rozběhne PLL z HSI na 48MHz a zvolí ho jako clock jádra i všech periferií. Tedy provoz na "plný výkon"

  • clock_8() Jádro i periferie poběží na frekvenci HSI, tedy 8MHz

  • clock_1() Clock z HSI podělím 8 a jádro i periferie poběží na 1MHz

  • clock_31k() Clock z HSI podělím 256 a jádro i periferie poběží na 31.25kHz


Sluší se poznamenat, že naše aplikace má valnou většinu periferií vypnutou. Zapnuli jsme jen GPIOA a SYSCFG. Všimněte si také, že udržuji proměnnou SystemCoreClock aktuální neboť ji ke své činnosti potřebuje moje _delay_ms() funkce (a mnoho dalších z SPL knihoven). Ke stanovení hodnoty jsem mohl použít knihovní funkci SystemCoreClockUpdate() ale stálo by mě to přibližně 0.14kB paměti a já s ní nechtěl na tak malém čipu plýtvat. Před konfigurací GPIO volám funkci pull_unused_gpio() jejímž úkolem je nastavit všechny piny, krom PA13 a PA14, jako vstupy s pull-down rezistorem. Na zmíněných dvou pinech máme SWD a jejich rekonfigurací bychom se připravili o možnost debugu (navíc v roli SWD pullup/down rezistory obsahují). Tento postup má zaručit, že na všech pinech bude nějaká rozumně definovaná vstupní hodnota. Jak už bylo napsáno, bez tohoto ošetření, pokud jsou piny ponechány jako vstup bez pull-down nebo pull-up, zvyšují odběr řádově o desítky až stovky uA.


// A) vliv frekvence na odběr

#include "stm32f0xx.h"
// výstupy pro LEDku
#define TEST_H GPIOA->BSRR = GPIO_Pin_5
#define TEST_L GPIOA->BRR = GPIO_Pin_5

void _delay_ms(uint32_t Delay);
void init_test_output(void);
void pull_unused_gpio(void);
void init_exti(void);
void clock_48(void);
void clock_8(void);
void clock_1(void);
void clock_31k(void);

volatile uint8_t irq_flag=0; // vlajka idnikující potřebu bliknout LEDkou

int main(void){
 //clock_48();
 //clock_8();
 clock_1();  // clock například 1MHz
 //clock_31k();
 pull_unused_gpio(); // ošetřit nepoužité GPIO
 init_test_output(); // výstup na LEDku
 init_exti(); // přerušení od PA0

 while (1){
  if(irq_flag){ // pokud došlo ke stisku, blikni LEDkou
   TEST_H;
   _delay_ms(500);
   TEST_L;
   irq_flag=0;
  }
 }
}

// EXTI z PA0 bude sloužit k buzení z režimů spánku
// Na PA0 připojeno tlačítko proti VCC (interní pull down)
void init_exti(void){
EXTI_InitTypeDef exti;
NVIC_InitTypeDef nvic;
GPIO_InitTypeDef gp;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // kvůli PA0
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); // kvůli EXTI

// PA0 jako vstup s pull-down
gp.GPIO_Pin = GPIO_Pin_0;
gp.GPIO_Mode = GPIO_Mode_IN;
gp.GPIO_OType = GPIO_OType_PP;
gp.GPIO_PuPd = GPIO_PuPd_DOWN;
gp.GPIO_Speed = GPIO_Speed_Level_1;
GPIO_Init(GPIOA, &gp);

// Přiřadíme Lince 0 port GPIOA (tedy mapujeme pin PA0)
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);

// povolíme externí přerušení z Linky 0 na vzestupnou hranu
exti.EXTI_Line = EXTI_Line0;
exti.EXTI_Mode=EXTI_Mode_Interrupt;
exti.EXTI_Trigger=EXTI_Trigger_Rising;
exti.EXTI_LineCmd=ENABLE;
EXTI_Init(&exti);

// povolíme externí přerušení (Linky 0) v NVIC
nvic.NVIC_IRQChannel=EXTI0_1_IRQn;
nvic.NVIC_IRQChannelPriority=3;
nvic.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&nvic);
}

// Rutina přerušení od EXTI jen nastaví "vlajku" podle které v mainu blikneme LEDkou
void EXTI0_1_IRQHandler(void){
 if(EXTI_GetITStatus(EXTI_Line0)){
  EXTI_ClearITPendingBit(EXTI_Line0);
  irq_flag = 1;
 }
}

// PA5 je indikační výstup (k ověření že čip žije a pracuje)
void init_test_output(void){
 GPIO_InitTypeDef gp;
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

 gp.GPIO_Pin = GPIO_Pin_5;
 gp.GPIO_Mode = GPIO_Mode_OUT;
 gp.GPIO_OType = GPIO_OType_PP;
 gp.GPIO_PuPd = GPIO_PuPd_NOPULL;
 gp.GPIO_Speed = GPIO_Speed_Level_1;
 GPIO_Init(GPIOA, &gp);
}


void pull_unused_gpio(void){
 GPIO_InitTypeDef gp;
 // nastavíme všem pinům že jsou to vstupy s pull-down rezistorem
 // ponecháme si jen konfiguraci pinů SWD, které mají interní pullup/pulldown rezistory
 // na našem čipu jsou jen GPIOA,GPIOB a GPIOF
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB | RCC_AHBPeriph_GPIOF, ENABLE);
 gp.GPIO_Pin = GPIO_Pin_All;
 gp.GPIO_Mode = GPIO_Mode_IN;
 gp.GPIO_OType = GPIO_OType_PP;
 gp.GPIO_PuPd = GPIO_PuPd_DOWN;
 gp.GPIO_Speed = GPIO_Speed_Level_1;
 GPIO_Init(GPIOB, &gp);
 GPIO_Init(GPIOF, &gp);
 gp.GPIO_Pin = GPIO_Pin_All & (~(GPIO_Pin_13 | GPIO_Pin_14));  // vše krom PA13 a PA14 (SWD)
 GPIO_Init(GPIOA, &gp);
 // nezapomeneme vypnout clock ;)
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB | RCC_AHBPeriph_GPIOF, DISABLE);
}

void clock_48(void){
 RCC_PLLConfig(RCC_PLLSource_HSI,RCC_PLLMul_12);  // nastavit PLL na násobení 12x (8MHz / 2 * 12 = 48MHz)
 RCC_PLLCmd(ENABLE); // spustit PLL
 while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) != SET); // počkat na rozběh PLL
 RCC_HCLKConfig(RCC_SYSCLK_Div1); // SYSCLK z PLL nijak nedělit
 RCC_PCLKConfig(RCC_HCLK_Div1); // HCLK ze SYSCLK nijak nedělit (jedeme naplno)
 RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // přepnout SYSCLK na PLL (jedeme na 48MHz)
 //SystemCoreClockUpdate(); // 0.14kB zbytečně...
 SystemCoreClock = 48000000; // clock je 48MHz
}

void clock_8(void){
 RCC_HCLKConfig(RCC_SYSCLK_Div1); // SYSCLK nijak nedělit
 RCC_PCLKConfig(RCC_HCLK_Div1); // HCLK ze SYSCLK nijak nedělit (periferiím stejný takt jako jádru)
 RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI); // zdrojem clocku je 8MHz HSI
 SystemCoreClock = 8000000; // clock je 8MHz
}

void clock_1(void){
 RCC_HCLKConfig(RCC_SYSCLK_Div8); // SYSCLK dělit 8 => jádro jede na 1MHz...
 RCC_PCLKConfig(RCC_HCLK_Div1); // ...periferiím stejný takt jako jádru
 RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI); // zdrojem clocku je 8MHz HSI
 SystemCoreClock = 1000000; // clock je 1MHz
}

void clock_31k(void){
 RCC_HCLKConfig(RCC_SYSCLK_Div256); // SYSCLK dělit 256 => jádro jede na 31.25kHz...
 RCC_PCLKConfig(RCC_HCLK_Div1); // ...periferiím stejný takt jako jádru
 RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI); // zdrojem clocku je 8MHz HSI
 SystemCoreClock = 31250; // clock je 31.25kHz
}

// Delay na bázi systicku (vyžaduje korektní nastavení SystemCoreClock)
void _delay_ms(uint32_t Delay){
 __IO uint32_t  tmp = SysTick->CTRL;  // Clear the COUNTFLAG first
 ((void)tmp);

 // init systick to us delays ...
 SysTick->LOAD  = (SystemCoreClock/1000)-1; // 1us time
 SysTick->VAL   = 0UL;
 SysTick->CTRL  = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk;

 if(Delay < 0xffffff){Delay++;} // Add a period to guaranty minimum wait
 while (Delay){
  if((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != 0U){Delay--;}
 }
}
 


Při měření odběru mějte na paměti že připojený debugger (ST-Link z Nucleo kitu) má odběr okolo 350uA. Před měřením jsem tedy ST-Link vždy odpojil (což je mírně řečeno otravné). Hodnoty odběru berte jako orientační.

Orientační spotřeba naší aplikace v závislosti na frekvenci
Frekvence    Odběr
48MHz        12.05mA
8MHz         2.40mA
1MHz         0.68mA
31kHz        0.42mA


Z tabulky je vidět, že každý MHz clocku zvedne spotřebu přibližně o 240uA. A taky si můžete všimnout, že od určitého bodu nemá další snižování frekvence smysl. Jinak řečeno snížit takt ze 48MHz na 24MHz vám přinese úsporu okolo 6mA, kdežto snížení frekvence z 64kHz na 32kHz nebude bez použití dalších technik nijak zvlášť patrné.

Režim SLEEP


Jestliže potřebujete přečkat delší období (pod nímž si můžete představit jednotky ms nebo celé hodiny) bez aktivity MCU, můžete k tomu využít jden ze tří režimů spánku. Jeho výběr záleží na tom co všechno nepotřebujete. Nejmělčí režim spánku je SLEEP, který vypne clock pouze jádru. Jak brzy uvidíte v aktivním režimu to může znamenat značnou úsporu. Probudit vás z něj může jakékoli přerušení nebo event (což jak brzy uvidíte je skoro to samé). V podstatě vás nijak neomezuje a ideálně se hodí na překlenutí období kdy nemá jádro nic na práci a pracují periferie. Například pokud pomocí DMA přijímáte nebo posíláte skrze nějaké rozhraní data. Nebo když čekáte na akci timeru a nebo když čekáte na výsledek AD převodů. Příkladů použití je asi nekonečno. K dispozici máme tři metody jak a kdy se probouzet a usínat.

  • A) Můžeme usínat příkazem WFI (Wait For Interrupt) a probouzet se pomocí přerušení. Příslušné přerušení, které má čip vzbudit musí být povolené v NVIC. Po probuzení program skočí do rutiny přerušení a vykoná ji. Pak pokračuje do té doby než opět narazí na příkaz ke spánku.

  • B) Můžeme usínat příkazem WFE (Wait For Event) a budit se eventem z EXTI (což prakticky znamená některým z externích přerušení nebo RTC). Před usnutím si musíme mazat příslušnou vlajku v EXTI.

  • C) Můžeme usínat příkazem WFE (Wait For Event) a budit se přerušením od libovolné periferie aniž bychom vstupovali do rutiny přerušení. Zdroj přerušení musíme povolit v periferii a nepovolovat v NVIC. Program se rozběhne od místa kde usnul a před usnutím si musíme vymazat příslušné vlajky jak v periferii tak v NVIC (jinak by příště MCU neusnulo).

  • D) Můžeme usínat automaticky po skončení rutiny přerušení. Aplikace pak nemusí volat instrukci WFI a usne ihned jakmile dokončí rutinu a žádné další přerušení nečeká na obsloužení. Veškerý kód aplikace (kromě inicializace) se pak musí nacházet v rutinách přerušení, protože od prvního uspání už program pracuje jen v nich.

Všechny čtyři metody si na našem modelovém příkladu vyzkoušíme.

A - Začneme první z nich - Wait for Interrupt. V našem kódu nemusíme nic měnit, stačí přidat jediný řádek s funkcí PWR_EnterSleepMode(). Jejím argumentem je metoda uspání, tedy buď WFI nebo WFE. Nejprve tedy WFI. Program po vykonání této funkce usne a probere se až s příchodem externího přerušení na PA0, vykoná rutinu přerušení a vrátí se do hlavní smyčky za příkaz ke spánku. Tam na něj čeká kód který blikne LEDkou . Pak opět narazí na příkaz ke spánku a usne. Tento příklad lze snadno modifikovat a budit se libovolnou periferií. Protože celý zbytek kódu zůstává stejný, dovolím si zveřejnit jen jeho část.


int main(void){
 clock_8(); // pracujeme například s 8MHz clockem
 pull_unused_gpio(); // ošetřit nepoužité GPIO
 init_test_output(); // PA5 jako výstup na LED
 init_exti(); // PA0 jako vstup

 while (1){
  PWR_EnterSleepMode(PWR_SLEEPEntry_WFI); // Spi dokud nepřijde přerušení
  if(irq_flag){ // pokud došlo ke stisku, blikni LEDkou
   TEST_H;
   _delay_ms(500);
   TEST_L;
   irq_flag=0;
  }
 }
}
 


B - Druhou zmíněnou možností jak čip uspat je instrukcí WFE. V takovém případě nás může vzbudit buď některá z EXTI linek nastavená jako Event. A nebo to může být jakékoli přerušení libovolné periferie, které není povolené v NVIC. Pojďme si nejprve vyzkoušet první možnost. V konfiguraci EXTI změníme EXTI_Mode z "Interrupt" na "Event". Smažeme konfiguraci NVIC i celou rutinu přerušení od EXTI. Odpadne nám také potřeba "vlajky" irq_flag. Program se probudí na místě kde usnul, vůbec nebude vstupovat do rutiny přerušení. My ho po probuzení necháme jen bliknout LEDkou, pak smažeme vlajku EXTI a opět uspíme. Vlajku mažu záměrně až za bliknutí LEDkou, abych se vyvaroval problémům se zákmity tlačítka.


int main(void){
 clock_8(); // pracujeme například s 8MHz clockem
 pull_unused_gpio(); // ošetřit nepoužité GPIO
 init_test_output(); // PA5 jako výstup na LED
 init_exti(); // PA0 jako vstup

 while (1){
  PWR_EnterSleepMode(PWR_SLEEPEntry_WFE); // Spi dokud nepřijde přerušení
  // tady by bylo na místě si zkontrolovat která periferie nás probudila !
  // já ale vím že to bylo EXTI0 (jiný event jsem nepovolil)
  TEST_H; // Blikneme LEDkou
  _delay_ms(500);
  TEST_L;
  EXTI_ClearITPendingBit(EXTI_Line0); // vymažu vlajku abych mohl usnout a čekat na další stisk
 }
}

// EXTI z PA0 bude sloužit k buzení z režimů spánku
// Na PA0 připojeno tlačítko proti VCC (interní pull down)
void init_exti(void){
EXTI_InitTypeDef exti;
GPIO_InitTypeDef gp;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // kvůli PA0
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); // kvůli EXTI

// PA0 jako vstup s pull-down
gp.GPIO_Pin = GPIO_Pin_0;
gp.GPIO_Mode = GPIO_Mode_IN;
gp.GPIO_OType = GPIO_OType_PP;
gp.GPIO_PuPd = GPIO_PuPd_DOWN;
gp.GPIO_Speed = GPIO_Speed_Level_1;
GPIO_Init(GPIOA, &gp);

// Přiřadíme Lince 0 port GPIOA (tedy mapujeme pin PA0)
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);

// povolíme event z Linky 0 na vzestupnou hranu
exti.EXTI_Line = EXTI_Line0;
exti.EXTI_Mode=EXTI_Mode_Event; // Event (!)
exti.EXTI_Trigger=EXTI_Trigger_Rising;
exti.EXTI_LineCmd=ENABLE;
EXTI_Init(&exti);
// vůbec nepovolujeme EXTI v NVIC
}
 


C - Předchozím způsobem lze budit čip jen pomocí externího přerušení a vybraných periferií vedoucích do EXTI jako například RTC. To vám často nebude stačit, takže si příklad upravíme tak aby MCU směla budit každá periferie. Úprava to bude snadná. Vrátíme EXTI zpět do módu "Interrupt" (případně pokud někdo z vás chce, nastavte si přerušení od jiné periferie). Někde v inicializaci nastavíme pomocí funkce NVIC_SystemLPConfig() bit SEVONPEND, který povolí buzení libovolným přerušením. Pro další vstup do spánku budeme muset mazat nejen vlajku v periferii (teď v EXTI) ale i v NVIC a to pomocí funkce NVIC_ClearPendingIRQ(). Opět si dovolím zveřejnit jen část kódu.


int main(void){
 clock_48(); // pracujeme například s 8MHz clockem
 init_test_output(); // PA5 jako výstup na LED
 init_exti(); // PA0 jako vstup
 NVIC_SystemLPConfig(NVIC_LP_SEVONPEND, ENABLE); // budit jakýmkoli přerušením

 while (1){
  PWR_EnterSleepMode(PWR_SLEEPEntry_WFE); // Spi dokud nepřijde přerušení
  // tady by bylo na místě si zkontrolovat která periferie nás probudila !
  // já ale vím že to bylo EXTI0 (jiný event jsem nepovolil)
  TEST_H;
  _delay_ms(500);
  TEST_L;
  EXTI_ClearITPendingBit(EXTI_Line0); // vymažu vlajku v periferii
  NVIC_ClearPendingIRQ(EXTI0_1_IRQn); // vymazat vlajku i v NVIC
 }
}
 


D - Poslední variantou bude program žijící jen v rutinách přerušení. Bude automaticky usínat po dokončení poslední rutiny přerušení. Jakmile čip uspíte už nikdy nevykoná žádný kód mimo rutiny přerušení. EXTI je opět nastavené jako "interrupt", povolené v NVIC a bliknutí LEDkou máme uvnitř rutiny přerušení. A ano máme tam i delay ! Což se na první pohled může zdát jako prohřešek proti slušným mravům. Jenže náš program jiný kód než rutiny přerušení vykonávat nemůže a tudíž nehrozí, že by tento delay zdržoval zbytek kódu od práce. Navíc pokud bychom chtěli upřednostnit nějaké další přerušení, máme možnost dát mu vyšší prioritu. Opět zveřejním jen část kódu a dovolím si vypustit inicializaci EXTI, neboť je shodná s prvním příkladem.


int main(void){
 clock_31k(); // volím nižší clock když v aktivním režimu jen tupě čekám na LEDku
 pull_unused_gpio(); // ošetřit nepoužité GPIO
 init_test_output();
 init_exti();
 NVIC_SystemLPConfig(NVIC_LP_SLEEPONEXIT, ENABLE); // usnout po skončení IRQ rutiny
 PWR_EnterSleepMode(PWR_SLEEPEntry_WFI); // jdeme spát :)
 while (1){
  // žádný kód tady teď nemá smysl ... program se sem nedostane
 }
}

// V rutině přerušení blikneme LEDkou
void EXTI0_1_IRQHandler(void){
 if(EXTI_GetITStatus(EXTI_Line0)){
  TEST_H; // blikneme LEDkou
  _delay_ms(500); // ano, máme delay v IRQ rutině a není to proti slušnému chování :D
  TEST_L;
  EXTI_ClearITPendingBit(EXTI_Line0); // vlajku mažu opět až nakonec abych neměl problémy se zákmity
 }
}
 


Když už jsem všechny ty různé režimy vyzkoušel, změřil jsem i jejich odběr. Takže než se vrhneme na režimy spánku, můžete si v následující tabulce prohlédnout jak si různá řešení vedou.

Odběr v testovaných režimech

Frekvence  RUN      SLEEP (WFI) SLEEP (WFE + Event z EXTI)  SLEEP (WFE)   SLEEP (WFI + Sleeponexit)
48MHz      12.10mA   4.60mA      4.55mA                      4.44mA        4.56mA
8MHz        2.40mA   0.83mA      0.82mA                      0.82mA        0.82mA
1MHz        0.68mA   0.49mA      0.48mA                      0.47mA        0.47mA
31kHz       0.42mA   0.42mA      0.42mA                      0.42mA        0.42mA
 


Vidíte, že obecně může mít SLEEP režim citelný dopad na spotřebu a pro mnoho aplikací bohatě postačí (zvláště pokud má okolní HW například o řád větší spotřebu než MCU).

Režim STOP


Dalším režimem spánku je režim STOP. Je to hluboký spánek z něhož vás může probrat pouze EXTI. Do EXTI naštěstí vedou nejen linky externího přerušení, ale i vnitřní signály. V našem malém čipu konkrétně signály z RTC. V jiných čipech to ale budou další periferie jako komparátory, USB, I2C, UART, PVD nebo třeba Ethernet. Pokud vám tyto zdroje stačí, odmění vás režim spotřebou v řádech desítek uA. Ve STOP módu neběží HSE ani HSI oscilátor, ale regulátor napětí ano. Díky tomu zůstává zachován obsah RAM a GPIO si ponechávají svoje stavy. Regulátor můžete spustit v "low power" módu s drobně nižší spotřebou. Detailnější informace o regulátoru datasheet neuvádí, takže se můžeme jen dovozovat že v Low-power módu prostě není schopen dodat větší proudy. Otázka zní kdo to v režimu spánku potřebuje. Probuzení ze STOP módu trvá přibližně 5us, protože se musí startovat HSI oscilátor, který se vždy nastaví jako zdroj clocku. Pokud tedy plánujete používat například PLL, musíte si ho po každém probuzení rozběhnout. Stejně jako v režimu SLEEP lze MCU uspat pomocí WFI nebo WFE. Jak později uvidíte STOP je vlastně ten nejhlubší spánek v běžném slova smyslu.

Protože v tomto režimu neběží clock jádru ani periferiím (až na vyjímky), nemá clock ani debugovací systém. Pokud chcete v tomto režimu debugovat, musíte si to funkcí DBGMCU_Config() povolit. Přirozeně za cenu jisté spotřeby navíc. Takže do cílové aplikace je vhodné tuto funkci vypnout. Tím vyvstává otázka jak se s čipem spojit, když nemá v provozu SWD rozhraní. Jedna z možností je připojovat se během restartu ("connection under reset"). Vyvedete si reset na tlačítko. V konfiguraci debuggeru si nastavíte "connection under reset". Debugger pak čeká na váš reset, během něj se připojí a přebere kontrolu nad čipem. A jakmile tlačítko uvolníte nahraje program a umožní vám debug (do té doby než program vypne SWD rozhraní). Další možností je využít 5ti drátové připojení ST-Linku, na kterém jsem při návrhu bastldesky bohužel nemyslel

Úkol naší aplikace (bliknout po stisku tlačítka) zůstává nezměněn. K uspání jsem si dovolil použít variantu uspat pomocí WFE a budit eventem z EXTI (tedy B ). Z prostorových důvodů opět nezveřejňuji celý zdrojový kód, ale jen klíčové části.


int main(void){
 clock_8(); // pracujeme například s 8MHz clockem
 pull_unused_gpio(); // nenecháme GPIO viset "ve vzduchu"
 init_test_output(); // PA5 jako výstup na LED
 init_exti(); // PA0 jako vstup

 while (1){
  PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_SLEEPEntry_WFE); // Spi dokud nepřijde event
  // odtud se aplikace rozbíhá s clockem z HSI
  // tady by bylo na místě si zkontrolovat která periferie nás probudila !
  // já ale vím že to bylo EXTI0 (jiný event jsem nepovolil)
  TEST_H; // Blikneme LEDkou
  _delay_ms(500);
  TEST_L;
  EXTI_ClearITPendingBit(EXTI_Line0); // vymažu vlajku abych mohl usnout a čekat na další stisk
 }
}

// EXTI z PA0 bude sloužit k buzení z režimů spánku
// Na PA0 připojeno tlačítko proti VCC (interní pull down)
void init_exti(void){
EXTI_InitTypeDef exti;
GPIO_InitTypeDef gp;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // kvůli PA0
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); // kvůli EXTI

// PA0 jako vstup s pull-down
gp.GPIO_Pin = GPIO_Pin_0;
gp.GPIO_Mode = GPIO_Mode_IN;
gp.GPIO_OType = GPIO_OType_PP;
gp.GPIO_PuPd = GPIO_PuPd_DOWN;
gp.GPIO_Speed = GPIO_Speed_Level_1;
GPIO_Init(GPIOA, &gp);

// Přiřadíme Lince 0 port GPIOA (tedy mapujeme pin PA0)
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);

// povolíme externí přerušení z Linky 0 na vzestupnou hranu
exti.EXTI_Line = EXTI_Line0;
exti.EXTI_Mode=EXTI_Mode_Event; // Event (!)
exti.EXTI_Trigger=EXTI_Trigger_Rising;
exti.EXTI_LineCmd=ENABLE;
EXTI_Init(&exti);
// vůbec nepovolujeme EXTI v NVIC
}
 


Reakční dobu, něco málo přes 5us, si můžete prohlédnout na oscilogramu. Spotřeba naší aplikace je teď 17.6uA. Protože mám VDDA a VDD na své desce spojené, mohu v option bytes vypnout VDDA monitor, tedy obvod dohledu nad VDDA a snížit tak spotřebu na 16.5uA.


Doba probuzení ze STOP režimu. Světle modrá signál pro indikační LED, tmavě modrá stav tlačítka.


Režim STANDBY


STANDBY bych asi neoznačil za režim spánku. Spíš než spánek je to smrt. Tenhle nejúspornější režim totiž spotřebě obětuje téměř vše. Odpojí od energie drtivou většinu čipu, takže až na pár výjimek (které budu diskutovat) všechny piny přejdou do stavu vysoké impedance. Všechny periferie jsou vypnuty, oscilátory HSI a HSE neběží. Obětován je také obsah RAM, protože i ta přijde o energii. Čip může probudit jen RTC, vzestupná hrana na WKUP pinu, Reset nebo Watchdog a po probuzení projde čip restartem ! Spíš než probuzení ze spánku se dá mluvit o zmrtvých vstání. Existuje velmi omezená možnost jak si přece jen něco z minulého života (tedy z doby před uspáním / smrtí) zapamatovat. Energii dostává jen wakeup obvod a backup doména. V backup doméně se nachází RTC a nízkofrekvenční oscilátory (tedy LSE a LSI). Spolu s nimi tam je také skupina 5ti 32bitových registrů do nichž si můžeme uložit data, která mají přečkat smrt. Legrační je fakt, že se o nich datasheet zmiňuje jen náznakem a to větou "Tamper detection erases the backup registers". Žádné další informace v datasheetu nenajdete (takže ani nevíte co ta Tamper událost vlastně maže). Jiné datasheety (např k čipům F0x1 se jim věnuje). Je tedy otázkou jestli je to záměr nebo chyba. Každopádně jak brzy uvidíte, fungují. Osobně se domnívám, že STANDBY režim najde uplatnění jen vyjmečně, ale to není důvod si ho nevyzkoušet.

Pro jednoduchost budeme čip budit k životu signálem na WKUP1 pinu. Na našem malém čipu je to pin PA0. Jak už bylo řečeno, k probuzení je nutné přivést vzestupnou hranu. Proto mám tlačítko připojené trochu netradičně proti VCC a pro tuto aplikaci jsem ho vybavil externím pull-down rezistorem. Náš program bude mít za úkol počítat kolikrát byl probuzen od posledního restartu a po každém probuzení bliknout LEDkou právě tolikrát. Tím získáme jakýsi důkaz, že si alespoň tuto minimální informaci dokáže zapamatovat. Díky tomu, že se po usnutí všechny piny "odpojí" nemusíme je ošetřovat tak jako v předchozích ukázkách. Protože ale čip prochází při každém probuzení restartem, musíme nějak rozpoznat zda restart proběhl následkem probuzení (skrze WKUP1 pin) a nebo zda šlo o tvrdý reset pomocí tlačítka na RST. Rozpoznat to lze pomocí vlajky WUF (resp. WU). Ta se nachází v PWR periferii a abychom ji mohli číst (a později provádět další akce), musíme do PWR přivést clock. To bude tedy náš první krok po "spuštění". Pokud je vlajka WU (Wake Up) nastavená, znamená to že jde o "probuzení" a ne restart. V takovém případě si odemkneme přístup do backup domény (fcí PWR_BackupAccessCmd() ) a přečteme si z nultého backup registru aktuální počet vzbuzení (fcí RTC_ReadBackupRegister() ). Pak počet startů inkrementujeme a novou hodnotu opět uložíme do backup registru (fcí RTC_WriteBackupRegister() ). Následně ze slušnosti ještě zakážeme přístup do backup domény. Nastavíme si výstup pro LED a zablikáme zjištěný počet probuzení. Pokud čip prošel restartem (místo legálního probuzení), bude vlajka WU vynulovaná a my si vynulujeme počítadlo (v backup registru). Po jedné z těchto dvou akcí čip uspíme. Což proběhne ve třech krocích. Nejprve si vymažeme WU vlajku abychom příště rozpoznali zdroj probuzení, povolíme buzení z WKUP pinu (fcí PWR_WakeUpPinCmd() ) a usneme příkazem PWR_EnterSTANDBYMode().


uint32_t i,pocet;

int main(void){
 clock_1(); // pracujeme například s 1MHz clockem
 //DBGMCU_Config(DBGMCU_STANDBY, ENABLE); // povolit debug v STANDBY módu
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); // spustit clock PWR periferii
 // rozlišíme jestli se čip probudil Wakeup pinem nebo Resetem
 if(PWR_GetFlagStatus(PWR_FLAG_WU) == SET){ // pokud jde o Wakeup
 PWR_BackupAccessCmd(ENABLE); // povolíme přístup do backup domény
 pocet=RTC_ReadBackupRegister(RTC_BKP_DR0); // přečteme si počet startů...
 pocet++; // ...inkrementujeme ho ...
 RTC_WriteBackupRegister(RTC_BKP_DR0,pocet); //... a uložíme
 PWR_BackupAccessCmd(DISABLE); // zakážeme přístup do backup domény (není nutné)
  init_test_output(); // nastavíme PA5 jako výstup
 // Blikneme tolikrát kolikrát už se aplikace probudila od posledního restartu
 for(i=0;i<pocet;i++){
  TEST_H;
  _delay_ms(250);
  TEST_L;
  _delay_ms(250);
 }
 }else{
 // pokud jsme se probudili restartem ...
 PWR_BackupAccessCmd(ENABLE);
 RTC_WriteBackupRegister(RTC_BKP_DR0,0); // ... vymažeme počítadlo "probuzení"
 PWR_BackupAccessCmd(DISABLE);
 }
 // vyčistíme vlajku "Wake up" abychom příště poznali co nás probudilo
 PWR_ClearFlag(PWR_FLAG_WU);
 PWR_WakeUpPinCmd(PWR_WakeUpPin_1, ENABLE); // povolíme buzení z WKUP1 pinu (PA0)
 PWR_EnterSTANDBYMode(); // uspíme čip
 while (1){
 // sem se vůbec nedostaneme
 }
}
 


Na našem čipu se nachází jen jeden WKUP pin (PA0), STM32F0x0 ve větších pouzdrech mají piny dva. A lepší čipy (např F072) pak ještě více. Mimo tlačítko můžete na pin přivádět signál například z low-power časovačů, případně z různých externích obvodů. Mnohem širší využití má buzení pomocí RTC. To si ale necháme na příště. Nejspíš jste zvědaví na spotřebu aplikace z poslední ukázky. Tak vás nebudu napínat, naměřil jsem 2.1uA.

Zdrojové kódy ke stažení: zdrojaky.zip


Testovací sestava. Na bastl desce STM32F030F4, jako debugger ST-Link z nucleo kitu.


Odkazy
STM32F030 Reference manual
STM32F030 Datasheet
Režimy spánku na STM32L0

Rozcestník na další díly seriálu naleznete na www.elektromys.eu/stm32.php V1.00 29.4.2019
By Michal Dudka (m.dudka@seznam.cz)


O dispenserech na SMT pastu jsme již psali několikrát. Tentokráte je to asi to nejjednodušší co může být. Vytisknete si vlastní dispenser.

Není k tomu hodně co říct. Podívejte se na video a pokud se vám tento nápad bude líbit, stáhněte si 3D soubory. Toť vše.

Ke stažení dispenser 3D soubory ve forátu STL.

Zdroj: Solder paste and flux Dispenser


Popis sestavy mikroskopické kamery, objektivu a držáku pro osazovaní a opravy desek plošných spojů.



Už nějakou dobu jsem pokukoval po mikroskopických kamerách pro osazování a opravy desek plošných spojů, obecně pro nahrazení binokulárního mikroskopu. Ne že by měl mikroskop špatné parametry, ba naopak, ale práce s mikroskopem je při delší používání dosti unavující. Po špatných zkušenostech jsem předem zavrhnul takový USB kukátka s cenou přibližně od 600 do 2000 Kč, zvětšení 20 – 400x, rozlišení 1.3 – 5Mpx, kde je většinou plastová optika (čočka z mléčné lahve ), vratký stojánek….. To je opravdu na nic. Zase jsem se nechtěl s cenou vyšplhat někam do desetitisíců, takže jsem začal lovit na Aliexpressu, kde je přehršel takových obchodů, které nabízejí buď jednotlivé díly – kamery, objektivy, držáky…., nebo celé sestavy. A zase od nějakých $50 až nad $500 s automatickým ostřením atd. S optikou to moc neumím tak jsem pořád váhal co vybrat a kolik za to zaplatit. Jeden den jsem si něco vybral a druhý den jsem to zas změnil. A navíc otázka kvality z Číny. Nakonec jsem se tedy rozhodnul, že nakoupím jednotlivé díly, bez stojanu. Stojan jsem použil z mikroskopu, který je robustní a ke kterému jsem nechal zhotovit pochromovanou tyč o průměru 25mm.

Výběr dílů a celý popis je zde.




Přejít na stranu  1 2 3 ... 247 248 249
Prohledat MCU-mikroelektronika
Chatbox