Atomthreads&STM8S207C8

来源:互联网 发布:道教 知乎 编辑:程序博客网 时间:2024/05/20 23:36

第一次发帖请大家见谅,请大家多多吐槽。


在STM8S207C8上跑AtomThreads嵌入式系统,AtomThreads官网有移植到STM8S207C8的工程可以下载。

Atomthreads is a free, lightweight, portable, real-time scheduler for embedded systems.

跨平台的内核代码在kernel文件夹中。移植到特定单片机的代码在port文件夹中。

支持编译器Cosmic,Raisonance,IAR。

开发环境:STVD + Cosmic ,MCU:STM8S207C8(6K RAM)。

 

默认内存模式使用

modsl0:将数据全部放到page0(0x0~0xFF)之外。

(mods0:将数据放到page0,使用@near放到page0之外)


创建工程:

需要注意的地方:

C Compiler Memory Model:"+modsl0"

Linker Input : Zero Page from 0x2 ~0xFF(空出前面2个bytes)

Linker Input : Ram from 0x100~0x17BF。

0x100~0x17BF:应用使用

0x17C0~0x17FF:启动堆栈

 

内存主要消耗为线程(任务)堆栈。每个线程堆栈保存自己调度时的寄存器,没有单独的中断堆栈(线程堆栈需要足够堆栈处理中断)。

128bytes足够线程调度使用,需要根据线程使用情况增大。我在使用过程中出现了3个任务的溢出,所有把所有任务的堆栈全部设成了256。

 

中断使用被其中断的任务的堆栈。中断中需要调用内核引发任务切换需要调用:atomIntEnter() atomIntExit()。如果中断中需要调用内核,需要给中断处理函数添加@svlreg修饰符(保存虚拟寄存器c_lreg)。

 

Atom任务切换利用当调用一个C函数时编译器自动入栈全部CPU和虚拟寄存器。

自己将自己调度出去(cooperative context switches):调用函数时,所有的需要保存的寄存器编译器自动入栈。任务切换时没有寄存器需要被保存。

中断调度非中断任务(preemptive switches):中断处理函数自动保存所有寄存器。Cosmic中断处理函数调用C函数时保存CPU寄存器和c_x,c_y。对于AtomThreads强制中断处理函数保存c_lreg。保证中断处理函数的c_lreg(被编译器用于处理longs、floats)在任务调度时被保存,longs可以在调用内核代码时使用。

 

任务切换汇编实现在atomport_asm_cosmic.s(备注写的非常清晰~)

_archContextSwitch:

将当前任务的堆栈地址保存到TCB中,将堆栈地址设置为需要调入的任务的TCB中保存的堆栈地址。

_archFirstThreadRestore:

作用:atomOSStart() 时启动第一个任务

对于一个新的任务没有必要初始化其A、X、Y、CC,然后就剩下SP和PC了。调度任务必须恢复SP,没有必要恢复PC,任务的入口已经在堆栈中了(创建任务时,将thread_shell入栈了,因为当RET返回时PC自动改为任务的入口)。

当一个任务启动时必须开中断,所以有很多地方可以开中断。对于所有的新任务都要这样做,而不是只有第一个任务,采用另外一种模式:使用一个thread shell routine(所有的任务第一次都从这里启动,并且开启中断)。参见atomport.c 中 static NO_REG_SAVE void thread_shell (void);

 

 

 

atomport.c

//每个任务的入口,启动中断。在archThreadContextInit()中调用,将函数地址入栈。

static NO_REG_SAVE void thread_shell(void); 

//任务上下文初始化 设置栈顶,入栈thread_shell地址,保存当前堆栈地址到TCB

void archThreadContextInit (ATOM_TCB*tcb_ptr, void *stack_top, void (*entry_point)(uint32_t), uint32_tentry_param);

//系统嘀嗒,所有的定时和延时都基于此,使用随便的一个MCU的定时器,我使用了TIM3 定时1ms(1.024ms)。

void archInitSystemTickTimer ( void );

//时钟的中断处理函数

INTERRUPT_ATOM void TIM3_SystemTickISR(void);

system_ticks++;调用定时器回调atomTimerCallbacks();   

atomTimerCallbacks();循环遍历timer_queue,并且调用tmr到期的回调。(这个地方处理在中断里,所以tmr回调不能处理太长。ucOSIII有一个任务专门处理这个)这个地方可以改进下~。

 

atom.h

//任务控制部件

typedef struct atom_tcb

{

   /* Thread's current stack pointer. When a thread is scheduled

    * out the architecture port can save*/

   POINTER sp_save_ptr;

 

   /* Thread priority (0-255) */

   uint8_t priority;

 

   /* Thread entry point and parameter */

   void (*entry_point)(uint32_t);

   uint32_t entry_param;

 

   /* Queue pointers */

   struct atom_tcb *prev_tcb;    /*Previous TCB in doubly-linked TCB list */

   struct atom_tcb *next_tcb;    /*Next TCB in doubly-linked list */

 

   /* Suspension data */

   uint8_t suspended;            /*TRUE if task is currently suspended */

   uint8_t suspend_wake_status;  /*Status returned to woken suspend calls */

   ATOM_TIMER *suspend_timo_cb;  /*Callback registered for suspension timeouts */

 

   /* Details used if thread stack-checking is required */

#ifdef ATOM_STACK_CHECKING

   POINTER stack_top;            /*Pointer to top of stack allocation */

   uint32_t stack_size;          /*Size of stack allocation in bytes */

#endif

} ATOM_TCB;

 

 

atomkernel.c

 

全局变量:

ATOM_TCB *tcbReadyQ ;准备等待执行的任务队列。

uint8_t atomOSStarted; 系统是否已经启动


任务调度

/*

判断系统是否启动 

系统启动关中断

         判断当前任务是否被挂起

         挂起

                   从tcbReadyQ取出第一个线程调用atomThreadSwitch进行任务切换

         没挂起

                   从tcbReadyQ取出任务优先级大于或者大于等去当前线程优先级等待执行的任务     进行任务切换,将当前任务添加到tcbReadyQ中。

处理完成开中断

 

系统没事干的时候一直在空闲任务里while(1){};直到调用atomSched()去处理别的任务。

*/

voidatomSched (uint8_t timer_tick);

 

/*

如果old_tcb!=new_tcb 调用汇编写好的任务调度 然后就进入到了另外一个任务进行执行

当任务被重新调度回来之后,设置任务状态为没有挂起 old_tcb->suspended = FALSE;

*/

staticvoid atomThreadSwitch(ATOM_TCB *old_tcb, ATOM_TCB *new_tcb);

 

出列入列

/*

出列队列的第一个元素

需要修改tcb_queue的值,所以传进去的是tcb_queue的地址

*/

ATOM_TCB*tcbDequeueHead (ATOM_TCB **tcb_queue_ptr);


/*

出列tcb_queue_ptr中指定的tcb_ptr

用在mutex , sem , queue实现中。

当tcb_ptr满足出列条件时,将其在mutex->suspQ , sem->suspQ , queue->putSuspQ, queue->getSuspQ中出列。

*/

ATOM_TCB*tcbDequeueEntry (ATOM_TCB ** tcb_queue_ptr, ATOM_TCB *tcb_ptr);

/*

出列tcbReadyQ第一个优先级大于等于priority(<=priority)的TCB 

NULL表示没有

*/

ATOM_TCB *tcbDequeuePriority (ATOM_TCB**tcb_queue_ptr, uint8_t priority);

 

/*

根据tcb_ptr的优先级将其插入到tcb_queue_ptr中

*/

uint8_t tcbEnqueuePriority (ATOM_TCB **tcb_queue_ptr,ATOM_TCB *tcb_ptr);

 

创建任务

/*

初始化TCB中的各种参数:挂起状态、优先级、队列指针、超时回调函数、任务入口、入口参数。

调用archThreadContextInit()入栈thread_shell,保存堆栈地址。

入列tcbReadyQ,调用atomSched(),任务调度(系统没有启动则不会启动调度)。

*/

uint8_t atomThreadCreate (ATOM_TCB*tcb_ptr, uint8_t priority, void (*entry_point)(uint32_t), uint32_tentry_param, void *stack_top, uint32_t stack_size);

 

启动系统

/*

如果离开中断时需要调用调度,进入中断时需要先调用此函数使atomIntCnt++

*/

void atomIntEnter (void);

/*

离开中断时调用调度atomIntCnt--

*/

void atomIntExit (uint8_t timer_tick);

/*

根据atomIntCnt判断当前的上下文是任务还是中断。

*/

ATOM_TCB *atomCurrentContext (void);

/*

初始化系统

初始化参数     curr_tcb = NULL;

                       tcbReadyQ = NULL;

                      atomOSStarted = FALSE;

创建空闲任务(只有while(1){})。

*/

uint8_t atomOSInit (void*idle_thread_stack_top, uint32_t idle_thread_stack_size);

/*

启动系统

atomOSStarted = TRUE;

出列空闲任务,调用archFirstThreadRestore(); 系统启动!

*/

void atomOSStart (void);

 

atomTimer.c


/* Callback function prototype 定时器超时回调函数*/

typedef void ( * TIMER_CB_FUNC ) ( POINTERcb_data ) ;

 

/*定时器数据类型定义*/

typedef struct atom_timer

{

   TIMER_CB_FUNC   cb_func;    /* Callback function */

   POINTER          cb_data;    /* Pointer to callback parameter/data */

   uint32_t       cb_ticks;  /* Ticks until callback */

         /*Internal data */

   struct atom_timer *next_timer;              /*Next timer in doubly-linked list */

 

} ATOM_TIMER;

 

/* 延时定时器数据类型 */

typedef struct delay_timer

{

   ATOM_TCB *tcb_ptr;  /* Threadwhich is suspended with timeout */

} DELAY_TIMER;

文件内全局变量

/** 定时器队列Queue*/

static ATOM_TIMER *timer_queue= NULL;

/** 系统当前tick*/

static uint32_t system_ticks = 0;


注册取消定时器

/*

将timer_ptr添加到timer_queue中。uc OSIII 这个地方使用了一个类似于Java 中Map的结构。这里仅用了单向链表。

*/

uint8_t atomTimerRegister (ATOM_TIMER*timer_ptr);

 

/*

将定时器从timer_queue中删除。可以在中断中调用,但是还是不要调用的好。

*/

uint8_t atomTimerCancel (ATOM_TIMER*timer_ptr);

 

系统tick

/*

系统时钟system_ticks++;调用atomTimerCallbacks()查找超时的定时器,并调用回调函数(注意这里是在中断里的)。

*/

void atomTimerTick (void);

/*

遍历timer_queue,将其cb_ticks--,调用cb_ticks==0的回调函数。

*/

static void atomTimerCallbacks (void);


延时函数

/*

挂起当前任务ticks。如果是延时1个tick,任务会在调用当前函数时和下一个系统tick之间的任何时刻被唤醒(不准确)。

atomCurrentContext()获取当前任务。挂起当前任务。将当前tcb的指针赋值给DELAY_TIMER timer_data->tcb_ptr,当做参数传递给timer_cb的回调函数atomTimerDelayCallback中。

注册定时器,调用调度函数atomSched()。

*/

uint8_t atomTimerDelay (uint32_t ticks);

/*

定时时间到,唤醒挂起的任务(将当前任务入列到tcbReadyQ)。

*/

static void atomTimerDelayCallback (POINTERcb_data);

 

 

atomsem.c

/*

信号的数据定义

*/

typedef struct atom_sem

{

   ATOM_TCB *  suspQ;  /* Queue of threads suspended on thissemaphore */

   uint8_t       count;  /* Semaphore count */

} ATOM_SEM;

/*

使用信号时定时器回调函数传入参数的数据类型定义

*/

typedef struct sem_timer

{

   ATOM_TCB *tcb_ptr;  /* Threadwhich is suspended with timeout */

   ATOM_SEM *sem_ptr;  /* Semaphorethe thread is suspended on */

} SEM_TIMER;

 

创建删除信号

/*

创建一个信号,将initial_count(一般为0)设置为count,suspQ = NULL;

*/

uint8_t atomSemCreate (ATOM_SEM *sem, uint8_tinitial_count);

/*

删除一个信号,唤醒等待队列中所有的任务,返回ATOM_ERR_DELETED,这个应该一般不会用到吧。

如果任务等待这个信号时有定时则取消。标记tcb_ptr->suspend_timo_cb = NULL;

*/

uint8_t atomSemDelete (ATOM_SEM *sem);

 

获取信号

/*

如果sem->count ==0;阻塞特定时间直到另外一个任务增加sem->count或者超时。在任务上下文中将当前任务添加到等待sem队列中,挂起当前任务,如果timeout>0,为等待任务添加定时器timer_cb,将当前任务tcb添加到sem->suspQ中,将当前任务tcb和sem赋值到timer_data中传递给atomSemTimerCallback回调函数。将timer_cb赋值给curr_tcb_ptr->suspend_timo_cb。调用调度函数atomSched(),等待被重新唤醒。

当被重新唤醒时,将返回值设置为curr_tcb_ptr->suspend_wake_status;(suspend_wake_status在atomSemPut和callback中被更改。),当前任务继续执行。不用sem->count--; 因为Put中就没有++。

如果sem->count !=0;  sem->count--;返回ATOM_OK

*/

uint8_t atomSemGet (ATOM_SEM *sem, int32_ttimeout);

/*

timer_data_ptr->tcb_ptr->suspend_wake_status= ATOM_TIMEOUT;   告诉任务我超时了

timer_data_ptr->tcb_ptr->suspend_timo_cb= NULL;

从sem->suspQ中出列,在tcbReadyQ中入列。

*/

static void atomSemTimerCallback (POINTERcb_data);

 

产生信号

/*

如果有任务在等待此信号,直接唤醒优先级最高的任务,如果有定时器存在则取消定时器,不会sem->count++。

如果没有任务在等待此信号,则sem->count++,如果sem->count==255,则溢出错误ATOM_ERR_OVF,否则返回ATOM_OK。

*/

uint8_t atomSemPut (ATOM_SEM * sem);

 

 

atomMutex.c

/*

互斥体的数据类型定义

*/

typedef struct atom_mutex

{

   ATOM_TCB *  suspQ;  /* Queue of threads suspended on this mutex*/

   ATOM_TCB *  owner;  /* Thread which currently owns the lock */

   uint8_t     count;  /* Recursive count of locks by the owner  */

} ATOM_MUTEX;

/*

使用互斥体时定时器回调函数传入参数的数据类型定义

*/

typedef struct mutex_timer

{

   ATOM_TCB *tcb_ptr;      /* Threadwhich is suspended with timeout */

   ATOM_MUTEX *mutex_ptr;  /* Mutexthe thread is suspended on */

} MUTEX_TIMER;


创建删除互斥体

/*

创建一个互斥体mutex,mutex->count =0;mutex->owner = NULL; mutex->suspQ=NULL;

*/

uint8_t atomMutexCreate (ATOM_MUTEX *mutex);

/*

删除一个互斥体,唤醒等待队列中所有的任务,返回ATOM_ERR_DELETED,如果在任务上下文则调用atomSched()。这个应该一般不会用到吧。

如果任务等待这个互斥体时有定时则取消。标记tcb_ptr->suspend_timo_cb = NULL;

*/

uint8_t atomMutexDelete (ATOM_MUTEX *mutex);

 

获取互斥体

/*

如果mutex没有被别的线程占用,则当前线程获得mutex的所有权,直到当前线程调用atomMutexPut(),mutex可以被当前拥有者重复获取。当一个线程获得所有权时别的线程不能获取或者释放mutex。

如果timeout >0,为等待任务添加定时器timer_cb,将当前任务tcb添加到mutex ->suspQ中,将当前任务tcb和mutex赋值到timer_data中传递给atomMutexTimerCallback回调函数。将timer_cb赋值给curr_tcb_ptr->suspend_timo_cb。调用调度函数atomSched(),等待被重新唤醒。

当被重新唤醒时,将返回值设置为curr_tcb_ptr->suspend_wake_status;(suspend_wake_status在atomMutexPut和callback中被更改。),如果返回值为ATOM_OK则mutex->count++,表示当前任务获得了mutex的所有权,继续执行当前任务。

*/

uint8_t atomMutexGet (ATOM_MUTEX *mutex,int32_t timeout);

/*

将timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT;   告诉任务我超时了

timer_data_ptr->tcb_ptr->suspend_timo_cb= NULL;

从mutex->suspQ中出列,在tcbReadyQ中入列。

*/

static void atomMutexTimerCallback (POINTERcb_data);


释放互斥体

/*

首选检测mutex是被当前线程所有,mutex->count--; 如果mutex->count==0表示当前任务放弃mutex的所有权。

如果mutex被释放时有线程在等待此mutex,则唤醒优先级最高的,如果有定时器存在则取消定时器。

*/

uint8_t atomMutexPut (ATOM_MUTEX * mutex);


atomqueue.c

atomqueue进行了一个小小的更改,queue传的数据只是数据块 (参见atommem.c)的指针。

/*

队列的数据结构的定义

*/

typedef struct atom_queue

{

   ATOM_TCB *  putSuspQ;       /* Queue of threads waiting to send */

   ATOM_TCB *  getSuspQ;       /* Queue of threads waiting to receive*/

   uint8_t *   buff_ptr;       /* Pointer to queue data area */

   uint32_t    unit_size;      /* Size of each message */

   uint32_t    max_num_msgs;   /* Max number of storable messages */

   uint32_t    insert_index;   /* Next byte index to insert into */

   uint32_t    remove_index;   /* Next byte index to remove from */

   uint32_t    num_msgs_stored;/*Number of messages stored */

} ATOM_QUEUE;

/*

使用队列时定时器回调函数传入参数的数据类型定义

*/

typedef struct queue_timer

{

   ATOM_TCB   *tcb_ptr;    /* Thread which is suspended with timeout*/

   ATOM_QUEUE *queue_ptr;  /* Queuethe thread is interested in */

   ATOM_TCB   **suspQ;     /* TCB queue which thread is suspended on*/

} QUEUE_TIMER;

 

创建删除队列

/*

初始化一个队列。buff_ptr(buff大小)= unit_size * max_num_msgs;  由于我们设定传送的值只是指针,所有unit_size= sizeof(void *);  == 2

*/

uint8_t atomQueueCreate (ATOM_QUEUE *qptr,uint8_t *buff_ptr, uint32_t unit_size, uint32_t max_num_msgs);

 

/*

删除一个队列,唤醒等待此队列的所有的任务,返回ATOM_ERR_DELETED,tcb_ptr->suspend_wake_status = ATOM_ERR_DELETED; 。如果在任务上下文则调用atomSched()。这个应该一般不会用到吧。

如果这个任务等待这个队列时有定时则取消。标记tcb_ptr->suspend_timo_cb = NULL;

*/

uint8_t atomQueueDelete (ATOM_QUEUE *qptr);


获取队列中的信息

/*

获取一条信息,信息的起始地址被copy到msgptr中。

在任务调用时定义一个u16 rcv_ptr_;

将&rcv_ptr_传进去,u8 * rcv_ptr = (u8 *)rcv_ptr_;

rcv_ptr指向的就是信息所在的地址。

如果qptr->num_msgs_stored != 0 队列中有数据,将数据地址copy到msgptr指向的地址queue_remove()。如果在任务上下文则调用atomSched()。

如果qptr->num_msgs_stored == 0 队列中没有数据,将当前线程入列qptr->getSuspQ中,如果timeout>0,为等待任务添加定时器timer_cb,将curr_tcb_ptr、qptr、&qptr->getSuspQ复制到timer_data中传入到atomQueueTimerCallback回调函数中。

调用atomSched()。

如果curr_tcb_ptr->suspend_wake_status == ATOM_OK ,表示队列中有数据,将数据地址copy到msgptr。继续执行当前任务。

*/

uint8_t atomQueueGet (ATOM_QUEUE *qptr,int32_t timeout, uint8_t *msgptr);

/*

从队列中移除一条信息。将其copy到我们提供的msgptr指向的内存。

查看是否有任务在等待发送数据到队列中,如果没有直接返回。如果有,则将其在qptr->putSuspQ中出列,在tcbReadyQ中入列,如果存在定时器则取消。

*/

static uint8_t queue_remove (ATOM_QUEUE*qptr, uint8_t* msgptr);

 

向队列中发送信息

/*

发送一条信息到队列中。信息将msgptr的地址复制到队列的buff中。

如果qptr->num_msgs_stored != qptr->max_num_msgs 队列中还有空。将数据插入到队列中queue_insert()。如果在任务上下文则调用atomSched()。

如果qptr->num_msgs_stored == qptr->max_num_msgs 队列中没空了。将当前线程入列qptr->putSuspQ中,如果timeout>0,为等待的任务添加定时器timer_cb,curr_tcb_ptr、qptr、&qptr->putSuspQ复制到timer_data中传入到atomQueueTimerCallback回调函数中。

调用atomSched()。

如果curr_tcb_ptr->suspend_wake_status == ATOM_OK ,表示队列中有空了,将数据插入到队列中queue_insert(),继续执行当前任务。

一般使用timeout=-1,不往putSuspQ队列中添加等待。

*/

uint8_t atomQueuePut (ATOM_QUEUE *qptr,int32_t timeout, uint8_t *msgptr);

/*

队列中有空,将msgptr的地址复制到队列buff中。原来是复制msgptr指向的内容。

查看是否有任务在等待数据,如果有则出列getSuspQ的第一个任务,添加到tcbReadyQ中,如果存在寄存器则取消。

*/

static uint8_t queue_insert (ATOM_QUEUE*qptr, uint8_t* msgptr);

 

队列的回调函数

/*

获取创建定时器时传入的参数,

将timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT;   告诉任务我超时了

timer_data_ptr->tcb_ptr->suspend_timo_cb = NULL;

从timer_data_ptr ->suspQ中出列,在tcbReadyQ中入列。

*/

static void atomQueueTimerCallback (POINTERcb_data);

 

 

atomMem.c


此模块参考uC OSIII,做了些修改,不知算不算侵权。

/*

内存块的数据类型定义

*/

typedef struct os_mem { /* MEMORY CONTROLBLOCK */

   void *AddrPtr; /* Pointer to beginning of memory partition */

   void *FreeListPtr; /* Pointer to list of free memory blocks */

   uint16_t BlkSize; /* Size (in bytes) of each block of memory */

   uint16_t NbrMax; /* Total number of blocks in this partition */

   uint16_t NbrFree; /* Number of memory blocks remaining in this partition*/

} ATOM_MEM;

 

创建内存块

/*

内存块的数量n_blks必须大于1,每个内存块的大小blk_size必须足够大可以放下一个指针(STM8S中指针占2个bytes)。在每一个内存块(除最后一个)的前2个字节存放下一个内存块的地址,形成一个链表。

*/

void atomMemCreate (ATOM_MEM *p_mem, void*p_addr, uint16_t n_blks, uint16_t blk_size, uint8_t *p_err);


获取内存块

/*

如果NbrFree == 0,表示没有空闲的内存块了。

如果NbrFree !=0,取出p_mem->FreeListPtr指向的地址p_blk,将p_mem->FreeListPtr指向p_blk内存块前两个字节指向的内存块,p_mem->NbrFree--,返回p_blk。

*/

void *atomMemGet (ATOM_MEM *p_mem, uint8_t*p_err);


释放内存块

/*

将p_mem->FreeListPtr 赋值到p_blk(将要释放的内存块)的前两个字节,将p_blk赋值给p_mem->FreeListPtr,p_mem->NbrFree++。

*/

void atomMemPut (ATOM_MEM *p_mem, void*p_blk, uint8_t *p_err);

 

全儿 20150831
1 0
原创粉丝点击