【系統(tǒng)功能】
CW32l083為主控制的無線終端數(shù)據(jù)收發(fā)。運(yùn)行國產(chǎn)RT-Thread操作系統(tǒng)。主要功能為實(shí)現(xiàn)用E31-TTL-50接收各個(gè)模塊發(fā)送上來的數(shù)據(jù),解析數(shù)據(jù),分析數(shù)據(jù),顯示數(shù)據(jù),并實(shí)現(xiàn)信息的顯示,以及異常情況的顯示、警告功能。
無線終端主要以cw32L031為主控,采集sht30溫濕度傳感器數(shù)據(jù),通過E31-TTL-50無線模塊將數(shù)據(jù)上傳。實(shí)現(xiàn)5微安的待機(jī)電流的超低功耗。
【功能模塊】
主機(jī):
1、接收模塊:接收無線終端模塊發(fā)送的溫濕度數(shù)據(jù),解析數(shù)據(jù)后,更新模塊數(shù)據(jù)。
2、巡檢模塊:定時(shí)巡檢各個(gè)無線終端的數(shù)據(jù),判定工作狀況、更新顯示、報(bào)警標(biāo)志。
3、顯示模塊:根據(jù)各個(gè)模塊的工作狀態(tài),生成為示數(shù)據(jù),用TFT屏展示。
4、報(bào)警模塊:驅(qū)動(dòng)pwm模塊,裝載pwm重載值,發(fā)出警示聲音。
無線終端:
1、溫濕采集模塊:采集sht30數(shù)據(jù)。
2、發(fā)送模塊:將數(shù)據(jù)打包,通過無線發(fā)送。
3、休眠模塊:發(fā)送完數(shù)據(jù)后進(jìn)入深度休眠狀態(tài),由AWT模塊定時(shí)喚醒。
【硬件】
主機(jī):
1、CW32L083VxTx StartKit REV01開發(fā)板。
2、ST7735TFT顯示屏。
3、E31-TTL-50無線串口模塊。
無線終端:
1、cw32l031開發(fā)板
2、Sht30溫濕度傳感器。
3、E31-TTL-50無線串口模塊。
4、可充電鋰電池。
【開發(fā)環(huán)境】
1、代碼編譯環(huán)境采集ubuntu20.4;
2、代碼編輯工具為vscode 1.79.2;
3、交叉編譯器為arm-nano-eabi-gcc;
4、固件庫為cw32提供的固件庫;
5、gcc啟動(dòng)文件與鏈接由作者在cortex-M0+的其他軟件上移植過來;
6、下載器為CW32配送的wch-link;
7、代碼下載軟件為pyocd;
8、調(diào)式工具為gdb。
本次開發(fā)板的編譯環(huán)境、工具均采用開源工具。
【操作系統(tǒng)】
本工程的主控,作者移植了RTT-Thread Nano 3.15版本。RTT作為一款國產(chǎn)開源免費(fèi)的操作系統(tǒng)可以提供強(qiáng)大的功能,為CW32的性能發(fā)揮提供強(qiáng)力的支持。
【程序流程圖】
1、主機(jī)端由RTT開啟兩個(gè)主要任務(wù),用于數(shù)據(jù)顯示與巡檢,同時(shí)利用串口中斷來實(shí)時(shí)處理接收的數(shù)據(jù)。GTIM定時(shí)開啟PWM任務(wù),來驅(qū)動(dòng)開發(fā)板板載的BEEP。流程圖如下:
2、無線終端采用單線流程,主要是采集數(shù)據(jù)后進(jìn)入休眠,做到極簡才能實(shí)現(xiàn)最好的功耗控制。流程圖如下:
【原理圖】
1、無線端終采集:
2、主機(jī)端:
【程序設(shè)計(jì)】
一、無線采集端
二、主機(jī)端
1.主機(jī)端我們處理數(shù)據(jù)的核心為sht30數(shù)據(jù),聲明結(jié)構(gòu)體如下:
typedef struct _sht30_data
{
uint32_t ID;
int temp; //溫度值
int temp_upper_limit; //溫度值上限
int temp_lower_limit; //溫度值下限
uint16_t humi; //濕度
uint16_t humi_upper_limit; //濕度上限
uint16_t humi_lower_limit; //濕度上限
uint32_t time_tick; //更新數(shù)據(jù)計(jì)時(shí)
enum _sht30_errcode sht_errcode;
} SHT30_infor;
主要用于存儲數(shù)據(jù)的核心,以后所有的任務(wù)都是針對這個(gè)模塊進(jìn)行。
2.同時(shí)聲明一個(gè)枚舉,來確定測量點(diǎn)的狀態(tài):
enum _sht30_errcode{
NORMAL=0,
ABNORMAL,
OFFLINE,
};
3.先約定好默的一些參數(shù),最大傳感器個(gè)數(shù),溫濕度報(bào)警上下限,巡檢次數(shù)初值:
#define maxID 2
#define MaxTime 300
#define HUMI_LOWER 500
#define HUMI_UPPER 750
#define TMPE_LOWER 100
#define TMPE_UPPER 300
到此我們的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)完成。
4.時(shí)鐘的初始化,由于主機(jī)端需要高速處理數(shù)據(jù)這里配置為64MHz:
void RCC_cofiguration(void)
{
RCC_HSI_Enable(RCC_HSIOSC_DIV6);
// 使能PLL,通過HSI倍頻到 64MHz
RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 8); //HSI 默輸出8MHz
///< 當(dāng)使用的時(shí)鐘源HCLK大于24M,小于等于48MHz:設(shè)置FLASH 讀等待周期為2 cycle
///< 當(dāng)使用的時(shí)鐘源HCLK大于48M,小于等于72MHz:設(shè)置FLASH 讀等待周期為3 cycle
__RCC_FLASH_CLK_ENABLE();
FLASH_SetLatency(FLASH_Latency_3);
//時(shí)鐘切換到PLL
RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
RCC_SystemCoreClockUpdate(64000000);
}
5.主機(jī)端的無線接收使用了uart1,端口選擇了PE8與PE9作為TXD、RXD,初始化代碼為:
void E31_UART_Init(void)
{
uint32_t PCLK_Freq;
GPIO_InitTypeDef GPIO_InitStructure = {0};
UART_InitTypeDef UART_InitStructure = {0};
PCLK_Freq = SystemCoreClock > > pow2_table[CW_SYSCTRL- >CR0_f.HCLKPRS];
PCLK_Freq > >= pow2_table[CW_SYSCTRL- >CR0_f.PCLKPRS];
// 調(diào)試串口使用UART3
// PA8- >TX
// PA9< -RX
// 時(shí)鐘使能
RCC_AHBPeriphClk_Enable(E31_UART_GPIO_CLK, ENABLE);
E31_UART_APBClkENx(E31_UART_CLK, ENABLE);
// 先設(shè)置UART TX RX 復(fù)用,后設(shè)置GPIO的屬性,避免口線上出現(xiàn)毛刺
E31_UART_AFTX;
E31_UART_AFRX;
GPIO_InitStructure.Pins = E31_UART_TX_GPIO_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(E31_UART_TX_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pins = E31_UART_RX_GPIO_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
GPIO_Init(E31_UART_RX_GPIO_PORT, &GPIO_InitStructure);
UART_InitStructure.UART_BaudRate = E31_UART_BaudRate;
UART_InitStructure.UART_Over = UART_Over_16;
UART_InitStructure.UART_Source = UART_Source_PCLK;
UART_InitStructure.UART_UclkFreq = PCLK_Freq;
UART_InitStructure.UART_StartBit = UART_StartBit_FE;
UART_InitStructure.UART_StopBits = UART_StopBits_1;
UART_InitStructure.UART_Parity = UART_Parity_No ;
UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(E31_UARTx, &UART_InitStructure);
//優(yōu)先級,無優(yōu)先級分組
NVIC_SetPriority(E31_UART_IRQ, 0);
//UARTx中斷使能
NVIC_EnableIRQ(E31_UART_IRQ);
}
同時(shí)配置中斷函數(shù),主要功能是判斷是否接到了幀屬,如果接收到幀尾則把數(shù)據(jù)交給回調(diào)函數(shù)進(jìn)行處理,代碼如下:
void UART1_UART4_IRQHandler(void)
{
/* USER CODE BEGIN */
uint8_t TxRxBuffer;
if(UART_GetITStatus(CW_UART1, UART_IT_RC) != RESET)
{
TxRxBuffer = UART_ReceiveData_8bit(CW_UART1);
if(e31_rx_cnt < E31_RX_MAXLEN)
{
if ((TxRxBuffer == 0x0A) && (e31_rx_state == 1))
{
e31_rx_state = 2;
e31_exp_data();
}
else if ((TxRxBuffer == 0x0D) && (e31_rx_state == 0))
{
e31_rx_state = 1;
}
else if (e31_rx_state == 0)
{
e31_rx_buff[e31_rx_cnt] = TxRxBuffer;
e31_rx_cnt ++;
}
}
else
{
e31_rx_cnt = 0;
e31_rx_state = 0;
}
UART_ClearITPendingBit(CW_UART1, UART_IT_RC);
}
/* USER CODE END */
}
同時(shí)回調(diào)函數(shù),為處理與解析數(shù)據(jù)更新到sht30數(shù)據(jù)之中:
void e31_exp_data(void)
{
int temp;
uint16_t humi;
uint32_t ID;
if(e31_rx_state == 2)
{
if(e31_rx_cnt == 14)
{
temp = e31_rx_buff[10]< 8 | e31_rx_buff[11];
humi = e31_rx_buff[12]< 8 | e31_rx_buff[14];
ID = e31_rx_buff[6]< 24 | e31_rx_buff[7]< 16 | e31_rx_buff[8]< 8 | e31_rx_buff[9];
updata_sht30(temp, humi, ID);
rt_kprintf("ID:%X, temp:%d, humi:%d\\r\\n", ID, temp, humi);
}
}
e31_rx_cnt = 0;
e31_rx_state = 0;
}
6.ST7735的驅(qū)動(dòng),驅(qū)動(dòng)采集模擬SPI進(jìn)行驅(qū)動(dòng),詳細(xì)的驅(qū)動(dòng)見工程源碼包。
7.PWM驅(qū)動(dòng),pwm選用PA6為pwm輸出端,初始化為1KHz的輸出來驅(qū)動(dòng)板載的蜂鳴器。在初始化驅(qū)動(dòng)后,我們裝載最大的裝截時(shí),占空比為100%,使得蜂鳴器停止,在后面的需要輸入報(bào)警聲后,調(diào)整為50%的占空比,來實(shí)現(xiàn)蜂鳴器的報(bào)警聲:
void init_beep(void)
{
GTIM_InitTypeDef GTIM_InitStruct = {0};
__RCC_GTIM1_CLK_ENABLE(); // GTIM2時(shí)鐘使能
/* PA6 PWM 輸出 */
__RCC_GPIOA_CLK_ENABLE();
PA06_AFx_GTIM1CH1();
PA06_DIR_OUTPUT();
PA06_DIGTAL_ENABLE();
GTIM_InitStruct.Mode = GTIM_MODE_TIME;
GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE;
GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV2;
// GTIM_InitStruct.ReloadValue = 60100UL - 1; // PWM頻率為 48M/60100=800Hz, SPWM周期 = 800/2/1000= 0.4Hz
GTIM_InitStruct.ReloadValue = 32000UL - 1; // PWM頻率為 64M/2/64000=1000Hz, SPWM周期 = 800/2/1000= 0.4Hz
GTIM_InitStruct.ToggleOutState = DISABLE;
GTIM_TimeBaseInit(CW_GTIM1, >IM_InitStruct);
GTIM_OCInit(CW_GTIM1, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_HIGH);
GTIM_SetCompare1(CW_GTIM1, 32000-1);
GTIM_Cmd(CW_GTIM1, ENABLE);
}
void alarm_ON(void)
{
GTIM_SetCompare1(CW_GTIM1, 16000-1);;
}
void alarm_OFF(void)
{
GTIM_SetCompare1(CW_GTIM1, 32000-1);
}
8.按照程序流程圖,我們創(chuàng)建了兩個(gè)任務(wù),一個(gè)為巡檢任務(wù)來實(shí)現(xiàn)對傳感器模塊的數(shù)據(jù)監(jiān)控,并實(shí)理更新工作狀態(tài),代碼如下:
/* 巡檢任務(wù) */
void thread_sht30_check_entry(void *parameter)
{
int i;
uint8_t alarm_sta;
while(1)
{ alarm_sta = 0;
for(i=0; i< maxID; i++)
{
if(sht30[i].time_tick == 0)
{
//發(fā)送離線的警告
sht30[i].sht_errcode = OFFLINE;
sht30[i].temp = 0;
sht30[i].humi = 0;
alarm_sta ++;
}
else if (sht30[i].temp < sht30[i].temp_lower_limit \\
|| sht30[i].temp > sht30[i].temp_upper_limit \\
|| sht30[i].humi < sht30[i].humi_lower_limit \\
|| sht30[i].humi > sht30[i].humi_upper_limit )
{
sht30[i].sht_errcode = ABNORMAL;
sht30[i].time_tick--;
alarm_sta++;
}
else
{
sht30[i].sht_errcode = NORMAL;
sht30[i].time_tick--;
}
}
if(alarm_sta > 0)
{
alarm_ON();
}
else
{
alarm_OFF();
}
rt_thread_mdelay(500);
}
}
/* 巡檢任務(wù) */
void sht30_check(void)
{
rt_thread_init(&tid_check_sht30,
"sht30_check",
thread_sht30_check_entry,
RT_NULL,
&thread_sht30_check_stack[0],
sizeof(thread_sht30_check_stack),
THREAD_PRIORITY - 1, THREAD_TIMESLICE);
rt_thread_startup(&tid_check_sht30);
}
9.顯示任務(wù),為定時(shí)按照傳感器的工作狀態(tài)來實(shí)現(xiàn)數(shù)據(jù)的展示,主要是根據(jù)三個(gè)狀態(tài)、以及溫濕度是否超過或者低于限值來顯示不同的顏色,代碼如下:
/* 線程 顯示 的入口函數(shù) */
static void thread_lcd_entry(void *parameter)
{
sht30_data_Init();
char buff_temp[15];
char buff_humi[15];
uint16_t temp_background_color, temp_font_color;
uint16_t humi_background_color, humi_font_color;
int y_offset = 0;
int i = 0;
while (1)
{
y_offset = 46;
for(i=0;i< maxID;i++)
{
rt_kprintf("sensorID:%d stata: %d", i+1, sht30[i].sht_errcode);
y_offset = y_offset + i*70;
sprintf(buff_temp,"%d%d.%d",sht30[i].temp/100, sht30[i].temp/10%10, sht30[i].temp%10);
sprintf(buff_humi,"%d%d.%d",sht30[i].humi/100, sht30[i].humi/10%10, sht30[i].humi%10);
switch (sht30[i].sht_errcode)
{
case NORMAL:
temp_background_color = GRAY0;
temp_font_color = BLUE;
humi_background_color = GRAY0;
humi_font_color = BLUE;
break;
case OFFLINE:
temp_background_color = GRAY2;
temp_font_color = BLUE;
humi_background_color = GRAY2;
humi_font_color = BLUE;
sprintf(buff_temp, " ");
sprintf(buff_humi, " ");
break;
case ABNORMAL:
if(sht30[i].humi< sht30[i].humi_lower_limit || sht30[i].humi > sht30[i].humi_upper_limit)
{
humi_background_color = YELLOW;
humi_font_color = BLACK;
}
else
{
humi_background_color = GRAY0;
humi_font_color = BLUE;
}
if(sht30[i].temp< sht30[i].temp_lower_limit || sht30[i].temp > sht30[i].temp_upper_limit)
{
temp_background_color = YELLOW;
temp_font_color = BLACK;
}
else
{
temp_background_color = GRAY0;
temp_font_color = BLUE;
}
break;
default:
break;
}
Gui_DrawFont_GBK16(90,y_offset,temp_font_color,temp_background_color,buff_temp); //更新顯示
Gui_DrawFont_GBK16(90,y_offset+20,humi_font_color,humi_background_color,buff_humi);
}
rt_thread_mdelay(10000);
}