[stm32][ucos] 1、基于ucos操作系统的LED闪烁、串口通信简单例程
* 内容简述:
本例程操作系统采用ucos2.86a版本, 建立了5个任务
任务名 优先级
APP_TASK_START_PRIO 2 主任务
Task_Com1_PRIO 4 COM1通信任务
Task_Led1_PRIO 7 LED1 闪烁任务
Task_Led2_PRIO 8 LED2 闪烁任务
Task_Led3_PRIO 9 LED3 闪烁任务
当然还包含了系统任务:
OS_TaskIdle 空闲任务-----------------优先级最低
OS_TaskStat 统计运行时间的任务-------优先级次低
1、主任务建立:
1 //建立主任务, 优先级最高 建立这个任务另外一个用途是为了以后使用统计任务 2 os_err = OSTaskCreate((void (*) (void *)) App_TaskStart, //指向任务代码的指针 3 (void *) 0, //任务开始执行时,传递给任务的参数的指针 4 (OS_STK *) &App_TaskStartStk[APP_TASK_START_STK_SIZE - 1], //分配给任务的堆栈的栈顶指针 从顶向下递减 5 (INT8U) APP_TASK_START_PRIO); //分配给任务的优先级
- 这个采用老版本的任务建立函数,第一个参数通俗的说法就是该任务对应的函数,如下:
1 static void App_TaskStart(void* p_arg) 2 { 3 (void) p_arg; 4 //初始化ucos时钟节拍 5 OS_CPU_SysTickInit(); /* Initialize the SysTick. */ 6 7 //使能ucos 的统计任务 8 #if (OS_TASK_STAT_EN > 0) 9 //----统计任务初始化函数 10 OSStatInit(); /* Determine CPU capacity. */ 11 #endif 12 //建立其他的任务 13 App_TaskCreate(); 14 15 while (1) 16 { 17 //1秒一次循环 18 OSTimeDlyHMSM(0, 0,1, 0); 19 } 20 }
- 当主任务建立之后,程序就转到该函数处,调用 App_TaskCreate();建立其他任务,然后进入死循环,我们会发现:这里的主任务在建立其他任务后就没啥作用的,这时可以调用相应的函数将主任务给杀死,这里没有这样做,只是让主任务进入循环。
2、其他任务建立:
1 static void App_TaskCreate(void) 2 { 3 //CPU_INT08U os_err; 4 5 //Com1_SEM=OSSemCreate(1); //建立串口1中断的信号量 6 Com1_MBOX=OSMboxCreate((void *) 0); //建立串口1中断的消息邮箱 7 8 //串口1接收及发送任务--------------------------------------------------------- 9 OSTaskCreateExt(Task_Com1, //指向任务代码的指针 10 (void *)0, //任务开始执行时,传递给任务的参数的指针 11 (OS_STK *)&Task_Com1Stk[Task_Com1_STK_SIZE-1],//分配给任务的堆栈的栈顶指针 从顶向下递减 12 Task_Com1_PRIO, //分配给任务的优先级 13 Task_Com1_PRIO, //预备给以后版本的特殊标识符,在现行版本同任务优先级 14 (OS_STK *)&Task_Com1Stk[0], //指向任务堆栈栈底的指针,用于堆栈的检验 15 Task_Com1_STK_SIZE, //指定堆栈的容量,用于堆栈的检验 16 (void *)0, //指向用户附加的数据域的指针,用来扩展任务的任务控制块 17 OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); //选项,指定是否允许堆栈检验,是否将堆栈清0,任务是否要进行浮点运算等等。 18 //LED1 闪烁任务------------------------------------------------------ 19 OSTaskCreateExt(Task_Led1,(void *)0,(OS_STK *)&Task_Led1Stk[Task_Led1_STK_SIZE-1],Task_Led1_PRIO,Task_Led1_PRIO,(OS_STK *)&Task_Led1Stk[0], 20 Task_Led1_STK_SIZE, 21 (void *)0, 22 OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); 23 24 //LED2 闪烁任务------------------------------------------------------ 25 OSTaskCreateExt(Task_Led2,(void *)0,(OS_STK *)&Task_Led2Stk[Task_Led2_STK_SIZE-1],Task_Led2_PRIO,Task_Led2_PRIO,(OS_STK *)&Task_Led2Stk[0], 26 Task_Led2_STK_SIZE, 27 (void *)0, 28 OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); 29 30 //LED3 闪烁任务------------------------------------------------------ 31 OSTaskCreateExt(Task_Led3,(void *)0,(OS_STK *)&Task_Led3Stk[Task_Led3_STK_SIZE-1],Task_Led3_PRIO,Task_Led3_PRIO,(OS_STK *)&Task_Led3Stk[0], 32 Task_Led3_STK_SIZE, 33 (void *)0, 34 OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); 35 }
- 这里是建立四个子任务第一个是串口通信的任务,一会再说,下面三个是LED闪烁任务,这里举Task_Led1说明:
1 //LED1闪烁任务---------------------------------------- 2 static void Task_Led1(void* p_arg) 3 { 4 (void) p_arg; 5 while (1) 6 { 7 LED_LED1_ON(); 8 OSTimeDlyHMSM(0, 0, 0, milsec1); 9 10 LED_LED1_OFF(); 11 OSTimeDlyHMSM(0, 0, 0, milsec1); 12 } 13 }
- 可见LED闪烁任务其实就是一个无限循环,让灯的电平每隔一定时间高、每隔一定时间低来呈现闪烁的效果。那么,他是怎样实现任务切换的呢?这就是操作系统的功能了,操作系统根据每个任务的优先级,在每个子任务执行到一定时期查询当前挂起任务的优先级来选择优先级最高的进行执行。下面是在app_cfg.h中对这些任务优先级的声明:
1 #define APP_TASK_START_PRIO 2 2 #define APP_TASK_USER_IF_PRIO 13 3 #define APP_TASK_KBD_PRIO 12 4 #define Task_Com1_PRIO 4 5 #define Task_Led1_PRIO 7 6 #define Task_Led2_PRIO 8 7 #define Task_Led3_PRIO 9
- 可见主任务的优先级最高,串口通信的优先级其次,LED闪烁的优先级趋于中等,依次为7、8、9,
1 static void Task_Com1(void *p_arg){ 2 INT8U err; 3 unsigned char * msg; 4 (void)p_arg; 5 while(1){ 6 7 //OSSemPend(Com1_SEM,0,&err); //等待串口接收指令成功的信号量 8 msg=(unsigned char *)OSMboxPend(Com1_MBOX,0,&err); //等待串口接收指令成功的邮箱信息 9 //USART_OUT(USART1,&TxBuffer1[0]); 10 if(msg[0]=='L'&&msg[1]==0x31){ 11 milsec1=atoi(&msg[3]); //LED1 的延时毫秒 (mini and V3) 12 USART_OUT(USART1,"\r\n"); 13 USART_OUT(USART1,"LED1: %d ms 间隔闪烁",milsec1); 14 } 15 else if(msg[0]=='L'&&msg[1]==0x32){ 16 milsec2=atoi(&msg[3]); //LED2 的延时毫秒 (only V3) 17 USART_OUT(USART1,"\r\n"); 18 USART_OUT(USART1,"LED2: %d ms 间隔闪烁",milsec2); 19 } 20 else if(msg[0]=='L'&&msg[1]==0x33){ 21 milsec3=atoi(&msg[3]); //LED3 的延时毫秒 (only V3) 22 USART_OUT(USART1,"\r\n"); 23 USART_OUT(USART1,"LED3: %d ms 间隔闪烁",milsec3); 24 } 25 } 26 }
- 这里重点讲一下串口通信的任务:这里采用消息邮箱进行消息传递,在建立其他任务App_TaskCreate(void)的开始就首先建立串口的消息邮箱:Com1_MBOX=OSMboxCreate((void *) 0);然后在串口通信的任务中进入循环后就一直等待消息邮箱的信息(第8行),如果没有消息过来就一直等待,在此期间其他任务可以进行,一旦有消息发送过来,由于串口通信的优先级较高,就能很快响应,根据收到的消息msg来重新设置led闪烁的频率。这里因为串口接收要用到中断,所以下面就说说串口通信的接收中断部分。
1 void USART1_IRQHandler(void) 2 { 3 unsigned int i; 4 unsigned char msg[50]; 5 OS_CPU_SR cpu_sr; 6 7 OS_ENTER_CRITICAL(); //保存全局中断标志,关总中断// Tell uC/OS-II that we are starting an ISR 8 OSIntNesting++; 9 10 OS_EXIT_CRITICAL(); //恢复全局中断标志 11 12 //OSTimeTick(); // Call uC/OS-II's OSTimeTick(),在os_core.c文件里定义,主要判断延时的任务是否计时到 13 14 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断读寄存器是否非空 15 { 16 // Read one byte from the receive data register 17 18 msg[RxCounter1++]= USART_ReceiveData(USART1); //将读寄存器的数据缓存到接收缓冲区里 19 20 if(msg[RxCounter1-1]=='L'){msg[0]='L'; RxCounter1=1;} //判断起始标志 21 if(msg[RxCounter1-1]=='F') //判断结束标志是否是"F" 22 23 { 24 for(i=0; i< RxCounter1; i++){ 25 TxBuffer1[i] =msg[i]; //将接收缓冲器的数据转到发送缓冲区,准备转发 26 27 } 28 29 TxBuffer1[RxCounter1]=0; //接收缓冲区终止符 30 RxCounter1=0; 31 //OSSemPost(Com1_SEM); 32 OSMboxPost(Com1_MBOX,(void *)&msg); 33 } 34 } 35 if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) // 36 { 37 USART_ITConfig(USART1, USART_IT_TXE, DISABLE); 38 } 39 OSIntExit(); //在os_core.c文件里定义,如果有更高优先级的任务就绪了,则执行一次任务切换 40 }
- 这里接收串口数据,当发现是完整的帧时,就调用OSMboxPost(Com1_MBOX,(void *)&msg);发送一个邮箱消息,进而那边的串口任务从挂起到唤醒,执行相应的过程。
3、硬件初始化部分
说了这么多,竟然忘了说硬件初始化的部分啦!这里包括系统时钟设置、引脚使能、中断使能...放在bsp.c文件里,在main函数开始直接调用BSP_Init();就可以。
1 void BSP_Init(void) 2 { 3 /* System Clocks Configuration --72M*/ 4 RCC_Configuration(); 5 GPIO_Configuration(); 6 /* NVIC configuration */ 7 /*嵌套向量中断控制器 8 说明了USART1抢占优先级级别0(最多1位) ,和子优先级级别0(最多7位) */ 9 NVIC_Configuration(); 10 USART_Config(USART1,115200); //串口1初始化 11 }
1 void RCC_Configuration(void) 2 { 3 SystemInit(); 4 }
1 void GPIO_Configuration(void) 2 { 3 GPIO_InitTypeDef GPIO_InitStructure; 4 5 /*对控制LED指示灯的IO口进行了初始化,将端口配置为推挽上拉输出,口线速度为50Mhz。PA9,PA10端口复用为串口1的TX,RX。 6 在配置某个口线时,首先应对它所在的端口的时钟进行使能。否则无法配置成功,由于用到了端口B,D,E, 因此要对这几个端口的时钟 7 进行使能,同时由于用到复用IO口功能用于配置串口。因此还要使能AFIO(复用功能IO)时钟。*/ 8 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOB , ENABLE); 9 10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1 11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 12 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 13 GPIO_Init(GPIOB, &GPIO_InitStructure); 14 15 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_3; //LED2, LED3 16 GPIO_Init(GPIOD, &GPIO_InitStructure); 17 }
1 void NVIC_Configuration(void) 2 { 3 4 //EXTI_InitTypeDef EXTI_InitStructure; 5 NVIC_InitTypeDef NVIC_InitStructure; 6 7 /* Configure one bit for preemption priority */ 8 #if defined (VECT_TAB_RAM) 9 /* Set the Vector Table base location at 0x20000000 */ 10 NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); 11 #elif defined(VECT_TAB_FLASH_IAP) 12 NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x2000); 13 #else /* VECT_TAB_FLASH */ 14 /* Set the Vector Table base location at 0x08000000 */ 15 NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); 16 #endif 17 18 /* Configure the NVIC Preemption Priority Bits */ 19 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); 20 21 22 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //设置串口1中断 23 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 24 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 25 NVIC_Init(&NVIC_InitStructure); 26 27 }
1 void USART_Config(USART_TypeDef* USARTx,u32 baud){ 2 USART_InitTypeDef USART_InitStructure; 3 GPIO_InitTypeDef GPIO_InitStructure; 4 5 //PA9,PA10 复用IO口功能用于配置串口。因此要使能AFIO(复用功能IO)时钟。 6 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 7 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); 8 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); 9 10 //usart_init---------------------------------------------------- 11 /* Configure USART1 Rx (PA.10) as input floating */ 12 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; 13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式 14 GPIO_Init(GPIOA, &GPIO_InitStructure); 15 16 /* Configure USART1 Tx (PA.09) as alternate function push-pull */ 17 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 18 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 19 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 20 GPIO_Init(GPIOA, &GPIO_InitStructure); 21 22 23 USART_InitStructure.USART_BaudRate =baud; //速率115200bps 24 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位8位 25 USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1位 26 USART_InitStructure.USART_Parity = USART_Parity_No; //无校验位 27 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控 28 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 29 30 /* Configure USART1 */ 31 USART_Init(USARTx, &USART_InitStructure); //配置串口参数函数 32 33 34 /* Enable USART1 Receive and Transmit interrupts */ 35 USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE); //使能接收中断 36 //USART_ITConfig(USARTx, USART_IT_TXE, ENABLE); //使能发送缓冲空中断 37 38 39 /* Enable the USART1 */ 40 USART_Cmd(USARTx, ENABLE); 41 42 //USART_ClearFlag(USARTx, USART_FLAG_TXE); /* 清发送完成标志,Transmission Complete flag */ 43 }
PS:相关链接
LZ blog:http://www.cnblogs.com/zjutlitao/
工程代码:http://pan.baidu.com/s/1jG850X4