Bazı Gömülü Sistem projelerinde kullanıcı tarafından değiştirilen bir değişkene (şifre vb.), sistemin enerjisi kesilip tekrar geldiğinde veya sistem resetlendiğinde tekrar erişmek gereği duyulabiliyor. Böyle durumlarda mikrodenetleyicinin programcılar için ayrılan adreslerine veri yazılıp ve gerektiğinde o adrese erişerek veriler çekilebilir. Flash hafızanın bir yazma sınırı olduğunu unutmayalım. Yani sürekli flash hafızaya veri yazılıp okunduğu bir uygulama önerilmemektedir. Bu yazıda STM32F1 serisi için Flash Hafızaya veri yazma veya Flash Hafızadan veri okuma işleminin bir örnek uygulamasından bahsedeceğim.
Öncelikle STM32f1 de flash adrese veri yazma ve okuma işlemi 16 bitlik(half-word) değişkenler ile yapılır. İlk olarak en basit olanından yani veri okuma fonksiyonundan başlayalım. Aşağıda görüldüğü gibi bu fonksiyon iki satırdan oluşmaktadır. C programlamadan hatırlayacağınız üzere pointerlar ile adrese erişip adresteki veriyi alma mantığına dayanır.
1 2 3 4 5 | uint16_t Read_Flash(uint32_t adr) { uint16_t * Pntr = (uint16_t *)adr; return(*Pntr); } |
Flash adrese veri yazmak ve flash adresteki veriyi silmek , okumaya göre biraz daha farklıdır. STM32 de bu işlemleri yapabilmek için öncelikle flash kilidini açmak gerekir. Bunun için STM32 programming manual‘den yararlanabiliriz. Flash kilidini açmak için FLASH->KEYR registerına programming manualdeki key1 ve key2 sırası ile gönderilmelidir. Böylece flash kilidi açılmış olur. Yazma veya silme işlemi bittikten sonra FLASH_CR registerı resetlendiği zaman flash tekrar kilitlenmiş olur ve yazma,silme işlemi yapılamaz. Aşağıda programming manuel den alınan flash key değerlerine ait ekran görüntüsü verilmiştir.
Peki hangi adrese veri yazacağız. Bunu da yine programming manual veya reference manual’deki “flash memory map” tablosundan bulabiliriz. Aşağıda bu tablo verilmiştir.
Dikkat edilirse main memory 128 adet 1 kbyte’lık sayfalardan oluşmaktadır. Burada önemli bir noktaya değinmek gerekir. Yazma işlemi yapılırken tek bir adrese yazılır fakat silme işlemi tüm sayfayı siler.
1 2 3 4 5 6 7 8 9 10 11 | void Erase_Flash (uint32_t adr) { FLASH->KEYR=0x45670123; // Silme veya yazma islemi yapilmadan önce Flash kilidi mutlaka açilmalidir. FLASH->KEYR=0xCDEF89AB; // FLASH->KEYR registerine KEY1 ve KEY2 degerleri atandiginda Flash kilidi açilir. FLASH->CR|=0x00000002; //PER enable FLASH->AR=adr; //FLASH->AR registerine silinmek istenen adres yazilir FLASH->CR|=0x00000040; //STRT anable while((FLASH->SR&0x00000001)); //Islem bitene kadar bekle(BUSY kontrol ediliyor) FLASH->CR &= ~0x00000042; //FLASH->CR ilk durumuna aliniyor (kilit hala açik!) FLASH->CR=0x00000080; //FLASH_CR registeri resetlendiginde FLASH kiltlenmis olur } |
1 2 3 4 5 6 7 8 9 | void Write_Flash (uint32_t adr, uint16_t data) { FLASH->KEYR=0x45670123; // Silme veya yazma islemi yapilmadan önce Flash kilidi mutlaka açilmalidir. FLASH->KEYR=0xCDEF89AB; // FLASH->KEYR registerine KEY1 ve KEY2 degerleri atandiginda Flash kilidi açilir. FLASH->CR|=0x00000001; //PG enable *(__IO uint16_t*)adr = data; //istenen adrese istenen data yaziliyor while((FLASH->SR&0x00000001)); //Islem bitene kadar bekle(BUSY kontrol ediliyor) FLASH->CR=0x00000080; //FLASH_CR registeri resetlendiginde FLASH kiltlenmis olur } |
Yukarıda yazma ve silme işlemleri için yazılan fonksiyonlar verilmiştir. Aşağıda kodda dikkat edilirse yazma adresi olarak Page 127‘nin başlangıç adresi seçilmiştir.
1 2 3 4 5 | data = 10; Erase_Flash(0x0801FC00); // adresteki değer silindi. Write_Flash(0x0801FC00,data); // 16 bitlik veri adrese yazıldı. HAL_Delay(2000); // flash_data degiskeninin baslangiçta sifir oldugunu görebilmek için gecikme eklendi. flash_data = Read_Flash(0x0801FC00); |
Kod yüklendikten sonra değişkenlerdeki değişimleri STMStudio ile izleyebilirsiniz.
Kodların Tamamı
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | /** ****************************************************************************** * File Name : main.c * Description : Main program body ****************************************************************************** ** This notice applies to any and all portions of this file * that are not between comment pairs USER CODE BEGIN and * USER CODE END. Other portions of this file, whether * inserted by the user or by software development tools * are owned by their respective copyright owners. * * COPYRIGHT(c) 2019 STMicroelectronics * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of STMicroelectronics nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "stm32f1xx_hal.h" /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* Private variables ---------------------------------------------------------*/ uint16_t data,flash_data=0; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); /* USER CODE BEGIN PFP */ /* Private function prototypes -----------------------------------------------*/ /*######################## Adresten Veri Okuma Fonksiyonu ####################*/ uint16_t Read_Flash(uint32_t adr) { uint16_t * Pntr = (uint16_t *)adr; return(*Pntr); } /*######################## Verilen adresteki veriyi silmek için yazilan fonksiyon ############*/ void Erase_Flash (uint32_t adr) { FLASH->KEYR=0x45670123; // Silme veya yazma islemi yapilmadan önce Flash kilidi mutlaka açilmalidir. FLASH->KEYR=0xCDEF89AB; // FLASH->KEYR registerine KEY1 ve KEY2 degerleri atandiginda Flash kilidi açilir. FLASH->CR|=0x00000002; //PER enable FLASH->AR=adr; //FLASH->AR registerine silinmek istenen adres yazilir FLASH->CR|=0x00000040; //STRT anable while((FLASH->SR&0x00000001)); //Islem bitene kadar bekle(BUSY kontrol ediliyor) FLASH->CR &= ~0x00000042; //FLASH->CR ilk durumuna aliniyor (kilit hala açik!) FLASH->CR=0x00000080; //FLASH_CR registeri resetlendiginde FLASH kiltlenmis olur } /*################ Verilen adrese veri yazma fonksiyonu ##############################*/ void Write_Flash (uint32_t adr, uint16_t data) { FLASH->KEYR=0x45670123; // Silme veya yazma islemi yapilmadan önce Flash kilidi mutlaka açilmalidir. FLASH->KEYR=0xCDEF89AB; // FLASH->KEYR registerine KEY1 ve KEY2 degerleri atandiginda Flash kilidi açilir. FLASH->CR|=0x00000001; //PG enable *(__IO uint16_t*)adr = data; //istenen adrese istenen data yaziliyor while((FLASH->SR&0x00000001)); //Islem bitene kadar bekle(BUSY kontrol ediliyor) FLASH->CR=0x00000080; //FLASH_CR registeri resetlendiginde FLASH kiltlenmis olur } /* USER CODE END PFP */ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration----------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); /* USER CODE BEGIN 2 */ data = 10; Erase_Flash(0x0801FC00); // adresteki değer silindi. Write_Flash(0x0801FC00,data); // 16 bitlik veri adrese yazıldı. HAL_Delay(2000); // flash_data degiskeninin baslangiçta sifir oldugunu görebilmek için gecikme eklendi. flash_data = Read_Flash(0x0801FC00); // addresten 16 bitlik veri okundu. /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** System Clock Configuration */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; /**Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL4; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Configure the Systick interrupt time */ HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000); /**Configure the Systick */ HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); /* SysTick_IRQn interrupt configuration */ HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); } /** Configure pins as * Analog * Input * Output * EVENT_OUT * EXTI */ static void MX_GPIO_Init(void) { /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @param None * @retval None */ void _Error_Handler(char * file, int line) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ while(1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /** * @} */ /** * @} */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ |
HAL Kütüphanesi ile Flash Hafıza Örneği
Bir örnek te HAL kütüphanesi ile yapalım. Bu örnekte silme işlemi ile yazma işlemini tek bir fonksiyon içerisinde yapacağız. Silme işlemi bir kaç satırdan oluştuğu için aynı fonksiyon içinde aşağıdaki gibi kullanabiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void Write_Flash( uint32_t address , uint16_t data) { FLASH_EraseInitTypeDef EraseStruct; uint32_t PageError; EraseStruct.TypeErase = FLASH_TYPEERASE_PAGES; EraseStruct.PageAddress = address; EraseStruct.NbPages = 1; HAL_FLASH_Unlock(); HAL_FLASHEx_Erase(&EraseStruct,&PageError); HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,address,data); HAL_FLASH_Lock(); } |
Bu fonksiyon içerisinde HAL_FLASH_Program() fonksiyonu ile istersek 32 bit veya 64 bitlik değerleri hafızaya yazabiliriz. Tabi arka planda değerler yine 16 bit olarak hafızaya yazılır. HAL_FLASHx_Erase() fonskiyonu bizden bir hata parametresi bekler. Eğer silme işleminde hata oluşur ise bu parametreye girilen değişkeni hangi adresi silerken hata oluştu ise o adresin değerine eşitler.
1 | Write_Flash(Flash_address,0x0001); |
NOT: Ben bu örneği STM32F103C8T6(Blue Pill) üzerinde denedim. Yazının başında verdiğim tabloda STM32F1 serisinin Memory Map tablosunda 128 sayfa olduğu görülmektedir fakat her f1 modeli 128 sayfalık yani 128 kb flash hafızaya sahip değildir. STM32F103C8T6 da C den sonra gelen 8 kodu 64 kb lık bir flash hafızaya sahip olduğunu belirtmektedir.
Memory Map tablosunda 63. sayfanın hangi adreste olduğu yazmamaktadır. Peki herhangi bir sayfanın adresini nasıl hesaplayabiliriz? Bunu basitçe aşağıdaki gibi hesaplayabiliriz.
1 | #define Flash_address (uint32_t)(0x08000000+1024*63) // page 63 -> 0x0800FC00 |
Memory map’ten page 0′ ın başlangıç adresini ve her sayfanın 1 kb(1024 byte) olduğunu biliyoruz. Buna göre istenilen sayfanın başlangıç adresini yukarıdaki gibi hesaplayabiliriz.
Kaynak: http://www.picproje.org/
Okunan adres boş ise 0xFF olarak okunuyor.
Evet doğrudur. Flash hafızanın boş olduğu durum 0xFF değeridir. Aynı zamanda hafızadan veri silindiğinde değeri 0xFF olmalıdır.
Mehmet bey merhabalar, öncelikle verdiğiniz bilgiler için teşekkür ederim. Takıldığım bir nokta var. STM32F103CBT6 kodlu işlemci kullanıyorum. Float değerim var ve flash hafızaya kaydetmek istiyorum. Nasıl bir yol izleyebilirim?
Merhaba, rica ederim. Öncelikle float 32 bitlik bir değişkendir. Flash hafızaya yazarken 16 bitlik değerler yazabiliyoruz. Float değişkenlerde bit kaydırma işlemi(<<) yapılmadığı için float değişkeni 16 bitlik iki değişkene parçalamak zor bir iş. Bunun yerine float değişkenin virgülden sonraki basamağını sabit kabul ederek virgülden önceki ve virgülden sonraki sayıyı iki ayrı 16 bitlik değişkene atayıp hafızaya kaydedebilirsin. Örneğin; Float değişkeni şöyle iki değişkene atayabilirsin.
float a = 13.35;
uint16_t b = a; // b = 13
uint16_t c = a*100;
c = c%100; // c = 35
Burada değişkenin birini örnek olarak 0x0801FC00 adresine diğerini ise 0x0801FC02 adresine kaydedebilirsin. Daha sonra bu adreslerden veri okuduktan sonra tekrar bu iki sayıyı float tipine dönüştürmek istiyorsan bunu da şu şekilde yapabilirsin.
float d = b; // d = 13.0
d = d+ (float)c/100; // d = 13.35000
Bunu böyle bir deneyiniz. Eğer kod çalışırsa buraya tekrar yazarsanız sevinirim. Sonucu ben de merak ediyorum.
Sorunumu şu şekilde çözüme ulaştırdım. Belki ihtiyacı olan arkadaşlar olabilir;
typedef union
{
uint16_t dataBytes[2];
float f;
}floatToChar_t ;
fonksiyon olarak kullanımı;
Write_Flash(carpanDegeriAdress, degisken.dataBytes[0]);
Write_Flash(carpanDegeriAdress1, degisken.dataBytes[1]);
ve;
degisken.dataBytes[0] = Read_Flash(carpanDegeriAdress);
degisken.dataBytes[1] = Read_Flash(carpanDegeriAdress1);
şeklinde. (Read_Flash ve Write_Flash fonksiyonları sizin yukarıda yazmış olduğunuz fonksiyonlar.)
İşlemler bittikten sonra Flash_Lock komutunun önemi nedir?
Resetten sonra flash’ı unlock etsek ve bir daha hiç Lock etmesek, veri kaybı yaşar mıyız? Veya veri kaybı yaşama olasılığımız ne oranda etkilenir?
Bu komut sadece flasha yazmayı engeller. Genelde yanlışlıkla program kodları değiştirilmesin diye kullanılır. Mikrodenetleyici üzerinde çalışan kodlar da flash hafızda tutulur. Bu kodlardan herhangi bir byte silinse mikrodenetleyicide bir süre sonra hata oluşur. Bunun önüne geçmek için konulmuş bir önlem olabilir. Flashı lock etmesende bir sıkıntı çıkacağını zannetmiyorum program hafızasına veri yazmadığın sürece tabi. Mikrodenetleyici resetlendiğinde tekrar otomatik kilitlenecektir.