基于STM32与FreeRTOS的四轴机械臂项目

目录

一、项目介绍

二、前期准备

1.硬件准备

2.开发环境

3.CubeMX配置

三、裸机各种模块测试

1.舵机模块

2.蓝牙模块

3.按键摇杆传感器模块和旋钮电位器模块

4.OLED模块

5.W25Q128模块

四、裸机三种控制测试

1.摇杆控制

2.示教器控制

3.蓝牙控制

五、裸机与FreeRTOS

1.CubeMX配置

2.移植裸机三种控制代码

六、项目演示视频


一、项目介绍

        该项目是基于FreeRTOS实时操作系统,主控为 STM32F103C8T6 开发板 ,机械臂为四轴分别被四个舵机控制。本项目实现了 3 种控制方法,分别为摇杆控制示教器控制串口蓝牙控制,采用8路ADC采集按键摇杆传感器和旋钮电位器的模拟量并由DMA搬运数据,可自制手机蓝牙APP或者直接使用官方手机蓝牙助手作为上位机,USART串口蓝牙实时收发信息,IIC驱动OLED屏幕实时显示机械臂移动张爪夹爪信息,人为控制抓取目标物。

        扩展:后续可以通过二维数组或者链表实现存储动作,通过SPI驱动W25Q128模块进行动作记忆扩容,即可以录制上百组动作,还可以附加树莓派等开发板进行视觉抓取开发,等以后有时间我再把扩展功能一起实现呈现给大家。

二、前期准备

1.硬件准备

本项目可用步进电机和驱动器作为支撑,以便提高项目扩展性,但在这里我直接用四个舵机实现。

首先你可以自己建模3D打印四轴机械臂模型,也可以直接去网上购买一套成品套件。然后需要四个舵机控制机械臂,型号无所谓,控制起来是一样的,注意需要是180度的角度型舵机,而不是360度的速度型舵机。

然后需要购买两个按键摇杆传感器实现摇杆控制,购买蓝牙模块实现串口蓝牙控制,购买四个旋钮电位器实现示教器控制。

硬件清单:

  • 四轴机械臂模型
  • 四个舵机
  • 两个按键摇杆传感器
  • HC系列蓝牙串口模块
  • 四个旋钮电位器
  • IIC协议OLED屏幕
  • SPI协议W25Q128模块

2.开发环境

单片机型号为STM32F103C8T6,开发环境为STM32CubeMX和Keil5,蓝牙控制需要手机下载蓝牙助手,在这里我下载的是官方给的HC蓝牙助手,(需要蓝牙调试助手的可以私信作者提供

STM32F103C8T6原理图:

3.CubeMX配置

我用STM32CubeMX配置如下,仅供参考:

RCC:配置外部高速晶振        

SYS:Debug设置成Serial Wire

      ADC:打开8个通道

   

DMA:搬运ADC数据

TIM2:PWM输出:选用799*1799,这样可以把舵机有效的 0.5~2.5ms / 20ms 这个区间分成180段,对应0~180度。

usart:设置波特率为9600,因为蓝牙模块默认波特率为9600,开启NVIC中断接收信息

I2C:用来显示OLED模块

时钟树配置:

最后点击 generate code 生成代码

三、裸机各种模块测试

硬件模块接线:

  • 四个舵机分别接CH1_A15,CH2_B3,CH3_B10, CH4_B11
  • 蓝牙模块TX接RX,RX接TX
  • 两个按键摇杆传感器分别接 PA0 到 PA3 对应 IN0 到 IN3
  • 四个旋钮电位器分别接 PA4 到 PA7 对应 IN4 到 IN7
  • OLED模块 SCL 和 SDA 分别接 PB6 和 PB7
  • W25Q128模块自行扩展

1.舵机模块

舵机模块测试可以看之前我写过的文章,链接如下:

SG90舵机模块测试

然后对四个舵机进行函数封装,舵机初始化角度跟运动角度自行配置调试:

//舵机A,夹爪	CH4_B11  45-135 张开闭合 初始化135
void sg90_A()
{if(adc_dma[3] > 4000 && angle[3] < 135){angle[3]++;}else if(adc_dma[3] <1000 && angle[3] > 45){angle[3]--;}
}//舵机B,上下	CH3_B10  45-180    初始化180
void sg90_B()
{if(adc_dma[2] <1000 && angle[2] < 180){angle[2]++;}else if(adc_dma[2] > 4000 && angle[2] > 45){angle[2]--;}
}//舵机C,前后	CH2_B3  45-180  前后   初始化45
void sg90_C()
{if(adc_dma[1] <1000 && angle[1] < 180){angle[1]++;}else if(adc_dma[1] > 4000 && angle[1] > 45){angle[1]--;}
}//舵机D,底座	CH1_A15 45-135 左到右   初始化45
void sg90_D()
{if(adc_dma[0] <1000 && angle[0] < 135){angle[0]++;}else if(adc_dma[0] > 4000 && angle[0] > 45){angle[0]--;}
}//开启4路PWMHAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);

2.蓝牙模块

可以使用HC-05(下图蓝色)或者HC-08(下图绿色),这两种我都测试通过。

有关蓝牙模块具体使用跟AT指令可以看以下两篇博客,链接如下:

HC-05介绍 和 HC-08介绍

使用串口中断测试收发的数据,串口重映射设置:

重映射代码:

int fputc(int ch, FILE *f)
{      unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);  return ch;
}

中断接收信息代码:

// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{// 判断中断是由哪个串口触发的if(huart->Instance == USART1){// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)if((UART1_RX_STA & 0x8000) == 0){// 如果已经收到了 0x0d (回车),if(UART1_RX_STA & 0x4000){// 则接着判断是否收到 0x0a (换行)if(buf == 0x0a)// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1UART1_RX_STA |= 0x8000;else// 否则认为接收错误,重新开始UART1_RX_STA = 0;}else	// 如果没有收到了 0x0d (回车){//则先判断收到的这个字符是否是 0x0d (回车)if(buf == 0x0d){// 是的话则将 bit14 位置为1UART1_RX_STA |= 0x4000;}else{// 否则将接收到的数据保存在缓存数组里UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;UART1_RX_STA++;// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收if(UART1_RX_STA > UART1_REC_LEN - 1)UART1_RX_STA = 0;}}}// 重新开启中断HAL_UART_Receive_IT(&huart1, &buf, 1);}
}

在main.c函数中开启中断测试,然后打开蓝牙助手:

  // 开启接收中断HAL_UART_Receive_IT(&huart1, &buf, 1);while (1){//判断判断串口是否接收完成if(UART1_RX_STA & 0x8000){if(!strcmp((const char *)UART1_RX_Buffer, "open")){printf("张爪\r\n");}else if (!strcmp((const char *)UART1_RX_Buffer, "close")){printf("夹爪\r\n");}else{if(UART1_RX_Buffer[0] != '\0')printf("指令发送错误:%s\r\n", UART1_RX_Buffer);}printf("\r\n");// 重新开始下一次接收UART1_RX_STA = 0;}HAL_Delay(40);}

手机打开蓝牙助手,记得把发送新行勾上,不然中断接收不到数据

3.按键摇杆传感器模块和旋钮电位器模块

这里用两个按键摇杆传感器分别接收 IN0~3 ADC的模拟量,用四个旋钮电位器分别接收 IN4~7 ADC的模拟量

利用DMA传输接收到的ADC的值通过串口打印进行调试,通过按钮和电位器控制角度

代码示例:

uint16_t adc_dma[8];//DMA搬运的ADC采集值uint8_t angle[4] = {45,45,180,135};//舵机角度uint8_t cnt = 0;//计数用,定时串口打印信息//根据输入的0~180角度获取对应pwm占空比参数
uint8_t Angle(uint8_t pwm_pulse)
{return pwm_pulse + 44;
}//开始ADC和DMA采集HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,8);while (1){sg90_A();sg90_B();sg90_C();sg90_D();//输出PWM波使舵机运动__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));cnt++;//计数,每循环一次+1if(cnt>= 50)//循环50次,每次20ms,即一共1s。每一秒发送一次数据{printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);printf("adc_dma = {%d, %d, %d, %d, %d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3],adc_dma[4],adc_dma[5],adc_dma[6],adc_dma[7]);cnt = 0;}HAL_Delay(20);//每20ms循环一次}

4.OLED模块

在这里我们可以用取模软件显示机械臂动作,张爪夹爪,向左向右,向前向后,向上向下,也可以后续自行扩展其他内容,例如角度和控制模式等。

oled介绍可看我之前写过的:OLED取模生成文字图片

代码实现如下:

/*--  文字:  向  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char x1[16] = {0x00,0xF8,0x08,0x08,0x0C,0xCA,0x49,0x48,0x48,0xC8,0x08,0x08,0x08,0xF8,0x00,0x00};
char x2[16] = {0x00,0xFF,0x00,0x00,0x00,0x1F,0x08,0x08,0x08,0x1F,0x00,0x40,0x80,0x7F,0x00,0x00};/*--  文字:  前  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char f1[16] = {0x08,0x08,0xE8,0x29,0x2E,0x28,0xE8,0x08,0x08,0xC8,0x0C,0x0B,0xE8,0x08,0x08,0x00};
char f2[16] = {0x00,0x00,0xFF,0x09,0x49,0x89,0x7F,0x00,0x00,0x0F,0x40,0x80,0x7F,0x00,0x00,0x00};/*--  文字:  后  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char b1[16] = {0x00,0x00,0x00,0xFC,0x24,0x24,0x24,0x24,0x22,0x22,0x22,0x23,0x22,0x20,0x20,0x00};
char b2[16] = {0x40,0x20,0x18,0x07,0x00,0xFE,0x42,0x42,0x42,0x42,0x42,0x42,0xFE,0x00,0x00,0x00};/*--  文字:  上  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char u1[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,0x00};
char u2[16] = {0x40,0x40,0x40,0x40,0x40,0x40,0x7F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00};/*--  文字:  下  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char d1[16] = {0x02,0x02,0x02,0x02,0x02,0x02,0xFE,0x02,0x02,0x42,0x82,0x02,0x02,0x02,0x02,0x00};
char d2[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x01,0x06,0x00,0x00,0x00};/*--  文字:  左  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char l1[16] = {0x08,0x08,0x08,0x08,0x88,0x78,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00};
char l2[16] = {0x20,0x10,0x48,0x46,0x41,0x41,0x41,0x41,0x7F,0x41,0x41,0x41,0x41,0x40,0x40,0x00};/*--  文字:  右  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char r1[16] = {0x08,0x08,0x08,0x08,0xC8,0x38,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00};
char r2[16] = {0x08,0x04,0x02,0x01,0xFF,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0xFF,0x00,0x00,0x00};/*--  文字:  张  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char o1[16] = {0x02,0xE2,0x22,0x22,0x3E,0x80,0x80,0xFF,0x80,0xA0,0x90,0x88,0x86,0x80,0x80,0x00};
char o2[16] = {0x00,0x43,0x82,0x42,0x3E,0x00,0x00,0xFF,0x40,0x21,0x06,0x08,0x10,0x20,0x40,0x00};/*--  文字:  夹  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char c1[16] = {0x00,0x08,0x08,0x28,0x48,0x08,0x08,0xFF,0x08,0x08,0x48,0x28,0x08,0x08,0x00,0x00};
char c2[16] = {0x81,0x81,0x41,0x41,0x21,0x11,0x0D,0x03,0x0D,0x11,0x21,0x41,0x41,0x81,0x81,0x00};/*--  文字:  爪  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char z1[16] = {0x00,0x00,0x00,0xFC,0x04,0x04,0xFC,0x04,0x02,0x02,0xFE,0x03,0x02,0x00,0x00,0x00};
char z2[16] = {0x80,0x60,0x18,0x07,0x00,0x00,0x7F,0x00,0x00,0x00,0x01,0x0E,0x30,0x40,0x80,0x00};void Oled_Write_Cmd(uint8_t dataCmd)
{HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,&dataCmd, 1, 0xff);
}void Oled_Write_Data(uint8_t dataData)
{HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,&dataData, 1, 0xff);
}void Oled_Init(void){Oled_Write_Cmd(0xAE);//--display offOled_Write_Cmd(0x00);//---set low column addressOled_Write_Cmd(0x10);//---set high column addressOled_Write_Cmd(0x40);//--set start line address  Oled_Write_Cmd(0xB0);//--set page addressOled_Write_Cmd(0x81); // contract controlOled_Write_Cmd(0xFF);//--128   Oled_Write_Cmd(0xA1);//set segment remap Oled_Write_Cmd(0xA6);//--normal / reverseOled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)Oled_Write_Cmd(0x3F);//--1/32 dutyOled_Write_Cmd(0xC8);//Com scan directionOled_Write_Cmd(0xD3);//-set display offsetOled_Write_Cmd(0x00);//Oled_Write_Cmd(0xD5);//set osc divisionOled_Write_Cmd(0x80);//Oled_Write_Cmd(0xD8);//set area color mode offOled_Write_Cmd(0x05);//Oled_Write_Cmd(0xD9);//Set Pre-Charge PeriodOled_Write_Cmd(0xF1);//Oled_Write_Cmd(0xDA);//set com pin configuartionOled_Write_Cmd(0x12);//Oled_Write_Cmd(0xDB);//set VcomhOled_Write_Cmd(0x30);//Oled_Write_Cmd(0x8D);//set charge pump enableOled_Write_Cmd(0x14);//Oled_Write_Cmd(0xAF);//--turn on oled panel		
}void Oled_Screen_Clear(void){unsigned char i,n;Oled_Write_Cmd (0x20);                    //set memory addressing modeOled_Write_Cmd (0x02);                    //page addressing modefor(i=0;i<8;i++){Oled_Write_Cmd(0xb0+i);               //Oled_Write_Cmd(0x00);                 //Oled_Write_Cmd(0x10);                 //for(n=0;n<128;n++)Oled_Write_Data(0x00); 			}	
}void Oled_Show_open()
{unsigned char  i;Oled_Init();// 选择一个位置确认页寻址模式Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();// 选择PAGE0   1011 0000 0xB0			Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(o1[i]);}for(i=0;i<16;i++){Oled_Write_Data(z1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(o2[i]);}for(i=0;i<16;i++){Oled_Write_Data(z2[i]);}}void Oled_Show_close()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(c1[i]);}for(i=0;i<16;i++){Oled_Write_Data(z1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(c2[i]);}for(i=0;i<16;i++){Oled_Write_Data(z2[i]);}}void Oled_Show_up()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(u1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(u2[i]);}}void Oled_Show_down()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(d1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(d2[i]);}}void Oled_Show_left()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(l1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(l2[i]);}}void Oled_Show_right()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(r1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(r2[i]);}}void Oled_Show_front()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(f1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(f2[i]);}}void Oled_Show_behind()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(b1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(b2[i]);}}while (1){Oled_Show_open();HAL_Delay(1000);Oled_Show_close();HAL_Delay(1000);Oled_Show_up();HAL_Delay(1000);Oled_Show_down();HAL_Delay(1000);Oled_Show_left();HAL_Delay(1000);Oled_Show_right();HAL_Delay(1000);Oled_Show_front();HAL_Delay(1000);	Oled_Show_behind();HAL_Delay(1000);}

5.W25Q128模块

W25Q128模块测试可看我之前写过的文章然后自行扩展:W25Q128模块测试

四、裸机三种控制测试

首先我在Core文件夹里面的Src和Inc里分别创建pwm和oled的.c和.h文件

1.摇杆控制

摇杆控制在这里我用上面讲到的两个按键摇杆传感器模块实现,只需要开启四路ADC采集,两个按键摇杆传感器分别接收 IN0~3 ADC的模拟量,其中需要结合实际操作控制舵机初始化角度和运动过程中的角度变化。

代码示例:

pwm.c

extern uint16_t adc_dma[4];//DMA搬运的ADC采集值extern int8_t angle[4] = {45,45,180,135};//舵机角度//根据输入的0~180角度获取对应pwm占空比参数
unsigned char Angle(unsigned char pwm_pulse)
{return pwm_pulse + 44;
}//舵机A,夹爪	CH4_B11  45-135 张开闭合 初始化135
void sg90_A()
{if(adc_dma[3] > 4000 && angle[3] < 135){angle[3]++;}else if(adc_dma[3] <1000 && angle[3] > 45){angle[3]--;}
}//舵机B,上下	CH3_B10  45-180    初始化180
void sg90_B()
{if(adc_dma[2] <1000 && angle[2] < 180){angle[2]++;}else if(adc_dma[2] > 4000 && angle[2] > 45){angle[2]--;}
}//舵机C,前后	CH2_B3  45-180  前后   初始化45
void sg90_C()
{if(adc_dma[1] <1000 && angle[1] < 180){angle[1]++;}else if(adc_dma[1] > 4000 && angle[1] > 45){angle[1]--;}
}//舵机D,底座	CH1_A15 45-135 左到右   初始化45
void sg90_D()
{if(adc_dma[0] <1000 && angle[0] < 135){angle[0]++;}else if(adc_dma[0] > 4000 && angle[0] > 45){angle[0]--;}
}

pwm.h

#ifndef __PWM_H__
#define __PWM_H__unsigned char Angle(unsigned char pwm_pulse);void sg90_A(void);void sg90_B(void);void sg90_C(void);void sg90_D(void);#endif

oled.c

#include "main.h"
#include "i2c.h"
#include "oled.h"/*--  文字:  向  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char x1[16] = {0x00,0xF8,0x08,0x08,0x0C,0xCA,0x49,0x48,0x48,0xC8,0x08,0x08,0x08,0xF8,0x00,0x00};
char x2[16] = {0x00,0xFF,0x00,0x00,0x00,0x1F,0x08,0x08,0x08,0x1F,0x00,0x40,0x80,0x7F,0x00,0x00};/*--  文字:  前  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char f1[16] = {0x08,0x08,0xE8,0x29,0x2E,0x28,0xE8,0x08,0x08,0xC8,0x0C,0x0B,0xE8,0x08,0x08,0x00};
char f2[16] = {0x00,0x00,0xFF,0x09,0x49,0x89,0x7F,0x00,0x00,0x0F,0x40,0x80,0x7F,0x00,0x00,0x00};/*--  文字:  后  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char b1[16] = {0x00,0x00,0x00,0xFC,0x24,0x24,0x24,0x24,0x22,0x22,0x22,0x23,0x22,0x20,0x20,0x00};
char b2[16] = {0x40,0x20,0x18,0x07,0x00,0xFE,0x42,0x42,0x42,0x42,0x42,0x42,0xFE,0x00,0x00,0x00};/*--  文字:  上  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char u1[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,0x00};
char u2[16] = {0x40,0x40,0x40,0x40,0x40,0x40,0x7F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00};/*--  文字:  下  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char d1[16] = {0x02,0x02,0x02,0x02,0x02,0x02,0xFE,0x02,0x02,0x42,0x82,0x02,0x02,0x02,0x02,0x00};
char d2[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x01,0x06,0x00,0x00,0x00};/*--  文字:  左  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char l1[16] = {0x08,0x08,0x08,0x08,0x88,0x78,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00};
char l2[16] = {0x20,0x10,0x48,0x46,0x41,0x41,0x41,0x41,0x7F,0x41,0x41,0x41,0x41,0x40,0x40,0x00};/*--  文字:  右  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char r1[16] = {0x08,0x08,0x08,0x08,0xC8,0x38,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00};
char r2[16] = {0x08,0x04,0x02,0x01,0xFF,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0xFF,0x00,0x00,0x00};/*--  文字:  张  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char o1[16] = {0x02,0xE2,0x22,0x22,0x3E,0x80,0x80,0xFF,0x80,0xA0,0x90,0x88,0x86,0x80,0x80,0x00};
char o2[16] = {0x00,0x43,0x82,0x42,0x3E,0x00,0x00,0xFF,0x40,0x21,0x06,0x08,0x10,0x20,0x40,0x00};/*--  文字:  夹  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char c1[16] = {0x00,0x08,0x08,0x28,0x48,0x08,0x08,0xFF,0x08,0x08,0x48,0x28,0x08,0x08,0x00,0x00};
char c2[16] = {0x81,0x81,0x41,0x41,0x21,0x11,0x0D,0x03,0x0D,0x11,0x21,0x41,0x41,0x81,0x81,0x00};/*--  文字:  爪  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char z1[16] = {0x00,0x00,0x00,0xFC,0x04,0x04,0xFC,0x04,0x02,0x02,0xFE,0x03,0x02,0x00,0x00,0x00};
char z2[16] = {0x80,0x60,0x18,0x07,0x00,0x00,0x7F,0x00,0x00,0x00,0x01,0x0E,0x30,0x40,0x80,0x00};void Oled_Write_Cmd(uint8_t dataCmd)
{HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,&dataCmd, 1, 0xff);
}void Oled_Write_Data(uint8_t dataData)
{HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,&dataData, 1, 0xff);
}void Oled_Init(void){Oled_Write_Cmd(0xAE);//--display offOled_Write_Cmd(0x00);//---set low column addressOled_Write_Cmd(0x10);//---set high column addressOled_Write_Cmd(0x40);//--set start line address  Oled_Write_Cmd(0xB0);//--set page addressOled_Write_Cmd(0x81); // contract controlOled_Write_Cmd(0xFF);//--128   Oled_Write_Cmd(0xA1);//set segment remap Oled_Write_Cmd(0xA6);//--normal / reverseOled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)Oled_Write_Cmd(0x3F);//--1/32 dutyOled_Write_Cmd(0xC8);//Com scan directionOled_Write_Cmd(0xD3);//-set display offsetOled_Write_Cmd(0x00);//Oled_Write_Cmd(0xD5);//set osc divisionOled_Write_Cmd(0x80);//Oled_Write_Cmd(0xD8);//set area color mode offOled_Write_Cmd(0x05);//Oled_Write_Cmd(0xD9);//Set Pre-Charge PeriodOled_Write_Cmd(0xF1);//Oled_Write_Cmd(0xDA);//set com pin configuartionOled_Write_Cmd(0x12);//Oled_Write_Cmd(0xDB);//set VcomhOled_Write_Cmd(0x30);//Oled_Write_Cmd(0x8D);//set charge pump enableOled_Write_Cmd(0x14);//Oled_Write_Cmd(0xAF);//--turn on oled panel		
}void Oled_Screen_Clear(void){unsigned char i,n;Oled_Write_Cmd (0x20);                    //set memory addressing modeOled_Write_Cmd (0x02);                    //page addressing modefor(i=0;i<8;i++){Oled_Write_Cmd(0xb0+i);               //Oled_Write_Cmd(0x00);                 //Oled_Write_Cmd(0x10);                 //for(n=0;n<128;n++)Oled_Write_Data(0x00); 			}	
}void Oled_Show_open()
{unsigned char  i;Oled_Init();// 选择一个位置确认页寻址模式Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();// 选择PAGE0   1011 0000 0xB0			Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(o1[i]);}for(i=0;i<16;i++){Oled_Write_Data(z1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(o2[i]);}for(i=0;i<16;i++){Oled_Write_Data(z2[i]);}}void Oled_Show_close()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(c1[i]);}for(i=0;i<16;i++){Oled_Write_Data(z1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(c2[i]);}for(i=0;i<16;i++){Oled_Write_Data(z2[i]);}}void Oled_Show_up()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(u1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(u2[i]);}}void Oled_Show_down()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(d1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(d2[i]);}}void Oled_Show_left()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(l1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(l2[i]);}}void Oled_Show_right()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(r1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(r2[i]);}}void Oled_Show_front()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(f1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(f2[i]);}}void Oled_Show_behind()
{unsigned char  i;Oled_Init();Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x1[i]);}for(i=0;i<16;i++){Oled_Write_Data(b1[i]);}Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(x2[i]);}for(i=0;i<16;i++){Oled_Write_Data(b2[i]);}}

oled.h

#ifndef __OLED_H__
#define __OLED_H__void Oled_Write_Cmd(uint8_t dataCmd);void Oled_Write_Data(uint8_t dataData);void Oled_Init(void);void Oled_Screen_Clear(void);// oled显示封装
void Oled_Show_open();
void Oled_Show_close();
void Oled_Show_up();
void Oled_Show_down();
void Oled_Show_left();
void Oled_Show_right();
void Oled_Show_front();
void Oled_Show_behind();#endif

main.c

uint16_t adc_dma[8];//DMA搬运的ADC采集值uint8_t angle[4] = {45,45,180,135};//舵机角度uint8_t cnt = 0;//计数用,定时串口打印信息//覆写printf,用于串口打印数据
int fputc(int ch, FILE *f)
{      unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);  return ch;
}//int main//开始ADC和DMA采集HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4);//开启4路PWMHAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);//延时半秒,系统稳定一下HAL_Delay(500);printf("test\r\n");while (1){sg90_A();sg90_B();sg90_C();sg90_D();//输出PWM波使舵机运动__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));cnt++;//计数,每循环一次+1if(cnt>= 50)//循环50次,每次20ms,即一共1s。每一秒发送一次数据{printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);cnt = 0;}HAL_Delay(20);//每20ms循环一次(改成15更流畅)}

2.示教器控制

示教器控制在这里我用上面讲到的四个旋钮电位器模块实现,跟上面摇杆控制一样只需要开启四路ADC采集,代码跟摇杆控制基本差不多,新增加一个函数封装把采集的模拟值转换为角度,即0~4095 变为 0~180,除以22.75即可

在pwm.c上新增加一个转换函数


void translate()//直接用8通道就是adc_dma[4~7]
{angle[3] = (uint8_t)((double)adc_dma[0] / 22.75)/2;angle[2] = (uint8_t)((double)adc_dma[1] / 22.75);angle[1] = (uint8_t)((double)adc_dma[2] / 22.75) - 10;angle[0] = 180 - (uint8_t)((double)adc_dma[3] / 22.75);//电位器装反,改为 180 - 即可
}

在main.c上增加打印调试信息在while(1)循环里面

printf("adc_dma = {%d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3]);

3.蓝牙控制

使用蓝牙模块,打开串口中断,用串口调试助手查看中断测试收发的数据,只需要新增加串口接收中断代码和在原先的pwm.c上面做一些修改

usart.c

#include "stdio.h"
#include "string.h"
#include "pwm.h"#include "adc.h"
#include "dma.h"/*蓝牙控制机械臂指令:
s		停
l/r 左右
u/d 上下
f/b 前后
o/c 开合*/
uint8_t cmd_BLE = 's';extern uint16_t adc_dma[4];//DMA搬运的ADC采集值//覆写printf
int fputc(int ch, FILE *f)
{      unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);  return ch;
}//=====串口(中断)=======
//串口接收缓存(1字节)
uint8_t buf=0;
//定义最大接收字节数 200,可根据需求调整
#define UART1_REC_LEN 200
// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];
//  接收状态
//  bit15,      接收完成标志
//  bit14,      接收到0x0d
//  bit13~0,    接收到的有效字节数目
uint16_t UART1_RX_STA=0;// 串口中断:接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{// 判断中断是由哪个串口触发的if(huart->Instance == USART1){// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)if((UART1_RX_STA & 0x8000) == 0){// 如果已经收到了 0x0d (回车),if(UART1_RX_STA & 0x4000){// 则接着判断是否收到 0x0a (换行)if(buf == 0x0a){// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1UART1_RX_STA |= 0x8000;//=======中断信息处理=======//获取蓝牙控制指令,A打头,后面一个字母就是指令内容if(UART1_RX_Buffer[0] == 'A'){HAL_ADC_Stop_DMA(&hadc1);//停止ADC DMAMX_ADC1_Init();//初始化ADC1HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开启ADC DMAcmd_BLE = UART1_RX_Buffer[1];}else {if(UART1_RX_Buffer[0] != '\0')printf("指令发送错误:%s\r\n", UART1_RX_Buffer);}//==========================memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer));// 重新开始下一次接收UART1_RX_STA = 0;//==========================}else// 否则认为接收错误,重新开始UART1_RX_STA = 0;}else	// 如果没有收到了 0x0d (回车){//则先判断收到的这个字符是否是 0x0d (回车)if(buf == 0x0d){// 是的话则将 bit14 位置为1UART1_RX_STA |= 0x4000;}else{// 否则将接收到的数据保存在缓存数组里UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;UART1_RX_STA++;// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收if(UART1_RX_STA > UART1_REC_LEN - 1)UART1_RX_STA = 0;}}}// 重新开启中断HAL_UART_Receive_IT(&huart1, &buf, 1);}
}// 在串口初始化中开启接收中断HAL_UART_Receive_IT(&huart1, &buf, 1);

pwm.c

#include "pwm.h"
#include "main.h"
#include "oled.h"extern uint16_t adc_dma[8];//DMA搬运的ADC采集值extern uint8_t angle[4];//舵机角度extern uint8_t Mode;
extern uint8_t cmd_BLE;//根据输入的0~180角度获取对应pwm占空比参数unsigned char Angle(unsigned char pwm_pulse)
{return pwm_pulse + 44;
}//舵机A,夹爪	CH4_B11  45-135 张开闭合 初始化135
void sg90_A()
{if((cmd_BLE == 'c' || adc_dma[3] > 4000) && angle[3] < 135)//合{angle[3]++;Oled_Show_close();}else if((cmd_BLE == 'o' || adc_dma[3] <1000) && angle[3] > 45)//开{angle[3]--;Oled_Show_open();}}//舵机B,上下	CH3_B10  45-180    初始化180
void sg90_B()
{if((cmd_BLE == 'u' || adc_dma[2] <1000) && angle[2] < 180)//上{angle[2]++;Oled_Show_up();}else if((cmd_BLE == 'd' || adc_dma[2] > 4000) && angle[2] > 45)//下{angle[2]--;Oled_Show_down();}
}//舵机C,前后	CH2_B3  45-180  前后   初始化45
void sg90_C()
{if((cmd_BLE == 'f' || adc_dma[1] <1000) && angle[1] < 180)//前{angle[1]++;Oled_Show_front();}else if((cmd_BLE == 'b' || adc_dma[1] > 4000) && angle[1] > 45)//后{angle[1]--;Oled_Show_behind();}
}//舵机D,底座	CH1_A15 45-135 左到右   初始化45
void sg90_D()
{if((cmd_BLE == 'l' || adc_dma[0] <1000) && angle[0] < 135)//左{angle[0]++;Oled_Show_left();}else if((cmd_BLE == 'r' || adc_dma[0] > 4000) && angle[0] > 45)//右{angle[0]--;Oled_Show_right();}
}

五、裸机与FreeRTOS

移植 FreeRTOS 到 STM32F103C8T6上我们可以手动移植或者使用CubeMX快速移植,在这里我用CubeMX快速移植,具体介绍可看我之前写过的文章,链接如下:CubeMx快速移植FreeRTOS

接着我们要创建三个任务,一个任务负责角度信息处理,一个任务负责串口收发数据,一个任务负责显示OLED屏幕,具体创建删除任务介绍可看我之前写过的文章,链接如下:任务的创建和删除

1.CubeMX配置

2.移植裸机三种控制代码

在这里我们只需要在CubeMX生成的freertos.c文件中移植我们裸机控制的代码放在对应的任务中,可自行进行代码扩展,例如:  

六、项目演示视频

四轴机械臂演示视频

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/681221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW智能温度监控系统

LabVIEW智能温度监控系统 介绍了一个基于LabVIEW的智能温度监控系统&#xff0c;实现对工业环境中温度的实时监控与调控。通过集成传感器技术和LabVIEW软件平台&#xff0c;系统能够自动检测环境温度&#xff0c;及时响应温度变化&#xff0c;并通过图形用户界面(GUI)为用户提…

【头歌·计组·自己动手画CPU】二、运算器设计(讲解版) 【计算机硬件系统设计】

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

什么是智慧隧道,如何建设智慧隧道

一、隧道管理的难点痛点 近年来隧道建设规模不断扩大&#xff0c;作为隧道通车里程最多、规模最大的国家&#xff0c;截至2022年底&#xff0c;我国公路隧道共有24850处、2678.43万延米&#xff0c;其中特长隧道1752处、795.11万延米&#xff0c;长隧道6715处、1172.82万延米。…

【北邮鲁鹏老师计算机视觉课程笔记】06 corner 局部特征

【北邮鲁鹏老师计算机视觉课程笔记】06 corner 局部特征 1 局部特征的任务牵引&#xff1a;全景拼接 ①提取特征 ②匹配特征 ③拼接图像 我们希望特征有什么特性&#xff1f; ①可重复性 ②显著性 ③计算效率和表达紧凑性 ④局部性 2 特征点检测的任务 3 角点 在角点&#…

记一次Spring for Kotlin中JacksonConfig配置Long转String失败

目录 起因真相解决方案 起因 众所周知&#xff0c;浏览器在处理 Long类型&#xff08;比如雪花算法生成的id&#xff09;时&#xff0c;往往会出大事情。 浏览器在处理长整型&#xff08;Long&#xff09;类型时可能会遇到问题&#xff0c;主要原因是浏览器在处理数字时有限制…

8.【CPP】Vector(扩容问题||迭代器失效问题简述迭代器的种类)

vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它的大小会被容器自…

ubuntu服务器部署gitlab docker并配置nginx反向代理https访问

拉取镜像 docker pull gitlab/gitlab-ce运行容器 docker run --detach \--publish 9080:80 --publish 9022:22 --publish 9443:443\--namegitlab \--restartalways \--volume /home/docker/gitlab/config:/etc/gitlab \--volume /home/docker/gitlab/logs:/var/log/gitlab \-…

如何学习VBA_3.2.14:VBA中字符串的处理和判断函数

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的劳动效率&#xff0c;而且可以提高数据处理的准确度。我推出的VBA系列教程共九套和一部VBA汉英手册&#xff0c;现在已经全部完成&#xff0c;希望大家利用、学习。 如果…

RestFul的认识

前言 RESTful 是 Representational State Transfer 的缩写&#xff0c;是一种软件架构风格&#xff0c;用于在网络上构建和整合应用程序。它基于 HTTP 协议&#xff0c;并定义了一组约束和规范&#xff0c;用于规范客户端和服务器之间的通信。 RESTful API 是遵循 REST 架构规…

【复现】Supabase后端服务 SQL注入漏洞_48

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 Supabase是什么 Supabase将自己定位为Firebase的开源替代品&#xff0c;提供了一套工具来帮助开发者构建web或移动应用程序。 Sup…

441. Arranging Coins( 排列硬币)

问题描述 你总共有 n 枚硬币&#xff0c;并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯&#xff0c;其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。 给你一个数字 n &#xff0c;计算并返回可形成 完整阶梯行 的总行数。 问题分析 等差数列求和问…

【c++基础】国王的魔镜

说明 国王有一个魔镜&#xff0c;可以把任何接触镜面的东西变成原来的两倍——只是&#xff0c;因为是镜子嘛&#xff0c;增加的那部分是反的。 比如一条项链&#xff0c;我们用AB来表示&#xff0c;不同的字母表示不同颜色的珍珠。如果把B端接触镜面的话&#xff0c;魔镜会把…

LeetCode、1268. 搜索推荐系统【中等,前缀树+优先队列、排序+前缀匹配】

文章目录 前言LeetCode、1268. 搜索推荐系统【中等&#xff0c;前缀树优先队列、排序前缀匹配】题目类型及分类思路API调用&#xff08;排序前缀匹配&#xff09;前缀树优先队列 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创…

车载诊断协议DoIP系列 —— DoIP应用(Application)需求

车载诊断协议DoIP系列 —— DoIP应用(Application)需求 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一…

基于python深度学习的中文情感分析的系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【Chrono Engine学习总结】4-vehicle-4.2-车辆轨迹跟踪

由于Chrono的官方教程在一些细节方面解释的并不清楚&#xff0c;自己做了一些尝试&#xff0c;做学习总结。 0、Vehicle的driver driver在上一篇总结中有过介绍&#xff0c;【Chrono Engine学习总结】4-vehicle-4.1-vehicle的基本概念&#xff0c;这里进一步介绍。 对于一个…

部分意图分类【LLM+RAG】

在生成人工智能领域工作最有价值的事情之一就是发现新兴技术如何融入新的解决方案。 举个例子&#xff1a;在为北美顶级金融服务公司之一设计对话式人工智能助手时&#xff0c;WillowTree 的数据和人工智能研究团队 (DART) 发现&#xff0c;将意图分类与大型语言模型 (LLM) 结合…

推荐高端资源素材图库下载平台整站源码

推荐高端图库素材下载站的响应式模板和完整的整站源码&#xff0c;适用于娱乐网资源网。该模板支持移动端&#xff0c;并集成了支付宝接口。 演示地 址 &#xff1a; runruncode.com/tupiao/19692.html 页面设计精美&#xff0c;不亚于大型网站的美工水准&#xff0c;并且用户…

【STL】vector模拟实现

vector模拟实现 一、vector函数接口总览二、vector当中的成员介绍三、list模拟实现1、默认成员函数&#xff08;1&#xff09;构造函数1&#xff08;2&#xff09;构造函数2&#xff08;3&#xff09;构造函数3 2、拷贝构造函数&#xff08;1&#xff09;写法一&#xff1a;老式…

键盘重映射禁用 CtrlAltDel 键的利弊

目录 前言 一、Scancode Map 的规范 二、禁用 CtrlAltDel 的方法及其缺陷 三、编程实现和测试 3.1 C 实现的简易修改工具 3.2 C# 实现的窗口工具 四、总结 本文属于原创文章&#xff0c;转载请注明出处&#xff1a; https://blog.csdn.net/qq_59075481/article/details…