你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【NUCLEO-C031C6】基于TobudOS的步进电机控制系统

[复制链接]
lugl 发布时间:2024-3-12 14:27

【实验器材】

1、NOCLEO-C031C6开发板

2、0.96寸OLED屏

3、旋转编码器

4、双微步进电机开发板

5、步进电机。

【软件】

1、TobudOS——国产开源的实时操作系统

2、STM32CubeMAX——意法半导体提供的免费图形化配置工具。

3、MDK5.38

【程序流程】

image.png

【实现步骤】

1、使用stm32cubeMAX新建工程,初始化TIM3为编码器模式,修改TIM3的通道GPIO为PA6、PA7来采集IO,采集旋转编码器的脉冲信号。

image.png

2、配置TIM1的通道1为PWM输出通首,配置分频为48-1,计数值是1000-1,即输出为1KHz的PWM波,同时设置脉冲什500输出50%的占空比。

image.png

3、同时配置I2C,配置速度为400K,IO配置为PB8、PB9。

image.png

4、配置时钟,主频为48M。

image.png

生成工程后开始用keil打开工程。

【移植TobudOS]

1、复制TobudOS源码的kernel、arch到生成工程的OS目录下面:

image.png

2、新建工程分组OS/kernel、OS/arch、OS/config,并把OS\kernel\core下面所有的.c添加进工程kernel工程分组。把\arm\arm-v7m\common下面的所有的.c添回进工程arch分组,把arch\arm\arm-v7m\cortex-m0+\gcc下面的port.c、port_s.S添加进工程分组arch中。

image.png

image.png

同时新建OS/config分组,添加tos_config.h,内容如下:

#ifndef _TOS_CONFIG_H_
#define  _TOS_CONFIG_H_

#include "main.h"

#define TOS_CFG_TASK_PRIO_MAX           10u

#define TOS_CFG_ROUND_ROBIN_EN          1u

#define TOS_CFG_OBJECT_VERIFY_EN        1u

#define TOS_CFG_OBJ_DYNAMIC_CREATE_EN   1u

#define TOS_CFG_EVENT_EN                1u

#define TOS_CFG_MMBLK_EN                1u

#define TOS_CFG_MMHEAP_EN               1u

#define TOS_CFG_MMHEAP_DEFAULT_POOL_SIZE        0x400

#define TOS_CFG_MUTEX_EN                1u

#define TOS_CFG_TIMER_EN                1u

#define TOS_CFG_PWR_MGR_EN              0u

#define TOS_CFG_TICKLESS_EN             0u

#define TOS_CFG_SEM_EN                  1u

#define TOS_CFG_TASK_STACK_DRAUGHT_DEPTH_DETACT_EN      1u

#define TOS_CFG_FAULT_BACKTRACE_EN      0u

#define TOS_CFG_IDLE_TASK_STK_SIZE      512u

#define TOS_CFG_CPU_TICK_PER_SECOND     1000u

#define TOS_CFG_CPU_CLOCK               (SystemCoreClock)

#define TOS_CFG_TIMER_AS_PROC           1u

#define TOS_CFG_MAIL_QUEUE_EN           1u

#endif

3、添加../OS/arch/arm/arm-v7m/common/include、../OS/arch/arm/arm-v7m/cortex-m0+/gcc、../OS/kernel/core/include到头文件引用。

image.png

【OLED驱动】

移植OLED开源驱动,修改写命令、写数据两个函数内容如下:

/**
  * 函    数:OLED写命令
  * 参    数:Command 要写入的命令值,范围:0x00~0xFF
  * 返 回 值:无
  */
void OLED_WriteCommand(uint8_t Command)
{
        uint8_t buff[2];
        buff[0] = 0x00;
        buff[1] = Command;
        HAL_I2C_Master_Transmit(&hi2c1, 0x78, buff, 2, 100);
}

/**
  * 函    数:OLED写数据
  * 参    数:Data 要写入数据的起始地址
  * 参    数:Count 要写入数据的数量
  * 返 回 值:无
  */
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
        uint8_t i;
        uint8_t buff[Count +1];
  buff[0] = 0x40;

        for (i = 1; i < Count+1; i ++)
        {
                buff[i] = Data[i-1];        //依次发送Data的每一个数据
        }

        HAL_I2C_Master_Transmit(&hi2c1, 0x78, buff, Count+1, 100);
}

其余代码见工程附件。

【旋转编码器】

增加Encoder.c到工程中,代码如下:

int16_t Encoder_Get(void)
{

        int16_t temp;
        temp = (__HAL_TIM_GET_COUNTER(&htim3)+2)/4;
        if(temp)
        {
                __HAL_TIM_SET_COUNTER(&htim3,__HAL_TIM_GET_COUNTER(&htim3)-(temp*4));
                return temp;
        }
        return 0;
}

【按键代码】

按键的IO选择为PC13,与开发板的用户按键同接。配置为中断上面拉输入,下降沿触发,代码如下:

uint8_t Key_Enter;        //确认键
static void EXTI4_15_IRQHandler_Config(void);
void Key_Init(void)
{
        EXTI4_15_IRQHandler_Config();
}

/********************************************/

int8_t Key_Enter_Get(void)
{
        if(Key_Enter)
        {
                Key_Enter = 0;
                return 1;
        }
        return 0;
}

static void EXTI4_15_IRQHandler_Config(void)
{
  GPIO_InitTypeDef   GPIO_InitStructure;


  /* Enable GPIOC clock */
  __HAL_RCC_GPIOC_CLK_ENABLE();

  /* Configure PC.13 pin as input floating */
  GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING;


  GPIO_InitStructure.Pull = GPIO_PULLUP;
  GPIO_InitStructure.Pin = BUTTON_USER_Pin;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);


  /* Enable and set line 13 Interrupt to the lowest priority */
  HAL_NVIC_SetPriority(EXTI4_15_IRQn, 10, 0);
  HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
}

void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == BUTTON_USER_Pin)
  {

                        Key_Enter += 1;

  }
//        __HAL_GPIO_EXTI_CLEAR_IT(BUTTON_USER_Pin);
}

【菜单设计】

先设计结构体如下:

typedef struct display_menu{
        char Name[16];                        //显示字符串
        uint8_t X;         //起始坐标
        uint8_t Y;         //起始坐标
        uint8_t Width;         //起始坐标
        uint8_t Height;         //起始坐标
        uint8_t Select;     //选中状态
        void (*func)(void);                //函数指针
}_display_block;

初始化5个显示块分别为运行状态、方向、前进、后退、运行/停止按键。其初始化代码如下:

_display_block displasy_block[5];
void main_menu(void)
{
        //显示4个块
        strcpy(displasy_block[0].Name,"stoping");
        displasy_block[0].X = 40;
        displasy_block[0].Y = 0;
        displasy_block[0].Width = 60;
        displasy_block[0].Height = 16;
        displasy_block[0].Select = SET;
        strcpy(displasy_block[1].Name,"REV");
        displasy_block[1].X = 0;
        displasy_block[1].Y = 36;
        displasy_block[1].Width = 24;
        displasy_block[1].Height = 16;
        displasy_block[1].Select = SET;
        strcpy(displasy_block[2].Name,"RUN");
        displasy_block[2].X = 48;
        displasy_block[2].Y = 36;
        displasy_block[2].Width = 32;
        displasy_block[2].Height = 16;
        displasy_block[2].Select = RESET;
        strcpy(displasy_block[3].Name,"FWD");
        displasy_block[3].X = 100;
        displasy_block[3].Y = 36;
        displasy_block[3].Width = 24;
        displasy_block[3].Height = 16;
        displasy_block[3].Select = RESET;
        strcpy(displasy_block[4].Name,"FWD");
        displasy_block[4].X = 0;
        displasy_block[4].Y = 0;
        displasy_block[4].Width = 24;
        displasy_block[4].Height = 16;
}

具体菜音实现的代码如下,获取编码状态,根据状态更新选择状态,如果被选中,则置背景为反色。当按下按键时,根据相应的位置,做出相应的状态,代码如下:

void run_play_option_class(void)
{

        uint8_t i;

        int8_t Show_i = 2;                 //显示起始下标

        int8_t Max = 3;                        //选项数量
        int8_t Incident = 0;        //编码器事件
        while(1)
        {
                Incident = Encoder_Get();                //获取按键事件//轮询;

                if(Incident)                                        //如果有按键事件;
                {
                        Show_i += Incident;

                        if(Show_i < 1)
                                        {Show_i = 1;}                        //限制选中下标

                        else
                        {

                                if(Show_i > Max )
                                        {Show_i = Max;}                        //限制选中下标
                        }
                }
                //如果有按键事件
                if(Key_Enter_Get())                //获取按键
                {
                        //如果是FWD 测设置
                        if(Show_i ==1)
                        {
                                //设置DIR为返转
                                strcpy(displasy_block[4].Name,"REV");
                                displasy_block[1].Select = SET;
                                displasy_block[3].Select = RESET;
                                HAL_GPIO_WritePin(DIR_GPIO_Port,DIR_Pin,GPIO_PIN_SET);
                        }
                        else if(Show_i == 3)
                        {
                                //设置DIR为正转
                                strcpy(displasy_block[4].Name,"FWD");
                                displasy_block[3].Select = SET;
                                displasy_block[1].Select = RESET;
                                HAL_GPIO_WritePin(DIR_GPIO_Port,DIR_Pin,GPIO_PIN_RESET);
                        }
                        else
                        {
                                //设置是否开启定时器
                                if(displasy_block[2].Select == RESET)
                                {
                                        displasy_block[2].Select = SET;
                                        strcpy(displasy_block[0].Name,"runing");
                                        strcpy(displasy_block[2].Name,"STOP");
                                        OLED_ReverseArea(displasy_block[0].X, displasy_block[0].Y, displasy_block[0].Width, displasy_block[0].Height);
                                        //开启定时器
                                        HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);        //开启定时器1的通道1的PWM输出
                                }
                                else
                                {
                                        displasy_block[2].Select = RESET;
                                        strcpy(displasy_block[0].Name,"stoping");
                                        strcpy(displasy_block[2].Name,"RUN");
                                        //开启定时器
                                        HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_1);        //开启定时器1的通道1的PWM输出
                                }
                        }
                }
                OLED_Clear();
                for(i=0;i<5;i++)
                {
                        OLED_ShowString(displasy_block[i].X, displasy_block[i].Y, displasy_block[i].Name, OLED_8X16);
                }
                OLED_ReverseArea(displasy_block[Show_i].X, displasy_block[Show_i].Y, displasy_block[Show_i].Width, displasy_block[Show_i].Height);
                OLED_Update();
                tos_task_delay(10);
        }
}

【程序调度】

创建两个任务,一个任务为LED定期闪烁。另一个任务为菜单显示。代码如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "OLED.h"
#include "menu.h"
#include "tos_k.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
k_task_t task, led_task;

k_stack_t task_stack[1024], task_stack_led[1024];

void test_task(void *Parameter)

{

        while(1)

        {

                HAL_GPIO_TogglePin(LED4_GPIO_Port,LED4_Pin);

                tos_task_delay(100);

        }

}

void led_task_entry(void *Parameter)

{
                menu_Init();

                while(1)

                {

                }

}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
        uint32_t enc1;
        uint8_t DirectionA;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
        k_err_t err;
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM3_Init();
  MX_USART2_UART_Init();
  MX_TIM1_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
        OLED_Init();
        Key_Init();
        HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);

        HAL_GPIO_WritePin(EN_GPIO_Port,EN_Pin,GPIO_PIN_SET);
        HAL_GPIO_WritePin(DIR_GPIO_Port,DIR_Pin,GPIO_PIN_SET);
        tos_knl_init();

        err = tos_task_create(&task, "task1",test_task,NULL, 2, task_stack,1024,20);

        err = tos_task_create(&led_task, "task_led",led_task_entry,NULL, 2, task_stack_led,1024,20);

        tos_knl_start();

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

【电机驱动板】

使用的步进电机的驱动器为双极微步电机评估版:

image.png

CLK接PA0,接供时钟驱动脉冲信号,EN接开发板的PA1,提供使能信号。DIR为PA4,提供正反转信号。

附工程源代码:

Encode.zip

stm32c031电机综合控制系统-封面.jpg

附视频介绍:

<iframe src="https://player.bilibili.com/player.html?aid=1601627138&bvid=BV1H2421T75D&cid=1467394607&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe> [/i][/i][/i][/i]

image.png
image.png
收藏 评论2 发布时间:2024-3-12 14:27

举报

2个回答
STMCU-管管 回答时间:2024-3-12 14:35:15
补充视频:

0 k) m% N! U3 o  c$ |6 j$ G5 }' e% x( A0 Y3 e% |

* l, e/ p. [, Z4 Y, {# s1 N7 c! C9 \1 w
背影101 回答时间:2024-4-3 16:48:46

学到了

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版