Hi !
1. I'm trying to port FreeRTOS to an evaluation circuit board designed by my
company. The circuit board has the LPC2368 procesor.
a. I have the Keil tools & uVision V4 installed on my Windows 8 PC.
b. The Keil simulator works on my PC. So I'm able to use the Debug features
in uVision (set breakpoints, run, stop, step into, step over etc).
c. To run my project on the circuit board:
i. I create a .hex file using the "Project->Build target" option
in uVision.
ii. I write the .hex file to the circuit board using Flash Magic V7.
iii. I reset the circuit board.
iv. I use LEDs & the serial port to debug.
2. I downloaded the FreeRTOS source:
a. Apparently, FreeRTOS is ported to the LPC2129 processor, for the Keil
tools. I used the folder named
"FreeRTOSv202112.00\FreeRTOS\Demo\ARM7_LPC2129_Keil_RVDS".
b. Previously, developers in my company had configured drivers for LEDs &
the serial port. I merged the FreeRTOS code with their drivers:
i. I was able to make the FreeRTOS driver for LEDs to work.
ii. I was unable to make the FreeRTOS driver for the serial port to
work. So I used my companies' code as a base for the serial port
driver.
3. After some attempts, I was able to configure the Startup.s file to switch to
supervisor mode before main() was called. Here's a snippet from Startup.s
that sets up the last 2 modes before the call to main().
; Sid - 2022.01.20
MSR CPSR_c, #Mode_SYS
MOV SP, R0
SUB R0, R0, #SYS_Stack_Size ;SUB SL, SP, #SYS_Stack_Size
IMPORT TargetResetInit
BL TargetResetInit
; Enter Supervisor Mode and set its Stack Pointer
MSR CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit
MOV SP, R0
SUB R0, R0, #SVC_Stack_Size
If you prefer to see my main() function now, see step 6 & then return here.
4. My uVision project compiles without errors or warnings. Regarding timers:
a. In the prvSetupTimerInterrupt() function, I have set vPreemptiveTick() as
the Interrupt Service Routine (ISR) for timer0.
b. But after main() runs, the timer keeps invoking another function which I
created in Startup.s - this function is glued to the vector table. The
relevant snippets from Startup.s are shown below:
; Sid - 2022.01.21
LDR PC, Timer0_Addr
Timer0_Addr DCD Timer0_Handler
Timer0_Handler
MOV R4, #1
LDR R5, =0xE0004000 ; Timer0 Interrupt Register
STR R4, [R5] ; Write 1 to the Timer0
; Interrupt Register to
; clear the interrupt.
SUBS PC, LR, #0x4 ; Return ? (I'm not sure.)
5. When I write my code to the circuit board & execute there:
a. Initial LED blinks & serial port prints (programmed by me) occur.
b. The LED of vErrorChecks():
i. toggles once if I use Flash Magic's Terminal tool for the serial
port.
ii. doesn't toggle if I use Putty for the serial port.
6. I DON'T see tasks running. My main() function is shown below, I haven't
changed FreeRTOS' main() in terms of tasks, I've only performed hardware
inits (LEDs & serial port). If you understand the issue(s?), please give
suggestions. Thanks in advance !
int main( void )
{
/* Setup the hardware for use with the Keil demo board. */
prvSetupHardware(); // My code inside prvSetupHardware() sets up the serial port (UART0).
led_init(); // My code
/* Start the demo/test application tasks. */
vAltStartComTestTasks( mainCOM_TEST_PRIORITY, mainCOM_TEST_BAUD_RATE, mainCOM_TEST_LED );
vStartLEDFlashTasks( mainLED_TASK_PRIORITY );
uart_print_tests(); // My code - seems like it works.
led_tests_rtos(); // My code & FreeRTOS code - seems like it works.
vStartPolledQueueTasks( mainQUEUE_POLL_PRIORITY );
vStartBlockingQueueTasks( mainBLOCK_Q_PRIORITY );
vStartSemaphoreTasks( mainSEM_TEST_PRIORITY );
vStartDynamicPriorityTasks();
/* Start the check task - which is defined in this file. This is the task
that periodically checks to see that all the other tasks are executing
without error. */
xTaskCreate( vErrorChecks, "Check", configMINIMAL_STACK_SIZE, NULL, mainCHECK_TASK_PRIORITY, NULL );
/* Now all the tasks have been started - start the scheduler.
NOTE : Tasks run in system mode and the scheduler runs in Supervisor mode.
The processor MUST be in supervisor mode when vTaskStartScheduler is
called. The demo applications included in the FreeRTOS.org download switch
to supervisor mode prior to main being called. If you are not using one of
these demo application projects then ensure Supervisor mode is used here. */
vTaskStartScheduler();
/* Should never reach here! If you do then there was not enough heap
available for the idle task to be created. */
for( ;; );
}
Thanks for the details, but it is not clear exactly what is working and what not. Is it that you don’t enter the first task, or the the timer interrupt isn’t working, or that the task only executes once, or something else?
I don’t know if the timers on the LPC2368 and LPC2129 are the same or not - but there are LPC2368 ports for other compilers you can check the code for: https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/c4f9e27c28c018c85b9d23e5ac7470ada508bdb8/portable/GCC/ARM7_LPC23xx/port.c#L181
Then I would try one thing at a time. For example:
-
Try getting the timer interrupt working without running FreeRTOS. Just configure the timer, and install a handler for it that does nothing other than increment a variable. Once the timer is running at the correct frequency replace the interrupt handler with the FreeRTOS one.
-
Next create a very simple task:
volatile int x = 0;
void vMyTask( void * pv )
{
for( ;; )
{
x++;
}
}
Create this task at priority 1 (so it is first to run), and no other tasks (so set configUSE_TIMERS to 0 so the timer task is not created). Set a break point at the start of the task. Then start the scheduler and see if you hit the break point. If you do, you enter the first task correctly.
Do the above without the tick running - we just want to see if you get to the task.
- Next add another task, and put a yield into each:
volatile int x = 0;
void vMyTask( void * pv )
{
for( ;; )
{
x++;
taskYIELD();
}
}
volatile int y = 0;
void vMyTask( void * pv )
{
for( ;; )
{
y++;
taskYIELD();
}
}
Now, still without the tick interrupt, create both tasks, put a break point in each on the line that increments the variable. Each task should yield to the other - so when you run the code each break point should get hit in turn.
- Next take the yields out again, but this time start the tick interrupt properly. Now both break points should still get his but the variable should increment many times between each as you should only be switching tasks when the tick interrupt executes.
How far do you get?
Thanks for the response Richard !
It might be easier for me to take out code with #if 0 ... #endif
blocks & then bring in the code you suggest in steps.
Give me a day / two, I’ll get back to you !
https:__embdev.net_topic_179152
(replace _ with / - new users can’t post links apparently)
Hi ! I’m going to check if I can get ‘blinky.zip’ (from Michael G.) to work first.
Probably, I’ll get back to you next Monday / Tuesday !
Hi !
1. I got the timer example (blinkyy) to work ! Its ISR is run whenever Timer0
counts up to its match value. So far, I haven't replaced its ISR with the
one in FreeRTOS (because this seems difficult).
2. I created the following task (with priority 1) & then started the scheduler:
volatile int x = 0;
void increment_x_task( void * pv )
{
/* Parameters are not used. */
( void ) pv;
for( ;; )
{
x++;
//taskYIELD();
}
}
a. When taskYIELD() is commented:
i. x is incremented till some upper value (like 0x7A).
ii. then, a UART0 ISR executes.
iii. after the UART0 ISR, a Prefetch Abort occurs.
b. When taskYIELD() is not commented:
i. x is incremented without abort.
3. I left taskYIELD() uncommented. I created a second task (shown below, also
with priority 1) & then started the scheduler:
volatile int y = 0;
void increment_y_task( void * pv )
{
/* Parameters are not used. */
( void ) pv;
for( ;; )
{
y++;
taskYIELD();
}
}
a. This task ran forever & the breakpoint inside the increment_x_task was
never reached.
4. I'll continue with your remaining suggestions from tomorrow. In a day / two,
I'll tell you what I see.
Strange the UART interrupt only occurs when taskYIELD() is commented - make sure the interrupt is disabled for now.
Do you install vPortYieldProcessor() as the SWI handler in the vector table. I don’t know how you are defining the vector table, but if in the startup code it would look something like this, although your code may use different names for vectors not used by FreeRTOS:
b _start /* reset - _start */
ldr pc, _undf /* undefined - _undf */
ldr pc, _swi /* SWI - _swi */
ldr pc, _pabt /* program abort - _pabt */
ldr pc, _dabt /* data abort - _dabt */
nop /* reserved */
ldr pc, [pc,#-0xFF0] /* IRQ - read the VIC */
ldr pc, _fiq /* FIQ - _fiq */
_undf: .word __undf /* undefined */
_swi: .word vPortYieldProcessor /* SWI */
_pabt: .word __pabt /* program abort */
_dabt: .word __dabt /* data abort */
_fiq: .word __fiq /* FIQ */
__undf: b . /* undefined */
__pabt: b . /* program abort */
__dabt: b . /* data abort */
__fiq: b . /* FIQ */
Hi !
Ok, I'll disable the UART interrupt & see what happens !
Though the timer code in the blinkyy example isn't merged with the FreeRTOS code
yet:
1. I see that the timer code uses an IntEnable() function which calls SWI with a
parameter.
a. Based on the parameter, SoftwareInterrupt (which is glued to SWI_Handler
in the vector table) calls 1 of 4 functions (IRQDisable / IRQEnable /
FIQDisable / FIQEnable).
2. I suppose the timer code mentioned above must be preserved. So maybe I don't
want to glue vPortYieldProcessor to SWI_Handler.
a. In the FreeRTOS code, taskYIELD() uses portYIELD(), which uses
vPortYield().
b. vPortYield() is an assembly language function which uses the "SVC 0"
instruction to invoke SWI_Handler.
c. Since I don't want vPortYieldProcessor to be glued to SWI_Handler, I
could remove the "SVC 0" instruction & use "BL vPortYieldProcessor"
instead.
3. Does the above approach look valid ? I'll try it & tell you what I see !
No, it didn’t work ! I get a Data Abort after vPortYieldProcessor
.
I’ll check if IntEnable()
in the timer code can be rewritten without the SWI.
Hi !
1. In the timer code, I rewrote IntEnable() without the SWI. It has only 1
statement (B IRQEnable), which seems enough for now. The blinkyy timer
worked !
2. In the FreeRTOS code:
a. I glued vPortYieldProcessor to SWI_Handler.
b. Now the taskYIELD() function worked ! (I saw the switch between the x & y
tasks for each increment).
c. But the UART0 interrupt occurred when y was 0xB & x was 0xA. Following
the UART0 ISR, a Prefetch Abort occurred.
d. I had to:
i. disable the UART interrupt.
ii. remove the install_irq() call which glued the UART ISR.
Then the x & y tasks switched and x & y were incremented continuously
without abort.
3. Next, as you suggested, I will:
a. bring the timer code into FreeRTOS.
b. remove the taskYIELD() calls.
c. see if tasks are switched (by the scheduler ?) at timer tick interrupts.
[FYI - when you format your posts like that I can’t read them on my phone, so response times are longer]
The purpose of the instructions I provided was to make the system as simple as possible and then try one thing at a time. That is not going to work if you have other interrupts interfering with what you are trying to observe.
I see that the timer code uses an IntEnable() function which calls SWI with a
parameter.
What is that timer code? It’s not going to work if both your timer and FreeRTOS are trying to use the SWI interrupt.
Since I don’t want vPortYieldProcessor to be glued to SWI_Handler, I
could remove the “SVC 0” instruction & use “BL vPortYieldProcessor”
instead.
That would be 100% guaranteed not to work. If that were an option, it wouldn’t be using the SWI interrupt.
Sorry about that Richard ! It’s not my intention to make posts hard to read on your phone. By the way, I may not mind if response times are longer. I tried using the “Numbered List” button earlier, but it’s very difficult to use (for me at least). When I make nested lists, even the best GUI tools (like Microsoft Word) are hard to use. Web GUIs are miles behind !
Yes, I understand, interrupts must not interfere ! I’m a bit curious about how we’ll bring such interrupts in later, but that can wait !
Yes, at the moment, both the timer & FreeRTOS use the SWI
interrupt. That’s precisely why I was trying to un-glue one or the other.
Hmm …, it’s true that my attempt to un-glue FreeRTOS (vPortYieldProcessor
) from SWI_Handler
gave errors. But I was able to un-glue the timer’s IntEnable()
from SWI_Handler
. (See point 1 in my post on “Feb 2, 2022 1:38 pm”.)
Anyway, I’ll post either today / tomorrow about what I see, when switching tasks at a timer tick !
Hi !
-
Setup:
- I removed the
taskYIELD()
calls in bothincrement_x_task
&increment_y_task
. Both tasks have priority 1. -
configUSE_TIMERS
is 1:-
configTIMER_TASK_PRIORITY
is 1. -
configTIMER_QUEUE_LENGTH
is 10. -
configTIMER_TASK_STACK_DEPTH
is 10.
-
-
configUSE_PREEMPTION
is 1-
vPreemptiveTick
is installed as the ISR for the timer. It has priority 1.
-
- I removed the
-
Upon running the program in the simulator:
-
increment_x_task()
runs & x is continuously incremented. -
In parallel, the timer counts up. When the timer reaches its match value:
- The
vPreemptiveTick
ISR callsxTaskIncrementTick()
&xTickCount
becomes 1. - When the Prescale Counter counts up to its match value, the Timer Counter is reset (made 0).
- The
vPreemptiveTick
ISR callsvTaskSwitchContext()
which seems to finish successfully. - After the last statement of the
vPreemptiveTick
ISR,increment_y_task()
starts running & y is continuously incremented.
- The
-
In parallel, the timer counts up. When the timer reaches its match value:
- The
vPreemptiveTick
ISR callsxTaskIncrementTick()
&xTickCount
becomes 2. - When the Prescale Counter counts up to its match value, the Timer Counter is reset (made 0).
- The
vPreemptiveTick
ISR callsvTaskSwitchContext()
which seems to finish successfully. - After the last statement of the
vPreemptiveTick
ISR, the next statement that runs is NOT inincrement_x_task()
. Instead, it is inIRQ_Handler()
in Startup.s. Specifically it is the “branch to the ISR” statement inIRQ_Handler()
.
- The
-
Have you seen such events before ? What do you suggest ?
That is good progress. It sounds like both the yield and tick ISR are now switching between tasks. If you end up in an IRQ handler that is branching to itself it sounds like it is a default handler for an interrupt you haven’t installed a real handler for - do you know which interrupt it is? As I recall the LPC2368 uses a vectored interrupt controller, so you would need to see which vector is active.
Hi !
Some study of the following is needed:
- assembly language functions like
vPreemptiveTick
,portSAVE_CONTEXT
&portRESTORE_CONTEXT
- their manipulation of processor modes
I’ll get back to you on next Monday / Tuesday.
Hi !
It might be easier to read this post with 2 web browser windows containing the same link !
The code below shows IRQ_Handler
, vPreemptiveTick
, portRESTORE_CONTEXT
& portSAVE_CONTEXT
with statement numbers in the left margin.
IRQ_Handler
comes from the blinkyy timer code. The other 3 functions come from FreeRTOS ! The code below is my attempt to merge them !
; /**********************************************************************/
IRQ_Handler
;- Manage Exception Entry
;- Adjust and save LR_irq in IRQ stack
01 SUB lr, lr, #4
02 STMFD sp!, {lr}
;- Save SPSR need to be saved for nested interrupt
03 MRS r14, SPSR
04 STMFD sp!, {r14}
;- Save and r0 in IRQ stack
05 STMFD sp!, {r0}
;- Load the ISR-Address from VICVectAddr
06 LDR r14, =LPC_BASE_VIC
07 LDR r0 , [r14, #VIC_VectAddr]
;- Save scratch/used registers and LR in User Stack
08 STMFD sp!, { r1-r3, r12, r14 }
;- Branch to the routine pointed by the VIC_VectAddr
09 MOV r14, pc
10 BX r0
;- Restore scratch/used registers and LR from User Stack
11 LDMIA sp!, { r1-r3, r12, r14 }
;- Restore R0
12 LDMIA sp!, {r0}
;- Restore SPSR_irq and r0 from IRQ stack
13 LDMIA sp!, {r14}
14 MSR SPSR_cxsf, r14
;- Restore adjusted LR_irq from IRQ stack directly in the PC
15 LDMIA sp!, {pc}^
16 END
; /**********************************************************************/
vPreemptiveTick
PRESERVE8
17 portSAVE_CONTEXT ; Save the context of the current task.
18 LDR R0, =xTaskIncrementTick ; Increment the tick count.
19 MOV LR, PC ; This may make a delayed task ready
20 BX R0 ; to run.
21 CMP R0, #0
22 BEQ SkipContextSwitch
23 LDR R0, =vTaskSwitchContext ; Find the highest priority task that
24 MOV LR, PC ; is ready to run.
25 BX R0
SkipContextSwitch
26 MOV R0, #T0MATCHBIT ; Clear the timer event
27 LDR R1, =T0IR
28 STR R0, [R1]
29 LDR R0, =VICVECTADDR ; Acknowledge the interrupt
30 STR R0,[R0]
31 portRESTORE_CONTEXT ; Restore the context of the highest
; priority task that is ready to run.
32 END
; /**********************************************************************/
MACRO
portRESTORE_CONTEXT
;
33 LDR R0, =pxCurrentTCB ; Set the LR to the task stack. The location was...
34 LDR R0, [R0] ; ... stored in pxCurrentTCB
35 LDR LR, [R0] ;
36 LDR R0, =ulCriticalNesting ; The critical nesting depth is the first item on...
37 LDMFD LR!, {R1} ; ...the stack. Load it into the ulCriticalNesting var.
38 STR R1, [R0] ;
39 LDMFD LR!, {R0} ; Get the SPSR from the stack.
40 MSR SPSR_cxsf, R0 ;
41 LDMFD LR, {R0-R14}^ ; Restore all system mode registers for the task.
42 NOP ;
;
43 LDR LR, [LR, #+60] ; Restore the return address
; And return - correcting the offset in the LR to obtain ...
44 SUBS PC, LR, #4 ; ...the correct address.
MEND
; /**********************************************************************/
MACRO
portSAVE_CONTEXT
;
45 STMDB SP!, {R0} ; Store R0 first as we need to use it.
46 STMDB SP,{SP}^ ; Set R0 to point to the task stack pointer.
47 NOP ;
48 SUB SP, SP, #4 ;
49 LDMIA SP!,{R0} ;
50 STMDB R0!, {LR} ; Push the return address onto the stack.
51 MOV LR, R0 ; Now we have saved LR we can use it instead of R0.
52 LDMIA SP!, {R0} ; Pop R0 so we can save it onto the system mode stack.
53 STMDB LR,{R0-LR}^ ; Push all the system mode registers onto the task stack.
54 NOP ;
55 SUB LR, LR, #60 ;
56 MRS R0, SPSR ; Push the SPSR onto the task stack.
57 STMDB LR!, {R0} ;
58 LDR R0, =ulCriticalNesting ;
59 LDR R0, [R0] ;
60 STMDB LR!, {R0} ;
61 LDR R0, =pxCurrentTCB ; Store the new top of stack for the task.
62 LDR R1, [R0] ;
63 STR LR, [R1] ;
MEND
; /**********************************************************************/
First timer interrupt = First time at IRQ_Handler
in the debugger (simulator on Windows 8 PC):
-
At statement 01, R14 (LR = Link Register) has the address of the
x++;
statement inincrement_x_task
! -
At statement 08, R14 (LR) has 0xFFFF_F000.
-
At statement 17, R14 (LR) has the address of statement 11.
- At statement 50, R14 (LR) has the address of statement 11.
-
At statement 44, R14 (LR) has the address of the
y++;
statement ofincrement_y_task
! As expected,increment_y_task
executes next !
Second timer interrupt = Second time at IRQ_Handler
in the debugger (simulator on Windows 8 PC):
-
At statement 01, R14 (LR = Link Register) has the address of the
x++;
statement inincrement_x_task
! -
At statement 08, R14 (LR) has 0xFFFF_F000.
-
At statement 17, R14 (LR) has the address of statement 11.
- At statement 50, R14 (LR) has the address of statement 11.
-
At statement 44, R14 (LR) has the address of statement 11 ! I’m not sure why R14 (LR) has this value !
As you mentioned earlier, it’s branching to statement 11 in IRQ_Handler
! But is this an ISR for another vectored interrupt ? (Wouldn’t such an ISR start at statement 01 of IRQ_Handler
?)
Hi ! Here is some more information (using the code of the previous post) !
First time at IRQ_Handler
:
0xFFFF_F110: 0x0000_5990 0xFFFF_FF00: 0x0000_5990
(VICVectAddr4 = TIMER0_INT) (VICAddress)
Second time at IRQ_Handler
:
0xFFFF_F110: 0x0000_5990 0xFFFF_FF00: 0x0000_5990
(VICVectAddr4 = TIMER0_INT) (VICAddress)
After (single step beyond) statement 44:
R14(LR) - 4 = Address of statement before statement 11 ( = statement 10):
At statement 10:
0xFFFF_F110: 0x0000_5990 0xFFFF_FF00: 0x0000_5990
(VICVectAddr4 = TIMER0_INT) (VICAddress)
The following pictures are from the LPC2368 User Manual:
-
VIC Register Map: (Image On The Left)
-
Interrupt Sources Bit Allocation Table: (Image In The Center)
The following picture is from the Keil IDE:
- Defines in irq.h: (Image On The Right)
New users are allowed only 1 image upload, so I had to combine them !
Seems like the only interrupt in play, is that of Timer0 !
Not gone into your last two posts in detail yet - but your IRQ handler does not look right. Been a while since I used an ARM7 but I’m pretty sure on that device you should be branching directly to the ISR for whichever interrupt is active, then writing the ISR exactly as per the tick handler - that is entering via portSAVE_CONTEXT and exiting via portRESTORE_CONTEXT. The RTOS kernel has to manage the stacking of registers before they get altered, and your IRQ entry code is altering the registers. Take a look at the LPC2000 example in the download, and their respective documentation pages. For example, this is branching directly to the ISR: https://github.com/FreeRTOS/FreeRTOS/blob/main/FreeRTOS/Demo/ARM7_LPC2106_GCC/boot.s#L145 and this is an example ISR: https://github.com/FreeRTOS/FreeRTOS/blob/main/FreeRTOS/Demo/ARM7_LPC2106_GCC/serial/serialISR.c#L99
Great suggestion Richard ! When I used the code below, x & y seem to increment to high values without errors. At a timer tick, there is a switch between increment_x_task
& increment_y_task
!
IRQ_Handler
LDR r10, =LPC_BASE_VIC ; Sid - 2022.02.11 - Assuming r10 is rarely used
LDR pc , [r10, #VIC_VectAddr]
Now, I’ll enable the UART interrupt, restore its ISR & see how that goes ! If it works, I’ll start bringing in other tasks from the FreeRTOS main() function one by one !
Hi ! I enabled the UART interrupt & installed its ISR !
Initially, I perform many transmits to the UART. I expect that its ISR will get called on each character’s transmit, but this doesn’t happen. Yet the characters are printed !
I’d like to post to the forum about this in detail. Shall I make a new topic / continue on this topic ?
Thanks in advance !
Your IRQ handler is still modifying r10 before branching to its handler. You need to branch directly to the handler. The code I linked to shows how to do that, as does the code from the chip vendor. Note the offset between irq vector and the interrupt vector (-0xFF0 in the example I linked to) may be different for your chip compared to the lpc2106 used in the linked code.