1. DMA简介

  • DMA(Direct Memory Access)直接存储器存取
  • DMA可以提供外设(一般是外设的数据寄存器,比如ADC的数据寄存器、串口的数据寄存器)和存储器(运行内存SRAM和程序存储器Flash)或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
  • 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
  • 每个通道都支持软件触发(一般用于存储器到存储器的数据转运)和特定的硬件触发(一般用于外设到存储器的数据转运)
  • STM32F103C8T6 DMA资源:DMA1(7个通道)

2. 存储器映像

3. DMA框图

  • 仲裁器:用于调度各个通道,防止产生冲突
  • AHB从设备:用于配置DMA参数
  • DMA请求:用于硬件触发DMA的数据转运

4. DMA基本结构

  • 由于Flash是只读的,所以DMA不可以进行SRAM到Flash,或者Flash到Flash的转运操作。
  • 数据宽度:指定一次转运要按多大的数据宽度来进行,可以选择字节Byte(8位)、半字HalfWord(16位)和字Word(32位)。
  • 地址是否自增:指定一次转运完成后,下一次转运是否要把地址移动到下一个位置。
  • 如果要进行存储器到存储器的数据转运,就需要把其中一个存储器的地址放在外设站点(左侧表格)。
  • 传输计数器:指定转运次数,是一个自减计数器。当减到0后DMA就不会再进行数据转运了,之前自增的地址也会恢复到起始地址的位置。
  • 自动重装器:指定传输计数器自减到0后,是否要自动恢复到最初的值。如果不重装,就是正常的单次模式;如果重装,就是循环模式。
  • M2M(Memory to Memory):选择触发源(决定DMA在什么时机开始转运),置1为软件触发,置0为硬件触发。
  • 软件触发:以最快的速度,连续不断地触发DMA,快速清零传输计数器,完成本轮转换。软件触发和自动重装器的循环模式不能同时使用,会导致DMA一直工作。一般用于存储器到存储器的转运。
  • 硬件触发:触发源可以选择ADC、串口、定时器等。
  • 写传输计数器时,必须先关闭DMA,再进行。

每个通道的硬件触发源不同, 必须使用它所在的通道。使用软件触发,通道可以任意选择。

5. 例:数据转运+DMA

将SRAM里的数组DataA,转运到另一个数组DataB中。

  • 外设地址:DataA数组的首地址;存储器地址:DataB数组的首地址
  • 数据宽度:两个数组的类型都是uint8_t,所以数据宽度按8位字节传输
  • 地址是否自增:左右两个数组都自增
  • 方向:由外设站点到存储器站点
  • 传输计数器:需要转运7次,所以值给7
  • 软件触发:因为是存储器到存储器的数据转运,不需要等待硬件时机
  • 自动重装器:不需要,转运7次后传输计数器自减到0,DMA停止,转运完成

这里的数据转动是一种复制转运,转运完成后DataA的数据并不会消失。 

6. 例:ADC扫描模式+DMA 

左边是ADC扫描模式的执行流程,触发一次后,7个通道依次进行AD转换,转换结果均放在ADC_DR数据寄存器内。需要在每个单独的通道转换完成后,进行一次DMA数据转运,并且目的地址进行自增。

  • 外设地址:写入ADC_DR寄存器的地址
  • 存储器地址:可以在SRAM中定义一个数组ADValue,然后把ADValue的地址当做存储器的地址
  • 数据宽度:因为ADC_DR和SRAM数组,要的都是uint16_t的数据,所以数据宽度都是16位的半字传输
  • 地址是否自增:外设地址不自增,存储器地址自增
  • 方向:外设站点到存储器站点
  • 传输计数器:有7个通道,所以计数7次
  • 自动重装器:如果ADC是单次扫描,那么DMA的传输计数器可以不自动重装;如果ADC是连续扫描,那么DMA就可以使用自动重装
  • 触发选择:ADC_DR的值在ADC单个通道转换完成后才会有效,所以DMA转运的时机,需要和ADC单个通道转换完成同步,所以DMA的触发选择ADC的硬件触发(ADC扫描模式,在每个单独的通道转换完成后,没有任何标志位,也不会触发中断,但应该会产生DMA请求去触发DMA转运)

7. DMA数据转运

7.1 接线图

7.2 代码

定义一个变量:uint8_t aa = 0x66;变量被存储的地址为2000 0000,其存储的位置是SRAM区。如果在变量前面加const关键字:const uint8_t aa = 0x66;变量被存储的地址为0800 0DF8,其存储的位置是Flash(只读)。当程序中出现大量不需要更改的数据时(比如查找表、字库数据),为了节省SRAM的空间,可以加const,将其定义在Flash内。

DMA初始化步骤:

  1. RCC开启DMA时钟(AHB总线设备)
  2. 直接调用DMA_Init函数,初始化各项参数
  3. 开关控制DMA_Cmd。如果选择硬件触发,需要在对应外设调用XXX_DMACmd,开启触发信号的输出
  4. 如果需要DMA的中断,调用DMA_ITConfig,开启中断输出。再在NVIC里配置相应的中断通道,然后写中断函数
  5. 如果转运完成,传输计数器清零了,这时想再给传输计数器赋值,需要DMA失能、写传输计数器、DMA使能

MyDMA.c(直接起DMA.c会与系统函数冲突)

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
    MyDMA_Size = Size;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//外设站点的起始地址
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设站点的数据宽度
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//外设站点的是否自增
    DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;//存储器站点的起始地址
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器站点的数据宽度
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器站点的是否自增
    DMA_InitStructure.DMA_BufferSize = Size;//缓冲区大小(传输计数器)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向。外设站点作为数据源
    DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//选择硬件触发or软件触发。使用软件触发
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//传输模式(自动重装器)。不自动重装
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);//因为是存储器到存储器的转运,使用的是软件触发,所以通道可以任意选择
    
    DMA_Cmd(DMA1_Channel1, DISABLE);
}

//  调用一次就再启动一次DMA转运。要更改传输计数器的值,需要首先给DMA失能
void MyDMA_Transfer(void)
{
    DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);// 检查DMA1通道1转运完成标志位。转运完成置1
    DMA_ClearFlag(DMA1_FLAG_TC1);// 这个标志位需要手动清除
}

MyDMA.h

#ifndef __MYDMA_H
#define __MYDMA_H

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);

#endif

main.c

#include "stm32f10x.h"                  // Device 
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};//   DMA转运的源端数组
uint8_t DataB[] = {0, 0, 0, 0};//  DMA转运的目的数组

int main(void)
{
    OLED_Init();
    OLED_ShowString(1, 1, "DataA");
    OLED_ShowString(3, 1, "DataB");
    OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);//    显示地址
    OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);

    MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);//    数组名称就是地址,所以不需要加取地址符号

	while(1)
	{
		DataA[0]++;
        DataA[1]++;
        DataA[2]++;
        DataA[3]++;
        //  显示转运前的DataA和DataB
        OLED_ShowHexNum(2, 1, DataA[0], 2);
        OLED_ShowHexNum(2, 4, DataA[1], 2);
        OLED_ShowHexNum(2, 7, DataA[2], 2);
        OLED_ShowHexNum(2, 10, DataA[3], 2);
        OLED_ShowHexNum(4, 1, DataB[0], 2);
        OLED_ShowHexNum(4, 4, DataB[1], 2);
        OLED_ShowHexNum(4, 7, DataB[2], 2);
        OLED_ShowHexNum(4, 10, DataB[3], 2);
        
        Delay_ms(1000);

        MyDMA_Transfer();
        
        //  显示转运后的DataA和DataB
        OLED_ShowHexNum(2, 1, DataA[0], 2);
        OLED_ShowHexNum(2, 4, DataA[1], 2);
        OLED_ShowHexNum(2, 7, DataA[2], 2);
        OLED_ShowHexNum(2, 10, DataA[3], 2);
        OLED_ShowHexNum(4, 1, DataB[0], 2);
        OLED_ShowHexNum(4, 4, DataB[1], 2);
        OLED_ShowHexNum(4, 7, DataB[2], 2);
        OLED_ShowHexNum(4, 10, DataB[3], 2);
        
        Delay_ms(1000);
	}
}

其他引用的头文件和c代码可在此处查阅:OLED.h(【江协STM32】4 OLED调试工具,第5节)、  Delay.h(【江协STM32】3-2 LED闪烁&LED流水灯&蜂鸣器,第1.3节)

转运后结果出现“04 00 00 00”的原因:设置数据宽度和是否自增的参数,外设站点(DMA_PeripheralDataSize_Byte、DMA_PeripheralInc_Enable)和存储器站点(DMA_MemoryDataSize_Byte、DMA_MemoryInc_Enable)的参数名称是不一样的。

8. DMA+AD多通道

8.1 接线图

8.2 代码

基于AD多通道代码修改(【江协STM32】7-1 ADC模数转换器、AD单通道&AD多通道,第3节)。 

把数组AD_Value作为一个外部可调用数组,也放到头文件中声明。

ADC单次扫描+DMA单次转运: 

AD.c

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

void AD_Init(void)
{
    //  1、开启RCC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    //  2、配置ADDCLK的分频器
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    
    //  3、配置GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入。在AIN模式下,GPIO口是无效的,断开GPIO,防止GPIO口的输入输出对模拟电压造成干扰,所以AIN模式就是ADC的专属模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
    
    //  4、配置多路开关,选择规则组的输入通道
    
    //ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_55Cycles5);//   在序列2的位置选择其他通道
    
    //  5、配置ADC转换器
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//  连续转换模式。连续转换(ENABLE)or单次转换(DISABLE)
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//   数据对齐。左对齐or右对齐
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//    触发控制的触发源。不使用外部触发源,使用软件触发
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//  配置ADC是工作在独立模式or双ADC模式
    ADC_InitStructure.ADC_NbrOfChannel = 4;//    通道数目。指定在扫描模式下,总共会用到几个通道。1~16
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;//    扫描转换模式。扫描模式(ENABLE)or非扫描模式(DISABLE)
    ADC_Init(ADC1, &ADC_InitStructure);
    
    //  初始化DMA
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设站点的起始地址
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设站点的数据宽度。想要DR寄存器低16位的数据,所以用半字16位来转运
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设站点的是否自增。不自增,如果自增源头地址就不是DR了
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//存储器站点的起始地址
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存储器站点的数据宽度
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器站点的是否自增
    DMA_InitStructure.DMA_BufferSize = 4;//缓冲区大小(传输计数器)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向。外设站点作为数据源
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//选择硬件触发or软件触发。不使用软件触发
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//传输模式(自动重装器)。不自动重装
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);//ADC1的硬件触发只接在了DMA1的通道1上
    
    DMA_Cmd(DMA1_Channel1, ENABLE);
    ADC_DMACmd(ADC1, ENABLE);//开启ADC到DMA的输出
    //  6、调用ADC_Cmd函数,开启ADC
    ADC_Cmd(ADC1, ENABLE);
    
    //  7、校准
    ADC_ResetCalibration(ADC1);//   复位校准
    while(ADC_GetResetCalibrationStatus(ADC1) == SET);//  返回复位校准的状态。如果没校准完成,就在while空循环内一直等待。软件置1,硬件就会开始复位校准,当复位校准完成后,该位就会由硬件自动清零
    ADC_StartCalibration(ADC1);//   开始校准
    while(ADC_GetCalibrationStatus(ADC1) == SET);//   获取校准状态
    
}

//  调用后ADC开始转换,连续扫描4个通道,DMA也同步进行转运,AD转换结果依次存放在AD_Value数组里
void AD_GetValue(void)
{
    //  因为DMA也是单次模式,所以在触发ADC之前,需要重新写入一下传输计数器
    DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1, 4);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);//   软件触发转换
    
    //  因为转运完成是在转换完成之后,所以等待ADC转换完成的代码就不需要了,只需要等待转运完成
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);// 检查DMA1通道1转运完成标志位。转运完成置1
    DMA_ClearFlag(DMA1_FLAG_TC1);// 这个标志位需要手动清除
}

AD.h

#ifndef __AD_H
#define __AD_H

extern uint16_t AD_Value[4];

void AD_Init(void);
void AD_GetValue(void);

#endif

main.c

#include "stm32f10x.h"                  // Device 
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

int main(void)
{
    OLED_Init();
    AD_Init();
    OLED_ShowString(1,1,"AD0:");
    OLED_ShowString(2,1,"AD1:");
    OLED_ShowString(3,1,"AD2:");
    OLED_ShowString(4,1,"AD3:");

    while(1)
	{
		AD_GetValue();//    数据直接存储到AD_Value数组中
        OLED_ShowNum(1, 5, AD_Value[0], 4);
        OLED_ShowNum(2, 5, AD_Value[1], 4);
        OLED_ShowNum(3, 5, AD_Value[2], 4);
        OLED_ShowNum(4, 5, AD_Value[3], 4);
        Delay_ms(100);//    减缓刷新速度
	}
}

ADC连续扫描+DMA循环转运:

把ADC触发直接放在初始化的最后一行,当ADC触发之后,ADC连续转换,DMA循环转运。这样GetValue函数也就不需要了。

AD.c

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

void AD_Init(void)
{
    //  1、开启RCC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    //  2、配置ADDCLK的分频器
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    
    //  3、配置GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入。在AIN模式下,GPIO口是无效的,断开GPIO,防止GPIO口的输入输出对模拟电压造成干扰,所以AIN模式就是ADC的专属模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
    
    //  4、配置多路开关,选择规则组的输入通道
    
    //ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_55Cycles5);//   在序列2的位置选择其他通道
    
    //  5、配置ADC转换器
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//  连续转换模式。连续转换(ENABLE)or单次转换(DISABLE)
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//   数据对齐。左对齐or右对齐
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//    触发控制的触发源。不使用外部触发源,使用软件触发
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//  配置ADC是工作在独立模式or双ADC模式
    ADC_InitStructure.ADC_NbrOfChannel = 4;//    通道数目。指定在扫描模式下,总共会用到几个通道。1~16
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;//    扫描转换模式。扫描模式(ENABLE)or非扫描模式(DISABLE)
    ADC_Init(ADC1, &ADC_InitStructure);
    
    //  初始化DMA
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设站点的起始地址
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设站点的数据宽度。想要DR寄存器低16位的数据,所以用半字16位来转运
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设站点的是否自增。不自增,如果自增源头地址就不是DR了
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//存储器站点的起始地址
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存储器站点的数据宽度
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器站点的是否自增
    DMA_InitStructure.DMA_BufferSize = 4;//缓冲区大小(传输计数器)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向。外设站点作为数据源
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//选择硬件触发or软件触发。不使用软件触发
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//传输模式(自动重装器)。自动重装
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);//ADC1的硬件触发只接在了DMA1的通道1上
    
    DMA_Cmd(DMA1_Channel1, ENABLE);
    ADC_DMACmd(ADC1, ENABLE);//开启ADC到DMA的输出
    //  6、调用ADC_Cmd函数,开启ADC
    ADC_Cmd(ADC1, ENABLE);
    
    //  7、校准
    ADC_ResetCalibration(ADC1);//   复位校准
    while(ADC_GetResetCalibrationStatus(ADC1) == SET);//  返回复位校准的状态。如果没校准完成,就在while空循环内一直等待。软件置1,硬件就会开始复位校准,当复位校准完成后,该位就会由硬件自动清零
    ADC_StartCalibration(ADC1);//   开始校准
    while(ADC_GetCalibrationStatus(ADC1) == SET);//   获取校准状态
    
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);//   软件触发转换
}

AD.h

#ifndef __AD_H
#define __AD_H

extern uint16_t AD_Value[4];

void AD_Init(void);

#endif

main.c

#include "stm32f10x.h"                  // Device 
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

int main(void)
{
    OLED_Init();
    AD_Init();
    OLED_ShowString(1,1,"AD0:");
    OLED_ShowString(2,1,"AD1:");
    OLED_ShowString(3,1,"AD2:");
    OLED_ShowString(4,1,"AD3:");

    while(1)
	{
        OLED_ShowNum(1, 5, AD_Value[0], 4);
        OLED_ShowNum(2, 5, AD_Value[1], 4);
        OLED_ShowNum(3, 5, AD_Value[2], 4);
        OLED_ShowNum(4, 5, AD_Value[3], 4);
        Delay_ms(100);//    减缓刷新速度
	}
}

其他引用的头文件和c代码可在此处查阅:OLED.h(【江协STM32】4 OLED调试工具,第5节)、  Delay.h(【江协STM32】3-2 LED闪烁&LED流水灯&蜂鸣器,第1.3节)

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐