Good day!
In [the previous] article, we developed a program for a bike computer, which allows you to determine the speed and distance traveled. It's time to implement the full set of features available for the current hardware version of my computer.
The idea is very simple: the computer display has two independent sectors: left and right, and I want to make it possible to select the parameters displayed on the screen separately for the left and right sectors. The choice of the parameter will be carried out by pressing the button SW2 for the left sector and SW3 for the right sector.
I want the following parameters to be displayed:
- Current speed [km/h];
- Current revs (turns) [RPM];
- Current gear;
- Distance traveled [km];
- Spent energy [kcal];
- Training time;
- Screens of smart assistant (3 separate screens).
In total we have 9 different functions. The first 4 do not cause any questions – we have already discussed them in detail in previous articles. The remaining ones are of interest, let's look at them in more detail.
Training time determining
However paradoxical, but we have not yet talked about this simple at first glance parameter. Determining the training time seems a very simple task: you need to determine the time during which the speed was not zero.
But as you can remember, the screen always displays non-zero speed, because I entered this restriction in my program: the speed can lie in the range [5..80] km / h.
Another problem is when the rider stops the rotation of pedals at the moment when the speed sensor magnet is near the reed switch, in this case the speed measurement cycle will never end, which means that the computer will "forever" think that we continue to ride.
I managed to solve both these problems by slightly improving the measurement thread, below we give the code fragment responsible for the measurement of time:
// this is a part of the thread 'sensors' if (!state && gpio::read(REED)) { stop_time = osKernelSysTick() - start_time; stop_time = stop_time / osKernelSysTickMicroSec(1000); if (stop_time < 4000) time += stop_time / 1000.0f; // if speed > 6 km/h then we ride osDelay(10); state = 1; count++; } else if (state && !gpio::read(REED)) { start_time = osKernelSysTick(); osDelay(10); state = 0; }
In this program, the training time is stored in the variable time. The unit of time is a second.
Smart assistant screen
Many of my readers probably wondered why I used the word smart in the title of my project. It's time to give an answer to this question.
A conventional bike computer is essentially a measuring device that does not give a rider any clues how it is necessary to ride. For 3 years of training, I have chosen for myself several programs, which, I think, will help anyone who wants to achieve the maximum result from training on an exercise bike. I want to tell you about results of training in the final part of my project, and today we will only talk about the technical side of the issue – the function smart assistant, which helps to carry out my training program.
The smart assistant works according to the following principle: "+" symbols are displayed on the screen if the rider should add speed and "-" symbols if it is necessary to slow down. Each "+" or "-" says that you need to add / subtract 1 km/h, for example, if "+++" is displayed you need to ride 3 km/h faster, and if "--- " 3 km/h slower. If the computer thinks that you are moving at an ideal speed, then "OK" is displayed on the screen.
Now, having understood the principle, let's consider each program separately.
1. Training with constant speed. It's very simple, you get on the exercise bike and drive for 100 minutes at a speed of V = 32 km/h. The bike computer determines the required speed (Vn) according to the following formula:
Vn = (Dt – D) / (Tt – T) * 3600
where:
- Dt – distance to be passed (in our case, it is 53.33 km for V = 32 km/h);
- D – distance traveled;
- Tt – required training time (in our case, 100 min or 6000 s);
- T – current training time.
2. Interval training. The essence of this training is very simple: every 20 seconds the rider changes speed from small (24 km/h) to high (40 km/h) and viceversa. The training mode (M) is determined by the formula:
M = (T / 20) mod 2
Further, the required speed Vn is determined from the value of M by following formulas:
if M = 0 then: Vn = (Dt – D) / (Tt – T) * 3600 * 0,75
if M = 1 then: Vn = (Dt – D) / (Tt – T) * 3600 * 1,25
3. Multistage training. Starting training with a big tension is a bad idea, you need to warm up for a bit. Warming up in a multistage training takes 20 minutes and runs at a speed of 24 km/h. Then, within 60 minutes, the rider runs at a normal speed of 32 km/h, and finally the last 20 minutes rider travels at a speed of 40 km/h.
The mode in multistage training (M) is determined by the formula:
M = (T / 1200 ) mod 5
Further, the required speed Vn is determined from the value of M by following formulas:
if M = 0 then Vn = (Dt_1 – D) / (Tt_1 – T) * 3600 * 0,75
elif M < 4 then Vn = (Dt_2 – D) / (Tt_2 – T) * 3600 * 1,00
elif M = 4 then Vn = (Dt – D) / (Tt – T) * 3600 * 1,25
where:
- Dt_1 – distance of the first stage of training (Dt_1 = 8 km);
- Dt_2 – distance of the first and second stages of training (Dt_1 = 40 km);
- Tt_1 – time of the first stage of training (Tt_2 = 20 min or 1200 s);
- Tt_2 – time of the first and second training stages (Tt_2 = 80 min or 4800 s);
Realization of the screen selection
We have got ready all functions necessary for high-grade work of the bike computer. It remains only to implement the output of these parameters to the screen. I've compiled an algorithm that allows you to cycle through the parameter displayed on both left and right screens. Simply put, the parameter changes sequentially from the first to the 9th, and then at the next pressing of button, the first parameter is displayed and so on the circle.
Due to the specifics of my LCD, I had to add to the program an individual RTOS thread, which will be responsible for working with keyboard. I named it keyboard:
// thread for working with keyboard static void keyboard(void const *args); osThreadDef(keyboard, osPriorityNormal, 1, 0); static void keyboard(void const *args) { for(;;) { // switch bitween screens if (gpio::read(SW2) == 1) // left screen { gpio::write(RED, 1); osDelay(10); while (gpio::read(SW2) == 1) __ASM("NOP"); gpio::write(RED, 0); left++; if (left > 8) left = 0; } if (gpio::read(SW3) == 1) // right screen { gpio::write(GREEN, 1); osDelay(10); while (gpio::read(SW3) == 1) __ASM("NOP"); gpio::write(GREEN, 0); right++; if (right > 8) right = 0; } } }
After adding the keyboard thread, I was able to greatly simplify the RTOS thread indication:
// thread for working with indication static void indication(void const *args); osThreadDef(indication, osPriorityNormal, 1, 0); static void indication(void const *args) { static int counts = 0; // counter for LCD refrashing for (;;) { // refrash LCD data counts++; if (counts > 100) { // output data to LCD lcd::write(TEXT_UP_LEFT, mode_name(left)); lcd::write(TEXT_UP_RIGHT, mode_name(right)); lcd::write(TEXT_DOWN_LEFT, mode_value(left)); lcd::write(TEXT_DOWN_RIGHT, mode_value(right)); counts = 0; } } }
The indication thread causes additional functions that are designed to form an information on LCD display. Below are listings of these functions.
// convert from 'value' to smart assistent string const char *smart_line(int i) { if (i > +5) i = +5; if (i < -5) i = -5; switch { case -5: return " ----- "; case -4: return " ---- "; case -3: return " --- "; case -2: return " -- "; case -1: return " - "; case 0: return " OK "; case 1: return " + "; case 2: return " ++ "; case 3: return " +++ "; case 4: return " ++++ "; case 5: return " +++++ "; } return "ERROR "; } // get the 'value' of the parameter in 'mode' const char *mode_value(char i) { int min, sec, m; float smart; switch(i) { // 'normal' functions case 0: sprintf(str, " %02.2f ", speed); break; case 1: sprintf(str, " %03.1f ", turns); break; case 2: sprintf(str, " %1.2f ", gear); break; case 3: sprintf(str, " %2.2f ", distance); break; case 4: sprintf(str, " %3.1f ", calories); break; case 5: min = (long long)time / 60; sec = (long long)time - min * 60; sprintf(str, " %03d:%02d ", min, sec); break; // 'smart' functions case 6: // smart mode #2 (standard) smart = (53.33f - distance) / (6e3f - time) * 3600; memcpy(str, smart_line(smart - speed), 8); break; case 7: // smart mode #2 (interval) m = ((long long)time / 20) % 2; if (m==0) smart = (53.33f - distance) / (6e3f - time) * 3600 * 0.75f; if (m==1) smart = (53.33f - distance) / (6e3f - time) * 3600 * 1.25f; memcpy(str, smart_line(smart - speed), 8); break; case 8: // smart mode #3 (multimode) m = ((long long)time / 1200) % 5; if (m==0) smart = (8.000f - distance) / (1.2e3f - time) * 3600 * 0.75f; else if (m<4) smart = (40.00f - distance) / (4.8e3f - time) * 3600 * 1.00f; else smart = (53.33f - distance) / (6.0e3f - time) * 3600 * 1.25f; memcpy(str, smart_line(smart - speed), 8); break; } // return value return str; } // get the 'name' of the 'mode' const char *mode_name(char i) { switch { case 0: return " Speed "; case 1: return " Turns "; case 2: return " Gear "; case 3: return " Dist. "; case 4: return " Energy "; case 5: return " Time "; case 6: return " AST 1 "; case 7: return " AST 2 "; case 8: return " AST 3 "; } return "ERROR "; }
Photos of the computer in different modes of operation are given below. In total, we now have 81 options for composing information on the screen!
Conclusion
Today I have finished the development of a “full featured” version of the bike computer's software. Ahead we have to manufacture the bike computer case, realize functions of work with Bluetooth, and further there will be only last strokes!
The program of my bike computer is still available in the [official repository] of the project. Today's version of the software has not been checked for metrology, I plan to do this after putting the bike computer into case and installing it on exercise bike, but we'll talk about this in one of future articles.
Thanks for reading and have a nice day!
Top Comments