基于STM32F103C8T6+uC/OS-II的例程笔记(二)

请大家先想一下,为什么想学uc/OS-II?
在通过例程学习单片机的时候有没有这样的疑问,为什么例程是一个个孤立的呢,能否整合在一起?RTOS就是这样一个多线程运行的好工具,我选取UCOS来实现,这篇文章主要是以uc/OS-II来讲,想更深入可以继续学uc/OS-III,有时间片轮转的新功能。
这篇先讲述代码移植和例程,后面在补充UCOS的工作原理。
一.首先是将ucosII移植到STM32C8T6上来,原来黄老师用的编译器是IAR,这里也选用IAR的ucos移植包,具体可以参考这位兄弟的移植教程

https://blog.csdn.net/azhuty/article/details/30515415

二.移植好以后可以我们以显示屏为主界面做个多任务并发运行,在用按键做多任务切换
1.建立任务前先定义优先级、堆栈大小、任务堆栈、检测实际使用堆栈大小的结构体、任务函数

//任务优先级
#define	TASKKey_PRIO	10
//任务堆栈大小	
#define	TASKKey_STK_SIZE	256
//任务控制块 UCOSIII用
//OS_TCB TaskKeyTaskTCB;
//任务堆栈	
OS_STK taskKey_stk[TASK1_STK_SIZE]; 
//实际使用堆栈大小
OS_STK_DATA TaskKeyStackBytes;
//任务函数
static void taskKey(void *p_arg);

2.再写开始任务,驱动的初始化都放在BSP_Init里,还给每个按键都定义了一个信号量,供其他函数使用,最后在开始函数里打印系统状态信息:OS版本、1秒几个节拍、CPU使用率、已运行的时间节拍、任务切换次数、任务x空闲字节、系统错误代码(这些打印在调试的时候很好使)

static void startup(void *p_arg)
{
  OS_CPU_SR cpu_sr=0;
  delay_init();
  BSP_Init();   //BSP初始化
  OSStatInit();  //开启统计任务
  EventSem_Key0=OSSemCreate(0);   //按键0信号量
  EventSem_Key1=OSSemCreate(0);   //按键1信号量
  EventSem_Key2=OSSemCreate(0);
  EventSem_Key3=OSSemCreate(0);
  OS_ENTER_CRITICAL();  //进入临界区(关闭中断)  
  os_err = OSTaskCreateExt(taskKey, 
  						   (void *)0,
  						   (OS_STK *)&task1_stk[TASK1_STK_SIZE - 1],
  						   (INT8U)TASK1_PRIO,
  						   (INT16U)TASK1_PRIO,
  						   (OS_STK*)&task1_stk[0],
  						   (INT32U) TASK1_STK_SIZE,
  						   (void *)0, 
  						   (INT16U)(OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR));	
							......
							
  OS_EXIT_CRITICAL();  //退出临界区(开中断)
  while(1)
  {
  	//OSTaskStkChk(OS_PRIO_SELF, &STARTUPStackBytes);
	OSTaskStkChk(TASK1_PRIO, &Task1StackBytes);
	......

	printf("uC/OS-II:V%ld.%ld%ld\r\n",OSVersion()/100,(OSVersion()%100)/10,(OSVersion()%10));   //输出版本
	printf("TickRate: %ld \r\n",OS_TICKS_PER_SEC);   //输出时钟节拍
	printf("CPU Usage: %ld% \r\n",OSCPUUsage); 
	printf("已运行的时间节拍#Ticks: %ld \r\n",OSTime);
	printf("任务切换次数#CtxSw: %ld \r\n",OSCtxSwCtr);
	printf("任务1空闲字节=%d\r\n",Task1StackBytes.OSFree);
	......
	delay_ms(5000);
	if(os_err!=0u)
	{
		printf("Error Code=%d\r\n",os_err);  //系统错误代码
	}
  }
}

3.给按键单独建个任务,用于输出信号量,为了更有感觉我们让每按一次叫一下

static void task1(void *p_arg)
{
	while(1)
	{
		delay_ms(10);
		if(KEY_Scan(1)==KEY0_PRES)  //KEY0按下
		{
			LED0=ENABLE;
			BEEP1=!BEEP1;  //蜂鸣器响
			delay_ms(300);
			BEEP_OFF();
			OSSemPost(EventSem_Key0);    //有按键按下,发送信号量
			while(KEY_Scan(1)==KEY0_PRES)  
			{
				delay_ms(5);   //等待按键释放
			}
			LED0=DISABLE;			
		}
		else if(KEY_Scan(1)==KEY1_PRES)
		{ .......

4.我在写例程的时候也介绍下UCOS里常用的功能,如果后期想用按键做任务切换,可以使用任务悬挂和恢复函数,用于切换显示屏。

			OSTaskSuspend(12); //关闭状态显示
			OSTaskSuspend(13); //关闭DMA传输
			OSTaskSuspend(15); //关闭日历
			OSTaskResume(14); //打开ADC 

5.接下来介绍软件定时器,和硬件定时器的区别是这不用通过定时器中断来实现,节省资源

OS_TMR * tmr1;            //软件定时器1
tmr1=OSTmrCreate(0,10,OS_TMR_OPT_PERIODIC(OS_TMR_CALLBACK)tmr1_callback,0,"tmr1",&err);//100ms执行一次tmr1里的任务
OSTmrStart(tmr1,&err);//启动软件定时器1
void tmr1_callback(OS_TMR *ptmr,void *p_arg)  //定时器处理函数
{
    LED3=!LED3;
}

6.接下来介绍信号量使用,前面按键提供了一个EventSem_Keyx的信号量,用于启动DMA传输

#define SEND_BUF_SIZE 4200   //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍
u8 SendBuff[SEND_BUF_SIZE];  //发送数据缓冲区
const u8 TEXT_TO_SEND[]={"xxxxxxxx"};  //为了时间足够长,把字符串循环写入SendBuff中
static void task4(void *p_arg)
{
    u8 t;
	u8 event_err;
	float pro=0;//进度
	OS_CPU_SR cpu_sr=0;
	MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);//DMA1通道4,外设为串口1,存储器为SendBuff,长度SEND_BUF_SIZE.
	while(1)
    {
			OSSemPend(EventSem_Key3, 0, &event_err);
			display_clean();
			display_string(0, 0, 1, "Start_Transimit");
			printf("\r\nDMA DATA:\r\n"); 
			delay_ms(700);
			USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送      
			MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输
			while(1)
			{
				if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET) //判断通道4传输完成
				{
					DMA_ClearFlag(DMA1_FLAG_TC4); //清除通道4传输完成标志
					break;
				}
				pro=DMA_GetCurrDataCounter(DMA1_Channel4);//得到当前还剩余多少个数据
				pro=1-pro/SEND_BUF_SIZE;//得到百分比
				pro*=100;
				display_num(1, 0, 1, pro);
			}
            display_num(1, 0, 1, 100);
            display_string(2, 0, 1, "Transimit_Finished");
	}
}

7.接下来介绍UCOS任务调度功能,只要函数中有OS延时,等待OS信号量挂起等操作才会引起任务切换。如果想锁定任务不让切换,可以使用OSSchedLock()和OSSchedUnlock(),display里面有写延时函数,这里不像让他打断adc转换。

	while(1)
	{
		OSSemPend(EventSem_Key0, 0, &event_err);
		display_clean();		
		display_string(2, 0, 1, "ADC_Value");
		display_string(5, 0, 1, "ADC_Voltage");
        OSSchedLock();
        adcx=Get_Adc_Average(ADC_Channel_1, 10);
		display_num(2, 60, 1, adcx);
		temp=(float)adcx*(3.3/4096);
		adcx=temp;
		display_num(5, 70, 1, adcx);
		temp-=adcx;
		temp*=1000;
		display_num(5, 80, 1, adcx);
		OSSchedUnlock();
		delay_ms(100);
	}

8.最后是RTC,时间作为主界面使用,因为时间一直需要刷新,不过优先级放最低,其他任务可以在延时的时候抢占。

	while(1)
	{
		display_string(2, 0, 1, "Year");
		display_string(3, 0, 1, "Mouth");
		display_string(4, 0, 1, "Date");
		display_string(5, 0, 1, "Hour");
		display_string(6, 0, 1, "Minute");
		display_string(7, 0, 1, "Second");
		if(RTC_Alarm_Flag==DISABLE)
		{
			if(t!=calendar.sec)
			{
				t=calendar.sec;
				display_num(2, 30, 1, calendar.w_year);
				display_num(3, 36, 1, calendar.w_month);
				display_num(4, 30, 1, calendar.w_date);
				display_num(5, 30, 1, calendar.hour);
				display_num(6, 42, 1, calendar.min);
				display_num(7, 42, 1, calendar.sec);									
			}
		}
		else 
		{
			LED2=!LED2;		
			printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间
			delay_ms(500);
		}

如下串口输出打印:
在这里插入图片描述
程序例程会在稍后提供,另外编写程序的时候会遇上堆栈溢出等问题,IAR编译器可以在flash.icf文件里修改堆栈大小

/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x08000000;  //向量表的起始地址
/*-Memory Regions-*/
define symbol __ICFEDIT_region_ROM_start__   = 0x08000000;  //ROM起始地址
define symbol __ICFEDIT_region_ROM_end__     = 0x080FFFFF;  //ROM结束地址
define symbol __ICFEDIT_region_RAM_start__   = 0x20000000;  //RAM起始地址
define symbol __ICFEDIT_region_RAM_end__     = 0x20017FFF;  //RAM结束地址
/*-Sizes-*/
define symbol __ICFEDIT_size_cstack__   = 0x400;  //栈大小
define symbol __ICFEDIT_size_heap__     = 0x200;  //堆大小
/**** End of ICF editor section. ###ICF###*/


define memory mem with size = 4G;   //定义芯片的存储空间4G 32位寻址空间最大4G
define region ROM_region   = mem:[from __ICFEDIT_region_ROM_start__   to __ICFEDIT_region_ROM_end__];  //ROM大小
define region RAM_region   = mem:[from __ICFEDIT_region_RAM_start__   to __ICFEDIT_region_RAM_end__];  //RAM大小

define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };   //堆与栈大小,8字节对齐
define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };   

initialize by copy { readwrite };  //启动时将RW数据搬移到RAM中,进行RW数据初始化
do not initialize  { section .noinit };   //不初始化有.noinit性质的块

place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };  //在0x08000000处放置.intvec,即向量表

place in ROM_region   { readonly };  //在ROM中放置1.只读数据
place in RAM_region   { readwrite,
                        block CSTACK, block HEAP };  //在RAM中放置1.可读写数据2.栈区3.堆区
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值