【RISC-V MCU CH32V103测评】+ 一氧化碳报警器
<p> <span style="font-size:20px;"> 一氧化碳是无色、无味、无刺激性的有毒的气体.含碳物质不完全燃烧时均会产生一氧化碳,例如部分家庭燃煤取暖,燃气热水器,机车尾气等.吸入高浓度一氧化碳可致人死亡,一氧化碳会影响氧和红细胞结合.</span></p><p><span style="font-size:20px;"> 50PPM已经是安全极限了,200PPM会造成轻微头痛,400PPM则可引起呕吐甚至眩晕,浓度再高就可能致人死亡.因其无色无味,犹如无形杀手,每年因一氧化碳中毒而死亡的也不再少数,年前我一位亲人也因一氧化碳中毒而去世.</span></p>
<p> </p>
<p><span style="font-size:20px;"> 目前市面上的一氧化碳报警器仅有声光报警,不能主动通风,降低浓度,挽救生命.</span></p>
<p><span style="font-size:20px;"> 本设计基于沁恒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 > 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 > 2) //1.5秒读取一次
{ /*调用DHT11_Read_TempAndHumidity读取温湿度,若成功则输出该信息*/
DHT11_Read_TempAndHumidity ( & 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<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, &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, &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(&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)&ADC1->RDATAR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&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, &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, &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, &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, &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, &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(&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 > 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> </p>
<p><span style="font-size:20px;">以下是视频展示<a href="https://www.bilibili.com/video/BV1r64y1U7q3/" target="_blank">CO报警器功能演示</a></span></p>
<p> </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> </p>
<p><span style="font-size:20px;"> 总的来说CH32V103是替代STM32F103的产品,在引脚和驱动库上和STD库保持高度一致,且是基于RISC-V,免费开源不易被邦交政策影响,还有一个M3核的CH32F103的姊妹作伴,沁恒做了优化,可以无缝互相替代,</span></p>
<p><span style="font-size:20px;"> CH32V103用的PFIC中断控制器,</span></p>
<p>PFIC 控制器<br />
44+3个可单独屏蔽中断,每个中断请求都有独立的触发和屏蔽位、状态位<br />
提供一个不可屏蔽中断NMI<br />
2级嵌套中断进入和退出硬件自动压栈和恢复,无需指令开销<br />
4路可编程快速中断通道,自定义中断向量地址<br />
<span style="font-size:20px;">希望官方可以出一个更详细介绍PDIC的文档以及例程参考.</span></p>
<p><span style="font-size:24px;"> Bye~~~~</span></p>
<p>生活中的智慧,考虑的情景挺周全的。</p>
<p>赞!视频可以通过编辑器上的视频按钮,直接把B站的视频链接贴上去就可以在线播放了</p>
<p>也可以把视频上传到我们的大学堂</p>
<p>很详细,谢谢分析!</p>
<p>风扇是怎么驱动的?还有蜂鸣器是有源的还是无源的?</p>
littleshrimp 发表于 2021-4-20 11:04
风扇是怎么驱动的?还有蜂鸣器是有源的还是无源的?
<p>GPIO驱动三极管或场管开关来控制风扇和蜂鸣器的.蜂鸣器无所谓够响亮就行,有源就直接给电断电,无源就PWM就ok了</p>
页:
[1]