tterev3's PIC quickstart guide

I'm doing a bit of reading and I'm really interested in using that little 6-pin PIC10F322. I've been looking at making a really small 3 mode flashlight driver for years, but don't want to use QFN parts. The PIC10F322 is actually small enough for my project! Obviously it can do PWM out, and I'm assuming it can accept a voltage divider input for battery monitoring (on it's single input-only pin). However, can I by any chance use the internal voltage reference to estimate the supply voltage (less parts required)?

Yes, an a-d conversion, with no resistor divider solution, per this thread Anybody doing custom UIs for 12F629 PIC?

Oh, sweet. Thanks.

Another question. I note in the OP that ttreve3 lists assembly beside the PIC10F322. Does this mean it will only work with assembly code, or does it simply mean there probably won't be enough room to use C on it? I was going to take the code supplied in post #18, remove the strobe and see if I could get that working on the PIC10F322.

No eeprom on the 10f322, although it can self write for a pseudo eeprom. A no nonsense driver shouldn’t be a problem using C, or even Basic.

This may give you an idea about who you're dealing with here, but I don't know what that means in regards to my application lol. So is this a waste of time? It's ttreve3's code with the strobe removed (I think...I would be grateful if someone would check it over). Will I be able to compile that in MPLAB 8 and flash it using my PICkit 3?

Edit: note I have not taken the time as yet to check things like registers line up etc. And seen as there is no eeprom the last section is likely a waste of time.

Correct on the _eeprom_data code, it is irrelevant, so just get rid of it. I don’t play with C, so can’t say what compiler problems you may encounter, if any, with MPLAB 8. The htc.h include file will give an indication on what compiler is being used, and compatibility.

Hi Matt, pastebin is blocked for me so I can't check your code, but here's an adaptation for the 10F322 that I've tested to work. only uses 33% of the code space as is. I also added in the voltage monitor to demonstrate that; it will force the mode from high to medium if battery goes below 3.0V. You can't use the input-only pin for ADC on this part, but the internal fixed voltage reference makes it possible to monitor supply voltage without using the pin.

//v0 5/1/2014 by Everett
    //initial version
    //simple flashlight controller. mode change on power cycle

#include <htc.h>
#include <pic10f322.h>
#define _XTAL_FREQ 8000000
__CONFIG(CP_OFF & BOREN_OFF & LVP_OFF & MCLRE_ON & WDTE_OFF & PWRTE_OFF & FOSC_INTOSC & WRT_HALF);

#define pwm PWM1DCH

persistent unsigned char mode; //must be declared persistent for ram retention trick to work
enum mode{
max=0,
med=1,
low=2,
};
#define max_mode 2
#define default_mode 0

persistent unsigned int key;
unsigned int v_timer;
bit v_sample;
#define voltage_rate 100 //milliseconds

void delayms(int milliseconds);
void configure(void);
unsigned char read_voltage(void);
void initialize_mode(void);

void interrupt isr(void)
{

if(TMR0IF){ //fires at 1kHz
    TMR0IF=0;
    if(++v_timer==voltage_rate){v_timer=0; v_sample=1;}
}

}

void main(void)
{
configure(); //set up hardware peripherals
delayms(15); //short delay to avoid power glitches incrementing mode
if(key==12345){ //RAM retention trick to detect quick power cycles
mode++; //go to next mode
if(mode>max_mode){mode=0;}
}
else{ //long power loss. default to first mode
mode=default_mode;
key=12345;
}

initialize_mode();  
GIE=1;  //turn on interrupts

while(1){
    
    if(v_sample){
        v_sample=0;
        if(mode==max){              //if battery goes below 3.0V in max mode, force down to medium mode
            if(read_voltage()&gt;87){
                mode=med;
                initialize_mode();
            }
        }
    }           
    
}   

}

void initialize_mode(void)
{
switch(mode){ //initialize current mode
default:
case max:
pwm=255;
break;
case med:
pwm=25;
break;
case low:
pwm=1;
break;
}
}

unsigned char read_voltage(void)
{
//fvr is at 1.024V. ADRES = 1.024/Vin*255. Vin = 1.024/(ADRES/255). voltage limit set to 3.0V -> 87. values below this correspond to voltage above 3.0V
GO_nDONE=1;
while(GO_nDONE);
return ADRES;
}

void delayms(int milliseconds)
{
while(milliseconds!=0){ __delay_ms(1); milliseconds–;}
}

void configure(void)
{
INTCON=0b00100000; //tmr0 only

T2CON=0b00000100; //on, no prescale
PR2=255; 
TMR2=0;
    
LATA=0;
TRISA=0b11111110;   //GP0 output
ANSELA=0b11110000;  //
WPUA=0b11111110;

OSCCON=0b01100000;  //8MHz

OPTION_REG=0b00000010;  //8 prescale for 1ms interrupts

FVRCON=0b10000001;  //1.024v to adc
ADCON=0b10111101;   // fvr

PWM1DCH=0;
PWM1CON=0b11000000; //on, output

}

 

Probably a dumb question, but here goes. Wouldn't a "C" program and an "Assembly" program, both written with the same functionality and same basic approach, compile about into the same sized "Machine" language program?

Depends on a lot of factors including programmer skill, how good the compiler is, mcu architecture, etc.
Also as a clarification, assembly doesn’t get compiled, it gets assembled (hence the name) because it is actually machine language, but with mnemonics to make it human-readable

Ah, thank you.

Oh wow...thank you so much. I'll have to compare this against what I've tried writing myself. I'll comment it to hell and back in an effort to fully understand exactly what's going on, and paste it here to see if I've got it right...

Hi Matt,

now that I’m home from work I can see your code, here’s a few comments:

  • Since this part is quite different from the 12F1822, essentially all of the configure() function needs to change. The instructions accomplish similar things, but most of the SFRs are named differently.
  • The #define for max_mode has been removed, which will throw a compiler error on line 53 (which you’d figure out right away)
  • You correctly removed the runtime actions taken in the main loop that applied to strobe only, but you still need to have a main loop. After interrupts are enabled (GIE=1;) just put while(1) even if nothing is in the loop. The processor needs to know where to sit and do nothing, otherwise it will probably loop back and reset (depends on how the compiler handles the end of the main() function)
  • You’re correct on EEPROM, the last part needs to be removed. I have some functions written for saving NVM values by doing flash write on the 10F322 if you need them in the future
  • The ISR for timer 1 needs to be removed since this part doesn’t have timer 1 (related to bullet one, you’d also have figured this out pretty quick when the compiler started complaining)

hope this helps, you’re definitely on the right track. please don’t take any of this as criticism, just trying to help you understand the changes you’d need to make it run on the 10f part

Lol, definitely not taking this the wrong way. I'm an engineer. I prefer to be educated than blindly follow.

That being said I've basically started again using the code you provided the first time. I'm using the datasheet to try and fully understand what bits are being set, what they do, and how they relate to the code. What you've provided is pretty much exactly what I need but I will have questions

The only thing I'm going to try and do is change the low voltage step down from automatically going to medium when the light is on high, but rather step the brightness down until the battery voltage is back up over 3V. This should happen regardless of the mode. So start the light, and constantly check the battery voltage and adjust accordingly. I should be able to do this with everything that's been set as it is. I haven't really put any thought into it yet though. In theory this would extend the run time immensely even if the resultant brightness is in the sub-lumen range. I have a few lights at the moment that do this, and I really like it. Especially when I'm back country snowboarding and don't want to loose myself around my tent at night!!!

OK, so surprisingly I actually get 90% of what's going on in the code. There's just one section I'm a bit hung up on. The interrupt isr function.

I understand what an interrupt service routine is for in general, but I'm not following what it does in this particular example. Obviously it's setting v_timer and v_sample, however I don't fully understand the conditions that are required to be met for this to happen (++v_timer==voltage_rate).

Another question is in regards to the delayms function. Why have you created a separate delayms function to perform the delay function (for example running delayms(15)) rather than just running __delay_ms(15) after you call the configure? The way I see it saves a few lines of code. Or has this just been pulled from a 'universal' firmware?

One last thing just to make sure I am reading the datasheet properly. You've set the PWM1CON control register. I'm assuming this correlates to PWM1 and hence RA0 which in turn means Pin 1 of the PIC?

- Matt

hi Matt,
These are all great questions:

This is just a way to schedule tasks since it there’s no need to check voltage constantly. Since the timer0 interrupt fires every millisecond, we can set a rate to sample voltage by defining voltage_rate as the period in ms, so at 100 it will check 10 times per second. The line "if(++v_timervoltage_rate)” is just a concise way of writing

v_timer; //count up another millisecond

if(v_timer==voltage_rate) // do something if 100ms have elapsed

After we hit 100, the timer is set back to zero to start again, and the v_sample flag is set to tell the main loop to take action. This may not appear to make much sense since there is only one task in the main loop, but with this format you could add tons of other tasks in exactly the same way and have them fire off at whatever rate you want, allowing the microcontroller to multitask and perform a lot of operations simultaneously.


The reason for this is to save code space after the program is compiled. The directive __delay_ms() is actually a macro, and it will tell the compiler to insert a delay loop at the location that you write it. If you do this in more than one place, it will generate duplicates of the same code in every instance, wasting space. Since I wrapped it into a function that can handle different amounts of time, the macro is only put in once, and I just call the function when I want to use it, potentially saving a lot of space in program memory.


You got it. Table 2 on page 4 of the datasheet is the best place to check for things like this, it will tell you what functions every pin can have including PWM, ADC, Interrupt on change, etc.

Sweet :D

I see exactly how the voltage sampling works. Every 100 milliseconds it resets the timer (so it can count back up to 100 again) and sets the v_sample 'flag' to 1. Now that the v_sample 'flag' is 1, the trips if statement in the while loop inside the main function, which causes it to read the ADC result and change the PWM output accordingly (via the initialize_mode function of course). When the if statement is tripped, it resets the v_sample flag back to 0, which stops the if statement from being tripped until 100ms is up again.

So the delayms function you created won't necessarily save much space in this code (only called once), but it would if the delayms function was called more than once. Gotcha.

Seems I'm well on my way :) Thanks!

- Matt

EDIT: just remembered a note I made in the code, down in the configuration function. PWM1DCH = 0; is this effectively setting the PWM to zero (off)? At the start of the code you make pwm = PWM1DCH. Actually, you #define PWM PWM1DCH. What does that mean exactly? Does that just mean wherever you see 'pwm' you are actually seeing 'PWM1DCH'? So if I ever make PWM = something, I'm actually changing the value of PWM1DCH, and thus changing the PWM value being output?

I ask because I'm not sure if this is valid:

case batt_save: //battery saver case

pwm--; //reduce current pwm value by one

break; //force exit from statement body

In my code, if enter the voltage read loop (v_sample flag has been set to 1) it sets the current mode to "batt_save", and then executes the initialize_mode function. In the initialize_mode function, the above code is an additional case to the standard max, med, low cases. In theory it should take the existing pwm value and reduce it by one. The idea is that if you leave the light on, every time it detects the battery voltage has fallen below 3V it will reduce the PWM value by one. This will happen up to 10 times a second and in theory will continue to do so until the load on the cell is small enough that the voltage can rise back up above 3V. I will probably have to experiment with this and reduce it by more than 1 if it takes too long to react (and figure out how to reduce it in lots of 5 or so). This should work regardless of what mode you are in. If it cannot get it back above 3V, then it will simply turn PWM all the way down to zero, effectively turning the light off. This will protect your battery ;)

Preprocessor command. Any line starting with #define or #include or other similar commands will effectively modify the source code in place before sending it off to the compiler.

Using “#define foo bar” means that it should replace all instances of “foo” with “bar” before attempting to compile. It’s literally just an automatic search and replace. It’s handy for a lot of different things, like adding a layer of abstraction which doesn’t actually show up or take any space in the resulting binary after compilation. Hard-coding things in a manner which is easier to maintain, or providing tweakable compile-time parameters.

It’s a normal convention to use all-caps for #defined symbols, to avoid confusing them with variables or accidentally doing weird things to the code.

Another newbie question. I don't know about tterev3's code, but I think all the open-source, momentary-switch FW being used on BLF "turns off" to light by setting PWM to zero. Wouldn't there be less parasitic drain if the MCU was instructed to go into a stand-by or sleep type mode? I'm sure the answer to that is "Yes" based on the datasheets. So I guess the real question is the reduction in parasitic drain so small that it doesn't justify the complications (if any other than coding) that would be added by such a feature.

Thank you all and especially tterev3 for your patience with layman questions.

Yes, as ToyKeeper explained, this just tells the compiler that “pwm” actually means “PWM1DCH.” The reason I do this is so that if you wanted to use the other pin on PWM2, you could change just the one line to define “pwm” as “PWM2DCH” and all the functions that access “pwm” would be instanlty updated to use the other channel.

Hi Matt,
Your idea is good and will work with one slight adjustment: if you have just the line
pwm—;
when the battery gets very low and the pwm value reaches zero but battery is still below 3.0V, the decrement will roll the value back around to 255 (maximum brightness), which is the opposite of what you want in this case. To fix that, simply change the line to
if(pwm) pwm—;
which will only decrement if the value is not zero. If you wanted to build on this further, you could include a minimum level that the battery saver function wouldn’t go below:
#define minimum 5 //set value here to whatever you want (including zero)
if(pwm>minimum) pwm—;

hi ImA4Wheelr,
You’re right, the drain is much much less in sleep (generally a few microamps compared to about a milliamp when running) and it’s crucial that sleep mode is used on lights like this. I always use sleep mode when the light is off on things like my SkyRay and MELD2