第一次发帖请大家见谅,请大家多多吐槽。
在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