    由于公司大量使用stm32h7系列单片机，开发了很多嵌入式设备，这些设备算是中等复杂程度的系统，大部分都带点界面什么的，不是特别复杂，但也不是特别简单，不太可能用裸奔程序搞定的那种等级，上linux系统又太过复杂难搞，没必要，因此选了几款RTOS，使用起来也不是那么顺手，效果不甚理想，于是老板请了一位大神朋友，专门为公司写了一个操作系统内核，取名为e-kernel，意思是小型电子设备专用操作系统内核，总共只有17个API函数，非常简洁，基于消息通信为核心模式，功能灵活且强大，感觉有点像是仿照windows内核原理写的，在项目中确实好用，公司各大项目已经大量使用这个内核，稳如老狗，快如脱兔，开发起来十分方便。
     本着好东西大家一起分享的开源精神，本人再三要求大神走开源社区，发扬光大这个大神的杰作，数月的来回博弈，最终大神经不住我的磨工，说他的代码太烂了，不想丢人现眼，只准许让我公布库文件和API使用说明即可，我是意犹未尽，仍然跟各位stm32单片机工程师分享这个神作，嘿嘿。


我现在简要说明一下这个内核的使用方法：


首先，文件组成：
库文件： ek-stm32h7xx.lib,  不好意思，目前只有h7系列库文件，因为其它系列太小， 就像大神说的，小芯片小功能，裸奔代码更合适。
头文件：ek.h

使用环境：keil IDE


--------------------------------------------------------分界线------------------------------------------------------------------
API简要说明：
1)   void ek_init(void *mem_base,  u32 mem_size)；

在你的main函数首先要调用的内核初始化函数，需要分一块内存给内核自己用，这个内存一般是你定义一个数组，传给它。
mem_base：内存块起始地址。
mem_size：内存块大小，最小得8KB，我们一般就用32KB。


2)   void ek_start();
启动内核开始工作，一般是你的main函数最后调用它。

3)   void ek_tick(int period_ms);
内核tick滴答函数，一般在你的定时器里面调用，我们喜欢在SysTick中断里面调用
period_ms：表示你调用的周期是多少，我们一般喜欢设置为1mS调用一次。

4)   void ek_sleep(int ms);
task休眠，单位mS。


5)   HANDLE ek_create_task(
                                         int    priority,
                                         void   (*entry)(void *),
                                         void   *data,
                                         void   *stack,
                                         int    stack_size,
                                         char   *name
                                     );

创建一个任务task，返回是这个任务的句柄，以后就可以通过这个句柄，向这个任务发送消息。
priority：优先级，0~31，31优先级最高，0最低。同优先级支持时间片调度，默认10毫秒。
entry：任务代码函数入口。
data：任务私有数据。
stack：任务的堆栈起始地址，通常你可以定义一个数组给它。
stack_size：堆栈大小，单位字节。
name:  给任务取个名字，如果不要的话，可以是NULL。
返回：任务句柄，注意，不是直接返回指针，句柄是内核对象的代号，通过句柄进行操作。

6)   int ek_send_message(HANDLE h, int code, u32 data1, u32 data2, u32 data3, u32 data4);
向task发送一条用户自定义消息：可以在任务里面调用（任务会被阻塞，直到该条消息被对方执行完成），但是不可以在中断服务程序里面调用。
h:  任务句柄。
code：消息代码，用户自定义，大于32即可。因为0~31为内核自身用消息代码。
data1~data4：消息参数1~4.
返回：0----发送失败；1-----发送成功。

7)   int ek_post_message(HANDLE h, int code, u32 data1, u32 data2, u32 data3, u32 data4);
向task投递一条用户自定义消息：可以在任务里面调用，也可以在中断函数里面调用，（任务不会被阻塞，而是直接返回，不管对方有没有执行完成）。
h:  任务句柄。
code：消息代码，用户自定义，大于32即可。因为0~31为内核自身用消息代码。
data1~data4：消息参数1~4.
返回：0----发送失败；1-----发送成功。

跟大多数RTOS系统不同的是：
这个内核没有task删除和挂起的API接口，问大神，回复说没有必要这些接口，纳闷了好一阵子，最后发现确实在嵌入式项目中几乎用不上。


8)   int ek_get_message(HANDLE *h, int *code, u32 *data1, u32 *data, u32 data3, u32 data4);
获取一条消息，这个函数只能在任务代码里面调用，表示从本任务消息队列里面，取出一条消息数据，如果没取到，task会阻塞。
*h：返回发送这条消息的是哪个任务，如果是NULL，表示这个消息是从中断服务程序里面发出的消息，
      例如：我们很多时候当ADC采集完数据时，就会发送一个消息交给某个任务去处理数据。
*code：返回的任务代码。
 *data1~data4：消息参数1~4。
返回：0----表示没有获取到消息；1----表示获取到一条消息。


9)   void ek_enable_kmsg(u32 msg_code);
使能内核消息，内核消息代码范围：0~31，一个任务调用此接口，使能某个内核消息，例如使能定时器MSG_TIM_1000mS消息，那么，每隔1秒钟，任务就会自动收到来自内核的周期为1秒的定时器消息。

10)   void ek_disable_kmsg(u32 msg_code);
禁止内核消息。


11)   HANDLE ek_create_pool(void *addr, u32 total_size,  u32 blk_size, char *name);
创建内存池，
addr：         内存池起始地址，
total_size：  内存空间大小。
blk_size：    最小可分配块大小。
name：       名字，可以是NULL。
返回：         内存池对象句柄。

下面的函数，不用解释了吧：
12)   void *ek_alloc(HANDLE h, int size);
13)   void *ek_malloc(HANDLE h, int size);
14)   void ek_free(HANDLE h, void *ptr);

15)   HANDLE ek_create_lock(int level);
创建一个锁，这个锁不是普通的锁，它是可以避免优先级翻转的，就是当一个高优先级任务，去获取一个被低优先级任务霸占的锁时候，它会让这个优先级低的任务先调度起来运行，可以迅速释放这个锁给高优先级任务使用。
level：IRQ_LEVEL----表示这个锁可以在中断服务程序里面用，说白了就是全部把中断给锁住，在强实时系统中慎用，以免导致锁中断时间太长；
          TASK_LEVEL---任务task之间用的锁，不会长时间锁住中断，只能在任务之间作资源互斥加锁。
返回：锁对象的句柄。

下面的函数，不用解释了吧：
16)   void ek_lock(HANDLE h_lock);
17)   void ek_unlock(HANDLE h_lock);


所有内核API介绍完毕，是不是足够简单，可能10分钟不到就看完了。



--------------------------------------------------------分界线------------------------------------------------------------------
下面重点来了，给个最基本的示范代码：

1）在你的main函数里面：

int main(void)
{
..............................................

	//e-kernel内核初始化：这是使用e-kernel第一步要做的事情,  mem_pool 是随便定义一个数组,
	ek_init(mem_pool, 1024*32);
	
	//创建e-kernel任务：
	h_task1 = ek_create_task(17, test_task1, NULL, test_stack1, 256,  "test task1");
	h_task2 = ek_create_task(27, test_task2, NULL, test_stack2, 256,  "test task2");
	h_task3 = ek_create_task(19, test_task3, NULL, test_stack3, 1024,  "test task3");

	//创建一个锁：
	h_lock = ek_create_lock(TASK_LEVEL);

	//创建内存池：
	h_pool = ek_create_pool(addr_pool, 1024*128, 32, NULL);

	//启动e-kernel开始调度：
	ek_start();

...........................................
}


2）在你的周期为1mS定时器中断函数里面：

void SysTick_Handler(void)
{
..................................................

	ek_tick(1); //如果你的周期是5mS一次，那就应该是ek_tick(5)这样子。

..............................................
}


3）在你的某个task函数代码里面：

void test_task1(void *data)
{
	HANDLE peer;
	int code;
	u32 data1;
	u32 data2;
	u32 data3;
	u32 data4;
	
	ek_enable_kmsg(KM_TIM_1000MS);  //使能内核周期为1秒的定时器消息。
	
	while(1)
	{
		int rc = ek_get_message(&peer, &code, &data1, &data2, &data3, &data4);
		if (rc == 0) continue;

		switch(code)
		{
		case KM_TIM_1000MS:
			do something......
		break;

		case 33:   //用户自定义消息
			do something......
		break;

		default:  break;
		}
	}
}


4）在你某个中断服务程序里面：

void ISR_xxx()
{
	//向task1发送消息号为33的消息：
	ek_post_message(h_task1, 33, 0, 1);
}


5)  删除keil里面自带的PendSV_Handler函数，因为内核接管了这个函数。


就这么简单，剩下全靠用户自己灵活发挥，去实现一个功能完整的系统。
技术交流，大神只玩qq（比较另类）：qq  710608379

