This repository contains my solutions to a challenge on Sisoog website.
Caution
Overclocking may cause the microcontroller to overheat and result in damage. Therefore, please exercise caution and only attempt this if you know exactly what you are doing. Use the instructions in this repository at your own risk.
Write a program for the STM32F103C8T6 microcontroller to toggle the PC14 pin at the highest possible frequency.
The program should meet the following criteria:
- The program that achieves the highest frequency on PC14 will be considered the winner.
- The generated signal on PC14 should maintain the same frequency for a minimum of 10 minutes.
- It is recommended to use a Blue-Pill board as the hardware for this challenge. If required, you are allowed to replace the crystal on the board.
The challenge statement forces us to generate the output signal using only PC14. This is because PC14 cannot be used as the MCO, PWM, or timer outputs, so we need to find alternative methods to create a high-frequency pulse on PC14.
We start the process of overclocking the STM32F103C8T8 microcontroller with an external crystal of 8 MHz and setting the clock speed to 72 MHz.
We set the PC14 pin as a Push-Pull output without Pull-Up and connect it to the oscilloscope to see the output pulse frequency.
We write the program in such a way that the toggling of PC14 pin is done as quickly as possible. That's why we are using assembly instructions.
The fastest way to toggle a bit in a register is to use the XOR operation. So, before the while
loop, we store the address of the GPIOC_ODR
register and the mask needed to change the value of 14th bit of the PORTC in the general-purpose registers:
asm volatile(
"ldr r0, =0x4001100C\n" // GPIOC_ODR address
"ldr r1, [r0]\n" // Load GPIOC_ODR value
"ldr r2, =0x00004000\n" // Bit 14 mask
);
The address of GPIOC_ODR
is adopted from the RM0008 document.
Then, inside the while
loop, we first perform the XOR operation between the mask and the current value of the GPIOC_ODR register using the EOR
instruction, and then write the resulting value to the GPIOC_ODR
register using the STR
instruction:
asm volatile(
"eor r1, r1, r2\n" // XOR the value with the mask
"str r1, [r0]\n" // Store the new value back to GPIOC_ODR
);
In this way, in a short time, the PC14 pin is toggled in the while
loop, and a high frequency output pulse will be obtained.
By setting SYSCLK = 72 MHz
, the output pulse frequency on PC14 will have a frequency about 5.13 MHz:
Now we step by step increase the clock of the microcontroller, until it eventually reaches a point where the output signal on PC14 becomes unstable.
For SYSCLK = 80 MHz
, the frequency of the output pulse on PC14 reaches approximately 5.71 MHz:
For SYSCLK = 88 MHz
, the frequency of PC14 reaches about 6.29 MHz:
For SYSCLK = 96 MHz
, the output pulse frequency on PC14 reaches about 6.94 MHz:
For SYSCLK = 104 MHz
, the output pulse frequency on PC14 reaches about 7.46 MHz:
For SYSCLK = 112 MHz
, the output pulse frequency on PC14 reaches about 8.0 MHz:
For SYSCLK = 120 MHz
, the output pulse frequency on PC14 reaches about 8.62 MHz:
For SYSCLK = 128 MHz
, the output pulse frequency on PC14 reaches about 9.17 MHz:
128 MHz was the highest clock frequency achievable with an 8 MHz crystal. Therefore, we change the external crystal to 16 MHz so that we can reach higher clock frequencies.
After configuring the project for SYSCLK = 144 MHz
, the output pulse frequency on PC14 increases to approximately 10.25 MHz.
The frequency of 10.25 MHz is the highest frequency that the output remains stable for at least 10 minutes.
Now we configure the project for SYSCLK = 160 MHz
. In this case, the output pulse frequency on PC14 reaches about 11.47 MHz, but after about 20 seconds, it becomes unstable and the output is cut off.
From now on, by increasing the clock frequency, we do not see any pulses on PC14, which indicates that the microcontroller is not functioning properly and it is not possible to increase the clock frequency anymore.
By changing the code and using the EORS
instruction instead of EOR
, the output frequency increased slightly. The results are as follows:
asm volatile(
"eors r1, r2\n" // XOR the last value of GPIOC_ODR (r1) with the mask (r2)
"str r1, [r0]\n" // Store the new value (r1) back to the address of GPIOC_ODR
);
External crystal = 8 MHz, SYSCLK = 128 MHz
, output frequency on PC14 = 10.64 MHz stable:
External crystal = 16 MHz, SYSCLK = 144 MHz
, output frequency on PC14 = 12.05 MHz stable:
External crystal = 16 MHz, SYSCLK = 160 MHz
, output frequency on PC14 = 13.33 MHz Unstable (lasts only about 20 seconds):
To increase the pulse frequency, before entering the while
loop, the corresponding values for GPIOC_ODR to set the PC14 pin to 0
and 1
is stored in two genral-purpose registers r1 and r2:
asm volatile(
"ldr r0, =0x4001100C\n" // Load GPIOC_ODR address into register r0
"ldr r1, =0x00000000\n" // Move the value for PC14 = 0 into register r1
"ldr r2, =0x00004000\n" // Move the value for PC14 = 1 into register r2
);
Inside the while
loop, we use two STR
commands to set PC14 to zero and one.
asm volatile(
"str r2, [r0]\n" // Store value of r2 (PC14 = 1) to the address of GPIOC_ODR
"str r1, [r0]\n" // Store value of r1 (PC14 = 0) to the address of GPIOC_ODR
);
Now, with an external crystal of 16 MHz and SYSCLK = 144 MHz
, a frequency of 20.66 MHz can be reached on PC14:
The branch instruction b
executed at the end of each while
loop, reduces the frequency of output pulse on PC14;
To solve this problem, we toggle PC14 multiple times and sequentially in each execution of the loop:
By removing the delay caused by the branch, we were able to reach a frequency of 36.23 MHz on PC14 with an external crystal of 16 MHz and setting the system clock to 144 MHz:
The EVENTOUT function on PC14 pin can be used to achieve higher frequency. After enabling the EVENTOUT function, the value of PC14 can be set to 1
only for one clock cycle using the SEV
instruction. In this way, half of a full pulse is created. Now, with the help of the NOP
instruction, we create a delay for one clock cycle in the state where the value of PC14 is 0
, so that a complete pulse is created.
asm volatile(
"sev\n" // Set EVENTOUT pin (PC14) for one clock (and then reset it) => PC14 =1
"nop\n" // Wait for one clock cycle and do nothing (while EVENTOUT is reset) => PC14 = 0
);
Similar to solution 3, it is possible to eliminate the delay caused by branch instruction b
in the generated signal by sequentially executing SEV
and NOP
inside the while
loop. The results of this program are as follows:
External crystal = 8 MHz, SYSCLK = 128 MHz
, output signal frequency = 64.10 MHz:
External crystal = 16 MHz, SYSCLK = 128 MHz
, output signal frequency = 64.10 MHz:
External crystal = 16 MHz, SYSCLK = 144 MHz
, output signal frequency = 72.46 MHz (highest stable frequency):
As you can see, the quality and amplitude of the output signal is still not very good. To increase the quality of the output signal, it is necessary increase clock frequencies of all involving microcontroller units to their highest level. For this purpose, changes were made in the code as follows:
PC14 settings:
Clock settings for 8 MHz crystal:
Signal obtained on PC14 with 8 MHz crystal and SYSCLK = 128 MHz
: (64.10 MHz)
Clock settings for 16 MHz crystal:
Signal obtained on PC14 with 16 MHz crystal and SYSCLK = 128 MHz
: (64.10 MHz)
Signal obtained on PC14 with 16 MHz crystal and SYSCLK = 144 MHz
: (72.46 MHz - the highest stable frequency achievable)
I tried to store the whole program or some parts of it in SRAM in order to prevent microcontroller from hanging in higher clock frequencies. The results after storing the whole program in SRAM was not successful, but storing the toggle_v5()
in SRAM caused to get a stable signal with the frequency of 80.00 MHz while SYSCLK = 160 MHz
.
I cannot explain why, but it worked for me in the first attempt and was stable for more than an hour. But in the next day, when I turned on the board, I saw that the output is unstable!
__attribute__((section(".data"))) inline void toggle_v5(void)
{
// Toggle PC14 using EVENTOUT function
asm volatile(
"sev\n" // Set EVENTOUT pin (PC14) for one clock cycle (and then reset it) => PC14 = 1
"nop\n" // Wait for one clock cycle and do nothing (while EVENTOUT is reset) => PC14 = 0
);
}
- Getting Started with STM32 Programming
- STM32F103C8 Datasheet
- RM0008 STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced Arm®-based 32-bit MCUs
- PM0056 STM32F10xxx/20xxx/21xxx/L1xxxx Cortex®-M3 programming manual
- STM32H7 (Cortex-M7) GPIO toggling
- Executing code from RAM on STM32 ARM Microcontrollers