Firmware for electronics 3D scanner

    I continue, and this article probably ends the description of the design of a homemade 3D scanner, which was described in this article . In general, almost two years ago, when we were just starting this project, there weren’t as many such scanners as today. So it makes no sense to describe mechanics (it is the same for all such scanners), and I did not write the software part. And this article can still help those who nevertheless decide to assemble the scanner themselves. This article is written for them.


    About what


    There will be no pictures, schemes or even seals. Although not. Will be.
    image

    And all because only the code will be further. Here I will give the program only for the fourth scanner, because the firmware of the third scanner has been lost (it was after that incident that I started making backups). As you probably remember , the STM32F401RE microcontroller is on the motherboard for the fourth scanner; accordingly, the code will be written for the F4 family.

    Libraries


    First we need to connect all the necessary libraries:
    Libraries
    #include "stm32f4xx.h"
    #include "stm32f4xx_exti.h"
    #include "stm32f4xx_gpio.h"
    #include "stm32f4xx_rcc.h"
    #include "stm32f4xx_tim.h"
    #include "stm32f4xx_usart.h"
    #include

    From the name of the libraries it can be seen that they are the same for the entire STM32F4 family (which means the code can be easily transferred to any other microcontroller of this family. For example, to STM32F407VB). The second thing that can be seen from the names is the name of the periphery for which these libraries are needed. The library stands misc.h. This library is needed to handle interrupts.

    Initialization of peripherals and variables


    Let's start by declaring global variables:
    Variables
    int Step;
    int DelayTime = 100000;
    char ConfigState;
    uint8_t StepsPerComand = 1;
    uint8_t LaserPower = 0;
    uint8_t LightPower = 0;

    The Step variable shows at what step the motor is now. DelayTime sets the time between steps. This is necessary so that the motor has time to roll the shaft. It is set in the number of processor cycles. I will talk about ConfigState and StepsPerComand later. LaserPower and LightPower set the laser power and backlight, respectively (the backlight was not implemented, but PWM is still output).
    Global variables are over. Let's move on to the functions. First, we write here such a simple, but useful function:
    Delay
    void Delay (uint32_t n)
    {
    uint32_t i;
    for (i = 0; i }

    I think the comments are superfluous. Further, the function of initializing the pins to which the motor is connected. I have it A5, A6, A7 and B6:
    Motor pins
    void InitMotorGPIO (void)
    {
    GPIO_InitTypeDef MotorGPIO;

    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOB, ENABLE);

    MotorGPIO.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    MotorGPIO.GPIO_Mode = GPIO_Mode_OUT;
    MotorGPIO.GPIO_OType = GPIO_OType_PP;
    MotorGPIO.GPIO_PuPd = GPIO_PuPd_DOWN;
    MotorGPIO.GPIO_Speed ​​= GPIO_Speed_2MHz;
    GPIO_Init (GPIOA, & MotorGPIO);

    MotorGPIO.GPIO_Pin = GPIO_Pin_6;
    GPIO_Init (GPIOB, & MotorGPIO);
    }

    Next, the motor must be controlled. For this there are such functions:
    Motor control
    void MotorResetGPIO (void)
    {
    GPIO_ResetBits (GPIOA, GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
    GPIO_ResetBits (GPIOB, GPIO_Pin_6);
    }

    void MotorCoil (int coil)
    {
    MotorResetGPIO ();
    switch (coil)
    {
    case 1: GPIO_SetBits (GPIOA, GPIO_Pin_5); break;
    case 2: GPIO_SetBits (GPIOA, GPIO_Pin_6); break;
    case 3: GPIO_SetBits (GPIOA, GPIO_Pin_7); break;
    case 4: GPIO_SetBits (GPIOB, GPIO_Pin_6); break;
    }
    }

    void MotorStepUP (int n)
    {
    int i;
    for (i = 0; i {
    Step ++;
    if (Step> 4) {Step = 1;}
    MotorCoil (Step);
    Delay (DelayTime);
    }
    }

    void MotorStepDOWN (int n)
    {
    int i;
    for (i = 0; i {
    Step--;
    if (Step <1) {Step = 4;}
    MotorCoil (Step);
    Delay (DelayTime);
    }
    }

    The MotorResetGPIO function resets to 0 all motor pins. This function is used in the next MotorCoil function. This function sets the log. 1 to one of the motor pins. Since each pin corresponds to a stepper motor coil, we respectively turn on one of the coils. If you do this in the correct order (namely, in this function it is set), then the engine will take steps.
    The last two functions (MotorStepUP and MotorStepDOWN) take a number as input - the number of steps that must be completed.
    After the function for the motor, we initialize USART:
    Spoiler heading
    void InitUsartGPIO (void)
    {
    GPIO_InitTypeDef UsartGPIO;

    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE);

    GPIO_PinAFConfig (GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
    GPIO_PinAFConfig (GPIOA, GPIO_PinSource3, GPIO_AF_USART2);

    UsartGPIO.GPIO_OType = GPIO_OType_PP;
    UsartGPIO.GPIO_PuPd = GPIO_PuPd_UP;
    UsartGPIO.GPIO_Mode = GPIO_Mode_AF;
    UsartGPIO.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
    UsartGPIO.GPIO_Speed ​​= GPIO_Speed_50MHz;
    GPIO_Init (GPIOA, & UsartGPIO);
    }

    void InitUsart (void)
    {
    InitUsartGPIO ();

    USART_InitTypeDef USART_InitStructure;

    RCC_APB1PeriphClockCmd (RCC_APB1Periph_USART2, ENABLE);

    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init (USART2, & USART_InitStructure);
    USART_Cmd (USART2, ENABLE);

    NVIC_InitTypeDef UsartNVIC;

    UsartNVIC.NVIC_IRQChannel = USART2_IRQn;
    UsartNVIC.NVIC_IRQChannelPreemptionPriority = 2;
    UsartNVIC.NVIC_IRQChannelSubPriority = 2;
    UsartNVIC.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init (& UsartNVIC);

    USART_ITConfig (USART2, USART_IT_RXNE, ENABLE);
    }

    There is also nothing special to comment on. A2 - Rx, A3 - Tx. The speed is 9600 baud. Unless you can pay attention to the last 9 lines. There we initialize the interrupt. It will occur when receiving USART data.
    The last block of functions are functions for controlling the laser and backlight. To do this, you need to set the timer and display the PWM on the legs of interest to us (microcontroller):
    Timer initialization
    void InitLaserGPIO (void)
    {
    GPIO_InitTypeDef LaserGPIO;
    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOC, ENABLE);
    GPIO_PinAFConfig (GPIOC, GPIO_PinSource7, GPIO_AF_TIM3);

    LaserGPIO.GPIO_Mode = GPIO_Mode_AF;
    LaserGPIO.GPIO_OType = GPIO_OType_PP;
    LaserGPIO.GPIO_Pin = GPIO_Pin_7;
    LaserGPIO.GPIO_PuPd = GPIO_PuPd_UP;
    LaserGPIO.GPIO_Speed ​​= GPIO_Speed_100MHz;
    GPIO_Init (GPIOC, & LaserGPIO);
    }

    void InitLightGPIO (void)
    {
    GPIO_InitTypeDef LightGPIO;
    GPIO_PinAFConfig (GPIOB, GPIO_PinSource4, GPIO_AF_TIM3);

    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOB, ENABLE);
    LightGPIO.GPIO_Mode = GPIO_Mode_AF;
    LightGPIO.GPIO_OType = GPIO_OType_PP;
    LightGPIO.GPIO_Pin = GPIO_Pin_4;
    LightGPIO.GPIO_PuPd = GPIO_PuPd_NOPULL;
    LightGPIO.GPIO_Speed ​​= GPIO_Speed_100MHz;
    GPIO_Init (GPIOB, & LightGPIO);
    }

    void InitLaserAndLight (void)
    {
    InitLaserGPIO ();
    InitLightGPIO ();

    TIM_TimeBaseInitTypeDef BaseTIM;

    RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3, ENABLE);

    BaseTIM.TIM_Period = 0xFF;
    BaseTIM.TIM_Prescaler = 3;
    BaseTIM.TIM_CounterMode = TIM_CounterMode_Up;
    BaseTIM.TIM_ClockDivision = 0;
    TIM_TimeBaseInit (TIM3, & BaseTIM);

    TIM_OCInitTypeDef TimOC;

    TimOC.TIM_OCMode = TIM_OCMode_PWM1;
    TimOC.TIM_OutputState = TIM_OutputState_Enable;
    TimOC.TIM_Pulse = 0;
    TimOC.TIM_OCPolarity = TIM_OCPolarity_High;

    TIM_OC1Init (TIM3, & TimOC);
    TIM_OC1PreloadConfig (TIM3, TIM_OCPreload_Enable);

    TimOC.TIM_OutputState = TIM_OutputState_Enable;
    TimOC.TIM_Pulse = 0;

    TIM_OC2Init (TIM3, & TimOC);
    TIM_OC2PreloadConfig (TIM3, TIM_OCPreload_Enable);

    TIM_ARRPreloadConfig (TIM3, ENABLE);
    TIM_Cmd (TIM3, ENABLE);
    }

    void SetLaserPower (uint8_t p)
    {
    TIM3-> CCR2 = p;
    }

    void SetLightPower (uint8_t p)
    {
    TIM3-> CCR1 = p;
    }

    First, initialize the GPIO (C7 for the laser and B4 for the backlight). Then we set the timer. And the last two functions are very clearly called.
    This is where the periphery ends. It remains to make the function of completely turning off everything (so as not to warm up when not needed):
    Total shutdown
    void FullReset (void)
    {
    MotorResetGPIO ();
    SetLaserPower (0);
    SetLightPower (0);
    }

    And of course main:
    main
    int main (void)
    {
    InitMotorGPIO ();
    InitLaserAndLight ();
    InitUsart ();

    while (1)
    {
    }
    }

    Just something small some kind of code in main, don’t you? This is all due to the fact that all processing goes to interrupts. Most likely, the one that we initialized when we set up USSART. Consider the handler for this interrupt:
    Need more case !!!
    void USART2_IRQHandler (void)
    {
    char data;
    data = USART_ReceiveData (USART2);
    if (data <10) {MotorStepUP (data);}
    if (ConfigState) {
    switch (ConfigState) {
    case 'd': DelayTime = data; break;
    case 'p': LaserPower = data; SetLaserPower (LaserPower); break;
    case 'i': StepsPerComand = data; break;
    case 'l': LightPower = data; break;
    }
    ConfigState = 0;
    USART_SendData (USART2, 'R');
    }
    else {
    switch (data) {
    case 's': MotorStepUP (StepsPerComand); break;
    case 'n': SetLaserPower (LaserPower); break;
    case 'f': SetLaserPower (0); break;
    case 'r' : FullReset (); break;
    case 'b': MotorStepDOWN (StepsPerComand); break;
    case 'h': SetLightPower (LightPower); break;
    case 'u': SetLightPower (0); break;
    default: ConfigState = data;
    }
    if (ConfigState) {USART_SendData (USART2, '#');}
    else {USART_SendData (USART2, 'R');}
    }
    USART_ReceiveData (USART2);
    data = 0;
    USART_ClearITPendingBit (USART2, USART_IT_RXNE);
    }

    We understand line by line. First we got the data variable. Then we recorded data from USART in it. Em. And what is this magic number 10? And here is a little trick. If the number (symbol code) is less than 10, then the motor will take the number of steps equal to the symbol code. Next is the ConfigState check. And the second time we skip this variable, and consider what is written in else. Here is a set of commands:
    s - take StepsPerComand steps forward (this is the variable that we skipped at the beginning of the article).
    n - turn on the laser (power is indicated in LaserPower).
    f - turn off the laser
    r - TOTAL SHUT-OFF (eh, it sounds good).
    b - take StepsPerComand steps back.
    h - turn on the backlight (power is indicated in LightPower).
    u - turn off the backlight.
    If the command does not fit under any of the conditions above, then the character is written to the ConfigState variable. Let us now return again to the condition in the 4th line of the function. If ConfigState is not 0, then we configure the parameter. Set a new value. If d comes, then we set a new DelayTime, if p, then the new laser power (and this power is immediately installed on the laser, that is, it turns on), if i comes, then change the StepsPerComand variable, but if l, then change backlight power. Moreover, when something is written to ConfigState, the microcontroller sends a '#' to the terminal to indicate that it is waiting for the second character. If he just executed the command, then the answer is 'R'.
    In the end, we simply clear the receive buffer and clear the interrupt flag.
    This ends the code.

    The final


    That's all. I hope this material will help those who decide to repeat my experience (or whom the teachers will force). I do not pretend to be the perfect solution, or that everything is done correctly. Of course, at every stage there is something that can be done better. So it’s better not just to repeat, but also to add / finish by ourselves.
    Good luck to everyone in their development and projects! I hope this helps someone a little.

    Also popular now: