One of the end-of-year projects given to us by our microcontroller teacher was creating the technical specifications for a peanut butter mill powered by an 8051 microcontroller as well as writing the program that was going to run on the latter (Simulated as we were not going to build the actual mill).
The fictional mill should have a self-check process before starting and allows an optional second pass during milling. It should be able to self-clean at the end of the milling. Very much like this one, but where the container is enclosed as well as an additional container inside for the optional pass.
After pressing the start button, the program should check that at least 1/4 of the first container (The one containing the peanut) is filled and that the last container is empty. If the latter is not, it should turn on a warning light. When all these conditions are met, the milling process starts and the peanut butter is poured in the second container to be milled if the user has chosen to do two passes or the final container if he has chosen only one pass. The first motor stops when the first container is empty and the second motor will stop 5 minutes later if it was running
The self-clean process is very simple. The first 2 containers are filled with water and start both motors. After 3 minutes, they are stopped and the containers are emptied into the final container. This is repeated one more time and the machine is reset.
Even if we are not building the actual mill, we need to design the interfaces to the microcontroller. The 8051 microcontroller has 4 byte-sized I/O ports. We choose to use 2 for the purpose of this program.
We have three switches and 3 sensors for the content's level of the containers. Two of the sensors are 2 bits wide because we needed 3 states:
- 00 -> Empty
- 01 -> 1/4
- 11 -> Full
The sensor for the final container is 1 bit, we only need to detect if it's empty. The three switches are
- S -> On if the machine is started
- A -> On if the cleaning process should be started
- M -> On if two passes are requested
All 3 outputs are control bits. They are
- Mx -> Off if the motor x should be running
- P -> Off to fill the containers with water.
As for the implementation process, we have used the [edsim51[(https://edsim51.com) for simulating the program we wrote. We have not used interrupts because it will only make it more complex. If it was a real machine, we would have to make it more asynchronous. A lot of looping as used when the machine was idling and when using the timers in the microcontroller.
We start by setting the timer counter to be a 16 bits number (needed for later. We then read port 2 (inputs). We check if the final container is empty by reading its bit and light the warning light if it is set. if it's not, we check the first bit of sensor 1 (it's on if the first container is 1/4 full or more) and the start switch. We loop this part until all conditions are met.
mov tmod, #10h ; set timer 1 to 16bit verif: mov p2, #0ffh ; set p2 as input mov acc, p2; read p2 into the acc jb 0e4h, ll; jump if first bit of sensor 1 is not set (empty) anl acc, #41h; isolate s and sensor 1 bits xrl acc, #41h ; acc will clear if s and sensor 1 first bit is set jz lm1 ; jump if acc is zero jmp verif; loop verification ll: clr p1.0 ; light warning led jmp verif ; loop verification
Milling is very simple. We launch the first motor first. If two passes were selected (M is on), we wait until the second container is at least 1/4 full. Then, another looping until the first container is empty.
lm1: clr p1.4 ; launch motor 1 jnb p2.5, f1 ; jump if clapet is not on jnb p2.2, $; loop until sensor 2 says it's at least 1/4 clr p1.5 ;launch motor 2 if clapet is on f1: jb p2.0, $ ; loop until sensor 1 says empty
As soon as the sensor reports that the container is empty, we stop the first motor, then wait 5 seconds (For the purpose of the simulation, we used seconds instead of minutes) to stop the second one. Then we loop until the clean process is instantiated (The switch A is on).
setb p1.4 ; stop motor 1 mov r0, #10 ; 5 seconds (5 * 20) call secondsDelay ; delaying setb p1.5 ; stop motor 2 jnb p2.7, $ ; loop until a is set
First, we open the water pump and check until that the first 2 containers are full to close it. Then we start both motors, wait 3 seconds, then stop them. This process is repeated one more time, then we stop everything.
mov r7, #2; 2 cycle clean: clr p1.7 ; open pump checkEmpty: mov acc, p2 ; load port 2 anl acc, #0fh ; isolate sensor 1 & 2 xrl acc, #0fh ; clear if all is set jnz checkEmpty; loop until full setb p1.7 ; close pump clr p1.4; start motor 1 clr p1.5 ; start motor 2 mov r0, #10 ; 3 seconds (3 * 20) call secondsDelay setb p1.4 ; stop motor 1 setb p1.4 ; stop motor 2 djnz r7, clean jmp finish
We decided to use the timer imperatively instead of via interrupts. We implemented it via a procedure. As the timer uses a 16 bits counter, we can only set it for a duration up to 65 536 us. We decided to go with 50 ms in and loop it to attain the expected duration. The number of loops is set in the
r0 register (1 byte) before calling the timer subroutine.
; Setup Timer fiftyMsDelay: clr tr1 ; stop timer ; set up a 50 000 seconds delay by loading 65 536 - 50 000 = 15 536 (3CB0) mov th1, #3ch mov tl1, #0b0h setb tr1 ; start timer jnb tf1, $ ; loop until timer oveflow clr tf1 ; clear overflow clr tr1 ; stop timer ret secondsDelay: ; save processor state push psw loopTimer: call fiftyMsDelay ; 50 ms djnz r0, loopTimer ; loop until r0 equal zero ; restore processor state pop psw ret
Our code is very simple and makes the milling process a very straightforward one. But in the real world, there are often edge cases that the programs would have to handle. Like whether the motor is actually running or pausing the milling process.
Writing this was very stressful, but enjoyable as we have not had much experience writing assembly code. But we learn a lot during the implementation and it had become easier over time. Assembly is actually very simple, but also tedious to write. Next step will probably be porting this to Arduino.