PIC PWM - hamstack.comPIC PWM 9/16/13!! ! ! ! ! ! ... These notes demonstrate generating PWM on the HamStack platform using the PIC 18F4620 and the PIC 18F46K22. ... Asm ...
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
These notes demonstrate generating PWM on the HamStack platform using the PIC 18F4620 and the PIC 18F46K22. Most demonstrations can use the DEV-1 HamStack Development Board and some require it, using the LCD, encoder, and LEDs.
PWM is generated using on-chip PWM hardware peripherals, the CCP (Capture/Compare/PWM) modules.
Software: the free Swordfish compiler Special Edition, (ver. 2.2.1.6).
Most current data sheet versions:
DS39626E: PIC18F2525/2620/4525/4620 Data Sheet DS41412F: PIC18(L)F2X/4XK22 Data Sheet
Data Sheet references look like this:
(4620 DS pg. 141) (46K22 DS pg. 36)
kj6hfr! PIC PWM 9/16/13! ! ! ! ! ! ! page 2 of 88
My intention: read the data sheets and get the chip to “do something” with minimum code, and to understand Library files better such as the PWM.bas module on the Swordfish site. All code is “demonstration”, meaning that there is no error checking and certain Microchip data sheet guidelines are not followed, such as making sure that the very first PWM cycle is complete by waiting until an interrupt flag is set. I also use power-up values in registers rather than explicitly setting them.
When a control register is loaded, the number is displayed in binary so it’s easy to compare that register with the data sheets. Other registers are displayed in decimal.
There is some code explanation but it’s best to read the referenced data sheet.
Some 18F46K22 code requires “special handling” as the free version of Swordfish is not set to load values into 40 Special Function Registers (SFRs) not in Access Ram.
All Swordfish code in these notes assumes using the Include files and HamStack Library files from Sierra Radio Systems (see appendix for details).
My PICkit2 programmer would not recognize the 18F46K22 until it was upgraded with the latest device file (see appendix for details).
A Saleae Logic probe proved to be a valuable bench tool, both here and especially when sending I2C commands from the HamStack to an NXP PCA9685 16 channel, 12 bit PWM controller. That project (‘Dial-a-Color’), having 3 encoders each separately control red, green, and blue PWM to an RGB LED, started this project as I wondered if this same thing could be done on an 18F46K22, knowing that it could not on an 18F4620, at least in hardware.
Corrections are welcome.
kj6hfr! PIC PWM 9/16/13! ! ! ! ! ! ! page 3 of 88
kj6hfr! PIC PWM 9/16/13! ! ! ! ! ! ! page 4 of 88
61
63
64
75
84
86
Code marked above for the DEV-1 board uses the rotary encoder, LCD, and LEDs but all code can be run with the HamStack mounted on the DEV-1 board.
Some 46K22 code cannot be re-written for the 4620 since the 46K22 has more capabilities (steering, slew rate control, more CCPs, ...)
CCP2CON (Capture/Compare 2 Control Register): Bits 2-3 are set to enable PWM mode (4620 DS pg. 141; 46K22 DS pg. 207).
CCPR2L (Capture/Compare/PWM Register 2 Low Byte, also called Duty Cycle Register): this byte value partly determines the duty cycle, how long C1 (pin 16) will be high. (4620 DS pg.146). A value of 128 generates a 50% duty cycle.
TRISC.1 sets pin C1 as output.
T2CON: (Timer 2 Control Register): setting bit 2 turns Timer 2 on, starting PWM (4620 DS pg.135).
The code above does not explicitly set the Timer2 Period Register (PR2) even though the value in this register partly determines the PWM period.
PR2 has a default power-up setting of 255 (4620 DS pg. 52).
kj6hfr! PIC PWM 9/16/13! ! ! ! ! ! ! page 7 of 88
HamStack with 18F4620 and 10 MHz crystal:
! PWM Period = (255 + 1)(4)(.0000001)(1) = 102.4 µs (9766 Hz)! Pulse Width in time = (512)(.0000001)(1) = 51.2 µs! Duty Cycle Ratio = (512 / 1024) = .5 = 50%
HamStack with 18F46K22 and 16 MHz crystal (PLL off):
! Period = (255 + 1)(4)(0.0000000625)(1) = 64 µs (15625 Hz)! Pulse Width in time = (512)(.0000000625)(1) = 32 µs! Duty Cycle Ratio = (512 / 1024) = .5 = 50%
Period and Duty Cycle calculations ( screenshots from 46K22 DS pg. 187)
PR2 = 255 (default power-up value)Tosc = 1/10,000,000 = 0.0000001 (18F4620 running at 10 MHz)Tosc = 1/16,000,000 = 0.0000000625 (18F46K22 running at 16 MHz)TMR2 Prescale Value = 1
CCPR2L:CCP2CON<5:4> is a ten bit value.
The eight MSBs are the CCPR2L value, here 128 or 1000 0000 in binary. The remaining two LSBs are in CCP2CON and are 0 as a default power-up value.
Combining the 8 and 2 bits in this case = 10 000 0000 or 512 in decimal.
kj6hfr! PIC PWM 9/16/13! ! ! ! ! ! ! page 8 of 88
Screen shots from a Saleae Logic probe on C1
HamStack with 18F46K22 and 16 MHz crystal (PLL on):
Period = (255 + 1)(4)(0.000000015625)(1) = 16 µs (62500 Hz) Pulse Width in time = (512) (.0000000625)(1) = 8 µs Duty Cycle Ratio = (512 / 1024) = .5 = 50%
kj6hfr! PIC PWM 9/16/13! ! ! ! ! ! ! page 9 of 88
To minimize code, the previous examples used Timer2, the default power-up PWM Timer for both chips. Also, its Period Register, PR2, comes loaded with 255 on power-up.
The 2 CCP modules in the 18F4620 (CCP1, CCP2) must use Timer2 for PWM.
The 5 CCP modules in the 18F46K22 (CCP1, CCP2, CCP3, CCP4, CCP5) can use Timer2 (power-up default), Timer4, or Timer6.
CCP OUTPUT pins
18F4620: CCP1 C2 CCP2 C1 (or B3 via Config settings for CCP2MX)
18F46K22L CCP1 C2 CCP2 C1 (or B3 via Config settings for CCP2MX) CCP3 B5 (or E0 via Config settings for CCP3MX) CCP4 D1 CCP5 E2
Still using the default power-up Timer2 with its Period Register set for 255 on power-up, I thought the following code changes might work with the 18F46K22:
CCP3CON = %00001100 // enable CCP3 for PWMCCPR3L = 128 // set for 50% duty cycleTRISB.5 = 0 // pin 38 18F46K22T2CON = %00000100
OR
CCP4CON = %00001100 // enable CCP4 for PWMCCPR4L = 128 // set for 50% duty cycleTRISD.1 = 0 // pin 20 18F46K22T2CON = %00000100
OR
CCP5CON = %00001100 // enable CCP5 for PWMCCPR5L = 128 // set for 50% duty cycleTRISE.2 = 0 // pin 10 18F46K22T2CON = %00000100
#option SWORDFISH_SE = truePublic Macro write_sfr(sfr, val) #if (SWORDFISH_SE) WREG = val Asm movff WREG, sfr End Asm #else sfr = val #endifEnd Macro
write_sfr(CCP3CON, %00001100) // enable CCP3 for PWMwrite_sfr(CCPR3L, 128) // set for 50% duty cycleTRISE.0 = 0 // pin 8 18F46K22; scope this pin T2CON = %00000100
While (TRUE)Wend
The problem with CCP3 is that the default output pin, B5, is grounded on the HamStack board. I’m guessing that this pin, labelled PGM, is grounded “in order to prevent inadvertent entry into Programming mode” (Microchip Application Note AN910).
To achieve PWM with CCP3, we have to use both the macro and Config the output pin to E0.
Note: I think the 46K22 DS pg. 180 is mistaken when it states that the default output pin for CCP3 is E0. This configuration bit retains whatever value it has been programmed to through power cycles. But when the 18F46K22 is erased, the CCP3MX bit is set to 1, for B5.
Neither the Mecanique nor the Sierra Radio Systems 18F46K22. bas Include files explicitly have a default setting for CCP3MX.
Duty Cycle Calculations ( screenshot from 46K22 DS pg. 187)
In the previous code, when index = 0 and is loaded into CCPR2L, the duty cycle = 0% for either chip at any oscillator frequency. This depends on the power-up condition where the two lowest bits (CCPxCON<5:4>) are initialized to zero.
Duty Cycle Ratio = 0 / 1024 = 0 = 0%
When index = 255, the duty cycle is close to 100% for either chip at any oscillator frequency. Duty Cycle Ratio = 1020/1024 = 0.996 = 99.6%
The following code, by setting the two duty cycle LSBs, generates a duty cycle slightly higher than the 99.6% seen previously.
Duty Cycle Ratio = 1023/1024 = 0.999023 = 99.9%! ! Pulse Width in time = (1023)(.0000001)(1)! ! = 102.3 µs
{DutyCycle4 HamStack with 18F46K22; DEV-1 board optional}Device = 18F46K22Clock = 16Config PLLCFG = OFF
CCP2CON = %00111100 // Both duty cycle LSBs set CCPR2L = 255TRISC.1 = 0 // pin 16 18F46K22; scope this pinT2CON = %00000100
While (TRUE)Wend
The DutyCycle4 code above, by setting the two duty cycle LSBs, generates the highest possible duty cycle, not counting 100%, since the Timer has to count up every bit of the ten bits except one.
Duty Cycle Ratio = 1023/1024 = 0.999023 = 99.90%! ! Pulse Width in time = (1023)(0.0000000625)(1)!! = 63.93 µs
The period is 64 µs, so the off time is approximately 70 ns.
A Saleae Logic probe capture is shown below. The pulse shown is about 64 µs.
Similarly, the following code, by setting one of the two duty cycle LSBs, generates a duty cycle slightly higher than the 0% seen previously, the shortest possible pulse under these conditions.
Duty Cycle Ratio = 1/1024 = 0.000976 = .097%! ! Pulse Width in time!= (1)(0.0000000625)(1)! ! = 62.5 ns
{DutyCycle5 HamStack with 18F46K22; DEV-1 board optional}Device = 18F46K22Clock = 16Config PLLCFG = OFF
CCP2CON = %00011100 // One duty cycle LSB set CCPR2L = 0 // 8 duty cycle MSBs not setTRISC.1 = 0 // pin 16 18F4620T2CON = %00000100
While (TRUE)Wend
A Saleae Logic probe at its highest acquisition speed has trouble capturing this 62.5 nanosecond pulse; the pulse width varied from about 45 to 84 ns.
The code below uses the DEV-1 board’s rotary encoder, LCD, and LED4. Most of the encoder code is directly from the DEV-1 test program.
The encoder will crank the duty cycle up and down, displaying the value of the index variable on the LCD, from 0 to 1023.
Covering the full range requires slightly more than 51 full turns of the encoder.
The Config CCP2MX = portBE redirects the PWM from C1 to B3; hence the TRISB.3 statement so the dimming and brightening can be seen directly on the DEV-1 board’s LED4.
Dim encoder_last_a As BitDim encoder_last_b As BitDim encoder_now_a As BitDim encoder_now_b As BitDim button_l As PORTE.0 // Rotary encoder LeftDim button_r As PORTE.1 // Rotary encoder RightDim index As Word // variable to hold duty cycle valueindex = 0
ClsWriteAt(1,1, "index = ", DecToStr(index), " ")CCP2CON = %00001100TRISB.3 = 0 // set B3 as output to drive LED4T2CON = %00000100
While (TRUE) encoder_now_a = button_l encoder_now_b = button_r If encoder_now_a <> encoder_last_a Or encoder_now_b <> encoder_last_b Then If encoder_last_a=1 And encoder_last_b=1 And encoder_now_a=0 And encoder_now_b=1 Then If (index <= 1022) Then Inc(index) WriteAt(1,9, DecToStr(index), " ") EndIf EndIf If encoder_last_a=0 And encoder_last_b=1 And encoder_now_a=1 And encoder_now_b=1 Then If (index >= 1) Then Dec(index) WriteAt(1,9, DecToStr(index), " ") EndIf EndIf EndIf encoder_last_a = encoder_now_a encoder_last_b = encoder_now_b CCP2CON.5 = index.1 // load duty cycle LSBs CCP2CON.4 = index.0 CCPR2L = index >>2 // load duty cycle MSBs Wend
Dim encoder_last_a As BitDim encoder_last_b As BitDim encoder_now_a As BitDim encoder_now_b As BitDim button_l As PORTE.0 ' Rotary encoder LeftDim button_r As PORTE.1 ' Rotary encoder RightDim index As Word
Function DutyCycle(pValue As Word) As Word result = (pValue*10000)/1024End Function
In the following program, each turn of the encoder adds or subtracts 16 from the index variable so that slightly more than 3 full encoder turns will get to the full scale of 1023.
The approximate duty cycle is calculated and displayed using the same LCD display idea in the Analog Input program in the HamStack Microcontroller Project Platform manual.
The DEV-1 board is used with its encoder, LCD and LED4.
While (TRUE) encoder_now_a = button_l encoder_now_b = button_r
If encoder_now_a <> encoder_last_a Or encoder_now_b <> encoder_last_b Then If encoder_last_a=1 And encoder_last_b=1 And encoder_now_a=0 And encoder_now_b=1 Then If (index <= 992) Then index = index + 16 ElseIf (index = 1008) Then index = 1023 EndIf
If encoder_last_a=0 And encoder_last_b=1 And encoder_now_a=1 And encoder_now_b=1 Then If (index = 1023) Then index = 1008 ElseIf (index >= 16) Then index = index - 16 EndIf WriteAt(1,9, DecToStr(index), " ") WriteAt(2,9, DecToStr(DutyCycle(index)/100),".",DecToStr(DutyCycle(index),2),"% ") EndIf
The code below has been modified to work with the 18F46K22 on the DEV-1 board.
Each turn of the encoder adds or subtracts 8 from the index variable so that slightly more than 6 full encoder turns will get to the full scale of 1023.
The one code line in red allows the appropriate SFRs to be set with the SE version of Swordfish. Both the hs_convert.bas and the hs_lcd.bas libraries call the hs_utils.bas library which has the necessary macro and statements to make the PORTD pins digital.
Dim encoder_last_a As BitDim encoder_last_b As BitDim encoder_now_a As BitDim encoder_now_b As BitDim button_l As PORTE.0 ' Rotary encoder LeftDim button_r As PORTE.1 ' Rotary encoder RightDim index As Word
Function DutyCycle(pValue As Word) As Word result = (pValue*10000)/1024End Function
While (TRUE) encoder_now_a = button_l encoder_now_b = button_r If encoder_now_a <> encoder_last_a Or encoder_now_b <> encoder_last_b Then If encoder_last_a=1 And encoder_last_b=1 And encoder_now_a=0 And encoder_now_b=1 Then If (index <= 1008) Then index = index + 8 ElseIf (index = 1016) Then index = 1023 EndIf WriteAt(1,9, DecToStr(index)) WriteAt(2,9, DecToStr(DutyCycle(index)/100),".",DecToStr(DutyCycle(index),2),"% ") EndIf If encoder_last_a=0 And encoder_last_b=1 And encoder_now_a=1 And encoder_now_b=1 Then If (index = 1023) Then index = 1016 ElseIf (index >= 8) Then index = index - 8 EndIf WriteAt(1,9, DecToStr(index), " ") WriteAt(2,9, DecToStr(DutyCycle(index)/100),".",DecToStr(DutyCycle(index),2),"% ") EndIf EndIf encoder_last_a = encoder_now_a encoder_last_b = encoder_now_b CCP2CON.5 = index.1 CCP2CON.4 = index.0 CCPR2L = index >>2 Wend
As can be seen from the equation above (46K22, DS pg. 187), the PWM Period is determined by 3 factors:
• The value in the timer’s Period Register, PRx• The chip’s oscillator frequency, Tosc• The value of the prescale bits in the timer’s control register TMRx, determined
by the bits in the TxCON register.
{Period1: HamStack with 18F46K22; DEV-1 board optional}Device = 18F46K22Clock = 16Config PLLCFG = OFF Dim index As Byte, UpDir As Booleanindex = 255
The above code, Period1, keeps a 50% duty cycle through 64 different periods, from 64 µs (15625 Hz) to 1 µs (1 MHz), by decreasing and increasing the value in PR2. The value in CCPR2L is scaled so as to keep the 50% duty cycle.
The waveforms can be more easily seen by roughly increasing the delay as the period gets shorter by changing the DelayMS(50) to: DelayMS(255 - index) + 200).
Or the 5 shortest periods can be slowed way down for display with code something like this: If (index <= 19) then DelayMS(5000) else DelayMS(255 - index) EndIf
This code works as well on the 18F4620 by changing:
Device = 18F46K22 TO Device = 18F4620 Clock = 16 Clock = 10 Config PLLCFG = OFF // Config PLLCFG = OFF
The waveform can be listened to on the DEV-1 speaker by changing:
CCP2CON TO CCP1CON TRISC.1 TRISC.2 CCPR2L CCPR1L AND removing any Delay(MS) statements and adding:
If(UpDir) Then DelayMS(2) Else DelayMS(3) EndIf
Example Duty Cycle Ratio calculation when index = 19
The code below should generate the shortest period and the lowest duty cycle, running the HamStack with 18F46K22 at 64 MHz, minimum PR2, and no oscillator prescaling.
SLRCON = %00011011 // remove PORTC slew rate limitingCCP2CON = %00011100 // enable PWM on CCP2 and one LSBTRISC.1 = 0 // pin 16 18F46K22; scope this pinPR2 = 0 // load for shortest period ̃ 63 nsCCPR2L = 0 // load for shortest duty cycleT2CON = %00000100 // start Timer 2
While (TRUE)Wend
The third way to adjust the period is by setting the prescaler bits (46K22 DS pg. 177). Using the code above and changing a single bit will increase the period sixteen times to 1 µs (16)(62.5 ns).
CCP2CON = %00011100 // enable PWM on CCP2 and one LSBTRISC.1 = 0 // pin 16 18F4620; scope this pinPR2 = 0 // load for shortest period ̃ 100 nsCCPR2L = 0 // load for shortest duty cycleT2CON = %00000100 // start Timer 2
While (TRUE)Wend
The code above generates the shortest period, 100 ns, using the 18F4620 clocked at its maximum of 40 MHz. As before, changing one bit to prescale at 16 would increase the period to ~ 1.6 µs.
Generating the longest period from either chip would mean setting the prescaler to 16, leaving the Period Register PRx at its maximum of 255, and running a slow oscillator. The chip could be clocked externally at an extremely slow rate, even down to DC (46K22 DS pg. 449).
The code below uses the HamStack with 18F4620 in its normal configuration running at 10 MHz but with full prescaling of 16.
The period should be, and is when measured, (256)(4)(0.0000001)(16) = 0.0016384 or ~ 1.6 ms
OSCCON = %00000010 // use internal oscillator at 31 kHzCCP2CON = %00001100 // enable PWMTRISC.1 = 0 // pin 16 18F4620; scope this pinCCPR2L = 128 // load for 50% duty cycleT2CON = %00000110 // start Timer2; prescaler = 16
While (TRUE)Wend
The code below adds a single line, setting the OSCCON register to use its slowest internal oscillator INTRC at approximately 31 kHz (4620 DS pg. 32).
The period would then be: (256)(4)(0.00003225)(16) = 0.528 or ~ one half second.
4620 DS pg. 382 shows that INTRC can vary by approximately 10 kHz across temperature.
A Saleae Logic probe measures the period as .503 seconds. Assuming the Logic probe is fairly accurate capturing this low frequency signal, we can calculate the approximate frequency of INTRC.
.503 / (1024)(16) = ~0.0000307 = ~32572 Hz
Through only code changes, the 18F4620 can generate periods from .5 second to 100 ns, or more than six orders of magnitude.
The code below adds a single line, setting the OSCCON register to use its slowest internal oscillator INTRC at approximately 31.25 kHz (46K22 DS pg. 32).
The period would then be: (256)(4)(0.000032)(16) = 0.524 or ~ one half second.
{Period8: HamStack with 18F46K22; DEV-1 board optional}Device = 18F46K22Clock = 16Config PLLCFG = OFF
A Saleae Logic probe measured the period as .533 seconds. Assuming the Logic probe is fairly accurate capturing this low frequency signal, we can calculate the approximate frequency of INTRC.
.533 / (1024)(16) = ~0.0000325 = ~30769 Hz
Through only code changes, the 18F46K22 can generate periods from .5 second to 63 ns, or more than six orders of magnitude.
The 18F4620 has two CCPs both of which use Timer2. This chip can therefore generate, using both CCPs simultaneously, 1 period and 2 duty cycles.
With the 10 MHz crystal as the oscillator, the default power-up PR2 value of 255, and no pre-scaling, the code below generates PWM on both C1 and C2 with a period of 102.4 µs.
Pin C2, controlled by CCP1, has a duty cycle of 82.8%. Pin C1, controlled by CCP2, has a duty cycle of 18.75%
These Saleae Logic probe results agree fairly well with the calculated values.
The 18F46K22 has five CCPs, any of which can use 3 different Timers for PWM, Timer2, Timer4, or Timer6.
Consequently this chip can simultaneously generate PWM with 3 different periods and 5 different duty cycles.
To program the 18F46K22 for 5 duty cycles and 3 periods, the choices on the left shown below were made. The values on the right half were calculated from those choices on the left.
The decimal value representing the ten bit value CCPRxL:CCPxCON<5:4> was rounded before being converted to CCPRxL and the two LSBs.
Calculation Examples:
PR4 and Pre(scale) value:Given a Period of 128 µs, (PR4 + 1)(prescale value) = .000128 / (4)(0.0000000625) = 512
Since PR4 cannot be larger than 255: (PR4 + 1) = 512 / (try 4 for prescale value) = 128 PR4 = 128 - 1 = 127 and Pre(scale) = 4
10 bit decimal value for duty cycle:Given a duty cycle ratio of .1, CCPRxL:CCPxCON<5:4> = (.1)(4)(PR4 + 1) = 51.2 round(51.2) = 51 51 = % 00 0011 0011 = 0000 1100 11 (separating out the two LSBs) CCPR4L = %00001100 = 12 LSBs = %11 = 3
The 18F46K22 oscillator can be changed from the 16 MHz crystal to its internal, factory calibrated, 16 MHz oscillator by adding one line of code:
OSCCON = %01110010
The internal oscillator can then be tuned above and below its calibrated value by adding one additional line of code:
OSCTUNE = %00100000 // tune to minimum frequency OR OSCTUNE = %00000000 // factory calibrated frequency OR OSCTUNE = %00011111 // tune to maximum frequency
The Saleae Logic probe measurements change slightly as you move the cursor along the captured waveforms.
Below are both the minimum and maximum Saleae measurements for the 85 µs period with the internal oscillator set for minimum, factory, and maximum. Delta Hz shows the Saleae uncertainty of approximately 6 Hz capturing the 85 µs period while running at its maximum acquisition speed.
The frequency of the internal 16 MHz oscillator appears to be slightly higher and differ from the crystal oscillator by less than 1 %.
{EnhancedCCP1: HamStack with 18F4620; DEV-1 board optional}Device = 18F4620Clock = 10 Dim index As Byte, UpDir As Booleanindex = 255
CCP1CON = %10001100 // enable Half-Bridge on ECCP1TRISC.2 = 0 // make P1A an output, i.e. pin C2TRISD.5 = 0 // make P1B an output, i.e. pin D5T2CON = $00000100
While (TRUE) If (index = 255) Then UpDir = false ElseIf (index = 0) Then UpDir = true EndIf
If (UpDir) Then Inc(index) Else Dec(index) EndIf CCPR1L = index DelayMS(4)Wend
The 18F4620 has 2 CCP (Capture/Compare/PWM) modules. Although CCP1 can operate in standard PWM mode, it is also an Enhanced CCP or ECCP.
The enhanced mode can simultaneously drive two pins (Dual PWM: Half-Bridge; P1A and P1B) or four pins (Quad PWM: Full Bridge; P1A, P1B, P1C, and P1D).
The code above drives 2 pins with complementary output levels. LEDs hooked up to both output pins will brighten and dim out of phase.
{EnhancedCCP2: HamStack with 18F4620; DEV-1 board optional}Device = 18F4620Clock = 10 Dim index As Byte, UpDir As Booleanindex = 255
CCP1CON = %10001100 // enable Half-Bridge on Enhanced CCP1PWM1CON = %01111111 // set for maximum dead-band delayTRISC.2 = 0 // make P1A an output, i.e. C2TRISD.5 = 0 // make P1B an output, i.e. D5T2CON = %00000100
While (TRUE) If (index = 255) Then UpDir = false ElseIf (index = 0) Then UpDir = true EndIf
If (UpDir) Then Inc(index) Else Dec(index) EndIf CCPR1L = index DelayMS(4)Wend
If the complementary outputs are driving a real world load, then there could be “shoot-through current.
This can be avoided by programming one additional register for dead-band delay, so both outputs are never simultaneously on or off at the same time.
The 18F46K22 has 5 CCP (Capture/Compare/PWM) modules. Although CCP1-3 can operate in standard PWM mode, they also can operate as Enhanced CCPs or ECCPs.
1 ECCP can simultaneously drive two pins (Dual PWM: Half-Bridge) and 2 ECCPs can drive two pins or four pins (Quad PWM: Full Bridge).
ECCP OUTPUT pins
18F4620: ECCP1 Half Bridge P1A P1B C2 D5
Full Bridge P2A P2B P2C P2D C2 D5 D6 D7
18F46K22 ECCP1 Full or Half Bridge P1A P1B P1C P1D C2 D5 D6 D7
ECCP2 Full or Half Bridge P2A P2B P2C P2D (erased chip default pins) C1 D2 D3 D4 (set by config statement) B3 (CCP2MX fuse) C0 (P2BMX fuse)
ECCP3 Half Bridge P3A P3B (erased chip default pins) B5 E1 set by config statement) E0 (CCP3MX fuse)
18F46K22 Config statements (appropriate TRIS statements also needed) Config CCP2MX = PORTC1 Pin C1 is output; default if chip is erased or Config CCP2MX = PORTB3 Pin B3 is output
Config CCP3MX = PORTB5 Pin B5 is output; default if chip is erased or (B5 is grounded on HamStack board) Config CCP3MX = PORTE0 Pin E0 is output
Config P2BMX = PORTD2 Pin D2 is output; default if chip is erased or Config P2BMX = PORTC0 Pin C0 is output
While(true) If (index = 255) Then UpDir = false ElseIf (index = 0) Then UpDir = true EndIf If (UpDir) Then Inc(index) Else Dec(index) EndIf CCPR2L = index DelayMS(4)Wend
Both 18F46K22 ECCP modules which can drive full-bridge can also drive half-bridge (ECCP1 and ECCP2)
The code below uses both the status LED and LED4 on the DEV-1 board as well as the default Timer2 and default power-up value of 255 in PR2.
Once the code is running, comment out {PLLCFG = OFF} to let the board run at 64 MHz; you can still see on a scope that both LEDs are being pulse width modulated.
Duplicating the previous result using the Half-Bridge module, ECCP3, is more difficult on the DEV-1 board:
uses different output pins (default P3A = B5, P3B = E1) but configure P3A to E0 (B5 is grounded on the HamStack board) remove Enc_A and Enc_B jumpers on DEV-1 use SFR fix.
The following code shows some features of Full-Bridge PWM with direction change on the DEV-1 board using the 18F46K22.
IO ports B3 (DEV-1 LED4), C0 (HamStack status LED), and D4 are used; current limited LEDs have been added to pins D3 and D4.
Full-Bridge output reverse OFF: DEV-1 LED4( B3); D4 LED D3 LED turns on at full brightness status LED C0 turns on and ramps up to full brightness.
Full-Bridge output forward OFF: status LED (C0); D3 LED LED4 turns on at full brightness D4 LED comes on at full brightness and ramps off.
When the variable index is being incremented, CCP2CON is set for Full-Bridge output reverse (%11001100). This condition causes P2B (C0 status LED) to be modulated and P2C (D3) to be active (5V), while P2A(B3) and P2D (D4) are inactive (GND).
When the variable index starts to be decremented, there is a direction change to Full-Bridge output forward (CCP2CON = %01001100).
This condition causes P2D (D4 LED) to be modulated and P2A (B3 = DEV-1 LED4) to be active (5V). P2B (CO or status LED) and P2C (D3 LED) are inactive (GND).
46K22 DS pg. 206
All these conditions are easier to observe at a 4 MHz clock speed by uncommenting
//osccon = %01010010
This code line causes the HamStack to use its internal oscillator at 4 MHz rather than the 16 MHz crystal.
Like many previous programs, this one relies on Timer2 being the default timer for the CCP and ECCP modules and that its Period Register PR2 comes loaded with 255 on power-up.
The same results can be seen using PWM1CON, CCP1CON, CCPR1L, making C2, D5, D6, and D7 outputs with TRIS statements, and putting LEDs on those 4 outputs.
PWM2CON = %01111111 // maximum dead-band delayTRISB.3 = 0 // LED4 on DEV-1 boardTRISC.0 = 0 // status LED on HamStack boardTRISD.3 = 0 // add current limited LED to D3TRISD.4 = 0 // add current limited LED to D4T2CON = %00000100//osccon = %01010010
While(true) If (index = 255) Then UpDir = false ElseIf (index = 0) Then UpDir = true EndIf If (UpDir) Then Inc(index) CCP2CON = %11001100 // set Full-Bridge output reverse Else Dec(index) CCP2CON = %01001100 // set Full_Bridge output forward EndIf CCPR2L = index DelayMS(4) Wend
The 18F46K22, unlike the 18F4620, has a PWM Steering Mode. where the PWM signal can be put on any or all of the four output pins, PxA - PxD (46K22 DS pg. 200).
The code below identically modulates 4 pins at the same time:
CCP2CON = %00001100 // put ECCP2 in single output PWM modePSTR2CON = %00001111 // steer PWM to all 4 output pinsTRISB.3 = 0TRISC.0 = 0TRISD.3 = 0TRISD.4 = 0T2CON = %00000100
While(true) If (index = 255) Then UpDir = false ElseIf (index = 0) Then UpDir = true EndIf If (UpDir) Then Inc(index) Else Dec(index) EndIf CCPR2L = index DelayMS(4) Wend
{Sleep1: HamStack with 18F4620; DEV-1 board}Device = 18F4620Clock = 10Config CCP2MX = PORTBE, WDTPS = 512 // WDT OFF from Include file
#option WDT = true // compiler tickles WDT in delay routinesCCP2CON = %00001100TRISB.3 = 0 // shows on LED4 on DEV-1 boardT2CON = %00000100WDTCON = %00000001 // turn on WDT via software
While (TRUE)CCPR2L = 192 // 75% duty cycleDelayMS(2048)
Asm sleep // had to have WDT on to wake chip from sleepEnd Asm
CCPR2L = 16 // 6.25% duty cycleDelayMS(2048)
Asm sleepEnd Asm
Wend
After 2+ seconds, the code above puts the chip to sleep, which shuts off all clock sources, thus no longer clocking the CCP module, set for, at first, a 75% duty cycle.
When awakened by the Watch Dog Timer (WDT), the code executes in place, thereby clocking the CCP with a 6.25% duty cycle, sleeps, and back to 75%.
The WDT times out after the chip goes to sleep in approximately 2+ seconds. When the WDT is enabled, an internal oscillator is turned on ( ~32 kHz). The 32k clock is divided by 128 = 250 Hz or 4 ms. The WDTPS (Watch Dog Timer Post Scaler) multiples 4 ms by 512 to get 2.048 seconds.
The CCP2 output was redirected from RC1 to RB3 to show on LED4 on the DEV-1 board. The output is left either high or low when the chip goes to sleep as no effort was made to leave the shutdown CCP in a known state.
While (TRUE)CCPR2L = 192 // 75% duty cycleDelayMS(2048)
Asm sleepEnd Asm
CCPR2L = 16 // 6.25% duty cycleDelayMS(2048)
Asm sleepEnd Asm
Wend
After 2+ seconds, the code above puts the chip to sleep, but unlike before, the Idle bit has been set so that the primary oscillator keeps running, thus clocking the CCP module. On a scope the PWM does not disappear like before.
Each PWM duty cycle runs for about 4 seconds (2 from the delay + 2 from the WDT).
#option WDT = true // compiler tickles WDT in delay routinesCCP2CON = %00001100TRISB.3 = 0 // shows on LED4 on DEV-1 boardT2CON = %00000100WDTCON = %00000001 // turn on WDT via software
While (TRUE)CCPR2L = 192 // 75% duty cycleDelayMS(2048)
Asm sleep // had to have WDT on to wake chip from sleepEnd Asm
CCPR2L = 16 // 6.25% duty cycleDelayMS(2048)
Asm sleepEnd Asm
Wend
The Sierra 18F46K22.bas Include file sets the WDT to be controlled by software and sets the Postscaler to 8192. So the Postscaler is decreased in the Config statement to 512 which is approximately 2.048 seconds and the WDTCON is set to turn the WDT on.
As before, without the Idle bit set, entering sleep will turn the primary oscillator off so CCP2 will not be clocked for a pattern of 2+ seconds high duty cycle, 2+ seconds off, and 2+ seconds low duty cycle.
Enabling the Idle bit as in Sleep2 will keep clocking CCP2 giving the pattern: 4+ seconds high duty cycle, 4+ seconds low duty cycle.
The Parallax Continuous Rotation Servo rotates CCW with a 1.7 ms pulse, CW with a 1.3 ms pulse, and holds with a 1.5 ms pulse.
“In order for smooth rotation, the servo needs a 20 ms pause between pulses” (#900-00008 data sheet pg. 5).
Parallax generates the required PWM by generating a pulse and then separately generating the 20 ms delay.
This experiment will approximate the required conditions by setting the HamStack for PWM, to generate both the pulse and the delay together. The minimum and maximum periods are calculated below for all oscillator frequencies.
That worked! The servo pot was adjusted for no rotation.
The servo twitches slightly CW once every 15 - 20 HamStack resets, and also when the power is cycled.
Below are Saleae Logic probe screenshots, in close agreement with the calculated values
The code below, using an internal 500 kHz oscillator, prescale = 16, PR2 = 167 and duty cycle = 47, should produce an approximate 1.5 ms pulse followed by 20 ms.
This output will be used to center the servo, by adjusting the pot on the servo until rotation stops.
The following code combines all three states using the DEV-1 board. On power-up the servo has been adjusted for no rotation.
Turning the encoder once CCW will cause maximum servo CCW rotation. Turning back CW will stop it, and one more turn CW will cause maximum servo CW rotation.
{servo2 HamStack with 18F46K22; DEV-1 board}Device = 18F46K22Config PLLCFG = OFF
#option SWORDFISH_SE = true // SFR fixPublic Macro write_sfr(sfr, val) #if (SWORDFISH_SE) WREG = val Asm movff WREG, sfr End Asm #else sfr = val #endifEnd Macro
Dim encoder_last_a As BitDim encoder_last_b As BitDim encoder_now_a As BitDim encoder_now_b As BitDim button_l As PORTE.0 ' Rotary encoder LeftDim button_r As PORTE.1 ' Rotary encoder RightDim index As ByteDim indexcopy As Byte
While (TRUE)encoder_now_a = button_lencoder_now_b = button_rIf encoder_now_a <> encoder_last_a Or encoder_now_b <>encoder_last_b Then If encoder_last_a=1 And encoder_last_b=1 And encoder_now_a=0 And encoder_now_b=1 Then If (index = 53) Then index = 47 PR2 = 167 ElseIf (index = 47) Then index = 41 PR2 = 166 EndIf EndIf If encoder_last_a=0 And encoder_last_b=1 And encoder_now_a=1 And encoder_now_b=1 Then If (index = 41) Then index = 47 PR2 = 167 ElseIf (index = 47) Then index = 53 PR2 = 169 EndIf EndIfEndIfencoder_last_a = encoder_now_aencoder_last_b = encoder_now_bIf (indexcopy <> index) Then // load new PR2 and duty cycle indexcopy = index // values only if they have CCPR2L = indexcopy >>2 // changed CCP2CON.4 = index.0 CCP2CON.5 = index.1EndIf indexcopy = indexWend
The following code does not change PR2, using only the middle value of 167, thus not trying to preserve an off time close to 20 ms.
On power-up the servo has been adjusted for no rotation. The encoder can be turned approximately 6 - 7 times CW to ramp the speed up to maximum, and similarly for CCW.
{servo3 HamStack with 18F46K22; DEV-1 board}Device = 18F46K22Config PLLCFG = OFF
#option SWORDFISH_SE = true // SFR fixPublic Macro write_sfr(sfr, val) #if (SWORDFISH_SE) WREG = val Asm movff WREG, sfr End Asm #else sfr = val #endifEnd Macro
Dim encoder_last_a As BitDim encoder_last_b As BitDim encoder_now_a As BitDim encoder_now_b As BitDim button_l As PORTE.0 ' Rotary encoder LeftDim button_r As PORTE.1 ' Rotary encoder RightDim index As ByteDim indexcopy As Byte
While (TRUE)encoder_now_a = button_lencoder_now_b = button_rIf encoder_now_a <> encoder_last_a Or encoder_now_b <>encoder_last_b Then If encoder_last_a=1 And encoder_last_b=1 And encoder_now_a=0 And encoder_now_b=1 Then If (index > 41) Then Dec(index) EndIf EndIf If encoder_last_a=0 And encoder_last_b=1 And encoder_now_a=1 And encoder_now_b=1 Then If (index <53) Then Inc(index) EndIf EndIfEndIfencoder_last_a = encoder_now_aencoder_last_b = encoder_now_bIf (indexcopy <> index) Then indexcopy = index CCPR2L = indexcopy >>2 CCP2CON.4 = index.0 CCP2CON.5 = index.1 EndIf indexcopy = indexWend
It is important to know about the 18F4620.bas and 18F46K22.bas Include files, which are installed when Swordfish is installed.
On my XP machine, these files are in C:\Program Files\Mecanique\Swordfish\IncludesOn my Windows 8 machine: C:\ProgramData\Mecanique\SwordfishSE\Includes
Unless overridden with Config statements, these Include.bas files set the initial power-up conditions for the chip, like which oscillator to use.
The 18F4620.bas Include file has these possible choices for the oscillator fuse:
no choice is made for FOSC. By default, the compiler chooses ECHP, EC oscillator (high power, >16 MHz),thereby expecting an external oscillator rather than a crystal. The HamStack 402 CPU kit therefore does not power-up using its 16 MHz crystal, and does nothing since there is no external oscillator attached. (46K22 DS pg. 27, 357).
We can overcome this problem by including this statement in code: Config FOSC = HSMP which will turn on the HS oscillator (medium power, 4 MHz-16 MHz), thereby using the 16 MHz crystal on the HamStack board.
My PICkit2 would not recognize an 18F46K22 until I upgraded its device file to version 1.62.14.
I put both of these files, downloaded from Microchip, in the same folder:
PK2V023200.hex PK2DeviceFile.dat
With the standalone PICkit2 application open, choose Download PICkit 2 Operating System and choose the above folder. After a minute the PICkit2 will be upgraded.
The free version of the Swordfish Compiler has 2 limitations, RAM and SFR access:
“The SE version is limited to the amount of RAM available to the user. This is because no bank switching code is available in the SE version. Please note that some newer Microchip devices have Special Function Registers (SFRs) that reside outside of access RAM. The SE version will be unable to access these SFRs by default...”
Some projects will require much or all of the available RAM so the commercial version of the compiler would be essential.
The PIC 18F46K22 unfortunately is one of those newer chips which has 40 of its SFRs outside of access RAM.
Working on my PIC18F4620 COOKBOOK Hands-on Experiments gave me familiarity with access RAM. Many instructions allow easy access to the SFRs, without memory bank switching.
For example, a BCF or Bit Clear f instruction can be used to clear a single bit, like in the TRISE SFR, by using the RAM access bit rather than bank switching (46K22 DS pg. 382). Below the instruction to clear bit 2 in TRISE is illustrated.
SFR Fix
f is byte sizeThe bit to be cleared has to be bit 0, bit 1, ...,bit 7
A single bit is set to 0 by this instruction
The RAM access bit determines if access RAM is used or bank switching
The 1001, or 9 is the first nibble of the instruction.
The bbb determines which bit is cleared, 010, or bit 2 in this example.
The RAM access bit a, determines whether to use access RAM or bank switching. We want to use access RAM so a = 0. The second nibble then equals 0100, or 4
So the first byte of this BCF instruction is 94. The ffff ffff is the address of the appropriate SFR in access RAM.
46K22 DS pg. 83 shows the SFRs. The TRISE SFR is at address F96. Since we can use access RAM, we need only the 96.
ffff ffff = 96.
Therefore, the complete instruction to clear bit 2 in the TRISE register, and make pin E2 an output, is 9496. The Swordfish compiler will generate a 9496 as the output for the code line TRISE.2 = 0
On 46K22 DS pg. 80 it says:
Note 1: Addresses F38h through F5Fh are also used by SFRs, but are not part of the Access RAM. User must always use the complete address or load the proper BSR value to access these registers.
Therefore we cannot use an instruction like in the example above, setting a = 0.
The MOVFF, or Move f to f, is an unusual instruction in that it can copy the contents of any memory location from hex 000 to hex FFF (46K22 DS pg. 398).
Since all of the 18F46K22 SFRs are in this space, MOVFF should be able to load all SFRs, even those not in access RAM, without bank switching.
The source data could be loaded into the WREG (Working Register), an SFR which does live in access RAM, and then copied from there using the MOVFF instruction into any SFR.
Experiment #1: hand-assemble instructions to try and get CCP5 to output a square wave on pin E2.
MOVLW, 0x0C 0E0C // load the value 00001100 into WREGMOVFF, CCP5CON CFE8 // source is WREG at address FE8 FF54 // destination is CCP5CON at address F54MOVLW, 0x80 0E80 // load the value 10000000 into WREGMOVFF, CCPR5L CFE8 // source is WREG at address FE8 FF55 // destination is CCPR5L at address F55BCF, TRISE.2 9496 // make pin E2 an output, use access RAMMOVLW, 0x04 0E04 // load the value 00000100 into WREGMOVWF, T2CON 6EBA // load the value 4 into T2CON using access RAM
Those nine instruction words should make a square wave at pin E2, using the default power-up value of PR2 = 255. Those instructions are basically equivalent to:
CCP5CON = %00001100 // enable PWM mode in CCP5 CCPR5L = 128 // set for 50% duty cycle TRISE.2 = 0 // make pin E2 an output T2CON = $00000100 // turn on Timer 2
There is no device declaration but the PICkit2 has already identified the target chip as an 18F46K22. There is no While/Wend code but once a CCP is set to output PWM, it should just keep going.
The oscillator has to be set manually to 0011 using the Configuration Word editor as described in the PIC18F4620 COOKBOOK.
Below shows the nine instruction words entered and then written to chip, and the result from a Saleae Logic probe.
Since those minimum instructions worked, it seemed like the following program should also work. It compiles without error but does not generate PWM on E2.
Below are the instruction words written to chip, with no PWM generation. Nine of the instructions, underlined in red, are almost the same as the instructions which do work.
There are additional instructions like the first two words in the reset vector which jump over the interrupt vector positions at 0004 - 0007. The 8C18 is, to me, a mystery instruction, frequently but not always stuck in by the compiler; it can be FFFFed out with seemingly no consequences. And at the end is BRA code for an endless loop.
But the 2 MOVFF instructions are not correct. Both the first and second C000 should be CFE8. When these are edited to the correct values and written to chip, then there is PWM on E2.
So the free version of the compiler is not generating correct code for the first word of the MOVFF instruction. At least when the MOVFF instruction is using the WREG as the source register.
What if a different SFR is used as a “temporary” register, like OSCTUNE?
Device = 18F46K22Clock = 16Config PLLCFG = OFFAsm movlw 0x0C // load WREG with 12 movwf 0x9b, 0 // copy into OSCTUNE register movff 0xf9b, 0xf54 // copy from OSCTUNE into CCP5CON movlw 0x80 // load WREG with 128 for 50% duty cycle movwf 0x9b, 0 // copy into OSCTUNE register movff 0xf9b, 0xF55 // copy from OSCTUNE into CCPR5LEnd AsmTRISE.2 = 0 // those two zeroes above in red are requiredT2CON = %00000100 // to tell the compiler to use theWhile(true) // RAM access bit a = 0Wend
The code above compiles and outputs PWM on pin E2, at the expense of extra instructions to not use the WREG with the MOVFF instruction.
In this specific case, no harm is done using the OSCTUNE register; because the chip is using its external crystal, this register has no effect on chip operation. Other SFRs like TMR1L will also work.
Using SFRs in this unintended way, obviously bad programming practice, can work OK with such simple experiments. But there may be unintended consequences when not paying close attention to a specific register.
For example, why not use T2CON as a temporary storage place since it gets correctly set at the end anyway?
Asm movlw 0x0C // load WREG with 12 movwf 0xba, 0 // copy into T2CON register movff 0xfba, 0xf54 // copy from T2CON into CCP5CON movlw 0x80 // load WREG with 128 for 50% duty cycle movwf 0xba, 0 // copy into T2CON register movff 0xfba, 0xF55 // copy from T2CON into CCPR5LEnd AsmTRISE.2 = 0T2CON = %00000100
But, the code above outputs no PWM on pin E2. Looking at the bit settings for the T2CON register shows why (46K22 DS pg. 135).
Bit 7 is unimplemented and always read as 0. Trying to load 80 into the T2CON register fails to set bit 7, and 0 is then copied into the CCPR5L register, resulting in 0% duty cycle.
Changing movlw, 0x80 to movlw 0x7F will give a duty cycle as close as possible to 50%.
Therefore, the down side of using T2CON as a temporary storage register is that the only possible duty cycles are < 50%.
Hunting through the Swordfish wiki looking for an easier way finally turned up an answer, in the entry entitled: “Bug with SwordfishSE PIC18F46K22? (works with PICBASIC PRO), Dec 27, 2011”
If you include the following #option switch and macro, you can load values to those SFRs outside of access RAM and also read from them.
// set this option true if building with the SE compiler#option SWORDFISH_SE = true//// these macros are used to read and write SFR's that are located in the upper// bank (bank 15), but are not part of the access bank//// since SE does not contain code to set the bank select register, setting// #option SWORDFISH_SE true will enable the write_sfr()/read_sfr() macros to// generate a MOVFF instruction so that setting the BSR is not required//
load the power-up default condition of those registers (46K22 DS pg. 317), with the comparator OFF (bit 7 = 0).
The comparator inputs and outputs are on 8 pins, A0 - A5, B1, and B3. If previous code had turned comparator functions on, then setting all pins as digital might cause out-of-spec current consumption on those pins (46K22 DS pg. 315). I don’t however understand the point of setting bit 3 (1 = Cx operates in Normal-Power, Higher Speed mode) since the comparator is off.
Likewise, the instruction:
VREFCON1 = $00 // added V1.6
loads the power-up condition of that register with the DAC off, pin A2 (46K22 DS pg. 347), presumably since previous code might have A2 configured as a DAC output.
Then, to my amazement, I discovered that Sierra Radio had already included these macros and option switch into hs_utils.bas.
And, then I found that the SetDigitalIO Library module is directly available for download on the Swordfish Wiki under Swordfish User / Modules / Updated Modules.
In conclusion, there are then at least 3 ways to fix SFR problems:
a. “hard load” via assembly language, avoiding WREG as a temporary register. It will be necessary to avoid using many other SFRs as temporary registers. This method is only for simple experiments.
b. Use the #option SWORDFISH_SE = true switch and the Library file “hs_utils.bas” to remove the analog input function from all applicable pins. Other Library files such as “hs_lcd.bas” include the “hs_utils.bas” Library file.
This strategy makes the macro available for writing to additional SFRs not in Access RAM including those SFRs already written to (ANSELA, ...).
c. Include the option switch and macro in your code to specifically set SFRs, like CCP5CON (for example the code: BasicPWM3). It is kind of redundant to include the macro code if any Library file is included which already has the macro code in it.
{LCD1: HamStack with 18F46K22 and DEV-1 board}Device = 18F46K22Clock = 16Config PLLCFG = OFF // chip clocked at 16 MHz
#option SWORDFISH_SE = true // enables use of macro in the Library // file “hs.utils.bas”which is called#option LCD_DATA = PORTD.0 // by “hs_lcd.bas”. That macro is#option LCD_RS = PORTD.4 // used to turn off analog input and#option LCD_EN = PORTD.5 // make all pins digital, including the // PORTD pins, so that now the LCDInclude "hs_lcd.bas" // will work.Include "hs_convert.bas"
Those options statements in red above slow down the LCD signals from the default values which work at 16 MHz.
The specific values, i.e. 3135, were found by trial and error to just barely work with the chip clocked at 64 MHz. The values may need to be increased more.
However, the compiler no long knows that the chip is being clocked at 64 MHz so the DelayMS routines are not correct; the 1 second delay has become one quarter second.
Changing the clock statement to: Clock = 64 fixes this.
The Swordfish Help says that “the built in routines will only currently generate the correct timings for a certain range of frequencies. These include: 3.58, 4 to 13, 14.32, 15 to 40 and 64 MHz.”
The final program, just for fun, demonstrates several things:
1) Using the Swordfish LCD Programmable Chr Generator Plugin.2) Writing custom characters into the LCD character generator RAM (CGRAM)3) Writing new custom characters into CGRAM ‘on the fly’.4) Modifying the hs_lcd.bas Library file to include 4 new commands; these are minor
modifications. The modified file is called: hs_lcdmod.bas.a. cmdDisplayOff turns the LCD display OFFb. cmdDisplayOn turns the LCD display ONc. cmdShiftLeft shifts the display to the leftd. cmdShiftRight shifts the display to the right
(5) Writing characters into Display Data RAM (DDRAM) off the display and then shifting them in. Each line has 40 characters of which 16 are displayed at one time.
There is a handy Plugin available on the Swordfish site under Swordfish User/Plugins called: LCD Programmable Chr Generator. It allows easy construction of 8 custom LCD characters and pastes the necessary Const code into your program (I’ve run this only under Windows XP).
Below are screenshots of the 3 sets of custom characters used in this program.
Below, in red, are the four lines added to the hs_lcd.bas Library file. A copy of the original file was opened in WordPad, and then saved as hs_lcdmod.bas so I would not confuse it with the original. It was then copied into the Swordfish Library folder and Swordfish closed and reopened.
There are many data sheets available for the Hitachi HD44780 chip with its instruction set. For example, as shown below, the instruction to shift the cursor or display is, reading 8 bits from the right: 0 0 0 1 S/C R/L X X. On another page is decoding for the S/C and R/L. We want S/C = 1 so the display is shifted, and R/L = 0 so the shift is to the left, making the final instruction 0 0 0 1 1 0 00 (where the two LSBs don’t matter).
Const PatternArray(40) As Byte = (6,6,6,0,6,0,2,0,4,4,0,0,0,0,6,6,6,6,6,0,6,6,2,6,6,2,6,6,0,0,0,0,6,6,4,4,4,4,6,6)Include "hs_lcdmod.bas"Dim index1 As ByteDim index2 As Byte
MoveCursor(2,1) For index1 = 0 To 39 LCD.Write(PatternArray(index1)) // display custom “pwmchar”Next // characters into 40 LCD // positions, visible and off-For index1 = 1 To 40 // screen according to numbers LCD.Command(cmdShiftLeft) // in PatternArray DelayMS(100)NextFor index1 = 1 To 30 LCD.Command(cmdShiftRight) DelayMS(100)Next Wend
On the Swordfish page under Swordfish User / Modules there is a PWM module written by David Barker, who started Mecanique.
This module is very easy to download and use; it does work with the 18F4620 and 18F46K22 running at 10 and 16 MHz.
In this module is a reference: “ From an idea found at http://www.eng-serve.net/pic”, an interesting site with a calculator of values for the various registers.
Playing with these chips and registers enabled me to understand this module, and also how difficult it would be (at least for me) to code a comprehensive PWM module which could handle multiple frequencies, CCPS, and duty cycles, maintaining good results across such wide ranges.
PWM Module
{PWMmodule1: HamStack with 18F46K22; DEV-1 board optional}Device = 18F46K22Clock = 16Config PLLCFG = OFF
The PWM module depends on the Clock statement. The frequency input must be less than 65536 although that can be raised by modifying the module, making some Words into LongWords. If a value of 65536 or greater is entered, the compiler will throw the error: “Constant expression violates subrange bounds.”
However, if a frequency of 972 is entered, the program will compile but produce no PWM, whereas a frequency of 973 will. This can be verified by doing the actual calculations in the module by hand.
Although an unlikely use of a USART, it can output a decent square wave from its TX pin, with a limited range of possible frequencies, making it easy to test that pin and scope it. Additionally, it could serve as a ‘quick and dirty’ pulse generator.
{USART1: HamStack with 18F46K22; DEV-1 board optional}Device = 18F46K22Clock = 16Config PLLCFG = OFF // clocked at 16 MHz
SPBRG = 128 // load the baud rate generator registerRCSTA.7 = 1 // set Serial Port Enable bitTXSTA.5 = 1 // set Transmit Enable bit // scope pin 25, C6, TXWhile true TXREG = 85 // capital U, 01010101Wend
Capital ‘U’ has the bit pattern 01010101 (adding a zero for the MSB, 55 hex, 85 decimal).
Default config: 1 start bit, 1 stop bit, 8 data bits, no parity (10 symbols per transmission).Default Tx signal states: idle = High, start bit = Low, stop bit = High.
With the TXREG continuously reloaded with 55 hex, a capital ‘U’ would get shifted out like this: ...idle .... 01010101010101 ... The stop/start bits just add another pulse to the train.
Transmitting only capital ‘U’s, there are 256 possible square wave frequencies, six of which would be legitimate baud rates (although some would have too much error). Simply change the value in SPBRG from 0 to 255.
SPBRG = 0 frequency = 125 kHz SPBRG = 1 frequency = 62.5 kHz SPBRG = 10 frequency = 11.4 kHz! SPBRG = 128 frequency = 969 Hz SPBRG = 255 frequency = 488 Hz
A lowest frequency of approximately .9 Hz can be obtained by adding a line to use the slowest internal oscillator at approximately 32 kHz:
! ! OSCCON = %00000010
The TX output looks like below, captured using the PICKIT 2 as a logic tool. In this picture the pulses do not look completely regular but they do on a Saleae Logic probe.
Pulse GeneratorAnother (no doubt equally unlikely) use of the USART would be as a pulse generator, either high or low, with a non-continuous adjustable pulse width from approximately 500 ms (clock chip at 32 kHz) to 1 µs (clock chip at 64 MHz)
{USART2: HamStack with 18F46K22; DEV-1 board optional}Device = 18F46K22Clock = 16Config PLLCFG = OFF // clocked at 16 MHz
OSCCON = %00000010 // use internal oscillator at ̃32 KHzSPBRG = 255 // slowest pulseRCSTA.7 = 1 // set Serial Port Enable bitTXSTA.5 = 1 // set Transmit Enable bit // scope pin 25, C6, TXWhile true TXREG = %00000000 Wend
idle start bit0 bit1 bit 2 bit 3 bit 4 bit5 bit 6 bit 7 stop start bit 0 bit 1 bit 2 bit 3 bit 4 ....! LSB!! ! ! ! ! MSB! ! LSB
(46K22 DS pg. 269) ...“The heart of the transmitter is the serial Transmit Shift Register (TSR), which is not directly accessible by software. The TSR obtains its data from the transmit buffer, which is the TXREGx register.”...
...“If the TSR still contains all or part of a previous character, the new character data is held in the TXREGx until the Stop bit of the previous character has been transmitted. The pending character in the TXREGx is then transferred to the TSR in one TCY immediately following the Stop bit transmission. The transmission of the Start bit, data bits and Stop bit sequence commences immediately following the transfer of the data to the TSR from theTXREGx.”...
That makes me think that there would be an extra instruction cycle, TCY, after every stop bit, making every Start bit slightly longer, and the signal not quite square.
And yet, FIGURE 16-4: ASYNCHRONOUS TRANSMISSION (BACK-TO-BACK) (pg. 271) shows the TSR getting loaded with a subsequent byte during the Stop bit, not after.
Instruction cycle period (TCY) equals four times the input oscillator time base period for all configurations except PLL ( 46K22 DS pg. 449).
But, try as I might, I cannot capture any periodic added time on any cycle.