一、概述单片机能够实现的功能非常广泛主要包括信号采集与感知通过ADC、比较器等读取模拟信号如温度、电压或通过GPIO读取数字信号如开关状态。逻辑运算与决策运行程序代码进行逻辑判断、数学运算、数据处理和算法执行。设备控制与驱动通过GPIO、PWM、DAC等输出数字/模拟信号直接控制LED、电机、继电器、开关电源等执行部件。通信与组网通过UART、I2C、SPI、CAN、USB、以太网及各类无线模块实现设备间或与上位机的数据通信。人机交互驱动显示屏、管理键盘/触摸屏、控制指示灯和蜂鸣器等实现用户输入与信息输出。此综合小项目实现一个小demo,用到了单片机中的核心外设MPU,FSMC,DMA,ADC,DAC,串口,TIM。把这些知识点吃透我认为单片机的使用就基本上没什么问题了。剩下的就是往应用走需要上RTOS了。RTOS我开了RTTHREAD的专栏后续有时间就会不断更新。此小项目主要这到这些知识点DAC DMA TIM---正弦波ADCDMATIM (双缓存模式)采集LCDFSMC 显示当前电压值SDRAM(FSMC)做一个缓存区存储adc数据USB虚拟串口接口将数据上传因为硬件没有直连的串口了懒的用串口助手了QT写一个串口工具显示波形。原理框图二、实现过程1.工程搭建我为了偷懒也为了调试舒畅还是用正点原子的H7开发板的rtthread原始工程做例子。初学者如果看的头晕慎读。如下图所示原始工程这个样子。添加源代码修改配置文件修改hal库下的配置文件编译scons --targetmdk5然后在keil上编译。2.源码设计1.DAC部分DAC代码TIMDACDMA,1KHZ的正弦波。#include dac.h DMA_HandleTypeDef g_dma_dac_handle; /* 定义用于DAC数据的DMA句柄 */ DAC_HandleTypeDef g_dac_dma_handle; /* 定义DACDMA输出句柄 */ uint16_t g_dac_sin_buf[4096]; /* 发送数据缓冲区 */ /** * brief 生成正弦波序列 * note 前提保证: maxval samples/2 * param maxval : 峰值(0 maxval 2048) * param samples: 采样点个数 * retval 无 */ void dac_creat_sin_buf(uint16_t maxval, uint16_t samples) { uint8_t i; float inc (2 * 3.1415926) / samples; /* 计算增量一个周期DAC_SIN_BUF个点*/ float outdata 0; for (i 0; i samples; i) { outdata maxval * (1 sin(inc * i)); /* 计算DAC_SIN_BUF个点周期内每个点的值放大约maxval倍并偏移到正值区域 */ if (outdata 4095) { outdata 4095; /* 溢出限定 */ } g_dac_sin_buf[i] outdata; } } /** * brief 通过USMART设置正弦波输出参数,方便修改频率. * param arr : TIM7的自动重装载值 * param psc : TIM7的分频系数 * retval 无 */ void dac_dma_sin_set(uint16_t arr, uint16_t psc) { dac_dma_wave_enable(100, arr, psc); } /** * brief DAC DMA输出正弦波初始化函数 * note DAC的输入时钟来自APB1, 时钟频率100Mhz10ns * DAC在输出buffer关闭的时候建立时间tSETTLING 2us (H743数据手册) * 也就是说DAC输出的最大速度为:500Khz, 这里10个点为一个周期, 即最大50Khz的正弦波 * param 无 * retval 无 */ void dac_dma_wave_init(void) { DAC_ChannelConfTypeDef dac_ch_conf {0}; GPIO_InitTypeDef gpio_init_struct; __HAL_RCC_GPIOA_CLK_ENABLE(); /* DAC通道引脚口时钟使能 */ __HAL_RCC_DAC12_CLK_ENABLE(); /* DAC外设时钟使能 */ __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA时钟使能 */ gpio_init_struct.Pin GPIO_PIN_4; /* PA4 */ gpio_init_struct.Mode GPIO_MODE_ANALOG; /* 模拟 */ HAL_GPIO_Init(GPIOA, gpio_init_struct); /* 初始化DAC引脚 */ g_dma_dac_handle.Instance DMA2_Stream6; /* 使用的DMA2 Stream6 */ g_dma_dac_handle.Init.Request DMA_REQUEST_DAC1_CH1; /* DAC触发DMA传输 */ g_dma_dac_handle.Init.Direction DMA_MEMORY_TO_PERIPH; /* 存储器到外设模式 */ g_dma_dac_handle.Init.PeriphInc DMA_PINC_DISABLE; /* 外设地址禁止自增 */ g_dma_dac_handle.Init.MemInc DMA_MINC_ENABLE; /* 存储器地址自增 */ g_dma_dac_handle.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; /* 外设数据对齐为16位 */ g_dma_dac_handle.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; /* 存储器数据对齐为16位 */ g_dma_dac_handle.Init.Mode DMA_CIRCULAR; /* 循环模式 */ g_dma_dac_handle.Init.Priority DMA_PRIORITY_MEDIUM; /* 中等优先级 */ g_dma_dac_handle.Init.FIFOMode DMA_FIFOMODE_DISABLE; /* 不使用FIFO */ HAL_DMA_Init(g_dma_dac_handle); /* 初始化DMA */ __HAL_LINKDMA(g_dac_dma_handle, DMA_Handle1, g_dma_dac_handle); /* DMA句柄链接到DAC句柄上 */ g_dac_dma_handle.Instance DAC1; /* 选择第一个DAC */ HAL_DAC_Init(g_dac_dma_handle); /* DAC初始化 */ dac_ch_conf.DAC_SampleAndHold DAC_SAMPLEANDHOLD_DISABLE; /* 关闭采样保持模式这个模式可以降低功耗 */ dac_ch_conf.DAC_Trigger DAC_TRIGGER_T7_TRGO; /* 用定时器7触发 */ dac_ch_conf.DAC_OutputBuffer DAC_OUTPUTBUFFER_ENABLE; /* 使能输出缓存 */ dac_ch_conf.DAC_ConnectOnChipPeripheral DAC_CHIPCONNECT_DISABLE; /* 不将DAC连接到片内外设 */ dac_ch_conf.DAC_UserTrimming DAC_TRIMMING_FACTORY; /* 使用出厂校准 */ HAL_DAC_ConfigChannel(g_dac_dma_handle, dac_ch_conf, DAC_CHANNEL_1); /* DAC通道输出设置 */ } /** * brief DAC DMA使能正弦波输出 * note TIM7的输入时钟频率(f)来自APB1, f 100M * 2 200Mhz. * DAC触发频率 ftrgo f / ((psc 1) * (arr 1)) * 正弦波频率 ftrgo / ndtr; * * param ndtr : DMA通道单次传输数据个数 * param arr : TIM7的自动重装载值 * param psc : TIM7的分频系数 * retval 无 */ void dac_dma_wave_enable(uint16_t ndtr, uint16_t arr, uint16_t psc) { TIM_HandleTypeDef tim7_handle {0}; TIM_MasterConfigTypeDef master_config {0}; __HAL_RCC_TIM7_CLK_ENABLE(); /* TIM7时钟使能 */ tim7_handle.Instance TIM7; /* 选择定时器7 */ tim7_handle.Init.Prescaler psc; /* 分频系数 */ tim7_handle.Init.CounterMode TIM_COUNTERMODE_UP; /* 递增计数器 */ tim7_handle.Init.Period arr; /* 重装载值 */ tim7_handle.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; /* 自动重装 */ HAL_TIM_Base_Init(tim7_handle); /* 初始化定时器7 */ master_config.MasterOutputTrigger TIM_TRGO_UPDATE; /* 定时器更新事件产生触发 */ master_config.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(tim7_handle, master_config); /* 设置TIM7 TRGO */ HAL_TIM_Base_Start(tim7_handle); /* 使能定时器7 */ HAL_DAC_Stop_DMA(g_dac_dma_handle, DAC_CHANNEL_1); /* 先停止之前的传输 */ HAL_DAC_Start_DMA(g_dac_dma_handle, DAC_CHANNEL_1, (uint32_t *)g_dac_sin_buf, ndtr, DAC_ALIGN_12B_R); } int device_dac() { dac_dma_wave_init(); /* 初始化DAC通道1 DMA波形输出 */ dac_creat_sin_buf(2048, 100); dac_dma_wave_enable(100, 100 - 1, 20 - 1); /* 100KHz触发频率, 100个点,可以得到约1KHz的正弦波. */ } INIT_DEVICE_EXPORT(device_dac);2.ADC单次触发模式adc部分用了两种方法一种是单次触发单次触发这里留个问题为什么重启ADC的DMA传输要先停掉ADC?单次触发模式代码#include adc.h #include lcd.h //信号量 static struct rt_semaphore adc_dma_sem; ADC_HandleTypeDef g_adc_dma_handle; /* 带DMA功能的ADC句柄 */ DMA_HandleTypeDef g_dma_adc_handle; /* 带ADC功能的DMA句柄 */ /** * brief ADC DMA读取 初始化函数 * param par : 外设地址 * param mar : 存储器地址 * retval 无 */ void adc_dma_init(uint32_t par, uint32_t mar) { GPIO_InitTypeDef gpio_init_struct; ADC_ChannelConfTypeDef adc_ch_conf {0}; ADC_ADCX_CHY_GPIO_CLK_ENABLE(); /* 开启ADC通道IO口时钟 */ ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADC1/2时钟 */ if ((uint32_t)ADC_ADCX_DMASx (uint32_t)DMA2) /* 得到当前stream是来自DMA2还是DMA1 */ { __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */ } else { __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */ } __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP); /* ADC外设时钟选择PLL */ /* 初始化ADC缓存通道对应的IO引脚 */ gpio_init_struct.Pin ADC_ADCX_CHY_GPIO_PIN; /* ADC通道IO引脚 */ gpio_init_struct.Mode GPIO_MODE_ANALOG; /* 模拟 */ HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, gpio_init_struct); /* 初始化DMA */ g_dma_adc_handle.Instance ADC_ADCX_DMASx; /* 选择DMA数据流 */ g_dma_adc_handle.Init.Request ADC_ADCX_DMASx_REQ; /* 请求选择DMA_REQUEST_ADC1 */ g_dma_adc_handle.Init.Direction DMA_PERIPH_TO_MEMORY; /* DIR 1,外设到存储器模式 */ g_dma_adc_handle.Init.PeriphInc DMA_PINC_DISABLE; /* 外设地址不变模式 */ g_dma_adc_handle.Init.MemInc DMA_MINC_ENABLE; /* 存储器地址自增模式 */ g_dma_adc_handle.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; /* 外设数据对齐为16位 */ g_dma_adc_handle.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; /* 存储器数据对齐为16位 */ g_dma_adc_handle.Init.Mode DMA_NORMAL; /* 正常模式 */ g_dma_adc_handle.Init.Priority DMA_PRIORITY_MEDIUM; /* 中等优先级 */ g_dma_adc_handle.Init.FIFOMode DMA_FIFOMODE_DISABLE; /* 禁止FIFO*/ HAL_DMA_Init(g_dma_adc_handle); /* 初始化DMA */ __HAL_LINKDMA(g_adc_dma_handle, DMA_Handle, g_dma_adc_handle); /* 将DMA句柄链接到ADC句柄上 */ /* 初始化ADC */ g_adc_dma_handle.Instance ADC_ADCX; g_adc_dma_handle.Init.ClockPrescaler ADC_CLOCK_ASYNC_DIV2; /* 异步时钟2分频,即adc_ker_ckper_ck/232Mhz */ g_adc_dma_handle.Init.Resolution ADC_RESOLUTION_16B; /* 16位模式 */ g_adc_dma_handle.Init.ScanConvMode DISABLE; /* 非扫描模式 */ g_adc_dma_handle.Init.EOCSelection ADC_EOC_SINGLE_CONV; /* 关闭EOC标志 */ g_adc_dma_handle.Init.LowPowerAutoWait DISABLE; /* 自动低功耗关闭 */ g_adc_dma_handle.Init.ContinuousConvMode ENABLE; /* 开启连续转换 */ g_adc_dma_handle.Init.NbrOfConversion 1; /* 1个转换序列 */ g_adc_dma_handle.Init.DiscontinuousConvMode DISABLE; /* 禁止不连续采样模式 */ g_adc_dma_handle.Init.NbrOfDiscConversion 0; /* 不连续采样通道数为0 */ g_adc_dma_handle.Init.ExternalTrigConv ADC_SOFTWARE_START; /* 软件触发 */ g_adc_dma_handle.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发 */ g_adc_dma_handle.Init.Overrun ADC_OVR_DATA_OVERWRITTEN; /* 过载的数据用最新的数据覆盖旧的转换数据 */ g_adc_dma_handle.Init.OversamplingMode DISABLE; /* 过采样关闭 */ g_adc_dma_handle.Init.LeftBitShift ADC_LEFTBITSHIFT_NONE; /* 选择ADC转换结果对齐方式 */ g_adc_dma_handle.Init.ConversionDataManagement ADC_CONVERSIONDATA_DMA_ONESHOT; /* DMA单次传输ADC数据 */ HAL_ADC_Init(g_adc_dma_handle); /* 初始化 */ HAL_ADCEx_Calibration_Start(g_adc_dma_handle, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); /* ADC校准 */ /* 设置ADC通道 */ adc_ch_conf.Channel ADC_ADCX_CHY; /* 选择使用的ADC通道 */ adc_ch_conf.Rank ADC_REGULAR_RANK_1; /* 采样序列排第一个 */ adc_ch_conf.SamplingTime ADC_SAMPLETIME_810CYCLES_5; /* 采样时间为810.5个时钟周期 */ adc_ch_conf.SingleDiff ADC_SINGLE_ENDED ; /* 单端输入 */ adc_ch_conf.OffsetNumber ADC_OFFSET_NONE; /* 无偏移 */ adc_ch_conf.Offset 0; /* 无偏移补偿值这里用不到 */ adc_ch_conf.OffsetRightShift DISABLE; /* 禁止右移 */ adc_ch_conf.OffsetSignedSaturation DISABLE; /* 禁止有符号饱和 */ HAL_ADC_ConfigChannel(g_adc_dma_handle, adc_ch_conf); /* 设置ADC通道 */ /* 设置DMA数据流中断优先级 */ HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3); /* 设置DMA中断优先级为3子优先级为3 */ HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn); /* 使能DMA中断 */ HAL_DMA_Start_IT(g_dma_adc_handle, par, mar, 0); /* 启动DMA并开启中断 */ HAL_ADC_Start_DMA(g_adc_dma_handle, mar, 0); /* 开始DMA数据传输 */ } /** * brief 使能一次ADC DMA传输 * param ndtr: DMA传输的次数 * retval 无 */ void adc_dma_enable(uint16_t ndtr) { ADC_ADCX-CR ~(1 0); /* 关闭ADC */ ADC_ADCX_DMASx-CR ~(1 0); /* 关闭DMA传输 */ while (ADC_ADCX_DMASx-CR 0X1); /* 确保DMA可以被设置 */ ADC_ADCX_DMASx-NDTR ndtr; /* 要传输的数据个数 */ ADC_ADCX_DMASx-CR | 1 0; /* 开启DMA传输 */ ADC_ADCX-CR | 1 0; /* 启动ADC */ ADC_ADCX-CR | 1 2; /* 启动常规转换通道 */ } /** * brief ADC DMA缓存中断服务函数 * param 无 * retval 无 */ void ADC_ADCX_DMASx_IRQHandler(void) { if (ADC_ADCX_DMASx_IS_TC()) /* 判断DMA数据传输完成 */ { rt_sem_release(adc_dma_sem); ADC_ADCX_DMASx_CLR_TC(); /* 清除DMA1 数据流4 传输完成标志 */ } } #define ADC_DMA_BUF_SIZE 50 /* ADC DMA缓存 BUF大小 应该是ADC通道数的整数倍 */ uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */ //adc初始化 int device_adc() { adc_dma_init((uint32_t)ADC1-DR, (uint32_t)g_adc_dma_buf); /* 初始化ADC */ return 0; } INIT_DEVICE_EXPORT(device_adc); //adc任务 static void test_thread_entry(void *parameter) { uint16_t i; uint16_t adcx; uint32_t sum; float temp; adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA缓存 */ lcd_show_string(30, 50, 200, 16, 16, STM32, RED); lcd_show_string(30, 70, 200, 16, 16, ADC DMA TEST, RED); lcd_show_string(30, 90, 200, 16, 16, ATOMALIENTEK, RED); lcd_show_string(30, 130, 200, 16, 16, ADC1_CH19_VAL:, BLUE); lcd_show_string(30, 150, 200, 16, 16, ADC1_CH19_VOL:0.000V, BLUE); /* 在整数后面显示小数点 */ while (1) { /* 阻塞等待接收信号量等到信号量后再次读取数据 */ rt_sem_take(adc_dma_sem, RT_WAITING_FOREVER); /* 无效化 D Cache */ SCB_InvalidateDCache(); /* 计算DMA 缓存得到的ADC数据的平均值 */ sum 0; for (i 0; i ADC_DMA_BUF_SIZE; i) /* 累加 */ { sum g_adc_dma_buf[i]; } adcx sum / ADC_DMA_BUF_SIZE; /* 取平均值 */ /* 显示结果 */ lcd_show_xnum(142, 130, adcx, 5, 16, 0, BLUE); /* 显示ADC采样得到的原始值 */ temp (float)adcx * (3.3 / 65536); /* 获取根据公式计算的ADC转换的实际电压值比如0.1111 */ adcx temp; /* 赋值整数部分给adcx变量因为adcx为u16类型 */ lcd_show_xnum(142, 150, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分比如3.1111的显示这里只显示3 */ temp - adcx; /* 把已经显示的整数部分去掉得到小数部分比如0.1111-30.1111 */ temp * 1000; /* 小数部分乘以1000例如0.1111就转换为111.1相当于保留三位小数。 */ lcd_show_xnum(158, 150, temp, 3, 16, 0x80, BLUE); /* 显示小数部分前面已经转换为整数显示这里显示的就是111. */ adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA缓存 */ } } //任务 static int dac_adc_sample(int argc, char *argv[]) { rt_err_t ret RT_EOK; /* 初始化信号量 */ rt_sem_init(adc_dma_sem, adc_dma_sem, 0, RT_IPC_FLAG_FIFO); /* 创建 serial 线程 */ rt_thread_t thread rt_thread_create(test, test_thread_entry, RT_NULL, 2048, 25, 10); /* 创建成功则启动线程 */ if (thread ! RT_NULL) { rt_thread_startup(thread); } else { ret RT_ERROR; } return ret; } /* 导出到 msh 命令列表中 */ MSH_CMD_EXPORT(dac_adc_sample, dac_adc device sample);代码原理ADC 每转换完 1 个数据自动通知 DMA 搬走到内存填满一整个数组后 DMA 触发中断通知主任务采集完成1. 关键配置对应代码外设地址 PAR ADC1-DRADC 转换完的数字值永远存在 DR 寄存器16 位半字DMA 每次从这个地址读数据。内存地址 MAR g_adc_dma_buf [0]定义的uint16_t g_adc_dma_buf[50]数组首地址DMA 把 ADC 数据依次存到这里。DMA 参数设置DMA_PERIPH_TO_MEMORY外设 → 内存DMA_PINC_DISABLE外设地址不增加永远读 DRDMA_MINC_ENABLE内存地址自动 2下一个数组元素NDTR 50总共要传输 50 个数据2. 完整自动流程硬件自动跑不用 CPU 干预调用adc_dma_enable(50)开启 ADCDMA打开 ADC、DMA设置要传输 50 个样本ADC 开启连续转换模式转换完立刻自动开始下一次转换第一次 ADC 转换完成 ADC 硬件自动产生 DMA 请求硬件连线无需软件触发DMA 收到请求从ADC-DR读出 1 个 16 位采样值存入g_adc_dma_buf[0]内存地址自动偏移 2 字节指向下一个元素g_adc_dma_buf[1]NDTR 计数器自动减 1现在变成 49ADC 马上开始第二次转换重复步骤 2~3依次填充buf[0] → buf[1] → buf[2] → ... → buf[49]当 50 个数据全部搬运完成NDTR 减到 0 DMA 硬件触发传输完成中断 TC进入 DMA 中断服务函数ADC_ADCX_DMASx_IRQHandler 把全局标志g_adc_dma_sta 1告诉主循环一整组 50 个数据全部采完了3. 主循环if(g_adc_dma_sta 1) { // 此时 g_adc_dma_buf[0]~g_adc_dma_buf[49] 全部存好了ADC采样值 // CPU只需要一次性读取数组做平均、计算电压、屏幕显示 // 处理完再重新启动下一轮DMA采集50个点 g_adc_dma_sta 0; adc_dma_enable(50); }整段采集 50 个数据的过程CPU 全程不参与数据搬运只在数组填满后统一处理解放 CPU。这里的缺点就是需要启停 ADC3.adc连续触发模式一种是连续触发ADCDMATIM这个的特点TIM触发ADC使用TIM6定时器产生精确的触发信号可编程采样频率控制减少CPU干预DMA循环模式使用DMA_CIRCULAR模式自动循环采集无需每次重启支持双缓冲通过半传输中断暂时没加入半中断HAL库兼容使用标准HAL库中断处理添加DMA传输完成回调更规范的错误处理功能增强添加采样频率设置函数添加命令行控制接口更好的数据显示格式添加采样计数器性能优化减少中断频率精确的时序控制更稳定的数据流这里留个问题经典的双缓存模式如何实现#include adc.h #include lcd.h #include stdlib.h // 包含atoi函数 // 信号量 static struct rt_semaphore adc_dma_sem; ADC_HandleTypeDef g_adc_dma_handle; /* 带DMA功能的ADC句柄 */ DMA_HandleTypeDef g_dma_adc_handle; /* 带ADC功能的DMA句柄 */ TIM_HandleTypeDef htim_adc_trig; /* 用于触发ADC的定时器 */ #define ADC_DMA_BUF_SIZE 50 /* ADC DMA缓存 BUF大小 */ uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */ /* TIM配置 - 用于触发ADC */ static void TIM_ADC_Trigger_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig {0}; TIM_MasterConfigTypeDef sMasterConfig {0}; /* TIM6初始化用于ADC触发 */ htim_adc_trig.Instance TIM6; /* 使用TIM6 */ htim_adc_trig.Init.Prescaler 10 - 1; /* 预分频系统时钟为400MHz时TIM6时钟为200MHz */ htim_adc_trig.Init.CounterMode TIM_COUNTERMODE_UP; htim_adc_trig.Init.Period 200 - 1; /* 自动重装载值 */ htim_adc_trig.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_Base_Init(htim_adc_trig); /* 配置TIM时钟源 */ sClockSourceConfig.ClockSource TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(htim_adc_trig, sClockSourceConfig); /* 主输出配置触发ADC */ sMasterConfig.MasterOutputTrigger TIM_TRGO_UPDATE; /* 更新事件触发TRGO */ sMasterConfig.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(htim_adc_trig, sMasterConfig); } /** * brief ADC DMA读取 初始化函数 * retval 无 */ void adc_dma_init(void) { GPIO_InitTypeDef gpio_init_struct; ADC_ChannelConfTypeDef adc_ch_conf {0}; __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP); /* ADC外设时钟选择PLL */ /* 开启ADC通道IO口时钟和ADC时钟 */ ADC_ADCX_CHY_GPIO_CLK_ENABLE(); ADC_ADCX_CHY_CLK_ENABLE(); /* 初始化定时器用于ADC触发 */ __HAL_RCC_TIM6_CLK_ENABLE(); TIM_ADC_Trigger_Init(); /* 初始化DMA时钟 */ if ((uint32_t)ADC_ADCX_DMASx (uint32_t)DMA2) { __HAL_RCC_DMA2_CLK_ENABLE(); } else { __HAL_RCC_DMA1_CLK_ENABLE(); } /* 初始化ADC通道对应的IO引脚 */ gpio_init_struct.Pin ADC_ADCX_CHY_GPIO_PIN; gpio_init_struct.Mode GPIO_MODE_ANALOG; gpio_init_struct.Pull GPIO_NOPULL; HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, gpio_init_struct); /* 配置ADC */ g_adc_dma_handle.Instance ADC_ADCX; g_adc_dma_handle.Init.ClockPrescaler ADC_CLOCK_ASYNC_DIV2; /* 异步时钟2分频 */ g_adc_dma_handle.Init.Resolution ADC_RESOLUTION_12B; /* 12位模式 */ g_adc_dma_handle.Init.ScanConvMode DISABLE; /* 单通道非扫描模式 */ g_adc_dma_handle.Init.EOCSelection ADC_EOC_SEQ_CONV; /* 序列转换结束标志 */ g_adc_dma_handle.Init.LowPowerAutoWait DISABLE; g_adc_dma_handle.Init.ContinuousConvMode DISABLE; /* 关闭连续转换由外部触发 */ g_adc_dma_handle.Init.NbrOfConversion 1; /* 1个转换在规则序列中 */ g_adc_dma_handle.Init.DiscontinuousConvMode DISABLE; g_adc_dma_handle.Init.ExternalTrigConv ADC_EXTERNALTRIG_T6_TRGO; /* TIM6触发 */ g_adc_dma_handle.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_RISING; /* 上升沿触发 */ g_adc_dma_handle.Init.ConversionDataManagement ADC_CONVERSIONDATA_DMA_CIRCULAR; /* DMA循环模式 */ g_adc_dma_handle.Init.Overrun ADC_OVR_DATA_OVERWRITTEN; g_adc_dma_handle.Init.LeftBitShift ADC_LEFTBITSHIFT_NONE; g_adc_dma_handle.Init.OversamplingMode DISABLE; HAL_ADC_Init(g_adc_dma_handle); /* 校准ADC */ HAL_ADCEx_Calibration_Start(g_adc_dma_handle, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); /* 配置ADC通道 */ adc_ch_conf.Channel ADC_ADCX_CHY; /* ADC通道 */ adc_ch_conf.Rank ADC_REGULAR_RANK_1; /* 规则序列1 */ adc_ch_conf.SamplingTime ADC_SAMPLETIME_8CYCLES_5; /* 采样时间 */ adc_ch_conf.SingleDiff ADC_SINGLE_ENDED; adc_ch_conf.OffsetNumber ADC_OFFSET_NONE; adc_ch_conf.Offset 0; adc_ch_conf.OffsetRightShift DISABLE; adc_ch_conf.OffsetSignedSaturation DISABLE; HAL_ADC_ConfigChannel(g_adc_dma_handle, adc_ch_conf); /* 配置DMA */ g_dma_adc_handle.Instance ADC_ADCX_DMASx; g_dma_adc_handle.Init.Request ADC_ADCX_DMASx_REQ; g_dma_adc_handle.Init.Direction DMA_PERIPH_TO_MEMORY; g_dma_adc_handle.Init.PeriphInc DMA_PINC_DISABLE; g_dma_adc_handle.Init.MemInc DMA_MINC_ENABLE; g_dma_adc_handle.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; g_dma_adc_handle.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; g_dma_adc_handle.Init.Mode DMA_CIRCULAR; /* 循环模式 */ g_dma_adc_handle.Init.Priority DMA_PRIORITY_VERY_HIGH; g_dma_adc_handle.Init.FIFOMode DMA_FIFOMODE_DISABLE; HAL_DMA_Init(g_dma_adc_handle); /* 链接DMA到ADC */ __HAL_LINKDMA(g_adc_dma_handle, DMA_Handle, g_dma_adc_handle); /* 配置NVIC中断 */ HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3); HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn); } /** * brief 设置ADC采样频率 * param freq_hz: 采样频率(Hz) * retval 实际设置的采样频率 */ float adc_set_sample_freq(uint32_t freq_hz) { uint32_t tim_clock 200000000; /* TIM6时钟假设系统时钟400MHzAPB1为200MHz */ uint32_t prescaler 100 - 1; /* 预分频值 */ uint32_t period; float actual_freq; if (freq_hz 0 || freq_hz 2000000) /* 限制频率范围 */ return 0; period (tim_clock / (prescaler 1)) / freq_hz - 1; if (period 0xFFFF) /* 确保period在16位范围内 */ { period 0xFFFF; } __HAL_TIM_SET_AUTORELOAD(htim_adc_trig, period); actual_freq (float)tim_clock / ((prescaler 1) * (period 1)); return actual_freq; } /** * brief 启动ADC采集 * param 无 * retval 无 */ void adc_start(void) { /* 启动DMA传输循环模式 */ HAL_DMA_Start_IT(g_dma_adc_handle, (uint32_t)ADC1-DR, (uint32_t)g_adc_dma_buf, ADC_DMA_BUF_SIZE); /* 启动ADC DMA模式 */ HAL_ADC_Start_DMA(g_adc_dma_handle, (uint32_t*)g_adc_dma_buf, ADC_DMA_BUF_SIZE); /* 启动定时器触发 */ HAL_TIM_Base_Start(htim_adc_trig); } /** * brief 停止ADC采集 * param 无 * retval 无 */ void adc_stop(void) { /* 停止定时器 */ HAL_TIM_Base_Stop(htim_adc_trig); /* 停止ADC DMA */ HAL_ADC_Stop_DMA(g_adc_dma_handle); } void ADC_ADCX_DMASx_IRQHandler(void) { // 先调用HAL库中断处理函数 HAL_DMA_IRQHandler(g_dma_adc_handle); } // 添加DMA传输完成回调函数HAL库标准用法 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc-Instance ADC_ADCX) { rt_sem_release(adc_dma_sem); // 释放信号量 } } /* 设备初始化 - RT-Thread自动初始化 */ int device_adc_init(void) { /* 初始化信号量 */ rt_sem_init(adc_dma_sem, adc_dma_sem, 0, RT_IPC_FLAG_FIFO); /* 初始化ADC DMA */ adc_dma_init(); /* 设置默认采样频率Hz */ adc_set_sample_freq(30000); return 0; } INIT_DEVICE_EXPORT(device_adc_init); uint16_t int_part 0; uint16_t frac_part 0; /* ADC数据处理线程 */ static void adc_thread_entry(void *parameter) { uint16_t i; uint16_t adc_value; uint32_t sum; float voltage; uint32_t sample_count 0; /* LCD显示初始化 */ lcd_show_string(30, 50, 200, 16, 16, STM32H743 ADC, RED); lcd_show_string(30, 70, 200, 16, 16, TIM Trigger DMA, RED); lcd_show_string(30, 90, 200, 16, 16, first ver, RED); lcd_show_string(30, 120, 200, 16, 16, ADC Value:, BLUE); lcd_show_string(30, 140, 200, 16, 16, ADC1_CH19_VOL:0.000V, BLUE); lcd_show_string(30, 160, 200, 16, 16, Samples: 0, BLUE); /* 启动ADC采集 */ adc_start(); while (1) { /* 等待DMA完成信号 */ if (rt_sem_take(adc_dma_sem, RT_WAITING_FOREVER) RT_EOK) { /* 确保数据缓存一致性如果使用Cache */ //#ifdef SCB_InvalidateDCache_by_Addr uint32_t buf_addr (uint32_t)g_adc_dma_buf; uint32_t buf_len ADC_DMA_BUF_SIZE * sizeof(uint16_t); SCB_InvalidateDCache_by_Addr((uint32_t *)buf_addr, buf_len); // #endif //SCB_InvalidateDCache(); /* 计算平均值 */ sum 0; for (i 0; i ADC_DMA_BUF_SIZE; i) { sum g_adc_dma_buf[i]; } adc_value sum / ADC_DMA_BUF_SIZE; sample_count ADC_DMA_BUF_SIZE; /* 转换为电压 */ voltage (float)adc_value * (3.3f / 4096.0f); /* 显示结果 */ lcd_show_xnum(110, 120, adc_value, 5, 16, 0, BLUE); /* 显示电压格式: X.XXX */ int_part (uint16_t)voltage; frac_part (uint16_t)((voltage - int_part) * 1000); //占用时间 //rt_kprintf(ADC Value: %d, Voltage: %d.%03dV\n,adc_value, int_part, frac_part); lcd_show_xnum(142, 140, int_part, 1, 16, 0, BLUE); lcd_show_xnum(158, 140, frac_part, 3, 16, 0x80, BLUE); /* 显示采样计数 */ lcd_show_xnum(100, 160, sample_count, 10, 16, 0, BLUE); } } } /* 任务创建 */ static int adc_sample_start(int argc, char *argv[]) { rt_thread_t thread; /* 创建ADC处理线程 */ thread rt_thread_create(adc, adc_thread_entry, RT_NULL, 2048, 20, /* 优先级略高于默认 */ 10); if (thread ! RT_NULL) { rt_thread_startup(thread); rt_kprintf(ADC sample thread started.\n); return RT_EOK; } return -RT_ERROR; } MSH_CMD_EXPORT(adc_sample_start, Start ADC sampling with TIM trigger); /* 设置采样频率命令 */ static int adc_set_freq(int argc, char *argv[]) { uint32_t freq; if (argc ! 2) { rt_kprintf(Usage: adc_freq frequency_hz\n); rt_kprintf(Example: adc_freq 1000 (set 1kHz sampling)\n); return -RT_ERROR; } freq atoi(argv[1]); float actual_freq adc_set_sample_freq(freq); rt_kprintf(Set ADC sampling frequency:\n); rt_kprintf( Requested: %d Hz\n, freq); rt_kprintf( Actual: %.2f Hz\n, actual_freq); return RT_EOK; } MSH_CMD_EXPORT(adc_set_freq, Set ADC sampling frequency); /* 停止ADC采集命令 */ static int adc_stop_cmd(int argc, char *argv[]) { adc_stop(); rt_kprintf(ADC stopped.\n); return RT_EOK; } MSH_CMD_EXPORT(adc_stop_cmd, Stop ADC sampling); /* 启动ADC采集命令 */ static int adc_start_cmd(int argc, char *argv[]) { adc_start(); rt_kprintf(ADC started.\n); return RT_EOK; } MSH_CMD_EXPORT(adc_start_cmd, Start ADC sampling);时间关系先记录到这后面把数据存到SDRAM后累计一定包长上传上位机软件。慢慢更新。三、最终效果GPIO将DAC输出和ADC出入的引脚用跳线帽短接。最终效果如下DAC模拟的是实际开发中从物理信号--》传感器采集转换成电压信号ADC采集模拟信号然后通过DMA及TIM做到不占用CPU。一般工程化方案基本都这个原理。ADC_DAC