eew_Violet 发表于 2021-3-24 22:33

【RISC-V MCU CH32V103测评】+ 一氧化碳报警器

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size:20px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;一氧化碳是无色、无味、无刺激性的有毒的气体.含碳物质不完全燃烧时均会产生一氧化碳,例如部分家庭燃煤取暖,燃气热水器,机车尾气等.吸入高浓度一氧化碳可致人死亡,一氧化碳会影响氧和红细胞结合.</span></p>

<p><span style="font-size:20px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;50PPM已经是安全极限了,200PPM会造成轻微头痛,400PPM则可引起呕吐甚至眩晕,浓度再高就可能致人死亡.因其无色无味,犹如无形杀手,每年因一氧化碳中毒而死亡的也不再少数,年前我一位亲人也因一氧化碳中毒而去世.</span></p>

<p>&nbsp;</p>

<p><span style="font-size:20px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; 目前市面上的一氧化碳报警器仅有声光报警,不能主动通风,降低浓度,挽救生命.</span></p>

<p><span style="font-size:20px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; 本设计基于沁恒RISC-V架构的CH32V103, 在达到额定浓度声音报警之外还将控制排风扇进行强制通风,由CH32V103采集一氧化碳传感器的模拟电压并转换成有效浓度值,采集DHT11的温湿度数据,一并输出给OLED显示,两路GPIO控制蜂鸣器和排风扇.为了更人性化,光敏电阻模块通过感知光下线,开关OLED,避免晚上成小夜灯(我晚上有光就睡不着).其ADC的模拟看门狗实时检测CO传感器电压,超过设定值便响蜂鸣器报警浓度超过监控值,排风扇进行强制通风,降低CO浓度.OLED显示CO浓度.以达到警示保护生命安全.</span></p>

<p></p>

<p><span style="font-size:20px;">以下为工程代码:</span></p>

<pre>
<code>        Opening_page();//启动页
        OLED_Clear();
    if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) != Bit_SET)
    {
      exti0_status = 1;
    }

        ADC_SoftwareStartConvCmd(ADC1, ENABLE);             //启动ADC转换
        TIM_Cmd(TIM2,ENABLE);         //使能定时器
        while(1)
{


          if(exti0_status == 1)       //光敏电阻感到光
          {
                OLED_DisPlay_On();      //开启OLED

             }else
              {
                OLED_DisPlay_Off();   //关闭oled

              }
             if(refre != 0)       //500ms刷屏一次
             {
                 if(count &gt; 120 ){//mq初次上电有预热时间
                    OLED_Showdecimal(0,38,MQ7_GetPPM(),4,1,24);
                    OLED_ShowString(92,46,"PPM",16);//PPM
                    MQ_ready = 1;
                    if(ADC_GetFlagStatus(ADC1, ADC_IT_AWD)!= SET){
                      ADC_ITConfig( ADC1, ADC_IT_AWD, ENABLE);
                       }
                 }
             if(DHT11_Updata &gt; 2)   //1.5秒读取一次
                 {    /*调用DHT11_Read_TempAndHumidity读取温湿度,若成功则输出该信息*/
                   DHT11_Read_TempAndHumidity ( &amp; DHT11_Data ) ? Main_page() : DHT_ERR();
                   DHT11_Updata = 0;
                 }

             }
             refre = 0;

}
}


//开屏页界面
void Opening_page(void)
{
      OLED_Clear();
      OLED_ShowChinese(8,16,0,16);//一
      OLED_ShowChinese(24,16,1,16);//氧
      OLED_ShowChinese(40,16,2,16);//化
      OLED_ShowChinese(56,16,3,16);//碳
      OLED_ShowChinese(72,16,4,16);//报
      OLED_ShowChinese(88,16,5,16);//警
      OLED_ShowChinese(104,16,6,16);//器
      OLED_ShowString(48,48,"by",16);//by
      OLED_ShowChinese(64,48,7,16);//梨
      OLED_ShowChinese(80,48,8,16);//子
      OLED_Refresh();
      Delay_Ms(3000);
}

//传感器主界面
void Main_page(void)
{
      //DHT11数据

      OLED_ShowChinese(0,4,9,16);//温
      OLED_ShowChinese(16,4,10,16);//度
      OLED_ShowChar(32,4,':',16);//:
      OLED_ShowNum(40,4,DHT11_Data.temp_int,2,16);
      OLED_ShowChar(60,4,'.',16);//.
      OLED_ShowNum(68,4,DHT11_Data.temp_deci,1,16);
      OLED_ShowChar(90,4,'C',16);//C

      OLED_ShowChinese(0,20,11,16);//湿
      OLED_ShowChinese(16,20,10,16);//度
      OLED_ShowChar(32,20,':',16);//:
      OLED_ShowNum(40,20, DHT11_Data.humi_int,2,16);
      OLED_ShowChar(60,20,'.',16);//.
      OLED_ShowNum(68,20, DHT11_Data.humi_deci,1,16);
      OLED_ShowChar(90,20,'%',16);//%
      OLED_ShowString(100,20,"RH",16);//RH
      OLED_Refresh();
}

void DHT_ERR(void)
{

    OLED_Clear();
    OLED_ShowChinese(0,4,9,16);//温
    OLED_ShowChinese(16,4,11,16);//湿
    OLED_ShowChinese(32,4,10,16);//度
    OLED_ShowChinese(48,4,12,16);//读
    OLED_ShowChinese(64,4,13,16);//取
    OLED_ShowChinese(80,4,14,16);//失
    OLED_ShowChinese(96,4,15,16);//败
    OLED_ShowChar(112,4,'!',16);//!
    OLED_Refresh();
}
</code></pre>

<p><span style="font-size:20px;">中断服务函数</span></p>

<pre>
<code>void NMI_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void HardFault_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void TIM2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void ADC1_2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
extern uint8_t exti0_status;
extern uint8_t DHT11_Updata;
extern uint8_t boot_time_ms;
extern uint8_t refre;
extern __IO uint16_t ADC_Value;
extern uint8_t count;
extern uint8_t MQ_ready;
/*******************************************************************************
* Function Name: NMI_Handler
* Description    : This function handles NMI exception.
* Input          : None
* Return         : None
*******************************************************************************/
void NMI_Handler(void)
{
}

/*******************************************************************************
* Function Name: HardFault_Handler
* Description    : This function handles Hard Fault exception.
* Input          : None
* Return         : None
*******************************************************************************/
void HardFault_Handler(void)
{
while (1)
{
}
}

/*******************************************************************************
* Function Name: EXTI0_IRQHandler
* Description    : This function handles EXTI0 Handler.
* Input          : None
* Return         : None
*******************************************************************************/
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)!=RESET)
{
    if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) != Bit_SET)
    {
      exti0_status = 1;
    }else
    {
      exti0_status = 0;
    }
    EXTI_ClearITPendingBit(EXTI_Line0);   /* Clear Flag */
}
if(EXTI_GetITStatus(EXTI_Line4)!=RESET)
    {

   if( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4)!= Bit_SET)
   {

         GPIO_ResetBits(GPIOA,GPIO_Pin_3);
   }

   EXTI_ClearITPendingBit(EXTI_Line4);
    }
}

/*******************************************************************************
* Function Name: TIM2_IRQHandler
* Description    : This function handles TIM2 Handler.
* Input          : None
* Return         : None
*******************************************************************************/
void TIM2_IRQHandler(void)
{
    if(TIM_GetFlagStatus(TIM2, TIM_IT_Update)!= RESET){
      refre = 1;
      DHT11_Updata ++;
      boot_time_ms ++;
      if(MQ_ready == 0){
          count++;
      }
      ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    }
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}

/*******************************************************************************
* Function Name: ADC1_2_IRQHandler
* Description    : This function handles analog wathdog exception.
* Input          : None
* Return         : None
*******************************************************************************/
void ADC1_2_IRQHandler(void)
{
    if(ADC_GetITStatus( ADC1, ADC_IT_AWD)){
      if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2 | GPIO_Pin_3)!= Bit_SET){
      GPIO_SetBits(GPIOA,GPIO_Pin_2 | GPIO_Pin_3);
      }
    }
    if(ADC_Value&lt;1500){
      GPIO_ResetBits(GPIOA,GPIO_Pin_2 | GPIO_Pin_3);
    }

    ADC_ClearITPendingBit( ADC1, ADC_IT_AWD);
}

</code></pre>

<p><span style="font-size:20px;">ADC及DMA配置</span></p>

<pre>
<code>
/*******************************************************************************
* Function Name: ADC_Function_Init
* Description    : Initializes ADC collection.
* Input          : None
* Return         : None
*******************************************************************************/
void ADC_Function_Init(void)
{
    ADC_InitTypeDef ADC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE );
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &amp;GPIO_InitStructure);

    ADC_DeInit(ADC1);
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &amp;ADC_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5 );

    /* 高阈值:1400, 低阈值:0 */
    ADC_AnalogWatchdogThresholdsConfig(ADC1, 1300, 0);
    ADC_AnalogWatchdogSingleChannelConfig( ADC1, ADC_Channel_1);
    ADC_AnalogWatchdogCmd( ADC1, ADC_AnalogWatchdog_SingleRegEnable);

    NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&amp;NVIC_InitStructure);




    ADC_Cmd(ADC1, ENABLE);
    ADC_DMACmd(ADC1, ENABLE); // ADC DMA 使能

    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));


}

/*******************************************************************************
* Function Name: DMA_Tx_Init
* Description    : Initializes the DMAy Channelx configuration.
* Input          : DMA_CHx:
*                  x can be 1 to 7.
*                  ppadr: Peripheral base address.
*                  memadr: Memory base address.
*                  bufsize: DMA channel buffer size.
* Return         : None
*******************************************************************************/

void DMA_Tx_Init(void)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

    DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&amp;ADC1-&gt;RDATAR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&amp;ADC_Value;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = 1;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
    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_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &amp;DMA_InitStructure );
    DMA_Cmd(DMA1_Channel1, ENABLE);


}

</code></pre>

<p><span style="font-size:20px;">SPI初始化</span></p>

<pre>
<code>void SPI1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef SPI_InitStructure;

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE );


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &amp;GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &amp;GPIO_InitStructure );

    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx   ;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init( SPI1, &amp;SPI_InitStructure );

    SPI_Cmd( SPI1, ENABLE );
}

</code></pre>

<p><span style="font-size:20px;">定时器时基配置</span></p>

<pre>
<code>void TIM2_Base_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE );

    TIM_TimeBaseInitStructure.TIM_Period = 5000-1;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit( TIM2, &amp;TIM_TimeBaseInitStructure);

    TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn ;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&amp;NVIC_InitStructure);

    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);

}
</code></pre>

<p><span style="font-size:20px;">一氧化碳转换浓度</span></p>

<pre>
<code>
/****************************************
*RS/R0      ppm                  *
*1.6          50                     *
*1            100                  *
*0.6          200                  *
*0.46         300                  *
*0.39         400                  *
*0.28         600                  *
*0.21         1000                   *
* ppm = 98.322f * pow(RS/R0, -1.458f)*
****************************************/
// 传感器校准函数
void MQ7_PPM_Calibration(float RS)
{
    R0 = RS / pow(CAL_PPM / 98.322, 1 / -1.458f);
}

// 获取传感器的值

float MQ7_GetPPM(void)
{
    float Vrl = 3.3f * ADC_Value / 4095.f;
    float RS = (3.3f - Vrl) / Vrl * RL;
    if(boot_time_ms &gt; 6) // 获取系统执行时间,3s校准
    {
    MQ7_PPM_Calibration(RS);
    boot_time_ms =0;
    }
    float ppm = 98.322f * pow(RS/R0, -1.458f);
    returnppm;
}
</code></pre>

<p><span style="font-size:20px;">以下是功能演示:</span></p>

<p></p>

<p><span style="font-size:20px;">搭建的硬件平台</span></p>

<p></p>

<p><span style="font-size:20px;">上电开机</span></p>

<p></p>

<p><span style="font-size:20px;">关灯效果,OLED关闭</span></p>

<p></p>

<p><span style="font-size:20px;">开灯效果OLED打开,CO传感器有预热时间,时间没到,还没显示.</span></p>

<p></p>

<p>正常显示</p>

<p></p>

<p>达到报警阈值,风扇运行</p>

<p></p>

<p>回落到正常阈值,风扇停转</p>

<p></p>

<p>再次达到报警阀值,</p>

<p>&nbsp;</p>

<p><span style="font-size:20px;">以下是视频展示<a href="https://www.bilibili.com/video/BV1r64y1U7q3/" target="_blank">CO报警器功能演示</a></span></p>

<p>&nbsp;</p>

<p><span style="font-size:20px;">附上工程代码:</span></p>

<p><a href="javascript:;" id="attachname529911" isimage="0" onclick="insertAttachTag('529911');doane(event);" title="GPIO_Toggle.7z
上传日期: 2021-3-24 22:20
文件大小: 393.38 KB">GPIO_Toggle.7z</a></p>

<p>&nbsp;</p>

<p><span style="font-size:20px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;总的来说CH32V103是替代STM32F103的产品,在引脚和驱动库上和STD库保持高度一致,且是基于RISC-V,免费开源不易被邦交政策影响,还有一个M3核的CH32F103的姊妹作伴,沁恒做了优化,可以无缝互相替代,</span></p>

<p><span style="font-size:20px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; CH32V103用的PFIC中断控制器,</span></p>

<p>PFIC 控制器<br />
&nbsp;44+3个可单独屏蔽中断,每个中断请求都有独立的触发和屏蔽位、状态位<br />
&nbsp;提供一个不可屏蔽中断NMI<br />
&nbsp;2级嵌套中断进入和退出硬件自动压栈和恢复,无需指令开销<br />
&nbsp;4路可编程快速中断通道,自定义中断向量地址<br />
<span style="font-size:20px;">希望官方可以出一个更详细介绍PDIC的文档以及例程参考.</span></p>

<p><span style="font-size:24px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Bye~~~~</span></p>

soso 发表于 2021-3-25 09:10

<p>生活中的智慧,考虑的情景挺周全的。</p>

okhxyyo 发表于 2021-3-25 09:52

<p>赞!视频可以通过编辑器上的视频按钮,直接把B站的视频链接贴上去就可以在线播放了</p>

okhxyyo 发表于 2021-3-25 09:52

<p>也可以把视频上传到我们的大学堂</p>

freebsder 发表于 2021-3-25 14:12

<p>很详细,谢谢分析!</p>

littleshrimp 发表于 2021-4-20 11:04

<p>风扇是怎么驱动的?还有蜂鸣器是有源的还是无源的?</p>

eew_Violet 发表于 2021-4-20 22:23

littleshrimp 发表于 2021-4-20 11:04
风扇是怎么驱动的?还有蜂鸣器是有源的还是无源的?

<p>GPIO驱动三极管或场管开关来控制风扇和蜂鸣器的.蜂鸣器无所谓够响亮就行,有源就直接给电断电,无源就PWM就ok了</p>
页: [1]
查看完整版本: 【RISC-V MCU CH32V103测评】+ 一氧化碳报警器