1. 程式人生 > >新研究了個東東,家裡的廢舊顯示器終於有了利用價值

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

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

這些顯示器都有個共性,就是帶有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

&n