打印
[开发板]

CW32L010F8P6根据光强数据驱动42步进电机

[复制链接]
327|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 suncat0504 于 2024-11-26 15:02 编辑

前次完成了对I2C接口的BH1750的数据访问,以此为基础利用采集到的光强数据作为阈值条件驱动42步进电机。步进电机与螺杆联动,驱动诸如窗帘这样的机构。42步进电机的驱动采用比较简单的时序。

4相5线步进电机 28YBJ-48


电机使用4根驱动口线
正向驱动时,使用0x01,0x02,0x0,,0x08顺序,利用B0~B3驱动,反相驱动时,以相反的顺序驱动即可。由于是减速的,减速比为1/64,再加上联动螺杆的螺纹间距比较小,所以实际运行起来,正反转带动的螺杆上的行进部件走行会显得很慢。
为了实现步进方式的控制,在参考ATIM的例程的基础上,在原有的程序中加入了定时器ATIM的中断处理,利用定时器的定时周期作为步进电机步进脉冲时间间隔。如果这个时间周期不合适,是无法让步进电机运转起来的。经过尝试,按照以下配置可以让电机实现正反转。
1、系统时钟设置为16MHz。
2、设置ATIM的时钟为80分频,计数模式,计数值>=390即可,数值越大,速度越慢。
在定时器的中断处理中,根据光强情况,依次按照固定的定时周期向步进电机发送步进时序脉冲,从而实现电机的正反转。
在实际测试中,使用PA03、PA04、PB02、PB03作为驱动口,通过ULN2003驱动步进电机。原本是打算用PB0、PB1、PB02、PB03的,但不知为什么PB0、PB1无法作为独立的输出口使用。
整个装置是自己手工制作的,没啥欣赏性,只能作为参考、演示用。

装置中没有设置限位点(设置限位点需要额外追加位置传感器,并提供对应的GPIO输入口),所以把移动点的初始设置设置在螺杆的中心点,许可最大进步数为12000。BH1750光强传感器非常灵敏,虽然同样是在窗户附近,在冬日的上、下午时采集到的数据会有非常明显的变化。
下面是在光强的检测结果在设置的阈值点发生变化时,步进电机的正反转情况。

以下是主程序代码,也很简单。
#include "main.h"
#include "oled.h"

#define I2C_BH1750ADDR        0x46

#define  I2C1_SCL_GPIO_PORT       CW_GPIOA
#define  I2C1_SCL_GPIO_PIN        GPIO_PIN_6    //如果改动口线则GPIO初始化代码需要做同步修改
#define  I2C1_SDA_GPIO_PORT       CW_GPIOA
#define  I2C1_SDA_GPIO_PIN        GPIO_PIN_5   //如果改动口线则GPIO初始化代码需要做同步修改

//UARTx
#define  DEBUG_UARTx                   CW_UART2
#define  DEBUG_UART_CLK                SYSCTRL_APB1_PERIPH_UART2
#define  DEBUG_UART_APBClkENx          SYSCTRL_APBPeriphClk_Enable1
#define  DEBUG_UART_BaudRate           115200
#define  DEBUG_UART_UclkFreq           16000000

//UARTx GPIO
#define  DEBUG_UART_GPIO_CLK           (SYSCTRL_AHB_PERIPH_GPIOB)
#define  DEBUG_UART_TX_GPIO_PORT       CW_GPIOB
#define  DEBUG_UART_TX_GPIO_PIN        GPIO_PIN_5
#define  DEBUG_UART_RX_GPIO_PORT       CW_GPIOB
#define  DEBUG_UART_RX_GPIO_PIN        GPIO_PIN_6

//GPIO AF
#define  DEBUG_UART_AFTX               PB05_AFx_UART2TXD()
#define  DEBUG_UART_AFRX               PB06_AFx_UART2RXD()

#define MAX_LIGHT1 150
#define MAX_LIGHT2 150
#define MAX_LIGHT 100

//// LED用GPIO
//#define LED_GPIO_PORT CW_GPIOB
//#define LED_GPIO_PINS GPIO_PIN_2

// 驱动步进马达的接口:PB00,PB01,PB02,PB03
#define MOTOR_GPIO_PORT1 CW_GPIOA
#define MOTOR_GPIO_PIN0 GPIO_PIN_3
#define MOTOR_GPIO_PIN1 GPIO_PIN_4

#define MOTOR_GPIO_PORT2 CW_GPIOB
#define MOTOR_GPIO_PIN2 GPIO_PIN_2
#define MOTOR_GPIO_PIN3 GPIO_PIN_3

// 按钮Key:PB4,PA6
#define KEY1_GPIO_PORT CW_GPIOB
#define KEY1_GPIO_PIN GPIO_PIN_4

void SYSCTRL_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);
void I2C_Configuration(void);
void Delay(uint16_t nCount);
void DelayMs(__IO uint16_t nCount);
void DelayUs(__IO uint16_t nCount);
void UART_Configuration(void);
void ATIM_IRQHandlerCallBack(void);

#ifdef __GNUC__
    /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
    set to 'Yes') calls __io_putchar() */
    #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
    #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */


// ADC转换数据
uint32_t valueAdc;
// 中断标志
volatile uint8_t gFlagIrq;
   
// 马达控制
// 系统时钟设置为HSI时钟3分频,16MHz, PCLK、HCLK不分频,PCLK=HCLK=SysClk=16MHz
// ATIM边沿计数,上计数模式,80分频,ARR设置为19999,溢出周期200ms
// 每隔200模式,通过PB0~PB3输出步进电机的控制信号
#define MOTOR_MAX_STEP 12000
// 顺时针驱动信号
uint16_t motor_driver_s[4] = {1, 2, 4, 8};
// 逆时针驱动信号
uint16_t motor_driver_n[4] = {8, 4, 2, 1};
        
// 马达驱动信号间隔(200ms)
uint16_t motor_delay=200;
// 马达步进步数 : 发送一个完整周期,算一步
int32_t motor_step = 0;
// 马达正反转时,脉冲标志,范围0-3,按照顺时针/逆时针的脉冲顺序发送驱动信号
uint8_t motor_step_no=0;

typedef struct {
    uint8_t    direction ;      // 转动方向 1-顺时针;2-逆时针;0-停转
    uint8_t    no;              // 当前脉冲编号,0-3
    int32_t    stepcnt;         // 步进计数
} MOTOR_InitTypeDef;

MOTOR_InitTypeDef motor;

/**
* [url=home.php?mod=space&uid=247401]@brief[/url] 配置ATIM
*
*/

void ATIM_Configuration(void) {
    ATIM_InitTypeDef ATIM_InitStruct = {DISABLE,0};

    ATIM_InitStruct.BufferState = ENABLE;                               //使能缓存寄存器   
    ATIM_InitStruct.CounterAlignedMode = ATIM_COUNT_ALIGN_MODE_EDGE;    //边沿对齐
    ATIM_InitStruct.CounterDirection = ATIM_COUNTING_UP;                //向上计数;
    ATIM_InitStruct.CounterOPMode = ATIM_OP_MODE_REPETITIVE;            //连续运行模式   
    ATIM_InitStruct.Prescaler = 80-1;                                   // 80分频
    ATIM_InitStruct.ReloadValue = 599;                                // 重载周期499+1
    ATIM_InitStruct.RepetitionCounter = 0;                              // 重复周期0

    ATIM_Init(&ATIM_InitStruct);
    ATIM_ITConfig(ATIM_IT_UIE, ENABLE);             // 有重复计数器溢出产生进入中断
   
    ATIM_Cmd(ENABLE);
}

/**
* [url=home.php?mod=space&uid=247401]@brief[/url] 配置UART
*
*/
void UART_Configuration(void) {
    UART_InitTypeDef UART_InitStructure = {0};

    UART_InitStructure.UART_BaudRate = DEBUG_UART_BaudRate;
    UART_InitStructure.UART_Over = UART_Over_16;
    UART_InitStructure.UART_Source = UART_Source_PCLK;
    UART_InitStructure.UART_UclkFreq = DEBUG_UART_UclkFreq;
    UART_InitStructure.UART_StartBit = UART_StartBit_FE;
    UART_InitStructure.UART_StopBits = UART_StopBits_1;
    UART_InitStructure.UART_Parity = UART_Parity_No ;
    UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
    UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
    UART_Init(DEBUG_UARTx, &UART_InitStructure);
}

int32_t main(void) {
    uint8_t bh1750cmd[] = {0x01};
    uint8_t bh1750dat[8] = {0,0,0,0,0,0,0,0};
    float val = 0;
    //uint16_t v = motor.no%4;
   
    // 马达控制初始化
    motor.direction = 0;
    motor.no = 0;
    motor.stepcnt = 0;
   
         //时钟初始化
    SYSCTRL_Configuration();
        
        //IO口初始化
    GPIO_Configuration();
   
    //配置UART
    UART_Configuration();
   
    printf("\r\nStart test ... \r\n");
   
    // 初始化I2C外设
    I2C_Configuration();
   
    // 初始化OLED
    OLED_Init();
   
    // 显示光强标签:当前光强:
    SYSTEM_Init();
   
    // 显示参数
    OLED_ShowNum(76, 0, 0, 6, 16);
        
    // 设置ATIM参数
    ATIM_Configuration();
   
    /* 允许ATIM中断 */
    NVIC_Configuration();

    while (1) {
//        // LED
//        GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PINS);

        // 采集光强数据:BH1750(I2C方式)
        bh1750cmd[0] = 0x01;  // PowerOn
        I2C_MasterSendDataToSlave(CW_I2C1, I2C_BH1750ADDR, bh1750cmd, 1);
        //bh1750cmd[0] = 0x10;  // 高分辨率连续测量命令=0x10,测量时间120mS
        bh1750cmd[0] = 0x23;  // 一次低分辨率测量命令=0x23,测量时间16mS
        I2C_MasterSendDataToSlave(CW_I2C1, I2C_BH1750ADDR, bh1750cmd, 1);
        DelayMs(20);        // 根据测量模式,调整为足够的等待时间,等待测试完成
        I2C_MasterRecDataFromSlave(CW_I2C1, I2C_BH1750ADDR, bh1750dat, 2);
        val = (bh1750dat[0]>>8 | bh1750dat[1])/1.2;
        
        // 显示光强数据
        printf("\r\nBH1750 Value = %f", val);
        OLED_ShowNum(76, 0, (int)val, 6, 16);
        
        // 根据光强值,确定步进电机正转还是反转
        if (val >= MAX_LIGHT && motor.stepcnt < MOTOR_MAX_STEP) {
            motor.direction = 1;
            OLED_ShowChinese(48, 16, 11, 16);
        } else if (val < MAX_LIGHT && motor.stepcnt > 0) {
            motor.direction = 2;
            OLED_ShowChinese(48, 16, 14, 16);
        } else {
            motor.direction = 0;
            OLED_ShowChinese(48, 16, 9, 16);
        }
        // 显示步进计数
        OLED_ShowNum(76, 16, (int)motor.stepcnt , 6, 16);
        OLED_Refresh();
    }   
}

void ATIM_IRQHandlerCallBack(void) {
    uint8_t v = 0;
   
    if (ATIM_GetITStatus(ATIM_STATE_UIF)) {
        // 清除中断标志位
        ATIM_ClearITPendingBit(ATIM_STATE_UIF);
        
        // 根据马达状态,发出控制信号
        if (motor.direction == 0) {
            // 禁止转动
            GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN0, GPIO_Pin_RESET);
            GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN1, GPIO_Pin_RESET);
            GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN2, GPIO_Pin_RESET);
            GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN3, GPIO_Pin_RESET);
            motor.no=0;
        } else {
            // 根据正反转,获取不同的时序驱动数据
            if (motor.direction == 1) {
                v = motor_driver_s[motor.no];
            } else if (motor.direction == 2) {
                v = motor_driver_n[motor.no];
            } else {
                return;
            }
            
            // 输出驱动数据到GPIO口
            if (v==1) {
                GPIO_WritePin(CW_GPIOA, MOTOR_GPIO_PIN1, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN2, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN3, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOA, MOTOR_GPIO_PIN0, GPIO_Pin_SET);
            } else if (v==2) {
                GPIO_WritePin(CW_GPIOA, MOTOR_GPIO_PIN0, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN2, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN3, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOA, MOTOR_GPIO_PIN1, GPIO_Pin_SET);
            } else if (v==4) {
                GPIO_WritePin(CW_GPIOA, MOTOR_GPIO_PIN0, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOA, MOTOR_GPIO_PIN1, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN3, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN2, GPIO_Pin_SET);
            } else if (v==8) {
                GPIO_WritePin(CW_GPIOA, MOTOR_GPIO_PIN0, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOA, MOTOR_GPIO_PIN1, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN2, GPIO_Pin_RESET);
                GPIO_WritePin(CW_GPIOB, MOTOR_GPIO_PIN3, GPIO_Pin_SET);
            }

            // 根据正反转,计算下一次时序脉冲数据,并调整步进计数
            if (motor.direction == 1) {
                // 正转
                if (motor.stepcnt<MOTOR_MAX_STEP) {
                    motor.stepcnt++;
                    motor.no=(motor.no+1)%4;
                } else {
                    // 超出步进上限,停止转动
                    motor.direction = 0;
                }
               
            } else if (motor.direction == 2) {
                // 反转
                if (motor.stepcnt>0) {
                    motor.stepcnt--;
                    motor.no=(motor.no+1)%4;
                } else {
                    // 返回上电的初始位置时,停止转动
                    motor.direction = 0;
                }
            }
        }
    }

}

/**
  * @brief  Configures the different system clocks.
  * @param  None
  * @retval None
  */
void SYSCTRL_Configuration(void) {
        SYSCTRL_HSI_Enable(SYSCTRL_HSIOSC_DIV3);       // 三分频,16MHz主频(48/3)
    //SYSCTRL_HSI_Enable(SYSCTRL_HSIOSC_DIV6);
    __SYSCTRL_ATIM_CLK_ENABLE();     // ATIM用时钟
    __SYSCTRL_GPIOA_CLK_ENABLE();    // PA用PORT时钟
    __SYSCTRL_GPIOB_CLK_ENABLE();    // PB用PORT时钟
    __SYSCTRL_I2C_CLK_ENABLE();      // I2C用PORT时钟
   
    //外设时钟使能
    SYSCTRL_AHBPeriphClk_Enable(DEBUG_UART_GPIO_CLK, ENABLE);
    DEBUG_UART_APBClkENx(DEBUG_UART_CLK, ENABLE);   
}

/**
  * @brief  Configure the GPIO Pins.
  * @param  None
  * @retval None
  */

// 初始化GPIO口
void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure  = {0};
   
    // 按钮(PB4)
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_InitStructure.Pins = KEY1_GPIO_PIN;
    GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
   
   
    // 步进电机GPIO口:PA03、PA04、PB02、PB03
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
   
    GPIO_InitStructure.Pins = MOTOR_GPIO_PIN0 | MOTOR_GPIO_PIN1;
    GPIO_Init(MOTOR_GPIO_PORT1, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = MOTOR_GPIO_PIN2 | MOTOR_GPIO_PIN3;
    GPIO_Init(MOTOR_GPIO_PORT2, &GPIO_InitStructure);


    // PA05、PA06复用为SDA、SCL
        PA05_AFx_I2C1SDA();
        PA06_AFx_I2C1SCL();
    GPIO_InitStructure.Pins = I2C1_SCL_GPIO_PIN | I2C1_SDA_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_Init(I2C1_SCL_GPIO_PORT, &GPIO_InitStructure);
   
    // 初始化串口:PB6 - RX, PB5 - TX
    GPIO_WritePin(DEBUG_UART_TX_GPIO_PORT, DEBUG_UART_TX_GPIO_PIN,GPIO_Pin_SET);    // 设置TXD的默认电平为高,空闲

    GPIO_InitStructure.Pins = DEBUG_UART_TX_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(DEBUG_UART_TX_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = DEBUG_UART_RX_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_Init(DEBUG_UART_RX_GPIO_PORT, &GPIO_InitStructure);

     //UART TX RX 复用
    DEBUG_UART_AFTX;
    DEBUG_UART_AFRX;
}

// 初始化I2C参数
void I2C_Configuration(void) {
        
        I2C_InitTypeDef I2C_InitStruct = {0};
        
        //I2C初始化
    // 串行时钟发生器采用 PCLK 作为输入时钟,通过 1 个 8bit的计数器计数,输出所需波特率的 I2C 时钟信号。
        I2C_InitStruct.PCLK_Freq    = 16000000;               // fSCL = fPCLK / 8 / ( BRR + 1 )
    I2C_InitStruct.I2C_SCL_Freq = 1000000;                // SCL频率=1MHz
    I2C_InitStruct.I2C_SCL_Source = I2C_SCL_SRC_GPIO;     //
    I2C_InitStruct.I2C_SDA_Source = I2C_SDA_SRC_GPIO;     //
        
        I2C1_DeInit();
    I2C_Master_Init(CW_I2C1,&I2C_InitStruct);//初始化模块
        I2C_AcknowledgeConfig(CW_I2C1,ENABLE);//ACK打开
    I2C_Cmd(CW_I2C1, ENABLE);//模块使能   
}

/**
  * @brief  Configure the nested vectored interrupt controller.
  * @param  None
  * @retval None
  */
void NVIC_Configuration(void) {
    __disable_irq();
    //NVIC_EnableIRQ(I2C1_IRQn);
    NVIC_EnableIRQ(ATIM_IRQn);      // 允许ATIM中断
    __enable_irq();
}




/**
* @brief 循环延时
*
* @param nCount
*/
void Delay(__IO uint16_t nCount) {
    /* Decrement nCount value */
    while (nCount != 0) {
        nCount--;
    }
}
void DelayMs(__IO uint16_t nCount) {
    /* Decrement nCount value */
    while (nCount-- != 0) {
        Delay(1300);
    }
}
void DelayUs(__IO uint16_t nCount)
{
    /* Decrement nCount value */
    while (nCount-- != 0) {
        Delay(130);
    }
}

/**
* @brief Retargets the C library printf function to the UART.
*
*/
PUTCHAR_PROTOTYPE
{
    UART_SendData_8bit(DEBUG_UARTx, (uint8_t)ch);

    while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);

    return ch;
}

size_t __write(int handle, const unsigned char * buffer, size_t size)
{
    size_t nChars = 0;

    if (buffer == 0)
    {
        /*
         * This means that we should flush internal buffers.  Since we
         * don't we just return.  (Remember, "handle" == -1 means that all
         * handles should be flushed.)
         */
        return 0;
    }


    for (/* Empty */; size != 0; --size)
    {
        UART_SendData_8bit(DEBUG_UARTx, *buffer++);
        while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);
        ++nChars;
    }

    return nChars;
}


/******************************************************************************
* EOF (not truncated)
******************************************************************************/
#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
  * [url=home.php?mod=space&uid=266161]@return[/url] 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,
       tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
    /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */



使用特权

评论回复
沙发
AdaMaYun| | 2024-12-10 11:15 | 只看该作者
非常不错的步进案例

使用特权

评论回复
板凳
LOVEEVER| | 2024-12-12 08:44 | 只看该作者
光感元气件其实就是ADC检测

使用特权

评论回复
地板
suncat0504|  楼主 | 2024-12-12 09:03 | 只看该作者
LOVEEVER 发表于 2024-12-12 08:44
光感元气件其实就是ADC检测

应该是这样的

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:大连伊飞特信息技术有限公司软件工程师
简介:本人于1993年毕业于大连理工大学。毕业后从事单片机开发工作5年,之后转入软件开发工作至今。

138

主题

3983

帖子

5

粉丝