呐咯密密 发表于 2023-10-20 15:25

基于STM32F103+TEA5767的收音机实现

#申请原创#@21小跑堂 源码回复下载:
**** Hidden Message *****

前言前段时间给表弟捯饬了一个基于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_InitTypeDefGPIO_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_InitTypeDefGPIO_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_InitTypeDefGPIO_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);
}
/**
@brief CPU产生一个ACK信号
@param 无
@return 无
*/
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总线
}
/**
@brief CPU产生一个时钟,并读取器件的ACK应答信号
@param 无
@return 返回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=I2C_byte1;
      sbuf=I2C_byte2;
      sbuf=I2C_byte3;
      sbuf=I2C_byte4;
      I2C_start();                        // 发送启动信号
      sendbyte(sla);                  // 发送从器件地址字节
      checkack();                            // 检查应答位
   if(foo == 1)
      {
                NACK = 1;
                return;                  // 若非应答表明器件错误置错误标志位NACK
      }
      delay_us(5);
      
      p = &sbuf;
      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 = IIC_ReadByte();                            // 读取数据后,发送应答
    }
    stop();
    tempLow = s_radioReadData;                                       // 得到s_pll低8位
    tempHigh = s_radioReadData;                                    // 得到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)
{
      ucharn00 = 8;
      uchartemp00;
      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=byte1;
    ampint=byte2;
    ampint=byte3;
    ampint=byte4;
    ampint=byte5;      
      I2C_start();                        // 发送启动信号
      sendbyte(sla);                  // 发送从器件地址字节
      checkack();                            // 检查应答位
      
      delay_us(5);
    if(foo == 1)
      {
                NACK = 1;
                return;                  // 若非应答表明器件错误置错误标志位NACK
      }
      p=&int;
      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 & 0x80;
      radioIf = s_radioReadData & 0x7F;
      radioLev = s_radioReadData >> 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);//消除抖动
                              }
                              elseif(KEY1==0)                //按键显示切换
                              {
                                        rekey = 1;
                                        search_down();          //频率向上
                                        delay_ms(200);//消除抖动
                              }
                              elseif(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晶振,其余位的设置无所谓,可任意。



xinxianshi 发表于 2023-10-25 22:32

怎么接收AM单边带?

xinxianshi 发表于 2023-10-25 22:34

实物呢,看看效果如何。

wei565831866 发表于 2023-10-25 23:01

看看 是什么好东西

trucyw 发表于 2023-10-26 08:27

看看什么好东西

yygdzjs 发表于 2023-10-26 08:39

收音机好久不有用过了.看一下.

Peanut2077 发表于 2023-10-27 11:57

求源码

cooldog123pp 发表于 2023-10-29 17:41

666看下完整的方案

Stahan 发表于 2023-10-29 20:23

能收多少台啊?

chenqianqian 发表于 2023-10-30 08:39

这颗收音机芯片也是挺经典的。

V千里独行 发表于 2023-10-30 11:05

围观

昨天 发表于 2023-11-1 14:57

向大佬学习

ql2000 发表于 2023-11-6 08:44

学习中,支持楼主

wcheng13 发表于 2023-11-6 09:03

看看

xu@xupt 发表于 2023-11-8 19:03

好资源~~~

wangxinyue 发表于 2023-11-18 08:16

这个级别我想做到。

banshi 发表于 2023-12-8 20:09

想看源码

step3 发表于 2024-2-22 16:43

下载

xjc 发表于 2024-2-27 16:01

SSSSSSSSSSSSSSSSSSSS

慢醇 发表于 2024-2-27 17:46

资源哇塞,出个源码吧。大佬!
页: [1] 2 3 4
查看完整版本: 基于STM32F103+TEA5767的收音机实现