硬汉嵌入式论坛

 找回密码
 立即注册
查看: 6363|回复: 23
收起左侧

[STM32H7] 实战技能分享,各种数据类型的SPI, UART, I2C,FDCAN等方式的通信传输以及存储到EEPROM, Flash等设备的简易实现方法

  [复制链接]

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
发表于 2021-11-20 00:26:24 | 显示全部楼层 |阅读模式
背景知识:

各种类型的数据传输和存储就涉及到大小端的问题,所以我们在开头把这个知识点做个说明。


首先要简单说下芯片的大小端问题,我们这里主要讨论Cortex-M内核。

权威指南的说明如下:
Support for little endian and big endian memory systems e The Cortex-M3 and Cortex-M4 processors can work with either little endian or big endian memory systems. In practice, a microcontroller product is normally designed with just one endian configuration.

M内核支持大端或者小端,实际应用中大部分内核都是小端。以STM32为例,全部都是小端,而且是芯片设计之初就固化进去的,不可修改。市面上其他厂家基本也都固化的小端格式。
F1编程手册:
0.png

F3和F4编程手册:
1.png


F7和H7编程手册:
2.png


各种数据类型编程EEPROM,SPI Flash等存储器的简易方法

一般这些存储器都是字节编程,写入浮点等数据类型时不太方便。

这里分享一个方法,定义一个结构体,将各种数据类型封装进去:
1.png

写入的时候采用下面方式:
3.png

读取时可以采用下面方式:
2.png


各种数据类型的SPI,UART,I2C,FDCAN等传输问题

这里我们以串口通信为例,比如主机要发送如下格式数据给从机:

QQ截图20211119234537.png

我们可以做一个如下结构体格式:
  1. typedef struct
  2. {
  3.     uint8_t ucStart;                        

  4.     uint16_t usCO2;
  5.     uint16_t usPM25;        
  6.     uint16_t usHumidity;         
  7.     float    Temprature;
  8.     uint32_t ulParam;
  9.     uint8_t  ucEnd1;           
  10.     uint8_t  ucEnd2;   
  11. }
  12. UART_T;

  13. UART_T g_tUartParam;
复制代码

主机发送的时候我们就可以采用如下方法:
  1. comSendBuf(COM1, (uint8_t *)&g_tUartParam, sizeof(UART_T));
复制代码

从机工程也定义一个同样的结构体变量,比如我们把接收到一帧数据存到缓冲uint8_t buf[50]里面了。

我们就可以定义一个结构体指针变量:

  1. UART_T *pUartParam;
  2. pUartParam = (UART_T *)buf;
复制代码

那么我们就可以pUartParam->usCO2,  pUartParam->Temprature等方式来访问,非常方便。


实战案例:

大家可以下载我们论坛置顶帖V5,V6或者V7板子出厂程序,里面都有相关代码参考。


补充拓展贴,特别是结构体对齐问题:

1、【烧脑技术贴】无法回避的字节对齐问题,从八个方向深入探讨(变量对齐,栈对齐,DMA对齐,结构体成对齐,Cache, RTOS双堆栈等)
http://www.armbbs.cn/forum.php?mod=viewthread&tid=109400

2、推荐一种超简单的硬件位带bitband操作方法,让变量,寄存器控制,IO访问更便捷,无需用户计算位置
http://www.armbbs.cn/forum.php?mod=viewthread&tid=109508


评分

参与人数 2金币 +40 收起 理由
龙之谷 + 20 很给力!
byccc + 20

查看全部评分

回复

使用道具 举报

0

主题

15

回帖

15

积分

新手上路

积分
15
QQ
发表于 2021-11-20 07:58:06 | 显示全部楼层
对于结构体内单个变量的读写可以使用如下操作
#define OffsetAddr(struc, e) (size_t)&(((struc *)0)->e)  //计算e在结构体struc内的偏移

//保存数据  PARAM_ADDR数据存储基地址  nValue:待保存的值
AT24CXX_Write(PARAM_ADDR + OffsetAddr(g_tParam, usAdcX1), (u8 *)&nValue, sizeof(uint16_t));

//读数据  pValue:存储读取的值
AT24CXX_Read(PARAM_ADDR + OffsetAddr(g_tParam, usAdcX1), (u8 *)pValue, sizeof(uint16_t));

评分

参与人数 1金币 +10 收起 理由
厉飞雨 + 10 很给力!

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2021-11-20 08:05:29 | 显示全部楼层
楼主位特别补充两个之前发的帖子,方便大家看。
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2021-11-20 08:08:00 | 显示全部楼层
loudianxin 发表于 2021-11-20 07:58
对于结构体内单个变量的读写可以使用如下操作
#define OffsetAddr(struc, e) (size_t)&(((struc *)0)->e)  ...

谢谢分享。
这个只有EEPROM可以这么玩,Flash不行,要执行擦除。
所以整体做方便些,也方便用户在末尾追加CRC校验值,因为EEPROM里面会存入一些关键参数,整体读取出来后,可以配合末尾读出来的CRC做校验。

回复

使用道具 举报

44

主题

562

回帖

699

积分

金牌会员

积分
699
发表于 2021-11-20 08:08:02 | 显示全部楼层
loudianxin 发表于 2021-11-20 07:58
对于结构体内单个变量的读写可以使用如下操作
#define OffsetAddr(struc, e) (size_t)&(((struc *)0)->e)  ...

这个不错,刚看硬汉发的内容我就想问这个问题,单个读写操作。
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2021-11-20 08:10:58 | 显示全部楼层
ou513 发表于 2021-11-20 08:08
这个不错,刚看硬汉发的内容我就想问这个问题,单个读写操作。

这个只有EEPROM可以这么玩,Flash不行,要执行擦除。
所以整体做方便些,也方便用户在末尾追加CRC校验值,因为EEPROM里面会存入一些关键参数,整体读取出来后,可以配合末尾读出来的CRC做校验。
回复

使用道具 举报

36

主题

2039

回帖

2147

积分

至尊会员

积分
2147
发表于 2021-11-20 10:49:12 | 显示全部楼层
可以考虑把结构体对齐的介绍,也在这个帖子里面稍微做个介绍,方便用户看。
Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better.
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2021-11-20 11:55:46 | 显示全部楼层
byccc 发表于 2021-11-20 10:49
可以考虑把结构体对齐的介绍,也在这个帖子里面稍微做个介绍,方便用户看。

我已经把链接补上了。
回复

使用道具 举报

9

主题

103

回帖

130

积分

初级会员

积分
130
发表于 2021-11-20 14:47:08 | 显示全部楼层
eric2013 发表于 2021-11-20 08:10
这个只有EEPROM可以这么玩,Flash不行,要执行擦除。
所以整体做方便些,也方便用户在末尾追加CRC校验值 ...

我们也是这样做的。问您另外一个问题,我们保存的数据最大有二十几KB,但是由于产品升级需求改变导致这些数据增加或减少一部分,升级新程序后 校验不通过所有数据就复位成默认值了。有什么办法可以解决这个问题吗?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2021-11-20 17:43:54 | 显示全部楼层
xy201207 发表于 2021-11-20 14:47
我们也是这样做的。问您另外一个问题,我们保存的数据最大有二十几KB,但是由于产品升级需求改变导致这些 ...

升级修改这个最容易出问题。最新还是升级前将老数据读出来赋给新的结构体。然后统一下发,设置新的校验值。
回复

使用道具 举报

2

主题

15

回帖

21

积分

新手上路

积分
21
发表于 2022-1-1 13:24:35 | 显示全部楼层
汉哥请问   接收端定义了结构体指针:UART_T *pUartParam; 结构体所在文件里面可以正常调用,串口接收数据使用也很方便,但这个结构体指针如何在其他文件调用呢,就是怎么用extern 去声明这个结构体指针,请汉哥指导下哈

接收结构体指针定义

接收结构体指针定义

这样在其他文件声明对吗?

这样在其他文件声明对吗?

在其他文件调用时出现错误,水平有限希望汉哥指点下

在其他文件调用时出现错误,水平有限希望汉哥指点下
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2022-1-1 20:08:35 | 显示全部楼层
恺撒 发表于 2022-1-1 13:24
汉哥请问   接收端定义了结构体指针:UART_T *pUartParam; 结构体所在文件里面可以正常调用,串口接收数据 ...

结构体推荐定义到头文件里面。
回复

使用道具 举报

7

主题

190

回帖

216

积分

高级会员

积分
216
发表于 2022-2-24 21:33:41 | 显示全部楼层
硬汉哥有做eeprom磨损均衡的例程嘛
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2022-2-25 00:10:34 | 显示全部楼层
abcde1224 发表于 2022-2-24 21:33
硬汉哥有做eeprom磨损均衡的例程嘛

这个一般不用做磨损,大部分情况下我们都是做参数存储使用,修改很少。而支持100万次擦写。
image.png
回复

使用道具 举报

7

主题

190

回帖

216

积分

高级会员

积分
216
发表于 2022-2-25 07:52:52 | 显示全部楼层
eric2013 发表于 2022-2-25 00:10
这个一般不用做磨损,大部分情况下我们都是做参数存储使用,修改很少。而支持100万次擦写。

噢噢  我现在做的个产品  大概5s左右记录一次累计数据  有磨损均衡需求  自己写了个感觉不是那么好用
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2022-2-25 10:47:31 | 显示全部楼层
abcde1224 发表于 2022-2-25 07:52
噢噢  我现在做的个产品  大概5s左右记录一次累计数据  有磨损均衡需求  自己写了个感觉不是那么好用{:16 ...

这种的用铁电,ERAM,MRAM之类的更合适。
回复

使用道具 举报

0

主题

15

回帖

15

积分

新手上路

积分
15
发表于 2022-3-22 17:28:58 | 显示全部楼层
如果主机是大端模式,从机是小端模式,这种办法是不是就出错了?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2022-3-22 17:32:29 | 显示全部楼层
xxc 发表于 2022-3-22 17:28
如果主机是大端模式,从机是小端模式,这种办法是不是就出错了?

是的,M内核基本都是小端。所以没问题。



回复

使用道具 举报

0

主题

6

回帖

6

积分

新手上路

积分
6
发表于 2022-5-6 20:25:24 | 显示全部楼层
学习了!
回复

使用道具 举报

32

主题

103

回帖

199

积分

高级会员

积分
199
发表于 2022-6-6 10:39:22 | 显示全部楼层
loudianxin 发表于 2021-11-20 07:58
对于结构体内单个变量的读写可以使用如下操作
#define OffsetAddr(struc, e) (size_t)&(((struc *)0)->e)  ...

这个宏定义好像linux里,list.h也是这么干的。标准C语言也支持啊
回复

使用道具 举报

0

主题

12

回帖

12

积分

新手上路

积分
12
发表于 2023-10-10 23:14:20 | 显示全部楼层
大佬,有个问题请教下。
UART_T *pUartParam;
uint8_t buf[50];
pUartParam = (UART_T *)buf;

buf 对齐到1字节,pUartParam 对齐到4字节。这样转换会有问题么
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
 楼主| 发表于 2023-10-11 13:04:58 | 显示全部楼层
gua 发表于 2023-10-10 23:14
大佬,有个问题请教下。
UART_T *pUartParam;
uint8_t buf[50];

没问题,M内核支持非对齐访问
回复

使用道具 举报

0

主题

12

回帖

12

积分

新手上路

积分
12
发表于 2023-10-11 14:15:38 | 显示全部楼层
eric2013 发表于 2023-10-11 13:04
没问题,M内核支持非对齐访问

了解
回复

使用道具 举报

0

主题

6

回帖

6

积分

新手上路

积分
6
发表于 2023-12-5 11:51:46 | 显示全部楼层
学习!感谢硬汉哥
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-28 11:50 , Processed in 0.428165 second(s), 37 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表