PWM function in 16F876 I/O Processor
I have added a basic PWM service to the PIC 16F876 I2C Slave I/O Processor. Here is the updated JAL Source and a Blassic program for the Router. It reads the Analog input, and prints to the console or sounds a Beep using the PWM output.
Updated JAL Source for the 16F876 I/O Processor
-- Sebastien Lelong Copyright (c) 2008, http://sirloon.net
--
-- This file is part of the Sirbot Project (http://sirbot.org)
-- Released under the GPL license
--
-- !! changes by Phill, ProjectNotes.co.uk for
-- use with the 16F876a as an I/O extender for the Midge/OpenWrt
-- & Sunspot software driven routers.
-- 29/08/08 Added basic PWM support and I/O Command List
-- At Reset, PWM is set to 50% (2.5v)
-- I/O Commands
-- 01 - Write Next byte to Port B
-- 02 - Write Next Byte to H/W Serial Port
-- 03 - Next Read Byte is Latest Serial Port Rx Data OR ZERO
-- 04 - Next Read Byte is Analog in From Pin A0
-- 05 - Toggle Output Pin A1 (status led?)
-- 06 - Write Next Byte to Jal PWM Duty Cycle
-- Once a read data command has been sent, it will star in effect until
-- another command has been sent.
-- After a Write data or immediate command (05) is complete, the command
-- byte is set to zero (ready for another command)
include 16F876a_bert
--include sb_config
--include sb_protocol
--include sb_mainboard
-- !! 16F876a port C3&4 are the I2C i/o pins
-- AppNote says must be configured as input first
--pin_b4_direction = input
--pin_b1_direction = input
pin_c6_direction = output -- tx
pin_c7_direction = input -- rx
pin_c3_direction = input
pin_c4_direction = input
-- For testing purpose (checking if slave is responding to START/STOP signals)
-- you may want to activate 7bits address with interrupts (see spec)
;;SSPCON = 0b_0011_1110 -- slave 7bit address, start/stop interrupt
SSPCON = 0b_0011_0110 -- slave 7bit address
-- I2C slave hardware
-- Careful: we're in 7bits i2c address, *but* PIC
-- wants an address coded on 8bits, that is, with read/write bit
-- In master, slave address is:
-- 0x2E <=> 0b_0010_1110
-- So, in slave, by left-shifting once, we have:
-- 0x5C <=> 0b_0101_0110
SSPADD = 0x5C
-- init SSPSTAT
BF = false
WCOL = false
SSPOV = false
SSPIF = false
-- enable interrupts
SSPIE = true
GIE = true
PEIE = true
-- !! no ready/busy leds, we want port B all OUT
-- "Ready !" LED
-- var bit ready_led_direction is pin_b0_direction
-- var bit ready_led is pin_b0
-- ready_led_direction = output
-- Error LED
-- var bit error_led_direction is pin_b3_direction
-- var bit error_led is pin_b3
-- error_led_direction = output
var byte tmpstat
var byte tmpbuf
var byte data
-- !! add a few temp registers of our own
var byte serialtemp
var byte command
var bit a1f
command = 00
a1f = off
-- !! set up our a1 toggle bit
pin_a1_direction = output
pin_a1 = a1f
-- !! set up our adc in pin
pin_a0_direction = input
-- !!
-- test to set SSPBUF to see if it's changed while receiving
-- the first byte address
SSPBUF = "A"
function read_i2c() return byte is
tmpbuf = SSPBUF
return tmpbuf
end function
procedure write_i2c(byte in what) is
-- wait 'til buffer is empty
while BF loop end loop
var bit dosend = true
while dosend
loop
WCOL = false
-- try to write into buffer, checking collision
SSPBUF = what
if ! WCOL
then
-- ok, done
dosend = false
end if
-- else continue trying
end loop
CKP = 1
end procedure
procedure proceed_state_1() is
-- state 1:write operation, last byte is address, buffer full
-- byte is an address, it we get here, we just know master
-- wants to talk to us...
-- and we also know address is recognized (BF is set, see spec)
-- anyway, we must read buffer to reset BF bit
read_i2c()
end procedure
procedure proceed_state_2() is
-- state 2: write operation, last byte is data, buffer full
-- got data, need to echo char + 1
data = read_i2c()
-- ultimate data processing...
-- !! here do something useful
if command == 01 then
portb = data
data = 0
end if
if command == 02 then
serial_hw_write(data)
data = 0
end if
if command == 06 then
PWM_set_dutycycle (data,data)
data = 0
end if
command = data -- Not in a command, this must be a command.
-- if we don't handle it here, it may be a read data command
if command == 05 then
a1f = ! a1f
pin_a1 = a1f
command = 0 -- done with this command
end if
end procedure
procedure proceed_state_3() is
-- state 3: read operation, last byte is address, buffer empty
-- master wants to get a value from us
--!! decide what data to return
if command == 03 then
data = serialtemp -- last serial rx char
serialtemp = 0 -- clear it
end if
if command == 04 then
data = ADC_read_low_res(0)
end if
write_i2c(data)
end procedure
procedure proceed_state_4() is
-- state 4: read operation, last byte is data, buffer empty
-- master still wants to get a value from us
write_i2c(data)
-- note: this shouldn't occur
end procedure
procedure proceed_state_5() is
-- state 5: nack
-- master doesn't want to talk with us anymore
-- reset slave logic
-- AN734 does not talk about setting CKP, whereas spec says
-- it must be set. Some people say it can be error prone.
CKP = 1
data = 0
end procedure
procedure proceed_error() is
-- something went wrong, that is, XOR operations did not match
-- SSPSTAT bits
-- Just log current status
serial_hw_write("E")
serial_hw_write(SSPSTAT)
end procedure
procedure ssp_handler() is
pragma interrupt
if SSPIF
then
SSPIF = false
tmpstat = SSPSTAT
-- mask out unimportant bit
tmpstat = tmpstat & 0b_0010_1101
-- check state 1: write operation, last byte is address, buffer full
if (tmpstat ^ 0b_0000_1001) == false
then
proceed_state_1()
-- check state 2: write operation, last byte is data, buffer full
elsif (tmpstat ^ 0b_0010_1001) == false
then
proceed_state_2()
-- check state 3: read operation, last byte is address, buffer empty
elsif (tmpstat ^ 0b_0000_1100) == false
then
proceed_state_3()
-- check state 4: read operation, last byte is data, buffer empty
elsif (tmpstat ^ 0b_00101100) == false
then
proceed_state_4()
-- check state 5: nack
elsif (tmpstat ^ 0b_0010_1000) == false
then
proceed_state_5()
-- check only got a start signal (when using interrupts)
else
proceed_error()
end if
else
-- another interrupt. Weird...
-- ready_led = low
-- delay_10ms(100)
-- ready_led = high
serial_hw_write("*")
end if
end procedure
-- don't need status leds
--ready_led = low
--error_led = high
--delay_10ms(30)
--error_led = low
--delay_10ms(30)
--error_led = high
--delay_10ms(30)
--error_led = low
--delay_10ms(30)
--error_led = high
--delay_10ms(30)
--error_led = low
--ready_led = high
-- 29/08/08 add PWM support
pin_c2_direction = output
PWM_init_frequency (true,true)
PWM_set_dutycycle(128,128)
forever loop
-- just loop until interrupt is raised
-- !! poll the serial port
if RCIF then
serial_hw_read(serialtemp)
serial_hw_write(serialtemp) -- echo to confirm hardware is working!
end if
end loop
Next, the Blassic source
AnalogLightWarn.bas
The following Blassic program uses the PIC 16F876a I2C I/O processor to monitor a solar panel (1.5v max!) If the light reading changes by less than 2 up or down, no action is taken.
If the light increases by more than 2, a message is sent to the console.
If the light decreases by more than 2, a beep is sounded via a small speaker connected to the PWM output pin (Port C,2).
As the speaker is only 8 ohms, a 47 ohm resistor and 1uf capacitor are inline to limit the current through the speaker coil.
The Tone of the beep is quite low, this is because of the speed of the basic interpreter AND the time it takes to send 4 bytes of instruction via the I2C Interface.
Later I will look at using an alternative method of sending data to the 16F876 via two other led lines, simply clocking out the required data.
10 REM Read the Analog Input A0
15 REM if the level is a lot different from last time, say helo
20 REM
30 REM I2C Control
40 I2CAddress=34
50 I2CData=35
60 PIC16F876=46 : REM Address of Our Chip
70 REM Commands
80 AnalogRead = 4
90 PortA1Invert = 5 : REM Invert A1
100 REM Begin
105 last=0
110 POKE I2CAddress,PIC16F876 : REM Address the chip
120 POKE I2CData,AnalogRead
130 a = PEEK(I2CData)
135 REM PRINT a
140 POKE I2CData,PortA1Invert
150 IF ((a>last) AND (a-last > 2)) THEN GOSUB 300
160 IF ((a<last) AND ( last-a > 2)) THEN GOSUB 400
165 last = a : REM remember for next time
170 REM pause
180 FOR x=0 TO 1000: NEXT
190 GOTO 120
300 REM lighter
310 PRINT "Lighter"
320 RETURN
400 REM Darker
410 FOR N=1 TO 30
420 POKE I2CData,6 : POKE I2CData,255 : REM PWM=0v
430 POKE I2CData,6 : POKE I2CData,0 : REM PWM = 5v
450 NEXT N
460 RETURN
Ok