├── logs └── .gitignore ├── schematic-T962.pdf ├── src ├── images │ ├── stop-18x64.bmp │ ├── f3edit-18x16.bmp │ ├── graph-128x64.bmp │ ├── UEoSlogo-128x64.bmp │ ├── editprofile-18x64.bmp │ └── selectprofile-18x64.bmp ├── systemfan.h ├── adc.h ├── rtc.h ├── i2c.h ├── max31855.h ├── serial.h ├── version.h ├── eeprom.h ├── keypad.h ├── onewire.h ├── buzzer.h ├── io.h ├── sensor.h ├── setup.h ├── nvstorage.h ├── sc18is602b.h ├── vic.h ├── lcd.h ├── circbuffer.h ├── import.s ├── reflow.h ├── reflow_profiles.h ├── rtc.c ├── sched.h ├── circbuffer.c ├── buzzer.c ├── crp.c ├── sc18is602b.c ├── adc.c ├── nvstorage.c ├── setup.c ├── vic.c ├── smallfont.h ├── i2c.c ├── keypad.c ├── eeprom.c ├── systemfan.c ├── PID_v1.h ├── sched.c ├── t962.h ├── max31855.c ├── serial.c ├── sensor.c ├── io.c ├── reflow_profiles.c ├── PID_v1.c ├── reflow.c ├── lcd.c ├── onewire.c ├── cr_startup_lpc21.s └── main.c ├── .gitignore ├── COMPILING.md ├── .travis.yml ├── .project ├── .settings └── language.settings.xml ├── T-962-controller.ld ├── Makefile ├── README.md └── serial-control.py /logs/.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.png 3 | *.pdf 4 | -------------------------------------------------------------------------------- /schematic-T962.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/HEAD/schematic-T962.pdf -------------------------------------------------------------------------------- /src/images/stop-18x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/HEAD/src/images/stop-18x64.bmp -------------------------------------------------------------------------------- /src/images/f3edit-18x16.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/HEAD/src/images/f3edit-18x16.bmp -------------------------------------------------------------------------------- /src/images/graph-128x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/HEAD/src/images/graph-128x64.bmp -------------------------------------------------------------------------------- /src/images/UEoSlogo-128x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/HEAD/src/images/UEoSlogo-128x64.bmp -------------------------------------------------------------------------------- /src/images/editprofile-18x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/HEAD/src/images/editprofile-18x64.bmp -------------------------------------------------------------------------------- /src/systemfan.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEMFAN_H_ 2 | #define SYSTEMFAN_H_ 3 | 4 | void SystemFan_Init(void); 5 | 6 | #endif /* SYSTEMFAN_H_ */ 7 | -------------------------------------------------------------------------------- /src/images/selectprofile-18x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/HEAD/src/images/selectprofile-18x64.bmp -------------------------------------------------------------------------------- /src/adc.h: -------------------------------------------------------------------------------- 1 | #ifndef ADC_H_ 2 | #define ADC_H_ 3 | 4 | void ADC_Init(void); 5 | int32_t ADC_Read(uint32_t chnum); 6 | 7 | #endif /* ADC_H_ */ 8 | -------------------------------------------------------------------------------- /src/rtc.h: -------------------------------------------------------------------------------- 1 | #ifndef RTC_H_ 2 | #define RTC_H_ 3 | 4 | void RTC_Init(void); 5 | uint32_t RTC_Read(void); 6 | void RTC_Zero(void); 7 | 8 | #endif /* RTC_H_ */ 9 | -------------------------------------------------------------------------------- /src/i2c.h: -------------------------------------------------------------------------------- 1 | #ifndef I2C_H_ 2 | #define I2C_H_ 3 | 4 | void I2C_Init(void); 5 | int32_t I2C_Xfer(uint8_t slaveaddr, uint8_t* theBuffer, uint32_t theLength, uint8_t trailingStop); 6 | 7 | #endif /* I2C_H_ */ 8 | -------------------------------------------------------------------------------- /src/max31855.h: -------------------------------------------------------------------------------- 1 | #ifndef MAX31855_H_ 2 | #define MAX31855_H_ 3 | 4 | uint32_t SPI_TC_Init(void); 5 | int SPI_IsTCPresent(uint8_t tcid); 6 | float SPI_GetTCReading(uint8_t tcid); 7 | float SPI_GetTCColdReading(uint8_t tcid); 8 | 9 | #endif /* MAX31855_H_ */ 10 | -------------------------------------------------------------------------------- /src/serial.h: -------------------------------------------------------------------------------- 1 | #ifndef SERIAL_H_ 2 | #define SERIAL_H_ 3 | 4 | void Serial_Init(void); 5 | 6 | //non-blocking read 7 | char uart_readc(void); 8 | 9 | //non-blocking check 10 | int uart_isrxready(void); 11 | 12 | int uart_readline(char* buffer, int max_len); 13 | 14 | #endif /* SERIAL_H_ */ 15 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * version.h 3 | * 4 | * Created on: 9 Dec 2014 5 | * Author: 6 | */ 7 | 8 | #ifndef VERSION_H_ 9 | #define VERSION_H_ 10 | 11 | /* Returns the GIT version */ 12 | const char* Version_GetGitVersion(void); 13 | 14 | #endif /* VERSION_H_ */ 15 | -------------------------------------------------------------------------------- /src/eeprom.h: -------------------------------------------------------------------------------- 1 | #ifndef EEPROM_H_ 2 | #define EEPROM_H_ 3 | 4 | void EEPROM_Init(void); 5 | void EEPROM_Dump(void); 6 | int32_t EEPROM_Read(uint8_t* dest, uint32_t startpos, uint32_t len); 7 | int32_t EEPROM_Write(uint32_t startdestpos, uint8_t* src, uint32_t len); 8 | 9 | #endif /* EEPROM_H_ */ 10 | -------------------------------------------------------------------------------- /src/keypad.h: -------------------------------------------------------------------------------- 1 | #ifndef KEYPAD_H_ 2 | #define KEYPAD_H_ 3 | 4 | #define KEY_F1 (1<<0) 5 | #define KEY_F2 (1<<1) 6 | #define KEY_F3 (1<<2) 7 | #define KEY_F4 (1<<3) 8 | #define KEY_S (1<<4) 9 | #define KEY_ANY (KEY_F1 | KEY_F2 | KEY_F3 | KEY_F4 | KEY_S) 10 | 11 | uint32_t Keypad_Get(void); 12 | void Keypad_Init(void); 13 | 14 | #endif /* KEYPAD_H_ */ 15 | -------------------------------------------------------------------------------- /src/onewire.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEWIRE_H_ 2 | #define ONEWIRE_H_ 3 | 4 | uint32_t OneWire_Init( void ); 5 | int OneWire_PerformTemperatureConversion(void); 6 | float OneWire_GetTempSensorReading(void); 7 | int OneWire_IsTCPresent(uint8_t tcid); 8 | float OneWire_GetTCReading(uint8_t tcid); 9 | float OneWire_GetTCColdReading(uint8_t tcid); 10 | 11 | #endif /* ONEWIRE_H_ */ 12 | -------------------------------------------------------------------------------- /src/buzzer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUZZER_H_ 2 | #define BUZZER_H_ 3 | 4 | #include 5 | 6 | typedef enum eBuzzFreq { 7 | BUZZ_NONE = 0, 8 | BUZZ_2KHZ = 3, 9 | BUZZ_1KHZ = 4, 10 | BUZZ_500HZ = 5, 11 | BUZZ_250HZ = 6 12 | } BuzzFreq_t; 13 | 14 | void Buzzer_Init(void); 15 | void Buzzer_Beep(BuzzFreq_t freq, uint8_t volume, int32_t ticks); 16 | 17 | #endif /* BUZZER_H_ */ 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | Debug/ 32 | Release/ 33 | build 34 | lpc21isp 35 | -------------------------------------------------------------------------------- /src/io.h: -------------------------------------------------------------------------------- 1 | #ifndef IO_H_ 2 | #define IO_H_ 3 | 4 | #define IAP_READ_PART (54) 5 | #define IAP_REINVOKE_ISP (57) 6 | #define PART_REV_ADDR (0x0007D070) 7 | 8 | typedef struct { 9 | const char* name; 10 | const uint32_t id; 11 | } partmapStruct; 12 | 13 | void Set_Heater(uint8_t enable); 14 | void Set_Fan(uint8_t enable); 15 | void IO_InitWatchdog(void); 16 | void IO_PrintResetReason(void); 17 | int IO_Partinfo(char* buf, int n, char* format); 18 | void IO_JumpBootloader(void); 19 | void IO_Init(void); 20 | #endif /* IO_H_ */ 21 | -------------------------------------------------------------------------------- /src/sensor.h: -------------------------------------------------------------------------------- 1 | #ifndef SENSORS_H_ 2 | #define SENSORS_H_ 3 | 4 | typedef enum eTempSensor { 5 | TC_COLD_JUNCTION=0, 6 | TC_AVERAGE, 7 | TC_LEFT, 8 | TC_RIGHT, 9 | TC_EXTRA1, 10 | TC_EXTRA2, 11 | TC_NUM_ITEMS 12 | } TempSensor_t; 13 | 14 | 15 | void Sensor_ValidateNV(void); 16 | void Sensor_DoConversion(void); 17 | 18 | uint8_t Sensor_ColdjunctionPresent(void); 19 | 20 | float Sensor_GetTemp(TempSensor_t sensor); 21 | uint8_t Sensor_IsValid(TempSensor_t sensor); 22 | 23 | void Sensor_ListAll(void); 24 | 25 | #endif /* SENSORS_H_ */ 26 | -------------------------------------------------------------------------------- /COMPILING.md: -------------------------------------------------------------------------------- 1 | # Compiling without LPCXpresso 2 | 3 | We need the `gcc-arm-none-eabi` compiler, for example in Ubuntu: 4 | 5 | ``` 6 | sudo add-apt-repository -y ppa:terry.guo/gcc-arm-embedded 7 | sudo apt-get update 8 | sudo apt-get install gcc-arm-none-eabi 9 | ``` 10 | 11 | And then run 12 | 13 | ``` 14 | make 15 | ``` 16 | 17 | ## Flashing in Linux 18 | 19 | The makefile has a target to download and build the [lpc21isp utility from sourceforge](http://sourceforge.net/projects/lpc21isp/). Just run 20 | 21 | ``` 22 | make flash 23 | ``` 24 | and it wil be downloaded and compiled for you. 25 | -------------------------------------------------------------------------------- /src/setup.h: -------------------------------------------------------------------------------- 1 | #ifndef SETUP_H_ 2 | #define SETUP_H_ 3 | 4 | typedef struct { 5 | const char* formatstr; 6 | const NVItem_t nvval; 7 | const uint8_t minval; 8 | const uint8_t maxval; 9 | const int8_t offset; 10 | const float multiplier; 11 | } setupMenuStruct; 12 | 13 | int Setup_getNumItems(void); 14 | float Setup_getValue(int item); 15 | void Setup_setRealValue(int item, float value); 16 | void Setup_setValue(int item, int value); 17 | void Setup_increaseValue(int item, int amount); 18 | void Setup_decreaseValue(int item, int amount); 19 | void Setup_printFormattedValue(int item); 20 | int Setup_snprintFormattedValue(char* buf, int n, int item); 21 | 22 | #endif /* SETUP_H_ */ 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | before_install: 4 | - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa 5 | - sudo apt-get update -qq 6 | 7 | install: 8 | - sudo apt-get install gcc-arm-embedded 9 | 10 | script: 11 | - make all 12 | 13 | notifications: 14 | email: false 15 | 16 | git: 17 | depth: 10 18 | 19 | deploy: 20 | provider: releases 21 | api-key: 22 | secure: RYGBzgLnsjZ8JGjuZhor+lRVTvPYpm2QVccDyLf5Mr9cPhoOpULa6RhNkzdRAltJxoTlA7AS6l8ZpHIKyjMysFKC5pu8VJHWNdGvnrVURs4ML0qXUCPw2epDztTIdH2tMbkKI86Ov8EAmdvvR0GakEvkzC2z+rwPzGK95k3ovX4= 23 | file: "build/T-962-controller.hex" 24 | skip_cleanup: true 25 | on: 26 | tags: true 27 | all_branches: true 28 | repo: UnifiedEngineering/T-962-improvements 29 | -------------------------------------------------------------------------------- /src/nvstorage.h: -------------------------------------------------------------------------------- 1 | #ifndef NVSTORAGE_H_ 2 | #define NVSTORAGE_H_ 3 | 4 | // Right now we only have 28 bytes in total for NV storage, and 3 of those bytes 5 | // are used up for housekeeping so max 25 items will fit 6 | // Only append to the end of this list to avoid backwards incompatibilities 7 | typedef enum eNVItem { 8 | REFLOW_BEEP_DONE_LEN=0, 9 | REFLOW_PROFILE=1, 10 | TC_LEFT_GAIN, 11 | TC_LEFT_OFFSET, 12 | TC_RIGHT_GAIN, 13 | TC_RIGHT_OFFSET, 14 | REFLOW_MIN_FAN_SPEED, 15 | REFLOW_BAKE_SETPOINT_H, 16 | REFLOW_BAKE_SETPOINT_L, 17 | NVITEM_NUM_ITEMS // Last value 18 | } NVItem_t; 19 | 20 | void NV_Init(void); 21 | uint8_t NV_GetConfig(NVItem_t item); 22 | void NV_SetConfig(NVItem_t item, uint8_t value); 23 | int32_t NV_Work(void); 24 | 25 | #endif /* NVSTORAGE_H_ */ 26 | -------------------------------------------------------------------------------- /src/sc18is602b.h: -------------------------------------------------------------------------------- 1 | #ifndef SC18IS602B_H_ 2 | #define SC18IS602B_H_ 3 | 4 | typedef enum eSPIclk { 5 | SPICLK_1843KHZ=0, 6 | SPICLK_461KHZ=1, 7 | SPICLK_115KHZ=2, 8 | SPICLK_58KHZ=3, 9 | } SPIclk_t; 10 | 11 | typedef enum eSPImode { 12 | SPIMODE_0=(0<<2), 13 | SPIMODE_1=(1<<2), 14 | SPIMODE_2=(2<<2), 15 | SPIMODE_3=(3<<2), 16 | } SPImode_t; 17 | 18 | typedef enum eSPIorder { 19 | SPIORDER_MSBFIRST=(0<<5), 20 | SPIORDER_LSBFIRST=(1<<5), 21 | } SPIorder_t; 22 | 23 | typedef struct __attribute__ ((__packed__)) { 24 | uint8_t ssmask; 25 | uint8_t data[16]; // Max 16 bytes at the moment 26 | uint8_t len; 27 | } SPIxfer_t; 28 | 29 | int32_t SC18IS602B_Init( SPIclk_t clk, SPImode_t mode, SPIorder_t order ); 30 | int32_t SC18IS602B_SPI_Xfer( SPIxfer_t* item ); 31 | 32 | #endif /* SC18IS602B_H_ */ 33 | -------------------------------------------------------------------------------- /src/vic.h: -------------------------------------------------------------------------------- 1 | #ifndef VIC_H_ 2 | #define VIC_H_ 3 | 4 | typedef enum eVICInt { 5 | VIC_WDT=0, 6 | VIC_SWI, 7 | VIC_ICERX, 8 | VIC_ICETX, 9 | VIC_TIMER0, 10 | VIC_TIMER1, 11 | VIC_UART0, 12 | VIC_UART1, 13 | VIC_PWM0, 14 | VIC_I2C0, 15 | VIC_SPI0, 16 | VIC_SPI1, 17 | VIC_PLL, 18 | VIC_RTC, 19 | VIC_EINT0, 20 | VIC_EINT1, 21 | VIC_EINT2, 22 | VIC_EINT3, 23 | VIC_ADC0, 24 | VIC_I2C1, 25 | VIC_BOD, 26 | VIC_ADC1, 27 | VICINT_NUM_ITEMS // Last value 28 | } VICInt_t; 29 | 30 | void VIC_Init( void ); 31 | uint32_t VIC_IsIRQDisabled( void ); 32 | uint32_t VIC_DisableIRQ( void ); 33 | void VIC_RestoreIRQ( uint32_t mask ); 34 | int32_t VIC_RegisterHandler( VICInt_t num, void* ptr ); 35 | int32_t VIC_EnableHandler( VICInt_t num ); 36 | int32_t VIC_DisableHandler( VICInt_t num ); 37 | 38 | #endif /* VIC_H_ */ 39 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | T-962-Controller 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.cdt.managedbuilder.core.genmakebuilder 10 | clean,full,incremental, 11 | 12 | 13 | 14 | 15 | org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder 16 | full,incremental, 17 | 18 | 19 | 20 | 21 | 22 | org.eclipse.cdt.core.cnature 23 | org.eclipse.cdt.managedbuilder.core.managedBuildNature 24 | org.eclipse.cdt.managedbuilder.core.ScannerConfigNature 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/lcd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lcd.h 3 | * 4 | * Created on: 14 nov 2014 5 | * Author: wjo 6 | */ 7 | 8 | #ifndef LCD_H_ 9 | #define LCD_H_ 10 | 11 | #define FB_HEIGHT (64) 12 | #define FB_WIDTH (128) 13 | 14 | #define FONT6X6 (0) 15 | #define INVERT (0x80) 16 | 17 | #define LCD_CENTER (64) 18 | #define LCD_ALIGN_CENTER(x) (LCD_CENTER - (x * 3)) 19 | #define LCD_ALIGN_RIGHT(x) (127 - (x * 6)) 20 | 21 | void charoutsmall(uint8_t theChar, uint8_t X, uint8_t Y); 22 | void LCD_disp_str(uint8_t* theStr, uint8_t theLen, uint8_t startx, uint8_t y, uint8_t theFormat); 23 | void LCD_MultiLineH(uint8_t startx, uint8_t endx, uint64_t ymask); 24 | uint8_t LCD_BMPDisplay(uint8_t* thebmp, uint8_t xoffset, uint8_t yoffset); 25 | void LCD_SetPixel(uint8_t x, uint8_t y); 26 | void LCD_SetBacklight(uint8_t backlight); 27 | void LCD_Init(void); 28 | void LCD_FB_Clear(void); 29 | void LCD_FB_Update(void); 30 | 31 | #endif /* LCD_H_ */ 32 | -------------------------------------------------------------------------------- /src/circbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef CIRCBUFFER_H_ 2 | #define CIRCBUFFER_H_ 3 | 4 | /* Size of buffer. 5 | * The putchar() function used here will block when the buffer is full (buffer 6 | * is drained in interrupt), so if you want to continously write lots of data 7 | * increase the buffer size. Change putchar() to non-blocking and read the 8 | * 'dropped' parameter in the buffer structure to determine how your buffer 9 | * size is doing. 10 | */ 11 | #define CIRCBUFSIZE 256 12 | 13 | typedef struct { 14 | volatile unsigned int head; 15 | volatile unsigned int tail; 16 | volatile unsigned int dropped; 17 | char buf[CIRCBUFSIZE]; 18 | } tcirc_buf; 19 | 20 | void init_circ_buf(tcirc_buf * cbuf); 21 | void add_to_circ_buf(tcirc_buf *cbuf, char ch, int block); 22 | int circ_buf_has_char(tcirc_buf *cbuf); 23 | char get_from_circ_buf(tcirc_buf *cbuf); 24 | unsigned int circ_buf_count(tcirc_buf *cbuf); 25 | 26 | #endif /* CIRCBUFFER_H_ */ 27 | -------------------------------------------------------------------------------- /src/import.s: -------------------------------------------------------------------------------- 1 | .section ".text" 2 | .global logobmp 3 | .global logobmpsize 4 | logobmp: 5 | .incbin "../src/images/UEoSlogo-128x64.bmp" 6 | logobmpsize: 7 | .word .-logobmp 8 | 9 | .global graphbmp 10 | .global graphbmpsize 11 | graphbmp: 12 | .incbin "../src/images/graph-128x64.bmp" 13 | graphbmpsize: 14 | .word .-graphbmp 15 | 16 | .global stopbmp 17 | .global stopbmpsize 18 | stopbmp: 19 | .incbin "../src/images/stop-18x64.bmp" 20 | stopbmpsize: 21 | .word .-stopbmp 22 | 23 | .global selectbmp 24 | .global selectbmpsize 25 | selectbmp: 26 | .incbin "../src/images/selectprofile-18x64.bmp" 27 | selectbmpsize: 28 | .word .-selectbmp 29 | 30 | .global editbmp 31 | .global editbmpsize 32 | editbmp: 33 | .incbin "../src/images/editprofile-18x64.bmp" 34 | editbmpsize: 35 | .word .-editbmp 36 | 37 | .global f3editbmp 38 | .global f3editbmpsize 39 | f3editbmp: 40 | .incbin "../src/images/f3edit-18x16.bmp" 41 | f3editbmpsize: 42 | .word .-f3editbmp 43 | -------------------------------------------------------------------------------- /src/reflow.h: -------------------------------------------------------------------------------- 1 | #ifndef REFLOW_H_ 2 | #define REFLOW_H_ 3 | 4 | typedef enum eReflowMode { 5 | REFLOW_INITIAL=0, 6 | REFLOW_STANDBY, 7 | REFLOW_BAKE, 8 | REFLOW_REFLOW, 9 | REFLOW_STANDBYFAN 10 | } ReflowMode_t; 11 | 12 | #define SETPOINT_MIN (30) 13 | #define SETPOINT_MAX (300) 14 | #define SETPOINT_DEFAULT (30) 15 | 16 | // 36 hours max timer 17 | #define BAKE_TIMER_MAX (60 * 60 * 36) 18 | 19 | void Reflow_Init(void); 20 | void Reflow_SetMode(ReflowMode_t themode); 21 | void Reflow_SetSetpoint(uint16_t thesetpoint); 22 | void Reflow_LoadSetpoint(void); 23 | int16_t Reflow_GetActualTemp(void); 24 | uint8_t Reflow_IsDone(void); 25 | int Reflow_IsPreheating(void); 26 | uint16_t Reflow_GetSetpoint(void); 27 | void Reflow_SetBakeTimer(int seconds); 28 | int Reflow_GetTimeLeft(void); 29 | int32_t Reflow_Run(uint32_t thetime, float meastemp, uint8_t* pheat, uint8_t* pfan, int32_t manualsetpoint); 30 | void Reflow_ToggleStandbyLogging(void); 31 | 32 | #endif /* REFLOW_H_ */ 33 | -------------------------------------------------------------------------------- /src/reflow_profiles.h: -------------------------------------------------------------------------------- 1 | #ifndef REFLOW_PROFILES_H_ 2 | #define REFLOW_PROFILES_H_ 3 | 4 | // Number of temperature settings in a reflow profile 5 | #define NUMPROFILETEMPS (48) 6 | 7 | #define YAXIS (57) 8 | #define XAXIS (12) 9 | 10 | typedef struct { 11 | const char* name; 12 | const uint16_t temperatures[NUMPROFILETEMPS]; 13 | } profile; 14 | 15 | typedef struct { 16 | const char* name; 17 | uint16_t temperatures[NUMPROFILETEMPS]; 18 | } ramprofile; 19 | 20 | void Reflow_LoadCustomProfiles(void); 21 | void Reflow_ValidateNV(void); 22 | void Reflow_PlotProfile(int highlight); 23 | 24 | int Reflow_GetProfileIdx(void); 25 | int Reflow_SelectProfileIdx(int idx); 26 | int Reflow_SelectEEProfileIdx(int idx); 27 | 28 | int Reflow_GetEEProfileIdx(void); 29 | int Reflow_SaveEEProfile(void); 30 | void Reflow_ListProfiles(void); 31 | const char* Reflow_GetProfileName(void); 32 | uint16_t Reflow_GetSetpointAtIdx(uint8_t idx); 33 | void Reflow_SetSetpointAtIdx(uint8_t idx, uint16_t value); 34 | void Reflow_DumpProfile(int profile); 35 | 36 | #endif /* REFLOW_PROFILES_H */ 37 | -------------------------------------------------------------------------------- /src/rtc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rtc.c - RTC interface for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include "t962.h" 24 | #include "rtc.h" 25 | 26 | #define RTCINTDIV ((PCLKFREQ / 32768)-1) 27 | #define RTCFRACDIV (PCLKFREQ-((RTCINTDIV+1)*32768)) 28 | 29 | void RTC_Init(void) { 30 | PREINT = RTCINTDIV; 31 | PREFRAC = RTCFRACDIV; 32 | CCR = (1<<0); // Enable RTC 33 | RTC_Zero(); 34 | } 35 | 36 | uint32_t RTC_Read(void) { 37 | uint32_t retval,tmp; 38 | tmp = CTIME0; 39 | retval = tmp & 0x3f; // Seconds 40 | tmp >>= 8; 41 | retval += ((tmp&0x3f)*60); // Minutes 42 | tmp >>= 8; 43 | retval += ((tmp&0x1f)*3600); // Hours 44 | return retval; 45 | } 46 | 47 | void RTC_Zero(void) { 48 | CCR |= (1<<1); 49 | SEC = MIN = HOUR = 0; // Maybe need day for bake as well? 50 | CCR &= ~(1<<1); 51 | } 52 | -------------------------------------------------------------------------------- /src/sched.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHED_H_ 2 | #define SCHED_H_ 3 | 4 | #include "t962.h" // For frequency constant 5 | 6 | // This is the prescaler used to initialize the timer so we run a minimum of 6 times per us 7 | // (for 55.296MHz 7 results in 7.899 ticks per us) 8 | #define TIMER_PRESCALER (7) 9 | 10 | //#define TICKS_PER_SECOND (PCLKFREQ / TIMER_PRESCALER) 11 | //#define TICKS_PER_MS (PCLKFREQ / (TIMER_PRESCALER * 1000)) 12 | //#define TICKS_PER_US (PCLKFREQ / (TIMER_PRESCALER * 1000 * 1000)) 13 | #define TICKS_SECS(x) ((uint32_t)(((((double)x * (double)PCLKFREQ) / ((double)TIMER_PRESCALER))) + 0.5f)) 14 | #define TICKS_MS(x) ((uint32_t)(((((double)x * (double)PCLKFREQ) / ((double)TIMER_PRESCALER * 1000.0f))) + 0.5f)) 15 | #define TICKS_US(x) ((uint32_t)(((((double)x * (double)PCLKFREQ) / ((double)TIMER_PRESCALER * 1000.0f * 1000.0f))) + 0.5f)) 16 | #define TICKS_NS(x) ((uint32_t)(((((double)x * (double)PCLKFREQ) / ((double)TIMER_PRESCALER * 1000.0f * 1000.0f * 1000.0f))) + 0.5f)) 17 | 18 | typedef int32_t (*SchedCall_t)(void); 19 | typedef enum eTask { 20 | SLEEP_WORK=0, 21 | BUZZER_WORK, 22 | KEYPAD_WORK, 23 | SYSFANPWM_WORK, 24 | MAIN_WORK, 25 | ADC_WORK, 26 | ONEWIRE_WORK, 27 | SPI_TC_WORK, 28 | UI_WORK, 29 | REFLOW_WORK, 30 | SYSFANSENSE_WORK, 31 | NV_WORK, 32 | SCHED_NUM_ITEMS // Last value 33 | } Task_t; 34 | 35 | void Sched_Init(void); 36 | uint32_t Sched_GetTick(void); 37 | void Sched_SetState(Task_t tasknum, uint8_t enable, int32_t future); 38 | uint8_t Sched_IsOverride(void); 39 | void Sched_SetWorkfunc(Task_t tasknum, SchedCall_t func); 40 | int32_t Sched_Do(uint32_t fastforward); 41 | void BusyWait( uint32_t numticks ); 42 | 43 | #endif /* SCHED_H_ */ 44 | -------------------------------------------------------------------------------- /src/circbuffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "circbuffer.h" 3 | 4 | /**** Circular Buffer used by UART ****/ 5 | 6 | void init_circ_buf(tcirc_buf *cbuf) { 7 | cbuf->head = cbuf->tail = 0; 8 | cbuf->dropped = 0; 9 | } 10 | 11 | void add_to_circ_buf(tcirc_buf *cbuf, char ch, int block) { 12 | // Add char to buffer 13 | unsigned int newhead = cbuf->head; 14 | newhead++; 15 | if (newhead >= CIRCBUFSIZE) { 16 | newhead = 0; 17 | } 18 | while (newhead == cbuf->tail) { 19 | if (!block) { 20 | cbuf->dropped++; 21 | return; 22 | } 23 | 24 | // If blocking, this just keeps looping. Due to interrupt-driven 25 | // system the buffer might eventually have space in it, however 26 | // if this is called when interrupts are disabled it will stall 27 | // the system, so the caller is cautioned not to fsck it up. 28 | } 29 | 30 | cbuf->buf[cbuf->head] = ch; 31 | cbuf->head = newhead; 32 | } 33 | 34 | 35 | char get_from_circ_buf(tcirc_buf *cbuf) { 36 | // Get char from buffer 37 | // Be sure to check first that there is a char in buffer 38 | unsigned int newtail = cbuf->tail; 39 | uint8_t retval = cbuf->buf[newtail]; 40 | 41 | if (newtail == cbuf->head) { 42 | return 0xFF; 43 | } 44 | 45 | newtail++; 46 | if (newtail >= CIRCBUFSIZE) { 47 | // Rollover 48 | newtail = 0; 49 | } 50 | cbuf->tail = newtail; 51 | 52 | return retval; 53 | } 54 | 55 | 56 | int circ_buf_has_char(tcirc_buf *cbuf) { 57 | // Return true if buffer empty 58 | unsigned int head = cbuf->head; 59 | return (head != cbuf->tail); 60 | } 61 | 62 | unsigned int circ_buf_count(tcirc_buf *cbuf) { 63 | int count; 64 | 65 | count = cbuf->head; 66 | count -= cbuf->tail; 67 | if (count < 0) { 68 | count += CIRCBUFSIZE; 69 | } 70 | return (unsigned int)count; 71 | } 72 | -------------------------------------------------------------------------------- /src/buzzer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * buzzer.c - Simple buzzer interface for T-962 reflow controller 3 | * 4 | * Copyright (C) 2011,2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include "buzzer.h" 22 | #include 23 | #include "sched.h" 24 | 25 | static BuzzFreq_t requested_buzz_freq; 26 | static uint8_t requested_buzz_volume; 27 | static int32_t requested_buzz_length; 28 | 29 | /* 30 | * The buzzer is hooked up to PWM5 output, but contains an oscillator of 31 | * its own so volume and freq are ignored for now. :( 32 | */ 33 | 34 | static int32_t Buzzer_Work(void) { 35 | if (requested_buzz_freq != BUZZ_NONE) { 36 | FIO0SET = (1 << 21); 37 | requested_buzz_freq = BUZZ_NONE; 38 | } else { 39 | // Don't schedule until next beep is requested 40 | requested_buzz_length = -1; 41 | FIO0CLR = (1 << 21); 42 | } 43 | return requested_buzz_length; 44 | } 45 | 46 | void Buzzer_Init(void) { 47 | printf("\n%s ", __FUNCTION__); 48 | 49 | Sched_SetWorkfunc(BUZZER_WORK, Buzzer_Work); 50 | } 51 | 52 | void Buzzer_Beep(BuzzFreq_t freq, uint8_t volume, int32_t ticks) { 53 | if (ticks > 0 || freq == BUZZ_NONE) { 54 | requested_buzz_freq = freq; 55 | requested_buzz_volume = volume; 56 | requested_buzz_length = ticks; 57 | Sched_SetState(BUZZER_WORK, 2, 0); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.settings/language.settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/crp.c: -------------------------------------------------------------------------------- 1 | //***************************************************************************** 2 | // crp.c 3 | // 4 | // Source file to create CRP word expected by LPCXpresso IDE linker 5 | //***************************************************************************** 6 | // 7 | // Copyright(C) NXP Semiconductors, 2013 8 | // All rights reserved. 9 | // 10 | // Software that is described herein is for illustrative purposes only 11 | // which provides customers with programming information regarding the 12 | // LPC products. This software is supplied "AS IS" without any warranties of 13 | // any kind, and NXP Semiconductors and its licensor disclaim any and 14 | // all warranties, express or implied, including all implied warranties of 15 | // merchantability, fitness for a particular purpose and non-infringement of 16 | // intellectual property rights. NXP Semiconductors assumes no responsibility 17 | // or liability for the use of the software, conveys no license or rights under any 18 | // patent, copyright, mask work right, or any other intellectual property rights in 19 | // or to any products. NXP Semiconductors reserves the right to make changes 20 | // in the software without notification. NXP Semiconductors also makes no 21 | // representation or warranty that such application will be suitable for the 22 | // specified use without further testing or modification. 23 | // 24 | // Permission to use, copy, modify, and distribute this software and its 25 | // documentation is hereby granted, under NXP Semiconductors' and its 26 | // licensor's relevant copyrights in the software, without fee, provided that it 27 | // is used in conjunction with NXP Semiconductors microcontrollers. This 28 | // copyright, permission, and disclaimer notice must appear in all copies of 29 | // this code. 30 | //***************************************************************************** 31 | 32 | #if defined (__CODE_RED) 33 | #include 34 | // Variable to store CRP value in. Will be placed automatically 35 | // by the linker when "Enable Code Read Protect" selected. 36 | // See crp.h header for more information 37 | __CRP const unsigned int CRP_WORD = CRP_NO_CRP ; 38 | #endif 39 | -------------------------------------------------------------------------------- /src/sc18is602b.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sc18is602b.c - I2C to SPI bridge handling for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "i2c.h" 25 | #include "sc18is602b.h" 26 | 27 | #define SCADDR (0x28<<1) 28 | static uint8_t scaddr; 29 | 30 | int32_t SC18IS602B_Init( SPIclk_t clk, SPImode_t mode, SPIorder_t order ) { 31 | uint8_t function[2]; 32 | int32_t retval; 33 | printf("\n%s ",__FUNCTION__); 34 | for( uint8_t scan=0; scan<8; scan++ ) { 35 | function[0] = 0xf0; 36 | function[1] = clk | mode | order; 37 | retval = I2C_Xfer(SCADDR | (scan<<1), function, sizeof(function), 1); // Attempt to initialize I2C to SPI bridge 38 | if (retval==0) { 39 | scaddr = SCADDR | (scan<<1); 40 | break; 41 | } 42 | } 43 | if( retval == 0 ) { 44 | printf( "- Done (addr 0x%02x)", scaddr>>1); 45 | } else { 46 | printf( "- No chip found"); 47 | } 48 | return retval; 49 | } 50 | 51 | int32_t SC18IS602B_SPI_Xfer( SPIxfer_t* item ) { 52 | int32_t retval; 53 | if( item->len > (sizeof(SPIxfer_t) - 2) ) { 54 | printf("\n%s: Invalid length!",__FUNCTION__); 55 | return -1; 56 | } 57 | retval = I2C_Xfer(scaddr, (uint8_t*)item, item->len + 1, 1); // Initialize transfer, ssmask + data 58 | if( retval == 0 ) { 59 | // TODO: There should be a timeout here 60 | do { 61 | retval = I2C_Xfer(scaddr + 1, (uint8_t*)item->data, item->len, 1); // Initialize read transfer, data only 62 | } while( retval != 0 ); // Wait for chip to be done with transaction 63 | } 64 | return retval; 65 | } 66 | -------------------------------------------------------------------------------- /src/adc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * adc.c - AD converter interface for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include "adc.h" 24 | #include "t962.h" 25 | #include "sched.h" 26 | 27 | #define NUM_ADCH (2) 28 | #define OVERSAMPLING_BITS (4) // This is n (how many additional bits to supply) 29 | #define NUM_ACCUM (256) // This needs to be 4 ^ n 30 | #define ACCUM_MASK (NUM_ACCUM-1) 31 | 32 | static uint32_t ADres[NUM_ADCH]; 33 | static uint16_t ADidx[NUM_ADCH]; 34 | static uint16_t ADaccum[NUM_ADCH][NUM_ACCUM]; 35 | 36 | static void ADC_Accum(uint32_t chnum, uint32_t value) { 37 | uint16_t temp; 38 | chnum--; 39 | uint8_t idx = ADidx[chnum]; 40 | 41 | // Done 42 | if (value & (1 << 31)) { 43 | temp = (value >> 6) & 0x3ff; 44 | ADres[chnum] -= ADaccum[chnum][idx]; 45 | ADaccum[chnum][idx] = temp; 46 | ADres[chnum] += temp; 47 | idx++; 48 | idx &= ACCUM_MASK; 49 | ADidx[chnum] = idx; 50 | } 51 | } 52 | 53 | static int32_t ADC_Work(void) { 54 | ADC_Accum( 1, AD0DR1); 55 | ADC_Accum( 2, AD0DR2); 56 | return TICKS_US( 100 ); // Run 10000 times per second 57 | } 58 | 59 | void ADC_Init( void ) { 60 | printf("\n%s called", __FUNCTION__); 61 | Sched_SetWorkfunc(ADC_WORK, ADC_Work); 62 | 63 | // 1MHz adc clock, enabling ch1 and 2 64 | AD0CR = (1 << 21) | (((uint8_t)(PCLKFREQ / 1000000)) << 8) | 0x06; 65 | AD0CR |= (1 << 16); // Burst 66 | AD0DR1; 67 | AD0DR2; 68 | for (int i = 0; i < NUM_ADCH; i++) { 69 | ADres[i] = ADidx[i] = 0; 70 | for (int j = 0; j < NUM_ACCUM; j++) { 71 | ADaccum[i][j] = 0; 72 | } 73 | } 74 | Sched_SetState( ADC_WORK, 2, 0); // Start right away 75 | } 76 | 77 | int32_t ADC_Read(uint32_t chnum) { 78 | int32_t result=-1; 79 | if (chnum >= 1 && chnum <= 2) { 80 | result = ADres[chnum - 1] >> OVERSAMPLING_BITS; 81 | } 82 | return result; 83 | } 84 | -------------------------------------------------------------------------------- /src/nvstorage.c: -------------------------------------------------------------------------------- 1 | /* 2 | * nvstorage.c - Persistent settings storage for T-962 reflow controller 3 | * 4 | * Copyright (C) 2011,2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "nvstorage.h" 24 | #include "eeprom.h" 25 | #include "sched.h" 26 | 27 | #define NVMAGIC (('W' << 8) | 'J') 28 | 29 | typedef struct __attribute__ ((__packed__)) { 30 | uint16_t magic; 31 | uint8_t numitems; 32 | uint8_t config[NVITEM_NUM_ITEMS]; 33 | } NV_t; 34 | 35 | static NV_t myNV; // RAM copy of the NV data 36 | 37 | static uint8_t nvupdatepending=0; 38 | 39 | static void SetNVUpdatePending(void) { 40 | nvupdatepending = 1; 41 | Sched_SetState(NV_WORK, 1, 0); 42 | } 43 | 44 | void NV_Init(void) { 45 | EEPROM_Read((uint8_t*)&myNV, 0x62, sizeof(myNV)); 46 | if (myNV.magic != NVMAGIC ) { 47 | myNV.magic = NVMAGIC; 48 | myNV.numitems = NVITEM_NUM_ITEMS; 49 | memset(myNV.config, 0xff, NVITEM_NUM_ITEMS); 50 | printf("\nNV initialization cleared %d items", NVITEM_NUM_ITEMS); 51 | SetNVUpdatePending(); 52 | } else if(myNV.numitems < NVITEM_NUM_ITEMS) { 53 | uint8_t bytestoclear = NVITEM_NUM_ITEMS - myNV.numitems; 54 | memset(myNV.config + myNV.numitems, 0xff, bytestoclear); 55 | myNV.numitems = NVITEM_NUM_ITEMS; 56 | printf("\nNV upgrade cleared %d new items", bytestoclear); 57 | SetNVUpdatePending(); 58 | } 59 | #ifndef MINIMALISTIC 60 | Sched_SetWorkfunc(NV_WORK,NV_Work); 61 | Sched_SetState(NV_WORK, 1, 0); 62 | #endif 63 | } 64 | 65 | uint8_t NV_GetConfig(NVItem_t item) { 66 | if (item < NVITEM_NUM_ITEMS) { 67 | return myNV.config[item]; 68 | } else { 69 | return 0; 70 | } 71 | } 72 | 73 | void NV_SetConfig(NVItem_t item, uint8_t value) { 74 | if (item < NVITEM_NUM_ITEMS) { 75 | if (value != myNV.config[item]) { 76 | myNV.config[item] = value; 77 | SetNVUpdatePending(); 78 | } 79 | } 80 | } 81 | 82 | // Periodic updater of NV 83 | int32_t NV_Work(void) { 84 | static uint8_t count = 0; 85 | if (nvupdatepending) count ++; 86 | if (count == 4) { 87 | nvupdatepending = count = 0; 88 | printf("\nFlushing NV copy to EE..."); 89 | EEPROM_Write(0x62, (uint8_t*)&myNV, sizeof(myNV)); 90 | } 91 | return nvupdatepending ? (TICKS_SECS(2)) : -1; 92 | } 93 | -------------------------------------------------------------------------------- /src/setup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * setup.c - T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "nvstorage.h" 23 | #include "reflow_profiles.h" 24 | #include "setup.h" 25 | 26 | static setupMenuStruct setupmenu[] = { 27 | {"Min fan speed %4.0f", REFLOW_MIN_FAN_SPEED, 0, 254, 0, 1.0f}, 28 | {"Cycle done beep %4.1fs", REFLOW_BEEP_DONE_LEN, 0, 254, 0, 0.1f}, 29 | {"Left TC gain %1.2f", TC_LEFT_GAIN, 10, 190, 0, 0.01f}, 30 | {"Left TC offset %+1.2f", TC_LEFT_OFFSET, 0, 200, -100, 0.25f}, 31 | {"Right TC gain %1.2f", TC_RIGHT_GAIN, 10, 190, 0, 0.01f}, 32 | {"Right TC offset %+1.2f", TC_RIGHT_OFFSET, 0, 200, -100, 0.25f}, 33 | }; 34 | #define NUM_SETUP_ITEMS (sizeof(setupmenu) / sizeof(setupmenu[0])) 35 | 36 | int Setup_getNumItems(void) { 37 | return NUM_SETUP_ITEMS; 38 | } 39 | 40 | int _getRawValue(int item) { 41 | return NV_GetConfig(setupmenu[item].nvval); 42 | } 43 | 44 | float Setup_getValue(int item) { 45 | int intval = _getRawValue(item); 46 | intval += setupmenu[item].offset; 47 | return ((float)intval) * setupmenu[item].multiplier; 48 | } 49 | 50 | void Setup_setValue(int item, int value) { 51 | NV_SetConfig(setupmenu[item].nvval, value); 52 | Reflow_ValidateNV(); 53 | } 54 | 55 | void Setup_setRealValue(int item, float value) { 56 | int intval = (int)(value / setupmenu[item].multiplier); 57 | intval -= setupmenu[item].offset; 58 | Setup_setValue(item, intval); 59 | } 60 | 61 | void Setup_increaseValue(int item, int amount) { 62 | int curval = _getRawValue(item) + amount; 63 | 64 | int maxval = setupmenu[item].maxval; 65 | if (curval > maxval) curval = maxval; 66 | 67 | Setup_setValue(item, curval); 68 | } 69 | 70 | void Setup_decreaseValue(int item, int amount) { 71 | int curval = _getRawValue(item) - amount; 72 | 73 | int minval = setupmenu[item].minval; 74 | if (curval < minval) curval = minval; 75 | 76 | Setup_setValue(item, curval); 77 | } 78 | 79 | void Setup_printFormattedValue(int item) { 80 | printf(setupmenu[item].formatstr, Setup_getValue(item)); 81 | } 82 | 83 | int Setup_snprintFormattedValue(char* buf, int n, int item) { 84 | return snprintf(buf, n, setupmenu[item].formatstr, Setup_getValue(item)); 85 | } 86 | -------------------------------------------------------------------------------- /src/vic.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vic.c - Vectored Interrupt Controller interface for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include "t962.h" 23 | #include "vic.h" 24 | 25 | // Added functionality missing in the standard LPC header 26 | #define VICVectAddrX(x) (*(volatile unsigned long *)(VIC_BASE_ADDR + 0x100 + (x << 2))) 27 | #define VICVectCntlX(x) (*(volatile unsigned long *)(VIC_BASE_ADDR + 0x200 + (x << 2))) 28 | 29 | static void __attribute__ ((interrupt ("IRQ"))) VIC_Default_Handler( void ) { 30 | static uint32_t spuriousirq = 0; 31 | spuriousirq++; 32 | VICVectAddr = 0; // ACK irq 33 | } 34 | 35 | void VIC_Init( void ) { 36 | VICIntEnable = 0; // Make sure all interrupts are disabled 37 | VICIntSelect = 0; // All interrupts are routed to IRQ (not FIQ) 38 | VICDefVectAddr = (uint32_t)VIC_Default_Handler; 39 | } 40 | 41 | uint32_t VIC_IsIRQDisabled( void ) { 42 | uint32_t state; 43 | asm("MRS %0,cpsr" : "=r" (state)); 44 | return !!(state&0x80); 45 | } 46 | 47 | uint32_t VIC_DisableIRQ( void ) { 48 | uint32_t retval; 49 | asm("MRS %0,cpsr" : "=r" (retval)); 50 | asm("MSR cpsr_c,#(0x1F | 0x80 | 0x40)"); 51 | //FIO0CLR = (1<<11); // Visualize interrupts being disabled by turning backlight off 52 | return retval; 53 | } 54 | 55 | void VIC_RestoreIRQ( uint32_t mask ) { 56 | //if(!(mask & 0x80)) FIO0SET = (1<<11); // Visualize when interrupts are enabled again by turning backlight on 57 | asm("MSR cpsr_c,%0" : : "r" (mask)); 58 | } 59 | 60 | int32_t VIC_RegisterHandler( VICInt_t num, void* ptr ) { 61 | int32_t i, retval; 62 | for( i = 0; i < 16 ; i++ ) { // Find empty vector slot 63 | if( !( VICVectCntlX( i ) & (1<<5) ) ) break; 64 | } 65 | if( i < 16 ) { // Found empty slot, insert handler 66 | VICVectAddrX( i ) = (uint32_t)ptr; 67 | VICVectCntlX( i ) = num | (1<<5); // Enable SLOT, not necessarily the interrupt itself 68 | retval = 0; 69 | } else { 70 | retval = -1; // No space 71 | } 72 | return retval; 73 | } 74 | 75 | int32_t VIC_EnableHandler( VICInt_t num ) { 76 | VICIntEnable |= (1<. 18 | */ 19 | 20 | #ifndef SMALLFONT_H 21 | #define SMALLFONT_H 22 | 23 | const uint8_t smallfont[] ={ 24 | 0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x2e,0x00,0x00, 25 | 0x00,0x00,0x06,0x00,0x06,0x00, 0x00,0x14,0x3e,0x14,0x3e,0x14, 26 | 0x00,0x24,0x2a,0x6b,0x2a,0x12, 0x00,0x22,0x12,0x08,0x24,0x22, 27 | 0x00,0x10,0x2c,0x2a,0x14,0x28, 0x00,0x00,0x04,0x02,0x00,0x00, 28 | 0x00,0x00,0x1c,0x22,0x00,0x00, 0x00,0x00,0x22,0x1c,0x00,0x00, 29 | 0x00,0x2a,0x1c,0x08,0x1c,0x2a, 0x00,0x08,0x08,0x3e,0x08,0x08, 30 | 0x00,0x00,0x20,0x10,0x00,0x00, 0x00,0x08,0x08,0x08,0x08,0x08, 31 | 0x00,0x00,0x00,0x20,0x00,0x00, 0x00,0x20,0x10,0x08,0x04,0x02, 32 | 33 | 0x00,0x1c,0x32,0x2a,0x26,0x1c, 0x00,0x00,0x04,0x3e,0x00,0x00, 34 | 0x00,0x24,0x32,0x2a,0x2a,0x24, 0x00,0x22,0x2a,0x2a,0x2a,0x14, 35 | 0x00,0x0e,0x08,0x08,0x08,0x3e, 0x00,0x2e,0x2a,0x2a,0x2a,0x12, 36 | 0x00,0x18,0x2c,0x2a,0x2a,0x10, 0x00,0x02,0x22,0x12,0x0a,0x06, 37 | 0x00,0x14,0x2a,0x2a,0x2a,0x14, 0x00,0x04,0x2a,0x2a,0x2a,0x1c, 38 | 0x00,0x00,0x00,0x14,0x00,0x00, 0x00,0x00,0x20,0x14,0x00,0x00, 39 | 0x00,0x00,0x08,0x14,0x22,0x00, 0x00,0x14,0x14,0x14,0x14,0x14, 40 | 0x00,0x00,0x22,0x14,0x08,0x00, 0x00,0x04,0x02,0x2a,0x0a,0x04, 41 | 42 | 0x00,0x1c,0x22,0x2e,0x2e,0x0c, 0x00,0x3c,0x0a,0x0a,0x0a,0x3c, 43 | 0x00,0x3e,0x2a,0x2a,0x2a,0x14, 0x00,0x1c,0x22,0x22,0x22,0x14, 44 | 0x00,0x3e,0x22,0x22,0x22,0x1c, 0x00,0x3e,0x2a,0x2a,0x2a,0x22, 45 | 0x00,0x3e,0x0a,0x0a,0x02,0x02, 0x00,0x1c,0x2a,0x2a,0x2a,0x3a, 46 | 0x00,0x3e,0x08,0x08,0x08,0x3e, 0x00,0x22,0x22,0x3e,0x22,0x22, 47 | 0x00,0x10,0x20,0x20,0x20,0x1e, 0x00,0x3e,0x08,0x08,0x14,0x22, 48 | 0x00,0x3e,0x20,0x20,0x20,0x20, 0x00,0x3e,0x04,0x08,0x04,0x3e, 49 | 0x00,0x3e,0x04,0x08,0x10,0x3e, 0x00,0x1c,0x22,0x22,0x22,0x1c, 50 | 51 | 0x00,0x3e,0x0a,0x0a,0x0a,0x04, 0x00,0x1c,0x22,0x22,0x32,0x3c, 52 | 0x00,0x3e,0x0a,0x0a,0x1a,0x24, 0x00,0x24,0x2a,0x2a,0x2a,0x10, 53 | 0x00,0x02,0x02,0x3e,0x02,0x02, 0x00,0x1e,0x20,0x20,0x20,0x1e, 54 | 0x00,0x06,0x18,0x20,0x18,0x06, 0x00,0x3e,0x10,0x08,0x10,0x3e, 55 | 0x00,0x22,0x14,0x08,0x14,0x22, 0x00,0x02,0x04,0x38,0x04,0x02, 56 | 0x00,0x22,0x32,0x2a,0x26,0x22, 0x00,0x00,0x3e,0x22,0x22,0x00, 57 | 0x00,0x02,0x04,0x08,0x10,0x20, 0x00,0x00,0x22,0x22,0x3e,0x00, 58 | 0x00,0x08,0x04,0x02,0x04,0x08, 0x00,0x20,0x20,0x20,0x20,0x20, 59 | 60 | 0x00,0x02,0x00,0x1c,0x22,0x22, // Degrees Celsius symbol at 0x80 (`) 61 | }; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /src/i2c.c: -------------------------------------------------------------------------------- 1 | /* 2 | * i2c.c - I2C interface for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include "t962.h" 24 | #include "i2c.h" 25 | 26 | // Limit to i2c speed 200kHz because of the relatively weak 4k7 pullups 27 | #define I2CSPEED (200000) 28 | 29 | void I2C_Init(void) { 30 | uint8_t dummybyte; 31 | I20SCLL = I20SCLH = PCLKFREQ / I2CSPEED / 2; 32 | I20CONCLR = 0xff; 33 | I20CONSET = (1 << 6); // I2EN 34 | I2C_Xfer(0xff, &dummybyte, 0, 1); // Dummy initial xfer 35 | } 36 | 37 | #define I2CSTART (0x08) 38 | #define I2CRSTART (0x10) 39 | #define I2CWAACK (0x18) 40 | #define I2CWANOACK (0x20) 41 | #define I2CWDACK (0x28) 42 | #define I2CWDNOACK (0x30) 43 | #define I2CARBLOST (0x38) 44 | #define I2CRAACK (0x40) 45 | #define I2CRANOACK (0x48) 46 | #define I2CRDACK (0x50) 47 | #define I2CRDNOACK (0x58) 48 | 49 | int32_t I2C_Xfer(uint8_t slaveaddr, uint8_t* theBuffer, uint32_t theLength, uint8_t trailingStop) { 50 | int32_t retval = 0; 51 | int done = 0; 52 | uint8_t stat; 53 | 54 | I20CONSET = (1 << 5); // STA 55 | //printf("\n[STA]"); 56 | 57 | while (!done) { 58 | while (!(I20CONSET & (1 << 3))); // SI 59 | stat = I20STAT; 60 | //printf("[0x%02x]", stat); 61 | switch(stat) { 62 | case I2CSTART: 63 | case I2CRSTART: 64 | I20DAT = slaveaddr; 65 | I20CONCLR = (1 << 5); // Clear STA 66 | //printf("[WADDR]"); 67 | break; 68 | case I2CWANOACK: 69 | case I2CWDNOACK: 70 | case I2CARBLOST: 71 | case I2CRANOACK: 72 | //printf("[I2C error!]"); 73 | trailingStop = 1; // Force STOP condition at the end no matter what 74 | retval = -1; 75 | done = 1; 76 | break; 77 | 78 | case I2CWAACK: 79 | case I2CWDACK: 80 | if (theLength) { 81 | I20DAT = *theBuffer++; 82 | theLength--; 83 | //printf("[WDATA]"); 84 | } else { 85 | done=1; 86 | } 87 | break; 88 | 89 | case I2CRAACK: 90 | //printf("[RADDR]"); 91 | I20CONSET = (1 << 2); // Set AA 92 | break; 93 | 94 | case I2CRDACK: 95 | case I2CRDNOACK: 96 | *theBuffer++ = I20DAT; 97 | theLength--; 98 | if (theLength == 1) { 99 | I20CONCLR = (1 << 2); // Clear AA for last byte 100 | } else if (theLength == 0) { 101 | done = 1; 102 | } 103 | //if(!done) printf("[RDATA]"); 104 | break; 105 | } 106 | I20CONCLR = (1 << 3); // Clear SI 107 | } 108 | 109 | if (trailingStop) { 110 | I20CONSET = (1 << 4); // STO 111 | //printf("[STO]"); 112 | while (I20CONSET & (1 << 4)); // Wait for STO to clear 113 | } 114 | return retval; 115 | } 116 | -------------------------------------------------------------------------------- /src/keypad.c: -------------------------------------------------------------------------------- 1 | /* 2 | * keypad.c - Keypad interface for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include "t962.h" 24 | #include "keypad.h" 25 | #include "io.h" 26 | #include "sched.h" 27 | 28 | #define F1KEY_PORTBIT (1 << 23) 29 | #define F2KEY_PORTBIT (1 << 15) 30 | #define F3KEY_PORTBIT (1 << 16) 31 | #define F4KEY_PORTBIT (1 << 4) 32 | #define S_KEY_PORTBIT (1 << 20) 33 | 34 | #define KEYREPEATDELAY (6) 35 | 36 | static uint32_t latchedkeypadstate = 0; 37 | 38 | static uint32_t Keypad_GetRaw(void) { 39 | return ~FIO0PIN & (F1KEY_PORTBIT | F2KEY_PORTBIT | F3KEY_PORTBIT | F4KEY_PORTBIT | S_KEY_PORTBIT); 40 | } 41 | 42 | static int32_t Keypad_Work(void) { 43 | static uint32_t laststate = 0; 44 | static uint16_t laststateunchangedctr = 0; 45 | uint32_t keypadstate = 0; 46 | uint32_t inverted = Keypad_GetRaw(); 47 | uint32_t changed = inverted ^ laststate; 48 | 49 | // At this point we only care about when button is pressed, not released 50 | changed &= inverted; 51 | 52 | if (laststate != inverted) { 53 | laststate = inverted; 54 | laststateunchangedctr = 0; 55 | } else { 56 | laststateunchangedctr++; 57 | if (laststateunchangedctr > KEYREPEATDELAY) { 58 | changed = laststate; // Feed key repeat 59 | // For accelerating key repeats 60 | keypadstate |= ((laststateunchangedctr - KEYREPEATDELAY) << 16); 61 | } 62 | } 63 | 64 | if (changed) { 65 | if (changed & F1KEY_PORTBIT) keypadstate |= KEY_F1; 66 | if (changed & F2KEY_PORTBIT) keypadstate |= KEY_F2; 67 | if (changed & F3KEY_PORTBIT) keypadstate |= KEY_F3; 68 | if (changed & F4KEY_PORTBIT) keypadstate |= KEY_F4; 69 | if (changed & S_KEY_PORTBIT) keypadstate |= KEY_S; 70 | } 71 | 72 | latchedkeypadstate &= 0xffff; 73 | latchedkeypadstate |= keypadstate; // Make sure software actually sees the transitions 74 | 75 | if (keypadstate & 0xff) { 76 | //printf("[KEYPAD %02x]",keypadstate & 0xff); 77 | Sched_SetState(MAIN_WORK, 2, 0); // Wake up main task to update UI 78 | } 79 | 80 | return TICKS_MS(100); 81 | } 82 | 83 | uint32_t Keypad_Get(void) { 84 | uint32_t retval = latchedkeypadstate; 85 | latchedkeypadstate = 0; 86 | return retval; 87 | } 88 | 89 | void Keypad_Init(void) { 90 | Sched_SetWorkfunc(KEYPAD_WORK, Keypad_Work); 91 | printf("\nWaiting for keys to be released... "); 92 | // Note that if this takes longer than ~1 second the watchdog will bite 93 | while (Keypad_GetRaw()); 94 | printf("Done!"); 95 | 96 | // Potential noise gets suppressed as well 97 | Sched_SetState(KEYPAD_WORK, 1, TICKS_MS(250)); // Wait 250ms before starting to scan the keypad 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/eeprom.c: -------------------------------------------------------------------------------- 1 | /* 2 | * eeprom.c - I2C EEPROM interface for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include 24 | #include "t962.h" 25 | #include "eeprom.h" 26 | #include "i2c.h" 27 | 28 | #define EEADDR (0x50<<1) 29 | //#define DUMP_EEPROM 30 | 31 | void EEPROM_Init(void) { 32 | #ifdef DUMP_EEPROM 33 | EEPROM_Dump(); 34 | #endif 35 | // No init needed at this point, maybe detect the actual presence some day 36 | } 37 | 38 | void EEPROM_Dump(void) { 39 | uint8_t dumpbuf[256]; 40 | EEPROM_Read(dumpbuf, 0, sizeof(dumpbuf)); 41 | printf("\nEEPROM contents:"); 42 | for (int i = 0; i < sizeof(dumpbuf); i++) { 43 | if ((i & 0x0f) == 0){ 44 | printf("\n0x%04x:", i); 45 | } 46 | printf(" %02x", dumpbuf[i]); 47 | } 48 | } 49 | 50 | int32_t EEPROM_Read(uint8_t* dest, uint32_t startpos, uint32_t len) { 51 | int32_t retval = 0; 52 | if (startpos < 256 && dest && len && len <= 256) { 53 | uint8_t offset = (uint8_t)startpos; 54 | retval = I2C_Xfer(EEADDR, &offset, 1, 0); // Set address pointer to startpos 55 | if (!retval) { 56 | retval = I2C_Xfer(EEADDR | 1, dest, len, 1); // Read requested data 57 | } 58 | } 59 | return retval; 60 | } 61 | 62 | int32_t EEPROM_Write(uint32_t startdestpos, uint8_t* src, uint32_t len) { 63 | int32_t retval = 0; 64 | if (startdestpos < 256 && len && len <= 256) { 65 | uint8_t tmpbuf[9]; 66 | uint8_t i = startdestpos; 67 | while (len) { 68 | uint32_t loopcnt = 0; 69 | uint8_t startoffset = i & 0x07; 70 | uint8_t maxcopysize = 8 - startoffset; 71 | // up to 8 bytes at a time depending on alignment 72 | uint8_t bytestocopy = (len > maxcopysize) ? maxcopysize : len; 73 | tmpbuf[0] = i; 74 | memcpy(tmpbuf + 1, src, bytestocopy); 75 | // Set address pointer and provide up to 8 bytes of data for page write 76 | retval = I2C_Xfer(EEADDR, tmpbuf, bytestocopy + 1, 1); 77 | if (!retval) { 78 | do { 79 | // Dummy write to poll timed write cycle completion 80 | retval = I2C_Xfer(EEADDR, tmpbuf, 1, 1); 81 | loopcnt++; 82 | // 5ms max write cycle. 200kHz bus freq & 10 bits per poll makes this a 20ms timeout 83 | } while (retval && loopcnt < 400); 84 | if (retval) { 85 | printf("\nTimeout getting ACK from EEPROM during write!"); 86 | break; 87 | } 88 | len -= bytestocopy; 89 | i += bytestocopy; 90 | src += bytestocopy; 91 | } else { 92 | printf("\nFailed to write to EEPROM!"); 93 | retval = -2; 94 | break; 95 | } 96 | } 97 | } else { 98 | printf("\nInvalid EEPROM addressing"); 99 | retval = -3; 100 | } 101 | return retval; 102 | } 103 | -------------------------------------------------------------------------------- /src/systemfan.c: -------------------------------------------------------------------------------- 1 | /* 2 | * systemfan.c - Temperature controlled system fan handling for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | /* 21 | * This utilizes the (previously unused) ADO testpoint that is connected to 22 | * GPIO0.25 to drive a N-ch/NPN transistor to control the (noisy) system fan. 23 | * 24 | * System fan connector pin 1 = GND, pin 2 = ~10VDC 25 | * 26 | * Disconnect fan from GND, connect this wire to transistor drain/collector. Also 27 | * connect the anode of a catch diode here, cathode to pin 2 (~10VDC). 28 | * Transistor source/emitter connects to ground, gate connects directly to ADO testpoint. 29 | * If using a bipolar transistor connect base through a 4k7 resistor to ADO testpoint. 30 | * 31 | * Example transistors that works: 2N7000 (N-ch FET) or BC547B (bipolar NPN) 32 | */ 33 | 34 | #include "LPC214x.h" 35 | #include 36 | #include 37 | #include "systemfan.h" 38 | #include "sched.h" 39 | #include "sensor.h" 40 | 41 | #define SYSFAN_PWM_PERIOD (TICKS_MS( 10 )) 42 | 43 | static uint32_t syspwmval = 0; 44 | 45 | static int32_t SystemFanPWM_Work(void) { 46 | static uint8_t state = 0; 47 | int32_t retval; 48 | 49 | if (state) { 50 | FIO0CLR = (syspwmval != SYSFAN_PWM_PERIOD) ? (1<<25) : 0; // SysFan off 51 | retval = syspwmval ? (SYSFAN_PWM_PERIOD - syspwmval) : -1; 52 | } else { 53 | FIO0SET = syspwmval ? (1<<25) : 0; // SysFan on 54 | retval = (syspwmval != SYSFAN_PWM_PERIOD) ? syspwmval : -1; 55 | } 56 | state ^= 1; 57 | return retval; 58 | } 59 | 60 | static int32_t SystemFanSense_Work(void) { 61 | uint8_t sysfanspeed = 0; 62 | 63 | if (Sensor_IsValid(TC_COLD_JUNCTION)) { 64 | float systemp = Sensor_GetTemp(TC_COLD_JUNCTION); 65 | 66 | // Sort this out with something better at some point 67 | if (systemp > 50.0f) { 68 | sysfanspeed = 0xff; 69 | } else if (systemp > 45.0f) { 70 | sysfanspeed = 0xc0; 71 | } else if (systemp > 42.0f) { 72 | sysfanspeed = 0x80; 73 | } else if (systemp > 40.0f) { 74 | sysfanspeed = 0x50; 75 | } 76 | } else { 77 | // No sensor, run at full speed as a precaution 78 | sysfanspeed = 0xff; 79 | } 80 | 81 | uint32_t temp = SYSFAN_PWM_PERIOD >> 8; 82 | temp *= sysfanspeed; 83 | if (sysfanspeed == 0xff) { 84 | // Make sure we reach 100% duty cycle 85 | temp = SYSFAN_PWM_PERIOD; 86 | } 87 | syspwmval = temp; 88 | 89 | Sched_SetState(SYSFANPWM_WORK, 2, 0); 90 | 91 | return TICKS_SECS( 5 ); 92 | } 93 | 94 | void SystemFan_Init(void) { 95 | printf("\n%s", __FUNCTION__); 96 | Sched_SetWorkfunc(SYSFANPWM_WORK, SystemFanPWM_Work); 97 | Sched_SetWorkfunc(SYSFANSENSE_WORK, SystemFanSense_Work); 98 | 99 | // Turn on fan briefly at boot to indicate that it actually works 100 | syspwmval = SYSFAN_PWM_PERIOD; 101 | Sched_SetState(SYSFANPWM_WORK, 2, 0); // Enable PWM task 102 | Sched_SetState(SYSFANSENSE_WORK, 1, TICKS_SECS( 2 ) ); // Enable Sense task 103 | } 104 | -------------------------------------------------------------------------------- /src/PID_v1.h: -------------------------------------------------------------------------------- 1 | #ifndef PID_H 2 | #define PID_H 3 | 4 | typedef float FloatType; 5 | //typedef double floatType; 6 | #include 7 | 8 | //Constants used in some of the functions below 9 | typedef enum 10 | { 11 | PID_Mode_Automatic = 1, 12 | PID_Mode_Manual = 0 13 | } PidModeType; 14 | 15 | typedef enum 16 | { 17 | PID_Direction_Direct = 0, 18 | PID_Direction_Reverse = 1 19 | } PidDirectionType; 20 | 21 | typedef struct { 22 | FloatType dispKp; // * we'll hold on to the tuning parameters in user-entered 23 | FloatType dispKi; // format for display purposes 24 | FloatType dispKd; // 25 | 26 | FloatType kp; // * (P)roportional Tuning Parameter 27 | FloatType ki; // * (I)ntegral Tuning Parameter 28 | FloatType kd; // * (D)erivative Tuning Parameter 29 | 30 | PidDirectionType controllerDirection; 31 | 32 | FloatType myInput; // * Pointers to the Input, Output, and Setpoint variables 33 | FloatType myOutput; // This creates a hard link between the variables and the 34 | FloatType mySetpoint; // PID, freeing the user from having to constantly tell us 35 | // what these values are. with pointers we'll just know. 36 | 37 | // unsigned long lastTime; 38 | FloatType ITerm, lastInput; 39 | 40 | unsigned long SampleTime; 41 | FloatType outMin, outMax; 42 | bool inAuto; 43 | } PidType; 44 | 45 | //commonly used functions ************************************************************************** 46 | 47 | // constructor. links the PID to the Input, Output, and 48 | // Setpoint. Initial tuning parameters are also set here 49 | void PID_init(PidType* pid, 50 | FloatType kp, 51 | FloatType ki, 52 | FloatType kd, 53 | PidDirectionType controllerDirection); 54 | 55 | // sets PID to either Manual (0) or Auto (non-0) 56 | void PID_SetMode(PidType* pid, PidModeType mode); 57 | 58 | // performs the PID calculation. it should be 59 | // called every time loop() cycles. ON/OFF and 60 | // calculation frequency can be set using SetMode 61 | // SetSampleTime respectively 62 | bool PID_Compute(PidType* pid); 63 | 64 | // clamps the output to a specific range. 0-255 by default, but 65 | // it's likely the user will want to change this depending on 66 | // the application 67 | void PID_SetOutputLimits(PidType* pid, FloatType min, FloatType max); 68 | 69 | //available but not commonly used functions ******************************************************** 70 | 71 | // While most users will set the tunings once in the 72 | // constructor, this function gives the user the option 73 | // of changing tunings during runtime for Adaptive control 74 | void PID_SetTunings(PidType* pid, FloatType kp, FloatType ki, FloatType kd); 75 | 76 | // Sets the Direction, or "Action" of the controller. DIRECT 77 | // means the output will increase when error is positive. REVERSE 78 | // means the opposite. it's very unlikely that this will be needed 79 | // once it is set in the constructor. 80 | void PID_SetControllerDirection(PidType* pid, PidDirectionType Direction); 81 | 82 | // sets the frequency, in Milliseconds, with which 83 | // the PID calculation is performed. default is 100 84 | void PID_SetSampleTime(PidType* pid, int newSampleTime); 85 | 86 | //Display functions **************************************************************** 87 | // These functions query the pid for interal values. 88 | // they were created mainly for the pid front-end, 89 | // where it's important to know what is actually 90 | // inside the PID. 91 | FloatType PID_GetKp(PidType* pid); 92 | FloatType PID_GetKi(PidType* pid); 93 | FloatType PID_GetKd(PidType* pid); 94 | PidModeType PID_GetMode(PidType* pid); 95 | PidDirectionType PID_GetDirection(PidType* pid); 96 | 97 | //void PID_Initialize(PidType* pid); 98 | #endif 99 | -------------------------------------------------------------------------------- /T-962-controller.ld: -------------------------------------------------------------------------------- 1 | /** 2 | * Linker script for the T-962 controller improved firmware. 3 | * 4 | * Amended from a version generated by LPCXpresso v7.5.0 5 | */ 6 | 7 | GROUP( 8 | libgcc.a 9 | libc_nano.a 10 | libm.a 11 | libnosys.a 12 | ) 13 | 14 | MEMORY { 15 | /* Define each memory region */ 16 | MFlash128 (rx) : ORIGIN = 0x0, LENGTH = 0x20000 /* 128K bytes */ 17 | Ram16 (rwx) : ORIGIN = 0x40000000, LENGTH = 0x4000 /* 16K bytes */ 18 | } 19 | /* Define a symbol for the top of each memory region */ 20 | __top_MFlash128 = 0x0 + 0x20000; 21 | __top_Ram16 = 0x40000000 + 0x4000; 22 | 23 | ENTRY(_start) 24 | 25 | SECTIONS 26 | { 27 | 28 | /* MAIN TEXT SECTION */ 29 | .text : ALIGN(4) 30 | { 31 | FILL(0xff) 32 | __vectors_start__ = ABSOLUTE(.) ; 33 | KEEP(*(.isr_vector)) 34 | 35 | /* Global Section Table */ 36 | . = ALIGN(4) ; 37 | __section_table_start = .; 38 | __data_section_table = .; 39 | LONG(LOADADDR(.data)); 40 | LONG( ADDR(.data)); 41 | LONG( SIZEOF(.data)); 42 | __data_section_table_end = .; 43 | __bss_section_table = .; 44 | LONG( ADDR(.bss)); 45 | LONG( SIZEOF(.bss)); 46 | __bss_section_table_end = .; 47 | __section_table_end = . ; 48 | /* End of Global Section Table */ 49 | 50 | 51 | *(.after_vectors*) 52 | 53 | /* Code Read Protect data */ 54 | . = 0x000001FC ; 55 | PROVIDE(__CRP_WORD_START__ = .) ; 56 | KEEP(*(.crp)) 57 | PROVIDE(__CRP_WORD_END__ = .) ; 58 | /* 59 | Disabled: We're using newlib, not redlib. 60 | ASSERT(!(__CRP_WORD_START__ == __CRP_WORD_END__), "Linker CRP Enabled, but no CRP_WORD provided within application"); */ 61 | /* End of Code Read Protect */ 62 | 63 | } >MFlash128 64 | 65 | .text : ALIGN(4) 66 | { 67 | *(.text*) 68 | *(.rodata .rodata.* .constdata .constdata.*) 69 | . = ALIGN(4); 70 | 71 | } > MFlash128 72 | 73 | /* 74 | * for exception handling/unwind - some Newlib functions (in common 75 | * with C++ and STDC++) use this. 76 | */ 77 | .ARM.extab : ALIGN(4) 78 | { 79 | *(.ARM.extab* .gnu.linkonce.armextab.*) 80 | } > MFlash128 81 | __exidx_start = .; 82 | 83 | .ARM.exidx : ALIGN(4) 84 | { 85 | *(.ARM.exidx* .gnu.linkonce.armexidx.*) 86 | } > MFlash128 87 | __exidx_end = .; 88 | 89 | _etext = .; 90 | 91 | 92 | /* MAIN DATA SECTION */ 93 | 94 | 95 | .uninit_RESERVED : ALIGN(4) 96 | { 97 | KEEP(*(.bss.$RESERVED*)) 98 | . = ALIGN(4) ; 99 | _end_uninit_RESERVED = .; 100 | } > Ram16 101 | 102 | 103 | /* Main DATA section (Ram16) */ 104 | .data : ALIGN(4) 105 | { 106 | FILL(0xff) 107 | _data = . ; 108 | *(vtable) 109 | *(.ramfunc*) 110 | *(.data*) 111 | . = ALIGN(4) ; 112 | _edata = . ; 113 | } > Ram16 AT>MFlash128 114 | 115 | 116 | /* MAIN BSS SECTION */ 117 | .bss : ALIGN(4) 118 | { 119 | _bss = .; 120 | *(.bss*) 121 | *(COMMON) 122 | . = ALIGN(4) ; 123 | _ebss = .; 124 | PROVIDE(end = .); 125 | } > Ram16 126 | 127 | 128 | /* DEFAULT NOINIT SECTION */ 129 | .noinit (NOLOAD): ALIGN(4) 130 | { 131 | _noinit = .; 132 | *(.noinit*) 133 | . = ALIGN(4) ; 134 | _end_noinit = .; 135 | } > Ram16 136 | 137 | PROVIDE(_pvHeapStart = DEFINED(__user_heap_base) ? __user_heap_base : .); 138 | PROVIDE(_vStackTop = DEFINED(__user_stack_top) ? __user_stack_top : __top_Ram16 - 0); 139 | } 140 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Makefile to build the improved T-962 firmware without LPCXpresso 3 | # 4 | # Makes a 'build' directory in the root of the project. 5 | ################################################################################ 6 | BASE_NAME := T-962-controller 7 | 8 | SRC_DIR := ./src/ 9 | BUILD_DIR := ./build/ 10 | TARGET := $(BUILD_DIR)$(BASE_NAME).axf 11 | 12 | 13 | vpath %.c $(SRC_DIR) 14 | vpath %.o $(BUILD_DIR) 15 | vpath %.d $(BUILD_DIR) 16 | 17 | CC := arm-none-eabi-gcc 18 | RM := rm -rf 19 | 20 | # Flash tool settings 21 | FLASH_TOOL := ./lpc21isp 22 | FLASH_TTY := /dev/ttyUSB0 23 | FLASH_BAUD := 57600 24 | MCU_CLOCK := 11059 25 | 26 | COLOR_GREEN = $(shell echo "\033[0;32m") 27 | COLOR_RED = $(shell echo "\033[0;31m") 28 | COLOR_END = $(shell echo "\033[0m") 29 | 30 | # Source files 31 | C_SRCS += $(wildcard $(SRC_DIR)*.c) $(BUILD_DIR)version.c 32 | 33 | S_SRCS += $(wildcard $(SRC_DIR)*.s) 34 | 35 | OBJS := $(patsubst $(SRC_DIR)%.c,$(BUILD_DIR)%.o,$(C_SRCS)) $(patsubst $(SRC_DIR)%.s,$(BUILD_DIR)%.o,$(S_SRCS)) 36 | 37 | C_DEPS := $(wildcard *.d) 38 | 39 | all: axf 40 | 41 | $(BUILD_DIR)version.c: $(BUILD_DIR)tag 42 | git describe --tag --always --dirty | \ 43 | sed 's/.*/const char* Version_GetGitVersion(void) { return "&"; }/' > $@ 44 | 45 | # Always regenerate the git version 46 | .PHONY: $(BUILD_DIR)version.c 47 | 48 | $(BUILD_DIR)tag: 49 | mkdir -p $(BUILD_DIR) 50 | touch $(BUILD_DIR)tag 51 | 52 | $(BUILD_DIR)%.o: $(SRC_DIR)%.c $(BUILD_DIR)tag 53 | @echo 'Building file: $<' 54 | $(CC) -std=gnu99 -DNDEBUG -D__NEWLIB__ -Os -g -Wall -Wunused -c -fmessage-length=0 -fno-builtin -ffunction-sections -fdata-sections -flto -ffat-lto-objects -mcpu=arm7tdmi -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.o)" -MT"$(@:%.o=%.d)" -o "$@" "$<" 55 | @echo 'Finished building: $(COLOR_GREEN)$<$(COLOR_END)' 56 | @echo ' ' 57 | 58 | $(BUILD_DIR)%.o: $(SRC_DIR)%.s $(BUILD_DIR)tag 59 | @echo 'Building file: $<' 60 | $(CC) -c -x assembler-with-cpp -I $(BUILD_DIR) -DNDEBUG -D__NEWLIB__ -mcpu=arm7tdmi -o "$@" "$<" 61 | @echo 'Finished building: $(COLOR_GREEN)$<$(COLOR_END)' 62 | @echo ' ' 63 | 64 | 65 | axf: $(OBJS) $(USER_OBJS) 66 | @echo 'Building target: $@' 67 | @echo 'Invoking: MCU Linker' 68 | $(CC) -nostdlib -Xlinker -Map="$(BUILD_DIR)$(BASE_NAME).map" -Xlinker --gc-sections -flto -Os -mcpu=arm7tdmi --specs=nano.specs -u _printf_float -u _scanf_float -T "$(BASE_NAME).ld" -o "$(TARGET)" $(OBJS) $(USER_OBJS) $(LIBS) 69 | @echo 'Finished building target: $(COLOR_GREEN)$@$(COLOR_END)' 70 | @echo ' ' 71 | $(MAKE) --no-print-directory post-build 72 | 73 | clean: 74 | -$(RM) $(BUILD_DIR) 75 | -@echo ' ' 76 | 77 | post-build: 78 | -@echo 'Performing post-build steps' 79 | -arm-none-eabi-gcc --version 80 | -arm-none-eabi-size "$(TARGET)"; 81 | -arm-none-eabi-objcopy -v -O ihex "$(TARGET)" "$(BUILD_DIR)$(BASE_NAME).hex" 82 | -@echo ' ' 83 | 84 | lpc21isp: $(BUILD_DIR)tag 85 | -@echo '' 86 | -@echo 'Downloading lpc21isp 1.97 source from sourceforge' 87 | wget http://sourceforge.net/projects/lpc21isp/files/lpc21isp/1.97/lpc21isp_197.zip/download -O $(BUILD_DIR)lpc21isp.zip 88 | unzip -qq -o $(BUILD_DIR)lpc21isp.zip -d $(BUILD_DIR) 89 | 90 | -@echo 'Making lpc21isp' 91 | $(MAKE) -C $(BUILD_DIR)lpc21isp_197/ 92 | -@echo 'Copy lpc21isp binary to current directory' 93 | cp $(BUILD_DIR)lpc21isp_197/lpc21isp . 94 | -@echo '' 95 | 96 | flash: axf lpc21isp 97 | @echo '' 98 | @echo 'Flashing $(COLOR_GREEN)$(BASE_NAME).hex$(COLOR_END) to $(COLOR_RED)$(FLASH_TTY)$(COLOR_END)' 99 | $(FLASH_TOOL) "$(BUILD_DIR)$(BASE_NAME).hex" $(FLASH_TTY) $(FLASH_BAUD) $(MCU_CLOCK) 100 | 101 | .PHONY: clean dependents 102 | .SECONDARY: post-build 103 | 104 | -include ../makefile.targets 105 | -------------------------------------------------------------------------------- /src/sched.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sched.c - Simple scheduling for T-962 reflow controller 3 | * 4 | * Copyright (C) 2011,2012,2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include "sched.h" 24 | 25 | // Not a public struct - therefore it's defined here 26 | typedef struct SchedItem { 27 | uint32_t dueTicks; 28 | SchedCall_t workFunc; 29 | uint8_t enabled; 30 | } SchedItem_t; 31 | 32 | static SchedItem_t tasks[SCHED_NUM_ITEMS]; 33 | 34 | void Sched_Init(void) { 35 | T0CTCR = 0; // Normal timer mode 36 | T0PR = TIMER_PRESCALER - 1; // Prescaler divisor, timer now ticks in usecs/8 (-1.25% off as clk is 55.296MHz) 37 | T0TCR = 0x01; // Enable timer 38 | T0MCR = 0x01; // Interrupt on MCR0 match 39 | } 40 | 41 | uint32_t Sched_GetTick(void) { 42 | return T0TC; 43 | } 44 | 45 | // The enable is atomic but the dueTicks are not, so only call this with 0 or 2 as enable parameter 46 | // (2 will force scheduling as soon as possible without relying on dueTicks to be correct) 47 | void Sched_SetState(Task_t tasknum, uint8_t enable, int32_t future) { 48 | if (enable == 1) { 49 | tasks[tasknum].dueTicks = future; 50 | } 51 | tasks[tasknum].enabled = enable; 52 | } 53 | 54 | uint8_t Sched_IsOverride(void) { 55 | uint8_t retval = 0; // No override by default 56 | for (uint8_t lp = 0; lp < SCHED_NUM_ITEMS; lp++) { 57 | if (tasks[lp].enabled == 2) { 58 | retval = 1; 59 | //if(SelectiveDebugIsEnabled(SD_SCHED_OVERRIDE)) wjprintf_P(PSTR("\nTask 0x%x overrides sleep!"), lp); 60 | } 61 | } 62 | return retval; 63 | } 64 | 65 | void Sched_SetWorkfunc(Task_t tasknum, SchedCall_t func) { 66 | tasks[tasknum].workFunc = func; 67 | } 68 | 69 | int32_t Sched_Do(uint32_t fastforward) { 70 | static uint32_t oldTick = 0; 71 | int32_t shortestwait = 0x7fffffff; 72 | uint32_t curTick = Sched_GetTick(); 73 | 74 | // How many ticks will we should roll forward (including sleep time) 75 | uint32_t numRollFwd = (curTick - oldTick) + fastforward; 76 | oldTick = curTick; 77 | 78 | for (uint8_t lp = 0; lp < SCHED_NUM_ITEMS; lp++) { 79 | if (tasks[lp].enabled >= 1) { // Only deal with enabled tasks 80 | uint32_t tmp = tasks[lp].dueTicks; 81 | if ((tasks[lp].enabled == 2) || (tmp <= numRollFwd)) { // Time to execute this task 82 | int32_t nextdelta = tasks[lp].workFunc(); // Call the scheduled work 83 | if (nextdelta >= 0) { // Re-arm 84 | tmp = nextdelta; 85 | tasks[lp].enabled = 1; 86 | } else { // Putting task to sleep until awakened by Sched_SetState 87 | tasks[lp].enabled = 0; 88 | } 89 | } else { 90 | tmp -= numRollFwd; 91 | } 92 | tasks[lp].dueTicks = tmp; 93 | if (tmp < shortestwait) { 94 | shortestwait = tmp; 95 | } 96 | } 97 | } 98 | // Unless a (wake-up) interrupt calls Sched_SetState, this is how 99 | // long it's OK to sleep until next task is due 100 | return shortestwait; 101 | } 102 | 103 | void BusyWait( uint32_t numticks ) { 104 | T0IR = 0x01; // Reset interrupt 105 | T0MR0 = 1 + T0TC + numticks; // It's perfectly fine if this wraps 106 | while (!(T0IR & 0x01)); // Wait for match 107 | } 108 | -------------------------------------------------------------------------------- /src/t962.h: -------------------------------------------------------------------------------- 1 | #ifndef T962_H_ 2 | #define T962_H_ 3 | 4 | /* Hardware notes and pin mapping: 5 | * 6 | * LCD is KS0108 compatible, with two chip selects, active high 7 | * EEPROM is an AT24C02C with 2kbits capacity (256x8) 8 | * XTAL frequency is 11.0592MHz 9 | * 10 | * GPIO0 port 11 | * 0.0 ISP/Console UART TX output 12 | * 0.1 ISP/Console UART RX input 13 | * 0.2 EEPROM SCL line (SCL0) w/ 4k7 pullup 14 | * 0.3 EEPROM SDA line (SDA0) w/ 4k7 pullup 15 | * 0.4 F4 button (active low, with pullup) 16 | * 0.5 ? 17 | * 0.6 ? 18 | * 0.7 Used for 1-wire cold-junction DS18B20 temperature sensor retrofit 19 | * 0.8 Fan output (active low) PWM4 20 | * 0.9 Heater output (active low) PWM6 21 | * 0.10 ? 22 | * 0.11 LCD backlight control output (active high) 23 | * 0.12 LCD CS2 output (right side of display) 24 | * 0.13 LCD CS1 output (left side of display) 25 | * 0.14 Enter ISP mode if low during reset (4k7 pullup) 26 | * 0.15 F2 button (active low, with pullup) 27 | * 0.16 F3 button (active low, with pullup) 28 | * 0.17 ? 29 | * 0.18 LCD E(nable) output (data latched on falling edge) 30 | * 0.19 LCD RW output (0=write, 1=read) 31 | * 0.20 S button (active low, with pullup) 32 | * 0.21 Buzzer output (active high) PWM5 33 | * 0.22 LCD RS output (0=cmd, 1=data) 34 | * 0.23 F1 button (active low, with pullup) VBUS input as alt function on LPC214x 35 | * 0.24 (No pin) 36 | * 0.25 AD0.4 accessible on test point marked ADO - DAC output as alt function (now used for sysfan control) 37 | * 0.26 ? USB D+ on LPC214x-series of chips 38 | * 0.27 ? USB D- on LPC214x-series of chips 39 | * 0.28 Temperature sensor input 1 (AD0.1), this was the left-hand one in our oven 40 | * 0.29 Temperature sensor input 2 (AD0.2), this was the right-hand one in our oven 41 | * 0.30 ? 42 | * 0.31 Red LED on board (active low) 43 | * 44 | * GPIO1 port 45 | * 1.16-1.23 LCD D0-D7 I/O 46 | * 1.24 ? 47 | * 1.25 ? 48 | * 1.26 ? 49 | * 1.27 ? 50 | * 1.28 ? 51 | * 1.29 ? 52 | * 1.30 ? 53 | * 1.31 ? 54 | */ 55 | 56 | /* EEPROM contents: 57 | 58 | First two bytes and last two bytes needs to be 0x56, 0x57, 0x58 and 0x59 59 | respectively for original software to recognize the profile. 60 | Profile then is stored as 48 setpoint temperatures in Celsius in big-endian 16-bit words, 61 | spaced 10 seconds apart for a run-time of up to 470s (in original SW they're spaced 62 | 15 seconds apart due to the slowdown of the RTC clock though). Unused space from address 63 | 0x62 to 0x7d (28 bytes) in each profile. 64 | 65 | Wave7 66 | 0x56 0x57 0x00 0x14 0x00 0x23 0x00 0x2c 0x00 0x39 0x00 0x42 0x00 0x49 0x00 0x52 67 | 0x00 0x5b 0x00 0x64 0x00 0x67 0x00 0x6a 0x00 0x73 0x00 0x78 0x00 0x7b 0x00 0x82 68 | 0x00 0x8d 0x00 0x96 0x00 0x8d 0x00 0x82 0x00 0x79 0x00 0x6a 0x00 0x63 0x00 0x50 69 | 0x00 0x49 0x00 0x3c 0x00 0x35 0x00 0x2c 0x00 0x21 0x00 0x14 0x00 0x14 0x00 0x14 70 | 0x00 0x14 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x41 0x00 0x00 71 | 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x41 0x00 0x00 0x00 0x00 0x00 0x00 72 | 0x00 0x00 0x00 0x00 0x31 0x6a 0x00 0x00 0x3b 0x5d 0x00 0x00 0x00 0x11 0x00 0x00 73 | 0x07 0x6f 0x00 0x00 0x40 0x00 0x00 0x00 0x06 0x00 0x00 0x00 0x06 0x00 0x58 0x59 74 | 75 | Wave8 76 | 0x56 0x57 0x00 0x14 0x00 0x23 0x00 0x32 0x00 0x41 0x00 0x50 0x00 0x5f 0x00 0x6e 77 | 0x00 0x7d 0x00 0x8c 0x00 0x9b 0x00 0x9b 0x00 0x9b 0x00 0xa5 0x00 0xa5 0x00 0xa5 78 | 0x00 0xa5 0x00 0xa5 0x00 0xa5 0x00 0xaa 0x00 0xb9 0x00 0xc8 0x00 0xd7 0x00 0xe6 79 | 0x00 0xf5 0x00 0xf8 0x00 0xf5 0x00 0xe6 0x00 0xd7 0x00 0xc8 0x00 0xb9 0x00 0xaa 80 | 0x00 0x9b 0x00 0x8c 0x00 0x7d 0x00 0x6e 0x00 0x5f 0x00 0x50 0x00 0x41 0x00 0x00 81 | 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 82 | 0x00 0x00 0x00 0x00 0x31 0x6a 0x00 0x00 0x3b 0x5d 0x00 0x00 0x00 0x11 0x00 0x00 83 | 0x07 0x6f 0x00 0x00 0x40 0x00 0x00 0x00 0x07 0x00 0x00 0x00 0x07 0x00 0x58 0x59 84 | 85 | */ 86 | 87 | #define PCLKFREQ (5 * 11059200) 88 | 89 | #endif /* T962_H_ */ 90 | -------------------------------------------------------------------------------- /src/max31855.c: -------------------------------------------------------------------------------- 1 | /* 2 | * max31855.c - SPI TC interface handling for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "sc18is602b.h" 25 | #include "sched.h" 26 | 27 | #define INVALID_VALUE (999.0f) 28 | 29 | #define MAX_SPI_DEVICES (4) 30 | static int16_t spidevreadout[MAX_SPI_DEVICES]; // Keeps last readout from each device 31 | static int16_t spiextrareadout[MAX_SPI_DEVICES]; // Keeps last readout from each device 32 | static int numspidevices = 0; 33 | 34 | static int32_t SPI_TC_Work( void ) { 35 | for (int i = 0; i < numspidevices; i++) { 36 | SPIxfer_t xfer; 37 | xfer.ssmask = 1 << i; 38 | xfer.len = 4; // 32 bits from TC interface 39 | 40 | // Doesn't matter what data contains when sending, MOSI not connected 41 | if (SC18IS602B_SPI_Xfer(&xfer) == 0) { 42 | int16_t tmp = xfer.data[0] << 8 | xfer.data[1]; 43 | spidevreadout[i] = tmp; 44 | 45 | tmp = xfer.data[2] << 8 | xfer.data[3]; 46 | spiextrareadout[i] = tmp; 47 | } 48 | } 49 | return TICKS_MS(100); 50 | } 51 | 52 | uint32_t SPI_TC_Init(void) { 53 | printf("\n%s called", __FUNCTION__); 54 | Sched_SetWorkfunc(SPI_TC_WORK, SPI_TC_Work); 55 | 56 | for (int i = 0; i < MAX_SPI_DEVICES; i++) { 57 | spidevreadout[i] = -1; // Assume we don't find any thermocouple interfaces 58 | spiextrareadout[i] = -1; 59 | } 60 | 61 | // Only continue of we find the I2C to SPI bridge chip 62 | if (SC18IS602B_Init(SPICLK_1843KHZ, SPIMODE_0, SPIORDER_MSBFIRST) >= 0) { 63 | printf("\nProbing for MAX31855 devices..."); 64 | 65 | // Assume all devices are present for SPI_TC_Work 66 | numspidevices = MAX_SPI_DEVICES; 67 | 68 | // Run one iteration to update all data 69 | SPI_TC_Work(); 70 | 71 | // And reset it afterwards 72 | numspidevices = 0; 73 | 74 | for (int i = 0; i < MAX_SPI_DEVICES; i++) { 75 | if ((spidevreadout[i] == -1 && spiextrareadout[i] == -1) || 76 | (spidevreadout[i] == 0 && spiextrareadout[i] == 0)) { 77 | //printf(" Unknown/Invalid/Absent device"); 78 | } else { 79 | printf("\nSS%x: [SPI Thermocouple interface]", i); 80 | // A bit of a hack as it assumes all earlier devices are present 81 | numspidevices = i + 1; 82 | } 83 | } 84 | 85 | if (numspidevices) { 86 | // Enable SPI TC task if there's at least one device 87 | Sched_SetState(SPI_TC_WORK, 2, 0); 88 | } else { 89 | printf(" No MAX31855 devices found!"); 90 | } 91 | } 92 | return numspidevices; 93 | } 94 | 95 | int SPI_IsTCPresent(uint8_t tcid) { 96 | if (tcid < numspidevices) { 97 | if (!(spidevreadout[tcid] & 0x01)) { 98 | // A faulty/not connected TC will not be flagged as present 99 | return 1; 100 | } 101 | } 102 | return 0; 103 | } 104 | 105 | float SPI_GetTCReading(uint8_t tcid) { 106 | // Report 0C for missing sensors 107 | float retval = 0.0f; 108 | if (tcid < numspidevices) { 109 | if (spidevreadout[tcid] & 0x01) { // Fault detected 110 | retval = INVALID_VALUE; 111 | } else { 112 | retval = (float)(spidevreadout[tcid] & 0xfffc); // Mask reserved bit 113 | retval /= 16; 114 | } 115 | } 116 | return retval; 117 | } 118 | 119 | float SPI_GetTCColdReading(uint8_t tcid) { 120 | // Report 0C for missing sensors 121 | float retval = 0.0f; 122 | if (tcid < numspidevices) { 123 | if (spiextrareadout[tcid] & 0x07) { // Any fault detected 124 | retval = INVALID_VALUE; 125 | } else { 126 | retval = (float)(spiextrareadout[tcid] & 0xfff0); // Mask reserved/fault bits 127 | retval /= 256; 128 | } 129 | } 130 | return retval; 131 | } 132 | -------------------------------------------------------------------------------- /src/serial.c: -------------------------------------------------------------------------------- 1 | /* 2 | * serial.c - UART handling for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include "vic.h" 24 | #include "circbuffer.h" 25 | #include "serial.h" 26 | 27 | #ifdef __NEWLIB__ 28 | #define __sys_write _write 29 | #endif 30 | 31 | /* The following baud rates assume a 55.296MHz system clock */ 32 | #ifdef SERIAL_2MBs 33 | /* Settings for 2Mb/s */ 34 | #define BAUD_M 11 35 | #define BAUD_D 8 36 | /* Minimum allowed when running fractional brg is 3 according to UM10120 but 37 | * this works just fine! */ 38 | #define BAUD_DL 1 39 | #else 40 | /* Settings for 115kbps */ 41 | #define BAUD_M 1 42 | #define BAUD_D 0 43 | #define BAUD_DL 30 44 | #endif 45 | 46 | /* UART Buffers */ 47 | static tcirc_buf txbuf; 48 | static tcirc_buf rxbuf; 49 | 50 | static void uart_putc(char thebyte) { 51 | if (thebyte == '\n') 52 | uart_putc('\r'); 53 | 54 | /* The following is done blocking. This means when you call printf() with lots of data, 55 | * it relies on the ability of the interrupt to drain the txbuf, otherwise the system 56 | * will lock up. 57 | */ 58 | if (!VIC_IsIRQDisabled()){ 59 | add_to_circ_buf(&txbuf, thebyte, 1); 60 | } else { 61 | add_to_circ_buf(&txbuf, thebyte, 0); 62 | } 63 | 64 | // If interrupt is disabled, we need to start the process and enable the interrupt 65 | if ((U0IER & (1<<1)) == 0) { 66 | U0THR = get_from_circ_buf(&txbuf); 67 | U0IER |= 1<<1; 68 | } 69 | } 70 | 71 | // Blindly read character, assuming we knew one was available 72 | char uart_readc(void) { 73 | return get_from_circ_buf(&rxbuf); 74 | } 75 | 76 | int uart_isrxready(void){ 77 | return circ_buf_has_char(&rxbuf); 78 | } 79 | 80 | int uart_readline(char* buffer, int max_len) { 81 | int i = 0; 82 | while (uart_isrxready()) { 83 | buffer[i] = uart_readc(); 84 | if (buffer[i] == '\n' || i >= max_len) { 85 | break; 86 | } 87 | i++; 88 | } 89 | 90 | buffer[i] = '\0'; 91 | return i; 92 | } 93 | 94 | // Override __sys_write so we actually can do printf 95 | int __sys_write(int hnd, char *buf, int len) { 96 | int foo = len; 97 | while(foo--) uart_putc(*buf++); 98 | return (len); 99 | } 100 | 101 | static void __attribute__ ((interrupt ("IRQ"))) Serial_IRQHandler( void ) { 102 | // Figure out which interrupt that fired within the peripheral, and ACK it 103 | uint32_t intsrc = (U0IIR & 0b0001110); 104 | 105 | // THRE Interrupt 106 | if (intsrc == 0b00000010) { 107 | //Check if data to TX still 108 | if (circ_buf_has_char(&txbuf)){ 109 | //Still data to transmit - write to THR 110 | U0THR = get_from_circ_buf(&txbuf); 111 | } else { 112 | //No more data - disable future THRE interrupts 113 | U0IER &= ~(1<<1); 114 | } 115 | } 116 | 117 | // RDA Interrupt 118 | if (intsrc == 0b00000100) { 119 | //Don't block, as we are inside an interrupt! 120 | add_to_circ_buf(&rxbuf, U0RBR, 0); 121 | } 122 | 123 | // ACK IRQ with VIC as the last thing 124 | VICVectAddr = 0; 125 | } 126 | 127 | void Serial_Init(void) { 128 | // Pin select config already done in IO init 129 | 130 | // Setup buffers 131 | init_circ_buf(&txbuf); 132 | init_circ_buf(&rxbuf); 133 | 134 | // Baud rate defined above 135 | U0FCR = 7; // Enable and reset FIFOs 136 | U0FDR = ((BAUD_M)<<4) | ((BAUD_D)<<0); 137 | U0LCR = 0x83; // 8N1 + enable divisor loading 138 | U0DLL = BAUD_DL & 0xff; 139 | U0DLM = BAUD_DL >> 8; 140 | U0LCR &= ~0x80; // Divisor load done 141 | #ifdef __NEWLIB__ 142 | setbuf(stdout, NULL); // Needed to get rid of default line-buffering in newlib not present in redlib 143 | #endif 144 | 145 | VIC_RegisterHandler( VIC_UART0, Serial_IRQHandler ); 146 | VIC_EnableHandler( VIC_UART0 ); 147 | 148 | // Enable RX interrupt 149 | U0IER |= 1<<0; 150 | } 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | T-962 reflow oven improvements 2 | ============================== 3 | Custom firmware for the cheap T-962 reflow oven utilizing the _existing_ controller hardware. 4 | 5 | - [Wiki] for more info 6 | - [Hackaday post] 7 | - We have [Travis-CI] in place to build pull requests 8 | 9 | ### Introduction 10 | 11 | As we had use for a small reflow oven for a small prototype run we settled for the `T-962` even after having seen the negative reviews of it as there were plenty of suggestions all across the Internet on how it could be improved including replacing the existing controller and display(!). After having had a closer look at the hardware (replacing the masking tape inside with Kapton tape first) it was obvious that there was a simple way to improve the software disaster that is the T-962. 12 | 13 | 14 | ### Hardware improvements 15 | 16 | Here are a few improvements made to the cheap T-962 reflow oven utilizing the _existing_ controller hardware with only a small, cheap, but very necessary modification. As you have to open the top part of the oven anyway to reflash the software this is a no-brainer fix: 17 | 18 | #### Replace stinky masking tape 19 | 20 | Instructable suggesting [replacing masking tape with kapton tape](http://www.instructables.com/id/T962A-SMD-Reflow-Oven-FixHack/?ALLSTEPS). 21 | 22 | #### Cold junction compensation 23 | 24 | The existing controller makes the assumption that the cold-junction is at 20 degrees Celsius at all times which made keeping a constant temperature "a bit" challenging as the terminal block sits _on_top_of_an_oven_ with two TRIACs nearby. 25 | We can fix this by adding a temperature sensor to the connector block where the thermocouples are connected to the controller board. 26 | It turns out that both an analog input and at least one generic GPIO pin is available on unpopulated pads on the board. GPIO0.7 in particular was very convenient for 1-wire operation as there was an adjacent pad with 3.3V so a 4k7 pull-up resistor could be placed there, then a jumper wire is run from GPIO0.7 pad to the `Dq` pin of a cheap [DS18B20] 1-wire temperature sensor that gets epoxied to the terminal block, soldering both `Vcc` and ground pins to the ground plane conveniently located right next to it. Some hot-glue may have to be removed to actually get to the side of the connector and the ground plane, someone seems to have been really trigger-happy with the glue gun! 27 | 28 | [Wiki: cold junction compensation mod](https://github.com/UnifiedEngineering/T-962-improvements/wiki) 29 | 30 | 31 | #### Check mains earth connection 32 | 33 | As mentioned elsewhere, make sure the protective earth/ground wire from the main input actually makes contact with the back panel of the chassis and also that the back panel makes contact both with the top and bottom halves of the oven! 34 | 35 | #### System fan PWM control 36 | 37 | The system fan is very noisy an can be turned of most of the time. The custom firmware uses spare `ADO` test point to control it. 38 | 39 | [Wiki: system fan PWM mod](https://github.com/UnifiedEngineering/T-962-improvements/wiki/System-fan-control) 40 | 41 | ### New firmware 42 | 43 | The firmware was originally built with LPCXpresso 7.5.0 as I've never dealt with the LPC2000-series NXP microcontrollers before so I just wanted something that wouldn't require TOO much of work to actually produce a flashable image. Philips LPC2000 Flash Utility v2.2.3 was used to flash the controller through the ISP header present on the board. 44 | 45 | LPCXpresso requires activation but is free for everything but large code sizes (the limit is larger than the 128kB flash size on this controller anyway so it's not really an issue). The flash utility unfortunately only runs on Windows but Flash Magic is an alternative (see Wiki for more flashing instructions). 46 | 47 | With help from the community the project now also builds standalone using the standard `gcc-arm-none-eabi` toolchain, see `COMPILING.md` for more information. 48 | 49 | The MCU in this particular oven is an LPC2134/01 with 128kB flash/16kB RAM, stated to be capable of running at up to 60MHz. Unfortunately the PLL in this chip is not that clever so with the supplied XTAL at 11.0592MHz we can only reach 55.296MHz (5x multiplier). Other variants exist, the [Wiki] has more information about this. 50 | 51 | wiki: [Flashing firmware] 52 | 53 | ### Contributing 54 | This is mainly tested on a fairly recent build of the T-962 (smallest version), build time on the back panel states 14.07 which I assume means 2014 July (or less likely week 7 of 2014), success/failure reports from other users are welcome! 55 | 56 | This is very much a quick hack to get only the basic functionality needed up and running. Everything in here is released under the GPLv3 license in hopes that it might be interesting for others to improve on this. Feedback is welcome! 57 | 58 | Happy hacking! 59 | 60 | # Acknowledgements 61 | This project is using the [C PID Library - Version 1.0.1, GPLv3] 62 | 63 | [wiki]: https://github.com/UnifiedEngineering/T-962-improvements/wiki 64 | [Travis-CI]: https://travis-ci.org/UnifiedEngineering/T-962-improvements 65 | [Flashing firmware]: https://github.com/UnifiedEngineering/T-962-improvements/wiki/Flashing-the-LPC21xx-controller 66 | [DS18B20]: http://datasheets.maximintegrated.com/en/ds/DS18B20.pdf 67 | [hackaday post]: http://hackaday.com/2014/11/27/improving-the-t-962-reflow-oven/ 68 | [C PID Library - Version 1.0.1, GPLv3]:https://github.com/mblythe86/C-PID-Library 69 | -------------------------------------------------------------------------------- /src/sensor.c: -------------------------------------------------------------------------------- 1 | 2 | #include "LPC214x.h" 3 | #include 4 | #include 5 | #include "adc.h" 6 | #include "t962.h" 7 | #include "onewire.h" 8 | #include "max31855.h" 9 | #include "nvstorage.h" 10 | 11 | #include "sensor.h" 12 | 13 | /* 14 | * Normally the control input is the average of the first two TCs. 15 | * By defining this any TC that has a readout 5C (or more) higher 16 | * than the TC0 and TC1 average will be used as control input instead. 17 | * Use if you have very sensitive components. Note that this will also 18 | * kick in if the two sides of the oven has different readouts, as the 19 | * code treats all four TCs the same way. 20 | */ 21 | //#define MAXTEMPOVERRIDE 22 | 23 | // Gain adjust, this may have to be calibrated per device if factory trimmer adjustments are off 24 | static float adcgainadj[2]; 25 | // Offset adjust, this will definitely have to be calibrated per device 26 | static float adcoffsetadj[2]; 27 | 28 | static float temperature[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; 29 | static uint8_t tempvalid = 0; 30 | static uint8_t cjsensorpresent = 0; 31 | 32 | // The feedback temperature 33 | static float avgtemp; 34 | static float coldjunction; 35 | 36 | void Sensor_ValidateNV(void) { 37 | int temp; 38 | 39 | temp = NV_GetConfig(TC_LEFT_GAIN); 40 | if (temp == 255) { 41 | temp = 100; 42 | NV_SetConfig(TC_LEFT_GAIN, temp); // Default unity gain 43 | } 44 | adcgainadj[0] = ((float)temp) * 0.01f; 45 | 46 | temp = NV_GetConfig(TC_RIGHT_GAIN); 47 | if (temp == 255) { 48 | temp = 100; 49 | NV_SetConfig(TC_RIGHT_GAIN, temp); // Default unity gain 50 | } 51 | adcgainadj[1] = ((float)temp) * 0.01f; 52 | 53 | temp = NV_GetConfig(TC_LEFT_OFFSET); 54 | if (temp == 255) { 55 | temp = 100; 56 | NV_SetConfig(TC_LEFT_OFFSET, temp); // Default +/-0 offset 57 | } 58 | adcoffsetadj[0] = ((float)(temp - 100)) * 0.25f; 59 | 60 | temp = NV_GetConfig(TC_RIGHT_OFFSET); 61 | if (temp == 255) { 62 | temp = 100; 63 | NV_SetConfig(TC_RIGHT_OFFSET, temp); // Default +/-0 offset 64 | } 65 | adcoffsetadj[1] = ((float)(temp - 100)) * 0.25f; 66 | } 67 | 68 | 69 | void Sensor_DoConversion(void) { 70 | uint16_t temp[2]; 71 | /* 72 | * These are the temperature readings we get from the thermocouple interfaces 73 | * Right now it is assumed that if they are indeed present the first two 74 | * channels will be used as feedback 75 | */ 76 | float tctemp[4], tccj[4]; 77 | uint8_t tcpresent[4]; 78 | tempvalid = 0; // Assume no valid readings; 79 | for (int i = 0; i < 4; i++) { // Get 4 TC channels 80 | tcpresent[i] = OneWire_IsTCPresent(i); 81 | if (tcpresent[i]) { 82 | tctemp[i] = OneWire_GetTCReading(i); 83 | tccj[i] = OneWire_GetTCColdReading(i); 84 | if (i > 1) { 85 | temperature[i] = tctemp[i]; 86 | tempvalid |= (1 << i); 87 | } 88 | } else { 89 | tcpresent[i] = SPI_IsTCPresent(i); 90 | if (tcpresent[i]) { 91 | tctemp[i] = SPI_GetTCReading(i); 92 | tccj[i] = SPI_GetTCColdReading(i); 93 | if (i > 1) { 94 | temperature[i] = tctemp[i]; 95 | tempvalid |= (1 << i); 96 | } 97 | } 98 | } 99 | } 100 | 101 | // Assume no CJ sensor 102 | cjsensorpresent = 0; 103 | if (tcpresent[0] && tcpresent[1]) { 104 | avgtemp = (tctemp[0] + tctemp[1]) / 2.0f; 105 | temperature[0] = tctemp[0]; 106 | temperature[1] = tctemp[1]; 107 | tempvalid |= 0x03; 108 | coldjunction = (tccj[0] + tccj[1]) / 2.0f; 109 | cjsensorpresent = 1; 110 | } else if (tcpresent[2] && tcpresent[3]) { 111 | avgtemp = (tctemp[2] + tctemp[3]) / 2.0f; 112 | temperature[0] = tctemp[2]; 113 | temperature[1] = tctemp[3]; 114 | tempvalid |= 0x03; 115 | tempvalid &= ~0x0C; 116 | coldjunction = (tccj[2] + tccj[3]) / 2.0f; 117 | cjsensorpresent = 1; 118 | } else { 119 | // If the external TC interface is not present we fall back to the 120 | // built-in ADC, with or without compensation 121 | coldjunction = OneWire_GetTempSensorReading(); 122 | if (coldjunction < 127.0f) { 123 | cjsensorpresent = 1; 124 | } else { 125 | coldjunction = 25.0f; // Assume 25C ambient if not found 126 | } 127 | temp[0] = ADC_Read(1); 128 | temp[1] = ADC_Read(2); 129 | 130 | // ADC oversamples to supply 4 additional bits of resolution 131 | temperature[0] = ((float)temp[0]) / 16.0f; 132 | temperature[1] = ((float)temp[1]) / 16.0f; 133 | 134 | // Gain adjust 135 | temperature[0] *= adcgainadj[0]; 136 | temperature[1] *= adcgainadj[1]; 137 | 138 | // Offset adjust 139 | temperature[0] += coldjunction + adcoffsetadj[0]; 140 | temperature[1] += coldjunction + adcoffsetadj[1]; 141 | 142 | tempvalid |= 0x03; 143 | 144 | avgtemp = (temperature[0] + temperature[1]) / 2.0f; 145 | } 146 | 147 | #ifdef MAXTEMPOVERRIDE 148 | // If one of the temperature sensors reports higher than 5C above 149 | // the average, use that as control input 150 | float newtemp = avgtemp; 151 | for (int i=0; i < 4; i++) { 152 | if (tcpresent[i] && temperature[i] > (avgtemp + 5.0f) && temperature[i] > newtemp) { 153 | newtemp = temperature[i]; 154 | } 155 | } 156 | if (avgtemp != newtemp) { 157 | avgtemp = newtemp; 158 | } 159 | #endif 160 | } 161 | 162 | uint8_t Sensor_ColdjunctionPresent(void) { 163 | return cjsensorpresent; 164 | } 165 | 166 | 167 | float Sensor_GetTemp(TempSensor_t sensor) { 168 | if (sensor == TC_COLD_JUNCTION) { 169 | return coldjunction; 170 | } else if(sensor == TC_AVERAGE) { 171 | return avgtemp; 172 | } else if(sensor < TC_NUM_ITEMS) { 173 | return temperature[sensor - TC_LEFT]; 174 | } else { 175 | return 0.0f; 176 | } 177 | } 178 | 179 | uint8_t Sensor_IsValid(TempSensor_t sensor) { 180 | if (sensor == TC_COLD_JUNCTION) { 181 | return cjsensorpresent; 182 | } else if(sensor == TC_AVERAGE) { 183 | return 1; 184 | } else if(sensor >= TC_NUM_ITEMS) { 185 | return 0; 186 | } 187 | return (tempvalid & (1 << (sensor - TC_LEFT))) ? 1 : 0; 188 | } 189 | 190 | void Sensor_ListAll(void) { 191 | int count = 5; 192 | char* names[] = {"Left", "Right", "Extra 1", "Extra 2", "Cold junction"}; 193 | TempSensor_t sensors[] = {TC_LEFT, TC_RIGHT, TC_EXTRA1, TC_EXTRA2, TC_COLD_JUNCTION}; 194 | char* format = "\n%13s: %4.1fdegC"; 195 | 196 | for (int i = 0; i < count; i++) { 197 | if (Sensor_IsValid(sensors[i])) { 198 | printf(format, names[i], Sensor_GetTemp(sensors[i])); 199 | } 200 | } 201 | if (!Sensor_IsValid(TC_COLD_JUNCTION)) { 202 | printf("\nNo cold-junction sensor on PCB"); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/io.c: -------------------------------------------------------------------------------- 1 | /* 2 | * io.c - I/O handling for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include "t962.h" 24 | #include "io.h" 25 | #include "sched.h" 26 | #include "vic.h" 27 | 28 | void Set_Heater(uint8_t enable) { 29 | if (enable < 0xff) { 30 | PINSEL0 |= (2<<18); // Make sure PWM6 function is enabled 31 | } else { // Fully on is dealt with separately to avoid output glitch 32 | PINSEL0 &= ~(2<<18); // Disable PWM6 function on pin 33 | enable = 0xfe; // Not fully on according to PWM hardware but we force GPIO low anyway 34 | } 35 | PWMMR6 = 0xff - enable; 36 | PWMLER |= (1<<6); 37 | } 38 | 39 | void Set_Fan(uint8_t enable) { 40 | if (enable < 0xff) { 41 | PINSEL0 |= (2<<16); // Make sure PWM4 function is enabled 42 | } else { // Fully on is dealt with separately to avoid output glitch 43 | PINSEL0 &= ~(2<<16); // Disable PWM4 function on pin 44 | enable = 0xfe; // Not fully on according to PWM hardware but we force GPIO low anyway 45 | } 46 | PWMMR4 = 0xff - enable; 47 | PWMLER |= (1<<4); 48 | } 49 | 50 | static int32_t Sleep_Work(void) { 51 | // FIO0PIN ^= (1<<31); // Toggle debug LED 52 | // For some reason P0.31 status cannot be read out, so the following is used instead: 53 | static uint8_t flip = 0; 54 | if (flip) { 55 | FIO0SET = (1<<31); 56 | } else { 57 | FIO0CLR = (1<<31); 58 | } 59 | flip ^= 1; 60 | 61 | // If interrupts are used they must be disabled around the following two instructions! 62 | uint32_t save = VIC_DisableIRQ(); 63 | WDFEED = 0xaa; // Feed watchdog 64 | WDFEED = 0x55; 65 | VIC_RestoreIRQ(save); 66 | return TICKS_SECS(1); 67 | } 68 | 69 | void IO_InitWatchdog(void) { 70 | // Setup watchdog 71 | // Some margin (PCLKFREQ/4 would be exactly the period the WD is fed by sleep_work) 72 | WDTC = PCLKFREQ / 3; 73 | WDMOD = 0x03; // Enable 74 | WDFEED = 0xaa; 75 | WDFEED = 0x55; 76 | } 77 | 78 | void IO_PrintResetReason(void) { 79 | uint8_t resetreason = RSIR; 80 | RSIR = 0x0f; // Clear it out 81 | printf( 82 | "\nReset reason(s): %s%s%s%s", 83 | (resetreason & (1 << 0)) ? "[POR]" : "", 84 | (resetreason & (1 << 1)) ? "[EXTR]" : "", 85 | (resetreason & (1 << 2)) ? "[WDTR]" : "", 86 | (resetreason & (1 << 3)) ? "[BODR]" : ""); 87 | } 88 | 89 | 90 | // Support for boot ROM functions (get part number etc) 91 | typedef void (*IAP)(unsigned int [], unsigned int[]); 92 | static IAP iap_entry = (void*)0x7ffffff1; 93 | 94 | static partmapStruct partmap[] = { 95 | {"LPC2131(/01)", 0x0002ff01}, // Probably pointless but present for completeness (32kB flash is too small for factory image) 96 | {"LPC2132(/01)", 0x0002ff11}, 97 | {"LPC2134(/01)", 0x0002ff12}, 98 | {"LPC2136(/01)", 0x0002ff23}, 99 | {"LPC2138(/01)", 0x0002ff25}, 100 | 101 | {"LPC2141", 0x0402ff01}, // Probably pointless but present for completeness (32kB flash is too small for factory image) 102 | {"LPC2142", 0x0402ff11}, 103 | {"LPC2144", 0x0402ff12}, 104 | {"LPC2146", 0x0402ff23}, 105 | {"LPC2148", 0x0402ff25}, 106 | }; 107 | #define NUM_PARTS (sizeof(partmap) / sizeof(partmap[0])) 108 | 109 | static uint32_t command[1]; 110 | static uint32_t result[3]; 111 | 112 | int IO_Partinfo(char* buf, int n, char* format) { 113 | uint32_t partrev; 114 | 115 | // Request part number 116 | command[0] = IAP_READ_PART; 117 | iap_entry((void *)command, (void *)result); 118 | const char* partstrptr = NULL; 119 | for (int i = 0; i < NUM_PARTS; i++) { 120 | if (result[1] == partmap[i].id) { 121 | partstrptr = partmap[i].name; 122 | break; 123 | } 124 | } 125 | 126 | // Read part revision 127 | partrev = *(uint8_t*)PART_REV_ADDR; 128 | if (partrev == 0 || partrev > 0x1a) { 129 | partrev = '-'; 130 | } else { 131 | partrev += 'A' - 1; 132 | } 133 | return snprintf(buf, n, format, partstrptr, (int)partrev); 134 | } 135 | 136 | void IO_JumpBootloader(void) { 137 | /* Hold F1-Key at boot to force ISP mode */ 138 | if ((IOPIN0 & (1 << 23)) == 0) { 139 | // NB: If you want to call this later need to set a bunch of registers back 140 | // to reset state. Haven't fully figured this out yet, might want to 141 | // progmatically call bootloader, not sure. If calling later be sure 142 | // to crank up watchdog time-out, as it's impossible to disable 143 | // 144 | // Bootloader must use legacy mode IO if you call this later too, so do: 145 | // SCS = 0; 146 | 147 | // Turn off FAN & Heater using legacy registers so they stay off during bootloader 148 | // Fan = PIN0.8 149 | // Heater = PIN0.9 150 | IODIR0 = (1 << 8) | (1 << 9); 151 | IOSET0 = (1 << 8) | (1 << 9); 152 | 153 | //Re-enter ISP Mode, this function will never return 154 | command[0] = IAP_REINVOKE_ISP; 155 | iap_entry((void *)command, (void *)result); 156 | } 157 | } 158 | 159 | void IO_Init(void) { 160 | SCS = 0b11; // Enable fast GPIO on both port 0 and 1 161 | 162 | PINSEL0 = 0b10100000000001010101; // PWM6 + PWM4 + I2C0 + UART0 163 | PINSEL1 = 0b00000101000000000000000000000000; // ADC0 1+2 164 | 165 | FIO0MASK = 0b01001101000000100000010001100000; // Mask out all unknown/unused pins 166 | FIO1MASK = 0b11111111000000001111111111111111; // Only LCD D0-D7 167 | 168 | FIO0DIR = 0b10000010011011000011101100000001; // Default output pins 169 | FIO1DIR = 0b00000000000000000000000000000000; 170 | 171 | FIO0PIN = 0x00; // Turn LED on and make PWM outputs active when in GPIO mode (to help 100% duty cycle issue) 172 | 173 | PWMPR = PCLKFREQ / (256 * 5); // Let's have the PWM perform 5 cycles per second with 8 bits of precision (way overkill) 174 | PWMMCR = (1<<1); // Reset TC on mr0 overflow (period time) 175 | PWMMR0 = 0xff; // Period time 176 | PWMLER = (1<<0); // Enable latch on mr0 (Do I really need to do this?) 177 | PWMPCR = (1<<12) | (1<<14); // Enable PWM4 and 6 178 | PWMTCR = (1<<3) | (1<<0); // Enable timer in PWM mode 179 | 180 | Sched_SetWorkfunc(SLEEP_WORK, Sleep_Work); 181 | Sched_SetState(SLEEP_WORK, 2, 0); 182 | } 183 | -------------------------------------------------------------------------------- /serial-control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Log the temperatures reported by the oven in a live plot and 5 | # in a CSV file. 6 | # 7 | # Requires 8 | # python 2.7 9 | # - pyserial (python-serial in ubuntu, pip install pyserial) 10 | # - matplotlib (python-matplotlib in ubuntu, pip install matplotlib) 11 | # 12 | 13 | import csv 14 | import datetime 15 | import matplotlib.pyplot as plt 16 | import matplotlib.gridspec as gridspec 17 | import serial 18 | import sys 19 | from time import time 20 | 21 | # settings 22 | # 23 | FIELD_NAMES = 'Time,Temp0,Temp1,Temp2,Temp3,Set,Actual,Heat,Fan,ColdJ,Mode' 24 | TTYs = ('/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyUSB2') 25 | BAUD_RATE = 115200 26 | 27 | logdir = 'logs/' 28 | 29 | MAX_X = 470 30 | MAX_Y_temperature = 300 31 | MAX_Y_pwm = 260 32 | # 33 | # end of settings 34 | 35 | def timestamp(dt=None): 36 | if dt is None: 37 | dt = datetime.datetime.now() 38 | 39 | return dt.strftime('%Y-%m-%d-%H%M%S') 40 | 41 | 42 | def logname(filetype, profile): 43 | return '%s%s-%s.%s' % ( 44 | logdir, 45 | timestamp(), 46 | profile.replace(' ', '_').replace('/', '_'), 47 | filetype 48 | ) 49 | 50 | 51 | def get_tty(): 52 | for devname in TTYs: 53 | try: 54 | port = serial.Serial(devname, baudrate=BAUD_RATE) 55 | print 'Using serial port %s' % port.name 56 | return port 57 | 58 | except: 59 | print 'Tried serial port %s, but failed.' % str(devname) 60 | pass 61 | 62 | return None 63 | 64 | 65 | class Line(object): 66 | def __init__(self, axis, key, label=None): 67 | self.xvalues = [] 68 | self.yvalues = [] 69 | 70 | self._key = key 71 | self._line, = axis.plot(self.xvalues, self.yvalues, label=label or key) 72 | 73 | def add(self, log): 74 | self.xvalues.append(log['Time']) 75 | self.yvalues.append(log[self._key]) 76 | 77 | self.update() 78 | 79 | def update(self): 80 | self._line.set_data(self.xvalues, self.yvalues) 81 | 82 | def clear(self): 83 | self.xvalues = [] 84 | self.yvalues = [] 85 | 86 | self.update() 87 | 88 | class Log(object): 89 | profile = '' 90 | last_action = None 91 | 92 | def __init__(self): 93 | self.init_plot() 94 | self.clear_logs() 95 | 96 | def clear_logs(self): 97 | self.raw_log = [] 98 | map(Line.clear, self.lines) 99 | self.mode = '' 100 | 101 | def init_plot(self): 102 | plt.ion() 103 | 104 | gs = gridspec.GridSpec(2, 1, height_ratios=(4, 1)) 105 | fig = plt.figure(figsize=(14, 10)) 106 | 107 | axis_upper = fig.add_subplot(gs[0]) 108 | axis_lower = fig.add_subplot(gs[1]) 109 | plt.subplots_adjust(hspace=0.05, top=0.95, bottom=0.05, left=0.05, right=0.95) 110 | 111 | # setup axis for upper graph (temperature values) 112 | axis_upper.set_ylabel(u'Temperature [°C]') 113 | axis_upper.set_xlim(0, MAX_X) 114 | axis_upper.set_xticklabels([]) 115 | axis_upper.set_ylim(0, MAX_Y_temperature) 116 | 117 | # setup axis for lower graph (PWM values) 118 | axis_lower.set_xlim(0, MAX_X) 119 | axis_lower.set_ylim(0, MAX_Y_pwm) 120 | axis_lower.set_ylabel('PWM value') 121 | axis_lower.set_xlabel('Time [s]') 122 | 123 | # select values to be plotted 124 | self.lines = [ 125 | Line(axis_upper, 'Actual'), 126 | Line(axis_upper, 'Temp0'), 127 | Line(axis_upper, 'Temp1'), 128 | Line(axis_upper, 'Set', u'Setpoint'), 129 | Line(axis_upper, 'ColdJ', u'Coldjunction'), 130 | # Line(axis_upper, 'Temp2'), 131 | # Line(axis_upper, 'Temp3'), 132 | 133 | Line(axis_lower, 'Fan'), 134 | Line(axis_lower, 'Heat', 'Heater') 135 | ] 136 | 137 | axis_upper.legend() 138 | axis_lower.legend() 139 | plt.draw() 140 | 141 | self.axis_upper = axis_upper 142 | self.axis_lower = axis_lower 143 | 144 | def save_logfiles(self): 145 | print 'Saved log in %s ' % logname('csv', self.profile) 146 | plt.savefig(logname('png', self.profile)) 147 | plt.savefig(logname('pdf', self.profile)) 148 | 149 | with open(logname('csv', self.profile), 'w+') as csvout: 150 | writer = csv.DictWriter(csvout, FIELD_NAMES.split(',')) 151 | writer.writeheader() 152 | 153 | for l in self.raw_log: 154 | writer.writerow(l) 155 | 156 | def parse(self, line): 157 | values = map(str.strip, line.split(',')) 158 | # Convert all values to float, except the mode 159 | values = map(float, values[0:-1]) + [values[-1], ] 160 | 161 | fields = FIELD_NAMES.split(',') 162 | if len(values) != len(fields): 163 | raise ValueError('Expected %d fields, found %d' % (len(fields), len(values))) 164 | 165 | return dict(zip(fields, values)) 166 | 167 | def process_log(self, logline): 168 | # ignore 'comments' 169 | if logline.startswith('#'): 170 | print logline 171 | return 172 | 173 | # parse Profile name 174 | if logline.startswith('Starting reflow with profile: '): 175 | self.profile = logline[30:].strip() 176 | return 177 | 178 | if logline.startswith('Selected profile'): 179 | self.profile = logline[20:].strip() 180 | return 181 | 182 | try: 183 | log = self.parse(logline) 184 | except ValueError, e: 185 | if len(logline) > 0: 186 | print '!!', logline 187 | return 188 | 189 | if 'Mode' in log: 190 | # clean up log before starting reflow 191 | if self.mode == 'STANDBY' and log['Mode'] in ('BAKE', 'REFLOW'): 192 | self.clear_logs() 193 | 194 | # save png graph an csv file when bake or reflow ends. 195 | if self.mode in ('BAKE', 'REFLOW') and log['Mode'] == 'STANDBY': 196 | self.save_logfiles() 197 | 198 | self.mode = log['Mode'] 199 | if log['Mode'] == 'BAKE': 200 | self.profile = 'bake' 201 | 202 | if log['Mode'] in ('REFLOW', 'BAKE'): 203 | self.last_action = time() 204 | self.axis_upper.set_title('Profile: %s Mode: %s ' % (self.profile, self.mode)) 205 | 206 | if 'Time' in log and log['Time'] != 0.0: 207 | if 'Actual' not in log: 208 | return 209 | 210 | # update all lines 211 | map(lambda x: x.add(log), self.lines) 212 | self.raw_log.append(log) 213 | 214 | # update view 215 | plt.draw() 216 | 217 | def isdone(self): 218 | return ( 219 | self.last_action is not None and 220 | time() - self.last_action > 5 221 | ) 222 | 223 | 224 | def loop_all_profiles(num_profiles=6): 225 | log = Log() 226 | 227 | with get_tty() as port: 228 | profile = 0 229 | def select_profile(profile): 230 | port.write('stop\n') 231 | port.write('select profile %d\n' % profile) 232 | port.write('reflow\n') 233 | 234 | select_profile(profile) 235 | 236 | while True: 237 | logline = port.readline().strip() 238 | 239 | if log.isdone(): 240 | log.last_action = None 241 | profile += 1 242 | if profile > 6: 243 | print 'Done.' 244 | sys.exit() 245 | select_profile(profile) 246 | 247 | log.process_log(logline) 248 | 249 | def logging_only(): 250 | log = Log() 251 | 252 | with get_tty() as port: 253 | while True: 254 | log.process_log(port.readline().strip()) 255 | 256 | if __name__ == '__main__': 257 | action = sys.argv[1] if len(sys.argv) > 1 else 'log' 258 | 259 | if action == 'log': 260 | print 'Logging reflow sessions...' 261 | logging_only() 262 | 263 | elif action == 'test': 264 | print 'Looping over all profiles' 265 | loop_all_profiles() 266 | else: 267 | print 'Unknown action', action 268 | -------------------------------------------------------------------------------- /src/reflow_profiles.c: -------------------------------------------------------------------------------- 1 | #include "LPC214x.h" 2 | #include 3 | #include 4 | #include "t962.h" 5 | #include "lcd.h" 6 | #include "nvstorage.h" 7 | #include "eeprom.h" 8 | #include "reflow.h" 9 | 10 | #include "reflow_profiles.h" 11 | 12 | #define RAMPTEST 13 | #define PIDTEST 14 | 15 | extern uint8_t graphbmp[]; 16 | 17 | // Amtech 4300 63Sn/37Pb leaded profile 18 | static const profile am4300profile = { 19 | "4300 63SN/37PB", { 20 | 50, 50, 50, 60, 73, 86,100,113,126,140,143,147,150,154,157,161, // 0-150s 21 | 164,168,171,175,179,183,195,207,215,207,195,183,168,154,140,125, // Adjust peak from 205 to 220C 22 | 111, 97, 82, 68, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 320-470s 23 | } 24 | }; 25 | 26 | // NC-31 low-temp lead-free profile 27 | static const profile nc31profile = { 28 | "NC-31 LOW-TEMP LF", { 29 | 50, 50, 50, 50, 55, 70, 85, 90, 95,100,102,105,107,110,112,115, // 0-150s 30 | 117,120,122,127,132,138,148,158,160,158,148,138,130,122,114,106, // Adjust peak from 158 to 165C 31 | 98, 90, 82, 74, 66, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 320-470s 32 | } 33 | }; 34 | 35 | // SynTECH-LF normal temp lead-free profile 36 | static const profile syntechlfprofile = { 37 | "AMTECH SYNTECH-LF", { 38 | 50, 50, 50, 50, 60, 70, 80, 90,100,110,120,130,140,149,158,166, // 0-150s 39 | 175,184,193,201,210,219,230,240,245,240,230,219,212,205,198,191, // Adjust peak from 230 to 249C 40 | 184,177,157,137,117, 97, 77, 57, 0, 0, 0, 0, 0, 0, 0, 0 // 320-470s 41 | } 42 | }; 43 | 44 | #ifdef RAMPTEST 45 | // Ramp speed test temp profile 46 | static const profile rampspeed_testprofile = { 47 | "RAMP SPEED TEST", { 48 | 50, 50, 50, 50,245,245,245,245,245,245,245,245,245,245,245,245, // 0-150s 49 | 245,245,245,245,245,245,245,245,245, 50, 50, 50, 50, 50, 50, 50, // 160-310s 50 | 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 0, 0, 0, 0, 0, 0 // 320-470s 51 | } 52 | }; 53 | #endif 54 | 55 | #ifdef PIDTEST 56 | // PID gain adjustment test profile (5% setpoint change) 57 | static const profile pidcontrol_testprofile = { 58 | "PID CONTROL TEST", { 59 | 171,171,171,171,171,171,171,171,171,171,171,171,171,171,171,171, // 0-150s 60 | 180,180,180,180,180,180,180,180,171,171,171,171,171,171,171,171, // 160-310s 61 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 320-470s 62 | } 63 | }; 64 | #endif 65 | 66 | // EEPROM profile 1 67 | static ramprofile ee1 = { "CUSTOM #1" }; 68 | 69 | // EEPROM profile 2 70 | static ramprofile ee2 = { "CUSTOM #2" }; 71 | 72 | static const profile* profiles[] = { 73 | &syntechlfprofile, 74 | &nc31profile, 75 | &am4300profile, 76 | #ifdef RAMPTEST 77 | &rampspeed_testprofile, 78 | #endif 79 | #ifdef PIDTEST 80 | &pidcontrol_testprofile, 81 | #endif 82 | (profile*) &ee1, 83 | (profile*) &ee2 84 | }; 85 | 86 | #define NUMPROFILES (sizeof(profiles) / sizeof(profiles[0])) 87 | 88 | // current profile index 89 | static uint8_t profileidx = 0; 90 | 91 | static void ByteswapTempProfile(uint16_t* buf) { 92 | for (int i = 0; i < NUMPROFILETEMPS; i++) { 93 | uint16_t word = buf[i]; 94 | buf[i] = word >> 8 | word << 8; 95 | } 96 | } 97 | 98 | void Reflow_LoadCustomProfiles(void) { 99 | EEPROM_Read((uint8_t*)ee1.temperatures, 2, 96); 100 | ByteswapTempProfile(ee1.temperatures); 101 | 102 | EEPROM_Read((uint8_t*)ee2.temperatures, 128 + 2, 96); 103 | ByteswapTempProfile(ee2.temperatures); 104 | } 105 | 106 | void Reflow_ValidateNV(void) { 107 | if (NV_GetConfig(REFLOW_BEEP_DONE_LEN) == 255) { 108 | // Default 1 second beep length 109 | NV_SetConfig(REFLOW_BEEP_DONE_LEN, 10); 110 | } 111 | 112 | if (NV_GetConfig(REFLOW_MIN_FAN_SPEED) == 255) { 113 | // Default fan speed is now 8 114 | NV_SetConfig(REFLOW_MIN_FAN_SPEED, 8); 115 | } 116 | 117 | if (NV_GetConfig(REFLOW_BAKE_SETPOINT_H) == 255 || NV_GetConfig(REFLOW_BAKE_SETPOINT_L) == 255) { 118 | NV_SetConfig(REFLOW_BAKE_SETPOINT_H, SETPOINT_DEFAULT >> 8); 119 | NV_SetConfig(REFLOW_BAKE_SETPOINT_L, (uint8_t)SETPOINT_DEFAULT); 120 | printf("Resetting bake setpoint to default."); 121 | } 122 | 123 | Reflow_SelectProfileIdx(NV_GetConfig(REFLOW_PROFILE)); 124 | } 125 | 126 | int Reflow_GetProfileIdx(void) { 127 | return profileidx; 128 | } 129 | 130 | int Reflow_SelectProfileIdx(int idx) { 131 | if (idx < 0) { 132 | profileidx = (NUMPROFILES - 1); 133 | } else if(idx >= NUMPROFILES) { 134 | profileidx = 0; 135 | } else { 136 | profileidx = idx; 137 | } 138 | NV_SetConfig(REFLOW_PROFILE, profileidx); 139 | return profileidx; 140 | } 141 | 142 | int Reflow_SelectEEProfileIdx(int idx) { 143 | if (idx == 1) { 144 | profileidx = (NUMPROFILES - 2); 145 | } else if (idx == 2) { 146 | profileidx = (NUMPROFILES - 1); 147 | } 148 | return profileidx; 149 | } 150 | 151 | int Reflow_GetEEProfileIdx(void) { 152 | if (profileidx == (NUMPROFILES - 2)) { 153 | return 1; 154 | } else if (profileidx == (NUMPROFILES - 1)) { 155 | return 2; 156 | } else { 157 | return 0; 158 | } 159 | } 160 | 161 | int Reflow_SaveEEProfile(void) { 162 | int retval = 0; 163 | uint8_t offset; 164 | uint16_t* tempptr; 165 | if (profileidx == (NUMPROFILES - 2)) { 166 | offset = 0; 167 | tempptr = ee1.temperatures; 168 | } else if (profileidx == (NUMPROFILES - 1)) { 169 | offset = 128; 170 | tempptr = ee2.temperatures; 171 | } else { 172 | return -1; 173 | } 174 | offset += 2; // Skip "magic" 175 | ByteswapTempProfile(tempptr); 176 | 177 | // Store profile 178 | retval = EEPROM_Write(offset, (uint8_t*)tempptr, 96); 179 | ByteswapTempProfile(tempptr); 180 | return retval; 181 | } 182 | 183 | void Reflow_ListProfiles(void) { 184 | for (int i = 0; i < NUMPROFILES; i++) { 185 | printf("%d: %s\n", i, profiles[i]->name); 186 | } 187 | } 188 | 189 | const char* Reflow_GetProfileName(void) { 190 | return profiles[profileidx]->name; 191 | } 192 | 193 | uint16_t Reflow_GetSetpointAtIdx(uint8_t idx) { 194 | if (idx > (NUMPROFILETEMPS - 1)) { 195 | return 0; 196 | } 197 | return profiles[profileidx]->temperatures[idx]; 198 | } 199 | 200 | void Reflow_SetSetpointAtIdx(uint8_t idx, uint16_t value) { 201 | if (idx > (NUMPROFILETEMPS - 1)) { return; } 202 | if (value > SETPOINT_MAX) { return; } 203 | 204 | uint16_t* temp = (uint16_t*) &profiles[profileidx]->temperatures[idx]; 205 | if (temp >= (uint16_t*)0x40000000) { 206 | *temp = value; // If RAM-based 207 | } 208 | } 209 | 210 | void Reflow_PlotProfile(int highlight) { 211 | LCD_BMPDisplay(graphbmp, 0, 0); 212 | 213 | // No need to plot first value as it is obscured by Y-axis 214 | for(int x = 1; x < NUMPROFILETEMPS; x++) { 215 | int realx = (x << 1) + XAXIS; 216 | int y = profiles[profileidx]->temperatures[x] / 5; 217 | y = YAXIS - y; 218 | LCD_SetPixel(realx, y); 219 | 220 | if (highlight == x) { 221 | LCD_SetPixel(realx - 1, y - 1); 222 | LCD_SetPixel(realx + 1, y + 1); 223 | LCD_SetPixel(realx - 1, y + 1); 224 | LCD_SetPixel(realx + 1, y - 1); 225 | } 226 | } 227 | } 228 | 229 | void Reflow_DumpProfile(int profile) { 230 | if (profile > NUMPROFILES) { 231 | printf("\nNo profile with id: %d\n", profile); 232 | return; 233 | } 234 | 235 | int current = profileidx; 236 | profileidx = profile; 237 | 238 | for (int i = 0; i < NUMPROFILETEMPS; i++) { 239 | printf("%4d,", Reflow_GetSetpointAtIdx(i)); 240 | if (i == 15 || i == 31) { 241 | printf("\n "); 242 | } 243 | } 244 | printf("\n"); 245 | profileidx = current; 246 | } 247 | -------------------------------------------------------------------------------- /src/PID_v1.c: -------------------------------------------------------------------------------- 1 | /********************************************************************************************** 2 | * C PID Library - Version 1.0.1 3 | * modified my Matthew Blythe mjblythe.com/hacks 4 | * originally by Brett Beauregard brettbeauregard.com 5 | * 6 | * This Library is licensed under a GPLv3 License 7 | **********************************************************************************************/ 8 | 9 | #include "PID_v1.h" 10 | void PID_Initialize(PidType* pid); 11 | 12 | /*Constructor (...)********************************************************* 13 | * The parameters specified here are those for for which we can't set up 14 | * reliable defaults, so we need to have the user set them. 15 | ***************************************************************************/ 16 | void PID_init(PidType* pid, FloatType Kp, FloatType Ki, FloatType Kd, 17 | PidDirectionType ControllerDirection) { 18 | pid->myInput = 0; 19 | pid->myOutput = 0; 20 | pid->mySetpoint = 0; 21 | pid->ITerm = 0; 22 | pid->lastInput = 0; 23 | pid->inAuto = false; 24 | 25 | PID_SetOutputLimits(pid, 0, 0xffff); 26 | 27 | //default Controller Sample Time is 0.1 seconds 28 | pid->SampleTime = 100; 29 | 30 | PID_SetControllerDirection(pid, ControllerDirection); 31 | PID_SetTunings(pid, Kp, Ki, Kd); 32 | 33 | // pid->lastTime = millis() - pid->SampleTime; 34 | } 35 | 36 | 37 | /* Compute() ********************************************************************** 38 | * This, as they say, is where the magic happens. this function should be called 39 | * every time "void loop()" executes. the function will decide for itself whether a new 40 | * pid Output needs to be computed. returns true when the output is computed, 41 | * false when nothing has been done. 42 | **********************************************************************************/ 43 | bool PID_Compute(PidType* pid) { 44 | if (!pid->inAuto) { 45 | return false; 46 | } 47 | // unsigned long now = millis(); 48 | // unsigned long timeChange = (now - pid->lastTime); 49 | // if (timeChange >= pid->SampleTime) { 50 | /*Compute all the working error variables*/ 51 | FloatType input = pid->myInput; 52 | FloatType error = pid->mySetpoint - input; 53 | pid->ITerm += (pid->ki * error); 54 | if (pid->ITerm > pid->outMax) 55 | pid->ITerm = pid->outMax; 56 | else if (pid->ITerm < pid->outMin) 57 | pid->ITerm = pid->outMin; 58 | FloatType dInput = (input - pid->lastInput); 59 | 60 | /*Compute PID Output*/ 61 | FloatType output = pid->kp * error + pid->ITerm - pid->kd * dInput; 62 | 63 | if (output > pid->outMax) 64 | output = pid->outMax; 65 | else if (output < pid->outMin) 66 | output = pid->outMin; 67 | pid->myOutput = output; 68 | 69 | /*Remember some variables for next time*/ 70 | pid->lastInput = input; 71 | // pid->lastTime = now; 72 | return true; 73 | // } else { 74 | // return false; 75 | // } 76 | } 77 | 78 | 79 | /* SetTunings(...)************************************************************* 80 | * This function allows the controller's dynamic performance to be adjusted. 81 | * it's called automatically from the constructor, but tunings can also 82 | * be adjusted on the fly during normal operation 83 | ******************************************************************************/ 84 | 85 | void PID_SetTunings(PidType* pid, FloatType Kp, FloatType Ki, FloatType Kd) { 86 | if (Kp < 0 || Ki < 0 || Kd < 0){ 87 | return; 88 | } 89 | 90 | pid->dispKp = Kp; 91 | pid->dispKi = Ki; 92 | pid->dispKd = Kd; 93 | 94 | FloatType SampleTimeInSec = ((FloatType) pid->SampleTime) / 1000; 95 | pid->kp = Kp; 96 | pid->ki = Ki * SampleTimeInSec; 97 | pid->kd = Kd / SampleTimeInSec; 98 | 99 | if (pid->controllerDirection == PID_Direction_Reverse) { 100 | pid->kp = (0 - pid->kp); 101 | pid->ki = (0 - pid->ki); 102 | pid->kd = (0 - pid->kd); 103 | } 104 | } 105 | 106 | /* SetSampleTime(...) ********************************************************* 107 | * sets the period, in Milliseconds, at which the calculation is performed 108 | ******************************************************************************/ 109 | void PID_SetSampleTime(PidType* pid, int NewSampleTime) { 110 | if (NewSampleTime > 0) { 111 | FloatType ratio = (FloatType) NewSampleTime / (FloatType) pid->SampleTime; 112 | pid->ki *= ratio; 113 | pid->kd /= ratio; 114 | pid->SampleTime = (unsigned long) NewSampleTime; 115 | } 116 | } 117 | 118 | /* SetOutputLimits(...)**************************************************** 119 | * This function will be used far more often than SetInputLimits. while 120 | * the input to the controller will generally be in the 0-1023 range (which is 121 | * the default already,) the output will be a little different. maybe they'll 122 | * be doing a time window and will need 0-8000 or something. or maybe they'll 123 | * want to clamp it from 0-125. who knows. at any rate, that can all be done 124 | * here. 125 | **************************************************************************/ 126 | void PID_SetOutputLimits(PidType* pid, FloatType Min, FloatType Max) { 127 | if (Min >= Max) { 128 | return; 129 | } 130 | pid->outMin = Min; 131 | pid->outMax = Max; 132 | 133 | if (pid->inAuto) { 134 | if (pid->myOutput > pid->outMax) { 135 | pid->myOutput = pid->outMax; 136 | } else if (pid->myOutput < pid->outMin) { 137 | pid->myOutput = pid->outMin; 138 | } 139 | 140 | if (pid->ITerm > pid->outMax) { 141 | pid->ITerm = pid->outMax; 142 | } else if (pid->ITerm < pid->outMin) { 143 | pid->ITerm = pid->outMin; 144 | } 145 | } 146 | } 147 | 148 | /* SetMode(...)**************************************************************** 149 | * Allows the controller Mode to be set to manual (0) or Automatic (non-zero) 150 | * when the transition from manual to auto occurs, the controller is 151 | * automatically initialized 152 | ******************************************************************************/ 153 | void PID_SetMode(PidType* pid, PidModeType Mode) 154 | { 155 | bool newAuto = (Mode == PID_Mode_Automatic); 156 | if(newAuto == !pid->inAuto) 157 | { /*we just went from manual to auto*/ 158 | PID_Initialize(pid); 159 | } 160 | pid->inAuto = newAuto; 161 | } 162 | 163 | /* Initialize()**************************************************************** 164 | * does all the things that need to happen to ensure a bumpless transfer 165 | * from manual to automatic mode. 166 | ******************************************************************************/ 167 | void PID_Initialize(PidType* pid) { 168 | pid->ITerm = pid->myOutput; 169 | pid->lastInput = pid->myInput; 170 | if (pid->ITerm > pid->outMax) { 171 | pid->ITerm = pid->outMax; 172 | } else if (pid->ITerm < pid->outMin) { 173 | pid->ITerm = pid->outMin; 174 | } 175 | } 176 | 177 | /* SetControllerDirection(...)************************************************* 178 | * The PID will either be connected to a DIRECT acting process (+Output leads 179 | * to +Input) or a REVERSE acting process(+Output leads to -Input.) we need to 180 | * know which one, because otherwise we may increase the output when we should 181 | * be decreasing. This is called from the constructor. 182 | ******************************************************************************/ 183 | void PID_SetControllerDirection(PidType* pid, PidDirectionType Direction) { 184 | if (pid->inAuto && Direction != pid->controllerDirection) { 185 | pid->kp = (0 - pid->kp); 186 | pid->ki = (0 - pid->ki); 187 | pid->kd = (0 - pid->kd); 188 | } 189 | pid->controllerDirection = Direction; 190 | } 191 | 192 | /* Status Funcions************************************************************* 193 | * Just because you set the Kp=-1 doesn't mean it actually happened. these 194 | * functions query the internal state of the PID. they're here for display 195 | * purposes. this are the functions the PID Front-end uses for example 196 | ******************************************************************************/ 197 | FloatType PID_GetKp(PidType* pid) { 198 | return pid->dispKp; 199 | } 200 | FloatType PID_GetKi(PidType* pid) { 201 | return pid->dispKi; 202 | } 203 | FloatType PID_GetKd(PidType* pid) { 204 | return pid->dispKd; 205 | } 206 | PidModeType PID_GetMode(PidType* pid) { 207 | return pid->inAuto ? PID_Mode_Automatic : PID_Mode_Manual; 208 | } 209 | PidDirectionType PID_GetDirection(PidType* pid) { 210 | return pid->controllerDirection; 211 | } 212 | -------------------------------------------------------------------------------- /src/reflow.c: -------------------------------------------------------------------------------- 1 | /* 2 | * reflow.c - Actual reflow profile logic for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include "t962.h" 24 | #include "reflow_profiles.h" 25 | #include "io.h" 26 | #include "lcd.h" 27 | #include "rtc.h" 28 | #include "PID_v1.h" 29 | #include "sched.h" 30 | #include "nvstorage.h" 31 | #include "sensor.h" 32 | #include "reflow.h" 33 | 34 | // Standby temperature in degrees Celsius 35 | #define STANDBYTEMP (50) 36 | 37 | // 250ms between each run 38 | #define PID_TIMEBASE (250) 39 | 40 | #define TICKS_PER_SECOND (1000 / PID_TIMEBASE) 41 | 42 | static PidType PID; 43 | 44 | static uint16_t intsetpoint; 45 | static int bake_timer = 0; 46 | 47 | static float avgtemp; 48 | 49 | static uint8_t reflowdone = 0; 50 | static ReflowMode_t mymode = REFLOW_STANDBY; 51 | static uint16_t numticks = 0; 52 | 53 | static int standby_logging = 0; 54 | 55 | static int32_t Reflow_Work(void) { 56 | static ReflowMode_t oldmode = REFLOW_INITIAL; 57 | static uint32_t lasttick = 0; 58 | uint8_t fan, heat; 59 | uint32_t ticks = RTC_Read(); 60 | 61 | Sensor_DoConversion(); 62 | avgtemp = Sensor_GetTemp(TC_AVERAGE); 63 | 64 | const char* modestr = "UNKNOWN"; 65 | 66 | // Depending on mode we should run this with different parameters 67 | if (mymode == REFLOW_STANDBY || mymode == REFLOW_STANDBYFAN) { 68 | intsetpoint = STANDBYTEMP; 69 | // Cool to standby temp but don't heat to get there 70 | Reflow_Run(0, avgtemp, &heat, &fan, intsetpoint); 71 | heat = 0; 72 | 73 | // Suppress slow-running fan in standby 74 | if (mymode == REFLOW_STANDBY && avgtemp < (float)STANDBYTEMP) { 75 | fan = 0; 76 | } 77 | modestr = "STANDBY"; 78 | 79 | } else if(mymode == REFLOW_BAKE) { 80 | reflowdone = Reflow_Run(0, avgtemp, &heat, &fan, intsetpoint) ? 1 : 0; 81 | modestr = "BAKE"; 82 | 83 | } else if(mymode == REFLOW_REFLOW) { 84 | reflowdone = Reflow_Run(ticks, avgtemp, &heat, &fan, 0) ? 1 : 0; 85 | modestr = "REFLOW"; 86 | 87 | } else { 88 | heat = fan = 0; 89 | } 90 | Set_Heater(heat); 91 | Set_Fan(fan); 92 | 93 | if (mymode != oldmode) { 94 | printf("\n# Time, Temp0, Temp1, Temp2, Temp3, Set,Actual, Heat, Fan, ColdJ, Mode"); 95 | oldmode = mymode; 96 | numticks = 0; 97 | } else if (mymode == REFLOW_BAKE) { 98 | if (bake_timer > 0 && numticks >= bake_timer) { 99 | printf("\n DONE baking, set bake timer to 0."); 100 | bake_timer = 0; 101 | Reflow_SetMode(REFLOW_STANDBY); 102 | } 103 | 104 | // start increasing ticks after setpoint is reached... 105 | if (avgtemp < intsetpoint && bake_timer > 0) { 106 | modestr = "BAKE-PREHEAT"; 107 | } else { 108 | numticks++; 109 | } 110 | } else if (mymode == REFLOW_REFLOW) { 111 | numticks++; 112 | } 113 | 114 | if (!(mymode == REFLOW_STANDBY && standby_logging == 0)) { 115 | printf("\n%6.1f, %5.1f, %5.1f, %5.1f, %5.1f, %3u, %5.1f, %3u, %3u, %5.1f, %s", 116 | ((float)numticks / TICKS_PER_SECOND), 117 | Sensor_GetTemp(TC_LEFT), 118 | Sensor_GetTemp(TC_RIGHT), 119 | Sensor_GetTemp(TC_EXTRA1), 120 | Sensor_GetTemp(TC_EXTRA2), 121 | intsetpoint, avgtemp, 122 | heat, fan, 123 | Sensor_GetTemp(TC_COLD_JUNCTION), 124 | modestr); 125 | } 126 | 127 | if (numticks & 1) { 128 | // Force UI refresh every other cycle 129 | Sched_SetState(MAIN_WORK, 2, 0); 130 | } 131 | 132 | uint32_t thistick = Sched_GetTick(); 133 | if (lasttick == 0) { 134 | lasttick = thistick - TICKS_MS(PID_TIMEBASE); 135 | } 136 | 137 | int32_t nexttick = (2 * TICKS_MS(PID_TIMEBASE)) - (thistick - lasttick); 138 | if ((thistick - lasttick) > (2 * TICKS_MS(PID_TIMEBASE))) { 139 | printf("\nReflow can't keep up with desired PID_TIMEBASE!"); 140 | nexttick = 0; 141 | } 142 | lasttick += TICKS_MS(PID_TIMEBASE); 143 | return nexttick; 144 | } 145 | 146 | void Reflow_Init(void) { 147 | Sched_SetWorkfunc(REFLOW_WORK, Reflow_Work); 148 | //PID_init(&PID, 10, 0.04, 5, PID_Direction_Direct); // This does not reach the setpoint fast enough 149 | //PID_init(&PID, 30, 0.2, 5, PID_Direction_Direct); // This reaches the setpoint but oscillates a bit especially during cooling 150 | //PID_init(&PID, 30, 0.2, 15, PID_Direction_Direct); // This overshoots the setpoint 151 | //PID_init(&PID, 25, 0.15, 15, PID_Direction_Direct); // This overshoots the setpoint slightly 152 | //PID_init(&PID, 20, 0.07, 25, PID_Direction_Direct); 153 | //PID_init(&PID, 20, 0.04, 25, PID_Direction_Direct); // Improvement as far as I can tell, still work in progress 154 | PID_init(&PID, 0, 0, 0, PID_Direction_Direct); // Can't supply tuning to PID_Init when not using the default timebase 155 | PID_SetSampleTime(&PID, PID_TIMEBASE); 156 | PID_SetTunings(&PID, 20, 0.016, 62.5); // Adjusted values to compensate for the incorrect timebase earlier 157 | //PID_SetTunings(&PID, 80, 0, 0); // This results in oscillations with 14.5s cycle time 158 | //PID_SetTunings(&PID, 30, 0, 0); // This results in oscillations with 14.5s cycle time 159 | //PID_SetTunings(&PID, 15, 0, 0); 160 | //PID_SetTunings(&PID, 10, 0, 0); // no oscillations, but offset 161 | //PID_SetTunings(&PID, 10, 0.020, 0); // getting there 162 | //PID_SetTunings(&PID, 10, 0.013, 0); 163 | //PID_SetTunings(&PID, 10, 0.0066, 0); 164 | //PID_SetTunings(&PID, 10, 0.2, 0); 165 | //PID_SetTunings(&PID, 10, 0.020, 1.0); // Experimental 166 | 167 | Reflow_LoadCustomProfiles(); 168 | 169 | Reflow_ValidateNV(); 170 | Sensor_ValidateNV(); 171 | 172 | Reflow_LoadSetpoint(); 173 | 174 | PID.mySetpoint = (float)SETPOINT_DEFAULT; 175 | PID_SetOutputLimits(&PID, 0, 255 + 248); 176 | PID_SetMode(&PID, PID_Mode_Manual); 177 | PID.myOutput = 248; // Between fan and heat 178 | PID_SetMode(&PID, PID_Mode_Automatic); 179 | RTC_Zero(); 180 | 181 | // Start work 182 | Sched_SetState(REFLOW_WORK, 2, 0); 183 | } 184 | 185 | void Reflow_SetMode(ReflowMode_t themode) { 186 | mymode = themode; 187 | // reset reflowdone if mode is set to standby. 188 | if (themode == REFLOW_STANDBY) { 189 | reflowdone = 0; 190 | } 191 | } 192 | 193 | void Reflow_SetSetpoint(uint16_t thesetpoint) { 194 | intsetpoint = thesetpoint; 195 | 196 | NV_SetConfig(REFLOW_BAKE_SETPOINT_H, (uint8_t)(thesetpoint >> 8)); 197 | NV_SetConfig(REFLOW_BAKE_SETPOINT_L, (uint8_t)thesetpoint); 198 | } 199 | 200 | void Reflow_LoadSetpoint(void) { 201 | intsetpoint = NV_GetConfig(REFLOW_BAKE_SETPOINT_H) << 8; 202 | intsetpoint |= NV_GetConfig(REFLOW_BAKE_SETPOINT_L); 203 | 204 | printf("\n bake setpoint values: %x, %x, %d\n", 205 | NV_GetConfig(REFLOW_BAKE_SETPOINT_H), 206 | NV_GetConfig(REFLOW_BAKE_SETPOINT_L), intsetpoint); 207 | } 208 | 209 | int16_t Reflow_GetActualTemp(void) { 210 | return (int)Sensor_GetTemp(TC_AVERAGE); 211 | } 212 | 213 | uint8_t Reflow_IsDone(void) { 214 | return reflowdone; 215 | } 216 | 217 | uint16_t Reflow_GetSetpoint(void) { 218 | return intsetpoint; 219 | } 220 | 221 | void Reflow_SetBakeTimer(int seconds) { 222 | // reset ticks to 0 when adjusting timer. 223 | numticks = 0; 224 | bake_timer = seconds * TICKS_PER_SECOND; 225 | } 226 | 227 | int Reflow_IsPreheating(void) { 228 | return bake_timer > 0 && avgtemp < intsetpoint; 229 | } 230 | 231 | int Reflow_GetTimeLeft(void) { 232 | if (bake_timer == 0) { 233 | return -1; 234 | } 235 | return (bake_timer - numticks) / TICKS_PER_SECOND; 236 | } 237 | 238 | // returns -1 if the reflow process is done. 239 | int32_t Reflow_Run(uint32_t thetime, float meastemp, uint8_t* pheat, uint8_t* pfan, int32_t manualsetpoint) { 240 | int32_t retval = 0; 241 | 242 | if (manualsetpoint) { 243 | PID.mySetpoint = (float)manualsetpoint; 244 | 245 | if (bake_timer > 0 && (Reflow_GetTimeLeft() == 0 || Reflow_GetTimeLeft() == -1)) { 246 | retval = -1; 247 | } 248 | } else { 249 | // Figure out what setpoint to use from the profile, brute-force way. Fix this. 250 | uint8_t idx = thetime / 10; 251 | uint16_t start = idx * 10; 252 | uint16_t offset = thetime - start; 253 | if (idx < (NUMPROFILETEMPS - 2)) { 254 | uint16_t value = Reflow_GetSetpointAtIdx(idx); 255 | uint16_t value2 = Reflow_GetSetpointAtIdx(idx + 1); 256 | 257 | if (value > 0 && value2 > 0) { 258 | uint16_t avg = (value * (10 - offset) + value2 * offset) / 10; 259 | 260 | // Keep the setpoint for the UI... 261 | intsetpoint = avg; 262 | if (value2 > avg) { 263 | // Temperature is rising, 264 | // using the future value for PID regulation produces better result when heating 265 | PID.mySetpoint = (float)value2; 266 | } else { 267 | // Use the interpolated value when cooling 268 | PID.mySetpoint = (float)avg; 269 | } 270 | } else { 271 | retval = -1; 272 | } 273 | } else { 274 | retval = -1; 275 | } 276 | } 277 | 278 | if (!manualsetpoint) { 279 | // Plot actual temperature on top of desired profile 280 | int realx = (thetime / 5) + XAXIS; 281 | int y = (uint16_t)(meastemp * 0.2f); 282 | y = YAXIS - y; 283 | LCD_SetPixel(realx, y); 284 | } 285 | 286 | PID.myInput = meastemp; 287 | PID_Compute(&PID); 288 | uint32_t out = PID.myOutput; 289 | if (out < 248) { // Fan in reverse 290 | *pfan = 255 - out; 291 | *pheat = 0; 292 | } else { 293 | *pheat = out - 248; 294 | 295 | // When heating like crazy make sure we can reach our setpoint 296 | // if(*pheat>192) { *pfan=2; } else { *pfan=2; } 297 | 298 | // Run at a low fixed speed during heating for now 299 | *pfan = NV_GetConfig(REFLOW_MIN_FAN_SPEED); 300 | } 301 | return retval; 302 | } 303 | 304 | void Reflow_ToggleStandbyLogging(void) { 305 | standby_logging = !standby_logging; 306 | } 307 | -------------------------------------------------------------------------------- /src/lcd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lcd.c - Display handling (KS0108 compatible, with two chip selects, active high) 3 | * for T-962 reflow controller 4 | * 5 | * Copyright (C) 2010,2012,2013,2014 Werner Johansson, wj@unifiedengineering.se 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #include "LPC214x.h" 22 | #include 23 | #include 24 | #include 25 | #include "lcd.h" 26 | #include "smallfont.h" 27 | 28 | // Frame buffer storage (each "page" is 8 pixels high) 29 | static uint8_t FB[FB_HEIGHT / 8][FB_WIDTH]; 30 | 31 | typedef struct __attribute__ ((packed)) { 32 | uint8_t bfType[2]; // 'BM' only in this case 33 | uint32_t bfSize; // Total size 34 | uint16_t bfReserved[2]; 35 | uint32_t bfOffBits; // Pixel start byte 36 | uint32_t biSize; // 40 bytes for BITMAPINFOHEADER 37 | int32_t biWidth; // Image width in pixels 38 | int32_t biHeight; // Image height in pixels (if negative image is right-side-up) 39 | uint16_t biPlanes; // Must be 1 40 | uint16_t biBitCount; // Must be 1 41 | uint32_t biCompression; // Only 0 (uncompressed) supported at the moment 42 | uint32_t biSizeImage; // Pixel data size in bytes 43 | int32_t biXPelsPerMeter; 44 | int32_t biYPelsPerMeter; 45 | uint32_t biClrUsed; 46 | uint32_t biClrImportant; 47 | uint32_t aColors[2]; // Palette data, first color is used if pixel bit is 0, second if pixel bit is 1 48 | } BMhdr_t; 49 | 50 | void charoutsmall(uint8_t theChar, uint8_t X, uint8_t Y) { 51 | // First of all, make lowercase into uppercase 52 | // (as there are no lowercase letters in the font) 53 | if ((theChar & 0x7f) >= 0x61 && (theChar & 0x7f) <= 0x7a) { 54 | theChar -= 0x20; 55 | } 56 | uint16_t fontoffset = ((theChar & 0x7f) - 0x20) * 6; 57 | uint8_t yoffset = Y & 0x7; 58 | Y >>= 3; 59 | 60 | #ifndef MINIMALISTIC 61 | uint8_t width = (theChar & 0x80) ? 7 : 6; 62 | #else 63 | uint8_t width=6; 64 | #endif 65 | for (uint8_t x = 0; x < width; x++) { 66 | uint16_t temp=smallfont[fontoffset++]; 67 | #ifndef MINIMALISTIC 68 | if (theChar & 0x80) { temp ^= 0x7f; } 69 | #endif 70 | temp = temp << yoffset; // Shift pixel data to the correct lines 71 | uint16_t old = (FB[Y][X] | (FB[Y + 1][X] << 8)); 72 | #ifndef MINIMALISTIC 73 | old &= ~(0x7f << yoffset); //Clean out old data 74 | #endif 75 | temp |= old; // Merge old data in FB with new char 76 | if (X >= (FB_WIDTH)) return; // make sure we don't overshoot 77 | if (Y < ((FB_HEIGHT / 8) - 0)) { 78 | FB[Y][X] = temp & 0xff; 79 | } 80 | if (Y < ((FB_HEIGHT / 8) - 1)) { 81 | FB[Y + 1][X] = temp >> 8; 82 | } 83 | X++; 84 | } 85 | } 86 | 87 | void LCD_disp_str(uint8_t* theStr, uint8_t theLen, uint8_t startx, uint8_t y, uint8_t theFormat) { 88 | #ifdef MINIMALISTIC 89 | for (uint8_t q = 0; q < theLen; q++) { 90 | charoutsmall(theStr[q], startx, y); 91 | startx += 6; 92 | } 93 | #else 94 | uint8_t invmask = theFormat & 0x80; 95 | for(uint8_t q = 0; q < theLen; q++) { 96 | charoutsmall(theStr[q] | invmask, startx, y); 97 | startx += 6; 98 | } 99 | #endif 100 | } 101 | 102 | void LCD_MultiLineH(uint8_t startx, uint8_t endx, uint64_t ymask) { 103 | for (uint8_t x = startx; x <= endx; x++) { 104 | FB[0][x] |= ymask & 0xff; 105 | FB[1][x] |= ymask >> 8; 106 | FB[2][x] |= ymask >> 16; 107 | FB[3][x] |= ymask >> 24; 108 | #if FB_HEIGHT == 64 109 | FB[4][x] |= ymask >> 32; 110 | FB[5][x] |= ymask >> 40; 111 | FB[6][x] |= ymask >> 48; 112 | FB[7][x] |= ymask >> 56; 113 | #endif 114 | } 115 | } 116 | 117 | /* 118 | * At the moment this is a very basic BMP file reader with the following limitations: 119 | * The bitmap must be 1-bit, uncompressed with a BITMAPINFOHEADER. 120 | */ 121 | uint8_t LCD_BMPDisplay(uint8_t* thebmp, uint8_t xoffset, uint8_t yoffset) { 122 | BMhdr_t* bmhdr; 123 | uint8_t upsidedown = 1; 124 | uint8_t inverted = 0; 125 | uint16_t pixeloffset; 126 | uint8_t numpadbytes = 0; 127 | 128 | // The following code grabs the header portion of the bitmap and caches it locally on the stack 129 | BMhdr_t temp; 130 | uint8_t* xxx = (uint8_t*) &temp; 131 | for (uint16_t xx = 0; xx < sizeof(BMhdr_t); xx++) { 132 | xxx[xx] = *(thebmp + xx); 133 | } 134 | bmhdr = &temp; 135 | 136 | // printf("\n%s: bfSize=%x biSize=%x", __FUNCTION__, (uint16_t)bmhdr->bfSize, (uint16_t)bmhdr->biSize); 137 | // printf("\n%s: Image size is %d x %d", __FUNCTION__, (int16_t)bmhdr->biWidth, (int16_t)bmhdr->biHeight); 138 | if (bmhdr->biPlanes != 1 || bmhdr->biBitCount != 1 || bmhdr->biCompression != 0) { 139 | printf("\n%s: Incompatible bitmap format!", __FUNCTION__); 140 | return 1; 141 | } 142 | pixeloffset = bmhdr->bfOffBits; 143 | if (bmhdr->aColors[0] == 0) { 144 | inverted = 1; 145 | } 146 | if (bmhdr->biHeight<0) { 147 | bmhdr->biHeight = -bmhdr->biHeight; 148 | upsidedown = 0; 149 | } 150 | if ((bmhdr->biWidth+xoffset > FB_WIDTH) || (bmhdr->biHeight+yoffset > FB_HEIGHT)) { 151 | printf("\n%s: Image won't fit on display!", __FUNCTION__); 152 | return 1; 153 | } 154 | 155 | // Figure out how many dummy bytes that is present at the end of each line 156 | // If the image is 132 pixels wide then the pixel lines will be 20 bytes (160 pixels) 157 | // 132&31 is 4 which means that there are 3 bytes of padding 158 | numpadbytes = (4 - ((((bmhdr->biWidth) & 0x1f) + 7) >> 3)) & 0x03; 159 | // printf("\n%s: Skipping %d padding bytes after each line", __FUNCTION__, numpadbytes); 160 | 161 | for (int8_t y = bmhdr->biHeight - 1; y >= 0; y--) { 162 | uint8_t realY = upsidedown ? (uint8_t)y : (uint8_t)(bmhdr->biHeight) - y; 163 | realY += yoffset; 164 | uint8_t pagenum = realY >> 3; 165 | uint8_t pixelval = 1 << (realY & 0x07); 166 | for(uint8_t x = 0; x < bmhdr->biWidth; x += 8) { 167 | uint8_t pixel = *(thebmp + (pixeloffset++)); 168 | if (inverted) { pixel^=0xff; } 169 | uint8_t max_b = bmhdr->biWidth - x; 170 | if (max_b>8) { max_b = 8; } 171 | for (uint8_t b = 0; b < max_b; b++) { 172 | if (pixel & 0x80) { 173 | FB[pagenum][x + b + xoffset] |= pixelval; 174 | } 175 | pixel = pixel << 1; 176 | } 177 | } 178 | pixeloffset += numpadbytes; 179 | } 180 | return 0; 181 | } 182 | 183 | void LCD_SetPixel(uint8_t x, uint8_t y) { 184 | if (x >= FB_WIDTH || y >= FB_HEIGHT) { 185 | // No random memory overwrites thank you 186 | return; 187 | } 188 | FB[y >> 3][x] |= 1 << (y & 0x07); 189 | } 190 | 191 | void LCD_SetBacklight(uint8_t backlight) { 192 | if (backlight) { 193 | FIO0SET = (1 << 11); 194 | } else { 195 | FIO0CLR = (1 << 11); 196 | } 197 | } 198 | 199 | #define UNTIL_BUSY_IS_CLEAR while (FIO1PIN & 0x800000); // Wait for busy to clear 200 | 201 | // No performance gain by inlining the command code 202 | static void LCD_WriteCmd(uint32_t cmdbyte) { 203 | // Start by making sure none of the display controllers are busy 204 | FIO1DIR = 0x000000; // Data pins are now inputs 205 | FIO0CLR = (1 << 22) | (1 << 13); // RS low, also make sure other CS is low 206 | FIO0SET = (1 << 12); // One CS at a time 207 | FIO0SET = (1 << 19); // RW must go high before E does 208 | FIO0SET = (1 << 18); // E high for read 209 | FIO1PIN; // Need 320ns of timing margin here 210 | FIO1PIN; 211 | FIO1PIN; 212 | FIO1PIN; 213 | FIO1PIN; 214 | FIO1PIN; 215 | UNTIL_BUSY_IS_CLEAR; 216 | FIO0CLR = (1 << 12); // Swap CS 217 | FIO0CLR = (1 << 18); // E low again 218 | FIO1PIN; 219 | FIO1PIN; 220 | FIO1PIN; 221 | FIO1PIN; 222 | FIO1PIN; 223 | FIO1PIN; 224 | FIO1PIN; 225 | FIO1PIN; 226 | FIO0SET = (1 << 13); // One CS at a time 227 | FIO0SET = (1 << 18); // E high for read 228 | FIO1PIN; // Need 320ns of timing margin here 229 | FIO1PIN; 230 | FIO1PIN; 231 | FIO1PIN; 232 | FIO1PIN; 233 | FIO1PIN; 234 | UNTIL_BUSY_IS_CLEAR; 235 | FIO0CLR = (1 << 19) | (1 << 18); // RW + E low again 236 | FIO1DIR = 0xff0000; // Data pins output again 237 | 238 | FIO0SET = (1 << 12) | (1 << 13); // Both CS active (one already activated above, doesn't matter) 239 | FIO1PIN = cmdbyte << 16; // Cmd on pins 240 | FIO1PIN; // Need ~200ns of timing margin here 241 | FIO1PIN; 242 | FIO1PIN; 243 | FIO1PIN; 244 | FIO1PIN; 245 | FIO1PIN; 246 | FIO0SET = (1 << 18); // E high 247 | FIO1PIN; 248 | FIO1PIN; 249 | FIO1PIN; 250 | FIO1PIN; 251 | FIO1PIN; 252 | FIO1PIN; 253 | FIO1PIN; 254 | FIO1PIN; 255 | FIO1PIN; 256 | FIO0CLR = (1 << 18); // E low 257 | FIO1PIN; 258 | FIO1PIN; 259 | FIO1PIN; 260 | FIO1PIN; 261 | FIO1PIN; 262 | FIO1PIN; 263 | FIO0SET = (1 << 22); // RS high 264 | } 265 | 266 | // Because of the cycle time requirements for E inlining actually does not boost performance 267 | //static inline void LCD_WriteData(uint32_t databyte, uint8_t chipnum) __attribute__((always_inline)); 268 | static inline void LCD_WriteData(uint32_t databyte, uint8_t chipnum) { 269 | // Start by making sure that the correct controller is selected, then make sure it's not busy 270 | uint32_t csmask = chipnum ? (1 << 12) : (1 << 13); 271 | uint32_t csmask2 = chipnum ? (1 << 13) : (1 << 12); 272 | FIO0SET = csmask; // CS active 273 | FIO0CLR = csmask2; // CS inactive 274 | FIO1DIR = 0x000000; // Data pins are now inputs 275 | FIO0CLR = (1 << 22); // RS low 276 | FIO0SET = (1 << 19); // RW must go high before E does 277 | FIO0SET = (1 << 18); // E high for read 278 | FIO1PIN; // Need 320ns of timing margin here 279 | FIO1PIN; 280 | FIO1PIN; 281 | FIO1PIN; 282 | FIO1PIN; 283 | FIO1PIN; 284 | UNTIL_BUSY_IS_CLEAR; 285 | FIO0CLR = (1 << 18) | (1 << 19); // E and RW low 286 | FIO0SET = (1 << 22); // RS high 287 | FIO1DIR = 0xff0000; // Data pins output again 288 | FIO1PIN = databyte << 16; // Data on pins 289 | FIO1PIN; // Need ~200ns of timing margin here 290 | FIO1PIN; 291 | FIO1PIN; 292 | FIO1PIN; 293 | FIO1PIN; 294 | FIO1PIN; 295 | FIO0SET = (1 << 18); // E high 296 | FIO1PIN; 297 | FIO1PIN; 298 | FIO1PIN; 299 | FIO1PIN; 300 | FIO1PIN; 301 | FIO1PIN; 302 | FIO1PIN; 303 | FIO1PIN; 304 | FIO1PIN; 305 | FIO0CLR = (1 << 18); // E low 306 | /* When inlining additional padding needs to be done 307 | FIO1PIN; 308 | FIO1PIN; 309 | FIO1PIN; 310 | FIO1PIN; 311 | FIO1PIN; 312 | FIO1PIN;*/ 313 | } 314 | 315 | #define LCD_ON (0x3f) 316 | #define LCD_RESET_X (0xb8) 317 | #define LCD_RESET_Y (0x40) 318 | #define LCD_RESET_STARTLINE (0xc0) 319 | 320 | 321 | void LCD_Init(void) { 322 | FIO1DIR = 0xff0000; // Data pins output 323 | LCD_WriteCmd(LCD_ON); 324 | LCD_WriteCmd(LCD_RESET_X); 325 | LCD_WriteCmd(LCD_RESET_Y); 326 | LCD_WriteCmd(LCD_RESET_STARTLINE); 327 | LCD_FB_Clear(); 328 | LCD_FB_Update(); 329 | LCD_SetBacklight(1); 330 | } 331 | 332 | void LCD_FB_Clear(void) { 333 | // Init FB storage 334 | for (uint8_t j = 0; j < (FB_HEIGHT / 8); j++) { 335 | memset(FB[j], 0, FB_WIDTH); 336 | } 337 | } 338 | 339 | void LCD_FB_Update() { 340 | for (uint32_t page = 0; page < (FB_HEIGHT >> 3); page++) { 341 | LCD_WriteCmd(LCD_RESET_X + page); 342 | LCD_WriteCmd(LCD_RESET_Y); 343 | 344 | for(uint32_t i = 0; i < 64; i++) { 345 | LCD_WriteData(FB[page][i], 0); 346 | LCD_WriteData(FB[page][i + 64], 1); 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/onewire.c: -------------------------------------------------------------------------------- 1 | /* 2 | * onewire.c - Bitbang 1-wire DS18B20 temp-sensor handling for T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "onewire.h" 26 | #include "sched.h" 27 | #include "vic.h" 28 | 29 | static inline void setpin0() { 30 | FIO0CLR = (1<<7); 31 | FIO0DIR |= (1<<7); 32 | } 33 | 34 | static inline void setpin1() { 35 | FIO0SET = (1<<7); 36 | FIO0DIR |= (1<<7); 37 | } 38 | 39 | static inline void setpinhiz() { 40 | FIO0DIR &= ~(1<<7); 41 | } 42 | 43 | static inline uint32_t getpin() { 44 | return !!(FIO0PIN & (1<<7)); 45 | } 46 | 47 | static inline uint32_t xferbyte(uint32_t thebyte) { 48 | for (uint32_t bits = 0; bits < 8; bits++) { 49 | setpin0(); 50 | BusyWait(TICKS_US(1.5)); // 1.5us 51 | if (thebyte&0x01) setpinhiz(); 52 | thebyte >>= 1; 53 | BusyWait(TICKS_US(13)); 54 | if (getpin()) thebyte |= 0x80; 55 | BusyWait(TICKS_US(45)); 56 | setpinhiz(); 57 | BusyWait(TICKS_US(10)); 58 | } 59 | return thebyte; 60 | } 61 | 62 | static inline uint32_t xferbit(uint32_t thebit) { 63 | setpin0(); 64 | BusyWait(TICKS_US(1.5)); 65 | if (thebit) setpinhiz(); 66 | thebit = 0; 67 | BusyWait(TICKS_US(13)); 68 | if (getpin()) thebit = 0x01; 69 | BusyWait(TICKS_US(45)); 70 | setpinhiz(); 71 | BusyWait(TICKS_US(10)); 72 | return thebit; 73 | } 74 | 75 | static inline uint32_t resetbus(void) { 76 | uint32_t devicepresent = 0; 77 | 78 | setpin0(); 79 | BusyWait(TICKS_US(480)); 80 | setpinhiz(); 81 | BusyWait(TICKS_US(70)); 82 | if (getpin() == 0) devicepresent = 1; 83 | BusyWait(TICKS_US(410)); 84 | 85 | return devicepresent; 86 | } 87 | 88 | #define OW_SEARCH_ROM (0xf0) 89 | #define OW_READ_ROM (0x33) 90 | #define OW_MATCH_ROM (0x55) 91 | #define OW_SKIP_ROM (0xcc) 92 | #define OW_CONVERT_T (0x44) 93 | #define OW_WRITE_SCRATCHPAD (0x4e) 94 | #define OW_READ_SCRATCHPAD (0xbe) 95 | #define OW_FAMILY_TEMP1 (0x22) // DS1822 96 | #define OW_FAMILY_TEMP2 (0x28) // DS18B20 97 | #define OW_FAMILY_TEMP3 (0x10) // DS18S20 98 | #define OW_FAMILY_TC (0x3b) 99 | 100 | #define MAX_OW_DEVICES (5) 101 | static uint8_t owdeviceids[MAX_OW_DEVICES][8]; // uint64_t results in really odd code 102 | static int16_t devreadout[MAX_OW_DEVICES]; // Keeps last readout from each device 103 | static int16_t extrareadout[MAX_OW_DEVICES]; // Keeps last readout from each device 104 | static int numowdevices = 0; 105 | static int8_t tcidmapping[16]; // Map TC ID to ROM ID index 106 | static int8_t tempidx; // Which ROM ID index that contains the temperature sensor 107 | 108 | // OW functions from Application note 187 (modified for readability) 109 | // global search state 110 | static uint8_t ROM_NO[8]; 111 | static int LastDiscrepancy; 112 | static int LastFamilyDiscrepancy; 113 | static int LastDeviceFlag; 114 | static uint8_t crc8; 115 | 116 | static const unsigned char dscrc_table[] = { 117 | 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 118 | 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, 119 | 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 120 | 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, 121 | 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 122 | 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, 123 | 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, 124 | 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, 125 | 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, 126 | 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, 127 | 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 128 | 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, 129 | 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, 130 | 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, 131 | 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, 132 | 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 133 | }; 134 | 135 | /* 136 | * Calculate the CRC8 of the byte value provided with the current 137 | * global 'crc8' value. 138 | * Returns current global crc8 value 139 | */ 140 | static uint8_t docrc8(uint8_t value) { 141 | // See Application Note 27 142 | 143 | // TEST BUILD 144 | crc8 = dscrc_table[crc8 ^ value]; 145 | return crc8; 146 | } 147 | 148 | /* Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing 149 | * search state. 150 | * Return TRUE : device found, ROM number in ROM_NO buffer 151 | * FALSE : device not found, end of search 152 | */ 153 | static int OWSearch() { 154 | int id_bit_number; 155 | int last_zero, rom_byte_number, search_result; 156 | int id_bit, cmp_id_bit; 157 | uint8_t rom_byte_mask, search_direction; 158 | 159 | // initialize for search 160 | id_bit_number = 1; 161 | last_zero = 0; 162 | rom_byte_number = 0; 163 | rom_byte_mask = 1; 164 | search_result = 0; 165 | crc8 = 0; 166 | // if the last call was not the last one 167 | if (!LastDeviceFlag) { 168 | // 1-Wire reset 169 | if (!resetbus()) { // No devices found 170 | // reset the search 171 | LastDiscrepancy = 0; 172 | LastDeviceFlag = false; 173 | LastFamilyDiscrepancy = 0; 174 | return false; 175 | } 176 | // issue the search command 177 | xferbyte(OW_SEARCH_ROM); 178 | // loop to do the search 179 | do { 180 | // read a bit and its complement 181 | id_bit = xferbit( true ); 182 | cmp_id_bit = xferbit( true ); 183 | // check for no devices on 1-wire 184 | if ((id_bit == 1) && (cmp_id_bit == 1)) { 185 | break; 186 | } else { 187 | // all devices coupled have 0 or 1 188 | if (id_bit != cmp_id_bit) { 189 | search_direction = id_bit; // bit write value for search 190 | } else { 191 | // if this discrepancy if before the Last Discrepancy 192 | // on a previous next then pick the same as last time 193 | if (id_bit_number < LastDiscrepancy) { 194 | search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); 195 | } else { 196 | // if equal to last pick 1, if not then pick 0 197 | search_direction = (id_bit_number == LastDiscrepancy); 198 | } 199 | // if 0 was picked then record its position in LastZero 200 | if (search_direction == 0) { 201 | last_zero = id_bit_number; 202 | // check for Last discrepancy in family 203 | if (last_zero < 9) { 204 | LastFamilyDiscrepancy = last_zero; 205 | } 206 | } 207 | } 208 | // set or clear the bit in the ROM byte rom_byte_number 209 | // with mask rom_byte_mask 210 | if (search_direction == 1) { 211 | ROM_NO[rom_byte_number] |= rom_byte_mask; 212 | } else { 213 | ROM_NO[rom_byte_number] &= ~rom_byte_mask; 214 | } 215 | // serial number search direction write bit 216 | xferbit(search_direction); 217 | // increment the byte counter id_bit_number 218 | // and shift the mask rom_byte_mask 219 | id_bit_number++; 220 | rom_byte_mask <<= 1; 221 | // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask 222 | if (rom_byte_mask == 0) { 223 | docrc8(ROM_NO[rom_byte_number]); // accumulate the CRC 224 | rom_byte_number++; 225 | rom_byte_mask = 1; 226 | } 227 | } 228 | } while(rom_byte_number < 8); // loop until through all ROM bytes 0-7 229 | 230 | // if the search was successful then 231 | if (!((id_bit_number < 65) || (crc8 != 0))) { 232 | // search successful so set LastDiscrepancy,LastDeviceFlag,search_result 233 | LastDiscrepancy = last_zero; 234 | // check for last device 235 | if (LastDiscrepancy == 0) { 236 | LastDeviceFlag = true; 237 | } 238 | search_result = true; 239 | } 240 | } 241 | // if no device found then reset counters so next 'search' will be like a first 242 | if (!search_result || !ROM_NO[0]) { 243 | LastDiscrepancy = 0; 244 | LastDeviceFlag = false; 245 | LastFamilyDiscrepancy = 0; 246 | search_result = false; 247 | } 248 | return search_result; 249 | } 250 | 251 | /* 252 | * Find the 'first' devices on the 1-Wire bus 253 | * Return TRUE : device found, ROM number in ROM_NO buffer 254 | * FALSE : no device present 255 | */ 256 | static int OWFirst() { 257 | // reset the search state 258 | LastDiscrepancy = 0; 259 | LastDeviceFlag = false; 260 | LastFamilyDiscrepancy = 0; 261 | return OWSearch(); 262 | } 263 | 264 | /* 265 | * Find the 'next' devices on the 1-Wire bus 266 | * Return TRUE : device found, ROM number in ROM_NO buffer 267 | * FALSE : device not found, end of search 268 | */ 269 | static int OWNext() { 270 | // leave the search state alone 271 | return OWSearch(); 272 | } 273 | 274 | static void selectdevbyidx(int idx) { 275 | if (idx < numowdevices) { 276 | resetbus(); 277 | xferbyte(OW_MATCH_ROM); 278 | for (int idloop = 0; idloop < 8; idloop++) { // Send ROM device ID 279 | xferbyte(owdeviceids[idx][idloop]); 280 | } 281 | } 282 | } 283 | 284 | static int32_t OneWire_Work(void) { 285 | static uint8_t mystate = 0; 286 | uint8_t scratch[9]; 287 | int32_t retval = 0; 288 | 289 | if (mystate == 0) { 290 | uint32_t save = VIC_DisableIRQ(); 291 | if (resetbus()) { 292 | xferbyte(OW_SKIP_ROM); // All devices on the bus are addressed here 293 | xferbyte(OW_CONVERT_T); 294 | setpin1(); 295 | //retval = TICKS_MS(94); // For 9-bit resolution 296 | retval = TICKS_MS(100); // TC interface needs max 100ms to be ready 297 | mystate++; 298 | } 299 | VIC_RestoreIRQ( save ); 300 | } else if (mystate == 1) { 301 | for (int i = 0; i < numowdevices; i++) { 302 | uint32_t save = VIC_DisableIRQ(); 303 | selectdevbyidx(i); 304 | xferbyte(OW_READ_SCRATCHPAD); 305 | for (uint32_t iter = 0; iter < 4; iter++) { // Read four bytes 306 | scratch[iter] = xferbyte(0xff); 307 | } 308 | VIC_RestoreIRQ(save); 309 | int16_t tmp = scratch[1]<<8 | scratch[0]; 310 | devreadout[i] = tmp; 311 | tmp = scratch[3]<<8 | scratch[2]; 312 | extrareadout[i] = tmp; 313 | } 314 | mystate = 0; 315 | } else { 316 | retval = -1; 317 | } 318 | 319 | return retval; 320 | } 321 | 322 | uint32_t OneWire_Init(void) { 323 | printf("\n%s called", __FUNCTION__); 324 | Sched_SetWorkfunc(ONEWIRE_WORK, OneWire_Work); 325 | printf("\nScanning 1-wire bus..."); 326 | 327 | tempidx = -1; // Assume we don't find a temperature sensor 328 | for (int i = 0; i < sizeof(tcidmapping); i++) { 329 | tcidmapping[i] = -1; // Assume we don't find any thermocouple interfaces 330 | } 331 | 332 | uint32_t save = VIC_DisableIRQ(); 333 | int rslt = OWFirst(); 334 | VIC_RestoreIRQ( save ); 335 | 336 | numowdevices = 0; 337 | while (rslt && numowdevices < MAX_OW_DEVICES) { 338 | memcpy(owdeviceids[numowdevices], ROM_NO, sizeof(ROM_NO)); 339 | numowdevices++; 340 | save = VIC_DisableIRQ(); 341 | rslt = OWNext(); 342 | VIC_RestoreIRQ( save ); 343 | } 344 | 345 | if (numowdevices) { 346 | for (int iter = 0; iter < numowdevices; iter++) { 347 | printf("\n Found "); 348 | for (int idloop = 7; idloop >= 0; idloop--) { 349 | printf("%02x", owdeviceids[iter][idloop]); 350 | } 351 | uint8_t family = owdeviceids[iter][0]; 352 | if (family == OW_FAMILY_TEMP1 || family == OW_FAMILY_TEMP2 || family == OW_FAMILY_TEMP3) { 353 | const char* sensorname = "UNKNOWN"; 354 | if (family == OW_FAMILY_TEMP1) { 355 | sensorname = "DS1822"; 356 | } else if (family == OW_FAMILY_TEMP2) { 357 | sensorname = "DS18B20"; 358 | } else if (family == OW_FAMILY_TEMP3) { 359 | sensorname = "DS18S20"; 360 | } 361 | save = VIC_DisableIRQ(); 362 | selectdevbyidx(iter); 363 | xferbyte(OW_WRITE_SCRATCHPAD); 364 | xferbyte(0x00); 365 | xferbyte(0x00); 366 | xferbyte(0x1f); // Reduce resolution to 0.5C to keep conversion time reasonable 367 | VIC_RestoreIRQ(save); 368 | tempidx = iter; // Keep track of where we saw the last/only temperature sensor 369 | printf(" [%s Temperature sensor]", sensorname); 370 | } else if (family == OW_FAMILY_TC) { 371 | save = VIC_DisableIRQ(); 372 | selectdevbyidx(iter); 373 | xferbyte(OW_READ_SCRATCHPAD); 374 | xferbyte(0xff); 375 | xferbyte(0xff); 376 | xferbyte(0xff); 377 | xferbyte(0xff); 378 | uint8_t tcid = xferbyte(0xff) & 0x0f; 379 | VIC_RestoreIRQ( save ); 380 | tcidmapping[tcid] = iter; // Keep track of the ID mapping 381 | printf(" [Thermocouple interface, ID %x]",tcid); 382 | } 383 | } 384 | } else { 385 | printf(" No devices found!"); 386 | } 387 | 388 | if (numowdevices) { 389 | Sched_SetState(ONEWIRE_WORK, 2, 0); // Enable OneWire task if there's at least one device 390 | } 391 | return numowdevices; 392 | } 393 | 394 | float OneWire_GetTempSensorReading(void) { 395 | float retval = 999.0f; // Report invalid temp if not found 396 | if(tempidx >= 0) { 397 | retval = (float)devreadout[tempidx]; 398 | retval /= 16; 399 | //printf(" (%.1f C)",retval); 400 | } else { 401 | //printf(" (%.1f C assumed)",retval); 402 | } 403 | return retval; 404 | } 405 | 406 | int OneWire_IsTCPresent(uint8_t tcid) { 407 | if (tcid < sizeof(tcidmapping) && tcidmapping[tcid] >= 0) { 408 | if (!(devreadout[tcidmapping[tcid]] & 0x01)) { 409 | // A faulty/not connected TC will not be flagged as present 410 | return 1; 411 | } 412 | } 413 | return 0; 414 | } 415 | 416 | float OneWire_GetTCReading(uint8_t tcid) { 417 | float retval = 0.0f; // Report 0C for missing sensors 418 | if (tcid < sizeof(tcidmapping)) { 419 | uint8_t idx = tcidmapping[tcid]; 420 | if (idx >=0) { // Is this ID present? 421 | if (devreadout[idx] & 0x01) { // Fault detected 422 | retval = 999.0f; // Invalid 423 | } else { 424 | retval = (float)(devreadout[idx] & 0xfffc); // Mask reserved bit 425 | retval /= 16; 426 | //printf(" (%x=%.1f C)",tcid,retval); 427 | } 428 | } 429 | } 430 | return retval; 431 | } 432 | 433 | float OneWire_GetTCColdReading(uint8_t tcid) { 434 | float retval = 0.0f; // Report 0C for missing sensors 435 | if (tcid < sizeof(tcidmapping)) { 436 | uint8_t idx = tcidmapping[tcid]; 437 | if (idx >=0) { // Is this ID present? 438 | if (extrareadout[idx] & 0x07) { // Any fault detected 439 | retval = 999.0f; // Invalid 440 | } else { 441 | retval = (float)(extrareadout[idx] & 0xfff0); // Mask reserved/fault bits 442 | retval /= 256; 443 | //printf(" (%x=%.1f C)",tcid,retval); 444 | } 445 | } 446 | } 447 | return retval; 448 | } 449 | -------------------------------------------------------------------------------- /src/cr_startup_lpc21.s: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * +--+ 3 | * | ++----+ 4 | * +-++ | 5 | * | | 6 | * +-+--+ | 7 | * | +--+--+ 8 | * +----+ Copyright (c) 2009-13 Code Red Technologies Ltd. 9 | * 10 | * LPC21xx Startup code for use with Red Suite 11 | * 12 | * Version : 130903 13 | * 14 | * Software License Agreement 15 | * 16 | * The software is owned by Code Red Technologies and/or its suppliers, and is 17 | * protected under applicable copyright laws. All rights are reserved. Any 18 | * use in violation of the foregoing restrictions may subject the user to criminal 19 | * sanctions under applicable laws, as well as to civil liability for the breach 20 | * of the terms and conditions of this license. 21 | * 22 | * THIS SOFTWARE IS PROVIDED "AS IS". NO WARRANTIES, WHETHER EXPRESS, IMPLIED 23 | * OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF 24 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. 25 | * USE OF THIS SOFTWARE FOR COMMERCIAL DEVELOPMENT AND/OR EDUCATION IS SUBJECT 26 | * TO A CURRENT END USER LICENSE AGREEMENT (COMMERCIAL OR EDUCATIONAL) WITH 27 | * CODE RED TECHNOLOGIES LTD. 28 | * 29 | ******************************************************************************/ 30 | 31 | /******************************************************************************* 32 | * 33 | * Conventions used in the assembler code: 34 | * 35 | * Mnemonics are in upper case. 36 | * labels (prog or data) are in lower case. 37 | * Constants for initialization values and/or conditional assembly are 38 | * in upper case. 39 | * 40 | * The following C preprocessor 'switches' are used in this file to conditionally 41 | * assemble certain parts of the code: 42 | * 43 | * PLL_INIT : Define this to initialise the PLL 44 | * VPB_INIT : Define this to initialise the VPB divider 45 | * MAM_INIT : Define this to initialise the MAM 46 | * STACK_INIT : Define this to initialise the STACK 47 | * USB_INIT : Define this to enable USB (makes 8KB of USB RAM available 48 | * on LPC2146/2148) 49 | * USE_OLD_STYLE_DATA_BSS_INIT : Define this to build startup code with pre-3.6 50 | * version of the Code Red tools 51 | * 52 | *******************************************************************************/ 53 | 54 | .global main // int main(void) 55 | 56 | #ifndef USE_OLD_STYLE_DATA_BSS_INIT 57 | /***************************************************************************** 58 | / The following symbols are constructs generated by the linker, indicating 59 | / the location of various points in the "Global Section Table". This table is 60 | / created by the linker via the Code Red managed linker script mechanism. It 61 | / contains the load address, execution address and length of each RW data 62 | / section and the execution and length of each BSS (zero initialized) section. 63 | /*****************************************************************************/ 64 | .global __data_section_table 65 | .global __data_section_table_end 66 | .global __bss_section_table 67 | .global __bss_section_table_end 68 | #else 69 | /***************************************************************************** 70 | / The following symbols are constructs generated by the linker, indicating 71 | / the load address, execution address and length of the RW data section and 72 | / the execution and length of the BSS (zero initialized) section. 73 | / Note that these symbols are not normally used by the managed linker script 74 | / mechanism in Red Suite/LPCXpresso 3.6 (Windows) and LPCXpresso 3.8 (Linux). 75 | / They are provide here simply so this startup code can be used with earlier 76 | / versions of Red Suite which do not support the more advanced managed linker 77 | / script mechanism introduced in the above version. To enable their use, 78 | / define "USE_OLD_STYLE_DATA_BSS_INIT". 79 | /*****************************************************************************/ 80 | .global _etext 81 | .global _data 82 | .global _edata 83 | .global _bss 84 | .global _ebss; 85 | #endif 86 | 87 | .global _vStackTop // top of stack 88 | 89 | /* 90 | * Stack Sizes - remember these are bytes not words 91 | */ 92 | .set UND_STACK_SIZE, 0x00000010 93 | .set ABT_STACK_SIZE, 0x00000010 94 | .set FIQ_STACK_SIZE, 0x00000010 95 | .set IRQ_STACK_SIZE, 0X00000080 96 | .set SVC_STACK_SIZE, 0x00000080 97 | 98 | .set USR_STACK_SIZE, 0x00000400 // Shared with SYS mode 99 | 100 | .set TOTAL_STACK_SIZE, UND_STACK_SIZE + SVC_STACK_SIZE + \ 101 | ABT_STACK_SIZE + FIQ_STACK_SIZE + \ 102 | IRQ_STACK_SIZE + USR_STACK_SIZE 103 | 104 | /* 105 | * Standard definitions of Mode bits and Interrupt (I & F) flags in PSRs 106 | */ 107 | .set MODE_USR, 0x10 // User Mode 108 | .set MODE_FIQ, 0x11 // FIQ Mode 109 | .set MODE_IRQ, 0x12 // IRQ Mode 110 | .set MODE_SVC, 0x13 // Supervisor Mode 111 | .set MODE_ABT, 0x17 // Abort Mode 112 | .set MODE_UND, 0x1B // Undefined Mode 113 | .set MODE_SYS, 0x1F // System Mode 114 | 115 | .set I_BIT, 0x80 // when I bit is set, IRQ is disabled 116 | .set F_BIT, 0x40 // when F bit is set, FIQ is disabled 117 | 118 | .text 119 | .code 32 120 | .align 2 121 | 122 | .section .isr_vector,"x" 123 | .global _boot 124 | .func _boot 125 | _boot: 126 | 127 | /* 128 | * Exception Processing Vectors 129 | */ 130 | Vectors: 131 | B _start // reset 132 | MOV pc,#__undf // undefined 133 | MOV pc,#__swi // SWI/SVC 134 | MOV pc,#__pabt // program abort 135 | MOV pc,#__dabt // data abort 136 | NOP // Reserved for the flash checksum 137 | LDR pc,[pc,#-0xFF0] // IRQ - read the VIC register 138 | // LDR pc,_irq // or go to default handler 139 | // MOV pc,#__fiq // Do __fiq in-place instead 140 | __fiq: MOV r0,#_fiqstr // FIQ 141 | B _printexc 142 | __undf: MOV r0,#_undfstr // undefined 143 | B _printexc 144 | __pabt: MOV r0,#_pabtstr // program abort 145 | B _printexc 146 | __dabt: MOV r0,#_dabtstr // data abort 147 | // lr contains instruction that failed to access data +8 so adjust an additional 4 bytes 148 | sub lr,lr,#4 149 | B _printexc 150 | __swi: 151 | MOV r0,#_swistr 152 | // B _printexc // Fall-thru 153 | _printexc: 154 | .set UART0, 0xe000c000 155 | .set U0LSR_OFFS, 0x14 156 | .set U0IER_OFFS, 0x04 157 | .set U0THR_OFFS, 0x00 158 | .set FIO0CLR, 0x3fffc01c 159 | // lr contains address of failed instruction +4 160 | SUB lr,lr,#4 161 | LDR r1,=UART0 162 | // MOV r2,#0 163 | // STR r2,[r1,#U0IER_OFFS] 164 | 165 | // Output exception text 166 | _ploop: 167 | LDRB r2,[r0],#1 168 | _pwait: 169 | LDR r3,[r1,#U0LSR_OFFS] 170 | TST r3,#0x20 171 | BEQ _pwait 172 | CMP r2,#0 173 | STRNE r2,[r1,#U0THR_OFFS] 174 | BNE _ploop 175 | _done: 176 | 177 | // Output lr register in hex 178 | MOV r5,#8 179 | MOV r4,#_hexstr 180 | _hloop: 181 | LSR r0,lr,#0x1c 182 | LDR r2,[r4,r0] 183 | _pwait2: 184 | LDR r3,[r1,#U0LSR_OFFS] 185 | TST r3,#0x20 186 | BEQ _pwait2 187 | STR r2,[r1,#U0THR_OFFS] 188 | LSL lr,lr,#4 189 | SUBS r5,r5,#1 190 | BNE _hloop 191 | 192 | // Turn off backlight to indicate fault 193 | // LDR r1,=FIO0CLR 194 | // MOV r2,#(1<<11) 195 | // STR r2,[r1] 196 | _realdone: 197 | B _realdone 198 | 199 | _hexstr: .ASCII "0123456789abcdef" 200 | _undfstr: .ASCIZ "UNDF@" 201 | _pabtstr: .ASCIZ "PABT@" 202 | _dabtstr: .ASCIZ "DABT@" 203 | _fiqstr: .ASCIZ "FIQ?" 204 | _swistr: .ASCIZ "SWI?" 205 | 206 | .endfunc 207 | /* 208 | * Setup the operating mode & stack. 209 | */ 210 | .global _start, start, _mainCRTStartup 211 | .func _start 212 | 213 | _start: 214 | start: 215 | _mainCRTStartup: 216 | 217 | .set SYSCTRL, 0xE01FC040 218 | 219 | #ifdef PLL_INIT 220 | 221 | .set PLL, SYSCTRL+0x40 222 | .set PLLCON_OFFSET, 0x0 223 | .set PLLCFG_OFFSET, 0x4 224 | .set PLLSTAT_OFFSET, 0x8 225 | .set PLLFEED_OFFSET, 0xC 226 | 227 | .set PLOCK, (1<<10) //lock bit inside PLLSTAT 228 | 229 | .set SET_PLLCFG_MUL3, 0x2 230 | .set SET_PLLCFG_MUL4, 0x3 231 | .set SET_PLLCFG_MUL5, 0x4 232 | .set SET_PLLCFG_MUL6, 0x5 233 | 234 | .set SET_PLLCFG_DIV1, (0x0<<4) 235 | .set SET_PLLCFG_DIV2, (0x1<<4) 236 | .set SET_PLLCFG_DIV4, (0x2<<4) 237 | .set SET_PLLCFG_DIV8, (0x3<<4) 238 | 239 | 240 | .set PLLCFG_INIT_VAL, SET_PLLCFG_MUL6 | SET_PLLCFG_DIV1 241 | .set SET_PLLCON_ENABLE, 1 242 | .set SET_PLLCON_CONNECT, 2 243 | 244 | /* 245 | * Setup the PLL 246 | */ 247 | 248 | LDR R0,=PLL 249 | MOV R1,#0xAA 250 | MOV R2,#0x55 251 | 252 | MOV R3,#PLLCFG_INIT_VAL 253 | STR R3,[R0,#PLLCFG_OFFSET] 254 | 255 | MOV R3,#SET_PLLCON_ENABLE 256 | STR R3,[R0,#PLLCON_OFFSET] 257 | 258 | STR R1,[R0,#PLLFEED_OFFSET] 259 | STR R2,[R0,#PLLFEED_OFFSET] 260 | 261 | // Wait for the loop to lock 262 | 1: LDR R3,[R0,#PLLSTAT_OFFSET] 263 | ANDS R3,R3,#PLOCK 264 | BEQ 1b 265 | 266 | // Now swap the cpu clock to the PLL 267 | MOV R3,#(SET_PLLCON_ENABLE | SET_PLLCON_CONNECT) 268 | STR R3,[R0,#PLLCON_OFFSET] 269 | STR R1,[R0,#PLLFEED_OFFSET] 270 | STR R2,[R0,#PLLFEED_OFFSET] 271 | 272 | #endif 273 | 274 | 275 | 276 | #ifdef VPB_INIT 277 | /* 278 | * Setup the VPB/APB Peripheral bus clock 279 | */ 280 | 281 | .set VPBDIV_OFFSET, 0xc0 282 | .set VPBDIV, SYSCTRL+VPBDIV_OFFSET 283 | 284 | .set VPBDIV_INIT_VAL, 1 285 | 286 | LDR R0,=VPBDIV 287 | LDR R1,=VPBDIV_INIT_VAL 288 | STR R1,[R0] 289 | 290 | #endif 291 | 292 | 293 | #ifdef MAM_INIT 294 | /* 295 | * Setup the Memory Accelerator Block (MAM) 296 | */ 297 | 298 | .set MAM, 0xE01FC000 299 | .set MAMCR_OFFSET, 0x0 300 | .set MAMTIM_OFFSET, 0x4 301 | 302 | .set SET_MAMCR_DISABLE, 0x0 303 | .set SET_MAMCR_PARTIAL, 0x1 304 | .set SET_MAMCR_FULL, 0x2 305 | 306 | // How many cycles for flash access 307 | .set SET_MAMTIM_0CLK, 0x0 308 | .set SET_MAMTIM_1CLK, 0x1 309 | .set SET_MAMTIM_2CLK, 0x2 310 | .set SET_MAMTIM_3CLK, 0x3 311 | .set SET_MAMTIM_4CLK, 0x4 312 | .set SET_MAMTIM_5CLK, 0x5 313 | .set SET_MAMTIM_6CLK, 0x6 314 | .set SET_MAMTIM_7CLK, 0x7 315 | 316 | 317 | .set MAMTIM_INIT_VAL, SET_MAMTIM_3CLK 318 | .set MAMCR_INIT_VAL, SET_MAMCR_FULL 319 | 320 | LDR R0,=MAM 321 | MOV R1,#MAMTIM_INIT_VAL 322 | STR R1,[R0,#MAMTIM_OFFSET] 323 | MOV R1,#MAMCR_INIT_VAL 324 | STR R1,[R0,#MAMCR_OFFSET] 325 | 326 | #endif 327 | 328 | /*************************** 329 | * The LPC2146/2148 have 8KB of USB RAM available. If USB is 330 | * not being used, then this RAM can be used as general 331 | * purpose RAM by the application. However this requires the 332 | * USB subsystem to be enabled in the PCONP register 333 | ***************************/ 334 | #ifdef USB_INIT 335 | LDR r0, =0xE01FC0C4 // Load address of PCONP register 336 | LDR r1,[r0] // Load contents of PCONP 337 | ORR r1,r1,#(1 << 31) // Set bit 31 - USB 338 | STR r1,[r0] // Store updated value back to PCONP 339 | #endif 340 | 341 | /* 342 | * Setup some stack space for each ARM operating mode 343 | */ 344 | LDR r0,=_vStackTop 345 | MSR CPSR_c,#MODE_UND|I_BIT|F_BIT // Undefined Instruction Mode 346 | MOV sp,r0 347 | SUB r0,r0,#UND_STACK_SIZE 348 | MSR CPSR_c,#MODE_ABT|I_BIT|F_BIT // Abort Mode 349 | MOV sp,r0 350 | SUB r0,r0,#ABT_STACK_SIZE 351 | MSR CPSR_c,#MODE_FIQ|I_BIT|F_BIT // FIQ Mode 352 | MOV sp,r0 353 | SUB r0,r0,#FIQ_STACK_SIZE 354 | MSR CPSR_c,#MODE_IRQ|I_BIT|F_BIT // IRQ Mode 355 | MOV sp,r0 356 | SUB r0,r0,#IRQ_STACK_SIZE 357 | MSR CPSR_c,#MODE_SVC|I_BIT|F_BIT // Supervisor Mode 358 | MOV sp,r0 359 | SUB r0,r0,#SVC_STACK_SIZE 360 | MSR CPSR_c,#MODE_SYS|I_BIT|F_BIT // System Mode 361 | MOV sp,r0 362 | 363 | #ifndef USE_OLD_STYLE_DATA_BSS_INIT 364 | /* 365 | * Copy RWdata initial values from flash to its execution 366 | * address in RAM 367 | */ 368 | LDR r4, =Ldata_start // Load base address of data... 369 | LDR r4, [r4] // ...from Global Section Table 370 | LDR r5, =Ldata_end // Load end address of data... 371 | LDR r5, [r5] //...from Global Section Table 372 | start_data_init_loop: 373 | CMP r4,r5 // Check to see if reached end of... 374 | BEQ end_data_init_loop // ...data entries in G.S.T. 375 | LDR r0, [r4],#4 // Load LoadAddr from G.S.T. 376 | LDR r1, [r4],#4 // Load ExeAddr from G.S.T. 377 | LDR r2, [r4],#4 // Load SectionLen from G.S.T. 378 | BL data_init // Call subroutine to do copy 379 | B start_data_init_loop // Loop back for next entry in G.S.T. 380 | 381 | end_data_init_loop: 382 | /* 383 | * Clear .bss (zero'ed space) 384 | */ 385 | LDR r5, =Lbss_end // Load end address of BSS... 386 | LDR r5, [r5] //...from Global Section Table 387 | start_bss_init_loop: 388 | CMP r4,r5 // Check to see if reached end of... 389 | BEQ post_data_bss_init // ...bss entries in G.S.T. 390 | LDR r0, [r4],#4 // Load ExeAddr from G.S.T. 391 | LDR r1, [r4],#4 // Load SectionLen from G.S.T. 392 | BL bss_init // Call subroutine to do zero'ing 393 | B start_bss_init_loop // Loop back for next entry in G.S.T. 394 | 395 | Ldata_start: .word __data_section_table 396 | Ldata_end: .word __data_section_table_end 397 | Lbss_end: .word __bss_section_table_end 398 | 399 | /****************************************************************************** 400 | * Functions to carry out the initialization of RW and BSS data sections. These 401 | * are written as separate functions to cope with MCUs with multiple banks of 402 | * memory. 403 | ******************************************************************************/ 404 | // void data_init(unsigned int romstart, unsigned int start, unsigned int len) 405 | data_init: 406 | MOV r12,#0 407 | .di_loop: 408 | CMP r12,r2 409 | LDRLO r3,[r0],#4 410 | STRLO r3,[r1],#4 411 | ADDLO r12,r12,#4 412 | BLO .di_loop 413 | BX LR 414 | 415 | // void bss_init(unsigned int start, unsigned int len) 416 | bss_init: 417 | MOV r12,#0 418 | MOV r2, #0 419 | .bi_loop: 420 | CMP r12,r1 421 | STRLO r2,[r0],#4 422 | ADDLO r12,r12,#4 423 | BLO .bi_loop 424 | BX LR 425 | 426 | 427 | /****************************************************************************** 428 | * Back to main flow of Reset_Handler 429 | ******************************************************************************/ 430 | post_data_bss_init: 431 | 432 | #else 433 | // Use Old Style Data and BSS section initialization. 434 | // This will only initialize a single RAM bank 435 | // 436 | // Copy initialized data to its execution address in RAM 437 | LDR r1,=_etext // -> ROM data start 438 | LDR r2,=_data // -> data start 439 | LDR r3,=_edata // -> end of data 440 | 1: CMP r2,r3 // check if data to move 441 | LDRLO r0,[r1],#4 // copy it 442 | STRLO r0,[r2],#4 443 | BLO 1b // loop until done 444 | 445 | //Clear .bss (zero'ed space) 446 | MOV r0,#0 // get a zero 447 | LDR r1,=_bss // -> bss start 448 | LDR r2,=_ebss // -> bss end 449 | 2: CMP r1,r2 // check if data to clear 450 | STRLO r0,[r1],#4 // clear 4 bytes 451 | BLO 2b // loop until done 452 | #endif 453 | 454 | #ifdef STACK_INIT 455 | /* 456 | * Initialize the stack to known values to aid debugging 457 | * 458 | * Definitely optional, but can help early debugging for 459 | * stack overflows. Also system optimization for measuring 460 | * stack depth used. 461 | */ 462 | .global _vStackTop 463 | 464 | MOV r0,#0 // start of count 465 | LDR r1,=_vStackTop // start from top 466 | LDR r3,=TOTAL_STACK_SIZE 467 | SUB r2,r1,r3 468 | 3: 469 | CMP r1,r2 470 | STRGT r0,[r1,#-4]! // walk down the stack 471 | ADD r0,r0,#1 472 | BGT 3b 473 | #endif 474 | 475 | // 476 | // Call C++ library initilisation, if present 477 | // 478 | .cpp_init: 479 | LDR r3, .def__libc_init_array // if 480 | CMP r3, #0 481 | BEQ .skip_cpp_init 482 | BL __libc_init_array 483 | .skip_cpp_init: 484 | 485 | /* 486 | * Call main program: main(0) 487 | */ 488 | MOV r0,#0 // no arguments (argc = 0) 489 | // MOV r1,r0 490 | // MOV r2,r0 491 | MOV fp,r0 // null frame pointer 492 | // MOV r7,r0 // null frame pointer for thumb 493 | 494 | // Change to system mode (IRQs enabled) before calling main application 495 | 496 | MSR CPSR_c,#MODE_SYS|F_BIT // System Mode 497 | 498 | #ifdef __REDLIB__ 499 | LDR r10, =__main 500 | #else 501 | LDR r10, =main 502 | #endif 503 | 504 | MOV lr,pc 505 | BX r10 // enter main() - could be ARM or Thumb 506 | 507 | .size _start, . - _start 508 | .endfunc 509 | 510 | .global _reset, reset 511 | .func _reset 512 | _reset: 513 | reset: 514 | exit: 515 | 516 | B . // loop until reset 517 | 518 | .weak __libc_init_array 519 | .def__libc_init_array: 520 | .word __libc_init_array 521 | 522 | 523 | .weak init_lpc3xxx // void init_lpc31xx(void) 524 | .def__init_lpc3xxx: 525 | .word init_lpc3xxx 526 | 527 | 528 | .size _reset, . - _reset 529 | .endfunc 530 | 531 | .end 532 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * main.c - T-962 reflow controller 3 | * 4 | * Copyright (C) 2014 Werner Johansson, wj@unifiedengineering.se 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "LPC214x.h" 21 | #include 22 | #include 23 | #include 24 | #include "serial.h" 25 | #include "lcd.h" 26 | #include "io.h" 27 | #include "sched.h" 28 | #include "onewire.h" 29 | #include "adc.h" 30 | #include "i2c.h" 31 | #include "rtc.h" 32 | #include "eeprom.h" 33 | #include "keypad.h" 34 | #include "reflow.h" 35 | #include "reflow_profiles.h" 36 | #include "sensor.h" 37 | #include "buzzer.h" 38 | #include "nvstorage.h" 39 | #include "version.h" 40 | #include "vic.h" 41 | #include "max31855.h" 42 | #include "systemfan.h" 43 | #include "setup.h" 44 | 45 | extern uint8_t logobmp[]; 46 | extern uint8_t stopbmp[]; 47 | extern uint8_t selectbmp[]; 48 | extern uint8_t editbmp[]; 49 | extern uint8_t f3editbmp[]; 50 | 51 | // No version.c file generated for LPCXpresso builds, fall back to this 52 | __attribute__((weak)) const char* Version_GetGitVersion(void) { 53 | return "no version info"; 54 | } 55 | 56 | static char* format_about = \ 57 | "\nT-962-controller open source firmware (%s)" \ 58 | "\n" \ 59 | "\nSee https://github.com/UnifiedEngineering/T-962-improvement for more details." \ 60 | "\n" \ 61 | "\nInitializing improved reflow oven..."; 62 | 63 | static char* help_text = \ 64 | "\nT-962-controller serial interface.\n\n" \ 65 | " about Show about + debug information\n" \ 66 | " bake Enter Bake mode with setpoint\n" \ 67 | " bake