Starting PIC - Advice on route? and things like SPI? - Page 1
Forum section: electronics / general

author: TriodeTiger
author_rank: Regular Contributor
timestamp: December 23, 2013, 04:37:12 am
content: I started to like the range of PIC microcontrollers to further my hobby - mostly to make little widgets that could use the internal RC dedicating only a dollar or two besides spare parts.One issue I have ran in to is the whole compiler thing. I downloaded SDCC and found out it was pretty young and did not have many libraries. (on Linux) I eventually downloaded MPLAB X with the 16-line compiler from them and found none of the examples really working. An example was PORTB/TRISB/__CONFIG pragma not being found even if I added pic(*).h and xc.h among others.I next downloaded their HI-TECH C compiler seeing a few examples around the web had htc.h included and that there were a few libraries available maybe and things seem to go okay.Most config bits failed and so I had to look at the file (/opt/microchip/xc8/v1.21/dat/cfgmap/16f819.cfgmap) and change things such asFOSC_INTOSCin toFOSC_INTOSCCLKin my code for it to compile.Cool, I can make a blinker and do all the basics with ports:Code:#include <htc.h>#define _XTAL_FREQ 8000000#define led 0x01__CONFIG(MCLRE_OFF & CP_OFF & WDTE_OFF & BOREN_OFF & FOSC_INTOSCCLK);main(void) {unsigned char i;TRISB &= ~(1 << led);OSCCON = 0x70;while(1) {PORTB ^= 1 << led;__delay_ms(500);}}My first project was going to be a binary clock - I have dedicated a good hour reviewing the SPI protocol and find it alright in how to load it up for my use.An issue is I do not know how to use SPI unless I spend the time to hack together bitbanging functions which I suppose I can do to communicate, but are there any libraries available? I see references to spi.c/h that are often in HTC files however they are not found on my compiler.It seems odd that I cannot find a simple .c and .h library that someone wrote to not have to worry about implementing the SPI communication as much and justdothe protocol to write to the shift register. Maybe I am overthinking things and spi_init/read are simple functions and I should just write them.Thoughts? Maybe the jump in to PIC is a bit more of an undertaking than I thought? I don't expect an Arduino experience but anything to make 'needless' steps faster would be very welcomed including how people do this for simple hobby projects.TriodeTiger.

author: Rufus
author_rank: Super Contributor
timestamp: December 23, 2013, 05:19:35 am
content: The Hi-Tech compilers are obsolete. Microchip bought Hi-Tech a couple of years ago. Hi-Tech C for 8 bit parts became XC8 the rest of their products were scrapped.__CONFIG is also obsolete (possibly depreciated) you should be using #pragma config statements to set configuration options.The Microchip compilers have peripheral libraries for 32, 16, and 8 bit PIC18 parts which include SPI, but, I wouldn't bother. SPI hardware isn't really any more complicated than the library they wrap around it and the library is inefficient and limited.You should also be writing to LATs not PORTs unless your PIC doesn't have them.

author: TriodeTiger
author_rank: Regular Contributor
timestamp: December 23, 2013, 05:39:54 am
content: Ah. Yeah, I have one 18f along with my order but that is not something I would want to use in anything right now. I think I'll let that become a silly PICduino project in the future if anything.Useful information about HTC! Thank you. I suppose SPI really isn't too much of a concern. My concern kind of extended to harder protocols such as controlling my spare character LCD. It appears most of the "character LCD to PIC" examples go straight to 18f line with lcd.h, it doesn't help that searching "16f" gets mixed with "16x2 characters".How would I then control this LCD? I miiight be able to find an example to learn from, but will it be just learning the 4-bit protocol and bitbanging it? I have to ...to...define the alphabet in arrays? See where I am going? Confused at how to even start tackling one of these projects as reusing seems to make a lot more sense here.

author: hans
author_rank: Super Contributor
timestamp: December 23, 2013, 09:15:05 am
content: You can bitbang interfaces in software or see if you particular chip has a hardware peripheral for that interface.I usually never bother with standard libraries, especially for 8-bit or 16-bit parts. The amount of registers to be set is so limited, it's very manageable.I estimate you use the PIC16F819 (from your snippets)So, chapter 10 of the datasheet:http://ww1.microchip.com/downloads/en/DeviceDoc/39598e.pdfSo have to use pins RB1, RB2 and RB4.At init() you typically configure the module for a particular interface mode, speed etc. Also make sure to set the TRISB register for input & output. We use the SSP module which can also be used for I2C, so we have to set it up. Basically I walk through all the registers of the module and 'check or uncheck' the things I want.Typically PIC peripherals have 1 or multiple control registers, 1 (or multiple..) status register, and some additional data or clock rate divider registers to be set. I usually go by all the registers and see what I have to set or reset at any given time. Basically it's a checklist..For example, see the 'checklist' at page 73. In this case , it's a single control register we need to configure at init():QuoteSo for example,Code:void SpiInit(){TRISB |= 1<<1; // Serial Data IN: RB1 inputTRISB &= ~(1<<2); // Serial Data OUT: RB2 outputTRISB &= ~(1<<4); // Serial Clock OUT: RB4 output// You also need a chip select, but as this is required for each device you connect, I often leave it out of spi.cSSPCON = 0; // first disable port if it was still runningSSPSTAT = 0; // Reset all status flags (there are some R/W bits)SSPCON = (1<<5) | (1<<1); // enable port, SPI mode Fosc/64}For SPI it's a synchronous bus where both a byte is sent and received at the same time. One cannot happen without the other.. I therefore often combine read and write to 1 method, SpiTxRx():Code:unsigned char SpiTxRx(unsigned char byteOut){SSPBUF = byteout; // Write to buffer to start a transmit.while( (SSPSTAT & (1<<0)) == 0); // Wait till buffer indicator says a receive has been completedreturn SSPBUF; // Read the buffer for the received byte.}A read is where I would call this SpiTxRx(0) and just look at the returned value. A write is where I would write a value and ignore the returned value.And that's probably all there is to 'peripheral level'. Eventually you may (or may not) run it to problems in certain situations "why doesn't my SPI module work?". Then check if there are any hardware flags that are not checked by software, and see if any handling must be done. For example, in this case I ignored all error & collision flags. On PIC32 UART's for example, a frame error would seize all communication to a halt. So it's quite important to check.If you now want to use Spi in your code, it's a case of calling SpiInit() at start, and calling SpiTxRx() for each byte to transmit/receive, and of course handle chip select lines to your device. For example:Code:int ReadTemperature(){int temperature = 0;PORTx &= ~(1<<y); // set chip select low ('active')SpiTxRx(0xA5); // send command for reading temperature registertemperature = SpiTxRx(0); // low temperature bytetemperature |= (SpiTxRx(0) << 8); // high temperature bytePORTx |= 1<<y; // set chip select high againreturn temperature;}

author: dannyf
author_rank: Super Contributor
timestamp: December 23, 2013, 12:48:25 pm
content: QuoteThose are defined in the device .h file (towards the bottom) and they are device specific and meant to be changed when you port the code to a different device.QuoteYeah. You can either write your own and reuse them later, or to get from others. You should first decide if you want to use software or hardware spi.QuoteYes.

author: Maxlor
author_rank: Frequent Contributor
timestamp: December 23, 2013, 10:07:47 pm
content: Remember that the PICs have dedicated, single-cycle instructions to manipulate single bits (BCF/BSF). So instead of writingTRISB &= ~(1<<2);why don't you write:TRISBbits.TRISB2 = 0;Which is imo more readable. Might be a lot faster too, if the compiler decides to do the full read-modify-write cycle for the first example.You might even take this a step further and set up a couple of defines, like so:#define BUTTON1_TRIS (TRISAbits.TRISA0)#define INPUT (1)Then in your init routine do:BUTTON1_TRIS = INPUT;

author: Maxlor
author_rank: Frequent Contributor
timestamp: December 23, 2013, 10:47:38 pm
content: Quote from: TriodeTiger on December 23, 2013, 04:37:12 amIf your PIC has hardware SPI, I suggest you use it. It's really not that difficult to get it going. You don't really need to use a premade library, using the hardware registers directly isn't that big of a deal. Microchip's documentation usually explains it well.Let me show you some example code. I use MPLAB X and XC8. This is from a project using a PIC16F1824, which is a modern but low-end PIC. It'll work the same on other newer PICs, but maybe the registers are layed out a bit differently for older ones. For example, I was able to use the same code on an PIC18F46J50.Btw, for all the register names to work, you'll need to include the MCU-specific header. In my case this is:#include <pic16f1824.h>It might also prove useful to have a look at that file, to learn about naming conventions and the such. For example, there you'll see that to access an individual port bit, you can usePORTAbits.RA1, or that plainRA1works too (but personally I prefer the longer variant, since it's more selfdocumenting.)Anyway, here's how I setup the SPI controller (I'll skip the pin configuration) :SSP1STAT = 0b01000000; // SMP = 0, CKE = 1SSP1CON1 = 0b00100000; // WCOL = 0, SSPOV = 0, SSPEN = 1, CKP = 0 (idle low), SSPM = 0 (SPI Master, FOSC/4)oh, lots of cryptic names there. So lets refer to pages 292 and 293 of theDatasheet, where these two registers are explained. SSP1STAT has just two bits that you can actually set (indicated by R/W-0/0), all the others are read-only (indicated by R-0/0) so they're not important right now. SMP specifies the sampling time, but the default works just fine (you might need to set it do 1 when running at high speeds.) CKE specifies which edge of the clock to use for changing the signal. Unfortunately, this varies between devices, you'll have to check the datasheet of whatever device you're communicating with. The wording here is a bit complex, speaking of active and idle states instead of high and low, but this is because you get to define whether active = high or low yourself. Again, because device manufacturers couldn't agree on that either... *sigh*.Anyway, on to SSP1CON1 on the next page. There are a couple more status bits that you can ignore for the moment. The important bits are SSPEN, which actually turns the whole controller on, CKP which sets active to high or low as I just mentioned, and of course SSPM, through which you set the mode and clock rate.And thats it. Two registers to set, and the SPI module is ready to use.Now how do you actually use it? I use this method:Code:void spiTxRx(const uint8_t *txBuf, uint8_t *rxBuf, size_t len) {PIN_CS = 0; // CS is active lowfor (size_t i = 0; i < len; ++i) {PIR1bits.SSP1IF = 0; // reset interrupt flagSSP1BUF = txBuf; // writing to SSP1BUF immediately starts the transmissionwhile (0 == PIR1bits.SSP1IF); // Wait until the interrupt is sent, which means the byte is transmitted.// Even if interrupts aren't actually used. So you can just poll for it.for (uint8_t j = 0; j < 16; ++j) ; // Delay a bit.if (rxBuf) { rxBuf = SSP1BUF; } // SPI is full duplex, so we received something at the same time. Maybe save it. Or just ignore it.}PIN_CS = 1;}And that's really all there is to it. The data to be sent is copied byte by byte into SSP1BUF and is immediately sent. At the same time, data is received is made available in the same SSP1BUF once the transmission has ended. If you only want to write, just ignore it. If you only want to receive, you'll need to copy a dummy byte into SSP1BUF to start the transfer, a 0 will do.What about all the status bits? Well, I chose to ignore them here. If something goes wrong, it'll be the peripheral I'm talking to, and it'll be an issue I haven't found in testing. Instead of worrying too much about it, I'll just let the microcontroller spin in the while loop until the watchdog resets everything. For more complex boards with more than just two chips, some error handling might be advisable.Anyway, I hope this helps.

author: dannyf
author_rank: Super Contributor
timestamp: December 23, 2013, 11:21:34 pm
content: QuoteI am not sure. Using a bit mask is more portable and allows you to change multiple bits.QuoteThat's easy to test.Code:void spiTxRx(const uint8_t *txBuf, uint8_t *rxBuf, size_t len) {I would have written a lower level routine that sends / receives a byte first and implement a string send/receive based on that.Code:PIN_CS = 0; // CS is active low...PIN_CS = 1;I would implement PIN_CS outside of your spi routines so you can hang multiple spi devices off the same bus.

author: TriodeTiger
author_rank: Regular Contributor
timestamp: December 24, 2013, 11:04:02 am
content: Wow, a lot of valuable input thank you! I am swamped from preparing for tomorrow so I will digest this slowly over the next while for sure.SPI looks like it will be a fun tackle and get me in the habit of getting my hands wet with the protocols.Above I was afraid of advancing to a 4-bit LCD protocol, however these are not too different and the pixel arrays are not even specific to me - I can open up any lcd.h and swipe the arrays and look for inspiration on how they implement things such as scrolling, etc. I can safely ignore the seemingly hacky and ugly tutorials online for DIY writing and make something that works for me.As for using TRISBbits and such, maybe in the end I should just write a series of macros to make things easy for myself such as toggle(PORTB, statusled) to save from having to comment what TRISBbits.TRISBx is (edit: the "#define BUTTON1 TRISBblabla" define mentioned solves this!), or reduce mistakes from typing TRISB = ~(1 << statusled) or however from memory. I know it will come to me in time what works and does not work, a balance between clarity and functionality.QuoteWould the compiler be smart enough to take the hint and turn those bit flags in to those unicycle (heh) operations? I mean it is obvious to the compiler you are modifying a register, and changing a bit..

author: Maxlor
author_rank: Frequent Contributor
timestamp: December 24, 2013, 01:00:27 pm
content: Quote from: TriodeTiger on December 24, 2013, 11:04:02 amYes. The compiler gets a hint because a field like TRISCbits.TRISC3 has typebit. But lets look at what it does exactly. I have the following two lines:TRISCbits.TRISC3 = 0;TRISC &= ~(1 << 5);They both clear the TRIS bits for pins RC3 and RC5 respectively. But the code generated for them is different:BCF TRISC, 0x3 # clear bit 3MOVLW 0xDF # load the constant ~(1 << 5) into WMOVWF 0x70 # save in some RAM locationMOVF 0x70, W # load same RAM location againANDWF TRISC, F # AND with existing tris value and store in TRISCNow I don't know why the compiler generates those two lines with 0x70 in them, they're pointless. I'm guessing it's because I'm using the free version of xc8 and it generates some extra code to motivate me to move up to the paid versions? But anyway, even if those were omitted, you'd get two commands.

author: dannyf
author_rank: Super Contributor
timestamp: December 24, 2013, 01:21:25 pm
content: From PICC 9.60std:Code:148 ;main.c: 71: TRISB3 = 0;149 0135 1186bcf6,3150 ;main.c: 72: TRISB &=~(1<<3);151 0136 1186bcf6,3;volatileIt pays to avoid those OCG BS.

author: TriodeTiger
author_rank: Regular Contributor
timestamp: December 25, 2013, 01:14:16 am
content: Well that's interesting. I was playing around with looking at the assemblies everything is generating in my XC8 V1.21It appears TRISBx is the way to go for changing single ports, but I woke up today forgetting that is just the DDR. Although here it is easy, I gather how I am changing port directions is not too important in the end.I found something similar with PORTB to, say, flip a bit but the assemblies differ negatively:Code:;main.c: 6: PORTB ^= 1 << 1;bcf3,5;RP0=0, select bank0bcf3,6;RP1=0, select bank0xorwf6,f;volatile;main.c: 7: PORTBbits.RB1 ^= 1;movf6,w;volatileandlw1movwf??_mainmovlw1xorwf??_main,fmovf6,w;volatilexorwf??_main,wandlw-2xorwf??_main,wmovwf6;volatileAm I left where I began? I mean, no big deal and the first one is easy, but just a common theme in this thread to talk about this and I'd love to understand all I can of it while I canAfter a bit of steak I will tackle my SPI-from-scratch library and if it compiles, ask for a review before I can test it in person maybe next week with a shift-reg.Triode Tiger.

author: Maxlor
author_rank: Frequent Contributor
timestamp: December 25, 2013, 05:12:50 am
content: Crikey. That's ridiculous. And the code it produces for me is even worse, 5 lines for the first variant, 12 lines for the second. I'd say the conclusion is, arguing about performance or compiler optimization is pointless when the compiler in question is xc8 free.

author: hans
author_rank: Super Contributor
timestamp: December 25, 2013, 09:51:01 am
content: We're getting a bit deep & offtopic I think, but ah well.. I think it's important as an embedded programmer to understand how C code translates to assembler.I've done a test with XC8 Free and Pro. The PRO generates bcf & bsf for PORTB &= ~(1<<1); statements, where FREE doesn't. It saves about 3 to 4 instructions.PORTBbits generates single statements in both cases.PORTBbits.RB4 ^= 1; generates in PRO mode:7FA 3010 MOVLW 107FB 20 MOVLB 07FC 68D XORWF D, TrueAnd in FREE mode about 10 more instructionsI think it's a case of again knowing what line of code is best picked up by the particular compiler. Which unfortunately hasn't faded away since the horrible CCS days.Ah well, in most cases I wouldn't give a toss about whether the compiler generates 1 or 4 lines of code for a statement only executed once; however for 14 I/O pins it could be 40 Flash words saved.Oh yeah, TRISB and DDR is the same for PIC & AVR. ARM may have it in a "Port Bit Direction" register or something, buried away deep in a CMSIS library, but essentially it's the same thing: a 1 or 0 sets input, opposite sets output

author: TriodeTiger
author_rank: Regular Contributor
timestamp: December 25, 2013, 10:34:16 am
content: Yeah. Do all hobbyists for PIC use Microchip's MCP free? I mean I doubt they'd pay $900 for a workplace license, yet do some pretty awesome things on low memory devices. Maybe all of my silly projects will never push the edge? SDCC ain't no *-GCC yet.. and I r not smart enuf to work with it yet with little examples online.Alright here's my simple SPI go of hacked together code (whoops, SR_LAT meant to be output, but not like I can test it anyway)http://pastebin.com/265j4rGJCompiler tells me the following which I suppose does not seem too bad for having all the basics I need to create, say, a shift-register binary clock in my original plan:QuoteA bit hacked together but it compiles fine. I was a bit confused with Maxlor's example tx/rx function as I have SSPBUF but not SSP1BUF (I presume SSPBUF *is* the first buffer on my humble 16f819) but ultimately there is no union member of SSP1IF in PIRbits, so I jumped to hans' liking the simple return over buffer for now. It's opened me to the data sheet of course and I will definitely be able to extend it if need be.--------------------------------So..----------------------------------Do I have my protocol at all right? It's the first I've done any of this, so the basic ilogic of my code is hopefully is sound.I liked the idea of CS being controlled outside so as you can see I have that there (I've no clue if it starts by default high, or if I should initialise it high and then low before the first select.Delay library was from microchipc.com and seems to have a well-written delayus/ms/s library that can be tuned for accuracy.Whew, 2am, time to finish wrapping a few presents and get my 6 hoursHappy holidays.

author: dannyf
author_rank: Super Contributor
timestamp: December 25, 2013, 01:02:59 pm
content: Code:#include "delay.c"Include it in the project, not in the source file.Code:SET_BIT(TRISB, SPI_SDI);Those macros are meant to work with bit masks, not bit fields.Code:SSPCON = (1<<5) | (1<<1); // enable port, SPI mode Fosc/64Not necessarily an issue but you typically set / clear bits. I would use also macros defined in the header file (if so) to make the code more readable.Code:while((SSPSTAT & (1 << 0)) == 0); // Wait till buffer indicator says a receive has been completedSame as above.Code is actually in pretty good shape.