Add simple HPGL parser, PWM output

This commit is contained in:
fruchti 2020-11-28 23:01:21 +01:00
parent 7234eb8360
commit 57f486a4d2
11 changed files with 439 additions and 8 deletions

View file

@ -1 +1 @@
223
246

View file

@ -2,3 +2,4 @@
#define CONFIG_USB_RINGBUFFER_SIZE \
256
#define CONFIG_BUFFER_MOVEMENTS 32

196
stm32f103c8t6/src/hgpl.c Normal file
View file

@ -0,0 +1,196 @@
#include "hpgl.h"
#include "usb_cdc.h"
#include "pwm_output.h"
unsigned int HPGL_ParseErrorCounter = 0;
typedef enum
{
// Last command finished, waiting for more
HPGL_Parser_Idle,
// First byte of instruction has been received, waiting for second one
HPGL_Parser_ReceivingInstruction,
// Receiving the pen number (currently ignored)
HPGL_Parser_ReceivingPenNumber,
// Receiving coordinates after movement/drawing command
HPGL_Parser_ReceivingX,
HPGL_Parser_ReceivingY,
HPGL_Parser_Error,
} HPGL_ParserState_t;
// Adds a movement to the queue. Returns false when the queue is currently full
static bool HPGL_EnqueueMovement(HPGL_Movement_t movement)
{
return Output_EnqueueMovement(movement);
}
// Helper macro to combine two characters into one integer
#define INST(a,b) (((unsigned int)a) << 8 | (unsigned int)b)
void HPGL_Poll(void)
{
static HPGL_ParserState_t state = HPGL_Parser_Idle;
static char last_character = 0;
// Movement which is currently built from received characters
static HPGL_Movement_t current_movement;
// Movemenet which is waiting to be enqueued if the queue is full
static HPGL_Movement_t waiting_movement;
static bool waiting_for_movement_queue = false;
if(waiting_for_movement_queue)
{
if(!HPGL_EnqueueMovement(waiting_movement))
{
// Still not enough space
return;
}
waiting_for_movement_queue = false;
}
int received = USBCDC_ReceiveByte();
if(received == -1)
{
// Nothing received
return;
}
char character = received;
switch(state)
{
case HPGL_Parser_Idle:
if(character == '\r' || character == '\n'
|| character == ';' || character == ' ')
{
// Ignore delimiters
break;
}
if(character >= 'A' && character <= 'Z')
{
// First character of all supported instructions
state = HPGL_Parser_ReceivingInstruction;
}
else
{
// Unsupported command
HPGL_ParseErrorCounter++;
state = HPGL_Parser_Error;
}
break;
case HPGL_Parser_ReceivingInstruction:
switch(INST(last_character, character))
{
case INST('I', 'N'):
// Initialise
HPGL_ParseErrorCounter = 0;
// No parameters expected for this one
state = HPGL_Parser_Idle;
break;
case INST('S', 'P'):
// Set pen
state = HPGL_Parser_ReceivingPenNumber;
break;
case INST('P', 'U'):
// Movement with pen up
current_movement.pen_down = false;
current_movement.x = 0;
current_movement.y = 0;
state = HPGL_Parser_ReceivingX;
break;
case INST('P', 'D'):
// Movement with pen down
current_movement.pen_down = true;
current_movement.x = 0;
current_movement.y = 0;
state = HPGL_Parser_ReceivingX;
break;
default:
// Unsupported instruction
state = HPGL_Parser_Error;
break;
}
break;
case HPGL_Parser_ReceivingX:
case HPGL_Parser_ReceivingY:
if(character == ';' || character == '\n')
{
// Command finished
if(!HPGL_EnqueueMovement(current_movement))
{
// Not enough space in queue
waiting_movement = current_movement;
waiting_for_movement_queue = true;
}
state = HPGL_Parser_Idle;
}
else if(character == ',')
{
// Next coordinate
if(state == HPGL_Parser_ReceivingX)
{
state = HPGL_Parser_ReceivingY;
}
else
{
// Next movement
if(!HPGL_EnqueueMovement(current_movement))
{
// Not enough space in queue
waiting_movement = current_movement;
waiting_for_movement_queue = true;
}
// Reset coordinates, pen position stays unmodified
current_movement.x = 0;
current_movement.y = 0;
// Receive an X coordinate again
state = HPGL_Parser_ReceivingX;
}
}
else if(character >= '0' && character <= '9')
{
unsigned int digit = character - '0';
if(state == HPGL_Parser_ReceivingX)
{
current_movement.x = current_movement.x * 10 + digit;
}
else
{
current_movement.y = current_movement.y * 10 + digit;
}
}
else
{
// No decimal support so far. Sorry.
state = HPGL_Parser_Error;
}
break;
case HPGL_Parser_ReceivingPenNumber:
// Ignore everything except for end of command
if(character == ';' || character == '\n')
{
state = HPGL_Parser_Idle;
}
break;
case HPGL_Parser_Error:
// Exit error state only after end of command
if(character == '\n' || character == ';')
{
state = HPGL_Parser_Idle;
}
break;
}
last_character = character;
}

12
stm32f103c8t6/src/hpgl.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <stdbool.h>
typedef struct
{
bool pen_down;
unsigned int x;
unsigned int y;
} HPGL_Movement_t;
void HPGL_Poll(void);

View file

@ -34,17 +34,19 @@ int main(void)
Clock_Init();
LED_Init();
USB_Init();
Output_Init();
LED_ON();
uint8_t buffer[32];
// uint8_t buffer[32];
for(;;)
{
int length = USBCDC_ReceiveData(buffer, sizeof(buffer));
if(length)
{
USBCDC_SendData(buffer, length);
}
// int length = USBCDC_ReceiveData(buffer, sizeof(buffer));
// if(length)
// {
// USBCDC_SendData(buffer, length);
// }
// __WFI();
HPGL_Poll();
}
}

View file

@ -8,5 +8,7 @@
#include "usb.h"
#include "led.h"
#include "usb_cdc.h"
#include "hpgl.h"
#include "pwm_output.h"
int main(void);

View file

@ -1,6 +1,8 @@
#pragma once
// Port A
#define PIN_OUTPUT_X 8 // PA8 - TIM1_CH1
#define PIN_OUTPUT_Y 9 // PA9 - TIM1_CH2
#define PIN_USB_DM 11 // PA11 - USB_DM
#define PIN_USB_DP 12 // PA12 - USB_DP
// #define PIN_USB_PULLUP 15 // PA15 - 1.5 kΩ to D+

View file

@ -0,0 +1,163 @@
#include <string.h>
#include "stm32f1xx.h"
#include "pwm_output.h"
#include "config.h"
#include "pinning.h"
#include "ownership.h"
MODULE_OWNS_PIN(GPIOA, PIN_OUTPUT_X);
MODULE_OWNS_PIN(GPIOA, PIN_OUTPUT_Y);
static HPGL_Movement_t Output_Buffer[CONFIG_BUFFER_MOVEMENTS];
static const HPGL_Movement_t *Output_BufferRead = Output_Buffer;
static HPGL_Movement_t *Output_BufferWrite = Output_Buffer;
typedef struct
{
unsigned int x;
unsigned int y;
} Output_Coordinate_t;
static Output_Coordinate_t Output_CurrentPoint = {0, 0};
static Output_Coordinate_t Output_TargetPoint = {0, 0};
static int Output_CurrentVelocity = 10;
bool Output_EnqueueMovement(HPGL_Movement_t movement)
{
int buffer_space = Output_BufferRead - Output_BufferWrite - 1;
if(buffer_space < 0)
{
buffer_space += CONFIG_BUFFER_MOVEMENTS;
}
if(buffer_space == 0)
{
return false;
}
if(movement.x > OUTPUT_COORDINATE_LIMIT)
{
movement.x = OUTPUT_COORDINATE_LIMIT;
}
if(movement.y > OUTPUT_COORDINATE_LIMIT)
{
movement.y = OUTPUT_COORDINATE_LIMIT;
}
memcpy(Output_BufferWrite, &movement, sizeof(HPGL_Movement_t));
Output_BufferWrite++;
if(Output_BufferWrite >= Output_Buffer + CONFIG_BUFFER_MOVEMENTS)
{
Output_BufferWrite = Output_Buffer;
}
return true;
}
static bool Output_FetchNextPoint(void)
{
if(Output_BufferRead == Output_BufferWrite)
{
return false;
}
const HPGL_Movement_t *movement = Output_BufferRead;
Output_TargetPoint.x = movement->x;
Output_TargetPoint.y = movement->y;
// TODO: Handle pen state
Output_BufferRead++;
if(Output_BufferRead >= Output_Buffer + CONFIG_BUFFER_MOVEMENTS)
{
Output_BufferRead = Output_Buffer;
}
return true;
}
static int Output_ApproximateLength(int dx, int dy)
{
if(dx < 0)
{
dx = -dx;
}
if(dy < 0)
{
dy = -dy;
}
// Approximate vector length with alpha max plus beta min algorithm
if(dx < dy)
{
return dy + dx / 4;
}
else
{
return dx + dy / 4;
}
}
static void Output_Position()
{
uint16_t compare_x = OUTPUT_PWM_OFFSET + Output_CurrentPoint.x;
uint16_t compare_y = OUTPUT_PWM_OFFSET + Output_CurrentPoint.y;
TIM1->CCR1 = compare_x;
TIM1->CCR2 = compare_y;
}
void Output_Tick(void)
{
int step_length = Output_CurrentVelocity;
while(true)
{
// Compute distance to current target point
int diff_x = (int)(Output_TargetPoint.x) - (int)(Output_CurrentPoint.x);
int diff_y = (int)(Output_TargetPoint.y) - (int)(Output_CurrentPoint.y);
int length_ahead = Output_ApproximateLength(diff_x, diff_y);
if(step_length <= length_ahead)
{
// Move closer to current target point
Output_CurrentPoint.x += diff_x * step_length / length_ahead;
Output_CurrentPoint.y += diff_y * step_length / length_ahead;
break;
}
// Current target point is not far enough away to keep velocity
step_length -= length_ahead;
if(!Output_FetchNextPoint())
{
// No more points in queue, stop here
Output_CurrentPoint = Output_TargetPoint;
break;
}
}
Output_Position();
}
void Output_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRH = (GPIOA->CRH
& ~(0xf << (4 * PIN_OUTPUT_X - 32))
& ~(0xf << (4 * PIN_OUTPUT_Y - 32)))
| (0xa << (4 * PIN_OUTPUT_X - 32)) // AF output, 2 MHz
| (0xa << (4 * PIN_OUTPUT_Y - 32)) // AF output, 2 MHz
;
TIM1->CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1
| TIM_CCMR1_OC2PE | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1;
TIM1->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E;
TIM1->BDTR = TIM_BDTR_MOE;
TIM1->ARR = (1 << OUTPUT_PWM_RESOLUTION) - 1;
TIM1->DIER = TIM_DIER_UIE;
TIM1->CR1 = TIM_CR1_CEN;
NVIC_EnableIRQ(TIM1_UP_IRQn);
}
void TIM1_UP_IRQHandler(void)
{
Output_Tick();
TIM1->SR &= ~TIM_SR_UIF;
}

View file

@ -0,0 +1,12 @@
#pragma once
#include "hpgl.h"
#define OUTPUT_PWM_RESOLUTION 15 // In bits
#define OUTPUT_PWM_OFFSET 300 // Minimum value
#define OUTPUT_RESOLUTION 14 // In bits
#define OUTPUT_COORDINATE_LIMIT ((1 << OUTPUT_RESOLUTION) - 1)
void Output_Init(void);
bool Output_EnqueueMovement(HPGL_Movement_t movement);

View file

@ -148,11 +148,12 @@ void USBCDC_HandleDataOut(void)
// Determine the free space in the ring buffer and if another packet will
// fit in
int ring_buffer_space = USBCDC_RXBufferRead - USBCDC_RXBufferWrite - 1;
int ring_buffer_space = USBCDC_RXBufferRead - USBCDC_RXBufferWrite;
if(ring_buffer_space < 0)
{
ring_buffer_space += CONFIG_USB_RINGBUFFER_SIZE;
}
ring_buffer_space -= 1;
if(ring_buffer_space >= USBCDC_ENDPOINT_SIZE)
{
// Set EP3 ready for the next packet only when there is enough space in
@ -201,6 +202,10 @@ int USBCDC_ReceiveData(void *buffer, int length)
if(USBCDC_RXSuspended)
{
int ring_buffer_free = USBCDC_RXBufferRead - USBCDC_RXBufferWrite - 1;
if(ring_buffer_free < 0)
{
ring_buffer_free += CONFIG_USB_RINGBUFFER_SIZE;
}
if(ring_buffer_free >= USBCDC_ENDPOINT_SIZE)
{
USB_SetEPRXStatus(&(USB->EP3R), USB_EP_RX_VALID);
@ -211,6 +216,38 @@ int USBCDC_ReceiveData(void *buffer, int length)
return length;
}
int USBCDC_ReceiveByte()
{
if(USBCDC_RXBufferRead == USBCDC_RXBufferWrite)
{
return -1;
}
uint8_t byte = *USBCDC_RXBufferRead;
USBCDC_RXBufferRead++;
if(USBCDC_RXBufferRead >= USBCDC_RXBuffer + CONFIG_USB_RINGBUFFER_SIZE)
{
USBCDC_RXBufferRead = USBCDC_RXBuffer;
}
// If receiving has been suspended because the ring buffer is too full,
// resume if there is now enough space for another packet
if(USBCDC_RXSuspended)
{
int ring_buffer_free = USBCDC_RXBufferRead - USBCDC_RXBufferWrite - 1;
if(ring_buffer_free < 0)
{
ring_buffer_free += CONFIG_USB_RINGBUFFER_SIZE;
}
if(ring_buffer_free >= USBCDC_ENDPOINT_SIZE)
{
USB_SetEPRXStatus(&(USB->EP3R), USB_EP_RX_VALID);
USBCDC_RXSuspended = false;
}
}
return byte;
}
// Build a USB in packet out of data from the transmit ring buffer and mark it
// valid for the host to fetch it
static void USBCDC_BuildInPacket(void)

View file

@ -46,3 +46,7 @@ void USBCDC_SendData(const void *data, int length);
// Writes received data into a buffer. Returns the number of bytes written into
// the buffer.
int USBCDC_ReceiveData(void *buffer, int length);
// Reads a single byte from the receive buffer. If none are available, this
// function returns -1.
int USBCDC_ReceiveByte(void);