蓝桥杯第十六届嵌入式省赛程序设计题的实现
笔者参加了2025年的第16届蓝桥杯的嵌入式赛道,拿了个省一在此分享一下做题步骤,全凭考完剩下的记忆,第十六届嵌入式省赛真题。
笔者参加了2025年的第16届蓝桥杯的嵌入式赛道,拿了个省一

在此分享一下做题步骤,全凭考完剩下的记忆,如有错误敬请体谅(早知道带个U盘进去)
第十六届嵌入式省赛真题





CT117E_M4平台的原理图如下

可以看到系统框图如下

对应的连接如下:
模拟信号A对应R37 对应PB15引脚为ADC2的IN15
模拟信号B对应R38 对应PB12引脚为ADC1的IN11
按键连接如图

脉冲单元输入为PA15 为TIM2的Channel1
结合原理图可知采集到的频率受R40调节

PWM输出对应PA7 为TIM3的Channel2
LED的连接如图

LCD的连接如图

结合原理图在cubemx中进行配置
-
选择对应的芯片

-
时钟的配置


-
ADC的配置
ADC1 模拟输入B
ADC2 模拟输入A -
按键输入的配置

-
脉冲单元输入的配置

-
PWM输出的配置

-
LED引脚的配置

-
LCD引脚的配置

到此,我们的引脚配置就完毕了,接下来先生成代码编译看看

点击GENERATE CODE生成代码
KEIL的配置
DAP Link的配置,选择DAPLINK并勾选下载完运行


将LCD驱动文件拖进去,并在项目管理器中添加



项目所需的头文件
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "lcd.h"
/* USER CODE END Includes */
屏幕和LED的测试
/* USER CODE BEGIN 2 */
uint16_t i;
// close all leds
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port,LED_Ctrl_Pin,GPIO_PIN_SET);
for(i=0;i<8;i++){
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << i,GPIO_PIN_SET);
}
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port,LED_Ctrl_Pin,GPIO_PIN_RESET);
LCD_Init();
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
LCD_Clear(Black);
LCD_DisplayStringLine(Line1,(unsigned char *)"Hello World");
/* USER CODE END 2 */
效果如图,请忽略我有问题屏幕的白线

对应功能的实现
ADC采样的设计
PS:设ADC采样到的值为x,分辨率为n,则实际电压v为
uint16_t R37_voltage = 0 , R38_voltage;
void get_adc_value(){
uint16_t temp;
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1,20);
temp = HAL_ADC_GetValue(&hadc1);
R38_voltage = (3300 * temp) / 4096;
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,20);
temp = HAL_ADC_GetValue(&hadc2);
R37_voltage = (3300 * temp) / 4096;
HAL_ADC_Stop(&hadc1);
HAL_ADC_Stop(&hadc2);
}
测试一下

频率采集的实现
首先在cubeKMX开启TIM2的中断
然后开始编写频率的采集
从我们的设定可以知道计数脉冲为
可以知道采集的频率CCR从0到CCR1的时间为
于是乎,采集到频率为,采集完后对定时器清零
在中断回调函数中实现
// 输入频率的捕获
unsigned int fre = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
uint16_t ccr1;
// 判断定时器和通道是否符合
if(htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1){
ccr1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
fre = (int)1e6 / ccr1;
__HAL_TIM_SetCounter(htim,0);
}
}
在main函数中开启中断
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
测试一下

接下来就是按键的监听
按键监听的实现
此处我们配合10ms的定时器来实现状态机,定义函数为key_scan()
对应的结构体和数组
typedef struct KEY{
uint8_t sate;// 状态机状态
uint8_t pin_sate;// 引脚电平是否有效,低电平为1
uint8_t single_click_flag; // 短按
uint8_t long_click_flag; // 长按
uint16_t press_count; // 按下计数
uint16_t release_count; // 松开计数
}KEY;
KEY keys[4];
在main中对所有参数清零
for(i=0;i<4;i++){
keys[i].sate = 0;
keys[i].pin_sate = 0;
keys[i].press_count = 0;
keys[i].release_count = 0;
keys[i].single_click_flag = 0;
keys[i].long_click_flag = 0;
}
key_scan()函数的实现
void key_scan(){
uint8_t i;
// 获取引脚状态
keys[0].pin_sate = HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == GPIO_PIN_RESET ? 1 : 0;
keys[1].pin_sate = HAL_GPIO_ReadPin(B2_GPIO_Port,B2_Pin) == GPIO_PIN_RESET ? 1 : 0;
keys[2].pin_sate = HAL_GPIO_ReadPin(B3_GPIO_Port,B3_Pin) == GPIO_PIN_RESET ? 1 : 0;
keys[3].pin_sate = HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin) == GPIO_PIN_RESET ? 1 : 0;
for(i=0;i<4;i++){
switch(keys[i].sate){
case 0:
// 按下开始计数
if(keys[i].pin_sate){
keys[i].press_count++;
}
// 松开时判断是否进入下一个状态
if(!keys[i].pin_sate){
if(keys[i].press_count > 3){
keys[i].sate = 1;
}else{
keys[i].press_count = 0;
}
}
break;
case 1:
// 松开的消抖
if(!keys[i].pin_sate){
keys[i].release_count++;
}
if(keys[i].release_count > 3){
keys[i].sate =2;
}
break;
case 2:
// 给出对应的flag
if(keys[i].press_count > 200){ // 超过2s为长按
keys[i].long_click_flag = 1;
}
keys[i].single_click_flag = 1;
keys[i].press_count = 0;
keys[i].release_count = 0;
break;
default:
keys[i].sate = 0;
break;
}
}
}
定义对应的定时器TIM1开启10ms的中断回调扫描按键


在main中开启定时器
HAL_TIM_Base_Start_IT(&htim1);
在中断中扫描按键
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
// 10ms
if(htim->Instance == TIM1){
key_scan();
}
}
接下来实现切换页面
屏幕显示的实现
改写一下TIM1的中断来计时运行时间
uint32_t runtime = 0; // 运行的时间
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
// 10ms
if(htim->Instance == TIM1){
key_scan();
runtime +=10;
}
}
定义page来决定页面,LCD_show()函数用来显示
// PWM界面对应的参数
unsigned int CF = 2000; // 输出PWM频率
uint16_t CD = 80; // 输出PWM的占空比
unsigned int *DF = &fre; // 捕获的频率
uint8_t st_status = 0; // 0 为解锁,1为锁定
// RECD界面对应的参数
unsigned int CF_err = 0;
uint16_t CD_err = 0;
unsigned int DF_err = 0;
unsigned int XF = 0;
uint64_t runtime_err = 0;
// PARA界面对应的参数
uint16_t DS = 1;
uint16_t DR = 80;
unsigned int FS = 100;
unsigned int FR = 2000;
uint8_t page = 2;
char str[30];
void LCD_show(){
sprintf(str," ");
switch(page){
case 0:
// 12345678901234567890
sprintf(str," PWM ");
LCD_DisplayStringLine(Line1,(unsigned char *)str);
sprintf(str," CF=%dHz ",CF);
LCD_DisplayStringLine(Line3,(unsigned char *)str);
sprintf(str," CD=%d%% ",CD);
LCD_DisplayStringLine(Line4,(unsigned char *)str);
sprintf(str," DF=%dHz ",*DF);
LCD_DisplayStringLine(Line5,(unsigned char *)str);
sprintf(str," ST=%s ",!st_status ? "UNLOCK":"LOCK");
LCD_DisplayStringLine(Line6,(unsigned char *)str);
unsigned int sec = runtime / 1000;
unsigned int min = sec / 60;
unsigned int hour = min / 60;
sec = sec - 60*min;
min = min - min / 60;
sprintf(str," %02dH%02dM%02dS",hour,min,sec);
LCD_DisplayStringLine(Line7,(unsigned char *)str);
break;
case 1:
sprintf(str," RECD ");
LCD_DisplayStringLine(Line1,(unsigned char *)str);
sprintf(str," CF=%dHz ",CF_err);
LCD_DisplayStringLine(Line3,(unsigned char *)str);
sprintf(str," CD=%d%% ",CD_err);
LCD_DisplayStringLine(Line4,(unsigned char *)str);
sprintf(str," DF=%dHz ",DF_err);
LCD_DisplayStringLine(Line5,(unsigned char *)str);
sprintf(str," XF=%dHz ",XF);
LCD_DisplayStringLine(Line6,(unsigned char *)str);
sec = runtime_err / 1000;
min = sec / 60;
hour = min / 60;
sec = sec - 60*min;
min = min - min / 60;
sprintf(str," %02dH%02dM%02dS",hour,min,sec);
LCD_DisplayStringLine(Line7,(unsigned char *)str);
break;
case 2:
sprintf(str," PARA ");
LCD_DisplayStringLine(Line1,(unsigned char *)str);
sprintf(str," DS=%d%% ",DS);
LCD_DisplayStringLine(Line3,(unsigned char *)str);
sprintf(str," DR=%d%% ",DR);
LCD_DisplayStringLine(Line4,(unsigned char *)str);
sprintf(str," FS=%dHz ",FS);
LCD_DisplayStringLine(Line5,(unsigned char *)str);
sprintf(str," FR=%dHz ",FR);
LCD_DisplayStringLine(Line6,(unsigned char *)str);
break;
}
对应显示效果如下



按键功能的实现
void key_proc(){
// 页面的切换
if(keys[0].single_click_flag){
if(page < 2)
page ++;
else
page = 0;
LCD_Clear(Black);
// 默认修改DR
if(page == 2)
para_choose_index = 0;
keys[0].single_click_flag = 0;
keys[0].long_click_flag = 0;
keys[0].sate = 0;
}
// 参数切换以及计时清零,锁定切换
if(keys[1].single_click_flag){
if(page == 0 && keys[1].long_click_flag){
runtime = 0;
}else if(page == 0){
st_status = !st_status;
}
if(page == 2 ){
if(para_choose_index < 3)
para_choose_index ++;
else
para_choose_index = 0;
}
keys[1].single_click_flag = 0;
keys[1].long_click_flag = 0;
keys[1].sate = 0;
}
// 参数加
if(keys[2].single_click_flag){
if(page == 2){
switch(para_choose_index){
case 0:
if(DS + 1 < DR)
DS +=1;
break;
case 1:
if(DR + 10 <= 100)
DR += 10;
break;
case 2:
if(FS + 100 < FR)
FS += 100;
break;
case 3:
FR += 1000;
break;
default:
para_choose_index = 0;
}
}
keys[2].single_click_flag = 0;
keys[2].long_click_flag = 0;
keys[2].sate = 0;
}
// 参数减
if(keys[3].single_click_flag){
if(page == 2){
switch(para_choose_index){
case 0:
if(DS - 1 >= 1)
DS -=1;
break;
case 1:
if(DR - 10 >= 10)
DR -= 10;
break;
case 2:
if(FS - 100 >= 100)
FS -= 100;
break;
case 3:
if(FR - 1000 >= 1000)
FR -= 1000;
break;
default:
para_choose_index = 0;
}
}
keys[3].single_click_flag = 0;
keys[3].long_click_flag = 0;
keys[3].sate = 0;
}
}
PWM输出的实现
我们只需要修改TIM3的自动重新装载值ARR就可以实现pwm频率的修改,修改CCR2也就可以修改输出的占空比
// 在main中开启pwm输出
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
pwm输出控制函数
void pwm_out(){
uint16_t arr = (int)1e6 / CF - 1;
uint16_t comp = (arr * CD) / 100;
__HAL_TIM_SET_AUTORELOAD(&htim3,arr);
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,comp);
}
测试一下,默认
使用逻辑分析仪采集,可以看到我们的思路是没有问题的

电位器控制输出频率以及占空比

可以看到把电压0~3300mV划分为了n份,那每一份的电压为
根据采集到的电压就可以反推出n
然后改写pwm输出控制函数
void pwm_out(){
uint16_t n = 0;
// 计算出对应的V_step
uint16_t V_setp1 = (3300*DS)/(DR - 10 ) , V_setp2 = (3300*FS)/(FR -1000);
// 计算出实际输出占空比
CD = 10 + (DS * R37_voltage) / V_setp1;
// 计算出实际输出的频率
CF = 1000 + (FS * R38_voltage) / V_setp2;
// 计算出所需的ARR和CCR2
uint16_t arr = (int)1e6 / CF - 1;
uint16_t comp = (arr * CD) / 100;
__HAL_TIM_SET_AUTORELOAD(&htim3,arr);
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,comp);
}
随便设定个值测试一下,屏幕显示如图

逻辑分析仪采集到的结果与之接近

异常捕获的实现
要求如图,持续异常不更新


实现代码如下
uint8_t error_hold = 0;
void error_proc(){
int32_t t = (int32_t)fre - (int32_t)CF;
if(t < 0)
t = -t;
if(t < 1000)
error_hold = 0;
if(!error_hold){
if(t > 1000){
error_hold = 1;
CF_err = CF;
CD_err = CD;
DF_err = fre;
XF = t;
runtime_err = runtime;
}
}
}
LED显示的实现

实现代码如下
void leds(){
uint16_t i;
// close all leds
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port,LED_Ctrl_Pin,GPIO_PIN_SET);
for(i=0;i<8;i++){
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << i,GPIO_PIN_SET);
}
if(page == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << 0,GPIO_PIN_RESET);
if(st_status)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << 1,GPIO_PIN_RESET);
if(error_hold)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << 2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port,LED_Ctrl_Pin,GPIO_PIN_RESET);
}
锁定功能的实现

也就是st_status=1时,PWM输出不受电位器电压所影响
原先while(1)循环处代码为:
while (1)
{
leds();
get_adc_value();
LCD_show();
key_proc();
pwm_out();
error_proc();
HAL_Delay(50);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
修改为
/* USER CODE BEGIN WHILE */
while (1)
{
leds();
get_adc_value();
LCD_show();
key_proc();
error_proc();
if(!st_status)
pwm_out();
HAL_Delay(50);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
到此所有功能我们都实现了,所有代码如下
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "lcd.h"
#include <stdio.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
ADC_HandleTypeDef hadc2;
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_ADC2_Init(void);
static void MX_TIM2_Init(void);
static void MX_TIM3_Init(void);
static void MX_TIM1_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
typedef struct KEY{
uint8_t sate;// 状态机状态
uint8_t pin_sate;// 引脚电平是否有效,低电平为1
uint8_t single_click_flag; // 短按
uint8_t long_click_flag; // 长按
uint16_t press_count; // 按下计数
uint16_t release_count; // 松开计数
}KEY;
KEY keys[4];
// 电位器电压的获取
uint16_t R37_voltage = 0 , R38_voltage;
void get_adc_value(){
uint16_t temp;
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1,20);
temp = HAL_ADC_GetValue(&hadc1);
R38_voltage = (3300 * temp) / 4096;
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,20);
temp = HAL_ADC_GetValue(&hadc2);
R37_voltage = (3300 * temp) / 4096;
HAL_ADC_Stop(&hadc1);
HAL_ADC_Stop(&hadc2);
}
// 输入频率的捕获
unsigned int fre = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
uint16_t ccr1;
// 判断定时器和通道是否符合
if(htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1){
ccr1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
fre = (int)1e6 / ccr1;
__HAL_TIM_SetCounter(htim,0);
}
}
void key_scan(){
uint8_t i;
// 获取引脚状态
keys[0].pin_sate = HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == GPIO_PIN_RESET ? 1 : 0;
keys[1].pin_sate = HAL_GPIO_ReadPin(B2_GPIO_Port,B2_Pin) == GPIO_PIN_RESET ? 1 : 0;
keys[2].pin_sate = HAL_GPIO_ReadPin(B3_GPIO_Port,B3_Pin) == GPIO_PIN_RESET ? 1 : 0;
keys[3].pin_sate = HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin) == GPIO_PIN_RESET ? 1 : 0;
for(i=0;i<4;i++){
switch(keys[i].sate){
case 0:
// 按下开始计数
if(keys[i].pin_sate){
keys[i].press_count++;
}
// 松开时判断是否进入下一个状态
if(!keys[i].pin_sate){
if(keys[i].press_count > 3){
keys[i].sate = 1;
}else{
keys[i].press_count = 0;
}
}
break;
case 1:
// 松开的消抖
if(!keys[i].pin_sate){
keys[i].release_count++;
}
if(keys[i].release_count > 3){
keys[i].sate =2;
}
break;
case 2:
// 给出对应的flag
if(keys[i].press_count > 200){ // 超过2s为长按
keys[i].long_click_flag = 1;
}
keys[i].single_click_flag = 1;
keys[i].press_count = 0;
keys[i].release_count = 0;
break;
default:
keys[i].sate = 0;
break;
}
}
}
uint32_t runtime = 0; // 运行的时间
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
// 10ms
if(htim->Instance == TIM1){
key_scan();
runtime +=10;
}
}
// PWM界面对应的参数
unsigned int CF = 2000; // 输出PWM频率
uint16_t CD = 80; // 输出PWM的占空比
unsigned int *DF = &fre; // 捕获的频率
uint8_t st_status = 0; // 0 为解锁,1为锁定
// RECD界面对应的参数
unsigned int CF_err = 0;
uint16_t CD_err = 0;
unsigned int DF_err = 0;
unsigned int XF = 0;
uint64_t runtime_err = 0;
// PARA界面对应的参数
uint16_t DS = 1;
uint16_t DR = 80;
unsigned int FS = 100;
unsigned int FR = 2000;
uint8_t page = 0;
char str[30];
void LCD_show(){
sprintf(str," ");
switch(page){
case 0:
// 12345678901234567890
sprintf(str," PWM ");
LCD_DisplayStringLine(Line1,(unsigned char *)str);
sprintf(str," CF=%dHz ",CF);
LCD_DisplayStringLine(Line3,(unsigned char *)str);
sprintf(str," CD=%d%% ",CD);
LCD_DisplayStringLine(Line4,(unsigned char *)str);
sprintf(str," DF=%dHz ",*DF);
LCD_DisplayStringLine(Line5,(unsigned char *)str);
sprintf(str," ST=%s ",!st_status ? "UNLOCK":"LOCK");
LCD_DisplayStringLine(Line6,(unsigned char *)str);
unsigned int sec = runtime / 1000;
unsigned int min = sec / 60;
unsigned int hour = min / 60;
sec = sec - 60*min;
min = min - min / 60;
sprintf(str," %02dH%02dM%02dS",hour,min,sec);
LCD_DisplayStringLine(Line7,(unsigned char *)str);
break;
case 1:
sprintf(str," RECD ");
LCD_DisplayStringLine(Line1,(unsigned char *)str);
sprintf(str," CF=%dHz ",CF_err);
LCD_DisplayStringLine(Line3,(unsigned char *)str);
sprintf(str," CD=%d%% ",CD_err);
LCD_DisplayStringLine(Line4,(unsigned char *)str);
sprintf(str," DF=%dHz ",DF_err);
LCD_DisplayStringLine(Line5,(unsigned char *)str);
sprintf(str," XF=%dHz ",XF);
LCD_DisplayStringLine(Line6,(unsigned char *)str);
sec = runtime_err / 1000;
min = sec / 60;
hour = min / 60;
sec = sec - 60*min;
min = min - min / 60;
sprintf(str," %02dH%02dM%02dS",hour,min,sec);
LCD_DisplayStringLine(Line7,(unsigned char *)str);
break;
case 2:
sprintf(str," PARA ");
LCD_DisplayStringLine(Line1,(unsigned char *)str);
sprintf(str," DS=%d%% ",DS);
LCD_DisplayStringLine(Line3,(unsigned char *)str);
sprintf(str," DR=%d%% ",DR);
LCD_DisplayStringLine(Line4,(unsigned char *)str);
sprintf(str," FS=%dHz ",FS);
LCD_DisplayStringLine(Line5,(unsigned char *)str);
sprintf(str," FR=%dHz ",FR);
LCD_DisplayStringLine(Line6,(unsigned char *)str);
break;
}
}
uint8_t para_choose_index = 0;
void key_proc(){
// 页面的切换
if(keys[0].single_click_flag){
if(page < 2)
page ++;
else
page = 0;
LCD_Clear(Black);
// 默认修改DR
if(page == 2)
para_choose_index = 0;
keys[0].single_click_flag = 0;
keys[0].long_click_flag = 0;
keys[0].sate = 0;
}
// 参数切换以及计时清零,锁定切换
if(keys[1].single_click_flag){
if(page == 0 && keys[1].long_click_flag){
runtime = 0;
}else if(page == 0){
st_status = !st_status;
}
if(page == 2 ){
if(para_choose_index < 3)
para_choose_index ++;
else
para_choose_index = 0;
}
keys[1].single_click_flag = 0;
keys[1].long_click_flag = 0;
keys[1].sate = 0;
}
// 参数加
if(keys[2].single_click_flag){
if(page == 2){
switch(para_choose_index){
case 0:
if(DS + 1 < DR)
DS +=1;
break;
case 1:
if(DR + 10 <= 100)
DR += 10;
break;
case 2:
if(FS + 100 < FR)
FS += 100;
break;
case 3:
FR += 1000;
break;
default:
para_choose_index = 0;
}
}
keys[2].single_click_flag = 0;
keys[2].long_click_flag = 0;
keys[2].sate = 0;
}
// 参数减
if(keys[3].single_click_flag){
if(page == 2){
switch(para_choose_index){
case 0:
if(DS - 1 >= 1)
DS -=1;
break;
case 1:
if(DR - 10 >= 10)
DR -= 10;
break;
case 2:
if(FS - 100 >= 100)
FS -= 100;
break;
case 3:
if(FR - 1000 >= 1000)
FR -= 1000;
break;
default:
para_choose_index = 0;
}
}
keys[3].single_click_flag = 0;
keys[3].long_click_flag = 0;
keys[3].sate = 0;
}
}
void pwm_out(){
uint16_t n = 0;
// 计算出对应的V_step
uint16_t V_setp1 = (3300*DS)/(DR - 10 ) , V_setp2 = (3300*FS)/(FR -1000);
// 计算出实际输出占空比
CD = 10 + (DS * R37_voltage) / V_setp1;
// 计算出实际输出的频率
CF = 1000 + (FS * R38_voltage) / V_setp2;
// 计算出所需的ARR和CCR2
uint16_t arr = (int)1e6 / CF - 1;
uint16_t comp = (arr * CD) / 100;
__HAL_TIM_SET_AUTORELOAD(&htim3,arr);
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,comp);
}
uint8_t error_hold = 0;
void error_proc(){
int32_t t = (int32_t)fre - (int32_t)CF;
if(t < 0)
t = -t;
if(t < 1000)
error_hold = 0;
if(!error_hold){
if(t > 1000){
error_hold = 1;
CF_err = CF;
CD_err = CD;
DF_err = fre;
XF = t;
runtime_err = runtime;
}
}
}
void leds(){
uint16_t i;
// close all leds
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port,LED_Ctrl_Pin,GPIO_PIN_SET);
for(i=0;i<8;i++){
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << i,GPIO_PIN_SET);
}
if(page == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << 0,GPIO_PIN_RESET);
if(st_status)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << 1,GPIO_PIN_RESET);
if(error_hold)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << 2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port,LED_Ctrl_Pin,GPIO_PIN_RESET);
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC1_Init();
MX_ADC2_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
uint16_t i;
// close all leds
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port,LED_Ctrl_Pin,GPIO_PIN_SET);
for(i=0;i<8;i++){
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << i,GPIO_PIN_SET);
}
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port,LED_Ctrl_Pin,GPIO_PIN_RESET);
for(i=0;i<4;i++){
keys[i].sate = 0;
keys[i].pin_sate = 0;
keys[i].press_count = 0;
keys[i].release_count = 0;
keys[i].single_click_flag = 0;
keys[i].long_click_flag = 0;
}
// LCD init
LCD_Init();
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
LCD_Clear(Black);
LCD_DisplayStringLine(Line1,(unsigned char *)"Hello World");
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
leds();
get_adc_value();
LCD_show();
key_proc();
error_proc();
if(!st_status)
pwm_out();
HAL_Delay(50);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV3;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_MultiModeTypeDef multimode = {0};
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.GainCompensation = 0;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure the ADC multi-mode
*/
multimode.Mode = ADC_MODE_INDEPENDENT;
if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_11;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief ADC2 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC2_Init(void)
{
/* USER CODE BEGIN ADC2_Init 0 */
/* USER CODE END ADC2_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC2_Init 1 */
/* USER CODE END ADC2_Init 1 */
/** Common config
*/
hadc2.Instance = ADC2;
hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc2.Init.Resolution = ADC_RESOLUTION_12B;
hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc2.Init.GainCompensation = 0;
hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc2.Init.LowPowerAutoWait = DISABLE;
hadc2.Init.ContinuousConvMode = DISABLE;
hadc2.Init.NbrOfConversion = 1;
hadc2.Init.DiscontinuousConvMode = DISABLE;
hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc2.Init.DMAContinuousRequests = DISABLE;
hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc2.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc2) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_15;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC2_Init 2 */
/* USER CODE END ADC2_Init 2 */
}
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 800-1;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 999;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
}
/**
* @brief TIM2 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM2_Init(void)
{
/* USER CODE BEGIN TIM2_Init 0 */
/* USER CODE END TIM2_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 80-1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 4294967295;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM2_Init 2 */
/* USER CODE END TIM2_Init 2 */
}
/**
* @brief TIM3 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 80-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65535;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
HAL_TIM_MspPostInit(&htim3);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_0
|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4
|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8
|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED_Ctrl_GPIO_Port, LED_Ctrl_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5|GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET);
/*Configure GPIO pins : PC13 PC14 PC15 PC0
PC1 PC2 PC3 PC4
PC5 PC6 PC7 PC8
PC9 PC10 PC11 PC12 */
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_0
|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4
|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8
|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pin : B4_Pin */
GPIO_InitStruct.Pin = B4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B4_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : B1_Pin B2_Pin B3_Pin */
GPIO_InitStruct.Pin = B1_Pin|B2_Pin|B3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pin : PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : LED_Ctrl_Pin */
GPIO_InitStruct.Pin = LED_Ctrl_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED_Ctrl_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PB5 PB8 PB9 */
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)