[STM32F1]基于STM32F103+TEA5767的收音机实现

前言

前段时间给表弟捯饬了一个基于TEA5767模块的收音机,虽说目前收音机本身已经在市场没啥存在感了,但是技术的运用还是具有一定的研究意义,特别是对我这种技术新人来说,做一次简易的小玩意,可熟悉单片机的一些基础外设,同时可通过这个小玩意锻炼一下自己的画板能力,不得不说,画板真是我的硬伤,**大佬看到我的PCB图下手轻点。

一、方案选型

l 主控:STM32F103C8T6,主要考虑使用之前最熟练的单片机,在画板和代码编写上更为自由方便。
l 收音机模块:TEA5767收音机模块,因为本人硬件水平欠佳,直接买了模组,使用IIC接口进行通信。
l 音频功放:LM386D。
l 显示器:0.96 OLED

二、功能概述

l 通过0.96 OLED液晶实时显示收音机的频率。
l 2、通过按键可以调节频率,当调制解调成功后,喇叭输出广播或者通过耳机进行收听,喇叭音量可通过可调电阻进行控制。
l 3、频率调节范围:87.5MHZ--108MHZ。
l 4、可一键自动搜台。
三、系统结构

因手头9V的电源很多,所以此处电源的输入为9V DC电源,通过降压电路将9V的电源降至5V和3.3V,5V给TEA5767收音机模块、音频功放电路和OLED的显示,3.3V给单片机供电。STM32F103通过IIC和TEA5767通信,音频输出可以通过耳机或者通过音频功放电路通过喇叭进行输出,喇叭输出电路可通过可调电阻进行调节,通过按键进行频道的加减,也可通过自动搜台按键自动搜索可用频道,当前的频道可通过OLED进行显示。

四、硬件电路设计
1. STM32最小系统

STM32最小系统的电路包括复位电路,晶振电路和电源电路,同时添加一颗LED用于显示供电状态。

2. 电源电路

电源输入为9V直流电源,通过78L05将电压将至5V,再通过HT7533将至3.3V,同时也添加滤波。

3. 按键电路

按键一共三个,分别是加频道、减频道和自动搜台。
4. TEA5767模块电路

5. 0.96寸OLED电路

6.完整电路

7.PCB

五、软件代码设计
1. IIC驱动
TEA5767模块使用IIC协议通信,且对速率要求不高,此处采用软件模拟的方式进行。
首先使用宏定义对GPIO和电平输入/输出进行定义:
复制
#define SDA_RCC                        RCC_APB2Periph_GPIOB

#define SDA_GPIO                GPIOB

#define SDA_GPIO_PIN        GPIO_Pin_7



#define SCL_RCC                        RCC_APB2Periph_GPIOB

#define SCL_GPIO                GPIOB

#define SCL_GPIO_PIN        GPIO_Pin_6



#define SCL_OUT() SCL_Set_Output() //置位scl

#define SET_SCL() GPIO_SetBits(SCL_GPIO, SCL_GPIO_PIN) //置位scl

#define CLE_SCL() GPIO_ResetBits(SCL_GPIO, SCL_GPIO_PIN)//清楚scl

                    

#define SDA_OUT() SDA_Set_Output()

#define SDA_INT() SDA_Set_Input()

#define SET_SDA() GPIO_SetBits(SDA_GPIO, SDA_GPIO_PIN)//置位sda

#define CLE_SDA() GPIO_ResetBits(SDA_GPIO, SDA_GPIO_PIN)//清楚sda

#define SDA_VAL() GPIO_ReadInputDataBit(SDA_GPIO, SDA_GPIO_PIN)



#define SDA_V PBin(7)

#define SDA PBout(7)        

#define SCL PBout(6)        
IIC初始化及相关功能函数定义:
复制
void SCL_Set_Output(void)

{

        GPIO_InitTypeDef  GPIO_InitStructure;

        

        RCC_APB2PeriphClockCmd(SDA_RCC,ENABLE);//使能时钟

        

        GPIO_InitStructure.GPIO_Pin = SCL_GPIO_PIN;                                 

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                  

        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                 

        GPIO_Init(SCL_GPIO, &GPIO_InitStructure);                                                                                 

}        



void SDA_Set_Output(void)

{

        GPIO_InitTypeDef  GPIO_InitStructure;        

        

        RCC_APB2PeriphClockCmd(SDA_RCC,ENABLE);//使能时钟

        

        GPIO_InitStructure.GPIO_Pin = SDA_GPIO_PIN;                                 

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                  

        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                 

        GPIO_Init(SDA_GPIO, &GPIO_InitStructure);                                                                                 

}        



void SDA_Set_Input(void)

{

        GPIO_InitTypeDef  GPIO_InitStructure;



        RCC_APB2PeriphClockCmd(SCL_RCC,ENABLE);//使能时钟        



        GPIO_InitStructure.GPIO_Pin = SDA_GPIO_PIN;                                 

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;                  

        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                 

        GPIO_Init(SDA_GPIO, &GPIO_InitStructure);                                         

}



//******************************************

void init(void)

{         

        SCL_OUT();

    SDA_OUT();



    numbyte = 5;

        numbyte_AMP=5;

    ADDRESS_SEND = 0xC0;// TEA5767写地址 1100 0000

        ADDRESS_RECEIVE=0XC1;//TEA5767读地址 1100 0001

    ADDRESS_AMP=0X8E;        



    FM_PLL=0X302C;

    FM_FREQ=97000000; //开机预设频率 

    PLL_HIGH=0;

    PLL_LOW=0;

    delay_ms(100);// delay100ms();

    delay_ms(100);//delay100ms();

                

    I2C_byte1=0XF0;  //FM模块预设值

    I2C_byte2=0X2C;

    I2C_byte3=0XD0;

    I2C_byte4=0X10;

    I2C_byte5=0X40;

    byte1=0X27;  

    byte2=0X40;

    byte3=0X42;

    byte4=0X46;

    byte5=0XC3;

                

    sendnbyte(&ADDRESS_SEND,numbyte);

    delay_ms(100);//delay100ms();

    AMP_sendnbyte(&ADDRESS_AMP,numbyte_AMP);

}

/**

 [url=home.php?mod=space&uid=247401]@brief[/url] CPU产生一个ACK信号

 @param 无

 [url=home.php?mod=space&uid=266161]@return[/url] 无

*/

void IIC_Ack(void)

{

    SDA_OUT();         // SDA线输出模式   

    SDA=0;            // CPU驱动SDA = 0

    delay_us(5);

    SCL=1;            // CPU产生1个时钟

    delay_us(5);

    SCL=0;

    delay_us(5);

    SDA=1;            // CPU释放SDA总线

}

/**

 [url=home.php?mod=space&uid=247401]@brief[/url] CPU产生一个时钟,并读取器件的ACK应答信号

 @param 无

 [url=home.php?mod=space&uid=266161]@return[/url] 返回0表示正确应答,1表示无器件响应

*/

uint8_t IIC_WaitAck(void)

{

    uint8_t result = 0; 

    

    SDA_INT();          // SDA线输入模式  

    SDA = 1;            // CPU释放SDA总线

    delay_us(5);

    SCL = 1;            // CPU驱动SCL = 1, 此时器件会返回ACK应答

    delay_us(5);

    if(SDA_VAL())

    {

        result = 1;

    }

    else

    {

        result = 0;

    }

    SCL = 0;

    delay_us(5);



    return result;  

} 



//************************************************

//送n字节数据子程序 

void sendnbyte(uchar *sla, uchar n)

{          

        uchar *p;

        sbuf[0]=I2C_byte1;

        sbuf[1]=I2C_byte2;

        sbuf[2]=I2C_byte3;

        sbuf[3]=I2C_byte4;

        I2C_start();                        // 发送启动信号

        sendbyte(sla);                    // 发送从器件地址字节

        checkack();                            // 检查应答位

   if(foo == 1)

        { 

                NACK = 1;

                return;                    // 若非应答表明器件错误置错误标志位NACK

        }

        delay_us(5);

        

        p = &sbuf[0];

        while(n--)

        { 

                sendbyte(p);

                checkack();            // 检查应答位

                

                delay_us(5);

                

                if (foo == 1)

                {

                        NACK=1;

                        return;            // 若非应答表明器件错误置错误标志位NACK

                }

                p++;

        }

        stop();                            // 全部发完则停止

}

/**

 @brief CPU从I2C总线设备读取8bit数据

 @param 无

 @return 读到的数据

*/ 

uint8_t IIC_ReadByte(void)

{

    uint8_t i = 0;

    uint8_t value = 0;

    

    SDA_INT();          // SDA线输入模式

    for(i = 0; i < 8; i++)

    {

        value <<= 1;

        SCL=1;

        delay_us(5);//DELAY5US;

        if(SDA_VAL())

        {

            value++;

        }            

        SCL=0; 

        delay_us(5);//DELAY5US;

    }                                        

    IIC_Ack();  

    return value;

}

/**

 @brief 读TEA5767状态

 @param 无

 @return 无

*/

void TEA5767_Read(void)

{

    uint8_t i;

    uint8_t tempLow;

    uint8_t tempHigh;

        uint8_t addr;

        TEA5767_ADDR_R = 0xc1    ;

    s_pll = 0;

    

    I2C_start();

    sendbyte(&TEA5767_ADDR_R);                                       // TEA5767读地址

    IIC_WaitAck();

    for(i = 0; i < 5; i++)                                              // 读取5个字节数据

    {

        s_radioReadData[i] = IIC_ReadByte();                            // 读取数据后,发送应答

    }

    stop();

    tempLow = s_radioReadData[1];                                       // 得到s_pll低8位 

    tempHigh = s_radioReadData[0];                                      // 得到s_pll高6位

    tempHigh &= 0x3f;

    s_pll = tempHigh * 256 + tempLow;                                   // PLL值 

}



//*************************************************

//在SCL为高时,SDA由高变低即为I2C传输开始

void I2C_start(void)   

{

        SCL_OUT();

    SDA_OUT();

        

   SDA=1;

   SCL=1;

   delay_us(5);//DELAY5US;

   SDA=0;

   delay_us(5);//DELAY5US;

   SCL=0;

}



void stop(void)         //在SCL为高时,SDA由低变高即为I2C传输结束

{

        SCL_OUT();

        SDA_OUT();

        SDA=0;

        SCL=1;

        delay_us(5);//DELAY5US;

        SDA=1;

        delay_us(5);//DELAY5US;

        SCL=0;

}

//****************************************************

//发送一个字节数据子函数

void sendbyte(uchar *ch)

{ 

        uchar  n00 = 8;  

        uchar  temp00;

        temp00 = *ch;



        SCL_OUT();

        SDA_OUT();        

        while(n00--)

        { 

                if((temp00&0x80) == 0x80)    // 若要发送的数据最高位为1则发送位1

                {

                        SDA = 1;    // 传送位1

                        SCL = 1;

                        delay_us(15);//DELAY5US;

                    SCL = 0; 

                        SDA = 0;

                }

                else

                {  

                        SDA = 0;    // 否则传送位0

                        SCL = 1;

                        delay_us(15);//DELAY5US;

                        SCL = 0;  

                }

                temp00 = temp00<<1;    // 数据左移一位

        }

}

//发送n字节数据子程序

void AMP_sendnbyte(uchar  *sla, uchar n)

{          

        uchar  *p;

    ampint[0]=byte1;

    ampint[1]=byte2;

    ampint[2]=byte3;

    ampint[3]=byte4;

    ampint[4]=byte5;        

        I2C_start();                        // 发送启动信号

        sendbyte(sla);                    // 发送从器件地址字节

        checkack();                            // 检查应答位

        

        delay_us(5);

    if(foo == 1)

        { 

                NACK = 1;

                return;                    // 若非应答表明器件错误置错误标志位NACK

        }

        p=&int[0];

        while(n--)

        { 

                sendbyte(p);

                checkack();            // 检查应答位

                delay_us(5);                

                if (foo == 1)

                {

                        NACK=1;

                        return;            // 若非应答表明器件错误置错误标志位NACK

                }

                p++;                                                                                                                                                  

        }

        stop();                            // 全部发完则停止

}

//****************************************

//向上搜索        

void search_up(void)

{ 

   I2C_byte1 |= MUTEI2CB1;//MUTE=1;                        //静音

   I2C_byte3 |= SUDI2CB3;//SUD=1;                //搜索标志位设为向上

   if(FM_FREQ>108000000){FM_FREQ=87500000;} //        判断频率是否到顶

   FM_FREQ=FM_FREQ+100000;                        //频率加100K

   FM_PLL=(unsigned short)((4000*(FM_FREQ/1000+225))/32768);//计算PLL值

   setByte1Byte2();        //设置I2C第一第二字节PLL        值

        display();           

}

//*******************************

// 向下搜索

void search_down(void)

{    

   I2C_byte1 |= MUTEI2CB1;//MUTE=1;        //静音

   I2C_byte3 &= ~SUDI2CB3;// SUD=0;//搜索标志位设为向下

   if(FM_FREQ<87500000){FM_FREQ=108000000;}  //        判断频率是否到底

   FM_FREQ=FM_FREQ-100000;                                 //频率减100K

   FM_PLL=(unsigned short)((4000*(FM_FREQ/1000+225))/32768);         //计算PLL值

   setByte1Byte2();                //设置I2C第一第二字节PLL        值

        display();        

}

//*******************************

// 自动搜索

void TEA5767_AutoSearch(void)

{

        OLED_ShowString(10,5,"Auto Mode...",16); 

    // 直到搜台成功,RF=1,0x31<IF<0x3E

    while((radioRf==0) || ((0x31>=radioIf)||(radioIf>=0x3E)))

        {

                ADDRESS_SEND = 0xC0;// TEA5767写地址 1100 0000

                ADDRESS_RECEIVE=0XC1;//TEA5767读地址 1100 0001

                FM_FREQ=FM_FREQ+100000;        

                if(FM_FREQ > (TEA5767_MAX_KHZ*1000))                           // 频率达到最大值

                {

                        FM_FREQ = (TEA5767_MIN_KHZ*1000);

                }

                FM_PLL=(unsigned short)((4000*(FM_FREQ/1000+225))/32768);         //计算PLL值

                setByte1Byte2();        //设置I2C第一第二字节PLL        值

                delay_ms(20);

                TEA5767_Read();                                                 // 读取当前频率值 

                radioRf = s_radioReadData[0] & 0x80;

        radioIf = s_radioReadData[2] & 0x7F;

        radioLev = s_radioReadData[3] >> 4;        

                display();

        }

        radioRf = 0;

        radioIf = 0;

        radioLev = 0;

        OLED_ShowString(10,5,"PLAYING.....",16); 

}

//应答位检查子函数

void checkack(void)

{ 

        SCL_OUT();

        SDA_OUT();

        SDA_INT();        

        

        SDA = 1;                    // 应答位检查(将p1.0设置成输入,必须先向端口写1)        

        SCL = 1;

        foo = 0;

        

        delay_us(20);//DELAY5US;

        if(SDA_V == 1)            // 若SDA=1表明非应答,置位非应答标志F0

   foo = 1;

        SCL = 0;

        

        SDA_OUT();

}

//第一第二字节PLL值设定

void setByte1Byte2(void)

{

   PLL_HIGH=(uchar)((FM_PLL >> 8)&0X3f);         //PLL高字节值

   I2C_byte1=(I2C_byte1&0XC0)|PLL_HIGH;                 //I2C第一字节值

   PLL_LOW=(uchar)FM_PLL;                                         //PLL低字节值

   I2C_byte2= PLL_LOW;                                                 //I2C第二字节值

   sendnbyte(&ADDRESS_SEND,numbyte);             //I2C数据发送

   I2C_byte1 &= ~MUTEI2CB1;//MUTE=0;

   delay_ms(100);//delay100ms();                                                  //延时100ms

   sendnbyte(&ADDRESS_SEND,numbyte);          //I2C        数据发送

   delay_us(5);//DELAY5US;

}
2.在主函数循环检测按键状态
复制
 int main(void)

 {                

        RCC_ClocksTypeDef ClockInfo;



        SystemInit();

        delay_init();                     //延时函数初始化          

        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级

//        uart_init(115200);         //串口初始化为115200

         LED_Init();                             //LED端口初始化

        TIM3_Int_Init(499,7199);//10Khz的计数频率,计数到5000为500ms  

        RCC_GetClocksFreq(&ClockInfo);

        init();                              //  初始化TEA5767

        KEY_Init();

        OLED_Init();                        //初始化OLED  

        OLED_Clear(); 

        //上电初始化界面

        num1=FM_FREQ/100000000;                                                //提取频率值

        num2=(FM_FREQ%100000000)/10000000;

        num3=(FM_FREQ%10000000)/1000000;

        num4=(FM_FREQ%1000000)/100000;

        OLED_ShowString(35,0,"TEA5767",16); 

        OLED_ShowString(0,3,"FM:",16);                  

        OLED_ShowNum(50,3,num1,1,16);

        OLED_ShowNum(60,3,num2,1,16);

        OLED_ShowNum(70,3,num3,1,16);

        OLED_ShowString(80,3,".",16); 

        OLED_ShowNum(85,3,num4,1,16);

        OLED_ShowString(100,3,"MHz",16);                 

         while(1)

        {

                if((KEY0==0)||(KEY1==0)||(KEY2==0))//按键按下

                {

                        {

                                delay_ms(10);//消除抖动



                                if(KEY0==0)                //按键显示切换

                                {

                                        rekey = 1;

                                        search_up();          //频率向上

                                        delay_ms(200);//消除抖动

                                }

                                else  if(KEY1==0)                //按键显示切换

                                {

                                        rekey = 1;

                                        search_down();          //频率向上

                                        delay_ms(200);//消除抖动

                                }

                                else  if(KEY2==0)                //按键显示切换

                                {

                                        rekey = 1;

                                        TEA5767_AutoSearch();          //自动搜台

                                        delay_ms(200);//消除抖动

                                }

                        }                

                }                

                        



        }                                                                                            

}        

 

附录
写数据

向TEA5767 写入数据时,地址的最低位是0,即写地址是C0。读出数据时地址的最低位是1,即读地址是C1。TEA5767的控制寄存器要写入5个字节,每次写入数据时必须严格按照下列顺序进行:

地址、字节1、字节2、字节3、字节4、字节5。


 

每个字节的最高位首先发送。在时钟的下降沿后写入的数据生效。上电复位后,设置为静音,所有其它位均被置低,必须写入控制字初始化芯片。



 

TEA5767内部有一个5个字节的控制寄存器,在IC上电复位后必须通过总线接口向其中写入适当的控制字,它才能够正常工作。寄存器介绍如下

数据字节1的格式

数据字节1各位的说明

数据字节2的格式

数据字节2各位的说明

数据字节3的格式

数据字节3各位的说明

搜索停止电平设定

数据字节4的格式

数据字节4各位的说明

数据字节5的格式

数据字节5各位的说明

读数据

和写数据类似,从TEA5767 读出数据时,也要按照“地址、字节1、字节2、字节3、字节4、字节5”这样的顺序读出,读地址是C1。

举例

根据上面的算法,以106.8的天津交通台为例,它的PLL为32d1H,第一个字节的BIT7=0非静音,BIT6=0不搜索,第三个字节的BIT4=0低本振,第四个字节的BIT5=0欧美制式,BIT4=1用32768晶振,其余位的设置无所谓,可任意。
---------------------
作者:呐咯密密
链接:https://bbs.21ic.com/icview-3334598-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值