設置 | 登錄 | 註冊

目前共有10篇帖子。

【程序】使用定時器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++;
    }
}
10樓 巨大八爪鱼 2024-4-10 13:40
頂🆙

內容轉換:

回覆帖子
內容:
用戶名: 您目前是匿名發表。
驗證碼:
看不清?換一張
©2010-2025 Purasbar Ver3.0 [手機版] [桌面版]
除非另有聲明,本站採用知識共享署名-相同方式共享 3.0 Unported許可協議進行許可。