目录
前置模块8位8段数码管(74HC595)【软件部分】https://blog.csdn.net/m0_57585228/article/details/124577274
数控收音机(硬件设计)https://blog.csdn.net/m0_57585228/article/details/126060943RDA5807M收音机芯片驱动https://blog.csdn.net/m0_57585228/article/details/125940042旋转编码器(EC11)https://blog.csdn.net/m0_57585228/article/details/125458070
使用stm32f103c6t6作为主控,RDA5807M作为收音芯片,使用数码管进行显示,配合按键和旋转编码器作为控制
按键检测需要定时器,这里和数码管扫描的定时器合用,为2ms中断触发
注意在定时器初始化函数里打开定时器和中断
- HAL_TIM_Base_Start_IT(&htim2);
- HAL_TIM_Base_Start(&htim2);
- typedef struct
- {
- GPIO_TypeDef *GPIOx;
- uint16_t GPIO_Pin;
- uint8_t ins;
- uint8_t bool;
- uint8_t i;
- } Key_Struct;
不同的按键有不同的GPIO
ins是定时器扫描时用于指示的状态机
bool是按键状态
i是计数值,用于消抖
这是用到的按键
- Key_Struct Key_AMP = {GPIOA, GPIO_PIN_4, 0, 0, 0};
- Key_Struct Key_FUNCTION = {GPIOA, GPIO_PIN_7, 0, 0, 0};
- Key_Struct Key_DETERMINE = {GPIOA, GPIO_PIN_8, 0, 0, 0};
- Key_Struct Key_EC11 = {GPIOB, GPIO_PIN_4, 0, 0, 0};
有按键按下和松开的两个回调函数
- /**
- * @brief 键盘扫描
- * @param Key:键盘结构体地址
- * @param Down:按键按下的回调函数
- * @param Up:按键弹起的回调函数
- * @return 无
- * @author HZ12138
- * @date 2022-07-29 15:46:00
- */
- void Key_Scan(Key_Struct *Key, void (*Down)(void), void (*Up)(void))
- {
- if (HAL_GPIO_ReadPin(Key->GPIOx, Key->GPIO_Pin) == GPIO_PIN_RESET && Key->ins == 0)
- {
- Key->i++;
- if (Key->i > 10)
- {
- Key->i = 0;
- Key->ins = 1;
- Key->bool = 1;
- Down();
- }
- }
- else if (HAL_GPIO_ReadPin(Key->GPIOx, Key->GPIO_Pin) == GPIO_PIN_SET && Key->ins == 1)
- {
- Key->i++;
- if (Key->i > 10)
- {
- Key->i = 0;
- Key->ins = 0;
- Key->bool = 0;
- Up();
- }
- }
- }
在2ms的定时器中断里进行扫描
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- if (htim == &htim2)
- {
-
- if (Key_SW)
- {
- Function_Action();
- Key_Scan(&Key_AMP, AMP_Change, air);
- Key_Scan(&Key_FUNCTION, Function_Change, air);
- Key_Scan(&Key_EC11, EC11_Change, air);
- Key_Scan(&Key_DETERMINE, DETERMINE, air);
- }
- Tube_Send_Scan(Tube_Val, Tube_Spot_P);
- }
- else if (htim == &htim3)
- {
- if (Key_SW)
- check_R_D();
- }
- }
需要外部中断和一个GPIO上拉输入模式
之后在外部中断回调函数中调用即可
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
- {
- if (GPIO_Pin == GPIO_PIN_0)
- {
- EC11_Decode(EC11_Change_Up, EC11_Change_Down);
- }
- }
我设计的功能是按下选择按键时更改音量,电台,频率的选择
按下旋转编码器的按键时更改频率选择的位数(个位十位百位等)
转动旋转编码器更改数值
按下确定按键将数据发送给收音机芯片
因此设置了1个用于指示现在所处的功能的数据
1个用于指示当前频率位置的数据
也就是这个结构体里的
uint8_t function_ins;
uint8_t Freq_ins;
- typedef struct
- {
- uint8_t function_ins;
- uint8_t volume;
- uint8_t radio_station_num;
- uint16_t Freq;
- uint8_t Freq_ins;
- } MY_Struct;
这个是更改功能指示,进入音量设置和频率设置时先读取芯片里的设置数据,避免出现问题
- /**
- * @brief 功能选择改变函数(按键触发)
- * @param 无
- * @return 无
- * @author HZ12138
- * @date 2022-07-29 15:50:59
- */
- void Function_Change(void)
- {
- //改变指示的值
- MY.function_ins++;
- if (MY.function_ins > 2)
- MY.function_ins = 0;
-
- //根据功能更新需要的数据
- if (MY.function_ins == 0)
- {
- MY.volume = RDA5807M_Read_Reg(0x05) & 0xf;
- }
- else if (MY.function_ins == 2)
- {
- MY.Freq_ins = 0;
- MY.Freq = RDA5807M_Read_Freq();
- }
-
- check_R_D();
- }
这个是更改频率选则的位置
- /**
- * @brief 旋转编码器按键(改变频率的设定值)
- * @param 无
- * @return 无
- * @author HZ12138
- * @date 2022-07-29 16:13:47
- */
- void EC11_Change(void)
- {
- MY.Freq_ins++;
- if (MY.Freq_ins > 3)
- MY.Freq_ins = 0;
- }
上面的是改变标志,这里需要根据标志改变数据
在音量和电台时只需要观察function_ins即可
在频率改变时需要改变function_ins和Freq_ins两个标志
因为增加和减少有且仅有符号不同(一个是自加,一个是自减)
因此将其放入同一个函数里,根据传入的值进行调用
另外因为有些数据使用的是无符号型,因此在处理0时需要注意避免越位
- /**
- * @brief 旋转编码器实现
- * @param bool:0增加 1减少
- * @return 无
- * @author HZ12138
- * @date 2022-07-29 16:12:23
- */
- void EC11_Achive(uint8_t bool)
- {
- if (MY.function_ins == 0)
- { //音量
- int zj = MY.volume; //避免无负数问题
-
- if (bool == 0)
- zj++;
- else if (bool == 1)
- zj--;
-
- if (zj >= 15)
- zj = 15;
- else if (zj <= 0)
- zj = 0;
- MY.volume = zj;
- }
- else if (MY.function_ins == 1)
- { //电台
- int zj = MY.radio_station_num; //避免无负数问题
-
- if (bool == 0)
- zj++;
- else if (bool == 1)
- zj--;
-
- if (zj <= 0)
- zj = 0;
- if (zj >= radio_station_Len)
- zj = radio_station_Len;
- MY.radio_station_num = zj;
- MY.Freq = RDA5807M_RadioStadion_Freq[MY.radio_station_num];
- }
- else if (MY.function_ins == 2)
- { //频率
- int zj = 1;
-
- F_A_Freq_ins = 0; //开始时从最低位置开始
- F_A_Freq_js = 0; //更改后从亮开始闪烁
- //跟据选择增加频率
- if (bool == 0)
- zj = 1;
- else if (bool == 1)
- zj = -1;
-
- if (MY.Freq_ins == 0)
- MY.Freq += zj * 10;
- else if (MY.Freq_ins == 1)
- MY.Freq += zj * 100;
- else if (MY.Freq_ins == 2)
- MY.Freq += zj * 1000;
- else if (MY.Freq_ins == 3)
- MY.Freq += zj * 10000;
- //范围限制
- if (MY.Freq >= 10800)
- MY.Freq = 10800;
- else if (MY.Freq <= 7600)
- MY.Freq = 7600;
- }
- }
- /**
- * @brief 旋转编码器增加
- * @param 无
- * @return 无
- * @author HZ12138
- * @date 2022-07-29 16:06:14
- */
- void EC11_Change_Up(void)
- {
- EC11_Achive(0);
- }
- /**
- * @brief 旋转编码器减少
- * @param 无
- * @return 无
- * @author HZ12138
- * @date 2022-07-29 16:08:29
- */
- void EC11_Change_Down(void)
- {
- EC11_Achive(1);
- }
读取标志位function_ins后将结构体中的数据放入数码管的输出缓冲区里即可
将各个位置的数据分离开来放入数码管输出缓冲区
- /**
- * @brief 功能选择实现函数(定时器扫描)
- * @param 无
- * @return 无
- * @author HZ12138
- * @date 2022-07-29 15:55:51
- */
- void Function_Action(void)
- {
- if (MY.function_ins == 0)
- { //音量
-
- Tube_Val[0] = Tube_Lest[MY.volume / 10 % 10];
- Tube_Val[1] = Tube_Lest[MY.volume % 10];
- for (int i = 2; i <= 7; i++)
- Tube_Val[i] = Tube_Bleak;
- Tube_Spot_P = 0xff;
- }
- else if (MY.function_ins == 1)
- { //电台
- for (int i = 0; i <= 1; i++)
- Tube_Val[i] = Tube_Bleak;
-
- Tube_Val[2] = Tube_Lest[MY.radio_station_num / 10 % 10];
- Tube_Val[3] = Tube_Lest[MY.radio_station_num % 10];
-
- MY.Freq = RDA5807M_RadioStadion_Freq[MY.radio_station_num];
-
- Tube_Val[4] = Tube_Lest[MY.Freq / 10000 % 10];
- Tube_Val[5] = Tube_Lest[MY.Freq / 1000 % 10];
- Tube_Val[6] = Tube_Lest[MY.Freq / 100 % 10];
- Tube_Val[7] = Tube_Lest[MY.Freq / 10 % 10];
- Tube_Spot_P = 6;
- }
- else if (MY.function_ins == 2)
- { //频率
- //闪烁计时
- F_A_Freq_js++;
- if (F_A_Freq_js > 250)
- {
- if (F_A_Freq_ins == 0)
- F_A_Freq_ins = 1;
- else if (F_A_Freq_ins == 1)
- F_A_Freq_ins = 0;
- F_A_Freq_js = 0;
- }
- //前面变黑
- for (int i = 0; i <= 3; i++)
- Tube_Val[i] = Tube_Bleak;
- //更新数据到数码管
- Tube_Val[4] = Tube_Lest[MY.Freq / 10000 % 10];
- Tube_Val[5] = Tube_Lest[MY.Freq / 1000 % 10];
- Tube_Val[6] = Tube_Lest[MY.Freq / 100 % 10];
- Tube_Val[7] = Tube_Lest[MY.Freq / 10 % 10];
- //闪烁实现
- if (F_A_Freq_ins == 1)
- {
- Tube_Val[7 - MY.Freq_ins] = Tube_Bleak;
- }
- //小数点
- Tube_Spot_P = 6;
- }
- }
另外在频率选择部分做了选中的频率位数进行闪烁的功能,使用了计数和指示两个数据
- int F_A_Freq_js = 0; //频率闪烁计时
- int F_A_Freq_ins = 0; //频率闪烁状态
这段函数放在定时器中断(2ms)里进行,之后调用数码管显示函数进行输出即可
这个芯片的硬件I2C无法使用(咱也不知道为啥)
因此使用软件模拟I2C
需要:
2个GPIO,开漏浮空输出(电路设计时以加上拉电阻)
扫描指示灯时需要一个1s的定时器中断
注意在定时器初始化函数里打开定时器和中断
- HAL_TIM_Base_Start_IT(&htim3);
- HAL_TIM_Base_Start(&htim3);
为了方便使用,建立了一个结构体
- typedef struct
- {
- uint8_t function_ins;
- uint8_t volume;
- uint8_t radio_station_num;
- uint16_t Freq;
- uint8_t Freq_ins;
- } MY_Struct;
在这个部分中用到的是
- uint8_t volume;//音量
- uint8_t radio_station_num;//电台好
- uint16_t Freq;//频率
音量的取值范围是0-15
电台号是上电时搜索得到的
频率值单位是MHz*100,如106.7=>10670
radio_station_Len是个全局变量
这些代码均在程序开头
uint8_t radio_station_Len = 10; //电台数量
如模块介绍中所言,进行初始化,搜索并获取电台数量
- RDA5807M_init();
- HAL_Delay(500);
- RDA5807M_Set_Volume(10);
- HAL_Delay(50);
- radio_station_Len = RDA5807M_Search_ALL_Freq() - 1;
- RDA5807M_Set_Freq(10670);
一开始的时候是按键和功放禁用的,等待初始化完成后才打开按键和功放
之后检测当前频率是否是电台并改变指示灯
- Key_SW = 1;//按键启用
- AMP_EN(1);//启用功放
- check_R_D();//检测是否是电台
中断回调函数
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- if (htim == &htim2)
- {
-
- if (Key_SW)
- {
- Function_Action();
- Key_Scan(&Key_AMP, AMP_Change, air);
- Key_Scan(&Key_FUNCTION, Function_Change, air);
- Key_Scan(&Key_EC11, EC11_Change, air);
- Key_Scan(&Key_DETERMINE, DETERMINE, air);
- }
- Tube_Send_Scan(Tube_Val, Tube_Spot_P);
- }
- else if (htim == &htim3)
- {
- if (Key_SW)
- check_R_D();
- }
- }
需要3个GPIO 推挽上拉输出 最高等级
还有一个2ms定时器中断,和按键扫描连用,这边不需要设置了
功放是通过一个mos管进行控制的
- /**
- * @brief 功放开关
- * @param 无
- * @return 无
- * @author HZ12138
- * @date 2022-07-29 15:50:28
- */
- void AMP_Change(void)
- {
- if (HAL_GPIO_ReadPin(AMP_EN_GPIO_Port, AMP_EN_Pin) == GPIO_PIN_RESET)
- HAL_GPIO_WritePin(AMP_EN_GPIO_Port, AMP_EN_Pin, GPIO_PIN_SET);
- else if (HAL_GPIO_ReadPin(AMP_EN_GPIO_Port, AMP_EN_Pin) == GPIO_PIN_SET)
- HAL_GPIO_WritePin(AMP_EN_GPIO_Port, AMP_EN_Pin, GPIO_PIN_RESET);
- }
收音机