如何做一个单片机程序通用模版

来源:本站
导读:目前正在解读《如何做一个单片机程序通用模版》的相关信息,《如何做一个单片机程序通用模版》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《如何做一个单片机程序通用模版》的详细说明。
简介:随着一年多的真实产品开发——虽然,嘿嘿,按照他们的说法,我这实在有点非主流,但在后期的调试中吃了不少苦头,于是在蛋疼的调试中,以及在阅读诸如《代码大全》这些书籍时,渐渐地萌生了一种如何做一个方便调试,方便扩展,方便移植的单片机程序模板

而我,目前是渐渐地把它用在我自己的一个个人项目,一个用stm8的项目上,实际上,对这个模板的认识,最初只是有一种模糊的看法,渐渐的渐渐的,理论越来越清晰,但是,还需要在实际操作中得到完善和补充——比如之前对于io口的抽象,我一直简单的以为一个宏定义就可以解决,但如果不是到前天晚上 sjl 的那个问题,我也没有想到这个问题没有我想的那么简单,虽然后来我还是很快想到了办法解决。

所以,这只是一个开放讨论的帖子,把我的一些想法,以及我已经在做的部分代码贴出来,希望大家一起讨论讨论,给点建议,或者评论评论,只希望,我,还有大家,能够在共同讨论中,找到一种也许更好的方式去写我们的单片机程序。

(这里插一句题外话:事实上,我从来没有特别把单片机编程和一般意义上的编程分离开来,在我看来他们是一回事,并且也应该尝试使用一般意义上编程的一些成熟经典,方法,理论,来改善,来构建高质量的代码。)

--------------------------------------------------------------------------------------------------------------------------------------------------

首先解释一下,这个所谓的 单片机程序通用模板,我对它的定义,目前可以用以下特征来界定:

1.它是通过恰当的分层,使其应用层和硬件底层抽离开来,这样做的目的,主要是 实现 不同MCU可以用最少的改动来实现移植,并保持良好兼容性。

2.它是容易增删模块的,按照我个人的看法,单片机的功能模块,自然是以外设为单位来划分比较合适。

——注意,在这里,IO口也算是一种外设,当然,利用IO口去点亮LED,数码管或者读按键,这个和IO口的设置初始化是两回事。因为IO口设置和初始化可以为很多事情服务,点亮LED和读键只是其中一个功能。

--------------------------这个界定还不算很完整,但它的确是我目前主要考虑的----------------------------------

对于这个模板,我最先考虑的是利用分层和抽象的思想,实现第一个目标。

现在先来说说这个基本思路,纸上谈谈兵先~~

1.任何一个单片机程序都会包含两大层,一个是硬件底层的操作,比如操作相应寄存器,去打开某些外设,通过外设获取外部信息,或者影响外部。

另一个则是 上层应用层,比如说,我要对通过单总线读来的温度值进行存储,显示。

这两个层是单片机程序的最顶层 和 最底层。

最顶层,往往只是一个 main函数,当然还包括其他中断子函数,它们就像一个一个独立的线程,和main是可能并行处理的。

最底层,对于玩51的时候,可能没有这个概念,因为我们都是直接操作寄存器的。但是如果接触过其他单片机,特别是st系列的,了解过它那个固件库的必然就会知道我说的正是这个东西。

stm8/32都有一个架构上几乎一模一样的固件库——我想,很多人也许和我一样,最初的编程规范习惯,以及之所以会产生这种分层思想,就是因为受到这个固件库的启发。

这个最底层的目的是,使我们不需要直接面对寄存器,而是以外设为单元,方便使用。

假如有一天,我们需要在51上或者其他MCU上也使用这个思想,我们也可以参照这种分类,来自行写一个属于我们的给51的,给430的固件库。

对于我们熟悉的51,我相信这实在是一个简单不过的事情。

在最底层 和 最高层之间,按照我的理解,还需要做两个层。

我们叫做第二层 和 第三层,最高层为第一层,最底层为第四层。

第二层就是我们的功能模块能,它都包含一些什么东西呢?

它包含我们需要操作的外设,比如说 ds18b20啊,比如说数码管显示啊,比如说读键啊.....等等,因为,我们把所有这些都定义为 外设。

如果对比st的固件库,也许你会认为我多此一举,不,是多此两举。

第一,为什么要把外设的操作脱离寄存器那个层次的硬件底层?

第二,就算是把外设单独出去吧,那为什么还有一层呢?不可以直接在 第四层基础上,建立一个外设层么——就好像,把st现有的固件库里,把那些很基本的gpio啊,exti啊等这些辅助性的更一般的,通用的部分 和 什么 iic 啊 spi啊之类的外设分离就好了。

之所以这么做,我的考虑在于:

1.我这个模板并不只是给stm8或者stm32使用,我是希望它可以与MCU无关;

所以,在st提供的固件库,可以操作gpio啊iic等固件库的基础上,我希望增加一个 硬件抽象层,这一层,它可以通过一个宏选择到对应具体不同MCU的那个第四层硬件底层API函数。

举个简单的例子,还是以st固件库为例。

对于gpio的操作,通常由 设置方向 和 读写两个方向。

这些操作st的固件库里都有,但是,现在,为了兼容不同的MCU,我要在第三层这个抽离层,再次封装一个gpio的 设置方向函数,读函数,写函数,而它将会通过一个宏,来选择st固件库里相应的操作函数。

以我写的一段代码为例:

#define STM8S

/*这个宏是用于选择stm8s,这个取决于你的这个程序将使用在哪一个MCU上*/

#include "gpio.h"

/*

gpio.h中的内容

/*

For distinguish different MCU.

1.Here will be used a Macro to choose different GPIO hardware

headfile.

*/

#ifndef _GPIO_

#define _GPIO_

#ifdef STM8S

#include "stm8s_gpio.h"

#include "datastruct.h"

/* Typedef Data-struct for compation */

typedef GPIO_TypeDef GPIO_PORT;

typedef GPIO_Pin_TypeDef GPIO_PIN;

typedef GPIO_Mode_TypeDef GPIO_MODE;

typedef BitStatus GPIO_BIT;

/*

I/O mode & direction

In-Mode,concern two types:

1.Float Input(FL)

2.Pull-up(PU)

Interrupt will not setting here;

Out-Mode,no care about speed,concern two types:

1.OD

2.Push-Pull(PP)

default set the IO state to LOW,and no care about speed,as STM8 MCU can be set

different speed for GPIOs.

*/

#define IN_FL GPIO_MODE_IN_FL_NO_IT

#define IN_PU GPIO_MODE_IN_PU_NO_IT

#define OUT_OD GPIO_MODE_OUT_OD_LOW_FAST

#define OUT_PP GPIO_MODE_OUT_PP_LOW_FAST

/*

Basic Operation on IO,Include:

1. Set 1,Set 0;

2. Read in data;

All above two,include operation on bit & port

*/

/* Function declaration */

void Set_GPIO_Mode(GPIO_PORT* GPIOx, GPIO_PIN GPIO_Pin,GPIO_MODE mode);

void Set_Port_Mode(GPIO_PORT* GPIOx,GPIO_MODE PortMode[8]);

void Write_Port(GPIO_PORT* GPIOx,uint8_t PortValue);

void Write_Pin_High(GPIO_PORT* GPIOx, GPIO_PIN GPIO_Pin);

void Write_Pin_Low(GPIO_PORT* GPIOx, GPIO_PIN GPIO_Pin);

uint8_t Read_Port(GPIO_PORT* GPIOx);

GPIO_BIT Read_Pin(GPIO_PORT* GPIOx,GPIO_PIN GPIO_Pin);

#endif

#endif

*/

void Set_GPIO_Mode(GPIO_PORT* GPIOx, GPIO_PIN GPIO_Pin,GPIO_MODE mode)

{

GPIO_Init(GPIOx, GPIO_Pin, mode);

}

void Set_Port_Mode(GPIO_PORT* GPIOx,GPIO_MODE PortMode[8])

{

GPIO_PIN PinMask[8] = {

GPIO_PIN_0,GPIO_PIN_1,GPIO_PIN_2,GPIO_PIN_3,

GPIO_PIN_4,GPIO_PIN_5,GPIO_PIN_6,GPIO_PIN_7,

};

U8 i;

for(i = 0;i < 8;i++)

Set_GPIO_Mode(GPIOx,PinMask[i],PortMode[i]);

}

//---------------------------------------------------------------------

void Write_Port(GPIO_PORT* GPIOx,U8 PortValue)

{

GPIO_Write(GPIOx, PortValue);

}

void Write_Pin_High(GPIO_PORT* GPIOx, GPIO_PIN GPIO_Pin)

{

GPIO_WriteHigh(GPIOx, GPIO_Pin);

}

void Write_Pin_Low(GPIO_PORT* GPIOx, GPIO_PIN GPIO_Pin)

{

GPIO_WriteLow(GPIOx, GPIO_Pin);

}

//-----------------------------------------------------------------------

U8 Read_Port(GPIO_PORT* GPIOx)

{

return GPIO_ReadInputData(GPIOx);

}

GPIO_BIT Read_Pin(GPIO_PORT* GPIOx,GPIO_PIN GPIO_Pin)

{

return GPIO_ReadInputPin(GPIOx, GPIO_Pin);

}

这是一份对应于stm8s的抽象层gpio源文件。

我们可以以此为例子,写出针对于另一个平台的抽象层gpio.c

但是,这里要遵循一个原则:

那就是,所有的这些源文件里的函数,必须是同名。

比如说,不管是51的还是430的抑或stm8,设置IO方向的,永远叫 Set_Gpio_Direction(.....)

它们唯一的区别,只是在不同的宏条件下,实际的函数实现内容是不一样的。

这种同名不同内容,正是实现MCU抽象的核心方法所在。

BTW:

当然,刚刚我突然想,反正这个第三层抽离层,针对不同MCU都不一样,那还不如直接跳过第四层,直接把固件库里对寄存器的操作直接复制过来,把第四层去掉好了。

我觉得这也是不错的并且更直接的方法,我最开始总认为要第四层,那纯粹是因为受 st提供了一个固件库的影响所致。

补充一句。 经过了第三层的 硬件抽象层以后,我们再向上时,我们操作任何东西,都不希望见到,或者说会屏蔽掉具体的硬件信息,比如说,在第二层及其以上,我们绝对不会直接操作P0 P1,GPIOA GPIOB,它们将通过 第二层的头文件定义,从这一层开始向上屏蔽。包括第二层自身的操作函数,也将见不到这类名字。

第二层 这一层是以模块为单位抽离的。

我们要分清楚,模块的定义是什么?

比方说,ds18b20肯定算一个模块,这个你肯定不会怀疑,那么,gpio算不算一个模块呢?这个问题也许很奇怪,也许你会说,当然算啊,你第三层不是就以gpio为例子实现的 硬件抽象 么?

这里,要区分,那个硬件抽象 只是对gpio的基本操作,但是,并不代表,所有需要gpio来完成的功能,就属于那个抽象层。

比方说,我要用gpio来显示数码管,那数码管就是一个 模块,其实说白了,ds18b20也是用一个gpio来实现单线读写的。

所以,这里一定要区分清楚。

但有一个问题比较复杂。

那就是,对于一些硬件抽象层次的 模块该如何处理。

举个例子,在51里,我们没有硬件的 iic spi等外设——这里说的是 标准8051,不包括那些增强型。

而stm8s却一个不缺。

这个时候,如果我们把它归入 第三层 硬件抽象层,那以后,遇到51,或者那个MCU并不存在硬件上的数字接口,需要软件模拟的那该怎么办呢?

很明显,不能归入第三层,因为第三层是对 硬件 的抽象,但这种数字接口本身不属于 硬件必备,它不如gpio这样必不可确。

那我们就归到第二层吧?也不行,为什么呢?

第二层本来经过第三层隔离,它已经不再需要处理和相关硬件相关的信息,如果我们现在遇到一个硬件上存在数字接口的MCU,那我们势必要为此改动这个 第二层,但在我们的原则上,它是不应该因为换MCU而更换的。

在这里,我最终会选择,把它归为 第三层,尽管,它实际上,并不是实际存在的硬件,而是软件模拟,但是软件模拟所需的硬件底层信息已经在第三层同层的其他函数里包含了。而向上,它就如同一个真实存在的硬件一般,这样,至少我可以保证,第二层是无需更改的。

这一部分,我只能给出 一个 我写的 segment源文件,它是 动态扫描数码管(不通过IO扩展方式的),至于 前面更多篇幅讨论的数字接口部分,由于我还没有做到,所以,嘿嘿,暂时给不了了,只能纸上谈兵了。

下面贴segment的代码,之前合在一起发有点乱,这次,头文件和源文件分两个发,更清楚。

#ifdef STM8S

#ifndef _SEG_

#define _SEG_

#include "stm8s.h"

#include "gpio.h"

#include "datastruct.h"

#define MAX_SEG 11

/**/

#define Seg_Bit_Enable 0

/*

Hardware pin define

/ ./ ./ ./ ./

bit_control seg_control

1 a

2 b

3 f

4 e

----------- d

dp

c

g

left-->right

*/

/*

connecting:

seg_control: PB

bit_control:

1:PD3

2:PD5

3:PD7

4:PD6

*/

#define Seg_Bit_Port GPIOD

#define Seg_Bit_1 GPIO_PIN_7

#define Seg_Bit_2 GPIO_PIN_5

#define Seg_Bit_3 GPIO_PIN_3

#define Seg_Bit_4 GPIO_PIN_6

#define Seg_Dig_Port GPIOB

void Segment_IO_Initial(void);

void Segment_Show(GPIO_PORT *SegPort,GPIO_PIN SegNum,U8 value);

void Segment_Not_Show(GPIO_PORT *SegPort,GPIO_PIN SegNum,U8 value);

void Segment_demo(void);

void Segment_Bit_Enable(GPIO_PORT* Port,GPIO_PIN Pin);

void Segment_Show_Value(GPIO_PORT* Port,U8 value);

#endif

#endif

#define STM8S

#include "segment.h"

/* Segment code*/

/*

abcdefg dp --- bit 0~7

0 1 2 3 4 5 6 7 8 9

0xc0 0xf9 0xa4 0xb0 0x99 0x92 0x82 0xf8 0x80 0x90

if showdrop.

minus 0x80

0x40 0x79 0x24 0x30 0x11 0x12 0x02 0x78 0x00 0x10

*/

const U8 SegCode[MAX_SEG] = {

0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,

0xff

};

void Segment_IO_Initial(void)

{

Set_GPIO_Mode(Seg_Dig_Port, GPIO_PIN_0,OUT_OD);

Set_GPIO_Mode(Seg_Dig_Port, GPIO_PIN_1,OUT_OD);

Set_GPIO_Mode(Seg_Dig_Port, GPIO_PIN_2,OUT_OD);

Set_GPIO_Mode(Seg_Dig_Port, GPIO_PIN_3,OUT_OD);

Set_GPIO_Mode(Seg_Dig_Port, GPIO_PIN_4,OUT_OD);

Set_GPIO_Mode(Seg_Dig_Port, GPIO_PIN_5,OUT_OD);

Set_GPIO_Mode(Seg_Dig_Port, GPIO_PIN_6,OUT_OD);

Set_GPIO_Mode(Seg_Dig_Port, GPIO_PIN_7,OUT_OD);

Set_GPIO_Mode(Seg_Bit_Port, Seg_Bit_1, OUT_OD);

Set_GPIO_Mode(Seg_Bit_Port, Seg_Bit_2, OUT_OD);

Set_GPIO_Mode(Seg_Bit_Port, Seg_Bit_3, OUT_OD);

Set_GPIO_Mode(Seg_Bit_Port, Seg_Bit_4, OUT_OD);

}

void Segment_Show(GPIO_TypeDef *SegPort,GPIO_Pin_TypeDef SegNum,uint8_t value)

{

Write_Pin_Low(Seg_Bit_Port,SegNum);

}

void Segment_Not_Show(GPIO_TypeDef *SegPort,GPIO_Pin_TypeDef SegNum,uint8_t value)

{

Write_Pin_High(Seg_Bit_Port,SegNum);

}

void Segment_demo(void)

{

Segment_Show(Seg_Bit_Port,Seg_Bit_1,0);

Segment_Show(Seg_Bit_Port,Seg_Bit_2,0);

Segment_Show(Seg_Bit_Port,Seg_Bit_3,0);

Segment_Not_Show(Seg_Bit_Port,Seg_Bit_4,0);

}

void Segment_Bit_Enable(GPIO_PORT* Port,GPIO_PIN Pin)

{

Segment_Show(Port,Pin,0);

}

void Segment_Show_Value(GPIO_PORT* Port,U8 value)

{

Write_Port(Port,value);

}

提醒:《如何做一个单片机程序通用模版》最后刷新时间 2024-03-14 00:56:08,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《如何做一个单片机程序通用模版》该内容的真实性请自行鉴别。