📜 ⬆️ ⬇️

Firmware for electronics 3D scanner

I continue, and this article is probably completing the description of the construction 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 were not so many such scanners as today. So, it makes no sense to describe the 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 itself. This article was written for them.


About what


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

And all because only the code will continue. Here I will bring the program only for the fourth scanner, because the firmware of the third scanner is lost (it was after that incident that I began to make backups). As you probably remember , the STM32F401RE microcontroller is on the board for the fourth scanner, respectively, the code will be written for the F4 family.
')

Libraries


First we need to include 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 "misc.h"

It is clear from the name of the libraries that they are the same for the whole STM32F4 family (which means the code is transferred to any other microcontroller of this family without any problems. For example, on the 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 misc.h stands alone. This library is needed for interrupt handling.

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;

Variable Step shows at what step the motor is now. DelayTime sets the time between steps. This is necessary to ensure that the motor has time to rotate the shaft. Set in the number of processor cycles. About ConfigState and StepsPerComand I will tell 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 pass to functions. First, let's write this plain but useful function:
Delay
void Delay (uint32_t n)
{
uint32_t i;
for (i = 0; i <n; i ++) {}
}

I think comments are superfluous. Further, the initialization function of the pins to which the motor is connected. I have 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 <n; i ++)
{
Step ++;
if (Step> 4) {Step = 1;}
MotorCoil (Step);
Delay (DelayTime);
}
}

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

MotorResetGPIO function resets all motor pins to 0. This function is used in the next in order function of MotorCoil. This function sets the log. 1 on one of the engine pins. Since each pin corresponds to a coil of a stepper motor, we respectively switch on one of the coils. If you do this in the correct order (namely, in this function it is specified), the engine will perform the steps.
The last two functions (MotorStepUP and MotorStepDOWN) take as input a number — the number of steps that must be taken.
After the function for the motor, we initialize the USART:
Spoiler header
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 nothing special to comment here either. 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 data on the USART.
The last function block is the function for controlling the laser and the backlight. To do this, you need to set the timer and bring the PWM to 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, we initialize GPIO (C7 for the laser and B4 for the backlight). Then set the timer. And the last two functions are quite clearly called.
At this periphery ends. It remains to make the function of total shutdown of everything (so as not to get warm 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 code in main, don't you find? This is all due to the fact that all processing goes in interrupts. Most likely in one that we initialized when we configured USSART. Consider this interrupt handler:
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 set the data variable. Then we recorded the data with the USART. Em. What is this magic number 10? And then a little trick. If the number (character code) is less than 10, then the motor will take a number of steps equal to the character code. Next comes the ConfigState check. And the second time we skip this variable, and consider what is written in the else. Here is a set of commands:
s - take StepsPerComand steps forward (this is the variable that we missed at the beginning of the article).
n - turn on the laser (power indicated in LaserPower).
f - turn off the laser
r - TOTAL OFF (oh, sounds good).
b - take StepsPerComand back steps.
h - turn on the backlight (power is listed in LightPower).
u - turn off the backlight.
If the command did not fit under any of the conditions above, then the symbol is written to the ConfigState variable. We 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 has arrived, then we set a new DelayTime, if p, then a new laser power (and this power is immediately set on the laser, that is, it turns on), if i has arrived, then we change the variable StepsPerComand, and if l, then we change backlight power. And when something is written to ConfigState, the microcontroller sends a '#' to the terminal to show 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 reset 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 who will be forced by the teachers). I do not claim to be the perfect solution, or that everything is done correctly. Of course at each stage there is something that can be done better. So it is better not just to repeat, but also to add / finish.
Good luck to everyone in their development and projects! I hope this will help someone a little.

Source: https://habr.com/ru/post/256103/


All Articles