目前共有9篇帖子。 內容轉換:不轉換▼
 
點擊 回復
574 8
【程序】使用定时器DMA进行全自动8位数码管动态扫描
一派護法 十九級
1樓 發表于:2017-4-8 15:41

#include <stm32f10x.h>

const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; // 数码管0~9段码表
uint16_t segbuf[8][16]; // 共有8个数码管, 点亮每个数码管需要传送16位数据

void delay(void)
{
    uint32_t i;
    for (i = 0; i < 200000; i++);
}

// 设置数码管显示的数字
// 向74HC595发送的数据: SerIn(seg8[n]); SerIn(1 << i); ParOut(); (先段选后位选)
// 段选为低电平, 位选为高电平时点亮相应的笔画(共阳)
void seg_set(uint32_t num)
{
    uint8_t i, j;
    uint16_t data, temp;
    for (i = 0; i < 8; i++) // 从低位扫描到高位
    {
        data = (seg8[num % 10] << 8) | (1 << i); // 要向74HC595发送的数据
        for (j = 0; j < 16; j++) // 每一份CCER寄存器的值决定一位数据
        {
            temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E;
            if (j == 0)
                temp |= TIM_CCER_CC3P; // RCLK=1, 显示上次(第i-1次或第7次)点亮的数码管
            if (data & 0x8000)
                temp |= TIM_CCER_CC2P; // DIO=1
            segbuf[i][j] = temp;
            data <<= 1;
        }
        num /= 10;
    }
}

// 数码管熄灭
void seg_clr(void)
{
    uint8_t i, j;
    uint16_t temp;
    for (i = 0; i < 8; i++)
    {
        for (j = 0; j < 16; j++)
        {
            temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E;
            if (j == 0)
                temp |= TIM_CCER_CC3P;
            if (j < 8)
                temp |= TIM_CCER_CC2P; // 相当于上面的data=0xff00
            segbuf[i][j] = temp;
        }
    }
}

// 数码管动态扫描初始化
void seg_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
    TIM4->ARR = 624; // 定时时间: 625*0.25us=156.25us, 每个数码管点亮的时间: 16*156.25us=2.5ms
    TIM4->PSC = 17; // 基准: 72MHz/18=4MHz->0.25us
    TIM4->CR1 = TIM_CR1_URS; // EGR_UG=1不置位SR_UIF
    TIM4->EGR = TIM_EGR_UG; // 保存上述设置
    
    TIM4->CCMR1 = TIM_CCMR1_OC2M_2; // 通道2为强制输出模式, 输出的电平由CC2P决定
    TIM4->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC4M; // 通道3也是强制输出模式; 通道4为PWM模式2(用于产生SCLK), 先低电平后高电平
    TIM4->CCR4 = 600; // SCLK上升沿的出现时间(相对于TIM4->ARR)
    
    TIM4->DCR = (&TIM4->CCER - &TIM4->CR1) / 2; // 传送的寄存器为CCER, 每次只传送一个寄存器(DBL=0)
    TIM4->DIER = TIM_DIER_UDE; // 当定时器溢出时产生DMA请求, 对应的DMA通道是DMA1_Channel7
    
    // 配置时DMA必须处于关闭状态
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    DMA1_Channel7->CCR = DMA_CCR7_MSIZE_0 | DMA_CCR7_PSIZE_0 | DMA_CCR7_MINC | DMA_CCR7_CIRC | DMA_CCR7_DIR;
    DMA1_Channel7->CMAR = (uint32_t)segbuf;
    DMA1_Channel7->CNDTR = sizeof(segbuf) / sizeof(uint16_t);
    DMA1_Channel7->CPAR = (uint32_t)&TIM4->DMAR;
    DMA1_Channel7->CCR |= DMA_CCR7_EN; // 开DMA
    
    TIM4->CR1 |= TIM_CR1_CEN; // 开始动态扫描
}

int main(void)
{
    uint32_t num = 12345678;
    
    // 数码管动态扫描管脚配置(复用推挽50MHz输出)
    RCC->APB2ENR = RCC_APB2ENR_IOPBEN;
    GPIOB->CRL = 0xb0000000; // PB7->DIO(TIM4_CH2)
    GPIOB->CRH = 0x000000bb; // PB8->RCLK(TIM4_CH3), PB9->SCLK(TIM4_CH4)
    
    seg_init();
    while (1)
    {
        // 主循环里只负责改变数字, 不负责扫描数码管
        // 数码管的扫描任务完全交给DMA处理
        if (num % 100 < 20)
            seg_clr(); // 当最后两位小于20时熄灭数码管
        else
            seg_set(num);
        delay();
        num++;
    }
}
一派護法 十九級
2樓 發表于:2017-4-8 16:06

【原理说明】
通道4通过PWM模式2产生时钟信号SCLK,当计数值CNT<CCR4时输出低电平, 否则输出高电平
因此SCLK的上升沿出现在CNT=CCR4的瞬间,此时DIN中的数据移入74HC595

定时器每溢出一次,就向74HC595传输一位数据。每传输完16位数据, 在传送下一位时将RCLK置1,使74HC595刷新输出端, 点亮一位数码管。
通道2(连接数据端DIO)和通道3(连接输出锁存时钟端RCLK)都配置为强制输出模式,输出的电平仅由CCER寄存器中的CCxP决定。
而每次定时器溢出触发的DMA传输更新的就是CCER寄存器(TIM4->CCER=segbuf[i][j])。

【可靠性分析】
若在某一时刻需要熄灭数码管,则调用seg_clr函数。假设熄灭数码管时DMA刚好正在发送百位数码管段选的第6位,该位数码管显示的是0xa4。
也就是:10010[1]00
本来点亮该位数码管时发送的是: 10010100 00000100
但由于seg_clr函数将segbuf[2]_CC2P置成了:11111111 00000000
因此DMA实际发送的是: 10010111 00000000,由于位选为0,所以最终没有数码管被点亮, 不会出现闪烁。
更新数字时调用seg_set函数,因为segbuf变量中存储的低八位(位选代码)始终是固定的,所以不会出现篡位显示的情况。
一派護法 十九級
3樓 發表于:2017-4-8 16:09

【程序的运行效果】

一派護法 十九級
4樓 發表于:2017-4-8 16:12

【注意】
如果使用高级定时器TIM1/TIM8,则必须要将BDTR寄存器中的MOE置1(开总输出),否则4个输出通道将只能输出低电平:
TIM1->BDTR = TIM_BDTR_MOE; // 开总输出
一派護法 十九級
5樓 發表于:2017-4-8 20:02

【三个数码管的动态扫描】
#include <stm32f10x.h>

const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};
uint16_t segbuf[3][16];

void delay(void)
{
    uint32_t i;
    for (i = 0; i < 2000000; i++);
}

void seg_set(uint32_t num)
{
    uint8_t i, j;
    uint16_t data, temp;
    for (i = 0; i < sizeof(segbuf) / sizeof(segbuf[0]); i++)
    {
        data = (seg8[num % 10] << 8) | (1 << i);
        for (j = 0; j < 16; j++)
        {
            temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E;
            if (j == 0)
                temp |= TIM_CCER_CC3P;
            if (data & 0x8000)
                temp |= TIM_CCER_CC2P;
            segbuf[i][j] = temp;
            data <<= 1;
        }
        num /= 10;
    }
}

void seg_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
    TIM4->ARR = 624;
    TIM4->PSC = 17;
    TIM4->CR1 = TIM_CR1_URS;
    TIM4->EGR = TIM_EGR_UG;
   
    TIM4->CCMR1 = TIM_CCMR1_OC2M_2;
    TIM4->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC4M;
    TIM4->CCR4 = 600;
   
    TIM4->DCR = (&TIM4->CCER - &TIM4->CR1) / 2;
    TIM4->DIER = TIM_DIER_UDE;
   
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    DMA1_Channel7->CCR = DMA_CCR7_MSIZE_0 | DMA_CCR7_PSIZE_0 | DMA_CCR7_MINC | DMA_CCR7_CIRC | DMA_CCR7_DIR;
    DMA1_Channel7->CMAR = (uint32_t)segbuf;
    DMA1_Channel7->CNDTR = sizeof(segbuf) / sizeof(uint16_t);
    DMA1_Channel7->CPAR = (uint32_t)&TIM4->DMAR;
    DMA1_Channel7->CCR |= DMA_CCR7_EN;
   
    TIM4->CR1 |= TIM_CR1_CEN;
}

int main(void)
{
    uint8_t num = 0;
   
    RCC->APB2ENR = RCC_APB2ENR_IOPBEN;
    GPIOB->CRL = 0xb0000000; // PB7->DIO(TIM4_CH2)
    GPIOB->CRH = 0x000000bb; // PB8->RCLK(TIM4_CH3), PB9->SCLK(TIM4_CH4)
   
    seg_init();
    while (1)
    {
        seg_set(num);
        delay();
        num++;
    }
}
一派護法 十九級
6樓 發表于:2017-4-8 20:04

因为每次传输只涉及一个寄存器,所以还可以直接将CPAR指向CCER:
//TIM4->DCR = (&TIM4->CCER - &TIM4->CR1) / 2; // 不再需要这一步
DMA1_Channel7->CPAR = (uint32_t)&TIM4->CCER;
一派護法 十九級
7樓 發表于:2017-4-8 20:13

如果把CPAR设为GPIOx->ODR的地址,同时设置好CCR寄存器中的MSIZE和PSIZE(8位或16位),那么就可以操作更多的I/O口,甚至可以脱离74HC595这样的I/O扩展芯片,直接驱动数码管。
一派護法 十九級
8樓 發表于:2017-4-8 20:35

用下面的方法可以消除复位时数码管闪现的随机字符:
seg_init();
seg_clr();
while ((DMA1->ISR & DMA_ISR_TCIF7) == 0); // 等待清屏完毕
DMA1->IFCR |= DMA_IFCR_CTCIF7; // 清除标志
并将seg_clr函数中的if (j == 0)改为:if (i != 0 && j == 0)
但是这种方法不能消除上电时的随机字符,因为DMA的第一个请求是在定时器第一次溢出后才发出,连续发送16个DMA请求后随机字符才能被消除,这需要消耗(16+1)*156.25us=2.65625ms的时间,这还没有将DMA和定时器初始化的时间计算在内。
一派護法 十九級
9樓 發表于:2017-4-8 20:52

【消除复位时的随机字符】
#include <stm32f10x.h>

const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};
uint16_t segbuf[3][16];

void delay(void)
{
    uint32_t i;
    for (i = 0; i < 2000000; i++);
}

void seg_set(uint32_t num)
{
    uint8_t i, j;
    uint16_t data, temp;
    for (i = 0; i < sizeof(segbuf) / sizeof(segbuf[0]); i++)
    {
        data = (seg8[num % 10] << 8) | (1 << i);
        for (j = 0; j < 16; j++)
        {
            temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E;
            if (j == 0)
                temp |= TIM_CCER_CC3P;
            if (data & 0x8000)
                temp |= TIM_CCER_CC2P;
            segbuf[i][j] = temp;
            data <<= 1;
        }
        num /= 10;
    }
}

void seg_clr(void)
{
    uint8_t i, j;
    uint16_t temp;
    for (i = 0; i < sizeof(segbuf) / sizeof(segbuf[0]); i++)
    {
        for (j = 0; j < 16; j++)
        {
            temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E;
            if (i != 0 && j == 0) // 添加i!=0
                temp |= TIM_CCER_CC3P;
            if (j < 8)
                temp |= TIM_CCER_CC2P;
            segbuf[i][j] = temp;
        }
    }
}

void seg_init(void)
{
    seg_clr();
    RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
   
    TIM4->CCMR1 = TIM_CCMR1_OC2M_2;
    TIM4->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC4M;
   
    TIM4->DCR = (&TIM4->CCER - &TIM4->CR1) / 2;
    TIM4->DIER = TIM_DIER_UDE;
   
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    DMA1_Channel7->CCR = DMA_CCR7_MSIZE_0 | DMA_CCR7_PSIZE_0 | DMA_CCR7_MINC | DMA_CCR7_CIRC | DMA_CCR7_DIR;
    DMA1_Channel7->CMAR = (uint32_t)segbuf;
    DMA1_Channel7->CNDTR = sizeof(segbuf) / sizeof(uint16_t);
    DMA1_Channel7->CPAR = (uint32_t)&TIM4->DMAR;
    DMA1_Channel7->CCR |= DMA_CCR7_EN;
   
    TIM4->ARR = 624;
    TIM4->CCR4 = 600;
    //TIM4->PSC = 0; // 以较快的速度刷除复位时的随机字符
    TIM4->EGR = TIM_EGR_UG;
    TIM4->CR1 |= TIM_CR1_CEN;
    // 不能消除上电时产生的随机字符 (只能够通过手动控制74HC595的清零端或输出允许端消除)
   
    while ((DMA1->ISR & DMA_ISR_TCIF7) == 0);
    DMA1->IFCR |= DMA_IFCR_CTCIF7;
   
    TIM4->CR1 |= TIM_CR1_URS; // UG不再将UIF置位
    TIM4->PSC = 17; // 减慢扫描速度
}

int main(void)
{
    uint8_t num = 0;
   
    RCC->APB2ENR = RCC_APB2ENR_IOPBEN;
    GPIOB->CRL = 0xb0000000; // PB7->DIO(TIM4_CH2)
    GPIOB->CRH = 0x000000bb; // PB8->RCLK(TIM4_CH3), PB9->SCLK(TIM4_CH4)
   
    seg_init();
    while (1)
    {
        /*if (num % 4 == 0)
            seg_clr();
        else
            */seg_set(num);
        delay();
        num++;
    }
}

回復帖子

內容:
用戶名: 您目前是匿名發表
驗證碼:
(快捷鍵:Ctrl+Enter)
 

本帖信息

點擊數:574 回複數:8
評論數: ?
作者: 巨大八爪鱼
最後回復:巨大八爪鱼
最後回復時間:2017-4-8 20:52
 
©2010-2024 Arslanbar Ver2.0
除非另有聲明,本站採用創用CC姓名標示-相同方式分享 3.0 Unported許可協議進行許可。