├── .cproject ├── .gitignore ├── .project ├── .settings └── language.settings.xml ├── .travis.yml ├── COMPILING.md ├── LICENSE ├── Makefile ├── README.md ├── T-962-controller.ld ├── logs └── .gitignore ├── schematic-T962.pdf ├── serial-control.py └── src ├── LPC214x.h ├── PID_v1.c ├── PID_v1.h ├── adc.c ├── adc.h ├── buzzer.c ├── buzzer.h ├── circbuffer.c ├── circbuffer.h ├── cr_startup_lpc21.s ├── crp.c ├── eeprom.c ├── eeprom.h ├── i2c.c ├── i2c.h ├── images ├── UEoSlogo-128x64.bmp ├── editprofile-18x64.bmp ├── f3edit-18x16.bmp ├── graph-128x64.bmp ├── selectprofile-18x64.bmp └── stop-18x64.bmp ├── import.s ├── io.c ├── io.h ├── keypad.c ├── keypad.h ├── lcd.c ├── lcd.h ├── main.c ├── max31855.c ├── max31855.h ├── nvstorage.c ├── nvstorage.h ├── onewire.c ├── onewire.h ├── reflow.c ├── reflow.h ├── reflow_profiles.c ├── reflow_profiles.h ├── rtc.c ├── rtc.h ├── sc18is602b.c ├── sc18is602b.h ├── sched.c ├── sched.h ├── sensor.c ├── sensor.h ├── serial.c ├── serial.h ├── setup.c ├── setup.h ├── smallfont.h ├── systemfan.c ├── systemfan.h ├── t962.h ├── version.h ├── vic.c └── vic.h /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.png 3 | *.pdf 4 | -------------------------------------------------------------------------------- /schematic-T962.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/217281cccefd046c5547b69e21bc9e6670679fbd/schematic-T962.pdf -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/images/UEoSlogo-128x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/217281cccefd046c5547b69e21bc9e6670679fbd/src/images/UEoSlogo-128x64.bmp -------------------------------------------------------------------------------- /src/images/editprofile-18x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/217281cccefd046c5547b69e21bc9e6670679fbd/src/images/editprofile-18x64.bmp -------------------------------------------------------------------------------- /src/images/f3edit-18x16.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/217281cccefd046c5547b69e21bc9e6670679fbd/src/images/f3edit-18x16.bmp -------------------------------------------------------------------------------- /src/images/graph-128x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/217281cccefd046c5547b69e21bc9e6670679fbd/src/images/graph-128x64.bmp -------------------------------------------------------------------------------- /src/images/selectprofile-18x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/217281cccefd046c5547b69e21bc9e6670679fbd/src/images/selectprofile-18x64.bmp -------------------------------------------------------------------------------- /src/images/stop-18x64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnifiedEngineering/T-962-improvements/217281cccefd046c5547b69e21bc9e6670679fbd/src/images/stop-18x64.bmp -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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