Я пытаюсь создать полуупреждающую (кооперативную) RTOS для микроконтроллеров PIC x16. В моем предыдущем вопросе я узнал, что доступ к указателю аппаратного стека невозможен в этих ядрах. Я просмотрел эту страницу в PIClist, и это то, что я пытаюсь реализовать с помощью C.
Мой компилятор - Microchip XC8, и в настоящее время я работаю над PIC16F616 с внутренним RC-генератором 4 МГц, выбранным в битах конфигурации.
Я узнал, что я могу получить доступ к регистрам PCLATH и PCL с помощью C, просматривая заголовочный файл моего компилятора. Итак, я попытался реализовать простой переключатель задач.
Он работает так, как нужно в отладчике, если я приостанавливаю отладчик после перезапуска, сброса и установки ПК на курсор, когда курсор находится не в первой строке ( TRISA=0;
), а в другой строке (например ANSEL=0;
). При первом запуске отладчика я получаю следующие сообщения Debugger Console
:
Launching
Programming target
User program running
No source code lines were found at current PC 0x204
Редактировать: я не знаю, что заставило его работать, но теперь отладчик работает отлично. Итак, опустите приведенный выше вывод и абзац.
Изменить: такое изменение основного определения приводит к тому, что приведенный ниже код работает. Это запускает основную функцию по адресу программы 0x0099
. Я не знаю, чем это вызвано. Это не настоящее решение. Теперь я предполагаю, что есть конкретная ошибка компилятора.
void main(void) @ 0x0099
{
Вот мой код C:
/*
* File: main.c
* Author: abdullah
*
* Created on 10 Haziran 2012 Pazar, 14:43
*/
#include <xc.h> // Include the header file needed by the compiler
__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & CP_OFF & IOSCFS_4MHZ & BOREN_ON);
/*
* INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN
* WDT disabled and can be enabled by SWDTEN bit of the WDTCON register
* PWRT enabled
* MCLR pin function is digital input, MCLR internally tied to VDD
* Program memory code protection is disabled
* Internal Oscillator Frequency Select bit : 4MHz
* Brown-out Reset Selection bits : BOR enabled
*/
/*
* OS_initializeTask(); definition will copy the PCLATH register to the task's PCLATH holder, which is held in taskx.pch
* This will help us hold the PCLATH at the point we yield.
* After that, it will copy the (PCL register + 8) to current task's PCL holder which is held in taskx.pcl.
* 8 is added to PCL because this line plus the "return" takes 8 instructions.
* We will set the PCL after these instructions, because
* we want to be in the point after OS_initializeTask when we come back to this task.
* After all, the function returns without doing anything more. This will initialize the task's PCLATH and PCL.
*/
#define OS_initializeTask(); currentTask->pch = PCLATH;\
currentTask->pcl = PCL + 8;\
asm("return");
/*
* OS_yield(); definition will do the same stuff that OS_initializeTask(); definition do, however
* it will return to "taskswitcher" label, which is the start of OS_runTasks(); definition.
*/
#define OS_yield(); currentTask->pch = PCLATH;\
currentTask->pcl = PCL + 8;\
asm("goto _taskswitcher");
/*
* OS_runTasks(); definition will set the "taskswitcher" label. After that it will change the
* current task to the next task, by pointing the next item in the linked list of "TCB"s.
* After that, it will change the PCLATH and PCL registers with the current task's. That will
* make the program continue the next task from the place it left last time.
*/
#define OS_runTasks(); asm("_taskswitcher");\
currentTask = currentTask -> next;\
PCLATH = currentTask->pch;\
PCL = currentTask->pcl;
typedef struct _TCB // Create task control block and type define it as "TCB"
{
unsigned char pch; // pch register will hold the PCLATH value of the task after the last yield.
unsigned char pcl; // pcl register will hold the PCL value of the task after the last yield.
struct _TCB* next; // This pointer points to the next task. We are creating a linked list.
} TCB;
TCB* currentTask; // This TCB pointer will point to the current task's TCB.
TCB task1; // Define the TCB for task1.
TCB task2; // Define the TCB for task2.
void fTask1(void); // Prototype the function for task1.
void fTask2(void); // Prototype the function for task2.
void main(void)
{
TRISA = 0; // Set all of the PORTA pins as outputs.
ANSEL = 0; // Set all of the analog input pins as digital i/o.
PORTA = 0; // Clear PORTA bits.
currentTask = &task1; // We will point the currentTask pointer to point the first task.
task1.next = &task2; // We will create a ringed linked list as follows:
task2.next = &task1; // task1 -> task2 -> task1 -> task2 ....
/*
* Before running the tasks, we should initialize the PCL and PCLATH registers for the tasks.
* In order to do this, we could have looked up the absolute address with a function pointer.
* However, it seems like this is not possible with this compiler (or all the x16 PICs?)
* What this compiler creates is a table of the addresses of the functions and a bunch of GOTOs.
* This will not let us get the absolute address of the function by doing something like:
* "currentTask->pcl=low(functionpointer);"
*/
fTask1(); // Run task1 so that we get the address of it and initialize pch and pcl registers.
currentTask = currentTask -> next; // Point the currentTask pointer to the next pointer which
fTask2(); // is task2. And run task2 so that we get the correct pch and pcl.
OS_runTasks(); // Task switcher. See the comments in the definitions above.
}
void fTask1(void)
{
OS_initializeTask(); // Initialize the task
while (1)
{
RA0 = ~RA0; // Toggle PORTA.0
OS_yield(); // Yield
RA0 = ~RA0; // Toggle PORTA.0
}
}
void fTask2(void)
{
OS_initializeTask(); // Initialize the task
while (1)
{
RA1 = ~RA1; // Toggle PORTA.1
OS_yield(); // Yield
RA1 = ~RA1; // Toggle PORTA.1
}
}
А вот файл листинга дизассемблирования, который создал мой компилятор. Начинается в line 74
.
Я запрограммировал настоящий чип, и в PORTA вообще никаких изменений; это не работает.
По какой причине моя программа не работает?
То, что вы пытаетесь сделать, сложно, но очень поучительно (если вы готовы потратить много усилий).
Во-первых, вы должны понимать, что такое переключение задач только для ПК (в отличие от ПК+SP) (это единственное, что вы можете сделать на простом 12- или 14-битном PIC-ядре) будет работать только тогда, когда все yield( ) в задаче находятся в одной и той же функции: они не могут быть в вызываемой функции, и компилятор не должен вмешиваться в структуру функции (как может сделать оптимизация).
Следующий:
currentTask->pch = PCLATH;\
currentTask->pcl = PCL + 8;\
asm("goto _taskswitcher");
Решение вашей проблемы PCLATH состоит в том, чтобы объявить метку после goto и записать младшие и старшие биты этой метки в ваши местоположения pch и pcl. Но я не уверен, что вы можете объявить «локальную» метку во встроенной сборке. Вы, конечно, можете в простом MPASM (Олин улыбнется).
Наконец, для такого переключения контекста вы должны сохранить и восстановить ВЕСЬ контекст, от которого может зависеть компилятор, который может включать
Архитектура PIC более проблематична в этом отношении, потому что многие ресурсы размещаются по всей карте памяти, тогда как в более традиционных архитектурах они находятся в регистрах или в стеке. Как следствие, компиляторы PIC часто не генерируют реентерабельный код, который вам определенно нужен, чтобы делать то, что вы хотите (опять же, Олин, вероятно, улыбнется и соберет вместе).
Если вы занимаетесь этим для удовольствия от написания переключателя задач, я предлагаю вам переключиться на процессор с более традиционной организацией, такой как ARM или Cortex. Если вы застряли ногами в бетонной плите PIC, изучите существующие переключатели PIC (например, залп/тыква?).
Я просмотрел предоставленный вами лист сборки, и ничего не выскакивает из-за того, что оно явно сломано.
Если бы я был вами, мои следующие шаги были бы такими:
(1) Я бы выбрал другой способ мигания светодиодов. Пресловутая проблема «чтение-изменение-запись» может (а может и не быть) вызвана «XORWF PORTA, F» в листинге сборки.
Возможно, что-то вроде:
// Partial translation of code from abdullah kahraman
// untested code
// feel free to use however you see fit
void fTask2(void)
{
OS_initializeTask(2); // Initialize task 2
while (1)
{
PORTC = 0xAA;
OS_yield(2); // Yield from task 2
PORTC = 0x55;
OS_yield(2); // Yield from task 2
}
}
(Если вы действительно хотите увидеть подробные объяснения того, почему «XORWF PORTA, F» часто вызывает проблемы, см. « Что приводит к тому, что включение одного выходного контакта на Microchip PIC16F690 самопроизвольно выключает другой контакт на том же порту? »; « Что происходит ? когда данные записываются в LATCH? »; « Проблема чтения-модификации-записи »; « Чтение перед записью »)
(2) Я бы пошагово выполнил код, убедившись, что переменные устанавливаются в ожидаемые значения и в ожидаемой последовательности. Я не уверен, существует ли одношаговый аппаратный отладчик для PIC16F616, но есть много отличных симуляторов микроконтроллеров PIC , таких как PICsim , которые могут имитировать чипы серии PIC16.
Одношаговый код (в симуляторе или с одношаговым аппаратным отладчиком) — это хороший способ понять детали того, что происходит на самом деле, убедиться, что все происходит так, как вы задумали, и позволяет увидеть то, что происходит на самом деле. практически невозможно увидеть при запуске программы на полной скорости.
(3) Если я все еще в тупике, я бы попытался перевести код для использования массивов, а не указателей. Некоторые люди находят использование указателей немного сложным и трудным для отладки. Я часто обнаруживаю, что в процессе перевода хитроумного кода с указателями в код, ориентированный на массивы, я выясняю, в чем заключается ошибка. Даже если я в конечном итоге вернусь к исходному коду указателя и выброслю версию массива, это упражнение полезно, поскольку оно помогло мне найти и исправить ошибку. (Иногда компилятор может генерировать более короткий и быстрый код из кода, ориентированного на массив, поэтому иногда я выбрасываю исходный код указателя и сохраняю версию массива).
Возможно, что-то вроде
// Partial translation of code from abdullah kahraman
// untested code
// feel free to use however you see fit
struct TCB_t // Create task control block and type define it as "TCB_t"
{
unsigned char pch; // PCLATH value
unsigned char pcl; // PCL value
int next; // This array index points to the next task. We are creating a linked list.
};
int currentTask = 1; // This TCB index will point to the current task's TCB.
struct TCB_t tasks[3]; // Define the TCB for task1 and task2.
#define OS_initializeTask(x); tasks[x].pch = PCLATH;\
tasks[x].pcl = PCL + 8;\
asm("return");
#define OS_runTasks(); asm("_taskswitcher");\
currentTask = tasks[currentTask].next;\
PCLATH = tasks[currentTask].pch;\
PCL = tasks[currentTask].pcl;
#define OS_yield(x); tasks[x].pch = PCLATH;\
tasks[x].pcl = PCL + 8;\
asm("goto _taskswitcher");
В основном я согласен с Дэвидкари. Похоже, это может сработать.
Я не знаю, что заставило его работать, но теперь отладчик работает отлично.
Я предполагаю, что вы имеете в виду, что он отлично работает в симуляторе .
1) Проверьте, что ваши задачи работают сами по себе, в не-RTOS среде в реальном чипе.
2) Выполните внутрисхемную отладку. Пройдитесь по программе на реальном чипе и просмотрите все соответствующие переменные, чтобы убедиться, что все идет по плану.
Я лишь бегло взглянул на ваш код, но это не имеет смысла. В некоторых местах вы пишете в PCL, а затем ожидаете, что он выполнит другие инструкции, следующие за этим.
Как я уже говорил, C не подходит для такого низкоуровневого доступа к фундаментальным аппаратным регистрам. Вам действительно нужно использовать сборку для этого. Попытка выяснить, почему код C не работает, — просто бессмысленная трата времени.
Ниже приведен способ сделать это с помощью встроенной сборки с использованием компилятора XC8, и теперь он работает ! Однако мне нужно добавить больше кода для сохранения и восстановления STATUS
реестра, что кажется немного сложнее, чем для обычного реестра.
Изменить: код изменен. Пожалуйста, обратитесь к более старым версиям этого сообщения для предыдущего кода.
/*
* File: main.c
* Author: abdullah
*
* Created on 10 Haziran 2012 Pazar, 14:43
*/
#include <xc.h> // Include the header file needed by the compiler
#include "RTOS.h" // Include the header for co-operative RTOS.
__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & CP_OFF & IOSCFS_4MHZ & BOREN_ON);
unsigned char OS_currentTask; // This register holds the current task's place in the array OS_tasks
unsigned char OS_tasks[4]; // This array holds PCL and PCLATH for tasks. This array will have..
// .. (number of tasks)*2 elements, since every task occupies 2 places.
void fTask1(void); // Prototype the function for task1.
void fTask2(void); // Prototype the function for task2.
void main(void)
{
TRISA = 0; // Set all of the PORTA pins as outputs.
TRISC = 0; // Set all of the PORTC pins as outputs.
ANSEL = 0; // Set all of the analog input pins as digital i/o.
PORTA = 0; // Clear PORTA bits.
PORTC = 0; // Clear PORTC bits.
OS_currentTask = 0; // Current task is first task.
fTask1(); // Call task to initialize it.
OS_currentTask += 2; // Increment task pointer by two since every task occupies 2 places in the array.
fTask2(); // Call task to initialize it.
OS_runTasks(4); // Run the tasks in order. The argument of this macro takes is: (Number of tasks) * 2
}
void fTask1(void)
{
OS_initializeTask(); // Initialize the task so that task runner can get its ingredients.
while (1)
{
PORTC = 0xAA;
OS_yield(); // Yield CPU to other tasks.
PORTC = 0x55;
OS_yield(); // Yield CPU to other tasks.
}
}
void fTask2(void)
{
OS_initializeTask(); // Initialize the task so that task runner can get its ingredients.
while (1)
{
PORTC = 0xFF;
OS_yield(); // Yield CPU to other tasks.
PORTC = 0x00;
OS_yield(); // Yield CPU to other tasks.
}
}
А вот и заголовочный файл RTOS.h
:
/*
* File: RTOS.h
* Author: abdullah
*
* Created on 21 Haziran 2012 Perşembe, 10:51
*/
#ifndef RTOS_H
#define RTOS_H
asm("OS_yield MACRO");
asm("local OS_tmp");
asm("movlw _OS_tasks ; Store the address of tasks, which is the start address of our task 'array'.");
asm("addwf _OS_currentTask, w ; Add current task's index to the start address.");
asm("movwf fsr ; We have the index of current task in W. Copy it to FSR");
asm("movlw high(OS_tmp) ; Copy PCLATH register's contents for the label, to W register.");
asm("movwf indf ; Copy W to current task's first item. We now store PCLATH of the current state of the task.");
asm("incf fsr, f ; Increment index, so that we will point to the next item of current task.");
asm("movlw low(OS_tmp) ; Copy PCL of the label to W register. This will let us save the PCL of the current state of the task.");
asm("movwf indf ; Copy W to task's next item. With that, we will initialize the current task.");
asm("goto OS_taskswitcher");
asm("OS_tmp: ; We will use this label to gather the PC of the return point.");
asm("ENDM");
#define OS_yield(); asm("OS_yield");
asm("OS_initializeTask MACRO");
asm("local OS_tmp");
asm("movlw _OS_tasks ; Store the address of tasks, which is the start address of our task 'array'.");
asm("addwf _OS_currentTask, w ; Add current task's index to the start address.");
asm("movwf fsr ; We have the index of current task in W. Copy it to FSR");
asm("movlw high(OS_tmp) ; Copy PCLATH register's contents for the label, to W register.");
asm("movwf indf ; Copy W to current task's first item. We now store PCLATH.");
asm("incf fsr,f ; Increment index, so that we will point to the next item of current task.");
asm("movlw low(OS_tmp) ; Copy PCL of the label to W register. This will let us save the PCL of the current state of the task.");
asm("movwf indf ; Copy W to task's next item. With that, we will initialize the current task.");
asm("return ; We have gathered our initialazation information. Return back to main.");
asm("OS_tmp ; We will use this label to gather the PC of the return point.");
asm("ENDM");
#define OS_initializeTask(); asm("OS_initializeTask");
asm("OS_runTasks MACRO numberOfTasks");
asm("global OS_taskswitcher");
asm("OS_taskswitcher:");
asm("CLRWDT");
asm("movlw 0x02 ; W = 2");
asm("addwf _OS_currentTask, f ; Add 2 to currentTask, store it in currentTask.");
asm("movlw numberOfTasks ; W = numOfTasks");
asm("subwf _OS_currentTask, w ; w= f - w");
asm("btfsc status, 0 ; If currentTask >= numOfTasks");
asm("clrf _OS_currentTask ; Clear currentTask");
asm("movlw _OS_tasks ; Store the address of tasks, which is the start address of our task 'array'.");
asm("addwf _OS_currentTask, w ; Add current task's index to the start address.");
asm("movwf fsr ; We have the index of current task in W. Copy it to FSR");
asm("movf indf, w ; Copy the contents of current task's first item to W");
asm("movwf pclath ; Copy W to PCLATH. As a result, current task's PCLATH will be in PCLATH register.");
asm("incf fsr, f ; Increment index, so that we will point to the next item of current task.");
asm("movf indf, w ; Copy the contents of current task's second item to W.");
asm("movwf pcl ; Copy W to PCL. Finally, current task's PCL will be in PCL register.");
asm("ENDM");
#define OS_runTasks(numberOfTasks); asm("OS_runTasks "#numberOfTasks);
#endif /* RTOS_H */
Ниже показано, как реализовать это с помощью сборки. Доступ к тому же коду с форматированием (ссылки на Pastebin) . как это может быть улучшено? Это моя первая программа на ассемблере PIC, буду рад любым комментариям.
list p=16f616
#include p16f616.inc
;*** Configuration Bits ***
__CONFIG _FOSC_INTOSCIO & _WDTE_OFF & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _IOSCFS_8MHZ & _BOREN_ON
;**************************
;*** Variable Definitions ***
VARS UDATA ; Define undefined data(s).
numOfTasks res 1 ; This variable holds the number of tasks multiplied by 2.
currentTask res 1 ; Index variable that points to the current task's index in "tasks"
tasks res 4 ; This is task "array". Every task occupies 2 bytes.
;****************************
;*** Reset Vector ***
RESET CODE 0x0000 ; Define a code block starting at 0x0000, which is reset vector, labeled "RESET"
goto start ; Start the program.
;********************
;*** Main Code ***
MAIN CODE
start ; Label the start of the program as "start".
banksel TRISA ; Select appropriate bank for TRISA.
clrf TRISA ; Clear TRISA register. Configure all of the PORTA pins as digital outputs.
clrf TRISC ; Clear TRISC register. TRISC and TRISA are at the same bank, no need for "banksel".
clrf ANSEL ; Clear ANSEL register and configure all the analog pins as digital i/o.
banksel PORTA ; Select appropriate bank for PORTA.
clrf PORTA ; Clear PORTA register.
clrf PORTC ; Clear PORTC register. PORTC and PORTA are at the same bank, no need for "banksel".
movlw 0x04 ; W = Number of tasks * 2.
movwf numOfTasks ; Since every task has two datas in it, we will multiply by 2.
clrf currentTask ; Set the task#0 as current task.
CALL task0 ; Call task#0 since we need to initialize it. We are going to get..
; ..its PCL and PCLATH values at the start address.
movlw 0x02 ; W = 2
addwf currentTask, f ; Increment currentTask by 2, since every task occupies 2 places.
CALL task1 ; Call task#1, for initialazation.
taskswitcher
movlw 0x02 ; W = 2
addwf currentTask, f ; Add 2 to currentTask, store it in currentTask.
movf numOfTasks, w ; W = numOfTasks
subwf currentTask, w ; w= f - w
btfsc STATUS, 0 ; If currentTask >= numOfTasks
clrf currentTask ; Clear currentTask
movlw tasks ; Store the address of tasks, which is the start address of our task "array".
addwf currentTask, w ; Add current task's index to the start address.
; For example; task1's index is 2: [task0_1][task0_2][task1_1][task1_2]....
; 0 1 2 3
movwf FSR ; We have the index of current task in W. Copy it to FSR
movf INDF, w ; Copy the contents of current task's first item to W
movwf PCLATH ; Copy W to PCLATH. As a result, current task's PCLATH will be in PCLATH register.
incf FSR, f ; Increment index, so that we will point to the next item of current task.
movf INDF, w ; Copy the contents of current task's second item to W.
movwf PCL ; Copy W to PCL. Finally, current task's PCL will be in PCL register.
goto $ ; This instruction is not effective. But, enter the endless loop.
;*** TASK 0 ***
TASK0 CODE
;**************
task0
movlw tasks ; Store the address of tasks, which is the start address of our task "array".
addwf currentTask, w ; Add current task's index to the start address.
movwf FSR ; We have the index of current task in W. Copy it to FSR
movf PCLATH, w ; Copy PCLATH register's contents to W register.
movwf INDF ; Copy W to current task's first item. We now store PCLATH.
incf FSR,f ; Increment index, so that we will point to the next item of current task.
movlw low($+3) ; Copy PCL+3 to W register. This will let us save the PCL of the start of the task.
movwf INDF ; Copy W to task's next item. With that, we will initialize the current task.
return ; We have gathered our initialazation information. Return back to main.
task0main
banksel PORTA ; Select the appropriate bank for PORTA
movlw 0xAA ; Move literal to W so that W = 0xAA
movwf PORTA ; PORTA = 0xAA. Use a LATA register to create more robust code.
movlw tasks ; Store the address of tasks, which is the start address of our task "array".
addwf currentTask, w ; Add current task's index to the start address.
movwf FSR ; We have the index of current task in W. Copy it to FSR
movf PCLATH, w ; Copy PCLATH register's contents to W register.
movwf INDF ; Copy W to current task's first item. We now store PCLATH of the current state of the task.
incf FSR,f ; Increment index, so that we will point to the next item of current task.
movlw low($+3) ; Copy PCL+3 to W register. This will let us save the PCL of the current state of the task.
movwf INDF ; Copy W to task's next item. With that, we will initialize the current task.
goto taskswitcher ; Yield the CPU to the awaiting task by going to task switcher.
banksel PORTA ; Select the appropriate bank for PORTA
movlw 0x55 ; Move literal to W so that W = 0x55
movwf PORTA ; PORTA = 0xAA. Use a LATA register to create more robust code.
goto task0main ; Loop by going back to "task0main". We will continuously toggle PORTA.
;*** TASK 1 ***
TASK1 CODE
;**************
task1
movlw tasks ; Store the address of tasks, which is the start address of our task "array".
addwf currentTask, w ; Add current task's index to the start address.
movwf FSR ; We have the index of current task in W. Copy it to FSR
movf PCLATH, w ; Copy PCLATH register's contents to W register.
movwf INDF ; Copy W to current task's first item. We now store PCLATH.
incf FSR,f ; Increment index, so that we will point to the next item of current task.
movlw low($+3) ; Copy PCL+3 to W register. This will let us save the PCL of the start of the task.
movwf INDF ; Copy W to task's next item. With that, we will initialize the current task.
return ; We have gathered our initialazation information. Return back to main.
task1main
banksel PORTA ; Select the appropriate bank for PORTA
movlw 0xAA ; Move literal to W so that W = 0xAA
movwf PORTA ; PORTA = 0xAA. Use a LATA register to create more robust code.
movlw tasks ; Store the address of tasks, which is the start address of our task "array".
addwf currentTask, w ; Add current task's index to the start address.
movwf FSR ; We have the index of current task in W. Copy it to FSR
movf PCLATH, w ; Copy PCLATH register's contents to W register.
movwf INDF ; Copy W to current task's first item. We now store PCLATH of the current state of the task.
incf FSR,f ; Increment index, so that we will point to the next item of current task.
movlw low($+3) ; Copy PCL+3 to W register. This will let us save the PCL of the current state of the task.
movwf INDF ; Copy W to task's next item. With that, we will initialize the current task.
goto taskswitcher ; Yield the CPU to the awaiting task by going to task switcher.
banksel PORTA ; Select the appropriate bank for PORTA
movlw 0x55 ; Move literal to W so that W = 0x55
movwf PORTA ; PORTA = 0xAA. Use a LATA register to create more robust code.
goto task1main ; Loop by going back to "task1main". We will continuously toggle PORTA.
END ; END of the program.
Абдулла Кахраман
Абдулла Кахраман
Абдулла Кахраман
Воутер ван Оойен
Абдулла Кахраман
Воутер ван Оойен