目录

关于

使用AIR32的ADC, I2S 和 DMA 实现简单的语音录音和播放功能, 以及使用 ADPCM 编码提升录音时长. 使用的MCU型号为 AIR32F103CCT6. 如果用CBT6, 对应的音频数据数组大小需要相应减小.

音频录音和播放

工作方式

加电后开始录音, 录音结束后循环播放

  • 录音: 麦克风模块 -> ADC采样(12bit, 8K, 11K 或 16K) -> 存储在内存
  • 播放: I2S -> I2S外设(MAX98357A / PT8211) -> 喇叭

对中间每个环节的说明

存储

首先是存储, MCU的内存有限, 如果不借助AT24C, MX25L这类外部存储, 只用内存存储的数据是有限的, AIR32F103CCT6 带 64K Byte内存, 如果按原始采样值存储, 录音时长为

  • 16bit

    • 8K: 128kbps, 约4秒
    • 11K: 176kbps, 约3秒
    • 16K: 256kbps, 约2秒
  • 8bit
    • 8K: 64kbps, 约8秒
    • 11K: 88kbps, 约6秒
    • 16K: 128kbps, 约4秒

采样

使用AIR32的ADC, 配合定时器实现精确的每秒8K, 11K和16K采样. AIR32的ADC分辨率和STM32F103一样都是固定的12bit(STM32F4之后才可以用寄存器调节分辨率)

  • 如果使用ADC的中断, 可以向高位偏移做成16bit, 也可以去掉低位做成8bit
  • 如果使用DMA, 因为AIR32不能像STM32那样, 在4字节地址上偏移一个字节取值, 所以只能按16bit(halfword)传值

音频采集设备如果直接用驻极体话筒, 采样的信号很弱(不是没有, 但是非常小), 需要加一个三极管做放大. 也可以买成品的 MAX9814 模块. 两者的效果区别不大, 但是在调试阶段, 建议用 MAX9814, 因为不用担心信号是否过饱和和失真问题, 在调通之后, 再换回低成本的驻极体话筒和三极管.

驻极体话筒放大的电路和元件参数可以参考这一篇 https://www.cnblogs.com/milton/p/15315783.html

播放

播放可以使用PWM转DAC, 也可以直接用I2S.

  • 如果使用PWM, 因为PWM本身是方波, 会产生大量的谐振噪音, 只有将PWM频率设置到16KHz以上才能明显降低噪音(因为谐振频率超出人耳的听觉范围了), 用8KHz时的噪音非常明显.
  • 因为AIR32F103全系列都支持I2S(数据手册上写只有RPT7才有, 实际上CBT6和CCT6也有), 所以直接用I2S输出是最简单的. 这时候需要一个能接收I2S输出并转为音频的模块.

I2S模块可以用 MAX98357A 模块, 自带I2S解码和放大可以直连喇叭, 也可以买PT8211/TM8211/GH8211, 0.3元一片非常便宜还是双声道, 缺点是不带功放, 如果直连喇叭得贴着耳朵才能听到, 可以再加一个LM386或者PAM8403做放大, 都非常便宜.

实现

硬件

  • AIR32F103CCT6
  • MAX9814
  • PT8211
  • 8欧小喇叭

接线

  1. * AIR32F103 MAX98357A / PT8211
  2. * PB13(SPI1_SCK/I2S_CK) -> BCLK, BCK
  3. * PB15(SPI1_MOSI/I2S_SD) -> DIN
  4. * PB12(SPI1_NSS/I2S_WS) -> LRC, WS
  5. * GND -> GND
  6. * VIN -> 3.3V
  7. * + -> speaker
  8. * - -> speaker
  9. *
  10. * AIR32F103 MAX9814
  11. * PA2 -> Out
  12. * 3.3V -> VDD
  13. * GND -> GND
  14. * GND -> A/R
  15. * GAIN -> float:60dB, gnd:50dB, 3.3v:40dB

代码

完整的示例代码

定义了全局变量

  1. // 定义不同的AUDIO_FREQ值, 可以切换不同的采样频率, 8K, 11K, 16K, 越高的采样频率, 音质越好, 录音时长越短
  2. #define AUDIO_FREQ 8000
  3. //#define AUDIO_FREQ 11000
  4. //#define AUDIO_FREQ 16000
  5. // 定义存储的音频数据大小, CCT6用的是30000, CBT6 或 RPT6 可以相应的减小或增大
  6. #define BUFF_SIZE 30000
  7. // 音频数据数组, 同时用于DMA的接收地址
  8. uint16_t dma_buf[BUFF_SIZE];
  9. // I2S传输时, 用于记录传输的位置
  10. uint32_t index;
  11. // I2S传输时, 用于区分左右声道
  12. __IO uint8_t lr = 0;

初始化GPIO, PA2是采样输入, PB12, PB13, PB15 用于I2S传输, PC13 是板载的LED, 用于指示录音开始和结束. 如果使用的不是Bluepill而是合宙的开发板, 可以修改为开发板对应的LED GPIO.

  1. void GPIO_Configuration(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStructure;
  4. // PA2 as analog input
  5. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
  7. GPIO_Init(GPIOA, &GPIO_InitStructure);
  8. // PB12,PB13,PB15 as I2S AF output
  9. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_15;
  10. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  11. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  12. GPIO_Init(GPIOB, &GPIO_InitStructure);
  13. // PC13 as GPIO output
  14. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
  15. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  16. GPIO_Init(GPIOC, &GPIO_InitStructure);
  17. }

初始化ADC, 设置为外部触发模式, 这里使用TIM3的Update中断作为触发源, 初始化之后ADC并不会立即开始转换, 而是在TIM3的每次Update中断时进行转换. 所以如果要停止ADC, 需要先停掉TIM3

  1. void ADC_Configuration(void)
  2. {
  3. ADC_InitTypeDef ADC_InitStructure;
  4. // Reset ADC1
  5. ADC_DeInit(ADC1);
  6. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
  7. ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  8. ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
  9. // 设置 TIM3 为外置触发源
  10. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
  11. // 结果右对齐
  12. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  13. // 只使用一个通道
  14. ADC_InitStructure.ADC_NbrOfChannel = 1;
  15. ADC_Init(ADC1, &ADC_InitStructure);
  16. // PA2对应的channel是 ADC_Channel_2
  17. ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_239Cycles5);
  18. // 启用ADC1的外部触发源
  19. ADC_ExternalTrigConvCmd(ADC1, ENABLE);
  20. // 在 ADC1 上启用 DMA
  21. ADC_DMACmd(ADC1, ENABLE);
  22. ADC_Cmd(ADC1, ENABLE);
  23. // 校准
  24. ADC_ResetCalibration(ADC1);
  25. while (ADC_GetResetCalibrationStatus(ADC1));
  26. ADC_StartCalibration(ADC1);
  27. while (ADC_GetCalibrationStatus(ADC1));
  28. }

初始化DMA, 用 ADC1->DR 作为外设地址, dma_buf作为内存地址, 内存地址递增, 数据大小为16bit, 循环填充. 同时打开DMA的填充完成中断 DMA_IT_TC

  1. //调用
  2. DMA_Configuration(DMA1_Channel1, (uint32_t)&ADC1->DR, (uint32_t)dma_buf, BUFF_SIZE);
  3. // 函数实现
  4. void DMA_Configuration(DMA_Channel_TypeDef *DMA_CHx, uint32_t ppadr, uint32_t memadr, uint16_t bufsize)
  5. {
  6. DMA_InitTypeDef DMA_InitStructure;
  7. DMA_DeInit(DMA_CHx);
  8. DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
  9. DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
  10. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  11. DMA_InitStructure.DMA_BufferSize = bufsize;
  12. // Addresss increase - peripheral:no, memory:yes
  13. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  14. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  15. // Data unit size: 16bit
  16. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  17. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  18. DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  19. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
  20. // Memory to memory: no
  21. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  22. DMA_Init(DMA_CHx, &DMA_InitStructure);
  23. // Enable 'Transfer complete' interrupt
  24. DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
  25. // Enable DMA
  26. DMA_Cmd(DMA_CHx, ENABLE);
  27. }

打开外设的中断控制, DMA用于转换结束, SPI2的中断用于每次的数据发送

  1. void NVIC_Configuration(void)
  2. {
  3. // DMA1 interrupts
  4. NVIC_InitTypeDef NVIC_InitStructure;
  5. NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
  6. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
  7. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  8. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  9. NVIC_Init(&NVIC_InitStructure);
  10. // SPI2 interrupts
  11. NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
  12. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
  13. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  14. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  15. NVIC_Init(&NVIC_InitStructure);
  16. }

初始化定时器TIM3, 根据MCU频率72MHz, 计算得到分别在8K, 11K, 16K时的定时器周期和预分频系数. 启用计时器的Update中断, 但是不启动定时器, 因为启动后就会产生中断, 就会触发ADC转换. 需要将计时器的启动放到main()中.

  1. void TIM_Configuration(void)
  2. {
  3. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  4. TIM_TimeBaseStructure.TIM_Period = 9 - 1;
  5. #if AUDIO_FREQ == 8000
  6. // Period = 72,000,000 / 8,000 = 1000 * 9
  7. TIM_TimeBaseStructure.TIM_Prescaler = 1000 - 1;
  8. #elif AUDIO_FREQ == 11000
  9. // Period = 72,000,000 / 11,000 = 727 * 9
  10. TIM_TimeBaseStructure.TIM_Prescaler = 727 - 1;
  11. #else
  12. // Period = 72,000,000 / 16,000 = 500 * 9
  13. TIM_TimeBaseStructure.TIM_Prescaler = 500 - 1;
  14. #endif
  15. TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  16. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  17. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
  18. // Enable TIM3 'TIM update' trigger for adc
  19. TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
  20. // Timer will be started in main()
  21. }

初始化I2S, 如果使用的是PT8211, 需要将 I2S_Standard 设置为 I2S_Standard_LSB. 否则双声道传数据时工作不正常

  1. void IIS_Configuration(void)
  2. {
  3. I2S_InitTypeDef I2S_InitStructure;
  4. SPI_I2S_DeInit(SPI2);
  5. I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
  6. // PT8211:LSB, MAX98357A:Phillips
  7. I2S_InitStructure.I2S_Standard = I2S_Standard_Phillips;
  8. // 16-bit data resolution
  9. I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_16b;
  10. #if AUDIO_FREQ == 8000
  11. // 8K sampling rate
  12. I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_8k;
  13. #elif AUDIO_FREQ == 11000
  14. // 11K sampling rate
  15. I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_11k;
  16. #else
  17. // 16K sampling rate
  18. I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_16k;
  19. #endif
  20. I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
  21. I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;
  22. I2S_Init(SPI2, &I2S_InitStructure);
  23. I2S_Cmd(SPI2, ENABLE);
  24. }

中断处理

  • DMA中断: DMA中断时表示内存数组已经装满了, 此时要停掉TIM3和ADC1, 并关掉PC13 LED指示录音结束
  1. void DMA1_Channel1_IRQHandler(void)
  2. {
  3. // DMA1 Channel1 Transfer Complete interrupt
  4. if (DMA_GetITStatus(DMA1_IT_TC1))
  5. {
  6. DMA_ClearITPendingBit(DMA1_IT_GL1);
  7. // Stop ADC(by stopping TIM3)
  8. TIM_Cmd(TIM3, DISABLE);
  9. ADC_Cmd(ADC1, DISABLE);
  10. GPIO_SetBits(GPIOC, GPIO_Pin_13);
  11. }
  12. }
  • SPI2(I2S)中断, 用于每个I2S数据的传输, 因为传输时左右声道的数据是交替传输的, 所以这里需要用一个全局变量切换当前的声道. 因为录音是单声道, 所以传输时对应两个声道, 每个值会被传输两遍. 到达最后一个值后, 会停掉I2S.
  1. void SPI2_IRQHandler(void)
  2. {
  3. // If TX Empty flag is set
  4. if (SPI_I2S_GetITStatus(SPI2, SPI_I2S_IT_TXE) == SET)
  5. {
  6. // Put data to both channels
  7. if (lr == 0)
  8. {
  9. lr = 1;
  10. SPI_I2S_SendData(SPI2, (uint16_t)dma_buf[index] << 3);
  11. }
  12. else
  13. {
  14. lr = 0;
  15. SPI_I2S_SendData(SPI2, (uint16_t)dma_buf[index++] << 3);
  16. if (index == BUFF_SIZE)
  17. {
  18. index = 0;
  19. // Disable the I2S1 TXE Interrupt to stop playing
  20. SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_TXE, DISABLE);
  21. }
  22. }
  23. }
  24. }

主函数. 在主函数中, 先开启录音, 然后等待4秒(对应 3万个样本, 8K采样, 4秒之内就结束了), 然后开始播放. 每个循环等待5秒. 播放会在中断中判断是否结束, 结束就停止.

  1. int main(void)
  2. {
  3. Delay_Init();
  4. USART_Printf_Init(115200);
  5. printf("SystemClk:%ld\r\n", SystemCoreClock);
  6. RCC_Configuration();
  7. GPIO_Configuration();
  8. ADC_Configuration();
  9. DMA_Configuration(DMA1_Channel1, (uint32_t)&ADC1->DR, (uint32_t)dma_buf, BUFF_SIZE);
  10. NVIC_Configuration();
  11. TIM_Configuration();
  12. IIS_Configuration();
  13. GPIO_SetBits(GPIOC, GPIO_Pin_13);
  14. Delay_S(1);
  15. // Start timer to start recording
  16. printf("Start recording\r\n");
  17. TIM_Cmd(TIM3, ENABLE);
  18. // Turn on LED, DMA TC1 interrupt will turn it off
  19. GPIO_ResetBits(GPIOC, GPIO_Pin_13);
  20. Delay_S(4);
  21. printf("Start playing\r\n");
  22. while (1)
  23. {
  24. // Restart the playing
  25. SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_TXE, ENABLE);
  26. Delay_S(5);
  27. }
  28. }

使用ADPCM压缩音频数据

ADPCM 的原理和计算方式可以参考这一篇 https://www.cnblogs.com/milton/p/16914797.html.

使用ADPCM可以将16bit的数据压缩为4bit, 同时保持基本一致的听觉信息. 这样对于64kB的CCT6, 可以在12bit的效果下记录接近16秒的语音(64K = 16 * 8K * 0.5). 而且 ADPCM 的计算简单, AIR32这种M3核心的MCU处理起来非常轻松.

工作机制调整

如果使用ADPCM, 需要对前面的例子进行一些调整. 硬件和前面的一致, 改动都在代码.

去掉DMA

因为DMA必须是硬件到硬件, 如果想做成双缓冲, 比如做一个1K左右的DMA数组, 一半结束后批量编码, 再等另一半结束再编码? 这样其实不行, 因为集中编码时ADC也还在进行, 一边在计算一边在转换和中断, 会互相影响, 导致采样不均匀. 因为ADC转换使用定时器触发, 定时器两个中断之间, ADC转换的时间很短, 中间间隔的时间完全可以用于编码, 所以需要将DMA去掉, 改成使用ADC的转换完成中断, 在完成中断的处理函数中对采样值进行编码

调整数组

为了计算方便, 将语音数组转换为uint8_t, 这样每个值记录的是两个采样点, 相应的数组大小扩充到了60000

调整I2S传输

因为每个值存储的是两个采样, 因此在I2S的TXE中断处理中, 原先的左右声道判断需要叠加4bit偏移判断, 变成4种情况.

代码

完整的示例代码

ADC启用中断

  1. void ADC_Configuration(void)
  2. {
  3. ADC_InitTypeDef ADC_InitStructure;
  4. // Reset ADC1
  5. ADC_DeInit(ADC1);
  6. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
  7. ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  8. ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
  9. // Select TIM3 trigger output as external trigger
  10. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
  11. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  12. ADC_InitStructure.ADC_NbrOfChannel = 1;
  13. ADC_Init(ADC1, &ADC_InitStructure);
  14. // ADC_Channel_2 for PA2
  15. ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_7Cycles5);
  16. // Enable ADC1 external trigger
  17. ADC_ExternalTrigConvCmd(ADC1, ENABLE);
  18. ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
  19. // Enable ADC1
  20. ADC_Cmd(ADC1, ENABLE);
  21. // Calibration
  22. ADC_ResetCalibration(ADC1);
  23. while (ADC_GetResetCalibrationStatus(ADC1));
  24. ADC_StartCalibration(ADC1);
  25. while (ADC_GetCalibrationStatus(ADC1));
  26. }

在ADC中断中, 对ADC结果值的编码

  1. void Audio_Encode(void)
  2. {
  3. static uint32_t idx = 0;
  4. static uint8_t msb = 0;
  5. uint8_t val;
  6. val = ADPCM_Encode((uint16_t)(ADC1->DR << 2)) & 0x0F;
  7. if (msb == 0)
  8. {
  9. voice[idx] = val;
  10. msb = 1;
  11. }
  12. else
  13. {
  14. voice[idx] |= (val << 4);
  15. msb = 0;
  16. idx++;
  17. if (idx == BUFF_SIZE)
  18. {
  19. // Stop ADC(by stopping TIM3)
  20. TIM_Cmd(TIM3, DISABLE);
  21. ADC_Cmd(ADC1, DISABLE);
  22. ADC_ExternalTrigConvCmd(ADC1, DISABLE);
  23. GPIO_SetBits(GPIOC, GPIO_Pin_13);
  24. idx = 0;
  25. finish = 1;
  26. }
  27. }
  28. }

在I2S传输中断中, 对值的解码. 每传输四个数据(低4位左右声道, 高4位左右声道)下标才加1, 传输结束后重置下标.

  1. uint16_t Audio_Decode(void)
  2. {
  3. static uint32_t idx = 0;
  4. static __IO uint8_t msb = 0, lr = 0;
  5. static uint16_t val;
  6. if (msb == 0)
  7. {
  8. // Put data to both channels
  9. if (lr == 0)
  10. {
  11. val = ADPCM_Decode(voice[idx] & 0x0F);
  12. lr = 1;
  13. }
  14. else if (lr == 1)
  15. {
  16. lr = 0;
  17. msb = 1;
  18. }
  19. }
  20. else
  21. {
  22. if (lr == 0)
  23. {
  24. val = ADPCM_Decode((voice[idx] >> 4) & 0x0F);
  25. lr = 1;
  26. }
  27. else if (lr == 1)
  28. {
  29. lr = 0;
  30. msb = 0;
  31. idx++;
  32. if (idx == BUFF_SIZE)
  33. {
  34. idx = 0;
  35. ADPCM_Reset();
  36. }
  37. }
  38. }
  39. return val;
  40. }

使用ADPCM后, 在8K采样下语音音质没有明显下降, 但是录音时长增长到了15秒, 提升明显.

最后

以上说明了如何使用AIR32自带的内存实现简单的语音录制和播放功能, 以及使用 ADPCM 对音频数据进行压缩, 提高录制时长. 通过这些机制, 可以快速扩充为实用的录制设备, 例如外挂I2C或SPI存储, 或提升无线传输的音质, 在同样的码率下使用更高采样率.

AIR32F103(六) ADC,I2S,DMA和ADPCM实现录音播放功能的更多相关文章

  1. STM32F103ZET6 之 ADC+TIM+DMA+USART 综合实验

    1.实验目的 1)使用 TIM1 触发 ADC,ADC 采集的数据通过DMA 传至内存,然后通过串口打印出采集的数据: 2)学会 DMA 传输数据并将数据进行保存: 3)验证ADC 的采样率与实际设置 ...

  2. STM32L15x——ADC采集DMA数据只第一次正确(已解决)

    前提:我用的芯片是STM32L系列,可能对其它STM32系列不完全适用,仅供参考! 一.问题描述 我在使用DMA方式读取单ADC单通道采集的数据时,发现只能正确的采集一次数据,后来的就一直与第一次的相 ...

  3. 重学STM32---(六)DAC+DMA+TIM

    这两天复习了DAC,DMA再加上把基本定时器TIM6和TIM7看了一下,打算写一个综合点的程序,,,就在网上找了一些关于DAC,DMA和定时器相关的程序,最终打算写了输出正弦波的程序... 由于没有示 ...

  4. 记STM32F030多通道ADC DMA读取乱序问题

    问题描述通过 uint16_t ConvData[8]保存DMA搬运的ADC转换数值,但是这个数组数值的顺序总是和ADC不是顺序对应的.比如用7个通道的ADC,当设置ADC_InitStructure ...

  5. STM32 DMA USART ADC

    转载自:http://www.cnblogs.com/UQYT/articles/2949794.html 这是一个综合的例子,演示了ADC模块.DMA模块和USART模块的基本使用. 我们在这里设置 ...

  6. STM32学习笔记(七) ADC模数转换测电平(普通和DMA模式)

    嵌入式系统在微控制领域(温度,湿度,压力检测,四轴飞行器)中占据着重要地位,这些功能的实现是由微处理器cpu(如stm32)和传感器以及控制器共同完成的,而连接他们,使它们能够互相正常交流的正是本小节 ...

  7. 电子设计省赛--DMA与ADC

    //2014年4月17日 //2014年6月20日入"未完毕" //2014年6月21日 DMA可实现无需cpu控制中断的传输数据保存. 特别是ADC转换多个通道时要用到. 关键是 ...

  8. 【STM32】用DMA实现多路ADC通道数据采集

    今天尝试了下STM32的ADC采样,并利用DMA实现采样数据的直接搬运存储,这样就不用CPU去参与操作了. 找了不少例子参考,ADC和DMA的设置了解了个大概,并直接利用开发板来做一些实验来验证相关的 ...

  9. 关于Stm32定时器+ADC+DMA进行AD采样的实现

    Stm32的ADC有DMA功能这都毋庸置疑,也是我们用的最多的!然而,如果我们要对一个信号(比如脉搏信号)进行定时采样(也就是隔一段时间,比如说2ms),有三种方法: 1.使用定时器中断每隔一定时间进 ...

  10. stm32之TIM+ADC+DMA采集50HZ交流信号

    http://cache.baiducontent.com/c?m=9d78d513d98207f04fece47f0d01d7174a02d1743ca6c76409c3e03984145b5637 ...

随机推荐

  1. springMVC配置时,静态资源和jsp文件路径没错但是访问时controller的请求报404错误。

    springMVC配置时,静态资源和jsp文件路径没错但是访问时controller的请求报404错误. 1.场景 如果在web.xml中servlet-mapping的url-pattern设置的是 ...

  2. 聊聊asp.net core 授权流程

    在上一篇 聊聊 asp.net core 认证和授权 中我们提到了认证和授权的基本概念,以及认证和授权的关系及他们之间的协同工作流程,在这篇文章中,我将通过分析asp.net core 3.1 授权流 ...

  3. 第一章:模型层 - 5:模型的元数据Meta

    模型的元数据,指的是"除了字段外的所有内容",例如排序方式.数据库表名.人类可读的单数或者复数名等等.所有的这些都是非必须的,甚至元数据本身对模型也是非必须的.但是,我要说但是,有 ...

  4. Pod的滚动升级过程

  5. logstash 读取MySQL数据到elasticsearch 相差8小时解决办法

    logstash和elasticsearch是按照UTC时间的,kibana却是按照正常你所在的时区显示的,是因为kibana中可以配置时区信息. 具体看这个: logstash 的配置文件添加 fi ...

  6. TF-GNN踩坑记录(一)

    引言 Batch size作为一个在训练中经常被使用的参数,在图神经网络的训练中也是必不可少,但是在TF-GNN中要求使用 merge_batch_to_components() 把batch之后的图 ...

  7. Python凯撒密码加解密

    #凯撒密码第一个版本 #加密 pxpt=input("请输入明文文本:") for p in pxpt: if 'a'<=p<='z': print(chr(ord(' ...

  8. Linux 下搭建 Kafka 环境

    Linux 下搭建 Kafka 环境 作者:Grey 原文地址: 博客园:Linux 下搭建 Kafka 环境 CSDN:Linux 下搭建 Kafka 环境 环境要求 操作系统:CentOS 7 下 ...

  9. FJOI2007轮状病毒 行列式递推详细证明

    题目链接 题目给了你一个奇怪的图,让你求它的生成树个数. 开始写了一个矩阵树: #include<cstdio> #include<cstdlib> #include<c ...

  10. 【Firefox浏览器】关闭触摸板双指滑动进行前进后退的功能

    痛点 本以为只是Chrome浏览器存在这一奇葩功能,没成想Firefox也沦陷了!有好一阵子在使用Firefox的时候,并未发现其存在这个功能.直到有一天,打开自己的博客,翻阅上篇< [Chro ...