STM32定时器触发DMA传输及产生特定控制时序的应用

目录

一、前言

二、电路设计

三、程序设计

四、总结

五、参考资料


一、前言

最近闲着没事,搞了个“旋转LED”的小电路板,自己设计的电路板,上面有64个贴片LED排成一排显示,本文要介绍的是用定时器触发+DMA传输的方式在IO口上产生74HC573和74HC238的控制时序,完成循环点亮64个LED的功能。记录下调试的过程。

二、电路设计

用的单片机是STM32F103C8T6,直接用单片机引脚连接每个LED肯定是不够用的,因为也就只有30多个控制引脚,想了一下用了8个74HC573来控制,用PA0到PA7这8个IO统一连接到8个573芯片的输入端,如下图所示:

用一片74HC238芯片来控制8个573芯片的LE脚,如下图,74HC238和74HC138芯片功能是类似的,只不过74HC238的有效输出电平是高电平而已,和74HC138正好相反。

64个LED的连接电路如下:

三、程序设计

LED显示并没有采用while循环使劲刷新LED状态的方法,而是采用定时器触发DMA传输数据到GPIO口来改变GPIO口输出状态的方式来达到刷新LED显示状态的目的,整个时序刷新过程不需要CPU的干预,原理如下:

我们划分出一块连续的RAM内存区域作为“显存”,用定时器来产生固定周期频率的定时器溢出事件,然后将DMA的传输与定时器溢出事件进行绑定,DMA模块就可以循环往复的将“显存”内的数据送往GPIOA,让GPIOA的16个IO引脚根据“显存”输出不同的电平状态,也就产生了特定的控制时序,达到控制外围芯片的目的。

定时器触发DMA传输的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
WORD wLedStateBuf[64 + 8];

void TIM3_Init()
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; 

    NVIC_InitTypeDef NVIC_InitStructure;

    /* 开启定时器3时钟 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

    TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
    TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE );    //关闭定时器更新中断
   
    TIM_TimeBaseInitStructure.TIM_Period = 1000;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); 
    TIM_Cmd(TIM3,ENABLE);
   
    /* 设置NVIC参数 */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
   
    TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);  //定时器更新事件触发DMA传输
}

void DMA1_Init()       //DMA初始化
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
   
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

    DMA_DeInit(DMA1_Channel3);
    DMA_InitStructure.DMA_PeripheralBaseAddr =(u32)&GPIOA->ODR;//DMA外设地址
    DMA_InitStructure.DMA_MemoryBaseAddr=(u32)wLedStateBuf;//DMA内存地址
    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralDST;//外设作为数据传输的来源
    DMA_InitStructure.DMA_BufferSize=64 + 8;//指定DMA通道的DMA缓存的大小
    DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设地址寄存器递增
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_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_InitStructure.DMA_Priority=DMA_Priority_High;//DMA通道x拥有高优先级
    DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输
    DMA_Init(DMA1_Channel3,&DMA_InitStructure); //TIM3更新事件在DMA1通道3内
    DMA_Cmd(DMA1_Channel3,ENABLE);//使能DMA1通道3

    DMA_Cmd(DMA1_Channel3,ENABLE);
}

100ms触发一次DMA传输,具体的DMA通道不是随意选择的,需要查表看定时器3的更新事件映射在DMA1的哪个通道。

LED初始化和main函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void LED_Init()  
{
    GPIO_InitTypeDef GPIO_InitStructure;   

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); /* 开启GPIO时钟 */
       
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 \
                                                                | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 \
                                                                | GPIO_Pin_10 | GPIO_Pin_11;     
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;   
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  
    GPIO_Init(GPIOA,&GPIO_InitStructure);
   
    GPIOA->ODR &= ~((uint16_t) 0x0FFF);
}

int main()
{
    int i;

    //给显存赋值,内容为64个LED循环依次点亮
    for(i=0; i<64 + 8; i++)
    {
        if(i % 9 == 8)
        {
            wLedStateBuf[i] = (uint32_t)(i / 9 + 1) << 8;
        }
        else
        {
            //利用8、9、10引脚选中某个573芯片,0~7引脚操作要亮的LED
            wLedStateBuf[i] = ((uint16_t)(i / 9) << 8) | ((uint16_t)1 << (i % 9));
        }
    }

    LED_Init();
    TIM3_Init();
    DMA1_Init();

    while(1)
    {
        //处理其他事情
    }
}

用这种方式不需要CPU去不断的刷时序了,这些都由DMA去完成了。CPU要做的只是根据需要去改变“显存”里的东西,腾出时间给CPU去处理其他事情。

用逻辑分析仪抓取各个GPIO引脚的时序图如下:

四、总结

定时器触发+DMA传输的方式可以完成很多操作,就如本文所述,用这种方式来产生74HC573和74HC238的控制时序。还有比如我们要产生16路甚至更多路的精确的PWM波时,用定时器产生的话通道数不够而且必须在特定的引脚上才能产生,使用本文所述这种方式同样不需要占用CPU的处理时间而且输出引脚任意选择非常方便。再比如还有定时器+DMA传输+DAC输出的方式可以用来输出各种各样的波形,非常好用。

五、参考资料

《STM32F10xxx中文参考手册_V10》

链接:https://pan.baidu.com/s/14NGfTZkjhRfx9AXFhT8W5Q 提取码:8g4w