STM32的固件库文件功能相当完善,提供的API完全能满足一般的项目需要。刚从51单片机转到STM32的人,肯定会被这么庞大的东东吓到,51单片机上对IO口操作,简简单单几行代码就搞定了,一个C源文件就搞定。假如用STM32,对IO口进行简单操作,远不是几行代码能搞定的。(其实直接操作寄存器也能搞定)。

   废话不多说,这就带你解剖固件库文件,包你看完之后豁然开朗。

   首先,请看一段简单的C语言代码

#include <stdio.h>

#define base 0x0012ff60
#define flash ((TestType *)base)


typedef struct
{
int i;
int j;
int k;
}TestType;

void main()
{
flash->i = 0;
flash->j = 1;
flash->k = 2;

printf( "%x \n", flash->i);
printf( "%x", &(flash->i));
}

有人会问了这段代码有什么用?我告诉你ST的固件库都是按照这种方式编写的

上面程序打印出来结果为   0

                                  0x0012ff60

把上面的程序分析几分钟后,我再给你揭晓。就拿GPIOA口来说吧,你看stm32f10x.h中能看到

#define PERIPH_BASE            ((uint32_t)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)

 

小学学的加减法就能把GPIOA_BASE 算出来,为什么是这么多呢?看ST的手册,人家就是要规定这样,没办法。

有了地址之后,GPIOA的各个寄存器怎么办涅,它们是控制IO的关键所在。GPIO的各个寄存器都是连续排列的,为什么要连续排呢,方便找啊,用结构体就能很好搞定了

在stm32f10x.h中

typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;

 

 每个寄存器都是32位的,所以很方便用结构体寻找每个寄存器,准备工作都做好了,接下来就要开始针对GPIOA设置了

在core_cm3.c中

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)

把地址强制转换成结构体指针,这样就能很方便对寄存器操作。

寄存器搞定了,接下来就真对用户操作了,要很好的让用户使用,就得人性话点,所以用enum枚举变量就能实现,你就再也不用为想输入的值去烦恼了,直接输入名称就行了,拿IO口的输出速度来说

typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

 

要说人性化,ST还有一点不得不提,那就是位带操作,改变了 写地址-取地址数据-位操作-数据写入地址的操作,直接改为写地址-数据写入

,直接位操作。

ST的编程结构是很值得借鉴的,使得编程序时结构很清晰,脉络很清楚。至于如何去发挥,得看个人水平了

posted @ 2012-03-30 16:54 Mr.Ding++ 阅读(928) 评论(4) 编辑

1、STM32的输入输出管脚有下面8种可能的配置:(4输入+2输出+2复用输出)

① 浮空输入_IN_FLOATING

② 带上拉输入_IPU  

③ 带下拉输入_IPD           

④ 模拟输入_AIN
⑤ 开漏输出_OUT_OD     

⑥ 推挽输出_OUT_PP

⑦ 复用功能的推挽输出_AF_PP 

⑧ 复用功能的开漏输出_AF_OD

1.1   I/O口的输出模式下,有3种输出速度可选(2MHz、10MHz和50MHz),这个速度是指I/O口驱动电路的响应速度而不是输出信号的速度,输出信号的速度与程序有关(芯片内部在I/O口的输出部分安排了多个响应速度不同的输出驱动电路,用户可以根据自己的需要选择合适的驱动电路)。通过选择速度来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。高频的驱动电路,噪声也高,当不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的EMI性能。当然如果要输出较高频率的信号,但却选用了较低频率的驱动模块,很可能会得到失真的输出信号。

关键是GPIO的引脚速度跟应用匹配(推荐10倍以上?)。比如:

1.1.1       对于串口,假如最大波特率只需115.2k,那么用2M的GPIO的引脚速度就够了,既省电也噪声小。

1.1.2       对于I2C接口,假如使用400k波特率,若想把余量留大些,那么用2M的GPIO的引脚速度或许不够,这时可以选用10M的GPIO引脚速度。

1.1.3       对于SPI接口,假如使用18M或9M波特率,用10M的GPIO的引脚速度显然不够了,需要选用50M的GPIO的引脚速度。

1.2         GPIO口设为输入时,输出驱动电路与端口是断开,所以输出速度配置无意义。

1.3         在复位期间和刚复位后,复用功能未开启,I/O端口被配置成浮空输入模式。

1.4         所有端口都有外部中断能力。为了使用外部中断线,端口必须配置成输入模式。

1.5         GPIO口的配置具有上锁功能,当配置好GPIO口后,可以通过程序锁住配置组合,直到下次芯片复位才能解锁。

2、STM32中如何配置片内外设使用的IO端口

首先,一个外设经过 ①配置输入的时钟和 ②初始化后即被激活(开启);③如果使用该外设的输入输出管脚,则需要配置相应的GPIO端口(否则该外设对应的输入输出管脚可以做普通GPIO管脚使用);④再对外设进行详细配置。

对应到外设的输入输出功能有下述三种情况:
一、外设对应的管脚为输出:需要根据外围电路的配置选择对应的管脚为复用功能的推挽输出或复用功能的开漏输出。
二、外设对应的管脚为输入:则根据外围电路的配置可以选择浮空输入、带上拉输入或带下拉输入。
三、ADC对应的管脚:配置管脚为模拟输入。

如果把端口配置成复用输出功能,则引脚和输出寄存器断开,并和片上外设的输出信号连接。将管脚配置成复用输出功能后,如果外设没有被激活,那么它的输出将不确定。

3、通用IO端口(GPIO)初始化:

3.1            GPIO初始化

3.1.1       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | B | C, ENABLE):使能APB2总线外设时钟

3.1.2       RCC_ APB2PeriphResetCmd (RCC_APB2Periph_GPIOA | B | C, DISABLE):释放GPIO复位

3.2            配置各个PIN端口(模拟输入_AIN、输入浮空_IN_FLOATING、输入上拉_IPU、输入下拉_IPD、开漏输出_OUT_OD、推挽式输出_OUT_PP、推挽式复用输出_AF_PP、开漏复用输出_AF_OD)

3.3            GPIO初始化完成

posted @ 2012-02-22 13:56 Mr.Ding++ 阅读(35) 评论(0) 编辑

  队列,与栈相同,实现一个队列同样需要顺序表活者链表作为基础。队列是一种先进先出的线性表。数据只能从队尾进入队列,从对头出队列,这里重点说一下链队列。

首先说 链队列的实现。

定义队列

typedef struct QNode{
ElemType data;
struct QNode *next;
} QNode , *QueuePtr;
typedef struct{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;

初始化队列

void initQueue(LinkQueue *q)
{
/*初始化一个空队列*/
q->front = q->rear = (QueuePtr)malloc(sizeof(QNode)); /*创建一个头结点,队头队尾指针 指向该结点*/
if( !q->front) exit(0); /*创建头结点失败*/
q->front->next = NULL; /*头结点指针域置NULL*/
}


入队列操作

void EnQueue(LinkQueue *q, ElemType e)
{
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode)); /*创建一个队列元素结点*/
if( !q->front) exit(0); /*创建头结点失败*/
p->data = e;
p->next = NULL;
q->rear ->next = p;
q->rear = p;
}


出队列操作

void DeQueue(LinkQueue *q, ElemType *e)
{
/*如果队列q不为空,删除q的队头元素,用e返回其值*/
QueuePtr p;
if(q->front == q->rear) return; /*队列为空,返回*/
p = q->front->next;
*e = p->data;
q->front->next = p->next;
if(q->rear == p) q->rear = q->front; /*如果队头就是队尾,则修改队尾指针*/
free(p);
}



 

posted @ 2012-02-16 11:21 Mr.Ding++ 阅读(12) 评论(0) 编辑

栈,是一个先进后出的线性表。

最开始栈中不含有任何数据,叫做空栈,此时栈顶就是栈底。

线性表有两种存储形式,及顺序表存储和链表存储。一般的栈是用顺序表的形式实现的

定义一个顺序栈

typedef struct
{
ElemType *base;
ElemType *top;
int stacksize;
}sqStack;


创建一个栈

initStack(sqStack *s)
{
/*内存中开辟一段连续空间作为栈空间,首地址赋值给s->base*/
s->base = (ElemType *)malloc(STACK_INIT_SIZE * sizeof(ElemType));
if(!s->base) exit(0); /*分配空间失败*/
s->top = s->base; /*最开始,栈顶就是栈底*/
s->stacksize = STACK_INIT_SIZE; /*最大容量为STACK_INIT_SIZE */
}

入栈

Push(sqStack *s, ElemType e)
{
if(s->top - s->base >= s->stacksize){
/*栈满,追加空间*/
s->base = (ElemType *)realloc(s->base, (s->stacksize +
STACKINCREMENT)*sizeof(ElemType));
if(!s->base) exit(0); /*存储分配失败*/
s->top = s->base + s->stacksize;
s->stacksize = s->stacksize + STACKINCREMENT; /*设置栈的最大容量*/
}
*(s->top) = e; /*放入数据*/
s->top++;
}

出栈

Pop(sqStack *s , ElemType *e)
{
if(s->top == s->base) return;
*e = *--(s->top);
}



栈的长度

int StackLen(sqStack s)
{
return (s.top - s.base) ;
}



posted @ 2012-02-15 22:45 Mr.Ding++ 阅读(87) 评论(0) 编辑

  定义一张顺序表就是在内存中开辟一段连续的存储空间,并给他取个名字。

定义顺序列表的方法:一、静态定义;二、动态定义;

静态定义

#define MaxSize 100
ElemType Sqlist[MaxSize];
int len;

动态定义

#define MaxSize 100
typedef struct{
ElemType *elem;
int length;
int listsize;
} Sqlist;
void initSqlist(Sqlist *L)
{
L->elem = (int *)malloc(MaxSize*sizeof(ElemType));
if (!L->elem)
{
exit(0);
}
L->length = 0;
L->listsize = Maxsize;
}

像顺序表中插入元素

    在长度为n的顺序表中的第i个位置插入新元素。例如:

    A(a1,a2,...ai-1,ai,...an)

    在第i个位置插入新元素item后,变为

    A (a1,a2,...ai-1,item.ai,... an)

静态表中插入:

void InserElem(ElemType Sqlist[], int n, int i, ElemType item)
{
int t;
if (n==MaxSize || i<1 || i>n+1)
{
exit(0);
}
for (t=n-1; t>=i-1; t--)
{
Sqlist[t+1] = Sqlist[t];
}
Sqlist[i-1] = item;
n = n + 1;
}

 

  动态表中插入

 

void InsertElem(Sqlist *L, int i, ElemType item)
{
ElemType *insertPtr, *p;
if(i<1 || i>L->length+1)
{
exit(0);
}
if(L->length > L->listsize)
{
L->elem = (ElemType *)realloc(L->elem, (L->listsize+10)*sizeof (ElemType));
L->listsize = L->listsize + 10;
}
insertPtr = &(L->elem[i-1];
for (p=&(L->elem[L->length-1]); p>=inserPtr; p--)
{
*(p+1) = *p;
}
*insertPrt = item;
L->length++;
}

 

 

posted @ 2012-02-15 19:26 Mr.Ding++ 阅读(561) 评论(0) 编辑

 一、BDATA 区
下面的代码访问状态寄存器的特定位,把访问定义在DATA 段中的一个字节和通过位名和位号访问同样的可位寻址字节的位的代码对比。注意:对变量位进行寻址产生的汇编代码比检测定义在DATA 段的状态字节位所产生的汇编代码要好,如果你对定义在 BDATA 段中的状态字节中的位采用偏移量进行寻址,而不是用先前定义的位变量名时,编译后的代码是错误的

下面的例子中 use_bitnum_status 的汇编代码比 use_byte_status 的代码要大

1 //定义一个字节宽状态寄存器
2 unsigned char data byte_status=0x43;
3
4 //定义一个可位寻址状态寄存器
5 unsigned char bdata bit_status=0x43;
6 //把 bit_status 的第 3位设为位变量
7 sbit status_3=bit_status^3;
8
9 bit use_bit_status(void);
10
11 bit use_bitnum_status(void);
12
13 bit use_byte_status(void);
14
15 void main(void)
  {
16 unsigned char temp=0;
17 if (use_bit_status())
       {      //如果第 3位置位 temp 加1
18 temp++;
19 }
20 if (use_byte_status())
       {     //如果第 3 位置位 temp 再加 1
21 temp++;
22 }
23 if (use_bitnum_status())
       {    //如果第 3 位置位 temp 再加 1
24 temp++;
25 }
26 }
27
28 bit use_bit_status(void)
   {
29 return(bit)(status_3);
30 }
31
32 bit use_bitnum_status(void)
   {
33 return(bit)(bit_status^3);
34 }
35
36 bit use_byte_status(void)
   {
37 return byte _status&0x04;
38 }

目标代码列表
          ; FUNCTION main (BEGIN)
                                           ; SOURCE LINE # 15
                                           ; SOURCE LINE # 16
0000 E4                CLR    A
0001 F500   R          MOV    temp,A
                                           ; SOURCE LINE # 17
0003 120000 R          LCALL  use_bit_status
0006 5002              JNC    ?C0001
                                           ; SOURCE LINE # 18
0008 0500   R          INC    temp
                                           ; SOURCE LINE # 19
000A        ?C0001:
                                           ; SOURCE LINE # 20
000A 120000 R          LCALL  use_byte_status
000D 5002              JNC    ?C0002
                                           ; SOURCE LINE # 21
000F 0500   R          INC    temp
                                           ; SOURCE LINE # 22
0011        ?C0002:
                                           ; SOURCE LINE # 23
0011 120000 R          LCALL  use_bitnum_status
0014 5002              JNC    ?C0004
                                           ; SOURCE LINE # 24
0016 0500   R          INC    temp
                                           ; SOURCE LINE # 25
                                           ; SOURCE LINE # 26
0018        ?C0004:
0018 22                RET
           ; FUNCTION main (END)
           ; FUNCTION use_bit_status (BEGIN)
                                           ; SOURCE LINE # 28
                                           ; SOURCE LINE # 29
0000 A200   R          MOV C,status_3
                                           ; SOURCE LINE # 30

0002        ?C0005:
0002 22                RET
                  ; FUNCTION use_bit_status (END)
                  ; FUNCTION use_bitnum_status (BEGIN)
The compiler obtains the desired bit by using the entire byte instead of using
a bit address.
                                           ; SOURCE LINE # 32
                                           ; SOURCE LINE # 33
0000 E500  R           MOV  A,bit_status
0002 6403              XRL  A,#03H
0004 24FF              ADD  A,#0FFH
                                           ; SOURCE LINE # 34
0006       ?C0006:
0006 22                RET
                   ; FUNCTION use_bitnum_status (END)
                   ; FUNCTION use_byte_status (BEGIN)
                                           ; SOURCE LINE # 36
                                           ; SOURCE LINE # 37
0000 E500  R           MOV  A,byte_status
0002 A2E2              MOV  C,ACC.2

                                           ; SOURCE LINE # 38
0004       ?C0007:
0004 22                RET
                   ; FUNCTION use_byte_status (END)

二、PDATA和XDATA段
在这两个段声明变量和在其它段的语法是一样的 PDATA 段只有256 个字节,而XDATA段可达65536 个字节 下面是一些例子
unsigned char xdata system_status=0;
unsigned int pdata unit_id[2];
char xdata inp_string[16];
float pdata outp_value;
对 PDATA 和 XDATA 的操作是相似的 对 PDATA 段寻址比对 XDATA 段寻址要快 因为对PDATA 段寻址只需要装入 8 位地址 而对 XDATA 段寻址需装入 16 位地址 所以尽量把外部数据存储在PDATA段中对 DATA 和XDATA 寻址要使用 MOVX 指令需要两个处理周期

1 #include <reg51.h>
2
3 unisgned char pdata inp_reg1;
4
5 unsigned char xdata inp_reg2;
6
7 void main(void)
{
8 inp_reg1=P1;
9 inp_reg2=P3;
10 }

产生的目标代码列表
             ; FUNCTION main (BEGIN)
                                     ; SOURCE LINE # 7
                                     ; SOURCE LINE # 8
注意 'inp_reg1=P1' 需要4个指令周期
0000 7800    R     MOV      R0,#inp_reg1
0002 E590          MOV      A,P1
0004 F2            MOVX     @R0,A
                                     ; SOURCE LINE # 9
注意 'inp_reg2=P3' 需要5个指令周期
0005 900000  R     MOV      DPTR,#inp_reg2
0008 E5B0          MOV      A,P3
000A F0            MOVX     @DPTR,A
                                     ; SOURCE LINE # 10
000B 22            RET
             ; FUNCTION main (END)

    经常 外部地址段中除了包含存储器地址外还包含 I/O 器件的地址 对外部器件寻址可通过指针或 C51 提供的宏 我建议使用宏对外部器件进行寻址 因为这样更有可读性宏定义使得存储段看上去像 char 和 int 类型的数组 下面是一些绝对寄存器寻址的例子
    inp_byte=XBYTE[0x8500];     // 从地址8500H读一个字节
    inp_word=XWORD[0x4000];     // 从地址4000H读一个字和2001H
    c=*((char xdata *) 0x0000); // 从地址0000读一个字节
XBYTE[0x7500]=out_val;      // 写一个字节到 7500H可对除BDATA和BIT段之外的其它数据段采用以上方法寻址 通过包含头文件 absacc.h来进行绝对地址访问

 

三、指针
C51 提供一个 3 字节的通用存储器指针,通用指针的头一个字节表明指针所指的存储区空间,另外两个字节存储 16位偏移量 对于DATA IDATA 和PDATA 段 只需要 8 位偏移量Keil允许使用者规定指针指向的存储段这种指针叫具体指针,使用具体指针的好处是节省了存储空间,编译器不用为存储器选择和决定正确的存储器操作指令产生代码。这样就使代码更加简短,但你必须保证指针不指向你所声明的存储区以外的地方,否则会产生错误,而且很难调试。
下面的例子反映出使用具体指针比使用通用指针更加高效,使用通用指针的第一个循环需要378个处理周期,使用具体指针只需要 151 个处理周期
指针类型       大小
通用指针       3 字节
XDATA指针  2 字节
CODE 指针   2 字节
IDATA指针   1 字节
DATA指针    1 字节
PDATA指针  1 字节
         

1       #include <absacc.h>
2
3 char *generic_ptr;
4
5 char data *xd_ptr;
6
7 char mystring[]="Test output";
8
9 main()
{
10 1 generic_ptr=mystring;
11 1 while (*generic_ptr)
{
12 2 XBYTE[0x0000]=*generic_ptr;
13 2 generic_ptr++;
14 2 }
15 1
16 1 xd_ptr=mystring;
17 1 while (*xd_ptr)
{
18 2 XBYTE[0x0000]=*xd_ptr;
19 2 xd_ptr++;
20 2 }
21 1 }

                ; FUNCTION main (BEGIN)
                                          ; SOURCE LINE # 9
                                          ; SOURCE LINE # 10
0000 750004    R     MOV   generic_ptr,#04H
0003 750000    R     MOV   generic_ptr+01H,#HIGH mystring
0006 750000    R     MOV   generic_ptr+02H,#LOW mystring
0009           ?C0001:
                                          ; SOURCE LINE # 11
0009 AB00      R     MOV   R3,generic_ptr
000B AA00      R     MOV   R2,generic_ptr+01H
000D A900      R     MOV   R1,generic_ptr+02H
000F 120000    E     LCALL ?C_CLDPTR
0012 FF              MOV   R7,A
0013 6011            JZ    ?C0002
                                          ; SOURCE LINE # 12
0015 900000          MOV   DPTR,#00H
0018 F0              MOVX  @DPTR,A
                                          ; SOURCE LINE # 13
0019 7401            MOV   A,#01H
001B 2500      R     ADD   A,generic_ptr+02H
001D F500      R     MOV   generic_ptr+02H,A
001F E4              CLR   A
0020 3500      R     ADDC  A,generic_ptr+01H
0022 F500      R     MOV   generic_ptr+01H,A

                                          ; SOURCE LINE # 14
0024 80E3            SJMP  ?C0001
0026           ?C0002:
                                          ; SOURCE LINE # 16
0026 750000    R     MOV   xd_ptr,#LOW mystring
0029           ?C0003:
                                          ; SOURCE LINE # 17
0029 A800      R     MOV   R0,xd_ptr
002B E6              MOV   A,@R0
002C FF              MOV   R7,A
002D 6008            JZ    ?C0005
                                          ; SOURCE LINE # 18
002F 900000          MOV   DPTR,#00H
0032 F0              MOVX  @DPTR,A
                                          ; SOURCE LINE # 19
0033 0500      R     INC   xd_ptr
                                          ; SOURCE LINE # 20
0035 80F2            SJMP  ?C0003
                                          ; SOURCE LINE # 21
0037           ?C0005:

0037 22              RET
            ; FUNCTION main (END)
由于使用具体指针能够节省不少时间,所以我们一般都不使用通用指针。

尽量使用指定存储类型的指针(memory-specific pointer)不使用一般指针(generic pointer)

   如果程序移植的时候不做修改,所有的指针将都是“一般指针”,我们的建议是尽量修改为“指定存储类型”的指针,因为它的效率要高很多。

首先一般指针使用三个字节,第一个字节指示是什么存储类型,后两个字节是指针指向的地址。“指定存储类型”的指针则只用一个或者两个字节。可见“一般指针”占用内存多。

     另外,为了取得“一般指针”指向的数据,程序必须调用?C?CLDPTR函数,在?C?CLDPTR中根据指针第一字节指示的存储类型采取不同的读取RAM的方式。而使用“指定存储类型”的指针时,采取哪种读取RAM的方式在编译时已经确定,不用在运行时动态判断。可见“一般指针”运行效率低。

    “指定存储类型”的指针指向的变量必须要有明确的存储类型。一般情况下程序中使用指针是为了指向大块内存,而KeilC中大块内存一般定义为外部变量。依照第一点移植建议,所有的外部变量都定义为xdata或者pdata类型了,有明确的存储类型,这说明程序中的指针基本都可以改为“指定存储类型”的指针。

posted @ 2012-02-13 22:26 Mr.Ding++ 阅读(54) 评论(0) 编辑
摘要: 例:应用程序的 2 个任务使用了延时函数 OSTimeDly () 进行延时,在任务 MyTask 中还调用了函数 OSTimeDlyResume () 取消了任务 YourTask 的延时(为了观察任务YourTask 的延时时间的变化,在钩子函数 OSTimeTickHook () 输出了任务 YourTask在延时时间到时的时钟节拍数)。 OSTimeTickHook钩子函数程序如下#if OS_CPU_HOOKS_EN > 0 INT8U d=0;INT8U l=0;INT8U n=0;INT8U m=0; INT16U tt=1; ...阅读全文
posted @ 2012-02-10 11:30 Mr.Ding++ 阅读(113) 评论(0) 编辑