【实验器材】
1、NOCLEO-C031C6开发板
2、0.96寸OLED屏
3、旋转编码器
4、双微步进电机开发板
5、步进电机。
【软件】
1、TobudOS——国产开源的实时操作系统
2、STM32CubeMAX——意法半导体提供的免费图形化配置工具。
3、MDK5.38
【程序流程】
【实现步骤】
1、使用stm32cubeMAX新建工程,初始化TIM3为编码器模式,修改TIM3的通道GPIO为PA6、PA7来采集IO,采集旋转编码器的脉冲信号。
2、配置TIM1的通道1为PWM输出通首,配置分频为48-1,计数值是1000-1,即输出为1KHz的PWM波,同时设置脉冲什500输出50%的占空比。
3、同时配置I2C,配置速度为400K,IO配置为PB8、PB9。
4、配置时钟,主频为48M。
生成工程后开始用keil打开工程。
【移植TobudOS]
1、复制TobudOS源码的kernel、arch到生成工程的OS目录下面:
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中。
同时新建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到头文件引用。
【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 */
【电机驱动板】
使用的步进电机的驱动器为双极微步电机评估版:
CLK接PA0,接供时钟驱动脉冲信号,EN接开发板的PA1,提供使能信号。DIR为PA4,提供正反转信号。
附工程源代码:
附视频介绍:
<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]
6 j$ G5 }' e% x( A0 Y3 e% |
, {# s1 N7 c! C9 \1 w
学到了