Custom Search

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

Tags: , ,

Leave a Reply


Powered by WebRing.