iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等
第三十四章Cortex-M4 ADC实验
本章节最终所完成的实验例程存放路径为“iTOP-STM32MP157开发板网盘资料汇总\06_Cortex-M4实验例程\10_ADC.zip”。
34.1 ADC简介
34.1.1 ADC初步认识
ADC即Analog-to-Digital Converter(模拟数字转换器),将连续变化的模拟信号转换离散的数字信号的器件。STM32MP157 系列有 2 个 ADC(ADC1 和 ADC2),每个 ADC 都可以独立工作,每个 ADC都可以单独分配给 A7 或者 M4 内核来使用。
真实世界中的模拟信号,例如温度、湿度、音量、压力或者图像等等模拟信号,这些信号在时域上是连续的,需要转换成 MCU 更容易储存、处理和发射的数字形式信号,这个就需要模/数转换器了。
34.1.2 ADC 框图
为了更好的理解STM32MP157的ADC工作原理,我们将数据手册的ADC框图在下方进行了展示。并对相对应的功能进行了区域的划分,如下我们分成了六个区域,分别为
- VREF+电压区域
- ADC 的双时钟域架构区域
- ADC输入通道区域
- ADC转换序列区域
- ADC触发源区域
- ADC转换时间区域
- ADC数据存储区域
具体框图如下图所示:
34.1.2.1 VREF+电压区域
ADC 输入范围为:VREF- ≤ VIN ≤ VREF+。由 VREF-、VREF+ 、VDDA 、VSSA、这四个外部引脚决定。我们在设计原理图的时候一般把 VSSA 和 VREF- 接地,把 VREF+ 和 VDDA 接 3V3,得到 ADC 的输入电压范围为:0~3.3V。如果我们想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,我们可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量了。
34.1.2.2 ADC 的双时钟域架构区域
STM32MP157 采用双时钟结构,彼此之间不会互相影响, 。ADC 外设时钟是来自于 AHB,而 ADC 的内核时钟,可以通过 ADCx_CCR 的位 CKMODE 来选择,分别有:
- ADC 的外设时钟通过 1 分频、2 分频或者四分频得到的时钟。
二、选择 ADC 内核时钟(配置RCC 寄存器 RCC_D3CCIPR 的位 ADCSEL[1:0] 来选择内核时钟的来源,可以是外设时钟,PLL2P和 PLL3R)经过 PREC[3:0] 分频得到的时钟。
34.1.2.3 ADC输入通道区域
STM32MP157 的每个 ADC 最多有 20 个多路复用模拟通道。因为 STM32MP157 的ADC支持差分通道输入,因此有 ADCx_INP[19:0]和 ADCx_INN[19:0]两组信号,其中,INP 是差分正向输入,INN 是差分反向输入,其中的 ADC_INP[0:5]和 ADC_INN[0:5]是快速模拟输入,ADC_INP[6:19]和 ADC_INN[6:19]是慢速模拟输入。
34.1.2.4 ADC转换序列区域
规则通道组和注入通道组
每个ADC模块通过内部的模拟多路开关,可以切换到不同的输入通道并进行转换。STM32特别地加入了多种成组转换的模式,可以由程序设置好之后,对多个模拟通道自动地进行逐个地采样转换。
有2种划分转换组的方式:规则通道组和注入通道组。通常规则通道组中可以安排最多16个通道,而注入通道组可以安排最多4个通道。在执行规则通道组扫描转换时,如有例外处理则可启用注入通道组的转换。
一个不太恰当的比喻是:规则通道组的转换好比是程序的正常执行,而注入通道组的转换则好比是程序正常执行之外的一个中断处理程序。
规则序列
规则序列寄存器有4个,分别为SQR1、SQR2、SQR3、SQR4。SQR1控制着规则序列中的第1个到第4个转换,对应的位为:SQ1[4:0]~SQ4[4:0],第一次转换的是位4:0 SQ1[4:0],如果通道16想第一次转换,那么在SQ1[4:0]写16即可。SQR2控制着规则序列中的第5到第9个转换,对应的位为:SQ5[4:0]~SQ9[4:0],如果通道1想第8个转换,则SQ8[4:0]写1即可。SQR4控制着规则序列中的第10到第14个转换,对应位为:SQ10[4:0]~SQ14[4:0],如果通道6想第10个转换,则SQ10[4:0]写6即可。具体使用多少个通道,由SQR1的位L[3:0]决定,最多16个通道。
注入序列
注入序列寄存器JSQR只有一个,最多支持4个通道,具体多少个由JSQR的JL[2:0]决定。转换顺序与规则序列寄存器SQR一样。
注入通道的转换可以打断常规通道的转换,在注入通道被转换完成之后,常规通道才得以继续转换。
34.1.2.5 ADC触发源区域
ADC 的触发转换有两种方法:分别是通过软件或外部事件(也就是硬件)触发转换。
软件触发:由ADC控制寄存器:ADC_CR的ADSTART这个位来控制,写1的时候开始转换,写0的时候停止转换。在本章节的实验我们就采用这种方法。
硬件触发:这个触发包括内部定时器触发和外部IO触发,由ADC控制寄存器:ADC_CR的EXTSEL[4:0]和ADC_JSQR的JEXTSEL[4:0]位来控制。EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[4:0]用于选择注入通道的触发源。 如果使能了外部触发事件,我们还可以通过设置ADC控制寄存器:ADC_CR的EXTEN[1:0]和ADC_JSQR的JEXTEN[1:0]来控制触发极性,可以有4种状态,分别是:禁止触发检测、上升沿检测、下降沿检测以及上升沿和下降沿均检测。
34.1.2.6 ADC转换时间区域
ADC需要若干个ADC_CLK周期完成对输入的电压进行采样,采样的周期数可通过ADC采样时间寄存器ADC_SMPR1和ADC_SMPR2中的SMP[2:0]位设置,ADC_SMPR1控制的是通道0~9,ADC_SMPR2控制的是通道10~19。每个通道可以分别用不同的时间采样。
STM32MP157 的 ADC 总转换时间的计算公式如下:
TCONV = 采样时间(TSMPL) + 逐次逼近时间(TSAR)
所有通道都可以通过编 程来控制使用不同的采样时间,可选采样时间值如下:
SMP设定值 | 对应的采样周期 |
000 | 1.5 个 ADC 时钟周期 |
001 | 2.5 个 ADC 时钟周期 |
010 | 8.5 个 ADC 时钟周期 |
011 | 16.5 个 ADC 时钟周期 |
100 | 32.5 个 ADC 时钟周期 |
101 | 64.5 个 ADC 时钟周期 |
110 | 387.5 个 ADC 时钟周期 |
111 | 810.5 个 ADC 时钟周期 |
逐次逼近时间(TSAR)是由分辨率决定的,分辨率通过对 ADC_CFGR 寄存器的 RES[1:0] 位进行编程,可将分辨率配置为 16 位、14 位、12 位、10 位、8 位。而逐次逼近时间和分辨率的对应关系如下表所示:
RES | TSAR ADC 时钟周期 |
16 | 8.5 个 ADC 时钟周期 |
14 | 7.5 个 ADC 时钟周期 |
12 | 6.5 个 ADC 时钟周期 |
10 | 5.5 个 ADC 时钟周期 |
8 | 4.5 个 ADC 时钟周期 |
我们的接下来的实验中 ADC 的时钟源采用的为64M时钟 hsi_ker_ck 经过 2 分频得到,即 32MHZ。我们就以 Fadc_ker_ck 的频率为 32MHZ 来举例,我们配置 SMP = 111,即设置最大采样周期,然后采用 16 位分辨率,那么得到:
TCONV = 810.5 个 ADC 时钟周期 + 8.5 个 ADC 时钟周期 = 819 个 ADC 时钟周期可得到:
34.1.2.7 ADC数据存储区域
这是 ADC 转换完成后的数据输出寄存器。其中 RDATA[31:0]用于保存常规通道的转换结果,JDATA1~4[31:0]用于保存注入通道的转换结果
34.2 实验目的
1)学习使用STM32CubeIED配置UART串口
2)学习使用STM32CubeIED配置ADC
3)STM32CubeIED的熟练
本次实验所用到的外设为串口和ADC电位器,串口在之前的章节已经讲解过,这里就不多余赘述,重点是adc电位器,原理图如下图所示:
当我们旋转旋钮时会改变滑动变阻器的电阻值,我们要读取的就是ANA0引脚上的电压,然后通过串口打印出来。
34.3实验步骤
34.3.1建立ADC工程
首先我们打开STM32CubeIDE软件,进入软件界面之后,我们点击File属性,选择NEW下的STM32 Project的选项,如下图所示:
然后我们会进入下图所示界面:在Part Number选择框输入STM32MP157A,然后在右边的选择界面选择STM32MP157AAA,然后点击Next选项
在Project Name框中输入工程名字ADC,然后点击Finish选项即可,如下图所示:
等待工程创建完毕,会询问我们是否要安装OpenSTLinux ,由于我们是在windows环境下,所以我们不需要安装,点击NO即可
至此我们的工程创建完毕,进入工程界面如下图所示界面:
34.3.1.1串口引脚的功能配置
首先我们在下面的搜索框之中输入我们要配置的引脚,我们在这里以PB2为例进行搜索,输入名称之后,对应的引脚在工程中会闪烁,如下图所示:
然后我们使用鼠标左键点击对应的引脚会弹出PB2的复用功能选择,我们在这里选择复用为UART4_RX功能,如下图所示:
用同样的方法对PG11进行搜索,然后我们使用鼠标左键点击对应的引脚会弹出PG11的复用功能选择,我们在这里选择复用为UART4_TX功能,如下图所示:
配置完成之后打开左侧菜单的 System CoreàGPIO 进入 GPIO 模式配置界面:如下图所示:
点击对应的引脚配置之后会弹出右下方的管脚配置界面,如上图所示:
在下方会列出要配置选项的具体说明和我们要进行的配置。
选项 GPIO Pull-up/Pull-down 用来设置 IO 口是上拉/下拉/没有上下拉。本实验我们设置为上拉(Pull-up)。
选项 GPIO mode用来设置 GPIO 口的模式,这里默认为alternate function,也就是复用功能。
选项 User Label 是用来设置初始化的 IO 口 Pin 值为我们自定义的宏,这里我们填写为 UART_RX。按照如上要求设置后的界面如下(由于PG11的配置相同,只是最后的Label值不同,也在下方列了出来):
而PG11和PB2的设置相似,只是多了出了一个选项:选项 Mzximum ouput speed 用来设置 IO 口输出速度为低速(Low)/中速(Medium)/高速 (Hign)/快速(Very High)。我们设置为高速Very High 。
设置完成之后在左侧菜单找到Connectivity下的UART4,打开之后首先选中Cortex-M4,然后配置Mode为Asynchronous(也就是异步通信模式),配置完成之后如下如图所示:
34.3.1.2 ADC配置
然后我们在搜索框之中输入ANA0,对应的引脚在工程中会闪烁,如下图所示:
然后我们使用鼠标左键点击对应的引脚会弹出ANA0的复用功能选择,我们在这里选择复用为ADC1_INP0功能,如下图所示:
配置完成之后打开左侧菜单的 System Core,进入Analog属性栏下的 ADC1的配置界面,首先将ADC1分配给M4内核,然后我们勾选IN0 Single-ended,配置完成如下图所示:
在ADC1 mode and Configuration 界面底部有我们的外部触发设置选项,由于我们没有使用硬件触发,使用的是软件触发,所以 EXTI Conversion Trigger选项要 Disable(默认为Disable),如下图所示:
然后我们在同一界面下的右下角可以看到Configuration功能配置界面,选择Parameter Setting 功能设置,软件已经默认为我们设置好了,我们只需要修改Clock Prescaler 时钟分频设置为2,Sampling Time 采样周期选择 810.5Cycles,设置完成如下图所示:
34.3.1.3 NVIC 配置
设置完成之后在左侧菜单找到Connectivity下的UART4,找到右下方的configuration配置栏,然后进入NVIC Settings功能设置界面,使能UART4 global interrupt,配置完成如下图所示:
然后需要在在左侧菜单栏中的System Core下选择NVIC可以看到Time base:System tick timer 的优先级默认设置为15,而串口中断的优先级默认为1,串口中断的优先级是高于System tick timer 优先级的,所以我们要对优先级进行修改,将Time base:System tick timer 的优先级设置为1,而串口中断的优先级设置为2,修改完成如下图所示:
34.3.1.4时钟配置
NVIC配置完成之后在左侧菜单找到System Core下的RCC,选择打开 HSE(也就是使用外部高速时钟),并选择Crystal/Ceramic Resonator 晶体/陶瓷谐振器选项。
选择HSE(高速外部时钟)之后,打开时钟设置界面,首先将PLL3 Source Mux 设置为HSE,然后设置MCU Clock Mux为PLL3P,设置完成之后在对应的功能框之中手动输入 209 以后按下回车键,STM32CubeMX 插件会自动计算分频和倍频系数,然后在APB1DIV、APB2DIV、APB3DIV处输入 2,因为 APB1、APB2、APB3时钟最大值只能为 104.5MHz,所以我们要手动设置分频值,设置完成之后如下图所示:
然后找到ADC1的时钟设置,我们在这里选择中间的PER,其中 PER 时钟源默认使用 HSI,即为 64MHz。我们在上面ADC参数配置中,我们配置分频系数为2,所以实际 ADC 的时钟是32MHz。
至此我们的基本设置就完成了,最后需要在Project Manage下的Code Generator选项下勾选 Generate peripheral initialization as a pair of ".c/.h' files per peripheral 选项,这样可以独立生成对应外设的初始化.h 和.c 文件(方便配置的查看),如下图所示:
34.3.2工程的生成与完善
在上述的步骤完成之后,按下键盘的“Ctrl+S”组合键保存保存 UART.ioc 文件,系统开始生成初始化代码,工程生成之后如下图所示:
然后我们进行工程的完善,以及添加对应的逻辑代码。
34.3.2.1 uart.h文件的完善
首先打开uart.h文件,文件存放位置如下图所示:
进入uart.h文件后在/* USER CODE BEGIN Private defines */和/* USER CODE END Private defines */之间添加定义
uint8_t RxBuffer;
用来设置串口发送和接收缓冲区的大小。添加完成如下图所示:
34.3.2.2 uart.c文件的完善
首先打开uart.h文件,文件存放位置如下图所示
进入uart.c文件后在/* USER CODE BEGIN 1 */和/* USER CODE END 1 */之间添加回调函数,在没有编写回调函数的时候,串口只能完成一次发送和接收,在完成一次中断接收以后,就关闭串口中断了,该回调函数的作用是实现字符的循环发送和接收,添加内容如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
HAL_UART_Transmit(&huart4,&RxBuffer,1,0);
HAL_UART_Receive_IT(&huart4,&RxBuffer,1);
}
添加完成之后如下图所示:
34.3.2.3 adc.c文件的完善
首先打开uart.h文件,文件存放位置如下图所示:
进入adc.c文件后在/* USER CODE BEGIN 0 */和/* USER CODE END 0 */之间添加函数定义内容如下:
uint32_t adc_get_result_average(uint32_t ch, uint8_t times);
添加完成如下图所示:
然后在/* USER CODE BEGIN 1 */和/* USER CODE END 1 */之间添加adc_get_result_average函数内容,该函数的主要内容是为确定通道并使能ADC并且确定求平均值的次数,添加内容如下:
uint32_t adc_get_result_average(uint32_t ch, uint8_t times)
{
uint32_t temp_val = 0;
uint8_t t;
HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET,ADC_SINGLE_ENDED);
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 0);
for (t = 0; t < times; t++)
{
temp_val += HAL_ADC_GetValue(&hadc1);
HAL_Delay(5);
}
return temp_val / times;
}
添加完成如下图所示:
34.3.2.4 main.c文件的完善
最后是我们要修改的main.c文件路径如下图所示:
在/* USER CODE BEGIN 0 */和/* USER CODE END 0 */之间添加以下内容,其中adcx表示ADC转换值,temp表示实际电压值:
uint16_t adcx;
float temp;
添加完成如下图所示:
在 /* USER CODE BEGIN 2 */和/* USER CODE END 2 */之间添加以下内容,以中断的方式接收数据:
HAL_UART_Receive_IT(&huart4,&RxBuffer,1);
添加完成如下图所示:
在whlie循环中即在/* USER CODE BEGIN 3 */和 /* USER CODE END 3 */之间添加以下内容,获取电压的转换值然后进行换算换算成实际的电压值。
adcx = adc_get_result_average(ADC_CHANNEL_0, 10);
temp = (float)adcx * (3.3 / 65536);
printf("ADC Value = %d, Voltage = %.3fV\r\n", adcx, temp);
HAL_Delay(3000);
添加完成如下图所示:
这时可以看到打印函数报错,原因是默认设置中不支持浮点,我们需要手动来进行设置的,在左侧工程页面,右击ADC_CM4,弹出选项,我们选择properties配置选项,如下图所示:
打开之后如下图所示:
根据下图对右下角允许输入输出浮点数选项进行勾选:
勾选完成之后点击Apply and Close 按钮,回到main.c文件,可以看到错误消失了,然后我们在/* USER CODE BEGIN 4 */和/* USER CODE END 4 */之间添加以下内容:
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
while ((UART4->ISR & 0X40) == 0);
UART4->TDR = (uint8_t) ch;
return ch;
}
该函数的目的是实现串口输出重定向,重定向是指将fputc里面的输出指向目标设备。因printf函数调用了fputc,而fputc输出有默认指向的目标,且不同库中的fputc输出指向不同,所以需要重写fputc。
34.3.4工程的编译
在完成以上步骤之后我们点击工具栏的小锤子进行编译,编译图标如下图所示:
编译完成会在下方的终端中显示打印信息,如下图所示:
如果报错,需要自己根据错误的提示信息来进行问题的寻找和改正。
本例程实现的最终功能是,发送一个字符串,会将发送的字符串打印出来,如下图所示:
34.3.5工程的调试
由于STM32MP157的裸机部分和一般的单片机有些区别,他没有内部的存储,所以只能在程序编译成功之后,通过debug的方式来进行调试(将程序放在内存之中),调试过程如下:
首先,点击菜单栏中的小甲虫Debug调试按钮,弹出以下界面,
在弹出来的界面,按步骤,选择响应的属性(该步骤为Jlink的步骤,如果是STLink,调试探头选择对应的即可)。如下图所示:
选择完成之后,点击右下角的Debug按钮,点击之后,会进行再一次的编译,编译完成之后会弹出如下内容(作者用的是J-LinK),这里弹出的是J-link关于设备的选择,不同调试器的弹窗可能会不同
在弹出来的界面中,选择Accept接受,会弹出以下内容,继续点击下方的OK。
之后会来到设备选择界面,我们选择Cortex-M4,如下图所示:
选择Cortex-M4之后,点击右下角的OK,会弹出以下界面,选择右下角Switch.
然后会弹出一个新的页面,选择菜单栏的 resume按钮开始调试。
连接好串口之后,打开串口调试助手,可以看到我们ANA0引脚输出电压为1.698V,如下图所示:
我们使用万用表对该引脚电压进行实际测量为1.7V,跟我们的串口打印值相近,证明我们的实验正确,万用表测量值如下图所示:
随后我们,对电位器旋钮进行旋转,可以看到串口打印数值发生了变化,如下图所示:
至此我们的测试就完成了。如果想关闭调试,则点击菜单栏的终止按钮即可。