本篇文章由 VeriMake 旧版论坛中备份出的原帖的 Markdown 源码生成
原帖标题为:以 CH549 为例的 51 教程 | 图文 7. 模拟信号与数字信号的互相转换
原帖网址为:https://verimake.com/topics/262 (旧版论坛网址,已失效)
原帖作者为:Xy(旧版论坛 id = 73,注册于 2020-10-15 13:03:26)
原帖由作者初次发表于 2021-08-19 15:41:03
📇 点此回到教程主索引
目录
ADC DAC 介绍
ADC 实验与具体代码解析
DAC 实验与具体代码解析
ADC DAC 介绍
ADC 是 Analog-to-Digital Converter,DAC 是 Digital-to-Analog Converter。
A 即 Analog 模拟的
D 即 Digital 数字的
即,ADC 是模数转换器,DAC 是数模转换器。
以MCU的角度看ADC是用于读取电压值,而DAC是用于产生模拟电压的。
ADC 实验与具体代码解析
根据下图连接电路:
如此连接的目的是让p0.7口的模拟电压可以通过电位器进行调节。
具体代码 main.c
/**
******************************************************************
* @file main.c
* @author 甜鱼酱,xy
* @version V1.0
* @date 2021-5-19
* @brief SPI
******************************************************************
* @attention
* verimake 用于
* 在 oled 屏幕上显示 p0.7 的电压值
******************************************************************
*/
#include <CH549_sdcc.h> //ch549的头文件,其中定义了单片机的一些特殊功能寄存器
#include <CH549_OLED.h> //其中有驱动屏幕使用的函数
#include <CH549_BMP.h> //用于显示图片的头文件
#include <CH549_DEBUG.h> //CH549官方提供库的头文件,定义了一些关于主频,延时,串口设置,看门口,赋值设置等基础函数
#include <CH549_SPI.h> //CH549官方提供库的头文件,定义了一些关于SPI初始化,传输数据等函数
#include <CH549_ADC.h> //CH549官方提供库的头文件,定义了一些关于ADC初始化,采集数据等函数
/********************************************************************
* TIPS:
* adc各通道对应引脚关系
* P1.1 AIN1
* P1.2 AIN2
* P1.3 AIN3
* P1.4 AIN4
* P1.5 AIN5
* P1.6 AIN6
* P1.7 AIN7
* P0.0 AIN8
* P0.1 AIN9
* P0.2 AIN10
* P0.3 AIN11
* P0.4 AIN12
* P0.5 AIN13
* P0.6 AIN14
* P0.7 AIN15
*********************************************************************/
int oled_colum;
int oled_row;
/********************************************************************
* 函 数 名 : setCursor
* 函数功能 : 设置printf到屏幕上的字符串起始位置
* 输 入 : 行坐标 列坐标
* 输 出 : 无
********************************************************************/
void setCursor(int column,int row)
{
oled_colum = column;
oled_row = row;
}
/********************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************/
void main(void)
{
UINT8 ch;
float Voltage;
CfgFsys(); //CH549时钟选择配置
mDelaymS(20);
/* OLED的初始化程序 */
SPIMasterModeSet(3); //SPI主机模式设置,模式3
SPI_CK_SET(12); //12分频
/* 初始化OLED */
OLED_Init();
OLED_Clear();
/* ADC的初始化配置 */
ADC_ExInit(3); //ADC初始化,选择采样时钟
/* 主循环 */
while (1)
{
ch = 15;
ADC_ChSelect(ch); //选择通道
ADC_StartSample(); //启动采样
while ((ADC_CTRL & bADC_IF) == 0)
{
; //查询等待标志置位
}
ADC_CTRL = bADC_IF; //清标志
Voltage = (ADC_DAT / 4095.0)*5.0 ;
/*
adc采集到的值为ADC_DAT,由于ch549是12位ADC模数转换器因此ADC_DAT为0-4095之间的数,它表达的含义是电压值的相对高低
因此 实际电压值的算法为ADC_DAT除以4095乘以ch549所使用的电压5v
*/
setFontSize(16); //设置显示在oled上屏幕的字号
setCursor(0,0);//设置printf到屏幕上的字符串起始位置
printf_fast_f("AD CJ %fV",Voltage);//显示数据到屏幕上 CJ含义为caiji(采集)
mDelaymS(300);
}
}
/********************************************************************
* 函 数 名 : putchar
* 函数功能 : 将printf映射到oled屏幕输出上
* 输 入 : 字符串
* 输 出 : 字符串
********************************************************************/
int putchar( int a)
{
OLED_ShowChar(oled_colum,oled_row,a);
oled_colum+=8;
if (oled_colum>120){oled_colum=0;oled_row+=2;}
return(a);
}
使用ADC进行采集模拟电压的步骤为:
1.使用ADC_ExInit()函数进行ADC初始化。
2.使用ADC_ChSelect()函数选择ad通道。
3.使用ADC_StartSample()函数启动采样。
4.等待采样完成。
5.获得 采样后数据 ADC_DAT。
6.由于ch549是12位ADC模数转换器因此ADC_DAT为0-4095之间的数,它表达的含义是电压值的相对高低,因此 实际电压值的算法为ADC_DAT除以4095乘以ch549所使用的电压5v。
将例程编译下载进开发板,可以看到当旋转电位器的时候,屏幕上p0.7采集到的电压发生了改变。
DAC 实验与具体代码解析
由于ch549没有硬件DAC,可以通过使用pwm波加一个简单的积分电路达到DAC的效果。
左边输入的pwm经过这个由一个电阻和一个电容组成的积分电路之后就会变为一条笔直模拟电压,具体的电压值为pwm占空比*5v
根据上图连接电路,如下图所示:
具体代码 main.c
/**
******************************************************************
* @file main.c
* @author 甜鱼酱,xy
* @version V1.0
* @date 2021-5-19
* @brief DA
******************************************************************
* @attention
* verimake 用于
* 输出pwm通过积分电路达到DA的效果,在通过AD采集显示在屏幕上
******************************************************************
*/
#include <CH549_sdcc.h> //ch549的头文件,其中定义了单片机的一些特殊功能寄存器
#include <CH549_OLED.h> //其中有驱动屏幕使用的函数
#include <CH549_BMP.h> //用于显示图片的头文件
#include <CH549_DEBUG.h> //CH549官方提供库的头文件,定义了一些关于主频,延时,串口设置,看门口,赋值设置等基础函数
#include <CH549_SPI.h> //CH549官方提供库的头文件,定义了一些关于SPI初始化,传输数据等函数
#include <CH549_ADC.h> //CH549官方提供库的头文件,定义了一些关于ADC初始化,采集数据等函数
#include <CH549_PWM.h> //CH549官方提供库的头文件,定义了一些关于pwm初始化,设置等函数
/********************************************************************
* TIPS:
* adc各通道对应引脚关系
* P1.1 AIN1
* P1.2 AIN2
* P1.3 AIN3
* P1.4 AIN4
* P1.5 AIN5
* P1.6 AIN6
* P1.7 AIN7
* P0.0 AIN8
* P0.1 AIN9
* P0.2 AIN10
* P0.3 AIN11
* P0.4 AIN12
* P0.5 AIN13
* P0.6 AIN14
* P0.7 AIN15
*********************************************************************/
int oled_colum;
int oled_row;
/********************************************************************
* 函 数 名 : setCursor
* 函数功能 : 设置printf到屏幕上的字符串起始位置
* 输 入 : 行坐标 列坐标
* 输 出 : 无
********************************************************************/
void setCursor(int column,int row)
{
oled_colum = column;
oled_row = row;
}
/********************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************/
void main(void)
{
UINT8 ch;
UINT8 i=0;
float Voltage;
float Output_Voltage;
float Output_Baud;
CfgFsys(); //CH549时钟选择配置
mDelaymS(20);
/* OLED的初始化程序 */
SPIMasterModeSet(3); //SPI主机模式设置,模式3
SPI_CK_SET(12); //12分频
/* 初始化OLED */
OLED_Init();
OLED_Clear();
/* ADC的初始化配置 */
ADC_ExInit(3); //ADC初始化,选择采样时钟
/* PWM的初始化配置 */
SetPWMClkDiv(1); //PWM时钟配置,Fsys/4,Fsys默认为12Mhz
SetPWMCycle256Clk(); //PWM周期 Fsys/4/256
PWM_SEL_CHANNEL(CH3,Enable); //对ch3,即p2.2的pwm外设初始化
/* 主循环 */
while (1)
{
/* 配置占空比 */
SetPWM3Dat(i);//配置通道3即p2.2的占空比,占空比等于i/256占空比
mDelaymS(1000);
ch = 15;
ADC_ChSelect(ch); //选择通道
P0_MOD_OC = 0;
P0_DIR_PU=0;
ADC_StartSample(); //启动采样
while ((ADC_CTRL & bADC_IF) == 0)
{
; //查询等待标志置位
}
ADC_CTRL = bADC_IF; //清标志
Voltage = (ADC_DAT / 4095.0)*5.0 ;
/*
adc采集到的值为ADC_DAT,由于ch549是12位ADC模数转换器因此ADC_DAT为0-4095之间的数,它表达的含义是电压值的相对高地
因此 实际电压值的算法为ADC_DAT除以4095乘以ch549所使用的电压5v
*/
Output_Baud = (i/256.0)*100;
setFontSize(16); //设置显示在oled上屏幕的字号
setCursor(0,0);//设置printf到屏幕上的字符串起始位置
printf_fast_f("PWM %f%% ",Output_Baud);//把p2.2输出pwm波的波特率显示在屏幕上
Output_Voltage = (i/256.0)*5.0;
setFontSize(16); //设置显示在oled上屏幕的字号
setCursor(0,2);//设置printf到屏幕上的字符串起始位置
printf_fast_f("DA SC %fV ",Output_Voltage);//把积分后的电压预测数据显示屏幕上 SC含义为shuchu1(输出)
setFontSize(16); //设置显示在oled上屏幕的字号
setCursor(0,4);//设置printf到屏幕上的字符串起始位置
printf_fast_f("AD CJ %fV ",Voltage);//显示数据到屏幕上 CJ含义为caiji(采集)
i+=10;
}
}
/********************************************************************
* 函 数 名 : putchar
* 函数功能 : 将printf映射到oled屏幕输出上
* 输 入 : 字符串
* 输 出 : 字符串
********************************************************************/
int putchar( int a)
{
OLED_ShowChar(oled_colum,oled_row,a);
oled_colum+=8;
if (oled_colum>120){oled_colum=0;oled_row+=2;}
return(a);
}
DA例程其实是由pwm例程和AD例程整合而成,目的是使用上文提到的积分电路将p2.2输出的逐步提升占空比的pwm波积分为模拟电压,然后让p0.7通过ADC进行采集。
将代码编译下载进开发板。
oled屏幕上显示了三行数据
第一行是p2.2输出的PWM占空比,
第二行是经过积分电路之后输出模拟电压的预测值,
第三行是p0.7通过AD实际采集到模拟电压的数据值.
可以看出第二行与第三行数据差距不大,表示搭建的DAC电路成功。