背景
DMA是一种计算机技术,允许某些硬件子系统直接访问系统内存,而不需要中央处理器(CPU)的介入,从而减轻CPU的负担。我们可以通过DMA来从外设(ADC、UART等)读取数据之后,搬运到指定的内存。
ADC是根据用户动作或者环境变化会造成传感器等设备的电压值发生变化,再通过STM32的ADC块实现采样、保持、量化、编码将模拟量转换成数据量。
本篇文章会介绍目前四轴飞行器常用的摇杆操作(左右、上下、按下等),变为电压值变化,以及STM32 如何实现DMA定期读取遥感ADC值。
摇杆的原理
JS_X:作为X轴方向的模拟信号输入口
JS_Y:作为Y轴方向的模拟信号输入口
JS_D:挥动开关的状态的检测端口
通过原理图可以通过X轴方向滑动可以改变接触点在电阻的位置,从而影响读取到的电压值。Y轴方向亦是同理。因此只需将JS_X和JS_Y的端口设置位模拟量输入端,然后由STM32单片机的ADC块处理。
而按下的操作显然是普通的Port Key, 因此只用将JS_D设置位上拉输入,然后通过读取该端口的状态,来判断是否摇杆被按下。
STM32的DMA通道:STM32系列最多有12个独立可配置的通道,包括DMA1(7个通道)和DMA2(5个通道)。每个通道可以分别设置源地址与目的地址,实现独立工作
程序
端口配置
#define ADC1_DR_Address ((uint32_t)0x4001244C) //ADC1这个外设的地址(查参考手册得出)
#define ADCPORT GPIOA //定义ADC接口
#define ADC_CH4 GPIO_Pin_4 //定义ADC接口 电压电位器
#define ADC_CH5 GPIO_Pin_5 //定义ADC接口 光敏电阻
#define ADC_CH6 GPIO_Pin_6 //定义ADC接口 摇杆X轴
#define ADC_CH7 GPIO_Pin_7 //定义ADC接口 摇杆Y轴
#define JoyStickPORT GPIOB //定义IO接口组
#define JoyStick_KEY GPIO_Pin_2 //定义IO接口
void ADC_GPIO_Init(void){ //GPIO初始化设置
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA时钟(用于ADC的数据传送)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//使能ADC1时钟
GPIO_InitStructure.GPIO_Pin = ADC_CH6 | ADC_CH7; //!!!选择端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //选择IO接口工作方式
GPIO_Init(ADCPORT, &GPIO_InitStructure);
}
void JoyStick_Init(void){ //摇杆的挥动开关的接口初始化
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO的初始化枚举结构
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Pin = JoyStick_KEY; //选择端口号(0~15或all)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻
GPIO_Init(JoyStickPORT,&GPIO_InitStructure);
}
1)GPIOA组、GPIOB组、DMA1的外设时钟使能起来(GPIOC组功能用于其他,我们可以忽略它)
2) X轴和Y轴的输入端要设置为模拟量输入端口
3)Port Key 要设置位上拉电阻输入口,因为在没有被按下时,要能读入高电平!
3)初始化端口
ADC 配置
void ADC_Configuration(void){ //初始化设置
ADC_InitTypeDef ADC_InitStructure;//定义ADC初始化结构体变量
ADC_GPIO_Init();//GPIO初始化设置
ADC_DMA_Init();//DMA初始化设置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //使能扫描
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//ADC转换工作在连续模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//有软件控制转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//转换数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 2;//!!!顺序进行规则转换的ADC输入口的数目(根据ADC采集通道数量修改)
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
//设置指定ADC的规则组通道,设置它们的转化顺序和采样时间
//ADC1,ADC通道x,规则采样顺序值为y,采样时间为28周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_28Cycles5);//!!! ADC1选择信道x,采样顺序y,采样时间n个周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 2, ADC_SampleTime_28Cycles5);//!!! ADC1选择信道x,采样顺序y,采样时间n个周期
ADC_DMACmd(ADC1, ENABLE);// 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
ADC_Cmd(ADC1, ENABLE);//使能ADC1
ADC_ResetCalibration(ADC1); //重置ADC1校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1));//等待ADC1校准重置完成
ADC_StartCalibration(ADC1);//开始ADC1校准
while(ADC_GetCalibrationStatus(ADC1));//等待ADC1校准完成
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能ADC1软件开始转换
}
● 12 位分辨率
● 转换结束、注入转换结束和发生模拟看门狗事件时产生中断
● 单次和连续转换模式
● 从通道 0 到通道 n 的自动扫描模式
● 自校准
● 带内嵌数据一致性的数据对齐
● 采样间隔可以按通道分别编程
● 规则转换和注入转换均有外部触发选项
● 间断模式
● 双重模式 ( 带 2 个或以上 ADC 的器件 )
● ADC 转换时间:
─ STM32F103xx 增强型产品:时钟为 56MHz 时为 1 μ s( 时钟为 72MHz 为 1.17 μ s)
─ STM32F101xx 基本型产品:时钟为 28MHz 时为 1 μ s( 时钟为 36MHz 为 1.55 μ s)
─ STM32F102xxUSB 型产品:时钟为 48MHz 时为 1.2 μ s
─ STM32F105xx 和 STM32F107xx 产品:时钟为 56MHz 时为 1 μ s( 时钟为 72MHz 为 1.17 μ s)
● ADC 供电要求: 2.4V 到 3.6V
● ADC 输入范围: V REF- ≤ V IN ≤ V REF+
● 规则通道转换期间有 DMA 请求产生。
STM32F103C8T6由两个ADC。
ADC框图:
ADC1和ADC2独立工作,故配置位独立模式
因为要ADC1扫描有配置的两个通道,因此要使能扫描
因为要连续多次采样,因此要设置位连续模式,而非单次模式
DMA的触发可以通过软件触发(内部触发)和硬件触发(外部触发,硬件触发见截图)。
我们采用内部软件触发
常见的ADC数据对齐方式
右对齐(默认情况):这是大多数STM32 ADC寄存器的默认行为。例如,如果你使用12位分辨率的ADC,那么只有最低的12位将被用来存储转换结果,而最高的4位(在16位寄存器中)将会是0。
左对齐:在某些情况下,你可能需要将数据左对齐,这意味着将最高有效位(MSB)放在寄存器的最高位。这通常通过特定的硬件配置或软件操作来实现,比如在某些STM32系列中,可以通过配置ADC寄存器来实现数据的左对齐。
输入通道数配置,X轴通道和Y轴通道总共两个。
注册规则组
注册包含通道信息、采样顺序、采样时间。ADC通道通过查阅端口定义可知。
STM32F103C8T6
C8:48pin 64K Flash 属于中容量 SRAM 20K.
查看引脚定义可以知道PA6的ADC通道是6,PA7的通道是7.
使能ADC1的DMA请求
使能ADC1
ADC1自校准
软件触发开始ADC1转换
DMA配置
vu16 ADC_DMA_IN[2]; //ADC数值存放的变量
void ADC_DMA_Init(void){ //DMA初始化设置
DMA_InitTypeDef DMA_InitStructure;//定义DMA初始化结构体
DMA_DeInit(DMA1_Channel1);//复位DMA通道1
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //定义 DMA通道外设基地址=ADC1_DR_Address
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_DMA_IN; //!!!定义DMA通道ADC数据存储器(其他函数可直接读此变量即是ADC值)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//指定外设为源地址
DMA_InitStructure.DMA_BufferSize = 2;//!!!定义DMA缓冲区大小(根据ADC采集通道数量修改)
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//当前外设寄存器地址不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//!!! 当前存储器地址:Disable不变,Enable递增(用于多通道采集)
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//定义外设数据宽度16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //定义存储器数据宽度16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA通道操作模式位环形缓冲模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA通道优先级高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//禁止DMA通道存储器到存储器传输
DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化DMA通道1
DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA通道1
}
由于是从ADC1外设(ADC1的数据寄存器DR)到内存,因此方向配置如下
DMA设置为循环模式,可以连续多次的从ADC1外设搬运数据到指定内存。
因为ADC1外设(DR)是16位(ADC分辨率是12位),所以
DMA1的每个channel用于设置数据源外设和目的地内存地址。因为我们的外设是ADC1。所以我们选择Channel1.
ADC1的外设的地址
DMA优先级设置为HIGH
优先级相同情况下,由内部硬件优先级决定!
由于不是内存搬运到内存,故DMA_M2M要设置为Disable
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/sinat_36070482/article/details/145645118
|