1.驱动板的原理图如下:

驱动板怎么驱动电机就简单说下采用的是单极模式正转时Q1,Q2接PWM信号并且是互补的PWM信号Q1接PWM,Q2接PWMN.而Q3截止,Q4导通。Q1与Q4导通有电流从左到右流。当Q1截止时。Q2与Q4导通由于自感电动势的原因也会有电流从左到右流,但是电流是衰减的。反转时Q3,Q4接PWM。Q1截止Q2导通。电流从右到左流的。(具体跟正转一样的分析)。

图中的Current_amp点,是电流采集通道,DC-BUS是电压采集通道。分别接在对应MCU的PF10(ADC3的通道8),PF8(ADC3的通道6)。Currennt_amp点采集到的电压可以求出采样电流的值。

2.dome列子

1.电压,电流采集

ADC.h

#ifndef __ADC_H__
#define __ADC_H__#include "stm32f4xx_hal.h"// 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响
/********************ADC输入通道(引脚)配置**************************/
#define ADCx_RCC_CLK_ENABLE()            __HAL_RCC_ADC3_CLK_ENABLE()
#define ADCx_RCC_CLK_DISABLE()           __HAL_RCC_ADC3_CLK_DISABLE()
#define ADCx                             ADC3
#define ADC_CURRENT_CHANNEL              ADC_CHANNEL_8
#define ADC_VOLT_CHANNEL                 ADC_CHANNEL_6
#define ADC_OVP_IRQx                     ADC_IRQn
#define ADC_OVP_IRQHandler               ADC_IRQHandler#define DMAx_RCC_CLK_ENABLE()            __HAL_RCC_DMA2_CLK_ENABLE()
#define ADCx_DMA_IRQx                    DMA2_Stream0_IRQn
#define ADCx_DMA_IRQx_Handler            DMA2_Stream0_IRQHandler
#define DMAx_Stream_x                    DMA2_Stream0
#define DMAx_CHANNEL_x                   DMA_CHANNEL_2#define ADC_CUR_GPIO_ClK_ENABLE()        __HAL_RCC_GPIOF_CLK_ENABLE()
#define ADC_CUR_GPIO                     GPIOF
#define ADC_CUR_GPIO_PIN                 GPIO_PIN_10        #define ADC_VOLT_GPIO_ClK_ENABLE()       __HAL_RCC_GPIOF_CLK_ENABLE()
#define ADC_VOLT_GPIO                    GPIOF
#define ADC_VOLT_GPIO_PIN                GPIO_PIN_8//  Vcurrent_amp=1.65v + 7.5K/(1K+68)VIsensor 根据公式求VIsensor电压从而求出采样电流#define VOLT_REF                3.3f     // ADC参考电压
/* 根据驱动板设置放大倍数 和 采样电阻 */
#define GAIN                    6.8f      // 放大倍数 因为VIsensor太小ADC采集不到进行放大根据上面公式求出#define SAMPLING_RES            0.01f     // 采样电阻/** 电压分辨率 =  ADC(Hex) * 3.3 / 2^n * 1000(mV) 单位是mV * STM32的ADC分辨率是n = 12bit,电机控制脉冲是20KHz,理论上采样率40Kz就可以正确采集到波形计算电流,* 根据过采样理论:实际采样率 > 40KHz * 4^2,所以可以提高分辨率到14bit.所以这里用 2^14 = 16384*/#define VOLT_RESOLUTION     ((float)((VOLT_REF/(float)(16384))*(float)1000)) // ADC 电压分辨率,单位:0.201mV
#define VOLTBUS_RESOLUTION  ((float)( 3.3f/(float)4096) * (42.4f+42.4f) / 3.9f)/* 总线电压参数相关 */
#define VOLT_MAX                         60.0f // 最大电压值
#define VOLT_MIN                         12.0f/* 总线分压电阻:3.9 kΩ,80.4 kΩ 把最大电压与最小电压转换为16进制*/
#define VOLT_LIMIT_MAX                   (int32_t)((((VOLT_MAX * 3.9f) / (3.9f+42.4f) ) /3.9f) *4096.0f)
#define VOLT_LIMIT_MIN                   (int32_t)((((VOLT_MIN * 3.9f) / (3.9f+42.4f) ) /3.9f) *4096.0f)              //分压电阻:42.4kΩ.3.9kΩ/* 扩展变量 ------------------------------------------------------------------*/
extern ADC_HandleTypeDef hadcx;
extern DMA_HandleTypeDef hdma_adcx;/* 函数声明 ------------------------------------------------------------------*/
void MX_ADCx_Init(void);
void MX_DMA_Init(void) ;
void SetChannelAsRank1(ADC_HandleTypeDef* hadc,uint32_t Channel);#endif  /* __ADC_H__ */

ADC.c

#include "adc/bsp_adc.h"
ADC_HandleTypeDef hadcx;       // ADC结构体
DMA_HandleTypeDef hdma_adcx;  // DMA结构体
__IO int32_t OffSetHex = 0;void MX_ADCx_Init(void)
{ ADC_ChannelConfTypeDef sConfig;ADC_AnalogWDGConfTypeDef AWDGConfig; // ADC看门狗结构体/* 外设时钟使能 */ADCx_RCC_CLK_ENABLE();hadcx.Instance = ADCx;hadcx.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;hadcx.Init.Resolution = ADC_RESOLUTION_12B;hadcx.Init.ScanConvMode = DISABLE;hadcx.Init.ContinuousConvMode = ENABLE;hadcx.Init.DiscontinuousConvMode = DISABLE;hadcx.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;hadcx.Init.ExternalTrigConv = ADC_SOFTWARE_START;hadcx.Init.DataAlign = ADC_DATAALIGN_RIGHT;hadcx.Init.NbrOfConversion = 1;hadcx.Init.DMAContinuousRequests = ENABLE;hadcx.Init.EOCSelection = ADC_EOC_SINGLE_CONV;HAL_ADC_Init(&hadcx);/* 配置电流采样通道 */sConfig.Channel = ADC_CURRENT_CHANNEL;sConfig.Offset = 0;sConfig.Rank = 0x01;  // 先采集电流通道sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;HAL_ADC_ConfigChannel(&hadcx,&sConfig);/* 配置总线电压采集 *//* 模拟看门狗配置 */AWDGConfig.Channel = ADC_VOLT_CHANNEL;  // 哪个通道启动看门狗AWDGConfig.HighThreshold = VOLT_LIMIT_MAX; // 设置上限AWDGConfig.LowThreshold = VOLT_LIMIT_MIN;  // 设置下限 AWDGConfig.ITMode = ENABLE;                // 开启中断AWDGConfig.WatchdogMode = ADC_ANALOGWATCHDOG_SINGLE_REG; // 看门狗模式AWDGConfig.WatchdogNumber = 0; // Reserved for future use, can be set to 0HAL_ADC_AnalogWDGConfig(&hadcx,&AWDGConfig);sConfig.Channel = ADC_VOLT_CHANNEL;sConfig.Offset = 0;sConfig.Rank = 0x02;sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;HAL_ADC_ConfigChannel(&hadcx,&sConfig);HAL_NVIC_SetPriority(ADC_OVP_IRQx, 0, 1);HAL_NVIC_EnableIRQ(ADC_OVP_IRQx);
}void MX_DMA_Init(void)
{ /* 使能外设时钟 */DMAx_RCC_CLK_ENABLE();hdma_adcx.Instance = DMAx_Stream_x;hdma_adcx.Init.Channel = DMAx_CHANNEL_x;hdma_adcx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_adcx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_adcx.Init.MemInc = DMA_MINC_ENABLE;hdma_adcx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_adcx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_adcx.Init.Mode = DMA_CIRCULAR;hdma_adcx.Init.Priority = DMA_PRIORITY_HIGH;hdma_adcx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;HAL_DMA_Init(&hdma_adcx);__HAL_LINKDMA(&hadcx,DMA_Handle,hdma_adcx);/* 外设中断优先级配置和使能中断 */HAL_NVIC_SetPriority(ADCx_DMA_IRQx, 1, 1);HAL_NVIC_EnableIRQ(ADCx_DMA_IRQx);
}/*** 函数功能: ADC外设初始化配置* 输入参数: hadc:AD外设句柄类型指针* 返 回 值: 无* 说    明: 该函数被HAL库内部调用*/
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{GPIO_InitTypeDef GPIO_InitStruct;if(hadc->Instance==ADCx){/* AD转换通道引脚时钟使能 */ADC_CUR_GPIO_ClK_ENABLE();/* AD转换通道引脚初始化 */GPIO_InitStruct.Pin = ADC_CUR_GPIO_PIN;GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(ADC_CUR_GPIO, &GPIO_InitStruct);GPIO_InitStruct.Pin = ADC_VOLT_GPIO_PIN;HAL_GPIO_Init(ADC_VOLT_GPIO, &GPIO_InitStruct);}
}/** 函数功能: 设置AD转换通道的转换顺序为1* 输入参数: hadc ADC句柄 , Channel可以是ADC_VOLT_CHANNEL,ADC_CURRENT_CHANNEL* 返 回 值: 无* 说    明: 无*/
void SetChannelAsRank1(ADC_HandleTypeDef* hadc,uint32_t Channel)
{ADC_ChannelConfTypeDef sConfig;if(Channel == ADC_VOLT_CHANNEL){/* 配置电压通道 */sConfig.Channel = ADC_VOLT_CHANNEL;sConfig.Offset = 0;sConfig.Rank = 0x01;sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;HAL_ADC_ConfigChannel(&hadcx,&sConfig);/* 配置电流通道 */sConfig.Channel = ADC_CURRENT_CHANNEL;sConfig.Rank = 0x02;HAL_ADC_ConfigChannel(&hadcx,&sConfig);}else{/* 配置电流通道 */sConfig.Channel = ADC_CURRENT_CHANNEL;sConfig.Offset = 0;sConfig.Rank = 0x01;sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;HAL_ADC_ConfigChannel(&hadcx,&sConfig);/* 配置电压通道 */sConfig.Channel = ADC_VOLT_CHANNEL;sConfig.Rank = 0x02;HAL_ADC_ConfigChannel(&hadcx,&sConfig);}
}

time.h  做互补PWM

#ifndef __BDCMOTOR_TIM_H__
#define __BDCMOTOR_TIM_H__/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f4xx_hal.h"/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/
#define BDCMOTOR_TIMx                         TIM1
#define BDCMOTOR_TIM_RCC_CLK_ENABLE()         __HAL_RCC_TIM1_CLK_ENABLE()
#define BDCMOTOR_TIM_RCC_CLK_DISABLE()        __HAL_RCC_TIM1_CLK_DISABLE()#define BDCMOTOR_TIM_CH1_GPIO_CLK_ENABLE()    __HAL_RCC_GPIOA_CLK_ENABLE()     // 输出PWM脉冲给电机控制器的的IN引脚
#define BDCMOTOR_TIM_CH1_PORT                 GPIOA                            // CH1和CH1N两个引脚配套使用
#define BDCMOTOR_TIM_CH1_PIN                  GPIO_PIN_8                       // 如果电机接在驱动器的OUT1和OUT2端子上
#define BDCMOTOR_TIM_CH1N_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOB_CLK_ENABLE()     // CH1和CH1N对应接在IN1和IN2
#define BDCMOTOR_TIM_CH1N_PORT                GPIOB                            // 如果电机接在驱动器的OUT3和OUT4端子上
#define BDCMOTOR_TIM_CH1N_PIN                 GPIO_PIN_13                      // CH1和CH1N对应接在IN3和IN4#define SHUTDOWN_GPIO_CLK_ENABLE()            __HAL_RCC_GPIOH_CLK_ENABLE()     // CH1和CH1N对应接在IN1和IN2
#define SHUTDOWN_PORT                         GPIOH                            // 如果电机接在驱动器的OUT3和OUT4端子上
#define SHUTDOWN_PIN                          GPIO_PIN_6                       // CH1和CH1N对应接在IN3和IN4#define ENABLE_MOTOR()                        HAL_GPIO_WritePin(SHUTDOWN_PORT,SHUTDOWN_PIN,GPIO_PIN_RESET)
#define SHUTDOWN_MOTOR()                      HAL_GPIO_WritePin(SHUTDOWN_PORT,SHUTDOWN_PIN,GPIO_PIN_SET)#define BDCMOTOR_TIM_CC_IRQx                  TIM1_CC_IRQn
#define BDCMOTOR_TIM_CC_IRQxHandler           TIM1_CC_IRQHandler// 定义定时器预分频,定时器实际时钟频率为:168MHz/(BDCMOTOR_TIMx_PRESCALER+1)
#define BDCMOTOR_TIM_PRESCALER               1    // 实际时钟频率为:84MHz// 定义定时器周期,PWM频率为:168MHz/(BDCMOTOR_TIMx_PRESCALER+1)/(BDCMOTOR_TIM_PERIOD+1)
#define BDCMOTOR_TIM_PERIOD                  4199  // PWM频率为84MHz/(4199+1)=20KHz#define BDCMOTOR_DUTY_ZERO                   (((BDCMOTOR_TIM_PERIOD+1)>>1)-1)       // 0%占空比
#define BDCMOTOR_DUTY_FULL                   (BDCMOTOR_TIM_PERIOD-100)            // 100%占空比
#define VC_OFFSET                            // 定义高级定时器重复计数寄存器值
// 实际PWM频率为:168MHz/(BDCMOTOR_TIMx_PRESCALER+1)/(BDCMOTOR_TIM_PERIOD+1)/(BDCMOTOR_TIM_REPETITIONCOUNTER+1)
#define BDCMOTOR_TIM_REPETITIONCOUNTER       0/* 扩展变量 ------------------------------------------------------------------*/
extern TIM_HandleTypeDef htimx_BDCMOTOR;
extern __IO int16_t PWM_Duty;/* 函数声明 ------------------------------------------------------------------*/void BDCMOTOR_TIMx_Init(void);#endif   /* __BDCMOTOR_TIM_H__ */

timer.c

#include "DCMotor/bsp_BDCMotor.h" TIM_HandleTypeDef htimx_BDCMOTOR;
__IO int16_t PWM_Duty=BDCMOTOR_DUTY_ZERO;// 占空比:PWM_Duty/BDCMOTOR_TIM_PERIOD*100%void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{/* BDCMOTOR相关GPIO初始化配置 */if(htim == &htimx_BDCMOTOR){GPIO_InitTypeDef GPIO_InitStruct; /* 引脚端口时钟使能 */__HAL_RCC_GPIOE_CLK_ENABLE();BDCMOTOR_TIM_CH1_GPIO_CLK_ENABLE();BDCMOTOR_TIM_CH1N_GPIO_CLK_ENABLE();SHUTDOWN_GPIO_CLK_ENABLE();/* BDCMOTOR输出脉冲控制引脚IO初始化 */GPIO_InitStruct.Pin = BDCMOTOR_TIM_CH1_PIN;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;HAL_GPIO_Init(BDCMOTOR_TIM_CH1_PORT, &GPIO_InitStruct);GPIO_InitStruct.Pin = BDCMOTOR_TIM_CH1N_PIN;HAL_GPIO_Init(BDCMOTOR_TIM_CH1N_PORT, &GPIO_InitStruct);__HAL_RCC_GPIOE_CLK_ENABLE();GPIO_InitStruct.Pin = GPIO_PIN_11;HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);GPIO_InitStruct.Pin = SHUTDOWN_PIN;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = 0;HAL_GPIO_Init(SHUTDOWN_PORT, &GPIO_InitStruct);/* 使能电机控制引脚 */ENABLE_MOTOR();}
}/*** 函数功能: BDCMOTOR定时器初始化* 输入参数: 无* 返 回 值: 无* 说    明: 无*/
void BDCMOTOR_TIMx_Init(void)
{TIM_ClockConfigTypeDef sClockSourceConfig;             // 定时器时钟TIM_OC_InitTypeDef sConfigOC; TIM_BreakDeadTimeConfigTypeDef  sBDTConfig;            // 定时器死区时间比较输出
//  TIM_MasterConfigTypeDef sMasterConfig;/* 基本定时器外设时钟使能 */BDCMOTOR_TIM_RCC_CLK_ENABLE();/* 定时器基本环境配置 */htimx_BDCMOTOR.Instance = BDCMOTOR_TIMx;                                 // 定时器编号htimx_BDCMOTOR.Init.Prescaler = BDCMOTOR_TIM_PRESCALER;                  // 定时器预分频器htimx_BDCMOTOR.Init.CounterMode = TIM_COUNTERMODE_UP;                  // 计数方向:向上计数htimx_BDCMOTOR.Init.Period = BDCMOTOR_TIM_PERIOD;                        // 定时器周期htimx_BDCMOTOR.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;              // 时钟分频htimx_BDCMOTOR.Init.RepetitionCounter = BDCMOTOR_TIM_REPETITIONCOUNTER;  // 重复计数器/* 初始化定时器比较输出环境 */HAL_TIM_PWM_Init(&htimx_BDCMOTOR);/* 定时器时钟源配置 */sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;       // 使用内部时钟源HAL_TIM_ConfigClockSource(&htimx_BDCMOTOR, &sClockSourceConfig);sBDTConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE ;sBDTConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW ;sBDTConfig.BreakState = TIM_BREAK_DISABLE ;sBDTConfig.DeadTime = 0 ;sBDTConfig.LockLevel = TIM_LOCKLEVEL_OFF ;sBDTConfig.OffStateIDLEMode= TIM_OSSI_DISABLE ;sBDTConfig.OffStateRunMode = TIM_OSSR_ENABLE ;HAL_TIMEx_ConfigBreakDeadTime(&htimx_BDCMOTOR,&sBDTConfig);/* 定时器比较输出配置 */sConfigOC.OCMode = TIM_OCMODE_PWM1;                  // 比较输出模式:PWM1模式sConfigOC.Pulse =  PWM_Duty;                         // 占空比sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;          // 输出极性sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;        // 互补通道输出极性sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;           // 快速模式sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;       // 空闲电平sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;     // 互补通道空闲电平HAL_TIM_PWM_ConfigChannel(&htimx_BDCMOTOR, &sConfigOC, TIM_CHANNEL_1);}

main.c

#include "stm32f4xx_hal.h"
#include "DCMotor/bsp_BDCMotor.h"
#include "key/bsp_key.h"
#include "usart/bsp_usartx.h"
#include "adc/bsp_adc.h"
#include "stdlib.h"#define ADC_Base      8                     // 取2的整数倍作为缓存区大小,得到14bits的ADC值#define ADC_BUFFER    1024                  // 采样数据缓存区 // 用于保存转换计算后的数值
__IO float ADC_VoltValue;
__IO float ADC_VoltBus; /* PF8电压的采集值 */// AD转换结果值
__IO int16_t ADC_ConvValueHex[ADC_BUFFER];  // AD转换结果
__IO int32_t ADCSum = 0;                    // ADC结果累加值
__IO int32_t AverSum = 0;                   // 平均值的累加值
__IO int32_t AverCnt = 0;                   // 平均值的计数器
__IO uint32_t OffsetCnt_Flag = 0 ;          // 偏差值的计数器标志
uint32_t  Motor_Dir = 0 ;                   // 电机方向
extern __IO  int32_t OffSetHex ;            // 偏差值
extern __IO uint32_t uwTick;__IO int32_t CaptureNumber = 0;      // 输入捕获数void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct;RCC_ClkInitTypeDef RCC_ClkInitStruct;__HAL_RCC_PWR_CLK_ENABLE();                                     // 使能PWR时钟__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  // 设置调压器输出电压级别1RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHzRCC_OscInitStruct.HSEState = RCC_HSE_ON;                        // 打开HSE RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    // 打开PLLRCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            // PLL时钟源选择HSERCC_OscInitStruct.PLL.PLLM = 8;                                 // 8分频MHzRCC_OscInitStruct.PLL.PLLN = 336;                               // 336倍频RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     // 2分频,得到168MHz主时钟RCC_OscInitStruct.PLL.PLLQ = 7;                                 // USB/SDIO/随机数产生器等的主PLL分频系数HAL_RCC_OscConfig(&RCC_OscInitStruct);RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHzRCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHzRCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHzRCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHzHAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次// HAL_RCC_GetHCLKFreq()/100000  10us中断一次// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                 // 配置并启动系统滴答定时器/* 系统滴答定时器时钟源 */HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);/* 系统滴答定时器中断优先级配置 */HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}int main(void)
{ /* 复位所有外设,初始化Flash接口和系统滴答定时器 */HAL_Init();/* 配置系统时钟 */SystemClock_Config();/* 串口初始化 */MX_USARTx_Init();/* 按键初始化 */KEY_GPIO_Init();/* 高级控制定时器初始化并配置PWM输出功能 */BDCMOTOR_TIMx_Init();/* 启动定时器 */HAL_TIM_Base_Start(&htimx_BDCMOTOR);/* 启动定时器通道和互补通道PWM输出 */PWM_Duty = 0;__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,PWM_Duty);  // 0%/* ADC-DMA 初始化 */HAL_Delay(100);MX_ADCx_Init();MX_DMA_Init();/* 启动AD转换并使能DMA传输和中断 */HAL_ADC_Start_DMA(&hadcx,(uint32_t*)ADC_ConvValueHex,ADC_BUFFER);  __HAL_DMA_DISABLE_IT(&hdma_adcx,DMA_IT_HT);  // 失能一些DMA中断标志__HAL_DMA_DISABLE_IT(&hdma_adcx,DMA_IT_TE);__HAL_DMA_DISABLE_IT(&hdma_adcx,DMA_IT_FE);__HAL_DMA_DISABLE_IT(&hdma_adcx,DMA_IT_DME);/* 无限循环 */while (1){/* 停止按钮 */if(KEY1_StateRead()==KEY_DOWN){HAL_TIM_PWM_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,0);  // 0%}if(KEY2_StateRead()==KEY_DOWN){HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);      // 停止输出}if(KEY3_StateRead()==KEY_DOWN)//加速{PWM_Duty += 200;if(PWM_Duty >=BDCMOTOR_DUTY_FULL)PWM_Duty = BDCMOTOR_DUTY_FULL;__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,PWM_Duty);}if(KEY4_StateRead()==KEY_DOWN)//减速{PWM_Duty -= 200;if(PWM_Duty <=0)PWM_Duty = 0;__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,PWM_Duty);}if(KEY5_StateRead()==KEY_DOWN)//  换方向{if(Motor_Dir){Motor_Dir = 0;HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);}else {Motor_Dir = 1;HAL_TIM_PWM_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);}}}
}/*** 函数功能: 系统滴答定时器中断回调函数* 输入参数: 无* 返 回 值: 无* 说    明: 每发生一次滴答定时器中断进入该回调函数一次*/
void HAL_SYSTICK_Callback(void)
{__IO int32_t ADC_Resul= 0;__IO float Volt_Result = 0;__IO float ADC_CurrentValue;/* 数据反馈周期是50ms,由于电流采集周期大约是 2ms,所以数据反馈周期最好不要低于2ms */if((uwTick % 50) == 0){ADC_Resul = AverSum/AverCnt ; // 求两次平均值(降低误差)/* 连续采样16次以后,以第17次作为偏差值 */OffsetCnt_Flag++;if(OffsetCnt_Flag >= 16){if(OffsetCnt_Flag == 16){OffSetHex = ADC_Resul;}OffsetCnt_Flag = 32;ADC_Resul -= OffSetHex;//减去偏差值}/* 计算电压值和电流值 */Volt_Result = ( (float)( (float)(ADC_Resul) * VOLT_RESOLUTION) );ADC_CurrentValue = (float)( (Volt_Result / GAIN) / SAMPLING_RES);/* 清空计数 */AverCnt = 0;AverSum = 0;      printf("Volt: %.1f mV -- Curr: %d mA\n",Volt_Result,(int32_t) (ADC_CurrentValue+17)); // 电机未启动有驱动板有17mA}
}/*** 函数功能: ADC转换完成回调函数* 输入参数: hadc:ADC外设设备句柄* 返 回 值: 无* 说    明: 中断一次的时间是1.479ms,利用过采样和求均值方法,提高分辨率*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{uint16_t ConvCnt = 0;int32_t ADConv = 0 ; /* ADC采集太快,需要先停止再处理数据 */HAL_ADC_Stop_DMA(hadc);/* 采集总线电压 */SetChannelAsRank1(hadc,ADC_VOLT_CHANNEL);HAL_ADC_Start(hadc);// ADC设置先电流采集,并且ADC通道数设置为1。电压采集与电流采集要轮流切换采集/* 取平均值 */for(ConvCnt = 0; ConvCnt < (ADC_BUFFER ); ConvCnt++){ADConv += ((int32_t)ADC_ConvValueHex[ConvCnt]);}/* 计算平均值,采样数据设置为2的整数倍,获得14bitsADC值*/ADConv >>= ADC_Base;/* 累加采样结果并记录采样次数*/AverSum += ADConv;AverCnt++;HAL_ADC_Stop(hadc);SetChannelAsRank1(hadc,ADC_CURRENT_CHANNEL);HAL_ADC_Start_DMA(hadc,(uint32_t*)ADC_ConvValueHex,ADC_BUFFER);
}/*** 函数功能: ADC看门狗中断回调函数* 输入参数: ADC句柄* 返 回 值: 无* 说    明: ADC窗口看门狗,检测到电压过低或者过高的时候就调用这个函数,停止输出.*/
void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc)
{/* 使能电机控制引脚 */static uint8_t i = 0;i++;if(ADC_VoltBus > VOLT_LIMIT_MIN  && ADC_VoltBus < VOLT_LIMIT_MAX)i = 0 ;else if(i>=6){SHUTDOWN_MOTOR();HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);PWM_Duty = 0;
//    ADC_VoltBus = (float)ADC_VoltBus * VOLTBUS_RESOLUTION;// ADC_VoltBus是在中断响应函数中读取的adc值printf("Bus Voltage is out of range!!\n");printf("Please Reset the Target!\n");while(1);  // PF8 的电压不在范围内就会触发看门狗 然后就一直死循环}
}void ADC_OVP_IRQHandler(void)
{/* 读取总线电压值 */ADC_VoltBus = HAL_ADC_GetValue(&hadcx);  // PF8的电压采集HAL_ADC_IRQHandler(&hadcx);}

其实PF8(电压采集在超出范围启动看门狗,然后在看门狗中断里采集电压。不超过不会采集。

2.过流保护

Current_amp点的电压是求采样电流的大小。先设置一个最大电流,如果采样电流超过这个最大电流就停止PWM输出从而电机也停止起到保护作用。采样电流是否在安全电流取决Current_amp点的电压,当Current_amp电压大于Verf电压比较器输出(接在SD引脚))低电平。

ADC代码,timer(产生互补PWM)跟上个列子一样直接看main.c


#include "stm32f4xx_hal.h"
#include "DCMotor/bsp_BDCMotor.h"
#include "key/bsp_key.h"
#include "encoder/bsp_encoder.h"
#include "usart/bsp_usartx.h"
#include "adc/bsp_adc.h"
#include "DCMotor/bsp_BDCMotor.h"
#include "stdlib.h"
/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
#define ADC_Base      8                      // 取2的整数倍作为缓存区大小,得到14bits的ADC
#define ADC_BUFFER    1024                      // 采样数据缓存区#define CURRENT_MAX   400.0f         // 最大电流值 400 mA/* 私有变量 ------------------------------------------------------------------*/// 用于保存转换计算后的数值
__IO float ADC_VoltValue;
__IO float ADC_VoltBus;
// AD转换结果值
__IO int16_t ADC_ConvValueHex[ADC_BUFFER];  // AD转换结果
__IO int32_t ADCSum = 0;                    // ADC结果累加值
__IO int32_t AverSum = 0;                   // 平均值的累加值
__IO int32_t AverCnt = 0;                   // 平均值的计数器
__IO uint32_t OffsetCnt_Flag = 0 ;          // 偏差值的计数器标志
uint32_t  Motor_Dir = 0 ;                   // 电机方向
extern __IO  int32_t OffSetHex ;            // 偏差值
extern __IO uint32_t uwTick;
static __IO uint32_t OverCurCount;          // 过流次数记录/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/
/*** 函数功能: 系统时钟配置* 输入参数: 无* 返 回 值: 无* 说    明: 无*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct;RCC_ClkInitTypeDef RCC_ClkInitStruct;__HAL_RCC_PWR_CLK_ENABLE();                                     // 使能PWR时钟__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  // 设置调压器输出电压级别1RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHzRCC_OscInitStruct.HSEState = RCC_HSE_ON;                        // 打开HSERCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    // 打开PLLRCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            // PLL时钟源选择HSERCC_OscInitStruct.PLL.PLLM = 8;                                 // 8分频MHzRCC_OscInitStruct.PLL.PLLN = 336;                               // 336倍频RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     // 2分频,得到168MHz主时钟RCC_OscInitStruct.PLL.PLLQ = 7;                                 // USB/SDIO/随机数产生器等的主PLL分频系数HAL_RCC_OscConfig(&RCC_OscInitStruct);RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHzRCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHzRCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHzRCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHzHAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次// HAL_RCC_GetHCLKFreq()/100000   10us中断一次// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                 // 配置并启动系统滴答定时器/* 系统滴答定时器时钟源 */HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);/* 系统滴答定时器中断优先级配置 */HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}int main(void)
{/* 复位所有外设,初始化Flash接口和系统滴答定时器 */HAL_Init();/* 配置系统时钟 */SystemClock_Config();/* 串口初始化 */MX_USARTx_Init();/* 按键初始化 */KEY_GPIO_Init();/* 高级控制定时器初始化并配置PWM输出功能 */BDCMOTOR_TIMx_Init();/* 启动定时器 */HAL_TIM_Base_Start(&htimx_BDCMOTOR);/* 启动定时器通道和互补通道PWM输出 */PWM_Duty = 100;__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,PWM_Duty);  // 0%/* ADC-DMA 初始化 */HAL_Delay(100);MX_ADCx_Init();MX_DMA_Init();/* 启动AD转换并使能DMA传输和中断 */HAL_ADC_Start_DMA(&hadcx,(uint32_t*)ADC_ConvValueHex,ADC_BUFFER);__HAL_DMA_DISABLE_IT(&hdma_adcx,DMA_IT_HT);__HAL_DMA_DISABLE_IT(&hdma_adcx,DMA_IT_TE);__HAL_DMA_DISABLE_IT(&hdma_adcx,DMA_IT_FE);__HAL_DMA_DISABLE_IT(&hdma_adcx,DMA_IT_DME);/* 无限循环 */while (1){/* 启动按钮 */if(KEY1_StateRead()==KEY_DOWN){HAL_TIM_PWM_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,0);  // 0%}if(KEY2_StateRead()==KEY_DOWN){HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,0);  // 0%}if(KEY3_StateRead()==KEY_DOWN)//加速{PWM_Duty += 200;if(PWM_Duty >=BDCMOTOR_DUTY_FULL)PWM_Duty = BDCMOTOR_DUTY_FULL;__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,PWM_Duty);}if(KEY4_StateRead()==KEY_DOWN)//减速{PWM_Duty -= 200;if(PWM_Duty <=0)PWM_Duty = 0;__HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,PWM_Duty);}if(KEY5_StateRead()==KEY_DOWN)    //  换方向{if(Motor_Dir){Motor_Dir = 0;HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);}else{Motor_Dir = 1;HAL_TIM_PWM_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);}}}
}void HAL_SYSTICK_Callback(void)
{__IO int32_t ADC_Resul= 0;__IO float Volt_Result = 0;__IO float ADC_CurrentValue;/* 数据反馈周期是50ms,由于电流采集周期大约是 2ms,所以数据反馈周期最好不要低于2ms */if((uwTick % 50) == 0){ADC_Resul = AverSum/AverCnt ;/* 连续采样16次以后,以第17次作为偏差值 */OffsetCnt_Flag++;if(OffsetCnt_Flag >= 16){if(OffsetCnt_Flag == 16){OffSetHex = ADC_Resul;}OffsetCnt_Flag = 32;ADC_Resul -= OffSetHex;//减去偏差值}/* 计算电压值和电流值 */Volt_Result = ( (float)( (float)(ADC_Resul) * VOLT_RESOLUTION) );ADC_CurrentValue = (float)( (Volt_Result / GAIN) / SAMPLING_RES);printf("Volt: %.2f -- Cur: %.2f mA\n",Volt_Result,ADC_CurrentValue);/* 清空计数 */AverCnt = 0;AverSum = 0;/* 过流保护 */if(OffsetCnt_Flag >= 32 ){if(ADC_CurrentValue >= CURRENT_MAX )  // 检测到五次如果超过最大电流就关掉PWM输出{OverCurCount++;if(OverCurCount >= 5){printf("Over Current %.2f \n",ADC_CurrentValue);printf("Please reset the target!!\n");SHUTDOWN_MOTOR();HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);OverCurCount = 0;while(1);}}elseOverCurCount = 0;}}
}/*** 函数功能: ADC转换完成回调函数* 输入参数: hadc:ADC外设设备句柄* 返 回 值: 无* 说    明: 中断一次的时间是1.479ms,利用过采样和求均值方法,提高分辨率*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{uint16_t ConvCnt = 0;int32_t ADConv = 0 ;/* ADC采集太快,需要先停止再处理数据 */HAL_ADC_Stop_DMA(hadc);/* 采集总线电压 */SetChannelAsRank1(hadc,ADC_VOLT_CHANNEL);HAL_ADC_Start(hadc);/* 取平均 */for(ConvCnt =0; ConvCnt < ADC_BUFFER ; ConvCnt++){ADConv += ((int32_t)ADC_ConvValueHex[ConvCnt]);}/* 计算平均值,采样数据设置为2的整数倍,获得14bitsADC值*/ADConv >>= ADC_Base;/* 累加采样结果并记录采样次数*/AverSum += ADConv;AverCnt ++;HAL_ADC_Stop(hadc);SetChannelAsRank1(hadc,ADC_CURRENT_CHANNEL);HAL_ADC_Start_DMA(hadc,(uint32_t*)ADC_ConvValueHex,ADC_BUFFER);}/*** 函数功能: ADC看门狗中断回调函数* 输入参数: ADC句柄* 返 回 值: 无* 说    明: ADC窗口看门狗,检测到电压过低或者过高的时候就调用这个函数,停止输出.*/
void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc)
{/* 使能电机控制引脚 */static uint8_t i = 0;i++;if(ADC_VoltBus > VOLT_LIMIT_MIN  && ADC_VoltBus < VOLT_LIMIT_MAX)i = 0 ;else if(i>=6){SHUTDOWN_MOTOR();HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);PWM_Duty = 0;
//    ADC_VoltBus = (float)ADC_VoltBus * VOLTBUS_RESOLUTION;// ADC_VoltBus是在中断响应函数中读取的adc值printf("Bus Voltage is out of range!!\n");printf("Please Reset the Target!\n");while(1);}
}void ADC_OVP_IRQHandler(void)
{/* 读取总线电压值 */ADC_VoltBus = HAL_ADC_GetValue(&hadcx);HAL_ADC_IRQHandler(&hadcx);}

跟上个列子差不多只是加了限流保护。

直流有刷电机的电流采集及过流过压保护相关推荐

  1. 直流有刷电机电流采集基于STM32F302R8+X-NUCLEO-IHM07M1

    文章目录 前言 一.驱动板X-NUCLEO-IHM07M1电流采集电路 二.STM32F302R8+X-NUCLEO-IHM07M1直流电机电流采集 2.1.功能需求 2.2.硬件设计 2.3.软件设 ...

  2. 直流有刷电机转速、电流双闭环调速系统及Matlab/Simulink仿真分析

    文章目录 前言 一.降压斩波电路(Buck Chopper) 二.转速.电流双闭环直流调速系统 三.Matlab/Simulink仿真 3.1.仿真电路分析 3.2.仿真电路结果分析 总结 前言 变压 ...

  3. 直流有刷电机电流闭环控制基于STM32F302R8+X-NUCLEO-IHM07M1

    文章目录 前言 一.STM32F302R8+X-NUCLEO-IHM07M1直流电机的电流闭环控制 1.1.功能需求 1.2.硬件设计 1.3.软件设计 1.3.1.底层配置 1.3.2.应用层开发 ...

  4. STM32电压电流采集与检测方案(直流)

    成熟STM32电压电流采集与检测方案(直流),PCB,KEIL源码,原理图,设计说明 YID:719672094519878SiuthwindWK

  5. 一文帮你了解小型直流有刷电机内部结构

    简 介: 对于直流有刷电机内部结构,本文整理了网络上的动图资料,以备今后动图混编使用. 关键词: DC,有刷电机 #mermaid-svg-A40sSHLYHlDlmzyt {font-family: ...

  6. proteus如何添加stm32_【Proteus】单片机H桥驱动24V直流有刷电机

    前言 一般有关直流有刷电机的仿真都是直接高低电平驱动,或者ULN2003,这种电路是只能驱动小电压小功率的电机的,如果碰到电压稍高一些,电流大一些的电机,2003驱动是驱动不起来的,这时候对于大电流的 ...

  7. 直流有刷电机驱动项目需求分析

    文章来源:直流有刷电机驱动项目需求分析,超实用! 一. 项目名称:<直流电机驱动器设计> 二. 项目需求分析: 我们想设计一款直流有刷电机驱动器,那么在设计驱动器之前,我们需要明确驱动器的 ...

  8. 直流有刷电机并联小电容作用分析

    直流有刷电机驱动中电机旁并联小电容作用的个人分析,讲得不对或不清楚的地方,欢迎在评论区中指出. 下图为直流有刷电机驱动的一个简单电路示意图,本文主要是讨论电机旁并联电容的作用,并没有画出完整的驱动电路 ...

  9. 【电机应用控制】——直流有刷电机驱动板/编码器介绍PID算法实操代码思路

    目录 前言 一.电机简介 二.直流有刷电机 1.基本知识 2.直流有刷驱动板 3.编码器介绍 三.PID算法 四.实操思路 1.单环控制 2.双环控制 3.三环控制 拓:闭环死区 总结 前言 声明:学 ...

最新文章

  1. C/C++ 中左值和右值的区别
  2. C# Byte数组与Int16数组之间的转换
  3. 移动语义-右值引用-完美转发-万字长文让你一探究竟
  4. php方法重载方法重写_PHP面向对象之旅:方法覆盖
  5. 分享50佳高质量免费按钮图标资源(上篇)[zz]
  6. poj 2594 Treasure Exploration 最小路径覆盖
  7. word中装订线位置_Word操作技巧:Word文档双面打印全攻略,解决打印难题
  8. 【深入理解JVM笔记】什么是元数据?
  9. python 微信聊天机器人_python操作微信自动发消息的实现(微信聊天机器人)
  10. 如何以CustomValidator搭配jQuery AJAX进行Server端验证(转)
  11. rainmeter使用教程_如何使用Rainmeter自定义Windows桌面
  12. java ssm商城_SSM网上购物商城系统
  13. FME数据转换教程——MapGIS .WL/WP 转ArcGIS .Shp
  14. MCSA 70-740 windows 安装和部署工具汇总学习
  15. arcGis for js 3D marker
  16. 第3章 Hive数据类型
  17. python更改图片存储大小_python不改变图片尺寸压缩到指定大小
  18. MAC 软件安装打不开解决办法
  19. 原生JS快速实现拖放(drag and drop)效果
  20. pytest文档56-插件打包上传到 pypi 库

热门文章

  1. Filter过滤器是什么?
  2. 计算机控制接口板设计,计算机控制实验报告(过程接口板设计)
  3. Windows Server 系统怎么显示桌面图标“这台电脑”。
  4. 数据结构第二版(朱昌杰版)习题2答案
  5. django admin 账户 输入汉字会报错 'ascii' codec can't encode character u'\u4eba' in position 0: ordinal not i
  6. 登峰造极,师出造化,Pytorch人工智能AI图像增强框架ControlNet绘画实践,基于Python3.10
  7. 适合Web前端程序员发展的二三线城市有哪些?
  8. 数字化办公,需要这个免费低代码平台来助力
  9. python提取图片感兴趣区域_Python+OpenCV感兴趣区域ROI提取方法
  10. Jester5k 数据集推荐系统模型预测R语言实现