|
I am working on an embedded product with an ARM-processor and I need to come up with an approach on how scheduling of tasks is going to work and then implement it. The following requirements apply:
• Standard C (no assembler).
• Can run on "any" microcontroller (of course, it needs to have a certain amount of RAM and flash).
• No use of malloc.
• If running on ARM and there's nothing to do and all timers are turned off, then it should enter stop mode (=system clock is turned off).
• If running on ARM and there's nothing to do but at least one timer is running, then it should enter sleep mode (=some peripherals are turned off, but the system clock and timers are still working).
• Needs to have some sort of prioritization, for example, low-level driver tasks needs to be handled before higher level tasks (such as tasks that arisen due to, for example, a push button pressed event) may be handled.
• It needs to, in some way, support calling of asynchronous functions, such as starting a DMA-transfer and then you get an interrupt when it's finished and in between it should execute other tasks.
My project is fairly limited in scope (not a lot things going on simultaneously), but I'm still struggling a bit to meet all the requirements. I will share below some of my thoughts. I was thinking of something like a modified round-robin scheduler with priorities with a number of queues:
void (*taskFunctionPtr_t)(uint32_t arg1, uint32_t arg2)
enum queueSelector_e {LOW_LEVEL_DRIVERS, USER_EVENTS};
addTaskToQueue(taskFunctionPtr_t, queueSelector_e);
In main.c:
while (TRUE) {
if (0 < numQueuedTasks(LOW_LEVEL_DRIVERS)) {
handleTask(LOW_LEVEL_DRIVERS);
continue;
}
if (0 < numQueuedTasks(USER_EVENTS)) {
handleTask(USER_EVENTS);
continue;
}
if (anyTimerRunning()) {
enterIdleMode(); } else {
enterStopMode(); }
}
In order to support asynchronous tasks, I probably need to make my tasks and queues more advanced:
void (*asynchronousTaskFinishedFunctionPtr)(asynchronousTaskResult_e);
void someAsynchronousFunction(asynchronousTaskFinishedFunctionPtr);
enum asyncTaskStatus_e {TASK_NOT_STARTED, TASK_IN_WAIT_PHASE, TASK_FINISHED_NEEDS_REPEAT_DUE_TO_ERROR, TASK_SUCCESSFULLY_FINISHED};
asyncTaskStatus_e (*getAsyncTaskStatusPtr)();
addSynchronousTaskToQueue(taskFunctionPtr_t, queueSelector_e);
addAsynchronousTaskToQueue(taskFunctionPtr_t, queueSelector_e, getAsyncTaskStatusPtr);
Then I'd have to have something like this in main.c:
while (TRUE) {
Bool_t taskIsInWaitPhase;
if (0 < numQueuedTasks(LOW_LEVEL_DRIVERS)) {
taskIsInWaitPhase = handleTask(LOW_LEVEL_DRIVERS);
if (!taskIsInWaitPhase) {
continue;
}
}
if (0 < numQueuedTasks(USER_EVENTS)) {
taskIsInWaitPhase = handleTask(USER_EVENTS);
if (!taskIsInWaitPhase) {
continue;
}
}
if (anyTimerRunning() || atLeastOneTaskInWaitPhase()) {
enterIdleMode(); } else {
enterStopMode(); }
}
Bool_t handleTask(queueSelector_e) {
if (taskIsAsynchronous()) {
if (asynchronousTaskStatus == TASK_SUCCESSFULLY_FINISHED) {
incrementTaskQueueReadPointer();
} else if ((asynchronousTaskStatus == TASK_NOT_STARTED) ||
(asynchronousTaskStatus == TASK_FINISHED_NEEDS_REPEAT_DUE_TO_ERROR)) {
callTaskFunction();
} else if (asynchronousTaskStatus == TASK_IN_WAIT_PHASE) {
return TRUE;
}
} else {
callTaskFunction();
incrementTaskQueueReadPointer();
}
return FALSE;
}
There are still issues with this approach, for example, how do I handle long tasks? I don't want a driver task to have to wait for a long user task to finish. I guess I need to have a requirement that all tasks need to be short and if they are long in reality, then they need to broken up and then the task will need to tell scheduler to repeat the same task:
void longTask(uint32_t arg1, uint32_t arg2) {
enum partSelector_e {
PART1,
PART2,
PART3,
PART4,
PART5
};
static partSelector_e partSelector = PART1;
switch (partSelector) {
case PART1:
do1stPartOfLongTask();
partSelector++;
repeatThisTask(); return;
case PART1:
do2ndPartOfLongTask();
partSelector++;
repeatThisTask(); return;
case PART3:
do3rdPartOfLongTask();
partSelector++;
repeatThisTask(); return;
case PART4:
do4thPartOfLongTask();
partSelector++;
repeatThisTask(); return;
case PART5:
do5thPartOfLongTask();
partSelector = PART1;
return;
}
}
Another problem is how asynchronuous calls can be handled. Let's say I want to do the following and the order of sequence is important:
void button1pressedTask(uint32_t arg1, uint32_t arg2) {
readADC(); setPinHigh(); startDMAtransfer writeI2Cdata(); writeUARTdata(); }
Remember, when calling an asynchronous function, other tasks should be allowed to execute. Does anybody have any suggestions how I can solve this? Am I on the right track or would someone suggest a completely different approach?
modified 10-Jun-18 13:47pm.
|
|
|
|
|
All of your code you have done is a co-operative system for the situation of longer tasks you need to do pre-emptive context switches and for the asynchronous order problem you need semaphores,mutex or spinlock implementation.
For pre-emptive context switching you usually have a timer interrupt it triggers 10-1000 times a second. When the interrupt triggers it looks to see what task should run next in a scheduler scheme, it then forcibly saves every cpu register and every fpu register to a context stack so the program can resume from that point. Next it loads the cpu registers and fpu registers for the next task to run from a context stack and then it proceeds to run that task until a subsequent timer tick stops it passing control to another task. You will require critical section process which is small areas of code when it enters the task switcher can not interrupt and often that is as simple as switching off interrupts so the timer interrupt doesn't fire.
Semaphores or spinlocks are the usual way of dealing with sharing the I/O functionality. So in your DMA example the use of the DMA would have an aquire and release process. So any function wanting to do a DMA transfer would first ask for ownership of the DMA, if the DMA is in use it will already be aquired and the caller must wait for whoever has it to release. So anyone that has the DMA then releases and the act of doing so then allows waiting callers to then execute so it looks like.
void DMA_Write ( /.* some variables */)
{
SpinLock_Acquire ();
ActualDMAFunction(/.* some variables */);
SpinLock_Release ();
}
For the processor you are on if you search for "ARM Synchronization Primitives" and ARM will have a whitepaper on typical setups and minimum requirements. It will usually involve the use of the special opcodes LDREX/STREX (as well as WFE/WFI on multicore systems if you want to sleep the core while waiting).
You will also find the code for the context switch by a simple search of "ARM Context switch" with the processor name and you will get an ARM white paper describing it. They will all generally work on entering the call with one register pointing to the where to save all the current registers and another to where to load the registers from.
There are some really simple AVR task switcher projects which would be easy to adapt .. you simply need to change the context_switch assembler and replace the ATOMIC_BLOCK store and release with the interrupt enable/disable (it is all it generally does on smaller AVR processors) it is a basic CRITICAL SECTION control so it doesn't get interruptted with the code inside the curly brackets.
GitHub - kcuzner/kos-avr: Kevin's RTOS for AVR microcontrollers[^]
That is basically one file kos.c and 1 header kos.h
That uses a simple round robin scheduler (every task has same priority) if you want advanced priority based scheduler you can simply replace the scheduler code but I would suggest get the most simple running first then add the advanced scheduler. All you will end up doing is replacing the scheduler function with a more advanced one and your task creation call will have a priority you assign.
Final note if you are on a multicore system you will need to change the semaphore to the proper arm code the presented one generally won't work with multicores. It should be fine on a single core.
If you need further help we really need what ARM processor.
In vino veritas
modified 11-Jun-18 0:54am.
|
|
|
|
|
How is this different from using an off-the-shelf RTOS such as FreeRTOS?
|
|
|
|
|
It's similar if you look only at the very basic Kernel but things can be much more complex, painful and they may already have known problems
Some examples
1.) ARM Security/protection ring system usually at least two levels EL0 and EL1 and possibly EL2 & EL3 on an ARM8.
As an example FreeRTOS was not designed with those in mind, none of the existing ports will utilize the scheme with any intelligence.
2.) Multicore support on ARM cpus like ARM7 & ARM8.
You usually have to bring the MMU up to get cache coherency and that often means memory virtualization will need to be operating. For example on Free-RTOS I know of only a simple SMP port on a multicore system, no AMP and no BMP implementations. So FreeRTOS was not designed in an era with multicore support in mind.
3.) Multiple implementations which adds a whole complexity as your implementation becomes one of x number and not the most efficient or best suited to your CPU. You may struggle with massive code complexity on code that may not even compile in your implementation
If you are on a multicore cpu like a cortex-a53 points 1 & 2 will probably cause you a great deal of problem. That is why there are no real fully functional ports of Free-RTOS on things like Raspberry Pi 3 and beagle board blacks (there are a couple of half functional ports). So Free-RTOS is easy to port for simple single CPU it is harder to port and often at odds with how you want to operate complex multicore CPU's. Hence we come back to the problem what CPU are you talking about?
I would also add FreeRTOS has some obvious shortcomings of features you may like, like no concept of task aging etc (Aging (scheduling) - Wikipedia[^]
As with everything you are better of playing and working out what is a good system rather than mindlessly porting some system and not understanding it properly. So I might for example port FreeRTOS on a simple ARM5 or 6 cpu but I wouldn't bother about it on an ARM8 the later being far to different for what Free-RTOS was designed around.
Again I say if we know what CPU we are dealing with it is easier to make more detailed and helpful suggestions.
In vino veritas
modified 11-Jun-18 9:02am.
|
|
|
|
|
leon de boer wrote: Again I say if we know what CPU we are dealing with it is easier to make more detailed and helpful suggestions.
Single core (STM32F4 and STM32F7). Since I'm working with rather basic microcontrollers and my applications really aren't that heavy, it's seems a bit overkill to do the context switching like an RTOS would do.
modified 11-Jun-18 9:21am.
|
|
|
|
|
You can't reach your goals without some switcher, I believe you already worked that our yourself it gets more and more complex.
There is actually less code in writing the switcher it's a couple hundred lines of code and it's faster as your CPU is designed to context switch
From "The Definitive Guide to ARM Cortex M3 and Cortex-M4" that is a 4 task round robin switcher in 150 lines of code.
You just need to change the scheduler (SysTick_Handler) to a priority based one .. you will find dozens on the net
You can extend the number of task etc, the code is very very obvious and easier than your current scheme you have reached.
#include "stm32f4xx.h"
#define LED0 (1<<7)
#define LED1 (1<<8)
#define LED2 (1<<9)
#define LED3 (1<<10)
#define HW32_REG(ADDRESS) (*((volatile unsigned long *)(ADDRESS)))
#define stop_cpu __breakpoint(0)
void LED_initialize(void);
void task0(void);
void task1(void);
void task2(void);
void task3(void);
volatile uint32_t systick_count=0;
long long task0_stack[1024], task1_stack[1024],
task2_stack[1024], task3_stack[1024];
uint32_t curr_task=0;
uint32_t next_task=1;
uint32_t PSP_array[4];
int main(void)
{
SCB->CCR |= SCB_CCR_STKALIGN_Msk;
LED_initialize();
PSP_array[0] = ((unsigned int) task0_stack)
+ (sizeof task0_stack) - 16*4;
HW32_REG((PSP_array[0] + (14<<2))) = (unsigned long) task0;
HW32_REG((PSP_array[0] + (15<<2))) = 0x01000000;
PSP_array[1] = ((unsigned int) task1_stack)
+ (sizeof task1_stack) - 16*4;
HW32_REG((PSP_array[1] + (14<<2))) = (unsigned long) task1;
HW32_REG((PSP_array[1] + (15<<2))) = 0x01000000;
PSP_array[2] = ((unsigned int) task2_stack)
+ (sizeof task2_stack) - 16*4;
HW32_REG((PSP_array[2] + (14<<2))) = (unsigned long) task2;
HW32_REG((PSP_array[2] + (15<<2))) = 0x01000000;
PSP_array[3] = ((unsigned int) task3_stack)
+ (sizeof task3_stack) - 16*4;
HW32_REG((PSP_array[3] + (14<<2))) = (unsigned long) task3;
HW32_REG((PSP_array[3] + (15<<2))) = 0x01000000;
curr_task = 0;
__set_PSP((PSP_array[curr_task] + 16*4));
NVIC_SetPriority(PendSV_IRQn, 0xFF);
SysTick_Config(168000);
__set_CONTROL(0x3);
__ISB();
task0();
while(1){
stop_cpu;
};
}
void task0(void)
{ while (1) {
if (systick_count & 0x80) {GPIOE->BSRRL = LED0;}
else {GPIOE->BSRRH = LED0;}
};
}
void task1(void)
{ while (1) {
if (systick_count & 0x100){GPIOE->BSRRL = LED1;}
else {GPIOE->BSRRH = LED1;}
};
}
void task2(void)
{ while (1) {
if (systick_count & 0x200){GPIOE->BSRRL = LED2;}
else {GPIOE->BSRRH = LED2;}
};
}
void task3(void)
{ while (1) {
if (systick_count & 0x400){GPIOE->BSRRL = LED3;}
else {GPIOE->BSRRH = LED3;}
};
}
__asm void PendSV_Handler(void)
{
MRS R0, PSP
STMDB R0!,{R4-R11}
LDR R1,=__cpp(&curr_task)
LDR R2,[R1]
LDR R3,=__cpp(&PSP_array)
STR R0,[R3, R2, LSL #2]
LDR R4,=__cpp(&next_task)
LDR R4,[R4]
STR R4,[R1]
LDR R0,[R3, R4, LSL #2]
LDMIA R0!,{R4-R11}
MSR PSP, R0
BX LR
ALIGN 4
}
void SysTick_Handler(void)
{
systick_count++;
switch(curr_task) {
case(0): next_task=1; break;
case(1): next_task=2; break;
case(2): next_task=3; break;
case(3): next_task=0; break;
default: next_task=0;
stop_cpu;
break;
}
if (curr_task!=next_task){
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}
return;
}
void LED_initialize(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN;
GPIOE->MODER |= (GPIO_MODER_MODER7_0 |
GPIO_MODER_MODER8_0 |
GPIO_MODER_MODER9_0 |
GPIO_MODER_MODER10_0 ) ;
GPIOE->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR7 |
GPIO_OSPEEDER_OSPEEDR8 |
GPIO_OSPEEDER_OSPEEDR9 |
GPIO_OSPEEDER_OSPEEDR10 );
GPIOE->PUPDR = 0;
return;
}
In vino veritas
modified 11-Jun-18 10:49am.
|
|
|
|
|
Thank you for your very elaborate answers.
|
|
|
|
|
I've done cooperative multitaskers on a number of platforms (not ARM, but I don't think it should present any unusual problems) and found them to be extremely handy in cases where the set of tasks is fixed. Even if a task would spend most of its time in a loop:
while(!x_ready)
task_spin();
the cost to switch to the task, check x_ready, and then switch to the next task may be less than the cost of a more complicated scheduler trying to decide if it should switch to that task.
The biggest design issue with a cooperative task switcher is deciding what invariants are going to hold any time code does a task_spin(). While a preemptive multitasker would require that functions acquire locks before breaking any invariant even temporarily, and re-establish the invariant before releasing the lock, cooperative task switching doesn't require that. More significantly, it doesn't require that tasks do anything special to handle the fact that a lock isn't available. The gotcha is that if an invariant can't be upheld during some operation, and the time required to perform that operation could grow beyond the maximum amount of time one wants to go without a task_spin(), handling that situation may be complicated.
|
|
|
|
|
String Area = AreaCBox.Text;
DAL.CaseBarCode csbrt = new DAL.CaseBarCode();
System.Data.DataSet csbrs = csbrt.GetPO(Area);
foreach (DataRow dr in csbrs.Tables[0].Rows)
{
POCBox.Items.Add(dr["ProductionOrder"]);
}
Hello,
In the above the code I am calling a stored procedure(csbrt.GetPO(Area)) which retrieves two values "ProductionOrder" and "colorflag" columns. I am inserting ProductionOrder into 'POCBox' combobox.
Now my requirement is to color the text for ProductionOrder in the combobox to green if the colorflag value is 0 and color it red if the colorflag value is 1. Please help with the code. Thanks in advance.
|
|
|
|
|
What you want is called an "owner drawn" combobox.
There's a great article right here on CP that explains how to do it with all the code you need:
Implementing an OwnerDrawn ComboBox[^]
And by the way, you posted this in the C++ forum instead of the C# forum where it belongs.
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
#include <iostream>
using namespace std;
template <class T>
class Num {
protected:
T val;
public:
Num(T x) { val = x;}
virtual T getval() { return val; }
};
template <class T>
class SqrNum : public Num<T> {
public:
SqrNum(T x) : Num<T>(x) {}
T getval() { return (val * val); }
};
int main(){
Num<int> *bp, numInt_ob(2);
SqrNum<int> *dp, sqrInt_ob(3);
Num<double> numDouble_ob(3.3);
bp = dynamic_cast<Num<int>*> (&sqrInt_ob);
if(bp) {
cout << "Cast from sqrNum<int>* to Num<int>* OK.\n"
<< "Value is " << bp->getval() << endl;
}
else
cout << "Error !\n\n";
dp = dynamic_cast<SqrNum<int>> (&numInt_ob);
if(dp)
cout << "Error !\n";
else {
cout << "Cast from Num<int>* to SqrNum<int>* not OK.\n"
<< "Can't cast a pointer to a base object into\n"
<< "a pointer to a derived object\n\n";
}
bp = dynamic_cast<Num<int>*> (&numDouble_ob);
if(bp)
cout << "Error" << endl;
else {
cout << "Can't cast from Num<double>* to Num<int>*.\n"
<< "These are 2 different types.\n\n";
}
return 0;
}
in the code above i m getting error that
Quote: error: 'val' was not declared in this scope
T getval() { return (val val); }
^~~
but
Quote: class SqrNum : public Num<t>
according to this the protected value in the base class should just work fine as protected value in the derived class, but it isn't ?
How do i solve this problem ?
EDIT :
below is the working code:
#include <iostream>
using namespace std;
template <class T>
class Num {
protected:
T val;
public:
Num(T x) { val = x;}
virtual T getval() { return val; }
};
template <class T>
class SqrNum : public Num<T> {
public:
SqrNum(T x) : Num<T>(x) {}
T getval() { return (Num<T>::val * Num<T>::val); }
};
int main(){
Num<int> *bp, numInt_ob(2);
SqrNum<int> *dp, sqrInt_ob(3);
Num<double> numDouble_ob(3.3);
bp = dynamic_cast<Num<int>*> (&sqrInt_ob);
if(bp) {
cout << "Cast from sqrNum<int>* to Num<int>* OK.\n"
<< "Value is " << bp->getval() << endl;
}
else
cout << "Error !\n\n";
dp = dynamic_cast<SqrNum<int>*> (&numInt_ob);
if(dp)
cout << "Error !\n";
else {
cout << "Cast from Num<int>* to SqrNum<int>* not OK.\n"
<< "Can't cast a pointer to a base object into\n"
<< "a pointer to a derived object\n\n";
}
bp = dynamic_cast<Num<int>*> (&numDouble_ob);
if(bp)
cout << "Error" << endl;
else {
cout << "Can't cast from Num<double>* to Num<int>*.\n"
<< "These are 2 different types.\n\n";
}
return 0;
}
Thank you
modified 6-Jun-18 0:49am.
|
|
|
|
|
dp = dynamic_cast<SqrNum<int>*> (&numInt_ob);
Needs an '*' after <int>.
Code now runs successfully.
|
|
|
|
|
i added * after SqrNum<int> but it is still giving that error .
|
|
|
|
|
Did not give that error in the original code that I compiled.
|
|
|
|
|
Quote: template <class t="">
class SqrNum : public Num<t> {
public:
SqrNum(T x) : Num<t>(x) {}
T getval() { return (val * val); }
};
Should be
template <class T>
class SqrNum : public Num<T> {
public:
SqrNum(T x) : Num<T>(x) {}
T getval() { return (Num<T>::val * Num<T>::val); }
};
|
|
|
|
|
Quote: return (Num<t>::val * Num<t>::val);
but i have already inherited all the members of the base class to the derived class, then why i need to use scope resolution operator to point where to look for val ?
|
|
|
|
|
|
|
|
How can i access the Device manager part of the Windows API?
I want to beable to show what certain devices are connected in a project in a custom application.
ill be Writing it in C++
Any ideas?
|
|
|
|
|
This may be a good starting point.
"One man's wage rise is another man's price increase." - Harold Wilson
"Fireproof doesn't mean the fire will never come. It means when the fire comes that you will be able to withstand it." - Michael Simmons
"You can easily judge the character of a man by how he treats those who can do nothing for him." - James D. Miles
|
|
|
|
|
Hi everyone!
I've been, for the past days, searching for the smallest executable for Windows. The reason is that I want to start a small macro for a game (basically just presses a key and maybe draw some pixels on screen), and I'd like it to be tiny, just for the sake of it. I'll not use NASM, however; I intend to use Ollydbg or another such tool to directly "code" in the executable itself (sort of like a challenge).
I searched the web for the smallest executable, and it appears to be 97 bytes large. I couldn't find it for download, but I don't think it would run in any version of Windows (I want it to run from XP up (XP,7,8,10)). I found one called "hh2 golden.exe" (~400 bytes), which apparently runs from XP up, but it prints "Hello World!" in the screen (I'd like one that did nothing).
Also, I couldn't assemble them (I don't know how to properly use NASM or other assembler). I'd use "hh2 golden.exe", but or other assemblerthen I had the idea to ask here if one of you guys have a better executable that I could use in this adventure.
tl;dr: I'm asking for the smallest executable capable of running from winXP up. Preferrably it does nothing, since I want to modify it and put some functionality to it (but I need it to be completely clear at the beginning)
Thanks!
|
|
|
|
|
|
How can i be good on coding to start programming?
|
|
|
|
|
Understand problem solving.
Ask questions.
Ability to think outside the box.
et al
"One man's wage rise is another man's price increase." - Harold Wilson
"Fireproof doesn't mean the fire will never come. It means when the fire comes that you will be able to withstand it." - Michael Simmons
"You can easily judge the character of a man by how he treats those who can do nothing for him." - James D. Miles
|
|
|
|
|