新研究了個東東,家里的廢舊顯示器終於有了利用價值


這些年下來,家里,公司有很多廢舊的電視機,顯示器,投影機。你說扔掉吧,有點可惜,賣給收廢品的吧,其實和扔也差不多。總想着怎么把這個淘汰下來的顯示器給利用上呢。

這些顯示器都有個共性,就是帶有VGA接口。上網搜索研究了一下,發現VGA接口是可以編程驅動的。

VGA的電氣接口除了GND以外,基本的必須有5條信號線:hsync行同步,vsync場同步,red紅,green綠,blue藍。VGA的時序要求是比較嚴格的,差一點點都無法正常顯示。具體的VGA時序,這里就不贅述了,大家可以網上搜索一下。

由於我打算用單片機實現VGA的時序,使用STM32F103測試后可以實現,但由於103的內存太少了,像素的計算搬運有點吃力,最后還是決定使用STM32F4,手頭剛好有F401,主頻84M,內存64K,足夠我使用了。

我這里設計的是一個320*200(橫向320個點,縱向200行)的VGA輸出音樂頻譜模塊,基本參數如下:

電源電壓:DC5-12V

工作電流:<30mA

頻率響應300-18kHz

聲道數:2

 

為了能夠穩定輸出時序,使用了兩個定時器中斷分別輸出行頻和場頻。其次需要對音頻進行40Khz的高速采樣,這里也使用了一個定時器+DMA,最后還需要對音頻進行RFFT運算,得到幅值后轉換為像素顯示。前前后后打了5次PCB,花了將近4個月的時間完成,中間也遇到不少坑,這里只把最后的成果展示一下,作為疫情宅家紀念。

關鍵代碼:

使用TIM1和TIM2分別做為行輸出和場輸出信號,在行輸出中斷中,使用GPIOB發送顏色信號,在場消隱中計數,並復位圖像顯示頭部。

void TIMER_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef nvic;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    u32 TimerPeriod = 0;
    u16 Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0;

    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource8,GPIO_AF_TIM1);
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM2);
    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_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
//    RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_4);
//return;
        
    /*
        Horizontal timing
        -----------------
    
    */

    TimerPeriod = 2048;
    Channel1Pulse = 144;        /* HSYNC */
    Channel2Pulse = 352;         /* HSYNC + BACK PORCH */
    
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV2;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
    TIM_OCInitStructure.TIM_Pulse = Channel1Pulse;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Set;

    TIM_OC1Init(TIM1, &TIM_OCInitStructure);
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive;
    TIM_OCInitStructure.TIM_Pulse = Channel2Pulse;
    TIM_OC2Init(TIM1, &TIM_OCInitStructure);

    /* TIM1 counter enable and output enable */
    TIM_CtrlPWMOutputs(TIM1, ENABLE);

    /* Select TIM1 as Master */
    TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);
    TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
    
    /*
        Vertical timing
        ---------------        
    */

    /* VSYNC (TIM2_CH2) and VSYNC_BACKPORCH (TIM2_CH3) */
    /* Channel 2 and 3 Configuration in PWM mode */
    TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Gated);
    TIM_SelectInputTrigger(TIM2, TIM_TS_ITR0);
    
    TimerPeriod =600;//625;        /* Vertical lines */
    Channel2Pulse = 2;        /* Sync pulse */
    Channel3Pulse = 24;        /* Sync pulse + Back porch */
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
    TIM_TimeBaseStructure.TIM_ClockDivision =2;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
    TIM_OCInitStructure.TIM_Pulse = Channel2Pulse;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Set;
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive;
    TIM_OCInitStructure.TIM_Pulse = Channel3Pulse;
    TIM_OC3Init(TIM2, &TIM_OCInitStructure);

    /*    TIM2 counter enable and output enable */
    TIM_CtrlPWMOutputs(TIM2, ENABLE);

    /* Interrupt TIM2 */
    nvic.NVIC_IRQChannel = TIM2_IRQn;
    nvic.NVIC_IRQChannelPreemptionPriority = 0;
    nvic.NVIC_IRQChannelSubPriority = 0;
    nvic.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&nvic);
    TIM_ITConfig(TIM2, TIM_IT_CC3, ENABLE);

    /* Interrupt TIM1 */
    nvic.NVIC_IRQChannel = TIM1_CC_IRQn;
    nvic.NVIC_IRQChannelPreemptionPriority = 0;
    nvic.NVIC_IRQChannelSubPriority = 0;
    nvic.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&nvic);
    TIM_ITConfig(TIM1, TIM_IT_CC2, ENABLE);
    
    TIM_Cmd(TIM2, ENABLE);
    TIM_Cmd(TIM1, ENABLE);

}

為了提高速度,一開始使用了DMA傳輸像素數據,但是,太快了,在行中斷后,DMA輸出的速度,經過多次嘗試無法控制在合理的時序內,導致顯示器識別的分辨率過高,圖像縮成一點點。最后還是放棄了,使用CPU循環將點陣輸出。

 

以下是電路圖:

以下是PCB

以下是實物圖和效果圖

 

可以當時鍾用

視頻鏈接:https://v.youku.com/v_show/id_XNDUzMzAyNDExMg==.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM