打印
[CW32F030系列]

基于CW32F030小蓝板的简易二氧化碳+温湿度检测仪

[复制链接]
416|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 yuyy1989 于 2023-8-30 15:56 编辑

#申请原创# @21小跑堂  

功能描述
读取环境中的二氧化碳浓度和温湿度并显示,按键长按控制开关机单击刷新数据,开关机时通过无源蜂鸣器播放音乐
需要用到的硬件:
CW32F030小蓝板:作为主控
LCD12864:显示二氧化碳浓度和温湿度,spi通讯
DHTC12:温湿度传感器,I2C通讯
S8-0053:二氧化碳传感器,串口通讯
无源蜂鸣器:用于播放启动音乐盒按键声音
杜邦线若干
开发环境搭建:
CW32的开发环境搭建方法请参考https://yuyy1989.github.io/CW32FAQ或者社区QQ群中的CW32开发常见问题一览20230624.pdf
IO分配:
按键:PB2,使用小篮板上的现有按键,长按开机关机,点击强制刷新数据
LCD12864:sck PA05 mosi PA07 cs PA04 rst PA03 a0 PA02
DHTC12:scl PB10 sda PB11
S8-0053:tx PA09 rx PA10
蜂鸣器:PB09
按键及定时器功能
按键保持按下超过2秒识别为长按,触发长按后切换开关机状态,按下时间不足1秒识别为单击,触发单击后强制刷新二氧化碳和温湿度数据,使用定时器查询按键IO状态来实现长按和单击的判断

按键IO初始化为输入,不开中断,小篮板上的下拉电阻是没有焊的这里开启内部下拉

uint8_t poweron = 1;
uint16_t refresh_count = 0;
uint8_t keysoundcount = 0;
uint16_t key_downcount = 0;
void key_init()
{
    GPIO_InitTypeDef gpiodef;
    gpiodef.Pins = GPIO_PIN_2;
    gpiodef.Mode = GPIO_MODE_INPUT_PULLDOWN;
    gpiodef.IT = GPIO_IT_NONE;
    GPIO_Init(CW_GPIOB,&gpiodef);
}
定时器初始化,开启溢出中断,实现一个1ms的定时器
void gtimer1_init()
{
    GTIM_InitTypeDef GTIM_InitStruct = {0};
    __RCC_GTIM1_CLK_ENABLE();
    GTIM_InitStruct.Mode = GTIM_MODE_TIME;
    GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV64;
    GTIM_InitStruct.ReloadValue = 1000-1;
    GTIM_TimeBaseInit(CW_GTIM1, >IM_InitStruct);
    GTIM_ITConfig(CW_GTIM1, GTIM_IT_OV, ENABLE);
    NVIC_EnableIRQ(GTIM1_IRQn);
    GTIM_Cmd(CW_GTIM1, ENABLE);
}
在定时器中断中实现按键检测、定时刷新数据和串口空闲判断等功能,默认每5秒刷新一次二氧化碳和温湿度数据,检测到单击动作后立即刷新
void poweronoff()
{
    if(poweron == 0)
    {
        poweron = 1;
    }
    else
    {
        poweron = 3;
    }
}
void GTIM1_IRQHandler(void)
{
    if (GTIM_GetITStatus(CW_GTIM1, GTIM_IT_OV))
    {
        GTIM_ClearITPendingBit(CW_GTIM1, GTIM_IT_OV);
        if(uart_rxtime_count > 0)
        {
            uart_rxtime_count -= 1;
            if(uart_rxtime_count == 0)
                show_co2();
        }
        if(keysoundcount > 0)
        {
            keysoundcount -= 1;
            if(keysoundcount == 0)
            {
                yuyy_keybeep(0);
            }
        }
        if(refresh_count > 0)
            refresh_count -= 1;
        else
        {
            refresh_data();
            refresh_count = 4999;
        }
        if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_2)==GPIO_Pin_SET)
        {
            if(key_downcount < 0xFFFF)
                key_downcount += 1;
            if(key_downcount == 2000)
            {
                poweronoff();
            }
        }
        else
        {
            if(poweron == 2 && key_downcount > 10 && key_downcount < 1000)
            {
                refresh_count = 0;
                yuyy_keybeep(1);
                keysoundcount = 100;
            }
            key_downcount = 0;
        }
    }
}
LCD12864初始化
LCD12864使用SPI进行通讯,5个IO都是输出模式

cs rst a0使用软件控制,sck mosi复用为SPI,这里只有mcu到lcd的单向通讯所以SPI配制成只发送模式就行了
YUYY_HS12864G18B_DEV_t hs12864_dev;
void hs12864g18b_init(void)
{
    GPIO_InitTypeDef gpiodef;
    hs12864_dev.cs.gpio = CW_GPIOA;
    hs12864_dev.cs.pin = GPIO_PIN_4;
    hs12864_dev.rst.gpio = CW_GPIOA;
    hs12864_dev.rst.pin = GPIO_PIN_3;
    hs12864_dev.a0.gpio = CW_GPIOA;
    hs12864_dev.a0.pin = GPIO_PIN_2;
    hs12864_dev.sck.gpio = CW_GPIOA;
    hs12864_dev.sck.pin = GPIO_PIN_5;
    hs12864_dev.mo.gpio = CW_GPIOA;
    hs12864_dev.mo.pin = GPIO_PIN_7;
   
    RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA, ENABLE);
    gpiodef.Pins = GPIO_PIN_5|GPIO_PIN_7|GPIO_PIN_4|GPIO_PIN_3|GPIO_PIN_2;
    gpiodef.Mode = GPIO_MODE_OUTPUT_PP;
    gpiodef.IT = GPIO_IT_NONE;
    gpiodef.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOA,&gpiodef);
   
    RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_SPI1, ENABLE);
    PA05_AFx_SPI1SCK();
    PA07_AFx_SPI1MOSI();
    SPI_InitTypeDef spidef;
    spidef.SPI_Direction = SPI_Direction_1Line_TxOnly;
    spidef.SPI_Mode = SPI_Mode_Master;
    spidef.SPI_DataSize = SPI_DataSize_8b;
    spidef.SPI_CPOL = SPI_CPOL_High;
    spidef.SPI_CPHA = SPI_CPHA_2Edge;
    spidef.SPI_NSS = SPI_NSS_Soft;
    spidef.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
    spidef.SPI_FirstBit = SPI_FirstBit_MSB;
    spidef.SPI_Speed = SPI_Speed_High;
    SPI_Init(CW_SPI1,&spidef);
    SPI_Cmd(CW_SPI1, ENABLE);
   
    hs12864_dev.spix = CW_SPI1;
    yuyy_hs12864g18b_init(&hs12864_dev);
    yuyy_hs12864g18b_clear_screen(&hs12864_dev);
}
DHTC12初始化
DHTC12使用I2C通讯,DHTC12最高支持100K的通讯速率,注意SCL和SDA要接上拉电阻


根据当先系统时钟调整I2C速率,CW32的硬件I2C通讯代码可以参考这篇https://bbs.21ic.com/icview-3306992-1-1.html
YUYY_DHTC12_DEV_t dhtc12_dev;
YUYY_IIC_DEV_t iic_dev;
void dhtc12_init()
{
    I2C_InitTypeDef I2C_InitStruct;
    GPIO_InitTypeDef GPIO_InitStructure;

    __RCC_GPIOB_CLK_ENABLE();
    PB10_AFx_I2C1SCL();
    PB11_AFx_I2C1SDA();
   
    GPIO_InitStructure.Pins = GPIO_PIN_10|GPIO_PIN_11;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; //
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
   
    __RCC_I2C1_CLK_ENABLE();
    I2C_InitStruct.I2C_BaudEn = ENABLE;
    I2C_InitStruct.I2C_Baud = 79;  //100K=(64000000/(8*(1+79))
    I2C_InitStruct.I2C_FLT = ENABLE;
    I2C_InitStruct.I2C_AA = DISABLE;
    I2C1_DeInit();
    I2C_Master_Init(CW_I2C1,&I2C_InitStruct);//初始化模块
    I2C_Cmd(CW_I2C1,ENABLE);  //模块使能
   
    iic_dev.iictype = YUYY_IIC_TYPE_HARD;
    iic_dev.hiicx = CW_I2C1;
    dhtc12_dev.iicx = &iic_dev;
    yuyy_dhtc12_init(&dhtc12_dev);
}
串口初始化

S8的数据通讯格式如图

S8的串口波特率是9600,初始化串口3并开启中断,
#define UART_BUFFER_LEN 10
uint8_t uart_rxbuffer[UART_BUFFER_LEN] = {0};
uint8_t readco2_cmd[] = { 0xFE, 0x04, 0x00, 0x03, 0x00, 0x01, 0xD5, 0xC5};
uint8_t uart_rxlen = 0;
uint8_t uart_rxtime_count = 0;
uint8_t uart_txlen = 0;
uint8_t uart_txindex = 0;
void uart_init(void)
{
    //UYSART3 TX PA9 RX PA10
    RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA,ENABLE);
    RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_UART3, ENABLE);
   
    GPIO_InitTypeDef GPIO_InitStructure;

    //UART TX RX 复用
    PA09_AFx_UART3TXD();
    PA10_AFx_UART3RXD();

    GPIO_InitStructure.Pins = GPIO_PIN_9;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = GPIO_PIN_10;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
   
    USART_InitTypeDef USART_InitStructure;

    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_Over = USART_Over_16;
    USART_InitStructure.USART_Source = USART_Source_PCLK;
    USART_InitStructure.USART_UclkFreq = 64000000;
    USART_InitStructure.USART_StartBit = USART_StartBit_FE;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No ;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(CW_UART3, &USART_InitStructure);
   
    //优先级,无优先级分组
    NVIC_SetPriority(UART3_IRQn, 0);
    //UARTx中断使能
    NVIC_EnableIRQ(UART3_IRQn);
   
    USART_ITConfig(CW_UART3, USART_IT_RC, ENABLE);
}
在串口中断函数中处理接收和发送数据,每次接收数据重置接收时间计数,当在定时中断中计数减为0时判断为数据已接收完成,进入数据显示方法
void UART3_IRQHandler(void)
{
    if(USART_GetITStatus(CW_UART3, USART_IT_RC) != RESET)
    {
        uart_rxtime_count = 5;
        uart_rxbuffer[uart_rxlen++] = USART_ReceiveData_8bit(CW_UART3);
        USART_ClearITPendingBit(CW_UART3, USART_IT_RC);
    }
    if(USART_GetITStatus(CW_UART3, USART_IT_TXE) != RESET)
    {
        if(uart_txlen > 0)
        {
            USART_SendData_8bit(CW_UART3, readco2_cmd[uart_txindex++]);
            uart_txlen--;
        }
        else
        {
            USART_ITConfig(CW_UART3, USART_IT_TXE, DISABLE);
        }
        USART_ClearITPendingBit(CW_UART3, USART_IT_TXE);
    }
}
S8的查询命令,开启发送空中断开始发送查询命令
void uart_read_co2cmd()
{
    uart_txlen = 8;
    uart_txindex = 0;
    uart_rxlen = 0;
    USART_ITConfig(CW_UART3, USART_IT_TXE, ENABLE);
}
二氧化碳浓度数据的展示方法
void show_co2()
{
    uint16_t co2ppm = 0;
    char out[20];
    uint8_t outlen;
    if(uart_rxlen > 0 && uart_rxbuffer[0] == 0xFE && uart_rxbuffer[1] == 0x04 && uart_rxbuffer[2] == 0x02)
    {
        co2ppm = (uart_rxbuffer[3]<<8)+uart_rxbuffer[4];
        outlen = sprintf(out,"CO2 %d ppm",co2ppm);
        yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,2,0,(uint8_t *)out);
        yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,2,8*outlen,(uint8_t *)"      ");
    }
}
蜂鸣器功能
这里使用的是无源蜂鸣器,与有源蜂鸣器不同内部没有振荡源,需要输出波形才能发声,通过改变输出波形的频率可以让蜂鸣器发出不同的声调
PB09是Gtimer4的输出io

初始化GTimer4输出PWM
void beeppwminit()
{
    GTIM_InitTypeDef GTIM_InitStruct = {0};
    GPIO_InitTypeDef gpiodef;
    __RCC_GPIOC_CLK_ENABLE();
    gpiodef.Pins = GPIO_PIN_9;
    gpiodef.IT = GPIO_IT_NONE;
    gpiodef.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(CW_GPIOB,&gpiodef);
    PB09_AFx_GTIM4CH1();
   
    __RCC_GTIM4_CLK_ENABLE();
    GTIM_InitStruct.Mode = GTIM_MODE_TIME;
    GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV32;
    GTIM_InitStruct.ReloadValue = 1000-1;
    GTIM_TimeBaseInit(CW_GTIM4, >IM_InitStruct);
    GTIM_OCInit(CW_GTIM4, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_HIGH);
    GTIM_SetCompare1(CW_GTIM4, 0);
    GTIM_Cmd(CW_GTIM4, ENABLE);
}
利用无源蜂鸣器实现播放开关机音乐和按键声音,通过改变输出的PWM频率就能改变蜂鸣器音调从而实现开关机音乐的播放
//定义低音音名 (单位:HZ)
#define L1 262
#define L2 294
#define L3 330
#define L4 349
#define L5 392
#define L6 440
#define L7 494

//定义中音音名
#define M1 523
#define M2 587
#define M3 659
#define M4 698
#define M5 784
#define M6 880
#define M7 988

//定义高音音名
#define H1 1047
#define H2 1175
#define H3 1319
#define H4 1397
#define H5 1568
#define H6 1760
#define H7 1976

//定义时值单位,决定演奏速度(数值单位:ms)
#define T 2000

typedef struct
{
        short mName;//音名:取值L1~L7、M1~M7、H1~H7分别表示低音、中音、高音的1234567,取0表示休止符
        short mTime;//时值:取值T、T/2、T/4、T/8、T/16、T/32分别表示全音符、二分音符、四音符、八音符...取0表示演奏结束
}music_data_t;

const music_data_t powerupbeepdata[]={
                {M3,T/8},{M3,T/8},{M3,T/8},{M1,T/16},{M3,T/16},{0,T/16},{M5,T/8},{0,T/16},{L5,T/8},
                {0,0}
};
const music_data_t powerdownbeepdata[]={
                {L5,T/8},{M4,T/8},{0,T/16},{M4,T/8},{M4,T/8},{M3,T/8},{M2,T/8},{M1,T/8},
                {0,0}
};

void sound(uint16_t hz)
{
        if(hz == 0)
                return;
        uint32_t p = 2000000/hz;
    GTIM_SetReloadValue(CW_GTIM4,p-1);
    GTIM_SetCompare1(CW_GTIM4,p/2);
}

void beep_music(music_data_t *md)
{
        uint8_t i=0;
        while(md[i].mTime > 0)
        {
                sound(md[i].mName);
                yuyy_delay_ms(md[i].mTime);
                GTIM_SetCompare1(CW_GTIM4,0);
                yuyy_delay_ms(15);
                i++;
        }
        GTIM_SetCompare1(CW_GTIM4,0);
}

void yuyy_powerupbeep()
{
        beep_music(powerupbeepdata);
}

void yuyy_powerdownupbeep()
{
        beep_music(powerdownbeepdata);
}
void yuyy_keybeep(uint8_t on)
{
        if(on == 0)
        {
                GTIM_SetCompare1(CW_GTIM4,0);
        }
        else
        {
                sound(M3);
        }
}
主函数中调用初始化方法,在主循环中实现开关机画面
int32_t main(void)
{
    RCC_HSE_8M_PLL64M_init();
    __RCC_GPIOA_CLK_ENABLE();
    __RCC_GPIOB_CLK_ENABLE();
    __RCC_GPIOC_CLK_ENABLE();
   
    key_init();
    uart_init();
    gtimer1_init();
    beeppwminit();
    dhtc12_init();
    hs12864g18b_init();
    GPIO_InitTypeDef gpiodef;
    gpiodef.Pins = GPIO_PIN_2;
    gpiodef.IT = GPIO_IT_NONE;
    gpiodef.Speed = GPIO_SPEED_HIGH;
    gpiodef.Mode = GPIO_MODE_OUTPUT_PP;
    gpiodef.Pins = GPIO_PIN_13;
    GPIO_Init(CW_GPIOC,&gpiodef);
    GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_SET);
    while(1)
    {
        if(poweron == 1)
        {
            yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,0,0,(uint8_t *)"  CO2 Detector");
            yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,2,0,(uint8_t *)"  CW32 Inside");
            yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,4,0,(uint8_t *)"Code by yuyy1989");
            yuyy_powerupbeep();
            poweron = 2;
            refresh_count = 0;
        }
        else if(poweron == 3)
        {
            yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,2,0,(uint8_t *)"  CW32 Inside");
            yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,4,0,(uint8_t *)"Code by yuyy1989");
            yuyy_powerdownupbeep();
            yuyy_hs12864g18b_clear_screen(&hs12864_dev);
            GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_SET);
            poweron = 0;
        }
        if(poweron > 0)
        {
            yuyy_delay_ms(500);
            GPIO_TogglePin(CW_GPIOC,GPIO_PIN_13);
        }
        else
        {
            yuyy_delay_ms(10);
        }
    }
}
最终效果


使用特权

评论回复
沙发
forgot| | 2023-8-31 08:18 | 只看该作者
不错,赞!

使用特权

评论回复
板凳
langgq| | 2023-8-31 19:48 | 只看该作者
好长的例子,今晚好好学习下

使用特权

评论回复
地板
szt1993| | 2023-12-22 14:30 | 只看该作者
学习一下这个例子很好用

使用特权

评论回复
5
中国龙芯CDX| | 2024-1-9 15:48 | 只看该作者
很好的学习资源

使用特权

评论回复
6
CW32F030小蓝板功能比较强大

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

87

主题

469

帖子

4

粉丝