针对STM32F103RC学习过程中遇到的较为复杂、难以理解的地方,此处对其进行详细分析,以避免学习笔记中容易出错、混淆、看不懂的地方
基于正点原子ALIENTEKmini版,由于其手册对于部分模块的讲解有些模糊,此处记录一些个人理解,便于日后重温
模拟信号:
在时间上连续,在数值上也连续的信号,其伴随的信息不仅与数值有关,也和时间有关
数字信号:
在时间上离散,在数值上只能取得几个固定值,其伴随的信息不仅与数值有关,也和时间有关
相比于模拟信号,数字信号不仅在时间上采样,在数值上也采样
对于单片机的对外交互,模拟信号可理解为电压信号,数字信号理解为每一个时刻、与之对应的寄存器静态数据
ADC模拟-数字转换:
将端口的输入模拟信号,转换为数字信号,便于计算、显示。
可用于测量电压值、传感器数据采集与处理等
转换通道:总20,按照规律顺序进行转换称为规则16,插入打断规则组进行转换称为注入组4
启动方式:规则组既可以中断触发也可以软件触发,注入组仅中断触发
ADCx_INn为被检测通道n
ADC输入模拟电压Ur=(ADC_DR/2^ADC位数N)*标准电压Vcc
DAC数字-模拟转换:
根据数字信号,将数字信号转换为模拟信号,从端口输出,便于外设控制
可用于波形生成,产生目标电平等
转换通道:仅2
了解ADC和DAC原理,学习如何使用ADC和DAC达到具体目的
接下来以一个小实验为例,实验目标如下:
1、温度传感器获取温度信息,并在LCD显示
2、DAC产生数字信号,该数字信号由按键WKUP与KEY0控制,WKUP增大电压值,KEY0减小电压值,在LCD显示输出电压
3、ADC接受上述数字信号,并在LCD显示测量电压
4、usmar串口调试工具可以直接通过串口助手,向DAC设定输出电压值
理清数字信号、模拟信号在本实验中的关系,ADC、DAC在各个环节的具体作用,如图所示
1)信号的作用域:
模拟信号主要用于单片机与外界的交互,例如获取传感器电压、发送信号、控制外设等;
数字信号主要用于单片机内CPU的计算,例如处理输入数据,设计输出数据等;
2)检测信号:
检测即获取传感器数据,由CPU处理,将数字信号可视化的过程。
需要用到ADC模块将模拟信号转化为数字信号,交由单片机处理即可,如有需要则可以再将数据可视化(例如串口发送、LED、LCD等)
3)控制外设:
控制即对于已知原理的外设,给予自定义信号,保证外设按照目标执行。
已知需要的电压值,根据模拟信号近似等于数字信号的原则(位数越大精度越高),根据DAC内数字信号与寄存器数据的关系,由程序设计出相应的寄存器数据,再通过DAC转化为需要的模拟信号进行外设控制
4)实验原理
本实验设计理想模拟信号的产生,和模拟输入的检测,其中DAC负责产生模拟信号,ADC负责检测信号,两者对应的寄存器数据,均可以由CPU计算得到一个响应的电压值,理论上只要精度够高,两者应该一致,但是由于经历了数字信号转换为模拟信号的过程,会带来微小误差
5)误差:
无论是数-模-数,还是模-数-模,经历了ADC/DAC后的信号复现均存在误差,这是由于采样周期和转换位数决定,转换位数越高误差越小,采样周期太长在时间上会有信号滞后失真,采样周期也不能太短,必须等待CPU处理完上一个数据才能继续向CPU传值
1)温度传感:
对于外界输入模拟信号进行处理,得到寄存器数据(数字信号),根据传感器物理量纲关系,计算得到量纲数值
2)信号复现:
由单片机设置/设计一个寄存器数据(数字信号),由DAC转换为模拟信号至端口,再由端口发送至ADC接受,将其再转换为数字信号。此过程中,最初的DAC寄存器数据(原始数字信号),与ADC寄存器数据(接受数字信号)均可换算为电压数值。因此可对比两者,发现两者数值相近
3)数字信号设计:
按键(外设IO口)、串口助手(串口)改变寄存器数据,便可以以此改变相应的数字信号,也就改变了对应的模拟信号。这样就可以实现软件程序设计/设置向外输出的模拟信号电压值
待检测端口时钟使能、模拟输入配置
外设时钟使能寄存器RCC_APB2ENR设置ADC时钟使能、接口复位
外设复位寄存器RCC_APB2RSTR让ADC复位,然后恢复该位(否则一直复位)
时钟配置寄存器RCC->CFGR设置ADC预分频,为保证ADC准确性,频率不可超过14MHz
控制寄存器ADC_CR,设置触发方式、对齐方式
规则序列寄存器ADC_SQR设置转换规则中的序列数
采样时间寄存器ADC_SMPR设置通道的采样时间
控制寄存器ADC_CR,开启AD转换,复位校准及AD校准,等待校准完成硬件复位
规则序列寄存器ADC_SQR匹配规则序列和通道值
状态寄存器ADC_SR判断转换是否结束
规则数据寄存器ADC_DR获得转换结果
使能待输入端口使能,模拟输入配置
外设时钟使能寄存器RCC_APB2ENR设置DAC时钟使能
控制寄存器DAC_CR,设置触发方式、波形产生、输出缓存等,使能DAC
通道n的m位右对齐数据保持寄存器DAC_DHRmRn清空DAC通道,向其写入DAC值
通道n对应IO口模拟电压
DAC输出模拟电压Ur=(DAC_DHRmRn/2^DAC位数m)*标准电压Vcc
#include
#include
//DAC初始化
void DAC_Init(void)
{
RCC->APB2ENR|=1<<2; //使能PA时钟
RCC->APB1ENR|=1<<29; //使能DAC时钟
GPIOA->CRL&=0XFFF0FFFF;
GPIOA->CRL|=0X00000000; //PA4模拟输入
DAC->CR|=3<<0; //不使用屏蔽发生、触发功能、DMA及输出缓存,使能DAC
DAC->DHR12R1=0;
}
//设置通道1输出电压
//mvol 0~3300mV
void DAC_Set_Vol(u16 mvol)
{
float temp=mvol;
temp=temp*4096/3300;
DAC->DHR12R1=temp;
}
#include
#include
#include
//初始化ADC
//仅开启规则通道1
void ADC_Init(void)
{
RCC->APB2ENR|=1<<2; //被检测IO口时钟使能
GPIOA->CRL&=0XFFFFFF0F; //IO口模拟输入
RCC->APB2ENR|=1<<9; //ADC时钟使能
RCC->APB2RSTR|=1<<9; //ADC接口复位
RCC->APB2RSTR&=~(1<<9); //复位一次后手动恢复,避免一直复位
RCC->CFGR&=0XFFFF3FFF; //预分频,不超过14MHz
RCC->CFGR|=0X00008000;
ADC1->CR2|=0X1E0000; //软件触发SWSTART
ADC1->CR2|=1<<23; //开启温度传感器
ADC1->SQR1&=0X0FFFFF; //1个转换在规则序列中(只转换规则序列1)
ADC1->SMPR2&=~(7<<3);
ADC1->SMPR2|=7<<3; //通道1,ADC采样时间(越大输出越准确)
ADC1->SMPR1&=~(7<<18);
ADC1->SMPR1|=7<<18; //通道16设置采样时间
ADC1->CR2|=1<<0; //开启AD转换
ADC1->CR2|=1<<3; //复位校准
while(ADC1->CR2&1<<3); //等待复位校准完成
ADC1->CR2|=1<<2; //AD校准
while(ADC1->CR2&1<<2); //等待AD校准完成
}
//获取ADC值
//ch通道值 0~16
u16 Get_ADC(u8 ch)
{
ADC1->SQR3&=0XFFFFFFE0;
ADC1->SQR3|=ch<<0; //匹配规则序列和通道
ADC1->CR2|=1<<22; //启用规则转换通道
while((ADC1->SR&1<<1)==0);//等待转换结束
return ADC1->DR;
}
/*
多次转换取平均,提高精度
ch 通道值
times 获取次数
此函数应添加到usmart_config.c的func_table内,允许串口助手调用
*/
u16 Get_ADC_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
u16 adc1;
u16 adc16;
u16 dac1;
float temp;
float temperate;
Stm32_Clock_Init(9);
delay_init(72);
uart_init(72,9600);
LED_Init();
KEY_Init();
LCD_Init();
ADC_Init();
DAC_Init();
usmart_dev.init(72);
LCD_Clear(WHITE);
POINT_COLOR=BLACK;
//固定文字部分显示
LCD_ShowString(100,240,200,24,24,"WKUP+ KEY0-");
LCD_ShowString(100,270,200,24,24,"DAC VAL:");
LCD_ShowString(100,300,200,24,24,"DAC VOL:0.000V");
LCD_ShowString(100,330,250,24,24,"ADC VOL:0.000V");
LCD_ShowString(100,360,250,24,24,"TEMPERATE:00.00C");
while(1)
{
switch(KEY_Scan(0))
{
case WKUP_PRES:
if(dac1<4000)dac1+=200;
DAC->DHR12R1=dac1;
break;
case KEY0_PRES:
if(dac1>200)dac1-=200;
else dac1=0;
DAC->DHR12R1=dac1;
break;
default:break;
}
//DAC电压显示
dac1=DAC->DHR12R1;
LCD_ShowxNum(196,270,dac1,4,24,0); //得到dac值
temp=(float)dac1*(3.3/4096);
LCD_ShowxNum(196,300,(u16)temp,1,24,0);//展示整数部分
temp-=(u16)temp;
temp*=1000;
LCD_ShowxNum(220,300,temp,3,24,0x80); //展示3位小数
//ADC电压显示
adc1=Get_ADC_Average(1,10);
temp=(float)adc1*(3.3/4096); //得到电压值(浮点型),但是LCD无法直接展示小数
adc1=temp;
LCD_ShowxNum(196,330,adc1,1,24,0); //展示整数部分
temp-=adc1;
temp*=1000;
LCD_ShowxNum(220,330,temp,3,24,0x80); //展示3位小数
//温度计算显示
adc16=Get_ADC_Average(16,10);
temp=(float)adc16*(3.3/4096);
temperate=(1.43-temp)/0.0043+25;
LCD_ShowxNum(220,360,(u8)temperate,2,24,0);
temperate-=(u8)temperate;
temperate*=100;
LCD_ShowxNum(256,360,temperate,2,24,0);
LED0=0;
delay_ms(10);
}
}
首先注意,此实验硬件配置中,ADC1_IN1与PA1接通,DAC_OUT1与PA4接通,PA1与PA4接通
DAC产生的电压,与ADC测得的电压,数值上几乎保持相等;如图:
按下KWUP则电压值对应增加约0.161V,相应的,按下KEY0则电压值对应减小约0.161V
串口调试助手调用电压设置函数并传入数据,发送给单片机,观察电压值变为设定的电压,如图
可以发现此处出现了一个小问题,温度小数位显示有缺失,这是由于此时小数首位为0,由于我们处理小数显示时,只是将小数部分倍乘100,并未考虑首位为零的情况,例如温度为30.04时,小数乘100为4,整数部分为30,因此显示为30. 4。
这个问题可通过在显示时加入一个判断,对数据进一步处理即可,不是本章重点,此处不予细究
对于程序中的按键、LED、LCD、串口等配置,见 STM32学习笔记(未更新)