OK. I finally got my debug probe from Adafruit and have it working with code via C/C++ SDK. So far, no luck via Ada (btw - I’m doing everything via VS Code on Mint Linux running on
System:
Kernel: 6.8.0-57-generic arch: x86_64 bits: 64 compiler: gcc v: 13.3.0 clocksource: tsc
Desktop: Xfce v: 4.18.1 tk: Gtk v: 3.24.41 wm: xfwm4 v: 4.18.0 with: xfce4-panel
tools: light-locker vt: 7 dm: LightDM v: 1.30.0 Distro: Linux Mint 22.1 Xia
base: Ubuntu 24.04 noble
Machine:
Type: Portable System: Dell product: Inspiron 3531 v: A00 serial: <superuser required>
Mobo: Dell model: 00FTTX v: A00 serial: <superuser required> part-nu: Inspiron 3531
uuid: <superuser required> UEFI-[Legacy]: Dell v: A00 date: 04/11/2014
So, I decided to post the C code that I know works. All I need is the i2c part to work in Ada and the rest will fall into place. So, if someone could just look at the two sets of code - focusing on the I2C part - maybe someone can help me get this working. My goal is to get this project printed up in the official Raspberry Pi magazine. So, it’d give Ada some exposure.
This is lcd_2004_i2c.c
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "lcd_2004_i2c.h"
// I2C reserves some addresses for special purposes. We exclude these from the scan.
// These are any addresses of the form 000 0xxx or 111 1xxx
static bool reserved_addr(uint8_t addr) {
return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
}
static void i2c_write_byte(i2c_inst_t *i2c, uint8_t addr, uint8_t val) {
i2c_write_blocking(i2c, addr, &val, 1, false);
}
static int lcd_check_available(struct lcd_device *lcd)
{
uint8_t rxdata;
if (reserved_addr(lcd->addr))
return false;
return i2c_read_blocking(lcd->i2c, lcd->addr, &rxdata, 1, false) < 0 ? false : true;
}
static void lcd_toggle_enable(struct lcd_device *lcd, uint8_t val) {
sleep_us(ENABLE_DELAY_US);
i2c_write_byte(lcd->i2c, lcd->addr, val | LCD_ENABLE_BIT);
sleep_us(ENABLE_DELAY_US);
i2c_write_byte(lcd->i2c, lcd->addr, val & ~LCD_ENABLE_BIT);
sleep_us(ENABLE_DELAY_US);
}
static void lcd_send_byte(struct lcd_device *lcd, uint8_t val, int mode) {
uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT;
uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT;
// The data can be sent before or after sending RS and RW signals.
// But data should be available before toggling LCD enable pin.
i2c_write_byte(lcd->i2c, lcd->addr, high);
lcd_toggle_enable(lcd, high);
i2c_write_byte(lcd->i2c, lcd->addr, low);
lcd_toggle_enable(lcd, low);
}
static void inline lcd_char(struct lcd_device *lcd, char val) {
lcd_send_byte(lcd, val, LCD_CHARACTER);
}
static void lcd_home(struct lcd_device *lcd) {
lcd_send_byte(lcd, LCD_RETURNHOME, LCD_COMMAND);
}
void lcd_clear(struct lcd_device *lcd) {
lcd_send_byte(lcd, LCD_CLEARDISPLAY, LCD_COMMAND);
}
void lcd_set_cursor(struct lcd_device *lcd, int line, int position) {
int val;
switch(line) {
case 0:
val = LCD_ROW0;
break;
case 1:
val = LCD_ROW1;
break;
case 2:
val = LCD_ROW2;
break;
case 3:
val = LCD_ROW3;
break;
}
val += position;
lcd_send_byte(lcd, val, LCD_COMMAND);
}
void lcd_string(struct lcd_device *lcd, const char *s) {
while (*s) {
lcd_char(lcd, *s++);
}
}
bool lcd_init(struct lcd_device *lcd) {
if(!lcd_check_available(lcd))
return false;
// please check the initialization flow from https://cdn-shop.adafruit.com/datasheets/TC2004A-01.pdf
lcd_send_byte(lcd, 0x03, LCD_COMMAND);
lcd_send_byte(lcd, 0x03, LCD_COMMAND);
lcd_send_byte(lcd, 0x03, LCD_COMMAND);
lcd_send_byte(lcd, 0x02, LCD_COMMAND);
lcd_send_byte(lcd, LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND);
lcd_send_byte(lcd, LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND);
lcd_send_byte(lcd, LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND);
lcd_clear(lcd);
lcd_home(lcd);
return true;
}
This is lcd_2004_i2c.h
#ifndef LCD2004_I2C_H
#define LCD2004_I2C_H
#include <stdlib.h>
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80
// flags for display entry mode
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYLEFT 0x02
// flags for display and cursor control
#define LCD_BLINKON 0x01
#define LCD_CURSORON 0x02
#define LCD_DISPLAYON 0x04
// flags for display and cursor shift
#define LCD_MOVERIGHT 0x04
#define LCD_DISPLAYMOVE 0x08
// flags for function set
#define LCD_5x10DOTS 0x04
#define LCD_2LINE 0x08
#define LCD_8BITMODE 0x10
// flag for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_ENABLE_BIT 0x04
// Modes for lcd_send_byte
#define LCD_CHARACTER 1
#define LCD_COMMAND 0
#define MAX_LINES 4
#define MAX_CHARS 20
#define LCD_ROW0 0x80
#define LCD_ROW1 0xC0
#define LCD_ROW2 0x94
#define LCD_ROW3 0xD4
#define ENABLE_DELAY_US 666
struct lcd_device
{
uint8_t addr;
i2c_inst_t *i2c;
};
bool lcd_init(struct lcd_device *lcd);
void lcd_string(struct lcd_device *lcd, const char *s);
void lcd_set_cursor(struct lcd_device *lcd, int line, int position);
void lcd_clear(struct lcd_device *lcd);
#endif
Here’s the main program
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
#include "lcd_2004_i2c.h"
static char *message[] =
{
"Cherie Hsieh",
"Software Engineer", "at PUFsecurity",
"Golang", "Community",
"GDG Group", "Thank You",
};
int main() {
//struct lcd_device lcd = {.i2c = i2c_default, .addr = 0x3F};
struct lcd_device lcd = {.i2c = i2c_default, .addr = 0x27};
size_t message_len;
i2c_init(lcd.i2c, 100 * 1000);
//gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
if (!lcd_init(&lcd))
{
printf("failed to init LCD device\n");
return 1;
}
message_len = sizeof(message) / sizeof(message[0]);
while (true) {
for (int m = 0; m < message_len; m += MAX_LINES) {
for (int line = 0; line < MAX_LINES && (m + line) < message_len; line++) {
lcd_set_cursor(&lcd, line, (MAX_CHARS / 2) - strlen(message[m + line]) / 2);
lcd_string(&lcd, message[m + line]);
}
sleep_ms(5000);
lcd_clear(&lcd);
}
}
return 0;
}
Here’s my Ada specification - hd44780lcd.ads
with Ada.Strings; use Ada.Strings;
with Interfaces; use Interfaces;
with RP.GPIO; use RP.GPIO;
with RP.i2c;
with RP.I2C_Master;
with RP.Device;
with RP.Clock;
with HAL; use HAL;
with HAL.I2C; use HAL.I2C;
package hd44780lcd is
SDA : GPIO_Point := (Pin => 4);
SCL : GPIO_Point := (Pin => 5);
Addr_HAL : constant HAL.I2C.I2C_Address := 16#37#;
Port : RP.i2c.I2C_Port renames RP.Device.I2C_0;
Status : RP.I2C.I2C_Status;
subtype Unsigned_Byte is HAL.UInt8;
-- commands
LCD_CLEARDISPLAY : constant Unsigned_Byte := 16#01#;
LCD_RETURNHOME : constant Unsigned_Byte := 16#02#;
LCD_ENTRYMODESET : constant Unsigned_Byte := 16#04#;
LCD_DISPLAYCONTROL : constant Unsigned_Byte := 16#08#;
LCD_CURSORSHIFT : constant Unsigned_Byte := 16#10#;
LCD_FUNCTIONSET : constant Unsigned_Byte := 16#20#;
LCD_SETCGRAMADDR : constant Unsigned_Byte := 16#40#;
LCD_SETDDRAMADDR : constant Unsigned_Byte := 16#80#;
-- flags for display entry mode
LCD_ENTRYSHIFTINCREMENT : constant Unsigned_Byte := 16#01#;
LCD_ENTRYLEFT : constant Unsigned_Byte := 16#02#;
-- flags for display and cursor control
LCD_BLINKON : constant Unsigned_Byte := 16#01#;
LCD_CURSORON : constant Unsigned_Byte := 16#02#;
LCD_DISPLAYON : constant Unsigned_Byte := 16#04#;
-- flags for display and cursor shift
LCD_MOVERIGHT : constant Unsigned_Byte := 16#04#;
LCD_DISPLAYMOVE : constant Unsigned_Byte := 16#08#;
-- flags for function set
LCD_5x10DOTS : constant Unsigned_Byte := 16#04#;
LCD_2LINE : constant Unsigned_Byte := 16#08#;
LCD_8BITMODE : constant Unsigned_Byte := 16#10#;
-- flag for backlight control
LCD_BACKLIGHT : constant Unsigned_Byte := 16#08#;
LCD_ENABLE_BIT : constant Unsigned_Byte := 16#04#;
-- Modes for lcd_send_byte
LCD_CHARACTER : constant Unsigned_Byte := 1;
LCD_COMMAND : constant Unsigned_Byte := 0;
MAX_LINES : constant Unsigned_Byte := 4;
MAX_CHARS : constant Unsigned_Byte := 20;
LCD_ROW0 : constant Unsigned_Byte := 16#80#;
LCD_ROW1 : constant Unsigned_Byte := 16#C0#;
LCD_ROW2 : constant Unsigned_Byte := 16#94#;
LCD_ROW3 : constant Unsigned_Byte := 16#D4#;
ENABLE_DELAY_US : constant Integer := 666;
--struct lcd_device
--{
-- uint8_t addr;
-- i2c_inst_t *i2c;
--};
--bool lcd_init(struct lcd_device *lcd);
--void lcd_string(struct lcd_device *lcd, const char *s);
--void lcd_set_cursor(struct lcd_device *lcd, int line, int position);
--void lcd_clear(struct lcd_device *lcd);
function reserved_addr return Boolean;
procedure i2c_write_byte (val : Unsigned_Byte);
function lcd_check_available return Boolean;
procedure lcd_toggle_enable (val : Unsigned_Byte);
procedure lcd_send_byte (val : Unsigned_Byte; mode : Unsigned_Byte);
procedure lcd_char (val : Unsigned_Byte);
procedure lcd_home;
procedure lcd_clear;
procedure lcd_set_cursor (line : Unsigned_Byte; position : Unsigned_Byte);
procedure lcd_string (s : String);
function lcd_init return Boolean;
procedure rp_i2c_init;
end hd44780lcd;
Here’s my hd44780lcd.adb
with HAL.GPIO;
with Interfaces;
with Interfaces.C;
with RP; use RP;
with RP.GPIO; use RP.GPIO;
with HAL.GPIO; use HAL.GPIO;
with RP.I2C_Master;
with RP.Device;
with RP.Clock;
with HAL.I2C; use HAL;
with Pico;
with hd44780lcd; use hd44780lcd;
package body hd44780lcd is
-- I2C reserves some addresses for special purposes. We exclude these from the scan.
-- These are any addresses of the form 000 0xxx or 111 1xxx
function reserved_addr return Boolean is
temp1 : Unsigned_Byte;
ret : Boolean;
begin
--return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
temp1 := UInt8 (Addr_HAL) and 16#78#;
if ((temp1 = 0) or (temp1 = 16#78#)) then
ret := True; -- true
else
ret := False;
end if;
return ret;
end reserved_addr;
procedure i2c_write_byte (val : Unsigned_Byte) is
use HAL.I2C;
begin
null;
-- i2c_write_blocking(i2c, addr, &val, 1, false);
Port.Write
(Data => val,
Status => Status);
--Port.Master_Transmit
-- (Addr => Addr_HAL, Data => Data, Status => Status,
-- Timeout => 1);
--Port.Master_Transmit (Default_Address, Data, Status);
end i2c_write_byte;
function lcd_check_available return Boolean is
use RP.I2C;
ret : Boolean;
Data : UInt8;
begin
--if (reserved_addr(lcd->addr))
-- return false;
-- return i2c_read_blocking(lcd->i2c, lcd->addr, &rxdata, 1, false) < 0 ? false : true;
if (reserved_addr) then
ret := False; -- false
else
--i2c_read_blocking(lcd->i2c, lcd->addr, &rxdata, 1, false)
Port.Read
(Data => Data,
Status => Status);
if (Status = Ok) then
ret := False; --false
else
ret := True; --true
end if;
end if;
return ret;
end lcd_check_available;
procedure lcd_toggle_enable (val : Unsigned_Byte) is
begin
RP.Device.Timer.Delay_Microseconds (ENABLE_DELAY_US);
--i2c_write_byte(lcd->i2c, lcd->addr, val | LCD_ENABLE_BIT);
i2c_write_byte (val or LCD_ENABLE_BIT);
RP.Device.Timer.Delay_Microseconds (ENABLE_DELAY_US);
--i2c_write_byte(lcd->i2c, lcd->addr, val & ~LCD_ENABLE_BIT);
i2c_write_byte (val and not LCD_ENABLE_BIT);
end lcd_toggle_enable;
procedure lcd_send_byte (val : Unsigned_Byte; mode : Unsigned_Byte) is
high, low, temp : Unsigned_Byte;
Status : HAL.I2C.I2C_Status;
begin
high := mode or (val and 16#F0#) or LCD_BACKLIGHT;
temp := Unsigned_Byte'Value (Unsigned_Byte'Image (Shift_Left (val, 4)));
low := mode or (temp and 16#F0#) or LCD_BACKLIGHT;
-- The data can be sent before or after sending RS and RW signals.
-- But data should be available before toggling LCD enable pin.
--i2c_write_byte(lcd->i2c, lcd->addr, high);
--lcd_toggle_enable(lcd, high);
i2c_write_byte (high);
lcd_toggle_enable (high);
--i2c_write_byte(lcd->i2c, lcd->addr, low);
--lcd_toggle_enable(lcd, low);
i2c_write_byte (low);
lcd_toggle_enable (low);
end lcd_send_byte;
procedure lcd_char (val : Unsigned_Byte) is
begin
lcd_send_byte (val, LCD_CHARACTER);
end lcd_char;
procedure lcd_home is
begin
lcd_send_byte (LCD_RETURNHOME, LCD_COMMAND);
end lcd_home;
procedure lcd_clear is
begin
lcd_send_byte (LCD_CLEARDISPLAY, LCD_COMMAND);
end lcd_clear;
procedure lcd_set_cursor (line : Unsigned_Byte; position : Unsigned_Byte) is
val : Unsigned_Byte;
begin
case line is
when 0 =>
val := LCD_ROW0;
when 1 =>
val := LCD_ROW1;
when 2 =>
val := LCD_ROW2;
when 3 =>
val := LCD_ROW3;
when others =>
val := LCD_ROW0;
end case;
val := val + position;
lcd_send_byte (val, LCD_COMMAND);
end lcd_set_cursor;
procedure lcd_string (s : String) is
i : Integer;
begin
-- while (*s) {
-- lcd_char(lcd, *s++);
--}
for i in 1 .. s'Length loop
-- null;
-- need correct conversion
-- lcd_char (Character'Pos (s (i)));
lcd_char (41); --letter A
end loop;
end lcd_string;
function lcd_init return Boolean is
ret : Boolean;
begin
if (lcd_check_available) then
ret := False; --false
else
-- please check the initialization flow from https://cdn-shop.adafruit.com/datasheets/TC2004A-01.pdf
lcd_send_byte (16#03#, LCD_COMMAND);
lcd_send_byte (16#03#, LCD_COMMAND);
lcd_send_byte (16#03#, LCD_COMMAND);
lcd_send_byte (16#02#, LCD_COMMAND);
lcd_send_byte (LCD_ENTRYMODESET or LCD_ENTRYLEFT, LCD_COMMAND);
lcd_send_byte (LCD_FUNCTIONSET or LCD_2LINE, LCD_COMMAND);
lcd_send_byte (LCD_DISPLAYCONTROL or LCD_DISPLAYON, LCD_COMMAND);
lcd_clear;
lcd_home;
ret := True; --true
end if;
return ret;
end lcd_init;
procedure rp_i2c_init is
begin
SDA.Configure (Output, Pull_Up, RP.GPIO.I2C, Schmitt => True);
SCL.Configure (Output, Pull_Up, RP.GPIO.I2C, Schmitt => True);
Port.Configure ((Role => RP.I2C.Target, others => <>));
Port.Set_Address (Addr_HAL);
Port.Enable;
--Port.Set_Address := Default_Address;
--Port.enable
-- This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico)
-- i2c_init(i2c_default, 100 * 1000);
-- gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
-- gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
-- gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
-- gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
-- -- Make the I2C pins available to picotool
-- bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
end rp_i2c_init;
end hd44780lcd;
Here’s my main - hd44780lcd_test.adb
with Ada.Text_IO; use Ada.Text_IO;
with System.Unsigned_Types; use System.Unsigned_Types;
with Ada.Characters.Handling; use Ada.Characters.Handling;
with Interfaces; use Interfaces;
with RP.GPIO; use RP.GPIO;
with RP.I2C_Master;
with RP.Device;
with RP.Clock;
with HAL.I2C;
with Pico;
--with lcd_2004_i2c; use lcd_2004_i2c;
with hd44780lcd; use hd44780lcd;
procedure hd44780lcd_test is
Status : Boolean;
-- struct lcd_device lcd = {.i2c = i2c_default, .addr = 0x27};
begin
-- null;
-- i2c_init(lcd.i2c, 100 * 1000);
-- i2c_init (lcd.i2c, 100 * 1_000);
-- need to replace the following 5 lines with Ada equivalent
--
--gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
--gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
--gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
--gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
--bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
-- if (!lcd_init(&lcd))
-- {
-- printf("failed to init LCD device\n");
-- return 1;
-- }
-- message_len = sizeof(message) / sizeof(message[0]);
-- while (true) {
-- for (int m = 0; m < message_len; m += MAX_LINES) {
-- for (int line = 0; line < MAX_LINES && (m + line) < message_len; line++) {
-- lcd_set_cursor(&lcd, line, (MAX_CHARS / 2) - strlen(message[m + line]) / 2);
-- lcd_string(&lcd, message[m + line]);
-- }
-- sleep_ms(5000);
-- lcd_clear(&lcd);
-- }
-- }
-- return 0;
rp_i2c_init;
Status := lcd_init;
Put_Line (Status'Image);
if (Status) then
Put_Line ("failed to init LCD device");
else
lcd_clear;
--length := Unsigned_Byte(message'Length);
--for m in 1 .. length loop
lcd_set_cursor (0, 0);
lcd_char (65);
--lcd_string ("Hello World");
--end loop;
end if;
end hd44780lcd_test;