I have a nice Cortex M3 board around since some time, it is based on a NXP LPC1768, which is quite a neat processor.
It can run up to 100MHz, has 512K Flash, 64K Sram and a nice set of peripherals.
The board itself is quite nice too, not sure you can still find it around -ebay listings below, to give you an idea on what's available-, mine is called "LandTiger" and I guess it is just some clone of some popular dev board.
That said, I wanted to play with the UART using a standard CMSIS approach.
CMSIS stands for Cortex Microcontroller Software Intertface Standard, it is defined by ARM and supported by various IDEs.
CMSIS deserves a post on its own, if you don't know it, there is quite a lot of info around already, but I might post myself a sort of "CMSIS for dummies" some time.
Regarding the IDE/toolchain there are quite a few options, currently I am using Keil uVision5 (there is a free version, codesize limited), but I also tried IAR and CoIDE (which is open source).
They all work, and all of them require a bit of fight to get the things the way you want them (at least this happened to me), I couldn't really pick a winner in the end, but decided to stick to Keil uVision.
So, the UART.
Why the UART first of all?
Meh, getting serial communication is pretty key to allow some remote debugging, so it is a good place to start (maybe just after blinking a few LEDs).
Now, CMSIS provides a layer that gives some kind of standard helping you when you have to port your code from a device to another.
For what I can see, you cannot simply change device, recompile and hope everything works.
NXP devices have some peculairities and need to be handled differently from STM devices etc.
The way you normally manage this is to generate your own abstraction layer (or use some available libraries that do just that).
This post is about the LPC1768, but most of the info might eventually apply to other devices too, provided the register addresses will be different, but those are defined in a CMSIS include file usually provided by the vendor.
So, the LPC1768 has 4 UARTs and uarts 0,2 and 3 work pretty much the same way.
Uart1 is a bit special, so it will not be discussed here.
(@NXP)
There are quite a few registers to play with, as you can see from the LPC17xx manual.
Two easy ones : Receive Register (RBR) and Transmit Register (THR), these should not require a lot of explanations.
Can you see the specification (DLAB=0)?
That means that the Divisor LAtch Bit must be set to zero.
We use the DLAB to set the frequency divider (hence the name) to obtain specific baud rates, basically when you set the baud rates, the DLAB bit must be 1, in any other moment it has to be 0 (and in some case it does not matter, but leave it to zero in that case :) ).
The divisor is set via the DLL and DLM registers, containing the low 8 bits and the high 8 bits of a 16 bit unsigned integer, being the divisor.
DLAB is the bit 7 of the LCR register.
But setting the baud rates and the typical 8,N,1 configuration is no all you have to do.
First of all, Cortex Microcontrollers are usually designed with power saving objectives in mind, so it is often the case that by default peripherals are turned OFF.
Before you can configure the UART, it is a good idea to turn it on!
This is quite easy, it is done via the Power CONtrol for Peripherals (PCONP) register
(@NXP)
LPC_UART_TypeDef * __get_uart_base(int uart)
{
switch (uart)
{
case 0: LPC_SC->PCONP |= (1<<3); break;
case 2: LPC_SC->PCONP |= (1<<24); break;
case 3: LPC_SC->PCONP |= (1<<25); break;
}
}
If you check the simple code and the register, you will notice that depending on which uart we want to use, we turn on one single bit of the register (3, 24 or 25).
So, now the UART is on, but there is another typical feature of cortex microcontrollers : pins are used for different purposes, so there is a multiplexer that has to be configured to make sure we have a RX and a TX pin connected to our uart.
This is done via the PINSELx registers, for the uarts we will need PINSEL0 (uart0 and 2) and PINSEL1 (uart3).
Next comes the peripheral (uartx) clock configuration.
Cortex M3 MCUs can use different clock signals per each peripheral, the LPC1768 allows to divide the system clock by 1,2,4 or 8 times.
These dividers are configured using two bits per each peripheral in the PCLKSELx registers
If we need to have reliable communication at high baud rates, it is advisable to set the value to 0x01, equal to no clock division.
We can expand the previous switch to include both pin and clock configuration and embed everything in a SER_On(int uart) function
void SER_On(int uart)
{
switch (uart)
{
case 0:
LPC_PINCON->PINSEL0 &= (0x0F<<4);
LPC_PINCON->PINSEL0 |= (1 << 4);
/* Pin P0.2 used as TXD0 (Com0) */
LPC_PINCON->PINSEL0 |= (1 << 6);
/* Pin P0.3 used as RXD0 (Com0) */
LPC_SC->PCONP |= (1<<3);
LPC_SC->PCLKSEL0 &= ~(3<<6);
LPC_SC->PCLKSEL0 |= (1<<6); // = CCLK
break;
case 2:
LPC_PINCON->PINSEL0 &= (0x0F<<20);
LPC_PINCON->PINSEL0 |= (1 << 20);
/* Pin P0.10 used as TXD2 (Com2) */
LPC_PINCON->PINSEL0 |= (1 << 22);
/* Pin P0.11 used as RXD2 (Com2) */
LPC_SC->PCONP |=(1<<24);
LPC_SC->PCLKSEL1 &= ~(3<<16);
LPC_SC->PCLKSEL1 |= (1<<16); // = CCLK
break;
case 3:
LPC_PINCON->PINSEL1 &= (0x0F<<18);
LPC_PINCON->PINSEL1 |= (3 << 18);
/* Pin P0.25 used as TXD3 (Com3) */
LPC_PINCON->PINSEL1 |= (3 << 20);
/* Pin P0.26 used as RXD3 (Com3) */
LPC_SC->PCONP |=(1<<25);
LPC_SC->PCLKSEL1 &= ~(3<<18);
LPC_SC->PCLKSEL1 |= (1<<18); // = CCLK
break;
}
}
We can now configure the UARTs, but it is good to define a handy function that returns the uart base address to access the registers, so that each time we don't need to do stuff like "if (uart==0) then ... " etc.
LPC_UART_TypeDef * __get_uart_base(int uart)
{
switch (uart)
{
case 0: return LPC_UART0;
case 2: return LPC_UART2;
case 3: return LPC_UART3;
}
return LPC_UART0;
}
the LPC_UART_TypeDef structure and the LPC_UARTx constants are provided in the LPC17xx.h CMSIS include file, other vendors (non NXP) will use different names, but should not be difficult to find the matching structures and constants.
void SER_init (int uart,int baudrate)
{
LPC_UART_TypeDef *pUart;
uint32_t fdiv;
pUart = __get_uart_base(uart);
SER_On(uart);
// set to 8 databits, no parity, and 1 stop bit
pUart->LCR = 0x83; //DLAB on
// do baudrate calculation
fdiv = (SystemCoreClock / 16) / baudrate;
pUart->DLM = (fdiv >> 8) & 0xFF;
pUart->DLL = (fdiv) & 0xFF;
pUart->LCR = 0x3;
pUart->FCR = 0x6; //reset read and write fifo
}
First we get the base address with the __get_uart_base(uart) function we defined at the beginning.
Then we turn the UART on, select the clock and configure the pins with the SER_On function.
At this point we are ready to configure the baud rate divider and we know that for this one the DLAB must be on, turns out the DLAB is bit 7 ot the Line Control Register (LCR), the 3 in the lower part of the register sets the 8,N,1 part.
Now we need to calculate the fdiv divider.
There is a formula for that one:
fdiv = (SystemCoreClock / 16) / baudrate;
However, trap for young players -it tricked me initially- this formula assumes that your uart pclk divider is 1 (!!).
If you set pclk, say to /4 , then also the divider will have to be divided by 4.
fdiv is then copied into DLM and DLL (high part and low part), finally the DLAB is turned off again.
Note : SystemCoreClock is a variable provided by the CMSIS core modules, if you decide to set manually in your code the cpu clock, then you should also call SystemCoreClockUpdate(); right after to ensure that the variable SystemCoreClock is properly updated.
The final line resets both the TX and RX fifos and disables them via the Fifo Control Register (FCR).
Ok, so, now we are all set to send out some data, we need a putchar function:
int SER_putChar (int uart, int c) {
LPC_UART_TypeDef *pUart;
pUart = __get_uart_base(uart);
while (!(pUart->LSR & SER_THRE));
return (pUart->THR = c);
}
Writing a char to the uart is as simple as putting it in the Transmit Hold Register (THR)... well, almost that simple, we just need to wait the uart is redy to send out a new character and we do that by polling the Line Status Register (LSR)
I defined a few constants to map to the register bits:
#define SER_RDR 0x1
#define SER_OE 0x2
#define SER_PER 0x4
#define SER_FE 0x8
#define SER_BI 0x10
#define SER_THRE 0x20
#define SER_TEMT 0x40
If you check bit 5, you notice that it tells us if the THR is empty and ready to receive another byte.
Using the same register we can discover if we have incoming data
int SER_hasData(int uart)
{
LPC_UART_TypeDef *pUart;
pUart = __get_uart_base(uart);
return pUart->LSR & SER_RDR;
}
and then we can read from RX (register RBR)
int SER_getChar (int uart)
{
LPC_UART_TypeDef *pUart;
pUart = __get_uart_base(uart);
while (!(pUart->LSR & SER_RDR));
return (pUart->RBR);
}
and handle output strings as well
void SER_putString (int uart, unsigned char *s)
{
while (*s != 0) {
SER_putChar(uart, *s++);
}
}
Ok, now a simple test program:
#include "lpc17xx.h"
void delay(uint32_t count)
{
while (count>0) count--;
}
int main(void)
{
SystemInit(); //CMSIS standard
SER_init(2,115200);
while (1)
{
delay(10000000); // I am running @100MHz
SER_putString (2, ".");
while (SER_hasData(2))
{
SER_putString (2, "Received : ");
SER_putChar(2,SER_getChar(2));
SER_putString (2, "\r\n");
}
}
return 0;
}
This is really a basic program, but should give you a basic start to play with the uart.
So far we did not enable interrupts, but if you would like to play with my code, you can find it here.
No comments:
Post a Comment