查看原文
其他

【连载】通过"库文件"学单片机驱动编程(4)

bug菌 最后一个bug 2021-01-31

1、每篇一聊

    下午6点下班,6点半到家,做饭、刷碗、收拾。8点成功坐到了电脑桌面前开始了今天的写作,开始了今天的构思!今天在处理一个同事遗留的大项目,虽然说我大致熟悉这一套系统的实现过程,不过对于里面的一些细节我并不是很熟悉,偶尔在调试的过程中还陷入了怪圈,无法自拔,所以今天我也遇到了,而且还折腾了一会,大体也总结一下经验供大家参考。

    在工作中我们往往会有这样的想法:"这代码我看了4-5遍了,不可能有问题呀""这个现象不可能是这样的","难道硬件有问题?"......,这个时候真的是很折腾的事情,因为有时候程序员的思维就定格在那个地方了,怎么也找不到bug的所在,这个时候我觉得你应该换个思路,比如喝个茶,转移一下注意力,或者是换一种情景或者现象看本质,这样一些问题会自然露出破绽。


2、"库"中结构体的妙用

    我们昨天提到,stm32中大部分的函数第一参数都是结构体寄存器的指针,其后的一些变量便是对这些寄存器进行配置的参数,除了一种特殊情况我们的同类的外设就只有一个,我们就不需要传入结构体寄存器的指针了,直接传入你所选择的寄存器参数即可。那具体的细节为什么配置这个结构体寄存器指针就可以实际写入到我们芯片的物理寄存器中呢?下面我就来为大家剖析一下:

    1)上图是我从stm32文档中截取出来的芯片系统架构图,对于这个图的理解我们把它就好比高速公路,那么我们的外设都是通过不同的总线来进行访问的,我们通过这样的图可以知道CPU能够访问那些设备,并且通过怎样的路径来进行访问。既然要独立访问总线连接的设备,那么他们的地址肯定是唯一的。这样就形成了一种映射关系,我们的CPU通过不同的映射地址即可访问到各个设备。那么我们的库中肯定是为大家定义好了每个设备的地址。

    2)在stm32f4xx.h该文件中我们可以找到如上代码,库中为我们定义了总线的地址,在总线基地址的基础上又映射出了各个外设的基地址。这样我们就可以定位到每个外设的所属区域。

    3)我们已经确定了外设的基地址,那么我们通过定义结构体指针就可以覆盖该外设的整块内存,通过这个寄存器结构体指针进而可以访问每个寄存器。(如下图所示)

知识小贴士之volatile

    结构体的每个成员前面几乎都使用__IO,我们可以定位到为如下宏定义

 那我们在这里就简单讲讲这个关键字:

    1)volatile的本意是"易变的",说白了就是告诉编译器这个变量容易发生变化

    2)编译器在遇到该关键字以后不会对其进行优化,而且每次都会从内存中读取数据来使用,因为我们为了高速读取内存数据,处理器可能首先会把相关数据放到寄存器或者cache中保存,等我们下次再需要数据的时候就直接从寄存器中获取,而不是从内存中获取,这样就导致该变量可能不能体现外设的状态情况,程序就会出问题。

    3)我们还看到上面库里面有个volatile const看来这个两个关键字可以同时修饰一个变量,这样表示的就是只能读的了,并且是每次都会从内存中取值。(所以这里大家又get到一个新技能,更加详细的可以大家看完后继续研究研究,后面可能也会总结成文章给大家。)

    4)好了这样我们就非常清楚这个结构体寄存器指针是如何实现的了,以后你玩寄存器封装什么的照着这种方式做准没错.那么下图我们传给结构体寄存器指针就只需要传这块外设的基地址了。


3、这里才是重点!!!

    我们看到了ST大佬这么玩结构体指针的,那么我们有没有想到可以衍生出其他有用的玩法呢?难道我今天的内容还是只是封装一块寄存器?NO、NO、NO;下面才是认为自己关注本公众号是正确选择的理由:

1)抽象出结构体指针

    我们要用抽象的思维看待结构体指针,我们的结构体指针就好像把一堆模具按照顺序组合起来,然后在橡皮泥上印一系列的图案,假如说我们事先就已经按照你想要的图案印好了,那么我们只需要把这一堆模具向上一放就可以获得了单个我们想要的图案内容了!(想象一下)


2)项目中的扩展应用

    在通讯数据解析中,我们大部分通讯格式都是固定的,在前面一篇《modbus协议分析》中我向大家提到了一种字节流模型,那么我们发送方发送数据按照结构体传输,那么我们接受方就不再需要进行协议的拼字节了,直接用结构体指针印图案即可,直接解析出来使用,如果有些小伙伴还不理解,作者直接上图和代码。(还有很多应用,我就不举例子了,给你们一个眼神自己体会!)

    参考代码:

  1. #include <stdio.h>

  2. #include <stdlib.h>


  3. /*****************************************************

  4. * @Fuction: 数据类型定义区

  5. * @Author : (公众号:最后一个bug)

  6. ****************************************************/

  7. typedef unsigned char uint8_t;


  8. /*****************************************************

  9. * @Fuction: 结构体数据定义区

  10. * @Author : (公众号:最后一个bug)

  11. ****************************************************/

  12. typedef struct _tag_SendFrame

  13. {

  14. uint8_t head;

  15. uint8_t length;

  16. uint8_t data1;

  17. uint8_t data2;

  18. uint8_t Chk;

  19. }sSendFrame;


  20. typedef struct _tag_ReceiverFrame

  21. {

  22. uint8_t head;

  23. uint8_t length;

  24. uint8_t data1;

  25. uint8_t data2;

  26. uint8_t Chk;

  27. }sReceiverFrame;


  28. /*****************************************************

  29. * @Fuction: 全局变量定义区

  30. * @Author : (公众号:最后一个bug)

  31. ****************************************************/

  32. sSendFrame stSendProtocol;

  33. sReceiverFrame* pSimulReceiver = NULL;

  34. uint8_t TransChannelBuff[100] ={0};

  35. uint8_t* pSimulSender = NULL;


  36. /*****************************************************

  37. * @Fuction: main函数

  38. * @Author : (公众号:最后一个bug)

  39. ****************************************************/

  40. int main(int argc, char *argv[]) {


  41. uint8_t tansCnt = 0;


  42. //1)打包好协议数据

  43. stSendProtocol.head = 0xFF;

  44. stSendProtocol.length = 0x02;

  45. stSendProtocol.data1 = 0x01;

  46. stSendProtocol.data2 = 0x02;

  47. stSendProtocol.Chk = 0xAB;

  48. pSimulSender = (uint8_t*)(&stSendProtocol);



  49. //2)模拟发送数据

  50. for(tansCnt = 0; tansCnt < sizeof(sSendFrame);tansCnt++)

  51. {

  52. TransChannelBuff[tansCnt] = *(pSimulSender+tansCnt);

  53. }


  54. //3)模拟接受数据

  55. //由于接受数据会放在一个buff里面,这里认为已经放到了接受buff里面

  56. //直接进行接受结构体数据提取

  57. pSimulReceiver = (sReceiverFrame*)TransChannelBuff;


  58. //4)然后就是通讯的常规操作了(校验-->解析-->处理)


  59. //5)输出检测结果

  60. printf("发送方报文协议格式:\n");

  61. printf("stSendProtocol.head = 0X%x\n",stSendProtocol.head);

  62. printf("stSendProtocol.length = 0X%x\n",stSendProtocol.length);

  63. printf("stSendProtocol.data1 = 0X%x\n",stSendProtocol.data1);

  64. printf("stSendProtocol.data2 = 0X%x\n",stSendProtocol.data2);

  65. printf("stSendProtocol.Chk = 0X%x\n",stSendProtocol.Chk);


  66. printf("\n接收方报文协议格式:\n");

  67. printf("pSimulReceiver->head = 0X%x\n",pSimulReceiver->head);

  68. printf("pSimulReceiver->length = 0X%x\n",pSimulReceiver->length);

  69. printf("pSimulReceiver->data1 = 0X%x\n",pSimulReceiver->data1);

  70. printf("pSimulReceiver->data2 = 0X%x\n",pSimulReceiver->data2);

  71. printf("pSimulReceiver->Chk = 0X%x\n",pSimulReceiver->Chk);


  72. printf("欢迎大家关注公众号:最后一个bug!");

  73. return 0;

  74. }

    输出测试结果:

    今天的内容就到这里,这里是公众号:“最后一个bug”大家多多分享,一起进步,最后感谢大家关注和认可,我们下期再见,有想和作者沟通的都可以公众号私信我哦

推荐阅读

【连载】通过"库文件"学单片机驱动编程(1)

【连载】通过"库文件"学单片机驱动编程(2)

【连载】通过"库文件"学单片机驱动编程(3)

嵌入式编程之动态接口技术(经验干货)

嵌入式编程之"重构"代码(C语言版本)

嵌入式编程必备之多方法测定程序运行时间(经验篇)

单片机常用程序框架之分时轮询(详注代码)

看门狗你确定会用了?(经验干货满满)

单片机开发之节省内存大法(C语言版本)

一种 “ 超强 ” 队列的C语言实现(附代码)

一文搞定通信协议中CRC校验(附代码)

【连载】嵌入式测试驱动开发(9)

C语言数值常量的“那些事”(细节分析)

向Modbus协议说"So easy!"

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存