STM32F411RE Nucleo PCD8544/Nokia5110 Simplistic Chronograph (Timer) Program

stopwatch

A very simplistic timer implemented on an STM32 Nucleo board using a PCD8544 controller/Nokia 5110 LCD for display output. The display uses a memory buffer to construct the screen display, and sends the complete buffer to the LCD instead of drawing directly on the LCD screen. This method would be suitable for displaying video or gaming.

A finite state machine for the main loop results in rather compact code:

  while(1) {
    if (!ignore_input) {
      //button press?			
      if ((GPIOC->IDR & GPIO_Pin_13) == (uint32_t)Bit_RESET) {
        ignore_input = 100;  //start debounce
        switch (state) {     //change timer state
          case STOPPED:
            state = RUNNING;
            break;
          case RUNNING:
          default:
            state = STOPPED;
            break;
        }
      }
    }
    if (ignore_input)     
      ignore_input--;     //decrement debounce timer
    ClearDisplayBuffer(); //erase
    DisplayTime(msTicks); //draw
    PCD8544Update();      //display
  }

Admittedly, this FSM has only 2 states, however adding additional states is as simple as inserting another case to the switch statement. For example, if we added a reset button we might add the following code:

          case RESET:
            state = STOPPED
            msTicks = 0UL;
            break;

The font was constructed using MikroElectronika GLCD Font Creator program. One could easily extract the PCD8544/Nokia5110 routines and incorporate them in their own project, or make a library. Here is the full source code:

//pcd8544/nokia5110 test
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_spi.h"
#include <stdlib.h>
#include "string.h"

//pcd8544/nokia5110 pins
//CS  GPIOA, GPIO_Pin_8
//RST GPIOB, GPIO_Pin_10
//DC  GPIOB, GPIO_Pin_4
//MO  GPIOB, GPIO_Pin_5
//SCK GPIOB, GPIO_Pin_3

//possible timer states
#define STOPPED 0
#define RUNNING 1

//timer state
volatile uint16_t state;
//counts 1ms timeTicks
volatile uint32_t msTicks; 
//debounce button by ignoring input after state change
uint16_t ignore_input;

void SysTick_Handler(void) {
  if (state)
    msTicks++;    
}

//
// PCD8551/Nokia5110 Display stuff
//
#define BLACK                       1
#define WHITE                       0
#define LCDWIDTH                    84
#define LCDHEIGHT                   48
#define PCD8544_POWERDOWN           0x04
#define PCD8544_ENTRYMODE           0x02
#define PCD8544_EXTENDEDINSTRUCTION 0x01
#define PCD8544_DISPLAYBLANK        0x0
#define PCD8544_DISPLAYNORMAL       0x4
#define PCD8544_DISPLAYALLON        0x1
#define PCD8544_DISPLAYINVERTED     0x5
#define PCD8544_FUNCTIONSET         0x20
#define PCD8544_DISPLAYCONTROL      0x08
#define PCD8544_SETYADDR            0x40
#define PCD8544_SETXADDR            0x80
#define PCD8544_SETTEMP             0x04
#define PCD8544_SETBIAS             0x10
#define PCD8544_SETVOP              0x80

//bit value
#define _BV(x) (1<<x) 

//buffer for the LCD screen
uint8_t pcd8544_buffer[LCDWIDTH*LCDHEIGHT/8] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0,
  0, 16, 0, 0, 16, 16, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 192,
  192, 192, 192, 96, 96, 96, 255, 252, 254, 254, 206, 204, 230, 243, 97, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  128, 192, 224, 48, 24, 12, 14, 6, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
  3, 7, 31, 255, 255, 254, 252, 248, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 252, 255, 7, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 240, 252, 255, 255, 255, 255, 63, 3, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 127, 255, 248, 224,
  192, 192, 128, 128, 128, 128, 128, 128, 128, 128, 128, 192, 192, 224, 224, 240,
  248, 248, 252, 254, 127, 127, 63, 31, 15, 7, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 7, 7, 7, 15, 15, 15,
  15, 15, 15, 15, 15, 15, 7, 7, 7, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

//Font Generated by MikroElektronika GLCD Font Creator 1.2.0.0
//GLCD FontName : Courier_New
//GLCD FontSize : 18 x 23
static const unsigned char Courier_New18x23[] = {
  0x11, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x01, 0xF0, 0xFF, 0x07, 0xF8, 0xFF, 0x0F, 0x7E, 0x00, 0x3F, 0x1E, 0x00, 0x3C, 0x0F, 0x00, 0x78, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x0F, 0x00, 0x78, 0x1E, 0x00, 0x3C, 0x7C, 0x00, 0x3F, 0xF8, 0xFF, 0x0F, 0xF0, 0xFF, 0x07, 0xC0, 0xFF, 0x01, 0x00, 0x00, 0x00,  // Code for char 0
  0x11, 0x00, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x1E, 0x00, 0x70, 0x0E, 0x00, 0x70, 0x0E, 0x00, 0x70, 0xFE, 0xFF, 0x7F, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,  // Code for char 1
  0x11, 0x00, 0x00, 0x70, 0xF0, 0x00, 0x78, 0xF8, 0x00, 0x7C, 0xFC, 0x00, 0x7E, 0x1E, 0x00, 0x77, 0x0E, 0x00, 0x73, 0x0F, 0x80, 0x73, 0x07, 0xC0, 0x71, 0x07, 0xE0, 0x70, 0x07, 0xF0, 0x70, 0x07, 0x70, 0x70, 0x07, 0x38, 0x70, 0x0E, 0x1C, 0x70, 0x1E, 0x1F, 0x70, 0xFC, 0x0F, 0x78, 0xF8, 0x07, 0x78, 0xE0, 0x01, 0x70, 0x00, 0x00, 0x00,  // Code for char 2
  0x11, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x38, 0x1E, 0x00, 0x38, 0x1E, 0x00, 0x78, 0x0E, 0x00, 0x70, 0x0F, 0x00, 0x70, 0x07, 0x0E, 0x70, 0x07, 0x0E, 0x70, 0x07, 0x0E, 0x70, 0x07, 0x0E, 0x70, 0x07, 0x0E, 0x70, 0x0F, 0x1F, 0x70, 0x8E, 0x3F, 0x38, 0xFE, 0x7B, 0x3C, 0xFC, 0xF1, 0x1F, 0xF0, 0xF0, 0x0F, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x00,  // Code for char 3
  0x11, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0xE0, 0x03, 0x00, 0xF8, 0x03, 0x00, 0xFE, 0x03, 0x00, 0x9F, 0x03, 0xC0, 0x87, 0x03, 0xF0, 0x81, 0x73, 0x7C, 0x80, 0x73, 0x3E, 0x80, 0x73, 0x0F, 0x80, 0x73, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x7F, 0x00, 0x80, 0x73, 0x00, 0x80, 0x73, 0x00, 0x80, 0x73, 0x00, 0x00, 0x00,  // Code for char 4
  0x11, 0x00, 0x00, 0x18, 0x00, 0x00, 0x3C, 0xFF, 0x0F, 0x3C, 0xFF, 0x1F, 0x38, 0xFF, 0x1F, 0x78, 0x07, 0x0E, 0x70, 0x07, 0x0F, 0x70, 0x07, 0x07, 0x70, 0x07, 0x07, 0x70, 0x07, 0x07, 0x70, 0x07, 0x07, 0x70, 0x07, 0x07, 0x70, 0x07, 0x0E, 0x38, 0x07, 0x1E, 0x3C, 0x07, 0xFC, 0x1F, 0x03, 0xF8, 0x0F, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x00,  // Code for char 5
  0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xC0, 0xFF, 0x07, 0xE0, 0xFF, 0x1F, 0xF0, 0x79, 0x3E, 0x78, 0x3C, 0x38, 0x3C, 0x1C, 0x70, 0x1E, 0x1E, 0x70, 0x0E, 0x0E, 0x70, 0x0F, 0x0E, 0x70, 0x07, 0x0E, 0x70, 0x07, 0x0E, 0x70, 0x07, 0x1C, 0x78, 0x07, 0x3C, 0x3C, 0x0F, 0xF8, 0x3F, 0x0F, 0xF0, 0x1F, 0x06, 0xC0, 0x07,  // Code for char 6
  0x11, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x70, 0x07, 0x00, 0x7E, 0x07, 0xC0, 0x7F, 0x07, 0xF0, 0x0F, 0x07, 0xFE, 0x01, 0x87, 0x7F, 0x00, 0xF7, 0x0F, 0x00, 0xFF, 0x03, 0x00, 0x7F, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char 7
  0x11, 0x00, 0x00, 0x00, 0xF0, 0xC1, 0x07, 0xF8, 0xF7, 0x1F, 0xFC, 0xFF, 0x1F, 0x1E, 0x7F, 0x3C, 0x0E, 0x3E, 0x38, 0x07, 0x3C, 0x70, 0x07, 0x1C, 0x70, 0x07, 0x1C, 0x70, 0x07, 0x1C, 0x70, 0x07, 0x1C, 0x70, 0x07, 0x3C, 0x70, 0x0E, 0x3E, 0x38, 0x1E, 0x7F, 0x3C, 0xFC, 0xFF, 0x1F, 0xF8, 0xF7, 0x1F, 0xF0, 0xC1, 0x07, 0x00, 0x00, 0x00,  // Code for char 8
  0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x30, 0xF8, 0x0F, 0x78, 0xFC, 0x1F, 0x78, 0x1E, 0x3C, 0x70, 0x0F, 0x38, 0x70, 0x07, 0x70, 0x70, 0x07, 0x70, 0x70, 0x07, 0x70, 0x78, 0x07, 0x70, 0x38, 0x07, 0x78, 0x3C, 0x0E, 0x38, 0x1E, 0x1E, 0x3C, 0x1F, 0x3C, 0x9E, 0x0F, 0xF8, 0xFF, 0x07, 0xF0, 0xFF, 0x03, 0xC0, 0x7F, 0x00   // Code for char 9
};

//holds number to display in ascii
uint8_t d[5];

#define swap(a, b) { int16_t t = a; a = b; b = t; }

void PCD8544Write(uint8_t d) {
  uint8_t i;
  
  GPIOA->BSRR |= (GPIO_Pin_8<<16);   //clear cs
  //bit bang
  for (i=0; i<8; i++) {
    if (d & 0x80)                    //output bit
      GPIOB->BSRR |= GPIO_Pin_5;     //din
    else
      GPIOB->BSRR |= (GPIO_Pin_5<<16);
    d <<= 1;                         //next bit
    GPIOB->BSRR |= (GPIO_Pin_3<<16); //toggle clock
    GPIOB->BSRR |= GPIO_Pin_3;
  }
  //transmission complete
  GPIOA->BSRR |= GPIO_Pin_8;         //set cs
}

void PCD8544SendCommand(uint8_t c) {
  //clear dc
  GPIOB->BSRR |= (GPIO_Pin_4<<16);
  //send command
  PCD8544Write(c);
}

//write buffer to lcd
void PCD8544Update(void) {
  uint8_t column, max_column, p;
  
  max_column = LCDWIDTH - 1;
  for (p=0; p<6; p++) {
    PCD8544SendCommand(PCD8544_SETYADDR | p);
    PCD8544SendCommand(PCD8544_SETXADDR);
    GPIOB->BSRR |= GPIO_Pin_4;                //set dc
    for (column=0; column<=max_column; column++)
      PCD8544Write(pcd8544_buffer[(LCDWIDTH*p) + column]);
  }
  //no idea why this is necessary but it is to finish the last byte?
  PCD8544SendCommand(PCD8544_SETYADDR );  
}

//set a single pixel
void SetPixel(int16_t x, int16_t y) {
  if ((x < 0) || (x >= LCDWIDTH) || (y < 0) || (y >= LCDHEIGHT))
    return;
  pcd8544_buffer[x + (y/8)*LCDWIDTH] |= _BV(y%8);
}

//set a single pixel
void ClearPixel(int16_t x, int16_t y) {
  if ((x < 0) || (x >= LCDWIDTH) || (y < 0) || (y >= LCDHEIGHT))
    return;
  pcd8544_buffer[x + (y/8)*LCDWIDTH] &= ~_BV(y%8);
}

//Bresenham's algorithm
void DrawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
  int16_t steep, dx, dy, y_step, err;

  steep = abs(y1 - y0) > abs(x1 - x0);
  if (steep) {
    swap(x0, y0);
    swap(x1, y1);
  }
  if (x0 > x1) {
    swap(x0, x1);
    swap(y0, y1);
  }
  dx = x1 - x0;
  dy = abs(y1 - y0);
  err = dx/2;
  if (y0 < y1)
    y_step = 1;
  else
    y_step = -1;
  for (; x0<=x1; x0++) {
    if (steep)
      SetPixel(y0, x0);
    else
      SetPixel(x0, y0);
    err -= dy;
    if (err < 0) {
      y0 += y_step;
      err += dx;
    }
  }
}

void FillRect(int16_t x, int16_t y, int16_t w, int16_t h) {
  int16_t i;
  
  for (i=x; i<x + w; i++)
    DrawLine(i, y, i, y + h - 1);
}

//font data: Courier_New18x23
#define FONT_WIDTH      18
#define FONT_HEIGHT     23
#define FONT_START_CHAR 48
#define FONT_END_CHAR   57

//draw character
uint8_t DrawCharXY(uint8_t x, uint8_t y, char c) {
  uint8_t i, char_width, bytes_high = FONT_HEIGHT/8 + 1;
  uint8_t bytes_per_char = FONT_WIDTH*(FONT_HEIGHT/8+1) + 1; //+1 for width byte at start
  const unsigned char *p;

  if (c < FONT_START_CHAR || c > FONT_END_CHAR)
    c = '0';
  p = Courier_New18x23 + (c - FONT_START_CHAR)*bytes_per_char;
  //first byte of character is always width of character
  char_width = *p;
  p++;                                //step over width field
  for (i=0; i<char_width; i++) {
    uint8_t j;

    for (j=0; j<bytes_high; j++) {
      uint8_t b, dat;

      dat = *(p + i*bytes_high + j);
      for (b=0; b<8; b++) {
        if (x + i >= LCDWIDTH || y + j*8 + b >= LCDHEIGHT)
          return 0;                   //don't write past dimensions of LCD, skip entire char
        if ((j*8 + b) >= FONT_HEIGHT)  //we should not write if y bit exceeds font height
          continue;                   //skip bit
        if (dat & (1<<b))
          SetPixel(x + i, y + j*8 + b);
        else
          ClearPixel(x + i, y + j*8 + b);
      }
    }
  }
  return char_width;
}

//convert uint32_t millis value to array of 4 digits
void ExtractDigits(uint32_t number) {
  uint8_t i;
  
  number /= 10;  //eliminate thousands digit
  i = 0;
  while (number > 0) {
    //pull individual digits from 'number' and stuff into 'd' array
    d[i++] = (uint8_t)(number%10) + 48;
    number /= 10;
  }
  while (i <= 4) //display needs 4 chars
    d[i++] = 48; //fill with leading '0's 
}

void DisplayTime(uint32_t number) {
  ExtractDigits(number);    //convert UL to byte array
  DrawCharXY(1, 10, d[3]);
  DrawCharXY(21, 10, d[2]);
  DrawCharXY(46, 10, d[1]);
  DrawCharXY(66, 10, d[0]);
  //decimal point
  FillRect(41, 30, 3, 3);
}

void PCD8544SetContrast(uint8_t val) {
  if (val > 0x7f)
    val = 0x7f;
  PCD8544SendCommand(PCD8544_FUNCTIONSET | PCD8544_EXTENDEDINSTRUCTION);
  PCD8544SendCommand(PCD8544_SETVOP | val); 
  PCD8544SendCommand(PCD8544_FUNCTIONSET);
}

//clear buffer
void ClearDisplayBuffer(void) {
  memset(pcd8544_buffer, 0, LCDWIDTH*LCDHEIGHT/8);
}

void PCD8544GPIOConfig(void) {
  //cs
  GPIOA->MODER &= ~(GPIO_MODER_MODER0<<(8*2));
  GPIOA->MODER |= (((uint32_t)GPIO_Mode_OUT)<<(8*2));
  GPIOA->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(8*2));
  GPIOA->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(8*2));
  GPIOA->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)8)); 
  GPIOA->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)8));
  GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)8*2));
  GPIOA->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(8*2));
  //mo
  GPIOB->MODER &= ~(GPIO_MODER_MODER0<<(10*2));
  GPIOB->MODER |= (((uint32_t)GPIO_Mode_OUT)<<(10*2));
  GPIOB->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(10*2));
  GPIOB->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(10*2));
  GPIOB->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)10)); 
  GPIOB->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)10));
  GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)10*2));
  GPIOB->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(10*2));
  //dc
  GPIOB->MODER &= ~(GPIO_MODER_MODER0<<(4*2));
  GPIOB->MODER |= (((uint32_t)GPIO_Mode_OUT)<<(4*2));
  GPIOB->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(4*2));
  GPIOB->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(4*2));
  GPIOB->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)4)); 
  GPIOB->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)4));
  GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)4*2));
  GPIOB->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(4*2));
  //rst
  GPIOB->MODER &= ~(GPIO_MODER_MODER0<<(5*2));
  GPIOB->MODER |= (((uint32_t)GPIO_Mode_OUT)<<(5*2));
  GPIOB->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(5*2));
  GPIOB->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(5*2));
  GPIOB->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)5)); 
  GPIOB->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)5));
  GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)5*2));
  GPIOB->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(5*2));
  //sck
  GPIOB->MODER &= ~(GPIO_MODER_MODER0<<(3*2));
  GPIOB->MODER |= (((uint32_t)GPIO_Mode_OUT)<<(3*2));
  GPIOB->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(3*2));
  GPIOB->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(3*2));
  GPIOB->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)3)); 
  GPIOB->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)3));
  GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)3*2));
  GPIOB->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(3*2));
}

void PCD8544Init(void) {
  uint32_t i;
  
  PCD8544GPIOConfig();  
  GPIOB->BSRR |= GPIO_Pin_4;        //dc
  GPIOB->BSRR |= GPIO_Pin_5;        //mo
  GPIOB->BSRR |= GPIO_Pin_3;        //clock
  GPIOA->BSRR |= GPIO_Pin_8;        //cs
  GPIOB->BSRR |= (GPIO_Pin_10<<16); //rst
  //slight delay
  for (i=0;i<10000; i++) 
    ;
  GPIOB->BSRR |= GPIO_Pin_10;       //rst
  //get into the EXTENDED mode
  PCD8544SendCommand(PCD8544_FUNCTIONSET | PCD8544_EXTENDEDINSTRUCTION );
  //contrast
  PCD8544SendCommand(PCD8544_SETVOP | 0x40);
  //LCD bias select (4 is optimal?)
  PCD8544SendCommand(PCD8544_SETBIAS | 0x04);
  //normal mode
  PCD8544SendCommand(PCD8544_FUNCTIONSET);
  PCD8544SendCommand(PCD8544_DISPLAYCONTROL | PCD8544_DISPLAYNORMAL);
  //push out pcd8544_buffer to the display
  PCD8544Update();
}

//configure SystemCoreClock using HSI (HSE is not populated on Nucleo board)
void SystemCoreClockConfigure(void) {
  //enable HSI
  RCC->CR |= ((uint32_t)RCC_CR_HSION);                     
  while ((RCC->CR & RCC_CR_HSIRDY) == 0)
    ; //Wait for HSI Ready RCC->CFGR = RCC_CFGR_SW_HSI;
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI)
    ; //wait for HSI used as system clock
  FLASH->ACR  = FLASH_ACR_PRFTEN;      //enable Prefetch Buffer
  FLASH->ACR |= FLASH_ACR_ICEN;        //instruction cache enable
  FLASH->ACR |= FLASH_ACR_DCEN;        //data cache enable
  FLASH->ACR |= FLASH_ACR_LATENCY_5WS; //flash 5 wait state
  //HCLK = SYSCLK
  RCC->CFGR |= RCC_CFGR_HPRE_DIV1;                         
  //APB1 = HCLK/2
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;                        
  //APB2 = HCLK/1
  RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;                        
  //disable PLL
  RCC->CR &= ~RCC_CR_PLLON;                                
  //PLL configuration: VCO=HSI/M*N, Sysclk=VCO/P
  //PLL_M=16, PLL_N=320, PLL_P=4, PLL_SRC=HSI, PLL_Q=8 for 80MHz SYSCLK/APB2, 40MHz APB1
  RCC->PLLCFGR = (16ul | (320ul<<6) | (1ul<<16) | (RCC_PLLCFGR_PLLSRC_HSI) | (8ul<<24));
  //enable PLL
  RCC->CR |= RCC_CR_PLLON;                                 
  //Wait till PLL is ready
  while((RCC->CR & RCC_CR_PLLRDY) == 0) 
    __NOP();
  //select PLL as system clock source
  RCC->CFGR &= ~RCC_CFGR_SW;                               
  RCC->CFGR |=  RCC_CFGR_SW_PLL;
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
    ; //wait till PLL is system clock src
}

int main(void) {
  //configure HSI as System Clock
  SystemCoreClockConfigure();                              
  SystemCoreClockUpdate();

  //enable GPIOA peripheral clock
  RCC->AHB1ENR |= (1ul<<0);

  //configure nucleo led (pa5) pin as output, push-pull, no pull-up/down 
  GPIOA->MODER &= ~((3ul<<2*5));                //clear both mode bits
  GPIOA->MODER |= ((GPIO_Mode_OUT<<2*5));       //set as general purpose output
  GPIOA->OTYPER &= ~((1ul<<5));                 //clear (push/pull)
  GPIOA->OSPEEDR &= ~((3ul<<2*5));              //clear both speed bits
  GPIOA->OSPEEDR |= ((GPIO_Medium_Speed<<2*5)); //set medium speed
  GPIOA->PUPDR &= ~((3ul<<2*5));                //clear both pull up/down status (none)
  //configure nucleo blue button (pa13) pin as input, push-pull with pull-down 
  RCC->AHB1ENR |= (2ul<<1);                     //enable GPIOC clock
  GPIOC->MODER &= ~((3ul<<2*13));               //clear both mode bits
  GPIOC->MODER |= ((GPIO_Mode_IN<<2*13));       //set as input
  GPIOC->OTYPER &= ~((1ul<<13));                //clear (push/pull)
  GPIOC->OSPEEDR &= ~((3ul<<2*13));             //clear both speed bits
  GPIOC->OSPEEDR |= ((GPIO_High_Speed<<2*13));  //set high speed
  GPIOC->PUPDR |= (GPIO_PuPd_DOWN<<2*13);       //set pull down status

  //SysTick 1 msec interrupts
  SysTick_Config(SystemCoreClock/1000);                  

  //enable GPIOB peripheral clock
  RCC->AHB1ENR |= (1ul<<1);
  //configure pcd8544 pins & lcd setup
  PCD8544Init();

  //inital states
  state = STOPPED;
  msTicks = 0UL;
  ignore_input = 0;

  //endless loop
  while(1) {
    if (!ignore_input) {
      if ((GPIOC->IDR & GPIO_Pin_13) == (uint32_t)Bit_RESET) {
        ignore_input = 100;
        switch (state) {
          case STOPPED:
            state = RUNNING;
            break;
          case RUNNING:
            state = STOPPED;
            break;
          default:
            state = STOPPED;
            break;
        }
      }
    }
    if (ignore_input)     
      ignore_input--;     //debounce button
    ClearDisplayBuffer(); //erase
    DisplayTime(msTicks); //draw
    PCD8544Update();      //display
  }
}
Advertisements

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s