Skip to main content

Buttons and alarm

Now I have my buttons. And it didn't go smooth sailing.


Before all, let's try to review what I want and how to do that :
  • I want buttons to control the KaRadio. 6 buttons are enough. We will detail them a bit afterwards.
  • I want my buttons to also set an alarm clock. This should stay visible, and should use the already implemented buttons to be modified and set.
  • I want that any push in a button results in changes that are visible on the LCD screen. If the volume changes, I want to see it. If the station changes, well that's already implemented.
First, I went to read the documentation on how to operate the GPIO. I knew I needed only to read digital states of the GPIO. At first, I found this document straight from Maple docs :
http://docs.leaflabs.com/static.leaflabs.com/pub/leaflabs/maple-docs/0.0.12/libmaple/api/gpio.html
It's quite dense, but there is some information. GPIOs must be initialized with gpio_init(), then you need to set their state to output or input. For me, I decided to rely on the internal pull-ups and set all my pins to INPUT_PU (for input-pull-up).

Then, after that, I found the STM32duino documentation, which is much more concise but also simpler :
http://wiki.stm32duino.com/index.php?title=API#Reading_from_.26_Writing_to_pins

As they warn that the original Arduino functions (digitalRead, digitalWrite) are working but are rather slow, and I wanted to read quickly, I decided completely arbitrarily to rely on the internal registers to read the values.

So reading a button is made through the following macro :
#define PLAYPAUSE 0
#define VOLPLUS 1
#define VOLMINUS 2
#define CHANPLUS 3
#define CHANMINUS 4
#define MODE 5

#define READPORTA(n) (GPIOA->regs->IDR & (1 << (6+n)))
 Here, I put 6+n because the first button, PLAYPAUSE, is at PA6. Actually, the GPIOs I use are PA6, PA7, PA8, PA9, PA10, PA15. So when accessing MODE, I add an offset of 4 to access correctly PA15.

Now this can seem odd. Why did I choose these ones ?
Actually, at first I chose PA8, 9, 10, 11, 12, 15, because they were contiguous. But this caused a lot of problems because PA11 and PA12 are actually the two data lines from USB. This caused the buttons not to work, or to provoke a reset, even when simply plugged on a power socket !!
The other possibility was PA1 to PA7, as they were contiguous too. I don't know why, only PA6 and PA7 were actually working. Plus, I need either PA2 and PA3, or PB10 and PB11 for Serial communication to the ESP8266 (see pinout below), which broke continuity. So I took the best of both worlds, and choose non-contiguous buttons.

I decided to make these buttons work in a rather straighforward polling way :
  • When you start pushing it (0 are coming), and previously it was not pushed (1 are previous values), start filling a counter.
  • When the counter reaches a threshold, put the previously know state to "pushed" (0), and activate the current button_flag (which will be read in another function), and reset the counter
  • If at some point, the counter is not filled and a 0 comes, this means the 1's were parasites, so reset the counter. Said differently, you need to push the button for a certain amount of time to trigger it. This is basically software debouncing.
  • Proceed exactly the same way with releases.
The thing is, I was a bit unsatisfied with my software debouncing. It was either too fast, hence the poor STM32 was spending all its time in polling the buttons, or too slow, and the buttons werent reactive enough. I needed it to be fast for debouncing, but slow for the general case.
This is why I implemented a dual-speed polling. I adjust the polling speed (the wait in the function) depending if the counter is filling or not.
  • When nothing is pushed, the polling occurs at 15Hz, which is rather slow.
  • As soon as a button is pushed, the polling is done at 500Hz. This allows to check precisely if it's parasitic signal or actual button. The counter is filling up.
  • When the counter is reset, get back to slow 15Hz polling.
I think it's a nice way to do both accurate debouncing and low CPU use for general tasks.

When a button is triggered, a flag is set, and for most of the functions, this flag is checked by the UART task, that will send the correct command to the KaRadio. The UART task is actually very fast (1 KHz), so no doubt that the action will be very quickly taken into account.

I told you I needed 6 buttons. You can see their basic (Mode 1) behaviour above. But when you press the MODE button, you switch mode. In Mode 2, the buttons are controlling the alarm clock. CHANPLUS and CHANMINUS are modifying hours (+/-), and the same goes for the VOLPLUS and VOLMINUS with minutes (+/-).


But before moving to the alarm clock part, I still had a bit of work to do, in particular for the volume control. I want it to be displayed, right ? And I want it to be displayed, like, for 3 seconds, not all the time. It's not useful.

So when I trigger the flag_button[VOLPLUS] or VOLMINUS, this will add to the flag_screen[VOLUME] the value 3*[the frequency of the LCD task]. This value is checked by the LCD task, and at every call, it will lower the value by 1. When it's 3*[the frequency of the LCD task], it sends the volume value to be displayed. When it reaches 0, it justs erases the volume display. In between, nothing is done.

So finally, the alarm clock. This is quite a piece of junk.
My alarm clock is defined as a simple uint_16. In the next step, I will put it in Flash memory and access it with the EEPROM emulation for the Flash memory : I want it to be stored safely between reboots.
Basically, I need an hour and a date. To store that simply, I decided to only keep minutes. I store the number of minutes after 0:00. Want to set the alarm to 7:41 ? Just write 7*60+41. This way, the maximum number you need is 23*60+59 = 1439, which fits in 11 bits. But I also wants to keep if the alarm is on or off. I store that in the MSB (15th bit). So, to access the time, I need some binary operations that are described by the following macros :
#define READ_ALARM_HOURS   ((alarm & 0b111111111111) / 60)
#define READ_ALARM_MINUTES ((alarm & 0b111111111111) % 60)
#define READ_ALARM_STATE   ((alarm & 1<<15) >> 15)
#define FLIP_ALARM_STATE   alarm = (~(alarm & 1<<15) & 1<<15) | (alarm & 0b111111111111)
#define CHANGE_ALARM(h,m)    ((alarm & 1<<15) & 1<<15) | \
                              (((((alarm & 0b111111111111) / 60 + h + (((alarm & 0b111111111111) / 60 + h) < 0 ? 24 : 0)) %24) * 60) \
                              + ((((alarm & 0b111111111111) % 60) + m + ((((alarm & 0b111111111111) % 60) + m) < 60 ? 60 : 0))%60))


FLIP_ALARM_STATE just flips the MSB without looking at the

CHANGE_ALARM is dense.Basically, I alter the number without touching the MSB, and calculating the hours and minutes with a modulo. Things go wrong when I reach negative numbers though. This is why, in these cases, I add a full-round value (24 for the hours, 60 for the minutes) when that happens (this is tested with the ternary form. I like ternary forms.)

Now that I think of it, I could add that in every case, since the calculation is done with modulo in the end. Probably faster.

Anyway, I won't add anything else in my code before doing a HUGE cleanup.

Finally, I did some subtle additions to my code that I didn't mention, like the fact that in Mode 2, the alarm clock is displayed shifted on the right, or that it is displayed anyways on the 3rd line. I introduced a new flag_screen[CHANGEALARM] to update the screen when needed. I also put the local time functions in a dedicated timer function that's called by the MCU at every second. I directly stole that function from Jean-Pierre Cocatrix's code. I may use it in the end to reduce the number of RTOS Tasks and alleviate my code.

OK, time for some pictures and a nice little video !

I used an old IDE plug to connect on the wires. That's why I prefered to have contiguous pins. In the background, buttons !

Soldering done ! The buttons have common ground, and they are soldered to the IDE cable.
Video coming soon ! Gotta convert it before.


Now, I relocated the pins. I stripped down my IDE cable to only the pins I need, and I wired to the Blue Pill using DuPont wires. I definitely like these ones.




As a reminder, I put here the Blue Pill pinout, a true goldmine for anyone playing with pins. You can see that lots of pins have multiple interesting functions : maybe your next project will arise by looking at that pinout ?




Next thing coming, huge code cleanup and optimization. Sometimes my MCU is actually crashing, so I must go through that.
On another topic, I will soon try to make the holes on the top of my case for the buttons, and try to see if they fit in there. This may come sooner than the code optimizations, as it doesn't require much thinking, just action.
Still no news about the 7-segments 4-digits display... I hope some day it will arrive safely and working.
Bye-bye !


Comments

Popular posts from this blog

Flashing an STM32 "Blue Pill"

Flashing this STM32 "Blue Pill" board took me 5 hours. I finally recieved my microcontrollers. I have at disposal : A brand new WeMos D1 Mini (it's a smallish NodeMCU) An Arduino Pro Mini (it acts just like a Leonardo, but it lacks some interesting pins sadly :(  ) An STM32F103C8T6, I'll call this the Blue Pill for short. The Arduino Pro Mini has an Arduino bootloader out-of-the-box, so  I could play with it directly. It's nice ! Still, it lacks the double Serial of the original Leonardo, and most importantly, there's no A4 and A5 pins, so libraries for I2C won't work without modification. But the Blue Pill needs more work. Basically, I followed the instructions on this site : http://wiki.stm32duino.com/index.php?title=Installation Since it's quite complicated, consider the following as a tutorial to set up your Blue Pill. I tried different things, the following has worked for me. Before doing anything software-related, while reading the B

Anti-optimization, hardware crumbling apart, GitHub

Today, short post about deceiving software. Remember when last time, I said I had to do optmizations on my code ? Well I tried. And it went worse than I expected ! First, I tried to gather similar codes in some tasks, but it came out they were too heavy for FreeRTOS to handle them correctly, resulting in heap panic... So trying to enhance my code, I worsen it ! So I hard to roll it back. Then, I tried to implement real mutexes for some resources that are shared among the tasks, in particular the screen (and maybe the UART one day, but I can manage to use it only in one task, if I split it in several functions). When I though I understood it and implemented it correctly, it turned out to make the STM32 crash at bootup, and being non recognizable by my PC anymore ! So again, I had to rollback to the previous version. I'm still trying to figure out how to re-design my code, while keeping it runnable on that picky Blue Pill. You can FINALLY find my code on Github ! I suggest