Transcript 结构体
如何用C 来完成SN8系列芯片
的程序设计
2015/4/9
Review
SN8 C studio 的安装与使用
数据类型与运算
程序流程控制
函数
结构体、联合在SN8 C程序中的应用
中断
位操作
内嵌汇编
程序结构
2
SN8 C studio 的安装与使用
SN8 C studio 的安装
SN8 C studio应用实例
3
数据类型与运算
字 符 型
(char)
整型(int)
基本类型
长 整 型
(long)
浮 点 型
(float)
数组(array)
数据类型
构造类型
指针类型
结 构 体
(struct)
共
用
体
(union)
枚举(enum)
空类型
4
专有数据类型
数据类型
Size(Byte)
数据取值范围
Signed char(short、int)
1
-128~+127
Unsigned char(short、int)
1
0~255
Signed long
2
-32768~+32767
Unsigned long
2
0~65535
float、double
4
Pointer
2
enum
1
5
常量的定义
先来看看汇编的常量定义:
door_service_c equ
t0int_c
equ
segment_c
equ
#80
#224
#3
;80ms去门抖动
;t0中断时间
;最多3段烹调
注:上面数值前的#号,是SN8ASM的符号,用于提示后
面的是立即数。
6
常量的定义
再来看看用SN8 C是如何定义相同的常量的:
#define
#define
#define
door_service_c
t0int_c
segment_c
80
224
3
//80ms去门抖动
//t0中断时间
//最多3段烹调
NOTE: 对习惯于写汇编的人来说,千万注意C对大小写
敏感!并从变量定义就要开始注意!
7
数值列表
汇编的表:
disp_automenu:
dw
0000h
dw
0ae1fh
dw
0ae2fh
dw
0ae3fh
dw
0ae4fh
dw
0ae5fh
dw
0ae6fh
dw
0ae7fh
;显示菜单用第二数字表格
;A-1
;
;
;
;
表的内容都是用DW
;
;关键字来定义
8
变量定义与限制
__RAM与__ROM关键字的使用:
Unsigned int __RAM ramVeriable;
将变量存放在
RAM中 [默认]
__RAM unsigned int ramVeriable2;
Unsigned int __ROM romVeriable;
将变量存放
在ROM中
__ROM unsigned int romVeriable2;
9
常量数值列表定义
C定义的数值列表:
unsigned long __ROM disp_automenu[]= {
0x0000,0x0ae1f,0x0ae2f,0x0ae3f,
0x0ae4f,0x0ae5f,0x0ae6f,0x0ae7f
定义一个数
};
组来存储这
些表的数值
10
变量的定义
汇编的定义变量的方法:
.DATA
org
temp1
temp2
led_dp
step
Job_mode
Pow_mode
0h
ds
ds
ds
ds
ds
ds
分别占用的Byte
单位的RAM空间
1
1
1
1
2
4
用DS关键字来
定义变量空间
11
变量的定义
用C定义变量:
C支持不同长度的变量类型,这
unsigned int temp1;
样就方便了程序员的使用
unsigned int temp2;
unsigned int led_dp;
unsigned int step;
unsigned long job_mode;
unsigned long power_mode1;
float powerValue;
int temp1_1;
long temp2_2;
12
变量定义的对比
ASM.的定义
C的定义
DS
1
(un)signed int/short/char
DS
2
(un)signed long
DS
4
Float/double
13
变量类型的选择
在选择数据类型的时候,在能够顺利完成功
能的情况下,请尽量选择占空间少的数据类
型,这样不管是在RAM空间使用上还是在产
生代码效率上都有很多的好处!
能使用无符号数的都使用无符号数,以免处
理出错,因为芯片内部是以无符号数处理的。
14
数据的存储
SN8芯片的数据存储区
Bank0
128Byte 用户存储区
0000H
通用存储区
007FH
0080H
系统寄存器区
80H~FFH为系统寄存
器区域
00FFH
Bank1
更多的用户存储区
00100
通用存储区
01FFh
15
系统寄存器
系统寄存器表
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
8
L
H
R
Z
Y
X
PFLAG
RBANK
-
-
-
-
-
-
-
-
9
AMPM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
A
-
-
-
-
-
-
-
-
-
-
-
-
-
-
P4CON
-
B
DAM
ADM
ADB
ADR
-
-
-
-
-
-
-
-
-
-
-
PEDGE
C
-
-
P2M
-
P4M
P5M
-
-
INTRQ
INTEN
OSCM
-
WDTR
TC0R
PCL
PCH
D
P0
-
P2
-
P4
P5
-
-
T0M
-
TC0M
TC0C
TC1M
TC1C
TC1R
STKP
E
P0UR
-
P2UR
-
P4UR
P5UR
-
@YZ
-
-
-
-
-
-
-
-
F
STK7L
STK7H
STK6L
STK6H
STK5L
STK5H
STK4L
STK4H
STK3L
STK3H
STK2L
STK2H
STK1L
STK1H
STK0L
STK0H
16
.H档中对系统寄存器定义
#include <sn8p2708a.h>
#define
#define
#define
#define
#define
#define
#define
每个程序都要包含相应芯片的.H档
L
(*((__RAM unsigned int*)0x80))
H
(*((__RAM unsigned int*)0x81))
R
(*((__RAM unsigned int*)0x82))
Z
(*((__RAM unsigned int*)0x83))
Y
(*((__RAM unsigned int*)0x84))
X
(*((__RAM unsigned int*)0x85))
PFLAG
(*((__RAM unsigned int*)0x86))
……
这些系统寄存器都是以大
写字母进行定义的,在程
序中使用时要注意这一点。
17
程序流程控制
顺序结构
判断分支结构
串行分支结构
并行分支结构
循环结构
18
顺序结构
顺序结构流程
A操作
框内A、B操
作按顺序执
行
B操作
19
顺序结构的实现对比
.ASM
b0bset
mov
mov
mov
mov
mov
mov
key_bibi_f
a,#0f0h
menu_disp_h,a
a,#00h
menu_disp_l,a
a,#11111111b
disp5,a
.C
key_bibi_f = 1;
menu_disp_h = 0xf0U;
menu_disp_l = 0;
disp5 = 0xffU;
20
判断分支结构
判断分支结构的流程
P为真?
A操作
B操作
通过判断条件P
是否成立来选择
A或B进行操作
21
判断分支结构的实现对比
.ASM
cmprs
a,#0ah
nop
b0bts0
fc
jmp $+3
b0mov
a,y
ret
b0mov
a,y
add
.C
if(result_buf > 0x0a)
{
result_buf = input + 6;
}
else
{
result_buf = input;
}
a,#6h
22
串行分支结构
串行分支结构流程
N
Y
P1 为 真 ?
Y
N
P2 为 真 ?
Y
N
C1
C2
Pn 为 真 ?
C3
这就是多级判
断分支组合,
想想它们又都
是怎么实现的
呢?
Cn
23
串行分支结构实现对比
.ASM
Buzzer00:
b0bts1
jmp
……
jmp
buzzer10:
b0bts1
jmp
……
jmp
buzzer20:
……
.C
if(P1){
P1
buzzer10
buzzer40
P2
buzzer20
……
}
else if(P2){
……
C用if,else if
嵌套来实现串
行分支结构
}
buzzer40
else if(Pn){
……
}
24
并行分支结构
并行分支结构流程
P=?
P=1
A1
P=2
A2
P=3
A3
P=n
并行分支结构其实
是一个条件判断有
多种可能,这又如
何实现呢?
An
25
并行分支结构实现对比
.ASM
mov
b0bts1
Jmp
cmprs
Jmp
cmprs
Jmp
cmprs
Jmp
cmprs
Jmp
.C
a,step
switch(step)
fz
{
ks82
case
0:
a,ONE_PRESS_C
ks81();break;
ks83
case ONE_PRESS_C:
a,TWO_PRESS_C
ks82();break;
ks84
case TWO_PRESS_C :
ks83();break;
a,BESPOKE_ING_C
case BESPOKE_ING_C :
ks85
ks84();break;
a,SELECT_TIME_C
……
ks86
}
C可以用
switch…Case
来实现
26
循环结构1——While
While循环流程
N
P为真?
Y
先判断,
后执行!
A
27
While循环实现对比
.ASM
Clr
y
Loop:
B0mov
Cmprs
Jmp
Jmp
decms
jmp
loop90:
a,y
a,#15
$+2
loop90
y
loop
.C
tempbuf = 0;
while(tempbuf==15)
{
++tempbuf;
}
编译器的转换
并非和我们的
比较代码一样
RET
28
循环结构2——do…while循环
Do…while循环流程
A
先执行,
后判断!
Y
P为真?
N
29
Do…while循环实现对比
.ASM
.C
ClrRAM:
clr
b0mov
ClrRAM10:
clr
Y
Z,#0x7f
@YZ
decms
Z
jmp
ClrRAM10
clr
@YZ
mov
a,#00H
unsigned int * pyz =
(unsigned int *)0x7f;
do{
*pyz = 0x00;
--i;
}while(i);
Do…while循
环在编译器的
转换中具有最
高的效率
30
函数
函数的定义
函数参数的传递
函数参数与全局变量
31
函数的定义
SN8 C 函数声明方式:
返回值类型 函数名(形参1数据类型,形参2数据类型,……);
如:
unsigned int bcd(unsigned int);
在C当中,函
数都应该先声
明,后调用 !
32
函数的定义方式
函数定义方式:
返回值类型 函数名(参数列表)
参数类型表;
Note:
仅在传统格
{
对于无返回值的函
式当中出现
函数体; 数,都要声明是
void,以免系统为
}
其预留空间!
33
函数参数传递与返回
clock_min = bcd(clock_min);
unsigned int bcd(unsigned int input)
{
………
return(result_buf);
}
函数调用是通过参
数传递和返回来传
递数值
34
函数传递的内部实现
假设于caller函数内调用callee函式. callee 的参数名
称为:
_callee_arg ?
; ? 为参数个数。
例如:
int foo(int a, int b, long c) 会产生
_foo_data SEGMENT DATA INBANK ;OVERLAYABLE
_foo_arg0 DS 1
; int a
_foo_arg1 DS 1
; int b
这些都由系
_foo_arg2 DS 2
; long c
统来完成!
35
函数参数传递实现对比
.ASM
.C
MOV
A, (_clock_min)
clock_min = bcd(clock_min);
__SelectBANK _bcd_arg0
MOV
_bcd_arg0, A
实际参数clock_min被
;End push arg....
直接赋值给函数的形参
call
_bcd
_bcd_arg0,然后在调用
__SelectBANK (_clock_min)
函数中参加运算。最后
MOV
_clock_min, A
返回值是在A中得到的 。
36
返回值的存放
返回值类型
寄存器
unsigned/signed char
A
unsigned/signed short
A
unsigned/signed int
A
unsigned/signed long
float
A,R
A,R,Y,Z
返回值是数据结构的,系统则会在调用函数的时候增
加一个隐含参数(地址)传递给被调函数,被调函数
在完成功能运算后将结果放到指定的位置上,程序从
函数里返回后就可以从该地址读取返回值。
37
尽管从原理上看起来,函数的参数
传递与返回值的C与汇编的转换的形
式是一样的,但是编译器产生代码
时依然可能产生一些冗余代码。所
以,在面对单片机这样的资源的硬
件编程,过多的参数传递及过于复
杂的返回值往往造成代码转换的低
效率,这是用户需要注意的。
38
全局变量与函数参数
全局变量
函数参数
贴近汇编的形式,产
生代码的效率高。
变量关联性高,模块
化,可维护性,封装
性差。
模块化,可维护性,封
装性好。
存在数据传递的环节 ,
更增加了转换的代码量。
对于面对硬件的单
片机编程,我们还
是建议多用全局变
量来传递数值,而
不是用参数 。
39
构造数据类型
结构体
联合体
40
结构体的定义
结构体定义的形式:
Struct 结构体类型名{
成员1数据类型
成员2数据类型
……
};
例如:
成员1名称;
成员2名称;
Struct structType{
Unsigned int memb1;
Unsigned long memb2;
Float memb3;
};
41
结构体存储形式
结构体存储
memb1
memb2
memb3
结构体在存储形
式上是按定义形
式的先后,将成
员逐个存储摆放
形成一个数据块。
42
用结构体定义位域
位域的定义形式:
Struct 结构体名称{
Unsigned bit0:1;
Unsigned bit1:2;
Unsigned bit2:1;
Unsigned bit3:1;
Unsigned num:4;
成员数据类型都是
Unsigned。
冒号后面的数据是
指占用位的数量,
如成员bit1会占用2
个bit 。
};
43
结构体定义的限制
对于struct,我们只能对其实例安排存放的空间,
加__RAM和__ROM限制字加于限制。而对于
struct的成员我们就无法对其进行限制,这个原
因是非常明显的,若是都允许对存放空间进行限
制的话,就会造成冲突。如下定义是系统不允许
的:
struct StuType {
int __ROM data;
// 错误
};
44
联合体的定义
定义一个联合类型的一般形式为:
union 联合名
{
成员表
};
例如:
union perdata
{
int memb1;
long memb2;
float memb3;
};
45
联合体的存储形式
联合体的存储
memb1
memb2
memb3
在“联合”中,各成员共享
一段内存空间, 一个联合
变量的长度等于各成员中最
长的长度。事实上是同一个
空间通过不同的名称调用 。
46
联合体在程序中的应用
将一个Long型数据与两个int型数据的
结构体组成Union,如:
union longtype
{
unsigned long longV;
struct inttype
{
unsigned int int_l;
unsigned int int_h;
}intV;
};
通过定义longType
的实例,我们就可以
既对一个long型数据
进行操作,又可以对
数据的高低字节进行
操作,既兼顾了C的
方便特性,又能有汇
编的灵活性。
47
与struct一样,我们只能对
UNION实例安排存放的空
间,加__RAM和__ROM限
制字加于限制。而对于
union的成员我们就无法对
其进行限制。
48
中断
中断函数的定义
中断过程的分析
中断函数的结构
49
中断函数的定义
中断函数声明方式如下:
__interrupt MyHandler () { .... }
这是一个SN8 C专有的函数,用关键字
__interrupt 来声明。__interrupt关键字(以两个
底线开头)指示函数是要作中断向量的处理函数。
中断向量函数是一个无参数, 也无返回值的特
殊函数。
中断向量进入点会备份所有寄存器,中断向量
结束前, 前述备份的项目均会被还原。这些都是
由编译器内部完成的。
50
中断过程分析
②
①
④
③
程序在主循环中运行到①的时候,
中断条件成立,系统产生中断,
此时,系统将会去执行中断程序。
而为了能从中断中正确返回,在
进入中断程序之前,系统会对当
前的状态(ACC,PC等等)进行
保存。然后,在②处程序运行进
入中断程序,③处中断程序结束,
系统又需要将原来的运行状态还
原,然后在④处继续运行主循环
程序。一次中断完成。
51
中断函数的结构
N
是中断1?
建议对中
断程序作
这样的安
排
N
Y
是中断2?
N
Y
是中断3?
Y
中断1处
理程序
中断2处
理程序
是中断n?
中断3处
理程序
中断n处
理程序
RETI
52
中断程序与主循环的关联
__interrupt intserv(void)
{
if(INTRQ&0x10)
{
_bCLR(&INTRQ,4);
T0INT();
}
else if(INTRQ&0x20)
{
_bCLR(&INTRQ,5);
TC0INT();
}
}
…
void TC0INT(void)
{
TC0C=0x64;
ftc0int = 1;
}
void INTround(void)
{
unsigned int bitValue = 0;
if(ftc0int)
{
ftc0int = 0;
fkeyTimer = 1;
fflashTimer = 1;
ThandDelay = 1;
}
…
}
...
Void main(void)
{
….
INTround();
}
为了使中断资源不
被长时间占用,我
们的中断程序内不
能运行任何长时间
占用系统时间的程
序!其实占用中断
资源的任务,我们
完全可以安排到中
断外去完成!我们只
需要告诉主控程序
发生了中断以及是
哪个中断就可以了!
53
位操作
位的定义
位的运算
位比较在程序流程控制中的应用
54
用户自定义位
Step1
Step2
位域的定义 :
定义一些具体的结构体实例:
Struct bitDefine{
Unsigned bit0:1;
Unsigned bit1:1;
Unsigned bit2:1;
Unsigned bit3:1;
Unsigned bit4:1;
Unsigned bit5:1;
Unsigned bit6:1;
Unsigned bit7:1;
Struct bitDefine flag1,flag2,flag3;
Step3
用宏定义的方法将我们需要的位
名称赋予相对应的位。如:
};
#define fkeypress (flag1.bit1)
#define fchatfinish (flag1.bit2)
#define fkeyProcessing (flag1.bit3)
#define FhandDelay (flag1.bit4)
55
位运算
对位的操作主要包含以下几种:置位,清除,
位与(&),位或(|),位非(~),位异或(^),左移
(<<),右移(>>)。
例如:
Fkeypress = 1;
Fkeypress = 0;
keyinbuf <<=2;
tempbuf = P0&0x03;
keyinbuf |= tempbuf;
keyinbuf = ~keyinbuf;
//置位
//清除
56
系统寄存器位操作函数
位操作函数原型 :
void _bSET(unsigned long address, unsigned int bitOffset);
void _bCLR(unsigned long address, unsigned int bitOffset);
int _bTest0(unsigned long address, unsigned int bitOffset);
int _bTest1(unsigned long address, unsigned int bitOffset);
传入参数 address 与 bitOffset
必须为常数不可为变量。
BitOffset 有效值为 0 ~7。
address 的高位为 bank
number。
57
系统寄存器位操作
对系统寄存器进行位的置位和清除:
_bSET(&TC0M,7);
_bCLR(&TC0M,7);
系统转换的对应代码:
PreB0SET 218 7 0
PreB0CLR 218 7 0
58
位比较在程序流程控制中的应用
1
系统寄存器的位判断可用以下3种方法:
用专用函数:
if(_bTest1(&INTRQ,4))
{
_bCLR(&INTRQ,4);
T0INT();
}
2
用系统定义的位名称:
if(FT0IRQ)
{
_bCLR(&INTRQ,4);
T0INT();
}
3
用位运算的方法:
if(INTRQ&0x10)
{
_bCLR(&INTRQ,4);
T0INT();
}
三种方式产生的代
码基本相同,第二
种使用更方便。
59
内嵌汇编
如何内嵌汇编
内嵌汇编时变量的传递
60
SN8 C 提供专门的关键字__asm(两个下
划线)用于在C的源代码内嵌入汇编。
__asm关键词有以下两种用法:
__asm(“code\n”)
__asm { asm_text }
内嵌的汇编会被原
样转换嵌入生成的
汇编码当中 !
61
内嵌汇编时变量的传递
“#pragma ref id”预处理指令
例如:
void func(void)
{
int x;
#pragma ref x
__asm {
程序用“#pragma
ref x”通知sn8cc,
变量x是有用到的!
…
;; 存取局部变量 x.
…
}
return;
}
62
全局变量与内嵌汇编的传递
.C
union flagWord2
{
unsigned int
flagByte;
struct bitdefine2
{
unsigned bit0:1;
unsigned bit1:1;
unsigned bit2:6;
}flagBit;
}led_dp;
unsigned int door_cnt;
unsigned int door_cnt1;
unsigned int door_cnt2;
生成代码档中的定义
.stabs "led_dp:G43",32,0,0,_led_dp
.stabs "door_cnt2:G14",32,0,0,_door_cnt2
.stabs "door_cnt1:G14",32,0,0,_door_cnt1
.stabs "door_cnt:G14",32,0,0,_door_cnt
原来它们将每一全局变量
的前面都增加了一个“_”
来标识它们!
在嵌入的汇编中,如果要
读写全局变量,就在它们
的前面加上一个“_”
63
SN8 C程序的结构组织
主函数与子函数
构建可复用文件
构建具有实用性的程序
64
任务的分时处理
对于整个程序而言,安排好main()函数对其
他子函数的调用起着至关重要的作用。我们知道,大
多任务我们没法在1毫秒内完成,有的甚至需要很长时
间的延时,而在很短的时间内环境有可能发生改变,
外部可能产生很多个请求。怎么样让程序既能完成任
务(响应)又能及时接受外部请求及时处理?
我们把所有的任务都分别用一个标志位来标示
他们是不是完成,对于完成的任务会产生什么样新的
需求也用一个标志来标识它。而对于正在进行的任务
我们每次只去完成应该完成的一部分,将这些正在完
成的任务轮换进行处理直到完成。改换这一思想是实
现我们的编程方案的关键。
65
传统按键扫描与分时按键扫描的对比
1
传统按键扫描
①
2
②
分时按键扫描
…
①
我们可以将一个任
务分为60或70次来
完成,实现了分时
实现。而很多占用
很长时间的任务也
都可以这么做。
…
②
66
程序层次结构图
任务队列
主控程序
功能1
功能2
……
…
功能3
功能n
功能实现层
请求
响应、使能
67
程序结构可以分为任务排列层、主控层和功能实现层。
主控层依据功能实现层的请求标志设置来形成队列形成任
务排列层,并对获取的请求安排响应和使能的优先顺序。然
后读取FIFO里面的任务并发出使能和响应标志,主控层为系
统的主循环,控制整个程序的运行,并且将通过设置响应和
使能标志来调用功能实现层的子程序来实现功能。
任务排列层建立一个FIFO,FIFO里面存储系统标志位,按
事件发生的顺序和主控程序的安排逐个地排列。
功能实现层为系统的功能程序(子程序),它由可复用的
各种功能模块组成。在每个模块的开头进行程序判断,看是
否需要执行程序或使能部分功能,并且在执行过程中,根据
程序执行的结果对标志位进行置位,并返回给主控层,由主
控程序安排响应!
68
实现层的子函数
No
执行程序的
条件是不是
成立?
Yes
程序主体
在程序开头进行判断是否需
要运行,若是条件没有满足
或者没有接收到主控程序的
使能信息,就直接跳转到程
序结束,返回主控程序。而
主控程序则每个循环(<1ms)
都调用一次子程序进行查询!
退出
69
构建可复用文件
#include <custom.h>
Extern keyScan();
Extern keyInbuf;
Extern keyChkbuf;
Extern keyCvtbuf;
Extern keyCode;
…
Void main(void)
{
…
keyScan();
…
}
…
Public keyScan();
Public unsigned int keyInbuf;
Public unsigned int keyChkbuf;
Public unsigned int keyCvtbuf;
Public unsigned int keyCode;
…
Void keyScan(void)
{
…
}
对于全局变量我
们可以通过在子
程序文件中通过
定义public型数
据来实现程序的
模块化。
70
Thanks!
联系我:
Phone:86-755-267196666-247
E-mail:[email protected]
71