STM32F4ucossystem.docx
《STM32F4ucossystem.docx》由会员分享,可在线阅读,更多相关《STM32F4ucossystem.docx(16页珍藏版)》请在冰豆网上搜索。
STM32F4ucossystem
1、内核简介
我使用的是STM公司生产的STM32F407Discovery开发板,其CPU内核是ARM公司车技COTEX-M4内核。
Cortex-M4处理器具有大量高效的信号处理功能,可满足数字信号控制市场的需要。
Cortex-M4处理器采用扩展单周期乘法累加(MAC)指令、优化的SIMD算术、饱和算术指令以及一个可选的单精度浮点单元(FPU)。
其最高的主频可以到达168Mhz,非常实用与信号处理等方面的控制。
ARMCortex微控制器软件接口标准(CMSIS)是Cortex-M处理器系列的与供应商无关的硬件抽象层。
CMSIS可为接口外设、实时操作系统和中间件实现一致且简单的处理器软件接口,简化了软件的重用。
相对于cotex-m3,内核优势:
M4内核主频更高,可以达到168Mhz,而M3内核最高只有73Mhz,更高的主频就是意味着有更高的运算速度,可以再更短的时间里面完成更多的事情。
其次,M4内核还有一个浮点处理单元,这是M3内核所不具有的,这个优势将是一个翻天覆地的差异。
我们都知道在单片机里面如果遇到一个浮点数的计算将是非常痛苦的一件事情,但是有了一个浮点处理单元过后,浮点运算的周期可以大幅减少,对于数字信号处理来时无疑是一个天大的福音。
最后,价格上的优势,M4和M3内核的处理器几个几乎不相上下。
当然,除此之外,M4较M3内核有个很大的缺点,就是上市时间较短,还没有得到人们的认同。
而且现在市面的参考程序大都是M3内核基础上开发,而M4内核的信息或者可愿意参考的东西变得很少,对于自己学习开发的人来说是一个很不好的因素。
2、uCOSII系统简介
uCOSII系统适用于没有MCU的处理器上面的实时操作系统。
它是一种可移植的,可植入ROM的,可裁剪的,抢占式的,实时多任务操作系统内核。
μC/OS和μC/OS-II是专门为计算机的嵌入式应用设计的,绝大部分代码是用C语言编写的。
CPU硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的CPU上。
严格地说uC/OS-II只是一个实时操作系统内核,它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。
没有提供输入输出管理,文件系统,网络等额外的服务。
uC/OS-II中最多可以支持64个任务,分别对应优先级0~63,其中0为最高优先级。
63为最低级,系统保留了4个最高优先级的任务和4个最低优先级的任务,所有用户可以使用的任务数有56个。
uC/OS-II提供了任务管理的各种函数调用,包括创建任务,删除任务,改变任务的优先级,任务挂起和恢复等。
系统初始化时会自动产生两个任务:
一个是空闲任务,它的优先级最低,该任务仅给一个整型变量做累加运算;另一个是统计任务,它的优先级为次低,该任务负责统计当前cpu的利用率。
uC/OS-II的时间管理是通过定时中断来实现的,该定时中断一般为10毫秒或100毫秒发生一次,时间频率取决于用户对硬件系统的定时器编程来实现。
中断发生的时间间隔是固定不变的,该中断也成为一个时钟节拍。
uC/OS-II要求用户在定时中断的服务程序中,调用系统提供的与时钟节拍相关的系统函数,例如中断级的任务切换函数,系统时间函数。
uC/OS-II可以大致分成核心、任务处理、时间处理、任务同步与通信,CPU的移植等5个部分。
1)核心部分(OSCore.c):
是操作系统的处理核心,包括操作系统初始化、操作系统运行、中断进出的前导、时钟节拍、任务调度、事件处理等多部分。
能够维持系统基本工作的部分都在这里。
2)任务处理部分(OSTask.c):
任务处理部分中的内容都是与任务的操作密切相关的。
包括任务的建立、删除、挂起、恢复等等。
因为μC/OS-II是以任务为基本单位调度的,所以这部分内容也相当重要。
3)时钟部分(OSTime.c):
μC/OS-II中的最小时钟单位是timetick(时钟节拍)。
任务延时等操作是在这里完成的。
4)任务同步和通信部分:
为事件处理部分,包括信号量、邮箱、消息队列、事件标志等部分;主要用于任务间的互相联系和对临界资源的访问。
5)与CPU的接口部分:
是指μC/OS-II针对所使用的CPU的移植部分。
三、开发环境搭建:
我的电脑上的开发可以开发cotex-M4的环境有两个,一个是Keil一个是IAR。
因为从一开始接触到Cotex-m4开始就是使用的Iar开发,主要是因为Iar这个软件相对于Keil来说最后声称的.out或者说是.hex或者是.bin文件更小,就是说用IAR编译的效率更高。
但是由于用IAR开发的原因,走了许多许多的弯路,主要还是因为现在互联网上的开发历程都是基于Keil软件的,而且大都是基于M3内核的。
这里我们使用IAR为例讲述怎么建立一个STM32f4的project:
1、新建一个文件夹,命名为这个工程的名字,在这个文件夹里面创建工程,然后我们要在此文件夹里面新建三个文件夹,分别命名为src,inc,startup,结果如图所示:
;
2、从上面我们可以知道CMSIS是cotex系列的开发软件接口,因此我们需要把相关的文件或者是库函数放到我们的工程目录中:
Inc文件夹:
放置库函数的头文件
User文件夹:
放置库函数的c语言文件以及用户自己创立的文件
StartUp文件夹:
放置处理器一开始自启动时候的汇编代码
3、打开Iar,电机Project->Creatnewprject,出现下面对话框
点击ok,并将工程放到我们刚才建立的文件夹当中,我们便建立好了一个工程。
4、点击File下面的工程,点击Add,AddGroup,创建三个类分别是Starup,user,inc。
然后分别点击三个类添加file,将我们刚才文件夹里面添加的问价全部一一对应的project的虚拟目录中去。
5、点击option,打开C/c++Compler,打开Prerocessor按照下图设置
打开Iinker选项,进行设置,如图
Debuger选择STlink,在STlink中选择SWD方式,工程设置结束。
3、uCos操作系统开发
1、ucos代码移植,因为现在的网上大都是用的M3内核的ucos的移植,对于M4内核现在移植还不是很成熟,所以使用基于M3的ucos进行开发,因为M3和M4在很大程度还是具有很大的相似性。
2、更改到适合M4内核,首先它的c语言的大部分代码都是脱离硬件的,因此可以不用更改,唯独有一个OS_CPU_A.asm文件是一个汇编文件,里面对m4内核的寄存器设置需要更改,主要更改的地方有这么几个:
OSStartHighRdy
LDRR0,=NVIC_SYSPRI14;SetthePendSVexceptionpriority
LDRR1,=NVIC_PENDSV_PRI
STRBR1,[R0]
MOVSR0,#0;SetthePSPto0forinitialcontextswitchcall
MSRPSP,R0
LDRR0,=OSRunning;OSRunning=TRUE
MOVSR1,#1
STRBR1,[R0]
LDRR0,=NVIC_INT_CTRL;TriggerthePendSVexception(causescontextswitch)
LDRR1,=NVIC_PENDSVSET
STRR1,[R0]
CPSIEI;Enableinterruptsatprocessorlevel
OSStartHang
BOSStartHang;Shouldnevergethere
OSCtxSw
LDRR0,=NVIC_INT_CTRL;TriggerthePendSVexception(causescontextswitch)
LDRR1,=NVIC_PENDSVSET
STRR1,[R0]
BXLR
其中有个NVIC_INT_CTRL以及NVIC_PENDSVSET都是需要更改其中这些变量定义是:
NVIC_INT_CTRLEQU0xE000ED04;Interruptcontrolstateregister.
NVIC_SYSPRI14EQU0xE000ED22;Systempriorityregister(priority14).
NVIC_PENDSV_PRIEQU0xFF;PendSVpriorityvalue(lowest).
NVIC_PENDSVSETEQU0x10000000;ValuetotriggerPendSVexception.
3、Ucos操作系统启动过程:
在经过硬件初始化以及始终设置(这些都是在startup_stm32f4xx.s这个汇编代码和startup_stm32f4xx.c文件中进行设置的),经过一个OSInit();的函数进行系统的初始化。
其代码如下
voidOSInit(void)
{
OSInitHookBegin();/*Callportspecificinitializationcode*/
OS_InitMisc();/*Initializemiscellaneousvariables*/
OS_InitRdyList();/*InitializetheReadyList*/
OS_InitTCBList();/*InitializethefreelistofOS_TCBs*/
OS_InitEventList();/*InitializethefreelistofOS_EVENTs*/
#if(OS_FLAG_EN>0u)&&(OS_MAX_FLAGS>0u)
OS_FlagInit();/*Initializetheeventflagstructures*/
#endif
#if(OS_MEM_EN>0u)&&(OS_MAX_MEM_PART>0u)
OS_MemInit();/*Initializethememorymanager*/
#endif
#if(OS_Q_EN>0u)&&(OS_MAX_QS>0u)
//OS_QInit();/*Initializethemessagequeuestructures*/
#endif
//OS_InitTaskIdle();/*CreatetheIdleTask*/
#ifOS_TASK_STAT_EN>0u
OS_InitTaskStat();/*CreatetheStatisticTask*/
#endif
#ifOS_TMR_EN>0u
OSTmr_Init();/*InitializetheTimerManager*/
#endif
OSInitHookEnd();/*Callportspecificinit.code*/
#ifOS_DEBUG_EN>0u
OSDebugInit();
#endif
}
此函数是OS运行的第一个函数,它完成各初始变量的初始化,建立任务空闲键表OSTCBFreeList,建立事件空闲键表OSEventFreeList.创建一个空闲任务OS_TaskIdle,以及初始化其它如果有用到的功能Q,MEM等。
我个人的理解也是从这里开始的。
通过空闲任务的创建基本上涉及到OS中任务块和事件块的管理,同时还对系统中的一些端口进行了初始化。
4、Ucos操作系统的任务建立:
Ucos是一个实时的多任务抢占式的系统,因此对于任务的建立显得尤为重要。
建立任务的函数有两个OSTaskCreateExt和OSTaskCreate,第一个函数定义如下:
INT8UOSTaskCreateExt(void(*task)(void*p_arg),
void*p_arg,
OS_STK*ptos,
INT8Uprio,
INT16Uid,
OS_STK*pbos,
INT32Ustk_size,
void*pext,
INT16Uopt)
这个函数的调用者:
任务或者是启动代码,但是不可以在中断中调用。
此函数建立一个新文件,是OSTaskCreate()的升级版。
参数:
task指向任务代码的指针。
p_arg指向一个类型可以选择的数据区,很多时候是(void*)0。
ptos指向任务堆栈栈顶的指针。
任务堆栈用于保存局部变量、函数参数、返回地址及中断时cpu寄存器的状态。
如果初始化常量OS_STK_GROWTH设为1(在os_cpu.h中定义),将堆栈设为StackgrowsfromHIGHtoLOWmemoryonARM,此时ptos应该指向任务堆栈空间的最高地址。
prio任务的优先级。
数字越小,优先级越高。
id任务的标识,可以设置同优先级。
pbos指向堆栈低端的指针。
stk_size指定任务堆栈的大小。
pext定义数据结构的指针,很多时候是(void*)0。
opt存放与任务相关的操作信息。
另外一个建立任务的函数是OSTaskCreate,它的简单的一个版本其中变量的定义和上面的一致,因此不再陈述,下面放上这个函数完整的代码:
INT8UOSTaskCreate(void(*task)(void*p_arg),void*p_arg,OS_STK*ptos,INT8Uprio)
{
OS_STK*psp;
INT8Uerr;
#ifOS_CRITICAL_METHOD==3u/*AllocatestorageforCPUstatusregister*/
OS_CPU_SRcpu_sr=0u;
#endif
#ifdefOS_SAFETY_CRITICAL_IEC61508
if(OSSafetyCriticalStartFlag==OS_TRUE){
OS_SAFETY_CRITICAL_EXCEPTION();
return(OS_ERR_ILLEGAL_CREATE_RUN_TIME);
}
#endif
#ifOS_ARG_CHK_EN>0u
if(prio>OS_LOWEST_PRIO){/*Makesurepriorityiswithinallowablerange*/
return(OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if(OSIntNesting>0u){/*Makesurewedon'tcreatethetaskfromwithinanISR*/
OS_EXIT_CRITICAL();
return(OS_ERR_TASK_CREATE_ISR);
}
if(OSTCBPrioTbl[prio]==(OS_TCB*)0){/*Makesuretaskdoesn'talreadyexistatthispriority*/
OSTCBPrioTbl[prio]=OS_TCB_RESERVED;/*Reservetheprioritytopreventothersfromdoing...*/
/*...thesamethinguntiltaskiscreated.*/
OS_EXIT_CRITICAL();
psp=OSTaskStkInit(task,p_arg,ptos,0u);/*Initializethetask'sstack*/
err=OS_TCBInit(prio,psp,(OS_STK*)0,0u,0u,(void*)0,0u);
if(err==OS_ERR_NONE){
if(OSRunning==OS_TRUE){/*Findhighestprioritytaskifmultitaskinghasstarted*/
OS_Sched();
}
}else{
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio]=(OS_TCB*)0;/*Makethispriorityavailabletoothers*/
OS_EXIT_CRITICAL();
}
return(err);
}
OS_EXIT_CRITICAL();
return(OS_ERR_PRIO_EXIST);
}
理论上,Ucos系统还有非常多的机制值得讨论,多任务的抢占的机制,系统时间的生成,系统错误以及矫正等等,限于篇幅,这里就不在一一阐述。
从实际出发,我们可以不去重视这个系统的内部机制具体是什么,只需要了解他的工作原理就可以开始编写程序,设定合适的堆栈等。
4、我的应用程序:
我的应用程序是通过stm32F4播放一段音频,并且有个TFT显示器进行显示。
1、应用程序接口:
希望把我的应用程序嵌入到Ucos系统当中去需要按照Ucos系统的要求制定一个软件接口:
我的软件接口代码如下:
staticvoidApp_TaskCreate(void)
{
INT8Uos_err;
os_err=OSTaskCreate((void(*)(void*))App_TaskMUSIC,(void*)0,
(OS_STK*)&App_TaskMUSICStk[APP_TASK_MUSIC_STK_SIZE-1],
(INT8U)APP_TASK_LCD_PRIO);
}
我调用的是简单化的任务创建函数进行任务生成,其中一些具体变量说明如下:
●App_TaskMUSIC:
是一个指向我实际函数的函数指针,将这个函数传递给任务生成函数,达到创建任务的目的。
●(void*)0:
这个变量代表一个类型可以选择的数据区,这里我给的变量是(void*)0,表示这个任务没有一个数据区域。
●&App_TaskMUSICStk[APP_TASK_MUSIC_STK_SIZE-1]:
表示的这个任务所拥有的堆栈的大小,其中这个堆栈是从高地址向低地址递增的堆栈,App_TaskMUSICStk是栈顶的指针,APP_TASK_MUSIC_STK_SIZE表示的是这个任务堆栈的大小,在这里我设置APP_TASK_MUSIC_STK_SIZE是128byte。
●APP_TASK_LCD_PRIO:
便是这个任务的优先级,我设定的是5。
通过以上的设置从理论上我们便可以创建出一个任务了。
2、我的音频播放:
我的音频播放是将音频信号量化后储存在flash中,然后通过AD转换并且放大后放到耳机上面,达到音乐播放的效果。
我的音频播放的顶层函数是:
WavePlayBack(I2S_AudioFreq_48k);
主要代码如下:
voidWavePlayBack(uint32_tAudioFreq)
{
AudioPlayStart=1;
RepeatState=0;
WavePlayerInit(AudioFreq);/*Initializewaveplayer(Codec,DMA,I2C)*/
AudioFlashPlay((uint16_t*)(AUDIO_SAMPLE+AUIDO_START_ADDRESS),AUDIO_FILE_SZE,AUIDO_START_ADDRESS);
LED_Toggle=6;/*LEDBlueStarttoggling*/
while
(1)/*Infiniteloop*/
{
/*checkontherepeatestatus*/
if(RepeatState==0)
{
if(PauseResumeStatus==0)
{
LED_Toggle=0;/*LEDBlueStopToggling*/
WavePlayerPauseResume(PauseResumeStatus);/*Pauseplaying*/
PauseResumeStatus=2;
}
elseif(PauseResumeStatus==1)
{
LED_Toggle=6;/*LEDBlueToggling*/
WavePlayerPauseResume(PauseResumeStatus);/*Resumeplaying*/
PauseResumeStatus=2;
}
}
else
{
WavePlayerStop();/*Stopplaying*/
LED_Toggle=4;/*GreenLEDtoggling*/
}
}
}
,太过于硬件设置的顶层函数就不多讲了,但是顶层的有这么几个函数需要介绍一下:
●WavePlayerInit(AudioFreq):
AudioFreq定义的是((uint32_t)48000),表示IIS的传输速度是48Khz。
这个函数的功能主要是为播放做准备包括硬件系统的初始化:
包括GPIO端口,IIS传输总行设置,端口和功能的时钟开启,NVIC中断,AD功能的设置等。
●AudioFlashPlay((uint16_t*)(AUDIO_SAMPLEAUIDO_START_ADDRESS),AUDIO_FILE_SZE,AUIDO_START_ADDRESS):
表示开始播放音频:
穿进去的变量是储存音频信号的地址。
经过这么设置过后就可以完成音频播放的功能。
3、24万色TFT显示的程序设置:
顶层调用的函数主要有这么两个:
LCD_Init();
LCD_Clear(WHITE);
LCD_ShowString(30,50,"embededclass^_^");
这三个函数的功能是:
●LCD_Clear(WHITE);:
首先清除显示屏的信息,将背景显示为指定的颜色;
●LCD_Init();:
完成硬件的初始化和寄存器配置;
●LCD_ShowString(30,50,"embededclass^_^");:
从像素点(30,50)写入指定的内容;
我使用的TFT驱动芯片是ILI9328,使用的是16位数据线驱动的方式;
其中LCD_Init();函数代码如下:
voidLCD_Init(void)
{
TFT_ModuleInit();
delay_ms(50);//del