Quantcast
Channel: Raspberry Pi Forums
Viewing all articles
Browse latest Browse all 4504

Bare metal, Assembly language • Trouble with I2C and LCD setup with a custom OS

$
0
0
For a computer science project, I figured it would be a fun idea to try and make a simple operating system for raspberry pi with I/O and file management capabilities. My main systems for output are the GPIO pins (of course), but I'm also trying to get a LCD screen to display text through the I2C (BSC) peripheral. I've been able to get the GPIO to function just as expected, but the main problem has been getting the LCD to display any text. I've been told that I can just use an I2C library, but that is not my goal here; my goal is to build something from scratch. My main ask is, do you see any errors with my code or setup that may be preventing the LCD from displaying the text? I've gone through the datasheets that I could find, and even asked Chat GPT for help if the code looked correct, but I honestly cannot find anywhere I may have messed up. The main issue that may possibly be happening is that I don't have the right address for the LCD screen - I've put it as 0x27 (in function I2CInitialize writing to the I2C_A (address) register), but I've seen that this I2C "communicator" (is that the right term?) can take anything from 0x20 to 0x27, so I have no clue. The only other thing is maybe I didn't send "LCDSendCmd(0x30)" enough times to wake the LCD up? Thank you so much for helping, if you do. Everything relevant (and possibly irrelevant) should be provided below.

Relavent Hardware:

16x2 LCD screen with a PCF8574T I2C communicator

Raspberry Pi 2 Model B V1.1

Compiling on a 13" 2024 M3 MacBook air running Sonoma 14.6.1

Photos and Videos of setup (Ignore the Arduino, it's a failed UART experiment (I don't have a TTC to USB converter)): Help

Software (Relevant and (Possibly)Irrelevant):

kernel.c:

Code:

#include <stddef.h>#include <stdint.h>// The GPIO registers base address.#define I2C_BASE 0x3F804000  // Adjust based on Pi version  // all different regesters for reading and writing different things.#define I2C_C    *(volatile uint32_t *)(I2C_BASE + 0x00)  // C= Control Regester#define I2C_S    *(volatile uint32_t *)(I2C_BASE + 0x04)  // S= Status Regester#define I2C_DLEN *(volatile uint32_t *)(I2C_BASE + 0x08)  // DLEN= Data Length#define I2C_A    *(volatile uint32_t *)(I2C_BASE + 0x0C)  // A= Slave regester, the 1st 7 bits of which contain the address of the I2C component (unique to each device, it depends.) for LCD displays, usualy 0x27, but can be 0x3F rarely#define I2C_FIFO *(volatile uint32_t *)(I2C_BASE + 0x10)  // FIFO= first in first out, very small amount of storage/* enum {  // Probobly not going to use this as I can't find a ttc to usb, but good just in case  GPPUD = (GPIO_BASE + 0x94),  GPPUDCLK0 = (GPIO_BASE + 0x98),  // The base address for UART.  UART0_BASE = 0x3F201000, // for raspi2 & 3, 0x20201000 for raspi1  UART0_DR     = (UART0_BASE + 0x00),  UART0_RSRECR = (UART0_BASE + 0x04),  UART0_FR     = (UART0_BASE + 0x18),  UART0_ILPR   = (UART0_BASE + 0x20),  UART0_IBRD   = (UART0_BASE + 0x24),  UART0_FBRD   = (UART0_BASE + 0x28),  UART0_LCRH   = (UART0_BASE + 0x2C),  UART0_CR     = (UART0_BASE + 0x30),  UART0_IFLS   = (UART0_BASE + 0x34),  UART0_IMSC   = (UART0_BASE + 0x38),  UART0_RIS    = (UART0_BASE + 0x3C),  UART0_MIS    = (UART0_BASE + 0x40),  UART0_ICR    = (UART0_BASE + 0x44),  UART0_DMACR  = (UART0_BASE + 0x48),  UART0_ITCR   = (UART0_BASE + 0x80),  UART0_ITIP   = (UART0_BASE + 0x84),  UART0_ITOP   = (UART0_BASE + 0x88),  UART0_TDR    = (UART0_BASE + 0x8C),}; */#define GPIO_BASE      0x3F200000  // for raspi2 & 3, 0x20200000 for raspi1  // turn gpio base into an address (pointer) to be writen to with a new versoin of the variable that is now lowercase#define GPFSEL0        0x00#define GPFSEL1        0x04#define GPFSEL2        0x08#define GPFSEL3        0x0c#define GPFSEL4        0x10#define GPFSEL5        0x14#define GPFSET0        *(volatile uint32_t *)(GPIO_BASE + 0x1c)#define GPFCLR0        *(volatile uint32_t *)(GPIO_BASE + 0x28)#define GPFSET1        *(volatile uint32_t *)(GPIO_BASE + 0x20)#define GPFCLR1        *(volatile uint32_t *)(GPIO_BASE + 0x2c)void pinFunc(unsigned int pinN, uint32_t funcSet){  // function to bits:    // setToInput: 0b000    // setToOutput: 0b001    // setToAltFuc0: 0b100    // setToAltFuc1: 0b101    // setToAltFuc2: 0b110    // setToAltFuc3: 0b111    // setToAltFuc4: 0b011    // setToAltFuc5: 0b010  // pin function group regesters (each group of 3 of the 30-some bits of the regester corrispond to the function set for each pin)    // pins 0-9: GPFSEL0    // pins 10-19: GPFSEL1    // pins 20-29: GPFSEL2    // pins 30-39: GPFSEL3    // pins 40-49: GPFSEL4    // pins 50-53: GPFSEL5  unsigned int bitPos = 3*(pinN%10);  volatile uint32_t* GPFSEL = (volatile uint32_t *)(GPIO_BASE + ((pinN / 10) * 4));  // intager division  // declare a pointer with a * before the name at any point, refrence the target of the pointer and not just the pos by including the defferance operator, which is conincedentaly also an astrix before a pointer var  *GPFSEL &= ~(0b111 << bitPos);  // clears the position of the funcset area, by putting ...000000011100000000... at the pos, then inverting to only and (clear) the necessary 3 bits, and set everything else to 1, so that it preserves the original setting when anded.  *GPFSEL |= (funcSet << bitPos);  // gets the full 32 bit func like ...00000000"001"000000... then oring it to make sure not to clear anytning else by setting it to 0}// set/clear regesters for different pins  // pins 0-31: CLR0/SET0  // pins 32-53: CLR1/SET1void pinOn(unsigned int pinN){  // 1 leftshifted by the pin number into the regester  if (pinN <= 31){    GPFSET0 = (1 << pinN);  // assignes to the regester its pointing at. deference operator * not neccessary as it's baked into the definition at the top  }  else if (pinN >= 32){    GPFSET1 = (1 << (pinN-32));  }}void pinOff(unsigned int pinN){  if (pinN <= 31){    GPFCLR0 = (1 << pinN);  }  else if (pinN >= 32){    GPFCLR1 = (1 << (pinN-32));  }}void OSDelay(int reps){  while (reps--) {    asm volatile("nop"); // empty loop to create delay, compiler might optomize but I don't trust macos that much  }}void I2CInitalize() {  // Set the I2C clock rate, address, and enable I2C mode  I2C_C = 0;  // Disable I2C temporarily for configuration  I2C_A = 0x27;  // Address for your LCD, adjust as needed. only the 1st 7 bits of the 32 of this regester matter/are written to, it is the address. in this case, it's address 39, or 0b100111, or 0x27  // Set clock rate by configuring I2C_C (Clock Control)  // Enable the interface after configuration  I2C_C |= (1 << 15);  // Enable I2C. Bit 15 is the BSC (Broadcom (manufacturer of raspi2 processor) Serial Controller) enable/disable (1/0) (page 29)}void I2CByteSend(uint8_t cmdOrData){  I2C_DLEN = 1;  // DLEN has 16 bits to store the length. it counts through this to send the bits one at a time  I2C_FIFO = cmdOrData;  // there are some commands that I can send to the screen to do certain things. EG, 0x01 clears, 0x0C turns on w/o cursor, 0x06 set auto increment for cursor, 0x30 wake up (send multiple times), 0x28 turn on 4 bit mode and 2 line display   I2C_C |= (1 << 7);  // sets the start (7) bit to 1, to start transmission of command  while (!(I2C_S & (1 << 1)));  // checks if the DONE (1) bit, anded with 1 = 1. if it equals 0, then invertend and the loop continues, but if 1, inverted is 0, loop ends  I2C_S |= (1 << 1); // clear the DONE bit }/*the FIFO regester sends data to the LCD. the bits work out like this:bits 0:3 - Char/Commandbit 4 - Regester select (RS) (0 for sending command, 1 for sending char/data)bit 5 - read/write (R/W) (0 for writing to LCD, 1 for reading from LCD)bit 6 - enable (E), rising edge signal (0 -> 1), then falling edge (1 -> 0) (send command twice with same data, exept change enable from 1 to 0) latches then sends data to LCDbit 7 - backlight control (BL) (if appliccable). usualy 1 for backlight on, 0 for backlight off*/unsigned short backlight = 1;  // bool backlight = true;void LCDSendCmd(uint8_t cmd){    uint8_t highNybble = cmd >> 4;  // Extract MSB  uint8_t lowNybble = cmd & 0x0F;  // Extract LSB  uint8_t settings = 0b00000000 | (backlight << 7); // & 0b[BL][E][RS = 1][R/W = 0]0000, *sends* a *command*  // send first nibbyl  I2CByteSend(settings | highNybble | (1 << 6));  // send it by 1ing Enable bit  I2CByteSend(settings | highNybble | (0 << 6));  // latch it by 0ing Enable bit  // send second nibbyl  I2CByteSend(settings | lowNybble | (1 << 6));  I2CByteSend(settings | lowNybble | (0 << 6));}void LCDSendChar(char ch){    uint8_t highNybble = ch >> 4;  // Extract MSB  uint8_t lowNybble = ch & 0x0F;  // Extract LSB  uint8_t settings = 0b00010000 | (backlight << 7); // & 0b[BL][E][RS = 0][R/W = 0]0000, *sends* a *command*  //0b[backlight]0000000  // send first nibbyl  I2CByteSend(settings | highNybble | (1 << 6));  I2CByteSend(settings | highNybble | (0 << 6));  // send second nibbyl  I2CByteSend(settings | lowNybble | (1 << 6));  I2CByteSend(settings | lowNybble | (0 << 6));}void kernel_main() {  // Initialize I2C for LCD  I2CInitalize();  // Set GPIO pins 16, 20, and 21 to output  pinFunc(16, 0b001);  pinFunc(20, 0b001);  pinFunc(21, 0b001);  // Test GPIO by blinking LEDs  for (int i = 0; i < 5; i++) {    pinOn(16);  // Turn on red LED    OSDelay(500000);    pinOff(16); // Turn off red LED    pinOn(20);  // Turn on green LED    OSDelay(500000);    pinOff(20); // Turn off green LED    pinOn(21);  // Turn on blue LED    OSDelay(500000);    pinOff(21); // Turn off blue LED  }  // Test LCD by sending initialization commands  LCDSendCmd(0x30); // Wake up LCD (send multiple times if needed)  LCDSendCmd(0x30);  LCDSendCmd(0x30);  LCDSendCmd(0x30);  LCDSendCmd(0x30);  OSDelay(50000);   // Small delay  LCDSendCmd(0x28); // 4-bit mode, 2-line display  LCDSendCmd(0x0C); // Display ON, cursor OFF  LCDSendCmd(0x01); // Clear display  OSDelay(2000);    // Wait for clear to complete  LCDSendCmd(0x06); // Auto-increment cursor  // Send a test message to the LCD  const char *message = "Hello, World!";  for (size_t i = 0; message[i] != '\0'; i++) {    LCDSendChar(message[i]);  }  // Toggle backlight for testing  backlight = 0; // Turn backlight off  LCDSendCmd(0x0C); // Refresh display with new settings  OSDelay(500000);  backlight = 1; // Turn backlight on  LCDSendCmd(0x0C); // Refresh display with new settings}
boot.S:

Code:

.section ".text.boot".global _start_start:  mrc p15, #0, r1, c0, c0, #5     // read value from coprocessors to r1: https://developer.arm.com/documentation/den0042/a/ARM-Processor-modes-and-Registers/Registers/Coprocessor-15  and r1, r1, #3                  // isolates the first 2 bits of the cp15 Cache Level ID Register (CLIDR) which displays: 00 - no cache, 01 - instrucution-only cache, 01 - data-only cach, 11 - unified cache   cmp r1, #0  bne halt  mov sp, #0x8000                 // stack pointer, only place I can put variables without trying to murder the pi and kernal (cuz the pi starts booting from there)  ldr r4, =__bss_start  ldr r9, =__bss_end  mov r5, #0  mov r6, #0  mov r7, #0  mov r8, #0  b       2f1:  stmia r4!, {r5-r8}  // stmia = store multiple increment after. all regesters from r5 to r8 get stored to r4, and then the address is incremented by one to the next pos (-ia suffix), and that address is stored back into r4 (the ! after r4)  // what this does is store the 16 bytes of 0 stored in r5-r8 (as defined in line 15-18) into r4, 0s out the whole bss section (the section for uninitialized c variables)  // this makes sure that all uninitialized c variables are set to 0 at runtime, else an error gets thrown  // technicaly you could just use 1 regester to 0 out the whole bss section by looping, but that's less efficient then 4 at a time, 0ing out 4 bytes per loop instead of 162:  cmp r4, r9  // checks every time it loops to function 1, only not branching back to 1 when the r4 address is at the r9 address (when all of the bss regesters have been set to 0 - when bss start = bss end - when the pointer for bss start reaches the address of bss end?)  blo 1b  // branch if (unsigned) less than (r4 than r9)  ldr r3, =kernel_main  // gets address of kernel main function from c. the = means address, kinda like & in c funciton param declaration  blx r3  // branches to address in r3 (kernal_main script)  //b halt  // not neccessary as it would go there anyways, but helps in this case to make absolutly sure that no undefined actions happen// this is what the unused cores branch to, and what core 0 branches to once kernel_main returnshalt:    wfe  // do nothing at low power mode  b halt  // loop
linker.ld:

Code:

ENTRY(_start)SECTIONS{  /* this is copied from https://jsandler18.github.io/tutorial/boot.html */    /* Starts at LOADER_ADDR. */    . = 0x8000;    __start = .;    __text_start = .;    .text :    {        KEEP(*(.text.boot))        *(.text)    }    . = ALIGN(4096); /* align to page size */    __text_end = .;     __rodata_start = .;    .rodata :    {        *(.rodata)    }    . = ALIGN(4096); /* align to page size */    __rodata_end = .;     __data_start = .;    .data :    {        *(.data)    }    . = ALIGN(4096); /* align to page size */    __data_end = .;     __bss_start = .;    .bss :    {        bss = .;        *(.bss)    }    . = ALIGN(4096); /* align to page size */    __bss_end = .;    __end = .;}
makefile:

Code:

# this is mostly copied from https://jsandler18.github.io/tutorial/boot.html, though i did need to change the command to fit mac and change the files arounddefault:  arm-none-eabi-gcc -mcpu=cortex-a7 -fpic -ffreestanding -c boot.S -o objects/boot.o  arm-none-eabi-gcc -mcpu=cortex-a7 -fpic -ffreestanding -std=gnu99 -c kernel.c -o objects/kernel.o -O2 -Wall -Wextra  arm-none-eabi-gcc -T linker.ld -o objects/myos.elf -ffreestanding -O2 -nostdlib objects/boot.o objects/kernel.o  arm-none-eabi-objcopy -O binary objects/myos.elf kernel7.img
I'm putting the img file in a micro SD with the config.txt, start.elf, and bootcode.bin from Raspberry Pi OS, and booting it (no using balena etcher or anything (it doesn't work I've tried)). This has worked fine with GPIO and some other things, so that's not the problem.

Statistics: Posted by Winter_Sand_ — Sat Nov 23, 2024 5:58 am — Replies 0 — Views 13



Viewing all articles
Browse latest Browse all 4504

Trending Articles