目录
一、摘要
- 在遥控器的电路板设计完成后,我们就要开始我们本项目的第二大部分--程序部分;
- 本章是程序的第一章,为了第一次做该项目的同学能循序渐进,便于理解,在程序部分,我将程序模块化并依次介绍本项目中所用到的各模块的详细内容以及程序编写;
- 这一章我将介绍ADC采集,在本项目中ADC用于采集摇杆的行程位置不同所产生的信息以及电池电量的信息;
二、ADC介绍
- ADC其实就是模数转换,顾名思义就是模拟量转换为数字量,那么什么是模拟量呢,比如我们常说的温度湿度,电压电流等等都是模拟量,而数字量就是我们芯片可以处理计算的量;其实最终都是0101的信号,这就是数字量,并且模拟量是连续的,而数字量是是不连续的(后面第6中最后介绍为什么不连续);
- 为什么要转换为数字量呢?是因为我们的芯片可以处理的就是数字量,当我们把温度量化以后,我们就可以去计算、显示、传输这个数字量,我们对这个数据使用起来就方便多了;
- 首先我们需要有被采集模拟量对应的传感器,传感器基准电压要和单片机的ADC或者外设的ADC芯片(有些单片机没有ADC接口)基准电压一致;因为传感器最终转换回来的信号就是电压,所以我们把电压采集回来转换为数字信号就可以了;
- 如何转换?这里我简单说一下逐次逼近法,我们先选择基准电压最大值的一半,实际电压的大小比较,偏小就再增加总电压的1/4,偏小减少总电压的1/4,再次比较,偏大增加1/8,偏下减少1/8,以此类推,最终会选出最接近的电压值所对应的数值;
- 有一个重要的转换参数叫分辨率,比如分辨率为8,那么4中的逼近中最多会分为256份,如果电压3.3V,采样回来数据为100,则被采电压为3.3*100/256V;输入电压:0~3.3V,转换结果为0~256;
- 还有一个参数要采样率,采样不是完全连续的,一定是每俩次中间有一定时间的,不管时间有多么短,采样率越大,每秒中采样的次数越多,数据越多,越连续;所以数字量不是连续的;
- 这部分内容我这里只介绍大致理解,我讲的比较笼统,各位有为学习过该内容的可以专门去学习一下,ADC在每个地方原理概念是相通的,所以单独学习是有必要的;
三、STM32F103C8T6中不同的ADC模式
注:图片来源为(江科大自化协),在B站可以找到他的视频,stm32讲的很好很清楚,入门stm32很适合,他讲的是标准库,但原理都是一样的,大家可以听一下他的课程,相信好多内容会茅塞顿开的,课程是免费的。
stm32ADC不止一个通道,我们可以配置一个或者多个通道,并且为他们排任意一个序列,单次转换代表这些通道按序列依次转换完成就停止;连续转换是在最后一个序列转换结束后回到第一个序列循环开始,但在非扫描模式下只能转换一个序列;扫描模式是在不止一个序列时,自动按序列依次转换,可以通过判断对应标志位置位判断转换情况,我们可以判断标志位也可以设置中断来判断是否转换完成。
下面是图文介绍,便于理解,EOC代表转换完成标志位置位:
注: 1、2模式下为非扫描模式,只有第一个序列会被扫描;
1、单次转换,非扫描模式
- 该模式下只转换一个序列一次;
2、连续转换、非扫描模式:
- 该模式下,一个序列会被连续转换;
注: 3、4模式下,为扫描模式,每一个序列都会被转换,每次转换完成都会有标志位置位;
我们可以对配置好的通道任意排序,它会自动按排好的序列,自动转换。
3、单次转换,扫描模式
- 所有序列只转换一次
4、连续转换,扫描模式
- 所有序列循环转换
四、DMA的使用
1、DMA:DAM可以提供外设和存储器或者着存储器和存储器之间的告诉数据传输,无需CPU干扰,节省CPU资源;
- 也就是说DMA就像一个搬运工,根据你的配置自动帮你把数据从一个地方搬运到另一个地方,是由硬件电路实现,不需要CPU控制单独自己跑,这样不需要占用CPU时间了,效率也就提高了;
- DMA可以将数据从外设搬运到存储器,从存储器搬运到外设,从存储器搬运到存储器;
- 外设:例如模数转换(ADC),串口、SPI、IIC等;
- 存储器:例如数组,或者一个变量等;
2、DAM结构:
- 外设和存储器都有大小、地址以及数量,所以我们在CUBEMX中需要配置它们的起始地址、数据宽度、在不只一块内存长度时要选择地址是否自增,例如将不同通道的ADC采集数据放在数组中,要想自动采集所有序列,数组长度不是一个,所以数组地址需要自增(下面一部分介绍为什么ADC外设地址不需要自增)。
- DMA是有方向的,我们要配置由谁搬运到谁;
- DMA可以指定一轮搬运几次,所以我们要配置传输计数器;
- 在搬运完一轮后是否重新再自动搬运呢?这里我们通过配置自动重装器配置;
- 我们还要选择是不是内存到内存(M2M);
- 最后还要选择触发条件,(软件出发、硬件触发)即谁来下达开启DMA搬运的指令呢,这里我们是ADC下达,ADC转运结束后自动触发DMA将采集回来的数据放到数组。
内存间数据转运:
- 这里我们本项目不用,道理相同,都是从一个地址把数据搬运到另一个地址;
- 但是这里的触发条件是软件触发,再程序中写指令。
ADC扫描模式+DMA:
- 这里不止一个ADC通道,所以我们使用扫描模式,DMA搬运时候,地址不自增,因为每个通道的数据都会放到外设地址ADC_DRl里,同时上一个通道的数据会清除,并且完成一个通道采集后自动触发DMA,DMA搬运到数组,数组的地址(DMA存储器地址)需要自增;
五、CUBEMX配置
- 新建工程
进入软件后点击此处进入MCU型号选择:
查找型号,注意使用在英文状态下输入:
选择时钟源:
因为我们使用ST-Link,所以这里在SYS中选择调试(Debug)模式为串行线(Serial Wire):
在时钟配置里输入最高配置72MHz按下回车点击OK:
在项目管理页-->Project中设置三个地方:
注意:路径不要有中文
在项目管理页-->代码管理(Code Generator)中勾选第一个:
生成工程后可以选择打开工程也可以在刚才的保存路径下找到打开:
2、各种资源外设配置
Keil不用关闭,在工程文件中再次打开CUBEMX,根据我们的电路设计配置对应的外设接口:
我们这里配置五个ADC通道,分别是:左边摇杆俩个方向的行程、电源电压采集、右边摇杆俩个方向的行程;
2、ADC配置:
- 选择ADC通道:
下面进行ADC的一些模式配置:
- 这里我们主要设置独立模式、扫描模式(后面序列配置决定)、非连续模式、非间断模式
然后我们配置通道和序列:
- 使能规则组,后面的注入组不用管;
- 写入通道个数,我们用到5个,这里配置完成后,前面的扫描模式自动使能;
- 展开每个序列,配置序列号,扫描转换时将按照序列号扫描;
- 将各个通道分配到各个序列,就是给各个通道排个队;
- ADC转换时间决定ADC采集快慢,时间久一点,采集精度相对高一点;
配置DMA:
1、这里第一步相当于(四、DMA使用->2、DMA结构)中自动重装是否打开,Circular相当于开启自动重装,搬运完成要求的次数后继续重新开始;Normal不开启重装,比如要求搬运50次,那么50次之后需要再次开启才会搬运;
2、我们需要DMA将数据从ADC外设地址搬运到数组,所选择外设到内存(Memory);
3、选择半字是因为ADC数据长度位12位(该单片机为12位ADC),所以16位就够用。
生成代码:
- 每次在CubeMx中修改配置后都要生成代码;
切换到KEIL:
六、程序介绍
采集数据求平均值:
- 这里我们使用二维数组存放这50个数据,10行5列,每一列都是一路ADC的10个数据;
- 二维数据数据存放顺序是一行一行存放,刚好ADC每一轮存放一行
这里我做了一个示意图,红色箭头线条表示数据存储顺序:
在adc.c里面申明这些变量以及数组:
求平均值函数,在adc.c文件的最下方:
这是需要的程序,按照上图放在对应位置即可:
adc.c
uint16_t ADC_Value[10][5];//ADC数据存放
uint16_t left_x_sum=0;//左边摇杆X方向数据之和
uint16_t left_y_sum=0;//左边摇杆Y方向数据之和
uint16_t right_x_sum=0;//右边摇杆X方向数据之和
uint16_t right_y_sum=0;//右边摇杆Y方向数据之和
uint16_t battery_sum=0;//电池电量之和
uint16_t left_x; //左边摇杆X方向数据均值
uint16_t left_y; //左边摇杆Y方向数据均值
uint16_t right_x; //右边摇杆X方向数据均值
uint16_t right_y; //右边摇杆Y方向数据均值
uint16_t battery; //电池电量均值
void ADC1_Value_average(void)
{
for(int i=0;i<10;i++)
{
left_x_sum += ADC_Value[i][0];//行增加,列不变
left_y_sum += ADC_Value[i][1];
right_x_sum += ADC_Value[i][2];
right_y_sum += ADC_Value[i][3];
battery_sum += ADC_Value[i][4];
}
left_x = left_x_sum/10 ;//求均值
left_y = left_y_sum/10 ;
right_x = right_x_sum/10;
right_y = right_y_sum/10;
battery = battery_sum/10;
left_x_sum=0;//求和变量清零不影响下次调用
left_y_sum=0;
right_x_sum=0;
right_y_sum=0;
battery_sum=0;
}
因为这些变量在main.c中要引用,所以在adc.c中extern这些变量,使这些变量不会被重复定义,adc.h在配置好CUBEMX后会自动include在main.c中
adc.h
extern uint16_t ADC_Value[10][5];
extern uint16_t left_x;
extern uint16_t left_y;
extern uint16_t right_x;
extern uint16_t right_y;
extern uint16_t battery;
工程添加内容:
开启ADC和DMA:
- 这个开启函数写在了定时器中断,因为我们本次项目不需要ADC一直采集,每隔一段时间进中断开启一次,也可以把这个函数拿出来在main函数中开启作为本章的一个实验;
- 这句函数的意思是打开ADC和DMA,ADC会开始转换,转后完成后会触发DMA搬运;
- 里面参数分别代表使用ADC1(&hadc1,配置哪个ADC就写哪个),数组地址(&ADC_Value),DMA搬运次数(50);
- 这里我们的需要五个ADC通道分别采集10次,(电池1个、左摇杆2个,右摇杆2个);
- 采集10次是为了求平均值减小误差;
开启ADC&DMA的这句函数每执行一次就存放50个数据,所以在每次存够50个数据后就可以就平均值,然后根据ADC采集数值大小执行相应动作,大家也可以在串口打印出来看一下数据。
七、结语
- 本章主要是完成对摇杆和电池电压的采集,之后整体程序逻辑介绍的时候会讲如何使用采集回来的数据,这个先放在这里,我们之后可以随时到数组ADC_Value里面调用;
- 还是那句话,有相关问题或者建议欢迎讨论。
- 下一章我们介绍定时器与按键,上面用到定时器的地方也可以使用了;