怎么将结构体映射到单片机结构体调用的地址

查看: 1478|回复: 4
请教如何将定义好的IO地址映射到一个数组里
主题帖子精华
新手上路, 积分 28, 距离下一级还需 22 积分
在线时间0 小时
各位大侠,我用原子哥的位操作方法定义了如下一组IO,YO1~YO8,因为各个IO在不同的IO口里,有A有B也有C,有没有办法把我定义的这些IO操作的地址存到一个数据里,如YON[8],我操作YON[1]=1,就相当于直接对YO1赋值了,谢谢啊!
[C] 纯文本查看 复制代码//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考&&CM3权威指南&&第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xFx2000000+((addr &0xFFFFF)&&5)+(bitnum&&2))&
#define MEM_ADDR(addr) &*((volatile unsigned long &*)(addr))&
#define BIT_ADDR(addr, bitnum) & MEM_ADDR(BITBAND(addr, bitnum))&
//IO口地址映射
#define GPIOA_ODR_Addr & &(GPIOA_BASE+12) //0x4001080C&
#define GPIOB_ODR_Addr & &(GPIOB_BASE+12) //0x40010C0C&
#define GPIOC_ODR_Addr & &(GPIOC_BASE+12) //0x4001100C&
#define GPIOD_ODR_Addr & &(GPIOD_BASE+12) //0x4001140C&
#define GPIOE_ODR_Addr & &(GPIOE_BASE+12) //0x4001180C&
#define GPIOF_ODR_Addr & &(GPIOF_BASE+12) //0x40011A0C & &
#define GPIOG_ODR_Addr & &(GPIOG_BASE+12) //0x40011E0C & &
#define GPIOA_IDR_Addr & &(GPIOA_BASE+8) //0x&
#define GPIOB_IDR_Addr & &(GPIOB_BASE+8) //0x40010C08&
#define GPIOC_IDR_Addr & &(GPIOC_BASE+8) //0x&
#define GPIOD_IDR_Addr & &(GPIOD_BASE+8) //0x&
#define GPIOE_IDR_Addr & &(GPIOE_BASE+8) //0x&
#define GPIOF_IDR_Addr & &(GPIOF_BASE+8) //0x40011A08&
#define GPIOG_IDR_Addr & &(GPIOG_BASE+8) //0x40011E08&
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) & BIT_ADDR(GPIOA_ODR_Addr,n) &//输出&
#define PAin(n) & &BIT_ADDR(GPIOA_IDR_Addr,n) &//输入&
#define PBout(n) & BIT_ADDR(GPIOB_ODR_Addr,n) &//输出&
#define PBin(n) & &BIT_ADDR(GPIOB_IDR_Addr,n) &//输入&
#define PCout(n) & BIT_ADDR(GPIOC_ODR_Addr,n) &//输出&
#define PCin(n) & &BIT_ADDR(GPIOC_IDR_Addr,n) &//输入&
#define PDout(n) & BIT_ADDR(GPIOD_ODR_Addr,n) &//输出&
#define PDin(n) & &BIT_ADDR(GPIOD_IDR_Addr,n) &//输入&
#define PEout(n) & BIT_ADDR(GPIOE_ODR_Addr,n) &//输出&
#define PEin(n) & &BIT_ADDR(GPIOE_IDR_Addr,n) &//输入
#define PFout(n) & BIT_ADDR(GPIOF_ODR_Addr,n) &//输出&
#define PFin(n) & &BIT_ADDR(GPIOF_IDR_Addr,n) &//输入
#define PGout(n) & BIT_ADDR(GPIOG_ODR_Addr,n) &//输出&
#define PGin(n) & &BIT_ADDR(GPIOG_IDR_Addr,n) &//输入
#define YO1 PCout(6)// PC6
#define YO2 PAout(7)// PA7
#define YO3 PCout(7)// PC7
#define YO4 PBout(0)// PB0
#define YO5 PCout(8)// PC8
#define YO6 PBout(1)// PB1
#define YO7 PBout(10)// PB10
#define YO8 PBout(11)// PB11
组合。
没有捷径。
主题帖子精华
在线时间788 小时
组合。
没有捷径。
主题帖子精华
新手上路, 积分 28, 距离下一级还需 22 积分
在线时间0 小时
回复【2楼】xuande:
---------------------------------
没有办法把通过函数指针数组之类的操作进行地址映射吗?我试了一下,怎么弄都编译不过去
主题帖子精华
高级会员, 积分 643, 距离下一级还需 357 积分
在线时间27 小时
好像没有办法哦
主题帖子精华
在线时间788 小时
CPU没有提供这个办法,——&即使编译器有,那也是组合。
CPLD就行。
Powered by扫二维码下载作业帮
2亿+学生的选择
下载作业帮安装包
扫二维码下载作业帮
2亿+学生的选择
SFR映射在片内ram是什么意思,51单片机片内ram不是只有128B吗,怎么sfr的地址是从80H到FFH呢
扫二维码下载作业帮
2亿+学生的选择
51内部RAM,分为直接寻址空间和间接寻址空间两个部分;MOV 10H,A ;直接寻址访问MOV R0,#10H ;间接寻址MOV @R0,A其中,两者寻址范围都有0~255,只不过前面的128字节(0~127)地址是重叠的,128~255这个区域,就独立开来了,直...
为您推荐:
其他类似问题
扫描下载二维码下载费用:2 元 &
当前资源信息
编号: 44676
格式: PDF
大小: 66.10KB
上传时间:
C51的结构体数据在单片机编程中的应用
下载文档到电脑,查找使用更方便
2 元&&0人已收藏
还剩页未读,继续阅读
<a href="UserManage/CopyrightAppeal.aspx?bid=81815" title="版权申诉" class="fLeft works-manage-item works-manage-report" target="_blank"
关&键&词: 单片机 构中 51单片机结构 片机 51单片机 结构体在单片机 结构体的应用 结构体 单片机应用 单片机应用开发 单片机的
资源描述:
数组变量已不能完全有效地表达参数的特性。 使用译支持的一种结构体 ( 数据类型, 可有效地解决许多编程中所面临的种种数据结构管理问题。1 结构体数据除了基本类型数据, 数组是 程中一种常用的构造类型数据。 它由上述基本类型数据 (位、 指针除外)组成, 并且一个数组的各元素应属于同一个类型。但是, 只有这些数据类型并不能很好地表达我们所要引用的数据, 有时需要将不同类型的数据组合成一个有机的整体, 以便于引用。 例如, 要保存一组采样值 :时间 (月、 日、 时、 分)、 温度、 流量等, 如果分别将它们定义为互相独立的简单变量, 难以反映它们的内在联系。 应当把它们组织成一个组合项, 在一个组合项中包含若干个类型不同 (也可以相同) 的数据项。 这样的数据结构称为结构体 ( 。定义一个结构体类型的一般形式为 :结构体名{ 成员表列 };例如上面提到的一组数据, 可以表示为 :* 结构体名 */{ /* 月 */ /* 日 */ /* 时 */ /* 分 */ /* 温度 */ …… /* 流量等 */} 它定义了一个反映某一时刻采得的温度值、 流量值等的结构体 同时定义了两个具有 或参数)。2 结构体数据的优点(1) 有效利用内存空间在 译时, 结构成员在内存中是顺序存放的,因而不同类型的数据被有机地结合成了一个数据块, 使单片机有限的内存资源空间得以充分利用。(2) 参数组织结构紧密、 清晰结构体数据在多参数的智能化仪表设计中尤为适用, 它可将同一属性、 不同类型的数据组织在一起, 参数便于识别、 调用。 如编制某些通用型仪表的程序, 可定义下列结构体数据。例 1 : 放大环节描述} 上定义了一个表示放大环节 ( 的结构体类型。 它由放大环节增益 ( 和放大环节零点( 组成, 并且定义了输入环节 ( 和输出环节 ( 两个结构体数据。例 2 : 仪表特征描述描述仪表特征的数据可以组合成一个有机的结构体, 以便于识别与调用。ct * 仪表编号 */0]; /* 仪表编码 */* 仪表口径 */* 仪表系数 */* 仪表零点 */} 79 & ) 参数调用方便结构体数据的调用方式有多种, 最直接的方式为 :结构体变量名 . 成员名如例 2 中, 可以对变量赋值 : . 0;这里着重介绍一种通过指针调用的方式。 该方式在对结构体数据作顺序存取时尤为方便。在数据存储区有以下结构 :……} 在程序存储区与之相对应存放一组成员名数据表m][n] 。 该表包含表 1 所列内容。在引用时, 先设计两个指针变量, 分别指向数据存表 1 参数类型 地址偏移量 参数名 备注 i 0 “ 成员 0 c 2 “ 成员 1 f 3 “ 成员 2 …… …… …… 成员 n 储区和程序存储区 :p — —指向数据存储区 ; —指向程序存储区 ;p=&定位指针指向参数值首地址 ;定位指针指向参数名首地址。需要调用第 i 个参数时 :p+*(n* i +1) 指向第 m 个参数 ;(n* i ) 中存放的是该参数的数据类型。这样, 就可以根据数据类型的不同分支, 对不同类型的数据进行相应的处理。(4) 提高编程效率数据变量的有效组织使得程序的编制更便于结构化、 模块化, 从而方便了程序后续的扩展, 可以极大地加快单片机开发者的开发进度。3 总 结由于单片机内存资源的限制, 如何合理、 有效地组织利用数据内存空间, 对于编制大型的单片机程序尤为重要。 尽可能利用编译器提供的便利, 优化程序, 使程序做到更简化、 更易于扩展, 是每个编程人员追求的目标。 结构体数据类型即是 单片机开发人员提供的一种有效的数据组织方式。(收稿日期 : 2003i] & 0=0;/* 单片机 2 把数据位移至数据线上 */0_6=0;j=2;j<=8;j++) {} 1_4==* 测试数据接收完否?否, 则等待 */1_4;i]=i]+i];/* 单片机 2 的数据右移 */i] & 0=0;/* 单片机 2 把数据最高位置于数据线上 */0_6=0;0_7^1; /* “数据准备好” 信号翻转 */}} 1_4==* 等待 “数据接收完” 信号电平翻转 */1_4;i+1] & 0=0;/* 下一字节的数据准备发送 */0_6=0;0_7^1; / * “数据准备好” 信号翻转 */}}结 语上述通信方法在笔者开发的一复杂系统中得到了良好的应用。 在 片机的频率为 12每秒内传送数据可达 2800 字节 (22 400b/s) , 而且十分可靠。 比单片机串口常用的 9600b/s 的波特率设计, 优点显而易见。 究其原因主要是, 用电平查询代替时钟触发移位, 有效克服了丢脉冲现象 ; 两机之间的 “握手”是基于最短时间的, 中间没有不必要的时间浪费, 因此效率极高。(收稿日期 :
& 温馨提示:
1. 本站作品为确保原创性及保密性仅显示部分内容,需要完整作品请下载。 2. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。文件的所有权益归上传用户所有。 3. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。 4.【温馨提示】不支持迅雷下载,如下载错误或无法下载,请使用IE浏览器,或者360浏览器下载。 5. 本站仅提供交流平台,不保证下载资源的准确性、安全性和完整性。并不能对任何下载内容负责。
& 大分享文库|设计图纸下载|设计程序下载分享平台 所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。
《我与地坛》——由挫折走向人生真谛心路之旅[读后感-读书笔记]
《围城——剖析文中个别女主人公性格特征》[读后感-读书笔记]
《小窗幽记》读书笔记[读后感-读书笔记]
《周易》中的建筑之道[读后感-读书笔记]
道家生死观[读后感-读书笔记]
等待中的改变——读《等待》有感[读后感-读书笔记]
《庄子》之道[读后感-读书笔记]
读《活着》有感[读后感-读书笔记]
读《俄狄浦斯王》浅谈人与命运抗争[读后感-读书笔记]
读《倾城之恋》有感[读后感-读书笔记]
读《三国志》有感[读后感-读书笔记]
读《少年维特的烦恼》有感[读后感-读书笔记]
读《围城》有感[读后感-读书笔记]
读《围城》有感1[读后感-读书笔记]
读《围城》有感[读后感-读书笔记]
读《蝇王》有感[读后感-读书笔记]
读红楼梦有感[读后感-读书笔记]
进攻与防守的智慧——读《资治通鉴》有感[读后感-读书笔记]
论《安娜o卡列尼娜》的人生悲剧的原因
论《围城》讽刺艺术[读后感-读书笔记]
棋之道——读《棋王》有感[读后感-读书笔记]
浅谈《本杰明o富兰克林自传》主人公成功启示
论《韩非子》中的“法”、“术”、“势”[读后感-读书笔记]
浅谈《围城》[读后感-读书笔记]
浅谈《诗经》爱情诗[读后感-读书笔记]
浅谈《我与地坛》中爱与生命的力量[读后感-读书笔记]
浅谈艾略特《荒原》的神话引用[读后感-读书笔记]
浅谈曹操诗歌中的生命意识[读后感-读书笔记]
浅析《半生缘》中三段擦肩而过的爱情[读后感-读书笔记]
浅析《老子》中的无为思想[读后感-读书笔记]
浅析《围城》中女性的爱情与婚姻[读后感-读书笔记]
人文经典 《三国志》有感[读后感-读书笔记]
本文标题:C51的结构体数据在单片机编程中的应用 链接地址:
&& 举报该文档为侵权文档。
&& 举报该文档为重复文档。
&& 反馈该文档无法正常浏览。
&& 举报该文档含有违规或不良信息。
网站地图: copyright@
大分享文库|设计图纸下载|设计程序下载分享平台 网站版权所有 经营许可证编号:豫ICP备号-2查看: 7241|回复: 10
结构体成员地址是否连续?
主题帖子精华
中级会员, 积分 376, 距离下一级还需 124 积分
在线时间5 小时
最近看了一下“ALIENTEK 战舰STM32开发板资料”-基于库开发手册,属于我看到的最好的文档资料,内容详实,而且是原始著作,真是稀物呀!肯定花了不少时间去创造的,值得我们这些学习者的肯定。
&&&&以上是我对正点原子创造的感谢。
&&& 我看到"STM32开发指南V1.0+库函数版本.pdf“第109页中写道:这里就设计到结构体的一个特征了,那就是结构体存储的成员他们的地址是连续的。
这句话我想到了#pragma pack(n)对齐方式,好像是如果结构体成员是同类型的就是连续的,不同类型可能就不会连续。在开发文档中结构体成员地址肯定是连续的,因为他们的类型是一致的,但是那句话好像没有加到前提。
&&&&不知道我这样理解是否正确,如果错误,就当帮我改正一个错误的知识点,请大家见谅。
主题帖子精华
金钱112123
在线时间801 小时
百度过来的:
在Project-&&Setting-&&C/C++-&&Code&&&Generation-&&Struct&&&member&&&alignment中可以设置结构的对齐方式。&
进入VS2005的菜单Tools-&Options,选择Debugging-&General&勾上&Enable&address-level&debugging&选项&和&Require&source&files&to&exactly&match&the&original&version。查看窗口&memory(Debug&-&&windows&-&&memory)
在调试时下断点~然后在监视窗口键入&&变量名&来查看变量地址~也可以直接将&&变量名&的值写入代码并显示
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
&
1)&结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
&
2)&结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal&adding);&
3)&结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing&padding)。&
对于上面的准则,有几点需要说明:&
1)&前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。&
结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:&
#define&offsetof(s,m)&(size_t)&(((s&*)0)-&m)&
例如,想要获得S2中c的偏移量,方法为&
size_t&pos&=&offsetof(S2,&c);//&pos等于4&
2)&基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。&
这里叙述起来有点拗口,思考起来也有点挠头,还是让我们看看例子吧(具体数值仍以VC6为例,以后不再说明):&
struct&S3&
{&
char&c1;&
S1&s;&
char&c2;&
};&
S1的最宽简单成员的类型为int,S3在考虑最宽简单类型成员时是将S1“打散”看的,所以S3的最宽简单类型为int,这样,通过S3定义的变量,其存储空间首地址需要被4整除,整个sizeof(S3)的值也应该被4整除。&
c1的偏移量为0,s的偏移量呢这时s是一个整体,它作为结构体变量也满足前面三个准则,所以其大小为8,偏移量为4,c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13是不能被4整除的,这样末尾还得补上3个填充字节。最后得到sizeof(S3)的值为16。&
通过上面的叙述,我们可以得到一个公式:&
结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:&
sizeof(&struct&)&=&offsetof(&last&item&)&+&sizeof(&last&item&)&+&sizeof(&trailing&padding&)&
到这里,朋友们应该对结构体的sizeof有了一个全新的认识,但不要高兴得太早,有一个影响sizeof的重要参量还未被提及,那便是编译器的pack指令。它是用来调整结构体对齐方式的,不同编译器名称和用法略有不同,VC6中通过#pragma&pack实现,也可以直接修改/Zp编译开关。#pragma&pack的基本用法为:#pragma&pack(&n&),n为字节对齐数,其取值为1、2、4、8、16,默认是8,如果这个值比结构体成员的sizeof值小,那么&
该成员的偏移量应该以此值为准,即是说,结构体成员的偏移量应该取二者的最小值,&
公式如下:&
offsetof(&item&)&=&min(&n,&sizeof(&item&)&)&
再看示例:&
#pragma&pack(push)&//&将当前pack设置压栈保存&
#pragma&pack(2)&//&必须在结构体定义之前使用&
struct&S1&
{&
char&c;&
int&i;&
};&
struct&S3&
{&
char&c1;&
S1&s;&
char&c2;&
};&
#pragma&pack(pop)&//&恢复先前的pack设置&
计算sizeof(S1)时,min(2,&sizeof(i))的值为2,所以i的偏移量为2,加上sizeof(i)等于6,能够被2整除,所以整个S1的大小为6。&
同样,对于sizeof(S3),s的偏移量为2,c2的偏移量为8,加上sizeof(c2)等于9,不能被2整除,添加一个填充字节,所以sizeof(S3)等于10。&
现在,朋友们可以轻松的出一口气了,&
还有一点要注意,“空结构体”(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。如下:&
struct&S5&{&};&
sizeof(&S5&);&//&结果为1
主要用来设置结构定义的字节对齐方式,比如是单字节对齐,双字节对齐等,比如如果是双字节对齐,那么结构的成员变量的地址必须是2的整数倍,这就造成了字节补齐,但是提高了访问速度。单字节呢,就是没有补齐,成员变量的地址是连续的,其他依次类推,通常是4,8等。通常用于网络传输数据,特别是传输整个结构时,必须采取单字节对齐,这样才可以直接把结构地址,以及结构长度,作为Send的参数发送整个结构,否则只能依次发送结构的成员,要不然会出现结构解释的差异。
传输结构时和pack无关,只要Recv端定义的结构和Send方一样就没问题了。&
pack多用于Hook程序,比如Hook&&&Api技术,因为需要硬编码,所以必须将结构&
压缩,将内容补齐!&
比如:&
ASM_STRUCT{&
&&&&BYTE&&&bJ&
&&&&DWORD&&&dwD&
}a;&
如果不用Pack时,编译为:&
&&a.bJmp&&&=&&&0xEB;&&&//&&&jmp的编码&
&&a.dwDes&&&=&&&0x;&&&//&&&jmp&&&0x&
不用pack的话,内存内容为&&&&&0xEB&&&XX&&&XX&&&XX&&&23&&&01&&&41&&&00&&&//&&&共8BYTE&
其中XX为不定值,用pack后&&&&&0xEB&&&23&&&01&&&41&&&00&&&//&&&共5BYTE&
这样,在Hook时运行这些指令,就必须用#parama&&&pack(1)&&&//&&&1&&&BYTE方式对齐。
如果直接把结构地址,以及结构长度,作为Send的参数发送整个结构,难道不需要pack吗?
不需要。&
send(struct,&&&sizeof(struct));就可以了,如果两个程序都没有pack的话,&
相同的结构体在接收数据时就没有问题。必须保证两边的pack都是一样才行。
附加:
_T或者TEXT是一个宏,&
他将窄字符(1个byte)和宽字符(2个byte)统一起来&
如果程序定义了UNICODE(宽字符)&
则所有形如_T("")或者TEXT("")的字符串将被存储当作是宽字符来存储&
如果没有定义,那就用作一般的窄字符。&
其实质目的其实是为了程序的国际通用性,因为很多国家的语言中字符是要占&
两个字节的。&
另外,c里面凡是涉及字符串的函数都有两个版本,比如DrawText函数,&
如果程序定义了UNICODE,则DrawText在编译时被DrawTextA代替(此函数处理窄字符)&
如果没有定义,他就被DrawTextW代替(处理宽字符)。
我是开源电子网站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺:
微信公众平台:正点原子& &
主题帖子精华
初级会员, 积分 58, 距离下一级还需 142 积分
在线时间2 小时
这个回答应该算是结构体的精华了,原子哥对结构体了解的真透呀,我网上找了半天没找在,在这里学习了。
做好的软件为人类服务
主题帖子精华
初级会员, 积分 58, 距离下一级还需 142 积分
在线时间2 小时
我一直有一个问题解决不了,希望网友帮忙解决一下。&我想定义几十个变量,都是u8,现要求他们的地址连续,而且定义时就赋值。如,u8&a=8;u8&b=9&;……
用结构体当然是可以的,按原子哥上面说的。但是结构体定义完还要一个一个赋值,一不小心就行对错号了。不知还有没有其他方法?
请高手帮忙呀
做好的软件为人类服务
主题帖子精华
中级会员, 积分 312, 距离下一级还需 188 积分
在线时间0 小时
帮顶&结构体
主题帖子精华
新手上路, 积分 24, 距离下一级还需 26 积分
在线时间0 小时
回复【2楼】正点原子:
---------------------------------
请问原子哥,这个例子中S1&s;的大小为什么是8呢?没有理解。。望解释,谢谢
主题帖子精华
新手上路, 积分 24, 距离下一级还需 26 积分
在线时间0 小时
结构体S1的大小不是应该取INT的大小4么,怎么会是8呢?
主题帖子精华
金钱112123
在线时间801 小时
回复【7楼】z5071507:
---------------------------------
自己测试下.
我是开源电子网站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺:
微信公众平台:正点原子& &
主题帖子精华
初级会员, 积分 88, 距离下一级还需 112 积分
在线时间7 小时
学习了&&原子大哥解释的相当清晰
主题帖子精华
新手上路, 积分 32, 距离下一级还需 18 积分
在线时间7 小时
在把连续内存转换成结构体指针时,成员有不同类型,就会出现移位,查半天烦死你。。
主题帖子精华
金牌会员, 积分 1127, 距离下一级还需 1873 积分
在线时间141 小时
__packed typedef struct
加上__packed,keil会帮你自动对齐~
Powered byarm驱动linux设备地址映射到用户空间 - ARM单片机 - 电子工程世界网
arm驱动linux设备地址映射到用户空间
09:48:20来源: eefocus
[《[]到》涉及内核驱动函数二个,内核结构体二个,分析了内核驱动函数二个;可参考的相关应用程序模板或内核驱动模板二个,可参考的相关应用程序模板或内核驱动四个
一、问题描述:一般情况下,用户空间是不可能也不应该直接访问设备的,但是,设备驱动程序中可实现mmap()函数,这个函数可使用户空间直接访问设备的物理地址。
1、mmap()函数工作原理:mmap()实现了这样的一个映射过程,它将用户的内存空间的一般内存(准确来说是执行mmap进程的映射区域内存)与设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问(linux上一切皆文件)。
文件内存映射原理图示 a
2、mmap优点:1、对于设备文件,最大的有点就是用户空间可以直接访问设备内存;2、普通文件被映射到进程地址空间后,进程进程访问文件的速度也变块,不必再调read(),write(),可以用memcpy,strcpy等操作写文件,写完后用msync()同步一下。(感觉还是很抽象,看了后面的实例一就明白了)
3、应用场景:mmap()的这种能力用于显示适配器一类的设备,屏幕帧的像素不再需要从一个用户空间到内核空间的复制过程。
二、应用程序相关函数
1、建立映射:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
a) 参数含义:
addr:&指定映射的起始地址, 通常设为NULL, 由系统指定。
length: 映射到内存的文件长度。
prot: 映射区的保护方式, 可以是:
PROT_EXEC: 映射区可被执行
PROT_READ: 映射区可被读取
PROT_WRITE: 映射区可被写入
PROT_NONE 映射区不可访问.
flags: 映射区的特性, 可以是:
MAP_SHARED:对此区域所做的修改内容奖写入文件内;允许其他映射该文件的进程共享,意思是:n个mmap.out程序在运行,这n个进程的&虚拟内存区域&的物理空间空间都相同。详看《虚拟内存共享原理图b》
虚拟内存共享原理图b
MAP_PRIVATE:对此区域所做的修改不会更改原来的文件内容,对映射区的写入操作会产生一个映射区的复制(copy-on-write);意思是:n个mmap.out程序在运行,但是虚拟内存区域的物理地址会被内核另外分配。详看《虚拟内存共享原理图c》
虚拟内存共享原理图c
fd: 由open返回的文件描述符, 代表要映射的文件。
offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射。
b)返回值:返回成功----函数的返回值为最后文件映射到进程空间的地址(参照文件内存映射原理图示 a),进程可直接操作起始地址为该值的有效地址。返回失败返回MAP_FAI(-1),错误原因存于errno 中。
2、解除映射:
int munmap(void *addr, size_t length);
3、 同步回写函数:
int msync(const void *start, size_t length, int flags);
如果您希望立即将数据写入文件中,可使用msync。
start为记忆体开始位置(mmap函数返回的值---地址),length为长度。
flags则有三个:
MS_ASYNC : 请Kernel快将资料写入,发出回写请求后立即返回
MS_SYNC : 在msync结束返回前,将资料写入。
MS_INVALIDATE使用回写的内容更新该文件的其它映射
实例一)mmap普通文件被映射到进程地址空间实例
mmapfile.c
void printfMapChar(char *nameChar, char *mapChar){//打印mapChar的内容
printf("%s = %s\n\n", nameChar,mapChar);
void printfMmapAddr(char *nameChar, char *mmapChar){//打印mmapChar的地址
printf("%s&#39;address = %p\n",nameChar, mmapChar);
void printfDivLine(char *desc){
printf("********%s*******\n", desc);
int main(){
char *mapC//mapchar存放虚拟内存地址
char *checkC//验证是否mapChar是映射地址
printf("mypid is %d\n",getpid());//输出本pid
/*获得映射区域地址,赋值mapChar*/
fd = open("/tmp/test.txt",O_RDWR);
mapChar = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//获得映射区域地址MAP_SHARED更改mapchar后改变fd文件内容
/*****************/
Tip:此时mapchar就是虚拟内存区域的物理地址部分的首地址;也就是《文件内存映射原理图示 a》中的fd文件存储映射部分对应的的首地址,当进车访问mapchar这段地址范围时,实际上会转化为对文件fd的访问
/********打印映射区域内容;和mapChar*********/
printfDivLine("打印映射区域内容;和mapChar");
printfMapChar("mapChar", mapChar);
/**************/
/*******通过mapChar将数据写入映射区域*******/
strcpy(mapChar, "writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,");//写入映射区域
printfDivLine("通过mapChar将数据写入映射区域");
printfMapChar("mapChar", mapChar);
/**********checkChar验证*********/
checkChar = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//获得映射区域地址
close(fd);//不使用fd时就可以close
printfDivLine("checkChar验证");
printfMmapAddr("mapChar", mapChar);
printfMmapAddr("checkChar", checkChar);
printfMapChar("checkChar", checkChar);
munmap(mapChar, 100);//释放mapchar的映射,此时文件的映射在内存内然存在
munmap(checkChar, 100);
运行结果:
mypid is 28529
********打印映射区域内容;和mapChar*******
mapChar = this is a just test temp file
********通过mapChar将数据写入映射区域*******
mapChar = writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,
********checkChar验证*******
mapChar&#39;address = 0x7f356eaaa000
checkChar&#39;address = 0x7f356eaa9000
checkChar = writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,
Tip:一个进程的内存映象由下面几部分组成:程序代码、数据、BSS和栈区域,以及内存映射的区域。一个进程的内存区域可以通过查看/proc/pid/maps
三、给驱动设备添加mmap虚拟内存映射
内核函数一)1、 驱动中的mmap(内核空间):
int(*mmap)(struct file *,struct vm_area_struct *);
2、在struct file_operations中与mmap接口函数关联
static struct file_operations module_drv_fops = {
//............
.mmap = memdev_mmap,
//...............
结构体一)3、struct vm_area_struct(VMA)结构体如下
struct vm_area_struct {
struct mm_struct * vm_ /* The address space we belong to. */
unsigned long vm_ /* Our start address within vm_mm. */
unsigned long vm_ /* The first byte after our enddress within vm_mm. */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_
pgprot_t vm_page_ /* Access permons of this VMA. */
unsigned long vm_ /* Flags, listed below. */
struct rb_node vm_
struct vm_operations_struct * vm_
/* Information about our backing store: */
unsigned long vm_ /* Offset (within vm_file) in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
struct file * vm_ /* File we map to (can be NULL). */
void * vm_private_ /* was vm_pte (shared mem) */
unsigned long vm_truncate_/* truncate_count or restart_addr */
//..................
4、struct vm_area_struct(VMA)结构体flag参数
VM_IO将该VMA标记为内存映射的IO区域,VM_IO会阻止系统将该区域包含在进程的存放转存(core dump )中
VM_RESERVED标志内存区域不能被换出
内核函数二)&5、内核mmap中创建页表函数:remap_pfn_range();
作用用&addr ~ addr + size之间的虚拟地址&构造页表,参考《虚拟内存共享原理图b》和《虚拟内存共享原理图c》
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
1) vma: 虚拟内存区域指针(默认使用vma)
2)addr: 虚拟地址的起始值(默认使用vma->vm_start)
3)pfn:总的来说(pfn = virt_to_phys(void *mem))>>PAGE_SHIFT(常用);或使用默认方式vma->vm_pgoff)
推理:pfn是虚拟地址应该映射到的物理地址所在的物理页帧号就是物理地址右移PAGE_SHIFT位,若PAGE_SIZE为4k则PAGE_SHIFT为12(2的12次方为4k),因此PAGE_SIZE为1<>PAGE_SHIFT"得到(虚拟地址 = 物理地址>>PAGE_SHIFT)。如何得到物理地址:将驱动设备中某个内存变量用函数virt_to_phys(void *mem)转换成物理地址;(物理地址 = virt_to_phys(void *mem));
4)size: 要映射的区域的大小。(默认使用vma->vm_end - vma->vm_start)
5)prot: VMA的保护属性。(默认使用vma->vm_page_prot)
模板一)6、mmap驱动模板
static int memdev_mmap(struct file*file, struct vm_area_struct *vma){
struct VialDisk *devp = file->private_ /*鑾峰緱璁惧缁撴瀯浣撴寚閽?/
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(devp->mem)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
file_oprations中添加或修改.mmap
static struct file_operations module_drv_fops = {
//............
.mmap = memdev_mmap,
//...............
实例二)7、完整实例
a)驱动部分
//&module_drv&,"module_","module"
#include //模块所需的大量符号和函数定义
#include //文件系统相关的函数和头文件
//指定初始化和清除函数
//cdev结构的头文件包含
//#include //包含驱动程序使用的大部分内核API的定义,包括睡眠函数以及各种变量声明
#include //在内核和用户空间中移动数据的函数
#define VIRTUALDISK_SIZE 0x1000//4k
#define MEM_CLEAR 0x1
#define VIRTUALDISK_MAJOR 250
int VirtualDisk_major = VIRTUALDISK_MAJOR;
struct VirtualDisk{
//详细看cdev机制
unsigned char mem[VIRTUALDISK_SIZE ];
/*记录设备目前被多少设备打开*/
static struct class *module_
static struct class_device *module_class_
struct VirtualDisk *VirtualD
static int module_drv_open(struct inode *inode, struct file *file)
printk("module_dev read\n");
file->private_data = VirtualD
VirtualDiskp->count++; /*增加设备打开次数*/
static int module_drv_release(struct inode *inode, struct file *file)
printk("module_dev release\n");
VirtualDiskp->count--; /*减少设备打开次数*/
/*seek文件定位函数:seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)、文件尾(SEEK_END,2)*/
static loff_t module_drv_llseek(struct file *file, loff_t offset, int origin){
loff_t ret = 0;/*返回的位置偏移*/
switch (origin)
case SEEK_SET: /*相对文件开始位置偏移*/
if (offset < 0)/*offset不合法*/
ret = - EINVAL; /*无效的指针*/
if ((unsigned int)offset > VIRTUALDISK_SIZE)/*偏移大于设备内存*/
ret = - EINVAL; /*无效的指针*/
file->f_pos = (unsigned int) /*更新文件指针位置*/
ret = file->f_/*返回的位置偏移*/
case SEEK_CUR: /*相对文件当前位置偏移*/
if ((file->f_pos + offset) > VIRTUALDISK_SIZE)/*偏移大于设备内存*/
ret = - EINVAL;/*无效的指针*/
if ((file->f_pos + offset) < 0)/*指针不合法*/
ret = - EINVAL;/*无效的指针*/
file->f_pos +=/*更新文件指针位置*/
ret = file->f_/*返回的位置偏移*/
ret = - EINVAL;/*无效的指针*/
/*设备控制函数:ioctl()函数接受的MEM_CLEAR命令,这个命令将全局内存的有效数据长度清零,对于设备不支持的命令,ioctl()函数应该返回-EINVAL*/
static int module_drv_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){
struct VirtualDisk *devp = file->private_/*获得设备结构体指针*/
switch (cmd)
case MEM_CLEAR:/*设备内存清零*/
memset(devp->mem, 0, VIRTUALDISK_SIZE);
printk(KERN_INFO "VirtualDisk is set to zero\n");
return - EINVAL;
/*读函数:读写函数主要是让设备结构体的mem[]数组与用户空间交互数据,并随着访问字节数变更返回用户的文件读写偏移位置*/
static ssize_t module_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
unsigned long p = * /*记录文件指针偏移位置*/
unsigned int countt =/*记录需要读取的字节数*/
int ret = 0; /*返回值*/
struct VirtualDisk *devp = file->private_ /*获得设备结构体指针*/
printk("module_dev read\n");
/*分析和获取有效的读长度*/
if (p >= VIRTUALDISK_SIZE ) /*要读取的偏移大于设备的内存空间*/
return countt ? - ENXIO: 0;/*读取地址错误*/
if (countt > VIRTUALDISK_SIZE - p)/*要读取的字节大于设备的内存空间*/
countt = VIRTUALDISK_SIZE -/*将要读取的字节数设为剩余的字节数*/
/*内核空间->用户空间交换数据*/
if (copy_to_user(buf, (void*)(devp->mem + p), countt))
ret = - EFAULT;
printk("read %d bytes(s) is %ld\n", countt, p);
printk("bytes(s) is %s\n", buf);
file 是文件指针,count 是请求的传输数据长度,buff 参数是指向用户空间的缓冲区,这个缓冲区或者保存要写入的数据,或者是一个存放新读入数据的空缓冲区,该地址在内核空间不能直接读写,ppos 是一个指针指向一个"long offset type"对象, 它指出用户正在存取的文件位置. 返回值是一个"signed size type。写的位置相对于文件开头的偏移。
static ssize_t module_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
unsigned long p = * /*记录文件指针偏移位置*/
int ret = 0; /*返回值*/
unsigned int countt =/*记录需要写入的字节数*/
struct VirtualDisk *devp = file->private_ /*获得设备结构体指针*/
printk("module_dev write\n");
/*分析和获取有效的写长度*/
if (p >= VIRTUALDISK_SIZE )/*要写入的偏移大于设备的内存空间*/
return countt ? - ENXIO: 0;/*写入地址错误*/
if (countt > VIRTUALDISK_SIZE - p)/*要写入的字节大于设备的内存空间*/
countt = VIRTUALDISK_SIZE -/*将要写入的字节数设为剩余的字节数*/
/*用户空间->内核空间*/
if (copy_from_user(devp->mem + p, buf, countt))
ret = - EFAULT;
*ppos +=/*增加偏移位置*/
ret =/*返回实际的写入字节数*/
printk("written %d bytes(s) from%ld\n", countt, p);
static int memdev_mmap(struct file*file, struct vm_area_struct *vma){
struct VirtualDisk *devp = file->private_ /*获得设备结构体指针*/
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(devp->mem)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
static struct file_operations module_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = module_drv_open,
.read = module_drv_read,
.write = module_drv_write,
.release = module_drv_release,
.llseek = module_drv_llseek,
.ioctl = module_drv_ioctl,
.mmap = memdev_mmap,
/*将 cdev 结构嵌入一个你自己的设备特定的结构,你应当初始化你已经分配的结构使用以上函数,有一个其他的 struct cdev 成员你需要初始化. 象 file_operations 结构,struct cdev 有一个拥有者成员,应当设置为 THIS_MODULE,一旦 cdev 结构建立, 最后的步骤是把它告诉内核, 调用:
cdev_add(&dev->cdev, devno, 1);*/
static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minorIndex){
int devno = MKDEV(VirtualDisk_major, minorIndex);
cdev_init(&dev->cdev, &module_drv_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
printk("error %d cdev file added\n", err);
static int module_drv_init(void)
dev_t devno = MKDEV(VirtualDisk_major, 0);
if(VirtualDisk_major){
result = _region(devno, 1, "module");
result = alloc_chrdev_region(&devno, 0, 1, "module");
VirtualDisk_major = MAJOR(devno);
if(result < 0 ){
VirtualDiskp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL);
if(!VirtualDiskp){
result = -ENOMEM;
goto fail_
memset(VirtualDiskp, 0, sizeof(struct VirtualDisk));
VirtualDisk_setup_cdev(VirtualDiskp, 0);
module_class = class_create(THIS_MODULE, "module_drv");
if (IS_ERR(module_class))
return PTR_ERR(module_class);
module_class_dev = class_device_create(module_class, NULL, MKDEV(VirtualDisk_major, 0), NULL, "module"); /* /dev/xyz */
if (IS_ERR(module_class_dev))
return PTR_ERR(module_class_dev);
fail_malloc:
unregister_chrdev_region(devno, 1);
static void module_drv_exit(void)
cdev_del(&VirtualDiskp->cdev);
kfree(VirtualDiskp);
unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
class_device_unregister(module_class_dev);
class_destroy(module_class);
module_init(module_drv_init);
module_exit(module_drv_exit);
MODULE_LICENSE("GPL");
实例三)b)与驱动对应的应用程序部分
int main(){
//char buf[100];
char key_vals[20];
/*打开文件*/
printf("mypid is %d",getpid());
fd = open("/dev/module",O_RDWR);
buf = (char *)malloc(100);
memset(buf, 0, 100);
start=mmap(NULL,10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
end = mmap(NULL, 20,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//addr为NULL,由系统分配
/* 读出数据 */
strcpy(buf,start);
sleep (1);
printf("buf 1 = %s\n",buf);
/* 写入数据 */
strcpy(start,"Buf Is Not Null!rgrgrfgfgfdg");
memset(buf, 0, 100);
strcpy(buf,start);
sleep (1);
printf("buf 2 = %s\n",buf);
/* 写入数据 */
strcpy(end,"is it reality! is not sure,are you ok, make sure ,you");
memset(buf, 0, 100);
strcpy(buf,start);
sleep (1);
printf("buf 3 = %s\n",buf);
printf("buf 3 = %s\n",end);
read(fd, key_vals, sizeof(key_vals));
printf("key_vals 3 = %s\n",key_vals);
// while(1);
munmap(start,10); /*解除映射*/
munmap(end,20); /*解除映射*/
free(buf);
close(fd);
第三部分:struct stat 作用
1、stat,lstat,fstat1 函数都是获取文件(普通文件,目录,管道,socket,字符,块()的属性。函数原型#include
2、向stat,fstat1、lstat传入文件名字path、fd、path,获取文件对应属性buf。
int stat(const char *path, struct stat *buf); //文件路径或文件名
int fstat(int fd, struct stat *buf);//文件描述符
int lstat(const char *path, struct stat *buf);//连接文件
结构体二)struct stat结构
struct stat {
mode_t st_ //文件对应的模式,文件,目录等
ino_t st_ //inode节点号
dev_t st_ //设备号码
dev_t st_ //特殊设备号码
nlink_t st_ //文件的连接数
uid_t st_ //文件所有者
gid_t st_ //文件所有者对应的组
off_t st_ //普通文件,对应的文件字节数(常用)
time_t st_ //文件最后被访问的时间
time_t st_ //文件内容最后被修改的时间
time_t st_ //文件状态改变时间
blksize_t st_ //文件内容对应的块大小
blkcnt_t st_ //文件内容对应的块数量
四、与mmap应用程序中&普通文件虚拟内存映射模板和实例
模板二)1、&mmap()应用程序模板
/*获得映射区域地址,赋值mapChar*/
fd = open("/tmp/test.txt",O_RDWR);
struct stat fileS
/* 获取文件的属性 */
if ((fstat(fd, &fileStat)) == -1) {
perror("fstat");
unsigned int fileBufferS
fileBufferSize = fileStat.st_/*mmap回写时,字节最大大小
为fileStat.st_size,所以定义字节大fileStat.st_size*/
mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//获得映射区域地址
munmap(checkChar, fileBufferSize);
实例四)2、完整实例
void printfMapChar(char *nameChar, char *mapChar){
printf("%s = %s\n\n", nameChar,mapChar);
void printfDivLine(char *desc){
printf("********%s*******\n", desc);
int main(){
char *mapC
char *checkC//验证是否mapChar是映射地址
struct stat fileS
printf("mypid is %d\n",getpid());//输出本pid
/*获得映射区域地址,赋值mapChar*/
fd = open("/tmp/test.txt",O_RDWR);
/* 获取文件的属性 */
if ((fstat(fd, &fileStat)) == -1) {
perror("fstat");
unsigned int fileBufferS
fileBufferSize = fileStat.st_/*mmap回写时,字节最大大小
为fileStat.st_size,所以定义字节大fileStat.st_size*/
Tip:mmap回写时,回写字节最大大小为fileStat.st_size,所以定义字节大fileStat.st_size。(这个我没有根据,只是实验结果是这样)
mapChar = mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//获得映射区域地址MAP_SHARED更改mapchar后改变fd文件内容
/*****************/
/********打印映射区域内容;和mapChar*********/
printfDivLine("打印映射区域内容;和mapChar");
printfMapChar("mapChar", mapChar);
/**************/
/*******通过mapChar将数据写入映射区域*******/
strcpy(mapChar, "writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,");//写入映射区域
printfDivLine("通过mapChar将数据写入映射区域");
printfMapChar("mapChar", mapChar);
/**********checkChar验证*********/
checkChar = mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//获得映射区域地址
close(fd);//不使用fd时就可以close
printfDivLine("checkChar验证");
printfMapChar("checkChar", checkChar);
munmap(mapChar, fileBufferSize);//释放mapchar的映射,此时文件的映射在内存内然存在
munmap(checkChar, fileBufferSize);
关键字:&&&&&&&&
编辑:什么鱼 引用地址:
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。
微信扫一扫加关注 论坛活动 E手掌握
微信扫一扫加关注
芯片资讯 锐利解读
大学堂最新课程
汇总了TI汽车信息娱乐系统方案、优质音频解决方案、汽车娱乐系统和仪表盘参考设计相关的文档、视频等资源
热门资源推荐
频道白皮书
何立民专栏
北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。}

我要回帖

更多关于 单片机结构体定义 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信