|**银杏科技有限公司旗下技术文档发布平台** |||| |技术支持电话|**0379-69926675-801** ||| |技术支持邮件|Gingko@vip.163.com ||| ^ 版本 ^ 日期 ^ 作者 ^ 修改内容 ^ | V1.0 | 2020-04-13 | gingko | 初次建立 | ===== STM32CubeMX教程十——ADC实验 ===== 1. 新建工程:在主界面选择File-->New Project 或者直接点击ACCEE TO MCU SELECTOR {{ :icore3:icore3_cube_10_1.png?direct | }} 2. 出现芯片型号选择,搜索自己芯片的型号,双击型号,或者点击Start Project进入配置 在搜索栏的下面,提供的各种查找方式,可以选择芯片内核、型号等等,可以帮助你查找芯片。本实验选取的芯片型号为:STM32F407IGTx。 {{ :icore3:icore3_cube_10_2.png?direct | }} 3. 配置RCC,使用外部时钟源 {{ :icore3:icore3_cube_10_3.png?direct | }} 4. 配置调试引脚 {{ :icore3:icore3_cube_10_4.png?direct | }} 5. 将LED对应的3个引脚(PI5,PI6,PI7)设置为GPIO_Output {{ :icore3:icore3_cube_10_5.png?direct | }} 6. 引脚模式配置 {{ :icore3:icore3_cube_10_6.png?direct | }} 7. 配置ADC引脚 {{ :icore3:icore3_cube_10_7.png?direct | }} {{ :icore3:icore3_cube_10_8.png?direct | }} 8. 配置UART {{ :icore3:icore3_cube_10_9.png?direct | }} 9. 时钟源设置,选择外部高速时钟源,配置为最大主频 {{ :icore3:icore3_cube_10_10.png?direct | }} 10. 工程文件的设置, 这里就是工程的各种配置 我们只用到有限几个,其他的默认即可 IDE我们使用的是 MDK5 {{ :icore3:icore3_cube_10_11.png?direct | }} 11. 点击Code Generator,进行进一步配置 {{ :icore3:icore3_cube_10_12.png?direct | }} * **Copy all used libraries into the project folder** * 将HAL库的所有.C和.H都复制到所建工程中 * 优点:这样如果后续需要新增其他外设又可能不再用STM32CubeMX的时候便会很方便 * 缺点:体积大,编译时间很长 * **Copy only the necessary library files** * 只复制所需要的.C和.H(推荐) * 优点:体积相对小,编译时间短,并且工程可复制拷贝 * 缺点:新增外设时需要重新用STM32CubeMX导入 * **Add necessary library files as reference in the toolchain project configuration file** * 不复制文件,直接从软件包存放位置导入.C和.H * 优点:体积小,比较节约硬盘空间 * 缺点:复制到其他电脑上或者软件包位置改变,就需要修改相对应的路径 自行选择方式即可 12. 然后点击GENERATE CODE 创建工程 {{ :icore3:icore3_cube_10_13.png?direct | }} 创建成功,打开工程。 \\ \\ \\ \\ ===== 实验十:ADC实验——电源监控 ===== ==== 一、 实验目的与意义 ==== - 了解STM32 ADC结构。 - 了解STM32 ADC特征。 - 掌握ADC的使用方法。 - 掌握STM32 HAL库中ADC属性的配置方法。 - 掌握KEIL MDK 集成开发环境使用方法。 ==== 二、 实验设备及平台 ==== - iCore3 双核心板。[[https://item.taobao.com/item.htm?spm=a1z10.1-c.w4024-251734887.3.5923532fXD2RIN&id=524229438677&scene=taobao_shop|点击购买]] - JLINK(或相同功能)仿真器。[[https://item.taobao.com/item.htm?spm=a1z10.5-c.w4002-251734908.13.20822b61MmPeNN&id=554869837940|点击购买]] - Micro USB线缆。 - Keil MDK 开发平台。 - STM32CubeMX开发平台。 - 装有WIN XP(及更高版本)系统的计算机。 ==== 三、 实验原理 ==== === 1、ADC简介 === * ADC是A/D转换部件,单片机不能直接处理模拟量,所以需要ADC将模拟量转换为数字量后再进行处理。在使用单片机进行模拟数据处理的过程中,ADC至关重要。STM32F407IGT6具有3个12位逐次逼近式ADC,共24个通道,可以配置成12位、8位、6位使用。可使用软件对要使用的ADC进行使能和设置,进行模拟量的采集。 === 2、ADC分为以下几种类型: === - 积分型:积分型AD工作原理是将输入电压转换成时间(脉冲宽度信号)或频率(脉冲频率),然后由定时器/计数器获得数字值。其优点是用简单电路就能获得高分辨率,抗干扰能力强,但缺点是由于转换精度依赖于积分时间,因此转换速率极低。初期的单片AD转换器大多采用积分型,现在逐次比较型已逐步成为主流。 - 逐次比较型:逐次比较型AD由一个比较器和DA转换器通过逐次比较逻辑构成,从MSB开始,顺序地对每一位将输入电压与内置DA转换器输出进行比较,经n次比较而输出数字值。其电路规模属于中等。其优点是速度较高、功耗低,在低分辩率(<12位)时价格便宜,但高精度(>12位)时价格很高。 - 并行比较型/串并行比较型:并行比较型AD采用多个比较器,仅作一次比较而实行转换,又称FLash(快速)型。由于转换速率极高,n位的转换需要2n-1个比较器,因此电路规模也极大,价格也高,只适用于视频AD转换器等速度特别高的领域。串并行比较型AD结构上介于并行型和逐次比较型之间,最典型的是由2个n/2位的并行型AD转换器配合DA转换器组成,用两次比较实行转换,所以称为 Half flash(半快速)型。还有分成三步或多步实现AD转换的叫做分级(Multistep/Subrangling)型AD,而从转换时序角度又可称为流水线(Pipelined)型AD,现代的分级型AD中还加入了对多次转换结果作数字运算而修正特性等功能。这类AD速度比逐次比较型高,电路规模比并行型小。 - Σ-Δ(Sigma delta)调制型:Σ-Δ型AD由积分器、比较器、1位DA转换器和数字滤波器等组成。原理上近似于积分型,将输入电压转换成时间(脉冲宽度)信号,用数字滤波器处理后得到数字值。电路的数字部分基本上容易单片化,因此容易做到高分辨率。主要用于音频和测量。 - 电容阵列逐次比较型:电容阵列逐次比较型AD在内置DA转换器中采用电容矩阵方式,也可称为电荷再分配型。一般的电阻阵列DA转换器中多数电阻的值必须一致,在单芯片上生成高精度的电阻并不容易。如果用电容阵列取代电阻阵列,可以用低廉成本制成高精度单片AD转换器。最近的逐次比较型AD转换器大多为电容阵列式的。 - 压频变换型:压频变换型(Voltage-Frequency Converter)是通过间接转换方式实现模数转换的。其原理是首先将输入的模拟信号转换成频率,然 后用计数器将频率转换成数字量。从理论上讲这种AD的分辨率几乎可以无限增加,只要采样的时间能够满足输出频率分辨率要求的累积脉冲个数的宽度。其优点是分辩率高、功耗低、价格低,但是需要外部计数电路共同完成AD转换。 === 3、ADC主要参数介绍 === - 分辨率:数字量变化一个最小量时模拟量的变化量,定义为满刻度与2n的比值。分辩率又称精度,通常以数字信号的位数来表示。 - 转换速率:完成一次A/D转换所需要时间的倒数,值越大表示转换得越快。积分型AD的转换时间是毫秒级属低速AD,逐次比较型AD是微秒级属中速AD,全并行/串并行型AD可达到纳秒级。 - 量化误差:由于AD的有限分辩率而引起的误差,即有限分辩率AD的阶梯状转移特性曲线与无限分辩率AD(理想AD)的转移特 性曲线(直线)之间的最大偏差。通常是1 个或半个最小数字量的模拟变化量,表示为1LSB、1/2LSB。 - 偏移误差:输入信号为零时输出信号不为零的值,可外接电位器调至最小。 - 满刻度误差:满度输出时对应的输入信号与理想输入信号值之差。 - 线性度:实际转换器的转移函数与理想直线的最大偏移。 * 本试验使用的芯片STM32F407IGT6,使用ADC的五个通道,分别监测5种电源信息,使用程序进行相应的转换后,使用putty串口工具将采集到的电源信息打印到PC机屏幕上,了解开发板的电源状态。 * STM32内部集成三个12位ADC,iCore3的所有电源经过电阻分压或者直接接入STM32的ADC的输出通道内,输入电流经过高端电流检测芯片ZXCT1009F输入到ADC的输入通道内,从而实现电源监控功能。电源监控引脚分配情况见下表: {{ :icore3:icore3_arm_hal_10_1.png?direct |}} {{ :icore3:icore3_arm_hal_10_2.png?direct |}} ||监测内容|ADC选用|选用引脚| |1|5V电压|ADC1-14通道|PC4| |2|输入电流|ADC1-15通道|PC5| |3|1.2V电压|ADC3-15通道|PF5| |4|3.3V电压|ADC3-4通道|PF6| |5|2.5V电压|ADC3-5通道|PF7| * 电压监控硬件连接示意图如下图所示: {{ :icore3:icore3_arm_hal_10_3.png?direct&250 |}} * 由上图可知: * **VCC = (1 + R1 / R2)*ADC_IN** * 故知: * **VCC = (1 + 49.9K / 10K)*ADC_IN = 6*ADC_IN** * 其他电源监控同理可得: * **D3V3 = 2*ADC_IN** * **A2V5 = 2*ADC_IN** * **D1V2 = ADC_IN** * 电流监控硬件连接示意图如下图: {{ :icore3:icore3_arm_hal_10_4.png?direct&200 |}} * 由ZXCT1009F的原理可知: * **ADC_IN = 0.01 * (VCC - LOAD)*R2** *通过R1的电流: * **ADC_IN = 0.01 * (VCC - LOAD)*R2** * 带入R2 = 10K,R1 = 0.02: * **I = ADC_IN / 2** ==== 四、 实验程序 ==== === 1. 主函数 === * 在主函数中,初始化所有已配置的外围设备后,将串口清屏并将字体在串口终端设置为绿色,同时在串口终端打印“Hello! I am iCore3”,调用ADC读取函数监控电源,并使用串口打印各路电压值。 int main(void) { int i; HAL_Init(); SystemClock_Config(); //配置系统时钟 MX_GPIO_Init(); //初始化所有已配置的外围设备 MX_ADC1_Init(); MX_ADC3_Init(); MX_UART4_Init(); LED_RED_ON; //红灯亮 while (1) { //ADC监控电源 for(i = 0;i < 100000;i++); for(i = 0;i < 5;i++){ adc.read(i); } uart4.printf("\x0c"); //清屏 uart4.printf("\033[1;32;40m");//字体终端设置为绿色 uart4.printf("\r\n\r\nhello! I am iCore3!\r\n\r\n\r\n"); //在串口终端打印“Hello! I am iCore3” //打印系统供电电压 uart4.printf(" [V] %4.2fV\r\n",adc1_3.value[0]*6); uart4.printf(" [I] %3.0fmA\r\n",adc1_3.value[1] / 2* 1000); uart4.printf(" [1.2V] %4.2fV\r\n",adc1_3.value[2]); uart4.printf(" [3.3V] %4.2fV\r\n",adc1_3.value[3] * 2); uart4.printf(" [2.5V] %4 } } === 2. ADC结构体定义 === ADC_HandleTypeDef hadc1; ADC_HandleTypeDef hadc3; * ADC的名称定义,这个结构体中存放了ADC所有用到的功能,后面的别名就是我们所用的adc串口的别名,在本实验中我们用到了ADC1、ADC3。 typedef struct { ADC__TypeDef *Instance; //ADC寄存器基地址 ADC_InitTypeDef Init; //ADC所需参数 __IO uint32_t NbrOfCurrentConversionRank; //当前转换列的ADC数 DMA_HandleTypeDef *DMA_Handle; //指针DMA处理程序 HAL_LockTypeDef Lock; //锁定对象 __IO uint32_t State; //ADC通信状态 __IO uint32_t ErrorCode; //ADC错误代码 } ADC_HandleTypeDef; * 可以看到ADC_HandleTypeDef除了包含一个指向ADC_InitTypeDef的指针之外,还包含指向寄存器的指针、互斥锁、一个描述状态的变量、一个保存错误代码的变量、一个指向DMA结构体的指针。所有与对ADC进行操作的函数都使用这个结构体的指针作为参数。 typedef struct { uint32_t Channel; //指定要配置为ADC常规组的通道。 uint32_t Rank; //指定常规组序列器中的列组,此参数必须是介于Min Data=1和Max Data=16之间的数字 uint32_t SamplingTime; //要为选定通道设置的采样时间值。 uint32_t Offset; //保留供将来使用,可设置为0 }ADC_ChannelConfTypeDef; * 上述ADC_ChannelConfTypeDef结构体用来表述单个通道在组序列里的排序位置及工作速率 HAL_StatusTypeDef HAL_ADC_ConfigChannel (ADC_HandleTypeDef* hadc, ADC_ChannelConfTypeDef* sConfig) * 上述HAL_ADC_ConfigChannel表示ADC配置通道,用来处理以上两个结构体。 === 3. ADC相关函数 === * HAL_ADC_START()表示开始一次采集(直接软触发或者等待外部触发) * HAL_ADC_START_DMA()表示数据用DMA传输 * HAL_ADC_START_IT()表示使能中断 * HAL_ADC_IRQHander()是中断处理函数 * HAL_ADC_GET_FLAG 表示读取标志位 * HAL_ADC_GetValue取转换后的数值 * **ADC在DMA模式下开启数据传输** HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length) * **参数:** * ADC_HandleTypeDef* hadc为ADC的别名,在本实验中使用了ADC1、ADC3通道 * uint32_t* pData 需要发送的数据 * uint32_t Length 需要发送的数据长度 * **ADC开启数据传输** HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc) * **参数:** * ADC_HandleTypeDef* hadc为ADC的别名,在本实验中使用了ADC1、ADC3通道 * **ADC读取读取转换后的数值** uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc) * **参数:** * ADC_HandleTypeDef* hadc为ADC的别名,在本实验中使用了ADC1、ADC3通道 * **ADC开启中断模式** void HAL_ADC_IRQHandler(ADC_HandleTypeDef* hadc) * **参数:** * ADC_HandleTypeDef* hadc为ADC的别名,在本实验中使用了ADC1、ADC3通道 === 4. ADC读取数据程序 === * 首先将ADC读取得到的100个转换值进行从小到大的排序,取排在中间的20个转换值的平均值作为读取结果,然后连续取20次这样的平均值,再求这20次所得数据的平均值作为最终结果,这样采用中值平均滤波的方式,使得到的结果具有更高的准确性。 void sort(unsigned short int a[], int n) { int i, j, t; //元素从大到小排列 for (i = 0; i < n - 1; i++) { for (j = 0; j < n - i - 1; j++) { if (a[j] > a[j + 1]) { t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; } } } } static int read_adc(int channel) { int i,k; unsigned long int temp[20] = {0}; unsigned long int value; unsigned short int data[100]; ADC_ChannelConfTypeDef channel_config; unsigned char channel_remap[5] = {ADC_CHANNEL_14,ADC_CHANNEL_15,ADC_CHANNEL_15,ADC_CHANNEL_4,ADC_CHANNEL_5}; //取得到的100个转换值的中间的20个的平均值作为结果 //连续取20次这样的平均值,再求平均值作为最终结果 channel_config.Channel = channel_remap[channel]; channel_config.Rank = 1; channel_config.SamplingTime = ADC_SAMPLETIME_3CYCLES; if(channel == 0 || channel == 1){ HAL_ADC_ConfigChannel(&hadc1,&channel_config); }else { HAL_ADC_ConfigChannel(&hadc3,&channel_config); } for(k = 0;k < 20;k++){ //取20组数值做滤波 for(i = 0;i < 100;i++){ if(channel == 0 || channel == 1){ HAL_ADC_Start(&hadc1); //开启ADC1采集通道 while(!__HAL_ADC_GET_FLAG(&hadc1,ADC_FLAG_EOC));//等待转换结束 data[i] = HAL_ADC_GetValue(&hadc1); //将结果保存 }else { HAL_ADC_Start(&hadc3); //开启ADC3采集通道 while(!__HAL_ADC_GET_FLAG(&hadc3,ADC_FLAG_EOC));//等待转换结束 data[i] = HAL_ADC_GetValue(&hadc3); //将结果保存 } } sort(data,100); for(i = 40;i < 60;i++){ // 对采集到的数值做均值滤波处理 temp[k] += data[i]; } temp[k] = temp[k] / 20; } value = 0; for(k = 0;k < 20;k++){ value += temp[k]; } value /= 20; adc13.value[channel] = value * ADC_REF / 4096; //12位的ADC满量程为2^12=4096,参考电压ADC_REF 为2.483V return value; } * 使用ADC_ChannelConfTypeDef结构体对选用的单个通道的速率及采样时间值进行设置,数组channel_remap[5]中下标为0、1的元素是ADC1通道,下标为2、3、4的元素是ADC3的通道,通过channel == 0 || channel == 1来开启对应的ADC通道,进行数据采集。对采集到的数值进行均值滤波处理,将最终结果进行电压转换,并由串口打印显示。 ==== 五、 实验步骤 ==== * 1、把仿真器与iCore3的SWD调试口相连(直接相连或者通过转接器相连); * 2、把iCore3通过Micro USB线与计算机相连,为iCore3供电; * 3、打开putty软件,从设备管理器内查看端口号,设置波特率为115200; {{ :icore3:icore3_arm_hal_10_5.png?direct |}} * 4、点击Open; * 5、打开Keil MDK 开发环境,并打开本实验工程; * 6、烧写程序到iCore3上; * Putty出现如下界面: {{ :icore3:icore3_arm_hal_10_6.png?direct |}} * 7、也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。 ==== 六、 实验现象 ==== * iCore3 双核心板红色LED常亮,串口一直向终端输出电源监控的数据。