# test_pen **Repository Path**: sunjode/test_pen ## Basic Information - **Project Name**: test_pen - **Description**: CW32多功能测试笔软件 - **Primary Language**: C - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 39 - **Created**: 2023-10-20 - **Last Updated**: 2024-12-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 多功能测试笔 ## 1.文件构成 GUI --- LCD驱动和UI FreeRtos --- RTOS系统 USER --- 底层驱动和app ![文件构成](H:\open_project\Multifunction_test_pen\Doc\.png\文件构成.bmp) ## 2.GPIO初始化 ### 2.1开机电源 ```c #define PW_PORT CW_GPIOF #define PW_PIN GPIO_PIN_7 ``` ### 2.2LED ```c #define LED_PORT CW_GPIOA #define LED_GREEN_PIN GPIO_PIN_10 #define LED_RED_PIN GPIO_PIN_11 #define LED_LIGHT_PIN GPIO_PIN_15 ``` ### 2.3按键 ```c #define KEY_PORT CW_GPIOB #define KEY1_PIN GPIO_PIN_3 #define KEY2_PIN GPIO_PIN_9 #define KEY3_PIN GPIO_PIN_7 #define KEY4_PIN GPIO_PIN_5 #define KEY5_PIN GPIO_PIN_6 ``` ### 2.4模拟开关 ```c #define ASW_PORT CW_GPIOB #define ASW1_PIN GPIO_PIN_10 #define ASW2_PIN GPIO_PIN_11 #define ASW3_PIN GPIO_PIN_2 #define ASW4_PIN GPIO_PIN_0 ``` ### 2.5电流源开关 ```c #define CS_CT_PORT CW_GPIOB #define CS_CT_PIN GPIO_PIN_1 ``` ### 2.6初始化默认状态 关闭电流源开关; 绿色led点亮,开机指示; 红色led熄灭; 照明led熄灭; 开机情况下,模拟电子开关全低; ```c if (GPIO_ReadPin(KEY_PORT, KEY1_PIN) == GPIO_Pin_RESET) { GPIO_WritePin(PW_PORT, PW_PIN, GPIO_Pin_SET); // 开机 GPIO_WritePin(CS_CT_PORT, CS_CT_PIN, GPIO_Pin_RESET); // cs_ct GPIO_WritePin(LED_PORT, LED_RED_PIN, GPIO_Pin_SET); GPIO_WritePin(LED_PORT, LED_LIGHT_PIN, GPIO_Pin_RESET); // 关闭照明灯 ASW1_LOW(); ASW2_LOW(); ASW3_LOW(); ASW4_LOW(); GPIO_WritePin(LED_PORT, LED_GREEN_PIN, GPIO_Pin_RESET); } ``` ## 3.ADC初始化 ADC使用到两个通道别测量电池电压和测试笔尖电压。 ADC配置为连续采样,采用定时器触发dma传输ADC采集数据。 ### 3.1ADC配置 ADC通道0采集的是笔尖电压,ADC通道1采集的是电池电压。 ```c ADC_InitTypeDef ADC_InitStructure = {0}; ADC_SerialChTypeDef ADC_SerialChStructure = {0}; __RCC_ADC_CLK_ENABLE(); ADC_InitStructure.ADC_AccEn = ADC_AccDisable; // ADC 累加功能不开启 ADC_InitStructure.ADC_Align = ADC_AlignRight; // 采样结果右对齐,即结果存于 bit11~bit0 ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div32; // ADC 的 采 样 时 钟 为 PCLK 的 32 分 频, 即ADCCLK=2MHz ADC_InitStructure.ADC_DMAEn = ADC_DmaEnable; // ADC 转换完成触发 DMA ADC_InitStructure.ADC_InBufEn = ADC_BufDisable; // 高速采样,ADC 内部电压跟随器不使能 ADC_InitStructure.ADC_OpMode = ADC_SingleChOneMode; // 单次单通道采样模式 ADC_InitStructure.ADC_SampleTime = ADC_SampTime10Clk; // 设置为 10 个采样周期,须根据实际况调整 ADC_InitStructure.ADC_TsEn = ADC_TsDisable; // 内部温度传感器禁止 ADC_InitStructure.ADC_VrefSel = ADC_Vref_BGR2p5; // 采样参考电压选择为 2.5v ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh0; ADC_SerialChStructure.ADC_Sqr1Chmux = ADC_SqrCh1; ADC_SerialChStructure.ADC_SqrEns = ADC_SqrEns01; ADC_SerialChStructure.ADC_InitStruct = ADC_InitStructure; /* 序列通道连续采样模式 */ ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure); adc_dma_config(); ADC_Enable();// 启用 ADC ADC_SoftwareStartConvCmd(ENABLE); ``` ### 3.2DMA配置 ADC_ResultBuff数组存储笔尖电压数据。 BAT_ADC_ResultBuff数组存储电池电压数据。 ```c DMA_InitTypeDef DMA_InitStruct = {0}; __RCC_DMA_CLK_ENABLE(); DMA_InitStruct.DMA_DstAddress = (uint32_t)&ADC_ResultBuff; // 目标地址 DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase; // 目标地址递增 DMA_InitStruct.DMA_Mode = DMA_MODE_BULK; // BULK 传输模式 DMA_InitStruct.DMA_SrcAddress = (uint32_t)&CW_ADC->RESULT0; // 源地址: ADC 的结果寄存器 DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix; // 源地址固定 DMA_InitStruct.DMA_TransferCnt = 0x6; // DMA 传输次数 DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_16BIT; // 数据位宽 16bit DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE; // ADC 转换完成硬触发 DMA_InitStruct.TrigMode = DMA_HardTrig; // 硬触发模式 DMA_Init(CW_DMACHANNEL1, &DMA_InitStruct); DMA_Cmd(CW_DMACHANNEL1, ENABLE); DMA_InitStruct.DMA_DstAddress = (uint32_t)BAT_ADC_ResultBuff; // 目标地址 DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase; // 目标地址递增 DMA_InitStruct.DMA_Mode = DMA_MODE_BULK; // BULK 传输模式 DMA_InitStruct.DMA_SrcAddress = (uint32_t)&CW_ADC->RESULT1; // 源地址 DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix; // 源地址固定 DMA_InitStruct.DMA_TransferCnt = 0x6; // DMA 传输次数 DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_16BIT; // 数据位宽 16bit DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE; // ADC 转换完成硬触发 DMA_InitStruct.TrigMode = DMA_HardTrig; // 硬触发模式 DMA_Init(CW_DMACHANNEL2, &DMA_InitStruct); DMA_Cmd(CW_DMACHANNEL2, ENABLE); ``` ### 3.3DMA连续采集 由于CW32没有自动重载DMA模式,需要手动检测DMA传输完成,然后重新配置DMA。 ``` void wait_dma_complete(void) { // 等待DMA传输完成 if (DMA_GetITStatus(DMA_IT_TC1) == SET) { DMA_ClearITPendingBit(DMA_IT_TC1|DMA_IT_TC2); adc_dma_config(); } } ``` ### 3.4笔尖电压计算 1. 将笔尖采集的ADC值去掉最大最小值,计算出均值。 2. 根据ADC分辨率和ADC基准电压计算出电压值 3. 计算电压值进行滑动平均算法 4. 滑动平均电压*档位系数 = 最终电压 ``` uint16_t pen_volt(void) { uint32_t sum = 0; uint16_t val; uint32_t len = sizeof(ADC_ResultBuff) / 2; uint16_t max = 0; uint16_t min = 0xffff; int i; for ( i = 0; i < len; i++) { sum += ADC_ResultBuff[i]; if(ADC_ResultBuff[i] > max) { max = ADC_ResultBuff[i]; } if(ADC_ResultBuff[i] < min) { min = ADC_ResultBuff[i]; } } sum -= max + min; sum = sum / (len - 2); val = sum * 2500 / 4095; val = dynamic_mean(pen_cahe, val, &len_cahe[1]); return val; } ``` ### 3.5电池电量测量 BAT_ADC_ResultBuff数组存储的是电池采集的ADC值。 1.计算BAT_ADC_ResultBuff数组的平均值,去掉最大最小值,防止数据突变。 2.将计算的平均值 ```c uint16_t get_bat_val(void) { uint32_t sum = 0; uint16_t bat_val; uint32_t len = sizeof(BAT_ADC_ResultBuff) / 2; uint16_t max = 0; uint16_t min = 0xffff; int i; for ( i = 0; i < len; i++) { sum += BAT_ADC_ResultBuff[i]; if(BAT_ADC_ResultBuff[i] > max) { max = BAT_ADC_ResultBuff[i]; } if(BAT_ADC_ResultBuff[i] < min) { min = BAT_ADC_ResultBuff[i]; } } sum -= max + min; sum = sum / (len - 2); sum = dynamic_mean(bat_cahe,sum,len_cahe); // sum -= 30; bat_val = sum * 2500 * 2 /4095; return bat_val; } ``` ## 4.pwm初始化 GTIM3_CH4连接的是无源蜂鸣器,蜂鸣器的额定频率为4k,GTIM3_CH4配置为4K,真占空比50%即可。 GTIM1_CH1是控制PWM输出和DC输出,默认配置为20K,占空比90%; ``` GTIM_InitTypeDef GTIM_InitStruct; pwm_gpio_init(); __RCC_GTIM1_CLK_ENABLE(); GTIM_InitStruct.Mode = GTIM_MODE_TIME; GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE; GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV16; // DCLK = PCLK / 16 = 64MHz/16 = 4MHz GTIM_InitStruct.ReloadValue = Period - 1; GTIM_InitStruct.ToggleOutState = DISABLE; GTIM_TimeBaseInit(CW_GTIM1, >IM_InitStruct); GTIM_OCInit(CW_GTIM1, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_HIGH); GTIM_SetCompare3(CW_GTIM1, PosWidth); GTIM_ITConfig(CW_GTIM1, GTIM_IT_OV, ENABLE); GTIM_Cmd(CW_GTIM1, ENABLE); __RCC_GTIM3_CLK_ENABLE(); GTIM_InitStruct.Mode = GTIM_MODE_TIME; GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE; GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV16; // DCLK = PCLK / 16 = 64MHz/16 = 4MHz GTIM_InitStruct.ReloadValue = fm_Period - 1; GTIM_InitStruct.ToggleOutState = DISABLE; GTIM_TimeBaseInit(CW_GTIM3, >IM_InitStruct); GTIM_OCInit(CW_GTIM3, GTIM_CHANNEL4, GTIM_OC_OUTPUT_PWM_HIGH); GTIM_SetCompare3(CW_GTIM3, fm_PosWidth); GTIM_ITConfig(CW_GTIM3, GTIM_IT_OV, ENABLE); GTIM_Cmd(CW_GTIM3, ENABLE); NVIC_EnableIRQ(GTIM1_IRQn); NVIC_EnableIRQ(GTIM3_IRQn); ``` ### 4.1占空比调节 占空比修改在PWM中断里面修改,只需要改变PosWidth值。 ``` void GTIM1_IRQHandler(void) { static uint16_t TimeCnt = 0; GTIM_ClearITPendingBit(CW_GTIM1, GTIM_IT_OV); if (TimeCnt++ >= 100) { TimeCnt = 0; GTIM_SetCompare1(CW_GTIM1, PosWidth); } /* USER CODE END */ } ``` ### 4.2频率调节 在PWM输出模式下是需要调节输出频率,调节代码如下: ``` void set_pwm_hz(uint32_t hz_val) { Period = 4000000 / hz_val; GTIM_SetReloadValue(CW_GTIM1, Period); } ``` GTIM_SetReloadValue函数就是设置pwm的频率。 ## 5.显示屏驱动 LCD显示屏采用的是SPI通信. SPI和GPIO初始化: ```c RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA | RCC_AHB_PERIPH_GPIOB | RCC_AHB_PERIPH_GPIOF, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; // 命名结构体 GPIO_InitStruct.IT = GPIO_IT_NONE; // 不配置中断 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pins = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_15; // GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 引脚速度设置高速 GPIO_Init(CW_GPIOB, &GPIO_InitStruct); GPIO_InitStruct.IT = GPIO_IT_NONE; // 不配置中断 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pins = GPIO_PIN_8 | GPIO_PIN_9; // GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 引脚速度设置高速 GPIO_Init(CW_GPIOA, &GPIO_InitStruct); //---------blk---------- GPIO_InitStruct.IT = GPIO_IT_NONE; // 不配置中断 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pins = GPIO_PIN_6; // GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 引脚速度设置高速 GPIO_Init(CW_GPIOF, &GPIO_InitStruct); // spi init RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_SPI1, ENABLE); PB12_AFx_SPI1CS(); PB13_AFx_SPI1SCK(); // PB14_AFx_SPI1MISO(); PB15_AFx_SPI1MOSI(); SPI_InitTypeDef SPI_InitStructure = {0}; SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_TxOnly; // 单发送模式 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 帧数据长度为8bit SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // 时钟空闲电平为高 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 第二个边沿采样 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 片选信号由SSI寄存器控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 波特率为PCLK的4分频 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 最高有效位 MSB 收发在前 SPI_InitStructure.SPI_Speed = SPI_Speed_High; // 高速SPI SPI_Init(CW_SPI1, &SPI_InitStructure); SPI_Cmd(CW_SPI1, ENABLE); ``` ## 6.测试笔模式 模式真值表如下所示: | 控制脚\模式 | 电平测量10x | 电平测量1x | PWM输出 | 直流输出 | 通断/二极管检测 | | ----------- | ----------- | ---------- | ------- | -------- | --------------- | | ASW1 | L | L | H | L | L | | ASW2 | L | H | L | L | L(1x档为H) | | ASW3 | L | L | H | H | L | | ASW4 | L | L | L | L | H | ## 7.RTOS RTOS使用的是FreeRTOS。移植参考freertos例子里面的M0内核工程 本项目任务一个开启了四个如下: ```c //创建任务 xTaskCreate(task_1,"task1",40,NULL,0,NULL); xTaskCreate(task_2,"task2",120,NULL,1,NULL); xTaskCreate(task_3, "task3", 40, NULL, 0, NULL); xTaskCreate(show_task,"show",120,NULL,2,NULL); ``` ### 7.1关机任务 任务一是检测长按关机键 ```c void task_1(void *msg) { while (1) { /* code */ scan_shutdown(); vTaskDelay(10); } } ``` ### 7.2控制任务 任务二功能: * 按键扫描 * UI控制函数 * 检测dma完成 ```c void task_2(void *msg) { while (1) { /* code */ wait_dma_complete(); key_msk = key_scan(); run_control(key_msk); vTaskDelay(10); } } ``` ### 7.3显示任务 显示任务执行的是UI窗口函数 ```c void show_task(void *msg) { while (1) { run_window(0); vTaskDelay(10); } } ``` ### 7.4自动档位任务 自动调节测试档位任务x1或x10 ```c void task_3(void *msg) { while (1) { /* code */ auto_multiple(); vTaskDelay(1); } } ``` #### 7.4.1自动档位检测 在x1档位下,最大测量电压为2400mv,超过就切换x10档位(档位切换看第6章) 在x10档位下,电压是缩小了10倍,检测到电压值为240以下,就切换为x1档。 ```c /** * @brief 设置倍数 * * @return uint8_t 返回0是10x档 */ void auto_multiple(void) { uint16_t volt = pen_volt(); static uint16_t cnt; if (auto_disable == 0) { if (pen_gears == 10) { // x10档是否切换x1 if (volt < 240) { cnt++; if (cnt > 5) { set_multiple_xn(1); cnt = 0; } } else { cnt = 0; } } else { if (volt >= 2400) { cnt++; if (cnt > 5) { // 10x set_multiple_xn(10); cnt = 0; } } else { cnt = 0; } } } } ``` ## 8.UI UI界面主要由界面显示和控制两部分组成. ![UI构成](H:\open_project\Multifunction_test_pen\Doc\.png\UI构成.bmp) ### 8.1界面显示 界面显示包含了显示内容和功能检测. ![UI界面](H:\open_project\Multifunction_test_pen\Doc\.png\UI界面.bmp) ### 8.2控制 控制部分分为菜单控制和每个二级菜单控制. ![](H:\open_project\Multifunction_test_pen\Doc\.png\UI控制1.bmp) ![](H:\open_project\Multifunction_test_pen\Doc\.png\UI控制2.bmp) ### 8.3可调参数范围 | 参数名 | 最小值 | 最大值 | | ------------- | ------ | ---------- | | TTL高电平阈值 | 0mV | 18000mV | | TTL低电平阈值 | 0mV | 2000mV | | PWM占空比 | 0% | 100% | | PWM周期 | 0 | 100.999khz | | DC输出电压 | 600mV | 5800mV | | On/Off电阻值 | 0欧 | 200欧 | ### 8.4电池电量显示 根据电池的充放电曲线,将电池电量划分为三段: 第一段电压>3.9V 第二段3.7V < 电压 <3.9V 第三段3.5<电压<3.7V 电压在第一段,显示三格电量即满电 电压在第二段,显示二格电量 电压在第三段,显示一格电量 电压<3.5V就显示空电量,提示电量低自动关机 ## 9.功能 ### 9.1测量TTL电平 1. 根据第6章的模式,切换到TTL模式 2. 采集笔尖电压,显示电压值 3. 根据采集的电压值与阈值做判断,高于高电平阈值亮红灯,低于低电压阈值亮绿灯。 4. 控制任务里面可以设置高低电平阈值 ### 9.2输出PWM 1. 根据第6章的模式,切换到PWM模式 2. 更改设置的频率和占空比调节GTIM1的参数(调节部分看第4章) 3. 控制任务里面可以设置PWM的频率和占空比 ### 9.3DC输出 1. 根据第6章的模式,切换到DC OUT模式 2. 根据公式y = 0.0647x - 0.019(y是电量,x是占空比)计算出当前输出的电压值是多少,并显示出来。 3. 控制任务里面可以设置DC输出的电压值 4. 设置好电压值,根据公式x=(y+0.019) /0.0647(y是电量,x是占空比)计算出对应的占空比 ### 9.4通断档测量 1. 根据第6章的模式,切换到通断模式。 2. 采集笔尖电压。 3. 计算电阻值,电流源的电流为2ma,根据欧姆定律计算。 4. 电阻补偿如下: ```c //分段电阻补偿 if(test_r <4) { test_r = 0; } else if (test_r < 85) { test_r += 6; } else if (test_r < 106) { test_r += 5; } else if (test_r < 157) { test_r += 4; } else if (test_r < 177) { test_r += 3; } ``` 5. 显示电阻值,电阻值最大为200欧,超过的话显示OL表示量程溢出。 6. 判断电阻值是否小于电阻阈值,小于则亮绿灯,蜂鸣器响。 ### 9.5二极管档测量 1. 根据第6章的模式,切换到二极管模式 2. 采集笔尖电压。 3. 电压补偿,电压值大于2500mv时补偿-50mv,显示补偿后的电压值。 ### 9.6校准 校准主要是电压采集校准。 1. 根据第6章的模式,切换到TTL模式,x1档位 2. 采集100次ADC数据,做平均值,记录到flash(掉电后数据会保存) 3. 根据第6章的模式,切换到TTL模式,x10档位 4. 采集100次ADC数据,做平均值,记录到flash(掉电后数据会保存) 5. 校准完成,退回菜单界面 ## 10.蓝牙 蓝牙模块使用的是串口通信,使用cw32官方代码里面的log.c和.h切换为UART2,波特率设置为115200即可。 log.c对接了fputc函数,就可以使用printf来输出串口数据。 在TTL模式下,调用以下函数,就可以对接PC上位。 vlote是电压值,在上位机就可以看到电压值的变化曲线。 ```c void ttl_volte_to_ble(uint16_t vlote) { static uint16_t cnt = 0; cnt++; if (cnt >= 10) { cnt = 0; printf("vlote:%u\n", vlote); } } ```