STM32實現四自由度機械臂(SG90舵機)多功能控制(軟件篇freertos)

書接上回的硬件篇STM32控制四自由度機械臂(SG90舵機)(硬件篇)(簡單易復刻)-CSDN博客

此時硬件平臺已經搭建完畢,軟件總共設計了三種模式,分別為

模式1:搖桿&藍牙模式,此模式下可用搖桿或手機操作機械臂

模式2:示教器模式,此模式下由電位器控制機械臂

模式3:執行記憶動作,此模式下機械臂重復數組/鏈表中存儲的動作

三種模式的切換以及存儲動作可由按鍵或者手機藍牙切換。

代碼使用了FREERTOS操作系統,接下來我會將代碼分開解析,這樣大家看完后再結合起來回顧整體,就會更加容易理解了,請大家不要著急,慢慢看下去,一定能夠復刻成功。

先說模式1(搖桿&藍牙)的代碼,其中cmd_BLE是藍牙的數據,adc_dma[0-3]是搖桿的數據。

void check_sg_cmd()//搖桿&藍牙模式控制舵機函數
{check_A();//舵機Acheck_B();//舵機Bcheck_C();//舵機Ccheck_D();//舵機D
}
//舵機A,夾爪	CH4_B11-D1;adc4_A3
void check_A()
{if(Mode == 1){if((cmd_BLE == 'c' || adc_dma[3] > 4000) && angle[3] < 90)//合{angle[3]++;}else if((cmd_BLE == 'o' || adc_dma[3] <1000) && angle[3] > 0)//開{angle[3]--;}}
}
//舵機B,上下	CH3_B10-D2;adc3_A2
void check_B()
{if(Mode == 1){if((cmd_BLE == 'u' || adc_dma[2] <1000) && angle[2] < 135)//上{angle[2]++;}else if((cmd_BLE == 'd' || adc_dma[2] > 4000) && angle[2] > 45)//下{angle[2]--;}}
}
//舵機C,前后	CH2_B3-D3;adc2_A1
void check_C()
{if(Mode == 1){if((cmd_BLE == 'f' || adc_dma[1] <1000) && angle[1] < 135)//前{angle[1]++;}else if((cmd_BLE == 'b' || adc_dma[1] > 4000) && angle[1] > 45)//后{angle[1]--;}}
}
//舵機D,底座	CH1_A15-D0;adc1_A0
void check_D()
{if(Mode == 1){if((cmd_BLE == 'l' || adc_dma[0] <1000) && angle[0] < 180)//左{angle[0]++;}else if((cmd_BLE == 'r' || adc_dma[0] > 4000) && angle[0] > 0)//右{angle[0]--;}}
}

再看模式2(示教器)的代碼,先通過translate()獲取舵機目標角度,再通過reach_target()控制舵機達到目標角度,還有什么不懂的代碼里的注釋也很詳細。

void reach_target()//控制舵機到達目標角度
{for(j = 0;j <4;j++){if(angle[j] > angle_target[j]){angle[j]--;}else if(angle[j] < angle_target[j]){angle[j]++;}}
}
void translate()//示教器獲取舵機目標角度函數
{//adc_dma[4-7]表示四個電位器,將電位器數據轉換成目標角度angle_target[3] = (uint8_t)((double)adc_dma[7] / 22.75)/2;angle_target[2] = (uint8_t)((double)adc_dma[6] / 22.75);angle_target[1] = (uint8_t)((double)adc_dma[5] / 22.75) - 10;angle_target[0] = 180 - (uint8_t)((double)adc_dma[4] / 22.75);//180減只是改變一下電位器方向,不影響總體//限幅if(angle_target[1]<45)	angle_target[1]=45;else if(angle_target[1]>135)	angle_target[1]=135;if(angle_target[2]<45)	angle_target[1]=45;else if(angle_target[2]>135)	angle_target[1]=135;
}

再看模式3(記憶動作)的代碼,memory[i][j]數組里存儲的就是記憶數據。

void get_target()//獲取記憶角度數據,這里用的是數組存儲記憶數據
{angle_target_flag = 0;for(j=0;j<4;j++){if(angle[j] == angle_target[j])	angle_target_flag++;}if(angle_target_flag == 4)	i++;for(j=0;j<4;j++){if(memory[i][j] == '\0'){i = 0;}angle_target[j] = memory[i][j];}
}void reach_target()//控制舵機到達目標角度,這個函數和示教器模式下的是同一個函數
{for(j = 0;j <4;j++){if(angle[j] > angle_target[j]){angle[j]--;}else if(angle[j] < angle_target[j]){angle[j]++;}}
}

到這里上述說的三個模式的代碼都在代碼工程的PWM.c中可以找到。?

接下來給大家下說一下freertos.c里面主要的三個任務?,分別是舵機任務Start_check_angle(),主要用于在不同的模式下控制舵機,在舵機任務我們也能看到上述提到了三個模式下的函數,藍牙串口S任務tart_usart_show(),主要用于打印信息到手機app上,這里并沒有通過手機控制機械臂的函數,那個在串口函數里,后面會說,oled顯示屏任務Start_OLED_Task(),和藍牙串口任務類似,只不過它是打印信息到oled顯示屏上。

void Start_check_angle(void const * argument)//舵機任務
{/* USER CODE BEGIN Start_check_angle *///開啟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);/* Infinite loop */for(;;){if(Mode == 1){//搖桿&藍牙模式check_sg_cmd();}else if(Mode == 2){//示教器模式translate();reach_target();}else if(Mode == 3){//執行記憶動作get_target();reach_target();}if_BLE_cmd();//藍牙控制記憶動作模式//輸出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]));osDelay(15);//通過調整此延時可以改變機械臂運行速度}/* USER CODE END Start_check_angle */
}void Start_usart_show(void const * argument)//藍牙串口任務
{/* USER CODE BEGIN Start_usart_show *//* Infinite loop */for(;;){printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);printf("adc_dma1 = {%d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3]);printf("adc_dma2 = {%d, %d, %d, %d}\r\n",adc_dma[4],adc_dma[5],adc_dma[6],adc_dma[7]);printf("\r\n");osDelay(1000);}/* USER CODE END Start_usart_show */
}void Start_OLED_Task(void const * argument)//oled顯示屏任務
{/* USER CODE BEGIN Start_OLED_Task */Oled_Init();Oled_Clear();/* Infinite loop */for(;;){//串口數據的字符串拼裝,speed是格子,每個格子1cmsprintf(speedMes,"A: %d ",angle[0]);sprintf(speedMes1,"B: %d ",angle[1]);sprintf(speedMes2,"C: %d ",angle[2]);sprintf(speedMes3,"D: %d ",angle[3]);sprintf(speedMes4,"Mode %d ",Mode);sprintf(speedMes5,"S %d ",i);Oled_Show_Str(1,5,speedMes);Oled_Show_Str(1,69,speedMes1);Oled_Show_Str(2,5,speedMes2);Oled_Show_Str(2,69,speedMes3);Oled_Show_Str(4,0,speedMes4);Oled_Show_Str(4,64,speedMes5);osDelay(500);}/* USER CODE END Start_OLED_Task */
}

接下來給大家說一下有關藍牙部分的相關代碼以及藍牙手機app的使用

首先是usart.c里的一段代碼,這里最重要的部分就是通過藍牙模式控制機械臂時候,切換模式只需發送M1,M2,M3就可切換相應模式,而執行相關動作時,必須在發送的指令前面加上A開頭,例如Ao就是夾爪開。

/*機械臂控制模式,默認為1
1:搖桿控制	
2:示教器控制
3:執行記憶動作
*/
uint8_t Mode = 1;/*藍牙控制機械臂指令:
s/m	停/儲存當前動作
l/r 左右
u/d 上下
f/b 前后
o/c 開合*/
uint8_t cmd_BLE = 's';// 串口中斷:接收完成回調函數,收到一個數據后,在這里處理
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;//=======中斷信息處理=======//模式切換在手機藍牙app中只需發送M1,M2,M3即可切換模式if (!strcmp((const char *)UART1_RX_Buffer, "M1")) {			Mode = 1;printf("搖桿模式\r\n");}else if(!strcmp((const char *)UART1_RX_Buffer, "M2")) {			Mode = 2;printf("示教模式\r\n");}else if(!strcmp((const char *)UART1_RX_Buffer, "M3")) {			Mode = 3;printf("執行記憶動作\r\n");}//藍牙指令的切換則必須以A開頭,例:發送Ao就是夾爪開else if(UART1_RX_Buffer[0] == 'A'){cmd_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);}
}

然后是PWM.c中的if_BLE_cmd(),這里藍牙控制記憶動作模式同樣需要在發送指令前面加上一個A開頭才可以。

//藍牙控制記憶動作模式
void if_BLE_cmd()
{switch(cmd_BLE){case 'm':if(i < location_cnt){for(j=0;j<4;j++){memory[i][j] = angle[j];}printf("儲存動作\r\n");cmd_BLE = 's';i++;}else{printf("動作已滿\r\n");cmd_BLE = 's';}break;case 'g':for(i=0;i < location_cnt;i++){for(j=0;j<4;j++){printf("%d ",memory[i][j]);}printf("\r\n");if(memory[i][j] == '\0')	break;}cmd_BLE = 's';break;case 'D':for(i=0; i < location_cnt ;i++){memset(memory[i],'\0',4);}i = 0;printf("已清除動作");cmd_BLE = 's';break;}
}

最后介紹一下HC藍牙助手手機app(APP會在文末與代碼一起開源分享給大家)的使用方法 ,話不多說,直接上圖。

接下來再給大家介紹一下按鍵控制的代碼,A0按鍵切換模式1(搖桿&藍牙模式),A1按鍵切換模式2(示教器模式),B4按鍵切換模式3(執行記憶動作),B5按鍵用于存儲動作。

void Callback01(void const * argument)
{/* USER CODE BEGIN Callback01 */anti_shake = 0;/* USER CODE END Callback01 */
}void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//按鍵任務
{switch(GPIO_Pin){case GPIO_PIN_0://A0按鍵HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_RESET);//led亮HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET);//led滅Mode = 1;printf("搖桿模式\r\n");		break;case GPIO_PIN_1://A1按鍵HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_SET);//led滅HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);//led亮Mode = 2;printf("示教模式\r\n");		break;case GPIO_PIN_4://B4按鍵if(anti_shake == 0){anti_shake = 1;Mode = 3;HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_RESET);//led亮HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);//led亮printf("執行記憶動作\r\n");osTimerStart(myTimer01Handle,800);}break;case GPIO_PIN_5://B5按鍵if(anti_shake == 0){//軟件消抖		      anti_shake = 1;			osTimerStart(myTimer01Handle,800);//存儲動作		if(i<location_cnt){for(j=0;j<4;j++){memory[i][j] = angle[j];}printf("儲存動作\r\n");i++;}else if(i>=9){printf("動作已滿\r\n");}					}break;			}
}

到此為止,核心代碼基本上都介紹差不多了,還有一些例如i2c.c,OLED.c顯示屏引腳初始化用,gpio.c按鍵以及led初始化使用,adc.c,dma.c搖桿以及電位器初始化使用,tim.c舵機pwm初始化使用,這些代碼主要是初始化相關引腳,而相關引腳對應在硬件篇我已經介紹過了,當然對于這些代碼感興趣的也可以自己去代碼工程中看。

最后再帶大家簡單看一下main.c中的代碼:

int main(void)
{/* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();  //GPIO初始化,四個按鍵和兩個LEDMX_DMA_Init();   //DMA初始化MX_ADC1_Init();  //ADC初始化,2個搖桿用and4個電位器用MX_TIM2_Init();  //定時器PWM初始化,舵機用MX_USART1_UART_Init();//串口初始化,藍牙用MX_I2C1_Init();  //I2C初始化,oled顯示屏用/* USER CODE BEGIN 2 */printf("Start\r\n");//程序開始運行HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,8); //開啟ADC和DMAHAL_Delay(500);/* Call init function for freertos objects (in freertos.c) */MX_FREERTOS_Init();//freertos任務初始化/* Start scheduler */osKernelStart();//freertos開啟任務調度while (1){}
}

相信大家結合這篇軟件篇代碼解說來閱讀工程代碼看到這里就算不會freertos的人也已經復刻成功了,創造不易,感謝c友的一鍵三聯,球球了,這個對我真的很重要!?

工程文件和藍牙資料的鏈接給大家放這里了!!!http://通過網盤分享的文件:stm32機械臂源代碼.zip 鏈接: https://pan.baidu.com/s/1o1IBFdVu9Ybk9gXJIZTY8Q 提取碼: 0531

?

?

?

?

?

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88891.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88891.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88891.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

docker常用命令集(2)

接前一篇文章&#xff1a;docker常用命令集&#xff08;1&#xff09; 本文內容參考&#xff1a; Docker build 命令 | 菜鳥教程 docker基礎(二)之docker build-CSDN博客 Docker push 命令 | 菜鳥教程 Docker pull 命令 | 菜鳥教程 特此致謝&#xff01; 3. docker build …

舒爾特方格訓練小游戲流量主微信小程序開源

功能特點 游戲核心功能&#xff1a; 隨機生成55舒爾特方格 按順序點擊數字1-25 實時計時和嘗試次數統計 錯誤點擊反饋&#xff08;視覺和觸覺&#xff09; 數據統計&#xff1a; 記錄每次完成時間 保存歷史最佳成績 保存最近5次嘗試記錄 統計嘗試次數&#xff08;錯誤點擊&…

在Spring Boot 開發中 Bean 的聲明和依賴注入最佳的組合方式是什么?

在Spring Boot 開發中&#xff0c;社區和 Spring 官方已經形成了一套非常明確的最佳實踐。這個黃金組合就是&#xff1a; Bean 聲明&#xff1a;使用構造型注解&#xff08;Stereotype Annotations&#xff09;&#xff0c;如 Service, Repository, Component 等。依賴注入&…

Oxygen XML Editor 26.0編輯器

Oxygen XML Editor 26.0編輯器 歡迎使用Oxygen XML Editor 26.0編輯器準備工作安裝javajdk安裝jdk驗證Oxygen XML Editor 26.0安裝歡迎使用Oxygen XML Editor 26.0編輯器 準備工作安裝java Java官網下載地址:https://www.oracle.com/java/technologies/ Oxygen XML Editor 2…

AWS Lambda Container 方式部署 Flask 應用并通過 API Gateway 提供訪問

前言 一年前寫過一篇 Lambda 運行 Flask 應用的博文: https://lpwmm.blog.csdn.net/article/details/139756140 當時使用的是 ZIP 包方式部署應用代碼, 對于簡單的 API 開發用起來還是可以的, 但是如果需要集成到 CI/CD pipeline 里面就有點不太優雅. 本文將介紹使用容器方式…

React虛擬DOM的進化之路

引言 在Web前端開發中&#xff0c;用戶交互的流暢性和頁面性能一直是核心挑戰。早期&#xff0c;開發者直接操作真實DOM&#xff08;Document Object Model&#xff09;時&#xff0c;頻繁的重排&#xff08;reflow&#xff09;和重繪&#xff08;repaint&#xff09;導致性能…

(7)機器學習小白入門 YOLOv:機器學習模型訓練詳解

— (1)機器學習小白入門YOLOv &#xff1a;從概念到實踐 (2)機器學習小白入門 YOLOv&#xff1a;從模塊優化到工程部署 (3)機器學習小白入門 YOLOv&#xff1a; 解鎖圖片分類新技能 (4)機器學習小白入門YOLOv &#xff1a;圖片標注實操手冊 (5)機器學習小白入門 YOLOv&#xff…

初識MySQL(三)之主從配置與讀寫分離實戰

主重復制 主重復制原理master開啟二進制日志記錄slave開啟IO進程&#xff0c;從master中讀取二進制日志并寫入slave的中繼日志slave開啟SQL進程&#xff0c;從中繼日志中讀取二進制日志并進行重放最終&#xff0c;達到slave與master中數據一致的狀態&#xff0c;我們稱作為主從…

RabbitMQ面試精講 Day 2:RabbitMQ工作模型與消息流轉

【RabbitMQ面試精講 Day 2】RabbitMQ工作模型與消息流轉 開篇 歡迎來到"RabbitMQ面試精講"系列的第2天&#xff0c;今天我們將深入探討RabbitMQ的工作模型與消息流轉機制。這是面試中最常被問到的核心知識點之一&#xff0c;90%的RabbitMQ面試都會涉及消息流轉流程…

基于SpringBoot3集成Kafka集群

1. build.gradle依賴引入 implementation org.springframework.kafka:spring-kafka:3.2.02. 新增kafka-log.yml文件 在resource/config下面新增kafka-log.yml&#xff0c;配置主題與消費者組 # Kafka消費者群組 kafka:consumer:group:log-data: log-data-grouptopic:log-data: …

wpf Canvas 導出圖片

在WPF中將Canvas導出為圖片主要涉及以下關鍵步驟和注意事項: ?核心實現方法?使用RenderTargetBitmap將Canvas渲染為位圖,再通過PngBitmapEncoder保存為PNG文件。需注意臨時移除Canvas的布局變換(LayoutTransform)以避免渲染異常?1。示例代碼片段:CanvasExporter.cs pu…

lvs負載均衡實操模擬

目錄 一、配置準備 二、NET模式 修改LVS端 開啟路由 修改對內網卡 ens160 修改對外網卡 ens224 加載網卡配置文件 修改web1端 修改網卡信息 重啟網絡 檢測 配置web2 檢測 驗證配置是否正常 啟動nginx服務 驗證以上配置 添加lvs規則 驗證 三、DR模式 修改…

Spring Boot 是如何簡化 IoC 的配置的?

首先Spring Boot 并沒有發明新的 IoC 理論&#xff0c;它做的也不是替換掉 Spring IoC 容器。相反&#xff0c;Spring Boot 是 Spring IoC 思想的實踐者和簡化者。它通過**“約定優于配置”&#xff08;Convention over Configuration&#xff09;**的理念&#xff0c;將原本繁…

Go語言中的組合式接口設計模式

文章目錄Go語言中的組合式接口設計模式背景和需求組合式接口設計Go語言中的組合式接口設計模式 背景和需求 在微服務架構和復雜業務系統中&#xff0c;我們經常需要調用多個外部服務或內部模塊。傳統的做法是將所有方法都放在一個大接口中&#xff0c;但這種設計會導致接口臃…

React - createPortal

什么是createPortal&#xff1f;注意這是一個API&#xff0c;不是組件&#xff0c;他的作用是&#xff1a;將一個組件渲染到DOM的任意位置&#xff0c;跟Vue的Teleport組件類似。用法 import { createPortal } from react-dom;const App () > {return createPortal(<div…

Cursor的使用

Cursor的使用 Ctrl L 打開歷史對話記錄 Tab智能助手 1.單行/多行補全 已有代碼片段&#xff1a; //需求&#xff1a;寫一個工具類計算數組平均值 public class ArrayUtils {//按tab會完成補全 }按tab鍵- Cursor 自動生成代碼: //需求&#xff1a;寫一個工具類計算數組平均值 p…

17.使用DenseNet網絡進行Fashion-Mnist分類

17.1 DenseNet網絡結構設計import torch from torch import nn from torchsummary import summary #卷積層 def conv_block(input_channels,num_channels):netnn.Sequential(nn.BatchNorm2d(input_channels),nn.ReLU(),nn.Conv2d(input_channels,num_channels,kernel_size3,pad…

網安系列【16】之Weblogic和jboss漏洞

文章目錄一 Weblogic1.1 Weblogic相關漏洞1.2 Weblogic漏洞發現1.3 Weblogic漏洞利用二 Jboss2.1 Jboss漏洞2.2 Jboss識別與漏洞利用一 Weblogic WebLogic 是由 Oracle公司 開發的一款基于Java EE&#xff08;現稱Jakarta EE&#xff09;的企業級應用服務器&#xff0c;主要用…

Unity URP + XR 自定義 Skybox 在真機變黑問題全解析與解決方案(支持 Pico、Quest 等一體機)

在使用 Unity 的 URP 渲染管線開發 XR 應用&#xff08;如 Pico Neo、Pico 4、Quest 2/3 等一體機&#xff09;時&#xff0c;很多開發者遇到一個奇怪的問題&#xff1a;打包后&#xff0c;Skybox&#xff08;天空盒&#xff09;在某些角度下突然變黑&#xff0c;只在轉動頭部后…

Cursor、飛算JavaAI、GitHub Copilot、Gemini CLI 等熱門 AI 開發工具合集

Cursor&#xff1a;代碼編寫的智能伙伴?Cursor 是 Anysphere 公司推出的一款 AI 編程工具&#xff0c;它基于微軟開源代碼編輯器 VS Code 開發&#xff0c;將 AI 技術深度整合到開發人員的工作流程中。Cursor 的功能十分強大&#xff0c;不僅能夠自動用純英文編寫代碼&#xf…