软件系统(SDK51) SDK51提供4+1组与底层硬件相关的服务,总计12+1个应用程序接口(API,ApplicationProgram Interface)。这里先浏览一下SDK51的用户接口文件“SDK51.h”,这个文件中列出了用户需要了解的所有关于SDK51的内容。 /* SDK51.h */ #ifndef __SDK51_H__ #define __SDK51_H__ #include "SDK51_type.h" /* SYSTEM. */ typedef struct { uint32_t SysBusClkHz; uint16_t UartBaudrate; uint16_t TicksPerSecond; } SYSTEM_CONFIG_STRUCT_T; void SYSTEM_Init(const SYSTEM_CONFIG_STRUCT_T *cfgPtr); void SYSTEM_Start(void); /* UART. */ void UART_PutChar(uint8_t ch); uint8_t UART_GetChar(void); void UART_PutStr(uint8_t *str); /* TICK. */ void TICK_Delay(uint8_t ticks); void TICK_InstallCallback( void (*callback)(void) ); /* GPIO. */ #define GPIO_PIN(x) (1U<<(x)) #define GPIO_PORT(x) (x) #define eGPIOPinDirOut true #define eGPIOPinDirIn false void GPIO_SetPinsDir(uint8_t portNum, uint8_t pinsMsk, bool enOut); void GPIO_SetPins(uint8_t portNum, uint8_t pinsMsk); void GPIO_ResetPins(uint8_t portNum, uint8_t pinsMsk); uint8_t GPIO_GetPins(uint8_t portNum, uint8_t pinsMsk); /* External Interrupt. */ void INT0_InstallCallback( void (*callback)(void) ); void INT1_InstallCallback( void (*callback)(void) ); #endif /* __SDK51_H__ */ |
细心的你可能注意到这里引用了一个“SDK51_type.h”文件,它定义了整个SDK51平台的基本数据类型: /* SDK51_type.h */ #ifndef __SDK51_TYPE_H__ #define __SDK51_TYPE_H__ typedef unsigned char uint8_t; typedef unsigned int uint16_t; typedef unsigned long uint32_t; typedef bit bool; #define true 1 #define false 0 #define NULL 0 #endif /* __SDK51_TYPE_H__ */ |
这些基本类型本来应该由标准类型文件“stdint.h”和“stdbool.h”定义的,奈何并不是每个集成开发环境的代码库里都有它们的身影,这里考虑到SDK51的独立性,只好勉为其难给出一个明确的设定的。不管怎么说,“这个可以有!” 系统初始配置(SYSTEM)SYSTEM模块是为SDK51运行做好准备,本身并不提供直接的控制功能。是“4+1”个功能模块中的“1”。 /* SYSTEM. */ typedef struct { uint32_t SysBusClkHz; uint16_t UartBaudrate; uint16_t TicksPerSecond; } SYSTEM_CONFIG_STRUCT_T; void SYSTEM_Init(const SYSTEM_CONFIG_STRUCT_T *cfgPtr); void SYSTEM_Start(void); |
SDK51是一个深度定制的开发平台,在配置系统的过程中,为整个SDK51的功能做好准备,包括: 1. 初始化UART(T1+UART) 2. 初始化系统的滴答发生器(T0)为每隔一个滴答就产生一次定时器中断。 3. 初始化两个外部中断(INT0+INT1)为下降沿触发产生中断 SYSTEM_Init()函数接收一个指向“SYSTEM_CONFIG_STRUCT_T”类型结构体变量的指针,在结构体变量中保存了:系统总线时钟频率(SysBusClkHz)、串行通信波特率(UartBaudrate)和每秒跑多少个滴答(TicksPerSecond)。需要注意的是,这里用了“const”关键字修饰这个结构体变量,意思是说在函数内部不会修改这个结构体变量中的内容,而编译器明确也限制了这个结构体变量只能被读不能修改,否则在编译的时候就会报错。而实际上用这个关键字是希望编译器能够将这个结构体变量的内存分配到FLASH(ROM,Read OnlyMemory,只读存储器)上去,节省宝贵的RAM内存空间。或许对于其它的编译器这样已经足够了,但51的编译器就是懒,得要我们再向它屁股上踹一脚,加上“code”关键字进行修饰,这个在样例程序的代码中将会看到。 还需要注意的是,SYSTEM_Init()是对系统各模块工作模式的设定,但并没有启动它们。只有调用了SYSTEM_Start()才能真正把系统的各个功能启动。也就是说,调用SYSTEM_Init()就是喊“预备!”,而调用了SYSTEM_Start()才能“跑!”。在这之间,可能还会插入一些特定功能的设定,需要在开始起跑的之前准备好,比如说提提裤子,系个鞋带什么的。在SDK51中,主要是为可能触发的中断注册回调函数,这个在样例程序中将会看到。 串口通信服务(UART)UART模块提供了一个同外界进行数字通信的接口。UART可谓是数字通信总线中的常青树了,在嵌入式系统中不分贵贱,芯片的档次高低,UART永远是嵌入式开发者的“贴心小棉袄”(Honey),应用程序可以很容易地通过UART把信息传出来。当然,电脑上也有UART,所以可以同电路板对接通信。即使在只有USB接口的笔记本电脑上,也可以通过一根USB转串口的转换器同具有UART的电路板对接。不过在使用UART的时候还要注意电平匹配,关注一下“TTL”电平和“232”电平。 /* UART. */ void UART_PutChar(uint8_t ch); uint8_t UART_GetChar(void); void UART_PutStr(uint8_t *str); |
没错,这里不需要对UART进行初始化设定,神马”波特率”之类的东东,早都在配置系统的时候(SYSTEM_Init)搞定啦,直接使用发送(UART_PutChar)和接收字符(UART_GetChar)就好。是不是感觉像回家不用做饭呢就有现成饭可以吃一样爽,哈哈。 UART模块中的UART_PutStr()函数实际上是UART_PutChar()的一个应用,在内部重复调用UART_PutChar()函数发送字符串中的字符,没完没了直到遇到字符串的结尾字符(’\0’)后执行完毕。所以说UART_PutStr()不是直接操作底层硬件的,便是“12+1”个API中的“1”。 滴答时基服务(TICK)滴答时基服务可是个高级货,这是源自于RTOS实时操作系统中的设计。在对程序的执行节奏有一定控制的应用程序中,可以由时基协调程序执行的进度。SDK51提供了一个精简版的时间管理组件,好吧,其实就是一个延时服务和定时回调服务。 /* TICK. */ void TICK_Delay(uint8_t ticks); void TICK_InstallCallback( void (*callback)(void) ); |
TICK_Delay()可不是简单地由各种for循环嵌套实现的,它血统高贵,是真正基于硬件定时器T0实现的。实际上在main函数的程序运行的时,定时器T0就一直在后台“默默无闻”的工作着,并且每过一段时间就产生一次定时溢出中断。当通过TICK_Delay()传入一个控制延时的滴答数,程序就卡在这里,直到在SDK51内部运行的定时器服务程序把这个数减到0,程序才能从TICK_Delay()函数掉出来继续运行。TICK_InstallCallback()函数可以将用户自定义的一个小函数插入到定时器服务程序中,在每个滴答到来之时执行一次。插入了小函数的滴答周期必然会长一点,如果不想让滴答定时器服务继续如此辛苦,可以通过传入一个空参数“NULL”,告诉系统内部的滴答定时服务没有额外的小程序需要执行了。 滴答时基服务在系统初始配置之后就已经开始运行了。滴答的周期长度在系统初始配置通过传参预先设定。 控制引脚电平(GPIO)GPIO是51单片机当之无愧的主角,点个灯响个蜂鸣器啥的,可就指着它了。 /* GPIO. */ #define GPIO_PIN(x) (1U<<(x)) #define GPIO_PORT(x) (x) #define eGPIOPinDirOut true #define eGPIOPinDirIn false void GPIO_SetPinsDir(uint8_t portNum, uint8_t pinsMsk, bool enOut); void GPIO_SetPins(uint8_t portNum, uint8_t pinsMsk); void GPIO_ResetPins(uint8_t portNum, uint8_t pinsMsk); uint8_t GPIO_GetPins(uint8_t portNum, uint8_t pinsMsk); |
51单片机的引脚本身并没有设定信号方向的寄存器,然而却有个比较特别的要求,若想从一个引脚上读信号,得先想这个引脚写一个高电平,这个是由芯片内部的引脚电路实现决定的。这个比较奇怪的要求在SDK51中被封装成GPIO_SetPinsDir(),这样看着就舒服多了。 注意,这里的API命名都是“…Pins()”,这个多出来的“s”是非常有意义的。对引脚的传参是引脚的掩码,也就是说,通过这些GPIO的API可以同时操作多个引脚。这个设计对于多引脚同时操作是非常方便的。关于具体的用法可以看一下样例程序。 还有个事还是得说一下,只有先设定引脚数据方向为输出(eGPIOPinDirOut)才能控制引脚电平输出,只有先设定引脚数据方向为输入(eGPIOPinDirIn)才能保证能读回有效的电平信号。 响应外部中断(INT0/INT1)外部中断算是单片机开发中比较高级的主题了,它主要是用来响应异步事件触发的。如果想让你把你的程序从乏味地轮询事件标志中解脱出来,那么就必然要用到外部中断。 /* External Interrupt. */ void INT0_InstallCallback( void (*callback)(void) ); void INT1_InstallCallback( void (*callback)(void) ); | 中断服务程序相对于main()函数中的那个主循环之外,相当于是一个独立的任务。用户自定义的一个小函数通过INT0(1)_InstallCallback()被注册到系统中成为外部中断的服务程序。当在外部中断的引脚(INT0/INT1)上检测到一个电平信号的下降沿时(外部中断时间到来),外部中断就被触发了。这个时候,程序的执行要从主循环中开个小差,转而执行外部中断的服务程序,然后再回到主循环继续运行。也就是说,用户可以自定义一个小程序(外部中断服务程序),可以通过这个通过在外部中断引脚上的电平信号下降沿触发执行。
|