【原创】树莓派3B开发Go语言(四)-自写库实现pwm输出 在前一小节中介绍了点亮第一个LED灯,这里我们准备进阶尝试下,输出第一段PWM波形 。(PWM也就是脉宽调制 , 一种可调占空比的技术,得到的效果就是:如果用示波器测量引脚会发现有方波输出,而且高电平、低电平的时间是可调的 。)
这里爪爪熊准备写成一个golang的库 , 并开源到github上,后续更新将直接更新到github中,如果你有兴趣可以和我联系 。github.com/dpawsbear/bear_rpi_go
我在很多的教程中都看到说树莓派的PWM(硬件)只有一个GPIO能够输出,就是GPIO1。这可是不小的打击,因为我想使用至少四个PWM,还是不死心,想通过硬件手册上找寻蛛丝马迹,看看究竟怎么回事 。
手册上找寻东西稍等下讲述,这里先提供一种方法测试树莓派3B的PWM方法:用指令控制硬件PWM 。
这里通过指令的方式掌握了基本的pwm设置技巧,决定去翻一下手册看看到底PWM怎么回事,这里因为没有BCM2837的手册,根据之前文章引用官网所说,BCM2835和BCM2837应该是一样的 。这里我们直接翻阅BCM2835的手册,直接找到PWM章节 。找到了如下图:
图中可以看到在博通的命名规则中 GPIO 12、13、18、19、40、41、45、52、53 均可以作为PWM输出 。但是只有两路PWM0 PWM1 。根据我之前所学知识,不出意外应该是PWM0 和 PWM1可以输出不一样的占空比 , 但是频率应该是一样的 。因为没有示波器,暂时不好测试 。先找到下面对应图:
根据以上两个图对比可以发现如下规律:
对照上面的表可以看出从 BCM2837 中印出来的能够使用在PWM上的就这几个了 。
为了验证个人猜想是否正确,这里先直接使用指令的模式,模拟配置下是否能够正常输出 。
通过上面一系列指令模拟发现,(GPIO1、GPIO26)、(GPIO23、GPIO24)是绑定在一起的,调节任意一个,另外一个也会发生变化 。也即是PWM0、PWM1虽然输出了两路,可以理解成两路其实都是连在一个输出口上 。这里由于没有示波器或者逻辑分析仪这类设备(仅有一个LED灯),所以测试很简陋,下一步是使用示波器这类东西对频率以及信号稳定性进行下测试 。
小节:树莓派具有四路硬件输出PWM能力 , 但是四路中只能输出两个独立(占空比独立)的PWM,同时四路输出的频率均是恒定的 。
上面大概了解清楚了树莓派3B的PWM结构,接下来就是探究如何使用Go语言进行设置 。
因为拿到了手册,这里我想直接操作寄存器的方式进行设置 , 也是顺便学习下Go语言处理寄存器的过程 。首先需要拿到pwm 系列寄存器的基地址,但是翻了一圈手册,发现只有偏移 , 没有找到基地址 。
经过了一段时间的努力后,决定写一个 树莓派3B golang包开源放在github上,只需要写相关程序进行调用就可以了,以下是相关demo(pwm)(在GPIO.12 上输出PWM波,放上LED灯会有呼吸灯的效果,具体多少频率还没有进行测试)
以下是demo(pwm) 源码
go语言的reflect(反射)1、反射可以在运行时 动态获取变量的各种信息 ,比如变量的类型、类别;
2、如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法);
3、通过反射,可以修改 变量的值,可以调用关联的方法;
4、使用反射,需要import " reflect ".
5、示意图:
1、不知道接口调用哪个函数 , 根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射 。
例如以下这种桥接模式:
示例第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数 。
1、reflect.TypeOf(变量名) , 获取变量的类型,返回reflect.Type类型 。
2、reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型reflect.Value是一个结构体类型 。
3、变量、interface{}和reflect.Value是可以互相转换的,这点在实际开发中,会经常使用到 。
1、reflect.Value.Kind,获取变量的 类别(Kind) , 返回的是一个 常量。在go语言文档中:
示例如下所示:
输出如下:
Kind的范畴要比Type大 。比如有Student和Consumer两个结构体,他们的 Type 分别是 Student 和 Consumer,但是它们的 Kind 都是 struct。
2、Type是类型,Kind是类别,Type和Kind可能是相同的,也可能是不同的 。
3、通过反射可以在让 变量 在 interface{} 和 Reflect.Value 之间相互转换,这点在前面画过示意图 。
4、使用反射的方式来获取变量的值(并返回对应的类型),要求数据类型匹配 , 比如x是int,那么久应该使用reflect.Value(x).Int(),而不能使用其它的 , 否则报panic 。
如果是x是float类型的话,也是要用reflect.Value(x).Float() 。但是如果是struct类型的话,由于type并不确定,所以没有相应的方法,只能 断言 。
5、通过反射的来修改变量 , 注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值 , 同时需要使用到reflect.Value.Elem()方法 。
输出num=20,即成功使用反射来修改传进来变量的值 。
6、reflect.Value.Elem()应该如何理解?
Go语言中恰到好处的内存对齐 在开始之前,希望你计算一下Part1共占用的大小是多少呢?
输出结果:
这么一算, Part1这一个结构体的占用内存大小为 1 4 1 8 1 = 15 个字节 。相信有的小伙伴是这么算的,看上去也没什么毛病
真实情况是怎么样的呢?我们实际调用看看 , 如下:
输出结果:
最终输出为占用 32 个字节 。这与前面所预期的结果完全不一样 。这充分地说明了先前的计算方式是错误的 。为什么呢?
在这里要提到 “内存对齐” 这一概念,才能够用正确的姿势去计算,接下来我们详细的讲讲它是什么
有的小伙伴可能会认为内存读?。褪且桓黾虻サ淖纸谑榘诜?
上图表示一个坑一个萝卜的内存读取方式 。但实际上 CPU 并不会以一个一个字节去读取和写入内存 。相反 CPU 读取内存是 一块一块读取 的,块的大小可以为 2、4、6、8、16 字节等大小 。块大小我们称其为 内存访问粒度。如下图:
在样例中 , 假设访问粒度为 4 。CPU 是以每 4 个字节大小的访问粒度去读取和写入内存的 。这才是正确的姿势
另外作为一个工程师,你也很有必要学习这块知识点哦 :)
在上图中,假设从 Index 1 开始读?。?将会出现很崩溃的问题 。因为它的内存访问边界是不对齐的 。因此 CPU 会做一些额外的处理工作 。如下:
从上述流程可得出,不做 “内存对齐” 是一件有点 "麻烦" 的事 。因为它会增加许多耗费时间的动作
而假设做了内存对齐,从 Index 0 开始读取 4 个字节,只需要读取一次 , 也不需要额外的运算 。这显然高效很多,是标准的 空间换时间 做法
在不同平台上的编译器都有自己默认的 “对齐系数”,可通过预编译命令#pragma pack(n)进行变更 , n 就是代指 “对齐系数” 。一般来讲,我们常用的平台的系数如下:
另外要注意 , 不同硬件平台占用的大小和对齐值都可能是不一样的 。因此本文的值不是唯一的,调试的时候需按本机的实际情况考虑
输出结果:
在 Go 中可以调用unsafe.Alignof来返回相应类型的对齐系数 。通过观察输出结果 , 可得知基本都是2^n,最大也不会超过 8 。这是因为我手提(64 位)编译器默认对齐系数是 8,因此最大值不会超过这个数
在上小节中,提到了结构体中的成员变量要做字节对齐 。那么想当然身为最终结果的结构体,也是需要做字节对齐的
接下来我们一起分析一下 , “它” 到底经历了些什么,影响了 “预期” 结果
在每个成员变量进行对齐后 , 根据规则 2,整个结构体本身也要进行字节对齐,因为可发现它可能并不是2^n ,不是偶数倍 。显然不符合对齐的规则
根据规则 2,可得出对齐值为 8 。现在的偏移量为 25,不是 8 的整倍数 。因此确定偏移量为 32 。对结构体进行对齐
【go语言动作图 go语言chan】 Part1 内存布局:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx
通过本节的分析,可得知先前的 “推算” 为什么错误?
是因为实际内存管理并非 “一个萝卜一个坑” 的思想 。而是一块一块 。通过空间换时间(效率)的思想来完成这块读取、写入 。另外也需要兼顾不同平台的内存操作情况
在上一小节,可得知根据成员变量的类型不同,其结构体的内存会产生对齐等动作 。那假设字段顺序不同,会不会有什么变化呢?我们一起来试试吧 :-)
输出结果:
通过结果可以惊喜的发现 , 只是 “简单” 对成员变量的字段顺序进行改变,就改变了结构体占用大小
接下来我们一起剖析一下Part2 ,看看它的内部到底和上一位之间有什么区别 , 才导致了这样的结果?
符合规则 2,不需要额外对齐
Part2 内存布局:ecax|bbbb|dddd|dddd
通过对比Part1和Part2的内存布局,你会发现两者有很大的不同 。如下:
仔细一看,Part1存在许多 Padding 。显然它占据了不少空间,那么 Padding 是怎么出现的呢?
通过本文的介绍 , 可得知是由于不同类型导致需要进行字节对齐,以此保证内存的访问边界
那么也不难理解,为什么 调整结构体内成员变量的字段顺序 就能达到缩小结构体占用大小的疑问了 , 是因为巧妙地减少了 Padding 的存在 。让它们更 “紧凑” 了 。这一点对于加深 Go 的内存布局印象和大对象的优化非常有帮
go语言动作图的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于go语言chan、go语言动作图的信息别忘了在本站进行查找喔 。
推荐阅读
- 快手小妈妈直播回放,快手小妈妈直播回放在哪里看
- ipad怎么下载视频,iPad怎么下载视频到文件
- 安卓手机怎么到顶部,安卓手机怎么回到顶部
- 虎牙董月月漏沟直播,虎牙主播董月月个人资料
- python遍历函数 python遍历循环怎么理解
- 关于四足机器人python代码的信息
- 华为手表破解方法软件安卓,华为智能手表表盘破解
- 好玩射击高画质的游戏手游,好玩的高画质射击游戏手机游戏
- mysql数据库怎么图片 mysql数据库图片用什么数据类型