这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 用STM32H747I-DISCO+EC200U打造一台触屏4G电话

共1条 1/1 1 跳转至页

用STM32H747I-DISCO+EC200U打造一台触屏4G电话

高工
2026-10-15 20:38:49     打赏

用 STM32H747I-DISCO + EC200U 打造一台触屏 4G 电话(Zephyr + LVGL)

用一块带 4 寸触摸屏的开发板 + 一片 4G Cat.1 模组,从零实现一台能打电话的电话机: 触屏拨号盘、来电弹窗显号、通话计时、免提通话、大音量振铃——全部开源方案。

<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=117444773484302&bvid=BV1kMJy65Eti&cid=42767092319&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

image.png


一、这是什么项目

用 STM32H747I-DISCO(Cortex-M7 @ 400MHz,4" 800x480 MIPI-DSI 电容触摸屏)

  • UniKnect Gen1-PRO 扩展板(移远 EC200U 4G Cat.1 模组), 在 Zephyr RTOS + LVGL 9.5 上实现了一台功能完整的触屏电话:

  • 触屏数字键盘拨号、拨打/挂断

  • 来电弹窗显示号码、接听/拒接、大音量振铃

  • 通话中计时、免提喇叭双向通话

  • 4G 网络状态实时显示(注册状态/信号)

软件零闭源依赖:Zephyr(Apache-2.0)+ LVGL(MIT)+ uniknect-c(移远官方 C 框架,Apache-2.0)。


二、硬件连接

UniKnect 扩展板提供 EC200U 模组及其电源管理。与 DISCO 之间只需 3 根线:

UniKnect 侧(J308 跳线块)信号DISCO 侧




pin 3(MCU_TXD,模组收)UART TXD1 / PJ8(UART8_TX)
pin 4(MCU_RXD,模组发)UART RXD0 / PJ9(UART8_RX)
J305 pin 5(GND)地Arduino GND

J308 针脚编号:左列从上到下 1/3/5,右列 2/4/6。拔掉 J308 上原有跳线帽再飞线。 两板各自独立供电(UniKnect 插自己的 DC5V,DISCO 插自己的 USB),只共享信号和地。

image.png

音频:

  • 通话声音:EC200U 的 SPK 输出 → UniKnect 板上的喇叭

  • 振铃:DISCO 板载 WM8994 codec → CN11 绿色耳机口/喇叭(比模组自带铃声大得多)


三、软件架构

┌─────────────────────────────────────────────┐
│  phone_ui.c   LVGL 9.5 界面(拨号盘/来电/通话) │  ← 主线程:触摸 + 显示
├─────────────────────────────────────────────┤
│  modem_task.c  电话状态机(独立线程)           │  ← AT 命令全在这
├─────────────────────────────────────────────┤
│  ringtone.c   铃声生成(正弦波→SAI/I2S→WM8994)│  ← 独立线程
├─────────────────────────────────────────────┤
│  at_client(uniknect-c,RT-Thread 血统 AT 引擎)│
│  OSAL(CMSIS-RTOS2 风格接口 → Zephyr 移植层)   │
│  hal_uart(UART8 中断收发 + 环形缓冲)           │
├─────────────────────────────────────────────┤
│  Zephyr v4.4 内核 + 驱动(UART/DSI/LTDC/I2C…)  │
└─────────────────────────────────────────────┘

线程模型:UI 主循环(10ms tick)/ 模组任务(AT 独占)/ 铃声线程(DMA 喂块), 三者经 k_msgq + 共享状态通信,互不阻塞。


四、关键实现

4.1 电话状态机(modem_task.c)

EC200U 的语音通话就是几条 AT 命令的事,配上 URC 监听就是完整状态机:

/* 状态机:IDLE / DIALING / INCOMING / ACTIVE */
switch (state) {
case IDLE:
   /* RING URC → INCOMING(+CLIP 抓来电号码) */
   break;
case INCOMING:
   /* UI 弹窗;ATA 接听 → ACTIVE;ATH 拒接 → IDLE */
   break;
case DIALING:
   /* ATD<号码>; 已发;每秒 AT+CLCC 轮询,stat=0 即接通 → ACTIVE */
   break;
case ACTIVE:
   /* 通话计时;ATH 或 NO CARRIER → IDLE */
   break;
}

来电显号和铃声使能(初始化时一次设置):

at_exec(client, "AT+CLIP=1");                 /* 来电显示号码 */
at_exec(client, "AT+QAUDMOD=2");              /* 音频通道:免提喇叭 */
at_exec(client, "AT+CLVL=5");                 /* 音量最大 */

URC 注册(at_client 的 URC 表机制,来电/挂断异步通知):

static const at_urc_t phone_urc[] = {
   {"RING",        "\r\n", on_ring,        NULL},   /* 来电 */
   {"+CLIP:",      "\r\n", on_clip,        NULL},   /* 来电号码 */
   {"NO CARRIER",  "\r\n", on_no_carrier,  NULL},   /* 对方挂断 */
};
at_obj_set_urc_table(client, phone_urc, ARRAY_SIZE(phone_urc));

4.2 LVGL 拨号界面(phone_ui.c)

800x480 大屏,手写 LVGL(不依赖 GUI Guider)。数字键盘是 3x4 按钮矩阵:

static const char *keys[] = {"1","2","3","4","5","6","7","8","9","*","0","#"};

for (int i = 0; i < 12; i++) {
   lv_obj_t *btn = lv_button_create(keypad);
   lv_obj_set_size(btn, 180, 70);                     /* 大按钮好按 */
   lv_obj_set_pos(btn, 40 + (i % 3) * 210, 130 + (i / 3) * 82);
   lv_obj_add_event_cb(btn, on_digit, LV_EVENT_PRESSED, (void *)keys[i]);
   /* 用 PRESSED 而非 CLICKED:手指轻微移动时 CLICKED 会被判成拖拽丢弃 */
   lv_obj_t *lb = lv_label_create(btn);
   lv_label_set_text(lb, keys[i]);
   lv_obj_set_style_text_font(lb, &lv_font_montserrat_24, 0);
   lv_obj_center(lb);
}

三个界面用 lv_screen_load() 切换:拨号屏 / 来电屏(大号号码 + 接听/挂断)/ 通话屏(计时 + 挂断)。

image.png

4.3 大音量振铃(ringtone.c)

模组自带铃声音量太小(内置 PA 上限),改用 DISCO 板载 WM8994 codec 播放程序生成的铃声—— 不需要音频文件,正弦波查表生成:

/* 双音 440+480Hz(美式回铃音),44.1kHz/16bit/立体声 */
#define TONE_AMP  12000

static void fill_block(int16_t *buf, size_t frames)
{
   static uint32_t phase1, phase2;
   for (size_t i = 0; i < frames; i++) {
       int32_t s = (sine(phase1) + sine(phase2)) / 2;   /* 双音叠加 */
       buf[i*2] = buf[i*2+1] = (int16_t)(s * TONE_AMP / 32767);
       phase1 += PHASE_STEP_440HZ;  phase2 += PHASE_STEP_480HZ;
   }
}
/* 节奏:响 1s 停 2s,来电期间循环;接听/挂断即停 */

音频通路(SAI1 + I2S DMA → WM8994 → CN11 耳机口),初始化时序要注意 "先起 SAI 时钟再配 codec"(codec 的电荷泵需要 MCLK/BCLK 在场)。



五、编译与烧录

环境:Windows + Zephyr 4.4(工作区自带 SDK 1.0.1),Git Bash:

export ZEPHYR_BASE=/d/luglZephyrproject/zephyr
export ZEPHYR_SDK_INSTALL_DIR=/d/luglZephyrproject/zephyr-sdk-1.0.1/zephyr-sdk-1.0.1
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr

cd /d/luglZephyrproject
west build -b stm32h747i_disco/stm32h747xx/m7 \
 --shield st_b_lcd40_dsi1_mb1166_a09 -p always -d build/h747phone \
 /d/luglZephyrproject/app/stm32H747_phone
west flash -d build/h747phone

控制台(ST-Link VCP,115200)启动日志:

*** Booting Zephyr OS build v4.4.0-25 ***
<inf> ringtone: audio hw ready
[INFO] [modem] probing AT... (round 0)
[INFO] [modem] online (round 0)
[modem] registered, phone ready

image.png


六、成果

功能清单

功能实现



触屏拨号LVGL 数字键盘,ATD 拨出
通话免提双向通话(喇叭+模组 mic),MM:SS 计时
来电RING 弹窗 + 号码显示 + 大音量振铃(DISCO 板载音频)
挂断主动挂断(ATH)/ 对方挂断(NO CARRIER)都识别
网络4G 自动注册,状态栏实时显示

资源占用

FlashRAM



~355KB / 1MB(35%)~168KB / 512KB(33%)

(H747 还有 M4 核和 8MB SDRAM 完全没用上——扩展空间很大)

整机实拍

e35163a11adf6792676afd39328ac413.jpg


七、技术要点回顾

  1. 电话 = 几条 AT 命令:ATD(拨)/ ATA(接)/ ATH(挂)/ +CLIP(显号)/ AT+CLCC(状态轮询)—— 4G 模组的语音功能没想象中复杂

  2. 分层抽象是移植的关键:OSAL + hal_uart 两层换掉,整个 AT 框架(at_client/ql_*)零修改跨平台

  3. 大屏触摸 + LVGL 是绝配:800x480 的拨号键盘比实体按键手感好

  4. 音频可以"借":模组铃声小就换板载 codec——程序生成正弦波,连音频文件都不用

  5. Uniknect 板的正确用法是飞线不是叠插:3 根线(TX/RX/GND)比整个 Arduino 排针可靠得多


全部代码基于开源组件:Zephyr v4.4 / LVGL 9.5 / uniknect-c(Apache-2.0)。





关键词: 移远产品体验官          EC200U     UniKnectG    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]