STM32 ADC
12位ADC是一种逐次逼近型模拟数字转换器,和比较型ADC相比,逐次逼近型ADC的优点是低功耗,缺点是速度慢。下图为逐次逼近型ADC。
STM32F103的ADC有单次和连续转换两种模式,有通道扫描和单独工作两种模式,并且有校准和触发三种中断,分别是转换结束标志位,注入转换结束标志位,模拟看门狗事件标志位。
其配置过程包括,ADC和GPIO时钟配置,GPIO初始化(输入模式等),ADC开关控制,通道选择,单次/连续转换模式选择,扫描模式,诸如通道和规则通道配置,ADC校准,数据对齐,采样时间,触发方式,以及模式选择和ADC中断。
本示例对ADC的内部通道16进行温度传感器采样以及串口输出。由于是内部通道,因此无需配置GPIO
时钟配置上,ADC支持最大时钟为14MHz,当初始时钟为72MHz时,6分频为12MHz,转换时间图如下,56MHz时,其四分频时最大时钟信号为14MHz,此时恰好满足其最大时钟频率,因此其转换速度比72MHz要快。
校准
ADC有一个内置自校准模式。通过设置ADC_CR2寄存器的CAL位启动校准。一旦校准结束,CAL位被硬件复位,可以开始正常转换。建议在上电时执行一次ADC校准。校准阶段结束后,校准码储存在ADC_DR中。
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准//参数ADC1,ADC2,ADC3
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//
数据对齐
数据对齐分左对齐和右对齐,/16位寄存器,当想降低精度可使用左对齐只读取高位的数据。
示例
//#include "LCD.h"
#include "stm32f10x.h"
//#include <stdio.h>
#include"UART.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
void delay(unsigned int i)
{
while(i--);
}
void delay_ms1(u16 ms)
{
while(ms--)delay(1000);
}
void usart_init()
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 115200; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*USART使能*/
USART_Cmd(USART1, ENABLE);
// /*中断输出配置*/
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
}
void AD_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟
/*设置ADC时钟*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
/*规则组通道配置*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5); //规则组序列1的位置,配置为通道0
/*ADC初始化*/
ADC_InitTypeDef ADC_InitStructure; //定义结构体变量
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续转换,失能,每转换一次规则组序列后停止
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置
ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1
/*ADC使能*/
ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
/**
* 函 数:获取AD转换的值
* 参 数:无
* 返 回 值:AD转换的值,范围:0~4095
*/
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
uint16_t ADValue; //定义AD值变量
//float Voltage; //定义电压变量
float Temperature;
int main(void)
{
usart_init();
//printf("画三个点\n");
AD_Init(); //AD初始化
while (1)
{
ADValue = AD_GetValue(); //获取AD转换的值
//Voltage = (float)ADValue / 4095 * 3.3; //将AD值线性变换到0~3.3的范围,表示电压
Temperature=(1.43-ADValue*3.3/4095)/4.35+25.0;
printf("温度 %f \n",Temperature);
Delay_ms(500); //延时100ms,手动增加一些转换的间隔时间
}
}
重写printf函数输出到串口"UART.c"
#include"UART.h"
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART1->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
“UART.h”
#ifndef __UART_H__
#define __UART_H__
#include "stm32f10x.h"
#include <stdio.h>
#endifc
“UART.h”
#ifndef __UART_H__
#define __UART_H__
#include "stm32f10x.h"
#include <stdio.h>
#endifc