单片机

单片机软件延时时间控制

cathy的头像

一、简述

记--通过代码方式实现软件延时(不精确延时)。

二、指令周期

单片机需要一个时钟信号送给内部各个电路,才能使它们有节拍地协同工作。时钟信号的频率是由外部震荡电路的晶振频率决定的。

外接晶振的频率 = 时钟信号的频率 = 工作频率。(如24MHz,12MHz,11.0592MHz)

震荡周期:为单片机提供时钟脉冲的振荡源的周期。

震荡周期 = 1/晶振频率          (如晶振频率是12MHz时,振荡周期 = 1/12MHz = (1/12)us)

机器周期:51系列单片机的一个机器周期由12个震荡周期组成。

机器周期 = 12 * 振荡周期      

(如晶振频率是24MHz时,振荡周期 = 1/24MHz = (1/24)us,机器周期 = 12*(1/24)us = 0.5us)

(如晶振频率是12MHz时,振荡周期 = 1/12MHz = (1/12)us,机器周期 = 12*(1/12)us = 1us)

单片机的内存分配(变量的存储位置)详解

cathy的头像

对于初学者而言,对单片机的内存分配往往最让人头疼,很多人学了单片机几年 都不知道单片机内部的内存使用情况是如何分配的。要了解 ROM(flash)、RAM(sram)启动,首先 需要对 链接器 Linker 如何分配内存有一定的了解。

嵌入式开发中,UART串口是最常见的一种通信接口,你知道为啥串口这么常见吗?本文就带你深入了解串口最底层的本质内容。

一、什么是串口通讯?

串行通讯是指仅用一根接收线和一根发送线就能将数据以位进行传输的一种通讯方式。尽管串行通讯的比按字节传输的并行通信慢,但是串口可以在仅仅使用两根线的情况下就能实现数据的传输。

典型的串口通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,所以端口能够在一根线上发送数据同时在另一根线上接收数据。串口通信最重要的参数是波特率、数据位、停止位和奇偶的校验。对于两个需要进行串口通信的端口,这些参数必须匹配,这也是能够实现串口通讯的前提。

“图1:串行通讯示数据传输意图"
图1:串行通讯示数据传输意图

二、串口通讯的通讯协议?

最初数据是模拟信号输出简单过程量,后来仪表接口出现了RS232接口,这种接口可以实现点对点的通信方式,但这种方式不能实现联网功能,这就促生了RS485。

我们知道串口通信的数据传输都是0和1,在单总线、I2C、UART中都是通过一根线的高低电平来判断逻辑1或者逻辑0,但这种信号线的GND再与其他设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。所以差分通信、支持多机通信、抗干扰强的RS485就被广泛的使用了。

RS485通信最大特点就是传输速度可以达到10Mb/s以上,传输距离可以达到3000米左右。大家需要注意的是虽然485最大速度和最大传输距离都很大,但是传输的速度是会随距离的增加而变慢的,所以两者是不可以兼得的。

三、串口通讯的物理层

串口通讯的物理层有很多标准,例如上面提到的,我们主要讲解RS-232标准,RS-232标准主要规定了信号的用途、通讯接口以及信号的电平标准。

“精华

在上面的通讯方式中,两个通讯设备的"DB9接口"之间通过串口信号线建立起连接,串口信号线中使用"RS-232标准"传输数据信号。由于RS-232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个"电平转换芯片"转换成控制器能识别的"TTL校准"的电平信号,才能实现通讯。

下图为DB9标准串口通讯接口:

“精华

DB9引脚说明:

“精华

上表中的是计算机端的DB9公头标准接法,由于两个通讯设备之间的收发信号(RXD与TXD)应交叉相连,所以调制调解器端的DB9母头的收发信号接法一般与公头的相反,两个设备之间连接时,只要使用"直通型"的串口线连接起来即可。

“精华

串口线中的RTS、CTS、DSR、DTR及DCD信号,使用逻辑 1表示信号有效,逻辑0表示信号无效。例如,当计算机端控制DTR信号线表示为逻辑1时,它是为了告知远端的调制调解器,本机已准备好接收数据,0则表示还没准备就绪。

四、波特率

波特率是指数据信号对载波的调制速率,它用单位时间内载波调制状态改变的次数来表示;

“精华

比如波特率为9600bps;代表的就是每秒中传输9600bit,也就是相当于每一秒中划分成了9600等份。

因此,那么每1bit的时间就是1/9600秒=104.1666...us。约0.1ms。既然是9600等份,即每1bit紧接着下一个比特,不存在额外的间隔。两台设备要想实现串口通讯,这收发端设置的波特率必须相同,否则是没办法实现通讯的。

收发波特率一致可以实现通讯:

“精华

收发波特率不一致,导致RX端不能正常接收:

“精华

五、串口通讯的数据结构

“精华

起始位: 起始位必须是持续一个比特时间的逻辑0电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步。

数据位: 数据位紧跟在起始位之后,是通信中的真正有效信息。数据位的位数可以由通信双方共同约定。传输数据时先传送字符的低位,后传送字符的高位。

奇偶校验位: 奇偶校验位仅占一位,用于进行奇校验或偶校验,奇偶检验位不是必须有的。如果是奇校验,需要保证传输的数据总共有奇数个逻辑高位;如果是偶校验,需要保证传输的数据总共有偶数个逻辑高位。

停止位: 停止位可以是是1位、1.5位或2位,可以由软件设定。它一定是逻辑1电平,标志着传输一个字符的结束。

空闲位: 空闲位是指从一个字符的停止位结束到下一个字符的起始位开始,表示线路处于空闲状态,必须由高电平来填充。

六、单双工通讯

单工: 数据传输只支持数据在一个方向上传输;

半双工: 允许数据在两个方向上传输,但某一时刻只允许数据在一个方向上传输,实际上是一种切换方向的单工通信,不需要独立的接收端和发送端,两者可合并为一个端口;

全双工: 允许数据同时在两个方向上传输,因此全双工通信是两个单工方式的结合,需要独立的接收端和发送端。

“精华

七、STM32中的串口通讯

STM32串口通信接口有两种,分别是:UART(通用异步收发器)、USART(通用同步异步收发器),对于大容量STM32F10x系列芯片,分别由3个USART和两个UART。

“精华

对于两芯片的间的连接,两个芯片GND共地,同时TXD和RXD交叉连接,这样两个芯片间可进行TTL电平通信。

但如果对于芯片和PC机相连,除了共地条件外,不能使用如上的直接交叉连接,虽然两者都有TXD和RXD引脚,但通常PC机使用的是RS232接口(9针),通常是TXC和RXD经过电平转换得到,故如果要使芯片与PC机的RS232接口直接通信,需要将芯片的输入输出端口也电平转换为RS232类型,再交叉连接,二者的电平标准不同:

单片机的点评标准(TTL电平):+5V表示1,0V表示0;RS232电平标准:+15/+13V表示0,-15/-13表示1。

“精华

因此单片机与PC机进行串口通信应该遵循:在单片机串口与上位机给出的RS232口之间,通过电平转换电路实现TTL电平与RS232电平间的转换。如果使用USB转串口也可以实现串口通讯,USB转串口电路图如下所示。

“精华

来源:嵌入式资讯精选
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 30

单片机如果缺少调试功能,就好比失去了灵魂。

如今众多Cortex-M处理器能这么方便调试,在于有一项基于Arm Cortex-M处理器设备的CoreSight技术,该技术引入了强大的新调试(Debug)和跟踪(Trace)功能。

下面就来重点讲讲关于CoreSight中调试和跟踪的相关内容。

调试和跟踪功能

CoreSight两个主要功能就是调试和跟踪功能。

1.调试功能

  • 运行处理器的控制,允许启动和停止程序
  • 单步调试源码和汇编代码
  • 在处理器运行时设置断点
  • 即时读取/写入存储器内容和外设寄存器
  • 编程内部和外部FLASH存储器

2.跟踪功能

  • 串行线查看器(SWV)提供程序计数器(PC)采样,数据跟踪,事件跟踪和仪器跟踪信息
  • 指令(ETM)跟踪直接流式传输到您的PC,从而实现历史序列的调试,软件性能分析和代码覆盖率分析

一张图了解整体内容:

“单片机调试时的那些技术"

JTAG

JTAG是行业标准的接口,用于下载和调试目标处理器上的程序以及许多其他功能。它提供了连接设备的简便方法,并且在所有基于Arm处理器的设备上都可用。JTAG接口可与基于Cortex-M的设备一起使用,以访问CoreSight调试功能。

1.JTAG历史

JTAG是联合测试工作组(Joint Test Action Group)的简称,是在名为标准测试访问端口和边界扫描结构的IEEE的标准1149.1的常用名称。此标准用于验证设计与测试生产出的印刷电路板功能。

1990年JTAG正式由IEEE的1149.1-1990号文档标准化,在1994年,加入了补充文档对边界扫描描述语言(BSDL)进行了说明。从那时开始,这个标准被全球的电子企业广泛采用,边界扫描几乎成为了JTAG的同义词。---引用维基百科

2.JTAG接口

JTAG的接口通常是4/5个接脚接口连到芯片上:

  • TDI(测试数据输入)
  • TDO(测试数据输出)
  • TCK(测试时钟)
  • TMS(测试模式选择)
  • TRST(测试复位)可选

“单片机调试时的那些技术"

SWD串行线调试

SWD,Serial Wire Debug(串行线调试)模式是标准JTAG接口的替代方法,它仅使用两个引脚即可提供与JTAG相同的调试功能,而不会降低性能,并通过串行线查看器(SWV)引入了数据跟踪功能。

在JTAG引脚中包含SWD接口引脚,从而允许标准目标连接器中使用。引脚包含:

  • TCLK-SWCLK(串行时钟)
  • TMS-SWDIO(串行数据输入/输出)
  • TDO-SWO(串行线输出-SWV使用)

关于SWV

SWV:Serial Wire Viewer,串行线查看器

基于Cortex-M3、 M4、 M7的设备能够根据所需信息或分析的类型,以多种方式提供高速数据跟踪信息。当系统处理器继续全速运行时,它通过SWO引脚传输。

可从ITM(仪器跟踪宏单元)和DWT(数据观察点和跟踪)单元获得信息,其中包括:

  • PC(程序计数器)采样
  • 显示CPU周期统计信息的事件计数器
  • 具有定时统计信息的异常和中断执行
  • 跟踪数据-用于时序分析的数据读取和写入
  • 用于简单printf样式调试的ITM跟踪信息

拓展:下载调试器

之前给大家分享过《下载调试接口 SWD 和 JTAG的区别》,选择下载调试器时也可以对比一下二者差异。

支持JTAG和SWD模式的下载调试器很多,J-Link、 ST-LINK、 ULINK这些大众化的工具都支持,还有很多小众的下载调试器(比如e-Link、 GD-Link等)同样也支持SWD模式。

那么,我们该选择哪一种呢? 哪一种更香呢?

支持全面的一点的算J-Link了,但正版的价格,对于普通个人来说,有压力。

有多种版本,算下来好几千一个。

“单片机调试时的那些技术"

同样,正版ULINK也和J-Link一样,价格上千。当然,很多人买到的ULINK和J-Link都是盗版。

相比ULINK和J-Link而言,正版的ST-Link价格就很划算,一个正版ST-Link V2才两三百,但缺点就是只能针对STM8/32使用。

当然,ST-Link还有一个神操作:变成J-link,请参看我的文章《手把手教你 ST-Link 秒变 J-link》。

作者 | strongerHuang
微信公众号 | strongerHuang
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 100

正常的程序,都不会跳出main,但是,如果跳出了 main 函数,程序到底去哪儿了,你有相关这个问题吗?

一、问题提出

今天在单片机led模块定义函数中看到一个有趣的问题。提问者在进行基本的C51编程实验,编写了一个简单的C51程序如下:

#include <REGX51.H>

void test(num) {
    switch(num) {
        case 1: P2_0=0; P2_1=0; 
            break;
    }
}

void main(void) {
    test(1);
}

程序执行完之后,可以看到实验板上的有两个LED被点亮,另外六个居然微微发亮。

“单片机main函数结束干嘛去了?"

如果在主程序中,增加一个无限循环:while(1); ,则电路板上的就不再会出现“微微点亮”的现象了。

#include <REGX51.H>

void test(num) {
    switch(num) {
        case 1: P2_0=0; P2_1=0; 
            break;
    }
}

void main(void) {
    test(1);
    while(1);
}

“单片机main函数结束干嘛去了?"

上面两种情况的区别,在于第二个程序中主循环 main()函数始终没有退出,而第一个程序,main()函数退出了。似乎前面LED微微点亮 应该与主函数退出之后,单片机都干了些啥有关系。

那么就剩下一个问题:对于普通的嵌入式系统,C语言编程中main()函数退出之后,程序去哪儿了?

二、程序去哪儿了?

从上面提问者书写的代码来看,应该是一位C51的爱好者,使用的是C51的编译器,在一款C51开发板上愉快的进行实验。他一开始没有安装嵌入式程序开发的惯例 在主程序void main(void)中利用无限循环将程序控制在主程序函数中,就出现了前面实验结果中令人迷惑的情况。

“注:他是一个胆大心细的人,观察还挺仔细的。”

2.1 盘古开天辟地

对于C语言编程来说,所有的用户程序世界是从主程序main()开始的。给用户程序开天辟地的任务是由一小段盘古代码STARTUP.A51。

51单片机程序执行流程(STARTUP.A51管理Main函数的执行)

下面截取了STARTUP.A51 代码的一段,可以看到盘古在单片机RESET之后做了点准备工作(初始化全局变量、堆栈指针)之后,就直接跳转至:?C_START

 NAME    ?C_STARTUP

?C_C51STARTUP   SEGMENT   CODE
?STACK          SEGMENT   IDATA

                RSEG    ?STACK
                DS      1

                EXTRN CODE (?C_START)
                PUBLIC  ?C_STARTUP

                CSEG    AT      0
?C_STARTUP:     LJMP    STARTUP1

                RSEG    ?C_C51STARTUP

STARTUP1:

IF IDATALEN <> 0
                MOV     R0,#IDATALEN - 1
                CLR     A
IDATALOOP:      MOV     @R0,A
                DJNZ    R0,IDATALOOP
ENDIF

IF XDATALEN <> 0
                MOV     DPTR,#XDATASTART
                MOV     R7,#LOW (XDATALEN)
  IF (LOW (XDATALEN)) <> 0
                MOV     R6,#(HIGH (XDATALEN)) +1
  ELSE
                MOV     R6,#HIGH (XDATALEN)
  ENDIF
                CLR     A
XDATALOOP:      MOVX    @DPTR,A
                INC     DPTR
                DJNZ    R7,XDATALOOP
                DJNZ    R6,XDATALOOP
ENDIF

IF PPAGEENABLE <> 0
                MOV     PPAGE_SFR,#PPAGE
ENDIF

IF PDATALEN <> 0
                MOV     R0,#LOW (PDATASTART)
                MOV     R7,#LOW (PDATALEN)
                CLR     A
PDATALOOP:      MOVX    @R0,A
                INC     R0
                DJNZ    R7,PDATALOOP
ENDIF

IF IBPSTACK <> 0
EXTRN DATA (?C_IBP)

                MOV     ?C_IBP,#LOW IBPSTACKTOP
ENDIF

IF XBPSTACK <> 0
EXTRN DATA (?C_XBP)

                MOV     ?C_XBP,#HIGH XBPSTACKTOP
                MOV     ?C_XBP+1,#LOW XBPSTACKTOP
ENDIF

IF PBPSTACK <> 0
EXTRN DATA (?C_PBP)
                MOV     ?C_PBP,#LOW PBPSTACKTOP
ENDIF

                MOV     SP,#?STACK-1
                LJMP    ?C_START

                END

上面的代码也被博文51单片机程序执行流程(STARTUP.A51)中进行逐步调试跟踪验证过:

“单片机main函数结束干嘛去了?"

2.2 世界尽头

由于进入main()函数是长跳转,所以main函数是不会正常返回到启动程序STARTUP.A51,那么程序去哪了?

在博文单片机C语言while(1)的问题中作者对于KEIL编译器和PIC的MAPLAB编译器对于main函数的最后时光进行了反汇编查看。

Keil编译器

在main函数的最后,程序增加了一下几行代码:

MOV R0, #0x7F
CLR A
MOV @R0, A
DJNZ R0, (3)
MOV SP, #0x0C
LJMP main

这几条语句,前4条,是将我们单片机的内存的前128个地址清零,第5条,是定义堆栈,第6条,是将程序重新跳转到main函数的首行进行执行。

MAPLAB编译器

PIC 单片机语言程序进行跟踪,发现main() 函数最后一条语句为 reset,也就是单片机直接复位,这是 MAPLAB编译器根据 PIC 单片机特点增加的复位语句。

总结

对于嵌入式系统,如果没有运行RTOS,那么程序开发中的主函数(main())需要通过某种机制使其永远愉快的运行下去,它没有终点。如果想从main函数中退出,具体干什么是由所使用的C语言编译器决定的。

来源:TsinghuaJoking
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 26

简介

在过去的二十年里,智能小工具的问世促使电容式触摸表面或触摸板在人机界面(HMI)领域的应用得到了广泛普及。随着业界对触摸需求的日益增长,触摸传感技术也在不断朝着可靠、稳健的方向迈进。因此,对于消费类电子、可穿戴设备、家用电器、家庭自动化、工业、医疗和汽车领域的各种产品而言,电容式触摸界面无疑都是一项必不可少的用户需求。许多设备目前都已使用电容式触摸表面替代机械按钮。

本应用笔记将演示 AVR® DA 单片机的 2D 触摸表面实现,其中使用片上外设触摸控制器(PTC)和 Microchip 触摸库实现集成手势识别。具体将演示 AVR DA 单片机的高级低功耗触摸测量功能,以及如何使用 PTC 外设(令 CPU 处于待机休眠模式)以最大限度地降低功耗。

我们借助 Microchip AVR128DA48 Curiosity Nano、Curiosity Nano 触摸适配器、QT2 Xplained Pro 扩展板和Microchip 触摸库实现了基本的贪吃蛇演示程序来演示 2D 触摸表面手势。补充固件借助 Microchip 的 Atmel START 开发而成,QTouch®配置器(嵌入到 Atmel START 中)用于配置触摸表面参数。

详阅请点击下载《利用外设触摸控制器实现带内置表面手势识别的触摸板》

来源:Microchip微芯
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 42

单片机开发或多或少都会接触一些汇编代码,今天就来说说关于汇编在C中的定义和调用,以及举例说明嵌套汇编代码。

概述

有认真研究,或者说细心一点的读者应该都知道:C中定义汇编代码与编译器有关。

比如:你在core_cm4.h文件会看到如下的代码:

#if   defined ( __CC_ARM )
  #define __ASM            __asm                                      /*!< asm keyword for ARM Compiler          */
  #define __INLINE         __inline                                   /*!< inline keyword for ARM Compiler       */
  #define __STATIC_INLINE  static __inline

#elif defined ( __GNUC__ )
  #define __ASM            __asm                                      /*!< asm keyword for GNU Compiler          */
  #define __INLINE         inline                                     /*!< inline keyword for GNU Compiler       */
  #define __STATIC_INLINE  static inline

#elif defined ( __ICCARM__ )
  #define __ASM            __asm                                      /*!< asm keyword for IAR Compiler          */
  #define __INLINE         inline                                     /*!< inline keyword for IAR Compiler. Only available in High optimization mode! */
  #define __STATIC_INLINE  static inline

#elif defined ( __TMS470__ )
  #define __ASM            __asm                                      /*!< asm keyword for TI CCS Compiler       */
  #define __STATIC_INLINE  static inline

#elif defined ( __TASKING__ )
  #define __ASM            __asm                                      /*!< asm keyword for TASKING Compiler      */
  #define __INLINE         inline                                     /*!< inline keyword for TASKING Compiler   */
  #define __STATIC_INLINE  static inline

#elif defined ( __CSMC__ )
  #define __packed
  #define __ASM            _asm                                      /*!< asm keyword for COSMIC Compiler      */
  #define __INLINE         inline                                    /*use -pc99 on compile line !< inline keyword for COSMIC Compiler   */
  #define __STATIC_INLINE  static inline
#endif

“单片机C代码嵌套汇编的一些方法"

如果你写过Keil C51,你还会发现有如下(通过预处理)嵌套汇编:

#pragma asm

; Assembler Code Here

#pragma endasm

所以,你会发现,不同的编译器,汇编代码还是有差异。当然,这里主要是说C中嵌套汇编与编译器有关。

C中嵌套汇编代码

常见两种定义:

1.在C函数中定义一段汇编代码;

2.在C文件中定义一个汇编函数;

(当然,两个意思差不多,都是在C中嵌套汇编)

上面说了C中定义汇编代码与编译器有关,换句话说:不同编译器解析汇编代码的方式不同。

这里还是拿core_cm3.c来举例说明,定义一个__get_PSP函数。

在Keil MDK中定义:

__ASM uint32_t __get_PSP(void)
{
  mrs r0, psp
  bx lr
}

在IAR EWARM中定义:

uint32_t __get_PSP(void)
{
  __ASM("mrs r0, psp");
  __ASM("bx lr");
}

__asm(__ASM)关键字用于调用内联汇编程序,并且可在 C 或 C++ 语句合法时出现。

看到这里,推荐阅读我分享的一篇文章:单片机用汇编和C语言点灯程序的区别

举例

下面举一些常见例子。

1.FreeRTOS中portmacro.h文件下源代码:

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

  __asm
  {
    /* Set BASEPRI to the max syscall priority to effect a critical section. */
    msr basepri, ulNewBASEPRI
    dsb
    isb
  }
}

2.FreeRTOS中port.c文件下源代码:

__asm void xPortPendSVHandler( void )
{
  extern uxCriticalNesting;
  extern pxCurrentTCB;
  extern vTaskSwitchContext;

  PRESERVE8

  mrs r0, psp
  isb

  ldr  r3, =pxCurrentTCB    /* Get the location of the current TCB. */
  ldr  r2, [r3]

  stmdb r0!, {r4-r11}      /* Save the remaining registers. */
  str r0, [r2]        /* Save the new top of stack into the first member of the TCB. */

  stmdb sp!, {r3, r14}
  mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
  msr basepri, r0
  dsb
  isb
  bl vTaskSwitchContext
  mov r0, #0
  msr basepri, r0
  ldmia sp!, {r3, r14}

  ldr r1, [r3]
  ldr r0, [r1]        /* The first item in pxCurrentTCB is the task top of stack. */
  ldmia r0!, {r4-r11}      /* Pop the registers and the critical nesting count. */
  msr psp, r0
  isb
  bx r14
  nop
}

3.内核复位

这是之前分享过的一篇文章《STM32复位来源》中的代码:

__asm void NVIC_CoreReset_a(void)
{
  LDR R0, =0xE000ED0C
  LDR R1, =0x05FA0001
  STR R1, [R0]
deadloop_Core
  B deadloop_Core
}

举了这些常见例子,没有说透,但相信只要认真理解了都能明白。

来源:微信公众号 | 嵌入式专栏
作者 | strongerHuang
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 120

介绍

一种无OS的MCU实用软件框架,包括任务轮询管理,命令管理器、低功耗管理、环形缓冲区等实用模块。系统中广泛利用自定义段技术减少各个模块间的耦合关系,大大提供程序的可维护性。

主要功能

  • 支持模块自动化管理,并提供不同优先等级初始化声明接口。
  • 支持任务轮询管理,通过简单的宏声明即可实现,不需要复杂的声明调用。
  • 支持低功耗管理,休眠与唤醒通知。
  • 支持命令行解析,命令注册与执行。
  • blink设备支持,统一管理LED、震动马达、蜂鸣器

使用说明

完整的代码可以参考工程文件,系统开发平台如下:

MCU:STM32F401RET6

IDE:IAR 7.4或者Keil MDK 4.72A

任务初始化及任务轮询管理(module)

使用此模块前需要系统提供滴答定时器,用于驱动任务轮询作业。(参考platform.c)

使用说明//定时器中断(提供系统滴答)
void SysTick_Handler(void)
{
systick_increase(SYS_TICK_INTERVAL); //增加系统节拍
}

注册初始化入口及任务(参考自key_task.c)

使用说明static void key_init(void)
{
/*do something*/
}

static void key_scan(void)
{
/*do something*/
}

module_init("key", key_init); //注册按键初始化接口
driver_register("key", key_scan, 20); //注册按键任务(20ms轮询1次)

命令管理器(cli)

适用于在线调试、参数配置等(参考使用cli_task.c),用户可以通过串口输出命令行控制设备行为、查询设备状态等功能。

命令格式

cli支持的命令行格式如下:

<cmd name> < param1> < param2> < paramn> < \r\n > <cmd name> ,< param1>, < param2>, < paramn>, < \r\n >

每行命令包含一个命令名称+命令参数(可选),命令名称及参数可以通过空格或者','进行分隔。

系统默认命令

cli系统自带了2条默认命令,分别是"?"与"help"命令,输入他们可以列出当前系统包含的命令列表,如下所示:

?         - alias for 'help'
help      - list all command.
pm        - Low power control command
reset     - reset system
sysinfo   - show system infomation.

适配命令管理器

完整的例子可以参考cli_task.c.

static cli_obj_t cli;                               /*命令管理器对象 */

/* 
 * @brief       命令行任务初始化
 * @return      none
 */ 
static void cli_task_init(void)
{
    cli_port_t p = {tty.write, tty.read};           /*读写接口 */

    cli_init(&cli, &p);                             /*初始化命令行对象 */

    cli_enable(&cli);

    cli_exec_cmd(&cli,"sysinfo");                   /*显示系统信息*/
}

/* 
 * @brief       命令行任务处理
 * @return      none
 */ 
static void cli_task_process(void)
{
    cli_process(&cli);
}

module_init("cli", cli_task_init);                  
task_register("cli", cli_task_process, 10);          /*注册命令行任务*/

命令注册

以复位命令为例(参考cmd_devinfo.c):

#include "cli.h"
//...
/* 
 * @brief       复位命令
 */ 
int do_cmd_reset(struct cli_obj *o, int argc, char *argv[])
{
    NVIC_SystemReset();
    return 0;
}cmd_register("reset",do_cmd_reset, "reset system");

低功耗管理器(pm)

控制间歇运行,降低系统功耗。其基本的工作原理是通过轮询系统中各个模块是否可以允许系统进入低功耗。实际上这是一种判决机制,所有模块都具有有票否决权,即只要有一个模块不允许休眠,那么系统就不会进入休眠状态。pm模块在休眠前会统计出各个模块会返回最小允许休眠时长,并以最小休眠时长为单位进行休眠。

如何适配

使用前需要通过pm_init进行初始化适配,并提供当前系统允许的最大休眠时间,进入休眠的函数接口,基本的接口定义如下:

/*低功耗适配器 ---------------------------------------------------------*/
typedef struct {
    /**
     * @brief    系统最大休眠时长(ms)
     */  
    unsigned int max_sleep_time;
    /**
     * @brief     进入休眠状态
     * @param[in] time - 期待休眠时长(ms)
     * @retval    实际休眠时长
     * @note      休眠之后需要考虑两件事情,1个是需要定时起来给喂看门狗,否则会在休眠
     *            期间发送重启.另外一件事情是需要补偿休眠时间给系统滴答时钟,否则会
     *            造成时间不准。
     */     
    unsigned int (*goto_sleep)(unsigned int time);
}pm_adapter_t;
void pm_init(const pm_adapter_t *adt);

void pm_enable(void);

void pm_disable(void);

void pm_process(void);

完成的使用例子可以参考platform-lowpower.c,默认情况下是禁用低功耗功能的,读者可以去除工程中原来不带低功耗版本的platform.c,并加入platform-lowpower.c文件进行编译即可使用。

注册低功耗设备

以按键扫描为例,正常情况下,如果按键没有按下,那么系统休眠可以进入休眠状态,对按键功能是没有影响的。如果按键按下时,那么系统需要定时唤醒并轮询按键任务。

所以在一个低功耗系统下,为了不影响按键实时性需要处理好两个事情:

1)系统休眠状态下,如果有按键按下,那系统系统应立即唤醒,以便处理接下来的扫描工作。

2)如果按键按下时,系统可以进入休眠,但需要定时唤醒起来轮询按键任务。

对于第一种情况,将按键配置为边沿中断唤醒即可,以STM32F4为例(参考key_task.c),它支持外部中断唤醒功能。

/* 
 * @brief       按键 io初始化
 *              PC0 -> key;
 * @return      none
 */ 
static void key_io_init(void)
{
    /* Enable GPIOA clock */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    gpio_conf(GPIOC, GPIO_Mode_IN, GPIO_PuPd_UP, GPIO_Pin_0);

    //低功耗模式下,为了能够检测到按键,配置为中断唤醒
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource0);
    exti_conf(EXTI_Line0, EXTI_Trigger_Falling, ENABLE);
    nvic_conf(EXTI0_IRQn, 0x0F, 0x0F);

    key_create(&key, readkey, key_event);            /*创建按键*/
}

对于第二种情况,可以通过pm_dev_register来处理,当系统请求休眠时,如果此时按键按下,则返回下次唤醒时间即可,如下面的例子所示。

//参考key_task.c
#include "pm.h"                                     
/*
 * @brief	   休眠通知
 */
static unsigned int  key_sleep_notify(void)
{
    return key_busy(&key) || readkey() ? 20 : 0;    /* 非空闲时20ms要唤醒1次*/
} pm_dev_register("key", NULL, key_sleep_notify, NULL);

blink模块

具有闪烁特性(led, motor, buzzer)的设备(led, motor, buzzer)管理

使用步骤:

  • 需要系统提供滴答时钟,blick.c中是通过get_tick()接口获取,依赖module模块
  • 需要在任务中定时进行轮询

或者通过"module"模块的任务注册来实现

task_register("blink", blink_dev_process, 50);  //50ms轮询1次

LED驱动

blink_dev_t led;                             //定义led设备

/*
 *@brief     红色LED控制(GPIOA.8)
 *@param[in] on - 亮灭控制
 */
static void led_ctrl(int on)
{
    if (on)
        GPIOA->ODR |= (1 << 8);
    else 
        GPIOA->ODR &= ~(1 << 8);
}

/*
 *@brief     led初始化程序
 */
void led_init(void)
{
    led_io_init(void);                  //led io初始化
    blink_dev_create(&led, led_ctrl);   //创建led设备

    blink_dev_ctrl(&led, 50, 100, 0);   //快闪(50ms亮, 100ms灭)
}

按键管理模块

类似blink模块,使用之前有两个注意事项:

  • 需要系统提供滴答时钟,key.c中是通过get_tick()接口获取,依赖module模块

  • 需要在任务中定时进行轮询

key_t key;                             //定义按键管理器

/*
 *@brief     按键事件
 *@param[in] type     - 按键类型(KEY_PRESS, KEY_LONG_DOWN, KEY_LONG_UP)  
 *@param[in] duration - 长按持续时间
 */
void key_event(int type, unsigned int duration)
{
	if (type == KEY_PRESS) {                //短按

	} else if (type == KEY_LONG_DOWN) {     //长按

	}
} 

//读取键值(假设按键输出口为STM32 MCU PA8)
int read_key(void)
{
	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == Bit_RESET;
}

/*
 *@brief     按键初始化
 */
void key_init(void)
{
    //打开GPIO 时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	//配置成输入模式
    gpio_conf(GPIOA, GPIO_Mode_IN, GPIO_PuPd_NOPULL, GPIO_Pin_8); 
    //创建1个按键
    key_create(&key, read_key, key_event);  
}

开源地址:

https://gitee.com/moluo-tech/CodeBrick

来源 | 魔罗技术
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 57

页面

订阅 RSS - 单片机