2025-05-18 12:31 PM - edited 2025-05-18 12:40 PM
Hello, for my first project with a microcontroller I'm doing a 16 button gamepad USB HID with the aid of ChatGPT. I have no idea of what I'm doing really. I'm Using a STM32f103C8T6 and STLink V2. I made a polling version that constantly read pins and then sends a report based on "button states"; it works well, but I am interested in a more efficient way of doing it.
So I got to interrupts. I made a version that triggers on falling and rising edges, then it updates the buttons_state in the callback. It works well, but the main loop is always running and sending reports every 1 Systick (1ms I think), so I guess it has almost the same efficiency as the polling method.
My intention was to to use __WFI() instruction in the main loop to make the CPU wake up only on button press/release (EXTI interrupts); something like this:
while (1)
{
__WFI();
uint32_t now = HAL_GetTick();
if ((now - last_report_time) >= 1)
{
send_report(button_state);
last_report_time = now;
}
}
}
But supposedly, SysTick might be triggering the __WFI() intruction because it's keeps sending interrupts to update the HAL tick (I think) every 1ms in theory. So even if that was the case, the program would still be much more efficient than the CPU running full always; but it does not work. __WFI() seems to be constantly ignored, and I think the CPU is not slowing down.
My idea tho, was to disable Systick to not trigger __WFI(), and just enable it when I need a 1ms timer to send the USB report. Something like;
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //Disable Systick
while (1)
{
__WFI(); //wait for EXTI or Systick interrupt
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //Enable Systick to get timer and
//bypass _WFI()
uint32_t now = HAL_GetTick();
if ((now - last_report_time) >= 1)
{
send_report(button_state);
last_report_time = now;
//If report was sent disable Systick again to wait with __WFI()
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
}
But it also does not work. I tried many methods that ChatGPT recommended but nothing works. I also looked around on the web but wasn't able to find a solution. __WFI() keeps getting ignored, and the methods I tried are useless (do nothing) or brick my program making the "gamepad" unresponsive.
SO my main questions are:
- How can I disable Systick without disabling other interrupts (mainly EXTI)?
- Why is the __WFI() instruction not working? Are there any other interrupts in a basic gamepad USB setup that trigger __WFI() other than SysTick and EXTI?
Thanks for your time.
2025-05-19 1:53 AM
Hello @Masfeo
What do you mean by "__WFI() ignored"?
2025-05-19 2:10 AM
@Masfeo wrote:Hello, for my first project with a microcontroller I'm doing a 16 button gamepad USB HID .
Welcome to the forum, and the world of microcontrollers!
Do you have any experience with electronics and/or programming in general?
USB isn't simple - it's really not a good place to start your very first project!
It would be wise to start with some simple, basic projects to gain familiarity with the tools, the product, and the development process:
2025-05-19 5:27 AM - edited 2025-05-19 5:30 AM
Hello, thank for the replys.
@Saket_Om What I meant was: Even tho I was supposedly disabling SysTick and using the __WFI() instruction, after 20 seconds of connecting the USB, the button 8 was lighting up in "joy.cpl" (gamepad properties), which meant the device was still ticking and checking the "if" conditions in the main loop. I tried to debug by seting a breakpoint at the __WFI() instruction and checking variable values after F8 and F6 shortcuts; and It seemed to me that some counters I set up were incrementing faster than they should if the device was waking up only every 1ms, but I have no real way to check.
Since yesterday I learned this way of doing it:
HAL_SuspendTick();
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
HAL_ResumeTick();
Which seem to work. I'm trying now with a small project that lights up a small led on the board and that's it, no other code. I tried a couple things and indeed, if I do:
uint32_t start = HAL_GetTick();
while (1)
{
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
uint32_t now = HAL_GetTick();
if(now - start >= 10000)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_SuspendTick();
}
}
The LED powers off after 10 seconds and never comes back on.
while (1)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(2000);
HAL_SuspendTick()
}
With this, the LED powers on 2 seconds, off 2 seconds, then on for ever because looks like flow gets stucked inside HAL_Delay(2000). If I add HAL_ResumeTick() before the first HAL_Delay() everything works normally again.
Which means the instructions work and there was something in the USB part of the code that keeps SysTick enabled even if I try to disable it (). As per ChatGPT: USB requires regular SOF (Start of Frame) interrupts — every 1ms — to function properly. So yeah I won't dive much deeper into that, I'll assume for the moment I can't disable SysTick in a USB setup.
The problem I'm facing now Is that with the code from the first snippet, I can't get out of Sleep mode and flow gets stucked there I think. I configured PA1 as EXTI interrupt, but when I press the button on my breadboard nothing happens. The LED does not light up again.
This is my callback:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
}
Which is blank because my only intention is to wake up the CPU. But for some reason I can't, my EXTI interrupt is not getting triggered or something.
Any ideas? I'm a little bit lost.
@Andrew Neil A little bit about programming. I completed a lot of chapters of an intro book to C programming like 6 years ago. I can still remember some basic stuff if I see it written, but I'm very much a noob. I'm trying to learn a little bit of Java now too, but still super basic stuff. No real experience with electronics; but my father is an electrician and he just retired from work, so he will have time for my questions hehe (to extent he can answer of course). ChatGPT is also helping.
This project aims to be a wireless gamepad. Will have a (hopefully) interrupt driven transmitter (with nrf24 module) which will be battery powered (so I want low power consuption); and a receiver that will act as a USB HID gamepad, constantly listening for info and sending that info to the PC. I already bought most of the needed materials so I'm determined to do it.
I won't dive too deep into the USB stuff. I just used the template CubeMX provides, and changed the USB descriptor to a custom one I made with Waratah (report lenght too). Then I set up the report structure and sender function. That's it. Right now I'm most interested into making the transmitter as power efficient as possible, so I need to know about interrupts and power saving modes.
Thanks for taking the time to read me. English is not my 1st language.
Have a nice day.
2025-05-19 6:35 AM
Hello @Masfeo
Could you please try to use a timer as time base instead of Systick?
2025-05-19 6:36 AM
I'm still trying more stuff, but now, with:
uint32_t start = HAL_GetTick();
uint32_t now;
bool first = true;
while (1)
{
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
if(first)
{
now = HAL_GetTick();
}
if(now - start >= 10000 && first)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
first = false;
HAL_SuspendTick();
}
}
Works fine. Starts 10 seconds on, then off, after button press LED turns on for ever. So it looks like the snippet from the previous post gets stuck in HAL_GetTick() before it turns on the LED, even tho the HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); line was before the GetTick function. So I guess some type of timing issue, not an actual "interrupt not detected" issue.
I guess that, with this, all my doubts about the topic are resolved. I now know how to enter sleep mode, suspend and resume Systick and some problem that might arise while working with interrupts and power saving modes.
I guess setting up the nrf24 is the next step.
Thanks everyone for taking the time to read me.
2025-05-19 6:55 AM
wow, the timing hehehe.
I guess this would be something like using TIM1? I'm not familiar at all with this approach. But for now, since I kinda got my doubts resolved (as I mention in my last post, posted 1 min after yours), I'm gonna keep advancing with the project and start seting up the nfr24, which looks like an interesting task.
I will come back to ask stuff about more things for sure, when more problems arise.
Thanks for reading me!