FLASH Programming in STM32

Every microcontroller have some memory allocated for the user flash. Today We will use this memory to store some data in it. The benefit of using this flash memory is that, even after the power disconnect, the data remains safe in the flash memory.

This tutorial is devided into two halves. The first Half will cover those microcontrollers, whose memory is divided into pages. For eg- Cortex M3, and M0 series. The second Half is for those microcontrollers, whose memory is divided into Sectors

UPDATE for H7 Series

FLASH PAGE TYPE

For the first half, I am using STM32F103 microcontroller, and you can see the memory distribution in the picture below

As you can see above that the main memory (Flash memory) is distributed in 128 pages.
Each page is of 1 KB, thus making the total memory of 128 KB

Now always remember that we should start programming as lower as possible in the flash memory. This is because the start of the flash is already allocated to the program, that is being executed on your controller right now. I have explained it properly in the video, you can check that out in the end.

Each page can hold 1024 Bytes of data, and i don’t have much data to write, so I will choose the last page of the flash memory, i.e. 0x0801FC00 – 0x0801FFFF


Some insight into the CODE

Write Data to FLASH

uint32_t Flash_Write_Data (uint32_t StartPageAddress, uint32_t *Data, uint16_t numberofwords)
{

	static FLASH_EraseInitTypeDef EraseInitStruct;
	uint32_t PAGEError;
	int sofar=0;

	  /* Unlock the Flash to enable the flash control register access *************/
	   HAL_FLASH_Unlock();

	   /* Erase the user Flash area*/

	  uint32_t StartPage = GetPage(StartPageAddress);
	  uint32_t EndPageAdress = StartPageAddress + numberofwords*4;
	  uint32_t EndPage = GetPage(EndPageAdress);

	   /* Fill EraseInit structure*/
	   EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
	   EraseInitStruct.PageAddress = StartPage;
	   EraseInitStruct.NbPages     = ((EndPage - StartPage)/FLASH_PAGE_SIZE) +1;

	   if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK)
	   {
	     /*Error occurred while page erase.*/
		  return HAL_FLASH_GetError ();
	   }

	   /* Program the user Flash area word by word*/

	   while (sofar<numberofwords)
	   {
	     if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, StartPageAddress, Data[sofar]) == HAL_OK)
	     {
	    	 StartPageAddress += 4;  // use StartPageAddress += 2 for half word and 8 for double word
	    	 sofar++;
	     }
	     else
	     {
	       /* Error occurred while writing data in Flash memory*/
	    	 return HAL_FLASH_GetError ();
	     }
	   }

	   /* Lock the Flash to disable the flash control register access (recommended
	      to protect the FLASH memory against possible unwanted operation) *********/
	   HAL_FLASH_Lock();

	   return 0;
}

Flash_Write_Data takes the following parameters

  • @ StartPageAddress is the Start address of the page, or memory in the page, from where you want to start writing the data
  • @ Data is the pointer to the 32 bit data array, that you want to write into the flash
  • @ numberofwords is the number of words, that needs to be written in the memory
  • Flash will be Unlocked to make modifications
  • Based on the number of words, it will calculate the number of pages required to store that data
  • Next, the required number of pages will be erased, and the new data will be written into them
  • Flash will be Locked again




Read Data from FLASH

void Flash_Read_Data (uint32_t StartPageAddress, uint32_t *RxBuf, uint16_t numberofwords)
{
	while (1)
	{

		*RxBuf = *(__IO uint32_t *)StartPageAddress;
		StartPageAddress += 4;
		RxBuf++;
		if (!(numberofwords--)) break;
	}
}

Flash_Read_Data reads the data from the flash. It takes the following parameters

  • StartPageAddress is the Start address of the page, from where you want to start reading the data
  • RxBuf is the address of the 32 bit array, where you want to store the data
  • numberofwords is the number of words that you want to read from the memory

Main Function

char *data = "hello FLASH from ControllerTech\
			  This is a test to see how many words can we work with";

uint32_t data2[] = {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9};

uint32_t Rx_Data[30];

char string[100];

int number = 123;

float val = 123.456;

float RxVal;

  Flash_Write_Data(0x08004410 , (uint32_t *)data2, 9);
  Flash_Read_Data(0x08004410 , Rx_Data, 10);


  int numofwords = (strlen(data)/4)+((strlen(data)%4)!=0);
  Flash_Write_Data(0x08004810 , (uint32_t *)data, numofwords);
  Flash_Read_Data(0x08004810 , Rx_Data, numofwords);
  Convert_To_Str(Rx_Data, string);


  Flash_Write_NUM(0x08005C10, number);
  RxVal = Flash_Read_NUM(0x08005C10);

  Flash_Write_NUM(0x08012000, val);
  RxVal = Flash_Read_NUM(0x08012000);

Here I am writing 4 different data types to the different locations in the memory.

After we perform the read, the same data will be available, and this indicates that the write and read was successful.


Result

Check out the Video Below



FLASH SECTOR TYPE

Some microcontrollers have memory distributed in Sectors.

sector type destribution

In my STM32F446RE, there are 7 sectors and the size is varying between them. Similarly, some microcontrollers can have 11, 15 or 23 sectors. This code will cover all these types, irrespective of the number of sectors the controller have.

IMPORTANT

If the memory for the controller is arranged in dual banks, like Bank1 or Bank2, by defaults these Banks are disabled and the memory uses only single Bank addressing. Check the sector addresses in the single Bank distribution.

Also, if the sector sizes are different than as shown in the picture above, All you need to do is, define new sectors as according to your device datasheet in the function static uint32_t GetSector(uint32_t Address)

Some insight into the CODE

Write Data to FLASH

uint32_t Flash_Write_Data (uint32_t StartSectorAddress, uint32_t *Data, uint16_t numberofwords)
{

	static FLASH_EraseInitTypeDef EraseInitStruct;
	uint32_t SECTORError;
	int sofar=0;


	 /* Unlock the Flash to enable the flash control register access *************/
	  HAL_FLASH_Unlock();

	  /* Erase the user Flash area */

	  /* Get the number of sector to erase from 1st sector */

	  uint32_t StartSector = GetSector(StartSectorAddress);
	  uint32_t EndSectorAddress = StartSectorAddress + numberofwords*4;
	  uint32_t EndSector = GetSector(EndSectorAddress);

	  /* Fill EraseInit structure*/
	  EraseInitStruct.TypeErase     = FLASH_TYPEERASE_SECTORS;
	  EraseInitStruct.VoltageRange  = FLASH_VOLTAGE_RANGE_3;
	  EraseInitStruct.Sector        = StartSector;
	  EraseInitStruct.NbSectors     = (EndSector - StartSector) + 1;

	  /* Note: If an erase operation in Flash memory also concerns data in the data or instruction cache,
	     you have to make sure that these data are rewritten before they are accessed during code
	     execution. If this cannot be done safely, it is recommended to flush the caches by setting the
	     DCRST and ICRST bits in the FLASH_CR register. */
	  if (HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK)
	  {
		  return HAL_FLASH_GetError ();
	  }

	  /* Program the user Flash area word by word
	    (area defined by FLASH_USER_START_ADDR and FLASH_USER_END_ADDR) ***********/

	   while (sofar<numberofwords)
	   {
	     if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, StartSectorAddress, Data[sofar]) == HAL_OK)
	     {
	    	 StartSectorAddress += 4;  // use StartPageAddress += 2 for half word and 8 for double word
	    	 sofar++;
	     }
	     else
	     {
	       /* Error occurred while writing data in Flash memory*/
	    	 return HAL_FLASH_GetError ();
	     }
	   }

	  /* Lock the Flash to disable the flash control register access (recommended
	     to protect the FLASH memory against possible unwanted operation) *********/
	  HAL_FLASH_Lock();

	   return 0;
}
  • Flash_Write_Data will write data to the given memory location.
  • It will calculate the start sector number based on the address given in the parameter
  • then, the sector address of the sector where the data is going to end
  • and finally the sector number of the last sector
  • It will than erase the required number of sectors and program it with the new data

Read Data from FLASH

void Flash_Read_Data (uint32_t StartSectorAddress, uint32_t *RxBuf, uint16_t numberofwords)
{
	while (1)
	{

		*RxBuf = *(__IO uint32_t *)StartSectorAddress;
		StartSectorAddress += 4;
		RxBuf++;
		if (!(numberofwords--)) break;
	}
}

Flash_Read_Data will read the given amount of words from the memory location, and save it in the RxBuf


Main Function

char *data = "hello FLASH from ControllerTech\
			  This is a test to see how many words can we work with";

uint32_t data2[] = {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9};

uint32_t Rx_Data[30];

char string[100];

int number = 123;

float val = 123.456;

float RxVal;

  Flash_Write_Data(0x08004100 , (uint32_t *)data2, 9);
  Flash_Read_Data(0x08004100 , Rx_Data, 10);


  int numofwords = (strlen(data)/4)+((strlen(data)%4)!=0);
  Flash_Write_Data(0x08008100 , (uint32_t *)data, numofwords);
  Flash_Read_Data(0x08008100 , Rx_Data, numofwords);
  Convert_To_Str(Rx_Data, string);


  Flash_Write_NUM(0x0800C100, number);
  RxVal = Flash_Read_NUM(0x0800C100);

  Flash_Write_NUM(0x0800D100, val);
  RxVal = Flash_Read_NUM(0x0800D100);

Here I am writing 4 different data types to the different locations in the memory.

After we perform the read, the same data will be available, and this indicates that the write and read was successful.



Result

Check out the Video Below










Info

You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

64 Comments. Leave new

  • I’m using your code from Github and I’m using STM32F103RC as well but the code didn’t work. I’m using the address 0x08005C10 and 0x08005D10. Write NUM function did not actually wrote anything into the flash (as I checked with the monitor it didn’t) and read NUM function only returns 0.

    Reply
  • Hello Sir,
    We are using STM32L432 and we are always getting error code as 128 that is FLASH_FLAG_MISERR. There are 2 changes done in our software.
    EraseInitStruct does not have PageAddress element
    EraseInitStruct.PageAddress = StartPage;
    This was updated as
    EraseInitStruct.Page = 127;

    This error FLASH_FLAG_MISERR is appeared as part of the Page erase process. the following function output was 1.
    status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE);

    The following error output was 128

    error = (FLASH->SR & FLASH_FLAG_SR_ERRORS);

    Do we have the example software for page erase for STM32L432 please?
    Show less

    Reply
  • Hello Sir,
    We could write once and we are anot able to erase and update the last page. It looks like the flash is write protected. I meant we can compile and flash the chip. but we are not able erase the last page in our application software. How do we know the flash is write protected and How to clear the write protect before page erase? Could you please help us out?

    Reply
  • I’m working on STM32H750 with FreeRTOS and code not work, the function HAL_FLASHEx_Erase error, the function run to FLASH_WaitForLastOperation and return HAL_TIMEOUT.
    When i comment:
    if (HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK)
    {
    return HAL_FLASH_GetError();
    }
    program work fine.

    What the happen, I’m a little confused here. Do you have any suggest for me.

    Reply
  • Hello,

    I would like to know, Why a STM32 board binary code doesn’t run after I just flashed back to it of what I just red from the same board by using STM32 Cube Programmer?

    Reply
  • Hi. It’s awesome tutorial. I have one question here.) What will happen to data in flash if power off and on

    2) what will happen, if we flash again with read only function in main loop

    Reply
  • Hi! Awesome tutorial, thanks for proper insight 🙂
    Short question, why do you need to typecast again the pointer to array (uint32_t *) in the function Flash_Write_Data?
    I am a little confused here 😀
    Thanks!!

    Reply
    • There is no need for that. I was testing with some strings (chars), so I might have done it because of that.

      Reply
  • Hi,
    I’m using STM32F429. I found your flash_sector_f4.c perfectly suited to my initial needs of storing binary data in a sector of flash. Thank you!

    Now I’d like to be able to overwrite my running firmware with a new application after I’ve gotten it into RAM. I can’t use the built in bootloader. Is there an example of that anywhere? Thanks, Mike

    Reply
  • Hello,

    I use STM32H725.
    I need to implement Flash_Write_Data() at Low Level , without HAL .
    Does anyone have low-level realization experience Write flash?

    Reply
  • hello I have a problem

    I can’t find the Flash_Write_NUM and Flash_Read_NUM functions

    Reply
  • Vinicius Morais
    July 7, 2021 5:58 AM

    Para stm32F030C8, sua memória flash é páginas, mas quantas?

    Reply
  • Hello

    I have a question, and how can I get the free memory space I have left?

    is there a HAL command?

    Greetings from Colombia, your projects have helped me a lot

    Reply
  • I’m work with the STM32F429 and stmcubeide. I’ve a error – conflicting types for ‘Flash_Write_Data’- when call a function uint32_t Flash_Write_Data (uint32_t StartSectorAddress, uint32_t *Data, uint16_t numberofwords)
    {

     int numofwords = (strlen(data)/4)+((strlen(data)%4)!=0);
      Flash_Write_Data(0x08008100 , (uint32_t *)data, numofwords);
      Flash_Read_Data(0x08008100 , Rx_Data, numofwords);

      sprintf(teste_data2_char, “%d”, Rx_Data);

      HAL_UART_Transmit(&huart1, (uint8_t *) teste_data2_char, strlen(teste_data2_char),500);

    Reply
    • You are using older one.
      Click the download button, it will take you to the github.
      Download according to your controller

      Reply
  • Emanuel Francisco Vicentini Carvalho
    June 14, 2021 11:44 PM

    .

    Reply
  • Hello sir Thank you very much for this code. I am working with the STM32G474RE. With your code I manage to write it but the delete is still not possible, what should I change or add? Thanks

    Reply
    • which one did you used ?

      Reply
      • hello dear

        I used the “Flash Page Type”.

        I do not know why I manage to write, but Erasing does not work, I always have to go through STM32 ST-LINK utility to be able to delete. could you please help me ?

        Reply
        • The memory division in G4 is different, so you can’t use it directly.
          It’s further divided into BANKS.
          Maybe you can disable the banks and force the controller to use single BANK.
          Also check the page size during debug.

          Reply
          • unfortunately I’m only a beginner in this field, I don’t know how to force my controller to use the single Bank. Thank you once again for your time and your answers

          • Check your reference manual to know how to do that.

    • DesperateStudent
      April 17, 2023 9:38 PM

      I am also working with STM32G474RE. Could you please share your insights or maybe even your code how you managed to write to the flash? Thank you so much!

      Reply
  • kishor sherolla
    April 30, 2021 2:51 PM

    hello sir .. i am new to this 32 bit this controller. i am writing code for stm32f207. while writing the flash ,controller gets stops in erase function, what is the problem , i am using base address is sector a10 and end address is sector11 .actually my flash data is 150 bytes only

    Reply
  • If this FLASH_PAGE would be used with STM32L4 series where page size is 64bits, how big of a modification would this need? Thank you.

    Reply
    • I also want to use the program for stm32L4 series please help as i got error while writing in similar fashion

      Reply
      • Thanks to the Admin for sharing the Knowledge! Did someone manage to change the code for a STM32L4xx, in my case L452 (512x1Kb Pages)?

        Reply
  • i write this code in keil MDK but has error for “(strlen(DATA_32)”
    ERROR text:
    (..\Src\FlASH_PAGE.c(109): error: #167: argument of type “uint32_t *” is incompatible with parameter of type “const char *”)

    Reply
  • Chandler TIMM CAGMAT Doloriel
    February 19, 2021 6:47 AM

    How can I use this code if my data is uint8_t?

    Reply
  • Muhammed Imdaad
    February 6, 2021 9:43 PM

    I cant use the read function for ota purposes because in your function it only breaks when there is an empty cell. otherwise it runs until the end!!!!!!!!!!!!!!!!
    therefore I added size parameter.

    Reply
  • Hi!
    I want to report some bug in FLASH_SECTOR. During saving I was getting a hardfault. The problem is that function HAL_FLASH_Program() expects the address of the variable to be saved. And you send a value DATA_32[sofar]. It should be the address: &DATA_32[sofar]. Great job again 🙂
    Greetings!
    F.Rak

    Reply
    • In FLASH_SECTOR read function should be some other stop condition. Your stop condition is when you find 0xffffffff value in the array where you store read data. When this array is defined as global, the momory is alocated just for given array size. So when you want to read 2 bytes then you define array for 2 elements. In memory is alocated only 2 cells. After this 2 cells, other program data exist. So when you read in flash_sector_read more then 2 bytes you will plow the memory. This function should get one more parameter that is number of variables to read. Then one if statement it works much better.
      Regards,
      F.Rak

      Reply
      • yeah ofcouse we can do that. The point in writing the 0xffffffff was that it will read the data of unknown length. But if you already know the length, then it should be easy just like you said.

        Reply
  • When I write to the Flash I get memory protection and page alignment errors

    Reply
  • Hi Sir.

    How to find start page address ? In datasheet show page 127 address. I want to know another start page address.

    Reply
    • They all are 1Kb in size. Just multiply 1024 to find the address.
      For eg- 4*1024 = 4096 (0x1000) is the start address of page 4
      127 *1024 = 130048 (0x1FC00) is the start address of page 127

      Reply
      • Oh… Thanks. May I dump.
        ex- 111*1024=113664(0x1bc00) right?
        Thanks again.

        Reply
        • yes. Make sure your flash size is 128 KB. Some bluepills have 64 KB

          Reply
          • I copy your library and your code. Then string flash in 0x1FC00 that my bluepills have 128KB right. I plan to make my custom pcb with STM32F103C8T6 chip on it for prototype product. I think to be sure my code is write on it have 64KB for safe my product. Many THANKS.

  • I’m using Black Pill when i write to memory address it write and read quite well. But if i write in a loop for example i want to write to 0x08000400 and 0x08000400 the last written address is only stored in flash others are not stored why it’s happening? It’s urgent. Thank you

    Reply
    • i didn’t understand the issue properly.. you have mentioned the same address twice in your comment

      Reply
      • I’m sorry for my bad explanation here is the sample code..

        for (int i = 0; i <= 5; i ++){
                if(i == 0){
                   Flash_Write_Data(0x08006010, data);
                          Flash_Read_Data(0x08006010, Rx_Data);
                          println(Rx_Data);
                } if(i == 2){
                   Flash_Write_Data(0x08006020, data1);
                          Flash_Read_Data(0x08006020, Rx_Data);
                          println(Rx_Data);
                } if(i == 3){
                   Flash_Write_Data(0x08006030, data2);
                          Flash_Read_Data(0x08006030, Rx_Data);
                          println(Rx_Data);
                } if(i == 4){
                   Flash_Write_Data(0x08006050, data3);
                          Flash_Read_Data(0x08006050, Rx_Data);
                          println(Rx_Data);
                }
        
                HAL_Delay(2000);
             }
        

        After running this code data is stored only at this address 0x08006050 but during the iteration i can see that data is being stored by calling Read_Flash function and the output is this

        14:10:19.482 -> Hello World
        14:10:23.535 -> Hello World 1
        14:10:25.564 -> Hello World 2
        14:10:27.595 -> Hello World 3

        But if i check the memory location using STM32CubeProgrammer i can only see last address data is stored in flash…

             for (int i = 0; i <= 8; i ++){
                if(i == 0){
                   Flash_Write_Data(0x08006010, data);
        
                } if(i == 2){
                   Flash_Write_Data(0x08006020, data1);
        
                } if(i == 3){
                   Flash_Write_Data(0x08006030, data2);
        
                } if(i == 4){
                   Flash_Write_Data(0x08006050, data3);
                }
                if(i ==5){
                  Flash_Read_Data(0x08006010, Rx_Data);
                                    println(Rx_Data);
                }if(i == 6){
                                   Flash_Read_Data(0x08006020, Rx_Data);
                                   println(Rx_Data);
                }if(i == 7){
                  Flash_Read_Data(0x08006030, Rx_Data);
                                   println(Rx_Data);
                }if(i == 8){
                  Flash_Read_Data(0x08006050, Rx_Data);
                                println(Rx_Data);
                }
        
                HAL_Delay(2000);
             }
        

        and the output is this

        14:22:06.325 ->
        14:22:08.329 ->
        14:22:10.322 ->
        14:22:12.354 -> Hello World 3

        I don’t know what causing this..

        Reply
        • you can not write data like that. You are trying to increase the address every few bytes. Check the page size of your controller in reference manual. Let’s assume it’s 1 KB. That means you can write once within that 1 KB size. If you try to write with an offset of 4 bytes, it will anyway erase the entire page and then write to that address.

          Reply
  • I am using this concept to stm32f030f4 the value is uploaded to the flash memory address correctly and stored even though when the power is off but it does not reflect on the project kit when i reset the stm it works as per the coding logic it does not get the address value

    thanks in advance

    Reply
  • Thank you sir.

    Reply
  • Manjunath K S
    July 5, 2020 8:30 PM

    Hi,
    i am using stm32H753 it has 2 memory banks BANK1 & BANK2 with 7 sectors each, and its a cortex-m7 controller,
    i tried to use our program.it is not working

    Reply
    • I checked the reference manual. Looks like there is a new memory system there. I need your project in order to modify it according to BANK type distribution. Contact me on that telegram group linked on the top right corner of website, or send a mail

      Reply
      • Muhammed Okur
        May 20, 2021 11:03 PM

        Hi, I am using STM32H753 and i have same problem. How can i fix the problem? Can you help me please.

        Reply
        • I will release an update for H7 series in a day or 2

          Reply
          • Jestina Joy
            May 28, 2021 3:28 PM

            Have you released the update for H7 series board? If yes, please share the link

          • Check the page again. It’s on the top

          • Jestina Joy
            May 29, 2021 11:17 AM

            Thanks for the update. When i used the code for H7 series i got below error:
            In function ‘Flash_Write_Data’:
            ../Drivers/STM32H7xx_HAL_Driver/Inc/Legacy/stm32_hal_legacy.h:425:39: error: ‘FLASH_VOLTAGE_RANGE_3’ undeclared (first use in this function);
            I am using STM32H7A3 Nucleo board. I could not find out a definition for FLASH_VOLTAGE_RANGE_3 in my code. Please help me..

          • Try to comment out that line, and check.
            Also make sure that the Sectors and Banks are defined properly in the flash_sector_h7.c file

  • Flash_Page.c file is written by you. Or It is in built HAL library file?

    Reply
  • yedem bala nagendra reddy
    May 21, 2020 2:29 PM

    Hello sir,

    Iam using STM32L073Rz microcontrolller for flashing the data. But once we write the data in Particular address like 0X0802F000 iam not able to modify the string or numbers in the same address but we can flash another string in 0X0802F020 address. Please explain how to erase the previous flash data.

    Iam trying to take external button as input after every press count value will be incremented and i am expecting to store the count value at any one address like 0X0802F000 after every increment count value stores at same address.

    When use the board after power off also, count will be incremented from last value and it should be displayed on LCD or UART . Is it possible to do like that in Flash Programming.

    I changed the GetPage function like this for STM32L073Rz is it correct

    static uint32_t GetPage(uint32_t Address)
    {
    for (int indx=0; indx<512; indx++)
    {
    if((Address = (0x08000000 + 128*indx)))
    {
    return (0x08000000 + 128*indx);
    }
    }

    return -1;
    }

    Please help to resolve the issue.

    Reply
    • no it’s not correct. There is a lot confusion about the memory of the device that you have.
      can u confirm the flash memory detail. does it have 1536 pages with each page being 128 bytes in size?

      Reply
      • yedem bala nagendra reddy
        May 25, 2020 11:39 AM

        Yes sir,

        Please verify in STM32L073RZ Referance Manual page no. 68/1034
        RM0367 Reference manual

        Reply
  • Hi there,
    Is Bluepill board 64kb flash memory?
    How do you use 128kb pages?

    Reply
    • There is some confusion about 64 KB or 128 KB. But you can see in the video that it definitely uses the 128th page to write data, so I can say that it does have 128 KB of flash memory

      Reply
      • Thanks for the reply and there is a confusion yes. But I thought it’s 64kb because it says 64kb in STM32CUBE IDE memory section on right bottom.

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

keyboard_arrow_up

Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add controllerstech.com to your ad blocking whitelist or disable your adblocking software.

×