0
\$\begingroup\$

I need some help communicating between python's pyserial and an STM32f030R8 microcontroller. This is for a 2 DOF robot which has a python base GUI. The GUI will send, via serial communication, the coordinates for the robot and some other commands to engage or disengage the toolhead. On the python side, the relevant code snippets are the following:

def GoToCoords(X, Y):
    Msg = "M," + "{0:0=4d}".format(int(X)) + "," + "{0:0=4d}".format(int(Y))
    #Msg = "{0:0=4d}".format(int(X))
    print(Msg)
    ser.write(bytes(Msg, 'UTF-8'))

def DeployMarker():
    Msg = "E,0000,0000"
    print(Msg)
    ser.write(bytes(Msg, 'UTF-8'))

def StowMarker():
    Msg = "D,0000,0000"
    print(Msg)
    ser.write(bytes(Msg, 'UTF-8'))

# set up serial comms
print("Serial coms connecting...")
ser = serial.Serial('com4', 9600, timeout=10) # create Serial Object, baud = 9600, read times out after 10s
time.sleep(3)  # delay 3 seconds to allow serial com to get established
print("Serial com connected")

... some code which builds the GUI and calls the above functions...

You can see the code is attempting to send the command type and the relevant data in the format M,####,####. The first character dictates what kind of command is being sent (M = move, E = engage toolhead, D = disengage toolhead).

Onto the hardware, the computer is connected to a HiLetgo FT232RL Mini USB to TTL Serial Converter Adapter Module (https://www.amazon.com/dp/B00IJXZQ7C?psc=1&ref=ppx_yo2ov_dt_b_product_details).

Next on the STM32 side we have the following code:

main.c:

#include "main.h"
#include "WhiteBoardRobotFuncts.h"
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

const uint8_t MsgLength = 11;

char UART2_rxBuffer[11];

TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim6;
TIM_HandleTypeDef htim15;

UART_HandleTypeDef huart2;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM3_Init(void);
static void MX_TIM6_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_TIM15_Init(void);

////---------[ UART Data Reception Completion CallBackFunc. ]---------
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    HAL_UART_Receive_IT(&huart2, (uint8_t*) UART2_rxBuffer, MsgLength); //You need to toggle a breakpoint on this line!
}

int main(void) {
    int x_msg = 0;
    int y_msg = 0;

    HAL_Init();

    SystemClock_Config();

    MX_GPIO_Init();
    MX_TIM3_Init();
    MX_TIM6_Init();
    MX_USART2_UART_Init();
    MX_TIM15_Init();

    HAL_TIM_Base_Start(&htim3); //start timer
    HAL_TIM_Base_Start(&htim6); //start timer
    HAL_TIM_PWM_Start(&htim15, TIM_CHANNEL_1);

    StepperStartup();
    SetSpeed(50);

    HAL_UART_Receive_IT(&huart2, (uint8_t*) UART2_rxBuffer, MsgLength);

    while (1) {

        if (UART2_rxBuffer[0] == 'D') {
            DisengagePen();
            HAL_UART_Receive_IT(&huart2, (uint8_t*) UART2_rxBuffer, MsgLength);

            //clear buffer
            for (int i = 0; i < MsgLength; i++)
                UART2_rxBuffer[i] = 0;
        }

        if (UART2_rxBuffer[0] == 'E') {
            EngagePen();

            HAL_UART_Receive_IT(&huart2, (uint8_t*) UART2_rxBuffer, MsgLength);

            //clear buffer
            for (int i = 0; i < MsgLength; i++)
                UART2_rxBuffer[i] = 0;
        }

        if (UART2_rxBuffer[0] == 'M') {

            sscanf (UART2_rxBuffer,"M,%d,%d",&x_msg, &y_msg);

            LinearMove(x_msg, y_msg);

            HAL_UART_Receive_IT(&huart2, (uint8_t*)UART2_rxBuffer, MsgLength);

            //clear buffer
            for (int i = 0; i < MsgLength; i++)
                UART2_rxBuffer[i] = 0;

        }

    }
    /* 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.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    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.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

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

/**
 * @brief TIM3 Initialization Function
 * @param None
 * @retval None
 */
static void MX_TIM3_Init(void) {

    /* USER CODE BEGIN TIM3_Init 0 */

    /* USER CODE END TIM3_Init 0 */

    TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
    TIM_MasterConfigTypeDef sMasterConfig = { 0 };

    /* USER CODE BEGIN TIM3_Init 1 */

    /* USER CODE END TIM3_Init 1 */
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 8;
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 65535;
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim3) != HAL_OK) {
        Error_Handler();
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig)
            != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN TIM3_Init 2 */

    /* USER CODE END TIM3_Init 2 */

}

/**
 * @brief TIM6 Initialization Function
 * @param None
 * @retval None
 */
static void MX_TIM6_Init(void) {

    /* USER CODE BEGIN TIM6_Init 0 */

    /* USER CODE END TIM6_Init 0 */

    /* USER CODE BEGIN TIM6_Init 1 */

    /* USER CODE END TIM6_Init 1 */
    htim6.Instance = TIM6;
    htim6.Init.Prescaler = 8;
    htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim6.Init.Period = 65535;
    htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim6) != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN TIM6_Init 2 */

    /* USER CODE END TIM6_Init 2 */

}

/**
 * @brief TIM15 Initialization Function
 * @param None
 * @retval None
 */
static void MX_TIM15_Init(void) {

    /* USER CODE BEGIN TIM15_Init 0 */

    /* USER CODE END TIM15_Init 0 */

    TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
    TIM_MasterConfigTypeDef sMasterConfig = { 0 };
    TIM_OC_InitTypeDef sConfigOC = { 0 };
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = { 0 };

    /* USER CODE BEGIN TIM15_Init 1 */

    /* USER CODE END TIM15_Init 1 */
    htim15.Instance = TIM15;
    htim15.Init.Prescaler = 0;
    htim15.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim15.Init.Period = 65535;
    htim15.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim15.Init.RepetitionCounter = 0;
    htim15.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim15) != HAL_OK) {
        Error_Handler();
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim15, &sClockSourceConfig) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_TIM_PWM_Init(&htim15) != HAL_OK) {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig)
            != HAL_OK) {
        Error_Handler();
    }
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 0;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    if (HAL_TIM_PWM_ConfigChannel(&htim15, &sConfigOC, TIM_CHANNEL_1)
            != HAL_OK) {
        Error_Handler();
    }
    __HAL_TIM_DISABLE_OCxPRELOAD(&htim15, TIM_CHANNEL_1);
    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime = 0;
    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
    if (HAL_TIMEx_ConfigBreakDeadTime(&htim15, &sBreakDeadTimeConfig)
            != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN TIM15_Init 2 */

    /* USER CODE END TIM15_Init 2 */
    HAL_TIM_MspPostInit(&htim15);

}

/**
 * @brief USART2 Initialization Function
 * @param None
 * @retval None
 */
static void MX_USART2_UART_Init(void) {

    /* USER CODE BEGIN USART2_Init 0 */

    /* USER CODE END USART2_Init 0 */

    /* USER CODE BEGIN USART2_Init 1 */

    /* USER CODE END USART2_Init 1 */
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 9600;
    huart2.Init.WordLength = UART_WORDLENGTH_8B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_NONE;
    huart2.Init.Mode = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart2.Init.OverSampling = UART_OVERSAMPLING_16;
    huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
    huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    if (HAL_UART_Init(&huart2) != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN USART2_Init 2 */

    /* USER CODE END USART2_Init 2 */

}

/**
 * @brief GPIO Initialization Function
 * @param None
 * @retval None
 */
static void MX_GPIO_Init(void) {
    /* USER CODE BEGIN MX_GPIO_Init_1 */
    /* USER CODE END MX_GPIO_Init_1 */

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /* USER CODE BEGIN MX_GPIO_Init_2 */
    /* USER CODE END MX_GPIO_Init_2 */
}

/* 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 */

The WhiteBoardRobotFuncts.c is as follows:

/*
 * WhiteBoardRobotFuncts.c
 *
 *  Created on: Jan 13, 2024
 *      Author: Ericw
 */

#include "main.h"
#include <math.h>
#include "WhiteBoardRobotFuncts.h"
//#include "PWMFunctions.h"
#include "stm32f0xx.h"

float MoveVelocity = 50;

float CurrentLl = 0;
float CurrentLr = 0;
float CurrentX = X_init;
float CurrentY = Y_init;
float CurrentStepl = 0;
float CurrentStepr = 0;

void setPortAndPinForOutput(GPIO_TypeDef *portOnMCU, int pinNumber) {
    //ENABLE PORTS
    if (portOnMCU == GPIOA) {
        RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    }
    if (portOnMCU == GPIOB) {
        RCC->AHBENR |= RCC_AHBENR_GPIOBEN;
    }
    if (portOnMCU == GPIOC) {
        RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
    }
    if (portOnMCU == GPIOD) {
        RCC->AHBENR |= RCC_AHBENR_GPIODEN;
    }
    if (portOnMCU == GPIOF) {
        RCC->AHBENR |= RCC_AHBENR_GPIOFEN;
    }

    //SET PIN TO OUTPUT, HIGH SPEED, PP, NO PUPD
    portOnMCU->MODER |= 1 << (2 * pinNumber);
    portOnMCU->MODER &= ~(1 << (2 * pinNumber + 1));

    portOnMCU->OSPEEDR |= (1 << (2 * pinNumber)) | (1 << (2 * pinNumber + 1));

    portOnMCU->OTYPER &= ~(1 << (pinNumber));

    portOnMCU->PUPDR &= ~(1 << (pinNumber));

}

void Delay_uS(uint16_t us) {

    TIM3->PSC = 8;
    TIM3->ARR = 65535;

    TIM3->CNT = 0x00000000;  // set the counter value a 0
    while (TIM3->CNT < us) {
    }  // wait for the counter to reach the us input in the parameter
}

void Delay_mS(uint16_t ms) {
    for (int i = 0; i < ms; i++) {
        Delay_uS(1000);
    }

}

void StepperStartup() {
    //setup Servo PWM
    //PWMSetup_CH4();

    //SETUP RIGHT STEPPER
    setPortAndPinForOutput(dirPortR, dirPinR);
    setPortAndPinForOutput(StepPortR, StepPinR);
    setPortAndPinForOutput(EnablePortR, EnablePinR);
    EnablePortR->BSRR |= (1 << EnablePinR); //disable stepper

    //SETUP LEFT STEPPER
    setPortAndPinForOutput(dirPortL, dirPinL);
    setPortAndPinForOutput(StepPortL, StepPinL);
    setPortAndPinForOutput(EnablePortL, EnablePinL);
    EnablePortL->BSRR |= (1 << EnablePinL); //disable stepper ,

    CurrentLl = sqrt(pow(X_init, 2) + pow(Y_init, 2));
    CurrentLr = sqrt(pow(X_init - Ls, 2) + pow(Y_init, 2));

//  //temporary trouble shooting pin
//  setPortAndPinForOutput(GPIOB, 13);
//  GPIOB->BRR |= (1 << 13); //TURN ON LED
}

void SetSpeed(float speedInput) {
    MoveVelocity = speedInput;
    //StepDelay [us/step] = [min/rev][s/min][us/s][rev/deg][deg/step]
    //StepDelay = floor((1 / speedInput) * 60. * 1000000. * (1. / 360.) * (1.8 / NumMicrosteps));
    //StepDelay = floor((300000 / (speedInput * NumMicrosteps)));
}

void LinearMove(float x, float y) {

//convert (x,y) coords to steps--------------------------------------------------
    float TargetLl = sqrt(pow(x, 2) + pow(y, 2));
    float TargetLr = sqrt(pow(Ls - x, 2) + pow(y, 2));

    int StepsToTakeL = floor(
            StepsPerRad * ((TargetLl - CurrentLl) / SpoolRadius));
    int StepsToTakeR = floor(
            StepsPerRad * ((TargetLr - CurrentLr) / SpoolRadius));

    int TargetStepL = StepsToTakeL + CurrentStepl;
    int TargetStepR = StepsToTakeR + CurrentStepr;

    float incrementerL = 0;
    float incrementerR = 0;

//determine Move distance --> find move time (given move speed) --> find delays for each stepper
    float MoveLinearTime = sqrt(pow(x - CurrentX, 2) + pow(y - CurrentY, 2))
            / MoveVelocity;

    //Find Left and right stepper delays
    int StepperDelay_L = ceil(1000000 * MoveLinearTime / abs(StepsToTakeL * 2)); //us / step
    int StepperDelay_R = ceil(1000000 * MoveLinearTime / abs(StepsToTakeR * 2)); //us / step

//Determine Directions for both steppers
    if (StepsToTakeL < 0) {
        dirPortL->BRR |= (1 << dirPinL);
        incrementerL = -0.5;
    } else {
        dirPortL->BSRR |= (1 << dirPinL);
        incrementerL = 0.5;
    }

    if (StepsToTakeR < 0) {
        dirPortR->BSRR |= (1 << dirPinR);
        incrementerR = -0.5;
    } else {
        dirPortR->BRR |= (1 << dirPinR);
        incrementerR = 0.5;
    }

//enable steppers
    EnablePortR->BRR |= (1 << EnablePinR);
    EnablePortL->BRR |= (1 << EnablePinL);

//start each clk at zero
    TIM3->CNT = 0x00000000;  // set the counter value a 0
    TIM6->CNT = 0x00000000;  // set the counter value a 0

    //WHILE EITHER STEPPER ISNT AT ITS TARGER
    while ((CurrentStepl != TargetStepL) | (CurrentStepr != TargetStepR)) {

        if ((CurrentStepl != TargetStepL) & (TIM3->CNT >= StepperDelay_L)) {
            TIM3->CNT = 0x00000000;  // clear counter
            StepPortL->ODR ^= (1 << StepPinL); //flip pin value with xor
            CurrentStepl += incrementerL; //note half a step has been taken
        }

        if ((CurrentStepr != TargetStepR) & (TIM6->CNT >= StepperDelay_R)) {
            TIM6->CNT = 0x00000000;  // clear counter
            StepPortR->ODR ^= (1 << StepPinR);
            CurrentStepr += incrementerR;
        }

    }

//after move is complete update Ll and Lr
    CurrentLl = TargetLl;
    CurrentLr = TargetLr;
    CurrentX = x;
    CurrentY = y;

//disable steppers
    EnablePortR->BSRR |= (1 << EnablePinR);
    EnablePortL->BSRR |= (1 << EnablePinL);
}

void EngagePen() {
    WriteServoAngle(0);
}

void DisengagePen() {
    WriteServoAngle(179);
}

void PWMSetPulseAndPeriod_inPulse(int PeriodInMS, int PeriodInPulses, int PulseWidth){
    int prescaler;
    int NewPeriodInPulses;

    if (PeriodInMS>0){
        prescaler = floor(((HSI_VALUE/1000) * PeriodInMS)/65536);
        NewPeriodInPulses= floor(((HSI_VALUE/(prescaler+1)) / 1000) * PeriodInMS);
    }else if(PeriodInPulses>0){
        prescaler = floor(PeriodInPulses/65536);
        NewPeriodInPulses= floor(PeriodInPulses/(prescaler+1));
    }


        //SET ARR AND PRESCALER
    TIM15->PSC = prescaler;
    TIM15->ARR = NewPeriodInPulses;

    int newPulseWidth = floor(PulseWidth/(prescaler+1));


    //set pulse width
    TIM15->CCR1 = newPulseWidth;

}

void PWMSetPulseAndPeriod_inDutyCycle(int PeriodInMS, int PeriodInPulses, int dutyCycle){
    int prescaler;
    int NewPeriodInPulses;

    if (PeriodInMS>0){
        prescaler = floor(((HSI_VALUE/1000) * PeriodInMS)/65536);
        NewPeriodInPulses= floor(((HSI_VALUE/(prescaler+1)) / 1000) * PeriodInMS);
    }else if(PeriodInPulses>0){
        prescaler = floor(PeriodInPulses/65536);
        NewPeriodInPulses= floor(PeriodInPulses/(prescaler+1));
    }


        //SET ARR AND PRESCALER
    TIM15->PSC = prescaler;
    TIM15->ARR = NewPeriodInPulses;

    int newPulseWidth = floor(NewPeriodInPulses*dutyCycle/100);

    //set pulse width
    TIM15->CCR1 = newPulseWidth;

}

void WriteServoAngle(float angle){

    int widthPulses = floor(4000. + angle*800./9.);
    PWMSetPulseAndPeriod_inPulse(20,0,widthPulses);
}

So what is my problem:

When I send a move command from my python GUI, on the first command the robot always moves to the same incorrect point on the board, regardless of my commanded coordinates. Then when I attempt to send additional move commands, the robot does not move (meaning the passed x, y coordinates are the same as in the previous calls of the function). My DeployMarker() and StowMarker() commands work and I have no issues controlling those via the python GUI. Additionally, the LinearMove command works flawlessly when I am not trying to feed it coordinates received via serial communication. Therefore, I have concluded that the issue must be in the way I am sending/receiving/parsing the x and y coordinates. So my question, in short, is what should I be doing differently to send/receive/parse a message in the format of "M,%d,%d" ?

As an aside, it is probably pretty obvious that my knowledge of Serial and Uart communication is pretty limited, so I am all ears if anyone has any recommended resources for me to learn more!

Update: I commanded x=100 and y= 130 in my python GUI and had the STM reply with the exact same message, as shown in the following code:

STM:

while (1) {

        if (UART2_rxBuffer[0] == 'D') {
            DisengagePen();
            HAL_UART_Receive_IT(&huart2, (uint8_t*) UART2_rxBuffer, MsgLength);

            //clear buffer
            for (int i = 0; i < MsgLength; i++)
                UART2_rxBuffer[i] = 0;
        }

        if (UART2_rxBuffer[0] == 'E') {
            EngagePen();

            HAL_UART_Receive_IT(&huart2, (uint8_t*) UART2_rxBuffer, MsgLength);

            //clear buffer
            for (int i = 0; i < MsgLength; i++)
                UART2_rxBuffer[i] = 0;
        }

        if (UART2_rxBuffer[0] == 'M') {

            sscanf (UART2_rxBuffer,"M,%d,%d",&x_msg, &y_msg);

            //LinearMove(x_msg, y_msg);

            HAL_UART_Transmit(&huart2, UART2_rxBuffer, sizeof(UART2_rxBuffer), 100);
            Delay_mS(100);

            HAL_UART_Receive_IT(&huart2, (uint8_t*)UART2_rxBuffer, MsgLength);

            //clear buffer
            for (int i = 0; i < MsgLength; i++)
                UART2_rxBuffer[i] = 0;

        }

    }
    /* USER CODE END 3 */
}

python:

def GoToCoords(X, Y):
    Msg = "M," + "{0:0=4d}".format(int(X)) + "," + "{0:0=4d}".format(int(Y))
    print(Msg)
    ser.write(bytes(Msg, 'UTF-8'))

    while (True):
        if ser.in_waiting:
            print(ser.readline())
            break

This gives the following from in python:

M,0100,0130

b'M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Likewise, just returning just x_msg gives:

M,0100,0130

b'\x00\x00\x00\x00'

\$\endgroup\$
3
  • 2
    \$\begingroup\$ instead of moving the robot, echo the command back to python ... also send the parsed values ... that might reveal a bug \$\endgroup\$
    – jsotola
    Mar 9 at 19:25
  • \$\begingroup\$ @jsotola I echoed the message and it looks like I am returning all zeros... However I am still confused as to why this would be the case \$\endgroup\$
    – Eric14003
    Mar 9 at 21:27
  • \$\begingroup\$ @Eric14003 That is how your STM32 code does. It will not work. It would be interesting to know how you expect it to work or how you think it could work the way you expect it to work. Then, there might be way to point you forward. \$\endgroup\$
    – Justme
    Mar 9 at 22:47

2 Answers 2

3
\$\begingroup\$

As someone said, "You're using it wrong"!

HAL_UART_Receive_IT() is a non-blocking function meant to prepare for receiving data. You pass your buffer address and length to the HAL, and then later, when some characters arrive on the UART, they will be written to the buffer. It has a return code, which you do not check.

This function does not block and does not receive any data, therefore your code looks into the buffer immediately afterwards and finds no data.

So the while() keeps looping until the first character, which is 'M' is written to the buffer, after which UART2_rxBuffer[0] == 'M'

When it sees the 'M' it enters the if(). At this point, the buffer contains only 'M' followed by zeros because the rest of the line hasn't arrived yet.

The scanf() therefore fails, but you don't check the return code.

Then it echoes the contents of the receive buffer with HAL_UART_Transmit(), giving the expected result, 'M' followed by zeros.

When the buffer is full and the transfer is complete, the HAL would call HAL_UART_RxCpltCallback() so you can handle the data, but the code in HAL_UART_RxCpltCallback() doesn't do anything with the received data. It is probably never called anyway, because after the first character is received, HAL_UART_Receive_IT() is called again which reinitializes the transfer.


You're using a line-based protocol, so you need to parse lines, which means knowing where your lines end, which means: read characters into a buffer until...

  • An end-of-line character arrives: you have a complete line, which you can parse with sscanf() or via other means, then either acknowledge and execute the command, or throw an error if the line contains an invalid command.

  • Or the buffer is full. So you keep skipping characters until the end of line, then ignore the whole line, and reply with a "line too long" error.

This does not fit the HAL_UART_Receive_IT() API because it expects fixed-length messages, and the length has to be known beforehand. If you use a line-oriented protocol, you pretty much have to read character by character, until an end of line character is found.

What API to use depends on what you want to do with the robot.

The simple and dumb approach is to use a blocking read. For example you can use HAL_UART_Receive() to grab one character. Then you build your line in a buffer and when an end of line arrive, you can parse it and execute it.

Drawback: the UART on these chips doesn't have a FIFO (it has DMA instead) so if data comes in while HAL_UART_Receive() is not waiting for it, it will be dropped. This means your robot is either listening to a new command with HAL_UART_Receive, or executing a command, but not both at the same time.

So this approach requires the robot to send Ready messages when it is ready to accept a new command. The python code should wait for Ready before sending a new one. Note I'm using "Ready" but you can use any message you want as long as both sides agree on what it means.

Ready's aren't a problem because you'd need them anyway: even if you use a smarter UART mode (DMA) which can receive while the MCU is doing something else, the RAM is still tiny on this chip, so you can't send the whole list of commands for the whole drawing in one go. The python code will always have to stream commands at the same speed as the robot is drawing, to avoid overflows.


To streamline things a little bit, a smarter version: you may want to send a bunch of commands without waiting for Ready every time in order to pipeline it. A way to do this is to send the first N commands without waiting for Ready (assuming the N commands do fit inside the UART receive buffer), then wait for Ready for all the subsequent commands. This way there are N commands pipelined. This needs a ring buffer on the mcu and using the UART in DMA mode (or in interrupt-per-character mode, but that's slower and not simpler).

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for your insightful answer. I was able to fix my issue using this! \$\endgroup\$
    – Eric14003
    Mar 10 at 15:57
  • \$\begingroup\$ Excellent news then! \$\endgroup\$
    – bobflux
    Mar 10 at 18:03
0
\$\begingroup\$

You never check what you receive. Or rather, not waiting the whole message to be received before acting on it.

When PC starts transmitting and has sent out the first letter, before it has transmitted any other letters, your MCU code has determined that the first letter D, E, or M has arrived, and it goes handle that message that starts with those letters.

The problem is, PC hasn't sent out anything other than the firsr letter yet, so MCU has not received any parameters for those commands yet.

So this isn't specific to PCs, STM32, Python or anything. Executing commands after command but no parameters is received does not depend on those. Bytes are transmitted and received one at a time, and if you want to wait for parameters then wait for the parameters too.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.