DMA地址获取

最近在研究一如何获得连续的物理地址用于DMA,看了很多博客,说是通过kmalloc,get_free_pages等等获取内存空间再通过mmap,ioremap等即可使用,但测试了很多代码,写到DMA寄存器进行DMA操作都没成功。最后发现,其实将获得的物理地址对应的虚拟地址转化成总线地址就可以进行DMA,基于DMA的硬件使用总线地址而非物理地址。

操作环境:Linux ubuntu 4.10.0-42-generic

具体操作如下:

1.网上能够搜到有很多分配一块连续物理地址的接口函数,http://blog.youkuaiyun.com/luckywang1103/article/details/48831433,这个博主写的很详细,各种分配方式,但是在虚拟机上跑的话就直接挂掉了。但是可以参照任何一种分配内存的方式,进行连续物理地址的获取。我采取的是get_free_pages,原型如下:

unsigned long __get_free_pages(unsigned int gfp_mask, unsigned intorder)——Allocates 2order number of pages and returns avirtual address。

gfp_mask没必要写GFP_DMA,填GFP_KERNEL即可,order是要分配的页的个数,取2的次方计算,ubantu支持最大order为10,也就是2的10次方个4K的pages,总共4M的物理内存,可以连续申请低于896M的内核物理空间。

2.get_free_pages返回值是虚拟地址,可以地址里面初始化一些值,再通过函数virt_to_bus转成总线地址,这个地址可以直接给硬件做DMA使用,至于写值是为了用DMA方式读看看是否正确,最后加几行读这个虚拟地址的代码,看进行了DMA写操作写进去的值是否正确。当然也可以通过mmap将对应的物理地址映射到用户虚拟地址进行使用。具体代码和流程以及效果图将后续贴出。


### 使用 DMA 获取 ADC 数据的方法 在 STM32 中,通过 DMA 方式获取 ADC 的数据是一种高效的方式。以下是实现该功能的具体方法以及注意事项。 #### 1. 配置 ADC 模块 首先需要配置 ADC 模块的相关参数,包括但不限于通道选择、采样时间、分辨率等。这些参数决定了 ADC 如何进行信号采集[^3]。 ```c // 初始化 ADC 模块 void ADC_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; // 启用 ADC 时钟 __HAL_RCC_ADC_CLK_ENABLE(); // 配置 ADC 参数 hadc.Instance = ADC1; hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 设置时钟分频 hadc.Init.Resolution = ADC_RESOLUTION_12B; // 设置分辨率为 12 位 hadc.Init.ScanConvMode = ENABLE; // 开启扫描模式 hadc.Init.ContinuousConvMode = ENABLE; // 开启连续转换模式 HAL_ADC_Init(&hadc); // 配置 ADC 通道 sConfig.Channel = ADC_CHANNEL_0; // 选择通道 0 sConfig.Rank = 1; // 排序优先级为 1 sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; // 设置采样时间为 3 个周期 HAL_ADC_ConfigChannel(&hadc, &sConfig); } ``` #### 2. 配置 DMA 模块 接着需要配置 DMA 模块,指定其传输方向、数据长度、传输模式等内容。DMA 将负责把 ADC 转换后的数据自动存储到目标缓冲区中。 ```c // 初始化 DMA 模块 void DMA_Init(void) { hdma_adc.Instance = DMA2_Stream0; // 选择 DMA 流 0 hdma_adc.Init.Channel = DMA_CHANNEL_0; // 选择通道 0 hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; // 数据从外设到内存 hdma_adc.Init.PeriphInc = DISABLE; // 禁止外围地址自增 hdma_adc.Init.MemInc = ENABLE; // 允许内存地址自增 hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 半字节对齐 hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; // 半字节对齐 hdma_adc.Init.Mode = DMA_CIRCULAR; // 循环模式 hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; // 高优先级 HAL_DMA_Init(&hdma_adc); // 关联 ADC 和 DMA __HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc); } ``` #### 3. 配置 ADC-DMA 传输 将 ADC 和 DMA 模块关联起来,确保当 ADC 完成一次转换后会自动触发 DMA 请求,并将数据传送到预定义的缓冲区内[^1]。 ```c uint16_t adc_dma_buffer[10]; // 创建用于存储 ADC 值的缓冲区 // 启动 ADC 并绑定 DMA 缓冲区 void Start_ADC_DMA(void) { HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_dma_buffer, 10); // 启动 ADC 并绑定缓冲区大小为 10 } ``` #### 4. 等待 DMA 传输完成 可以通过检查 DMA 是否完成了当前批次的数据传输来判断是否可以安全地读取缓冲区中的数据。 ```c if (__HAL_DMA_GET_FLAG(&hdma_adc, DMA_FLAG_TCIF0)) { // 如果 DMA 已经完成传输 __HAL_DMA_CLEAR_FLAG(&hdma_adc, DMA_FLAG_TCIF0); // 清除标志位 Process_Data(); // 处理数据 } ``` #### 5. 处理采集到的数据 一旦确认 DMA 成功传输了数据至缓冲区,则可以从 `adc_dma_buffer` 数组中提取所需的信息并进一步处理它们[^2]。 ```c void Process_Data() { for (int i = 0; i < 10; ++i) { printf("ADC Value %d: %u\n", i, adc_dma_buffer[i]); // 打印每个 ADC 值 } } ``` 需要注意的是,在实际应用过程中可能会遇到一些常见问题,比如由于 DMA 启动顺序不当而导致的数据错位现象或者因配置失误引起的数据丢失等问题[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值