inverse_thermal_camera/src/ltp1245.c

443 lines
12 KiB
C

#include "ltp1245.h"
#include "pinning.h"
uint8_t LTP1245_Buffer[LTP1245_LINEWIDTH / 8 * LTP1245_BUFFER_LINES];
typedef struct State_t
{
struct State_t (*fn)(void);
} State_t;
static volatile int Stepper_Delta = 0;
static int PrintLines;
static int CurrentBufferLine;
static volatile int PulseWidth = 2000;
// Main State machine states
static State_t State_Idle(void);
static State_t State_PaperLoad(void);
static State_t State_Printing(void);
static State_t State_PaperFeed(void);
static volatile State_t State = {State_Idle};
static void InitStepper(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
GPIOA->CRL = (GPIOA->CRL
& ~(0x0f << (4 * PIN_STEPPER_AM))
& ~(0x0f << (4 * PIN_STEPPER_AP))
& ~(0x0f << (4 * PIN_STEPPER_BM))
& ~(0x0f << (4 * PIN_STEPPER_BP)))
| (0x01 << (4 * PIN_STEPPER_AM)) // Output, max. 10 MHz
| (0x01 << (4 * PIN_STEPPER_AP)) // Output, max. 10 MHz
| (0x01 << (4 * PIN_STEPPER_BM)) // Output, max. 10 MHz
| (0x01 << (4 * PIN_STEPPER_BP)) // Output, max. 10 MHz
;
TIM3->PSC = 72000000 / 100 / LTP1245_MAX_DRIVE_FREQ - 1;
TIM3->ARR = 100;
TIM3->DIER = TIM_DIER_UIE;
TIM3->CR1 = TIM_CR1_CEN;
NVIC_EnableIRQ(TIM3_IRQn);
}
static void InitSensors(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRL = (GPIOA->CRL
& ~(0x0f << (4 * PIN_HEAD))
& ~(0x0f << (4 * PIN_PAPER)))
| (0x08 << (4 * PIN_HEAD)) // Input with pull-up/pull-down
| (0x08 << (4 * PIN_PAPER)) // Input with pull-up/pull-down
;
// Use pull-ups
GPIOA->BSRR = (1 << PIN_HEAD) | (1 << PIN_PAPER);
}
static void InitDataLines(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
TIM2->PSC = 719; // Each tick corresponds to ten microseconds
TIM2->ARR = 201; // 2 milliseconds
TIM2->CCR3 = 1;
TIM2->CCR4 = 1;
TIM2->CR1 = TIM_CR1_OPM; // One-pulse mode
TIM2->CCMR2 = TIM_CCMR2_OC3M // CH3 PWM mode 2
| TIM_CCMR2_OC4M // CH4 PWM mode 1
;
TIM2->CCER = 0;
TIM2->DIER = 0;
// Remap TIM2 lines
AFIO->MAPR |= AFIO_MAPR_TIM2_REMAP_FULLREMAP;
GPIOB->BRR = (1 << PIN_DST1) | (1 << PIN_DST2) | (1 << PIN_LATCH);
GPIOB->CRH = (GPIOB->CRH
& ~(0x0f << (4 * PIN_DST1 - 32))
& ~(0x0f << (4 * PIN_DST2 - 32))
& ~(0x0f << (4 * PIN_LATCH - 32))
& ~(0x0f << (4 * PIN_SCK - 32))
& ~(0x0f << (4 * PIN_DIN - 32)))
| (0x09 << (4 * PIN_DST1 - 32)) // Output in AF mode, max. 10 MHz
| (0x09 << (4 * PIN_DST2 - 32)) // Output in AF mode, max. 10 MHz
| (0x01 << (4 * PIN_LATCH - 32)) // Output, max. 10 MHz
| (0x09 << (4 * PIN_SCK - 32)) // Output in AF mode, max. 10 MHz
| (0x09 << (4 * PIN_DIN - 32)) // Output in AF mode, max. 10 MHz
;
SPI2->CR1 = SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SPE | SPI_CR1_MSTR
| SPI_CR1_BR_2;
SPI2->CR2 = SPI_CR2_TXDMAEN;
// SPI2_TX <-> DMA1_Channel5
DMA1_Channel5->CCR = DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_DIR;
DMA1_Channel5->CPAR = (uint32_t)&(SPI2->DR);
NVIC_EnableIRQ(DMA1_Channel5_IRQn);
}
static void InitThermistor(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
GPIOB->CRL = (GPIOB->CRL
& ~(0x0f << PIN_THERMISTOR))
| (0x00 << PIN_THERMISTOR) // Analog mode
;
// Enable ADC
ADC1->CR2 |= ADC_CR2_ADON;
for(volatile int i = 0; i < 100; i++);
// Calibrate ADC
ADC1->CR2 |= ADC_CR2_CAL;
while(ADC1->CR2 & ADC_CR2_CAL);
// Enable EOC interrupt
ADC1->CR1 = ADC_CR1_EOCIE;
NVIC_EnableIRQ(ADC1_2_IRQn);
// The thermistor is connected to ADC12_IN8 (PB0)
ADC1->SQR3 = (8 << ADC_SQR3_SQ1_Pos);
ADC1->SMPR2 = (7 << ADC_SMPR2_SMP8_Pos);
// Start conversion, continuous mode
ADC1->CR2 |= ADC_CR2_CONT;
ADC1->CR2 |= ADC_CR2_ADON;
}
static bool HasPaper(void)
{
return !(GPIOA->IDR & (1 << PIN_PAPER));
}
static bool HeadDown(void)
{
return !(GPIOA->IDR & (1 << PIN_HEAD));
}
void ActivateHead(int mask)
{
// Wait until timer has finished
while(TIM2->CR1 & TIM_CR1_CEN);
// Set activation pulse width
TIM2->ARR = (PulseWidth / 5) + 1;
TIM2->CCER = 0;
if(mask & 1)
{
TIM2->CCER |= TIM_CCER_CC4E;
}
if(mask & 2)
{
TIM2->CCER |= TIM_CCER_CC3E;
}
TIM2->CNT = 0;
TIM2->EGR = TIM_EGR_UG;
TIM2->CR1 |= TIM_CR1_CEN;
}
static void SendLine(uint8_t *line)
{
// Wait for previous transfer
while(DMA1_Channel5->CNDTR);
DMA1_Channel5->CCR &= ~DMA_CCR_EN;
DMA1_Channel5->CMAR = (uint32_t)line;
DMA1_Channel5->CNDTR = LTP1245_LINEWIDTH / 8;
DMA1_Channel5->CCR |= DMA_CCR_EN;
}
// Automatic paper loading
static State_t State_PaperLoad(void)
{
if(!HasPaper())
{
if(HeadDown())
{
Stepper_Delta = 0;
}
else if(Stepper_Delta < 1000)
{
Stepper_Delta = 1000;
}
}
else
{
if(Stepper_Delta == 0)
{
return (State_t){State_Idle};
}
}
return (State_t){State_PaperLoad};
}
// Actual printing
static State_t State_Printing(void)
{
if(!HeadDown() || !HasPaper())
{
return (State_t){State_Idle};
}
if(Stepper_Delta == 1)
{
ActivateHead(3);
}
else if(Stepper_Delta == 0)
{
SendLine(LTP1245_Buffer + CurrentBufferLine * LTP1245_LINEWIDTH / 8);
ActivateHead(3);
CurrentBufferLine++;
PrintLines--;
if(PrintLines > 0)
{
Stepper_Delta = 2;
}
else
{
// Print done
Stepper_Delta = 1;
return (State_t){State_Idle};
}
}
return (State_t){State_Printing};
}
static State_t State_PaperFeed(void)
{
if(!HasPaper())
{
return (State_t){State_Idle};
}
if(Stepper_Delta == 0)
{
return (State_t){State_Idle};
}
return (State_t){State_PaperFeed};
}
static State_t State_Idle(void)
{
if(!HasPaper())
{
return (State_t){State_PaperLoad};
}
return (State_t){State_Idle};
}
LTP1245_Result_t LTP1245_Print(uint8_t *data, int lines)
{
do
{
if(!HasPaper())
{
return LTP1245_NO_PAPER;
}
if(!HeadDown())
{
return LTP1245_HEAD_UP;
}
} while(State.fn != State_Idle);
if(lines > LTP1245_BUFFER_LINES)
{
lines = LTP1245_BUFFER_LINES;
}
memcpy(LTP1245_Buffer, data, lines * LTP1245_LINEWIDTH / 8);
PrintLines = lines;
CurrentBufferLine = 0;
State = (State_t){State_Printing};
return LTP1245_OK;
}
LTP1245_Result_t LTP1245_FeedPaper(int lines)
{
do
{
if(!HasPaper())
{
return LTP1245_NO_PAPER;
}
if(!HeadDown())
{
return LTP1245_HEAD_UP;
}
} while(State.fn != State_Idle);
Stepper_Delta = lines;
State = (State_t){State_PaperFeed};
return LTP1245_OK;
}
void LTP1245_Init(void)
{
InitDataLines();
InitSensors();
InitStepper();
InitThermistor();
}
void AdvanceStateMachine(void)
{
State = State.fn();
}
void TIM3_IRQHandler(void)
{
if(TIM3->SR & TIM_SR_UIF)
{
static int substep = 0;
static bool off;
const int TABLE[] = {0, 1, 3, 2};
const int GPIO_MASK = ((1 << PIN_STEPPER_AM) | (1 << PIN_STEPPER_AP)
| (1 << PIN_STEPPER_BM) | (1 << PIN_STEPPER_BP));
if(Stepper_Delta != 0)
{
off = false;
if(Stepper_Delta > 0)
{
substep++;
if(substep > 3)
substep = 0;
Stepper_Delta--;
}
else
{
substep--;
if(substep < 0)
substep = 3;
Stepper_Delta++;
}
GPIOA->ODR = (GPIOA->ODR & ~GPIO_MASK)
| ((TABLE[substep] & 1) ? (1 << PIN_STEPPER_AP)
: (1 << PIN_STEPPER_AM))
| ((TABLE[substep] & 2) ? (1 << PIN_STEPPER_BP)
: (1 << PIN_STEPPER_BM));
}
else
{
if(off)
{
GPIOA->ODR = (GPIOA->ODR & ~GPIO_MASK);
substep = 0;
}
off = true;
}
AdvanceStateMachine();
TIM3->SR &= ~TIM_SR_UIF;
}
}
void ADC1_2_IRQHandler(void)
{
const int READINGS[] =
{
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 375.54)), // -40 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 275.39)), // -35 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 204.55)), // -30 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 153.76)), // -25 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 116.89)), // -20 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 89.82)), // -15 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 69.71)), // -10 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 54.61)), // -5 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 43.17)), // 0 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 34.42)), // 5 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 27.66)), // 10 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 22.40)), // 15 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 18.27)), // 20 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 15.00)), // 25 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 12.40)), // 30 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 10.31)), // 35 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 8.63)), // 40 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 7.26)), // 45 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 6.14)), // 50 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 5.22)), // 55 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 4.46)), // 60 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 3.83)), // 65 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 3.30)), // 70 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 2.86)), // 75 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 2.48)), // 80 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 2.17)), // 85 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 1.90)), // 90 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 1.67)), // 95 °C
4095.0 * (1.0 - LTP1245_TH_REXT / (LTP1245_TH_REXT + 1.47)) // 100 °C
};
int adc = ADC1->DR;
// Find first temperature higher than the measured one
int lower_entry = 0;
for(int i = 1; i < sizeof(READINGS) / sizeof(READINGS[0]); i++)
{
if(adc >= READINGS[i])
{
lower_entry = i - 1;
break;
}
}
int higher_entry = lower_entry + 1;
int temp = lower_entry * 5 - 40; // Temperature in °C
// Interpolate linearly
if(higher_entry < sizeof(READINGS) / sizeof(READINGS[0]))
{
int diff = READINGS[lower_entry] - READINGS[higher_entry];
temp += (READINGS[lower_entry] - adc) * 5 / diff;
}
// Use the formula from section 3.6, adjusted for integer arithmetic and
// a pulse with in microseconds
PulseWidth = (285 * 178 - (int)(1000 * 178 * 0.003135) * (temp - 25))
/ (int)((5 * 1.4 - 2.9) * (5 * 1.4 - 2.9));
}
void DMA1_Channel5_IRQHandler(void)
{
DMA1->IFCR = DMA_IFCR_CTCIF5;
// Generate LATCH pulse
GPIOB->BSRR = (1 << PIN_LATCH);
for(volatile int i = 0; i < 1000; i++);
GPIOB->BRR = (1 << PIN_LATCH);
}