AIR32F103(六) ADC,I2S,DMA和ADPCM實現錄音播放功能

来源:https://www.cnblogs.com/milton/archive/2022/11/23/16919589.html
-Advertisement-
Play Games

使用的MCU型號為 AIR32F103CCT6. 通過工作機制和示例代碼, 說明如何使用AIR32自帶的記憶體實現簡單的語音錄製和播放功能, 以及使用 ADPCM 對音頻數據進行壓縮, 提高錄製時長. 通過這些機制, 可以快速擴充為實用的錄製設備, 例如外掛I2C或SPI存儲, 或提升無線傳輸的音質,... ...


目錄

關於

使用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歐小喇叭

接線

 *   AIR32F103                  MAX98357A / PT8211
 *   PB13(SPI1_SCK/I2S_CK)       -> BCLK, BCK
 *   PB15(SPI1_MOSI/I2S_SD)      -> DIN
 *   PB12(SPI1_NSS/I2S_WS)       -> LRC, WS
 *                               GND  -> GND
 *                               VIN  -> 3.3V
 *                               +    -> speaker
 *                               -    -> speaker
 * 
 *   AIR32F103                  MAX9814
 *   PA2                        -> Out
 *   3.3V                       -> VDD
 *   GND                        -> GND
 *   GND                        -> A/R
 *                                 GAIN -> float:60dB, gnd:50dB, 3.3v:40dB

代碼

完整的示例代碼

定義了全局變數

// 定義不同的AUDIO_FREQ值, 可以切換不同的採樣頻率, 8K, 11K, 16K, 越高的採樣頻率, 音質越好, 錄音時長越短
#define AUDIO_FREQ 8000
//#define AUDIO_FREQ 11000
//#define AUDIO_FREQ 16000

// 定義存儲的音頻數據大小, CCT6用的是30000, CBT6 或 RPT6 可以相應的減小或增大
#define BUFF_SIZE 30000

// 音頻數據數組, 同時用於DMA的接收地址
uint16_t dma_buf[BUFF_SIZE];

// I2S傳輸時, 用於記錄傳輸的位置
uint32_t index;
// I2S傳輸時, 用於區分左右聲道
__IO uint8_t lr = 0;

初始化GPIO, PA2是採樣輸入, PB12, PB13, PB15 用於I2S傳輸, PC13 是板載的LED, 用於指示錄音開始和結束. 如果使用的不是Bluepill而是合宙的開發板, 可以修改為開發板對應的LED GPIO.

void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // PA2 as analog input
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // PB12,PB13,PB15 as I2S AF output
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_15; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // PC13 as GPIO output
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}

初始化ADC, 設置為外部觸發模式, 這裡使用TIM3的Update中斷作為觸發源, 初始化之後ADC並不會立即開始轉換, 而是在TIM3的每次Update中斷時進行轉換. 所以如果要停止ADC, 需要先停掉TIM3

void ADC_Configuration(void)
{
    ADC_InitTypeDef ADC_InitStructure;

    // Reset ADC1
    ADC_DeInit(ADC1);
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    // 設置 TIM3 為外置觸發源
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
    // 結果右對齊
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    // 只使用一個通道
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    // PA2對應的channel是 ADC_Channel_2
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_239Cycles5);

    // 啟用ADC1的外部觸發源
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);

    // 在 ADC1 上啟用 DMA
    ADC_DMACmd(ADC1, ENABLE);
    ADC_Cmd(ADC1, ENABLE);

    // 校準
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1));
}

初始化DMA, 用 ADC1->DR 作為外設地址, dma_buf作為記憶體地址, 記憶體地址遞增, 數據大小為16bit, 迴圈填充. 同時打開DMA的填充完成中斷 DMA_IT_TC

//調用
DMA_Configuration(DMA1_Channel1, (uint32_t)&ADC1->DR, (uint32_t)dma_buf, BUFF_SIZE);

// 函數實現
void DMA_Configuration(DMA_Channel_TypeDef *DMA_CHx, uint32_t ppadr, uint32_t memadr, uint16_t bufsize)
{
    DMA_InitTypeDef DMA_InitStructure;

    DMA_DeInit(DMA_CHx);
    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = bufsize;
    // Addresss increase - peripheral:no, memory:yes
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    // Data unit size: 16bit
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    // Memory to memory: no
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_CHx, &DMA_InitStructure);
    // Enable 'Transfer complete' interrupt
    DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
    // Enable DMA
    DMA_Cmd(DMA_CHx, ENABLE);
}

打開外設的中斷控制, DMA用於轉換結束, SPI2的中斷用於每次的數據發送

void NVIC_Configuration(void)
{
    // DMA1 interrupts
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    // SPI2 interrupts
    NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

初始化定時器TIM3, 根據MCU頻率72MHz, 計算得到分別在8K, 11K, 16K時的定時器周期和預分頻繫數. 啟用計時器的Update中斷, 但是不啟動定時器, 因為啟動後就會產生中斷, 就會觸發ADC轉換. 需要將計時器的啟動放到main()中.

void TIM_Configuration(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

    TIM_TimeBaseStructure.TIM_Period = 9 - 1;
#if AUDIO_FREQ == 8000
    // Period = 72,000,000 / 8,000 = 1000 * 9
    TIM_TimeBaseStructure.TIM_Prescaler = 1000 - 1;
#elif AUDIO_FREQ == 11000
    // Period = 72,000,000 / 11,000 = 727 * 9
    TIM_TimeBaseStructure.TIM_Prescaler = 727 - 1;
#else
    // Period = 72,000,000 / 16,000 = 500 * 9
    TIM_TimeBaseStructure.TIM_Prescaler = 500 - 1;
#endif
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    // Enable TIM3 'TIM update' trigger for adc
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
    // Timer will be started in main()
}

初始化I2S, 如果使用的是PT8211, 需要將 I2S_Standard 設置為 I2S_Standard_LSB. 否則雙聲道傳數據時工作不正常

void IIS_Configuration(void)
{
    I2S_InitTypeDef I2S_InitStructure;

    SPI_I2S_DeInit(SPI2);
    I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
    // PT8211:LSB,  MAX98357A:Phillips
    I2S_InitStructure.I2S_Standard = I2S_Standard_Phillips;
    // 16-bit data resolution
    I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_16b;
#if AUDIO_FREQ == 8000
    // 8K sampling rate
    I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_8k;
#elif AUDIO_FREQ == 11000
    // 11K sampling rate
    I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_11k;
#else
    // 16K sampling rate
    I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_16k;
#endif
    I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
    I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;
    I2S_Init(SPI2, &I2S_InitStructure);

    I2S_Cmd(SPI2, ENABLE);
}

中斷處理

  • DMA中斷: DMA中斷時表示記憶體數組已經裝滿了, 此時要停掉TIM3和ADC1, 並關掉PC13 LED指示錄音結束
void DMA1_Channel1_IRQHandler(void)
{
    // DMA1 Channel1 Transfer Complete interrupt
    if (DMA_GetITStatus(DMA1_IT_TC1))
    {
        DMA_ClearITPendingBit(DMA1_IT_GL1);
        // Stop ADC(by stopping TIM3)
        TIM_Cmd(TIM3, DISABLE);
        ADC_Cmd(ADC1, DISABLE);
        GPIO_SetBits(GPIOC, GPIO_Pin_13);
    }
}
  • SPI2(I2S)中斷, 用於每個I2S數據的傳輸, 因為傳輸時左右聲道的數據是交替傳輸的, 所以這裡需要用一個全局變數切換當前的聲道. 因為錄音是單聲道, 所以傳輸時對應兩個聲道, 每個值會被傳輸兩遍. 到達最後一個值後, 會停掉I2S.
void SPI2_IRQHandler(void)
{
    // If TX Empty flag is set
    if (SPI_I2S_GetITStatus(SPI2, SPI_I2S_IT_TXE) == SET)
    {
        // Put data to both channels
        if (lr == 0)
        {
            lr = 1;
            SPI_I2S_SendData(SPI2, (uint16_t)dma_buf[index] << 3);
        }
        else
        {
            lr = 0;
            SPI_I2S_SendData(SPI2, (uint16_t)dma_buf[index++] << 3);
            if (index == BUFF_SIZE)
            {
                index = 0;
                // Disable the I2S1 TXE Interrupt to stop playing
                SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_TXE, DISABLE);
            }
        }
    }
}

主函數. 在主函數中, 先開啟錄音, 然後等待4秒(對應 3萬個樣本, 8K採樣, 4秒之內就結束了), 然後開始播放. 每個迴圈等待5秒. 播放會在中斷中判斷是否結束, 結束就停止.

int main(void)
{
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%ld\r\n", SystemCoreClock);

    RCC_Configuration();
    GPIO_Configuration();
    ADC_Configuration();
    DMA_Configuration(DMA1_Channel1, (uint32_t)&ADC1->DR, (uint32_t)dma_buf, BUFF_SIZE);
    NVIC_Configuration();
    TIM_Configuration();
    IIS_Configuration();
    GPIO_SetBits(GPIOC, GPIO_Pin_13);
    Delay_S(1);
    // Start timer to start recording
    printf("Start recording\r\n");
    TIM_Cmd(TIM3, ENABLE);
    // Turn on LED, DMA TC1 interrupt will turn it off 
    GPIO_ResetBits(GPIOC, GPIO_Pin_13);
    Delay_S(4);
    printf("Start playing\r\n");
    while (1)
    {
        // Restart the playing
        SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_TXE, ENABLE);
        Delay_S(5);
    }
}

使用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啟用中斷

void ADC_Configuration(void)
{
    ADC_InitTypeDef ADC_InitStructure;

    // Reset ADC1
    ADC_DeInit(ADC1);
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    // Select TIM3 trigger output as external trigger
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    // ADC_Channel_2 for PA2
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_7Cycles5);

    // Enable ADC1 external trigger
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);
    ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);

    // Enable ADC1
    ADC_Cmd(ADC1, ENABLE);

    // Calibration
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1));
}

在ADC中斷中, 對ADC結果值的編碼

void Audio_Encode(void)
{
    static uint32_t idx = 0;
    static uint8_t msb = 0;
    uint8_t val;

    val = ADPCM_Encode((uint16_t)(ADC1->DR << 2)) & 0x0F;
    if (msb == 0)
    {
        voice[idx] = val;
        msb = 1;
    }
    else
    {
        voice[idx] |= (val << 4);
        msb = 0;
        idx++;
        if (idx == BUFF_SIZE)
        {
            // Stop ADC(by stopping TIM3)
            TIM_Cmd(TIM3, DISABLE);
            ADC_Cmd(ADC1, DISABLE);
            ADC_ExternalTrigConvCmd(ADC1, DISABLE);
            GPIO_SetBits(GPIOC, GPIO_Pin_13);
            idx = 0;
            finish = 1;
        }
    }
}

在I2S傳輸中斷中, 對值的解碼. 每傳輸四個數據(低4位左右聲道, 高4位左右聲道)下標才加1, 傳輸結束後重置下標.

uint16_t Audio_Decode(void)
{
    static uint32_t idx = 0;
    static __IO uint8_t msb = 0, lr = 0;
    static uint16_t val;

    if (msb == 0)
    {
        // Put data to both channels
        if (lr == 0)
        {
            val = ADPCM_Decode(voice[idx] & 0x0F);
            lr = 1;
        }
        else if (lr == 1)
        {
            lr = 0;
            msb = 1;
        }
    }
    else
    {
        if (lr == 0)
        {
            val = ADPCM_Decode((voice[idx] >> 4) & 0x0F);
            lr = 1;
        }
        else if (lr == 1)
        {
            lr = 0;
            msb = 0;
            idx++;
            if (idx == BUFF_SIZE)
            {
                idx = 0;
                ADPCM_Reset();
            }
        }
    }
    return val;
}

使用ADPCM後, 在8K採樣下語音音質沒有明顯下降, 但是錄音時長增長到了15秒, 提升明顯.

最後

以上說明瞭如何使用AIR32自帶的記憶體實現簡單的語音錄製和播放功能, 以及使用 ADPCM 對音頻數據進行壓縮, 提高錄製時長. 通過這些機制, 可以快速擴充為實用的錄製設備, 例如外掛I2C或SPI存儲, 或提升無線傳輸的音質, 在同樣的碼率下使用更高採樣率.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 來源:blog.csdn.net/weixin_61594803 1.SQL數據脫敏實現 MYSQL(電話號碼,身份證)數據脫敏的實現 -- CONCAT()、LEFT()和RIGHT()字元串函數組合使用,請看下麵具體實現 -- CONCAT(str1,str2,…):返回結果為連接參數產生的字元 ...
  • ###知識點 php://filter php://filter是一種元封裝器,是PHP中特有的協議流,設計用於數據流打開時的篩選過濾應用,作用是作為一個“中間流”來處理其他流。 php://filter目標使用以下的參數作為它路徑的一部分。複合過濾鏈能夠在一個路徑上指定。 |名稱|描述|備註| | ...
  • ###結果以json格式輸出,可以用json線上解析,方便查看 package com.xintone.demo; import cn.hutool.json.JSONUtil; import lombok.Data; import org.springframework.util.Collecti ...
  • 關於全局事務的執行,雖然之前的文章中也有所涉及,但不夠細緻,今天再深入的看一下事務的整個執行過程是怎樣的。 1. TransactionManager io.seata.core.model.TransactionManager是事務管理器,它定義了一個全局事務的相關操作 DefaultTransa ...
  • 簡介 在C#中提起控制項綁定數據,大部分人首先想到的是WPF,其實Winform也支持控制項和數據的綁定。 Winform中的數據綁定按控制項類型可以分為以下幾種: 簡單控制項綁定 列表控制項綁定 表格控制項綁定 綁定基類 綁定數據類必須實現INotifyPropertyChanged介面,否則數據類屬性的變更 ...
  • 一:背景 1.講故事 前幾天群里很熱鬧,看了下在爭論兩個問題: 電腦里要不要裝殺毒軟體 ? 應該裝什麼殺毒軟體 ? 不管殺毒軟體流氓不流氓,在如今病毒肆虐的當下互聯網,裝一個還是能幫我們攔截很多意想不到的東西,為了眼見為實,這一篇我們就聊一個竊聽 鍵盤事件 的惡意代碼。 2. 思路 實現思路非常簡單 ...
  • windowserver2012伺服器部署.net core3.1環境操作文檔 一、安裝.net core3.1要先具備這些系統補丁,如果沒有則需要安裝,這些 KB 必須按以下順序安裝:(clearcompressionflag.exe、KB2919442、KB2919355、KB2932046、K ...
  • rpm環境安裝dpkg包管理工具 索引:dpkg-scanpackages、dpkg、dpkg-query、dpkg-source、dpkg-scansource 在centos、redhat、麒麟伺服器版本中想對deb包進行管理,那麼就需要安裝dpkg包管理工具 主要是解決一些內網環境的特定包的需 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...