本文的初衷一方面是将我的一些关于STM32开发方面浅显的个人经验分享给初学者、并期望得到大佬的批评指正,另一方面是记录自己的实验过程便于回顾。

我预感应该要写很多,不过鉴于之前的数篇笔迹中,对于SPI/DMA/ADXL3XX系列加表的使用已经详细描述过了,所以这篇博客只记录系统构建的整体流程。

摘要:

通过STM32H743VIT6驱动两片adxl355和1片adxl375,采用SYNC信号同步控制方式实现3个传感器的数据,采用FIFO流模式,采用3组SPI+DMA实现数据的同步采集,采用串口1+DMA进行数据传输,采用串口2+中断 构建指令系统,具体指令及对应的功能如下图。通过 定时器+计数 实现了频率可调的方波信号和周期可调的中断,通过RTC产生的秒中断实现定时采样、实际采样率检测 的功能,此外还可以自定义帧标记、获取温度 等功能。

0 硬件电路介绍

本文基于紫色的板子进行介绍,原理图和细节就不放了,需要用到的时候会指明,总体用到的外设有哪些呢?见下面cubeMX的引脚分配图

1 实现3组SPI+DMA,并进行数据采集

3组SPI+DMA的基本配置都很简单(且相同),前面的文章都详细介绍过,因此直接上图,

由于SPI+DMA的传输是非阻塞的,意思就是,如果我写了“SPI接收”,然后“片选拉高”,程序执行完SPI接收后,不管DMA是否传输完毕,都会继续执行“片选拉高”,这是不科学的,一种常见的解决方案是增加延时,但是这样做就失去了DMA的意义,既然有时间延时,为啥要用DMA?而且这种方式无法实现3组SPI同时接收(会差数个机器周期,忽略不计吧)

我的解决方案是:采用标志位的思想,如下,当spix(指spi1或2或3)在发送或接收的时候,将对应的标志位置位,3组spi在同时工作,这个时候,我不断查询标志位,如果3个标志位都回到 0 ,就可以继续使用SPI了,否则等待。

volatile uint8_t DMA_FLAG=0x7f;
//Reserved | usart | spi1TX | spi2TX | spi3TX | spi1RX | spi2RX | spi3RX

因此,3个传感器的数据同步采集函数如下,具体细节:SAMPLE_START;SAMPLE_ADDRESS;...这些抽象的东西是宏定义,即 拉高3个片选, 分别对3片传感器发送数据读取的地址,...,Delay_us是微秒延时函数,在前面的文章有介绍。

void data_sample(){    SAMPLE_START;Delay_us(5);DMA_FLAG&=0xc7;//spi send busy ,bit 0 is busySAMPLE_ADDRESS;while((DMA_FLAG&0x38)!=0x38){Delay_us(5);}//    wait until address is sentDMA_FLAG&=0xf8;//spi receive busySAMPLE_RECEIVE;while((DMA_FLAG&0x07)!=0x07){Delay_us(5);}//    wait until data is receivedSAMPLE_END;while((DMA_FLAG&0x40)!=0x40){Delay_us(5);} //    wait until uasrt1 is readyDMA_FLAG&=0xbf;                                    //usart1 set busyHAL_UART_Transmit_DMA(&huart1,SPI_RX_Buffer, 29);Delay_us(80);
}

为了怕难以理解,宏定义如下:

#define SAMPLE_START    HAL_GPIO_WritePin(XL355_CS_GPIO_Port, XL355_CS_Pin, GPIO_PIN_RESET);\
HAL_GPIO_WritePin(XL355_2_CS_GPIO_Port, XL355_2_CS_Pin, GPIO_PIN_RESET);\
HAL_GPIO_WritePin(XL357_CS_GPIO_Port, XL357_CS_Pin, GPIO_PIN_RESET)
#define SAMPLE_END    HAL_GPIO_WritePin(XL355_CS_GPIO_Port, XL355_CS_Pin, GPIO_PIN_SET);\
HAL_GPIO_WritePin(XL355_2_CS_GPIO_Port, XL355_2_CS_Pin, GPIO_PIN_SET);\
HAL_GPIO_WritePin(XL357_CS_GPIO_Port, XL357_CS_Pin, GPIO_PIN_SET)
#define SAMPLE_ADDRESS    HAL_SPI_Transmit_DMA(&hspi1, &SPI_READ_DATA_Address, 1);\
HAL_SPI_Transmit_DMA(&hspi2, &SPI_READ_DATA_Address, 1);\
HAL_SPI_Transmit_DMA(&hspi3, &SPI_READ_DATA_Address, 1)
#define SAMPLE_RECEIVE    HAL_SPI_Receive_DMA(&hspi1, &SPI_RX_Buffer[0], 9);\
HAL_SPI_Receive_DMA(&hspi2, &SPI_RX_Buffer[9], 9);\
HAL_SPI_Receive_DMA(&hspi3, &SPI_RX_Buffer[18], 9)

下面是DMA回调函数的写法,一共6个,只给出两个,其他的同理。这些回调函数在stm32h7xx_it.c里,只有 DMA_FLAG|=0x04;是我写进去的,写在处理中断前/后 都可以,目的就是给标志位置位。

void DMA1_Stream0_IRQHandler(void)
{/* USER CODE BEGIN DMA1_Stream0_IRQn 0 *///for SPI1-RXDMA_FLAG|=0x04;/* USER CODE END DMA1_Stream0_IRQn 0 */HAL_DMA_IRQHandler(&hdma_spi1_rx);/* USER CODE BEGIN DMA1_Stream0_IRQn 1 *//* USER CODE END DMA1_Stream0_IRQn 1 */
}/*** @brief This function handles DMA1 stream1 global interrupt.*/
void DMA1_Stream1_IRQHandler(void)
{/* USER CODE BEGIN DMA1_Stream1_IRQn 0 *///for SPI1-TXDMA_FLAG|=0x20;/* USER CODE END DMA1_Stream1_IRQn 0 */HAL_DMA_IRQHandler(&hdma_spi1_tx);/* USER CODE BEGIN DMA1_Stream1_IRQn 1 *//* USER CODE END DMA1_Stream1_IRQn 1 */
}

2 实现usart+DMA/中断,进行数据传输和菜单设置

开了两路usart,其中usart1用DMA方式来传输数据 1500000波特率(用镀银的杜邦线,极度奢侈),usart2用中断方式来接收指令,并用常规方式来打印信息,配置极为简单(只改波特率,其余默认)不上图了,需要注意的是,usart2用中断方式来接收指令因此中断的优先级可以高一点,比SPI+DMA(优先级3)略高,暂时设定为2,然后更高的优先级1 留给定时器和RTC的秒中断。

usart1用DMA方式来传输数据在上一部分的sample函数里,一行代码。

usart2的中断函数是这样的,几点细节:switch语句的查表方式比多个if连用的效率更高,串口接收一次之后,要重新执行接收函数才能再次接收。下面的代码应该是浅显易懂的

void USART2_IRQHandler(void)
{/* USER CODE BEGIN USART2_IRQn 0 *//* USER CODE END USART2_IRQn 0 */HAL_UART_IRQHandler(&huart2);/* USER CODE BEGIN USART2_IRQn 1 */switch(UART_RX){case 0x00:{SAMPLE_FLAG^=0x80;if(SAMPLE_FLAG&0x80)puts("Sampling...");elseputs("Pause...");break;}    case 0x01:SR_Counter_RES=800;puts("Set SR = 5Hz");break;case 0x02:SR_Counter_RES=80;puts("Set SR = 50Hz");break;case 0x03:SR_Counter_RES=40;puts("Set SR = 100Hz");break;case 0x04:SR_Counter_RES=20;puts("Set SR = 200Hz");break;case 0x05:SR_Counter_RES=8;puts("Set SR = 500Hz");break;case 0x06:SR_Counter_RES=5;puts("Set SR = 800Hz");break;case 0x07:SR_Counter_RES=4;puts("Set SR = 1000Hz");break;case 0x08:SR_Counter_RES=2;puts("Set SR = 2000Hz");break;    case 0x0a:{SAMPLE_FLAG^=0x40;if(SAMPLE_FLAG&0x40)puts("Time_Count mark : ON");else{puts("Time_Count mark : OFF");SAMPLES=0;}break;}case 0x10:{SAMPLE_FLAG&=0x7e;Timing=0;puts("Timing sample is OFF ");            break;}case 0x11:{SAMPLE_FLAG|=0x01;SAMPLE_FLAG&=0x7f;Timing=30;puts("Timing sample : 30s, enter 0x00 to start.");break;}case 0x12:{SAMPLE_FLAG|=0x01;SAMPLE_FLAG&=0x7f;Timing=60;puts("Timing sample : 60s, enter 0x00 to start.");break;}case 0x13:{SAMPLE_FLAG|=0x01;SAMPLE_FLAG&=0x7f;Timing=180;puts("Timing sample : 180s, enter 0x00 to start.");break;}case 0x14:{SAMPLE_FLAG|=0x01;SAMPLE_FLAG&=0x7f;Timing=300;puts("Timing sample : 300s, enter 0x00 to start.");break;}case 0x20:{SAMPLE_START;HAL_SPI_Transmit(&hspi1, &SPI_Temp_Address, 1,0xffff);HAL_SPI_Transmit(&hspi2, &SPI_Temp_Address, 1,0xffff);HAL_SPI_Transmit(&hspi3, &SPI_Temp_Address, 1,0xffff);HAL_SPI_Receive(&hspi1, Temperture, 2,0xffff);HAL_SPI_Receive(&hspi2, &Temperture[2], 2,0xffff);HAL_SPI_Receive(&hspi3, &Temperture[4], 2,0xffff);SAMPLE_END;puts("Temperture in HEX(2Bytes*3):");printf("%x %x %x %x %x %x\n\n",Temperture[0],Temperture[1],Temperture[2],Temperture[3],Temperture[4],Temperture[5]);break;}case 0x30:{puts("Enter User-Mark(1 Byte in HEX, must be confined in 0xE0~0xFF): ");HAL_UART_Receive(&huart2,&SPI_RX_Buffer[28],1,0xffff);SPI_RX_Buffer[28]=SPI_RX_Buffer[28]<0xE0? 0x55:SPI_RX_Buffer[28];printf("User-Mark is %x\n",SPI_RX_Buffer[28]);break;}default:puts("Don't have this Commond!");break;    }HAL_UART_Receive_IT(&huart2,&UART_RX,1);/* USER CODE END USART2_IRQn 1 */
}

3 实现定时器产生固定频率的SYNC信号,并进一步实现可调的采样率

关于定时器,我也是在博客上现学的,首先时钟树如下:

可见 右边写着:to APB2 Timer clocks 是240MHz,我将用到的Timer3就是在这个时钟域。Tim3的配置如下,开启通道1输出 作为SYNC信号,Tooggle on match 就是每次计数器溢出时翻转输出电平,根据图中的参数来计算,Tim3的溢出频率=240Mhz/(预分频系数+1)/(计数周期+1)=240M/(59+1)/(999+1)=4Khz。因此CH1输出的SYNC信号为2Khz的方波信号。Tim3中断函数的调用频率为4kHz。

开启Tim3的中断,

在用MX_TIM3_Init函数初始化完成后,用下面两行代码分别开启TIM3基本定时器和TIM3的CH1,如果没有第一行,CH1依然有输出,但是中断函数不会被调用。

    HAL_TIM_Base_Start_IT(&htim3);HAL_TIM_OC_Start(&htim3,TIM_CHANNEL_1);

Tim3的中断函数这样写(只有8-13行,其余是自动生成的),每次中断SR_Counter减一,当重装时,置为SAMPLE_FLAG中的采样标志位,程序查询到该标志位时进行采样。这样,就可以通过改变SR_Counter_RES的值,动态修改采样率了,而不需要重新配置TIM3,因为重新配置TIM3也势必会影响SYNC信号,再开一个定时器也没必要。

程序以200khz的频率查询,因此查询和标志位产生之间的误差可以忽略,如果需要进一步增加采样率精度,是否可以把采样函数写在该中断函数里?如果我中断间隔为250us,中断里写的程序需要运行50us,那我的中断间隔会被改变吗?我还没验证,不过采用我目前的实现方式,TIM3中断中的代码量极小,因此不用考虑这样的情况。

注意:我在两次采样间无延时的情况下,全速运行10秒,大约得到了7万个数据,这说明我完成一次3个传感器的采样和传输所需要的时间小于0.2ms,因此我通过上述的方式实现最大采样率为2000Hz是完全OK的。

void TIM3_IRQHandler(void)
{/* USER CODE BEGIN TIM3_IRQn 0 *//* USER CODE END TIM3_IRQn 0 */HAL_TIM_IRQHandler(&htim3);/* USER CODE BEGIN TIM3_IRQn 1 */if(SR_Counter)SR_Counter--;else{SR_Counter=SR_Counter_RES-1;SAMPLE_FLAG|=0x08;}/* USER CODE END TIM3_IRQn 1 */
}

主函数的主循环中,采样的部分这样写:

        if(SAMPLE_FLAG&0x80){while((SAMPLE_FLAG&0x08)!=0x08){Delay_us(5);}SAMPLE_FLAG&=0xf7;data_sample();SAMPLES=SAMPLE_FLAG&0x40?SAMPLES+1:SAMPLES;if ((SAMPLE_FLAG&0x01)&& !Timing){SAMPLE_FLAG&=0x7e;puts("Timing sample is done!");    }

其中的SAMPLE_FLAG是采样标志位,具体分配如下:(除去保留位,从左到右分别为:采样启停、时间-采样数的水印开关、采样标志位、定时采样标志位)

volatile uint8_t SAMPLE_FLAG=0;
//RUN | Time_Count_MARK | Reserved | Reserved | Sample | Reserved | Reserved | Timing

4 实现RTC产生秒中断,并进一步实现定时采样和实际采样率检测

RTC这地方我调了很久,因为经常RTC调着调着就不工作了,原因是: 我的板子没有复位按键,是上电自动复位,程序下载之后,如果板子不重新上电,32.768k的晶振就很有可能无法起振。

RTC产生秒中断,配置如下,internal wakeup意思是内部中断(不输出),wakeup clock 1hz很方便,自动产生秒信号。

中断函数如下,RTC_Second是我用来计秒的(从上电到现在的秒数),RTC应该有专门的秒寄存器,但是我懒得找了。LED0_Toggle是宏定义,翻转LED灯的;如果设置了定时采样,采样会暂停,当采样开始的时候,每过一秒Timing(定时的秒数)减一;如果开启了时间-采样数水印,会打印当前的秒数和当前累计采集到的样本SAMPLES,这样就可以看到实际的采样率,例如下下面的图,采样率设置为800hz,实际大约是779.5hz,当然这个误差是可以通过优化代码来进一步缩小的。

void RTC_WKUP_IRQHandler(void)
{/* USER CODE BEGIN RTC_WKUP_IRQn 0 *//* USER CODE END RTC_WKUP_IRQn 0 */HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);/* USER CODE BEGIN RTC_WKUP_IRQn 1 */LED0_Toggle;RTC_Second++;if((SAMPLE_FLAG&0x81)==0x81){Timing--;printf("Timing rest seconds : %4d\n",Timing);}if(SAMPLE_FLAG&0x40)printf("SAMPLES : %10d, Power-seconds %5d\n",SAMPLES,RTC_Second);/* USER CODE END RTC_WKUP_IRQn 1 */
}

ok,到此为止了,下面展示一下开机界面

代码多逻辑复杂,贴不尽。如果需要进一步讨论该工程的完整代码请私信或评论留言。欢迎讨论!

Note10:基于STM32H7+HAL+CubeMX+DMA+SPI+串口中断+定时器+RTC的多传感器数据采集系统(2*ADXL355和ADXL375通过Sync时序同步)相关推荐

  1. 基于STM32的光敏传感器数据采集系统-嵌入式系统与设计课程设计

    目录 1 项目概述 1.1 项目介绍 1.2 项目开发环境 1.3 小组人员及分工 2 需求分析 2.1 系统需求分析 2.2 可行性分析 2.3 项目实施安排 3 系统硬件设计 3.1 系统整体硬件 ...

  2. 基于物联网网关的水电表传感器数据采集系统

    水表.电表以及各种不同类型的传感器在工业生产中十分常见,成为物联网远程监控的重点.需要将用水量.用电量.温湿度.压力等数据采集上来,集中监控生产环境信息并产生预警通知,有助于保障安全稳定的生产环境,能 ...

  3. 【嵌入式基础】串口中断通信VS串口DMA通信

    目录 目录 前言 一.串口通信 1. 通信方式 2.通信速率 3.串口通信的三种工作方式 二.串口中断通信 1.串口中断特点 2.CubeMX配置初始化串口中断相关外设 3.串口中断程序分析 4.实验 ...

  4. 基于STM32F103RCT6实现串口中断发送,使用环形队列

    文章目录 一.开发环境 二.串口中断发送原理 三.实验现象 四.完整源码 五.后记 一.开发环境 /************************************************** ...

  5. tms320f28027 中断优先级_TMS320F28027 自带串口中断收发数据例子

    [实例简介] TMS320F28027自带有串口,利用串口中断与上位机(电脑)进行数据交换,软件设置成 上位机所发数据要以'*'结束. 仅供DSP板的学习所用,软件用的是CCS4.1,编译如果不能通过 ...

  6. STM32 HAL CubeMX 串口IDLE接收空闲中断+DMA

    关于DMA原理部分讲解,及CubeMx配置部分,请参考该文章 [STM32]HAL库 STM32CubeMX教程十一-DMA (串口DMA发送接收) 本篇文章我们仅针对例程进行详解剖析 历程详解 详解 ...

  7. stm32的rxne和idle中断_STM32 HAL CubeMX 串口IDLE接收空闲中断+DMA

    历程详解 详解包括: 中断原理讲解 例程流程详解 库函数分析详解 对应寄存器介绍 对应函数介绍 对应注释详解 本篇文章提供两种方法: 一种是 :IDLE 接收空闲中断+DMA 一种是: IDLE 接收 ...

  8. STM32CubeMX | 基于STM32使用HAL库硬件SPI驱动WK2124一拖四SPI转四路串口芯片

    STM32CubeMX | 基于STM32使用HAL库硬件SPI驱动WK2124一拖四SPI转四路串口芯片 STM32基础工程生成 首先使用STM32CUBEMX生成STM32的基础工程,配置时钟到7 ...

  9. STM32从零到一,从标准库移植到HAL库,UART串口1以DMA模式收发不定长数据代码详解+常见问题 一文解析

    前言 本文的参考资料 感谢提供标准库版本的CSDN同学:这两篇文章至少是我看过的最详细的标准库配置DMA版本.而且代码实测稳定能用. STM32 | DMA配置和使用如此简单(超详细)_...| .. ...

最新文章

  1. wireshark合并多个文件_TEQC合并多个时段GPS RINEX文件
  2. java的object如何转为具体的类_佛山个体户如何转为公司?
  3. 不再内卷!视觉字幕化新任务合集
  4. bootstrap模态框垂直居中显示
  5. Java企业面试算法新得体会之链表问题20问
  6. W25Q128 闪存芯片SPI详解
  7. Airflow 中文文档:插件
  8. 技术迭代快速。PyTorch 真的优于Tensorflow吗?
  9. VB Environ系统环境变量函数大全
  10. 能煮熟鸡蛋的慢 SQL!阿里巴巴数据中心的“煮蛋史”!
  11. 房屋装修设计更显档次需要从几个方面入手
  12. 自由宣言-- I Have a Dream 马丁 路德 金
  13. win10桌面图标有个白板,怎么去掉
  14. 谷歌54亿收购Mandiant:提高谷歌云竞争性
  15. 安装jdk并配置环境变量
  16. Android 预装APK
  17. 【阿里面试官的抨击】大厂面试竟该这么答?| 面试竟有这些坑?| 面试该如何准备?| 如何学习知识点?
  18. php创建wordpress主题,Wordpress子主题创建与使用方法
  19. STM8S REX_C900温控器硬件
  20. 疫情反复无常,2022年高校毕业生规模高达1076万,找工作方向如何确定?

热门文章

  1. 硬盘主分区、扩展分区和逻辑分区
  2. 手中无剑,心中有剑,无剑胜有剑
  3. MySQL基础教程系列-约束(三)唯一约束
  4. 程序员加薪升职之成长金字塔
  5. 解决文件上传重名的方案
  6. android系统底层的updater 命令,Android ROM 刷机脚本 updater-script 的基本流程和初级语句...
  7. git本地仓库与GitHub的同步
  8. Solidity Gas消耗
  9. 【原创】如何判断Win10计算机的硬盘是HHD还是SSD
  10. 超详细版本vue+capacitor(自定义capacitor插件)编写移动端应用