2015-01-18 12:30:23 wangshuwang 阅读数 3697
  • 单片机到底是个什么东西-1.2.第1季第2部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第2个课程,用通俗易懂的语言讲了很多和单片机有关的技术概念,如CPU、ROM、RAM、外设、电路板、软件硬件工作的差别等。目的是希望大家在轻松愉悦的氛围中对单片机加深认识。

    3038 人正在学习 去看看 朱有鹏


       时间片轮询法,在很多书籍中有提到,而且有很多时候都是与操作系统一起出现,也就是说很多时候是操作系统中使用了这一方法。不过我们这里要说的这个时间片轮询法并不是挂在操作系统下,而是在前后台程序中使用此法。也是本贴要详细说明和介绍的方法。

 

       对于时间片轮询法,虽然有不少书籍都有介绍,但大多说得并不系统,只是提提概念而已。下面本人将详细介绍本人模式,并参考别人的代码建立的一个时间片轮询架构程序的方法,我想将给初学者有一定的借鉴性。

 

使用1个定时器,可以是任意的定时器,这里不做特殊说明,下面假设有3个任务,那么我们应该做如下工作:

 

1. 初始化定时器,这里假设定时器的定时中断为1ms(当然你可以改成10ms,这个和操作系统一样,中断过于频繁效率就低,中断太长,实时性差)。

 

2. 定义一个数值:

 

  1. #define TASK_NUM   (3)                  //  这里定义的任务数为3,表示有三个任务会使用此定时器定时。

  2.  

  3. uint16 TaskCount[TASK_NUM] ;           //  这里为三个任务定义三个变量来存放定时值

  4. uint8  TaskMark[TASK_NUM];             //  同样对应三个标志位,为0表示时间没到,为1表示定时时间到。

复制代码

3. 在定时器中断服务函数中添加:

  1. /**************************************************************************************
    * FunctionName : TimerInterrupt()
    * Description : 定时中断服务函数
    * EntryParameter : None
    * ReturnValue : None
    **************************************************************************************/
    void TimerInterrupt(void)
    {
        uint8 i;

        for (i=0; i<TASKS_NUM; i++) 
        {
            if (TaskCount[i]) 
            {
                  TaskCount[i]--; 
                  if (TaskCount[i] == 0) 
                  {
                        TaskMark[i] = 0x01; 
                  }
            }
       }
    }
复制代码

 

代码解释:定时中断服务函数,在中断中逐个判断,如果定时值为0了,表示没有使用此定时器或此定时器已经完成定时,不着处理。否则定时器减一,知道为零时,相应标志位值1,表示此任务的定时值到了。

 

4. 在我们的应用程序中,在需要的应用定时的地方添加如下代码,下面就以任务1为例:

  1. TaskCount[0] = 20;       // 延时20ms

  2. TaskMark[0]  = 0x00;     // 启动此任务的定时器

复制代码

到此我们只需要在任务中判断TaskMark[0] 是否为0x01即可。其他任务添加相同,至此一个定时器的复用问题就实现了。用需要的朋友可以试试,效果不错哦。。。。。。。。。。。

 

通过上面对1个定时器的复用我们可以看出,在等待一个定时的到来的同时我们可以循环判断标志位,同时也可以去执行其他函数。

 

循环判断标志位:

那么我们可以想想,如果循环判断标志位,是不是就和上面介绍的顺序执行程序是一样的呢?一个大循环,只是这个延时比普通的for循环精确一些,可以实现精确延时。

 

执行其他函数:

那么如果我们在一个函数延时的时候去执行其他函数,充分利用CPU时间,是不是和操作系统有些类似了呢?但是操作系统的任务管理和切换是非常复杂的。下面我们就将利用此方法架构一直新的应用程序。

 

时间片轮询法的架构:

 

1.设计一个结构体:

 

  1. // 任务结构
    typedef struct _TASK_COMPONENTS
    {
        uint8 Run;                 // 程序运行标记:0-不运行,1运行
        uint8 Timer;              // 计时器
        uint8 ItvTime;              // 任务运行间隔时间
        void (*TaskHook)(void);    // 要运行的任务函数
    } TASK_COMPONENTS;       // 任务定义

复制代码

 

这个结构体的设计非常重要,一个用4个参数,注释说的非常详细,这里不在描述。

 

2. 任务运行标志出来,此函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数,这里独立出来,并于移植和理解。

 

  1. /**************************************************************************************
    * FunctionName   : TaskRemarks()
    * Description    : 任务标志处理
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskRemarks(void)
    {
        uint8 i;

  2.     for (i=0; i<TASKS_MAX; i++)          // 逐个任务时间处理
        {
             if (TaskComps[i].Timer)          // 时间不为0
            {
                TaskComps[i].Timer--;         // 减去一个节拍
                if (TaskComps[i].Timer == 0)       // 时间减完了
                {
                     TaskComps[i].Timer = TaskComps[i].ItvTime;       // 恢复计时器值,从新下一次
                     TaskComps[i].Run = 1;           // 任务可以运行
                }
            }
       }
    }

复制代码

 

大家认真对比一下次函数,和上面定时复用的函数是不是一样的呢?

 

3. 任务处理

 

  1. /**************************************************************************************
    * FunctionName   : TaskProcess()
    * Description    : 任务处理
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskProcess(void)
    {
        uint8 i;

  2.     for (i=0; i<TASKS_MAX; i++)           // 逐个任务时间处理
        {
             if (TaskComps[i].Run)           // 时间不为0
            {
                 TaskComps[i].TaskHook();         // 运行任务
                 TaskComps[i].Run = 0;          // 标志清0
            }
        }   
    }

复制代码

 

此函数就是判断什么时候该执行那一个任务了,实现任务的管理操作,应用者只需要在main()函数中调用此函数就可以了,并不需要去分别调用和处理任务函数。

 

到此,一个时间片轮询应用程序的架构就建好了,大家看看是不是非常简单呢?此架构只需要两个函数,一个结构体,为了应用方面下面将再建立一个枚举型变量。

 

下面我就就说说怎样应用吧,假设我们有三个任务:时钟显示,按键扫描,和工作状态显示。

 

1. 定义一个上面定义的那种结构体变量

  1. /**************************************************************************************
    * Variable definition                            
    **************************************************************************************/
    static TASK_COMPONENTS TaskComps[] = 
    {
        {0, 60, 60, TaskDisplayClock},            // 显示时钟
        {0, 20, 20, TaskKeySan},               // 按键扫描
        {0, 30, 30, TaskDispStatus},            // 显示工作状态

  2.      // 这里添加你的任务。。。。

  3. };

复制代码

 

在定义变量时,我们已经初始化了值,这些值的初始化,非常重要,跟具体的执行时间优先级等都有关系,这个需要自己掌握。

 

①大概意思是,我们有三个任务,没1s执行以下时钟显示,因为我们的时钟最小单位是1s,所以在秒变化后才显示一次就够了。

②由于按键在按下时会参数抖动,而我们知道一般按键的抖动大概是20ms,那么我们在顺序执行的函数中一般是延伸20ms,而这里我们每20ms扫描一次,是非常不错的出来,即达到了消抖的目的,也不会漏掉按键输入。

③为了能够显示按键后的其他提示和工作界面,我们这里设计每30ms显示一次,如果你觉得反应慢了,你可以让这些值小一点。后面的名称是对应的函数名,你必须在应用程序中编写这函数名称和这三个一样的任务。

 

2. 任务列表

  1. // 任务清单
    typedef enum _TASK_LIST
    {
        TAST_DISP_CLOCK,            // 显示时钟
        TAST_KEY_SAN,             // 按键扫描
        TASK_DISP_WS,             // 工作状态显示
         // 这里添加你的任务。。。。
         TASKS_MAX                                           // 总的可供分配的定时任务数目
    } TASK_LIST;

复制代码

 

好好看看,我们这里定义这个任务清单的目的其实就是参数TASKS_MAX的值,其他值是没有具体的意义的,只是为了清晰的表面任务的关系而已。

 

3. 编写任务函数

 

  1. /**************************************************************************************
    * FunctionName   : TaskDisplayClock()
    * Description    : 显示任务

  2. * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskDisplayClock(void)
    {

  3.  

  4. }

  5. /**************************************************************************************
    * FunctionName   : TaskKeySan()
    * Description    : 扫描任务
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskKeySan(void)
    {


  6. }

  7. /**************************************************************************************
    * FunctionName   : TaskDispStatus()
    * Description    : 工作状态显示
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskDispStatus(void)
    {


  8. }

  9.  

  10. // 这里添加其他任务。。。。。。。。。

复制代码

 

现在你就可以根据自己的需要编写任务了。

 

4. 主函数

 

  1. /**************************************************************************************
    * FunctionName   : main()
    * Description    : 主函数
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    int main(void) 

        InitSys();                  // 初始化

  2.     while (1)
        {
            TaskProcess();             // 任务处理
        }
    }

复制代码

 

到此我们的时间片轮询这个应用程序的架构就完成了,你只需要在我们提示的地方添加你自己的任务函数就可以了。是不是很简单啊,有没有点操作系统的感觉在里面?

2017-07-07 09:24:19 tszy208 阅读数 518
  • 单片机到底是个什么东西-1.2.第1季第2部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第2个课程,用通俗易懂的语言讲了很多和单片机有关的技术概念,如CPU、ROM、RAM、外设、电路板、软件硬件工作的差别等。目的是希望大家在轻松愉悦的氛围中对单片机加深认识。

    3038 人正在学习 去看看 朱有鹏
 总线的概念  总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束, 按照计算机所传输的信息种类,计算机的总线可以划分为数据总线、地址总线和控制总线,分别用来传输数据、数据地址和控制信号。

  总线是一种内部结构,它是cpu、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总 线相连接,从而形成了计算机硬件系统。在计算机系统中,各个部件之间传送信息的公共通路叫总线,微型计算机是以总线结构来连接各个功能部件的。

  工作原理

  当总线空闲(其他器件都以高阻态形式连接在总线上)且一个器件要与目的器件通信时,发起通信的器件驱动总线,发出地址和数据。其他以高阻态形式 连接在总线上的器件如果收到(或能够收到)与自己相符的地址信息后,即接收总线上的数据。发送器件完成通信,将总线让出(输出变为高阻态)。

  总线的分类

  总线按功能和规范可分为三大类型:[1]

  (1) 片总线(Chip Bus, C-Bus) 又称元件级总线,是把各种不同的芯片连接在一起构成特定功能模块(如CPU模块)的信息传输通路。

  (2) 内总线(Internal Bus, I-Bus)

  又称系统总线或板级总线,是微机系统中各插件(模块)之间的信息传输通路。例如CPU模块和存储器模块或I/O接口模块之间的传输通路。

  (3) 外总线(External Bus, E-Bus)

  又称通信总线,是微机系统之间或微机系统与其他系统(仪器、仪表、控制装置等)之间信息传输的通路,如EIA RS-232C、IEEE-488等。

  其中的系统总线,即通常意义上所说的总线,一般又含有三种不同功能的总线,即数据总线DB(Data Bus)、地址总线AB(Address Bus)和控制总线CB(Control Bus)。

  有的系统中,数据总线和地址总线是复用的,即总线在某些时刻出现的信号表示数据而另一些时刻表示地址;而有的系统是分开的。51系列单片机的地址总线和数据总线是复用的,而一般PC中的总线则是分开的。

  “数据总线DB”用于传送数据信息。数据总线是双向三态形式的总线,即他既可以把CPU的数据传送到存储器或I/O接口等其它部 件,也可以将其它部件的数据传送到CPU。数据总线的位数是微型计算机的一个重要指标,通常与微处理的字长相一致。例如Intel 8086微处理器字长16位,其数据总线宽度也是16位。需要指出的是,数据的含义是广义的,它可以是真正的数据,也可以是指令代码或状态信息,有时甚至 是一个控制信息,因此,在实际工作中,数据总线上传送的并不一定仅仅是真正意义上的数据。

  “地址总线AB”是专门用来传送地址的,由于地址只能从CPU传向外部存储器或I/O端口,所以地址总线总是单向三态的,这与数 据总线不同。地址总线的位数决定了CPU可直接寻址的内存空间大小,比如8位微机的地址总线为16位,则其最大可寻址空间为2^16=64KB,16位微 型机(个人觉得很有必要解释下x位处理器的意思:一个时钟周期内微处理器能处理的位数(1 、0)多少,即字长大小)的地址总线为20位,其可寻址空间为2^20=1MB。一般来说,若地址总线为n位,则可寻址空间为2^n字节。

  “控制总线CB”用来传送控制信号和时序信号。控制信号中,有的是微处理器送往存储器和I/O接口电路的,如读/写信号,片选信 号、中断响应信号等;也有是其它部件反馈给CPU的,比如:中断申请信号、复位信号、总线请求信号、设备就绪信号等。因此,控制总线的传送方向由具体控制 信号而定,一般是双向的,控制总线的位数要根据系统的实际控制需要而定。实际上控制总线的具体情况主要取决于CPU。

  按照传输数据的方式划分,可以分为串行总线和并行总线。串行总线中,二进制数据逐位通过一根数据线发送到目的器件;并行总线的数据线通常超过2根。常见的串行总线有SPI、I2C、USB及RS232等。

  按照时钟信号是否独立,可以分为同步总线和异步总线。同步总线的时钟信号独立于数据,而异步总线的时钟信号是从数据中提取出来的。SPI、I2C是同步串行总线,RS232采用异步串行总线。


很多入门的书上基本上都说:由运算器、控制器、存储器、输入设备、输出设备组成的系统 都叫冯氏结构。

也有的说:“程序存储器的数据线地址线”与“数据存储器的数据线地址线”共用的话,就 是冯氏结构,所以51是该结构。(我认为说得太绝对了)

我认为冯氏结构与哈佛结构的区别应该在存储器的空间分别上,哈佛结构的数据区和代码区是分开的,它们即使地址相同,但空间也是不同的,主要表现在数据不能够当作代码来运行。(比如51---)

口线复用,就将它认为成冯氏结构,我认为这样不足取,应该是按照空间是否完全重合来辨 别。比如PC机的代空间和数据空间是同一空间,所以是冯氏结构;51由于IO口不够,但代 码空间和数据空间是分开的,所以还是哈佛构.(此种观点才是正确的--)

另外,还有的把CISC RISC 和地址是否复用与是哪种结构 这3这都混到一起。我认为这三者都没有必然的关系。只不过 RISC因为精简了指令集,没有了执行复杂功能的指令,为了提高性能,常采用哈佛结构,并且不复用地址线。(这种说法不具体,有待补充---)

材料二:

哈佛结构是一种将程序指令存储和数据存储分开的存储器结构。中央处理器首先到程序指令存储器中读取程序指令内容,解码后得到数据地址,再到相应的数据存储器中读取数据,并进行下一步的操作(通常是执行)。程序指令存储和数据存储分开,可以使指令和数据有不同的数据宽度,如Microchip公司的PIC16芯片的程序指令是14位宽度,而数据是8位宽度。

目前使用哈佛结构的中央处理器和微控制器有很多,除了上面提到的Microchip公司的PIC系列芯片,还有摩托罗拉公司的MC68系列、Zilog公司的Z8系列、ATMEL公司的AVR系列和安谋公司的ARM9ARM10ARM1151单片机也属于哈佛结构

冯·诺伊曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,因此程序指令和数据的宽度相同,如英特尔公司的8086中央处理器的程序指令和数据都是16位宽。

目前使用冯·诺伊曼结构的中央处理器和微控制器有很多。除了上面提到的英特尔公司的8086,英特尔公司的其他中央处理器、安谋公司的ARM7MIPS公司的MIPS处理器也采用了冯·诺伊曼结构。

评论:哈佛结构和冯.诺依曼结构都是一种存储器结构。哈佛结构是将指令存储器和数据存储器分开的一种存储器结构;而冯.诺依曼结构将指令存储器和数据存储器合在一起的存储器结构。-----

材料三:

MCS-51单片机有着嵌入式处理器经典的体系结构,这种体系结构在当前嵌入式处理器的高端ARM系 列上仍然在延续,这就是哈佛结构。相对于大名鼎鼎的冯·诺依曼结构,哈佛结构的知名度显然逊色许多,但在嵌入式应用领域,哈佛结构却拥有着绝对的优势。哈 佛结构与冯·诺依曼结构的最大区别在于冯·诺依曼结构的计算机采用代码与数据的统一编址,而哈佛结构是独立编址的,代码空间与数据空间完全分开。

在通用计算机系统中,应用软件的多样性使得计算机要不断地变化所执行的代码的内容,并且频繁地对数据与代码占有的存储器进行重新分配,这种情况下,冯·诺 依曼结构占有绝对优势,因为统一编址可以最大限度地利用资源,而哈佛结构的计算机若应用于这种情形下则会对存储器资源产生理论上最大可达50%的浪费,这显然是不合理的。

但是在嵌入式应用中,系统要执行的任务相对单一,程序一般是固化在硬件里。当然这时使用冯·诺依曼结构也完全可以,代码区和数据区在编译时一次性分配好了而已,但是其灵活性得不到体现,所以现在大量的单片机也还在沿用冯·诺依曼结构,如TIMSP430系列、FreescaleHCS08系列等。

那是为什么说哈佛结构有优势呢?嵌入式计算机在工作时与通用计算机有着一些区别:嵌入式计算机在工作期间的绝大部分时间是无人值守的,而通用计算机工作期 间一般是有人操作的;嵌入式计算机的故障可能会导致灾难性的后果,而通用计算机一般就是死死机,重新启动即可。这两点决定了对嵌入式计算机的一个基本要 求:可靠性。

使用冯·诺依曼结构的计算机,程序空间不封闭,期程序空间的数据在运行期理论上可以被修改,此外程序一旦跑飞也有可能运行到数据区。虽然都是一些不常见的特殊情况下,但是看看哈佛结构德计算机在这些情况下是怎样的:基于哈佛结构的处理器入MCS-51,不需要可以对代码段进行写操作的指令,所以不会有代码区被改写的问题;程序只能在封闭的代码区中运行,不可能跑到数据区,这也是跑飞的几率减少并且跑飞后的行为有规律(数据区的数据是不断变化的而代码区是不变的)。

所以,相对于冯·诺依曼结构,哈佛结构更加适合于那些程序固化、任务相对简单的控制系统。

总结::::::::::

.诺依曼(Von Neumann)指出:程序只是一种(特殊)的数据,它可以像数据一样被处理,因此可以和数据一起被存储在同一个存储器中——这就是著名的冯.诺依曼原理。注意:数据总线和地址总线共用。--

哈佛结构:

哈佛结构是一种并行体系结构,它的主要特点是将程序和数据存储在不同的存储空间中,即程序存储器和数据存储器是两个独立的存储器,每个存储器独立编址、独立访问。与两个存储器相对应的是系统的4条总线:程序的数据总线与地址总线,数据的数据总线与地址总线。这种分离的程序总线和数据总线允许在一个机器周期内同时获得指令字(来自程序存储器)和操作数(来自数据存储器),从而提高了执行速度,使数据的吞吐率提高了1倍。又由于程序和数据存储器在两个分开的物理空间中,因此取指和执行能完全重叠。CPU首先到程序指令存储器中读取程序指令内容,解码后得到数据地址,再到相应的数据存储器中读取数据,并进行下一步的操作(通常是执行)。

 

   

2 Harvard architecture

哈佛结构采用数据存储器与程序代码存储器分开,各自有自己的数据总线与地址总线。但这是需要CPU提供大量的数据线,因而很少使用哈佛结构作为CPU外部构架来使用。但是对于 CPU内部,通过使用不同的数据和指令cache,可以有效的提高指令执行的效率,因而目前大部分计算机体系都是CPU内部的哈弗结构+CPU外部的冯·诺伊曼的结构。

 
2019-09-17 14:03:00 dipanfa0412 阅读数 11
  • 单片机到底是个什么东西-1.2.第1季第2部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第2个课程,用通俗易懂的语言讲了很多和单片机有关的技术概念,如CPU、ROM、RAM、外设、电路板、软件硬件工作的差别等。目的是希望大家在轻松愉悦的氛围中对单片机加深认识。

    3038 人正在学习 去看看 朱有鹏

  我们先普及一个概念,单片机(即Microcontroller Unit;MCU) 里面有什么。一个人最重要的是大脑,身体的各个部分都在大脑的指挥下工作。MCU跟人体很像,简单来说是由一个最重要的内核加其他外设组成,内核就相当于人的大脑,外设就如人体的各个功能器官。下面我们来简单介绍下51单片机和STM32单片机的结构。

1. 51系统结构

 

 

 

 

51系统结构框图

  我们说的51一般是指51系列的单片机,型号有很多,常见的有STC89C51、AT89S51,其中国内用的最多的是STC89C51/2,下面我们就以STC89C51来讲解,并以51简称。

1. 内核

  51单片机由一个IP核和片上外设组成,IP核就是上图中的CPU,片上外设就是上图中的:时钟电路、SFR和RAM、ROM、定时/计数器、并行I/O口、串行I/O口、中断系统。IP核跟外设之间由系统总线连接,且是8bit的,速度有限。

  51内核是上个世纪70年代Intel公司设计的,速度只有12M,外设是IC厂商(STC)在内核的基础上添加的,不同的IC厂商会在内核上添加不同的外设,从而设计出各具特色的单片机。这里Intel属于IP核厂商,STC属于IC厂商。我们后面要讲的STM32也一样,ARM属于IP核厂商,ARM给ST授权,ST公司在Cortex-M3内核的基础上设计出STM32单片机。

2. 外设

  我们在学习51的时候,关于内核部分接触的比较少,使用的最多的是片上外设,我们在编程的时候操作的也就是这些外设。

编程的时候操作的寄存器位于SFR和RAM这个部分,其中SFR(特殊功能寄存器)占有 128字节(实际上只用了 26 个字节,只有 26 个寄存器,其他都属于保留区),RAM占有 128 字节,我们在程序中定义的变量就是放在RAM中。其中SFR和RAM在地址上是重合的,都是在80~FF地址区间,但在物理区间上是分开的,所以51的RAM是有256个字节。

  编写好的程序是烧写到ROM区。剩下的外设都是我们非常熟悉的IO口,串口、定时器、中断这几个外设。

2. STM32系统结构

 

 

 

 

 

STM32系统结构框图

 

 

1. 内核

 

  在系统结构上,STM32和51都属于单片机,都是由内核和片上外设组成。只是STM32使用的Cortex-M3内核比51复杂得多,优秀得多,支持的外设也比51多得多,同时总线宽度也上升到32bit,无论速度、功耗、外设都强于51。

 

  从结构框图上看,对比51内核只有一种总线,取指和取数共用。Cortex-M3内部有若干个总线接口,以使CM3能同时取址和访内(访问内存),它们是:

 

  指令存储区总线(两条)、系统总线、私有外设总线。有两条代码存储区总线负责对代码存储区(即FLASH外设)的访问,分别是I-Code总线和D-Code总线。

I-Code用于取指,D-Code用于查表等操作,它们按最佳执行速度进行优化。

 

系统总线(System)用于访问内存和外设,覆盖的区域包括SRAM,片上外设,片外RAM,片外扩展设备,以及系统级存储区的部分空间。

 

私有外设总线负责一部分私有外设的访问,主要就是访问调试组件。它们也在系统级存储区。

 

还有一个DMA总线,从字面上看,DMA是data memory access的意思,是一种连接内核和外设的桥梁,它可以访问外设、内存,传输不受CPU的控制,并且是双向通信。简而言之,这个家伙就是一个速度很快的且不受老大控制的数据搬运工,这个在51里面是没有的。

 

2. 外设

 

从结构框图上看, STM32比51的外设多得多,51有的串口、定时器、IO口等外设 STM32 都有。STM32还多了很多特色外设:如FSMC、SDIO、SPI、I2C等,这些外设按照速度的不同,分别挂载到AHB、APB2、APB1这三条总线上。

 

 

 

 

转载于:https://www.cnblogs.com/ltybk/p/11533300.html

2017-09-24 10:42:47 shenghuaDay 阅读数 647
  • 单片机到底是个什么东西-1.2.第1季第2部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第2个课程,用通俗易懂的语言讲了很多和单片机有关的技术概念,如CPU、ROM、RAM、外设、电路板、软件硬件工作的差别等。目的是希望大家在轻松愉悦的氛围中对单片机加深认识。

    3038 人正在学习 去看看 朱有鹏

【C51】单片机定时器介绍http://www.cnblogs.com/lulipro/p/5064099.html

标准51架构的单片机有2个定时器 :T0  和  T1,他们2个的用法几乎一样。下面主要讲T0定时器的用法。

 

初步认知

 

定时器 和 计数器 都是单片机中同一个模块。他们的实质都是: 加法存储计数器。对于计数器很好理解,每来一个信号(信号从P3.4 或者P3.5输入),就加1,以此达到计数的目的。
对于定时器,每隔1个机器周期 加 1,假如(只是假如)一个机器周期为 1ms , 当加到1000时,我们就认为经过了1s,这就是定时器的原理。

 

加法存储寄存器THx & TLx

 
定时器依赖计数,需要把累计增加的那个量存储在某个地方,这就是THx和TLx(x 可以是 0 或者1)2个8位寄存器的的职责。
T0和T1都拥有一对加法存储寄存器。
T0 对应:TH0,TL0
T1 对应 : TH1 , TL1
 
在reg51.h头文件中我们发现这4个寄存器的定义:
 
sfr TL0  = 0x8A;              //  TL中的L是LOW的意思,代表低位,同理H代表HIGH高位。
sfr TL1  = 0x8B;
sfr TH0  = 0x8C;
sfr TH1  = 0x8D;
 

 

他们可以在程序中直接使用,复位值都是 0 。
当一直累加,使得他们保存不了太大的数据而发生溢出时,就会引发中断(后面讲中断)。并且对应的TFx溢出标志位会置为1,(没有溢出的情况下是0)。
如果不使用中断去处理溢出这个事件,那么我们就必须通过代码指令让TFx重置为 0 ,并让THx和TLx回归初始值,准备然后进入下一轮周期的计数。
 
 
复制代码
....
if(TF0==1)    //如果T0 溢出了
{
     TF0=0;
     //重新初始化 TH0  和 TL0
     //说明过了一个溢出周期了
}
 
复制代码

 

 

 2个重要的寄存器:TMOD 和 TCON

 
 

 

复位时所有位全  为 0

TFx:溢出标志位。溢出时置1。正常为0。
 
TRx:计数器/定时器 启动停止控制位 。R是run的意思。  TR0 = 1  开启定时器0,为TR0 = 0 则停止。
 
低4位与外部中断相关,这里用不到,先不用看。

 

 

 

 

 

复位时所有位全  为 0

高4位是定时器T1相关的,低4位是T0 相关的。
以T0来说明。
 
GATE:      门控制位
C/T    :     定时器/计数器切换位。 1为计数器模式, 0 为定时器模式。

 

②处 C/T = 0 表示为定时器模式,触发信号为①处的单片机内部时钟信号。(若②处CT = 1,则触发信号为Tn脚,信号从P3.4 或者P3.5输入单片机)
 
③处表明,信号能触发使加法计数器加1,还得受④处控制。不然时钟信号是不能让加法计数器累加的。 ④处这个是与门,TRn必须为1,表明我们要开启定时器。同时GATE为0,通过非门后为1,再通过或门,也是1,那么就让③处控制起来了。
 
(若GATE为1,那么,定时器的启动停止受 TRx和 INTx 共同控制。 )
 
 
于是我们需要:
TRn    为 1
GATE  为 0
INTn   为 X(X表示任意的意思,do not care)
 
 
 
加法存储寄存器的工作模式,是由M0和M1共同来决定的
M1 M0 模式
0 1 TH和TL2个组成16位计数存储器模式
1 0 TH负责初始化TL,TL计数。8位重装模式
0 0 THx的8位和TLx的位5组成13位加法计数器(很少用)
1 1 基本不用

 

 

 

时钟周期和机器周期

 
顺便提一下:标准C51的1个机器周期为12个时钟周期(增强型51单片机的机器周期会短一些,cc2530只有的机器周期只等于1个时钟周期)。
如果晶振的频率是11.0592MHz,那么时钟周期就是   1 / (11.0592x10^6) 秒   (1MHz = 10^6Hz)

那么,无论是定时器,还是计数器,每隔1个机器周期 ,加法存储器就1,代表时间经过了  12  x     1 / (11.0592x10^6) 秒。这就是我们衡量的基础依据。

 
 

为THx和TLx赋初始值

若TH0 和 TL0 以 16位 模式工作,那它的计数范围为   [0 , 65535 ]  ,  也就是累加 65536次发生溢出。 每累加一次是  12 / (11.0592x10^6) 秒。
那么从 0 累加到溢出 历时  ≈ 0.071s = 71ms 。
我们一般需要延时 10的整数倍ms,以便用倍数控制更长的延时时间。所以,我么要给 TH0 和  TL0赋一个初始值,使他们的溢出周期(TH0,TL0从初始值到溢出所用的时间)减少到 10ms,或者1ms。
就像一个瓶子,开始装了2/3,再来就只能装1/3就溢出了。
 

 12 / (11.0592x10^6) s       -----     1   次

 
 10x 10-3   s                        ------          x  次         (求出 x = 9216次 ,计数9216次后溢出)
 
65536 - 9216 = 56320  =  二进制( 11011100   00000000)
 
也就是  TH0 = 11011100 , TL0 = 00000000

 

 

代码例子验证

 

复制代码
#include<reg51.h>
typedef unsigned int uint; 


/**************函数声明******************/
void delay10ms(uint m) ;
void delay1ms(uint m) ;
/********************************/ 


/*****************************/
sbit LED = P0^0; 
/*****************************/



void main() 
{ 
     while(1)
     {
         LED = 1;
         delay10ms(100);   // delay1ms(1000)
         LED = 0;
         delay10ms(100);   // delay1ms(1000)
          
         
     }
} 


void delay10ms(uint m)     //溢出周期为10ms
{

  /********************
   使用到的寄存器(位)
   
   TH0   TL0

   TCON:  TR0    TF0
   TMOD:

  ***********************/



    uint count =0;
    TMOD = 1;        //GATE = 0    C/T =0    M1 = 0  M0 = 1;  16位计数器
    TL0 = 0 ;
    TH0 =220 ;

    TR0 = 1;

    for(;count<m;)
    {
         if(TF0 == 1)
         {
              TF0 = 0;
              TL0 = 0;
              TH0 = 220;

              ++count;

         }
    }

    TR0   =  0;   //关闭定时器
    
}



void delay1ms(uint m)       //溢出周期为1ms
{
     uint count=0;
     TMOD = 1;   //计时器0以16为存储计时器工作
     TH0 = 252 ;
     TL0 = 102;
     TR0 = 1;

         for(;count<m;)
         {

          if(TF0==1)        //发生一次溢出,也就是过了1ms          
          {
               TF0=0;       //溢出位清零,取消警报
               TH0 = 252 ;  //重新配置初始值
               TL0 = 102;
               count++;     //溢出次数加1 ,溢出1次是1ms,溢出t次就是t ms          
           }


        }
      
     TR0 = 0;

}
复制代码

 

 

 

8位重装模式

8位重装模式是:只有TL0计数,TH0不变,他只为TL0提供初始值。当TL计数溢出后,TF0就为1,如果继续工作,TH0就把自己的值赋给TL0,再开始计数,如此循环下去。

上面些写了一个毫秒级的delay函数,下面用8位重装模式写一个控制微秒级别的函数。并控制P0.0的LED实现呼吸灯。

 

计算方法和上面一样,大家可以自己算

 

复制代码
#include<reg51.h>
typedef unsigned int uint;


# define TRUE 1
# define FALSE 0

/**************函数声明******************/
void
delay1ms(uint t); /********************************/ sbit LED = P0^0; void main() { int step = 0; int again = FALSE; while(1) { LED = again?0:1; delay1ms(step); LED = again?1:0;; delay1ms((500-step)); step+=1; if(step>500) { step =0; again = !again; } } } void delay1ms(uint m) //延时t微秒 { int count=0; TMOD = 2; TH0= 255 ; TL0= 255; TR0=1; for(;count!=m;) { if(TF0==1) { TF0=0; //自动重装 count++; } } TR0=0; }
复制代码

 

 

值得注意的地方

我们应该尽量让溢出周期 越长越好。溢出周期为10ms 的优于 1ms 的。因为,在同样的延时时间下,如100ms,溢出周期为10ms 的 只需要溢出10次,为TH0 和 TL0重新赋值10次,而溢出周期为1ms的要溢出100次,为TH0 和 TL0重新赋值100次。减少溢出次数和赋值次数,可以减轻单片机的负担,提高定时的准确性。

 

 

 

/***************************************************/

 欢迎转载,请注明出处:www.cnblogs.com/lulipro

 为了获得更好的阅读体验,请访问原博客地址。

 代码钢琴家

/***************************************************/

 

作者:代码钢琴家 
出处:http://www.cnblogs.com/lulipro/ 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。为了获得更好的阅读体验,请访问原博客地址。限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

2017-02-28 08:32:04 u012329294 阅读数 2498
  • 单片机到底是个什么东西-1.2.第1季第2部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第2个课程,用通俗易懂的语言讲了很多和单片机有关的技术概念,如CPU、ROM、RAM、外设、电路板、软件硬件工作的差别等。目的是希望大家在轻松愉悦的氛围中对单片机加深认识。

    3038 人正在学习 去看看 朱有鹏
单片机和soc的区别:: 
MCU,即微控制器,是以前的一种做法,类似于单片机,只是集成了一些更多的功能模块,
它本质上仍是一个完整的单片机,有处理器,有各种接口,所有的开发都是基于已经存在的系统架构,应用者要做的就是开发软件程序和加外部设备
SOC,是个整体的设计方法概念,它指的是一种芯片设计方法,集成了各种功能模块,每一种功能都是由硬件描述语言设计程序,然后在SOC内由电路实现的;
每一个模块不是一个已经设计成熟的ASIC“器件”,只是利用芯片的一部分资源去实现某种传统的功能。这种功能是没有限定的,可以是存储器,当然也可以是处理器,如果这片SOC的系统目标就是处理器,那么做成的SOC就是一个MCU;
如果要做的是一个完整的带有处理器的系统,那么MCU就是整个SOC中的一个模块,一个IP。
SOC可以做成批量生产的通用器件,如MCU;也可以针对某一对象专门设计,可以集成任何功能,不像MCU那样有自身架构的限定。
它的体积可以很少,特殊设计的芯片可以根据需要减少体积、降低功耗,在比较大的范围内不受硬件架构的限制(当然,它也是会受芯片自身物理结构的限制,如晶圆类型、大小等)。
SOC的一大特点就是其在仿真时可以连同硬件环境一起仿真,仿真工具不只支持对软件程序的编译调试,同时也支持对硬件架构的编译调试,如果不满意硬件架构设计,想要加一个存储器,或是减少一个接口都可以通过程序直接更改,这一点,MCU的设计方法是无法实现的,MCU的方法中,硬件架构是固定的,是不可更改的,多了只能浪费,少了也只能在软件上想办法或是再加,存储空间不够可以再加,如果是接口不够则只能在软件上想办法复用。仿真之后可以通过将软、硬件程序下载到FPGA上进行实际硬件调试,以便更真实地进行器件测试。
如果硬件调试成功后直接投片生产成“固定结构的芯片”,则其为普通的SOC;如果其硬件就是基于FPGA的,也就是说它是“用FPGA做为最终实现”的,它在以后也可以随时进行硬件升级与
调试的,叫它为SOPC的设计方法,所以说SOPC是SOC的一种解决方案。

SOPC设计灵活、高效,且具有成品的硬件可重构特性(SOC在调试过程中也可硬件重构),的适用性可以很广,针对不同的对象,它可以进行实时的结构调整,如减少程序存储空间、增加接口数目等,这一附加价值是任何固定结构IC所无法具备的,但它的价格可能会比批量生产的固定结构IC要贵得多。

SoC为System on Chip的缩写,称为系统级芯片,也有称片上系统,意指它是一个产品,是一个有专用目标的集成电路,其中包含完整系统并有嵌入软件的全部内容,用VHDL或Verilog语言编程多吧。       原来用过实验箱上些的Soc实验箱里面就是xilinx的FPGA(Field-Programmable Gate Array即现场可编程门阵列)。它的硬件(FPGA)是可编程的,是一个通过硬件描述语言FPGA芯片上自定义集成电路的过程;但单片机的硬件是不可以用软件改变的;

单片机是比较低端的控制器。大多用汇编或C可以编程。单片机设计属软件范畴;它的硬件(单片机芯片)是固定的,通过软件编程语言描述软件指令在硬件芯片上的执行;


疯狂java Quartz框架

阅读数 454

没有更多推荐了,返回首页