当前位置: 首页 > news >正文

基于RT-Thread的STM32G4开发第二讲第二篇——ADC

文章目录

  • 前言
  • 一、RT-Thread工程创建
  • 二、ADC工程创建
  • 三、ADC功能实现
    • 1.ADC.c
    • 2.ADC.h
    • 3.mian.c
  • 四、效果展示和工程分享
  • 总结


前言

本文使用的是RT-Thread最新的驱动5.1.0,兼容下面的所有驱动。使用的开发板是蓝桥杯嵌入式国信长安的开发板,MCU是STM32G431RBT6。
我已经写了基于STM32F4关于ADC的文章(见上一篇),为什么还要写基于STM32G4的呢。原因是RT-Thread对STM32G4的ADC外设的适配极其不好,初始化缺失还不报错,为了实现这个功能,花费了我很多时间,我觉得有必要分享出来。
本章同上一章有很多内容相似,我都重新说一遍,这样大家按选择看一篇文章就行


一、RT-Thread工程创建

先在RT-Thread studio中创建好工程,参考下面的文章使得驱动5.1.0全构建不报错和警告,如图所示。
RT-Thread studio的驱动5.1.0报错修改
在这里插入图片描述
下面工程名改为IO_ADC
不要着急修改时钟配置,这里按我方法来,打开自动生成的CubeMX Settings(找不到的话点击窗口,恢复窗口布局,在项目资源管理器下。在CubeMX中按裸机编程一样,把时钟和需要用到的外设都配置好。配置详情我就不说了,看前面的文章就行。
注意使用到的外设都要配置,开局使用串口1作为控制台串口,所以这里也要配置。示例如下
在这里插入图片描述

在这里插入图片描述这里我使用了ADC1和ADC2,配置如下,对于ADC的详细参数,也要配置一下,可以作为后面RT-Thread的参考,关于ADC的详情配置见下文。
STM32LL库编程系列第八讲——ADC模数转换
在这里插入图片描述
这里的IDE要选择EWARM,也就是保持默认,很重要,其他照常
在这里插入图片描述
在这里插入图片描述
到这一步就可以生成工程了
在这里插入图片描述
第一次生成工程后要把cubeMX关闭掉,这样RT-Thread studio才会同步(下面每一步的图片参考上一篇文章)
点击左边文件,cubemx(没有的话,刷新一下),右键Src,资源配置,排除构建
打开cubemx的mian.c复制函数void SystemClock_Config(void),包括函数名全部复制,在打开drivers/drv_clk.c,把void system_clock_config(int target_freq_mhz)函数删了,把复制的void SystemClock_Config(void)粘贴原地,接着全编译,没有问题。
到这一步你可以把工程保存好,在RT-Thread studio中基于STM32G431系类的驱动5.1.0的初始工程创建完成,以后再用就直接复制工程就行,不用重复创建了。这一点也希望官方优化,不需要我们这么麻烦。
在这里插入图片描述

二、ADC工程创建

打开cubemx/src/adc.h。复制函数void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)到drivers/board.c的末尾,把全局变量HAL_RCC_ADC12_CLK_ENABLED删除,并删除该全局变量的if判断,也就是这样。

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};if(adcHandle->Instance==ADC1){/** Initializes the peripherals clocks*/PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK){Error_Handler();}/* ADC1 clock enable */__HAL_RCC_ADC12_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**ADC1 GPIO ConfigurationPB12     ------> ADC1_IN11*/GPIO_InitStruct.Pin = GPIO_PIN_12;GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);}else if(adcHandle->Instance==ADC2){/** Initializes the peripherals clocks*/PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK){Error_Handler();}/* ADC2 clock enable */__HAL_RCC_ADC12_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**ADC2 GPIO ConfigurationPB15     ------> ADC2_IN15*/GPIO_InitStruct.Pin = GPIO_PIN_15;GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);}
}void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{if(adcHandle->Instance==ADC1){__HAL_RCC_ADC12_CLK_DISABLE();/**ADC1 GPIO ConfigurationPB12     ------> ADC1_IN11*/HAL_GPIO_DeInit(GPIOB, GPIO_PIN_12);}else if(adcHandle->Instance==ADC2){__HAL_RCC_ADC12_CLK_DISABLE();/**ADC2 GPIO ConfigurationPB15     ------> ADC2_IN15*/HAL_GPIO_DeInit(GPIOB, GPIO_PIN_15);}
}

在这里插入图片描述
这两个函数不需要在board.h中去声明,有其他.h已经声明好了,所以这里复制过来就可以用。
打开board.h需要的ADC的宏,不需要再去stm32f4xx_hal_config.h中注释宏#define HAL_ADC_MODULE_ENABLED了,细心的同学可以发现了,drivers中更名为了stm32f4xx_hal_config_bak.h。而stm32f4xx_hal_config.h在cubemx/inc中了,已经在创建时开启宏HAL_ADC_MODULE_ENABLED了。
在这里插入图片描述
接着在RT-Thread Settings中打开ADC驱动
注意开启ulog日志,进入到里面开启使能浮点数支持,这将会使我们rt_kprintf能够输出浮点数。
(上面两步参考图片见上一篇)
全编译,会发现有如下报错
在这里插入图片描述
原因是没有声明函数__HAL_ADC_ENABLE0,这并不是头文件没有添加而是根本没有这个函数,经过我和前面工程的对比,发现所有使能ADC的函数换成了ADC_Enable,但是这里使用ADC_Enable的#if判断没有我们的型号,那就需要自己添加,如下(这也是bug,希望官方看到能修复)
在这里插入图片描述
还剩下两个报错原因是参数类型不一致,这和修改5.1.0报错一样
在这里插入图片描述
修改结构体和函数都行,他们保持一致就好,我的修改如下,我修改的是函数形参。

struct rt_adc_ops
{rt_err_t (*enabled)(struct rt_adc_device *device, rt_int8_t channel, rt_bool_t enabled);rt_err_t (*convert)(struct rt_adc_device *device, rt_int8_t channel, rt_uint32_t *value);rt_uint8_t (*get_resolution)(struct rt_adc_device *device);rt_int16_t (*get_vref) (struct rt_adc_device *device);
};static rt_err_t stm32_get_adc_value(struct rt_adc_device *device, rt_int8_t channel, rt_uint32_t *value)
static rt_err_t stm32_adc_enabled(struct rt_adc_device *device, rt_int8_t channel, rt_bool_t enabled)

到此全编译程序没有错误,到这里ADC工程创建完成了

三、ADC功能实现

这里我要讲点网上没有的(起码此刻孤陋寡闻的我没找到)
点击drivers/include/confing/adc_confing.h这里有我们使用的ADC的初始化参数,rtthread studio并没有ADC参数控制函数,想要修改,只能在这改,希望官方更新一下,可以像uart外设这样,建一个ADC参数结构体,里面包含了所有参数设计,再利用rt_device_control函数写进去,这样才符合常理,不能老是去驱动文件里改啊,很难找的。
这里说笑了,对于国产软件生态,还需要我们大家共同努力完善。所以我愿意把我的发现免费分享出来。
打开cubemx/src/adc.c对照里面的参数设置,对adc_confing.h进行更改(这也是我前面说把参数配置完全,后面好参照)
我的设置如下,对比cubemx/src/adc.c这里有些参数设置不全,可以自行添加,也可以默认,这些缺失参数不重要,添加注意需要最后的 **,不然会报错。
在这里插入图片描述
打开
drivers/drv_adc.c**找到函数stm32_get_adc_value这里面有如下设置语句

ADC_ChanConf.Channel =  stm32_adc_get_channel(channel);
ADC_ChanConf.Rank = 1;

对比cubemx/src/adc.c你会发现通道参数设置缺失严重,这也就是为什么程序不报错但功能实现不了的原因(再次呼吁官方完善)
我的修改如下

ADC_ChanConf.Channel =  stm32_adc_get_channel(channel);//不变
#if defined(SOC_SERIES_STM32G4)ADC_ChanConf.Rank = ADC_REGULAR_RANK_1;
#elseADC_ChanConf.Rank = 1;
#endif
#elif defined(SOC_SERIES_STM32G4)ADC_ChanConf.SamplingTime = ADC_SAMPLETIME_24CYCLES_5;
#ifdef SOC_SERIES_STM32G4 ADC_ChanConf.OffsetNumber = ADC_OFFSET_NONE;ADC_ChanConf.SingleDiff = LL_ADC_SINGLE_ENDED;ADC_ChanConf.Offset = 0;
#endif

图片也附上
在这里插入图片描述
对于ADC_ChanConf.Rank我们配置cubemx时选的是1,也就是只有一个通道,但是不同芯片下的赋值不一样,有的直接是1,有的是ADC_REGULAR_RANK_1宏,这个宏的值为6。开始我没注意这部分,导致程序不报错,各种初始化也成功,但就是采集值永远是0,折磨我很久,问题就在这。
官方的条件编译指令的判断缺失了很多,我使用的芯片就没有,这里我也用条件编译指令加上,这样不会影响其他程序。
设置完成就有HAL_ADC_ConfigChannel(stm32_adc_handler, &ADC_ChanConf);到这ADC的初始化才结束。编译程序没有错误。

把board.c的#include <drv_common.h>粘贴到board.h(不然很多引用board.h的文件不含drv_common.h,导致报错)

APP文件夹里是我自定义的文件夹,其他函数不用管,本工程只用到ADC.c和ADC.h。注意创建文件夹要把头文件目录添加进构建啊。如何添加见本系列第一讲

1.ADC.c

这里面包含adc初始化和线程初始化,代码逻辑我就不讲了,我的代码风格应该挺正规的,具体编写流程去看官方文档或其他人文章
和上一章的内容比只改变了通道号,其余没有变化,这也是操作系统的良好移植性。

#include "ADC.h"#define     ADC1_NAME   "adc1"
#define     ADC2_NAME   "adc2"
#define     REFER_VOLTAGE   3.3
#define     CONVERT_BITS    (1<<10)static void adc_thread_entry(void *parameter);
rt_adc_device_t adc1_handle,adc2_handle;
int adc_init(void)
{rt_err_t adc1_flag,adc2_flag;adc1_handle = (rt_adc_device_t)rt_device_find(ADC1_NAME);adc2_handle = (rt_adc_device_t)rt_device_find(ADC2_NAME);if((adc1_handle == RT_NULL) || (adc2_handle == RT_NULL)){rt_kprintf("failed to adc handle fine\n");return -1;}adc1_flag = rt_adc_enable(adc1_handle, 11);adc2_flag = rt_adc_enable(adc2_handle, 15);if((adc1_flag != RT_EOK) || (adc2_flag != RT_EOK)){rt_kprintf("failed to adc enable\n");return -1;}rt_kprintf("adc1 and adc2 init success\n");return 0;
}
int adc_thread_init(void)
{rt_thread_t adc_thread;adc_thread = rt_thread_create("adc_thread", adc_thread_entry, RT_NULL, 1024, 9, 100);if(adc_thread == RT_NULL){rt_kprintf("failed to adc thread create");return -1;}if(rt_thread_startup(adc_thread) != RT_EOK){rt_kprintf("failed to adc startup\n");return -1;}return 0;
}
static void adc_thread_entry(void *parameter)
{float adc1_V_old = 0,adc2_V_old = 0;float adc1_V_new,adc2_V_new;while(1){adc1_V_new = (float)rt_adc_read(adc1_handle, 11)*REFER_VOLTAGE/CONVERT_BITS;adc2_V_new = (float)rt_adc_read(adc2_handle, 15)*REFER_VOLTAGE/CONVERT_BITS;if( ((int)(adc1_V_old *100) != (int)(adc1_V_new *100)) || ((int)(adc2_V_old *100) != (int)(adc2_V_new *100)) ){rt_kprintf("get voltage for adc1 and adc2 is: %.2f and %.2f\n",adc1_V_new, adc2_V_new);adc1_V_old = adc1_V_new;adc2_V_old = adc2_V_new;}rt_thread_mdelay(100);}
}

2.ADC.h

#ifndef APP_ADC_H_
#define APP_ADC_H_#include <board.h>
#include <rtdevice.h>int adc_init(void);
int adc_thread_init(void);#endif /* APP_ADC_H_ */

3.mian.c

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "ADC.h"int main(void)
{adc_init();adc_thread_init();while (1){rt_thread_mdelay(1000);}return RT_EOK;
}

编译0错误0警告,到此工程结束。

四、效果展示和工程分享

在这里插入图片描述
这个时候效果和上一章一样,有点误差,这个时候我们加入校准函数
打开drivers/drv_adc.cHAL_ADC_Start前加入校准函数HAL_ADCEx_Calibration_Start(stm32_adc_handler,ADC_SINGLE_ENDED);
在这里插入图片描述
重新编译和下载
在这里插入图片描述
发现误差基本消除了(最高有3.29V,接近理论3.3V,前面最高只能到3.25V)

工程上传百度网盘,包括IO_ADC和初始工程文件,免费下载。同时也上传到CSDN,被强制成为VIP才能下载(其实我是想每个工程收1积分)。如果你刚好有VIP,就请CSDN下载支持一下,嘻嘻。没有的话,千万别开,死贵,去百度网盘下载。

通过网盘分享的文件:IO_ADC.zip
链接: https://pan.baidu.com/s/1wtQsLlgUVFpLt24pOOtUAA?pwd=br58 提取码: br58
通过网盘分享的文件:RT_driver_5.1.0_STM32G431RBTx.zip
链接: https://pan.baidu.com/s/1XsCLVMCYPWlEIj5bPXOCQg?pwd=tay6 提取码: tay6


总结

创建工程有点繁琐,如果有某些地方不会操作报错了,请下载工程,这些工程我是验证过的,没有问题。

http://www.xdnf.cn/news/328195.html

相关文章:

  • 2014年写的一个文档《基于大数据应用的综合健康服务平台研发及应用示范》
  • layui下拉框输入关键字才出数据
  • JMeter快速指南:命令行生成HTML测试报告(附样例命令解析)
  • Android学习总结之网络篇补充
  • conda init before conda activate
  • MVC是什么?分别对应SpringBoot哪些层?
  • 【C/C++】ARM处理器对齐_伪共享问题
  • autojs和冰狐智能辅助该怎么选择?
  • 从D盘分配空间为C盘扩容?利用工具1+1>2
  • 使用JMeter 编写的测试计划的多个线程组如何生成独立的线程组报告
  • 理解文本嵌入:语义空间之旅
  • 探索 H-ZERO 模态框组件:提升用户交互体验的利器
  • PaaS筑基,中国中化实现转型飞跃
  • ROS1和ROS2使用桥接工具通信
  • 【CF】Day53——Codeforces Round 1023 (Div. 2) CD
  • 中级网络工程师知识点1
  • 自定义分区器-基础
  • 【useOperatorData Hook 改造实践】
  • 7D-AI系列:模型微调之mlx-lm
  • Node.js 的 child_process 模块详解
  • Inference-Time Scaling for Generalist Reward Modeling
  • 课程10. Transformers与大型语言模型
  • css内容省略——text-overflow: ellipsis
  • RDD的基本概念及创建方式
  • 什么是RDD.RDD的创建方式
  • 小王包子铺的融资过程以及IPO上市过程
  • 自定义Widget开发:手势交互处理
  • cuda程序兼容性问题
  • 001 环境搭建
  • 对京东开展外卖业务的一些思考