Table of Contents - University of Scranton
1. The BrickOS Kernel[1]
1 Hitachi H8/3292 Computer
Inside of the RCX is a Hitachi H8/3292 single-chip microcomputer with on-chip modules designed for real-time application. The on-chip modules include ROM, RAM, three types of timers (1 16-bit free-running timer, 8-bit timers, and a watchdog timer), a serial communication interface, an analog-to-digital converter, and I/O ports. The CPU is capable of operating at 16MHz of system clock speed.
The H8/300 CPU contains sixteen 8-bit general registers, which may be configured as eight 16-bit registers. The general registers can be used as both data registers and address registers. When used as address registers, they are 16-bit registers (R0 to R7). When used as data registers, they may be as 16-bit data registers (R0 to R7) or 8-bit data registers (R0H, R0L, R1H, R1L, to R7L) by separating the high byte and low byte of R0 to R7. R7 (alias, SP) is also the stack pointer of the system, used by the hardware for processing interrupts and subroutine calls. There are also two control registers: a 16-bit program counter (PC) and an 8-bit condition code register (CCR). Each instruction is accessed in 16-bits or a word, so the PC’s least significant bit is ignored. The CCR contains flags the result of computations and an interrupt mask bit to enable or disable interrupts.
The CPU supports eight addressing modes: register direct, register indirect, register indirect with displacement, register indirect with post-increment or pre-decrement, absolute address, immediate, program-counter relative, and memory indirect. The instruction set consists of 57 basic instructions and is classified in seven categories: arithmetic operation, logic operations, shift operation, bit manipulation, branch operations, system control operations, and block data transfer operations. .
The H8/300 CPUs can operate in three different modes and in each mode the memory may be mapped differently. The RCX uses H8/3292 in mode 2, which maps the memory as shown in Figure 1. The 16K ROM (0x0000 to 0x3FFF) contains the firmware by the manufacture containing drivers for driving the RCX and its subsystem. Interrupt handlers in the ROM can call addresses in RAM to allow customized interrupt service routines.
The on-chip modules include five 8-bit input/output ports, one 8-bit input port, and one 3-bit input/output ports, an on-chip 16-bit free-running timer, which is configured to generate an interrupt every millisecond as the main system timer for the BrickOS kernel, a two- channel 8-bit timer which is used in BrickOS for monitoring buttons, LCD display, speaker, and infrared communication, and a 1-bit A/D converter with 8 channels, four of which are used in BrickOS to connect the three sensor inputs and battery.
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
Figure 1 H8/3292 memory map.
1.2 Task Management
The BrickOS operating system only supports threads, not processes. [Differences between threads and processes?] We will use task and thread interchangeably in this section. Tasks are divided into kernel tasks, which are located in the kernel address space, and user tasks in user address space. When the system starts, the kernel creates a set of kernel tasks. The main function of a user program is executed as a user task and in turn this task may create other tasks using the execi function. The kernel allocates memory and CPU time to tasks based their priority.
A task () goes through a sequence of states during its life as shown in Figure 2. When a task is created by the execi function, it enters sleeping state, which is commonly called ready state. When the scheduler selects the task for execution it enters running state, using the CPU. If the task’s execution is preermpted, it goes back to sleeping state waiting to be scheduled again. The task may make a wait_event, sleep, or msleep call; it goes to waiting state, when the condition the task is waiting for is checked to be true (the timer has expired for sleep() and msleep()) to running state. This seems to be all different from what we have learnt in almost all operating systems books. We will find out why shortly in the scheduling section. The task may be killed by another task or it finishes its execution, then the exit() function is called, which puts the task to zombie state, waiting for the scheduler to remove it from the task control structure and reclaim the memory occupied by the task and proceeding to dead state.
| |
|[pic] |
Figure 2 Task state transition diagram.
Tasks are organized in queues based on their priorities. Tasks with the same priority are put in a circular double-linked queue and a header (of type pchain_t) points to the first task in the queue. Queues of different priorities are double-linked together through their headers in the descending order of priority. The header of a queue only exists if the queue has at least one task. A global variable, priority_head, points to the first queue, highest non-empty queue. The structure of the queue headers is defined as the following table. The first field, priority, is the priority of the queue, the second, next, points to the next non-empty lower-priority queue, and the third, prev, to the next nonempty higher priority queue, and the last, ctid, to the first task of the queue. The prev pointer of the first header, highest priority queue, and the next pointer of the last header, lowest priority queue, are set to null.
|Field type & name |Description |
|priority_t priority; |priority level |
|Pchain_t *next; |lower priority chain |
|Pchain_t *prev; |higher priority chain |
|tdata_t *ctid; |first task in chain |
Figure 3 The structure of the priority queue header.
Task control blocks (TCBs) (of type tdata_t ) contains ten fields as shown in the following table. Reading the table, you may have noticed that there is no field for task identification. That is because the beginning address of task’s TCB is typecast to tid_t (the data type of task id’s) as the task ID. sp_save stores the stack pointer of the task, tstate represents the current state of the task (RUNNING, SLEEPING, ZOMBIE, WAITING, and DEAD), tflags indicates whether the task is kernel task, user task, idle task, or shutdown request for the task has been received, priority points to the header of the queue the task belongs to, next and prev point to the next and previous task of the same priority, respectively, parent points to the parent task, stack_base stores the base address of the task stack, wakeup points to the wakeup function when the task is in waiting state, and wakeup_data is the user-defined data for the wakeup function.
|Field type & name |Description |
|size_t *sp_save; |saved stack pointer |
|Tstate_t tstate; |task state |
|Tflags_t tflags; |task flags |
|Pchain_t *priority; |priority chain |
|tdata_t *next; |next task in queue |
|tdata_t *prev; |previous task in queue |
|tdata_t *parent; |parent task |
|size_t *stack_base; |lower stack boundary |
|Wakeup_t(*wakeup) (wakeup_t); |event wakeup function |
|Wakeup_t wakeup_data; |user data for wakeup fn |
Figure 4 The task control structure.
Figure 5 shows part of the task structure. We assume there are only two non-empty priority queues. To simplify drawing, we only include pointers that show the relationships in the figure; fields like tstate are not shown in the figure.
Tasks are divided into kernel tasks, user tasks, a idle task and they are marked as KERNEL, USER, and IDLE in the tflags field of TCBs, respectively. The IDLE task has priority 0 one lower than the lowest regular task priority. It is only executed when no other task is runnable, and it does nothing but executes the sleep machine level instruction to conserve power. The idle task is started at system initialization. The RAM starting from 0x8000 is shared by the BrickOS kernel and application tasks. The kernel occupies the lower part of the RAM and application tasks are allocated after the kernel. Any tasks that reside in the kernel space are KERNEL tasks, and those after the kernel space are USER tasks. This decision on KERNEL or USER task is made in the execi function. The killall() function does not kill the IDLE task. Both killall() and kill() functions set the resumption point of affected tasks to the exit() function so when each of them resumes its execution it would execute exit() and terminate. exit() marks the calling task ZOMBIE. The scheduler will remove ZOMBIE tasks from the task control structure and reclaims the memory occupied by those tasks.
The shutdown_task() may be used to mark a task SHUTDOWN in the tflags field and shutdown_tasks() may be used to mark all KERNEL and/or USER tasks. The effect of this flag is not known.
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
Figure 5 Task control structure.
When the kernel starts, it treats itself as a task which is to the root of all other tasks[2]. Let us refer this task as init, following the tradition of the UNIX operating systems. This task does not have a TCB like all other tasks and it does not appear in the task control structure. It is the parent of the first six tasks the kernel creates during its initialization. The six tasks are packet_consumer, which is used to monitor IR communication and is assigned the highest priority 20, key_handler, which monitors the four buttons and is at priority 20, and lr_thread, which is responsible for TBD at priority 20, tm_man_task, which displays the man figure on the LCD and is assigned priority 1, tm_battery_task, which is to update the battery power on the LCD at priority 1, and the last task, tm_idle_task, which is a dummy task and only runs when no other task is runnable. All the six tasksare kernel tasks. Tm_idle_task runs at priority 0, one lower than the lowest priority for regular task. It does nothing but puts the system to a power-conserving mode.
All user programs are tasks created by the packet_consumer task.
1.3 Thread Scheduling
BrickOS employs a preemptive priority based round-robin scheduling algorithm. The time quantum is 20 milliseconds by default. A task is allocated one time quantum of CPU time. When the task releases the CPU or the time quantum runs out, whichever comes first, the task is sent to sleeping state (commonly called ready state). The scheduler searches the task control structure to find a task to run next and the searching starts from highest priority queue. If the task is in sleeping state, choose it to run next. If the task is in waiting state, the scheduler calls its wakeup function. If the function returns a non-zero value, it is chosen for execution. This is why in the task state transition diagram the state transition from waiting state is to running state instead of sleeping state. If none of the above is true, the search moves to next task in the same priority queue. If none in the queue is runnable, the search continues to next non-empty lower priority queue. If no task is found runnable, the IDLE task is chosen. The IDLE task always exists and is always ready to run.
Let’s see how this works using Figure 2 as a reference. Let’s say the current running task is the second task of priority 12. When the system timer interrupts, signaling the end of current time quantum, the scheduler will search from highest non-empty priority queues. For Figure 2, the search would start with the first task of priority 15. If it is in sleeping state, then it would be chosen for execution next. If it is in waiting state, the scheduler would call its wakeup function. If the function returns true the task would be chosen for execution. Otherwise, the search moves to the second task of priority 15. If none of the tasks of priority 15 is runnable, then the search continues to next lower non-empty priority queue, which is priority 12 in this example. If none of the tasks at priority 15 or 12 are runnable, then the task of priority 0, the IDLE task will be executed.
Time quantum can be set to a new value using the following function:
void systime_set_timeslice(unsigned char slice)
New time quantum, slice, must be larger than 5. If slice is less than 5, the system would keep its current time quantum.
1.4 Memory Management
The H8/3292 computer does not have hardware support for more advanced memory management techniques like paging or segmentation. BrickOS divides the RAM to two separate spaces: the kernel space for the kernel and the user space for user tasks. The kernel occupies the lower RAM from 0x8000 to mm_start, which is global variable and is set when the kernel is compiled. mm_start, is located in the first word of the user space, in other words, the address of variable mm_start is the beginning address of the user space. The user space is organized and maintained as partitions. Each partition has a header that contains the task id of the task, for which the partition is allocated to, and the size of the partition in words (a word is two bytes) excluding the header. The value 0x0000 for the task id field represents free partition and the value 0xFFFF reserved partitions for memory-mapped I/O. Global variable mm_first_free points to the first free block.
BrickOS employs the first-fit scheme for memory allocation. When a task uses the malloc() to request for memory, it always searches from the partition pointed by mm_first_free for a partition that is big enough. If the leftover is larger than a predefined threshold (8 bytes by default), a new partition is created; otherwise the whole partition is allocated to the requesting task. When the When the free() function is called, it releases the partition by marking it free. For performance reasons, the system doe not perform memory compaction, but only join consecutive free partitions. In malloc() when a free partition is found, the function joins all the adjacent free partitions. [Note: it would be better algorithm if join is tried only when the partition is not big enough or the leftover is smaller than the threshold]. If the partition is not large enough for the current request, it moves on to find next free partition and join all adjacent partitions. This is repeated until a partition that is large enough is found. If no partition is found large enough, the function returns null. Note: it is the programmer’s responsibility to check whether the requested memory is allocated or not. Using a null pointer may cause unspecified behavior. The free() function does not try to join any free partitions.
| |
| |
| |
| |
| |
| |
Figure 6 Memory partition structure.
Figure 7 scans the contents of the current task control structure and the memory partition allocation. It uses two functions. One, tcb_scan(), scans the task control structure and prints the current task and the priority queues with task’s id, parent id, state, and flags, and the other function, mm_scan(), scans all partitions in RAM in user space. It prints the beginning address of the partition, task id, and size of the partition in words. The value 0 of the task id of a partition represents a free partition, and 0xFFFF (0d65535) represents a reserved partition, which is mainly used for memory-mapped I/O.
When the program shown in Figure 7 is compiled and executed on the RCX, the task structure contains tasks as shown in Figure 8. There are four queues – the first queue with three tasks at priority 20, the second queue with one task at priority ten, the third queue with two tasks at priority one, and the last queue with one task at priority 0. The root of all tasks is 44304, which created six tasks: packet_consumer task (task id 45506), key_handler (46046), ir_thread (44954), tm_man_task (44630), tm_battery_task (44798), and tm_idle_task (44462). The task for the main function of the user program is created by the packet_consumer (45506) and its ID is 47138, which is also the current running task indicated by global variable ctid = 47138 and its task state being running. All the tasks are kernel tasks except the task (47138) at priority 10, which is the user program in this example.
|#include |
|#include |
|#include |
|#include |
|#include |
|#include |
|#include |
|#include |
|#include |
| |
|extern size_t mm_start; |
|extern pchain_t *priority_head; |
|extern tdata_t *ctid; |
| |
|void display(unsigned int x, char* p); |
|void displaychar(unsigned char x, char* p); |
|void tcb_scan(); |
|void mm_scan(); |
| |
|int main(int argc, char** argv) { |
|tcb_scan(); |
|mm_scan(); |
|} |
| |
|void display(unsigned int x, char* p) { |
|unsigned int high, low; |
| |
|high = x / 1000; |
|low = x % 1000; |
|cputs(p); |
|sleep(3); |
|lcd_int(high); |
|sleep(3); |
|lcd_int(low); |
|sleep(3); |
|} |
|void displaychar(unsigned char x, char* p) { |
|cputs(p); |
|sleep(3); |
|lcd_int(x); |
|sleep(3); |
|} |
|void mm_scan() |
|{ |
|size_t *ptr=(size_t *)&mm_start; |
| |
|while( ptr>=(size_t *)&mm_start) |
|{ |
|display((unsigned int)(ptr), "addr"); |
|display((unsigned int)(*ptr), "tid"); |
|display((unsigned int)(*(ptr+1)), "size"); |
| |
|ptr+=*(ptr+1)+MM_HEADER_SIZE; |
|} |
|cputs("end"); |
|sleep(3); |
|} |
| |
|void tcb_scan() |
|{ |
|pchain_t *hptr = priority_head; |
|tdata_t *cptr; |
| |
|display((unsigned int)mm_start, "start"); |
|display((unsigned int)ctid, "crrnt"); |
| |
|while (hptr != 0) { |
| |
|display((unsigned int)hptr, "head"); |
|displaychar(hptr->priority, "prio"); |
| |
|cptr = hptr->ctid; |
|display((unsigned int)(cptr), "task"); |
|display((unsigned int)(cptr->parent), "parnt"); |
|displaychar((unsigned char)(cptr->tstate), "state"); |
|displaychar((unsigned char)(cptr->tflags), "flags"); |
|cptr = cptr->next; |
| |
|while (cptr != hptr->ctid) { |
|display((unsigned int)(cptr), "task"); |
|display((unsigned int)(cptr->parent), "parnt"); |
|displaychar((unsigned char)(cptr->tstate), "state"); |
|displaychar((unsigned char)(cptr->tflags), "flags"); |
|cptr = cptr->next; |
|} |
|cputs("end1"); |
|hptr = hptr->next; |
|} |
|cputs("end2"); |
|} |
Figure 7 Task and memory scan program.
|Beginning address of user space mm_start = 0d44304 -- &mm_start == 44458 |
|Current task ctid = 47138 |
|Queue header = 45494 Priority = 20 |
|Task ID |Parent ID |State |Flags |Comments |
|45506 |44304 |2 (waiting) |1 (kernel task) |packet_consumer task |
|46046 |44304 |2 (waiting) |1 (kernel task) |key_handler task |
|44954 |44304 |2 (waiting) |1 (kernel task) |ir_thread task???? |
|Queue header = 46604 Priority = 10 |
|47138 |45506 |4 (running) |2 (user task) |the user program |
|Queue header = 44786 Priority = 1 |
|44630 |44304 |2 (waiting) |1 (kernel task) |tm_man_task |
|44798 |44304 |2 (waiting) |1 (kernel task) |tm_battery_task |
|Queue header = 44618 Priority = 0 |
|44462 |44304 |3 (sleeping) |5 (kernel& idle) |tm_idel_task |
Figure 8 Task control structure.
The above program also displays the following memory map. The global variable mm_start is stored in the first free word of the user space, i.e., 44458 according to Figure 9, and thus, the address of mm_start, i.e., &mm_start is equal to 44458. Since the first free partition is allocated to the kernel task init, whose task id is 44304, the value of mm_start is equal to 44304. ,
|No. |Starting address |Task ID |Size | Comments |
|1 |44458 |44304 |10 |Beginning of user space |
|2 |44482 |44304 |64 | |
|3 |44614 |44304 |4 | |
|4 |44626 |44304 |10 | |
|5 |44650 |44304 |64 | |
|6 |44782 |44304 |4 | |
|7 |44794 |44304 |10 | |
|8 |44818 |44304 |64 | |
|9 |44950 |44304 |10 | |
|10 |44974 |44304 |256 | |
|11 |45490 |44304 |4 | |
|12 |45502 |44304 |10 | |
|13 |45526 |44304 |256 | |
|14 |46042 |44304 |10 | |
|15 |46066 |44304 |265 | |
|16 |46600 |45506 |7 | |
|17 |46618 |44954 |256 | |
|18 |47134 |45506 |10 | |
|19 |47158 |45506 |512 | |
|20 |48186 |00000 |6519 |Free partition |
|21 |61228 |65535 |16 |LCD data (memory-mapped I/O) |
|22 |61264 |00000 |84 |Free partition |
|23 |61436 |65535 |1792 |Motor registers |
|24 |65025 |00000 |124 |Free partition |
|25 |65276 |65535 |128 |Stack – onchip register field |
Figure 9 Memory map.
Tasks may use the malloc() and free() functions to request dynamic memory and release allocated memory, respectively. malloc() accepts one parameter, the number of bytes of memory requested and it returns a pointer to the allocated space. If there is no memory to satisfy the request, the value of null is returned. free() take as the only parameter a pointer to the memory to be freed. The algorithm of the malloc() function is shown in Figure 10. To calculate the address of next partition, let current point to the current partition, size the size of the current partition, and next point to next partition then,
next = current + 4 + size * 2 (1)
For example, the first partition starts at 44458, and its size is 10, use the above formula,
next = 44458 + 4 + 10 * 2 = 44482.
Reading the table, the second partition does start at 44482.
The above formula seems to be inconsistent with the computation used in the program of Figure 7, since it calculates the address of next partition using
ptr + =*(ptr+1)+MM_HEADER_SIZE; (2)
where ptr points to the current partition and MM_HEADER_SIZE is the size of the partition header in words. This is due to who so called pointer arithmetic in the C language. When a pointer is incremented by one, it will point to the next element from the current position. So if the type of the pointer is integer, the pointer will point to next integer, and if char, it will point to next char. A pointer incremented by N points to the Nth element after the current. The ptr pointer is of type size_t, which is an alias of data type int. An integer of data type int takes two bytes in BrickOS. Thus, the above formula is equal to
(byte*)ptr = (*(ptr+1) + MM_HEADER_SIZE) * 2
which is the same as formula (1).
The first free partition is at 48186 with size 6519 words, or 13038 bytes. A partition must start with an even address.
|Algorithm for malloc() |
|In parameter: size; // # of bytes requested |
|mm_first_free: pointer to the first free partition |
|&mm_start: mm_start is stored in the first word of user space |
| |
|ptr = mm_first_free; // pointer to the first free partition |
| |
|while (ptr >= &mm_start) { // still in user space |
|if (current partition is free) { |
|join the current partition with its adjacent free partitions; |
|if (new partition is large enough) { |
|set the tid field of the header to current task |
|if (leftover is large enough as a partition) { |
|create a new free partition |
|} |
|if (current partition is first free partition) { |
|update mm_first_free |
|} |
|return the address of the data area of the current partition |
|} |
|} |
|ptr = next partition; |
|} |
|return null; // when no free partition is large enough. |
Figure 10 The algorithm of malloc().
The algorithm for the free() function is shown in Figure 11. It is very straightforward. It marks the partition free and then updates the mm_first_free global variable if needed. One thing to note here is that the pointer passed in to the function points to the beginning of the data area of the partition, not the beginning of the partition. So to mark the tid field of the header, ptr needs to be decremented by the header size, i.e., *(ptr-MM_HEADER_SIZE) = MM_FREE.
|Algorithm for free() |
|In parameter: ptr; // data area to be freed |
|mm_first_free: pointer to the first free partition |
|&mm_start: mm_start is stored in the first word of user space |
| |
|if (ptr is null or not an even address) |
|return; // pointer to the first free partition |
| |
|mark the task id field free; |
| |
|if (ptr < mm_first_free OR // this is before mm_first_free |
|mm_first_free < &mm_start) { // there was no free partition |
|update mm_first_free; |
|} |
Figure 11 The algorithm of free().
1.5 System Timing
1.5.1 The 16-Bit Free-Running Timer
The H8/3292 microcontroller has an on-chip 16-bit free-running timer (FRT), which is the main timing source of the BrickOS system. It is programmed to generate an interrupt every millisecond, which causes the kernel to poll I/O devices, maintain system time, and used for thread scheduling. In this section, we will take a look at how to program this timer.
|Name |Description |Initial |Address |
|FRC(16bits) |Free-running counter |0000 |FF92 |
|OCRA(16bits) |Output compare A |FFFF |FF941 |
|OCRB(16bits) |Output compare B |FFFF |FF941 |
|TIER(8bits) |Timer interrupt enable |01 |FF90 |
|TCSR(8bits) |Time control/status |00 |FF91 |
|TCR(8bits) |Timer control |00 |FF96 |
|TOCR(8bits) |Timer output compare control |E0 |FF97 |
|ICRA-D(16bits) |Input capture A - D |0000 |FF98-FF9F |
1OCRA and OCRB share the same address. The OCRS bit of TOCR makes selection.
Figure 12 Registers of the 16-bit free-running timer.
Figure 12 shows the registers of the 16-bit free running timer. The 16-bit free-running counter (FRC) is the center piece of the timer. It can be driven by any of the three internal clock sources (system clock/ 2, /8, and /32) or an external clock source. The least two significant bits (CKS1 and CKS0) in the TCR can be programmed to select the driving clock for FRC as follows:
|CKS1 |CKS0 |Selection |
|0 |0 |System clock/2 |
|0 |1 |System clock/8 |
|1 |0 |System clock/32 |
|1 |1 |External clock |
The H8/3292 microcontroller uses a system clock of 16MHz, and the BrickOS selects System clock/32 as the driving clock for FRC. Thus, the speed of the driving clock for FRC is 16MHz/32 = 500KHz and a clock cycle is equal to 1,000,000/500,000 = 2 microseconds). In other words, FRC is incremented by one every 2 microseconds. The clock selection for FRC can be accomplished as follows:
T_CR = TCR_CLOCK_32;
T_CR is defined in /HOME/boot/brickos.lds and located in 0xFF96; TCR_CLOCK_32 is defined in /HOME/include/sys/h8.h with the value of 0x02.
The output compare registers A (OCRA) and B (OCRB) are 16-bit R/W registers. They are constantly compared with the contents of FRC. When a match is detected, an OCIA and/or OCIB interrupt signal is asserted, provided that the OCIAE and OCIBE bits are set in the TIER register to enable the OCIA and OCIB interrupts, respectively.
BrickOS uses this timer to generate an interrupt every millisecond from OCRB, and an interrupt every 2 milliseconds from OCRA. The speed of the input clock for FRC is 500KHz, or 2 us as the cycle size, for an interrupt every millisecond for the OCIB signal, OCRB is set to 500, and for an interrupt every two millisecond for OCIA, OCRA is set to 1000. When a match is detected, FRC is reset for a fresh millisecond. This is done by setting the Counter Clear A bit (CCLRA)(bit 0) of the TCSR register. Since OCRA and OCRB share the same address, we need to set the OCRS bit properly to access the two registers.
T_CSR = TCSR_RESET_ON_A;
T_OCR &= ~TOCR_OCRB; // select OCRA TOCR_OCRB=0x10
T_OCRA = 1000; // generate interrupt every 2ms
T_OCR &= ~TOCR_OCRA; // select OCRB?? TOCR_OCRA = 0x00
T_OCR |= TOCR_OCRB; // select OCRB
T_OCRB = 500;
After the two registers are set, we can enable the OCIA and OCIB interrupts by setting the OCIAE and OCIBE bits in the TIER register. Before we enable the two interrupts we need to specify interrupt service routines for them. BrickOS uses task_switch_handler() and subsystem_handler(), which are both defined in /HOME/kernel/systime.c , as the ISRs for OCIA and OCIB interrupts.
ocia_vector = &task_switch_handler;
ocib_vector = &subsystem_handler;
Then enable the two interrupts:
T_IER |= (TIER_ENABLE_OCB | TIER_ENABLE_OCA);
Figure 13 shows a complete listing of code that controls the 16-bit free-running timer.
| T_CSR = TCSR_RESET_ON_A; // reset FRC when FRC = OCRA |
|T_CR = TCR_CLOCK_32; // use the op-32 clock |
| |
|// set the two output compare registers |
|T_OCR &= ~TOCR_OCRB; // select OCRA TOCR_OCRB=0x10 |
|T_OCRA = 1000; // generate interrupt every 2ms |
|T_OCR &= ~TOCR_OCRA; // select OCRB?? TOCR_OCRA = 0x00 |
|T_OCR |= TOCR_OCRB; // select OCRB |
|T_OCRB = 500; //generate interrupt every 1ms |
| |
|// set ISR’s |
|ocia_vector = &task_switch_handler; // called every 2ms |
|ocib_vector = &subsystem_handler; // called every 1ms |
| |
|// enable both OCRA&A interrupt |
|T_IER |= (TIER_ENABLE_OCB | TIER_ENABLE_OCA); |
| |
|nmi_vector = &clock_handler; |
|WDT_CSR = WDT_CNT_PASSWORD | WDT_CNT_MSEC_64; // trigger every msec |
|WDT_CSR = WDT_CSR_PASSWORD |
|| WDT_CSR_CLOCK_64 |
|| WDT_CSR_WATCHDOG_NMI |
|| WDT_CSR_ENABLE |
|| WDT_CSR_MODE_WATCHDOG; |
|} |
Figure 13 Control of the 16-bit free-running timer.
You can use the following function to find out how long (in milliseconds) the system has been up running. The system uses a 32-bit counter to count the number of milliseconds since the system was started. The counter may overflow after 232 milliseconds, which is about 50 days. time_t is unsigned long int.
time_t get_system_up_time(void);
1.6 Input and Output
coming soon.
-----------------------
[1] This section is written based on BrickOS version 0.2.6.10 installed with default system configuration.
[2] It is like the init process in the UNIX operating system, which is created by the kernel and is the root of all other processes.
-----------------------
LCD I/O
0xFB80
0xFD7F
Kernel
User space
0x8000
0xFB7F
Reserved
0x4000
0x7FFF
prio=0
prev
next=0
ctid
priority
prev
next
ROM
Drivers
0x0000
0x3FFF
parent
wakeup
prio=15
prev=0
next
ctid
priority
prev
next
parent
wakeup
priority
prev
next
parent
wakeup
priority
prev
next
parent
wakeup
prio=12
prev
next
ctid
priority
prev
next
parent
wakeup
priority
prev
next
parent
wakeup
priority_heard
ctid
priority
prev
next
parent
wakeup
0xFF88
0xFFFF
Onchip register field
0xFF80
0xFF87
Reserved
0xFD80
0xFF7F
User space
Data
Size
Task ID
................
................
In order to avoid copyright disputes, this page is only a partial summary.
To fulfill the demand for quickly locating and searching documents.
It is intelligent file search solution for home and business.
Related searches
- university of scranton financial office
- university of scranton employment
- university of scranton fafsa code
- university of scranton finance
- university of scranton graduate tuition
- university of scranton map
- university of scranton school code
- university of scranton graduate admissions
- university of scranton job postings
- university of scranton graduate school
- university of scranton admission portal
- university of scranton application status