logo_elektromys.eu

/ User Bytes v STM32 (a AVR)|

Rád bych se s vámi podělil o jednoduchou ukázku jak a k čemu použít takzvaná "User data". Demonstrovat to budu na STM32F0, které mají v paměti k tomuto účelu vyhrazené místo, potom to zobecním na STM32G0, které žádné vyhrazené místo nemají a nakonec ukážu obdobný postup pro 8bitové mikrokontroléry AVR, které mají též vyhrazenou část paměti. Než se do toho pustím, zkusím nastínit jednoduchý příklad k čemu to může sloužit.

/ Motivace |

Mám shodných 30 zařízení. Například drivery k APD detektorům, teplotní kontroléry, snímače na CAN sběrnici atp. Na každém z nich je MCU a všechny komunikují na společné sběrnici (například RS485 nebo CAN). Ve všech běží stejný firmware, ale každý z nich potřebuje mít unikátní "ID", nebo "adresu" kterou bude na sběrnici poslouchat. Mohl bych vzít zdrojový kód, udělat si v něm konstantu s adresou, přeložit ho, nahrát do jednoho zařízení, pak zdrojový kód upravit (přepsat konstantu s adresou), přeložit, nahrát do druhého zařízení a tak to udělat třicetkrát. Asi by to nebyla taková hrůza, ale tohle martyrium bych musel absolvovat pokaždé když bych se rozhodl firmware změnit a určitě bych v tom někdy udělal chybu a adresy by neseděly. Takže by mi práci ulehčila nějaká jednoduchá metoda, která by umožnila do MCU nahrát konstantu, která zůstane stejná i při dalším nahráváním nového firmwaru. Obdobnou vlastnost můžu chtít třeba i v situaci kdy provádím individuální kalibraci - opět budu chtít do každého MCU nahrát obecně jiné hodnoty, které tam zůstanou i po přehrání firmwaru. Pojďme si to nejprve vyzkoušet na STM32F042, poté na STM32G031 a nakonec pro AVR.

/ Zápis user bytes pomocí grafické aplikace STM32CubeProgrammer |

  1. Otevřeme Cube Programmer, zvolíme parametry připojení (SWD...) a připojíme pomocí tlačítka "connect".
  2. Na levém panelu je ikona OB jako Option Bytes
  3. V záložce User Data jsou dvě sekce Data0 a Data1 a zde můžete hodnoty zapsat (a taky si je přečíst)
  4. Kliknutím na "Apply" pak hodnoty zapíšeme do paměti (dole v okně "Log" si všimněte příkazů, které grafická aplikace volá)

Postup v grafcké aplikaci STM32CubeProgrammer

/ Zápis user data pomocí STM32_Programmer_CLI |

/ Čtení User Data v programu|

Konfiguraci User Data máme tímto vyřešenou a teď se podíváme jak si je ve firmwaru přečíst. Nabízí se několik metod. Pokud jste familiární s knihovnou HAL, stačí zavolat funkci HAL_FLASHEx_OBGetUserData(). Pokud preferujete přímý přístup do registrů, respektive CMSIS, můžete využít některou z nabízených variant:

// pomocí HAL funkcí
uint32_t data0 = HAL_FLASHEx_OBGetUserData(OB_DATA_ADDRESS_DATA0); // čte User data 0
uint32_t data1 = HAL_FLASHEx_OBGetUserData(OB_DATA_ADDRESS_DATA1); // čte User data 1

// pomocí CMSIS, čte oba user byte zároveň (16bit číslo)
uint16_t user_data = (FLASH->OBR)>>16; // ne úplně vhodný zápis
uint16_t user_data = (FLASH->OBR)>>FLASH_OBR_DATA0_Pos; // vhodnější podoba, čte oba byty zároveň

// jednotlivé 8bitové hodnoty lze číst také takto:
uint8_t data0 = ((FLASH->OBR) & FLASH_OBR_DATA0_Msk)>>FLASH_OBR_DATA0_Pos;
uint8_t data1 = ((FLASH->OBR) & FLASH_OBR_DATA1_Msk)>>FLASH_OBR_DATA1_Pos;

// asi nejelegantnější a asi i nejefektivnější zápis, čte jednotlivé bajty
uint8_t data0 = OB->DATA0;
uint8_t data1 = OB->DATA1;

Mě osobně přijde nejelegantnější poslední zápis. U HAL funkce mě trochu zaráží že návratová hodnota je typu uint32, ale prakticky se vrací 8bit hodnota. Užitečný může být i způsob kdy se čtou oba bajty zároveň jako jedno 16bit číslo. Jinak je to vše přímočaré a není co komentovat.

/ Řešení na STM32G031 |

Protože STM32G0 nic jako "User Data" v sekci Opton bytes nemá, nelze postup z STM32F0 přímo použít. Lze ale provést obdobnou věc tak, že hodnotu prostě zapíšeme někam do flash paměti. Vezmeme si například STM32G031K8 (Nucleo kit), podíváme se do Reference Manuálu na obrázek 9. Flash memory organization. Tam vidíme, že flash je rozdělena na "page", každá o velikosti 2kB. Page je nejmenší část flash, kterou lze samostatně smazat. Můžeme si tedy vyhradit například poslední page pro naše účely. Já zvolil page 31 (poslední page), tedy paměť v rozsahu 0x0800F800 až 0x0800FFFF. Dále učiníme rozhodnutí na které adresy budeme naši konstantu zapisovat. Adresu musíme mít zarovnanou na 8bytů (takže musí končit "číslicí" 0 nebo 8 - v hexa). Já zvolil začátek page, tedy adresu 0x0800F800. Zapisovat se bude 32bitová hodnota (nejsem si jist jestli nástroje umožňují zapisovat 16bit nebo 8bit hodnoty).


Tady si budeme ukládat konstantu(y)

Zápis lze provést buď pomocí grafické aplikace CubeProgrammer postupem:

  1. Kikneme na Connect (zobrazí se obsah flash paměti, pokud ne, tak vlevo nahoře klikneme na "device memory")
  2. Nalistujeme příslušnou adresu (můžeme si pomoct roletkou "Address")
  3. Dvojklik na buňku kterou chceme přepsat, zapsat hodnotu a potvrdit klávesou Enter


Ruční editace obsahu paměti pomocí grafické aplikace CubeProgrammer

Přehlednější možnost je opět pomocí konzole příkazem:

STM32_Programmer_CLI -c port=SWD -w32 0x0800F800 0x01234567 --verify

Který zapíše hodnotu 0x01234567 na adresu 0x0800F800 a zpětně ji přečte pro ověření. Pokud chcete zapsat vícero hodnot, lze je zapsat třeba následovně:

STM32_Programmer_CLI -c port=SWD -w32 0x0800F800 0x01234567 0x89ABCDEF


Příkaz pro zápis 32bit hodnoty (0x01234567) do flash paměti na adresu 0x0800F800
Prohlížení obsahu pamětí pomocí grafické aplikace CubeProgrammer

Protože jsme si vyčlenili kus flash paměti, je rozumné sdělit to i linkeru - úpravou linker skriptu - aby věděl, že tuhle část paměti nemá použít (například k uložení programu, nebo konstant). Ve složce s projektem najdeme soubor STM32G031K8TX_FLASH.ld a upravíme v něm sekci /* Memories definition */. Zmenšíme flash paměť ze 64kB na 62kB (je to snadné díky tomu, že jsme si vyčlenili právě poslední page).


Upravený linker skript, velikost Flash paměti zmenšena o poslední page na 62kB

Přečíst v programu hodnotu z nějaké adresy v paměti není nic složitého a může vypadat třeba takto:

uint32_t hodnota = *(__IO uint32_t *)0x0800F800;

| Poznámky /

| Odkazy /

/ Řešení na AVR |

Na mikrokontrolérech Atmel / Microchip (říkejme AVR) je vyčleněná část paměti EEPROM jako User Row. Ta zůstává zachována při mazání paměti, čímž je předurčena mimo jiné pro podobné účely jak je ten náš. Pojďme si tedy předvést obdobný postup jako v předchozích ukázkách pro STM32. Ukážeme si jak do User Row zapsat nějaké hodnoty a jak si je pak v programu přečíst. K demonstraci použiji Attiny416 .

V Microchip studiu otevřeme okno Tools->Device Programming->Memories. Tam najdeme sekci označenou User Signatures (64Bytes). Označení je matoucí, protože jde ve skutečnosti o User Row a v mém případě (Tiny416) má 32Bytů. Kliknu na "Read" a poté zvolím cestu k souboru kam chci obsah User Row uložit a vyberu formát jako bin (protože s intel hex se mi pracuje hůř). Uložený binární soubor má 32 bytů a můžu ho editovat (například PSPadem v režimu "hex"). Upravím si tedy jeho obsah a některé byty přepíšu. Následně opět v okně "Device programming" vyberu upravený soubor a zapíšu tlačítkem "Program".
Ve vlastním programu pak můžeme k obsahu User Row přistupovat třeba některým z následujících způsobů:

uint8_t hodnota = USERROW_USERROW0; // přečte první byte user row
uint8_t hodnota = USERROW.USERROW3; // přečte čtvrtý byte user row
    
// makro pro přístup s pomocí "indexu" (číslo 0 až 31, respektive 63 na MCU s větším User Row)
#define USERROW_READ_BYTE(index)   (*((const uint8_t *)(USER_SIGNATURES_START + (index))))
uint8_t hodnota = USERROW_READ_BYTE(0); // přečte první byte user row
uint8_t hodnota = USERROW_READ_BYTE(1); // přečte druhý byte user row


Vyčtení User row z MCU (AVR).

Editace binárního souboru v PSPadu (v režimu Hexa)

Snímek z Microchip (Atmel) Studia kde je patrný obsah User Row i hodnoty z něj vyčtené

Home
V1.0 16.8.2025
By Michal Dudka (m.dudka@seznam.cz)