首先是一个对目前写的这些东西的总结。后面我会花大概两篇左右的内容来简单介绍一下STM32的使用。正文部分在后面一点,可以直接往后翻。
截止本篇为止,单片机的基本原理部分已经介绍完毕了。这个系列断断续续写了很久,一开始是2020年上半年疫情期间的一些单片机的心得什么的,后来整理了一下准备作为电子设计创新实验室2020年招新的时候使用。但实际上因为各种乱七八糟的原因就一直整理的很慢,一直到11月底才一次性把硬件概述/C语言/LED数码管/按键一次性发出来。其实外部中断的部分那时候也已经写好了,但是打算等定时器也写好一起发出来。
然后12月就忙着考研去了,考研后又发生了一些emmm。。。反正很影响心态的事情,就一直没有空接着写,然后这么一拖就拖到了2021年。
2021年伊始我找了个地儿上班(实习×摸鱼√),大体上是做一个网站开发,后台基于SpringBoot的框架,管理后台的前端页面,以及用户侧手机访问的前端页面,我会的只有忘的乱七八糟的Java和Mysql数据库,前端的Vue.js啥的全都现学,开发任务又有点紧,于是只能咕咕了。半个月下来代码上手了于是可以开始带薪摸鱼了,于是就继续更新咯。
下面是一个51单片机小练习,有兴趣的可以看一看。
点亮LED流水灯与数码管,数码管循环移位显示一组八位数字(如8位数码管显示2020216000并滚动)。同时每一位数字对应一个LED灯,奇数亮,偶数灭。通过按动矩阵键盘改变某一位数字的显示。设定定时刷新,运行状态中每3秒钟数字归位为初始状态,闪烁三下(亮-灭)然后继续滚动。
4x4键盘功能如下:
(如8位数字显示2020216000滚动时,可按暂停以停止滚动,按编辑使得第一位数字开始闪烁,左右移动使得闪烁的数字左右切换。选中数字6时按下7则显示为2020217000,按保存之后再按运行,则滚动显示2020217000。在运行状态下,无论滚动到什么位置,比如2160002020,计时器计时到3秒时立刻切换至2020216000显示并亮-灭闪烁三次,然后从2020216000开始,恢复滚动状态。所有过程中led灯均同步变化。)
数码管可换用LCD1602或12864进行显示,附加功能要求包括通过外置EEPROM实现数据的掉电存储以及与电脑(上位机)的串口通信收发修改数据。希望大家通过这个简单的练习,能够变得更强。后面有空更新的时候会讲一讲思路,画画流程图什么的。
μC/OS-II 的前身是μC/OS,最早出自于1992年美国嵌入式系统专家Jean J.Labrosse 在《嵌入式系统编程》杂志的5月和6月刊上刊登的文章连载,并把μC/OS 的源码发布在该杂志的BBS 上。
μC/OS 和μC/OS-II 是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C语言编写的。CPU硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的CPU 上。用户只要有标准的ANSI的C交叉编译器,有汇编器、连接器等软件工具,就可以将μC/OS-II嵌入到开发的产品中。μC/OS-II具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点,最小内核可编译至2KB 。μC/OS-II 已经移植到了几乎所有知名的CPU上。
严格地说μC/OS-II只是一个实时操作系统内核,它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。没有提供输入输出管理,文件系统,网络等额外的服务。但由于μC/OS-II良好的可扩展性和源码开放,这些非必须的功能完全可以由用户自己根据需要分别实现。
uC/OS-II目标是实现一个基于优先级调度的抢占式的实时内核,并在这个内核之上提供最基本的系统服务,如信号量,邮箱,消息队列,内存管理,中断管理等。
首先先介绍嵌入式操作系统里面的几个概念。首先是操作系统里面的基本单位:任务。每一个任务是单独的程序或者代码段,互相之间不能同时运行(对于单核CPU)。任务通过任务控制块进行调度和切换,在嵌入式操作系统中任务存在五种状态:休眠态,就绪态,运行态,挂起态和被中断态。
当任务创建时,任务驻留内存但并不调用,此时即处于休眠态。当任务将要被调用时,任务进入就绪态,在就绪队列中按优先级进行排序。当当前运行的任务结束,就绪队列中最靠前的任务进入运行态。运行过程中,若高优先级任务抢占,则当前任务退回就绪态;若发生中断进入被中断态,中断返回后继续运行态;若需要等待某事发生,进入挂起态,等待结束后重新进入就绪态在就绪队列中排队等待运行。μC/OS-II是可剥夺型内核,高优先级任务可抢占正在运行的任务运行。
任务通常是进程或者线程。进程是一个可独立拥有资源、可以与其它进程并发执行的计算部分的基本单位,包含程序、数据集合和进程控制块。线程是进程中的一个实体,是CPU调度和分配的基本单位,它基本上不拥有资源,只拥有维持运行的最少资源(如寄存器、堆栈、程序计数器等),可以共享同一进程中的所有资源并并发执行,但不可以脱离进程独立运行。我们常说的多线程就基于这一概念。
任务的切换通过多种调度算法实现,调度不当可能会导致死锁,即两个或者更多的任务相互等待对方占有的资源而无限期地僵持下去的局面。任务具有优先级,在特定情况下,任务优先级可能反转,高优先级可能低于低优先级。以上都需要通过一定策略来进行规避。
嵌入式实时操作系统中的任务间通信一般通过发送事件来实现。常见的事件包括:信号量、互斥信号量、消息邮箱、消息队列、事件标志组。信号量和互斥信号量常用于任务调度,也称PV操作,常见的PV操作问题有生产者-消费者问题、哲学家就餐问题等。消息邮箱是一种通信机制,可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量,该指针指向一个包含了特定“消息”的数据结构。邮箱发送的不是消息本身,而是指向消息的指针,指针指向的内容就是那则消息。消息队列是一种以消息链表的方式进行通信的机制,它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量,指针指向的内容就是那则消息,本质上是一个消息邮箱阵列。这两种都是常用的进程间通信手段。事件标志组是一种多个事件组合的通信机制,主要用于一个任务与多个事件的同步,包括独立型同步和关联型同步。
任务间通信的最简单方法就是使用共享数据结构或者共享变量,为了防止任务在使用共享资源时破坏数据,资源必须独占使用。这种独占使用资源的方法称为互斥,互斥的本质是为了有序地利用资源。实现互斥常用的方法主要有:禁止中断和抢占、利用信号位以及测试置位。
关于具体的实现过程此处并不详细介绍。详细的内容请查阅相关资料。下面,仅通过一个示例来演示嵌入式μC/OS-II操作系统的使用。
这是一个非常简单的小型程序,使用嵌入式μC/OS-II操作系统实现对ADC的电压读取并处理成PWM占空百分比输出控制电机运转。
由于嵌入式操作系统空间占用很大,此处利用P0和P2部分位外扩了一个总大小8k的6264SRAM,其中A12未接,实际外扩4k,实际使用xdata=3938。在真实单片机上使用嵌入式操作系统时也需要注意这一问题,大部分单片机SPAM在2k以内,也需要外扩SRAM方可使用。
这样一个程序包括以下部分:
void main(){
OSInit(); //系统初始化
InitTimer0();//定时器0初始化(用于启动操作系统)
OSTaskCreate(TaskInit,(void *)0,&TaskStk[0],8);//建立初始化任务
OSStart(); //系统启动
}
建立一个任务需要有任务内容,任务参数,任务栈顶指针和任务优先级。任务堆栈定义为堆栈数组形式:如OS_STK TaskStk[150];
初始化任务内容如下:
void TaskInit(void *ppdata) reentrant
{
Lcd1602Init();//LCD1602初始化
Lcd1602ShowStr(4, 1, "Welcome!");
Lcd1602ShowStr(1, 0, "UCOS-II V2.6.1");
InitTimer1();//定时器1初始化(用于启动电机)
OSTimeDlyHMSM (0, 0, 1, 0); //延时1秒
AckMbox = OSMboxCreate((void *)0);//建立消息邮箱
ADMbox = OSMboxCreate((void *)0);//建立消息邮箱
OSTaskCreate(Task1, (void *)0, &Task1Stk[0], 10);//建立任务
OSTaskCreate(Task2, (void *)0, &Task2Stk[0], 12);//建立任务
OSTaskCreate(Task3, (void *)0, &Task3Stk[0], 14);//建立任务
OSTaskCreateExt( //建立扩展任务
WorkLedCtrl, //任务名称
(void *)0, //任务参数
&Task0Stk[0], //任务栈顶指针
15, //任务优先级
15, //任务id
&Task0Stk[149], //任务栈底指针
150, //任务栈大小
(void *)0, //额外参数
OS_TASK_OPT_STK_CHK+OS_TASK_OPT_STK_CLR);//选项
OSTaskSuspend(OS_PRIO_SELF); //自身挂起
}
建立一个消息邮箱只需要你初始放入的消息指针,如果没有,则置为0。建立任务的扩展函数有更多的细节,但不常用。初始函数使用完毕后自身挂起,参数为需要挂起的任务的优先级,此处为任务自身优先级即OS_PRIO_SELF。
消息邮箱的收发函数分别为