通信从机
一.我的做法
一些注意事项:
1.由于循环队列是自己写的,所以线程优先级我最后设置的是发送>处理>接收,防止数据发一半队列被更改
2.为了防止最后输出奇怪的东西,最好输出前多加一些判断(比如是否为0之类的),可以冗余一点但不要发错
3.由于只需要最新的数据,每次使用循环队列时让front指向tail-16的位置,但是需要判断是否有16个数据以及这16个数据是否符合要求,如果不符合要求就再往前跳16个字节,但是记得判断是否有这么些数据。
4.刚开始我对队列的长度进行判断,如果不是16的倍数就重置队列,但是后来发现由于线程定时1ms比较长,几乎肯定会有不合格的长度,所以后来改成了进入队列时判断,这个是我神志不清了
5.记得判断长度为0的情况
6.缓冲区可以弄大一些,会减少len_err的出现频率
7.出队时记得不要只让校验的地方出队,要把十六个字节全部出队
二.分析代码
1.RTT版本
// 接收数据处理结构体
typedef struct
{
// 串口句柄
rt_device_t huart;
// 用于标记数据是否更新
uint8_t Data_Is_Refreshed;
// 缓冲数组
uint8_t Rx_Buffer[BUFFER_LENGTH][16];
// 本次接收了多少个字节
uint16_t Rece_Size;
// 最新数据的位置
uint8_t Place_Now;
// 最新有效数据的位置
uint8_t Place_Right;
// 长度错误计数
uint32_t Error_Len;
// 和校验错误计数
uint32_t Error_Sum;
// 正确个数
uint32_t Right_Num;
// 数据个数
uint32_t Rece_Num;
} Rx_Handle_t;
核心是对于这个结构体进行操作
一个注意的地方是place_now的类型是uint8,也就是在加一的过程中可以自动溢出,省去了取模的操作
// 接收数据帧
typedef __packed struct
{
uint32_t random;
uint32_t tick;
uint32_t sum;
uint32_t data;
} RxData_Frame_t;
另一个比较巧妙的地方是上面这个结构体,在读取数据的线程中使用它的地址可以快速转换得到对应的数据
核心代码是这个
data_frame = (RxData_Frame_t *)(&rx_buffer->Rx_Buffer[rx_buffer->Place_Now][0]);
/* 接收数据回调函数 */
static rt_err_t uart1_rx_ind(rt_device_t dev, rt_size_t size)
{
Rx_Handle[0].Rece_Size = size;
Rx_Handle[0].Rece_Num++;
Rx_Handle[0].Data_Is_Refreshed = 1;
rt_sem_release(&Rx_Sem);
return RT_EOK;
}
这里一个比较关键的地方是设置了标志位refreshed,用来表示是否传输了新的数据,在下面会用到
有了上述的工具,接收数据就很简单了,按照和校验的定义进行操作就可以
值得注意的一点是在下面这个位置

一定要先读取数据再对长度进行判断,否则我猜测会造成串口数据的阻塞
发送端同样是用的类似的结构体
typedef struct
{
uint32_t random_0;
uint32_t random_1;
uint32_t tick_0;
uint32_t tick_1;
uint32_t sum;
} TxData_Frame_t;
不同的是,在数据发送上面,这份代码用了定时器而非线程,这样的好处一是防止了复杂的线程调度,二是可以实现小于1ms的周期性发送,拉高频率,如果用线程定时器最高就只能拉到1khz
在我的方法中,用了四个线程,两个接收一个处理一个发送,在线程调度上要求比较高,但是这份代码只用了两个线程,一个接受一个处理,就没有这样的问题。我在接收数据时使用了消息队列,然后又放到循环队列,可能会造成效率的下降,而且循环队列对于每一个uint8都单独存储,不如直接用二维数组,可以把16次操作变成1次
2.RTT+HAL版本
相对于RTT版本的代码来说,答题思路是一样的,只是把rtt的驱动换成了hal库,比如用hal库开启dma,使用串口等
3.HAL库移植
0.更新hal库
大概有两种方式,一种是正常的,一种是不太正常但是也能用的(
只说正常的吧
RTT的HAL库是并不是调用cubemx生成的,而是这个路径下的:
rt-thread-4.0.2\bsp\stm32\libraries\STM32F1xx_HAL

因此我们需要把这个路径下的文件进行更新
0.1
先对rt-thread-4.0.2\bsp\stm32\libraries\STM32F1xx_HAL\STM32F1xx_HAL_Driver这个路径下的.c和.h文件进行更新
替换的文件在这里面:rt-thread-4.0.2\bsp!RM_STM32F103ZET6RTT hal\stm32f10x\board\CubeMX_Config\Drivers\STM32F1xx_HAL_Driver(就是自己建的bsp中cubemx生成的driver文件夹内)

之前在bsp学习过程中,我是把inc和src文件夹单独拿出来,然后删掉剩余文件夹。这样做的合理性是该文件夹中剩余的文件都不会被使用,rtt调用的库在rt-thread-4.0.2\bsp\stm32\libraries中。而当我们需要更新的时候,自然就需要生成的驱动,所以之后可以保留剩余的文件夹,防止不时之需
0.2
如果只进行上面一步的话,会报undefined symbol的错,原因是CMSIS中的.h文件没有更新
此时需要对下面的路径中.h文件进行更新:
rt-thread-4.0.2\bsp\stm32\libraries\STM32F1xx_HAL\CMSIS\Device\ST\STM32F1xx\Include
替换的文件路径如下:
rt-thread-4.0.2\bsp!RM_STM32F103ZET6RTT hal\stm32f10x\board\CubeMX_Config\Drivers\CMSIS\Device\ST\STM32F1xx\Include
1.修改menuconfig
首先要使用env工具进行修改
相对于原始的来说,有以下地方需要改动
关系统串口

关命令控制台

关驱动,用硬件定时器

关闭需要用到的串口,开启一个其他串口(因为rtt要求至少启用一个串口)

2.移植驱动层
之后在cubemx生成的文件夹中找到main.c,移植里面的函数放到驱动层(一般来说有不同的模块,比如uart和tim,可以分到不同的drv_xxx文件中)

我这里就是模仿学长的代码建立了三个驱动层的.c和.h文件,分别把上面main.c中的不同模块放入不同文件

3.解决Error_Handler问题:
#include "drv_HAL_Main.h"
#include "stm32f1xx_hal.h"
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifndef __DRV_HAL_MAIN_H__
#define __DRV_HAL_MAIN_H__
void Error_Handler(void);
#endif
建立这两个文件,然后把每一个驱动层的.c文件都include一下
4.编译通过
移植完之后:


浙公网安备 33010602011771号