目录
- STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解)
- STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解)
- STC8H开发(三): 基于FwLib_STC8的模数转换ADC介绍和演示用例说明
- STC8H开发(四): FwLib_STC8 封装库的介绍和使用注意事项
- STC8H开发(五): SPI驱动nRF24L01无线模块
- STC8H开发(六): SPI驱动ADXL345三轴加速度检测模块
- STC8H开发(七): I2C驱动MPU6050三轴加速度+三轴角速度检测模块
- STC8H开发(八): NRF24L01无线传输音频(对讲机原型)
关于PWM, DAC和音频
PWM是脉冲宽度调制的缩写, 因为介绍的文章很多, 自己做功课即可, 参考
- 维基百科 Pulse-width_modulation
- 百度百科 PWM技术
大部分低端MCU不带DAC转换, 但是可以使用PWM模拟, 对于音频传输
- 人普通谈话的声波频率在500-2000Hz之间, 人耳可以听到的声波的频率范围在20Hz至20kHz之间
- 用于通话, 8kHz的带宽就能达到较好的语音传输效果
- 通过PWM模拟DAC, 因为PWM是方波, 其频率会引入底噪, 底噪的频率是PWM频率的倍数
- PWM频率在8KHz时, 在听感上底噪很大, 与传输的音频一样明显, 将PWM的频率调节到16kHz以上才能有效抑制底噪
无线音频传输的实现
以下实现的是单声道 8kHz 8bit 采样的音频信号传输
发送部分
发送部分需要实现的是8kHz采样, 并通过NRF24L01将每秒的8000字节数据发送出去.
语音输入
语音输入可以使用驻极体话筒加S9013放大输入或者直接使用MAX9814. 在测试阶段建议使用后者, 可以保证采样输入不失真, 在调通后再用驻极体话筒电路替换.
ADC音频采样
因为ADC采样需要实现准确的每秒8000采样, 所以不能用DMA方式, 在STC8H(包括STM32等其它MCU)下, 无法在DMA情况下精确调节每秒的采样个数, 因为ADC的采样频率, 采样周期和转换周期在不同MCU中都是固定的, 所以很难正好做到8kHz的采样. 具体的实现中有两种方式:
1.定时器驱动采集
通过定时器设置为8kHz, 在中断中发起ADC转换, 是比较容易实现的. 这时候需要将ADC也实现为中断方式, 因为ADC的转换时间比较长, 如果在定时器中断中做同步的ADC转换, 容易影响主进程. 需要有定时器的中断处理和ADC的中断处理, 定时器的中断处理单纯用于发起转换, ADC的中断才用于读出结果.
2.连续采集定时读取
通过定时器设置为8kHz, 将ADC的采集设置为循环方式(中断采集, 但是在中断时再次发起), 在定时器中断中仅仅读取采集结果. 这种方式也能实现8kHz的采样. 因为这种方式实际上会多消耗电量, 所以实际使用中还是采用了前一种方法.
NRF24L01发送
NRF24L01在设置为1Mbps带宽时实际传输速度能达到23k字节每秒, 因此对于8bit 8kHz采样的传输是没问题的. 因为NRF24L01传输时的响应和重发机制, 在信号不好时, 容易导致发送中断, 为了避免传输时间的波动影响, 在实现中使用了双数组做缓冲. 采样到发送之间的逻辑为
- 两个256字节数组作为全局变量, 同时定义变量指向当前写入的数组编号和写入位置
- ADC中断读取结果时, 往当前编号的数组和位置中写入并移动位置, 当写满一个数组时, 将此数组标记为可发送, 并切换到下一个数组继续写入
- 在主进程中, 判断当前是否有可发送的数组, 如果可发送, 则在循环中按32个字节一组将数据全部发送.
因为在正常收发的信号强度下, NRF24L01的发送速度是比采样速度快的, 所以基本上NRF24L01的发送是发送 -> 等待 -> 发送的状态
接收部分
接收部分要实现的是将NRF24L01接收到的数据进行存储, 并按照8kHz的频率, 将每个值设为PWM输出的占空比, 实现DAC模拟
RNF24L01接收
因为NRF24L01发送是集中发送, 而PWM还原是匀速的, 所以在接收也需要有缓冲, 接收的机制和发送相似
- 两个256字节数组作为全局变量, 同时定义变量指向当前写入的数组编号和写入位置
- NRF24L01通过中断接收数据, 在接收时, 往当前编号的数组和位置中写入并移动位置, 当写满一个数组时, 将此数组标记为可用, 并切换到下一个数组继续写入
PWM模拟DAC还原
初始化一个PWM输出, PWM周期为256对应8bit的占空比调节范围, 确保PWM频率不低于16kHz. 在8kHz定时器的中断中, 判断当前读取的数组和位置, 每次读取一个值, 并将其设置为PWM占空比. 如果数组不可用, 就不做任何操作, 如果此时将占空比设为0, 会产生噪音.
音频输出
测时阶段, 可以在PWM输出上串联一个200R的电阻后值连喇叭, 可以听到输出的音频. 这个电阻不能太小, 测试中如果阻值小于100R, 会导致MCU供电不足反复重启. 在确定音频输出没问题后, 可以替换为 PAM8403 音频放大模块.
在使用 PAM8403 模块时
- 模块需要独立供电, 测试中如果与MCU都使用USB2TTL供电, 会使MCU供电不足而导致声音输出异常
- 模块与MCU的输出可以不共地, 即模块MCU的PWM输出和地, 可以直接接入PAM8403的音频输入
- 因为是单声道信号, 所以只能用PAM8403的一个声道, L或者R都可以
演示代码
- GitHub FwLib_STC8/tree/master/demo/spi/nrf24l01_audio
- Gitee FwLib_STC8/tree/master/demo/spi/nrf24l01_audio
接线说明
在测试中发送部分使用的是 STC8H3K32S2, 接收部分使用的是 STC8H1K08, 你可以使用STC8H系列的任意一个型号
共同的连接部分(NRF24L01)
8H3K32S2/8H1K08 NRF24L01
P35(SS, Ignored) => CSN 16
P34(MOSI) => MOSI 15
P33(MISO) => MISO 14
P32(SPCLK) => CLK 13
P36(INT2) => IRQ 17
P37(IO) => CE 18
发送部分
STC8H3K32S2 MAX9814
P11(ADC1) => MIC
3.3V => VDD
3.3V => GAIN
GND => A/R
GND => GND
ADC, 如果是STC8H3K32S2, 使用ADC采样需要将AVcc, AGnd 和 ADC_Vref+ 正确连线
AVcc => 3.3V
AGnd => GND
ADC_Vref+ => 3.3V
P11 => Output(MAX9814) or MIC
接收部分
STC8H1K08 PAM8403
P10(PWM1P) => 200R => L or R Input
GND => _|_ Input
Ext 3.3V/5V => VCC
Ext GND => GND
注意:
- MCU的pin脚布局不一定相同, STC8H3K32S2和STC8H1K08都是20pin的封装, 但是pin脚布局就不一样
- 烧录发送部分和接收部分时, 注意要调换 nrf24l01.c 中的 RX_ADDRESS 和 TX_ADDRESS
效果演示
B站视频 https://www.bilibili.com/video/BV1kZ4y1Z78v
调试说明
因为这个演示实际上包含了定时器, ADC采样, NRF24L01发送, 接收, PWM调制这几个环节, 任一个环节出问题, 都会导致演示失败. 在调试中, 需要遵循化整为零, 逐个确认的原则, 对每个节点是否工作正常进行确认.
定时器调试
因为8kHz的输出较难观测, 可以用一个uint16_t的全局变量自增到8000后串口输出观察时间间隔是否正确
ADC调试
- 先通过同步模式, 查看ADC采集是否正确, STC8H1K和STC8H3K的ADC接线是不一样的, 如果接线不正确, 输出的就是噪音.
- 同步采样没问题后, 再通过中断方式采集检查是否正确
- 中断没问题后, 就可以结合定时器, 通过定时器发起采样
NRF24L01 调试
可以参考前面的例子SPI驱动nRF24L01无线模块 单独运行 NRF24L01 进行收发是否功能正常
PWM 调试
有条件的可以用逻辑分析仪, 输出正常后, 用音频进行测试, 可以参考PWM输出音频这个例子, 循环播放一段8bit音频检查PWM输出是否正确, 因为音频较大, 测试这个需要使用Flash容量至少32K字节的芯片, 例如STC8H3K32S2.
最小系统联调
最小系统的发送端先不使用ADC, 使用固定的8bit音频作为输入进行发送, 接收端先不外接音频放大, 直接用200欧串联小喇叭进行检查, 工作正常的情况下, 音频播放效果应当是非常好的
在最小系统联调没问题后, 就可以开始调试ADC, 没问题后最后加入音频放大模块.