#申请原创#@21小跑堂 源码回复下载:
前言前段时间给表弟捯饬了一个基于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晶振,其余位的设置无所谓,可任意。
|