• 【STM32】【HAL库】【实用制作】数控收音机(软件设计)


    目录

    前置模块8位8段数码管(74HC595)【软件部分】https://blog.csdn.net/m0_57585228/article/details/124577274

    方案

    功能选择控制

    初始化

    按键检测

    结构体

    按键扫描函数 

    扫描

    旋转编码器

    功能选择

    按键部分 

    旋转编码器部分 

    功能选择实现

    RDA5807M

    初始化

    结构体

    初始化

    数码管

    初始化 

    其他

    功放开关 

    成品


    前置模块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中断触发

    注意在定时器初始化函数里打开定时器和中断

    1. HAL_TIM_Base_Start_IT(&htim2);
    2. HAL_TIM_Base_Start(&htim2);

    按键检测

    结构体

    1. typedef struct
    2. {
    3. GPIO_TypeDef *GPIOx;
    4. uint16_t GPIO_Pin;
    5. uint8_t ins;
    6. uint8_t bool;
    7. uint8_t i;
    8. } Key_Struct;

    不同的按键有不同的GPIO

    ins是定时器扫描时用于指示的状态机

    bool是按键状态

    i是计数值,用于消抖 

    这是用到的按键 

    1. Key_Struct Key_AMP = {GPIOA, GPIO_PIN_4, 0, 0, 0};
    2. Key_Struct Key_FUNCTION = {GPIOA, GPIO_PIN_7, 0, 0, 0};
    3. Key_Struct Key_DETERMINE = {GPIOA, GPIO_PIN_8, 0, 0, 0};
    4. Key_Struct Key_EC11 = {GPIOB, GPIO_PIN_4, 0, 0, 0};

    按键扫描函数 

    有按键按下和松开的两个回调函数

    1. /**
    2. * @brief 键盘扫描
    3. * @param Key:键盘结构体地址
    4. * @param Down:按键按下的回调函数
    5. * @param Up:按键弹起的回调函数
    6. * @return 无
    7. * @author HZ12138
    8. * @date 2022-07-29 15:46:00
    9. */
    10. void Key_Scan(Key_Struct *Key, void (*Down)(void), void (*Up)(void))
    11. {
    12. if (HAL_GPIO_ReadPin(Key->GPIOx, Key->GPIO_Pin) == GPIO_PIN_RESET && Key->ins == 0)
    13. {
    14. Key->i++;
    15. if (Key->i > 10)
    16. {
    17. Key->i = 0;
    18. Key->ins = 1;
    19. Key->bool = 1;
    20. Down();
    21. }
    22. }
    23. else if (HAL_GPIO_ReadPin(Key->GPIOx, Key->GPIO_Pin) == GPIO_PIN_SET && Key->ins == 1)
    24. {
    25. Key->i++;
    26. if (Key->i > 10)
    27. {
    28. Key->i = 0;
    29. Key->ins = 0;
    30. Key->bool = 0;
    31. Up();
    32. }
    33. }
    34. }

    扫描

    在2ms的定时器中断里进行扫描

    1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    2. {
    3. if (htim == &htim2)
    4. {
    5. if (Key_SW)
    6. {
    7. Function_Action();
    8. Key_Scan(&Key_AMP, AMP_Change, air);
    9. Key_Scan(&Key_FUNCTION, Function_Change, air);
    10. Key_Scan(&Key_EC11, EC11_Change, air);
    11. Key_Scan(&Key_DETERMINE, DETERMINE, air);
    12. }
    13. Tube_Send_Scan(Tube_Val, Tube_Spot_P);
    14. }
    15. else if (htim == &htim3)
    16. {
    17. if (Key_SW)
    18. check_R_D();
    19. }
    20. }

    旋转编码器

    需要外部中断和一个GPIO上拉输入模式 

     之后在外部中断回调函数中调用即可

    1. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    2. {
    3. if (GPIO_Pin == GPIO_PIN_0)
    4. {
    5. EC11_Decode(EC11_Change_Up, EC11_Change_Down);
    6. }
    7. }

    功能选择

    我设计的功能是按下选择按键时更改音量,电台,频率的选择

    按下旋转编码器的按键时更改频率选择的位数(个位十位百位等)

    转动旋转编码器更改数值

    按下确定按键将数据发送给收音机芯片

    因此设置了1个用于指示现在所处的功能的数据

    1个用于指示当前频率位置的数据 

    按键部分 

     也就是这个结构体里的

    uint8_t function_ins;

    uint8_t Freq_ins;

    1. typedef struct
    2. {
    3. uint8_t function_ins;
    4. uint8_t volume;
    5. uint8_t radio_station_num;
    6. uint16_t Freq;
    7. uint8_t Freq_ins;
    8. } MY_Struct;

    这个是更改功能指示,进入音量设置和频率设置时先读取芯片里的设置数据,避免出现问题

    1. /**
    2. * @brief 功能选择改变函数(按键触发)
    3. * @param 无
    4. * @return 无
    5. * @author HZ12138
    6. * @date 2022-07-29 15:50:59
    7. */
    8. void Function_Change(void)
    9. {
    10. //改变指示的值
    11. MY.function_ins++;
    12. if (MY.function_ins > 2)
    13. MY.function_ins = 0;
    14. //根据功能更新需要的数据
    15. if (MY.function_ins == 0)
    16. {
    17. MY.volume = RDA5807M_Read_Reg(0x05) & 0xf;
    18. }
    19. else if (MY.function_ins == 2)
    20. {
    21. MY.Freq_ins = 0;
    22. MY.Freq = RDA5807M_Read_Freq();
    23. }
    24. check_R_D();
    25. }

     这个是更改频率选则的位置

    1. /**
    2. * @brief 旋转编码器按键(改变频率的设定值)
    3. * @param 无
    4. * @return 无
    5. * @author HZ12138
    6. * @date 2022-07-29 16:13:47
    7. */
    8. void EC11_Change(void)
    9. {
    10. MY.Freq_ins++;
    11. if (MY.Freq_ins > 3)
    12. MY.Freq_ins = 0;
    13. }

    旋转编码器部分 

    上面的是改变标志,这里需要根据标志改变数据

    在音量和电台时只需要观察function_ins即可

    在频率改变时需要改变function_insFreq_ins两个标志

     因为增加和减少有且仅有符号不同(一个是自加,一个是自减)

    因此将其放入同一个函数里,根据传入的值进行调用

    另外因为有些数据使用的是无符号型,因此在处理0时需要注意避免越位

    1. /**
    2. * @brief 旋转编码器实现
    3. * @param bool:0增加 1减少
    4. * @return 无
    5. * @author HZ12138
    6. * @date 2022-07-29 16:12:23
    7. */
    8. void EC11_Achive(uint8_t bool)
    9. {
    10. if (MY.function_ins == 0)
    11. { //音量
    12. int zj = MY.volume; //避免无负数问题
    13. if (bool == 0)
    14. zj++;
    15. else if (bool == 1)
    16. zj--;
    17. if (zj >= 15)
    18. zj = 15;
    19. else if (zj <= 0)
    20. zj = 0;
    21. MY.volume = zj;
    22. }
    23. else if (MY.function_ins == 1)
    24. { //电台
    25. int zj = MY.radio_station_num; //避免无负数问题
    26. if (bool == 0)
    27. zj++;
    28. else if (bool == 1)
    29. zj--;
    30. if (zj <= 0)
    31. zj = 0;
    32. if (zj >= radio_station_Len)
    33. zj = radio_station_Len;
    34. MY.radio_station_num = zj;
    35. MY.Freq = RDA5807M_RadioStadion_Freq[MY.radio_station_num];
    36. }
    37. else if (MY.function_ins == 2)
    38. { //频率
    39. int zj = 1;
    40. F_A_Freq_ins = 0; //开始时从最低位置开始
    41. F_A_Freq_js = 0; //更改后从亮开始闪烁
    42. //跟据选择增加频率
    43. if (bool == 0)
    44. zj = 1;
    45. else if (bool == 1)
    46. zj = -1;
    47. if (MY.Freq_ins == 0)
    48. MY.Freq += zj * 10;
    49. else if (MY.Freq_ins == 1)
    50. MY.Freq += zj * 100;
    51. else if (MY.Freq_ins == 2)
    52. MY.Freq += zj * 1000;
    53. else if (MY.Freq_ins == 3)
    54. MY.Freq += zj * 10000;
    55. //范围限制
    56. if (MY.Freq >= 10800)
    57. MY.Freq = 10800;
    58. else if (MY.Freq <= 7600)
    59. MY.Freq = 7600;
    60. }
    61. }
    1. /**
    2. * @brief 旋转编码器增加
    3. * @param 无
    4. * @return 无
    5. * @author HZ12138
    6. * @date 2022-07-29 16:06:14
    7. */
    8. void EC11_Change_Up(void)
    9. {
    10. EC11_Achive(0);
    11. }
    12. /**
    13. * @brief 旋转编码器减少
    14. * @param 无
    15. * @return 无
    16. * @author HZ12138
    17. * @date 2022-07-29 16:08:29
    18. */
    19. void EC11_Change_Down(void)
    20. {
    21. EC11_Achive(1);
    22. }

    功能选择实现

    读取标志位function_ins后将结构体中的数据放入数码管的输出缓冲区里即可

    将各个位置的数据分离开来放入数码管输出缓冲区 

    1. /**
    2. * @brief 功能选择实现函数(定时器扫描)
    3. * @param 无
    4. * @return 无
    5. * @author HZ12138
    6. * @date 2022-07-29 15:55:51
    7. */
    8. void Function_Action(void)
    9. {
    10. if (MY.function_ins == 0)
    11. { //音量
    12. Tube_Val[0] = Tube_Lest[MY.volume / 10 % 10];
    13. Tube_Val[1] = Tube_Lest[MY.volume % 10];
    14. for (int i = 2; i <= 7; i++)
    15. Tube_Val[i] = Tube_Bleak;
    16. Tube_Spot_P = 0xff;
    17. }
    18. else if (MY.function_ins == 1)
    19. { //电台
    20. for (int i = 0; i <= 1; i++)
    21. Tube_Val[i] = Tube_Bleak;
    22. Tube_Val[2] = Tube_Lest[MY.radio_station_num / 10 % 10];
    23. Tube_Val[3] = Tube_Lest[MY.radio_station_num % 10];
    24. MY.Freq = RDA5807M_RadioStadion_Freq[MY.radio_station_num];
    25. Tube_Val[4] = Tube_Lest[MY.Freq / 10000 % 10];
    26. Tube_Val[5] = Tube_Lest[MY.Freq / 1000 % 10];
    27. Tube_Val[6] = Tube_Lest[MY.Freq / 100 % 10];
    28. Tube_Val[7] = Tube_Lest[MY.Freq / 10 % 10];
    29. Tube_Spot_P = 6;
    30. }
    31. else if (MY.function_ins == 2)
    32. { //频率
    33. //闪烁计时
    34. F_A_Freq_js++;
    35. if (F_A_Freq_js > 250)
    36. {
    37. if (F_A_Freq_ins == 0)
    38. F_A_Freq_ins = 1;
    39. else if (F_A_Freq_ins == 1)
    40. F_A_Freq_ins = 0;
    41. F_A_Freq_js = 0;
    42. }
    43. //前面变黑
    44. for (int i = 0; i <= 3; i++)
    45. Tube_Val[i] = Tube_Bleak;
    46. //更新数据到数码管
    47. Tube_Val[4] = Tube_Lest[MY.Freq / 10000 % 10];
    48. Tube_Val[5] = Tube_Lest[MY.Freq / 1000 % 10];
    49. Tube_Val[6] = Tube_Lest[MY.Freq / 100 % 10];
    50. Tube_Val[7] = Tube_Lest[MY.Freq / 10 % 10];
    51. //闪烁实现
    52. if (F_A_Freq_ins == 1)
    53. {
    54. Tube_Val[7 - MY.Freq_ins] = Tube_Bleak;
    55. }
    56. //小数点
    57. Tube_Spot_P = 6;
    58. }
    59. }

     另外在频率选择部分做了选中的频率位数进行闪烁的功能,使用了计数和指示两个数据

    1. int F_A_Freq_js = 0; //频率闪烁计时
    2. int F_A_Freq_ins = 0; //频率闪烁状态

     这段函数放在定时器中断(2ms)里进行,之后调用数码管显示函数进行输出即可

    RDA5807M

    初始化

    这个芯片的硬件I2C无法使用(咱也不知道为啥)

    因此使用软件模拟I2C

    需要:
    2个GPIO,开漏浮空输出(电路设计时以加上拉电阻)

    扫描指示灯时需要一个1s的定时器中断 

    注意在定时器初始化函数里打开定时器和中断

    1. HAL_TIM_Base_Start_IT(&htim3);
    2. HAL_TIM_Base_Start(&htim3);

    结构体

    为了方便使用,建立了一个结构体

    1. typedef struct
    2. {
    3. uint8_t function_ins;
    4. uint8_t volume;
    5. uint8_t radio_station_num;
    6. uint16_t Freq;
    7. uint8_t Freq_ins;
    8. } MY_Struct;

    在这个部分中用到的是

    1. uint8_t volume;//音量
    2. uint8_t radio_station_num;//电台好
    3. uint16_t Freq;//频率

    音量的取值范围是0-15

    电台号是上电时搜索得到的

    频率值单位是MHz*100,如106.7=>10670

    初始化

     radio_station_Len是个全局变量

    这些代码均在程序开头 

    uint8_t radio_station_Len = 10; //电台数量

     如模块介绍中所言,进行初始化,搜索并获取电台数量 

    1. RDA5807M_init();
    2. HAL_Delay(500);
    3. RDA5807M_Set_Volume(10);
    4. HAL_Delay(50);
    5. radio_station_Len = RDA5807M_Search_ALL_Freq() - 1;
    6. RDA5807M_Set_Freq(10670);

    一开始的时候是按键和功放禁用的,等待初始化完成后才打开按键和功放

    之后检测当前频率是否是电台并改变指示灯 

    1. Key_SW = 1;//按键启用
    2. AMP_EN(1);//启用功放
    3. check_R_D();//检测是否是电台

    中断回调函数

    1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    2. {
    3. if (htim == &htim2)
    4. {
    5. if (Key_SW)
    6. {
    7. Function_Action();
    8. Key_Scan(&Key_AMP, AMP_Change, air);
    9. Key_Scan(&Key_FUNCTION, Function_Change, air);
    10. Key_Scan(&Key_EC11, EC11_Change, air);
    11. Key_Scan(&Key_DETERMINE, DETERMINE, air);
    12. }
    13. Tube_Send_Scan(Tube_Val, Tube_Spot_P);
    14. }
    15. else if (htim == &htim3)
    16. {
    17. if (Key_SW)
    18. check_R_D();
    19. }
    20. }

    数码管

    初始化 

    需要3个GPIO 推挽上拉输出 最高等级

    还有一个2ms定时器中断,和按键扫描连用,这边不需要设置了

    其他

    功放开关 

    功放是通过一个mos管进行控制的

    1. /**
    2. * @brief 功放开关
    3. * @param 无
    4. * @return 无
    5. * @author HZ12138
    6. * @date 2022-07-29 15:50:28
    7. */
    8. void AMP_Change(void)
    9. {
    10. if (HAL_GPIO_ReadPin(AMP_EN_GPIO_Port, AMP_EN_Pin) == GPIO_PIN_RESET)
    11. HAL_GPIO_WritePin(AMP_EN_GPIO_Port, AMP_EN_Pin, GPIO_PIN_SET);
    12. else if (HAL_GPIO_ReadPin(AMP_EN_GPIO_Port, AMP_EN_Pin) == GPIO_PIN_SET)
    13. HAL_GPIO_WritePin(AMP_EN_GPIO_Port, AMP_EN_Pin, GPIO_PIN_RESET);
    14. }

    成品

    收音机

    githubhttps://github.com/HZ1213825/HAL_STM32_Radio

  • 相关阅读:
    笔记本CPU温度多少正常?这些知识不可忽视!
    数据结构 树练习题
    跳槽!3年Java面试经验总结
    JavaScript 下划线转换成驼峰命名
    【深度学习】实验4布置:脑部 MRI 图像分割
    【CVPR 2021】pixelNeRF: Neural Radiance Fields from One or Few Images
    关于设置html不让浏览器进行缓存的问题
    PointRend 原理与代码解析
    多商户商城系统功能拆解20讲-平台端分销概况
    JackSon工具类
  • 原文地址:https://blog.csdn.net/m0_57585228/article/details/126060958