08. DMA

1.DMA简介

2.DMA底层工作原理

3.DMA在游戏开发中的关键应用


1.DMA简介

DMA(Direct Memory Access, 直接内存访问)是计算机体系中核心的"解放CPU"技术 ——它允许外设(如 GPU、硬盘、网卡、音

频设备)不经过CPU中转, 直接读写系统内存, 从而让CPU从繁琐的数据拷贝工作中解脱, 专注于游戏逻辑、AI 计算、物理模

拟等核心任务
没有DMA时, 外设与内存的通信必须经过CPU, 就像:

- CPU是游戏公司的CEO(负责核心逻辑: AI、物理、剧情触发)

- 内存是文件柜(存储纹理、模型、音频等资源)

- GPU 是美术部门(需要从文件柜拿素材做渲染)

a.无DMA的"低效流程"

- 美术(GPU)需要纹理资源,CEO(CPU)发请求

- CEO放下手头的核心工作(比如计算敌人AI), 亲自去文件柜(内存)找纹理

- CEO 把纹理交给美术, 再回到自己的核心工作

若有多个外设(网卡、硬盘、音频设备)同时请求, CEO会被彻底"缠住", 导致游戏核心逻辑卡顿(如AI 反应慢、物理模拟跳帧)

b.有DMA的"高效流程"

- 美术(GPU)直接告诉"专门的跑腿员"——DMA控制器: "我要文件柜3层的纹理, 直接送我这来"

- CEO(CPU)只需要花1毫秒给DMA控制器下指令, 然后继续处理核心工作(敌人AI、物理碰撞)

- DMA控制器自己去文件柜(内存)取纹理, 直接交给美术(GPU)

传输完成后, DMA控制器给CPU发一个"完成信号"(中断), CPU只需简单确认, 无需全程参与

2.DMA底层工作原理

DMA的本质是"独立于CPU的总线控制器", 能直接控制系统总线(地址总线、数据总线、控制总线),实现外设与内存的直接通信
假设你用Resources.LoadAsync<Texture2D>("Texture/PlayerSkin")加载纹理, 底层DMA流程如下

1).CPU发起指令

a.Unity主线程调用异步加载API, CPU向DMA控制器发送指令

- 传输源: 硬盘上的PlayerSkin.dds文件(外设地址)

- 传输目的地: 内存中的非托管缓冲区(地址0x7F001234, Unity的AsyncOperation背后会分配该缓冲区)

- 传输长度: 4MB(纹理大小)

- 传输完成后发送中断信号

b.CPU发送完指令后, 立即返回主线程处理游戏逻辑(: UI更新、输入响应), 不等待传输完成
2).DMA控制器请求总线控制权

a.DMA控制器向系统总线的"仲裁器"(负责分配总线使用权)发起请求

b.仲裁器确认总线空闲后, 将总线控制权交给DMA控制器

3).直接传输数据

a.DMA控制器根据CPU指定的地址, 直接从硬盘读取PlayerSkin.dds数据, 通过数据总线写入内存的非托管缓冲区

b.传输过程中, CPU完全不参与, 可专注于核心计算(如敌人路径规划、物理碰撞检测)
4).传输完成, 触发中断

a.4MB纹理数据传输完成后, DMA控制器释放总线控制权, 并向CPU发送"中断请求"

b.CPU收到中断后, 暂停当前正在执行的低优先级任务(如UI动画), 处理中断

- 确认数据传输无误

- 通知Unity的AsyncOperation完成

- CPU将非托管缓冲区的纹理数据解压、格式转换(如从DDS转 RGBA), 再上传到GPU显存
5).CPU返回核心任务

中断处理完成后, CPU继续执行之前的核心工作(如AI计算), 整个过程中CPU仅花费微秒级时间处理指令和中断, 几乎不影响

游戏流畅度

3.DMA在游戏开发中的关键应用

游戏中所有"大数据量、低CPU干预"的IO/传输场景, 都依赖DMA技术; 以下是Unity开发者最关心的4个核心场景:

1).资源异步加载(Texture/Model/Audio)

a.场景: 用Addressables、AsyncOperation加载大型资源(如场景模型、4K 纹理、长音频)

b.DMA作用: 硬盘(外设) → 内存(非托管缓冲区)的传输由DMA完成, CPU无需等待IO操作, 避免"加载时卡顿"

c.Unity底层细节

- 异步加载的资源会先存入非托管内存(因为DMA只能直接访问非托管内存, 托管内存由GC管理, 地址可能变动)

- 若资源需要解压(如压缩纹理、AB包), 解压过程通常在后台线程执行, 不阻塞主线程, 而解压后的原始数据仍通过DMA传输

到GPU显存
2).GPU渲染数据传输(顶点 / 纹理 / Shader 资源)

a.场景: Unity每帧将顶点数据、纹理、Shader常量缓冲区传输到GPU

b.DMA作用: CPU只需将"数据地址和长度"写入GPU的DMA引擎, GPU通过DMA直接从系统内存读取数据, 无需CPU逐字节拷贝

c.性能影响

- 若顶点数据量大(如百万面模型), 无DMA会导致CPU在"数据拷贝"上花费数十毫秒, 导致帧率暴跌

- 现代GPU(如NVIDIA RTX、AMD RDNA)都内置高性能DMA引擎, 支持"零拷贝"传输(数据无需在系统内存和显存间重复拷贝)这

也是Unity中RenderTexture、ComputeBuffer高性能的核心原因之一
3).音频流播放(背景音乐、3D音效)

a.场景: Unity播放长音频(: 5分钟背景音乐)3D音效

b.DMA作用: 音频设备(外设)通过DMA直接从内存读取音频流数据(PCM格式), 无需CPU实时拷贝

c.关键注意

- 若音频数据需要实时解码(如MP3→PCM), 解码可在后台线程完成, 解码后的PCM数据存入非托管缓冲区, 由DMA直接传输给音

频设备

- 若DMA传输中断(如内存不足、总线冲突), 会导致音频卡顿或爆音, 这也是Unity中推荐使用AudioClip.LoadAudioDataAsync

异步加载音频的原因
4).网络数据接收(多人游戏同步)

a.场景: 多人游戏中, 网卡接收服务器发送的同步数据(如玩家位置、技能释放指令)

b.DMA作用: 网卡通过DMA直接将网络数据包写入内存的"接收缓冲区", CPU只需处理数据包(解析指令、更新游戏状态), 无需参

与数据搬运

c.性能优化点: 若没有DMA, CPU会被网卡的"数据接收"频繁打断, 导致游戏逻辑帧率不稳定, 尤其在高并发场景下, DMA的作

用至关重要
/** ****************************************************************************** * @file PWR_STOP/main.c * @author MCD Application Team * @version V1.0.1 * @date 11-October-2013 * @brief Main program body ****************************************************************************** * @attention * * <h2><center>© COPYRIGHT 2013 STMicroelectronics</center></h2> * * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.st.com/software_license_agreement_liberty_v2 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "MM32F031xxn.h" #include <math.h> //Library #include "user.h" #include "control_circuit.h" //函数声明 void on_off(uint8_t num); void uart2_report(void); void uart1_communication(void); void report2_format(void); void auto2_sending(void); void Delay_Ms(uint32_t time_ms); //变量声明 uint32_t counter_cycle, uart1_rate, uart2_rate, cntr_unse, cntr_tim14; uint16_t adc_v, voltage, o2c, o2f, o2t, o2RH, o2P; uint32_t o2p, kpa; uint8_t USART1_TXbuffer[30], USART1_RXbuffer[30], USART2_TXbuffer[30], USART2_RXbuffer[30], buf_us6300[4], report8[30]; u8 flag_pad, flag_menu, flag_light, battery, display_flag; uint16_t voltage_buffer[15], power_off_cntr; uint8_t rx_flag, code_right, code_num, flag_adj, flash_write; uint8_t USART1_RXtotal, USART1_TXtotal, USART2_RXtotal, USART2_TXtotal, flag_preparing_report, sensor_num; float o2c_factor, o2f_factor, atm, temperature; uint8_t I2C1_Tx_Buffer[16]; uint8_t I2C1_Rx_Buffer[16]; //void data_init(void) //{ // STMFLASH_Read (FLASH_STORE_BASE_ADDR,(uint8_t*)I2C1_Rx_Buffer,16); // if((I2C1_Rx_Buffer[0]==0xAA)&&(I2C1_Rx_Buffer[3]==0x55)) // { // o2c_add = I2C1_Rx_Buffer[1]; // o2f_add = I2C1_Rx_Buffer[2]; // } // else // { // I2C1_Tx_Buffer[0] = 0xAA; // I2C1_Tx_Buffer[1] = o2c_add; // I2C1_Tx_Buffer[2] = o2f_add; // I2C1_Tx_Buffer[3] = 0x55; // STMFLASH_Write(FLASH_STORE_BASE_ADDR,(uint8_t*)I2C1_Tx_Buffer,16); // } // if(o2f_add>0x80) // o2f_factor = 1 + 0.01*(o2f_add - 0x80); // else // o2f_factor = 1 - 0.01*(0x80-o2f_add); //} int main(void) { GPIO_InitTypeDef GPIO_InitStructure; uint32_t i; uint8_t enable; for (i=0;i<MILLISECOND*64*10;i++){} // 这段延时不可省去: // ------初始化全局变量------------ o2c = 210; o2f = 0; sensor_num = 0; flag_menu = 0; flash_write = 0; stm32_Init (); uart1_communication(); uart2_report(); ControlCircuit_Init(); // 初始化12V控制IO // data_init(); LED_ON; for (i=0;i<MILLISECOND*64*10;i++){} // 这段延时不可省去: LED_OFF; VAL_ON; // 开启12V输出 //Voltage12V_Control(1); // 关闭12V输出 Voltage12V_Control(0); // I2CInitMasterMode(); // I2CMasterWrite(I2C1,US6300_ADDR, 0x06, 0, buf_us6300); for (i=0;i<500000;i++){}; // 这段延时可导致标定时通信不正常;但有时,在线仿真时没有此延时,EEPROM无法通过; // I2CMasterWrite(I2C1,US6300_ADDR, 0x50, 0, buf_us6300); // for (i=0;i<500;i++){}; // I2CMasterWrite(I2C1,US6300_ADDR, 0x30, 0, buf_us6300); // for (i=0;i<500;i++){}; // I2CMasterWrite(I2C1,US6300_ADDR, 0xAA, 0, buf_us6300); // for (i=0;i<500000;i++){} // 这段延时可导致标定时通信不正常;但有时,在线仿真时没有此延时,EEPROM无法通过; // IWDG->KR = 0xAAAA; // 刷新看门狗IWDG ;写入0xAAAA=装入重载值; IWDG->KR = 0xCCCC; // 启动看门狗; IWDG->KR = 0xAAAA; // 刷新看门狗IWDG; 写入0xAAAA=装入重载值; // I2CMasterWrite(I2C1,US6300_ADDR, 0xAA, 0, buf_us6300);//读第一次数据时必须唤醒3次 // for (i=0;i<500000;i++){} // 这段延时可导致标定时通信不正常;但有时,在线仿真时没有此延时,EEPROM无法通过; // IWDG->KR = 0xAAAA; // 刷新看门狗IWDG ;写入0xAAAA=装入重载值; // ***************************************************** while(1) { IWDG->KR = 0xAAAA; // 刷新看门狗IWDG ;写入0xAAAA=装入重载值? //Voltage12V_Control(1); // PA15输出高电平 // Delay_Ms(5); // 保持高电平 //Voltage12V_Control(0); // PA15输出低电平 // Delay_Ms(5); // 保持低电平 if(flag_menu) { // I2CMasterWrite(I2C1,US6300_ADDR, 0x50, 0, buf_us6300); // for (i=0;i<5000;i++){}; // I2CMasterWrite(I2C1,US6300_ADDR, 0x50, 0, buf_us6300); // for (i=0;i<5000;i++){}; // I2CMasterWrite(I2C1,US6300_ADDR, 0x30, 0, buf_us6300); // for (i=0;i<5000;i++){}; // I2CMasterRead1_6300(I2C1,US6300_ADDR, 3, buf_us6300); // atm = ((buf_us6300[0]&0x0f)*256*256 + buf_us6300[1]*256 + buf_us6300[2])/1000.0; //value/10=hpa,kpa/6.895=psi,hpa/6.895=psi*10; // for (i=0;i<5000;i++){}; // I2CMasterWrite(I2C1,US6300_ADDR, 0x32, 0, buf_us6300); //温度 // for (i=0;i<5000;i++){}; // I2CMasterRead1_6300(I2C1,US6300_ADDR, 3, buf_us6300); // temperature = ((buf_us6300[0]&0x0f)*256*256 + buf_us6300[1]*256 + buf_us6300[2])/100.0; flag_menu = 0; } } } // ***************************************************************************************** // 准备常规报 // ***************************************************************************************** void report2_format(void) { uint32_t i,j; uint8_t temp, temp_AAA[4], temp2_AAA[20]; uint16_t temp_u16; uint32_t temp_u32; union AAA { float f[5] ; u8 u8_[20]; } convert; // convert.f[0] = (float)o2c/10; convert.f[1] = (float)o2RH/10; convert.f[2] = (float)o2t/10; //--传感器显示此数据 convert.f[3] = (float)o2P/10; convert.f[4] = (float)atm; for (i=0;i<5;i++) //5个浮点 { for (j=0;j<4;j++) temp_AAA[3-j] = convert.u8_[i*4+j]; for (j=0;j<4;j++) temp2_AAA[i*4+j] = temp_AAA[j]; } flag_preparing_report=1; //防止在变更过程中发送 for (i=0;i<20;i++) report8[i+4] = temp2_AAA[i]; //利用union将浮点转到report8中; report8[0]=0x16; report8[1]=0x09; report8[2]=0x01; report8[3]=sensor_num-1; temp = 0; for(i=0;i<24;i++) temp += report8[i]; report8[24] = 0x00 - temp; //计算校验字; for (i=0;i<25;i++) USART2_TXbuffer[i] = report8[i]; //将report8拷贝; report8[i] flag_preparing_report = 0; } // ******************************************************************************* // auto_sending自动发送浓度 // ******************************************************************************* void auto2_sending(void) //正常检测时,自动发送检测数据----复核时模式应该还是calibration, 符合完成后才改成NORMAL; { if(flag_preparing_report==0) //防护措施:防止数据准备未完成时发送; { DMA_Cmd(DMA1_Channel4, DISABLE); DMA1_Channel4->CMAR = (u32)&USART2_TXbuffer; DMA1_Channel4->CNDTR = USART2_TXtotal; //DMA通道的DMA缓存的大小 DMA_Cmd(DMA1_Channel4, ENABLE); } } void uart1_communication(void) { uint16_t temp_16; //GPIO端口设置 DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; UART_InitTypeDef UART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE); //使能UART2 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //开启GPIOA时钟 //UART1 NVIC 配置 //UART 初始化设置 GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_1); GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_1); uart1_rate = 9600; UART_InitStructure.UART_BaudRate = uart1_rate;//串口波特率 USART1_TXtotal = 4; USART1_RXtotal = 12; UART_InitStructure.UART_WordLength = UART_WordLength_8b;//字长为8位数据格式 UART_InitStructure.UART_StopBits = UART_StopBits_1;//一个停止位 UART_InitStructure.UART_Parity = UART_Parity_No;//无奇偶校验位 UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;//无硬件数据流控制 UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx; //收发模式 UART_Init(UART1, &UART_InitStructure); //初始化串口1 UART_Cmd(UART1, ENABLE); //使能串口1 //UART1_TX GPIOA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9 //UART1_RX GPIOA.10初始化 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 // printf("UART OK!\r\n"); //----------UART1_DMA配置部分------------------- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel2); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(UART1->TDR); //DMA外设ADC基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART1_TXbuffer; //DMA内存基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设 DMA_InitStructure.DMA_BufferSize = USART1_TXtotal; //DMA通道的DMA缓存的大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输 DMA_Init(DMA1_Channel2, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道UART1_Tx_DMA_Channel所标识的寄存器 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) &(UART1->RDR); //DMA外设ADC基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART1_RXbuffer; //DMA内存基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,赐馍栌读取发送到内存 DMA_InitStructure.DMA_BufferSize = USART1_RXtotal; //DMA通道的DMA缓存的大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Low; //DMA通道 x拥有中优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输 DMA_Init(DMA1_Channel3, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道UART1_Tx_DMA_Channel所标识的寄存器 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPriority = 2; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); DMA_ITConfig(DMA1_Channel2,DMA_IT_TC,ENABLE); // UART_DMACmd(UART1,UART_DMAReq_EN,ENABLE); //使能uart1 DMA DMA_ITConfig(DMA1_Channel3,DMA_IT_TC,ENABLE); UART_DMACmd(UART1,UART_DMAReq_EN,ENABLE); //使能uart1 DMA // DMA_Cmd(DMA1_Channel4, ENABLE); DMA_Cmd(DMA1_Channel3, ENABLE); } void uart2_report(void) { uint16_t temp_16; //GPIO端口设置 DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; UART_InitTypeDef UART_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART2, ENABLE); //使能UART2 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //开启GPIOA时钟 //UART1 NVIC 配置 //UART 初始化设置 GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1); GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_1); uart2_rate = 115200; UART_InitStructure.UART_BaudRate = uart2_rate;//串口波特率 USART2_TXtotal = 25; USART2_RXtotal = 8; UART_InitStructure.UART_WordLength = UART_WordLength_8b;//字长为8位数据格式 UART_InitStructure.UART_StopBits = UART_StopBits_1;//一个停止位 UART_InitStructure.UART_Parity = UART_Parity_No;//无奇偶校验位 UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;//无硬件数据流控制 UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx; //收发模式 UART_Init(UART2, &UART_InitStructure); //初始化串口1 UART_Cmd(UART2, ENABLE); //使能串口1 //UART1_TX GPIOA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9 //UART1_RX GPIOA.10初始化 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 // printf("UART OK!\r\n"); //----------UART1_DMA配置部分------------------- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //使能DMA传输 DMA_DeInit(DMA1_Channel4); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(UART2->TDR); //DMA外设ADC基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART2_TXbuffer; //DMA内存基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设 DMA_InitStructure.DMA_BufferSize = USART2_TXtotal; //DMA通道的DMA缓存的大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输 DMA_Init(DMA1_Channel4, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道UART1_Tx_DMA_Channel所标识的寄存器 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) &(UART2->RDR); //DMA外设ADC基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART2_RXbuffer; //DMA内存基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,赐馍栌读取发送到内存 DMA_InitStructure.DMA_BufferSize = USART2_RXtotal; //DMA通道的DMA缓存的大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Low; //DMA通道 x拥有中优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输 DMA_Init(DMA1_Channel5, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道UART1_Tx_DMA_Channel所标识的寄存器 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); // UART_DMACmd(UART1,UART_DMAReq_EN,ENABLE); //使能uart1 DMA DMA_ITConfig(DMA1_Channel5,DMA_IT_TC,ENABLE); UART_DMACmd(UART2,UART_DMAReq_EN,ENABLE); //使能uart1 DMA // DMA_Cmd(DMA1_Channel4, ENABLE); DMA_Cmd(DMA1_Channel5, ENABLE); } //** //矩阵切换 //** void on_off(uint8_t num) { uint8_t board, sensor_num; board = num%128/16; sensor_num = num%16; switch(board) //切换板编号 { case 0: EN0_ON; EN1_OFF; EN2_OFF; EN3_OFF; break; case 1: EN0_OFF; EN1_ON; EN2_OFF; EN3_OFF; break; case 2: EN0_OFF; EN1_OFF; EN2_ON; EN3_OFF; break; case 3: EN0_OFF; EN1_OFF; EN2_OFF; EN3_ON; break; } switch(sensor_num) //传感器编号 { case 15: A0_ON; A1_ON; A2_ON; A3_ON; break; case 14: A0_OFF; A1_ON; A2_ON; A3_ON; break; case 13: A0_ON; A1_OFF; A2_ON; A3_ON; break; case 12: A0_OFF; A1_OFF; A2_ON; A3_ON; break; case 11: A0_ON; A1_ON; A2_OFF; A3_ON; break; case 10: A0_OFF; A1_ON; A2_OFF; A3_ON; break; case 9: A0_ON; A1_OFF; A2_OFF; A3_ON; break; case 8: A0_OFF; A1_OFF; A2_OFF; A3_ON; break; case 7: A0_ON; A1_ON; A2_ON; A3_OFF; break; case 6: A0_OFF; A1_ON; A2_ON; A3_OFF; break; case 5: A0_ON; A1_OFF; A2_ON; A3_OFF; break; case 4: A0_OFF; A1_OFF; A2_ON; A3_OFF; break; case 3: A0_ON; A1_ON; A2_OFF; A3_OFF; break; case 2: A0_OFF; A1_ON; A2_OFF; A3_OFF; break; case 1: A0_ON; A1_OFF; A2_OFF; A3_OFF; break; case 0: A0_OFF; A1_OFF; A2_OFF; A3_OFF; break; } } //电压采样 void Get_Adc_Average(void) { uint8_t i; uint16_t temp_v, Temp_average_float; } void Delay_Ms(uint32_t time_ms) { uint32_t i, j; // 延时系数根据MM32F031主频调整(默认48MHz,约48000次循环为1ms) for(i = 0; i < time_ms; i++) { for(j = 0; j < 48; j++) { // 空循环延时,同时定期刷新看门狗避免复位 if(j % 10000 == 0) IWDG->KR = 0xAAAA; // 刷新看门狗 } } } //正常显示 void display(void) { } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif /** * @} */ /** * @} */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ 这个是主函数
08-30
下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中一个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这一议题中,电路板被构建为一个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的一种常用策略。 该算法与回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的一个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排列树,在探索过程中,每个节点仅被赋予一次成为扩展节点的机会,且会一次性生成其全部子节点。 针对布线问题的解决,队列式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将与该扩展节点相邻且可通行的方格加入至活跃节点队列中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队列中提取队首节点作为下一个扩展节点,并将与当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队列。 这一过程将持续进行,直至算法探测到目标方格 b 或活跃节点队列为空。 在实现上述算法时,必须定义一个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和列。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
源码来自:https://pan.quark.cn/s/a4b39357ea24 在VC++开发过程中,对话框(CDialog)作为典型的用户界面组件,承担着与用户进行信息交互的重要角色。 在VS2008SP1的开发环境中,常常需要满足为对话框配置个性化背景图片的需求,以此来优化用户的操作体验。 本案例将系统性地阐述在CDialog框架下如何达成这一功能。 首先,需要在资源设计工具中构建一个新的对话框资源。 具体操作是在Visual Studio平台中,进入资源视图(Resource View)界面,定位到对话框(Dialog)分支,通过右键选择“插入对话框”(Insert Dialog)选项。 完成对话框内控件的布局设计后,对对话框资源进行保存。 随后,将着手进行背景图片的载入工作。 通常有两种主要的技术路径:1. **运用位图控件(CStatic)**:在对话框界面中嵌入一个CStatic控件,并将其属性设置为BST_OWNERDRAW,从而具备自主控制绘制过程的权限。 在对话框的类定义中,需要重写OnPaint()函数,负责调用图片资源并借助CDC对象将其渲染到对话框表面。 此外,必须合理处理WM_CTLCOLORSTATIC消息,确保背景图片的展示不会受到其他界面元素的干扰。 ```cppvoid CMyDialog::OnPaint(){ CPaintDC dc(this); // 生成设备上下文对象 CBitmap bitmap; bitmap.LoadBitmap(IDC_BITMAP_BACKGROUND); // 获取背景图片资源 CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap* pOldBitmap = m...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值