(一)CEU6的ADC
因为之前已经和F411CEU6先生打过招呼了,这次呢,我们就让F411CEU6先生帮助我们完成电压采集的工作,我们要用一下F411CEU6的ADC模块。
这是F411CEU6的寄存器界地址,上面这块是Cortex-M4部分,即内核部分。
查了一下,发现对于STM32F411xC/xE系列,只有一个ADC1外设
厂家在手册里面写得也很清楚,1 个ADC
在Datasheet Charpter 3 Functional Overview 中,也专门提到ADC
在芯片描述里面,提到所有的F411xE/xC芯片都有一个12-bit ADC
F411xC/xE内部总线、外设的联系
F411xC/xE有1个ADC,16个外部输入通道,有single-shot mode和 scan mode两种采样模式。
厂家也交代了可以使用DMA, 并且在电压采样的时候会产生中断,厂家还提供了触发ADC的方法。
FunctionalState ADC_ScanConvMode; /*!< Specifies whether the conversion
is performed in Scan (multichannels)
or Single (one channel) mode.
This parameter can be set to ENABLE or DISABLE */
Scan Mode 就意味着我们在使用的时候,可以使用多个通道
作者特别指出, ADC1 的 ADC_IN18 input channel 和 芯片内部的温度传感器相连,不过也指出这个温度不能准确反映周围环境的温度,但可以侦察温度的变化。
(二)ADCCLK
ADC的时钟可以通过PCLK2通过分频得到,不过ADCCLK存在一个最大值。 参考手册说这个ADCCLK最大值在Datasheet 里面,但在第三章Functional Overview中,一直没有找到,一度怀疑参考手册会不会搞错了。
后来利用CTRL+F 搭配ADC关键字遍历,无意中发现第六章有所介绍。
这里厂家介绍了CEU6的ADC特性,发现在供电电压在2.4V~3.6V时,ADCCLK最大可以是36MHz,典型值是30MHz,和F407是一样的。事实证明,参考手册还是很靠谱的,不要轻易否定。
ADC1的通道
从图中可以看到,ADC1有16个外部通道,不过CEU6只有10个通道
(三)编写代码
实验采用中断的方式读取电压采集值
1、bsp_adc.h
#ifndef __BSP_ADC_H
#define __BSP_ADC_H
#include "stm32f4xx.h"
//ADC GPIO 宏定义
#define DEBUG_ADC_GPIO_PORT GPIOB
#define DEBUG_ADC_GPIO_PIN GPIO_Pin_1
#define DEBUG_ADC_GPIO_CLK RCC_AHB1Periph_GPIOB
// ADC 序号宏定义
#define DEBUG_ADC ADC1
#define DEBUG_ADC_CLK RCC_APB2Periph_ADC1
#define DEBUG_ADC_CHANNEL ADC_Channel_9
// ADC 中断宏定义
#define DEBUG_ADC_IRQ ADC_IRQn
#define DEBUG_ADC_INT_FUNCTION ADC_IRQHandler
void ADC_GPIO_Config(void);
void DEBUG_ADC_Mode_Config(void);
void ADC_NVIC_Config(void);
#endif /* __BSP_ADC_H */
使用的是ADC1的通道9,注意ADC1挂载在APB2上。很多时候,代码烧录后没有现象,极有可能是时钟没有打开。
2、bsp_adc.c
#include "./adc/bsp_adc.h"
void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(DEBUG_ADC_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = DEBUG_ADC_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(DEBUG_ADC_GPIO_PORT, &GPIO_InitStructure);
}
void DEBUG_ADC_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
RCC_APB2PeriphClockCmd(DEBUG_ADC_CLK , ENABLE);
// -------------------ADC Common 结构体 参数 初始化------------------------
// 独立ADC模式
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
// 时钟为fpclk x分频
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
// 禁止DMA直接访问模式
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
// 采样时间间隔
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
// -------------------ADC Init 结构体 参数 初始化--------------------------
ADC_StructInit(&ADC_InitStructure);
// ADC 分辨率
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
// 禁止扫描模式,多通道采集才需要
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
// 连续转换
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//外部触发通道
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
//数据右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//转换通道 1个
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(DEBUG_ADC, &ADC_InitStructure);
//---------------------------------------------------------------------------
// 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
ADC_RegularChannelConfig(DEBUG_ADC, DEBUG_ADC_CHANNEL, 1, ADC_SampleTime_3Cycles);
// ADC 转换结束产生中断,在中断服务程序中读取转换值
// ADC interupt at the end of convert(EOC)
ADC_ITConfig(DEBUG_ADC, ADC_IT_EOC, ENABLE);
// 使能ADC
ADC_Cmd(DEBUG_ADC, ENABLE);
}
// 配置中断优先级
void ADC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_ADC_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
3、中断
extern __IO uint16_t ADC_ConvertedValue;
// ADC 转换完成中断服务程序
void ADC_IRQHandler(void)
{
if(ADC_GetITStatus(ADC1,ADC_IT_EOC)==SET)
{
// 读取ADC的转换值
ADC_ConvertedValue = ADC_GetConversionValue(ADC1);
}
ADC_ClearITPendingBit(ADC1,ADC_IT_EOC);
}
ADC_ConvertedValue 是在主函数内定义的一个变量,用来提取转换的VALUE值,因为是12-bit的ADC, 所以选用uint16_t是足够大的。
4、主函数
#include "stm32f4xx.h"
#include "./usart/bsp_debug_usart.h"
#include "./adc/bsp_adc.h"
/**
* @brief main function
* @param none
* @retval none
*/
void delay(uint32_t count)
{
for(; count != 0; count-- );
}
__IO uint16_t ADC_ConvertedValue;
float ADC_Voltage;
int main(void)
{
Debug_USART_Config();
ADC_GPIO_Config();
DEBUG_ADC_Mode_Config();
ADC_NVIC_Config();
//开始adc转换,软件触发
ADC_SoftwareStartConv(ADC1);
while(1)
{
printf("\r\n Sir, the converted value is 0x%03x. \r\n", ADC_ConvertedValue);
ADC_Voltage = (float)((float) 3.3/4096)*ADC_ConvertedValue;
printf("\r\n Sir, the voltage value is %0.2f V. \r\n", ADC_Voltage);
delay(0xfffff);
}
}
调用USART6来查看ADC1的电压采集情况
(四)实验现象
ADC1已经可以正常读取采集到的电压值
(五)总结
至此,关于CEU6核心板的小实验就告结束了。简单进行一个小结;
- F411CEU6和F4其他系列芯片类似,完全可以使用固件库编程。网上有人说,这个F411只能通过HAL库进行烧写,这就很离谱了。从原理上看,只要能配置寄存器就能使用微控制器;从ST官方提供的固件库看,里面有关于F411CE芯片的相关宏定义,既然都有宏定义了,就肯定可以使用固件库开发呀;
- 当ST32芯片处于奇怪状态的时候,BOOT引脚可能会帮上大忙;
- 对于芯片的了解,必须要根据公司提供的参考手册和数据手册,以公司官方出厂的为准;
- 复位时尽量多按一会儿按键,确保实现程序复位,有时候可能是没有实现复位或程序没有真正烧写成功带来的问题;
- 确保正确开启各个外设的时钟,因为不同外设挂载在不同的总线上,如果外设时钟没有开,那么肯定是没有实验现象的。我在第一次对F411进行程序烧写的时候,当时程序烧录完之后,明明设置的是关闭板载的LED,但下载代码之后,进行复位,LED总是会亮。后来,我去下载厂家提供的代码,下载后LED闪烁了,说明程序成功烧录了。然后,我再次重新下载自己的程序,发现又可以了。我自己认为有三种可能性:第一种,时钟开启不正常;第二种,程序还是没有烧录成功;第三种,没有真正复位,复位按键应该多按一会儿。