项目说明
本项目采用air001主控芯片结合SR_FRS_2WUS无线对讲机模块,打造了一款简易对讲机。设备支持20个公共对讲机频道,支持亚音和扰频功能,提供更强的通信保密性。采用CW2015电池监测芯片,可实时监测电池电量,确保设备长时间稳定运行。同时,利用AT24C02芯片实现了参数设置的掉电存储功能,保障用户设置的参数在掉电情况下不会丢失,提升了用户体验和便利性。
开源协议
GPL3.0
项目相关功能
- 支持待机息屏功能,节省能量。
- 提供409.7500MHz~409.9875MHz共20个公共对讲机频道选择。
- 支持亚音技术,有效防止串频现象的发生。并且支持扰频功能,可对语音进行加密,提升通信安全性。
- 用户可灵活切换收发模式或监听模式,灵活应对不同场景需求。
- 在城市环境下,对讲机的通讯距离可达1公里以上,语音传输清晰且具备良好的分辨度。
- 内置电量监测功能,可实时监测电池电量。此外,还支持参数设置的掉电存储,提高了设备的可靠性和便利性。
电路设计原理
系统框图
电源电路
电源部分采用TP4056进行锂电池的充放电。需要注意的是,电路板未添加锂电池保护电路,因此建议选择带有保护电路的锂电池以确保安全使用。
电池电量监测采用CW2015芯片,其外围电路较为简单。并且可以通过I2C通信直接读取电量百分比值以及剩余运行时间。
主控电路和射频模块电路分别使用XC6201和SX1308进行供电。其中SX1308需要将EN引脚上拉从而启动电压转换。
air001最小系统电路
AIR001系列采用高性能的ARM Cortex-M0+ 32位内核,最高工作频率达到48MHz,并且支持低功耗模式。内置存储器包括32KB Flash和4KB SRAM。此外,还搭载了9个定时器、1个12位ADC,并配备了2个SPI接口、1个I2C接口以及2个USART接口。外围电路设计简单,无需外部晶振即可正常工作。这里仅需将boot引脚下拉,并接上复位电路即可完成air001最小系统电路的设计。
独立按键
考虑到air001拥有较多的GPIO口,我们采用了独立按键进行输入。各个按键的功能包括选择、取消、上、下、左、右。
eeprom存储
我们在对讲机的操作中需要设置频率、亚音、扰频、音量等级等参数。如果每次上电都需要重新设置,将会增加使用的复杂度。因此为了方便用户使用,我们采用AT24C02芯片对相关参数数据进行存储,实现了参数设置的掉电保存功能,从而提高了设备的便利性。该芯片使用I2C进行通信,与CW2015芯片共用I2C总线,减小GPIO的使用。
射频模块
射频模块采用SR_FRS_2WUS,它是一款性价比极高的无线语音对讲及数传模块。该模块内置高性能的射频收发芯片、微控制器以及射频功放。外部控制器可以通过标准的异步串行接口(UART)通信,实现设置模块的工作参数并控制整个模块的收发。此模块只需外接天线、MIC和语音功放,即可组装成一台完整的对讲机或数传电台。
在阻抗匹配部分采用了"嘉立创阻抗计算神器"进行走线线宽的简单计算,并预留了派型阻抗匹配电路,可用于更精确的阻抗匹配。如果不进行阻抗匹配,可在使用时通过接入0欧电阻将L2短路,即可正常使用。
mic输入
采用驻极体电容麦克风作为语音输入。
音频输出
音频输出部分采用LM4871音频功率放大器,能够轻松驱动4欧8W的扬声器,特别适用于小体积、轻便的便携系统。LM4871的使能端连接射频模块的SQ端,当未接收到信号时,可将LM4871置于休眠模式,有效降低功耗并防止噪音输出。此外,LM4871内置过热自动关断保护机制,工作稳定,单位增益可调。通过配置外部电阻,可轻松调整放大器的电压增益,满足不同应用需求。
软件部分说明
程序部分主要分为五大部分:OLED界面、按键控制、对讲模块功能设置、电量读取、参数存储。
OLED界面
IIC通信使用软件进行实现,实现较为简单。
#ifndef __I2C_H
#define __I2C_H
#include "air001xx_hal.h"
#define I2C_GPIO GPIOF
#define I2C_SDA GPIO_PIN_0
#define I2C_SCL GPIO_PIN_1
#define I2C_GPIO_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define I2C_CMD 0 //写命令
#define I2C_DATA 1 //写数据
void I2C_Init(void);
void I2C_Start(void);
void I2C_Stop(void);
void I2C_WaitAck(void);
void I2C_SendByte(uint8_t dat);
uint8_t I2C_ReadByte(uint8_t ack);
void OLED_WR_Byte(uint8_t dat,uint8_t mode);
#endif
air001通过IIC与OLED进行通信,从而设置显示内容。
void OLED_ShowChar(uint8_t x,uint8_t y,uint8_t chr,uint8_t size1,uint8_t mode)
{
uint8_t i,m,temp,size2,chr1;
uint8_t x0=x,y0=y;
if(size1==8)size2=6;
else size2=(size1/8+((size1%8)?1:0))*(size1/2); //得到字体一个字符对应点阵集所占的字节数
chr1=chr-' '; //计算偏移后的值
for(i=0;i<size2;i++)
{
if(size1==8)
{temp=asc2_0806[chr1][i];} //调用0806字体
else if(size1==12)
{temp=asc2_1206[chr1][i];} //调用1206字体
else if(size1==16)
{temp=asc2_1608[chr1][i];} //调用1608字体
else if(size1==24)
{temp=asc2_2412[chr1][i];} //调用2412字体
else return;
for(m=0;m<8;m++)
{
if(temp&0x01)OLED_DrawPoint(x,y,mode);
else OLED_DrawPoint(x,y,!mode);
temp>>=1;
y++;
}
x++;
if((size1!=8)&&((x-x0)==size1/2))
{x=x0;y0=y0+8;}
y=y0;
}
}
按键控制
按键部分采用六个独立按键,分别为确认、取消、上、下、左、右六个功能。在main函数的while循环中对按键进行扫描从而确定按下的按键并执行对应的程序。
uint8_t Key_Scan(void)
{
if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_SEL_PIN)==0)
{
HAL_Delay(20);
if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_SEL_PIN)==0)
{
while(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_SEL_PIN)==0);
return 1;
}
}else if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_RET_PIN)==0)
{
HAL_Delay(20);
if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_RET_PIN)==0)
{
while(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_RET_PIN)==0);
return 2;
}
}else if(HAL_GPIO_ReadPin(KEY_GPIOB,KEY_UP_PIN)==0)
{
HAL_Delay(20);
if(HAL_GPIO_ReadPin(KEY_GPIOB,KEY_UP_PIN)==0)
{
while(HAL_GPIO_ReadPin(KEY_GPIOB,KEY_UP_PIN)==0);
return 3;
}
}else if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_DOWN_PIN)==0)
{
HAL_Delay(20);
if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_DOWN_PIN)==0)
{
while(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_DOWN_PIN)==0);
return 4;
}
}else if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_LEFT_PIN)==0)
{
HAL_Delay(20);
if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_LEFT_PIN)==0)
{
while(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_LEFT_PIN)==0);
return 5;
}
}else if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_RIGHT_PIN)==0)
{
HAL_Delay(20);
if(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_RIGHT_PIN)==0)
{
while(HAL_GPIO_ReadPin(KEY_GPIOA,KEY_RIGHT_PIN)==0);
return 6;
}
}
return 0;
}
对讲模块功能设置
对讲机功能设置使用UART与对讲机模块进行通信,并通过AT指令进行参数的设置。
void Uart_TxData(uint8_t index, uint8_t level)
{
uint8_t *aTxBuffer;
uint8_t tx_len=0;
switch(index)
{
case 0://音量
AT_CMD2[10] = 0x31+level;
aTxBuffer = AT_CMD2;
tx_len=13;
AT24CXX_WriteOneByte(ADDR_VOL, level);
break;
case 1://亚音
strncpy(AT_CMD1+30, subtone[level],2);
strncpy(AT_CMD1+33, subtone[level],2);
aTxBuffer = AT_CMD1;
tx_len=41;
AT24CXX_WriteOneByte(ADDR_SUB, level);
break;
case 2://射频开关
HAL_GPIO_WritePin(GPIOB, PD_PIN, level);
AT24CXX_WriteOneByte(ADDR_SW, level);
return;
break;
case 3://扰频
AT_CMD4[16] = 0x30+level;
aTxBuffer = AT_CMD4;
tx_len=21;
AT24CXX_WriteOneByte(ADDR_SCR, level);
break;
case 4://免提灵敏度
AT_CMD3[10] = 0x30+level;
aTxBuffer = AT_CMD3;
tx_len=13;
AT24CXX_WriteOneByte(ADDR_VOX, level);
break;
case 5://静噪级别
AT_CMD4[10] = 0x30+level;
aTxBuffer = AT_CMD4;
tx_len=21;
AT24CXX_WriteOneByte(ADDR_SQU, level);
break;
case 6://麦克灵敏度
AT_CMD4[12] = 0x30+level;
aTxBuffer = AT_CMD4;
tx_len=21;
AT24CXX_WriteOneByte(ADDR_MIC, level);
break;
}
if (HAL_UART_Transmit_IT(&UartHandle, aTxBuffer, tx_len) != HAL_OK) {
Error_Handler();
}
}
电量读取
CW2015通过IIC与主控芯片进行通信。在上电时首先设置电池信息,之后通过读取SOC寄存器中的数值即可获取当前电池的电量信息。
uint8_t CW2015_ReadOneByte(uint8_t ReadAddr)
{
uint8_t temp=0;
I2C_Start();
I2C_SendByte(0XC4); //发送器件地址0XA0,写数据
I2C_WaitAck();
I2C_SendByte(ReadAddr); //发送低地址
I2C_WaitAck();
I2C_Start();
I2C_SendByte(0XC5); //进入接收模式
I2C_WaitAck();
temp=I2C_ReadByte(0);
I2C_Stop();//产生一个停止条件
return temp;
}
参数存储
对讲机的频道和其相关功能设置参数存储在AT24C02中,从而实现掉电存储功能。AT24C02通过IIC与主机进行通信。具体存储格式如下:
地址 | 存储内容 | 表示范围 |
---|---|---|
0x00 | 音量 | 0-7 |
0x01 | 亚音 | 0-4 |
0x02 | 射频开关 | 0-1 |
0x03 | 扰频 | 0-7 |
0x04 | 免提灵敏度 | 0-8 |
0x05 | 静噪级别 | 0-9 |
0x06 | 麦克灵敏度 | 0-7 |
整体核心代码
其中page_index为判断当前为主页还是菜单页。current_index和temp_list_level分别为当前菜单选中的功能和该功能参数的等级。 之后对OLED、UART、按键GPIO、CW2015进行初始化。最后进入循环,通过不断判断按键按下的状态从而实现功能的控制。
int main(void)
{
uint8_t page_index=0;//page index
uint8_t current_index=0;//list index
uint8_t temp_list_level = 0;
uint8_t key_index = 0;//key index
uint8_t i=0, list_len = (sizeof(list)/sizeof(MENU_LIST));
/* 初始化所有外设,Flash接口,SysTick */
HAL_Init();
UART2_INIT();
Get_Init_Data();
Show_HomePage(list[0].current_level, list[2].current_level);
CW2015_WriteOneByte(0x0a,0xff);//wake up cw2015
HAL_Delay(50);
CW2015_WriteOneByte(0x0a,0x00);//wake up cw2015
HAL_Delay(50);
uint8_t battary_data = 0;
battary_data = CW2015_ReadOneByte(0x08);
battary_data = battary_data & 0x02;
HAL_Delay(50);
if(battary_data == 0)
{
Update_Bat_Info();
}
temp_list_level = list[0].current_level;
while (1)
{
if(page_index == 0 && display_on)
{
OLED_ShowChar(8, 0, list[0].current_level + 0x30, 8, 1);
battary_data = CW2015_ReadOneByte(0x04);
HAL_Delay(50);
OLED_ShowNum(127-24,0,battary_data,3,8,1);
OLED_ShowChar(127-6,0,'%',8,1);
OLED_Refresh();
}
key_index=Key_Scan();
if(key_index == 1)
{
switch(page_index)//key_select
{
case 0://enter menu
if(display_on){
page_index +=1;
current_index=oled_showlist(list_len, current_index, (MENU_LIST *)list, 2);
}else{
Show_HomePage(list[0].current_level, list[2].current_level);
display_on = 1;
}
break;
case 1://select setting
list[current_index].current_level = temp_list_level;
Uart_TxData(current_index, list[current_index].current_level);
break;
default:
break;
}
}else if(key_index == 2)//key_return
{
switch(page_index)
{
case 0://none
display_on = 0;
OLED_Clear();
OLED_Refresh();
// current_index=oled_showlist(list_len, current_index, (MENU_LIST *)list, 1);
break;
case 1://return home page
page_index -= 1;
Show_HomePage(list[0].current_level, list[2].current_level);
current_index=0;
break;
default:
break;
}
}else if(key_index == 3)//key_up
{
if(page_index == 1)//current is menu page
{
current_index=oled_showlist(list_len, current_index, (MENU_LIST *)list, 0);//previous(up)
temp_list_level = list[current_index].current_level;
}else
{
Set_Level_Silent((MENU_LIST *)list, 0, 1);
Uart_TxData(0, list[0].current_level);
}
}else if(key_index == 4)//key_down
{
if(page_index == 1)
{
current_index=oled_showlist(list_len, current_index, (MENU_LIST *)list, 1);//next(down)
temp_list_level = list[current_index].current_level;
}else
{
Set_Level_Silent((MENU_LIST *)list, 0, 0);
Uart_TxData(0, list[0].current_level);
}
}else if(key_index == 5)//key_left
{
if(page_index == 1)
{
temp_list_level= Set_Level((MENU_LIST *)list, current_index, temp_list_level, 0);
}else{
channel_index = Uart_SetFreq(channel_index, 0);
}
}else if(key_index == 6)//key_right
{
if(page_index == 1)
{
temp_list_level = Set_Level((MENU_LIST *)list, current_index, temp_list_level, 1);
}else
{
channel_index = Uart_SetFreq(channel_index, 1);
}
}
key_index = 0;
}
}
实物展示
设计注意事项
- 在电路设计中未添加锂电池保护电路,建议选购带有保护电路的锂电池以确保安全使用。
- 在进行烧录程序时,建议使用DAPLink调试器进行烧录(理论上也可通过串口进行烧录)。在使用DAPLink烧录时,请确保烧录器的3v3和GND与板子相连,以避免烧录失败的可能性。如果出现烧录失败情况,可以适当降低烧录器的时钟频率,并重新尝试。
- 在使用CW2015时,需要先写入电池信息,然后再读取SOC,以确保读取到的SOC值准确。具体内容可以参考:CW2015电量计使用
- 注意SX1308芯片的EN引脚控制的是电压转换开关,而不是输出开关。当EN引脚接低电平时,该芯片仍然会输出电压。在第一版设计时可能未注意到这一问题,原理图和PCB对此已进行更正。
- 天线的匹配电路根据需要进行焊接。若无阻抗匹配条件,也可以使用0欧电阻直接将其短路,经验证也可实现较远距离的通信。
- 在未连接天线时,请勿按下PTT键,可能会造成未知后果。
- 软包锂电池接口的尺寸及线序为:1.25黑红
注意!!!
该项目仅供学习交流使用,测试使用时请遵守相关法律法规!
中华人民共和国无线电管理条例
关于公众对讲机管理有关问题的通知