当前位置:   article > 正文

基于STM32+FreeRTOS的四轴机械臂_机械别rtos处理

机械别rtos处理

目录

代码:

注释写的较少,但本文出现的代码都有注释,所以请直接在本文里看注释

项目概述:

一 准备阶段(都是些废话)

 二 裸机测试功能

1.摇杆控制

接线:

CubeMX配置:

代码

2.蓝牙控制

接线:

CubeMX配置

代码:

3.示教器控制

4.记录动作信息

5.执行记录的动作

注:

三 FreeRTOS上完成项目

1.加入IIC的OLED屏显 和 动作执行(数组版) 

CubeMX配置:

 代码:

2.链表的增删遍历实现动作记忆和执行

3.SPI扩容

功能测试

w25q128芯片分析与功能实现


代码:

注释写的较少,但本文出现的代码都有注释,所以请直接在本文里看注释

 链接:https://pan.baidu.com/s/14GJF8ZCnkNkKkz5R0uJwOA?pwd=1111 
提取码:1111 
--来自百度网盘超级会员V4的分享

项目概述

基于STM32的FreeRTOS四轴机械臂

        基于FreeRTOS实时操作系统,主控为 STM32F103C8T6 ,机械臂为四轴分别被四个Mg90s舵机控制。本项目实现了 3 种控制方法,分别为 摇杆控制  、 串口蓝牙控制 和 示教器控制。可以进行动作录制和执行。

        采用8路ADC采集摇杆和示教器的模拟量并由DMA搬运数据,USART串口实时收发信息,IIC驱动OLED屏幕实时显示信息。并且实现了动作录制和执行功能,动作记忆可以由二维数组或者链表实现存储。通过SPI驱动W25Q128模块进行动作记忆扩容,即可以录制上百组动作。

一 准备阶段(都是些废话)

        首先你需要一台四轴机械臂,才能开始这个项目。可以自己建模3D打印,也可以直接某宝购买了一套成品套件,来做功能实现。而你的机械臂会配备四个电机,本文采用的是舵机,型号无所谓,控制起来是一样的,注意需要是180度的角度型舵机,而不是360度的速度型舵机。

        然后是单片机及开发环境,使用STM32F103C8T6。开发环境为STM32cubeM和Keil5。

如果你没有STM32开发经验:首先你至少要有一点C语言基础,最基本的代码要能读懂什么意思;然后最好有过其它单片机开发经验,比如C51、ESP8266等等,或者直接学习一下STM32开发。板子随便买一个此型号的开发板就行,买最小系统板+面包板也可以。STM32cubeMX+Keil5,可以自行百度搜索并下载安装,我建议在B站找一个STM32HAL库的教程,跟着安装,且最好教程芯片型号与你使用的要一致。按照教程走一遍。确认开发板和开发环境可用之后,简单学习一下HAL库开发。然后可以继续下面的步骤。)

STM32cubeM和Keil5的教程推荐:

【中科大RM电控合集】手把手Keil+STM32CubeMX+VsCode环境配置_哔哩哔哩_bilibili

        其它硬件准备:

HC系列蓝牙串口模块,实测HC-05和HC-08都可以
摇杆模块,买两个即可。
四个旋钮电位器,质量别太差。
IIC协议OLED屏幕
SPI协议W25Q128模块
按钮模块若干,我用了四个,有板载的按钮也可以,尽量买带电容的防抖按钮
舵机拓展板,可有可无,面包板也能用。
各式杜邦线若干。

 二 裸机测试功能

1.摇杆控制

        首先是摇杆控制STM32,需要4路1ADC+DMA采集摇杆输出的模拟量。根据这个数据来控制舵机角度。蓝牙串口把ADC信息和舵机角度打印出来。蓝牙直接用HC官方的HC蓝牙串口助手就行。

接线:

摇杆4个输出模拟量的引脚连接stm32的A0,A1,A2,A3,VCC这里接5V。

舵机A,夹爪    CH4_B11;adc4_A3

舵机B,上下    CH3_B10;adc3_A2

舵机C,前后    CH2_B3;adc2_A1

舵机D,底座    CH1_A15;adc1_A0

蓝牙TX对板子RX A10,

蓝牙RX对板子TX A9。

CubeMX配置:

基本配置(后面每个工程都是这一套)

 

 ADC1:4路

 

 DMA:搬运ADC数据的

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

 usart,9600波特率给蓝牙模块用。

然后 generate code 即可

代码:

注:只有这种注释之间是用户自己写业务代码的地方,写其它地方再重生成功能会被清除。

  1. /* USER CODE BEGIN */
  2. 。。。。 。。。。
  3. /* USER CODE END */

main.c

关键控制代码在于check的四个函数,首先限制舵机的角度范围避免损坏,再根据采集的摇杆信息值判断每个舵机的角度是增加还是减小。

注释比较清楚,直接看代码就行。

  1. /* Private includes ----------------------------------------------------------*/
  2. /* USER CODE BEGIN Includes */
  3. #include "stdio.h"
  4. /* USER CODE END Includes */
  5. /* Private variables ---------------------------------------------------------*/
  6. /* USER CODE BEGIN PV */
  7. uint16_t adc_dma[4];//DMA搬运的ADC采集值
  8. uint8_t angle[4] = {90,90,90,90};//舵机角度
  9. uint8_t cnt = 0;//计数用,定时串口打印信息
  10. /* USER CODE END PV */
  11. /* Private user code ---------------------------------------------------------*/
  12. /* USER CODE BEGIN 0 */
  13. //覆写printf,用于串口打印数据
  14. int fputc(int ch, FILE *f)
  15. {
  16. unsigned char temp[1]={ch};
  17. HAL_UART_Transmit(&huart1,temp,1,0xffff);
  18. return ch;
  19. }
  20. //根据输入的0~180角度获取对应pwm占空比参数
  21. uint8_t Angle(uint8_t pwm_pulse)
  22. {
  23. return pwm_pulse + 44;
  24. }
  25. //舵机A,夹爪 CH4_B11
  26. void cheack_A()
  27. {
  28. if(adc_dma[3] > 4000 && angle[3] < 90)
  29. {
  30. angle[3]++;
  31. }
  32. else if(adc_dma[3] <1000 && angle[3] > 0)
  33. {
  34. angle[3]--;
  35. }
  36. }
  37. //舵机B,上下 CH3_B10
  38. void cheack_B()
  39. {
  40. if(adc_dma[2] <1000 && angle[2] < 135)
  41. {
  42. angle[2]++;
  43. }
  44. else if(adc_dma[2] > 4000 && angle[2] > 45)
  45. {
  46. angle[2]--;
  47. }
  48. }
  49. //舵机C,前后 CH2_B3
  50. void cheack_C()
  51. {
  52. if(adc_dma[1] <1000 && angle[1] < 135)
  53. {
  54. angle[1]++;
  55. }
  56. else if(adc_dma[1] > 4000 && angle[1] > 45)
  57. {
  58. angle[1]--;
  59. }
  60. }
  61. //舵机D,底座 CH1_A15
  62. void cheack_D()
  63. {
  64. if(adc_dma[0] <1000 && angle[0] < 180)
  65. {
  66. angle[0]++;
  67. }
  68. else if(adc_dma[0] > 4000 && angle[0] > 0)
  69. {
  70. angle[0]--;
  71. }
  72. }
  73. /* USER CODE END 0 */
  74. /* USER CODE BEGIN 2 */
  75. HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开始ADC和DMA采集
  76. //开启4路PWM
  77. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  78. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
  79. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
  80. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
  81. //延时半秒,系统稳定一下
  82. HAL_Delay(500);
  83. /* USER CODE END 2 */
  84. /* Infinite loop */
  85. /* USER CODE BEGIN WHILE */
  86. while (1)
  87. {
  88. /* USER CODE END WHILE */
  89. /* USER CODE BEGIN 3 */
  90. //根据摇杆DMA判断舵机该如何运动
  91. cheack_A();
  92. cheack_B();
  93. cheack_C();
  94. cheack_D();
  95. //输出PWM波使舵机运动
  96. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
  97. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
  98. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
  99. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
  100. cnt++;//计数,每循环一次+1
  101. if(cnt>= 50)//循环50次,每次20ms,即一共1s。每一秒发送一次数据
  102. {
  103. printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);
  104. cnt = 0;
  105. }
  106. HAL_Delay(20);//每20ms循环一次(改成15更流畅)
  107. }
  108. /* USER CODE END 3 */

这里要勾选才能使用printf串口打印信息 

2.蓝牙控制

这里提前写了一点示教器的业务代码,执行切换模式操作会切换获取摇杆模拟值还是电位器模拟值。

注意:我这里整活儿搞了个ADC通道切换,但实测还是存在一点问题,你们直接使用8通道一起就好。

接线:

先不使用示教器,但是可以先测试一下功能,切换模式和采集一下数据。

把四个旋钮电位器接好,四根线接到ADC 5 6 7 8

CubeMX配置

打开串口中断,中断接收数据。 

我这里是ADC再开四个,其它不配置。

你们开启共8个之后把下面个数也4改成8,新的组别5678也改成IN4 5 6 7 四个通道

代码:

都写在main.c里面会太冗长,我这里分文件编程了,不懂可以百度keil怎么添加.c .h文件,实在不行就都放在main.c里吧。。。

adc.c

纯粹整活儿,自定义了一个ADC初始化,把采集1234换成5678来采集电位器信号。直接用八个通道一起采集就行,然后把原来放采集数据的那个数组adc_dma长度也改成8。

  1. /* USER CODE BEGIN 1 */
  2. //写一个切换通道的函数
  3. /* ADC1_Mode2 init function */
  4. void MX_ADC1_Mode2_Init(void)
  5. {
  6. ADC_ChannelConfTypeDef sConfig = {0};
  7. /** Common config
  8. */
  9. hadc1.Instance = ADC1;
  10. hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
  11. hadc1.Init.ContinuousConvMode = ENABLE;
  12. hadc1.Init.DiscontinuousConvMode = DISABLE;
  13. hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  14. hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  15. hadc1.Init.NbrOfConversion = 4;
  16. if (HAL_ADC_Init(&hadc1) != HAL_OK)
  17. {
  18. Error_Handler();
  19. }
  20. /** Configure Regular Channel
  21. */
  22. sConfig.Channel = ADC_CHANNEL_4;
  23. sConfig.Rank = ADC_REGULAR_RANK_1;
  24. sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  25. if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  26. {
  27. Error_Handler();
  28. }
  29. /** Configure Regular Channel
  30. */
  31. sConfig.Channel = ADC_CHANNEL_5;
  32. sConfig.Rank = ADC_REGULAR_RANK_2;
  33. if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  34. {
  35. Error_Handler();
  36. }
  37. /** Configure Regular Channel
  38. */
  39. sConfig.Channel = ADC_CHANNEL_6;
  40. sConfig.Rank = ADC_REGULAR_RANK_3;
  41. if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  42. {
  43. Error_Handler();
  44. }
  45. /** Configure Regular Channel
  46. */
  47. sConfig.Channel = ADC_CHANNEL_7;
  48. sConfig.Rank = ADC_REGULAR_RANK_4;
  49. if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  50. {
  51. Error_Handler();
  52. }
  53. }
  54. /* USER CODE END 1 */

adc.h

  1. /* USER CODE BEGIN Prototypes */
  2. void MX_ADC1_Mode2_Init(void);
  3. /* USER CODE END Prototypes */

usart.c

注:STM32串口接收到的信息都在这里进行处理,千万别忘了最下面一行代码,开启中断。

  1. //=======中断信息处理=======
  2. 。。。。。。。。
  3. //==========================
  1. /* USER CODE BEGIN 0 */
  2. #include "stdio.h"
  3. #include "string.h"
  4. #include "PWM.h"
  5. #include "adc.h"
  6. #include "dma.h"
  7. /*机械臂控制模式,默认为1
  8. 1:摇杆控制
  9. 2:示教器控制 */
  10. uint8_t Mode = 1;
  11. /*蓝牙控制机械臂指令:
  12. s 停
  13. l/r 左右
  14. u/d 上下
  15. f/b 前后
  16. o/c 开合*/
  17. uint8_t cmd_BLE = 's';
  18. extern uint16_t adc_dma[4];//DMA搬运的ADC采集值
  19. //覆写printf
  20. int fputc(int ch, FILE *f)
  21. {
  22. unsigned char temp[1]={ch};
  23. HAL_UART_Transmit(&huart1,temp,1,0xffff);
  24. return ch;
  25. }
  26. //=====串口(中断)=======
  27. //串口接收缓存(1字节)
  28. uint8_t buf=0;
  29. //定义最大接收字节数 200,可根据需求调整
  30. #define UART1_REC_LEN 200
  31. // 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
  32. uint8_t UART1_RX_Buffer[UART1_REC_LEN];
  33. // 接收状态
  34. // bit15, 接收完成标志
  35. // bit14, 接收到0x0d
  36. // bit13~0, 接收到的有效字节数目
  37. uint16_t UART1_RX_STA=0;
  38. // 串口中断:接收完成回调函数,收到一个数据后,在这里处理
  39. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  40. {
  41. // 判断中断是由哪个串口触发的
  42. if(huart->Instance == USART1)
  43. {
  44. // 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
  45. if((UART1_RX_STA & 0x8000) == 0)
  46. {
  47. // 如果已经收到了 0x0d (回车),
  48. if(UART1_RX_STA & 0x4000)
  49. {
  50. // 则接着判断是否收到 0x0a (换行)
  51. if(buf == 0x0a)
  52. {
  53. // 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
  54. UART1_RX_STA |= 0x8000;
  55. //=======中断信息处理=======
  56. //模式切换
  57. if (!strcmp((const char *)UART1_RX_Buffer, "M1"))
  58. {
  59. Mode = 1;
  60. HAL_ADC_Stop_DMA(&hadc1);//停止ADC DMA
  61. MX_ADC1_Init();//初始化ADC1
  62. HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开启ADC DMA
  63. printf("摇杆模式\r\n");
  64. }
  65. else if(!strcmp((const char *)UART1_RX_Buffer, "M2"))
  66. {
  67. Mode = 2;
  68. HAL_ADC_Stop_DMA(&hadc1);//停止ADC DMA
  69. MX_ADC1_Mode2_Init();//自定义初始化ADC1,把1234换成5678采集电位器
  70. HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开启ADC DMA
  71. printf("示教器模式\r\n");
  72. }
  73. //获取蓝牙控制指令,A打头,后面一个字母就是指令内容
  74. else if(Mode == 1 && UART1_RX_Buffer[0] == 'A')
  75. {
  76. cmd_BLE = UART1_RX_Buffer[1];
  77. }
  78. else {
  79. if(UART1_RX_Buffer[0] != '\0')
  80. printf("指令发送错误:%s\r\n", UART1_RX_Buffer);
  81. }
  82. //==========================
  83. memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer));
  84. // 重新开始下一次接收
  85. UART1_RX_STA = 0;
  86. //==========================
  87. }
  88. else
  89. // 否则认为接收错误,重新开始
  90. UART1_RX_STA = 0;
  91. }
  92. else // 如果没有收到了 0x0d (回车)
  93. {
  94. //则先判断收到的这个字符是否是 0x0d (回车)
  95. if(buf == 0x0d)
  96. {
  97. // 是的话则将 bit14 位置为1
  98. UART1_RX_STA |= 0x4000;
  99. }
  100. else
  101. {
  102. // 否则将接收到的数据保存在缓存数组里
  103. UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
  104. UART1_RX_STA++;
  105. // 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
  106. if(UART1_RX_STA > UART1_REC_LEN - 1)
  107. UART1_RX_STA = 0;
  108. }
  109. }
  110. }
  111. // 重新开启中断
  112. HAL_UART_Receive_IT(&huart1, &buf, 1);
  113. }
  114. }
  115. /* USER CODE END 0 */
  116. /* USER CODE BEGIN USART1_Init 2 */
  117. // 开启接收中断
  118. HAL_UART_Receive_IT(&huart1, &buf, 1);
  119. /* USER CODE END USART1_Init 2 */

我这里新建了两个PWM.c和.h文件。

把蓝牙指令控制和摇杆控制放在一起判断了。

  1. #include "PWM.h"
  2. #include "main.h"
  3. extern uint16_t adc_dma[4];//DMA搬运的ADC采集值
  4. extern uint8_t angle[4];//舵机角度
  5. extern uint8_t Mode;
  6. extern uint8_t cmd_BLE;
  7. //根据输入的角度获取对应pwm占空比参数
  8. uint8_t Angle(uint8_t pwm_pulse)
  9. {
  10. return pwm_pulse + 44;
  11. }
  12. //舵机A,夹爪 CH4_B11
  13. void check_A()
  14. {
  15. if(Mode == 1)
  16. {
  17. if((cmd_BLE == 'c' || adc_dma[3] > 4000) && angle[3] < 90)//合
  18. {
  19. angle[3]++;
  20. }
  21. else if((cmd_BLE == 'o' || adc_dma[3] <1000) && angle[3] > 0)//开
  22. {
  23. angle[3]--;
  24. }
  25. }
  26. }
  27. //舵机B,上下 CH3_B10
  28. void check_B()
  29. {
  30. if(Mode == 1)
  31. {
  32. if((cmd_BLE == 'u' || adc_dma[2] <1000) && angle[2] < 135)//上
  33. {
  34. angle[2]++;
  35. }
  36. else if((cmd_BLE == 'd' || adc_dma[2] > 4000) && angle[2] > 45)//下
  37. {
  38. angle[2]--;
  39. }
  40. }
  41. }
  42. //舵机C,前后 CH2_B3
  43. void check_C()
  44. {
  45. if(Mode == 1)
  46. {
  47. if((cmd_BLE == 'f' || adc_dma[1] <1000) && angle[1] < 135)//前
  48. {
  49. angle[1]++;
  50. }
  51. else if((cmd_BLE == 'b' || adc_dma[1] > 4000) && angle[1] > 45)//后
  52. {
  53. angle[1]--;
  54. }
  55. }
  56. }
  57. //舵机D,底座 CH1_A15
  58. void check_D()
  59. {
  60. if(Mode == 1)
  61. {
  62. if((cmd_BLE == 'l' || adc_dma[0] <1000) && angle[0] < 180)//左
  63. {
  64. angle[0]++;
  65. }
  66. else if((cmd_BLE == 'r' || adc_dma[0] > 4000) && angle[0] > 0)//右
  67. {
  68. angle[0]--;
  69. }
  70. }
  71. }
  1. #ifndef __PWM_H__
  2. #define __PWM_H__
  3. //根据输入的角度获取对应pwm占空比参数
  4. unsigned char Angle(unsigned char pwm_pulse);
  5. //舵机A,夹爪 CH4_B11
  6. void check_A(void);
  7. //舵机B,上下 CH3_B10
  8. void check_B(void);
  9. //舵机C,前后 CH2_B3
  10. void check_C(void);
  11. //舵机D,底座 CH1_A15
  12. void check_D(void);
  13. #endif

main.c 

  1. /* Private includes ----------------------------------------------------------*/
  2. /* USER CODE BEGIN Includes */
  3. #include "stdio.h"
  4. #include "PWM.h"
  5. /* USER CODE END Includes */
  6. /* Private variables ---------------------------------------------------------*/
  7. /* USER CODE BEGIN PV */
  8. uint16_t adc_dma[4];//DMA搬运的ADC采集值
  9. uint8_t angle[4] = {90,90,90,90};//舵机角度
  10. uint8_t cnt = 0;//计数用
  11. /* USER CODE END PV */
  12. /* USER CODE BEGIN 2 */
  13. printf("Start\r\n");
  14. HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4);
  15. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  16. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
  17. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
  18. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
  19. HAL_Delay(500);
  20. /* USER CODE END 2 */
  21. /* Infinite loop */
  22. /* USER CODE BEGIN WHILE */
  23. while (1)
  24. {
  25. /* USER CODE END WHILE */
  26. /* USER CODE BEGIN 3 */
  27. //根据摇杆DMA判断舵机该如何运动
  28. check_A();
  29. check_B();
  30. check_C();
  31. check_D();
  32. //输出PWM波使舵机运动
  33. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
  34. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
  35. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
  36. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
  37. cnt++;
  38. if(cnt>= 50)
  39. {
  40. printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);
  41. cnt = 0;
  42. }
  43. HAL_Delay(20);
  44. }
  45. /* USER CODE END 3 */

3.示教器控制

把示教器控制的业务代码也写出来,和蓝牙/摇杆控制封装在一个函数里,main里直接调用这个函数就行。

主要是PWM.c添加了一些代码,直接修改上面代码即可。

  1. extern uint16_t adc_dma[4];//DMA搬运的ADC采集值,直接用8通道就改长度8
  2. extern uint8_t angle[4];//舵机角度
  3. extern uint8_t Mode;
  4. extern uint8_t cmd_BLE;
  5. //根据输入的角度获取对应pwm占空比参数
  6. uint8_t Angle(uint8_t pwm_pulse)
  7. {
  8. return pwm_pulse + 44;
  9. }
  10. //舵机角度如何变化和模式判断的函数
  11. void sg()
  12. {
  13. if(Mode == 1)//蓝牙/摇杆模式
  14. {
  15. check_A();
  16. check_B();
  17. check_C();
  18. check_D();
  19. }
  20. else if(Mode == 2)//示教器模式
  21. {
  22. translate();
  23. }
  24. //输出PWM波使舵机运动
  25. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
  26. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
  27. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
  28. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
  29. HAL_Delay(20);
  30. }
  31. void translate()//把采集的模拟值转变为角度。即0~4095变为0~180,除以22.75即可。
  32. {
  33. angle[3] = (uint8_t)((double)adc_dma[0] / 22.75)/2;
  34. angle[2] = (uint8_t)((double)adc_dma[1] / 22.75);
  35. angle[1] = (uint8_t)((double)adc_dma[2] / 22.75) - 10;
  36. angle[0] = 180 - (uint8_t)((double)adc_dma[3] / 22.75);//电位器装反,改为 180 - 即可
  37. //直接用8通道就是adc_dma[4~7]
  38. }

PWM.h

  1. #ifndef __PWM_H__
  2. #define __PWM_H__
  3. //根据输入的角度获取对应pwm占空比参数
  4. unsigned char Angle(unsigned char pwm_pulse);
  5. //舵机A,夹爪 CH4_B11
  6. void check_A(void);
  7. //舵机B,上下 CH3_B10
  8. void check_B(void);
  9. //舵机C,前后 CH2_B3
  10. void check_C(void);
  11. //舵机D,底座 CH1_A15
  12. void check_D(void);
  13. void sg(void);
  14. void translate(void);
  15. #endif

 main.c

  1. /* USER CODE BEGIN 2 */
  2. printf("Start\r\n");
  3. HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4);
  4. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  5. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
  6. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
  7. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
  8. HAL_Delay(500);
  9. /* USER CODE END 2 */
  10. /* Infinite loop */
  11. /* USER CODE BEGIN WHILE */
  12. while (1)
  13. {
  14. /* USER CODE END WHILE */
  15. /* USER CODE BEGIN 3 */
  16. sg();//判断舵机该如何运动
  17. cnt++;
  18. if(cnt>= 25)
  19. {
  20. printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);
  21. //printf("adc_dma = {%d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3]);
  22. cnt = 0;
  23. }
  24. }
  25. /* USER CODE END 3 */

至此,基本的控制功能代码已经完成了。小白的话完成到这里已经很不错了。

4.记录动作信息

本质上就是保存当前的舵机的四个角度值。

这里暂时先用二维数组来做。

被添加的代码:

蓝牙指令A后的 m g D ,对应我们这里 记录当前角度、获取所有记录的角度、删除所有记录。

  1. #include "stdio.h"
  2. #include "string.h"
  3. uint8_t memory[10][4];//记录用的数组
  4. uint8_t i,j = 0;
  5. void sg()
  6. {
  7. if(Mode == 1)
  8. {
  9. check_A();
  10. check_B();
  11. check_C();
  12. check_D();
  13. }
  14. else if(Mode == 2)
  15. {
  16. translate();
  17. if(cmd_BLE == 'm' && i<9)
  18. {
  19. for(j=0;j<4;j++)
  20. {
  21. memory[i][j] = angle[j];
  22. }
  23. printf("储存动作\r\n");
  24. cmd_BLE = 's';
  25. i++;
  26. }
  27. else if(cmd_BLE == 'm' && i>=9)
  28. printf("动作已满\r\n");
  29. cmd_BLE = 's';
  30. }
  31. if(cmd_BLE == 'g')
  32. {
  33. for(i=0;i<10;i++)
  34. {
  35. for(j=0;j<4;j++)
  36. {
  37. printf("%d ",memory[i][j] + 0x30);
  38. }
  39. printf("\r\n");
  40. if(memory[i][j] == '\0') break;
  41. }
  42. cmd_BLE = 's';
  43. }
  44. else if(cmd_BLE == 'D')
  45. {
  46. for(i=0;i<10;i++)
  47. {
  48. memset(memory[i],'\0',4);
  49. }
  50. i = 0;
  51. printf("已清除动作");
  52. cmd_BLE = 's';
  53. }
  54. //输出PWM波使舵机运动
  55. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
  56. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
  57. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
  58. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
  59. HAL_Delay(20);
  60. }

5.执行记录的动作

这里开始已经转到FreeRTOS上了,没继续在裸机上做。所以没写对应源码,不过可以拿后面FreeRTOS上实现的代码放在这里。没区别一样可以用。需要你们自己来实现和调试。

PWM.c

主要就是下面这两个函数:

location_cnt是数组长度,宏定义出来就行,自己调整长度

  1. uint8_t memory[location_cnt][4];
  2. uint8_t i,j = 0;
  3. uint8_t angle_target[4] = {90,90,90,90};
  4. uint8_t angle_target_flag = 0;
  5. void get_target()//从数组获得位置信息并转换位角度目标值
  6. {
  7. angle_target_flag = 0;
  8. for(j=0;j<4;j++)
  9. {
  10. if(angle[j] == angle_target[j]) angle_target_flag++;
  11. }
  12. if(angle_target_flag == 4) i++;
  13. for(j=0;j<4;j++)
  14. {
  15. if(memory[i][j] == '\0')
  16. {
  17. i = 0;
  18. }
  19. angle_target[j] = memory[i][j];
  20. }
  21. }
  22. void reach_target()//角度值像角度目标值靠近,用于简单防抖和执行记忆动作
  23. {
  24. for(j = 0;j <4;j++)
  25. {
  26. if(angle[j] > angle_target[j])
  27. {
  28. angle[j]--;
  29. }
  30. else if(angle[j] < angle_target[j])
  31. {
  32. angle[j]++;
  33. }
  34. }
  35. }
  36. void translate()//根据实际情况做了一点角度矫正和限位
  37. {
  38. angle_target[3] = (uint8_t)((double)adc_dma[4] / 22.75)/2;
  39. angle_target[2] = (uint8_t)((double)adc_dma[5] / 22.75);
  40. angle_target[1] = (uint8_t)((double)adc_dma[6] / 22.75) - 10;
  41. angle_target[0] = 180 - (uint8_t)((double)adc_dma[7] / 22.75);
  42. if(angle_target[1]<45) angle_target[1]=45;
  43. else if(angle_target[1]>135) angle_target[1]=135;
  44. if(angle_target[2]<45) angle_target[1]=45;
  45. else if(angle_target[2]>135) angle_target[1]=135;
  46. }
  47. //是否记录当前位置信息
  48. void if_BLE_cmd()
  49. {
  50. switch(cmd_BLE)
  51. {
  52. case 'm':
  53. if(i < location_cnt)
  54. {
  55. for(j=0;j<4;j++)
  56. {
  57. memory[i][j] = angle[j];
  58. }
  59. printf("储存动作\r\n");
  60. cmd_BLE = 's';
  61. i++;
  62. }
  63. else
  64. {
  65. printf("动作已满\r\n");
  66. cmd_BLE = 's';
  67. }
  68. break;
  69. case 'g':
  70. for(i=0;i < location_cnt;i++)
  71. {
  72. for(j=0;j<4;j++)
  73. {
  74. printf("%d ",memory[i][j]);
  75. }
  76. printf("\r\n");
  77. if(memory[i][j] == '\0') break;
  78. }
  79. cmd_BLE = 's';
  80. break;
  81. case 'D':
  82. for(i=0; i < location_cnt ;i++)
  83. {
  84. memset(memory[i],'\0',4);
  85. }
  86. i = 0;
  87. printf("已清除动作");
  88. cmd_BLE = 's';
  89. break;
  90. }
  91. }
  92. void check_sg_cmd()//蓝牙和摇杆控制
  93. {
  94. check_A();
  95. check_B();
  96. check_C();
  97. check_D();
  98. }

usart.c

  1. /*机械臂控制模式,默认为1
  2. 1:摇杆控制
  3. 2:示教器控制
  4. 3:执行记忆动作
  5. */
  6. uint8_t Mode = 1;
  7. //=======中断信息处理=======
  8. //模式切换
  9. if (!strcmp((const char *)UART1_RX_Buffer, "M1"))
  10. {
  11. Mode = 1;
  12. printf("摇杆模式\r\n");
  13. }
  14. else if(!strcmp((const char *)UART1_RX_Buffer, "M2"))
  15. {
  16. Mode = 2;
  17. printf("示教模式\r\n");
  18. }
  19. else if(!strcmp((const char *)UART1_RX_Buffer, "M3"))
  20. {
  21. Mode = 3;
  22. printf("执行记忆动作\r\n");
  23. }

freertos.c内相关代码       

和main.c的while循环一样理解就行,一样用

  1. /* Infinite loop */
  2. for(;;)
  3. {
  4. if(Mode == 1)//摇杆和蓝牙控制
  5. {
  6. check_sg_cmd();
  7. }
  8. else if(Mode == 2)//示教器控制
  9. {
  10. translate();
  11. reach_target();
  12. }
  13. else if(Mode == 3)//动作执行
  14. {
  15. get_target();
  16. reach_target();
  17. }
  18. if_BLE_cmd();//蓝牙指令处理
  19. //输出PWM波使舵机运动
  20. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
  21. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
  22. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
  23. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
  24. osDelay(15);//通过调整此延时可以改变机械臂运行速度
  25. }

注:

裸机开发到这里就结束了,大部分功能都简单实现出来了。

如果发现舵机运动每秒顿一次,请把每秒串口打印信息关掉就行,这是裸机的劣势之一所在。

三 FreeRTOS上完成项目

1.加入IIC的OLED屏显 和 动作执行(数组版) 

下面是移植到FreeRTOS操作系统上运行,没法介绍太详细,建议先系统学一下STM32 HAL开发以及FreeRTOS,再进行。

快速简述,就是把需求分给多个任务去执行,

一个任务负责角度信息处理,

一个任务负责串口发送数据

一个任务负责OLED屏显

且这里开始把adc改成直接测8组了

接线,把OLED的SDA和SCL对应板子接好 

CubeMX配置:

打开IIC

 设置4个外部中断,把原来的 ADC IN1~7 ==> IN2~9,因为我板载两个按钮位于A0和A1

然后B4 B5我自己外接了两个按钮。中断都是下降沿触发

FreeRTOS配置:使用了V1版本,创建三个任务,优先级都设为普通

 

打开定时器用于两个外部中断按钮B4 B5的定时器消抖,我这两个没有硬件防抖,如果你们的按钮有电容,就不用。

 

使用单次定时器 

 

 代码:

usart.c

  1. /* USER CODE BEGIN 0 */
  2. #include "stdio.h"
  3. #include "string.h"
  4. #include "PWM.h"
  5. #include "adc.h"
  6. #include "dma.h"
  7. /*机械臂控制模式,默认为1
  8. 1:摇杆控制
  9. 2:示教器控制
  10. 3:执行记忆动作
  11. */
  12. uint8_t Mode = 1;
  13. /*蓝牙控制机械臂指令:
  14. s/m 停/储存当前动作
  15. l/r 左右
  16. u/d 上下
  17. f/b 前后
  18. o/c 开合*/
  19. uint8_t cmd_BLE = 's';
  20. extern uint16_t adc_dma[8];//DMA搬运的ADC采集值
  21. extern uint8_t angle[4];
  22. uint8_t k;
  23. //覆写printf
  24. int fputc(int ch, FILE *f)
  25. {
  26. unsigned char temp[1]={ch};
  27. HAL_UART_Transmit(&huart1,temp,1,0xffff);
  28. return ch;
  29. }
  30. //=====串口(中断)=======
  31. //串口接收缓存(1字节)
  32. uint8_t buf=0;
  33. //定义最大接收字节数 200,可根据需求调整
  34. #define UART1_REC_LEN 200
  35. // 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
  36. uint8_t UART1_RX_Buffer[UART1_REC_LEN];
  37. // 接收状态
  38. // bit15, 接收完成标志
  39. // bit14, 接收到0x0d
  40. // bit13~0, 接收到的有效字节数目
  41. uint16_t UART1_RX_STA=0;
  42. // 串口中断:接收完成回调函数,收到一个数据后,在这里处理
  43. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  44. {
  45. // 判断中断是由哪个串口触发的
  46. if(huart->Instance == USART1)
  47. {
  48. // 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
  49. if((UART1_RX_STA & 0x8000) == 0)
  50. {
  51. // 如果已经收到了 0x0d (回车),
  52. if(UART1_RX_STA & 0x4000)
  53. {
  54. // 则接着判断是否收到 0x0a (换行)
  55. if(buf == 0x0a)
  56. {
  57. // 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
  58. UART1_RX_STA |= 0x8000;
  59. //=======中断信息处理=======
  60. //模式切换
  61. if (!strcmp((const char *)UART1_RX_Buffer, "M1"))
  62. {
  63. Mode = 1;
  64. printf("摇杆模式\r\n");
  65. }
  66. else if(!strcmp((const char *)UART1_RX_Buffer, "M2"))
  67. {
  68. Mode = 2;
  69. printf("示教模式\r\n");
  70. }
  71. else if(!strcmp((const char *)UART1_RX_Buffer, "M3"))
  72. {
  73. Mode = 3;
  74. printf("执行记忆动作\r\n");
  75. }
  76. //获取蓝牙控制指令
  77. else if(UART1_RX_Buffer[0] == 'A')
  78. {
  79. cmd_BLE = UART1_RX_Buffer[1];
  80. }
  81. else {
  82. if(UART1_RX_Buffer[0] != '\0')
  83. printf("指令发送错误:%s\r\n", UART1_RX_Buffer);
  84. }
  85. //==========================
  86. memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer));
  87. // 重新开始下一次接收
  88. UART1_RX_STA = 0;
  89. //==========================
  90. }
  91. else
  92. // 否则认为接收错误,重新开始
  93. UART1_RX_STA = 0;
  94. }
  95. else // 如果没有收到了 0x0d (回车)
  96. {
  97. //则先判断收到的这个字符是否是 0x0d (回车)
  98. if(buf == 0x0d)
  99. {
  100. // 是的话则将 bit14 位置为1
  101. UART1_RX_STA |= 0x4000;
  102. }
  103. else
  104. {
  105. // 否则将接收到的数据保存在缓存数组里
  106. UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
  107. UART1_RX_STA++;
  108. // 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
  109. if(UART1_RX_STA > UART1_REC_LEN - 1)
  110. UART1_RX_STA = 0;
  111. }
  112. }
  113. }
  114. // 重新开启中断
  115. HAL_UART_Receive_IT(&huart1, &buf, 1);
  116. }
  117. }
  118. /* USER CODE END 0 */
  119. /* USER CODE BEGIN USART1_Init 2 */
  120. HAL_UART_Receive_IT(&huart1, &buf, 1);
  121. /* USER CODE END USART1_Init 2 */

 PWM.c        解释注释在前面说过

  1. #include "PWM.h"
  2. #include "main.h"
  3. #include "tim.h"
  4. #include "stdio.h"
  5. #include "string.h"
  6. extern uint16_t adc_dma[8];//DMA搬运的ADC采集值
  7. extern uint8_t angle[4];//舵机角度
  8. extern uint8_t Mode;
  9. extern uint8_t cmd_BLE;
  10. uint8_t memory[location_cnt][4];
  11. uint8_t i,j = 0;
  12. uint8_t angle_target[4] = {90,90,90,90};
  13. uint8_t angle_target_flag = 0;
  14. //根据输入的角度获取对应pwm占空比参数
  15. uint8_t Angle(uint8_t pwm_pulse)
  16. {
  17. return pwm_pulse + 44;
  18. }
  19. void get_target()
  20. {
  21. angle_target_flag = 0;
  22. for(j=0;j<4;j++)
  23. {
  24. if(angle[j] == angle_target[j]) angle_target_flag++;
  25. }
  26. if(angle_target_flag == 4) i++;
  27. for(j=0;j<4;j++)
  28. {
  29. if(memory[i][j] == '\0')
  30. {
  31. i = 0;
  32. }
  33. angle_target[j] = memory[i][j];
  34. }
  35. }
  36. void reach_target()
  37. {
  38. for(j = 0;j <4;j++)
  39. {
  40. if(angle[j] > angle_target[j])
  41. {
  42. angle[j]--;
  43. }
  44. else if(angle[j] < angle_target[j])
  45. {
  46. angle[j]++;
  47. }
  48. }
  49. }
  50. void translate()//根据实际情况做了一点角度矫正和限位
  51. {
  52. angle_target[3] = (uint8_t)((double)adc_dma[4] / 22.75)/2;
  53. angle_target[2] = (uint8_t)((double)adc_dma[5] / 22.75);
  54. angle_target[1] = (uint8_t)((double)adc_dma[6] / 22.75) - 10;
  55. angle_target[0] = 180 - (uint8_t)((double)adc_dma[7] / 22.75);
  56. if(angle_target[1]<45) angle_target[1]=45;
  57. else if(angle_target[1]>135) angle_target[1]=135;
  58. if(angle_target[2]<45) angle_target[1]=45;
  59. else if(angle_target[2]>135) angle_target[1]=135;
  60. }
  61. //是否记录当前位置信息
  62. void if_BLE_cmd()
  63. {
  64. switch(cmd_BLE)
  65. {
  66. case 'm':
  67. if(i < location_cnt)
  68. {
  69. for(j=0;j<4;j++)
  70. {
  71. memory[i][j] = angle[j];
  72. }
  73. printf("储存动作\r\n");
  74. cmd_BLE = 's';
  75. i++;
  76. }
  77. else
  78. {
  79. printf("动作已满\r\n");
  80. cmd_BLE = 's';
  81. }
  82. break;
  83. case 'g':
  84. for(i=0;i < location_cnt;i++)
  85. {
  86. for(j=0;j<4;j++)
  87. {
  88. printf("%d ",memory[i][j]);
  89. }
  90. printf("\r\n");
  91. if(memory[i][j] == '\0') break;
  92. }
  93. cmd_BLE = 's';
  94. break;
  95. case 'D':
  96. for(i=0; i < location_cnt ;i++)
  97. {
  98. memset(memory[i],'\0',4);
  99. }
  100. i = 0;
  101. printf("已清除动作");
  102. cmd_BLE = 's';
  103. break;
  104. }
  105. }
  106. void check_sg_cmd()
  107. {
  108. check_A();
  109. check_B();
  110. check_C();
  111. check_D();
  112. }
  113. //舵机A,夹爪 CH4_B11-D1;adc4_A3
  114. void check_A()
  115. {
  116. if(Mode == 1)
  117. {
  118. if((cmd_BLE == 'c' || adc_dma[3] > 4000) && angle[3] < 90)//合
  119. {
  120. angle[3]++;
  121. }
  122. else if((cmd_BLE == 'o' || adc_dma[3] <1000) && angle[3] > 0)//开
  123. {
  124. angle[3]--;
  125. }
  126. }
  127. }
  128. //舵机B,上下 CH3_B10-D2;adc3_A2
  129. void check_B()
  130. {
  131. if(Mode == 1)
  132. {
  133. if((cmd_BLE == 'u' || adc_dma[2] <1000) && angle[2] < 135)//上
  134. {
  135. angle[2]++;
  136. }
  137. else if((cmd_BLE == 'd' || adc_dma[2] > 4000) && angle[2] > 45)//下
  138. {
  139. angle[2]--;
  140. }
  141. }
  142. }
  143. //舵机C,前后 CH2_B3-D3;adc2_A1
  144. void check_C()
  145. {
  146. if(Mode == 1)
  147. {
  148. if((cmd_BLE == 'f' || adc_dma[1] <1000) && angle[1] < 135)//前
  149. {
  150. angle[1]++;
  151. }
  152. else if((cmd_BLE == 'b' || adc_dma[1] > 4000) && angle[1] > 45)//后
  153. {
  154. angle[1]--;
  155. }
  156. }
  157. }
  158. //舵机D,底座 CH1_A15-D0;adc1_A0
  159. void check_D()
  160. {
  161. if(Mode == 1)
  162. {
  163. if((cmd_BLE == 'l' || adc_dma[0] <1000) && angle[0] < 180)//左
  164. {
  165. angle[0]++;
  166. }
  167. else if((cmd_BLE == 'r' || adc_dma[0] > 4000) && angle[0] > 0)//右
  168. {
  169. angle[0]--;
  170. }
  171. }
  172. }

PWM.h

  1. #ifndef __PWM_H__
  2. #define __PWM_H__
  3. #define location_cnt 20
  4. //根据输入的角度获取对应pwm占空比参数
  5. unsigned char Angle(unsigned char pwm_pulse);
  6. //舵机A,夹爪 CH4_B11
  7. void check_A(void);
  8. //舵机B,上下 CH3_B10
  9. void check_B(void);
  10. //舵机C,前后 CH2_B3
  11. void check_C(void);
  12. //舵机D,底座 CH1_A15
  13. void check_D(void);
  14. void check_sg_cmd(void);
  15. void if_BLE_cmd(void);
  16. void translate(void);
  17. void get_target(void);
  18. void reach_target(void);
  19. #endif

OLED.c

  1. #include "OLED.h"
  2. #include "i2c.h"
  3. #include "oledfont.h"
  4. void Oled_Write_Cmd(uint8_t OLED_cmd)
  5. {
  6. HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT, &OLED_cmd, 1, 0xff);
  7. }
  8. void Oled_Write_Data(uint8_t OLED_data)
  9. {
  10. HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT, &OLED_data, 1, 0xff);
  11. }
  12. //OLED初始化代码,直接复制粘贴
  13. void Oled_Init(void){
  14. Oled_Write_Cmd(0xAE);//--display off
  15. Oled_Write_Cmd(0x00);//---set low column address
  16. Oled_Write_Cmd(0x10);//---set high column address
  17. Oled_Write_Cmd(0x40);//--set start line address
  18. Oled_Write_Cmd(0xB0);//--set page address
  19. Oled_Write_Cmd(0x81); // contract control
  20. Oled_Write_Cmd(0xFF);//--128
  21. Oled_Write_Cmd(0xA1);//set segment remap
  22. Oled_Write_Cmd(0xA6);//--normal / reverse
  23. Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
  24. Oled_Write_Cmd(0x3F);//--1/32 duty
  25. Oled_Write_Cmd(0xC8);//Com scan direction
  26. Oled_Write_Cmd(0xD3);//-set display offset
  27. Oled_Write_Cmd(0x00);//
  28. Oled_Write_Cmd(0xD5);//set osc division
  29. Oled_Write_Cmd(0x80);//
  30. Oled_Write_Cmd(0xD8);//set area color mode off
  31. Oled_Write_Cmd(0x05);//
  32. Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
  33. Oled_Write_Cmd(0xF1);//
  34. Oled_Write_Cmd(0xDA);//set com pin configuartion
  35. Oled_Write_Cmd(0x12);//
  36. Oled_Write_Cmd(0xDB);//set Vcomh
  37. Oled_Write_Cmd(0x30);//
  38. Oled_Write_Cmd(0x8D);//set charge pump enable
  39. Oled_Write_Cmd(0x14);//
  40. Oled_Write_Cmd(0xAF);//--turn on oled panel
  41. }
  42. void Oled_Clear()
  43. {
  44. unsigned char i,j; //-128 --- 127
  45. for(i=0;i<8;i++){
  46. Oled_Write_Cmd(0xB0 + i);//page0--page7
  47. //每个page从0列
  48. Oled_Write_Cmd(0x00);
  49. Oled_Write_Cmd(0x10);
  50. //0到127列,依次写入0,每写入数据,列地址自动偏移
  51. for(j = 0;j<128;j++){
  52. Oled_Write_Data(0);
  53. }
  54. }
  55. }
  56. //在屏幕上显示
  57. //===============================官方提供的代码============================================
  58. void Oled_Show_Char(char row,char col,uint8_t oledChar){ //row*2-2
  59. unsigned int i;
  60. Oled_Write_Cmd(0xb0+(row*2-2)); //page 0
  61. Oled_Write_Cmd(0x00+(col&0x0f)); //low
  62. Oled_Write_Cmd(0x10+(col>>4)); //high
  63. for(i=((oledChar-32)*16);i<((oledChar-32)*16+8);i++){
  64. Oled_Write_Data(F8X16[i]); //写数据oledTable1
  65. }
  66. Oled_Write_Cmd(0xb0+(row*2-1)); //page 1
  67. Oled_Write_Cmd(0x00+(col&0x0f)); //low
  68. Oled_Write_Cmd(0x10+(col>>4)); //high
  69. for(i=((oledChar-32)*16+8);i<((oledChar-32)*16+8+8);i++){
  70. Oled_Write_Data(F8X16[i]); //写数据oledTable1
  71. }
  72. }
  73. /******************************************************************************/
  74. // 函数名称:Oled_Show_Char
  75. // 输入参数:oledChar
  76. // 输出参数:无
  77. // 函数功能:OLED显示单个字符
  78. /******************************************************************************/
  79. void Oled_Show_Str(char row,char col,char *str){
  80. while(*str!=0){
  81. Oled_Show_Char(row,col,*str);
  82. str++;
  83. col += 8;
  84. }
  85. }

OLED.h

  1. #ifndef __OLED_H__
  2. #define __OLED_H__
  3. void Oled_Init(void);
  4. void Oled_Clear(void);
  5. void Oled_Show_Str(char row,char col,char *str);
  6. #endif

main.c        放了一点初始化配置

  1. /* Private includes ----------------------------------------------------------*/
  2. /* USER CODE BEGIN Includes */
  3. #include "stdio.h"
  4. /* USER CODE END Includes */
  5. /* Private variables ---------------------------------------------------------*/
  6. /* USER CODE BEGIN PV */
  7. uint16_t adc_dma[8];//DMA搬运的ADC采集值
  8. uint8_t angle[4] = {90,90,90,90};//舵机角度
  9. /* USER CODE END PV */
  10. /* USER CODE BEGIN 2 */
  11. // 开启接收中断
  12. printf("Start\r\n");//程序开始运行
  13. HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,8); //开启ADC和DMA
  14. //系统稳定半秒
  15. HAL_Delay(500);
  16. /* USER CODE END 2 */

freeRTOS.c

uint8_t anti_shake = 0;//定时器按钮消抖标志位

最下面的函数就是外部中断回调函数,anti_shake为0才能通过判断,一旦进入就将anti_shake置为1,避免因为按下的抖动导致按一次触发好几次中断。通过判断之后,启动单次定时器800ms,定时器到时间后触发定时器中断回调函数,在这里再把anti_shake重新置为0。

其中由于我的板载按键0和1本身有硬件消抖,所以不用软件定时器消抖。

  1. /* Private includes ----------------------------------------------------------*/
  2. /* USER CODE BEGIN Includes */
  3. #include "stdio.h"
  4. #include "PWM.h"
  5. #include "tim.h"
  6. #include "adc.h"
  7. #include "OLED.h"
  8. /* USER CODE END Includes */
  9. /* Private variables ---------------------------------------------------------*/
  10. /* USER CODE BEGIN Variables */
  11. uint8_t anti_shake = 0;//定时器按钮消抖标志位
  12. extern uint16_t adc_dma[8];//DMA搬运的ADC采集值
  13. extern uint8_t angle[4];//舵机角度
  14. extern uint8_t Mode;
  15. extern uint8_t memory[location_cnt][4];
  16. extern uint8_t i,j;
  17. //角度信息字符串
  18. char speedMes[8]; //IIC发送角度数据的字符串缓冲区
  19. char speedMes1[8];
  20. char speedMes2[8];
  21. char speedMes3[8];
  22. char speedMes4[8];
  23. char speedMes5[8];
  24. /* USER CODE END Variables */
  25. /* USER CODE BEGIN Header_Start_check_angle */
  26. /**
  27. * @brief Function implementing the check_angle thread.
  28. * @param argument: Not used
  29. * @retval None
  30. */
  31. /* USER CODE END Header_Start_check_angle */
  32. void Start_check_angle(void const * argument)
  33. {
  34. /* USER CODE BEGIN Start_check_angle */
  35. //开启4路PWM
  36. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  37. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
  38. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
  39. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
  40. /* Infinite loop */
  41. for(;;)
  42. {
  43. if(Mode == 1)
  44. {
  45. check_sg_cmd();
  46. }
  47. else if(Mode == 2)
  48. {
  49. translate();
  50. reach_target();
  51. }
  52. else if(Mode == 3)
  53. {
  54. get_target();
  55. reach_target();
  56. }
  57. if_BLE_cmd();
  58. //输出PWM波使舵机运动
  59. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
  60. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
  61. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
  62. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
  63. osDelay(15);//通过调整此延时可以改变机械臂运行速度
  64. }
  65. /* USER CODE END Start_check_angle */
  66. }
  67. /* USER CODE BEGIN Header_Start_usart_show */
  68. /**
  69. * @brief Function implementing the usart_show thread.
  70. * @param argument: Not used
  71. * @retval None
  72. */
  73. /* USER CODE END Header_Start_usart_show */
  74. void Start_usart_show(void const * argument)
  75. {
  76. /* USER CODE BEGIN Start_usart_show */
  77. /* Infinite loop */
  78. for(;;)
  79. {
  80. printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);
  81. printf("adc_dma1 = {%d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3]);
  82. printf("adc_dma2 = {%d, %d, %d, %d}\r\n",adc_dma[4],adc_dma[5],adc_dma[6],adc_dma[7]);
  83. printf("\r\n");
  84. osDelay(1000);
  85. }
  86. /* USER CODE END Start_usart_show */
  87. }
  88. /* USER CODE BEGIN Header_Start_OLED_Task */
  89. /**
  90. * @brief Function implementing the OLED_Task thread.
  91. * @param argument: Not used
  92. * @retval None
  93. */
  94. /* USER CODE END Header_Start_OLED_Task */
  95. void Start_OLED_Task(void const * argument)
  96. {
  97. /* USER CODE BEGIN Start_OLED_Task */
  98. Oled_Init();
  99. Oled_Clear();
  100. /* Infinite loop */
  101. for(;;)
  102. {
  103. //串口数据的字符串拼装,speed是格子,每个格子1cm
  104. sprintf(speedMes,"A: %d ",angle[0]);
  105. sprintf(speedMes1,"B: %d ",angle[1]);
  106. sprintf(speedMes2,"C: %d ",angle[2]);
  107. sprintf(speedMes3,"D: %d ",angle[3]);
  108. sprintf(speedMes4,"Mode %d ",Mode);
  109. sprintf(speedMes5,"S %d ",i);
  110. Oled_Show_Str(1,5,speedMes);
  111. Oled_Show_Str(1,69,speedMes1);
  112. Oled_Show_Str(2,5,speedMes2);
  113. Oled_Show_Str(2,69,speedMes3);
  114. Oled_Show_Str(4,0,speedMes4);
  115. Oled_Show_Str(4,64,speedMes5);
  116. osDelay(500);
  117. }
  118. /* USER CODE END Start_OLED_Task */
  119. }
  120. /* Callback01 function */
  121. void Callback01(void const * argument)
  122. {
  123. /* USER CODE BEGIN Callback01 */
  124. anti_shake = 0;
  125. /* USER CODE END Callback01 */
  126. }
  127. /* Private application code --------------------------------------------------*/
  128. /* USER CODE BEGIN Application */
  129. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  130. {
  131. switch(GPIO_Pin)
  132. {
  133. case GPIO_PIN_0:
  134. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
  135. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
  136. Mode = 1;
  137. printf("摇杆模式\r\n");
  138. break;
  139. case GPIO_PIN_1:
  140. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
  141. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
  142. Mode = 2;
  143. printf("示教模式\r\n");
  144. break;
  145. case GPIO_PIN_4:
  146. if(anti_shake == 0)
  147. {
  148. anti_shake = 1;
  149. osTimerStart(myTimer01Handle,800);
  150. if(i<location_cnt)
  151. {
  152. for(j=0;j<4;j++)
  153. {
  154. memory[i][j] = angle[j];
  155. }
  156. printf("储存动作\r\n");
  157. i++;
  158. }
  159. else if(i>=9)
  160. {
  161. printf("动作已满\r\n");
  162. }
  163. }
  164. case GPIO_PIN_5:
  165. if(anti_shake == 0)
  166. {
  167. anti_shake = 1;
  168. Mode = 3;
  169. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
  170. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
  171. printf("执行记忆动作\r\n");
  172. osTimerStart(myTimer01Handle,800);
  173. }
  174. }
  175. }
  176. /* USER CODE END Application */

2.链表的增删遍历实现动作记忆和执行

创建 Memary_LinkList.c和.h文件,放链表相关代码

  1. #include "Memary_LinkList.h"
  2. #include "main.h"
  3. #include "stdio.h"
  4. #include <stdlib.h>
  5. struct Memary//每个链表节点的结构组成
  6. {
  7. uint8_t A,B,C,D,cnt;//四个角度和节点的对应编号
  8. struct Memary *next;//下一个链表节点的地址
  9. }*head,*tail,*temp;//声明三个指针,分别指向链表头尾,和一个临时指针
  10. void Memary_Init()//初始化链表
  11. {
  12. head = (struct Memary *)malloc(sizeof(struct Memary));
  13. head -> cnt = 0;//链表里没记忆信息时编号只有0
  14. head -> next = NULL;
  15. tail = head;
  16. temp = head;
  17. }
  18. void prinrt_List()//打印整个链表的内容
  19. {
  20. temp = head;
  21. while(1)
  22. {
  23. printf("%d:{%d,%d,%d,%d}\r\n",temp->cnt,temp->A,temp->B,temp->C,temp->D);
  24. if(temp->next != NULL) temp = temp->next;
  25. else break;
  26. }
  27. }
  28. void delete_List()//清空整个链表
  29. {
  30. while(head->next != NULL)
  31. {
  32. temp = head;
  33. head = head->next;
  34. free(temp);
  35. }
  36. temp = head;
  37. head->cnt = 0;
  38. }
  39. void addNode(uint8_t angle[4])//记忆动作,即新增一个节点
  40. {
  41. if(head->cnt != 0)//编号cnt从1开始,从头节点开始存数据
  42. {
  43. struct Memary *p = (struct Memary *)malloc(sizeof(struct Memary));
  44. tail->next = p;
  45. p->cnt = tail->cnt;
  46. tail = p;
  47. }
  48. tail->A = angle[0];
  49. tail->B = angle[1];
  50. tail->C = angle[2];
  51. tail->D = angle[3];
  52. tail->cnt++;
  53. temp = tail;
  54. }
  55. uint8_t angle_temp[4];//用于提取记忆的角度
  56. uint8_t *p = angle_temp;//用指针传递
  57. uint8_t *getNode()//把记忆信息传出去,自动循环传整个链表
  58. {
  59. angle_temp[0] = temp->A;
  60. angle_temp[1] = temp->B;
  61. angle_temp[2] = temp->C;
  62. angle_temp[3] = temp->D;
  63. if(temp->next == NULL)
  64. temp = head;
  65. else
  66. temp = temp->next;
  67. return p;
  68. }
  69. uint8_t sizeof_List()//别管名字,看temp在哪用的,
  70. {
  71. //反应在OLED上就是可以看见当前存储到第几个动作了或者正在执行第一个动作
  72. return temp->cnt;
  73. }
  1. #ifndef __MEMARY_LINKLIST__
  2. #define __MEMARY_LINKLIST__
  3. #include "main.h"
  4. void Memary_Init(void);
  5. void addNode(uint8_t angle[4]);
  6. void prinrt_List(void);
  7. void delete_List(void);
  8. uint8_t *getNode(void);
  9. uint8_t sizeof_List(void);
  10. #endif

原PWM.c改成了MG90s.c

  1. #include "MG90s.h"
  2. #include "main.h"
  3. #include "tim.h"
  4. #include "stdio.h"
  5. #include "string.h"
  6. #include "Memary_LinkList.h"
  7. extern uint16_t adc_dma[8];//DMA搬运的ADC采集值
  8. extern uint8_t angle[4];//舵机角度
  9. extern uint8_t Mode;
  10. extern uint8_t cmd_BLE;
  11. uint8_t i;
  12. uint8_t angle_target[4] = {90,90,90,90};
  13. uint8_t *p_angle_target;
  14. uint8_t angle_target_flag = 4;//默认第一次为4
  15. //根据输入的角度获取对应pwm占空比参数
  16. uint8_t Angle(uint8_t pwm_pulse)
  17. {
  18. return pwm_pulse + 44;
  19. }
  20. void getAngleFromMemary()
  21. {
  22. if(angle_target_flag == 4)
  23. {
  24. p_angle_target = getNode();
  25. for(i = 0;i <4;i++)
  26. {
  27. angle_target[i] = *(p_angle_target + i);
  28. }
  29. }
  30. angle_target_flag = 0;
  31. for(i=0;i<4;i++)
  32. {
  33. if(angle[i] == angle_target[i]) angle_target_flag++;
  34. }
  35. }
  36. void reach_target()
  37. {
  38. for(i = 0;i <4;i++)
  39. {
  40. if(angle[i] > angle_target[i])
  41. {
  42. angle[i]--;
  43. }
  44. else if(angle[i] < angle_target[i])
  45. {
  46. angle[i]++;
  47. }
  48. }
  49. }
  50. void translate()
  51. {
  52. angle_target[3] = (uint8_t)((double)adc_dma[4] / 22.75)/2;
  53. angle_target[2] = (uint8_t)((double)adc_dma[5] / 22.75);
  54. angle_target[1] = (uint8_t)((double)adc_dma[6] / 22.75) - 10;
  55. angle_target[0] = 180 - (uint8_t)((double)adc_dma[7] / 22.75);
  56. if(angle_target[1]<45) angle_target[1]=45;
  57. else if(angle_target[1]>135) angle_target[1]=135;
  58. if(angle_target[2]<45) angle_target[1]=45;
  59. else if(angle_target[2]>135) angle_target[1]=135;
  60. }
  61. //是否记录当前位置信息
  62. void if_BLE_cmd()
  63. {
  64. switch(cmd_BLE)
  65. {
  66. case 'm':
  67. addNode(angle);
  68. printf("储存动作\r\n");
  69. cmd_BLE = 's';
  70. break;
  71. case 'g':
  72. prinrt_List();
  73. cmd_BLE = 's';
  74. break;
  75. case 'D':
  76. delete_List();
  77. printf("已清除动作\r\n");
  78. cmd_BLE = 's';
  79. break;
  80. }
  81. }
  82. void check_sg_cmd()
  83. {
  84. check_A();
  85. check_B();
  86. check_C();
  87. check_D();
  88. }
  89. //舵机A,夹爪 CH4_B11-D1;adc4_A3
  90. void check_A()
  91. {
  92. if(Mode == 1)
  93. {
  94. if((cmd_BLE == 'c' || adc_dma[3] > 4000) && angle[3] < 90)//合
  95. {
  96. angle[3]++;
  97. }
  98. else if((cmd_BLE == 'o' || adc_dma[3] <1000) && angle[3] > 0)//开
  99. {
  100. angle[3]--;
  101. }
  102. }
  103. }
  104. //舵机B,上下 CH3_B10-D2;adc3_A2
  105. void check_B()
  106. {
  107. if(Mode == 1)
  108. {
  109. if((cmd_BLE == 'u' || adc_dma[2] <1000) && angle[2] < 135)//上
  110. {
  111. angle[2]++;
  112. }
  113. else if((cmd_BLE == 'd' || adc_dma[2] > 4000) && angle[2] > 45)//下
  114. {
  115. angle[2]--;
  116. }
  117. }
  118. }
  119. //舵机C,前后 CH2_B3-D3;adc2_A1
  120. void check_C()
  121. {
  122. if(Mode == 1)
  123. {
  124. if((cmd_BLE == 'f' || adc_dma[1] <1000) && angle[1] < 135)//前
  125. {
  126. angle[1]++;
  127. }
  128. else if((cmd_BLE == 'b' || adc_dma[1] > 4000) && angle[1] > 45)//后
  129. {
  130. angle[1]--;
  131. }
  132. }
  133. }
  134. //舵机D,底座 CH1_A15-D0;adc1_A0
  135. void check_D()
  136. {
  137. if(Mode == 1)
  138. {
  139. if((cmd_BLE == 'l' || adc_dma[0] <1000) && angle[0] < 180)//左
  140. {
  141. angle[0]++;
  142. }
  143. else if((cmd_BLE == 'r' || adc_dma[0] > 4000) && angle[0] > 0)//右
  144. {
  145. angle[0]--;
  146. }
  147. }
  148. }

FreeRTOS.c要修改的部分

  1. // 任务1
  2. /* USER CODE END Header_Start_check_angle */
  3. void Start_check_angle(void const * argument)
  4. {
  5. /* USER CODE BEGIN Start_check_angle */
  6. Memary_Init();
  7. //开启4路PWM
  8. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  9. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
  10. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
  11. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
  12. /* Infinite loop */
  13. for(;;)
  14. {
  15. if(Mode == 1)
  16. {
  17. check_sg_cmd();
  18. }
  19. else if(Mode == 2)
  20. {
  21. translate();
  22. reach_target();
  23. }
  24. else if(Mode == 3)
  25. {
  26. getAngleFromMemary();
  27. reach_target();
  28. }
  29. if_BLE_cmd();
  30. //输出PWM波使舵机运动
  31. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
  32. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
  33. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
  34. __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
  35. osDelay(18);//通过调整此延时可以改变机械臂运行速度
  36. }
  37. /* USER CODE END Start_check_angle */
  38. }
  39. // 任务3
  40. /* USER CODE END Header_Start_OLED_Task */
  41. void Start_OLED_Task(void const * argument)
  42. {
  43. /* USER CODE BEGIN Start_OLED_Task */
  44. Oled_Init();
  45. Oled_Clear();
  46. /* Infinite loop */
  47. for(;;)
  48. {
  49. //串口数据的字符串拼装,speed是格子,每个格子1cm
  50. sprintf(speedMes,"A: %d ",angle[0]);
  51. sprintf(speedMes1,"B: %d ",angle[1]);
  52. sprintf(speedMes2,"C: %d ",angle[2]);
  53. sprintf(speedMes3,"D: %d ",angle[3]);
  54. sprintf(speedMes4,"Mode %d ",Mode);
  55. sprintf(speedMes5,"S %d ",sizeof_List());
  56. Oled_Show_Str(1,5,speedMes);
  57. Oled_Show_Str(1,69,speedMes1);
  58. Oled_Show_Str(2,5,speedMes2);
  59. Oled_Show_Str(2,69,speedMes3);
  60. Oled_Show_Str(4,0,speedMes4);
  61. Oled_Show_Str(4,54,speedMes5);
  62. osDelay(500);
  63. }
  64. /* USER CODE END Start_OLED_Task */
  65. }
  66. // 外部中断
  67. case GPIO_PIN_4:
  68. if(anti_shake == 0)
  69. {
  70. anti_shake = 1;
  71. osTimerStart(myTimer01Handle,800);
  72. addNode(angle);
  73. printf("储存动作\r\n");
  74. }

到这里位置,视频里我实现的内容都可以完成了。

至于这个链表最大能存多长的动作我是没去实测,反正我们玩绝对是够用了。

3.SPI扩容

 不幸的是我板子的SPI2坏了,只有SPI1正常,所以我没真正去完成这个功能。

但是这里我提供了完整的存储思路。大家可自己来实现

功能测试

 PB12设为GPIO输出引脚默认设为高电平,充当CS口,接线就是CS对CS,SCK对CLK,MOSI对DI,MISO对DO

VCC用3.3,5v没试过。

 在spi.c中添加        比如如果SPI1就是hspi1,SPI2就是hspi2,别的都不用动

  1. /* USER CODE BEGIN 1 */
  2. uint8_t spi2_read_write_byte(uint8_t data)
  3. {
  4. uint8_t rec_data = 0;
  5. HAL_SPI_TransmitReceive(&hspi2, &data, &rec_data, 1, 1000);
  6. return rec_data;
  7. }
  8. /* USER CODE END 1 */

spi.h

  1. /* USER CODE BEGIN Prototypes */
  2. uint8_t spi2_read_write_byte(uint8_t data);
  3. /* USER CODE END Prototypes */

w25q128.h        这里你CS用的哪个GPIO_OUT,W25Q128_CS_GPIO就改成对应的。

  1. #ifndef __W25Q128_H__
  2. #define __W25Q128_H__
  3. #include "stdint.h"
  4. /* W25Q128片选引脚定义 */
  5. #define W25Q128_CS_GPIO_PORT GPIOB
  6. #define W25Q128_CS_GPIO_PIN GPIO_PIN_12
  7. /* W25Q128片选信号 */
  8. #define W25Q128_CS(x) do{ x ? \
  9. HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT, W25Q128_CS_GPIO_PIN, GPIO_PIN_SET) : \
  10. HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT, W25Q128_CS_GPIO_PIN, GPIO_PIN_RESET); \
  11. }while(0)
  12. /* FLASH芯片列表 */
  13. #define W25Q128 0XEF17 /* W25Q128 芯片ID */
  14. /* 指令表 */
  15. #define FLASH_WriteEnable 0x06
  16. #define FLASH_ReadStatusReg1 0x05
  17. #define FLASH_ReadData 0x03
  18. #define FLASH_PageProgram 0x02
  19. #define FLASH_SectorErase 0x20
  20. #define FLASH_ChipErase 0xC7
  21. #define FLASH_ManufactDeviceID 0x90
  22. /* 静态函数 */
  23. static void w25q128_wait_busy(void); /* 等待空闲 */
  24. static void w25q128_send_address(uint32_t address);/* 发送地址 */
  25. static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写入page */
  26. static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写flash,不带擦除 */
  27. /* 普通函数 */
  28. void w25q128_init(void); /* 初始化25QXX */
  29. uint16_t w25q128_read_id(void); /* 读取FLASH ID */
  30. void w25q128_write_enable(void); /* 写使能 */
  31. uint8_t w25q128_rd_sr1(void); /* 读取寄存器1的值 */
  32. void w25q128_erase_chip(void); /* 整片擦除 */
  33. void w25q128_erase_sector(uint32_t saddr); /* 扇区擦除 */
  34. void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 读取flash */
  35. void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写入flash */
  36. #endif

w25q128.c

  1. #include "w25q128.h"
  2. #include "spi.h"
  3. #include "stdio.h"
  4. /**
  5. * @brief 初始化W25Q128
  6. * @param 无
  7. * @retval 无
  8. */
  9. void w25q128_init(void)
  10. {
  11. uint16_t flash_type;
  12. spi2_read_write_byte(0xFF); /* 清除DR的作用 */
  13. W25Q128_CS(1);
  14. flash_type = w25q128_read_id(); /* 读取FLASH ID. */
  15. if (flash_type == W25Q128)
  16. printf("检测到W25Q128芯片\r\n");
  17. else
  18. printf("读取到的芯片ID为:%x\r\n",flash_type);
  19. printf("未检测到W25Q128芯片\r\n");
  20. }
  21. /**
  22. * @brief 等待空闲
  23. * @param 无
  24. * @retval 无
  25. */
  26. static void w25q128_wait_busy(void)
  27. {
  28. while ((w25q128_rd_sr1() & 0x01) == 0x01); /* 等待BUSY位清空 */
  29. }
  30. /**
  31. * @brief 读取W25Q128的状态寄存器1的值
  32. * @param 无
  33. * @retval 状态寄存器值
  34. */
  35. uint8_t w25q128_rd_sr1(void)
  36. {
  37. uint8_t rec_data = 0;
  38. W25Q128_CS(0);
  39. spi2_read_write_byte(FLASH_ReadStatusReg1); /* 读状态寄存器1 */
  40. rec_data = spi2_read_write_byte(0xFF);
  41. W25Q128_CS(1);
  42. return rec_data;
  43. }
  44. /**
  45. * @brief W25Q128写使能
  46. * @note 将S1寄存器的WEL置位
  47. * @param 无
  48. * @retval 无
  49. */
  50. void w25q128_write_enable(void)
  51. {
  52. W25Q128_CS(0);
  53. spi2_read_write_byte(FLASH_WriteEnable); /* 发送写使能 */
  54. W25Q128_CS(1);
  55. }
  56. /**
  57. * @brief W25Q128发送地址
  58. * @param address : 要发送的地址
  59. * @retval 无
  60. */
  61. static void w25q128_send_address(uint32_t address)
  62. {
  63. spi2_read_write_byte((uint8_t)((address)>>16)); /* 发送 bit23 ~ bit16 地址 */
  64. spi2_read_write_byte((uint8_t)((address)>>8)); /* 发送 bit15 ~ bit8 地址 */
  65. spi2_read_write_byte((uint8_t)address); /* 发送 bit7 ~ bit0 地址 */
  66. }
  67. /**
  68. * @brief 擦除整个芯片
  69. * @note 等待时间超长...
  70. * @param 无
  71. * @retval 无
  72. */
  73. void w25q128_erase_chip(void)
  74. {
  75. w25q128_write_enable(); /* 写使能 */
  76. w25q128_wait_busy(); /* 等待空闲 */
  77. W25Q128_CS(0);
  78. spi2_read_write_byte(FLASH_ChipErase); /* 发送读寄存器命令 */
  79. W25Q128_CS(1);
  80. w25q128_wait_busy(); /* 等待芯片擦除结束 */
  81. }
  82. /**
  83. * @brief 擦除一个扇区
  84. * @note 注意,这里是扇区地址,不是字节地址!!
  85. * 擦除一个扇区的最少时间:150ms
  86. *
  87. * @param saddr : 扇区地址 根据实际容量设置
  88. * @retval 无
  89. */
  90. void w25q128_erase_sector(uint32_t saddr)
  91. {
  92. //printf("fe:%x\r\n", saddr); /* 监视falsh擦除情况,测试用 */
  93. saddr *= 4096;
  94. w25q128_write_enable(); /* 写使能 */
  95. w25q128_wait_busy(); /* 等待空闲 */
  96. W25Q128_CS(0);
  97. spi2_read_write_byte(FLASH_SectorErase); /* 发送写页命令 */
  98. w25q128_send_address(saddr); /* 发送地址 */
  99. W25Q128_CS(1);
  100. w25q128_wait_busy(); /* 等待扇区擦除完成 */
  101. }
  102. /**
  103. * @brief 读取芯片ID
  104. * @param 无
  105. * @retval FLASH芯片ID
  106. * @note 芯片ID列表见: w25q128.h, 芯片列表部分
  107. */
  108. uint16_t w25q128_read_id(void)
  109. {
  110. uint16_t deviceid;
  111. W25Q128_CS(0);
  112. spi2_read_write_byte(FLASH_ManufactDeviceID); /* 发送读 ID 命令 */
  113. spi2_read_write_byte(0); /* 写入一个字节 */
  114. spi2_read_write_byte(0);
  115. spi2_read_write_byte(0);
  116. deviceid = spi2_read_write_byte(0xFF) << 8; /* 读取高8位字节 */
  117. deviceid |= spi2_read_write_byte(0xFF); /* 读取低8位字节 */
  118. W25Q128_CS(1);
  119. return deviceid;
  120. }
  121. /**
  122. * @brief 读取SPI FLASH
  123. * @note 在指定地址开始读取指定长度的数据
  124. * @param pbuf : 数据存储区
  125. * @param addr : 开始读取的地址(最大32bit)
  126. * @param datalen : 要读取的字节数(最大65535)
  127. * @retval 无
  128. */
  129. void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
  130. {
  131. uint16_t i;
  132. W25Q128_CS(0);
  133. spi2_read_write_byte(FLASH_ReadData); /* 发送读取命令 */
  134. w25q128_send_address(addr); /* 发送地址 */
  135. for(i=0;i<datalen;i++)
  136. {
  137. pbuf[i] = spi2_read_write_byte(0XFF); /* 循环读取 */
  138. }
  139. W25Q128_CS(1);
  140. }
  141. /**
  142. * @brief SPI在一页(0~65535)内写入少于256个字节的数据
  143. * @note 在指定地址开始写入最大256字节的数据
  144. * @param pbuf : 数据存储区
  145. * @param addr : 开始写入的地址(最大32bit)
  146. * @param datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
  147. * @retval 无
  148. */
  149. static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
  150. {
  151. uint16_t i;
  152. w25q128_write_enable(); /* 写使能 */
  153. W25Q128_CS(0);
  154. spi2_read_write_byte(FLASH_PageProgram); /* 发送写页命令 */
  155. w25q128_send_address(addr); /* 发送地址 */
  156. for(i=0;i<datalen;i++)
  157. {
  158. spi2_read_write_byte(pbuf[i]); /* 循环写入 */
  159. }
  160. W25Q128_CS(1);
  161. w25q128_wait_busy(); /* 等待写入结束 */
  162. }
  163. /**
  164. * @brief 无检验写SPI FLASH
  165. * @note 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
  166. * 具有自动换页功能
  167. * 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
  168. *
  169. * @param pbuf : 数据存储区
  170. * @param addr : 开始写入的地址(最大32bit)
  171. * @param datalen : 要写入的字节数(最大65535)
  172. * @retval 无
  173. */
  174. static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
  175. {
  176. uint16_t pageremain;
  177. pageremain = 256 - addr % 256; /* 单页剩余的字节数 */
  178. if (datalen <= pageremain) /* 不大于256个字节 */
  179. {
  180. pageremain = datalen;
  181. }
  182. while (1)
  183. {
  184. /* 当写入字节比页内剩余地址还少的时候, 一次性写完
  185. * 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
  186. */
  187. w25q128_write_page(pbuf, addr, pageremain);
  188. if (datalen == pageremain) /* 写入结束了 */
  189. {
  190. break;
  191. }
  192. else /* datalen > pageremain */
  193. {
  194. pbuf += pageremain; /* pbuf指针地址偏移,前面已经写了pageremain字节 */
  195. addr += pageremain; /* 写地址偏移,前面已经写了pageremain字节 */
  196. datalen -= pageremain; /* 写入总长度减去已经写入了的字节数 */
  197. if (datalen > 256) /* 剩余数据还大于一页,可以一次写一页 */
  198. {
  199. pageremain = 256; /* 一次可以写入256个字节 */
  200. }
  201. else /* 剩余数据小于一页,可以一次写完 */
  202. {
  203. pageremain = datalen; /* 不够256个字节了 */
  204. }
  205. }
  206. }
  207. }
  208. /**
  209. * @brief 写SPI FLASH
  210. * @note 在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
  211. * SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
  212. * 擦除的最小单位为Sector.
  213. *
  214. * @param pbuf : 数据存储区
  215. * @param addr : 开始写入的地址(最大32bit)
  216. * @param datalen : 要写入的字节数(最大65535)
  217. * @retval 无
  218. */
  219. uint8_t g_w25q128_buf[4096]; /* 扇区缓存 */
  220. void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
  221. {
  222. uint32_t secpos;
  223. uint16_t secoff;
  224. uint16_t secremain;
  225. uint16_t i;
  226. uint8_t *w25q128_buf;
  227. w25q128_buf = g_w25q128_buf;
  228. secpos = addr / 4096; /* 扇区地址 */
  229. secoff = addr % 4096; /* 在扇区内的偏移 */
  230. secremain = 4096 - secoff; /* 扇区剩余空间大小 */
  231. //printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
  232. if (datalen <= secremain)
  233. {
  234. secremain = datalen; /* 不大于4096个字节 */
  235. }
  236. while (1)
  237. {
  238. w25q128_read(w25q128_buf, secpos * 4096, 4096); /* 读出整个扇区的内容 */
  239. for (i = 0; i < secremain; i++) /* 校验数据 */
  240. {
  241. if (w25q128_buf[secoff + i] != 0XFF)
  242. {
  243. break; /* 需要擦除, 直接退出for循环 */
  244. }
  245. }
  246. if (i < secremain) /* 需要擦除 */
  247. {
  248. w25q128_erase_sector(secpos); /* 擦除这个扇区 */
  249. for (i = 0; i < secremain; i++) /* 复制 */
  250. {
  251. w25q128_buf[i + secoff] = pbuf[i];
  252. }
  253. w25q128_write_nocheck(w25q128_buf, secpos * 4096, 4096); /* 写入整个扇区 */
  254. }
  255. else /* 写已经擦除了的,直接写入扇区剩余区间. */
  256. {
  257. w25q128_write_nocheck(pbuf, addr, secremain); /* 直接写扇区 */
  258. }
  259. if (datalen == secremain)
  260. {
  261. break; /* 写入结束了 */
  262. }
  263. else /* 写入未结束 */
  264. {
  265. secpos++; /* 扇区地址增1 */
  266. secoff = 0; /* 偏移位置为0 */
  267. pbuf += secremain; /* 指针偏移 */
  268. addr += secremain; /* 写地址偏移 */
  269. datalen -= secremain; /* 字节数递减 */
  270. if (datalen > 4096)
  271. {
  272. secremain = 4096; /* 下一个扇区还是写不完 */
  273. }
  274. else
  275. {
  276. secremain = datalen;/* 下一个扇区可以写完了 */
  277. }
  278. }
  279. }
  280. }

main函数里用这段代码测试 

  1. /* USER CODE BEGIN 2 */
  2. w25q128_init();
  3. /* 写入测试数据 */
  4. sprintf((char *)datatemp, "stm32f103c8t6");
  5. w25q128_write(datatemp, FLASH_WriteAddress, TEXT_SIZE);
  6. printf("数据写入完成!\r\n");
  7. /* 读出测试数据 */
  8. memset(datatemp, 0, TEXT_SIZE);
  9. w25q128_read(datatemp, FLASH_ReadAddress, TEXT_SIZE);
  10. printf("读出数据:%s\r\n", datatemp);
  11. /* USER CODE END 2 */

运行结果第一句首先一定是都没读取到 w25q128 这个芯片,这段判断代码在w25q128_init();里

w25q128芯片分析与功能实现

以前在这个文章里讲过 

STM32:SPI_我有在好好学习的博客-CSDN博客

总之, 整个存储空间的组成是

256个Block * 16个Sector * 16个Page * 256个字节 = 共16MB

而我们代码增删改查储存信息的单位是按照Sector为单位的,即一个扇区一个扇区的处理信息

阅读官方的芯片手册:

可知存储地址为六位十六进制,刚好对应16MB

0        0        0        0        0        0

前两位表示BLOCK,16*16 即256个Block

第三位对应Sector,16个

第四位对应Page,16个

五六位对应16*16 即256个字节

所以通过设置这个六位的地址,我们就可以决定把数据存在哪个扇区

例如 3FD000 ,就表示第63Block的第13个Sector,又因为我们是按照Sector操作的

所以通过前三位来选择不同的扇区来存数据

存和读都是字符串形式。写两个函数分别把字符串转化为链表,链表转化为字符串即可。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/405216?site
推荐阅读
相关标签
  

闽ICP备14008679号