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

[复制链接]
1183|5
 楼主| yuyy1989 发表于 2023-8-30 15:52 | 显示全部楼层 |阅读模式
本帖最后由 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状态来实现长按和单击的判断
QQ截图20230830151407.png
按键IO初始化为输入,不开中断,小篮板上的下拉电阻是没有焊的这里开启内部下拉

  1. uint8_t poweron = 1;
  2. uint16_t refresh_count = 0;
  3. uint8_t keysoundcount = 0;
  4. uint16_t key_downcount = 0;
  5. void key_init()
  6. {
  7.     GPIO_InitTypeDef gpiodef;
  8.     gpiodef.Pins = GPIO_PIN_2;
  9.     gpiodef.Mode = GPIO_MODE_INPUT_PULLDOWN;
  10.     gpiodef.IT = GPIO_IT_NONE;
  11.     GPIO_Init(CW_GPIOB,&gpiodef);
  12. }
定时器初始化,开启溢出中断,实现一个1ms的定时器
  1. void gtimer1_init()
  2. {
  3.     GTIM_InitTypeDef GTIM_InitStruct = {0};
  4.     __RCC_GTIM1_CLK_ENABLE();
  5.     GTIM_InitStruct.Mode = GTIM_MODE_TIME;
  6.     GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV64;
  7.     GTIM_InitStruct.ReloadValue = 1000-1;
  8.     GTIM_TimeBaseInit(CW_GTIM1, >IM_InitStruct);
  9.     GTIM_ITConfig(CW_GTIM1, GTIM_IT_OV, ENABLE);
  10.     NVIC_EnableIRQ(GTIM1_IRQn);
  11.     GTIM_Cmd(CW_GTIM1, ENABLE);
  12. }
在定时器中断中实现按键检测、定时刷新数据和串口空闲判断等功能,默认每5秒刷新一次二氧化碳和温湿度数据,检测到单击动作后立即刷新
  1. void poweronoff()
  2. {
  3.     if(poweron == 0)
  4.     {
  5.         poweron = 1;
  6.     }
  7.     else
  8.     {
  9.         poweron = 3;
  10.     }
  11. }
  12. void GTIM1_IRQHandler(void)
  13. {
  14.     if (GTIM_GetITStatus(CW_GTIM1, GTIM_IT_OV))
  15.     {
  16.         GTIM_ClearITPendingBit(CW_GTIM1, GTIM_IT_OV);
  17.         if(uart_rxtime_count > 0)
  18.         {
  19.             uart_rxtime_count -= 1;
  20.             if(uart_rxtime_count == 0)
  21.                 show_co2();
  22.         }
  23.         if(keysoundcount > 0)
  24.         {
  25.             keysoundcount -= 1;
  26.             if(keysoundcount == 0)
  27.             {
  28.                 yuyy_keybeep(0);
  29.             }
  30.         }
  31.         if(refresh_count > 0)
  32.             refresh_count -= 1;
  33.         else
  34.         {
  35.             refresh_data();
  36.             refresh_count = 4999;
  37.         }
  38.         if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_2)==GPIO_Pin_SET)
  39.         {
  40.             if(key_downcount < 0xFFFF)
  41.                 key_downcount += 1;
  42.             if(key_downcount == 2000)
  43.             {
  44.                 poweronoff();
  45.             }
  46.         }
  47.         else
  48.         {
  49.             if(poweron == 2 && key_downcount > 10 && key_downcount < 1000)
  50.             {
  51.                 refresh_count = 0;
  52.                 yuyy_keybeep(1);
  53.                 keysoundcount = 100;
  54.             }
  55.             key_downcount = 0;
  56.         }
  57.     }
  58. }
LCD12864初始化
LCD12864使用SPI进行通讯,5个IO都是输出模式
QQ截图20230830151603.png
cs rst a0使用软件控制,sck mosi复用为SPI,这里只有mcu到lcd的单向通讯所以SPI配制成只发送模式就行了
  1. YUYY_HS12864G18B_DEV_t hs12864_dev;
  2. void hs12864g18b_init(void)
  3. {
  4.     GPIO_InitTypeDef gpiodef;
  5.     hs12864_dev.cs.gpio = CW_GPIOA;
  6.     hs12864_dev.cs.pin = GPIO_PIN_4;
  7.     hs12864_dev.rst.gpio = CW_GPIOA;
  8.     hs12864_dev.rst.pin = GPIO_PIN_3;
  9.     hs12864_dev.a0.gpio = CW_GPIOA;
  10.     hs12864_dev.a0.pin = GPIO_PIN_2;
  11.     hs12864_dev.sck.gpio = CW_GPIOA;
  12.     hs12864_dev.sck.pin = GPIO_PIN_5;
  13.     hs12864_dev.mo.gpio = CW_GPIOA;
  14.     hs12864_dev.mo.pin = GPIO_PIN_7;
  15.    
  16.     RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA, ENABLE);
  17.     gpiodef.Pins = GPIO_PIN_5|GPIO_PIN_7|GPIO_PIN_4|GPIO_PIN_3|GPIO_PIN_2;
  18.     gpiodef.Mode = GPIO_MODE_OUTPUT_PP;
  19.     gpiodef.IT = GPIO_IT_NONE;
  20.     gpiodef.Speed = GPIO_SPEED_HIGH;
  21.     GPIO_Init(CW_GPIOA,&gpiodef);
  22.    
  23.     RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_SPI1, ENABLE);
  24.     PA05_AFx_SPI1SCK();
  25.     PA07_AFx_SPI1MOSI();
  26.     SPI_InitTypeDef spidef;
  27.     spidef.SPI_Direction = SPI_Direction_1Line_TxOnly;
  28.     spidef.SPI_Mode = SPI_Mode_Master;
  29.     spidef.SPI_DataSize = SPI_DataSize_8b;
  30.     spidef.SPI_CPOL = SPI_CPOL_High;
  31.     spidef.SPI_CPHA = SPI_CPHA_2Edge;
  32.     spidef.SPI_NSS = SPI_NSS_Soft;
  33.     spidef.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
  34.     spidef.SPI_FirstBit = SPI_FirstBit_MSB;
  35.     spidef.SPI_Speed = SPI_Speed_High;
  36.     SPI_Init(CW_SPI1,&spidef);
  37.     SPI_Cmd(CW_SPI1, ENABLE);
  38.    
  39.     hs12864_dev.spix = CW_SPI1;
  40.     yuyy_hs12864g18b_init(&hs12864_dev);
  41.     yuyy_hs12864g18b_clear_screen(&hs12864_dev);
  42. }
DHTC12初始化
DHTC12使用I2C通讯,DHTC12最高支持100K的通讯速率,注意SCL和SDA要接上拉电阻

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

  7.     __RCC_GPIOB_CLK_ENABLE();
  8.     PB10_AFx_I2C1SCL();
  9.     PB11_AFx_I2C1SDA();
  10.    
  11.     GPIO_InitStructure.Pins = GPIO_PIN_10|GPIO_PIN_11;
  12.     GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; //
  13.     GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  14.     GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
  15.    
  16.     __RCC_I2C1_CLK_ENABLE();
  17.     I2C_InitStruct.I2C_BaudEn = ENABLE;
  18.     I2C_InitStruct.I2C_Baud = 79;  //100K=(64000000/(8*(1+79))
  19.     I2C_InitStruct.I2C_FLT = ENABLE;
  20.     I2C_InitStruct.I2C_AA = DISABLE;
  21.     I2C1_DeInit();
  22.     I2C_Master_Init(CW_I2C1,&I2C_InitStruct);//初始化模块
  23.     I2C_Cmd(CW_I2C1,ENABLE);  //模块使能
  24.    
  25.     iic_dev.iictype = YUYY_IIC_TYPE_HARD;
  26.     iic_dev.hiicx = CW_I2C1;
  27.     dhtc12_dev.iicx = &iic_dev;
  28.     yuyy_dhtc12_init(&dhtc12_dev);
  29. }
串口初始化

S8的数据通讯格式如图
QQ截图20230830104757.png
S8的串口波特率是9600,初始化串口3并开启中断,
  1. #define UART_BUFFER_LEN 10
  2. uint8_t uart_rxbuffer[UART_BUFFER_LEN] = {0};
  3. uint8_t readco2_cmd[] = { 0xFE, 0x04, 0x00, 0x03, 0x00, 0x01, 0xD5, 0xC5};
  4. uint8_t uart_rxlen = 0;
  5. uint8_t uart_rxtime_count = 0;
  6. uint8_t uart_txlen = 0;
  7. uint8_t uart_txindex = 0;
  8. void uart_init(void)
  9. {
  10.     //UYSART3 TX PA9 RX PA10
  11.     RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA,ENABLE);
  12.     RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_UART3, ENABLE);
  13.    
  14.     GPIO_InitTypeDef GPIO_InitStructure;

  15.     //UART TX RX 复用
  16.     PA09_AFx_UART3TXD();
  17.     PA10_AFx_UART3RXD();

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

  22.     GPIO_InitStructure.Pins = GPIO_PIN_10;
  23.     GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
  24.     GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
  25.    
  26.     USART_InitTypeDef USART_InitStructure;

  27.     USART_InitStructure.USART_BaudRate = 9600;
  28.     USART_InitStructure.USART_Over = USART_Over_16;
  29.     USART_InitStructure.USART_Source = USART_Source_PCLK;
  30.     USART_InitStructure.USART_UclkFreq = 64000000;
  31.     USART_InitStructure.USART_StartBit = USART_StartBit_FE;
  32.     USART_InitStructure.USART_StopBits = USART_StopBits_1;
  33.     USART_InitStructure.USART_Parity = USART_Parity_No ;
  34.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  35.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  36.     USART_Init(CW_UART3, &USART_InitStructure);
  37.    
  38.     //优先级,无优先级分组
  39.     NVIC_SetPriority(UART3_IRQn, 0);
  40.     //UARTx中断使能
  41.     NVIC_EnableIRQ(UART3_IRQn);
  42.    
  43.     USART_ITConfig(CW_UART3, USART_IT_RC, ENABLE);
  44. }
在串口中断函数中处理接收和发送数据,每次接收数据重置接收时间计数,当在定时中断中计数减为0时判断为数据已接收完成,进入数据显示方法
  1. void UART3_IRQHandler(void)
  2. {
  3.     if(USART_GetITStatus(CW_UART3, USART_IT_RC) != RESET)
  4.     {
  5.         uart_rxtime_count = 5;
  6.         uart_rxbuffer[uart_rxlen++] = USART_ReceiveData_8bit(CW_UART3);
  7.         USART_ClearITPendingBit(CW_UART3, USART_IT_RC);
  8.     }
  9.     if(USART_GetITStatus(CW_UART3, USART_IT_TXE) != RESET)
  10.     {
  11.         if(uart_txlen > 0)
  12.         {
  13.             USART_SendData_8bit(CW_UART3, readco2_cmd[uart_txindex++]);
  14.             uart_txlen--;
  15.         }
  16.         else
  17.         {
  18.             USART_ITConfig(CW_UART3, USART_IT_TXE, DISABLE);
  19.         }
  20.         USART_ClearITPendingBit(CW_UART3, USART_IT_TXE);
  21.     }
  22. }
S8的查询命令,开启发送空中断开始发送查询命令
  1. void uart_read_co2cmd()
  2. {
  3.     uart_txlen = 8;
  4.     uart_txindex = 0;
  5.     uart_rxlen = 0;
  6.     USART_ITConfig(CW_UART3, USART_IT_TXE, ENABLE);
  7. }
二氧化碳浓度数据的展示方法
  1. void show_co2()
  2. {
  3.     uint16_t co2ppm = 0;
  4.     char out[20];
  5.     uint8_t outlen;
  6.     if(uart_rxlen > 0 && uart_rxbuffer[0] == 0xFE && uart_rxbuffer[1] == 0x04 && uart_rxbuffer[2] == 0x02)
  7.     {
  8.         co2ppm = (uart_rxbuffer[3]<<8)+uart_rxbuffer[4];
  9.         outlen = sprintf(out,"CO2 %d ppm",co2ppm);
  10.         yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,2,0,(uint8_t *)out);
  11.         yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,2,8*outlen,(uint8_t *)"      ");
  12.     }
  13. }
蜂鸣器功能
这里使用的是无源蜂鸣器,与有源蜂鸣器不同内部没有振荡源,需要输出波形才能发声,通过改变输出波形的频率可以让蜂鸣器发出不同的声调
PB09是Gtimer4的输出io
QQ截图20230830151733.png
初始化GTimer4输出PWM
  1. void beeppwminit()
  2. {
  3.     GTIM_InitTypeDef GTIM_InitStruct = {0};
  4.     GPIO_InitTypeDef gpiodef;
  5.     __RCC_GPIOC_CLK_ENABLE();
  6.     gpiodef.Pins = GPIO_PIN_9;
  7.     gpiodef.IT = GPIO_IT_NONE;
  8.     gpiodef.Mode = GPIO_MODE_OUTPUT_PP;
  9.     GPIO_Init(CW_GPIOB,&gpiodef);
  10.     PB09_AFx_GTIM4CH1();
  11.    
  12.     __RCC_GTIM4_CLK_ENABLE();
  13.     GTIM_InitStruct.Mode = GTIM_MODE_TIME;
  14.     GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV32;
  15.     GTIM_InitStruct.ReloadValue = 1000-1;
  16.     GTIM_TimeBaseInit(CW_GTIM4, >IM_InitStruct);
  17.     GTIM_OCInit(CW_GTIM4, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_HIGH);
  18.     GTIM_SetCompare1(CW_GTIM4, 0);
  19.     GTIM_Cmd(CW_GTIM4, ENABLE);
  20. }
利用无源蜂鸣器实现播放开关机音乐和按键声音,通过改变输出的PWM频率就能改变蜂鸣器音调从而实现开关机音乐的播放
  1. //定义低音音名 (单位:HZ)
  2. #define L1 262
  3. #define L2 294
  4. #define L3 330
  5. #define L4 349
  6. #define L5 392
  7. #define L6 440
  8. #define L7 494

  9. //定义中音音名
  10. #define M1 523
  11. #define M2 587
  12. #define M3 659
  13. #define M4 698
  14. #define M5 784
  15. #define M6 880
  16. #define M7 988

  17. //定义高音音名
  18. #define H1 1047
  19. #define H2 1175
  20. #define H3 1319
  21. #define H4 1397
  22. #define H5 1568
  23. #define H6 1760
  24. #define H7 1976

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

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

  32. const music_data_t powerupbeepdata[]={
  33.                 {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},
  34.                 {0,0}
  35. };
  36. const music_data_t powerdownbeepdata[]={
  37.                 {L5,T/8},{M4,T/8},{0,T/16},{M4,T/8},{M4,T/8},{M3,T/8},{M2,T/8},{M1,T/8},
  38.                 {0,0}
  39. };

  40. void sound(uint16_t hz)
  41. {
  42.         if(hz == 0)
  43.                 return;
  44.         uint32_t p = 2000000/hz;
  45.     GTIM_SetReloadValue(CW_GTIM4,p-1);
  46.     GTIM_SetCompare1(CW_GTIM4,p/2);
  47. }

  48. void beep_music(music_data_t *md)
  49. {
  50.         uint8_t i=0;
  51.         while(md[i].mTime > 0)
  52.         {
  53.                 sound(md[i].mName);
  54.                 yuyy_delay_ms(md[i].mTime);
  55.                 GTIM_SetCompare1(CW_GTIM4,0);
  56.                 yuyy_delay_ms(15);
  57.                 i++;
  58.         }
  59.         GTIM_SetCompare1(CW_GTIM4,0);
  60. }

  61. void yuyy_powerupbeep()
  62. {
  63.         beep_music(powerupbeepdata);
  64. }

  65. void yuyy_powerdownupbeep()
  66. {
  67.         beep_music(powerdownbeepdata);
  68. }
  69. void yuyy_keybeep(uint8_t on)
  70. {
  71.         if(on == 0)
  72.         {
  73.                 GTIM_SetCompare1(CW_GTIM4,0);
  74.         }
  75.         else
  76.         {
  77.                 sound(M3);
  78.         }
  79. }
主函数中调用初始化方法,在主循环中实现开关机画面
  1. int32_t main(void)
  2. {
  3.     RCC_HSE_8M_PLL64M_init();
  4.     __RCC_GPIOA_CLK_ENABLE();
  5.     __RCC_GPIOB_CLK_ENABLE();
  6.     __RCC_GPIOC_CLK_ENABLE();
  7.    
  8.     key_init();
  9.     uart_init();
  10.     gtimer1_init();
  11.     beeppwminit();
  12.     dhtc12_init();
  13.     hs12864g18b_init();
  14.     GPIO_InitTypeDef gpiodef;
  15.     gpiodef.Pins = GPIO_PIN_2;
  16.     gpiodef.IT = GPIO_IT_NONE;
  17.     gpiodef.Speed = GPIO_SPEED_HIGH;
  18.     gpiodef.Mode = GPIO_MODE_OUTPUT_PP;
  19.     gpiodef.Pins = GPIO_PIN_13;
  20.     GPIO_Init(CW_GPIOC,&gpiodef);
  21.     GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_SET);
  22.     while(1)
  23.     {
  24.         if(poweron == 1)
  25.         {
  26.             yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,0,0,(uint8_t *)"  CO2 Detector");
  27.             yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,2,0,(uint8_t *)"  CW32 Inside");
  28.             yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,4,0,(uint8_t *)"Code by yuyy1989");
  29.             yuyy_powerupbeep();
  30.             poweron = 2;
  31.             refresh_count = 0;
  32.         }
  33.         else if(poweron == 3)
  34.         {
  35.             yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,2,0,(uint8_t *)"  CW32 Inside");
  36.             yuyy_hs12864g18b_display_string_8x16(&hs12864_dev,0,4,0,(uint8_t *)"Code by yuyy1989");
  37.             yuyy_powerdownupbeep();
  38.             yuyy_hs12864g18b_clear_screen(&hs12864_dev);
  39.             GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_SET);
  40.             poweron = 0;
  41.         }
  42.         if(poweron > 0)
  43.         {
  44.             yuyy_delay_ms(500);
  45.             GPIO_TogglePin(CW_GPIOC,GPIO_PIN_13);
  46.         }
  47.         else
  48.         {
  49.             yuyy_delay_ms(10);
  50.         }
  51.     }
  52. }
最终效果


forgot 发表于 2023-8-31 08:18 | 显示全部楼层
不错,赞!
langgq 发表于 2023-8-31 19:48 | 显示全部楼层
好长的例子,今晚好好学习下
szt1993 发表于 2023-12-22 14:30 | 显示全部楼层
学习一下这个例子很好用
中国龙芯CDX 发表于 2024-1-9 15:48 | 显示全部楼层
很好的学习资源
小夏天的大西瓜 发表于 2024-1-9 17:38 | 显示全部楼层
CW32F030小蓝板功能比较强大
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:同飞软件研发工程师
简介:制冷系统单片机软件开发,使用PID控制温度

168

主题

826

帖子

10

粉丝
快速回复 在线客服 返回列表 返回顶部